## 租户隔离修复 - 修复 SchoolCourseController 硬编码 tenantId=1L 的严重 bug - 为 SchoolClassController 8个接口添加租户验证 - 为 SchoolTeacherController 4个接口添加租户验证 - 为 SchoolStudentController 3个接口添加租户验证 - 为 SchoolParentController 6个接口添加租户验证 - 为 SchoolTaskController 3个接口添加租户验证 - 为 SchoolGrowthController 3个接口添加租户验证 ## Map 返回类型改 Response - SchoolTaskTemplateController: Map → TaskTemplateResponse - SchoolScheduleController: Map → SchedulePlanResponse - SchoolPackageController: Map → PackageInfoResponse/PackageUsageResponse - SchoolSettingsController: Map → SchoolSettingsResponse 等 - SchoolReportController: Map → ReportOverviewResponse 等 ## 新增 Response DTO - PackageInfoResponse, PackageUsageResponse - SchoolSettingsResponse, BasicSettingsResponse - NotificationSettingsResponse, SecuritySettingsResponse - ReportOverviewResponse, TeacherReportResponse - CourseReportResponse, StudentReportResponse ## 新增 Request DTO - RenewRequest, SchoolSettingsUpdateRequest - BasicSettingsUpdateRequest, NotificationSettingsUpdateRequest - SecuritySettingsUpdateRequest Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.2 KiB
7.2 KiB
开发日志 - 2026-03-17
多地点登录支持实现
修改内容
1. JwtTokenRedisService.java
文件路径: reading-platform-java/src/main/java/com/reading/platform/common/security/JwtTokenRedisService.java
修改内容:
- 修改
validateToken()方法,移除了 token 一致性检查 - 现在只检查 token 是否在黑名单中
- 允许同一账号有多个有效的 token,支持多地点同时登录
修改前逻辑:
// 检查是否与存储的 token 一致
String storedToken = getStoredToken(username);
if (storedToken == null) {
return true;
}
boolean isValid = token.equals(storedToken);
return isValid; // 如果不一致则返回 false,导致互踢下线
修改后逻辑:
// 仅检查是否在黑名单中
if (isBlacklisted(token)) {
return false;
}
// 不再检查是否与存储的 token 一致,允许同一账号有多个有效 token
return true;
2. JwtAuthenticationFilter.java
文件路径: reading-platform-java/src/main/java/com/reading/platform/common/security/JwtAuthenticationFilter.java
修改内容:
- 新增依赖注入:
AdminUserMapper,TenantMapper,TeacherMapper,ParentMapper - 在 token 验证通过后,增加账户状态检查
- 新增
isAccountActive()方法,根据用户角色查询对应表验证账户状态
新增代码:
// 检查账户状态是否为 active
if (!isAccountActive(payload)) {
log.debug("Account is not active for user: {}", payload.getUsername());
sendError(response, HttpStatus.UNAUTHORIZED, "账户已被禁用,请联系管理员");
return;
}
新增方法:
private boolean isAccountActive(JwtPayload payload) {
String role = payload.getRole();
Long userId = payload.getUserId();
return switch (role) {
case "admin" -> {
AdminUser adminUser = adminUserMapper.selectById(userId);
yield adminUser != null && "active".equalsIgnoreCase(adminUser.getStatus());
}
case "school" -> {
Tenant tenant = tenantMapper.selectById(userId);
yield tenant != null && "active".equalsIgnoreCase(tenant.getStatus());
}
case "teacher" -> {
Teacher teacher = teacherMapper.selectById(userId);
yield teacher != null && "active".equalsIgnoreCase(teacher.getStatus());
}
case "parent" -> {
Parent parent = parentMapper.selectById(userId);
yield parent != null && "active".equalsIgnoreCase(parent.getStatus());
}
default -> false;
};
}
3. AuthServiceImpl.java
文件路径: reading-platform-java/src/main/java/com/reading/platform/service/impl/AuthServiceImpl.java
修改内容:
- 更新
logout()方法的注释,说明当前实现
4. 状态判断忽略大小写修改
修改内容:
- 将所有
"active".equals(status)修改为"active".equalsIgnoreCase(status) - 确保大小写不敏感的状态判断,如 "Active"、"ACTIVE"、"active" 都被识别为激活状态
修改文件:
JwtAuthenticationFilter.java-isAccountActive()方法中的 4 处判断AuthServiceImpl.java-login()方法中的 8 处判断(admin、teacher、parent、tenant 各 2 处)
功能说明
多地点登录
- 同一账号可以在多个设备/浏览器同时登录
- 各个登录状态的 token 都有效,不会互踢下线
- JWT token 本身的过期时间(默认 24 小时)保证安全性
账户状态验证
- 每次请求都会验证账户状态是否为 "active"
- 如果管理员在后台禁用某个账号,该账号的所有已登录会话将立即失效
- 支持所有角色:admin、school、teacher、parent
黑名单机制
- 黑名单机制仍然有效
- 可以用于主动使特定 token 失效(如踢人下线场景)
验证步骤
-
多地点登录测试:
- 使用同一账号在两个不同的浏览器登录
- 在两个浏览器都发起 API 请求
- 预期:两个登录都保持有效
-
账户状态禁用测试:
- 使用账号 A 在浏览器 1 登录
- 在超管后台将账号 A 的状态修改为"非激活"
- 在浏览器 1 再次发起请求,应返回"账户已被禁用"
安全性考虑
- JWT token 有过期时间(默认 24 小时),过期后自动失效
- 黑名单机制仍然有效,可以主动使特定 token 失效
- 每次请求都会验证账户状态,确保禁用账号无法访问
影响范围
- 所有角色的登录认证流程
- Token 验证流程
- 账户状态检查
兼容性
- 此修改不影响现有功能
- 登出、黑名单等功能仍然正常工作
- 前端无需修改
Admin 控制器三层架构规范化
修改内容
1. AdminCourseCollectionController.java
文件路径: reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseCollectionController.java
问题:
findAll返回Result<List<?>>- 类型不明确findOne返回Result<?>- 类型不明确create/update返回Result<CourseCollection>- 直接返回 Entity
修改后:
- 重命名为
page方法,返回Result<PageResult<CourseCollectionResponse>> findOne返回Result<CourseCollectionResponse>create返回Result<CourseCollectionResponse>update返回Result<CourseCollectionResponse>
2. CourseCollectionService.java
文件路径: reading-platform-java/src/main/java/com/reading/platform/service/CourseCollectionService.java
新增方法:
pageCollections(Integer pageNum, Integer pageSize, String status)- 分页查询并转换为 Response
修改方法:
createCollection()- 返回类型从CourseCollection改为CourseCollectionResponseupdateCollection()- 返回类型从CourseCollection改为CourseCollectionResponse
3. CourseCollectionPageQueryRequest.java(新建)
文件路径: reading-platform-java/src/main/java/com/reading/platform/dto/request/CourseCollectionPageQueryRequest.java
@Data
@Schema(description = "课程套餐分页查询请求")
public class CourseCollectionPageQueryRequest {
@Schema(description = "页码", example = "1")
private Integer pageNum = 1;
@Schema(description = "每页数量", example = "10")
private Integer pageSize = 10;
@Schema(description = "状态")
private String status;
}
修改文件列表
| 文件 | 类型 | 说明 |
|---|---|---|
AdminCourseCollectionController.java |
修改 | 规范化返回类型 |
CourseCollectionService.java |
修改 | 新增分页方法,修改返回类型 |
CourseCollectionPageQueryRequest.java |
新增 | 分页查询请求 DTO |
验证
export JAVA_HOME="/f/Java/jdk-17"
mvn clean compile -DskipTests
# BUILD SUCCESS
规范的控制器
| 控制器 | 状态 |
|---|---|
| AdminCourseCollectionController | ✅ 已规范 |
| AdminStatsController | ✅ 已规范 |
| AdminTenantController | ✅ 已规范 |
| AdminResourceController | ✅ 已规范 |
| AdminCourseLessonController | ✅ 已规范 |
| AdminCourseController | ✅ 已规范 |
| AdminPackageController | ✅ 已规范 |
| AdminThemeController | ✅ 已规范 |
| AdminSettingsController | 🟡 可选(设置类接口允许使用 Map) |