以博客网站为背景的后端 CRUD 项目。
技术栈:SpringBoot, MyBatis, Redis, MySQL, RabbitMQ, Junit, Maven
中间件运行在 windows Linux 子系统中。
- User
- Blog
- Blog-User
- Tag
- Blog-Tag
- Role
- User-Role
- Comment
- Blog-Comment
- UserLike
典型的 MVC。Service 层不设 Service - ServiceImpl,感觉对于小项目来说意义不大。
本项目并没有使用 Shiro, Spring Security 等安全框架,为了更深入的了解相关机制,我自行编写了安全校验模块。
- 用户注册时必须先请求验证码,验证码随机生成后包装成对象交给消息队列,消息队列末端异步处理邮件发送。验证码、邮件发送状态将被放入 redis。
- 用户注册信息写入数据库:不直接保存密码,通过生成一段随机
salt
计算出加密后的密码:Pwdhash = MD5(salt + OriginalPassword)
将salt
和pwdhash
存入数据库。 - 用户登录后,生成 Token 放入 redis 保存, Token 作为头部返回给用户,以后每次访问都要携带 Token。
Token 的生成规则:
Token = Md5(SECRET + MAIL + EXPIRE_AT) + Base64(MAIL) + Base64(EXPIRE_AT)
- 若执行的操作需要检验用户 Token,则在 Controller 层调用
userService.denyServiceIfNotSameUser(token, uid)
检查是否是同一个用户在操作。 - 对于用户权限的检验,在 Controller 层调用
userService.denyServiceIfNoRole(token, role)
进行权限检查。 - 用户执行手动登出操作时,删除在 redis 中的 key。若不做登出功能,则 redis 没必要存 Token。
- 所有异常尽可能往上一层抛,不直接 try catch。
- 定义全局异常处理类,用于捕获前面抛出的异常。
- 对于参数的校验:
- 如果仅仅是参数格式的校验,交给 Controller 层即可。若需要校验的内容很多,不要用一堆
if else
,很不优雅。推荐适用springboot-starter-validate
包进行。 - 如果是对用户是否存在这样的校验,应当放到 Service 层,因为要保持服务层的复用和控制层的轻薄。
- 如果仅仅是参数格式的校验,交给 Controller 层即可。若需要校验的内容很多,不要用一堆
Redis 缓存的好处:
- 有定时失效的功能,适合存临时数据。
- 线程安全,可用于分布式锁。
- 访问速度快,适合热门数据存储。
- 存储用户登录注册的验证码。
- 用户登录失败次数,用户临时封禁(因为登录失败)列表。
- 存储验证码邮件发送状态。
- 热门文章、最新文章的存储。
- 点赞操作存储。
主要用在了邮件发送上。邮件的发送消耗时间较长,通过消息队列做到业务逻辑异步化,可以减轻主要业务逻辑的压力。
- 方案1:在 text 字段上建立前缀索引。适合业务量不大的情况。
- 方案2:使用 elastic-search 中间件,用 canal 做 mysql binlog 的增量转储,存入 elastic-search。适合业务量大的情况。
- 由于文章更新对数据库压力大,使用 springboot 提供的定时任务组件进行更新。
- 根据全表的博客文章访问次数、点赞次数等信息综合排序,选出 top n 作为热门文章。
- 第一次更新时,放入 redis,之后直接从 redis 拿。每次热门文章更新都同时更新 redis。
在 redis 中维护一个容量一定的队列(list数据结构),每次新文章发表,先存入数据库,然后操作redis,把文章从队尾入队,若容量超过限制,把队首文章移出队列,这样就保证了队列文章总是最新的。
用户请求会经过拦截器拦截,每次拦截,取用户 ip 作为 key,访问次数为 value 写入 redis,过期时间 2s。若 2s 内访问次数超过设定的阈值,将禁止用户继续访问。
由于点赞操作十分频繁,但点赞数据的准确性不是十分重要,因此选择先保存到 redis,每隔一定时间再写入 mysql 的策略。
评论楼层的深度不设限制。评论数据库中,设置两个辅助字段:
- 当前评论的直接父评论id
- 当前评论的根节点父亲评论id
前端传递给后端的评论必须包含直接父节点id。保存评论时,若直接父节点id为null,表明该评论是楼主。
删除评论时,采用递归方式获取它的所有child。(操作量太大。这里可以考虑用存储过程。)
若不使用单元测试,每次测试都要运行项目,然后通过浏览器、postman 等插件进行测试,十分麻烦,编写单元测试有利于快速验证接口的可靠性。
单元测试分为两层,先测了 Service,尽量保证 CRUD 和业务逻辑不出错。然后看情况测 Controller 层。这样分层测试更加可靠。
- 本项目是对 Springboot 相关后端技术栈的总结。
- 多用函数式编程,保持函数的可复用性,对复杂功能进行拆分。
- 缓存的使用是重中之重。
- SQL 语句的编写要用执行计划查看,避免索引失效。