学习内容:springboot博客项目完成登录、注册、文章详情、评论、写文章等功能
程序目录结构
common.aop
package com.mszlu.blogapi.common.aop; import java.lang.annotation.*; /* * 日志注解 */ //ElementType.TYPE代表可以放在类上面 method代表可以放在方法上 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAnnotation { String module() default ""; String operation() default ""; }
package com.mszlu.blogapi.common.aop; import com.alibaba.fastjson.JSON; import com.mszlu.blogapi.utils.HttpContextUtils; import com.mszlu.blogapi.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; /* * @Author ljm * @Date 2021/10/18 21:01 * @Version 1.0 */ @Component @Aspect //切面 定义了通知和切点的关系 @Slf4j public class LogAspect { @Pointcut("@annotation(com.mszlu.blogapi.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.operation()); //请求的方法名 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================================"); } }
config
package com.mszlu.blogapi.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.mszlu.blogapi.dao.mapper") public class MybatisPlusConfig { //分页插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } }
package com.mszlu.blogapi.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; @Configuration @EnableAsync //开启多线程 public class ThreadPoolConfig { @Bean("taskExecutor") public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 设置核心线程数 executor.setCorePoolSize(5); // 设置最大线程数 executor.setMaxPoolSize(20); //配置队列大小 executor.setQueueCapacity(Integer.MAX_VALUE); // 设置线程活跃时间(秒) executor.setKeepAliveSeconds(60); // 设置默认线程名称 executor.setThreadNamePrefix("码神之路博客项目"); // 等待所有任务结束后再关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); //执行初始化 executor.initialize(); return executor; } }
package com.mszlu.blogapi.config; import com.mszlu.blogapi.handler.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMVCConfig implements WebMvcConfigurer{ @Autowired private LoginInterceptor loginInterceptor; @Override public void addCorsMappings(CorsRegistry registry) { //跨域配置 registry.addMapping("/**").allowedOrigins("http://localhost:8080"); } @Override public void addInterceptors(InterceptorRegistry registry) { //假设拦截test接口,后续实际遇到拦截的接口时,再配置真正的拦截接口 registry.addInterceptor(loginInterceptor) .addPathPatterns("/test") .addPathPatterns("/comments/create/change") .addPathPatterns("/articles/publish"); } }
controller
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.common.aop.LogAnnotation; import com.mszlu.blogapi.service.ArticleService; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.ArticleParam; import com.mszlu.blogapi.vo.params.PageParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; //json数据进行交互 @RestController @RequestMapping("articles") public class ArticleController { @Autowired private ArticleService articleService; /* * 首页 文章列表 * @param pageParams * @return*/ @PostMapping //加上此注解,表示要对此接口记录日志 @LogAnnotation(module="文章",operation="获取文章列表") public Result listArticle(@RequestBody PageParams pageParams) { // List<ArticleVo> articles = articleService.listArticlesPage(pageParams); return articleService.listArticle(pageParams); } /* * 首页 最热文章 * @return*/ @PostMapping("hot") public Result hotArticle() { int limit = 5; return articleService.hotArticle(limit); } /* * 首页 最新文章 * @return*/ @PostMapping("new") public Result newArticles() { int limit = 5; return articleService.newArticle(limit); } /* * 首页 文章归档 * @return*/ @PostMapping("listArchives") public Result listArchives() { return articleService.listArchives(); } @PostMapping("view/{id}") public Result findArticleById(@PathVariable("id") Long articleId){ return articleService.findArticleById(articleId); } @PostMapping("publish") public Result publish(@RequestBody ArticleParam articleParam){ return articleService.publish(articleParam); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.service.CategoryService; import com.mszlu.blogapi.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("categorys") public class CategoryController { @Autowired private CategoryService categoryService; @GetMapping public Result categories() { return categoryService.findAll(); } @GetMapping("detail") public Result categoriesDetail(){ return categoryService.findAllDetail(); } @GetMapping("detail/{id}") public Result categoriesDetailById(@PathVariable("id") Long id){ return categoryService.categoriesDetailById(id); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.service.CommentsService; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.CommentParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("comments") public class CommentsController { @Autowired(required = false) private CommentsService commentsService; @GetMapping("article/{id}") public Result comments(@PathVariable("id") Long id){ return commentsService.commentsByArticleId(id); } @PostMapping("create/change") public Result comment(@RequestBody CommentParam commentParam){ return commentsService.comment(commentParam); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.service.LoginService; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("login") public class LoginController { @Autowired private LoginService loginService; @PostMapping public Result login(@RequestBody LoginParam loginParam){ //登陆 验证用户 访问用户表 return loginService.login(loginParam); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.service.LoginService; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("logout") public class LogoutController { @Autowired private LoginService loginService; @GetMapping public Result logout(@RequestHeader("Authorization") String token){ return loginService.logout(token); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.service.LoginService; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.LoginParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("register") public class RegisterController { @Autowired private LoginService loginService; @PostMapping public Result register(@RequestBody LoginParam loginParam){ //sso 单点登录,后期如果把登录注册功能 提出去(单独的服务,可以独立提供接口服务) return loginService.register(loginParam); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.service.TagService; import com.mszlu.blogapi.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("tags") public class TagController { @Autowired private TagService tagService; // tags/hot @GetMapping("hot") public Result hot() { int limit = 6; return tagService.hots(limit); } @GetMapping public Result findAll(){ /* * 查询所有的文章标签 * @return */ return tagService.findAll(); } @GetMapping("detail") public Result findAllDetail(){ return tagService.findAllDetail(); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.utils.UserThreadLocal; import com.mszlu.blogapi.vo.Result; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("test") public class TestController { @RequestMapping public Result test(){ SysUser sysUser = UserThreadLocal.get(); System.out.println(sysUser); return Result.success(null); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.utils.QiniuUtils; import com.mszlu.blogapi.vo.Result; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.util.UUID; @RestController @RequestMapping("upload") public class UploadController { @Autowired private QiniuUtils qiniuUtils; @PostMapping public Result upload(@RequestParam("image") MultipartFile file) { //原始文件名称 比如说aa.png String 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, "上传失败"); } }
package com.mszlu.blogapi.controller; import com.mszlu.blogapi.service.SysUserService; import com.mszlu.blogapi.vo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("users") public class UserController { @Autowired private SysUserService sysUserService; // users/currentUser @GetMapping("currentUser") public Result currentUser(@RequestHeader("Authorization") String token){ return sysUserService.findUserByToken(token); } }
dao
package com.mszlu.blogapi.dao.dos; import lombok.Data; @Data public class Archives { private Integer year; private Integer month; private Long count; }
package com.mszlu.blogapi.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blogapi.dao.pojo.ArticleBody; public interface ArticleBodyMapper extends BaseMapper<ArticleBody> { }
package com.mszlu.blogapi.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blogapi.dao.dos.Archives; import com.mszlu.blogapi.dao.pojo.Article; import org.springframework.context.annotation.Configuration; import java.util.List; public interface ArticleMapper extends BaseMapper<Article> { List<Archives> listArchives(); }
package com.mszlu.blogapi.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blogapi.dao.pojo.ArticleTag; public interface ArticleTagMapper extends BaseMapper<ArticleTag> { }
package com.mszlu.blogapi.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blogapi.dao.pojo.Category; public interface CategoryMapper extends BaseMapper<Category> { }
package com.mszlu.blogapi.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blogapi.dao.pojo.Comment; public interface CommentMapper extends BaseMapper<Comment> { }
package com.mszlu.blogapi.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.dao.pojo.Tag; public interface SysUserMapper extends BaseMapper<SysUser> { }
package com.mszlu.blogapi.dao.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mszlu.blogapi.dao.pojo.Tag; import java.util.List; public interface TagMapper extends BaseMapper<Tag> { /* * 根据文章id查询标签列表 * @param articleId * @return*/ List<Tag> findTagsByArticleId(Long articleId); /* * 查询最热的标签 前n条 * @param limit * @return*/ List<Long> findHotsTagIds(int limit); List<Tag> findTagsByTagId(List<Long> tagIds); }
package com.mszlu.blogapi.dao.pojo; import lombok.Data; @Data public class Article { public static final int Article_TOP = 1; public static final int Article_Common = 0; private Long id; private String title; private String summary; private Integer commentCounts; private Integer viewCounts; /** * 作者id */ private Long authorId; /** * 内容id */ private Long bodyId; /** *类别id */ private Long categoryId; /** * 置顶 */ private Integer weight ; /** * 创建时间 */ private Long createDate; }
package com.mszlu.blogapi.dao.pojo; import lombok.Data; @Data public class ArticleBody { private Long id; private String content; private String contentHtml; private Long articleId; }
package com.mszlu.blogapi.dao.pojo; import lombok.Data; @Data public class ArticleTag { private Long id; private Long articleId; private Long tagId; }
package com.mszlu.blogapi.dao.pojo; import lombok.Data; @Data public class Category { private Long id; private String avatar; private String categoryName; private String description; }
package com.mszlu.blogapi.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.mszlu.blogapi.dao.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; @Data public class SysUser { // @TableId(type = IdType.ASSIGN_ID) //默认id类型 // 以后 用户多了之后,要进行分表操作,id就需要用分布式id了 // @TableId(type = IdType.AUTO) 数据库自增 private Long id; private String account; private Integer admin; private String avatar; private Long createDate; private Integer deleted; private String email; private Long lastLogin; private String mobilePhoneNumber; private String nickname; private String password; private String salt; private String status; }
package com.mszlu.blogapi.dao.pojo; import lombok.Data; @Data public class Tag { private Long id; private String avatar; private String tagName; }
handler
package com.mszlu.blogapi.handler; import com.mszlu.blogapi.vo.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; //对加了@Controller注解的方法进行拦截处理 AOP的实现 @ControllerAdvice public class AllExceptionHandler { //进行异常处理,处理Exception.class的异常 @ExceptionHandler(Exception.class) @ResponseBody //返回json数据 public Result doException(Exception ex) { ex.printStackTrace(); return Result.fail(-999, "系统异常"); } }
package com.mszlu.blogapi.handler; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.service.LoginService; import com.mszlu.blogapi.utils.UserThreadLocal; import com.mszlu.blogapi.vo.ErrorCode; import com.mszlu.blogapi.vo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component @Slf4j public class LoginInterceptor implements HandlerInterceptor { @Autowired private LoginService loginService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //在执行controller方法(Handler)之前进行执行 /* * 1. 需要判断 请求的接口路径 是否为 HandlerMethod (controller方法) * 2. 判断 token是否为空,如果为空 未登录 * 3. 如果token 不为空,登录验证 loginService checkToken * 4. 如果认证成功 放行即可 */ if (!(handler instanceof HandlerMethod)){ //handler 可能是 RequestResourceHandler springboot 程序 访问静态资源 默认去classpath下的static目录去查询 return true; } String token = request.getHeader("Authorization"); log.info("=================request start==========================="); String requestURI = request.getRequestURI(); log.info("request uri:{}",requestURI); log.info("request method:{}",request.getMethod()); log.info("token:{}", token); log.info("=================request end==========================="); if(StringUtils.isBlank(token)){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } SysUser sysUser = loginService.checkToken(token); if (sysUser == null){ Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录"); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(JSON.toJSONString(result)); return false; } //是登录状态,放行 //我希望在controller中 直接获取用户的信息 怎么获取? UserThreadLocal.put(sysUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //如果不删除 ThreadLocal中用完的信息 会有内存泄漏的风险 UserThreadLocal.remove(); } }
service
package com.mszlu.blogapi.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.mszlu.blogapi.dao.dos.Archives; import com.mszlu.blogapi.dao.mapper.ArticleBodyMapper; import com.mszlu.blogapi.dao.mapper.ArticleMapper; import com.mszlu.blogapi.dao.mapper.ArticleTagMapper; import com.mszlu.blogapi.dao.pojo.Article; import com.mszlu.blogapi.dao.pojo.ArticleBody; import com.mszlu.blogapi.dao.pojo.ArticleTag; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.service.*; import com.mszlu.blogapi.utils.UserThreadLocal; import com.mszlu.blogapi.vo.*; import com.mszlu.blogapi.vo.params.ArticleParam; import com.mszlu.blogapi.vo.params.PageParams; import org.joda.time.DateTime; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @Service public class ArticleServiceImpl implements ArticleService { @Autowired(required = false) private ArticleMapper articleMapper; @Autowired private TagService tagService; @Autowired private SysUserService sysUserService; @Autowired(required = false) private ArticleTagMapper articleTagMapper; @Override public Result listArticle(PageParams pageParams) { /* * 1、分页查询article数据库表 */ Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize()); LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); //查询文章的参数 加上分类id,判断不为空 加上分类条件 if (pageParams.getCategoryId()!=null) { //and category_id=#{categoryId} queryWrapper.eq(Article::getCategoryId,pageParams.getCategoryId()); } //是否置顶进行排序, //时间倒序进行排列相当于order by create_data desc queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate); Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper); List<Article> records = articlePage.getRecords(); // 要返回我们定义的vo数据,就是对应的前端数据,不应该只返回现在的数据需要进一步进行处理 List<ArticleVo> articleVoList =copyList(records,true,true); return Result.success(articleVoList); } @Override public Result hotArticle(int limit) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Article::getViewCounts); queryWrapper.select(Article::getId, Article::getTitle); queryWrapper.last("limit " + limit); //select id,title from article order by view_counts desc limit 5 List<Article> articles = articleMapper.selectList(queryWrapper); return Result.success(copyList(articles, false, false)); } @Override public Result newArticle(int limit) { LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Article::getCreateDate); queryWrapper.select(Article::getId, Article::getTitle); queryWrapper.last("limit " + limit); //select id,title from article order by create_date desc limit 5 List<Article> articles = articleMapper.selectList(queryWrapper); return Result.success(copyList(articles, false, false)); } @Override public Result listArchives() { List<Archives> archivesList = articleMapper.listArchives(); return Result.success(archivesList); } @Autowired private ThreadService threadService; @Override public Result findArticleById(Long articleId) { /* * 1. 根据id查询 文章信息 * 2. 根据bodyId和categoryId 去做关联查询 */ Article article = this.articleMapper.selectById(articleId); ArticleVo articleVo = copy(article, true, true,true,true); //查看完文章了,新增阅读数,有没有问题呢? //查看完文章之后,本应该直接返回数据了,这时候做了一个更新操作 //更新时加写锁,阻塞其他的读操作,性能就会比较低(没办法解决,增加阅读数必然要加锁) //更新增加了此次接口的耗时(考虑减少耗时)如果一旦更新出问题,不能影响查看操作 //想到了一个技术 线程池 //可以把更新操作扔到 线程池中去执行,和主线程就不相关了 threadService.updateArticleViewCount(articleMapper,article); return Result.success(articleVo); } @Override @Transactional public Result publish(ArticleParam articleParam) { //想要拿到数据必须将接口加入登录拦截器 SysUser sysUser = UserThreadLocal.get(); /* * 1. 发布文章 目的 构建Article对象 * 2. 作者id 当前的登录用户 * 3. 标签 要将标签加入到 关联列表当中 * 4. body 内容存储 article bodyId */ Article article = new Article(); article.setAuthorId(sysUser.getId()); article.setWeight(Article.Article_Common); article.setViewCounts(0); article.setTitle(articleParam.getTitle()); article.setSummary(articleParam.getSummary()); article.setCommentCounts(0); article.setCreateDate(System.currentTimeMillis()); article.setCategoryId(articleParam.getCategory().getId()); article.setBodyId(-1L); //插入之后 会生成一个文章id(因为新建的文章没有文章id所以要insert一下 this.articleMapper.insert(article); //tags List<TagVo> tags = articleParam.getTags(); if (tags != null) { for (TagVo tag : tags) { ArticleTag articleTag = new ArticleTag(); articleTag.setTagId(tag.getId()); articleTag.setArticleId(article.getId()); this.articleTagMapper.insert(articleTag); } } //body ArticleBody articleBody = new ArticleBody(); articleBody.setArticleId(article.getId()); articleBody.setContent(articleParam.getBody().getContent()); articleBody.setContentHtml(articleParam.getBody().getContentHtml()); articleBodyMapper.insert(articleBody); article.setBodyId(articleBody.getId()); articleMapper.updateById(article); ArticleVo articleVo = new ArticleVo(); articleVo.setId(article.getId()); return Result.success(articleVo); } private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor) { List<ArticleVo> articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor,false,false)); } return articleVoList; } private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor,boolean isBody) { List<ArticleVo> articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor,isBody,false)); } return articleVoList; } private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor,boolean isBody,boolean isCategory) { List<ArticleVo> articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor,isBody,isCategory)); } return articleVoList; } @Autowired private CategoryService categoryService; private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody,boolean isCategory){ ArticleVo articleVo = new ArticleVo(); articleVo.setId(Long.valueOf(String.valueOf(article.getId()))); BeanUtils.copyProperties(article,articleVo); articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm")); if (isTag){ Long articleId = article.getId(); articleVo.setTags(tagService.findTagsByArticleId(articleId)); } if (isAuthor){ Long authorId = article.getAuthorId(); articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname()); } if (isBody){ Long bodyId = article.getBodyId(); articleVo.setBody(findArticleBodyById(bodyId)); } if (isCategory){ Long categoryId = article.getCategoryId(); articleVo.setCategory(categoryService.findCategoryById(categoryId)); } return articleVo; } private CategoryVo findCategory(Long categoryId) { return categoryService.findCategoryById(categoryId); } @Autowired(required = false) private ArticleBodyMapper articleBodyMapper; private ArticleBodyVo findArticleBodyById(Long bodyId) { ArticleBody articleBody = articleBodyMapper.selectById(bodyId); ArticleBodyVo articleBodyVo = new ArticleBodyVo(); articleBodyVo.setContent(articleBody.getContent()); return articleBodyVo; } }
package com.mszlu.blogapi.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blogapi.dao.mapper.CategoryMapper; import com.mszlu.blogapi.dao.pojo.Category; import com.mszlu.blogapi.service.CategoryService; import com.mszlu.blogapi.vo.CategoryVo; import com.mszlu.blogapi.vo.Result; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class CategoryServiceImpl implements CategoryService { @Autowired(required = false) private CategoryMapper categoryMapper; @Override public CategoryVo findCategoryById(Long categoryId){ Category category = categoryMapper.selectById(categoryId); CategoryVo categoryVo = new CategoryVo(); BeanUtils.copyProperties(category,categoryVo); return categoryVo; } @Override public Result findAll() { LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.select(Category::getId,Category::getCategoryName); List<Category> categories = categoryMapper.selectList(queryWrapper); //页面交互的对象 return Result.success(copyList(categories)); } @Override public Result findAllDetail() { List<Category> categories = categoryMapper.selectList(new LambdaQueryWrapper<>()); //页面交互的对象 return Result.success(copyList(categories)); } @Override public Result categoriesDetailById(Long id) { Category category = categoryMapper.selectById(id); //转换为CategoryVo CategoryVo categoryVo = copy(category); return Result.success(categoryVo); } public CategoryVo copy(Category category){ CategoryVo categoryVo = new CategoryVo(); BeanUtils.copyProperties(category,categoryVo); return categoryVo; } public List<CategoryVo> copyList(List<Category> categoryList){ List<CategoryVo> categoryVoList = new ArrayList<>(); for (Category category : categoryList) { categoryVoList.add(copy(category)); } return categoryVoList; } }
package com.mszlu.blogapi.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blogapi.dao.mapper.CommentMapper; import com.mszlu.blogapi.dao.pojo.Comment; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.service.CommentsService; import com.mszlu.blogapi.service.SysUserService; import com.mszlu.blogapi.utils.UserThreadLocal; import com.mszlu.blogapi.vo.CommentVo; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.UserVo; import com.mszlu.blogapi.vo.params.CommentParam; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class CommentsServiceImpl implements CommentsService { @Autowired(required = false) private CommentMapper commentMapper; @Autowired private SysUserService sysUserService; @Override public Result commentsByArticleId(Long id) { /* * 1. 根据文章id 查询 评论列表 从 comment 表中查询 * 2. 根据作者的id 查询作者的信息 * 3. 判断 如果 level = 1 要去查询它有没有子评论 * 4. 如果有 根据评论id 进行查询 (parent_id) */ LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Comment::getArticleId,id); queryWrapper.eq(Comment::getLevel,1); List<Comment> comments = commentMapper.selectList(queryWrapper); List<CommentVo> commentVoList = copyList(comments); return Result.success(commentVoList); } @Override public 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); } 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); //作者信息 Long authorId = comment.getAuthorId(); UserVo userVo = this.sysUserService.findUserVoById(authorId); commentVo.setAuthor(userVo); //子评论 Integer level = comment.getLevel(); if (1 == level){ Long id = comment.getId(); List<CommentVo> commentVoList = findCommentsByParentId(id); commentVo.setChildrens(commentVoList); } //to User 给谁评论 if (level > 1){ Long toUid = comment.getToUid(); UserVo toUserVo = this.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); return copyList(commentMapper.selectList(queryWrapper)); } }
package com.mszlu.blogapi.service.impl; import com.alibaba.fastjson.JSON; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.service.LoginService; import com.mszlu.blogapi.service.SysUserService; import com.mszlu.blogapi.utils.JWTUtils; import com.mszlu.blogapi.vo.ErrorCode; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.LoginParam; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Map; import java.util.concurrent.TimeUnit; @Service @Transactional public class LoginServiceImpl implements LoginService { private static final String slat = "mszlu!@#"; @Autowired private SysUserService sysUserService; @Autowired private RedisTemplate<String, String> redisTemplate; @Override public Result login(LoginParam loginParam) { /* * 1. 检查参数是否合法 * 2. 根据用户名和密码去user表中查询 是否存在 * 3. 如果不存在 登录失败 * 4. 如果存在 ,使用jwt 生成token 返回给前端 * 5. token放入redis当中,redis token:user信息 设置过期时间(相比来说session会给服务器产生压力,这么做也是为了实现jwt的续签) * (登录认证的时候 先认证token字符串是否合法,去redis认证是否存在) */ String account = loginParam.getAccount(); String password = loginParam.getPassword(); if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){ return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); } String pwd = DigestUtils.md5Hex(password + slat); SysUser sysUser = sysUserService.findUser(account,pwd); if (sysUser == null){ return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg()); } //登录成功,使用JWT生成token,返回token和redis中 String token = JWTUtils.createToken(sysUser.getId()); redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); return Result.success(token); } @Override public SysUser checkToken(String token) { if(StringUtils.isBlank(token)){ return null; } Map<String, Object> stringObjectMap = JWTUtils.checkToken(token); if(stringObjectMap ==null){ return null; } String userJson = redisTemplate.opsForValue().get("TOKEN_"+token); if (StringUtils.isBlank(userJson)) { return null; } SysUser sysUser = JSON.parseObject(userJson, SysUser.class); return sysUser; } @Override public Result logout(String token) { redisTemplate.delete("TOKEN_"+token); return Result.success(null); } @Override public Result register(LoginParam loginParam) { /* * 1. 判断参数 是否合法 * 2. 判断账户是否存在,存在 返回账户已经被注册 * 3. 不存在,注册用户 * 4. 生成token * 5. 存入redis 并返回 * 6. 注意 加上事务,一旦中间的任何过程出现问题,注册的用户 需要回滚 */ String account = loginParam.getAccount(); String password = loginParam.getPassword(); String nickname = loginParam.getNickname(); if (StringUtils.isBlank(account) || StringUtils.isBlank(password) || StringUtils.isBlank(nickname) ){ return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg()); } SysUser sysUser = this.sysUserService.findUserByAccount(account); if (sysUser != null){ return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),ErrorCode.ACCOUNT_EXIST.getMsg()); } sysUser = new SysUser(); sysUser.setNickname(nickname); sysUser.setAccount(account); sysUser.setPassword(DigestUtils.md5Hex(password+slat)); sysUser.setCreateDate(System.currentTimeMillis()); sysUser.setLastLogin(System.currentTimeMillis()); sysUser.setAvatar("/static/img/logo.b3a48c0.png"); sysUser.setAdmin(1); //1 为true sysUser.setDeleted(0); // 0 为false sysUser.setSalt(""); sysUser.setStatus(""); sysUser.setEmail(""); this.sysUserService.save(sysUser); String token = JWTUtils.createToken(sysUser.getId()); redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); return Result.success(token); } }
package com.mszlu.blogapi.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blogapi.dao.mapper.SysUserMapper; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.service.LoginService; import com.mszlu.blogapi.service.SysUserService; import com.mszlu.blogapi.vo.ErrorCode; import com.mszlu.blogapi.vo.LoginUserVo; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.UserVo; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @Service public class SysUserServiceImpl implements SysUserService { @Autowired(required = false) private SysUserMapper sysUserMapper; @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private LoginService loginService; @Override public UserVo findUserVoById(Long id) { SysUser sysUser = sysUserMapper.selectById(id); if (sysUser == null){ sysUser = new SysUser(); sysUser.setId(1L); sysUser.setAvatar("/static/img/logo.b3a48c0.png"); sysUser.setNickname("码神之路"); } UserVo userVo = new UserVo(); //BeanUtils.copyProperties(sysUser,userVo); userVo.setAvatar(sysUser.getAvatar()); userVo.setNickname(sysUser.getNickname()); userVo.setId(sysUser.getId()); return userVo; } @Override public SysUser findUserById(Long id) { SysUser sysUser = sysUserMapper.selectById(id); if (sysUser == null) { sysUser = new SysUser(); sysUser.setNickname("码神之路"); } return sysUserMapper.selectById(id); } @Override public SysUser findUser(String account, String password) { LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getAccount,account); queryWrapper.eq(SysUser::getPassword,password); queryWrapper.select(SysUser::getAccount,SysUser::getId,SysUser::getAvatar,SysUser::getNickname); queryWrapper.last("limit 1"); return sysUserMapper.selectOne(queryWrapper); } @Override public Result findUserByToken(String token) { /* * 1、token合法性校验 * 是否为空 ,解析是否成功,redis是否存在 * 2、如果校验失败,返回错误 * 3、如果成功,返回对应结果 LoginUserVo */ SysUser sysUser = loginService.checkToken(token); if(sysUser == null){ return Result.fail(ErrorCode.TOKEN_ERROR.getCode(),ErrorCode.TOKEN_ERROR.getMsg()); } LoginUserVo loginUserVo = new LoginUserVo(); loginUserVo.setId(sysUser.getId()); loginUserVo.setNickname(sysUser.getNickname()); loginUserVo.setAvatar(sysUser.getAvatar()); loginUserVo.setAccount(sysUser.getAccount()); return Result.success(loginUserVo); } @Override public SysUser findUserByAccount(String account) { LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getAccount,account); queryWrapper.last("limit 1"); return this.sysUserMapper.selectOne(queryWrapper); } @Override public void save(SysUser sysUser) { //保存用户这 id会自动生成 //这个地方 默认生成的id是 分布式id 雪花算法 //mybatis-plus this.sysUserMapper.insert(sysUser); } }
package com.mszlu.blogapi.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mszlu.blogapi.dao.mapper.TagMapper; import com.mszlu.blogapi.dao.pojo.Tag; import com.mszlu.blogapi.service.TagService; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.TagVo; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; import java.util.List; @Service public class TagServiceImpl implements TagService { @Autowired(required = false) private TagMapper tagMapper; public TagVo copy(Tag tag){ TagVo tagVo = new TagVo(); BeanUtils.copyProperties(tag,tagVo); return tagVo; } public List<TagVo> copyList(List<Tag> tagList){ List<TagVo> tagVoList = new ArrayList<>(); for (Tag tag : tagList) { tagVoList.add(copy(tag)); } return tagVoList; } @Override public List<TagVo> findTagsByArticleId(Long articleId) { //mybatis plus 无法进行多表查询 List<Tag> tags = tagMapper.findTagsByArticleId(articleId); return copyList(tags); } @Override public Result hots(int limit) { /* * 1. 标签所拥有的文章数量最多 最热标签 * 2. 查询 根据tag_id分组 计数,从大到小 排列 取前limit个 * */ List<Long> tagIds = tagMapper.findHotsTagIds(limit); if (CollectionUtils.isEmpty(tagIds)) { return Result.success(Collections.emptyList()); } //需求的是 tagId 和 tagName Tag对象 //select * from tag where id in (1,2,3,4) List<Tag> tagList = tagMapper.findTagsByTagId(tagIds); return Result.success(tagList); } @Override public 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)); } @Override public Result findAllDetail() { LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>(); List<Tag> tags = this.tagMapper.selectList(queryWrapper); return Result.success(copyList(tags)); } }
package com.mszlu.blogapi.service; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.ArticleParam; import com.mszlu.blogapi.vo.params.PageParams; public interface ArticleService { /* * 分页查询 文章列表 * @param pageParams * @return*/ Result listArticle(PageParams pageParams); /* * 最热文章 * @param limit * @return*/ Result hotArticle(int limit); /* * 最新文章 * @param limit * @return*/ Result newArticle(int limit); /* * 文章归档 * @param limit * @return*/ Result listArchives(); /* * 查看文章详情 * @param articleId * @return */ Result findArticleById(Long articleId); /* * 文章发布服务 * @param articleParam * @return */ Result publish(ArticleParam articleParam); }
package com.mszlu.blogapi.service; import com.mszlu.blogapi.vo.CategoryVo; import com.mszlu.blogapi.vo.Result; public interface CategoryService { CategoryVo findCategoryById(Long categoryId); Result findAll(); Result findAllDetail(); Result categoriesDetailById(Long id); }
package com.mszlu.blogapi.service; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.CommentParam; public interface CommentsService { /* * 根据文章id查询所有的评论列表 * @param id * @return */ Result commentsByArticleId(Long id); Result comment(CommentParam commentParam); }
package com.mszlu.blogapi.service; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.params.LoginParam; import org.springframework.transaction.annotation.Transactional; public interface LoginService { /* * 登录功能 * @param loginParam * @return*/ Result login(LoginParam loginParam); SysUser checkToken(String token); /* * 退出登陆 * @param token * @return */ Result logout(String token); /* * 注册 * @param loginParam * @return */ Result register(LoginParam loginParam); }
package com.mszlu.blogapi.service; import com.mszlu.blogapi.dao.pojo.SysUser; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.UserVo; public interface SysUserService { UserVo findUserVoById(Long id); SysUser findUserById(Long id); SysUser findUser(String account, String pwd); /* * 根据token查询用户信息 * @param token * @return */ Result findUserByToken(String token); /* * 根据账户查找用户 * @param account * @return */ SysUser findUserByAccount(String account); /* * 保存用户 * @param sysUser */ void save(SysUser sysUser); }
package com.mszlu.blogapi.service; import com.mszlu.blogapi.vo.Result; import com.mszlu.blogapi.vo.TagVo; import java.util.List; public interface TagService { List<TagVo> findTagsByArticleId(Long articleId); Result hots(int limit); /* * 查询所有文章标签 * @return */ Result findAll(); Result findAllDetail(); }
package com.mszlu.blogapi.service; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.mszlu.blogapi.dao.mapper.ArticleMapper; import com.mszlu.blogapi.dao.pojo.Article; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component public class ThreadService { //期望此操作在线程池执行不会影响原有主线程 @Async("taskExecutor") public void updateArticleViewCount(ArticleMapper articleMapper, Article article) { int viewCounts = article.getViewCounts(); Article articleUpdate = new Article(); articleUpdate.setViewCounts(viewCounts + 1); LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(Article::getId ,article.getId()); //设置一个为了在多线程的环境下线程安全 updateWrapper.eq(Article ::getViewCounts,viewCounts); // update article set view_count=100 where view_count=99 and id =111 //实体类加更新条件 articleMapper.update(articleUpdate,updateWrapper); try { Thread.sleep(5000); System.out.println("更新完成了"); } catch (InterruptedException e) { e.printStackTrace(); } } }
utils
package com.mszlu.blogapi.utils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /* * HttpServletRequest */ public class HttpContextUtils { public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } }
package com.mszlu.blogapi.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字符串,则为真实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_FORWARDED_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地址 * * @return */ public static String getIpAddr() { HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); return getIpAddr(request); } }
package com.mszlu.blogapi.utils; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JWTUtils { private static final String jwtToken = "123456Mszlu!@#$$"; public static String createToken(Long userId){ Map<String,Object> claims = new HashMap<>(); claims.put("userId",userId); JwtBuilder jwtBuilder = Jwts.builder() .signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken .setClaims(claims) // body数据,要唯一,自行设置 .setIssuedAt(new Date()) // 设置签发时间 .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// 一天的有效时间 String token = jwtBuilder.compact(); return token; } public static Map<String, Object> checkToken(String token){ try { Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token); return (Map<String, Object>) parse.getBody(); }catch (Exception e){ e.printStackTrace(); } return null; } }
package com.mszlu.blogapi.utils; import com.alibaba.fastjson.JSON; import com.qiniu.http.Response; import com.qiniu.storage.Configuration; import com.qiniu.storage.Region; import com.qiniu.storage.UploadManager; import com.qiniu.storage.model.DefaultPutRet; import com.qiniu.util.Auth; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; @Component public class QiniuUtils { public static final String url = "https://static.mszlu.com/"; //修改以下两个值放到proprietarties中,在密钥管理中获取 @Value("${qiniu.accessKey}") private String accessKey; @Value("${qiniu.accessSecretKey}") private String accessSecretKey; public boolean upload(MultipartFile file,String fileName){ //构造一个带指定 Region 对象的配置类 Configuration cfg = new Configuration(Region.huabei()); //...其他参数参考类注释 UploadManager uploadManager = new UploadManager(cfg); //...生成上传凭证,然后准备上传,修改上传名称为自己创立空间的空间名称(是你自己的) String bucket = "mszlu"; //默认不指定key的情况下,以文件内容的hash值作为文件名 try { byte[] uploadBytes = file.getBytes(); Auth auth = Auth.create(accessKey, accessSecretKey); String upToken = auth.uploadToken(bucket); Response response = uploadManager.put(uploadBytes, fileName, upToken); //解析上传成功的结果 DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class); return true; } catch (Exception ex) { ex.printStackTrace(); } return false; } }
package com.mszlu.blogapi.utils; import com.mszlu.blogapi.dao.pojo.SysUser; public class UserThreadLocal { private UserThreadLocal(){} //线程变量隔离 private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>(); public static void put(SysUser sysUser){ LOCAL.set(sysUser); } public static SysUser get(){ return LOCAL.get(); } public static void remove(){ LOCAL.remove(); } }
vo
package com.mszlu.blogapi.vo.params; import lombok.Data; @Data public class ArticleBodyParam { private String content; private String contentHtml; }
package com.mszlu.blogapi.vo.params; import com.mszlu.blogapi.vo.CategoryVo; import com.mszlu.blogapi.vo.TagVo; import lombok.Data; import java.util.List; @Data public class ArticleParam { private Long id; private ArticleBodyParam body; private CategoryVo category; private String summary; private List<TagVo> tags; private String title; }
package com.mszlu.blogapi.vo.params; import lombok.Data; @Data public class CommentParam { private Long articleId; private String content; private Long parent; private Long toUserId; }
package com.mszlu.blogapi.vo.params; import lombok.Data; @Data public class LoginParam { private String account; private String password; private String nickname; }
package com.mszlu.blogapi.vo.params; import lombok.Data; @Data public class PageParams { private int page = 1; private int pageSize = 10; private Long categoryId; private Long tagId; }
package com.mszlu.blogapi.vo; import lombok.Data; @Data public class ArticleBodyVo { private String content; }
package com.mszlu.blogapi.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 ArticleVo { @JsonSerialize(using = ToStringSerializer.class) private Long id; private String title; private String summary; private Integer commentCounts; private Integer viewCounts; private Integer weight; /** * 创建时间 */ private String createDate; private String author; private List<TagVo> tags; private ArticleBodyVo body; private CategoryVo category; }
package com.mszlu.blogapi.vo; import lombok.Data; @Data public class CategoryVo { private Long id; private String avatar; private String categoryName; private String description; }
package com.mszlu.blogapi.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.mszlu.blogapi.vo; public enum ErrorCode { PARAMS_ERROR(10001,"参数有误"), ACCOUNT_PWD_NOT_EXIST(10002,"用户名或密码不存在"), TOKEN_ERROR(10003,"token不合法"), ACCOUNT_EXIST(10004,"账号已存在"), NO_PERMISSION(70001,"无访问权限"), SESSION_TIME_OUT(90001,"会话超时"), NO_LOGIN(90002,"未登录"),; private int code; private String msg; ErrorCode(int code, String msg){ this.code = code; this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
package com.mszlu.blogapi.vo; import lombok.Data; @Data public class LoginUserVo { private Long id; private String account; private String nickname; private String avatar; }
package com.mszlu.blogapi.vo; import lombok.AllArgsConstructor; import lombok.Data; import javax.xml.soap.SAAJResult; @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); } }
package com.mszlu.blogapi.vo; import lombok.Data; @Data public class TagVo { private Long id; private String tagName; private String avatar; }
package com.mszlu.blogapi.vo; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.Data; @Data public class UserVo { private String nickname; private String avatar; @JsonSerialize(using = ToStringSerializer.class) private Long id; }
启动类
package com.mszlu.blogapi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BlogApiApplication { public static void main(String[] args) { SpringApplication.run(BlogApiApplication.class, args); } }
遇到的问题和bug
1.完成评论功能的时候,出现sysUser的nullpointexception空指针错误
解决思路:查找ThreadLocal,看是否缺少赋值语句,若缺少,添加上即可
UserThreadLocal.put(sysUser);
2.评论功能完成后,仍然出现“评论失败”的现象
解决思路:分布式id 比较长,传到前端会有精度损失,必须转为string类型进行传输。在id上加上特定注解
@JsonSerialize(using = ToStringSerializer.class)
明后两天学习计划
观看学习视频31-35集
学习进度和反思
- 学习视频看到30集
- 基本适应了教学视频的节奏,不会像刚上手那么艰难,解决报错的能力有提升。不过要反思的是适应之后反而有些懈怠了,学习的时间有所减少。还是得多投入时间学习,脚踏实地一步步走,不能浮躁。