承接上文:基于springboot + vue 的个人博客搭建过程

目录

  • 1. 评论列表
    • 1.1 接口说明
    • 1.2 controller
    • 1.3 service
    • 1.4 mapper
    • 1.5 实体类
  • 2. 评论
    • 2.1 接口说明
    • 2.2 加入到登录拦截器
    • 2.3 controller
    • 2.4 service
    • 2.5 vo
  • 3. 写文章
    • 3.1 所有文章分类
      • 3.1.1 接口说明
      • 3.1.2 controller
      • 3.1.3 service
    • 3.2 所有文章标签
      • 3.2.1 接口说明
      • 3.2.2 controller
      • 3.2.3 service
    • 3.3 发布文章
      • 3.3.1 接口说明
      • 3.3.2 controller
      • 3.2.3 service
  • 4. AOP日志
    • 4.1 切面应用点
    • 4.2 aop
    • 4.3 utils
  • 5. 文章上传
    • 5.1 接口说明
    • 5.2 pom.xml
    • 5.3 application.properties
    • 5.4 utils
    • 5.5 controller
  • 6. 导航-文章分类
    • 6.1 查询所有的文章分类
      • 6.1.1 接口说明
      • 6.1.2 vo
      • 6.1.3 controller
      • 6.1.4 service
    • 6.2 查询所有的标签
      • 6.2.1 接口说明
      • 6.2.2 vo
      • 6.2.3 controller
      • 6.2.4 service
  • 7. 分类文章列表
    • 7.1 接口说明
    • 7.2 controller
    • 7.2 vo
    • 7.2 service
  • 8. 标签文章列表
    • 8.1 接口说明
    • 8.2 controller
    • 8.3 service
    • 8.4 修改文章列表方法(添加tag查询)
  • 9. 归档文章列表
    • 9.1 接口说明
    • 9.2 vo
    • 9.3 service
    • 9.4 mapper
  • 10 缓存优化
    • 10.1 cache
    • 10.2 controller
  • 11. 思考别的优化
  • 12 管理后台
    • 12.1 新建maven工程 blog-admin
    • 12.2 pom.xml
    • 12.3 配置类
    • 12.4 实体类
    • 12.5 controller
    • 12.6 service
    • 12.7 mapper
  • 13. Security集成
    • 13.1 config
    • 13.2 pojo
    • 13.3 service
    • 13.4 mapper
  • 14. 总结
  • 15 下一章入口

1. 评论列表

1.1 接口说明

接口url:/comments/article/{id}

请求方式:GET

请求参数:

参数名称 参数类型 说明
id long 文章id(路径参数)

返回数据:

{"success": true,"code": 200,"msg": "success","data": [{"id": 53,"author": {"nickname": "李四","avatar": "http://localhost:8080/static/img/logo.b3a48c0.png","id": 1},"content": "写的好","childrens": [{"id": 54,"author": {"nickname": "李四","avatar": "http://localhost:8080/static/img/logo.b3a48c0.png","id": 1},"content": "111","childrens": [],"createDate": "1973-11-26 08:52","level": 2,"toUser": {"nickname": "李四","avatar": "http://localhost:8080/static/img/logo.b3a48c0.png","id": 1}}],"createDate": "1973-11-27 09:53","level": 1,"toUser": null}]
}

1.2 controller

package com.raxcl.blog.controller;import com.raxcl.blog.service.CommentsService;
import com.raxcl.blog.vo.Result;
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("comments")
public class CommentsController {private final CommentsService commentsService;public CommentsController(CommentsService commentsService){this.commentsService = commentsService;}@GetMapping("article/{id}")public Result comments(@PathVariable("id") Long articleId){return commentsService.commentsByArticleId(articleId);}
}

1.3 service

package com.raxcl.blog.service;import com.raxcl.blog.vo.Result;public interface CommentsService {Result commentsByArticleId(Long articleId);
}
package com.raxcl.blog.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.raxcl.blog.dao.mapper.CommentMapper;
import com.raxcl.blog.dao.pojo.Comment;
import com.raxcl.blog.service.CommentsService;
import com.raxcl.blog.service.SysUserService;
import com.raxcl.blog.vo.CommentVo;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.UserVo;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class CommentsServiceImpl implements CommentsService {private final CommentMapper commentMapper;private final SysUserService sysUserService;public CommentsServiceImpl(CommentMapper commentMapper, SysUserService sysUserService) {this.commentMapper = commentMapper;this.sysUserService = sysUserService;}@Overridepublic Result commentsByArticleId(Long articleId) {LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Comment::getArticleId,articleId);queryWrapper.eq(Comment::getLevel,1);List<Comment> comments = commentMapper.selectList(queryWrapper);return Result.success(copyList(comments));}private List<CommentVo> copyList(List<Comment> commentList) {List<CommentVo> commentVoList = new ArrayList<>();for (Comment comment : commentList){commentVoList.add(copy(comment));}return commentVoList;}private CommentVo copy(Comment comment) {CommentVo commentVo = new CommentVo();BeanUtils.copyProperties(comment,commentVo);//时间格式化commentVo.setCreateDate(new DateTime(comment.getCreateDate()).toString("yyyy-MM-dd HH:mm"));Long authorId = comment.getAuthorId();UserVo userVo = sysUserService.findUserVoById(authorId);commentVo.setAuthor(userVo);//评论的评论List<CommentVo> commentVoList = findCommentsByParentId(comment.getId());commentVo.setChildrens(commentVoList);if (comment.getLevel() >1){Long toUid = comment.getToUid();UserVo toUserVo = sysUserService.findUserVoById(toUid);commentVo.setToUser(toUserVo);}return commentVo;}private List<CommentVo> findCommentsByParentId(Long id) {LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Comment::getParentId, id);queryWrapper.eq(Comment::getLevel, 2);List<Comment> comments = this.commentMapper.selectList(queryWrapper);return copyList(comments);}}
package com.raxcl.blog.service;import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.UserVo;public interface SysUserService {UserVo findUserVoById(Long authorId);
}
package com.raxcl.blog.service.impl;import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.raxcl.blog.dao.mapper.SysUserMapper;
import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.service.SysUserService;
import com.raxcl.blog.utils.JWTUtils;
import com.raxcl.blog.vo.ErrorCode;
import com.raxcl.blog.vo.LoginUserVo;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.UserVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.Map;
import java.util.Objects;@Service
public class SysUserServiceImpl implements SysUserService {private final SysUserMapper sysUserMapper;private final RedisTemplate<String, String> redisTemplate;public SysUserServiceImpl(SysUserMapper sysUserMapper, RedisTemplate<String, String> redisTemplate) {this.sysUserMapper = sysUserMapper;this.redisTemplate = redisTemplate;}@Overridepublic UserVo findUserVoById(Long id) {SysUser sysUser = sysUserMapper.selectById(id);if(sysUser == null){sysUser = new SysUser();sysUser.setNickname("raxcl");}UserVo userVo = new UserVo();BeanUtils.copyProperties(sysUser,userVo);return userVo;}}

1.4 mapper

package com.raxcl.blog.dao.mapper;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.raxcl.blog.dao.pojo.Comment;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface CommentMapper extends BaseMapper<Comment> {}

1.5 实体类

package com.raxcl.blog.dao.pojo;import lombok.Data;@Data
public class Comment {private Long id;private String content;private Long createDate;private Long articleId;private Long authorId;private Long parentId;private Long toUid;private Integer level;}
package com.raxcl.blog.vo;import lombok.Data;import java.util.List;@Data
public class CommentVo {private Long id;private UserVo author;private String content;private List<CommentVo> childrens;private String createDate;private Integer level;private UserVo toUser;
}
package com.raxcl.blog.vo;import lombok.Data;@Data
public class UserVo {private String nickname;private String avatar;private Long id;
}

2. 评论

2.1 接口说明

接口url:/comments/create/change

请求方式:POST

请求参数:

参数名称 参数类型 说明
articleId long 文章id
content string 评论内容
parent long 父评论id
toUserId long 被评论的用户id

返回数据:

{"success": true,"code": 200,"msg": "success","data": null
}

2.2 加入到登录拦截器

package com.raxcl.blog.config;@Configuration
public class WebMVCConfig implements WebMvcConfigurer {registry.addInterceptor(loginInterceptor).addPathPatterns("/test").addPathPatterns("/comments/create/change");}
}

