# Week 5:Spring 全家桶核心组件 —— 安全、缓存、监控与进阶 **目标**:在 Week 4 的全栈项目基础上,引入 Spring Security + JWT 实现认证授权,集成 Redis 缓存,接入 Actuator 监控,自定义校验注解,掌握 MyBatis-Plus 逻辑删除与分页。 **前置**:已完成 Week 4 的 Spring Boot + ORM 双轨 + MVC 全栈项目。 **学习成果**:一个完整的"学生管理系统 v2",具备: - JWT 令牌认证(登录 / 注册) - RBAC 角色控制(ADMIN 可增删改,USER 只读) - Redis 缓存加速查询 - Actuator 健康检查与监控 - @ValidScore 自定义校验注解 - MyBatis-Plus 逻辑删除 --- ## Day 1:Spring Security 架构 & 过滤器链 **知识点**: - Spring Security 核心概念:Authentication(认证)vs Authorization(授权) - SecurityFilterChain:过滤器链的工作原理 - 关键过滤器:UsernamePasswordAuthenticationFilter、SecurityContextHolder - 无状态会话(STATELESS)vs 有状态会话 - CSRF 的概念及何时禁用(前后端分离 API) - BCrypt 密码编码器的不可逆特性 **动手 → 理解**: 1. 阅读 `SecurityConfig.java`:逐行理解每条配置的意图 2. 用 Postman / 浏览器测试:不登录直接访问 `/api/students`,观察 401 响应 3. 去掉 `@Bean` 的 SecurityFilterChain,对比默认行为(表单登录页) 4. 尝试把 `sessionCreationPolicy` 改回 `IF_REQUIRED`,观察 JSESSIONID 的出现 **核心文件**: - `config/SecurityConfig.java` — 安全配置入口 - `service/AuthService.java` — 认证逻辑(login / register) **思考题**:为什么要用 `requestMatchers(...).permitAll()` 放行 `/api/auth/**`?不放行会怎样? --- ## Day 2:JWT 认证 —— 登录 / 注册 **知识点**: - JWT(JSON Web Token)结构:Header.Payload.Signature - 对称密钥签名:HMAC-SHA256 - JJWT 库的使用:`Jwts.builder()` 生成,`Jwts.parser().verifyWith()` 验证 - Claims:subject、issuedAt、expiration、自定义 claim(role) - 前端 Token 存储:localStorage vs sessionStorage vs Cookie 的权衡 - OncePerRequestFilter:为什么用它而不是普通 Filter **动手 → 理解**: 1. 用浏览器 DevTools Application 面板查看 localStorage 中的 `wk5_token` 2. 把 token 粘贴到 [jwt.io](https://jwt.io) 在线解析,观察 payload 内容 3. 修改 `application.yml` 中 `jwt.expiration` 为 60000(1分钟),等一分钟再刷新页面 4. 手动篡改 localStorage 中 token 的某一个字符,观察 401 错误 **核心文件**: - `security/JwtUtil.java` — Token 生成与解析工具 - `security/JwtAuthFilter.java` — 过滤器:从请求头提取并验证 Token - `config/DataInitializer.java` — 启动时创建默认用户 **思考题**:JWT 的三个部分中,哪个是"不可伪造"的关键?为什么? --- ## Day 3:RBAC 方法级安全 —— 权限控制 **知识点**: - RBAC(Role-Based Access Control):角色 → 权限 → 资源 - `@EnableMethodSecurity` 开启方法级注解 - `@PreAuthorize("hasRole('ADMIN')")` 的 SpEL 表达式 - SecurityContextHolder:如何在 Controller 中获取当前用户 - `Principal` 参数注入 - 权限不足时的 AccessDeniedException → 全局异常处理返回 403 **动手 → 理解**: 1. 用 user/123456 登录,尝试新增学生,观察后台日志中的 AccessDeniedException 2. 在前端查看:USER 角色的"新增"按钮为何消失(app.js 中的 `checkRoleUI()`) 3. 在 `StudentController.java` 的 `deleteStudent` 方法上暂时注释掉 `@PreAuthorize`,用 USER 登录后通过 fetch 发送 DELETE 请求验证 SecurityConfig 的 URL 级别拦截 4. 登录 admin 后访问 `/api/students/me`,查看返回的 Principal 信息 **核心文件**: - `controller/StudentController.java` → `@PreAuthorize` 注解 - `exception/GlobalExceptionHandler.java` → AccessDeniedException 处理 **思考题**:URL 级别拦截(SecurityConfig)和方法级拦截(@PreAuthorize)各有什么适用场景? --- ## Day 4:Redis 缓存集成 **知识点**: - Spring Cache 抽象:`@EnableCaching`、`@Cacheable`、`@CacheEvict` - Redis 作为缓存后端 vs 默认 ConcurrentHashMap - 缓存 Key 的 SpEL 表达式:`#id`、`'list'` - `@CacheEvict(allEntries = true)` 的使用场景和风险 - application.yml 中的 Redis 连接配置 - Redis 命令行基本操作:`KEYS *`、`GET key`、`DEL key` **动手 → 理解**: 1. 启动项目,访问 `/api/students` 两次,观察第二次的响应速度变化 2. 用 `redis-cli KEYS *` 查看 Spring Cache 生成的 Key 格式 3. 用 `redis-cli GET "students::list"` 查看缓存的序列化数据 4. 新增一个学生后,再次 `KEYS *` 观察缓存是否被清空 5. 暂时关掉 Redis 服务,观察应用是否还能正常启动(`spring.cache.type: none`) **核心文件**: - `service/jpa/StudentJpaService.java` — JPA 服务的缓存注解 - `service/mp/StudentMpService.java` — MP 服务的缓存注解 - `application.yml` — Redis 与 Cache 配置 **思考题**:为什么新增/修改/删除时要 `allEntries = true` 清理全部缓存,而不是只清理单条? --- ## Day 5:Actuator 应用监控 **知识点**: - Spring Boot Actuator 的端点(Endpoints):health、info、beans、env、mappings - `/actuator/health`:应用健康状态(UP / DOWN) - `/actuator/beans`:Spring 容器中所有 Bean 的列表和依赖关系 - `/actuator/env`:运行时环境变量和配置属性 - `/actuator/mappings`:所有 URL 映射(Controller + Filter) - 生产环境中端点的安全控制 **动手 → 理解**: 1. 浏览器访问 `http://localhost:8080/actuator` 查看所有可用端点 2. 访问 `/actuator/health`,观察 JSON 中的 `status: "UP"` 3. 访问 `/actuator/beans`,搜索 "jwt" 或 "security" 找到相关 Bean 4. 访问 `/actuator/mappings`,找到自己写的 StudentController 的映射信息 5. 在 `application.yml` 中把 `include` 改为只保留 `health,info`,重启再访问 `/actuator` **核心文件**: - `application.yml` → `management.endpoints.web.exposure.include` - `pom.xml` → `spring-boot-starter-actuator` **思考题**:为什么不应该在生产环境暴露 `/actuator/env`?里面会有什么敏感信息? --- ## Day 6:自定义校验注解 —— @ValidScore **知识点**: - Bean Validation(JSR 380)的扩展机制 - `@Constraint(validatedBy = ...)` 元注解 - `ConstraintValidator` 接口:`initialize()` + `isValid()` - 自定义注解的三要素:`message`、`groups`、`payload` - `ConstraintValidatorContext` 自定义错误消息 - `@ValidScore` + `@NotNull` 的配合使用 **动手 → 理解**: 1. 阅读 `ScoreValidator.java` 的 `isValid` 方法,理解 null 的处理逻辑 2. 在前端新增学生时,输入成绩 150 并提交,观察后端返回的校验错误 3. 修改 `@ValidScore` 的 `message` 默认值为中文提示,观察返回的 JSON 4. 尝试把 `@ValidScore` 应用到 `Student` 实体的 `score` 字段(DTO 校验 vs 实体校验的区别) 5. 动手写一个新的自定义注解 `@ValidEmail`,校验邮箱格式 **核心文件**: - `annotation/ValidScore.java` — 自定义注解定义 - `annotation/ScoreValidator.java` — 校验逻辑实现 **思考题**:`isValid` 中对 null 返回 true,而不做 null 判断。如果调用方忘记加 `@NotNull`,会发生什么? --- ## Day 7:MyBatis-Plus 进阶 —— 逻辑删除 & LambdaQueryWrapper **知识点**: - 逻辑删除 vs 物理删除:概念差异与适用场景 - `@TableLogic`:标记逻辑删除字段 - `mybatis-plus.global-config.db-config.logic-delete-field` 全局配置 - 逻辑删除的 SQL 行为:DELETE → UPDATE SET deleted=1;SELECT 自动追加 deleted=0 - JPA 如何手动实现逻辑删除(@Query 中添加 `s.deleted = 0`) - LambdaQueryWrapper 条件构造:`.eq()`、`.like()`、`.orderByDesc()`、`.between()` - 分页拦截器 PaginationInnerInterceptor 的 `overflow` 和 `maxLimit` 配置 **动手 → 理解**: 1. 用 XML (MyBatisPlusConfig) 配置逻辑删除 vs 用 `@TableLogic` 注解的效果对比 2. 在 MySQL 中执行 `SELECT * FROM student WHERE deleted = 1`,验证逻辑删除后的数据是否存在 3. 对比 MP 服务(自动过滤 deleted)和 JPA 服务(手动写 deleted=0 条件)的实现差异 4. 尝试修改 `maxLimit` 为 5,观察分页查询被限制的效果 5. 在 LambdaQueryWrapper 上尝试组合条件:`like(Student::getName, keyword).between(Student::getAge, 18, 25)` **核心文件**: - `config/MyBatisPlusConfig.java` — 分页拦截器 + 逻辑删除全局配置 - `service/mp/StudentMpService.java` — MP 服务的 LambdaQueryWrapper 使用 - `service/jpa/StudentJpaService.java` — JPA 手动逻辑删除的 @Query 写法 - `application.yml` → `mybatis-plus.global-config.db-config` **思考题**:逻辑删除和物理删除各适用于什么业务场景?为什么 Week 5 选择了逻辑删除? --- ## Week 5 总结 | 维度 | Week 4(v1) | Week 5(v2) | |------|-------------|-------------| | 认证 | 固定 Token 字符串 | JWT 动态签发 + 过期控制 | | 授权 | 无角色概念 | RBAC(ADMIN / USER) | | 安全框架 | 手写 Interceptor | Spring Security + FilterChain | | 缓存 | 无 | Redis + Spring Cache 注解 | | 监控 | 无 | Actuator 端点 | | 校验 | @Min/@Max 内置注解 | 自定义 @ValidScore | | 数据库 | MP/JPA 直接操作 | MP 逻辑删除 + JPA 手动兼容 | **环境保障**: - MySQL 8.0+,数据库 `gc_plan`,表 `student` + `users` - Redis 需运行在 localhost:6379(默认端口) - `DataInitializer` 在首次启动时自动创建 admin/123456 和 user/123456 - 如需重置:删除 users 表中记录,重启应用即可重新初始化 **附加练习**: 1. 给 USER 角色新增一个权限:可以查看但不能修改自己的详细信息 2. 用 Redis 缓存 JWT Token,实现"黑名单"注销功能 3. 在 Actuator 中增加自定义 HealthIndicator(检查 Redis 连接状态) 4. 把 @ValidScore 扩展为支持不同学科的不同分数范围