深入研究 yudao-framework 模块:Java 编程能力提升指南

文章目录


前言

一、项目整体架构解析

首先来看 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(...);
    }
}

学习要点:

  1. Spring Boot 自动配置机制 :通过 @AutoConfiguration 注解声明这是一个自动配置类
  2. 配置属性绑定 :使用 @EnableConfigurationProperties 启用配置属性类
  3. 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);
    }
}

核心设计理念:

  1. 继承 OncePerRequestFilter:确保每个请求只被执行一次
  2. 异常捕获与统一处理:避免异常泄漏到容器层
  3. 多用户类型支持 :通过 userType 参数区分不同端(管理端、用户端等)
  4. 开发模式支持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;
    }
}

关键知识点:

  1. 序列化方式选择:JSON 序列化便于调试和跨语言
  2. Key 设计规范 :采用 前缀:业务:标识 的命名方式
  3. 连接池配置:合理配置连接池参数

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);
    }
}

核心知识点:

  1. Cron 表达式:用于定义任务执行时间
  2. JobDetail:任务的详细配置
  3. Trigger:触发器,定义执行时机
  4. 集群支持:多节点环境下任务的分布式调度

八、设计模式应用总结

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 自动配置原理

学习内容:

  1. 深入研究 yudao-spring-boot-starter-xxx 的模块结构
  2. 理解 @AutoConfiguration 注解的工作原理
  3. 掌握 spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 的使用

实践项目:

  • 尝试编写一个简单的自定义 Starter

第二阶段:安全框架深入(2-3周)

目标: 精通 Spring Security 认证授权机制

学习内容:

  1. 分析 TokenAuthenticationFilter 的实现细节
  2. 研究 SecurityFilterChain 的配置
  3. 理解 Token 的生成、验证、刷新机制

实践项目:

  • 实现一套基于 JWT 的认证授权系统

第三阶段:数据访问层(2-3周)

目标: 掌握 MyBatis 高级特性和数据权限

学习内容:

  1. 分析 DataPermissionHandler 的实现
  2. 研究 MyBatisPlusFillMetaObjectHandler 的自动填充机制
  3. 理解多数据源和读写分离的实现

实践项目:

  • 实现一个带数据权限的查询功能

第四阶段:高并发处理(2-3周)

目标: 掌握限流、幂等、缓存等高并发技术

学习内容:

  1. 分析 RateLimiterAspect 的实现
  2. 研究 Redis 分布式锁的实现原理
  3. 理解幂等性保证的各种方案

实践项目:

  • 实现一个完整的接口防护系统

十、核心知识点速查表

技术点 涉及模块 关键类 掌握程度
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 ⭐⭐⭐

十一、推荐阅读顺序

  1. 先读文档 :每个模块下都有 .md 文档,如 《芋道 Spring Boot 安全框架 Spring Security 入门》.md
  2. 再看配置 :从 AutoConfiguration 类开始,理解整体架构
  3. 深入实现:研究核心类的实现细节
  4. 动手实践:参考项目代码,编写自己的实现

十二、面试高频问题

  1. Spring Security 的过滤器链执行顺序是怎样的?

    • 答案:参考 TokenAuthenticationFilteraddFilterBefore 方法
  2. 如何实现接口的幂等性?

    • 答案:使用 Redis 存储请求标识,配合唯一键约束
  3. 分布式锁有哪些实现方式?

    • 答案:Redis SETNX、Zookeeper、数据库唯一索引
  4. 如何防止接口被恶意调用?

    • 答案:限流 + 签名验证 + 黑名单机制
  5. MyBatis-Plus 如何实现数据权限?

    • 答案:通过拦截 SQL,动态添加 WHERE 条件
相关推荐
逻辑驱动的ken1 小时前
Java高频考点场景题24
java·开发语言·面试·职场和发展·求职招聘
兔小盈1 小时前
多线程-(五)线程安全之内存可见性
java·开发语言·多线程
CeshirenTester2 小时前
LangChain的工具调用 vs 原生Skill API:性能差在哪儿?
java·人工智能·langchain
yaoxin5211232 小时前
400. Java 文件操作基础 - 使用 Buffered Stream I/O 读取文本文件
java·开发语言·python
Fox爱分享2 小时前
字节二面:10亿数据毫秒级查手机尾号后4位,答不出“异构索引”直接挂?
java·后端·面试
6190083362 小时前
win idea 控制台中文乱码
java·ide·intellij-idea
折哥的程序人生 · 物流技术专研2 小时前
《Java面试85题图解版(二)》进阶深化上篇:并发编程 + JVM
java·开发语言·后端·面试
abcnull2 小时前
用ASM做精准测试(Java)
java·jar·asm·字节码·精准测试
2501_931803752 小时前
Go:一门为解决C语言痛点而生的现代语言
c语言·开发语言·golang