在初始认证基础上,新增完整的 RBAC 权限模型(角色、权限、菜单三级管理), 集成审计日志、接口限流、登录失败锁定、Refresh Token 机制、Redis 分布式缓存与锁、 RocketMQ 消息队列,并引入 Flyway 数据库版本管理,同时补充项目文档与使用示例
12 KiB
12 KiB
Spring Boot 模板项目使用文档
项目概述
这是一个功能完整的 Spring Boot 3.x 项目模板,集成了以下核心功能:
- 用户认证与授权:基于 JWT + Spring Security
- RBAC 权限模型:角色-权限-菜单三级权限控制
- 数据库迁移:Flyway 版本化管理
- Redis 缓存:多种场景化缓存支持
- RocketMQ 消息队列:异步消息处理
- 分布式锁:基于 Redis 实现
- 接口限流:基于 Redis + AOP
- 审计日志:AOP 自动记录操作日志
- 账户安全:登录失败锁定、密码强度校验
1. 快速开始
1.1 环境要求
- JDK 17+
- MySQL 8.0+
- Redis 6.0+
- RocketMQ 5.x(可选)
1.2 数据库初始化
项目使用 Flyway 自动管理数据库迁移,启动时会自动执行以下操作:
- 创建用户表
- 创建 RBAC 相关表(角色、权限、菜单)
- 创建审计日志表
- 创建 Refresh Token 表
1.3 配置文件
修改 application-dev.yaml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=Asia/Shanghai
username: your_username
password: your_password
data:
redis:
host: localhost
port: 6379
password: your_redis_password
jwt:
secret: your-secret-key-at-least-64-characters-long
2. 核心功能使用
2.1 用户认证与授权
注册用户
@Autowired
private UserService userService;
UserDto userDto = new UserDto();
userDto.setUsername("testuser");
userDto.setPassword("Test123!"); // 必须包含大小写字母、数字、特殊字符
userDto.setEmail("test@example.com");
RestBean<LoginResponseVo> result = userService.register(userDto);
用户登录
UserDto loginDto = new UserDto();
loginDto.setUsername("testuser");
loginDto.setPassword("Test123!");
RestBean<LoginResponseVo> result = userService.login(loginDto);
// 返回 accessToken 和 refreshToken
使用 Token 访问接口
curl -H "Authorization: Bearer {accessToken}" \
http://localhost:8080/api/v1/user/info
Token 刷新
curl -X POST \
-H "Content-Type: application/json" \
-d '{"refreshToken": "{refreshToken}"}' \
http://localhost:8080/api/v1/user/refresh
登出
curl -X POST \
-H "Authorization: Bearer {accessToken}" \
http://localhost:8080/api/v1/user/logout
2.2 RBAC 权限控制
创建角色
@Autowired
private SysRoleService roleService;
RoleDto roleDto = new RoleDto();
roleDto.setRoleCode("ROLE_EDITOR");
roleDto.setRoleName("编辑");
roleDto.setDescription("内容编辑角色");
roleDto.setSortOrder(3);
roleDto.setStatus(1);
RoleVo role = roleService.create(roleDto);
为角色分配权限
List<Long> permissionIds = Arrays.asList(1L, 2L, 3L); // 文章创建、编辑、删除
roleService.assignPermissions(roleId, permissionIds);
为用户分配角色
@Autowired
private UserService userService;
UserRoleUpdateDto dto = new UserRoleUpdateDto();
dto.setRoleIds(Set.of(1L, 2L)); // 角色ID列表
userService.updateUserRole(userId, dto);
在 Controller 中使用权限注解
@RestController
@RequestMapping("/api/v1/articles")
public class ArticleController {
// 只有具有 'article:create' 权限的用户可以访问
@PreAuthorize("hasAuthority('article:create')")
@PostMapping
public RestBean<Void> create(@RequestBody ArticleDto dto) {
// ...
}
// 需要管理员角色或文章管理权限
@PreAuthorize("hasAnyRole('ROLE_ADMIN') or hasAuthority('article:delete')")
@DeleteMapping("/{id}")
public RestBean<Void> delete(@PathVariable Long id) {
// ...
}
}
2.3 Redis 缓存使用
基本缓存操作
@Autowired
private RedisCache redisCache;
// 设置缓存(30分钟过期)
redisCache.set("user:" + userId, userObject, 30, TimeUnit.MINUTES);
// 获取缓存
User user = redisCache.get("user:" + userId, User.class);
// 删除缓存
redisCache.delete("user:" + userId);
计数器(阅读数、点赞数)
// 原子自增
long viewCount = redisCache.increment("article:view:" + articleId, 1);
// 原子自减
long stock = redisCache.decrement("product:stock:" + productId, quantity);
哈希表(对象字段缓存)
// 设置单个字段
redisCache.hSet("user:profile:" + userId, "nickname", "张三");
redisCache.hSet("user:profile:" + userId, "age", 25);
// 获取单个字段
String nickname = (String) redisCache.hGet("user:profile:" + userId, "nickname");
// 获取所有字段
Map<Object, Object> profile = redisCache.hGetAll("user:profile:" + userId);
列表(消息队列)
// 从右侧推入(队列尾部)
redisCache.lRightPush("queue:email", emailObject);
// 从左侧弹出(队列头部)
Object email = redisCache.lLeftPop("queue:email");
集合(去重、标签)
// 添加标签(自动去重)
redisCache.sAdd("article:tags:" + articleId, "Java", "Spring", "Redis");
// 检查标签是否存在
boolean hasTag = redisCache.sIsMember("article:tags:" + articleId, "Java");
// 获取所有标签
Set<Object> tags = redisCache.sMembers("article:tags:" + articleId);
有序集合(排行榜)
// 添加到排行榜
redisCache.zAdd("leaderboard:user:score", userId, score);
// 获取用户排名
Long rank = redisCache.zReverseRank("leaderboard:user:score", userId);
// 获取用户分数
Double score = redisCache.zScore("leaderboard:user:score", userId);
// 增加分数
redisCache.zIncrementScore("leaderboard:user:score", userId, 10.0);
2.4 分布式锁使用
@Autowired
private RedisLock redisLock;
public void processOrder(Long orderId) {
// 1. 获取锁(30秒过期)
String lockValue = redisLock.tryLock("order:" + orderId, 30);
if (lockValue == null) {
throw new RuntimeException("订单正在处理中");
}
try {
// 2. 执行业务逻辑
// ...
} finally {
// 3. 释放锁(只有持有者才能释放)
redisLock.unlock("order:" + orderId, lockValue);
}
}
2.5 接口限流使用
@RestController
public class LoginController {
// 限制每 IP 每分钟最多 5 次登录尝试
@RateLimit(permits = 5, seconds = 60, limitType = RateLimit.LimitType.IP)
@PostMapping("/login")
public RestBean<LoginResponseVo> login(@RequestBody UserDto dto) {
// ...
}
}
2.6 审计日志使用
@RestController
public class UserController {
// 自动记录审计日志
@AuditLog(action = "update", resource = "user", description = "更新用户信息")
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('user:update')")
public RestBean<UserVo> updateUser(@PathVariable Long id, @RequestBody UserDto dto) {
// 操作会被自动记录到 sys_audit_log 表
}
}
2.7 事务使用
基本事务
@Service
public class UserService {
// 所有异常都回滚
@Transactional(rollbackFor = Exception.class)
public void createUserWithRole(UserDto userDto, Set<Long> roleIds) {
// 创建用户
User user = new User();
// ...
userRepository.save(user);
// 分配角色
for (Long roleId : roleIds) {
SysRole role = roleRepository.findById(roleId)
.orElseThrow(() -> new BusinessException("角色不存在"));
user.getRoles().add(role);
}
userRepository.save(user);
// 任何异常都会回滚整个事务
}
}
嵌套事务
// 外层事务
@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
// 业务逻辑
innerMethod(); // 加入外层事务
}
// 内层事务
@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() {
// 加入外层事务
}
独立事务
@Transactional(propagation = Propagation.REQUIRED)
public void mainMethod() {
// 主事务逻辑
// 独立事务(即使主事务回滚也不影响)
recordLog();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog() {
// 独立事务
}
2.8 RocketMQ 消息队列使用
发送消息
@Autowired
private UserMessageProducer messageProducer;
// 发送注册消息
messageProducer.sendRegisterMessage(userId, username, email);
// 发送登录消息
messageProducer.sendLoginMessage(userId, username, ipAddress);
消费消息
消息会被 UserMessageConsumer 自动消费,根据消息类型执行不同操作:
@RocketMQMessageListener(
consumerGroup = "user-consumer-group",
topic = "user-topic"
)
public class UserMessageConsumer implements RocketMQListener<UserMessage> {
@Override
public void onMessage(UserMessage message) {
switch (message.getMessageType()) {
case "REGISTER":
// 处理注册消息(发送欢迎邮件等)
break;
case "LOGIN":
// 处理登录消息(记录登录日志)
break;
// ...
}
}
}
3. 配置说明
3.1 JWT 配置
jwt:
# JWT 密钥(至少64字符)
secret: your-secret-key-at-least-64-characters-long
# Access Token 过期时间(秒)
access-token-expiration: 3600 # 1小时
# Refresh Token 过期时间(秒)
refresh-token-expiration: 604800 # 7天
3.2 登录安全配置
app:
login:
# 最大失败次数
max-attempts: 5
# 锁定时长(分钟)
lock-duration-minutes: 30
3.3 Redis 配置
spring:
data:
redis:
host: localhost
port: 6379
password: your-password
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
3.4 RocketMQ 配置
rocketmq:
name-server: localhost:9876
producer:
group: user-producer-group
user-topic: user-topic
4. 常见问题
4.1 如何添加新的权限?
- 在数据库中插入权限记录(通过 Flyway 迁移脚本)
- 在 Controller 上使用
@PreAuthorize注解
@PreAuthorize("hasAuthority('new:permission')")
public void someMethod() { }
4.2 如何自定义限流规则?
使用 @RateLimit 注解:
@RateLimit(
permits = 10, // 10次
seconds = 60, // 每分钟
limitType = RateLimit.LimitType.USER, // 按用户限流
keyPrefix = "custom:" // 自定义键前缀
)
public void customMethod() { }
4.3 如何实现缓存预热?
在应用启动时加载热点数据:
@Component
public class CacheWarmupRunner implements ApplicationRunner {
@Autowired
private RedisCache redisCache;
@Override
public void run(ApplicationArguments args) {
// 预热热点数据
List<User> hotUsers = userRepository.findHotUsers();
for (User user : hotUsers) {
redisCache.set("user:" + user.getId(), user, 1, TimeUnit.HOURS);
}
}
}
5. 最佳实践
5.1 密码安全
- 生产环境必须修改 JWT 密钥
- 使用强密码策略(已集成
@StrongPassword) - 定期更换密码
5.2 数据库事务
- 查询操作使用
@Transactional(readOnly = true) - 明确指定
rollbackFor = Exception.class - 避免大事务
5.3 Redis 使用
- 合理设置过期时间,避免内存溢出
- 使用 Redis 分布式锁防止并发问题
- 热点数据使用缓存,减少数据库压力
5.4 安全建议
- 修改 CORS 配置,限制允许的域名
- 使用 HTTPS
- 定期审查用户权限
- 启用审计日志