题外话:最近沉寂了好久,随着ai的兴起,感觉随时都面临淘汰出局,于是花了2月学习了下java,写了个项目练习,目前算是完成了所有前后端的开发,本篇主要分享java部分,有兴趣的初学者可以拿去参考。预览和远远都留在了最后
本文将完整介绍一个基于 Spring Boot 2.7 + MyBatis + Redis + JWT 的多模块个人博客后端系统设计与实现,适合想学习模块化项目架构的同学。
一、项目简介
这是一个前后端分离的个人博客系统后端服务,采用 Maven 多模块架构组织代码,后端提供 RESTful API 供前台用户端和后台管理端分别调用。
核心技术栈:
| 技术 | 版本 | 说明 |
|---|---|---|
| Spring Boot | 2.7.14 | 基础框架 |
| MyBatis | 2.3.1 | ORM 持久层 |
| MySQL | 8.0+ | 主数据库 |
| Druid | 1.2.18 | 数据库连接池 |
| Redis | - | 缓存 + Token 存储 |
| JWT (jjwt) | 0.9.1 | 无状态身份认证 |
| Swagger3 | 3.0.0 | 接口文档 |
| Lombok | 1.18.28 | 简化 Java 代码 |
二、模块结构设计
bash
blog-service/
├── blog-common # 公共模块:工具类、JWT、Redis、统一返回、全局异常
├── blog-user # 用户模块:注册/登录、评论、点赞、收藏
├── blog-article # 文章模块:文章 CRUD、阅读数统计
├── blog-admin-user # 管理员模块:后台登录、轮播图、分类管理
├── blog-api # 网关入口模块:路由聚合、跨域、权限控制
└── pom.xml # 父 POM,统一依赖版本管理
各模块职责清晰,blog-common 被其他所有模块依赖,避免重复代码;blog-api 作为统一入口模块对外暴露接口,内部通过 WebClient 调用其他服务。
三、数据库设计
系统共设计 7 张核心数据表 ,全部使用 utf8mb4 字符集以支持 emoji 等特殊字符。
3.1 表结构总览
bash
blog 数据库
├── app_user # 前台用户表
├── admin_user # 后台管理员表
├── article # 文章表
├── category # 文章分类表
├── banner # 首页轮播图表
├── comment # 文章评论表(支持层级回复)
├── article_like # 文章点赞表
└── article_favorite # 文章收藏表
3.2 核心表字段设计
文章表 article(blog-article 模块)
sql
CREATE TABLE `article` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '文章ID',
`title` VARCHAR(200) NOT NULL COMMENT '文章标题',
`content` LONGTEXT DEFAULT NULL COMMENT '文章正文(支持 Markdown)',
`category_id` VARCHAR(100) DEFAULT NULL COMMENT '分类ID,多个用逗号分隔',
`article_cover` VARCHAR(500) DEFAULT NULL COMMENT '文章封面图URL',
`read_counts` INT NOT NULL DEFAULT 0 COMMENT '阅读数',
`comment_counts` INT NOT NULL DEFAULT 0 COMMENT '评论数',
`like_count` INT NOT NULL DEFAULT 0 COMMENT '点赞数',
`favorite_count` INT NOT NULL DEFAULT 0 COMMENT '收藏数',
`is_hot` TINYINT NOT NULL DEFAULT 0 COMMENT '是否热门',
`is_top` TINYINT NOT NULL DEFAULT 0 COMMENT '是否置顶',
`is_delete` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标记',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计亮点:
category_id以逗号分隔存储多个分类 ID,Mapper 中使用CONCAT(',', category_id, ',') LIKE CONCAT('%,', #{categoryId}, ',%')实现精确匹配查询,避免误匹配。
评论表 comment(blog-user 模块)
评论表通过 parent_id 字段实现无限层级回复 ,reply_to_user_id 标记被回复人,前端可据此构建评论树。
sql
CREATE TABLE `comment` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`article_id` BIGINT NOT NULL COMMENT '文章ID',
`user_id` BIGINT NOT NULL COMMENT '评论者ID',
`parent_id` BIGINT DEFAULT NULL COMMENT 'NULL=根评论',
`content` TEXT NOT NULL,
`reply_to_user_id` BIGINT DEFAULT NULL COMMENT '被回复用户ID',
`create_time` DATETIME NOT NULL,
`is_delete` TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_article_id` (`article_id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
四、JWT + Redis 双重 Token 认证
这是整个系统安全性的核心设计,采用 JWT 签发 + Redis 存储的方案,兼顾无状态与可主动失效两大需求。
4.1 认证流程
ini
用户登录 → 生成 JWT Token → 写入 Redis(key=token,value=userId,TTL=7天)
↓
后续请求 → JwtInterceptor 拦截 → 先查 Redis(token 是否存在)→ 再验 JWT 签名 → 通过
↓
主动注销时 → deleteToken(Redis)
4.2 JwtUtils 核心实现
java
// 生成 Token,同时写入 Redis
public static String generateToken(Long userId, String mobile, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
String token = Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
// 写入 Redis,TTL 与 JWT 过期时间一致
staticRedisUtils.setToken(token, userId, EXPIRATION / 1000);
return token;
}
// 验证 Token:先查 Redis 再验签名
public static boolean validateToken(String token) {
try {
if (!staticRedisUtils.hasToken(token)) {
return false; // Redis 中不存在,视为已失效
}
parseToken(token);
return !isTokenExpired(token);
} catch (Exception e) {
return false;
}
}
4.3 JwtInterceptor 拦截器
java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行 OPTIONS 预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
String token = request.getHeader(Constants.TOKEN_HEADER);
if (!StringUtils.hasText(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code\":401,\"message\":\"未登录或token已过期\"}");
return false;
}
// 去除 Bearer 前缀
if (token.startsWith(Constants.TOKEN_PREFIX)) {
token = token.substring(Constants.TOKEN_PREFIX.length());
}
if (!JwtUtils.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code\":401,\"message\":\"token无效或已过期\"}");
return false;
}
// 将用户信息注入 request attribute,供 Controller 使用
request.setAttribute(Constants.USER_ID, JwtUtils.getUserId(token));
request.setAttribute(Constants.USERNAME, JwtUtils.getUsername(token));
return true;
}
五、图形验证码实现
登录时需要通过图形验证码防止暴力破解,使用 Java AWT 绘制并以 Base64 返回给前端展示。
核心思路
- 随机生成 4 位字母数字组合(排除易混淆字符 0/O/1/I)
- 绘制随机颜色、随机旋转角度的字符
- 添加干扰线(20 条)和干扰椭圆(20 个)
- 将验证码文本存入 Redis,有效期 60 秒
- 返回 Base64 编码的图片给前端
java
public String generateCaptchaCode() {
String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // 去除易混淆字符
StringBuilder code = new StringBuilder();
Random random = new Random();
for (int i = 0; i < CODE_LENGTH; i++) {
code.append(chars.charAt(random.nextInt(chars.length())));
}
return code.toString();
}
public void saveVerifyCode(String key, String code) {
redisUtils.setString("verify_code:" + key, code, 60L); // 60秒有效期
}
六、服务间通信:WebClient 替代 RestTemplate
blog-user 模块在处理评论、点赞、收藏时需要同步更新 blog-article 模块中的计数字段,使用 Spring WebFlux 的 WebClient 进行服务间 HTTP 调用。
java
@Service
public class ArticleServiceClient {
private final WebClient webClient;
public ArticleServiceClient() {
this.webClient = WebClient.builder()
.baseUrl("http://localhost:8082") // blog-article 服务地址
.build();
}
// 发表评论后,通知 article 服务 +1 评论数
public void incrementCommentCount(Long articleId) {
try {
String result = webClient.post()
.uri("/article/comment/increment/{articleId}", articleId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class)
.block();
log.info("增加文章评论数结果: {}", result);
} catch (Exception e) {
log.error("调用 article 服务增加评论数失败", e);
}
}
}
为什么用 WebClient 而不是 RestTemplate?
RestTemplate在 Spring 5 之后已进入维护模式,WebClient是官方推荐的替代方案,支持同步/异步两种调用模式,此处使用.block()以同步方式调用保证数据一致性。
七、阅读数统计:Redis 防刷设计
文章阅读数统计需要防止同一用户短时间内重复刷取,设计如下:
- 用
ip:articleId作为 Redis key - 每次访问先检查 key 是否存在
- 不存在则计数 +1,并写入 Redis,TTL = 24 小时
- 存在则直接返回,不更新计数
这种方案以 IP + 文章 ID 维度去重,实现简单、性能高,适合中小规模博客系统。
八、统一响应格式
所有接口统一返回如下 JSON 结构,前端处理更便捷:
json
{
"code": 200,
"message": "success",
"data": { ... }
}
java
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
九、项目快速部署
9.1 环境要求
- JDK 1.8+
- MySQL 8.0+
- Redis 6.0+
- Maven 3.6+
9.2 初始化数据库
执行 blog-user/src/main/resources/db/schema.sql 即可完成所有表的创建和初始数据写入。
bash
mysql -u root -p < schema.sql
9.3 修改配置文件
各模块 application.yml 中填写实际的数据库地址、账号密码及 Redis 连接信息:
yaml
spring:
datasource:
url: jdbc:mysql://your-host:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: your-username
password: your-password
redis:
host: your-redis-host
port: 6379
password: your-redis-password
9.4 启动顺序
由于 blog-user 调用 blog-article 的接口,建议按如下顺序启动:
less
1. blog-article (port: 8082)
2. blog-admin-user (port: 8081)
3. blog-user / blog-api (port: 8083)
十、接口文档
各模块均集成 Swagger3,启动后访问以下地址查看接口文档:
| 模块 | 地址 |
|---|---|
| blog-admin-user | http://localhost:8081/swagger-ui/index.html |
| blog-article | http://localhost:8082/swagger-ui/index.html |
| blog-api | http://localhost:8083/swagger-ui/index.html |
十一、后续规划
- 引入 Spring Cloud Gateway 替代手动路由
- 接入 OSS 对象存储替换本地文件上传
- 引入消息队列解耦服务间通信(评论数、点赞数更新)
- 添加全文搜索支持(Elasticsearch)
- 前端项目开源(Vue3 + Vite)
结语
本项目完整地演示了一个 Spring Boot 多模块项目从数据库设计、模块拆分、认证方案到服务间通信的完整实践路径。代码结构清晰,注释完善,适合作为学习参考或个人博客系统的起步模板。
预览
预览地址:www.messln.cn/
源代码地址
欢迎 Star 和 Fork,有问题欢迎在评论区交流!