创建完数据库对应的表 等操作之后,进入idea
根据自己的需求,修改相应的代码 比如 : 删除其中的继承东西等
我们这里暂时不做任何修改
接下来就是修改实体类对应的信息
比如: 我们需要添加实体类与数据库中表的对应关系用 @TableName("sg_article")
, 对于主键自增的字段使用@TableId
等
/**
* 文章表(Article)表实体类
*
* @author Ray2310
* @since 2023-03-11 09:42:17
*/
@SuppressWarnings("serial")
@TableName("sg_article")
public class Article {
@TableId
private Long id;
//标题
private String title;
//文章内容
private String content;
//文章摘要
private String summary;
//所属分类id
private Long categoryId;
//缩略图
private String thumbnail;
//是否置顶(0否,1是)
private String isTop;
//状态(0已发布,1草稿)
private String status;
//访问量
private Long viewCount;
//是否允许评论 1是,0否
private String isComment;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
//删除标志(0代表未删除,1代表已删除)
private Integer delFlag;
}
对于Mapper层 ,因为使用的是Mybatis-plus,所以使用的Mapper就方便很多
public interface ArticleMapper extends BaseMapper<Article> {
}
service层
public interface ArticleService extends IService<Article> {
}
impl实现类上加注解
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
}
Controller层
@RestController
@RequestMapping("/article")
public class ArticleController {
@Resource
private ArticleService articleService;
@GetMapping("/list")
public List<Article> test(){
List<Article> list = articleService.list();
return list;
}
}
注意事项:
对于使用模块化项目,我们再配置完成后需要重新install项目,这样不同模块配置的内容才会加载出来
package com.blog.domain;
import com.blog.enums.AppHttpCodeEnum;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.io.Serializable;
/**
* 响应类
* @param <T>
* @author Ray2310
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public ResponseResult() {
this.code = AppHttpCodeEnum.SUCCESS.getCode();
this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult() {
ResponseResult result = new ResponseResult();
return result;
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult okResult(Object data) {
ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
if(data!=null) {
result.setData(data);
}
return result;
}
public static ResponseResult errorResult(AppHttpCodeEnum enums){
return setAppHttpCodeEnum(enums,enums.getMsg());
}
public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){
return setAppHttpCodeEnum(enums,msg);
}
public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
return okResult(enums.getCode(),enums.getMsg());
}
private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){
return okResult(enums.getCode(),msg);
}
public ResponseResult<?> error(Integer code, String msg) {
this.code = code;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.data = data;
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
package com.blog.enums;
/**
* 专门存放枚举的类
*/
public enum AppHttpCodeEnum {
// 成功
SUCCESS(200,"操作成功"),
// 登录
NEED_LOGIN(401,"需要登录后操作"),
NO_OPERATOR_AUTH(403,"无权限操作"),
SYSTEM_ERROR(500,"出现错误"),
USERNAME_EXIST(501,"用户名已存在"),
PHONENUMBER_EXIST(502,"手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写用户名"),
CONTENT_NOT_NULL(506, "评论内容不能为空"),
FILE_TYPE_ERROR(507, "文件类型错误,请上传png文件"),
USERNAME_NOT_NULL(508, "用户名不能为空"),
NICKNAME_NOT_NULL(509, "昵称不能为空"),
PASSWORD_NOT_NULL(510, "密码不能为空"),
EMAIL_NOT_NULL(511, "邮箱不能为空"),
NICKNAME_EXIST(512, "昵称已存在"),
LOGIN_ERROR(505,"用户名或密码错误");
int code;
String msg;
AppHttpCodeEnum(int code, String errorMessage){
this.code = code;
this.msg = errorMessage;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
调用前端接口时出现权限不够
同时会出现无法显示的问题, 那是因为我们前后端不在同一个域中,需要在mvc配置文件中配置跨域连调
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 实现跨域配置
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
查询出浏览量最高的前10篇文章的信息。 要求展示文章标题和浏览量。八能够让用户自己点击跳转到具体的文章详请进行浏览
注意 : 不要把草稿展示出来 ,不要把删除的文章查询出来
将返回值使用 通用的返回响应
controller层
//todo 查询热门文章
@GetMapping("/hotArticleList")
public ResponseResult hotArticleList(){
//查询热门文章,然后封装成ResponseResult ,然后返回
return articleService.hotArticleList();
}
service层实现
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
//todo 查询热门文章
/*需求:
查询出浏览量最高的前10篇文章的信息。 要求展览示文章标题和浏量。八能够让用户自己点击跳转到具体的文章详请进行浏览
注意 :`不要把草稿展示出来 ,不要把删除的文章查询出来`
*/
@Override
public ResponseResult hotArticleList() {
//查询热门文章 封装返回
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//用 LambdaQueryWrapper 写查询条件
queryWrapper.eq(Article::getStatus,0);
queryWrapper.orderByDesc(Article::getViewCount);
Page<Article> page = new Page(1,10);
//判空
if (ObjectUtils.isEmpty(page)){
return ResponseResult.errorResult(AppHttpCodeEnum.valueOf("暂无热门文章"));
}
page(page,queryWrapper);
List<Article> articles = page.getRecords();
return ResponseResult.okResult(articles);
}
}
这里老师的代码有错误,分页没有被加进去 ,我们自己需要修改
需求 : 从我们的出的接口的返回值我们就可以看出,我们需要的只是博客内容的访问量 以及 博客名 而不是所有的内容都返回。这样不仅会造成信息泄露 ,如果文章字数过多,还会造成内存额外消耗。所以我们需要进行优化
经过处理的类我们叫做 VO类型的类
/**
* 接口文档中要去响应回去的字段
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HotArticle {
//文章id
private Long id;
//文章标题
private String title;
//访问量
private Long viewCount;
}
然后再通过类型拷贝,就可以将我们需要的数据返回,而不是返回所有【拷贝的原理是两个类的属性相同】
List<Article> articles = page.getRecords();
List<HotArticle> hotArticles = new ArrayList<>();
// 类的赋值拷贝 Article中的某些字段 ---> HotArticle
//使用BeanUtils进行拷贝
for (Article article : articles){
HotArticle vo = new HotArticle();
BeanUtils.copyProperties(article,vo);
hotArticles.add(vo);
}
return ResponseResult.okResult(hotArticles);
/**
* 有关拷贝工具类的封装
* @author Ray2310
*/
public class BeanCopyUtils {
private BeanCopyUtils(){
}
/**
* 实现属性拷贝
* @param source
* @param clazz
* @return
*/
public static <V> V copyBean(Object source,Class<V> clazz){
//利用反射创建目标对象
V result = null;
try {
result = clazz.newInstance();
//实现属性的拷贝
BeanUtils.copyProperties(source,result);
} catch (Exception e) {
e.printStackTrace();
}
//返回结果
return result;
}
/**
* 如果是list集合的属性拷贝 ,就直接调用该方法
* @param list 源列表
* @param clazz 目标对象
* @param <V> 需要转换的类型的泛型
* @return 返回转换后的集合
*/
public static <O,V> List<V> copyBeanList(List<O> list , Class<V> clazz ){
List<V> collect = list.stream()
.map(o -> copyBean(o, clazz))
.collect(Collectors.toList());
return collect;
}
}
页面上需要展示分类列表, 用户可以通过点击具体的分类查看该分类下的文章列表。
注意: 1. 要求只展示有发布展示文章的分类 。 2. 必须是正常状态的分类
@RequestMapping("/category")
@RestController
public class CategoryController {
@Resource
private CategoryService categoryService;
//todo 分类请求
@GetMapping("/getCategoryList")
public ResponseResult getCategoryList(){
return categoryService.getCategoryList();
}
}
思路:
首先我们使用EasyCode生成对应的mapper、pojo实体类等
完成service层等的代码实现
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Resource
private ArticleService articleService;
//todo 分类请求
@Override
public ResponseResult getCategoryList() {
//1. 先在文章表中查询 status(文章发布or未发布)为 0 的,也就是发布了的 。还有就是未删除的
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_PUT);
List<Article> articles = articleService.list(queryWrapper);
//2. 查出上一步的之后只需要查分类id就可以了(category_id)
//todo 函数式编程 ,用来将查询到的id查询category
Set<Long> categoryIds = articles.stream()
.map(new Function<Article, Long>() {
@Override
public Long apply(Article article) {
return article.getCategoryId();
}
}).collect(Collectors.toSet());
//3. 然后再到category表中查出对应的名称即可 ,还需要判断分类的状态是正常的
List<Category> categories = listByIds(categoryIds);
//4. 判断分类的状态是正常的
List<Category> collect = categories.stream().filter(category -> category.getStatus().equals(SystemConstants.ARTICLE_CATEGORY_STATUS)).collect(Collectors.toList());
//5. 封装状态
List<CategoryVo> categoryVoList = BeanCopyUtils.copyBeanList(collect, CategoryVo.class);
return ResponseResult.okResult(categoryVoList);
}
}
在首页查询文章页面都有文章列表 ,首页 :查询所有文章
分类页面: 查询对应分类的文章列表
要求 ::1. 只能查询正式发布的文章 2. 置顶文章要显示在最前面
//todo 文章分页
@GetMapping("/articleList")
//如果在请求路径后面直接 /+值的 需要使用 @PathVariable
//如果是从请求体中获取出来的就需要加上 @RequestBody
public ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId){
return articleService.articleList(pageNum,pageSize,categoryId);
}
按照要求 ,我们需要将查询到的信息传入前端, 但是 不能将全部信息传入 ,所以就需要将查询到的信息进行封装
List<ArticleListVo> articleListVo = BeanCopyUtils.copyBeanList(page.getRecords(), ArticleListVo.class);
//todo 文章分页
/*
在首页查询文章页面都有文章列表 ,首页 :查询所有文章
分类页面: 查询对应分类的文章列表
要求 ::1. 只能查询正式发布的文章 2. 置顶文章要显示在最前面
*/
@Override
public ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper();
//如果有categoryId ,那么查询和传入的就需要相同
queryWrapper.eq(Objects.nonNull(categoryId) && categoryId > 0,Article::getCategoryId,categoryId);
//状态 : 正式发布
queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_PUT);
//置顶的文章(对isTop进行排序)
queryWrapper.orderByDesc(Article::getIsTop);
//分页查询
Page<Article> pageN = new Page<>(pageNum,pageSize);
Page<Article> page = page(pageN, queryWrapper);
//封装查询结果
List<ArticleListVo> articleListVo = BeanCopyUtils.copyBeanList(page.getRecords(), ArticleListVo.class);
PageVo pageVo = new PageVo(articleListVo, page.getTotal());
return ResponseResult.okResult(pageVo);
}
因为我们封装的是categoryName,但是查询出来的确实categoryId,所以需要在查询后进行给categoryName赋值
for (Article article : records){
Category category = categoryService.getById(article.getCategoryId());
article.setCategoryName(category.getName());
}
/**
* myBatisPlus分页配置
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mb = new MybatisPlusInterceptor();
mb.addInnerInterceptor(new PaginationInnerInterceptor());
return mb;
}
}
在WebConfig中
//todo 修改时间格式 【yyyy-MM-dd HH:mm:ss】
// 其实也可以直接在相关字段上加注解
// @JsonFormat(timezone="GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
// 只是这样做可以使所有的时间格式都转换
@Bean//使用@Bean注入fastJsonHttpMessageConvert
public HttpMessageConverter fastJsonHttpMessageConverters() {
//1.需要定义一个Convert转换消息的对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
// SerializeConfig.globalInstance.put(Long.class, ToStringSerializer.instance);
fastJsonConfig.setSerializeConfig(SerializeConfig.globalInstance);
fastConverter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = fastConverter;
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(fastJsonHttpMessageConverters());
}
要在文章列表页面点击阅读全文时能够跳转到文章详情页面 ,可以让用户阅读文章正文
要求: 1. 要在文章详情中展示其分类名
响应格式
//todo 查询文章详情
@GetMapping("/{id}")
public ResponseResult getArticleDetails(@PathVariable("id") Long id){
return articleService.getArticleDetails(id);
}
//todo 查询文章详情
/*
要在文章列表页面点击阅读全文时能够跳转到文章详情页面 ,可以让用户阅读文章正文
要求: 1. 要在文章详情中展示其分类名
*/
@Override
public ResponseResult getArticleDetails(Long id) {
//根据文章id查询文章
Article article = getById(id);
//转换成vo格式
ArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);
//根据分类id查询分类名
Long categoryId = articleDetailVo.getCategoryId();
Category category = categoryService.getById(categoryId);
if(category == null){
return ResponseResult.okResult(articleDetailVo);
}
articleDetailVo.setCategoryName(category.getName());
//封装响应,返回
return ResponseResult.okResult(articleDetailVo);
}
如果进行评论 ,那么就可以将用户的网站名、地址、描述、logo放上去
响应格式
@RestController
@RequestMapping("/link")
public class LinkController {
@Resource
private LinkService linkService;
//todo 获取所有友链
@GetMapping("/getAllLink")
public ResponseResult getAllLink(){
return linkService.getAllLink();
}
}
@Service("linkService")
public class LinkServiceImpl extends ServiceImpl<LinkMapper, Link> implements LinkService {
// todo 获取所有友链
/*
如果进行评论 ,那么就可以将用户的网站名、地址、描述、logo放上去
*/
@Override
public ResponseResult getAllLink() {
//查询所有审核通过的
LambdaQueryWrapper<Link> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(Link::getStatus, SystemConstants.LINK_STATUS_NORMAL);
List<Link> links = list(queryWrapper);
List<LinkVo> linkVos = BeanCopyUtils.copyBeanList(links, LinkVo.class);
return ResponseResult.okResult(linkVos);
}
}
响应
不仅需要实现评论 ,还要实现”父子评论“
@RestController
@RequestMapping("/comment")
public class CommentController {
@Autowired
private CommentService commentService;
//todo 评论列表
@GetMapping("/commentList")
public ResponseResult commentList(Long articleId ,Integer pageNum , Integer pageSize){
return commentService.commentList(articleId,pageNum,pageSize);
}
}
service层
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Resource
private UserService userService;
//todo 评论列表
@Override
public ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize) {
//根据文章id 所对应的 根评论(root_id = -1)
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
//对articleId进行判断
queryWrapper.eq(Comment::getArticleId,articleId);
queryWrapper.eq(Comment::getRootId, SystemConstants.ARTICLE_ROOT_COMMENT);
//分页查询
Page<Comment> pageN = new Page<>(pageNum,pageSize);
Page<Comment> page = page(pageN, queryWrapper);
//封装返回
List<CommentVo> list = toCommentVoList(page.getRecords());
return ResponseResult.okResult(new PageVo(list,page.getTotal()));
}
//comment 集合 和commentVo 集合的拷贝
private List<CommentVo> toCommentVoList(List<Comment> list){
List<CommentVo> commentVos = BeanCopyUtils.copyBeanList(list, CommentVo.class);
//遍历vo
for (CommentVo commentVo : commentVos){
//通过createBy查询用户的昵称并且赋值
String nickName = userService.getById(commentVo.getCreateBy()).getNickName();
commentVo.setUsername(nickName);
//通过 toCommentUserId查询用户ude昵称并赋值
//如果 toCommentUserId != -1才进行查询
if (commentVo.getToCommentId() != -1){
String toCommentName = userService.getById(commentVo.getToCommentId()).getNickName();
commentVo.setToCommentUserName(toCommentName);
}
}
return commentVos;
}
}
查询根评论对应的子评论
也就是多条评论
//查询所有根评论对应的子评论的集合 ,并且赋值给对应的属性children
for (CommentVo commentVo : list){
//查询对应子评论
List<CommentVo> children = getChildren(commentVo.getId());
commentVo.setChildren(children);
}
service层
/todo 评论列表
@Override
public ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize) {
//根据文章id 所对应的 根评论(root_id = -1)
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
//对articleId进行判断
queryWrapper.eq(Comment::getArticleId,articleId);
queryWrapper.eq(Comment::getRootId, SystemConstants.ARTICLE_ROOT_COMMENT);
//分页查询
Page<Comment> pageN = new Page<>(pageNum,pageSize);
Page<Comment> page = page(pageN, queryWrapper);
//封装返回
List<CommentVo> list = toCommentVoList(page.getRecords());
//查询所有根评论对应的子评论的集合 ,并且赋值给对应的属性children
for (CommentVo commentVo : list){
//查询对应子评论
List<CommentVo> children = getChildren(commentVo.getId());
commentVo.setChildren(children);
}
return ResponseResult.okResult(new PageVo(list,page.getTotal()));
}
//todo 根据根评论的id查询对应的子评论的集合
private List<CommentVo> getChildren(Long commentId){
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getRootId,commentId);
List<Comment> list = list(queryWrapper);
//封装成为CommentVo,然后返回
return toCommentVoList(list);
}
//comment 集合 和commentVo 集合的拷贝
private List<CommentVo> toCommentVoList(List<Comment> list){
List<CommentVo> commentVos = BeanCopyUtils.copyBeanList(list, CommentVo.class);
//遍历vo
for (CommentVo commentVo : commentVos){
//通过createBy查询用户的昵称并且赋值
String nickName = userService.getById(commentVo.getCreateBy()).getNickName();
commentVo.setUsername(nickName);
//通过 toCommentUserId查询用户ude昵称并赋值
//如果 toCommentUserId != -1才进行查询
if (commentVo.getToCommentId() != -1){
String toCommentName = userService.getById(commentVo.getToCommentId()).getNickName();
commentVo.setToCommentUserName(toCommentName);
}
}
return commentVos;
}
如果是友链 type为 1
还需要有请求体
package com.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blog.domain.ResponseResult;
import com.blog.domain.entity.Comment;
import com.blog.domain.entity.LoginUser;
import com.blog.domain.entity.User;
import com.blog.domain.vo.CommentVo;
import com.blog.domain.vo.PageVo;
import com.blog.enums.AppHttpCodeEnum;
import com.blog.exception.SystemException;
import com.blog.mapper.CommentMapper;
import com.blog.service.CommentService;
import com.blog.service.UserService;
import com.blog.utils.BeanCopyUtils;
import com.blog.utils.SecurityUtils;
import com.blog.utils.SystemConstants;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.sql.rowset.BaseRowSet;
import java.util.List;
import java.util.Objects;
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Resource
private UserService userService;
//todo 评论列表
@Override
public ResponseResult commentList(Long articleId, Integer pageNum, Integer pageSize) {
//根据文章id 所对应的 根评论(root_id = -1)
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
//对articleId进行判断
queryWrapper.eq(Comment::getArticleId,articleId);
queryWrapper.eq(Comment::getRootId, SystemConstants.ARTICLE_ROOT_COMMENT);
//分页查询
Page<Comment> pageN = new Page<>(pageNum,pageSize);
Page<Comment> page = page(pageN, queryWrapper);
//封装返回
List<CommentVo> list = toCommentVoList(page.getRecords());
//查询所有根评论对应的子评论的集合 ,并且赋值给对应的属性children
for (CommentVo commentVo : list){
//查询对应子评论
List<CommentVo> children = getChildren(commentVo.getRootId());
commentVo.setChildren(children);
}
return ResponseResult.okResult(new PageVo(list,page.getTotal()));
}
//todo 添加评论
@Override
public ResponseResult addComment(Comment comment) {
//评论内容不能为空
if(!StringUtils.hasText(comment.getContent())){
throw new SystemException(AppHttpCodeEnum.CONTENT_NOT_NULL);
}
save(comment);
return ResponseResult.okResult();
}
//todo 根据根评论的id查询对应的子评论的集合
private List<CommentVo> getChildren(Long commentId){
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getRootId,commentId);
queryWrapper.orderByAsc(Comment::getCreateTime);
List<Comment> list = list(queryWrapper);
//封装成为CommentVo,然后返回
return toCommentVoList(list);
}
//todo comment 集合 和commentVo 集合的拷贝
private List<CommentVo> toCommentVoList(List<Comment> list){
List<CommentVo> commentVos = BeanCopyUtils.copyBeanList(list, CommentVo.class);
//遍历vo
for (CommentVo commentVo : commentVos){
//通过createBy查询用户的昵称并且赋值
String nickName = userService.getById(commentVo.getCreateBy()).getNickName();
commentVo.setUsername(nickName);
//通过 toCommentUserId查询用户ude昵称并赋值
//如果 toCommentUserId != -1才进行查询
if (commentVo.getToCommentUserId() != -1){
String toCommentUserName = userService.getById(commentVo.getToCommentUserId()).getNickName();
commentVo.setToCommentUserName(toCommentUserName);
}
}
return commentVos;
}
}
@GetMapping("/linkCommentList")
public ResponseResult listCommentList(Integer pageNum , Integer pageSize){
return commentService.commentList(SystemConstants.COMMENT_TYPE_FRIEND,null,pageNum,pageSize);
}
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Resource
private UserService userService;
//todo 评论列表
@Override
public ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize) {
//根据文章id 所对应的 根评论(root_id = -1)
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
//对articleId进行判断 ,必须是文章评论再显示这个条件
queryWrapper.eq(SystemConstants.COMMENT_TYPE_ARTICLE.equals(commentType),Comment::getArticleId,articleId);
queryWrapper.eq(Comment::getRootId, SystemConstants.ARTICLE_ROOT_COMMENT);
//评论类型
queryWrapper.eq(Comment::getType,commentType);
//分页查询
Page<Comment> pageN = new Page<>(pageNum,pageSize);
Page<Comment> page = page(pageN, queryWrapper);
//封装返回
List<CommentVo> list = toCommentVoList(page.getRecords());
//查询所有根评论对应的子评论的集合 ,并且赋值给对应的属性children
for (CommentVo commentVo : list){
//查询对应子评论
List<CommentVo> children = getChildren(commentVo.getRootId());
commentVo.setChildren(children);
}
return ResponseResult.okResult(new PageVo(list,page.getTotal()));
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/userInfo")
public ResponseResult userInfo(){
return userService.userInfo();
}
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
//todo 用户中心
@Override
public ResponseResult userInfo() {
//获取当前用户
Long userId = SecurityUtils.getUserId();
//根据当前用户id查询当前用户
User user = getById(userId);
//封装成userInfoVo返回
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
return ResponseResult.okResult(userInfoVo);
}
}
首先上传至web应用服务器 ,然后再从web服务器上传至 oss
上传文件需求 :
更新个人信息需求 :
上传文件
@RestController
public class UploadController {
@Autowired
private UploadService uploadService;
@PostMapping("/upload")
public ResponseResult uploadImg(MultipartFile img){
return uploadService.uploadImg(img);
}
}
/**
* 上传文件到七牛云
*/
@ConfigurationProperties(prefix = "oss")
@Service
@Data
public class UploadServiceImpl implements UploadService {
//todo 实现文件的上传
@Override
public ResponseResult uploadImg(MultipartFile img) {
//判断文件的大小
//获取原始文件名进行判断
String originalFilename = img.getOriginalFilename();
if(!originalFilename.endsWith(".png") && !originalFilename.endsWith(".jpg")){
return ResponseResult.errorResult(AppHttpCodeEnum.FILE_TYPE_ERROR);
}
//如果通过,上传文件到oss
String url = uploadOSS(img);
return ResponseResult.okResult(url);
}
private String accessKey;
private String secretKey;
private String bucket;
private String uploadOSS(MultipartFile imgFile){
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.autoRegion());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//默认不指定key的情况下,以文件内容的hash值作为文件名
//images目录下的文件
String originalFilename = imgFile.getOriginalFilename();
String key = "images/"+originalFilename;
try {
//将前端传过来的imgFile文件转换成一个inputStream,然后
InputStream inputStream = imgFile.getInputStream();
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(inputStream,key,upToken,null, null);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(putRet.key);
System.out.println(putRet.hash);
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
}
} catch (Exception ex) {
//ignore
}
//文件地址
return "http://rrpanx30j.hd-bkt.clouddn.com/images/"+ originalFilename;
}
}
更新个人信息实现 :
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/userInfo")
public ResponseResult userInfo(){
return userService.userInfo();
}
@PutMapping("/userInfo")
public ResponseResult updateUserInfo(@RequestBody User user){
return userService.updateUserInfo(user);
}
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
//todo 用户中心
@Override
public ResponseResult userInfo() {
//获取当前用户
Long userId = SecurityUtils.getUserId();
//根据当前用户id查询当前用户
User user = getById(userId);
//封装成userInfoVo返回
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
return ResponseResult.okResult(userInfoVo);
}
//todo 更新个人信息
@Override
public ResponseResult updateUserInfo(User user) {
updateById(user);
return ResponseResult.okResult();
}
}
登录
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证成功生成jwt
把信息存入redis中
②自定义UserDetailsServic e
在这个实现类中进行查询数据库操作
注意配置密码加密BCryptPasswordCoder
校验
①自定义jwt认证过滤器
获取token
解析token获取其中的userId
从redis中获取用户信息
存入securityContextHolder
<!--SpringSecurity启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
校验
①自定义jwt认证过滤器
获取token
解析token获取其中的userId
从redis中获取用户信息
存入securityContextHolder
JwtAuthenticationTokenFilter
/**
* jwt过滤器
*
*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
// 获取token
String token = httpServletRequest.getHeader("token");
if(!StringUtils.hasText(token)){
//说明该接口不需要登录,直接放行
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
// 解析token获取其中的userId
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
//如果异常, 那么就是token超时或者非法
e.printStackTrace();
//返回异常信息
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
return;
}
String userId = claims.getSubject();
//从redis中获取用户信息
LoginUser loginUser = redisCache.getCacheObject(SystemConstants.LOGIN_KEY_PREFIX + userId);
//如果获取不到
if(Objects.isNull(loginUser)){
//提示重新登录
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
return;
}
//存入securityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
/**
* 密码加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//todo 认证
@Override
protected void configure(HttpSecurity http) throws Exception {
HttpSecurity disable = http.csrf().disable();
System.out.println("----"+disable.toString());
http
.authorizeRequests()
//不通过session获取SecurityContext
//对于登录接口 ,匿名访问
.mvcMatchers("/login").anonymous()
//用于测试的接口---友链
.antMatchers("/link/getAllLink").authenticated()
//剩下的都不需要认证即可访问
.anyRequest().permitAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
http.logout().disable();
//允许跨域
http.cors();
//将自定义的filter添加到过滤器链中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
未处理的异常
上面这种异常处理的方式不符合项目接口的规范,所以我们需要自定义异常处理
实现的认证失败的接口处理
/**
* 认证失败处理
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//打印异常信息
e.printStackTrace();
//判断异常的类型信息
ResponseResult result = null;
if(e instanceof BadCredentialsException){
result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(),e.getMessage());
}else if (e instanceof InsufficientAuthenticationException){
result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR);
}//其他情况
else{
result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),"认证失败!!!");
}
//响应给前端
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
}
}
/**
* 授权失败处理
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
//打印异常信息
e.printStackTrace();
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);
//响应给前端
WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
}
}
//todo 认证
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置认证和授权的异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
比如向如下的异常, 用户登录时没有输入用户名,那么如果我们不在controller层进行拦截 ,他就会进入service层
@PostMapping("/login")
public ResponseResult login(@RequestBody User user){
if (!StringUtils.hasText(user.getUserName())){
//提示 要传用户名
throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
}
return blogLoginService.login(user);
}
所以我们直接在controller层进行拦截
public class SystemException extends RuntimeException{
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public SystemException(AppHttpCodeEnum httpCodeEnum) {
super(httpCodeEnum.getMsg());
this.code = httpCodeEnum.getCode();
this.msg = httpCodeEnum.getMsg();
}
}
/**
* 定义的controller层异常拦截
* 对于controller层的异常 ,直接进行拦截返回
*/
//@ControllerAdvice
//@ResponseBody
@RestControllerAdvice
@Slf4j //使用之后可以直接使用log
public class GlobalExceptionHandler {
@ExceptionHandler(SystemException.class)
public ResponseResult systemExceptionHandler(SystemException e){
//1. 打印异常信息
log.error("出现了异常! {}",e);
//2. 从异常对象中获取提示信息
//3. 封装返回
return ResponseResult.errorResult(e.getCode(),e.getMsg());
}
}
需要token
登录时 ,我们将用户的相关信息存入到了redis中 ,同时也包括token
如果想要退出 ,我们只需要删除redis中的用户登录数据即可
@PostMapping("/logout")
public ResponseResult logout(){
return blogLoginService.logout();
}
service
//todo 退出登录
@Override
public ResponseResult logout() {
//获取 token 解析获取 userId
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getUser().getId();
redisCache.deleteObject(SystemConstants.LOGIN_KEY_PREFIX + userId);
return ResponseResult.okResult();
}
@PostMapping("register")
public ResponseResult register(@RequestBody User user){
return userService.register(user);
}
业务层实现
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private PasswordEncoder passwordEncoder;
//todo 注册用户
@Override
public ResponseResult register(User user) {
//对数据进行非空判断 要求用户名 密码 等都不为空
if(!StringUtils.hasText(user.getUserName())){
ResponseResult.errorResult(AppHttpCodeEnum.USERNAME_NOT_NULL);
}
if( StringUtils.hasText(user.getPassword())){
ResponseResult.errorResult(AppHttpCodeEnum.PASSWORD_NOT_NULL);
}
if( StringUtils.hasText(user.getEmail())){
ResponseResult.errorResult(AppHttpCodeEnum.EMAIL_NOT_NULL);
}
if( StringUtils.hasText(user.getNickName())){
ResponseResult.errorResult(AppHttpCodeEnum.NICKNAME_EXIST);
}
//判断数据库中是否存在用户
if(usernameExist(user.getUserName())){
//用户已经存在
ResponseResult.errorResult(USERNAME_EXIST);
}
if(nickNameExist(user.getNickName())){
//昵称存在
ResponseResult.errorResult(NICKNAME_EXIST);
}
///密码加密处理
String encodePass = passwordEncoder.encode(user.getPassword());
user.setPassword(encodePass); //设置加密之后的密码
save(user);
//返回结果
return ResponseResult.okResult();
}
//todo 判断用户名是否存在
private boolean usernameExist(String username){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
int count = count(queryWrapper);
if(count >= 1){
return true;
}
return false;
}
//todo 判断昵称是否存在
private boolean nickNameExist(String nickName){
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getNickName,nickName);
int count = count(queryWrapper);
if(count >= 1){
return true;
}
return false;
}
}