bug修正
文章归档:
select FROM_UNIXTIME(create_date/1000,'%Y') as year, FROM_UNIXTIME(create_date/1000,'%m') as month,count(*) as count from ms_article group by year,month
1. 文章图片上传
1.1 接口说明
接口url:/upload
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
image | file | 上传的文件名称 |
返回数据:
{"success":true,"code":200,"msg":"success","data":"https://static.cherr.com/aa.png"
}
<dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>[7.13.0, 7.13.99]</version>
</dependency>
1.2 Controller
String fileName = UUID.randomUUID().toString() + “.” + StringUtils.substringAfterLast(originalFilename, “.”);
这行代码的作用是生成一个唯一的文件名,用于保存上传的文件。
UUID.randomUUID().toString()
: 这个方法调用生成一个随机的 UUID(Universally Unique Identifier),并将其转换为字符串形式。UUID 是一种用于唯一标识信息的标准化方法,通常由 32 个十六进制数字组成,例如:“550e8400-e29b-41d4-a716-446655440000”。使用toString()
方法将其转换为字符串。"." + StringUtils.substringAfterLast(originalFilename, ".")
: 这部分代码是获取上传文件的扩展名,并将其与随机生成的 UUID 字符串拼接起来。StringUtils.substringAfterLast(originalFilename, ".")
方法从原始文件名中获取最后一个点 (.) 后面的字符串,即文件的扩展名。然后再在扩展名前面添加一个点,用于连接随机生成的 UUID。通过这两个步骤,就可以生成一个形如 “random_uuid.png” 的唯一文件名,其这样可以确保每个上传的文件都有一个唯一的文件名,避免文件名冲突。
package com.cherriesovo.blog.controller;import com.cherriesovo.blog.utils.QiniuUtils;
import com.cherriesovo.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 {@Autowiredprivate QiniuUtils qiniuUtils;@PostMappingpublic Result upload(@RequestParam("image") MultipartFile file){//原始文件名称 比如:1.pngString originalFilename = file.getOriginalFilename();//唯一的文件名称String fileName = UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(originalFilename, ".");//上传文件到哪里?七牛云 云服务器按量付费,速度快,把图片发到离用户最近的服务器//降低我们自身应用服务器的带宽消耗boolean upload = qiniuUtils.upload(file, fileName);if (upload){//QiniuUtils.url 是一个用于存储七牛云存储的 URL 地址的变量return Result.success(QiniuUtils.url + fileName);}return Result.fail(20001,"上传失败");}
}
1.3 使用七牛云
# 上传文件总的最大值
spring.servlet.multipart.max-request-size=20MB
# 单个文件的最大值
spring.servlet.multipart.max-file-size=2MB
需要自行修改的配置:
//七牛云url或者自己的url(这里使用七牛云的url)public static final String url = "http://sbf25hzn6.hb-bkt.clouddn.com/";//AK@Value("")private String accessKey;//SK@Value("")private String accessSecretKey;//对象空间名称String bucket = "cherriesovo-blog";
package com.cherriesovo.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 {//七牛云url或者自己的urlpublic static final String url = "http://sbf25hzn6.hb-bkt.clouddn.com/";//AK@Value("")private String accessKey;//SK@Value("")private String accessSecretKey;//将文件上传到七牛云存储中//MultipartFile 是 Spring Framework 提供的一个接口,用于表示 HTTP 请求中的文件。在这段代码中,MultipartFile 类型的参数 file 用于接收客户端上传的文件。public boolean upload(MultipartFile file,String fileName){//创建一个配置类对象 cfg,并指定了上传的区域为华北Configuration cfg = new Configuration(Region.huabei());//创建一个上传管理器对象 uploadManager,用于执行文件上传操作UploadManager uploadManager = new UploadManager(cfg);//...生成上传凭证,然后准备上传,(对象空间名称)String bucket = "cherriesovo-blog";//默认不指定key的情况下,以文件内容的hash值作为文件名try {byte[] uploadBytes = file.getBytes();//使用 MultipartFile 对象的 getBytes() 方法获取上传文件的字节数组Auth auth = Auth.create(accessKey, accessSecretKey);//创建一个认证对象String upToken = auth.uploadToken(bucket);//用认证对象的 uploadToken 方法生成上传凭证 upTokenResponse response = uploadManager.put(uploadBytes, fileName, upToken);//执行文件上传//解析上传成功的结果,将上传结果的 JSON 字符串解析为 DefaultPutRet 类对象 putRetDefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);return true; //返回 true 表示上传成功} catch (Exception ex) {ex.printStackTrace();}return false;}
}
1.4 测试
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": "好多语言,该学哪个?"}]
}
package com.cherriesovo.blog.vo;
import lombok.Data;@Data
public class CategoryVo {private Long id;private String avatar;private String categoryName;private String description;
}
2.1.2 Controller
@RestController
@RequestMapping("categorys")
public class CategoryController {@GetMapping("detail")public Result categoriesDetail(){return categoryService.findAllDetail();}}
2.1.3 Service
CategoryService:
Result findAllDetail();
CategoryServiceImpl:
@Overridepublic Result findAll() {LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.select(Category::getId,Category::getCategoryName);//SELECT id, category_name FROM category;List<Category> categories = categoryMapper.selectList(queryWrapper);return Result.success(copyList(categories));}@Overridepublic Result findAllDetail() {//SELECT * FROM category;List<Category> categories = categoryMapper.selectList(new LambdaQueryWrapper<>());//页面交互的对象return Result.success(copyList(categories));}
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"}]
}
2.2.3 Controller
package com.cherriesovo.blog.vo;import lombok.Data;@Data
public class TagVo {private Long id;private String tagName;private String avatar;
}
@RestController
@RequestMapping("tags")
public class TagsController {@GetMapping("detail")public Result findAllDetail(){return tagsService.findAllDetail();}
}
2.2.4 Service
TagsService:
Result findAllDetail();//查询所有的标签
TagsServiceImpl:
@Overridepublic Result findAll() {LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.select(Tag::getId,Tag::getTagName);List<Tag> tags = this.tagMapper.selectList(queryWrapper);return Result.success(copyList(tags));}@Overridepublic Result findAllDetail() {LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();//select * from tagList<Tag> tags = this.tagMapper.selectList(queryWrapper);return Result.success(copyList(tags));}
3. 分类文章列表
3.1 接口说明
接口url:/category/detail/{id}
请求方式:GET
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
id | 分类id | 路径参数 |
返回数据:
{"success": true, "code": 200, "msg": "success", "data": {"id": 1, "avatar": "/static/category/front.png", "categoryName": "前端", "description": "前端是什么,大前端"}
}
3.2 Controller
CategoryController:
@GetMapping("detail/{id}")public Result categoriesDetailById(@PathVariable("id") Long id){return categoryService.categoriesDetailById(id);}
3.3 Service
CategoryService:
Result categoriesDetailById(Long id);
CategoryServiceImpl:
@Overridepublic Result categoriesDetailById(Long id) {Category category = categoryMapper.selectById(id);CategoryVo categoryVo = copy(category);return Result.success(categoryVo);}
ArticleServiceImpl:
新增如下代码:
//查询文章的参数 加上分类id,判断不为空 加上分类条件
if (pageParams.getCategoryId() != null) {
queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());
}
@Overridepublic List<ArticleVo> listArticlesPage(PageParams pageParams) {// 分页查询article数据库表Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();//查询文章的参数 加上分类id,判断不为空 加上分类条件,SELECT * FROM article WHERE category_id = ?if (pageParams.getCategoryId() != null) {queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());}//是否置顶排序,SELECT * FROM article ORDER BY weight DESC, create_date DESCqueryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);//SELECT * FROM article WHERE category_id = ? ORDER BY weight DESC, create_date DESCPage<Article> articlePage = articleMapper.selectPage(page,queryWrapper);List<Article> records = articlePage.getRecords();List<ArticleVo> articleVoList = copyList(records,true,false,true);return articleVoList;}
package com.cherriesovo.blog.vo.params;import lombok.Data;@Data
public class PageParams {private int page = 1;private int pageSize = 10;private Long categoryId;private Long tagId;
}
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"}
}
4.2 Controller
TagsController:
@GetMapping("detail/{id}")public Result findDetailById(@PathVariable("id") Long id){return tagService.findDetailById(id);}
4.3 Service
TagService:
Result findDetailById(Long id);
TagServiceImpl:
@Overridepublic Result findDetailById(Long id) {//select * from tag where id = ?Tag tag = tagMapper.selectById(id);TagVo copy = copy(tag);return Result.success(copy);}
4.4 修改原有的查询文章接口
ArticleServiceImpl:
新增如下代码:
核心逻辑:
- 创建一个列表articleIdList用于存放某个标签对应的文章id;
- 通过tag_id在article_tag表中查询所有数据;
- 通过循环遍历操作将第二步查出来的数据中的article_id存放到articleIdList列表;
- SELECT *FROM article WHERE id IN articleIdList
List<Long> articleIdList = new ArrayList<>(); //用于存储文章IDif (pageParams.getTagId() != null){/** 加入标签条件查询* article表中没有tag字段 一篇文章有多个标签* article_tag表中 article_id与tag_id是一对多的关系* */LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();articleTagLambdaQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());//SELECT * FROM article_tag WHERE tag_id = ?List<ArticleTag> articleTags = articleTagMapper.selectList(articleTagLambdaQueryWrapper);for (ArticleTag articleTag : articleTags) {articleIdList.add(articleTag.getArticleId());}if (articleIdList.size() > 0){//SELECT *FROM article WHERE id IN (articleId1, articleId2, articleId3, ...)queryWrapper.in(Article::getId,articleIdList);}}
@Overridepublic List<ArticleVo> listArticlesPage(PageParams pageParams) {// 分页查询article数据库表Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();//查询文章的参数 加上分类id,判断不为空 加上分类条件if (pageParams.getCategoryId() != null) {queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId());}List<Long> articleIdList = new ArrayList<>();if (pageParams.getTagId() != null){/** 加入标签条件查询* article表中没有tag字段 一篇文章有多个标签* article_tag表中 article_id与tag_id是一对多的关系* */LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();articleTagLambdaQueryWrapper.eq(ArticleTag::getTagId,pageParams.getTagId());List<ArticleTag> articleTags = articleTagMapper.selectList(articleTagLambdaQueryWrapper);for (ArticleTag articleTag : articleTags) {articleIdList.add(articleTag.getArticleId());}if (articleIdList.size() > 0){queryWrapper.in(Article::getId,articleIdList);}}//是否置顶排序queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);Page<Article> articlePage = articleMapper.selectPage(page,queryWrapper);List<Article> records = articlePage.getRecords();List<ArticleVo> articleVoList = copyList(records,true,false,true);return articleVoList;}