文章图片上传接口说明
接口url:/upload
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
image | file | 上传的文件名称 |
返回数据:
{
"success":true,
"code":200,
"msg":"success",
"data":"https://static.mszlu.com/aa.png"
}
导入七牛云的sdk(用于文件上传)
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.7.0, 7.7.99]</version>
</dependency>
限制上传大小
spring:
servlet:
multipart:
# 上传文件总的最大值
max-request-size: 20MB
# 单个文件的最大值
max-file-size: 2MB
七牛云工具类:
需要修改的有 存储区域、密钥、url(修改为自己域名的,不然前端找不到)、空间名
package com.example.blog.utils;
import com.alibaba.fastjson.JSON;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class QiNiuYunUtils
{
//http://+域名
public static final String url = "http://qiniuyun.suqiqaq.cn/";
//七牛云密钥对应的ky-value
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.accessSecretKey}")
private String accessSecretKey;
public boolean upload(MultipartFile file, String fileName)
{
//构造一个带指定 Region 对象的配置类,需要看七牛云的存储区域
Configuration cfg = new Configuration(Region.huanan());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
String bucket = "suqiblog";//空间名
//默认不指定key的情况下,以文件内容的hash值作为文件名
try {
byte[] uploadBytes = file.getBytes();
Auth auth = Auth.create(accessKey, accessSecretKey);
String upToken = auth.uploadToken(bucket);
Response response = uploadManager.put(uploadBytes, fileName, upToken);
//解析上传成功的结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return true;
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
}
yaml密钥配置:
qiniu:
accessKey: Re1oK3cavH_3mYf83adaXASSzMpA5gfnFhDx3YIk
accessSecretKey: uN_vAIZ1bIUh8eOCO_4mxLYqv_PWJOmR5cCahoKt
如果有自己的域名,七牛云可以添加域名,添加成功后,需要配置CNAME
复制CNAME
在阿里云服务器控制台中:在控制台页面的左侧,产品与服务栏中选择 域名。
在域名产品的列表中找到您加速域名对应的主域名
,点击域名后面的 解析设置
或 解析
,进入解析设置页。
选择 添加记录
,依次填写 主机记录,记录类型 以及 记录值,其他可设为默认值。
主机记录,由于我的是qiniuyun.suqiqaq.cn,主机记录为qiniuyun
记录值就是复制的CNAME
配置完成后,显示已配置
配置成功后,SpringBoot2的qiniuyunUtils需要修改域名
QiniuUtils.java:
package com.example.blog.utils;
import com.alibaba.fastjson.JSON;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class QiniuUtils
{
//http://+域名
public static final String url = "http://qiniuyun.suqiqaq.cn/";
//七牛云密钥对应的ky-value
@Value("${qiniu.accessKey}")
private String accessKey;
@Value("${qiniu.accessSecretKey}")
private String accessSecretKey;
public boolean upload(MultipartFile file,String fileName){
//构造一个带指定 Region 对象的配置类,需要看七牛云的存储区域
Configuration cfg = new Configuration(Region.huanan());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//...生成上传凭证,然后准备上传
String bucket = "suqiblog";//空间名
//默认不指定key的情况下,以文件内容的hash值作为文件名
try {
byte[] uploadBytes = file.getBytes();
Auth auth = Auth.create(accessKey, accessSecretKey);
String upToken = auth.uploadToken(bucket);
Response response = uploadManager.put(uploadBytes, fileName, upToken);
//解析上传成功的结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return true;
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
}
UploadController:
package com.example.blog.controller;
import com.example.blog.utils.QiniuUtils;
import com.example.blog.vo.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.UUID;
@RestController
@RequestMapping("upload")
public class UploadController
{
@Autowired
private QiniuUtils qiniuUtils;
@PostMapping
public Result upload(@RequestParam("image")MultipartFile file)
{
//获得原始文件名称
String originalFilename = file.getOriginalFilename();
//创建唯一的文件名称
//StringUtils.substringAfterLast(originalFilename, ".")获取在.之后的字符串
String fileName = UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(originalFilename, ".");
//上传文件到哪里=》云服务器,与应用服务器隔开,不占用他的带宽
boolean isUpload = qiniuUtils.upload(file, fileName);
if(isUpload)
{
return Result.success(QiniuUtils.url + fileName);
}else
return Result.fail(20001,"上传失败");
}
}
2. 导航-文章分类
2.1 查询所有的文章分类
2.1.1 接口说明
接口url:/categorys/detail
请求方式:GET
请求参数:无
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 1,
"avatar": "/static/category/front.png",
"categoryName": "前端",
"description": "前端是什么,大前端"
},
{
"id": 2,
"avatar": "/static/category/back.png",
"categoryName": "后端",
"description": "后端最牛叉"
},
{
"id": 3,
"avatar": "/static/category/lift.jpg",
"categoryName": "生活",
"description": "生活趣事"
},
{
"id": 4,
"avatar": "/static/category/database.png",
"categoryName": "数据库",
"description": "没数据库,啥也不管用"
},
{
"id": 5,
"avatar": "/static/category/language.png",
"categoryName": "编程语言",
"description": "好多语言,该学哪个?"
}
]
}
这里前端所需要的数据直接对应的就是category类,把category表查到数据之后,还需要查该分类下对应的文章数量,然后再封装成CategoryDetailVo对象返回
CategoryController:
package com.example.blog.controller;
import com.example.blog.entity.Category;
import com.example.blog.service.CategoryService;
import com.example.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/categorys")
public class CategoryController
{
@Autowired
private CategoryService categoryService;
@GetMapping
public Result categories()
{
return categoryService.findAll();
}
@GetMapping("/detail")
public Result categoriesDetail()
{
return categoryService.findAllDetail();
}
}
CategoryServiceImpl:
package com.example.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.blog.dao.mapper.CategoryMapper;
import com.example.blog.entity.Category;
import com.example.blog.service.CategoryService;
import com.example.blog.vo.CategoryVo;
import com.example.blog.vo.Result;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CategoryServiceImpl implements CategoryService
{
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private ArticleMapper articleMapper;
@Override
public CategoryVo findCategoryById(Long categoryId)
{
Category category = categoryMapper.selectById(categoryId);
return copy(category);
}
@Override
public Result findAllDetail()
{
List<Category> categories = categoryMapper.selectList(null);
List<CategoryDetailVo> categoryDetailVos = new ArrayList<>();
for (Category category : categories) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Article::getCategoryId, category.getId());
Integer articlesNumber = articleMapper.selectCount(queryWrapper);
categoryDetailVos.add(copyCategoryDetailList(category, articlesNumber));
}
return Result.success(categoryDetailVos);
}
private CategoryDetailVo copyCategoryDetailList(Category category, Integer articlesNumber)
{
CategoryDetailVo categoryDetailVo = new CategoryDetailVo();
BeanUtils.copyProperties(category, categoryDetailVo);
categoryDetailVo.setArticlesNumber(articlesNumber);
return categoryDetailVo;
}
}
CategoryDetailVo:
package com.example.blog.vo;
import lombok.Data;
@Data
public class CategoryDetailVo {
private String id;
private String avatar;
private String categoryName;
private String description;
private Integer articlesNumber;
}
2.2 查询所有的标签
2.2.1 接口说明
接口url:/tags/detail
请求方式:GET
请求参数:无
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 5,
"tagName": "springboot",
"avatar": "/static/tag/java.png"
},
{
"id": 6,
"tagName": "spring",
"avatar": "/static/tag/java.png"
},
{
"id": 7,
"tagName": "springmvc",
"avatar": "/static/tag/java.png"
},
{
"id": 8,
"tagName": "11",
"avatar": "/static/tag/css.png"
}
]
}
同理,这里前端所需要的数据直接对应的就是tag类,把tag表数据查到之后,还需要查该标签下对应的文章数量,然后再封装成TagDetailVo对象返回
TagsController:
package com.example.blog.controller;
import com.example.blog.service.TagService;
import com.example.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/tags")
public class TagsController
{
@Autowired
private TagService tagService;
@GetMapping
public Result findAll()
{
return tagService.findAll();
}
@GetMapping("detail")
public Result findAllDetail()
{
return tagService.findAllDetail();
}
}
TagServiceImpl:
package com.example.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.api.R;
import com.example.blog.dao.mapper.TagMapper;
import com.example.blog.entity.Category;
import com.example.blog.entity.Tag;
import com.example.blog.service.TagService;
import com.example.blog.vo.Result;
import com.example.blog.vo.TagVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Service
public class TagServiceImpl implements TagService
{
@Autowired
private TagMapper tagMapper;
@Autowired
private ArticleTagMapper articleTagMapper;
@Override
public Result findAllDetail()
{
List<Tag> tags = tagMapper.selectList(null);
List<TagDetailVo> tagDetailVos = new ArrayList<>();
for (Tag tag : tags)
{
LambdaQueryWrapper<ArticleTag> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ArticleTag::getTagId,tag.getId());
Integer articlesNumber = articleTagMapper.selectCount(queryWrapper);
tagDetailVos.add(copyTagDetailList(tag, articlesNumber));
}
return Result.success(tagDetailVos);
}
private TagDetailVo copyTagDetailList(Tag tag, Integer articlesNumber)
{
TagDetailVo tagDetailVo = new TagDetailVo();
BeanUtils.copyProperties(tag,tagDetailVo);
tagDetailVo.setArticlesNumber(articlesNumber);
return tagDetailVo;
}
}
3. 分类文章列表
3.1 接口说明
接口url:/categorys/detail/{id}
请求方式:GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | 分类id | 路径参数 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data":
{
"id": 1,
"avatar": "/static/category/front.png",
"categoryName": "前端",
"description": "前端是什么,大前端"
}
}
点击某一个文章的分类之后,需要将该文章分类的详情返回,以及哪些文章是这个分类,把这些文章的作为一个列表返回
文章分类的详情:
CategoryController:
package com.example.blog.controller;
import com.example.blog.entity.Category;
import com.example.blog.service.CategoryService;
import com.example.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/categorys")
public class CategoryController
{
@Autowired
private CategoryService categoryService;
@GetMapping("detail/{id}")//文章分类id(categoryId)
public Result categoriesDetailById(@PathVariable("id") Long id)
{
return categoryService.categoriesDetailById(id);
}
}
CategoryServiceImpl:
package com.example.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.blog.dao.mapper.CategoryMapper;
import com.example.blog.entity.Category;
import com.example.blog.service.CategoryService;
import com.example.blog.vo.CategoryVo;
import com.example.blog.vo.Result;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CategoryServiceImpl implements CategoryService
{
@Autowired
private CategoryMapper categoryMapper;
@Override
public Result categoriesDetailById(Long id)
{
Category category = categoryMapper.selectById(id);
return Result.success(category);
}
}
哪些文章是这个分类,把这些文章的作为一个列表返回:
逻辑:需要调整之前写的文章列表接口,queryWrapper需要加上数据库的categoryId为前端传入的categoryId,才添加进去
需要修改前端参数
PageParams:
package com.example.blog.vo.params;
import lombok.Data;
@Data
public class PageParams
{
//当前的页数
private int page = 1;
//每页查询的数量
private int pageSize = 10;
private Long categoryId;
private Long tagId;
}
ArticleServiceImpl:
/**
* 分页查询 article数据库表
* @param pageParams
* @return
*/
@Override
//加上此注解 代表要对此接口记录日志
@LogAnnotation(module="文章",operator="获取文章列表")
public Result listArticle(PageParams pageParams)
{
Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();/*查询器*/
queryWrapper.orderByDesc(Article::getWeight);/*是否置顶进行排序*/
queryWrapper.orderByDesc(Article::getCreateDate);/*根据创建时间进行降序排序 order by create_date desc*/
//如果ageParams.getCategoryId()不为空,说明是查找的某个文章分类下的文章列表,
//如果为空,说明查的是总文章列表
if(null != pageParams.getCategoryId())
{
queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());
}
Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);/*等同于编写一个普通list查询,mybatis-plus自动替你分页*/
List<Article> records = articlePage.getRecords();
List<ArticleVo> articleVoList = copyList(records,true,true);
return Result.success(articleVoList);
}
4. 标签文章列表
4.1 接口说明
接口url:/tags/detail/{id}
请求方式:GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | 标签id | 路径参数 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data":
{
"id": 5,
"tagName": "springboot",
"avatar": "/static/tag/java.png"
}
}
同理,点击某一个文章的标签之后,需要将该文章标签的详情返回,以及哪些文章是这个标签,把这些文章的内容作为一个列表返回
该文章标签的详情返回:
TagsController:
@GetMapping("/detail/{id}")
public Result findTagDetailById(@PathVariable("id")Long id)
{
return tagService.findTagById(id);
}
TagServiceImpl:
/**
* 通过标签id查询标签详情
* @param id
* @return
*/
@Override
public Result findTagById(Long id)
{
Tag tag = tagMapper.selectById(id);
return Result.success(tag);
}
对应的,查询文章列表的接口也需要修改
这里的article并没有tagId这个字段,而是通过ms_article_tag来查找的tag
所以这里的逻辑为:
1.通过传入的tagId,如果ms_article_tag表中tagId等于前端传入的tagId,就添加到articleTags中
2.遍历articleTags每个articleId,并添加到articleIdList中
3.通过articleIdList添加查询条件,如果article表中的articleId在articleIdList中,才添加
/**
* 分页查询 article数据库表
* @param pageParams
* @return
*/
@Override
//加上此注解 代表要对此接口记录日志
@LogAnnotation(module="文章",operator="获取文章列表")
public Result listArticle(PageParams pageParams)
{
Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();/*查询器*/
queryWrapper.orderByDesc(Article::getWeight);/*是否置顶进行排序*/
queryWrapper.orderByDesc(Article::getCreateDate);/*根据创建时间进行降序排序 order by create_date desc*/
if(null != pageParams.getCategoryId())//如果ageParams.getCategoryId()不为空,说明是查找的某个文章分类下的文章列表,如果为空,说明查的是总文章列表
{
queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());
}
//这里的article并没有tagId这个字段,而是通过ms_article_tag来查找的
//所以需要通过ms_article_tag表查到对应的articleId列表,再通过articleId列表查找article列表
if(null != pageParams.getTagId())
{
List<Long> articleIdList = new ArrayList<>();
LambdaQueryWrapper<ArticleTag> ATQueryWrapper = new LambdaQueryWrapper<>();
ATQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());
List<ArticleTag> articleTags = articleTagMapper.selectList(ATQueryWrapper);
for(ArticleTag articleTag: articleTags)
{
articleIdList.add(articleTag.getArticleId());
}
if(articleIdList.size() > 0)
{
queryWrapper.in(Article::getId,articleIdList);
}
}
Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);/*等同于编写一个普通list查询,mybatis-plus自动替你分页*/
List<Article> records = articlePage.getRecords();
List<ArticleVo> articleVoList = copyList(records,true,true);
return Result.success(articleVoList);
}