项目实战-前后端分离博客系统
1.项目介绍
- 该项目是一个前后端分离的前台博客展示系统, 主要实现了热门文章列表查询,分类列表查询,分页查询文章列表,文章详情查询,友链查询,用户登录、退出,文章评论列表展示,发送文章评论,友链评论,个人信息查询与更新,头像上传,AOP 日志,更新博客浏览量次数等功能。
- 主流技术栈(SpringBoot,MybatisPlus,SpringSecurity,EasyExcel,Swagger2,Redis,Echarts,Vue,ElementUI….)
2.创建工程
我们有前台和后台两套系统。两套系统的前端工程都已经提供好了。所以我们只需要写两套系统的后端。
但是大家思考下,实际上两套后端系统的很多内容是可能重复的。这里如果我们只是单纯的创建两个后端工程。那么就会有大量的重复代码,并且需要修改的时候也需要修改两次。这就是代码复用性不高。
所以我们需要创建多模块项目,两套系统可能都会用到的代码可以写到一个公共模块中,让前台系统和后台系统分别取依赖公共模块。
① 创建父模块
1 |
|
②创建公共子模块 sangeng-framework
1 |
|
③创建博客后台模块sangeng-admin
1 |
|
④创建博客前台模块sangeng-blog
1 |
|
3.博客前台
3.0 准备工作
3.1 SpringBoot和MybatisPuls整合配置测试
①创建启动类
1 | /** |
②创建application.yml配置文件
1 | server: |
③ SQL语句
SQL脚本:SGBlog\资源\SQL\sg_article.sql
④ 创建实体类,Mapper,Service
注意思考这些文件应该写在哪个模块下?
1 |
|
1 | public interface ArticleMapper extends BaseMapper<Article> { |
1 | public interface ArticleService extends IService<Article> { |
1 |
|
⑤ 创建Controller测试接口
注意思考这些文件应该写在哪个模块下?
1 |
|
我们可以暂时先注释掉sangeng-framework中的SpringSecurity依赖方便测试
3.1 热门文章列表
3.1.0 文章表分析
通过需求去分析需要有哪些字段。
3.1.1 需求
需要查询浏览量最高的前10篇文章的信息。要求展示文章标题和浏览量。把能让用户自己点击跳转到具体的文章详情进行浏览。
注意:不能把草稿展示出来,不能把删除了的文章查询出来。要按照浏览量进行降序排序。
3.1.2 接口设计
见接口文档
3.1.3 基础版本代码实现
①准备工作
统一响应类和响应枚举
1 | package com.sangeng.domain; |
1 | package com.sangeng.enums; |
② 代码实现
1 |
|
1 | public interface ArticleService extends IService<Article> { |
1 |
|
③ 解决跨域问题
1 |
|
3.1.4 使用VO优化
目前我们的响应格式其实是不符合接口文档的标准的,多返回了很多字段。这是因为我们查询出来的结果是Article来封装的,Article中字段比较多。
我们在项目中一般最后还要把VO来接受查询出来的结果。一个接口对应一个VO,这样即使接口响应字段要修改也只要改VO即可。
1 |
|
1 |
|
3.1.5 字面值处理
实际项目中都不允许直接在代码中使用字面值。都需要定义成常量来使用。这种方式有利于提高代码的可维护性。
1 | public class SystemConstants |
1 |
|
3.2 Bean拷贝工具类封装
1 | public class BeanCopyUtils { |
3.2 查询分类列表
3.2.0 分类表分析
通过需求去分析需要有哪些字段。
建表SQL及初始化数据见:SGBlog\资源\SQL\sg_category.sql
3.2.1 需求
页面上需要展示分类列表,用户可以点击具体的分类查看该分类下的文章列表。
注意: ①要求只展示有发布正式文章的分类 ②必须是正常状态的分类
3.2.2 接口设计
见接口文档
3.2.3 EasyCode代码模板
1 | ##导入宏定义 |
1 | ##导入宏定义 |
1 | ##导入宏定义 |
1 | ##导入宏定义 |
3.2.4 代码实现
1 |
|
1 | public interface CategoryService extends IService<Category> { |
1 |
|
3.3 分页查询文章列表
3.3.1 需求
在首页和分类页面都需要查询文章列表。
首页:查询所有的文章
分类页面:查询对应分类下的文章
要求:①只能查询正式发布的文章 ②置顶的文章要显示在最前面
3.3.2 接口设计
见文档
3.3.3 代码实现
MP支持分页配置
1 | /** |
在ArticleController中
1 |
|
在ArticleService中
1 | ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId); |
在ArticleServiceImpl中
1 |
|
PageVo
1 |
|
ArticleListVo
1 |
|
在Article中增加一个字段
1 |
|
3.3.4 FastJson配置
1 | //使用@Bean注入fastJsonHttpMessageConvert |
3.4 文章详情接口
3.4.1 需求
要求在文章列表点击阅读全文时能够跳转到文章详情页面,可以让用户阅读文章正文。
要求:①要在文章详情中展示其分类名
3.4.2 接口设计
请求方式 | 请求路径 |
---|---|
Get | /article/{id} |
响应格式:
1 | { |
3.4.3 代码实现
ArticleController中新增
1 |
|
Service
1 | ResponseResult getArticleDetail(Long id); |
ServiceImpl
1 |
|
3.5 友联查询
3.5.0 友链表分析
通过需求去分析需要有哪些字段。
建表SQL及初始化数据见:SGBlog\资源\SQL\sg_link.sql
3.5.1 需求
在友链页面要查询出所有的审核通过的友链。
3.5.2 接口设计
请求方式 | 请求路径 |
---|---|
Get | /link/getAllLink |
响应格式:
1 | { |
3.5.3 代码实现
Controller
1 |
|
Service
1 | public interface LinkService extends IService<Link> { |
ServiceImpl
1 |
|
SystemConstants
1 | /** |
3.6 登录功能实现
使用我们前台和后台的认证授权统一都使用SpringSecurity安全框架来实现。
3.6.0 需求
需要实现登录功能
有些功能必须登录后才能使用,未登录状态是不能使用的。
3.6.1 接口设计
请求方式 | 请求路径 |
---|---|
POST | /login |
请求体:
1 | { |
响应格式:
1 | { |
3.6.2 表分析
建表SQL及初始化数据见:SGBlog\资源\SQL\sys_user.sql
顺便生成下User和UserMapper后面会用到
3.6.3 思路分析
登录
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService
在这个实现类中去查询数据库
注意配置passwordEncoder为BCryptPasswordEncoder
校验:
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
3.6.4 准备工作
①添加依赖
注意放开Security依赖的注释
1 | <!--redis依赖--> |
②工具类和相关配置类
见 :SGBlog\资源\登录功能所需资源
3.6.5 登录接口代码实现
BlogLoginController
1 |
|
BlogLoginService
1 | public interface BlogLoginService { |
SecurityConfig
1 |
|
BlogLoginServiceImpl
1 |
|
UserDetailServiceImpl
1 |
|
LoginUser
1 |
|
BlogUserLoginVo
1 |
|
UserInfoVo
1 |
|
3.6.6 登录校验过滤器代码实现
思路
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
JwtAuthenticationTokenFilter
1 |
|
SecurityConfig
1 |
|
3.7 认证授权失败处理
目前我们的项目在认证出错或者权限不足的时候响应回来的Json是Security的异常处理结果。但是这个响应的格式肯定是不符合我们项目的接口规范的。所以需要自定义异常处理。
AuthenticationEntryPoint 认证失败处理器
AccessDeniedHandler 授权失败处理器
1 |
|
1 |
|
配置Security异常处理器
1 |
|
3.8 统一异常处理
实际我们在开发过程中可能需要做很多的判断校验,如果出现了非法情况我们是期望响应对应的提示的。但是如果我们每次都自己手动去处理就会非常麻烦。我们可以选择直接抛出异常的方式,然后对异常进行统一处理。把异常中的信息封装成ResponseResult响应给前端。
SystemException
1 | /** |
GlobalExceptionHandler
1 |
|
3.9 退出登录接口
3.9.1 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /logout | 需要token请求头 |
响应格式:
1 | { |
3.9.2 代码实现
要实现的操作:
删除redis中的用户信息
BlogLoginController
1 |
|
BlogLoginService
1 | ResponseResult logout(); |
BlogLoginServiceImpl
1 |
|
SecurityConfig
要关闭默认的退出登录功能。并且要配置我们的退出登录接口需要认证才能访问
1 |
|
3.10 查询评论列表接口
3.10.1 需求
文章详情页面要展示这篇文章下的评论列表。
效果如下:
3.10.2 评论表分析
通过需求去分析需要有哪些字段。
建表SQL及初始化数据见:SGBlog\资源\SQL\sg_comment.sql
顺便生成下对应的代码
3.10.3 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /comment/commentList | 不需要token请求头 |
Query格式请求参数:
articleId:文章id
pageNum: 页码
pageSize: 每页条数
响应格式:
1 | { |
3.10.4 代码实现
3.10.4.1 不考虑子评论
CommentController
1 |
|
CommentService
1 | public interface CommentService extends IService<Comment> { |
CommentServiceImpl
1 |
|
CommentVo
1 |
|
3.10.4.2 查询子评论
CommentVo在之前的基础上增加了 private List
1 |
|
CommentServiceImpl
1 |
|
3.11 发表评论接口
3.11.1 需求
用户登录后可以对文章发表评论,也可以对评论进行回复。
用户登录后也可以在友链页面进行评论。
3.11.2 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /comment | 需要token头 |
请求体:
回复了文章:
1 | {"articleId":1,"type":0,"rootId":-1,"toCommentId":-1,"toCommentUserId":-1,"content":"评论了文章"} |
回复了某条评论:
1 | {"articleId":1,"type":0,"rootId":"3","toCommentId":"3","toCommentUserId":"1","content":"回复了某条评论"} |
如果是友链评论,type应该为1
响应格式:
1 | { |
3.11.3 代码实现
CommentController
1 |
|
CommentService
1 | ResponseResult addComment(Comment comment); |
CommentServiceImpl
1 |
|
SecurityUtils
1 | /** |
配置MP字段自动填充
1 |
|
用注解标识哪些字段在什么情况下需要自动填充
1 | /** |
3.12 友联评论列表
3.12.1 需求
友链页面也需要查询对应的评论列表。
3.12.2 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /comment/linkCommentList | 不需要token请求头 |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
响应格式:
1 | { |
3.12.3 代码实现
CommentController 修改了之前的文章评论列表接口,并且增加了新的友联评论接口
1 |
|
SystemConstants增加了两个常量
1 | /** |
CommentService修改了commentList方法,增加了一个参数commentType
1 | ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize); |
CommentServiceImpl修改commentList方法的代码,必须commentType为0的时候才增加articleId的判断,并且增加了一个评论类型的添加。
1 |
|
3.13 个人信息查询接口
3.13.1 需求
进入个人中心的时候需要能够查看当前用户信息
3.13.2 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /user/userInfo | 需要token请求头 |
不需要参数
响应格式:
1 | { |
3.13.3 代码实现
UserController
1 |
|
UserService增加方法定义
1 | public interface UserService extends IService<User> { |
UserServiceImpl实现userInfo方法
1 |
|
SecurityConfig配置该接口必须认证后才能访问
1 |
|
3.14 头像上传接口
3.14.1 需求
在个人中心点击编辑的时候可以上传头像图片。上传完头像后,可以用于更新个人信息接口。
3.14.2 OSS
3.14.2.1 为什么要使用OSS
因为如果把图片视频等文件上传到自己的应用的Web服务器,在读取图片的时候会占用比较多的资源。影响应用服务器的性能。
所以我们一般使用OSS(Object Storage Service对象存储服务)存储图片或视频。
3.14.2.2 七牛云基本使用测试
秘钥
3.14.2.3 七牛云测试代码编写
①添加依赖
1 | <dependency> |
②复制修改案例代码
application.yml
1 | oss: |
OSSTest.java
1 |
|
3.14.2 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /upload | 需要token |
参数:
img,值为要上传的文件
请求头:
Content-Type :multipart/form-data;
响应格式:
1 | { |
3.14.3 代码实现
1 |
|
1 | public interface UploadService { |
1 |
|
PathUtils
1 | /** |
3.15 更新个人信息接口
3.15.1 需求
在编辑完个人资料后点击保存会对个人资料进行更新。
3.15.2 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
PUT | /user/userInfo | 需要token请求头 |
参数
请求体中json格式数据:
1 | { |
响应格式:
1 | { |
3.15.3 代码实现
UserController
1 |
|
UserService
1 | ResponseResult updateUserInfo(User user); |
UserServiceImpl
1 |
|
3.16 用户注册
3.16.1 需求
要求用户能够在注册界面完成用户的注册。要求用户名,昵称,邮箱不能和数据库中原有的数据重复。如果某项重复了注册失败并且要有对应的提示。并且要求用户名,密码,昵称,邮箱都不能为空。
注意:密码必须密文存储到数据库中。
3.16.2 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /user/register | 不需要token请求头 |
参数
请求体中json格式数据:
1 | { |
响应格式:
1 | { |
3.16.3 代码实现
UserController
1 |
|
UserService
1 | ResponseResult register(User user); |
UserServiceImpl
1 |
|
1 | public enum AppHttpCodeEnum { |
3.17 AOP实现日志记录
3.17.1 需求
需要通过日志记录接口调用信息。便于后期调试排查。并且可能有很多接口都需要进行日志的记录。
接口被调用时日志打印格式如下:
3.17.2 思路分析
相当于是对原有的功能进行增强。并且是批量的增强,这个时候就非常适合用AOP来进行实现。
3.17.3 代码实现
日志打印格式
1 | log.info("=======Start======="); |
3.18 更新浏览次数
3.18.1 需求
在用户浏览博文时要实现对应博客浏览量的增加。
3.18.2 思路分析
我们只需要在每次用户浏览博客时更新对应的浏览数即可。
但是如果直接操作博客表的浏览量的话,在并发量大的情况下会出现什么问题呢?
如何去优化呢?
①在应用启动时把博客的浏览量存储到redis中
②更新浏览量时去更新redis中的数据
③每隔10分钟把Redis中的浏览量更新到数据库中
④读取文章浏览量时从redis读取
3.18.3 铺垫知识
3.18.3.1 CommandLineRunner实现项目启动时预处理
如果希望在SpringBoot应用启动时进行一些初始化操作可以选择使用CommandLineRunner来进行处理。
我们只需要实现CommandLineRunner接口,并且把对应的bean注入容器。把相关初始化的代码重新到需要重新的方法中。
这样就会在应用启动的时候执行对应的代码。
1 |
|
3.18.3.2 定时任务
定时任务的实现方式有很多,比如XXL-Job等。但是其实核心功能和概念都是类似的,很多情况下只是调用的API不同而已。
这里就先用SpringBoot为我们提供的定时任务的API来实现一个简单的定时任务,让大家先对定时任务里面的一些核心概念有个大致的了解。
实现步骤
① 使用@EnableScheduling注解开启定时任务功能
我们可以在配置类上加上@EnableScheduling
1 |
|
② 确定定时任务执行代码,并配置任务执行时间
使用@Scheduled注解标识需要定时执行的代码。注解的cron属性相当于是任务的执行时间。目前可以使用 0/5 * * * * ? 进行测试,代表从0秒开始,每隔5秒执行一次。
注意:对应的bean要注入容器,否则不会生效。
1 |
|
3.18.3.2.1 cron 表达式语法
cron表达式是用来设置定时任务执行时间的表达式。
很多情况下我们可以用 : 在线Cron表达式生成器 来帮助我们理解cron表达式和书写cron表达式。
但是我们还是有需要学习对应的Cron语法的,这样可以更有利于我们书写Cron表达式。
如上我们用到的 0/5 * * * * ? *,cron表达式由七部分组成,中间由空格分隔,这七部分从左往右依次是:
秒(059),分钟(059),小时(0~23),日期(1-月最后一天),月份(1-12),星期几(1-7,1表示星期日),年份(一般该项不设置,直接忽略掉,即可为空值)
通用特殊字符:, - * / (可以在任意部分使用)
星号表示任意值,例如:
1 | * * * * * ? |
表示 “ 每年每月每天每时每分每秒 ” 。
,
可以用来定义列表,例如 :
1 | 1,2,3 * * * * ? |
表示 “ 每年每月每天每时每分的每个第1秒,第2秒,第3秒 ” 。
定义范围,例如:
1 | 1-3 * * * * ? |
表示 “ 每年每月每天每时每分的第1秒至第3秒 ”。
/
每隔多少,例如
1 | 5/10 * * * * ? |
表示 “ 每年每月每天每时每分,从第5秒开始,每10秒一次 ” 。即 “ / ” 的左侧是开始值,右侧是间隔。如果是从 “ 0 ” 开始的话,也可以简写成 “ /10 ”
日期部分还可允许特殊字符: ? L W
星期部分还可允许的特殊字符: ? L #
?
只可用在日期和星期部分。表示没有具体的值,使用?要注意冲突。日期和星期两个部分如果其中一个部分设置了值,则另一个必须设置为 “ ? ”。
例如:
1 | 0\* * * 2 * ? |
同时使用?和同时不使用?都是不对的
例如下面写法就是错的
1 | * * * 2 * 2 |
W
只能用在日期中,表示当月中最接近某天的工作日
1 | 0 0 0 31W * ? |
表示最接近31号的工作日,如果31号是星期六,则表示30号,即星期五,如果31号是星期天,则表示29号,即星期五。如果31号是星期三,则表示31号本身,即星期三。
L
表示最后(Last),只能用在日期和星期中
在日期中表示每月最后一天,在一月份中表示31号,在六月份中表示30号
也可以表示每月倒是第N天。例如: L-2表示每个月的倒数第2天
0 0 0 LW * ?
LW可以连起来用,表示每月最后一个工作日,即每月最后一个星期五
在星期中表示7即星期六
1 | 0 0 0 ? * L |
只能用在星期中,表示第几个星期几
1 | 0 0 0 ? * 6#3 |
3.18.4 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
PUT | /article/updateViewCount/{id} | 不需要token请求头 |
参数
请求路径中携带文章id
响应格式:
1 | { |
3.18.5 代码实现
①在应用启动时把博客的浏览量存储到redis中
实现CommandLineRunner接口,在应用启动时初始化缓存。
1 |
|
②更新浏览量时去更新redsi中的数据
RedisCache增加方法
1 | public void incrementCacheMapValue(String key,String hKey,long v){ |
ArticleController中增加方法更新阅读数
1 |
|
ArticleService中增加方法
1 | ResponseResult updateViewCount(Long id); |
ArticleServiceImpl中实现方法
1 |
|
③定时任务每隔10分钟把Redis中的浏览量更新到数据库中
Article中增加构造方法
1 | public Article(Long id, long viewCount) { |
1 |
|
④读取文章浏览量时从redis读取
1 |
|
4. Swagger2使用
4.1 简介
Swagger 是一套基于 OpenAPI 规范构建的开源工具,可以帮助我们设计、构建、记录以及使用 Rest API。
4.2 为什么使用Swagger
当下很多公司都采取前后端分离的开发模式,前端和后端的工作由不同的工程师完成。在这种开发模式下,维持一份及时更新且完整的 Rest API 文档将会极大的提高我们的工作效率。传统意义上的文档都是后端开发人员手动编写的,相信大家也都知道这种方式很难保证文档的及时性,这种文档久而久之也就会失去其参考意义,反而还会加大我们的沟通成本。而 Swagger 给我们提供了一个全新的维护 API 文档的方式,下面我们就来了解一下它的优点:
1.代码变,文档变。只需要少量的注解,Swagger 就可以根据代码自动生成 API 文档,很好的保证了文档的时效性。
2.跨语言性,支持 40 多种语言。
3.Swagger UI 呈现出来的是一份可交互式的 API 文档,我们可以直接在文档页面尝试 API 的调用,省去了准备复杂的调用参数的过程。
4.3 快速入门
4.3.1 引入依赖
1 | <dependency> |
4.3.2 启用Swagger2
在启动类上或者配置类加 @EnableSwagger2 注解
1 |
|
4.3.3 测试
访问:http://localhost:7777/swagger-ui.html 注意其中localhost和7777要调整成实际项目的域名和端口号。
4.4 具体配置
4.4.1 Controller配置
4.4.1 @Api 注解
属性介绍:
tags 设置标签
description 设置描述信息
1 |
|
4.4.2 接口配置
4.4.2.1 接口描述配置@ApiOperation
1 |
|
4.4.2.2 接口参数描述
@ApiImplicitParam 用于描述接口的参数,但是一个接口可能有多个参数,所以一般与 @ApiImplicitParams 组合使用。
1 |
|
4.4.3 实体类配置
4.4.3.1 实体的描述配置@ApiModel
@ApiModel用于描述实体类。
1 |
|
4.4.3.2 实体的属性的描述配置@ApiModelProperty
@ApiModelProperty用于描述实体的属性
1 |
|
4.4.4 文档信息配置
1 |
|
5. 博客后台
5.0 准备工作
前端工程启动
npm install
npm run dev
①创建启动类
1 | /** |
②创建application.yml配置文件
1 | server: |
③ SQL语句
SQL脚本:SGBlog\资源\SQL\sg_tag.sql
④ 创建实体类,Mapper,Service
注意思考这些文件应该写在哪个模块下?
Tag
1 |
|
TagMapper
1 | /** |
TagService
1 | /** |
TagServiceImpl
1 | /** |
⑤ 创建Controller测试接口
注意思考这些文件应该写在哪个模块下?
TagController /content/tag
1 |
|
⑥添加security相关类
1 |
|
1 |
|
5.1 后台登录
后台的认证授权也使用SpringSecurity安全框架来实现。
5.1.0 需求
需要实现登录功能
后台所有功能都必须登录才能使用。
5.1.1 接口设计
请求方式 | 请求路径 |
---|---|
POST | /user/login |
请求体:
1 | { |
响应格式:
1 | { |
5.1.2 思路分析
登录
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService
在这个实现类中去查询数据库
注意配置passwordEncoder为BCryptPasswordEncoder
校验:
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
5.1.3 准备工作
①添加依赖
前面已经添加过相关依赖,不需要做什么处理
1 | <!--redis依赖--> |
5.1.4 登录接口代码实现
LoginController
复制一份BlogLoginController ,命名为LoginController,其中注入 LoginService
请求地址修改为/user/login即可
1 |
|
LoginService
复制一份BlogLoginService命名为LoginService即可
1 | public interface LoginService { |
SecurityConfig
之前已经复制过了
SystemLoginServiceImpl
复制一份,LoginServiceImpl,命名为SystemLoginServiceImpl 实现 LoginService
login方法中存redis的key的前缀修改为login
返回的数据中只要返回token
1 |
|
UserDetailServiceImpl
复用原来的即可
LoginUser
复用原来的即可
5.2 后台权限控制及动态路由
需求
后台系统需要能实现不同的用户权限可以看到不同的功能。
用户只能使用他的权限所允许使用的功能。
功能设计
之前在我的SpringSecurity的课程中就介绍过RBAC权限模型。没有学习过的可以去看下 RBAC权限模型 。这里我们就是在RBAC权限模型的基础上去实现这个功能。
表分析
通过需求去分析需要有哪些字段。
建表SQL及初始化数据见:SGBlog\资源\SQL\sg_menu.sql
接口设计
getInfo接口
是
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /getInfo | 需要token请求头 |
请求参数:
无
响应格式:
如果用户id为1代表管理员,roles 中只需要有admin,permissions中需要有所有菜单类型为C或者F的,状态为正常的,未被删除的权限
1 | { |
getRouters接口
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /getRouters | 需要token请求头 |
请求参数:
无
响应格式:
前端为了实现动态路由的效果,需要后端有接口能返回用户所能访问的菜单数据。
注意:返回的菜单数据需要体现父子菜单的层级关系
如果用户id为1代表管理员,menus中需要有所有菜单类型为C或者M的,状态为正常的,未被删除的权限
数据格式如下:
1 | { |
代码实现
准备工作
生成menu和role表对于的类
getInfo接口
1 |
|
1 |
|
RoleServiceImpl selectRoleKeyByUserId方法
1 |
|
MenuMapper
1 | /** |
1 |
|
MenuServiceImpl selectPermsByUserId方法
1 |
|
1 | public interface RoleMapper extends BaseMapper<Role> { |
1 |
|
getRouters接口
RoutersVo
1 |
|
LoginController
1 |
|
MenuService
1 | public interface MenuService extends IService<Menu> { |
MenuServiceImpl
1 |
|
MenuMapper.java
1 | List<Menu> selectAllRouterMenu(); |
MenuMapper.xml
1 | <select id="selectAllRouterMenu" resultType="com.sangeng.domain.entity.Menu"> |
查询的列:
SELECT DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,’’) AS perms, m.is_frame, m.menu_type, m.icon, m.order_num, m.create_time
注意需要按照parent_id和order_num排序
5.3 退出登录接口
5.3.1 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /user/logout | 需要token请求头 |
响应格式:
1 | { |
5.3.2 代码实现
要实现的操作:
删除redis中的用户信息
LoginController
1 |
|
LoginService
1 | ResponseResult logout(); |
SystemLoginServiceImpl
1 |
|
SecurityConfig
要关闭默认的退出登录功能。并且要配置我们的退出登录接口需要认证才能访问
1 |
|
5.4 查询标签列表
5.4.0 需求
为了方便后期对文章进行管理,需要提供标签的功能,一个文章可以有多个标签。
在后台需要分页查询标签功能,要求能根据标签名进行分页查询。 后期可能会增加备注查询等需求。
注意:不能把删除了的标签查询出来。
5.4.1 标签表分析
通过需求去分析需要有哪些字段。
5.4.2 接口设计
请求方式 | 请求路径 |
---|---|
Get | content/tag/list |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
name:标签名
remark:备注
响应格式:
1 | { |
5.4.3 代码实现
Controller
1 |
|
Service
1 | public interface TagService extends IService<Tag> { |
1 |
|
5.5 新增标签
5.5.0 需求
点击标签管理的新增按钮可以实现新增标签的功能。
5.5.1 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /content/tag | 需要token请求头 |
请求体格式:
1 | {"name":"c#","remark":"c++++"} |
响应格式:
1 | { |
5.5.2 测试
测试时注意,添加到数据库中的记录有没有 创建时间,更新时间,创建人,更新人字段。
5.6 删除标签
5.6.1 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
DELETE | /content/tag/{id} | 需要token请求头 |
请求参数在path中
例如:content/tag/6 代表删除id为6的标签数据
响应格式:
1 | { |
5.6.2 测试
注意测试删除后在列表中是否查看不到该条数据
数据库中该条数据还是存在的,只是修改了逻辑删除字段的值
5.7 修改标签
5.7.1 接口设计
5.7.1.1 获取标签信息
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /content/tag/{id} | 需要token请求头 |
请求参数在path中
例如:content/tag/6 代表获取id为6的标签数据
响应格式:
1 | { |
5.7.1.2 修改标签接口
请求方式 | 请求地址 | 请求头 |
---|---|---|
PUT | /content/tag | 需要token请求头 |
请求体格式:
1 | {"id":7,"name":"c#","remark":"c++++"} |
响应格式:
1 | { |
5.8 写博文
5.8.1 需求
需要提供写博文的功能,写博文时需要关联分类和标签。
可以上传缩略图,也可以在正文中添加图片。
文章可以直接发布,也可以保存到草稿箱。
5.8.2 表分析
标签和文章需要关联所以需要一张关联表。
SQL脚本:SGBlog\资源\SQL\sg_article_tag.sql
5.8.2 接口设计
思考下需要哪些接口才能实现这个功能?
5.8.2.1 查询所有分类接口
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /content/category/listAllCategory | 需要token请求头 |
请求参数:
无
响应格式:
1 | { |
5.8.2.2 查询所有标签接口
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /content/tag/listAllTag | 需要token请求头 |
请求参数:
无
响应格式:
1 | { |
5.8.2.3 上传图片
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /upload | 需要token请求头 |
参数:
img,值为要上传的文件
请求头:
Content-Type :multipart/form-data;
响应格式:
1 | { |
5.8.2.4 新增博文
请求方式 | 请求地址 | 请求头 |
---|---|---|
POST | /content/article | 需要token请求头 |
请求体格式:
1 | { |
响应格式:
1 | { |
5.8.3 代码实现
5.8.3.1 查询所有分类接口
CategoryController
1 | /** |
CategoryVo修改,增加description属性
1 |
|
CategoryService增加listAllCategory方法
1 | public interface CategoryService extends IService<Category> { |
SystemConstants中增加常量
1 | /** 正常状态 */ |
CategoryServiceImpl增加方法
1 |
|
5.8.3.2 查询所有标签接口
TagVo
1 |
|
TagController
1 |
|
TagService 增加listAllTag方法
1 | List<TagVo> listAllTag(); |
TagServiceImpl
1 |
|
5.8.3.3 上传图片接口
在sangeng-admin中增加UploadController
1 | /** |
5.8.3.4 新增博文接口
ArticleController
1 | /** |
AddArticleDto
注意增加tags属性用于接收文章关联标签的id
1 |
|
Article 修改这样创建时间创建人修改时间修改人可以自动填充
1 |
|
ArticleService增加方法
1 | ResponseResult add(AddArticleDto article); |
创建ArticleTag表相关的实体类,mapper,service,serviceimpl等
1 |
|
ArticleServiceImpl增加如下代码
1 |
|
5.9 导出所有分类到Excel
5.9.1 需求
在分类管理中点击导出按钮可以把所有的分类导出到Excel文件中。
5.9.2 技术方案
使用EasyExcel实现Excel的导出操作。
https://github.com/alibaba/easyexcel
5.9.3 接口设计
请求方式 | 请求地址 | 请求头 |
---|---|---|
GET | /content/category/export | 需要token请求头 |
请求参数:
无
响应格式:
成功的话可以直接导出一个Excel文件
失败的话响应格式如下:
1 | { |
5.9.4 代码实现
工具类方法修改
WebUtils
1 | public static void setDownLoadHeader(String filename, HttpServletResponse response) throws UnsupportedEncodingException { |
CategoryController
1 |
|
ExcelCategoryVo
1 |
|
5.10 权限控制
5.10.1 需求
需要对导出分类的接口做权限控制。
sg eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJkZGJkNjM5MWJiZTA0NmMzOTc4NDg1ZTcxNWQ3YjQ0MSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY2MjI0NDE4NywiZXhwIjoxNjYyMzMwNTg3fQ.z4JGwFN3lWyVbOCbhikCe-O4D6SvCQFEE5eQY3jDJkw
sangeng
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI0Y2I1ZjhmMTc0Mjk0NzM0YjI4Y2M1NTQzYjQ2Yjc1YyIsInN1YiI6IjYiLCJpc3MiOiJzZyIsImlhdCI6MTY2MjI0NDQzMywiZXhwIjoxNjYyMzMwODMzfQ.yEkbyGYXBp5ndnyq-3acdgpvqx2mnI8B9fK9f3Y6Jco
5.10.2 代码实现
SecurityConfig
1 |
UserDetailsServiceImpl
1 |
|
LoginUser
增加属性
1 | private List<String> permissions; |
PermissionService
hasPermisson
1 |
|
CategoryController
1 |
|
5.11 文章列表
5.10.1 需求
为了对文章进行管理,需要提供文章列表,
在后台需要分页查询文章功能,要求能根据标题和摘要模糊查询。
注意:不能把删除了的文章查询出来
5.10.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | /content/article/list | 是 |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
title:文章标题
summary:文章摘要
响应格式:
1 | { |
5.12 修改文章
5.12.1 需求
点击文章列表中的修改按钮可以跳转到写博文页面。回显示该文章的具体信息。
用户可以在该页面修改文章信息。点击更新按钮后修改文章。
5.12.2 分析
这个功能的实现首先需要能够根据文章id查询文章的详细信息这样才能实现文章的回显。
如何需要提供更新文章的接口。
5.12.3 接口设计
5.12.3.1 查询文章详情接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | content/article/{id} | 是 |
Path格式请求参数:
id: 文章id
响应格式:
1 | { |
5.12.3.2 更新文章接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
PUT | content/article | 是 |
请求体参数格式:
1 | { |
响应格式:
1 | { |
5.13 删除文章
5.13.1 需求
点击文章后面的删除按钮可以删除该文章
注意:是逻辑删除不是物理删除
5.13.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
DELETE | content/article/{id} | 是 |
Path请求参数:
id:要删除的文章id
响应格式:
1 | { |
5.14 菜单列表
5.14.1 需求
需要展示菜单列表,不需要分页。
可以针对菜单名进行模糊查询
也可以针对菜单的状态进行查询。
菜单要按照父菜单id和orderNum进行排序
5.14.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
GET | system/menu/list | 是 |
Query请求参数:
status : 状态
menuName: 菜单名
响应格式:
1 | { |
5.15 新增菜单
5.15.1 需求
可以新增菜单
5.15.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
POST | content/article | 是 |
请求体参数:
Menu类对应的json格式
响应格式:
1 | { |
5.16 修改菜单
5.16.1 需求
能够修改菜单,但是修改的时候不能把父菜单设置为当前菜单,如果设置了需要给出相应的提示。并且修改失败。
5.16.2 接口设计
5.16.2.1 根据id查询菜单数据
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | system/menu/{id} | 是 |
Path格式请求参数:
id: 菜单id
响应格式:
1 | { |
5.16.2.2 更新菜单
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
PUT | system/menu | 是 |
请求体参数:
Menu类对应的json格式
响应格式:
1 | { |
如果把父菜单设置为当前菜单:
1 | { |
5.17 删除菜单
5.17.1 需求
能够删除菜单,但是如果要删除的菜单有子菜单则提示:存在子菜单不允许删除 并且删除失败。
5.17.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
DELETE | content/article/{menuId} | 是 |
Path参数:
menuId:要删除菜单的id
响应格式:
1 | { |
如果要删除的菜单有子菜单则
1 | { |
5.18 角色列表
5.18.1 需求
需要有角色列表分页查询的功能。
要求能够针对角色名称进行模糊查询。
要求能够针对状态进行查询。
要求按照role_sort进行升序排列。
5.18.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
GET | system/role/list | 是 |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
roleName:角色名称
status:状态
响应格式:
1 | { |
5.19 改变角色状态
5.19.1 需求
要求能够修改角色的停启用状态
5.19.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
PUT | system/role/changeStatus | 是 |
请求体:
1 | {"roleId":"11","status":"1"} |
响应格式:
1 | { |
5.20 新增角色!!
5.20.1 需求
需要提供新增角色的功能。新增角色时能够直接设置角色所关联的菜单权限。
5.20.2 接口设计
5.20.2.1 获取菜单树接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
GET | /system/menu/treeselect | 是 |
无需请求参数
响应格式:
1 | { |
5.20.2.2 新增角色接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
POST | system/role | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.21 修改角色
5.21.1 需求
需要提供修改角色的功能。修改角色时可以修改角色所关联的菜单权限
5.21.2 接口设计
5.21.2.1 角色信息回显接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | system/role/{id} | 是 |
Path格式请求参数:
id: 角色id
响应格式:
1 | { |
5.21.2.2 加载对应角色菜单列表树接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | /system/menu/roleMenuTreeselect/{id} | 是 |
Path格式请求参数:
id: 角色id
响应格式:
字段介绍
menus:菜单树。
checkedKeys:角色所关联的菜单权限id列表。
1 | { |
5.21.2.3 更新角色信息接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
PUT | system/role | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.22 删除角色
5.22.1 需求
删除固定的某个角色(逻辑删除)
5.22.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
DELETE | system/role/{id} | 是 |
Path请求参数:
id:要删除的角色id
响应格式:
1 | { |
5.23 用户列表
5.23.1 需求
需要用户分页列表接口。
可以根据用户名模糊搜索。
可以进行手机号的搜索。
可以进行状态的查询。
5.23.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
GET | system/user/list | 是 |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
userName:用户名
phonenumber:手机号
status:状态
响应格式:
1 | { |
5.24 新增用户!!!
5.24.1 需求
需要新增用户功能。新增用户时可以直接关联角色。
注意:新增用户时注意密码加密存储。
用户名不能为空,否则提示:必需填写用户名
用户名必须之前未存在,否则提示:用户名已存在
手机号必须之前未存在,否则提示:手机号已存在
邮箱必须之前未存在,否则提示:邮箱已存在
5.24.2 接口设计
5.24.2.1 查询角色列表接口
注意:查询的是所有状态正常的角色
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
GET | /system/role/listAllRole | 是 |
响应格式:
1 | { |
5.24.2.2 新增用户
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
POST | system/user | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.25 删除用户
5.25.1 需求
删除固定的某个用户(逻辑删除)
5.25.2 接口设计
不能删除当前操作的用户
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
DELETE | /system/user/{id} | 是 |
Path请求参数:
id:要删除的用户id
响应格式:
1 | { |
5.26 修改用户
5.26.1 需求
需要提供修改用户的功能。修改用户时可以修改用户所关联的角色。
5.26.2 接口设计
5.26.2.1 根据id查询用户信息回显接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | /system/user/{id} | 是 |
Path格式请求参数:
id: 用户id
响应格式:
roleIds:用户所关联的角色id列表
roles:所有角色的列表
user:用户信息
1 | { |
5.26.2.2 更新用户信息接口
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
PUT | /system/user | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.27 分页查询分类列表
5.27.1 需求
需要分页查询分类列表。
能根据分类名称进行模糊查询。
能根据状态进行查询。
5.27.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
GET | content/category/list | 是 |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
name:分类名
status: 状态
响应格式:
1 | { |
5.28 新增分类
5.28.1 需求
需要新增分类功能
5.28.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
POST | /content/category | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.29 修改分类
5.29.1 需求
需要提供修改分类的功能
5.29.2 接口设计
5.29.2.1 根据id查询分类
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | content/category/{id} | 是 |
Path格式请求参数:
id: 分类id
响应格式:
1 | { |
5.29.2.2 更新分类
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
PUT | /content/category | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.30 删除分类
5.30.1 需求
删除某个分类(逻辑删除)
5.30.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
DELETE | /content/category/{id} | 是 |
Path请求参数:
id:要删除的分类id
响应格式:
1 | { |
5.31 分页查询友链列表
5.31.1 需求
需要分页查询友链列表。
能根据友链名称进行模糊查询。
能根据状态进行查询。
5.31.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
GET | /content/link/list | 是 |
Query格式请求参数:
pageNum: 页码
pageSize: 每页条数
name:友链名
status:状态
响应格式:
1 | { |
5.32 新增友链
5.32.1 需求
需要新增友链功能
5.32.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
POST | /content/link | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.33 修改友链
5.33.1 需求
需要提供修改友链的功能
5.33.2 接口设计
5.33.2.1 根据id查询友联
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
Get | content/link/{id} | 是 |
Path格式请求参数:
id: 友链id
响应格式:
1 | { |
5.33.2.2 修改友链
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
PUT | /content/link | 是 |
请求体:
1 | { |
响应格式:
1 | { |
5.34 删除友链
5.34.1 需求
删除某个友链(逻辑删除)
5.34.2 接口设计
请求方式 | 请求路径 | 是否需求token头 |
---|---|---|
DELETE | /content/link/{id} | 是 |
Path请求参数:
id:要删除的友链id
响应格式:
1 | { |
6.问题总结
6.1端口号不一致
前端发送请求的端口号与后端配置的端口号不一致
①更改后端application.yml配置文件中的端口号
②前端config文件中的index.js配置的端口号为第一次访问页面的端口号
找到配置baseURL的配置文件,更改前端配置的baseURL的端口号