datai/docs/archive/sessions/2026-01-25-002-04-session.md

244 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 会话记录:时区国际化功能
## 元数据
- 需求编号2026-01-21-002-04
- 创建时间2026-01-25
- 创建人SSOT 架构师
- 状态:进行中
- 父需求2026-01-21-002-项目国际化需求
## 执行阶段
### 阶段 1需求定义
- **状态**:已完成
- **生成文档**[需求文档](../requirements/2026-01-21-002-04-时区国际化需求.md)
- **关键决策**
- 所有时间字段存储为 UTC 时间
- 时区转换在 Service 层进行
- 使用 Redis 缓存时区配置
- 时区切换后立即刷新页面
- 支持用户 > 租户 > 系统的时区优先级
- 时区列表固定存储在数据库中
### 阶段 2方案设计
- **状态**:已完成
- **生成文档**[设计文档](../design/2026-01-21-002-04-时区国际化设计.md)
- **关键设计决策**
- 使用 Java TimeZone APIJava 21 内置)进行时区转换
- 使用 Redis 缓存时区配置,提高性能
- 时区转换在 Service 层进行,使用 AOP 切面拦截响应
- 时区优先级:用户 > 租户 > 系统
- 时区列表固定存储在数据库中,后续不允许用户调整
- 时区切换后清除 Redis 缓存并刷新页面
- 所有时间字段存储为 UTC 时间LocalDateTime
- 缓存策略:时区列表 24 小时,用户时区 30 分钟
### 阶段 3方案决策
- **状态**:已完成
- **生成文档**[决策记录](../decisions/adr/2026-01-25-002-04-ADR-时区国际化技术选型.md)
- **关键决策**
- **决策 1时区转换技术选择** - Java TimeZone APIJava 21 内置)
- 理由:无额外依赖、功能完整、自动处理夏令时、性能优秀、成熟稳定、与框架兼容
- 放弃方案Joda-Time 库(需要额外依赖、已进入维护模式)、自定义时区偏移实现(维护成本高、难以处理夏令时)
- **决策 2缓存策略选择** - Redis 缓存
- 理由:已集成、性能优秀、分布式支持、自动过期、数据结构丰富、支持持久化
- 缓存策略时区列表缓存TTL = 24 小时、用户时区缓存TTL = 30 分钟)
- 放弃方案Caffeine 本地缓存(无法在分布式环境下共享)、数据库缓存(性能较差、无法自动过期)
- **决策 3时区转换层选择** - Service 层转换 + AOP 切面拦截
- 理由统一处理、AOP 切面自动转换、业务逻辑分离、易于维护、性能优化
- 实现方案:使用 @Around 切面拦截 Service 方法返回值,提供 @TimeZoneConvert 注解
- 放弃方案Controller 层转换(职责过重、代码重复)、数据库层转换(违反分层架构、兼容性差)
- **决策 4时区优先级策略选择** - 多级优先级策略(用户 > 租户 > 系统)
- 理由:灵活性高、用户体验好、租户隔离、系统默认、易于扩展
- 优先级规则:用户时区 > 租户时区 > 系统时区 > 硬编码默认
- 放弃方案:单级优先级(仅用户时区/仅系统时区)- 灵活性不足、无法满足多租户场景
### 阶段 4数据库结构
- **状态**:已完成
- **生成文档**[SQL 脚本](../sql/2026-01-25-002-04-timezone-internationalization.sql)
- **数据库变更**
- **创建新表**sys_timezone时区配置表
- 字段id、timezone_id、timezone_name、timezone_offset、is_default、sort_order、status、create_by、create_time、update_by、update_time、remark
- 索引主键索引id、唯一索引timezone_id、普通索引status、普通索引sort_order
- **修改现有表**sys_user用户表
- 新增字段time_zone时区 ID默认值为 Asia/Shanghai
- 新增索引idx_time_zonetime_zone 字段)
- **插入初始数据**
- 插入 20 个常用时区配置(包括中国、美国、欧洲、亚洲、澳洲等)
- 插入时区状态数据字典sys_timezone_status
- 插入时区默认标识数据字典sys_timezone_default
### 阶段 5提示词生成
- **状态**:已完成
- **生成文档**[提示词文档](../prompts/2026-01-25-002-04-prompt-时区国际化功能.md)
- **方案变更**
- **原方案**AOP 拦截 Controller 方法返回值
- **新方案**AOP 拦截 Service 方法返回值
- **变更原因**
- 所有调用 Service 的地方都会触发转换Controller、定时任务、内部调用等覆盖面更广
- 转换逻辑统一在 AOP 切面中,无需手动调用
- 时区转换逻辑与业务逻辑分离,代码清晰
- 定时任务、内部调用等也能自动转换
- **影响范围**
- 设计文档:已更新时区转换实现部分
- 提示词文档:已更新 Aspect 层实现部分
- 架构决策记录:已更新决策 3 的内容
- **提示词内容摘要**
- **引用真源**需求文档、设计文档、决策记录、SQL 脚本
- **需求描述**:时区转换功能、时区管理功能、时区优先级策略、缓存管理、自动时区转换
- **设计方案**Java TimeZone API、Redis、MySQL 8.3.0、Spring Boot 3.5.7 + 若依框架、分层架构
- **输出格式要求**
- Entity 层SysTimezone.java
- 修改现有文件SysUser.java添加 time_zone 字段)
- Utils 层TimeZoneUtils.java
- 注解:@TimeZoneConvert.java
- Aspect 层TimeZoneConvertAspect.java拦截 Service 方法返回值)
- Mapper 层SysTimezoneMapper.java、SysTimezoneMapper.xml
- Service 层ISysTimezoneService.java、SysTimezoneServiceImpl.java
- Controller 层SysTimezoneController.java
- 单元测试TimeZoneUtilsTest.java、SysTimezoneServiceTest.java
- **代码规范要求**:命名规范、注释规范、代码格式、导入规范
- **测试要求**:单元测试覆盖率 ≥ 80%、测试用例场景、测试框架、测试用例命名规范、测试数据
- **注意事项**时区转换、缓存、权限控制、日志记录、异常处理、性能优化、安全、AOP 切面
### 阶段 6代码生成
- **状态**:已完成
- **生成文档**[代码文档](../reference-code/2026-01-25-002-04-code-时区国际化功能.md)、[实施方案](../implementation/2026-01-25-002-04-implementation-时区国际化功能.md)
- **代码生成情况**
- **代码生成器生成**
- SysTimezone.java实体类
- SysTimezoneMapper.javaMapper 接口)
- SysTimezoneMapper.xmlMapper XML
- SysTimezoneService.javaService 接口基础方法)
- SysTimezoneServiceImpl.javaService 实现类基础方法)
- SysTimezoneController.javaController 类基础接口)
- **手动生成/扩展**
- ISysTimezoneService.java扩展方法getCurrentUserTimeZone、switchTimeZone、getDefaultTimeZone、getUserTimeZone
- SysTimezoneServiceImpl.java实现扩展方法包含 Redis 缓存逻辑
- TimeZoneUtils.java时区转换工具类convertUtcToTimeZone、convertTimeZoneToUtc、isValidTimeZone、getAllAvailableTimeZones、getDefaultTimeZone、getTimeZoneOffset
- @TimeZoneConvert.java时区转换注解
- TimeZoneConvertAspect.java时区转换切面拦截 Service 方法返回值)
- SysUser.java添加 time_zone 字段
- TimeZoneUtilsTest.java单元测试类
- **关键实现**
- 使用 AOP 切面拦截 Service 方法返回值进行时区转换
- Redis 缓存策略:时区列表缓存 24 小时,用户时区缓存 30 分钟
- 递归转换复杂对象中的 LocalDateTime 字段
- 异常处理无效时区使用默认时区null 值安全处理
- **修复的问题**
- I18nController.java 中的 TokenService 导入错误com.datai.web.service → com.datai.framework.web.service
- I18nController.java 中的 RedisUtils 导入错误(替换为 RedisCache
- RedisUtils.deleteObject() 方法不存在(使用 RedisCache.keys() + RedisCache.deleteObject(Collection)
- **补充的接口**
- GET /system/timezone/current获取当前用户时区
- POST /system/timezone/switch切换用户时区已添加 @Log 注解记录审计日志)
- GET /system/timezone/default获取系统默认时区已添加 @PreAuthorize 注解控制访问权限)
- **需求实现情况**
- ✅ 功能 1时区设置 - 已实现
- ✅ 功能 2时区转换 - 已实现
- ✅ 功能 3时区显示 - 已实现(新增接口)
- ✅ 功能 4时区列表管理 - 已实现
- ✅ 功能 5时区切换 - 已实现(新增接口,含审计日志)
- ✅ 功能 6时区缓存管理 - 已实现
- ⚠️ 功能 7时区优先级管理 - 部分实现(缺少租户时区支持)
- **未实现的需求**
- 租户时区支持(功能 7 的部分需求)
- 数据缓存策略(在原有缓存键基础上添加时区后缀)
### 阶段 7会话记录
- **状态**:已完成
- **生成文档**当前文档2026-01-25-002-04-session.md
- **更新内容**
- 更新阶段 6 的代码生成情况
- 记录补充的接口信息
- 记录需求实现情况
- 记录未实现的需求
### 阶段 8变更日志
- **状态**:待开始
- **生成文档**:待生成
### 阶段 9复盘与接口
- **状态**:待开始
- **生成文档**:待生成
### 阶段 10代码提交
- **状态**:待开始
- **生成文档**:待生成
## 关键设计决策
### 技术选型
1. **时区转换技术**Java TimeZone APIJava 21 内置)
- 理由Java 21 内置,无需引入额外依赖;支持所有 IANA 时区 ID自动处理夏令时转换性能优秀转换时间 < 10ms成熟稳定社区支持良好
2. **缓存技术**Redis
- 理由项目已集成 Redis无需额外配置性能优秀响应时间 < 1ms支持分布式部署支持自动过期机制丰富的数据结构支持
3. **数据库技术**MySQL 8.3.0
- 理由项目现有数据库支持 LocalDateTime 类型性能优秀支持高并发事务支持完善
4. **框架技术**Spring Boot 3.5.7 + 若依框架
- 理由项目现有框架Spring Boot 3.5.7 支持 Java 21若依框架提供完善的权限缓存日志等功能社区活跃文档完善
### 架构设计
1. **系统架构**前端层Vue 3)→ Controller Service Mapper 数据库层MySQL 8.3.0)→ 缓存层Redis
2. **模块架构**
- datai-admin启动模块Controller
- datai-system系统模块Service Mapper 实体类
- datai-common公共模块工具类切面
- datai-plugins插件模块Redis 缓存工具类
### 数据流设计
1. **用户登录流程**用户输入用户名密码 Controller 接收登录请求 Service 验证用户信息 从数据库读取用户时区偏好 Redis 缓存读取时区配置 将用户时区信息存储到 LoginUser 生成 Token 并返回 前端存储 Token 和时区信息
2. **数据查询流程**前端发起数据查询请求 Controller 接收请求 Service 层从数据库读取 UTC 时间 AOP 切面拦截响应 根据用户时区转换时间 返回转换后的时间 前端显示转换后的时间
3. **数据保存流程**前端发起数据保存请求携带用户时区时间)→ Controller 接收请求 Service 层接收用户时区时间 将用户时区时间转换为 UTC 时间 保存 UTC 时间到数据库 返回保存结果
4. **时区切换流程**用户选择新时区 Controller 接收时区切换请求 Service 验证时区 ID 有效性 更新用户时区偏好到数据库 清除 Redis 缓存 刷新 Token 前端刷新页面重新加载时间数据
### 数据模型设计
1. **时区表sys_timezone**
- 字段idtimezone_idtimezone_nametimezone_offsetis_defaultsort_orderstatuscreate_bycreate_timeupdate_byupdate_timeremark
- 索引主键索引id)、唯一索引timezone_id)、普通索引status)、普通索引sort_order
2. **用户表修改sys_user**
- 新增字段time_zone时区 ID
- 默认值Asia/Shanghai中国标准时间
- 位置 lang_code 字段之后
### 接口设计
1. **获取时区列表**GET /system/timezone/list
- 权限要求@PreAuthorize("@ss.hasPermi('system:timezone:list')")
- 响应数据时区列表包含 idtimezoneIdtimezoneNametimezoneOffsetisDefaultsortOrderstatus
2. **获取当前用户时区**GET /system/timezone/current
- 权限要求需要登录
- 响应数据时区 IDAsia/Shanghai
3. **切换时区**POST /system/timezone/switch
- 权限要求需要登录
- 请求参数timeZone时区 ID
- 响应数据成功/失败消息
4. **获取默认时区**GET /system/timezone/default
- 权限要求@PreAuthorize("@ss.hasPermi('system:timezone:query')")
- 响应数据默认时区 IDAsia/Shanghai
### 实现要点
1. **关键实现逻辑**
- 时区转换在 Service 层进行使用 AOP 切面拦截响应
- 使用 TimeZoneUtils 工具类进行时区转换
- 处理 null 避免空指针异常
- 处理无效时区 ID使用默认时区
2. **异常处理设计**
- 时区转换异常捕获异常记录日志返回 UTC 时间提示用户时区转换失败
- 时区 ID 无效异常捕获异常记录日志返回错误响应提示用户时区 ID 无效
- 缓存读取异常捕获异常记录日志从数据库重新加载数据提示用户缓存读取失败
3. **性能优化设计**
- 缓存优化使用 Redis 缓存时区配置时区列表缓存 24 小时用户时区缓存 30 分钟
- 索引优化为常用查询字段创建索引提高查询性能
- 批量转换优化批量转换时使用并行处理合理设置线程池大小
4. **安全设计**
- 数据验证验证时区 ID 有效性格式白名单
- 权限控制使用 @PreAuthorize 注解控制接口权限使用数据权限控制数据访问范围
- 审计日志使用 @Log 注解记录操作日志记录操作人操作时间操作内容
## 相关文档
- [需求文档](../requirements/2026-01-21-002-04-时区国际化需求.md)
- [设计文档](../design/2026-01-21-002-04-时区国际化设计.md)