2.3 controller

package com.raxcl.blog.controller;import com.raxcl.blog.service.CommentsService;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.CommentParam;
import org.springframework.web.bind.annotation.*;import java.sql.ResultSet;@RestController
@RequestMapping("comments")
public class CommentsController {private final CommentsService commentsService;public CommentsController(CommentsService commentsService){this.commentsService = commentsService;}@PostMapping("create/change")public Result comment(@RequestBody CommentParam commentParam){return commentsService.comment(commentParam);}
}

2.4 service

package com.raxcl.blog.service;import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.CommentParam;public interface CommentsService {Result comment(CommentParam commentParam);
}
package com.raxcl.blog.service.impl;
@Service
public class CommentsServiceImpl implements CommentsService {@Overridepublic Result comment(CommentParam commentParam) {SysUser sysUser = UserThreadLocal.get();Comment comment = new Comment();comment.setArticleId(commentParam.getArticleId());comment.setAuthorId(sysUser.getId());comment.setContent(commentParam.getContent());comment.setCreateDate(System.currentTimeMillis());Long parent = commentParam.getParent();if (parent == null || parent == 0){comment.setLevel(1);}else {comment.setLevel(2);}comment.setParentId(parent == null ? 0 : parent);Long toUserId = commentParam.getToUserId();comment.setToUid(toUserId == null ?0 : toUserId);this.commentMapper.insert(comment);return Result.success(null);}
}

2.5 vo

package com.raxcl.blog.vo;import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;import java.util.List;@Data
public class CommentVo {//防止前端精度损失,把id转为string//分布式id比较长,传到前端会有精度损失,必须转为string类型进行传输,就不会有问题@JsonSerialize(using = ToStringSerializer.class)private Long id;private UserVo author;private String content;private List<CommentVo> childrens;private String createDate;private Integer level;private UserVo toUser;
}
package com.raxcl.blog.vo.param;import lombok.Data;@Data
public class CommentParam {private Long articleId;private String content;private Long parent;private Long toUserId;
}

3. 写文章

3.1 所有文章分类

3.1.1 接口说明

接口url:/categorys

请求方式:GET

请求参数:

参数名称 参数类型 说明

返回数据:

{"success":true,"code":200,"msg":"success","data":[{"id":1,"avatar":"/category/front.png","categoryName":"前端"},  {"id":2,"avatar":"/category/back.png","categoryName":"后端"},{"id":3,"avatar":"/category/lift.jpg","categoryName":"生活"},{"id":4,"avatar":"/category/database.png","categoryName":"数据库"},{"id":5,"avatar":"/category/language.png","categoryName":"编程语言"}]
}

3.1.2 controller

package com.raxcl.blog.controller;import com.raxcl.blog.service.CategoryService;
import com.raxcl.blog.vo.Result;
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 {private final CategoryService categoryService;public CategoryController(CategoryService categoryService) {this.categoryService = categoryService;}@GetMappingpublic Result listCategory(){return categoryService.findAll();}
}

3.1.3 service

package com.raxcl.blog.service;import com.raxcl.blog.vo.CategoryVo;
import com.raxcl.blog.vo.Result;import java.util.List;public interface CategoryService {Result findAll();
}
package com.raxcl.blog.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.raxcl.blog.dao.mapper.CategoryMapper;
import com.raxcl.blog.dao.pojo.Category;
import com.raxcl.blog.service.CategoryService;
import com.raxcl.blog.vo.CategoryVo;
import com.raxcl.blog.vo.Result;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class CategoryServiceImpl implements CategoryService {private final CategoryMapper categoryMapper;public CategoryServiceImpl(CategoryMapper categoryMapper) {this.categoryMapper = categoryMapper;}@Overridepublic Result findAll() {List<Category> categories = categoryMapper.selectList(new LambdaQueryWrapper<>());return Result.success(copyList(categories));}private Object copyList(List<Category> categoryList) {List<CategoryVo> categoryVoList = new ArrayList<>();for(Category category : categoryList){categoryVoList.add(copy(category));}return categoryVoList;}private CategoryVo copy(Category category) {CategoryVo categoryVo = new CategoryVo();BeanUtils.copyProperties(category, categoryVo);return categoryVo;}
}

