文章目录
- 前言
-
- 一、项目整体架构解析
- [二、Spring Security 安全框架深度剖析](#二、Spring Security 安全框架深度剖析)
-
- [2.1 自动配置原理](#2.1 自动配置原理)
- [2.2 安全过滤器链设计](#2.2 安全过滤器链设计)
- [2.3 Token 认证过滤器实现](#2.3 Token 认证过滤器实现)
- 三、通用模块(yudao-common)设计理念
-
- [3.1 统一响应结果封装](#3.1 统一响应结果封装)
- [3.2 分页响应封装](#3.2 分页响应封装)
- [3.3 错误码枚举设计](#3.3 错误码枚举设计)
- [四、Redis 缓存框架深度剖析](#四、Redis 缓存框架深度剖析)
-
- [4.1 Redis 配置类设计](#4.1 Redis 配置类设计)
- [4.2 分布式锁实现](#4.2 分布式锁实现)
- [五、MyBatis 增强实现](#五、MyBatis 增强实现)
-
- [5.1 数据权限实现](#5.1 数据权限实现)
- [5.2 自动填充功能](#5.2 自动填充功能)
- 六、限流、幂等、签名防护机制
-
- [6.1 限流器实现](#6.1 限流器实现)
- [6.2 幂等性保证](#6.2 幂等性保证)
- [6.3 API 签名验证](#6.3 API 签名验证)
- 七、定时任务框架设计
-
- [7.1 任务处理器定义](#7.1 任务处理器定义)
- [7.2 任务注册与调度](#7.2 任务注册与调度)
- 八、设计模式应用总结
-
- [8.1 策略模式应用](#8.1 策略模式应用)
- [8.2 模板方法模式应用](#8.2 模板方法模式应用)
- [8.3 门面模式应用](#8.3 门面模式应用)
- 九、学习路线规划
- 十、核心知识点速查表
- 十一、推荐阅读顺序
- 十二、面试高频问题
前言
一、项目整体架构解析
首先来看 yudao-framework 的模块组织结构:
yudao-framework/
├── yudao-common # 通用组件层
├── yudao-spring-boot-starter-mybatis # MyBatis 增强
├── yudao-spring-boot-starter-redis # Redis 缓存封装
├── yudao-spring-boot-starter-web # Web 层增强
├── yudao-spring-boot-starter-security # Spring Security 封装
├── yudao-spring-boot-starter-websocket # WebSocket 支持
├── yudao-spring-boot-starter-monitor # 监控组件
├── yudao-spring-boot-starter-protection # 防护组件(限流、幂等)
├── yudao-spring-boot-starter-job # 定时任务
├── yudao-spring-boot-starter-mq # 消息队列
├── yudao-spring-boot-starter-excel # Excel 处理
├── yudao-spring-boot-starter-test # 测试支持
├── yudao-spring-boot-starter-biz-tenant # 多租户组件
├── yudao-spring-boot-starter-biz-data-permission # 数据权限
└── yudao-spring-boot-starter-biz-ip # IP 解析
这种模块划分遵循了分层架构 和职责分离原则,是学习如何设计企业级框架的绝佳范例。
二、Spring Security 安全框架深度剖析
2.1 自动配置原理
在 /yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoSecurityAutoConfiguration.java) 中,我们可以看到自定义 Starter 的标准写法:
java
@AutoConfiguration
@AutoConfigureOrder(-1) // 确保在 Spring Security 自动配置之前执行
@EnableConfigurationProperties(SecurityProperties.class)
public class YudaoSecurityAutoConfiguration {
/**
* 认证失败处理类 Bean
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPointImpl();
}
/**
* 密码加密器
* 使用 BCrypt 算法,这是目前最安全的单向哈希算法
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(securityProperties.getPasswordEncoderLength());
}
/**
* Token 认证过滤器
*/
@Bean
public TokenAuthenticationFilter authenticationTokenFilter(...) {
return new TokenAuthenticationFilter(...);
}
}
学习要点:
- Spring Boot 自动配置机制 :通过
@AutoConfiguration注解声明这是一个自动配置类 - 配置属性绑定 :使用
@EnableConfigurationProperties启用配置属性类 - Bean 的声明式管理 :所有组件都通过
@Bean方法返回,由 Spring 容器管理
2.2 安全过滤器链设计
在 /yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java) 中,展示了 Spring Security 的核心配置:
java
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 开启跨域
.cors(Customizer.withDefaults())
// CSRF 禁用(因为使用 Token 机制,不需要 Session)
.csrf(AbstractHttpConfigurer::disable)
// 基于 token 机制,不需要 Session
.sessionManagement(c -> c
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 配置异常处理
.exceptionHandling(c -> c
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler))
// 配置 URL 权限规则
.authorizeHttpRequests(c -> c
.requestMatchers(HttpMethod.GET, "/*.html", "/*.css", "/*.js").permitAll()
// ... 更多规则
.anyRequest().authenticated())
// 将 Token 过滤器添加到 UsernamePasswordAuthenticationFilter 之前
.addFilterBefore(authenticationTokenFilter,
UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
面试常问题解析:
- 为什么禁用 CSRF? 因为使用了 Token 认证,Token 已经起到了防 CSRF 的作用
- 为什么不使用 Session? Token 机制天然无状态,Session 反而增加复杂度
- 过滤器顺序为什么重要? Spring Security 按顺序执行过滤器,顺序错误会导致安全漏洞
2.3 Token 认证过滤器实现
TokenAuthenticationFilter.java 展示了认证过滤器的完整实现:
java
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 1. 从请求中获取 Token
String token = SecurityFrameworkUtils.obtainAuthorization(request,
securityProperties.getTokenHeader(),
securityProperties.getTokenParameter());
if (StrUtil.isNotEmpty(token)) {
try {
// 2. 验证 Token,构建登录用户
LoginUser loginUser = buildLoginUserByToken(token, userType);
// 3. 模拟登录功能(开发环境)
if (loginUser == null) {
loginUser = mockLoginUser(request, token, userType);
}
// 4. 将用户信息设置到 Security Context
if (loginUser != null) {
SecurityFrameworkUtils.setLoginUser(loginUser, request);
}
} catch (Throwable ex) {
// 5. 异常处理,统一返回错误信息
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
ServletUtils.writeJSON(response, result);
return;
}
}
// 6. 继续执行过滤器链
chain.doFilter(request, response);
}
}
核心设计理念:
- 继承
OncePerRequestFilter:确保每个请求只被执行一次 - 异常捕获与统一处理:避免异常泄漏到容器层
- 多用户类型支持 :通过
userType参数区分不同端(管理端、用户端等) - 开发模式支持 :
mockLoginUser方法方便开发调试
三、通用模块(yudao-common)设计理念
3.1 统一响应结果封装
java
public class CommonResult<T> implements Serializable {
/**
* 状态码
* 成功:0 或 200
* 失败:错误的业务码或 -1
*/
private long code;
/**
* 错误提示
* 成功时:返回 null
* 失败时:返回具体的错误信息
*/
private String msg;
/**
* 返回数据
*/
private T data;
/**
* 创建成功的结果
*/
public static <T> CommonResult<T> success(T data) {
CommonResult<T> result = new CommonResult<>();
result.setCode(0);
result.setData(data);
return result;
}
/**
* 创建失败的结果
*/
public static <T> CommonResult<T> error(long code, String message) {
CommonResult<T> result = new CommonResult<>();
result.setCode(code);
result.setMsg(message);
return result;
}
}
3.2 分页响应封装
java
public class PageResult<T> implements Serializable {
/**
* 数据列表
*/
private List<T> list;
/**
* 总数量
*/
private long total;
public PageResult(List<T> list, long total) {
this.list = list;
this.total = total;
}
public static <T> PageResult<T> of(List<T> list, long total) {
return new PageResult<>(list, total);
}
}
设计亮点:
- 泛型支持:灵活处理各种数据类型
- 静态工厂方法:提供便捷的对象创建方式
- Serializable 支持:支持分布式传输
3.3 错误码枚举设计
java
public enum ErrorCode {
// ========== 系统相关 ==========
SUCCESS(0, "操作成功"),
SYSTEM_ERROR(500, "系统出错了,请稍后再试"),
PARAMETER_ERROR(400, "请求参数错误"),
// ========== 用户相关 ==========
USER_NOT_LOGIN(401, "未登录,或登录已过期"),
USER_PASSWORD_FAILED(402, "密码校验失败,原因:{}"),
USER_DISABLE(403, "用户已被禁用");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
}
四、Redis 缓存框架深度剖析
4.1 Redis 配置类设计
java
@Configuration
@EnableCaching
@EnableConfigurationProperties(YudaoCacheProperties.class)
public class YudaoRedisAutoConfiguration {
@Bean
@SuppressWarnings("unchecked")
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 Jackson 序列化器
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
// 设置 key、hashKey 的序列化方式
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 设置 value、hashValue 的序列化方式
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
关键知识点:
- 序列化方式选择:JSON 序列化便于调试和跨语言
- Key 设计规范 :采用
前缀:业务:标识的命名方式 - 连接池配置:合理配置连接池参数
4.2 分布式锁实现
java
@Component
public class DistributedLock4jRedisDAO {
private static final String KEY_PREFIX = "lock:";
/**
* 获取锁
* @param lockKey 锁的 key
* @param expireTime 过期时间
* @return 是否获取成功
*/
public boolean lock(String lockKey, long expireTime) {
String key = KEY_PREFIX + lockKey;
String value = IdUtil.fastSimpleUUID();
// SET NX EX 原子操作
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, Duration.ofMillis(expireTime));
return Boolean.TRUE.equals(result);
}
/**
* 释放锁
*/
public void unlock(String lockKey, String value) {
String key = KEY_PREFIX + lockKey;
String currentValue = redisTemplate.opsForValue().get(key);
// 只能释放自己的锁
if (value.equals(currentValue)) {
redisTemplate.delete(key);
}
}
}
核心原理:
- SET NX EX:Redis 原子命令,保证加锁的原子性
- 唯一值:使用 UUID 作为值,释放锁时校验
- 过期时间:防止死锁
五、MyBatis 增强实现
5.1 数据权限实现
java
@DataPermission(all = true) // 标注在 Controller 上
public class UserController {
@GetMapping("/page")
public CommonResult<PageResult<UserRespVO>> getUserPage(UserPageReqVO reqVO) {
// 在查询用户时,会自动添加数据权限条件
// SQL 会自动追加:AND dept_id IN (1, 2, 3)
return success(userService.getUserPage(reqVO));
}
}
实现原理:
java
public class DataPermissionHandler {
@Around("execution(* cn.iocoder.yudao.module.*.dal.mysql.*.select*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 1. 获取当前用户的数据权限
LoginUser loginUser = getLoginUser();
Set<Long> allowedDeptIds = calculateAllowedDeptIds(loginUser);
// 2. 修改 SQL,添加数据权限条件
if (allowedDeptIds != null && !allowedDeptIds.isEmpty()) {
addDeptFilter(point, allowedDeptIds);
}
// 3. 执行原 SQL
return point.proceed();
}
}
5.2 自动填充功能
java
@Component
public class MyBatisPlusFillMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 创建时间
strictInsertFill(metaObject, "createTime", Date.class, DateUtil.now());
// 更新时间
strictInsertFill(metaObject, "updateTime", Date.class, DateUtil.now());
// 创建者
strictInsertFill(metaObject, "creator", Long.class, getUserId());
// 更新者
strictInsertFill(metaObject, "updater", Long.class, getUserId());
// 租户 ID
strictInsertFill(metaObject, "tenantId", Long.class, getTenantId());
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时间
strictUpdateFill(metaObject, "updateTime", Date.class, DateUtil.now());
// 更新者
strictUpdateFill(metaObject, "updater", Long.class, getUserId());
}
}
优势:
- 避免重复的
setCreateTime()、setUpdateTime()调用 - 统一管理审计字段
- 减少遗漏和错误
六、限流、幂等、签名防护机制
6.1 限流器实现
java
@RateLimiter(key = "user:login", count = 10, timeout = 1)
public void login(LoginReqVO reqVO) {
// 接口实现
}
实现原理:
java
@Aspect
@Component
public class RateLimiterAspect {
@Around("@annotation(rateLimiter)")
public Object around(ProceedingJoinPoint point, RateLimiter rateLimiter) throws Throwable {
String key = resolveKey(rateLimiter.key());
int count = rateLimiter.count();
long timeout = rateLimiter.timeout();
// 使用 Redis 计数器实现限流
String redisKey = "rate_limiter:" + key;
Long currentCount = redisTemplate.opsForValue().increment(redisKey);
// 第一次访问,设置过期时间
if (currentCount != null && currentCount == 1) {
redisTemplate.expire(redisKey, timeout, TimeUnit.SECONDS);
}
// 判断是否超过限制
if (currentCount != null && currentCount > count) {
throw new ServiceException(429, "请求过于频繁,请稍后再试");
}
return point.proceed();
}
}
6.2 幂等性保证
java
@Idempotent(key = "#reqVO.orderId", timeout = 1000)
public Result submitOrder(OrderSubmitReqVO reqVO) {
// 订单提交逻辑
}
实现思路:
java
@Aspect
@Component
public class IdempotentAspect {
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint point, Idempotent idempotent) {
String key = resolveKey(idempotent.value());
String lockKey = "idempotent:" + key;
// 尝试获取锁
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofMillis(idempotent.timeout()));
if (!Boolean.TRUE.equals(acquired)) {
throw new ServiceException("请求已提交,请勿重复操作");
}
try {
return point.proceed();
} finally {
// 业务执行完成后删除标记
redisTemplate.delete(lockKey);
}
}
}
6.3 API 签名验证
java
@Aspect
@Component
public class ApiSignatureAspect {
@Around("@annotation(apiSignature)")
public Object around(ProceedingJoinPoint point, ApiSignature apiSignature) {
HttpServletRequest request = getHttpServletRequest();
// 1. 获取签名参数
String sign = request.getHeader("X-Sign");
String timestamp = request.getHeader("X-Timestamp");
// 2. 验证时间戳(防止重放攻击)
if (System.currentTimeMillis() - Long.parseLong(timestamp) > 300000) {
throw new ServiceException("请求已过期");
}
// 3. 验证签名
String data = buildSignatureData(request);
String computedSign = generateSign(data, apiSecret);
if (!computedSign.equals(sign)) {
throw new ServiceException("签名验证失败");
}
return point.proceed();
}
}
七、定时任务框架设计
7.1 任务处理器定义
java
@JobHandler(name = "simpleJob")
public class SimpleJobHandler implements JobHandler {
@Override
public void execute(String params) {
// 任务执行逻辑
log.info("定时任务执行:{}", params);
}
}
7.2 任务注册与调度
java
@Configuration
public class JobScheduler {
@Resource
private Scheduler scheduler;
@Resource
private ApplicationContext applicationContext;
/**
* 注册任务
*/
public void registerJob(JobDO job) {
JobDetail jobDetail = JobBuilder.newJob(BaseJob.class)
.withIdentity(job.getName(), job.getGroup())
.usingJobData("className", job.getHandlerName())
.usingJobData("params", job.getHandlerParams())
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCronExpression()))
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
核心知识点:
- Cron 表达式:用于定义任务执行时间
- JobDetail:任务的详细配置
- Trigger:触发器,定义执行时机
- 集群支持:多节点环境下任务的分布式调度
八、设计模式应用总结
8.1 策略模式应用
java
// 限流 Key 解析策略
public interface RateLimiterKeyResolver {
String resolve(RateLimiterContext context);
}
// IP 限流策略
public class IpRateLimiterKeyResolver implements RateLimiterKeyResolver {
@Override
public String resolve(RateLimiterContext context) {
return "ip:" + context.getIp();
}
}
// 用户限流策略
public class UserRateLimiterKeyResolver implements RateLimiterKeyResolver {
@Override
public String resolve(RateLimiterContext context) {
return "user:" + context.getUserId();
}
}
// 表达式策略
public class ExpressionRateLimiterKeyResolver implements RateLimiterKeyResolver {
@Override
public String resolve(RateLimiterContext context) {
// 使用 SpEL 表达式计算 Key
return new SpelExpressionParser().parseExpression(context.getExpression())
.getValue(context, String.class);
}
}
8.2 模板方法模式应用
java
public abstract class AbstractFileClient {
/**
* 上传文件模板方法
*/
public final String upload(String path, InputStream fileStream) {
// 1. 验证文件
validateFile(path, fileStream);
// 2. 处理路径
String processedPath = processPath(path);
// 3. 上传文件(子类实现)
return doUpload(processedPath, fileStream);
}
protected void validateFile(String path, InputStream fileStream) {
// 通用验证逻辑
}
protected String processPath(String path) {
// 通用路径处理
return path;
}
protected abstract String doUpload(String path, InputStream fileStream);
}
// 本地存储实现
public class LocalFileClient extends AbstractFileClient {
@Override
protected String doUpload(String path, InputStream fileStream) {
// 上传到本地文件系统
}
}
// MinIO 存储实现
public class MinioFileClient extends AbstractFileClient {
@Override
protected String doUpload(String path, InputStream fileStream) {
// 上传到 MinIO
}
}
8.3 门面模式应用
java
/**
* 权限服务门面
* 提供统一的权限操作接口
*/
public class SecurityFrameworkService {
private final PermissionApi permissionApi;
/**
* 判断是否有指定权限
*/
public boolean hasPermission(Long userId, String permission) {
return permissionApi.hasPermission(userId, permission);
}
/**
* 判断是否有任意一个权限
*/
public boolean hasAnyPermissions(Long userId, String... permissions) {
return permissionApi.hasAnyPermissions(userId, permissions);
}
/**
* 判断是否有指定角色
*/
public boolean hasRole(Long userId, String role) {
return permissionApi.hasRole(userId, role);
}
/**
* 获取用户的所有权限
*/
public Set<String> getUserPermissions(Long userId) {
return permissionApi.getUserPermissions(userId);
}
}
九、学习路线规划
第一阶段:基础夯实(1-2周)
目标: 理解 Spring Boot 自动配置原理
学习内容:
- 深入研究
yudao-spring-boot-starter-xxx的模块结构 - 理解
@AutoConfiguration注解的工作原理 - 掌握
spring.factories和META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports的使用
实践项目:
- 尝试编写一个简单的自定义 Starter
第二阶段:安全框架深入(2-3周)
目标: 精通 Spring Security 认证授权机制
学习内容:
- 分析
TokenAuthenticationFilter的实现细节 - 研究
SecurityFilterChain的配置 - 理解 Token 的生成、验证、刷新机制
实践项目:
- 实现一套基于 JWT 的认证授权系统
第三阶段:数据访问层(2-3周)
目标: 掌握 MyBatis 高级特性和数据权限
学习内容:
- 分析
DataPermissionHandler的实现 - 研究
MyBatisPlusFillMetaObjectHandler的自动填充机制 - 理解多数据源和读写分离的实现
实践项目:
- 实现一个带数据权限的查询功能
第四阶段:高并发处理(2-3周)
目标: 掌握限流、幂等、缓存等高并发技术
学习内容:
- 分析
RateLimiterAspect的实现 - 研究 Redis 分布式锁的实现原理
- 理解幂等性保证的各种方案
实践项目:
- 实现一个完整的接口防护系统
十、核心知识点速查表
| 技术点 | 涉及模块 | 关键类 | 掌握程度 |
|---|---|---|---|
| Spring Boot 自动配置 | security | YudaoSecurityAutoConfiguration |
⭐⭐⭐⭐⭐ |
| Security 过滤器链 | security | YudaoWebSecurityConfigurerAdapter |
⭐⭐⭐⭐⭐ |
| Token 认证机制 | security | TokenAuthenticationFilter |
⭐⭐⭐⭐⭐ |
| Redis 缓存封装 | redis | YudaoRedisAutoConfiguration |
⭐⭐⭐⭐ |
| 分布式锁实现 | redis | DistributedLock4jRedisDAO |
⭐⭐⭐⭐ |
| 数据权限控制 | mybatis | DataPermissionHandler |
⭐⭐⭐⭐ |
| 自动填充功能 | mybatis | MyBatisPlusFillMetaObjectHandler |
⭐⭐⭐⭐ |
| 接口限流 | protection | RateLimiterAspect |
⭐⭐⭐⭐ |
| 幂等性保证 | protection | IdempotentAspect |
⭐⭐⭐⭐ |
| API 签名验证 | protection | ApiSignatureAspect |
⭐⭐⭐ |
| 定时任务调度 | job | SchedulerManager |
⭐⭐⭐ |
十一、推荐阅读顺序
- 先读文档 :每个模块下都有
.md文档,如《芋道 Spring Boot 安全框架 Spring Security 入门》.md - 再看配置 :从
AutoConfiguration类开始,理解整体架构 - 深入实现:研究核心类的实现细节
- 动手实践:参考项目代码,编写自己的实现
十二、面试高频问题
-
Spring Security 的过滤器链执行顺序是怎样的?
- 答案:参考
TokenAuthenticationFilter的addFilterBefore方法
- 答案:参考
-
如何实现接口的幂等性?
- 答案:使用 Redis 存储请求标识,配合唯一键约束
-
分布式锁有哪些实现方式?
- 答案:Redis SETNX、Zookeeper、数据库唯一索引
-
如何防止接口被恶意调用?
- 答案:限流 + 签名验证 + 黑名单机制
-
MyBatis-Plus 如何实现数据权限?
- 答案:通过拦截 SQL,动态添加 WHERE 条件