3.2 所有文章标签

3.2.1 接口说明

接口url:/tags

请求方式:GET

请求参数:

参数名称 参数类型 说明

返回数据:

{"success": true,"code": 200,"msg": "success","data": [{"id": 5,"tagName": "springboot"},{"id": 6,"tagName": "spring"},{"id": 7,"tagName": "springmvc"},{"id": 8,"tagName": "11"}]
}

3.2.2 controller

package com.raxcl.blog.controller;import com.raxcl.blog.service.TagService;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.TagVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("tags")
public class TagsController {@GetMappingpublic Result findAll(){return tagService.findAll();}
}

3.2.3 service

package com.raxcl.blog.service;import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.TagVo;import java.util.List;public interface TagService {Result findAll();
}
package com.raxcl.blog.service.impl;@Service
public class TagServiceImpl implements TagService {@Overridepublic Result findAll() {List<Tag> tags = this.tagMapper.selectList((new LambdaQueryWrapper<>()));return Result.success(copyList(tags));}
}

3.3 发布文章

3.3.1 接口说明

接口url:/articles/publish

请求方式:POST

请求参数:

参数名称 参数类型 说明
title string 文章标题
id long 文章id(编辑有值)
body object({content: “ww”, contentHtml: “

ww

↵”})

文章内容
category {id: 2, avatar: “/category/back.png”, categoryName: “后端”} 文章类别
summary string 文章概述
tags [{id: 5}, {id: 6}] 文章标签

返回数据:

{"success": true,"code": 200,"msg": "success","data": {"id":12232323}
}

3.3.2 controller

package com.raxcl.blog.controller;import com.raxcl.blog.dao.pojo.Article;
import com.raxcl.blog.service.ArticleService;
import com.raxcl.blog.vo.ArticleVo;
import com.raxcl.blog.vo.param.ArticleParam;
import com.raxcl.blog.vo.param.PageParams;
import com.raxcl.blog.vo.Result;
import org.springframework.web.bind.annotation.*;import java.sql.ResultSet;@RestController
@RequestMapping("articles")
public class ArticleController {private final ArticleService articleService;public ArticleController(ArticleService articleService) {this.articleService = articleService;}@PostMapping("publish")public Result publish(@RequestBody ArticleParam articleParam){return articleService.publish(articleParam);}
}

3.2.3 service

package com.raxcl.blog.service;import com.raxcl.blog.vo.ArticleVo;
import com.raxcl.blog.vo.param.ArticleParam;
import com.raxcl.blog.vo.param.PageParams;
import com.raxcl.blog.vo.Result;public interface ArticleService {/*** 文章发布* @param articleParam* @return*/Result publish(ArticleParam articleParam);
}
package com.raxcl.blog.service.impl;@Service
public class ArticleServiceImpl implements ArticleService {@Override@Transactionalpublic Result publish(ArticleParam articleParam) {//此接口要加入到登录拦截当中SysUser sysUser = UserThreadLocal.get();/*** 1. 发布文章 目的 构建Article对象* 2. 作者id 当前的登录用户* 3. 标签 要将标签加入到 关联列表当中*/Article article = new Article();article.setAuthorId(sysUser.getId());article.setCategoryId(articleParam.getCategory().getId());article.setCreateDate(System.currentTimeMillis());article.setCommentCounts(0);article.setSummary(articleParam.getTitle());article.setTitle(articleParam.getTitle());article.setWeight(Article.Article_Common);article.setBodyId(-1L);//插入之后会生成一个文章idthis.articleMapper.insert(article);//tagsList<TagVo> tags = articleParam.getTags();if (tags != null) {for (TagVo tag : tags) {ArticleTag articleTag = new ArticleTag();articleTag.setArticleId(article.getId());articleTag.setTagId(tag.getId());this.articleTagMapper.insert(articleTag);}}//bodyArticleBody articleBody = new ArticleBody();articleBody.setContent(articleParam.getBody().getContent());articleBody.setArticleId(article.getId());articleBodyMapper.insert(articleBody);article.setBodyId(articleBody.getId());articleMapper.updateById(article);ArticleVo articleVo = new ArticleVo();articleVo.setId(article.getId());return Result.success(articleVo);}
}

4. AOP日志

4.1 切面应用点

package com.raxcl.blog.controller;import com.raxcl.blog.common.aop.LogAnnotation;@RestController
@RequestMapping("articles")
public class ArticleController {/*** 首页 文章列表* @param pageParams* @return*/@PostMapping//加上此注解 代表要对此接口记录日志@LogAnnotation(module = "文章" , operator = "获取文章列表")public Result listArticle(@RequestBody PageParams pageParams){return articleService.listArticle(pageParams);}
}

4.2 aop

package com.raxcl.blog.common.aop;import java.lang.annotation.*;//Type 代表可以放在类上面,method代表可以放在方法上面
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {//模块String module() default "";//方法String operator() default  "";
}
package com.raxcl.blog.common.aop;import com.alibaba.fastjson.JSON;
import com.raxcl.blog.utils.HttpContextUtils;
import com.raxcl.blog.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;@Component
@Aspect //切面 定义了通知和切点的关系
@Slf4j
public class LogAspect {@Pointcut("@annotation(com.raxcl.blog.common.aop.LogAnnotation)")public void pt(){}//环绕通知@Around(("pt()"))public Object log(ProceedingJoinPoint joinPoint) throws Throwable {long beginTime = System.currentTimeMillis();//执行方法Object result = joinPoint.proceed();//执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;//保存日志recordLog(joinPoint, time);return result;}private void recordLog(ProceedingJoinPoint joinPoint, long time) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);log.info("==========================log start====================");log.info("module:{}", logAnnotation.module());log.info("operation:{}", logAnnotation.operator());//请求的方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();log.info("request method:{}", className +"."+ methodName + "()");//请求的参数Object[] args = joinPoint.getArgs();String params = JSON.toJSONString(args[0]);log.info("params:{}",params);//获取request 设置IP地址HttpServletRequest request = HttpContextUtils.getHttpServletRequest();log.info("ip:{}", IpUtils.getIpAddr(request));log.info("execute time : {} ms", time);log.info("==========================log end=======================");}
}

4.3 utils

package com.raxcl.blog.utils;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;import javax.servlet.http.HttpServletRequest;/*** 获取Ip*/
@Slf4j
public class IpUtils {/*** 获取ip地址* <p>* 使用Nginx等反向代理软件,则不能通过request.getRemoteAddr()获取ip地址* 如果使用了多级代理的话,X-Forwarded-For的值并不止一个,而是一串ip地址,X-Forwarded-For中第一个非unknown的有效ip字符串,则*/public static String getIpAddr(HttpServletRequest request) {String ip = null, unknown = "unknown", seperator = ",";int maxLength = 15;try {ip = request.getHeader("x-forwarded-for");if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARD_FOR");}if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}}catch (Exception e){log.error("IpUtils ERROR",e);}//使用代理,则获取第一个IP地址if (StringUtils.isEmpty(ip) && ip.length() > maxLength) {int idx = ip.indexOf(seperator);if (idx > 0){ip = ip.substring(0, idx);}}return ip;}/*** 获取ip地址*/
}
package com.raxcl.blog.utils;import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;public class HttpContextUtils {public static HttpServletRequest getHttpServletRequest() {return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();}
}

5. 文章上传

5.1 接口说明

接口url:/upload

请求方式:POST

请求参数:

参数名称 参数类型 说明
image file 上传的文件名称

返回数据:

{"success":true,"code":200,"msg":"success","data":"https://static.mszlu.com/aa.png"
}
<dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>[7.7.0, 7.7.99]</version>
</dependency>

5.2 pom.xml

<!--图片上传服务器--><dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>[7.7.0, 7.7.99]</version></dependency>

5.3 application.properties

#server
server.port=8888
spring.application.name=raxcl# datasource
spring.datasource.url=jdbc:mysql://106.54.170.191:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_spring.redis.host=localhost
spring.redis.port=6379# 上传文件总的最大值
spring.servlet.multipart.max-request-size=20MB
#单个文件的最大值
spring.servlet.multipart.max-file-size=2MB

5.4 utils

package com.raxcl.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.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;@Component
public class QiniuUtils {public static final String url = "r2aun4sy6.hn-bkt.clouddn.com";public boolean upload(MultipartFile file, String fileName) {//构造一个带指定 Region 对象的配置类Configuration cfg = new Configuration(Region.region2());//...其他参数参考类注释UploadManager uploadManager = new UploadManager(cfg);//...生成上传凭证,然后准备上传String accessKey = "jJtOs_dPIScrZVN_agbTogFaJWIF58Ta-moo_sWA";String secretKey = "YI7u3S2xUWthmtfbsGa0lKuwWT0CfOh-Zy0Lsbi9";String bucket = "raxcl-blog";//默认不指定key的情况下,以文件内容的hash值作为文件名try {byte[] uploadBytes = file.getBytes();Auth auth = Auth.create(accessKey, secretKey);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;}
}

5.5 controller

package com.raxcl.blog.controller;import com.raxcl.blog.utils.QiniuUtils;
import com.raxcl.blog.vo.Result;
import org.apache.commons.lang3.StringUtils;
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 {private final QiniuUtils qiniuUtils;public UploadController(QiniuUtils qiniuUtils) {this.qiniuUtils = qiniuUtils;}@PostMappingpublic Result upload(@RequestParam("image")MultipartFile file){//原始文件名称  比如 aa.pngString originalFilename = file.getOriginalFilename();//唯一的文件名称String fileName = UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(originalFilename,".");//上传文件,上传到哪儿呢? 七牛云 云服务器 按量付费 速度快 把图片发放到离用户最近的服务器上// 降低我们自身应用服务器的带宽消耗boolean upload = qiniuUtils.upload(file, fileName);if (upload){return Result.success(QiniuUtils.url+ fileName);}return Result.fail(20001, "上传失败");}
}

6. 导航-文章分类

6.1 查询所有的文章分类

6.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": "好多语言,该学哪个?"}]
}

6.1.2 vo

package com.mszlu.blog.vo;import lombok.Data;@Data
public class CategoryVo {private Long id;private String avatar;private String categoryName;private String description;
}

6.1.3 controller

package com.raxcl.blog.controller;@RestController
@RequestMapping("categorys")
public class CategoryController {private final CategoryService categoryService;public CategoryController(CategoryService categoryService) {this.categoryService = categoryService;}@GetMapping("/detail")public Result categoriesDetail() {return categoryService.findAllDetail();}
}

6.1.4 service

package com.raxcl.blog.service;public interface CategoryService {Result findAllDetail();
}
package com.raxcl.blog.service.impl;@Service
public class CategoryServiceImpl implements CategoryService {@Overridepublic Result findAll() {LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.select(Category::getId, Category::getCategoryName);List<Category> categories = categoryMapper.selectList(queryWrapper);return Result.success(copyList(categories));}@Overridepublic Result findAllDetail() {LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();List<Category> categories = categoryMapper.selectList(queryWrapper);//页面交互的对象return Result.success(copyList(categories));}
}

6.2 查询所有的标签

6.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"}]
}

6.2.2 vo

package com.raxcl.blog.vo;import lombok.Data;@Data
public class TagVo {private Long id;private String tagName;private String avatar;
}

6.2.3 controller

package com.raxcl.blog.controller;import com.raxcl.blog.service.TagService;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.TagVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("tags")
public class TagsController {@GetMapping("/detail")public Result findAllDetail(){return tagService.findAllDetail();}
}

6.2.4 service

package com.raxcl.blog.service;import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.TagVo;import java.util.List;public interface TagService {Result findAllDetail();
}
package com.raxcl.blog.service.impl;
@Service
public class TagServiceImpl implements TagService {@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<>();List<Tag> tags = this.tagMapper.selectList(queryWrapper);return Result.success(copyList(tags));}
}

7. 分类文章列表

7.1 接口说明

接口url:/category/detail/{id}

请求方式:GET

请求参数:

参数名称 参数类型 说明
id 分类id 路径参数

返回数据:

{"success": true, "code": 200, "msg": "success", "data": {"id": 1, "avatar": "/static/category/front.png", "categoryName": "前端", "description": "前端是什么,大前端"}
}

7.2 controller

package com.raxcl.blog.controller;import com.raxcl.blog.service.CategoryService;
import com.raxcl.blog.vo.Result;
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 {@GetMapping("detail/{id}")public Result categoriesDetailById(@PathVariable("id") Long id){return categoryService.categoriesDetailById(id);}
}

7.2 vo

package com.raxcl.blog.vo.param;import lombok.Data;@Data
public class PageParams {private int page = 1;private int pageSize = 10;private Long categoryId;private Long tagId;
}

7.2 service

package com.raxcl.blog.service;import com.raxcl.blog.vo.CategoryVo;
import com.raxcl.blog.vo.Result;import java.util.List;public interface CategoryService {Result categoriesDetailById(Long id);
}
package com.raxcl.blog.service.impl;@Service
public class CategoryServiceImpl implements CategoryService {@Overridepublic Result categoriesDetailById(Long id) {Category category = categoryMapper.selectById(id);CategoryVo categoryVo = copy(category);return Result.success(categoryVo);}
package com.raxcl.blog.service.impl;@Service
public class ArticleServiceImpl implements ArticleService {@Overridepublic Result listArticle(PageParams pageParams) {/*** 1. 分页查询 article数据库表*/Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();if (pageParams.getCategoryId() != null){// and category_id=#{categoryId}queryWrapper.eq(Article::getCategoryId, pageParams.getCategoryId());}//是否置顶进行排序queryWrapper.orderByDesc(Article::getWeight, Article::getCreateDate);Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);//能直接返回吗? 很明显不能List<Article> records = articlePage.getRecords();List<ArticleVo> articleVoList = copyList(records, true, true);return Result.success(articleVoList);}

8. 标签文章列表

8.1 接口说明

接口url:/tags/detail/{id}

请求方式:GET

请求参数:

参数名称 参数类型 说明
id 标签id 路径参数

返回数据:

{"success": true, "code": 200, "msg": "success", "data": {"id": 5, "tagName": "springboot", "avatar": "/static/tag/java.png"}
}

8.2 controller

@GetMapping("detail/{id}")public Result findDetailById(@PathVariable("id") Long id){return tagService.findDetailById(id);}

8.3 service

Result findDetailById(Long id);
  @Overridepublic Result findDetailById(Long id) {Tag tag = tagMapper.selectById(id);TagVo copy = copy(tag);return Result.success(copy);}

8.4 修改文章列表方法(添加tag查询)

package com.raxcl.blog.service.impl;@Service
public class ArticleServiceImpl implements ArticleService {@Overridepublic Result listArticle(PageParams pageParams) {/*** 1. 分页查询 article数据库表*/Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();if (pageParams.getCategoryId() != null){// and category_id=#{categoryId}queryWrapper.eq(Article::getCategoryId, pageParams.getCategoryId());}List<Long> articleIdList = new ArrayList<>();if (pageParams.getTagId() != null){//加入标签 条件查询//article表中 并没有tag字段 一篇文章 有多个标签//article_tag article_id 1:n tag_idLambdaQueryWrapper<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){//and id in (1,2,3)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, true);return Result.success(articleVoList);}
}

9. 归档文章列表

9.1 接口说明

接口url:/articles

请求方式:POST

请求参数:

参数名称 参数类型 说明
year string
month string

返回数据:

{"success": true, "code": 200, "msg": "success", "data": [文章列表,数据同之前的文章列表接口]}

9.2 vo

package com.raxcl.blog.vo.param;import lombok.Data;@Data
public class PageParams {private int page = 1;private int pageSize = 10;private Long categoryId;private Long tagId;private String year;private String month;public String getMonth(){if (this.month != null && this.month.length() == 1){return "0" + this.month;}return this.month;}
}

9.3 service

package com.raxcl.blog.service.impl;@Service
public class ArticleServiceImpl implements ArticleService {/*@Overridepublic Result listArticle(PageParams pageParams) {*//*** 1. 分页查询 article数据库表*//*Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();if (pageParams.getCategoryId() != null){// and category_id=#{categoryId}queryWrapper.eq(Article::getCategoryId, pageParams.getCategoryId());}List<Long> articleIdList = new ArrayList<>();if (pageParams.getTagId() != null){//加入标签 条件查询//article表中 并没有tag字段 一篇文章 有多个标签//article_tag article_id 1:n tag_idLambdaQueryWrapper<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){//and id in (1,2,3)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, true);return Result.success(articleVoList);}*/@Overridepublic Result listArticle(PageParams pageParams) {Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());IPage<Article> articleIPage = this.articleMapper.listArchive(page,pageParams.getCategoryId(),pageParams.getTagId(),pageParams.getYear(),pageParams.getMonth());return Result.success(copyList(articleIPage.getRecords(),true,true));}

9.4 mapper

package com.raxcl.blog.dao.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.raxcl.blog.dao.dto.Archives;
import com.raxcl.blog.dao.pojo.Article;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface ArticleMapper extends BaseMapper<Article> {IPage<Article> listArchive(Page<Article> page, Long categoryId, Long tagId, String year, String month);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.raxcl.blog.dao.mapper.ArticleMapper"><select id="listArchives" resultType="com.raxcl.blog.dao.dto.Archives">select from_unixtime(create_date/1000,'%Y') year, from_unixtime(create_date/1000,'%m') month, count(*) countfrom ms_articlegroup by year, month</select><select id="listArchive" resultType="com.raxcl.blog.dao.pojo.Article">select * from ms_article<where><if test="categoryId != null">and category_id = #{categoryId}</if><if test="year != null and year.length>0 and month !=null and month.length>0">and (from_unixtime(create_date/1000,'%Y') = #{year} and from_unixtime(create_date/1000,'%m') = #{month} )</if><if test="tagId != null">and id in (select article_id from ms_article_tag where tag_id=#{tagId})</if></where>order by create_date desc</select>
</mapper>

10 缓存优化

内存的访问速度远远大于磁盘的访问速度(1000倍起)

10.1 cache

package com.raxcl.blog.common.cache;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {long expire() default 60 * 1000;String name() default "";
}
package com.raxcl.blog.common.cache;import com.alibaba.fastjson.JSON;
import com.raxcl.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.time.Duration;@Aspect
@Component
@Slf4j
public class CacheAspect {private final RedisTemplate<String, String> redisTemplate;public CacheAspect(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}@Pointcut("@annotation(com.raxcl.blog.common.cache.Cache)")public void pt(){}@Around("pt()")public Object around(ProceedingJoinPoint pjp){try {Signature signature = pjp.getSignature();//类名String className = pjp.getTarget().getClass().getSimpleName();//调用的方法名String methodName = signature.getName();Class[] paramterTypes = new Class[pjp.getArgs().length];Object[] args = pjp.getArgs();//参数String params = "";for(int i=0; i<args.length; i++){if (args[i] != null) {params += JSON.toJSONString(args[i]);paramterTypes[i] = args[i].getClass();}else {paramterTypes[i] = null;}}if (StringUtils.isNotEmpty(params)){//加密 以防出现key过长以及字符转义获取不到的情况params = DigestUtils.md5Hex(params);}Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, paramterTypes);//获取Cache注解Cache annotation = method.getAnnotation(Cache.class);//缓存过期时间long expire = annotation.expire();//缓存名称String name = annotation.name();//先从redis获取String redisKey = name + "::" + className + "::" + methodName + "::" + params;String redisValue = redisTemplate.opsForValue().get(redisKey);if (StringUtils.isNotEmpty(redisValue)){log.info("走了缓存~~~{},{}",className,methodName);return JSON.parseObject(redisValue, Result.class);}Object proceed = pjp.proceed();redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed), Duration.ofMillis(expire));log.info("存入缓存~~~{},{}",className,methodName);return proceed;} catch (Throwable throwable) {throwable.printStackTrace();}return Result.fail(-999, "系统错误");}
}

10.2 controller

package com.raxcl.blog.controller;@RestController
@RequestMapping("articles")
public class ArticleController {/*** 首页 文章列表* @param pageParams* @return*/@PostMapping//加上此注解 代表要对此接口记录日志@LogAnnotation(module = "文章" , operator = "获取文章列表")@Cache(expire = 5 * 60 * 1000, name = "list_article")public Result listArticle(@RequestBody PageParams pageParams){return articleService.listArticle(pageParams);}/*** 首页 最热文章* @return*/@PostMapping("hot")@Cache(expire = 5 * 60 * 1000, name = "hot_article")public Result hotArticle(){int limit = 5;return articleService.hotArticle(limit);}/*** 首页 最新文章* @return*/@PostMapping("new")@Cache(expire = 5 * 60 * 1000, name = "new_article")public Result newArticle(){int limit = 5;return articleService.newArticle(limit);}}

11. 思考别的优化

  1. 文章可以放入es当中,便于后续中文分词搜索
  2. 评论数据,可以考虑放入mongodb当中(可以增加图片评论)
  3. 阅读数和评论数, 考虑把阅读数和评论数 增加的时候放入redis incr 自增, 使用定时任务 定时把数据固话到数据库当中
  4. 为了加快访问速度,部署的时候,可以把图片,js,css等放入七牛云存储中,加快网站访问速度
    (后续进行…)

12 管理后台

12.1 新建maven工程 blog-admin

12.2 pom.xml


```java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>blog-parent</artifactId><groupId>com.raxcl</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>blog-admin</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><!-- 排除 默认使用的logback  --><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- log4j2 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.2</version></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10.10</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency></dependencies></project>

12.3 配置类

server.port=8889
spring.application.name=mszlu_admin_blog#数据库的配置
# datasource
spring.datasource.url=jdbc:mysql://106.54.170.191:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_
package com.raxcl.blog.admin.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@MapperScan("com.raxcl.blog.admin.mapper")
public class MybatisPlusConfig {//分页插件@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}
}

12.4 实体类

package com.raxcl.blog.admin.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;@Data
public class Permission {@TableId(type = IdType.AUTO)private Long id;private String name;private String path;private String description;
}
package com.raxcl.blog.admin.model.params;import lombok.Data;@Data
public class PageParam {private Integer currentPage;private Integer pageSize;private String queryString;
}
package com.raxcl.blog.admin.vo;import lombok.Data;import java.util.List;@Data
public class PageResult<T> {private List<T> list;private Long total;
}
package com.raxcl.blog.admin.vo;import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class Result {private boolean success;private int code;private String msg;private Object data;public static Result success(Object data) {return new Result(true, 200, "success", data);}public static Result fail(int code, String msg) {return new Result(false, code, msg, null);}
}

12.5 controller

package com.raxcl.blog.admin.controller;import com.raxcl.blog.admin.model.params.PageParam;
import com.raxcl.blog.admin.pojo.Permission;
import com.raxcl.blog.admin.service.PermissionService;
import com.raxcl.blog.admin.vo.Result;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("admin")
public class AdminController {private final PermissionService permissionService;public AdminController(PermissionService permissionService) {this.permissionService = permissionService;}@PostMapping("permission/permissionList")public Result permissionList(@RequestBody PageParam pageParam){return permissionService.listPermission(pageParam);}@PostMapping("permission/add")public Result add(@RequestBody Permission permission){return permissionService.add(permission);}@PostMapping("permission/update")public Result update(@RequestBody Permission permission){return permissionService.update(permission);}@GetMapping("permission/delete/{id}")public Result delete(@PathVariable("id") Long id){return permissionService.delete(id);}
}

12.6 service

package com.raxcl.blog.admin.service;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.raxcl.blog.admin.mapper.PermissionMapper;
import com.raxcl.blog.admin.model.params.PageParam;
import com.raxcl.blog.admin.pojo.Permission;
import com.raxcl.blog.admin.vo.PageResult;
import com.raxcl.blog.admin.vo.Result;
import org.springframework.stereotype.Service;@Service
public class PermissionService {private final PermissionMapper permissionMapper;public PermissionService(PermissionMapper permissionMapper) {this.permissionMapper = permissionMapper;}public Result listPermission(PageParam pageParam){/*** 要的数据,管理台,表的所有的字段 Permission* 分页查询*/Page<Permission> page = new Page<>(pageParam.getCurrentPage(),pageParam.getPageSize());LambdaQueryWrapper<Permission> queryWrapper = new LambdaQueryWrapper<>();if (StringUtils.isNotBlank(pageParam.getQueryString())) {queryWrapper.eq(Permission::getName,pageParam.getQueryString());}Page<Permission> permissionPage = this.permissionMapper.selectPage(page, queryWrapper);PageResult<Permission> pageResult = new PageResult<>();pageResult.setList(permissionPage.getRecords());pageResult.setTotal(permissionPage.getTotal());return Result.success(pageResult);}public Result add(Permission permission) {this.permissionMapper.insert(permission);return Result.success(null);}public Result update(Permission permission) {this.permissionMapper.updateById(permission);return Result.success(null);}public Result delete(Long id) {this.permissionMapper.deleteById(id);return Result.success(null);}
}

12.7 mapper

package com.raxcl.blog.admin.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.raxcl.blog.admin.pojo.Permission;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {}

13. Security集成

13.1 config

package com.raxcl.blog.admin.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}public static void main(String[] args) {//加密策略 MD5 不安全 彩虹表  MD5 加盐String mszlu = new BCryptPasswordEncoder().encode("mszlu");System.out.println(mszlu);}@Overridepublic void configure(WebSecurity web) throws Exception {super.configure(web);}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests() //开启登录认证
//                .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色.antMatchers("/css/**").permitAll().antMatchers("/img/**").permitAll().antMatchers("/js/**").permitAll().antMatchers("/plugins/**").permitAll().antMatchers("/admin/**").access("@authService.auth(request,authentication)") //自定义service 来去实现实时的权限认证.antMatchers("/pages/**").authenticated().and().formLogin().loginPage("/login.html") //自定义的登录页面.loginProcessingUrl("/login") //登录处理接口.usernameParameter("username") //定义登录时的用户名的key 默认为username.passwordParameter("password") //定义登录时的密码key,默认是password.defaultSuccessUrl("/pages/main.html").failureUrl("/login.html").permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过.and().logout() //退出登录配置.logoutUrl("/logout") //退出登录接口.logoutSuccessUrl("/login.html").permitAll() //退出登录的接口放行.and().httpBasic().and().csrf().disable() //csrf关闭 如果自定义登录 需要关闭.headers().frameOptions().sameOrigin();}
}

13.2 pojo

package com.raxcl.blog.admin.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;@Data
public class Admin {@TableId(type = IdType.AUTO)private Long id;private String username;private String password;
}

13.3 service

package com.raxcl.blog.admin.service;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.raxcl.blog.admin.mapper.AdminMapper;
import com.raxcl.blog.admin.mapper.PermissionMapper;
import com.raxcl.blog.admin.pojo.Admin;
import com.raxcl.blog.admin.pojo.Permission;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class AdminService {private final AdminMapper adminMapper;private final PermissionMapper permissionMapper;public AdminService(AdminMapper adminMapper, PermissionMapper permissionMapper) {this.adminMapper = adminMapper;this.permissionMapper = permissionMapper;}public Admin findAdminByUserName(String username){LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(Admin::getUsername, username).last("limit 1");Admin adminUser = adminMapper.selectOne(queryWrapper);return adminUser;}public List<Permission> findPermissionsByAdminId(Long adminId){return permissionMapper.findPermissionsByAdminId(adminId);}
}
package com.raxcl.blog.admin.service;import com.raxcl.blog.admin.pojo.Admin;
import com.raxcl.blog.admin.pojo.Permission;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import java.util.List;@Service
@Slf4j
public class AuthService {private final AdminService adminService;public AuthService(AdminService adminService) {this.adminService = adminService;}public boolean auth(HttpServletRequest request, Authentication authentication){String requestURI = request.getRequestURI();log.info("request url:{}", requestURI);//true代表放行 false 代表拦截Object principal = authentication.getPrincipal();if (principal == null || "annoymousUser".equals(principal)){//未登录return false;}UserDetails userDetails = (UserDetails) principal;String username = userDetails.getUsername();Admin admin = adminService.findAdminByUserName(username);if (admin == null){return false;}if (admin.getId() == 1){//认为是超级管理员return true;}List<Permission> permissions = adminService.findPermissionsByAdminId(admin.getId());requestURI = StringUtils.split(requestURI, '?')[0];for (Permission permission : permissions) {if (requestURI.equals(permission.getPath())){log.info("权限通过");return true;}}return false;}
}
package com.raxcl.blog.admin.service;import org.springframework.security.core.GrantedAuthority;public class MySimpleGrantedAuthority implements GrantedAuthority {private String authority;private String path;public MySimpleGrantedAuthority() {}public MySimpleGrantedAuthority(String authority) {this.authority = authority;}public MySimpleGrantedAuthority(String authority, String path) {this.authority = authority;this.path = path;}@Overridepublic String getAuthority() {return authority;}public String getPath(){return path;}
}
package com.raxcl.blog.admin.service;import com.raxcl.blog.admin.pojo.Admin;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;import java.util.ArrayList;@Component
@Slf4j
public class SecurityUserService implements UserDetailsService {private final AdminService adminService;public SecurityUserService(AdminService adminService) {this.adminService = adminService;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {log.info("username:{}",username);//当用户登录的时候,springSecurity就会将请求转发到此//根据用户名查找用户,不存在则抛出异常, 存在则将用户名,密码,授权列表 组装成springSecurity的User对象并返回Admin adminUser = adminService.findAdminByUserName(username);if (adminUser == null){throw new UsernameNotFoundException("用户名不存在");}ArrayList<GrantedAuthority> authorities = new ArrayList<>();UserDetails userDetails = new User(username,adminUser.getPassword(),authorities);//剩下的认证 就由框架帮我们完成return userDetails;}
}

13.4 mapper

package com.raxcl.blog.admin.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.raxcl.blog.admin.pojo.Admin;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface AdminMapper extends BaseMapper<Admin> {}
package com.raxcl.blog.admin.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.raxcl.blog.admin.pojo.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {@Select("select * from ms_permission where id in (select permission_id from ms_admin_permission where admin_id=#{adminId})")List<Permission> findPermissionsByAdminId(Long adminId);
}

待优化项: 添加角色,用户拥有多个角色,一个角色拥有多个权限

14. 总结

总结:

  1. jwt + redis
    token令牌的登录方式,访问认证速度快,session共享,安全性
    redis做了令牌和用户信息的对应管理, 1. 进一步增加了安全性 2, 登录用户做了缓存 3. 灵活控制用户的过期(续期,提掉线等)
  2. threadLocal 使用了保存用户信息, 请求的线程之内, 可以随时获取登录的用户,做了线程隔离
  3. 在使用完ThreadLocal之后,做了value的删除,防止了内存泄漏
  4. 线程安全-update table set value = newValue where id=1 and value = oldValue
  5. 线程池,应用非常广,面试7个核心参数(对当前的主业务流程无影响的操作,放入线程池执行)
    1.登录,记录日志
  6. 权限系统 重点内容
  7. 统一日志记录,统一缓存处理

15 下一章入口

然后就是服务器购买,域名注册,备案,将项目部署上线
跳转: 基于springboot + vue 的个人博客搭建过程(上线)

基于springboot + vue 的个人博客搭建过程(续)相关推荐

  1. 基于springboot + vue 的个人博客搭建过程(上线)

    承接上文: 基于springboot + vue 的个人博客搭建过程(续) 目录 1. 搭建环境 1. 安装docker 2. 拉取并运行 2.1 拉取服务 2.2 部署运行mysql 2.3 部署运 ...

  2. 基于SpringBoot + Vue的个人博客

    博客介绍 基于Springboot + Vue 开发的前后端分离博客 在线地址 项目链接: www.ttkwsd.top 后台链接: admin.ttkwsd.top Github地址: https: ...

  3. 推荐一个基于Springboot+Vue的开源博客系统

    简介 这是一个基于Springboot2.x,vue2.x的前后端分离的开源博客系统,提供 前端界面+管理界面+后台服务 的整套系统源码.响应式设计,手机.平板.PC,都有良好的视觉效果! 你可以拿它 ...

  4. 基于SpringBoot + Vue的个人博客系统12——使用vue-admin-template展示文章列表(后台管理)

    简介 前面我们实现了博客系统的前台展示页面,还有留言功能没有实现,实现留言功能无非就是在后端增加留言表,对留言进行增删改查.和文章表类似,这里就不在赘述. 既然作为一款动态博客,那么后台管理是必不可少 ...

  5. java基于springboot+vue的旅游博客旅游经验分享系统

    如今社会飞快发展,人们生活节奏不断加快,压力也随之变大.为了释放压力,缓解疲劳,大多数人会选择旅游.但是现在基本上很少有免费网站注重介绍张家界的,大部分都是以"商"为主提供导游.酒 ...

  6. 基于SpringBoot + Vue的个人博客系统16——文章的修改和删除

    简介 删除文章 删除功能比较简单,只需进行如下操作: 调用删除接口删除文章 然后再刷新文章列表 修改文章 在文章列表页面点击修改文章按钮 跳转到写文章页面,同时带上文章 id 作为参数 在写文章界面创 ...

  7. 基于SpringBoot + Vue的个人博客系统07——文章列表和文章详情

    简介 由于本人不是专业的前端,所以写出来的界面可能会稍微有些丑陋,甚至有些地方的写法不是很专业,还请大家见谅 主界面 JS 部分 首先是 js 逻辑部分 我们先在@/http/request.js中定 ...

  8. 【简易搭建个人博客】------- 基于BT面板的个人博客搭建

    第三天笔记 时间:1月10日 BT面板 CentOs下载BT面板 yum install -y wget && wget -O install.sh http://download.b ...

  9. LAMP基于php模块实现个人博客搭建

    1.完成LAMP基本实现 http://guanm.blog.51cto.com/13126952/1974839 需要管理博客数据库的管理员,之前创建的test用户权限不够,给test用户加权限 g ...

最新文章

  1. 14个顶级开发社区 [程序员]
  2. Android 5.0+高级动画开发 矢量图动画 轨迹动画 路径变换
  3. .net中javascript去调用webservice
  4. 自动驾驶红旗车,背后站着小马哥
  5. Android/ios手机销售榜信息
  6. 大厂面试常问的机器学习,计算机视觉怎么学?详细指南来了!
  7. Dynamics CRM 2015Online Update1 new feature之 通过业务规则清空字段的值
  8. python实现不同图像数据的叠加处理、实现多张图像数据以子图形式组合为新的图像数据【图像叠加、图像组合】
  9. 2022年中国研究生数学建模竞赛
  10. 智慧校园: 00 开发流程
  11. 2018引汉济渭计算机监控系统,基于数字水网的引汉济渭受水区水资源调配业务化研究与应用...
  12. BulletProof vs snark vs stark性能对比
  13. python中有这样一条语句_在Python中一行书写两条语句时,语句之间可以使用__________作为分隔符。_学小易找答案...
  14. 程序员再忙也应该看看《琅琊榜》
  15. JSON parse error: Unrecognized field “abc“ (class cn.kk.xxxDto), not marked as ignorable;
  16. 网站响应速度慢,这些原因不可不知?
  17. IIS + PHP 配置
  18. 光阑,像差和成像光学仪器
  19. 2017华为软件精英挑战赛小结
  20. 探索语言交互技术在政务数字化的应用

热门文章

  1. spring boot 最全配置说明
  2. Windows下查看端口号与进程关系的命令
  3. hevc 中BLA图像
  4. 【bzoj1070】[SCOI2007]修车 网络流
  5. Drivedroid的读取写入速度初步测试
  6. 街区的最短路径 nyist7
  7. 售后服务管理系统,公司可以轻松地在全国范围内实现联网维修服务
  8. Multi-Metrics Graph-Based Unsupervised Domain Adaptation for Cross-Modal Hashing
  9. mysql fulltext_MySQL(FullText)
  10. BlackIce Server Protect用户防火墙规则修改漏洞