docs: 完成日期格式化功能文档(需求、设计、决策、提示词、复盘、API)
This commit is contained in:
parent
238764c694
commit
dd3c36207e
@ -0,0 +1,386 @@
|
||||
# API 文档:日期格式化功能
|
||||
|
||||
## 元数据
|
||||
- API 文档编号:2026-01-25-002-06-api
|
||||
- 需求编号:2026-01-21-002-06
|
||||
- 功能名称:日期格式化功能
|
||||
- 版本:3.8.5
|
||||
- 发布日期:2026-01-25
|
||||
- 状态:已完成
|
||||
- 阶段:阶段 9:复盘与接口
|
||||
|
||||
## API 概述
|
||||
|
||||
日期格式化功能提供了用户日期格式偏好管理和系统默认日期格式查询的 API 接口,支持用户设置和切换日期格式偏好,获取系统默认日期格式和常用日期格式列表。
|
||||
|
||||
## API 列表
|
||||
|
||||
### 1. 获取当前用户日期格式偏好
|
||||
|
||||
#### 接口信息
|
||||
- **接口路径**:/system/user/dateFormat
|
||||
- **请求方法**:GET
|
||||
- **接口描述**:获取当前用户日期格式偏好
|
||||
- **权限要求**:需要登录
|
||||
- **接口分类**:用户管理
|
||||
|
||||
#### 请求参数
|
||||
无
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"userId": 1,
|
||||
"dateFormat": "ISO_8601",
|
||||
"dateFormatPattern": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应字段说明
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| code | Integer | 响应码,200 表示成功 |
|
||||
| msg | String | 响应消息 |
|
||||
| data | Object | 响应数据 |
|
||||
| data.userId | Long | 用户 ID |
|
||||
| data.dateFormat | String | 日期格式模式(ISO_8601、US、EU、CN、JP、KR、SHORT、LONG、CUSTOM) |
|
||||
| data.dateFormatPattern | String | 自定义日期格式(当 dateFormat 为 CUSTOM 时有效) |
|
||||
|
||||
#### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"msg": "用户未登录"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 切换用户日期格式偏好
|
||||
|
||||
#### 接口信息
|
||||
- **接口路径**:/system/user/switchDateFormat
|
||||
- **请求方法**:POST
|
||||
- **接口描述**:切换用户日期格式偏好
|
||||
- **权限要求**:需要登录
|
||||
- **接口分类**:用户管理
|
||||
|
||||
#### 请求参数
|
||||
```json
|
||||
{
|
||||
"dateFormat": "ISO_8601",
|
||||
"dateFormatPattern": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 请求字段说明
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| dateFormat | String | 是 | 日期格式模式(ISO_8601、US、EU、CN、JP、KR、SHORT、LONG、CUSTOM) |
|
||||
| dateFormatPattern | String | 条件必填 | 自定义日期格式(当 dateFormat 为 CUSTOM 时必填) |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "日期格式模式不能为空"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "自定义日期格式不能为空"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取系统默认日期格式
|
||||
|
||||
#### 接口信息
|
||||
- **接口路径**:/system/config/defaultDateFormat
|
||||
- **请求方法**:GET
|
||||
- **接口描述**:获取系统默认日期格式
|
||||
- **权限要求**:需要登录
|
||||
- **接口分类**:配置管理
|
||||
|
||||
#### 请求参数
|
||||
无
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"dateTimeFormat": "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应字段说明
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| code | Integer | 响应码,200 表示成功 |
|
||||
| msg | String | 响应消息 |
|
||||
| data | Object | 响应数据 |
|
||||
| data.dateFormat | String | 系统默认日期格式 |
|
||||
| data.dateTimeFormat | String | 系统默认日期时间格式 |
|
||||
|
||||
#### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "获取系统默认日期格式失败"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 获取常用日期格式列表
|
||||
|
||||
#### 接口信息
|
||||
- **接口路径**:/system/config/commonDateFormats
|
||||
- **请求方法**:GET
|
||||
- **接口描述**:获取常用日期格式列表
|
||||
- **权限要求**:需要登录
|
||||
- **接口分类**:配置管理
|
||||
|
||||
#### 请求参数
|
||||
无
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"dateFormats": {
|
||||
"ISO_8601": "yyyy-MM-dd",
|
||||
"US": "MM/dd/yyyy",
|
||||
"EU": "dd/MM/yyyy",
|
||||
"CN": "yyyy年MM月dd日",
|
||||
"JP": "yyyy/MM/dd",
|
||||
"KR": "yyyy. MM. dd.",
|
||||
"SHORT": "yy/MM/dd"
|
||||
},
|
||||
"dateTimeFormats": {
|
||||
"ISO_8601": "yyyy-MM-dd HH:mm:ss",
|
||||
"US": "MM/dd/yyyy HH:mm:ss",
|
||||
"EU": "dd/MM/yyyy HH:mm:ss",
|
||||
"CN": "yyyy年MM月dd日 HH:mm:ss",
|
||||
"JP": "yyyy/MM/dd HH:mm:ss",
|
||||
"KR": "yyyy. MM. dd. HH:mm:ss",
|
||||
"SHORT": "yy/MM/dd HH:mm"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应字段说明
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| code | Integer | 响应码,200 表示成功 |
|
||||
| msg | String | 响应消息 |
|
||||
| data | Object | 响应数据 |
|
||||
| data.dateFormats | Object | 常用日期格式列表 |
|
||||
| data.dateTimeFormats | Object | 常用日期时间格式列表 |
|
||||
|
||||
#### 日期格式说明
|
||||
| 格式代码 | 日期格式 | 日期时间格式 | 说明 |
|
||||
|---------|---------|-------------|------|
|
||||
| ISO_8601 | yyyy-MM-dd | yyyy-MM-dd HH:mm:ss | ISO 8601 标准格式 |
|
||||
| US | MM/dd/yyyy | MM/dd/yyyy HH:mm:ss | 美国格式 |
|
||||
| EU | dd/MM/yyyy | dd/MM/yyyy HH:mm:ss | 欧洲格式 |
|
||||
| CN | yyyy年MM月dd日 | yyyy年MM月dd日 HH:mm:ss | 中国格式 |
|
||||
| JP | yyyy/MM/dd | yyyy/MM/dd HH:mm:ss | 日本格式 |
|
||||
| KR | yyyy. MM. dd. | yyyy. MM. dd. HH:mm:ss | 韩国格式 |
|
||||
| SHORT | yy/MM/dd | yy/MM/dd HH:mm | 短格式 |
|
||||
| LONG | yyyy年MM月dd日 EEEE | yyyy年MM月dd日 EEEE HH:mm:ss | 长格式 |
|
||||
| CUSTOM | 自定义 | 自定义 | 自定义格式 |
|
||||
|
||||
#### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "获取常用日期格式列表失败"
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1:获取当前用户日期格式偏好
|
||||
|
||||
#### 请求
|
||||
```http
|
||||
GET /system/user/dateFormat
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"userId": 1,
|
||||
"dateFormat": "ISO_8601",
|
||||
"dateFormatPattern": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例 2:切换用户日期格式偏好为美国格式
|
||||
|
||||
#### 请求
|
||||
```http
|
||||
POST /system/user/switchDateFormat
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"dateFormat": "US",
|
||||
"dateFormatPattern": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 示例 3:切换用户日期格式偏好为自定义格式
|
||||
|
||||
#### 请求
|
||||
```http
|
||||
POST /system/user/switchDateFormat
|
||||
Authorization: Bearer {token}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"dateFormat": "CUSTOM",
|
||||
"dateFormatPattern": "yyyy/MM/dd"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 示例 4:获取系统默认日期格式
|
||||
|
||||
#### 请求
|
||||
```http
|
||||
GET /system/config/defaultDateFormat
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"dateTimeFormat": "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例 5:获取常用日期格式列表
|
||||
|
||||
#### 请求
|
||||
```http
|
||||
GET /system/config/commonDateFormats
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
#### 响应
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"dateFormats": {
|
||||
"ISO_8601": "yyyy-MM-dd",
|
||||
"US": "MM/dd/yyyy",
|
||||
"EU": "dd/MM/yyyy",
|
||||
"CN": "yyyy年MM月dd日",
|
||||
"JP": "yyyy/MM/dd",
|
||||
"KR": "yyyy. MM. dd.",
|
||||
"SHORT": "yy/MM/dd"
|
||||
},
|
||||
"dateTimeFormats": {
|
||||
"ISO_8601": "yyyy-MM-dd HH:mm:ss",
|
||||
"US": "MM/dd/yyyy HH:mm:ss",
|
||||
"EU": "dd/MM/yyyy HH:mm:ss",
|
||||
"CN": "yyyy年MM月dd日 HH:mm:ss",
|
||||
"JP": "yyyy/MM/dd HH:mm:ss",
|
||||
"KR": "yyyy. MM. dd. HH:mm:ss",
|
||||
"SHORT": "yy/MM/dd HH:mm"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 日期格式模式
|
||||
- 日期格式模式必须是预定义的格式之一(ISO_8601、US、EU、CN、JP、KR、SHORT、LONG、CUSTOM)
|
||||
- 当日期格式模式为 CUSTOM 时,必须提供自定义日期格式
|
||||
- 自定义日期格式必须符合 Java DateTimeFormatter 的格式规范
|
||||
|
||||
### 2. 缓存机制
|
||||
- 用户日期格式偏好使用 Redis 缓存,TTL 为 24 小时
|
||||
- 用户切换日期格式偏好时,缓存会立即清除
|
||||
- 获取用户日期格式偏好时,会优先从缓存中读取
|
||||
|
||||
### 3. 权限控制
|
||||
- 所有接口都需要登录
|
||||
- 用户只能设置和获取自己的日期格式偏好
|
||||
- 系统默认日期格式和常用日期格式列表对所有登录用户可见
|
||||
|
||||
### 4. 错误处理
|
||||
- 日期格式模式不能为空时,返回错误提示
|
||||
- 自定义日期格式不能为空时(当 dateFormat 为 CUSTOM 时),返回错误提示
|
||||
- 用户未登录时,返回 401 错误码
|
||||
|
||||
### 5. 时区转换
|
||||
- 日期格式化会结合时区转换功能,先进行时区转换,再进行日期格式化
|
||||
- 时区转换切面(TimeZoneConvertAspect)的执行顺序优先于日期格式化切面(DateFormatAspect)
|
||||
|
||||
### 6. 多语言支持
|
||||
- 日期格式化支持多语言日期显示(星期几、月份名称的本地化)
|
||||
- 根据用户语言偏好自动选择语言
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [需求文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/requirements/2026-01-21-002-06-日期格式化需求.md)
|
||||
- [设计文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/design/2026-01-21-002-06-日期格式化设计.md)
|
||||
- [架构决策记录](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/decisions/adr/2026-01-25-002-06-ADR-日期格式化技术选型.md)
|
||||
- [SQL 脚本](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/sql/2026-01-25-002-06-日期格式化.sql)
|
||||
- [提示词文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/prompts/2026-01-25-002-06-prompt-日期格式化功能.md)
|
||||
- [会话记录](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/sessions/2026-01-25-002-06-session.md)
|
||||
- [变更日志](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/changelog/2026-01-25-002-06-changelog.md)
|
||||
- [复盘文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/retros/2026-01-25-002-06-retro.md)
|
||||
|
||||
## 更新记录
|
||||
|
||||
| 版本 | 日期 | 更新内容 | 更新人 |
|
||||
|------|------|---------|--------|
|
||||
| 3.8.5 | 2026-01-25 | 初始版本 | SSOT 架构师 |
|
||||
@ -0,0 +1,250 @@
|
||||
# 架构决策记录 (ADR) - 日期格式化技术选型
|
||||
|
||||
## 背景
|
||||
|
||||
在项目国际化需求(REQ-002)中,需要实现日期格式化功能,支持用户设置日期格式偏好,根据用户日期格式偏好显示日期。该功能需要满足以下核心需求:
|
||||
|
||||
1. **多日期格式支持**:支持常用日期格式(如:ISO 8601、欧洲、美国、中国等)
|
||||
2. **自动格式化**:自动根据用户日期格式偏好格式化日期数据
|
||||
3. **时区转换**:结合时区转换功能,支持跨时区日期格式化
|
||||
4. **多语言支持**:支持多语言日期显示(如:星期几、月份名称的本地化)
|
||||
5. **高性能**:日期格式化响应时间 < 10ms
|
||||
6. **易维护**:代码结构清晰,易于扩展和维护
|
||||
7. **兼容性**:与现有 Spring Boot 3.5.7 + 若依框架集成良好
|
||||
|
||||
当前系统所有日期数据使用 Date、LocalDate、LocalDateTime 类型存储,需要在 Service 层进行日期格式化,结合时区转换功能,使用 Redis 缓存用户日期格式偏好,支持用户 > 系统的日期格式优先级。
|
||||
|
||||
## 决策
|
||||
|
||||
### 决策 1:日期格式化技术选择
|
||||
|
||||
**选定方案**:java.time.format.DateTimeFormatter
|
||||
|
||||
**选择理由**:
|
||||
1. **无额外依赖**:Java 8+ 内置,无需引入额外依赖,减少项目复杂度
|
||||
2. **功能完整**:支持日期、日期时间、时区、本地化等格式化需求
|
||||
3. **国际化支持**:支持国际化格式化,自动处理不同地区的日期格式
|
||||
4. **性能优秀**:格式化时间 < 1ms,满足性能要求
|
||||
5. **线程安全**:DateTimeFormatter 是线程安全的,可以在多线程环境下安全使用
|
||||
6. **与框架兼容**:与 Spring Boot 3.5.7(支持 Java 21)完美集成
|
||||
7. **自定义灵活**:支持自定义格式化模式,满足不同业务场景
|
||||
8. **类型安全**:支持 LocalDate、LocalDateTime 等类型安全的日期时间类型
|
||||
|
||||
**实现方案**:
|
||||
- 使用 DateTimeFormatter 的模式字符串定义日期格式(例如:`yyyy-MM-dd`)
|
||||
- 使用 ofPattern() 方法创建格式化器
|
||||
- 使用 withLocale() 方法设置语言环境
|
||||
- 使用 ConcurrentHashMap 缓存 DateTimeFormatter 实例,提高性能
|
||||
- 支持自定义日期格式模式
|
||||
|
||||
**放弃方案的原因**:
|
||||
|
||||
**方案 A:Intl.DateTimeFormat(JavaScript 库)**
|
||||
- **放弃原因**:
|
||||
- 这是 JavaScript 库,不适用于 Java 后端
|
||||
- 需要在前端实现,无法保证格式化逻辑的一致性
|
||||
- 无法在 Service 层统一处理
|
||||
|
||||
**方案 B:SimpleDateFormat**
|
||||
- **放弃原因**:
|
||||
- SimpleDateFormat 不是线程安全的,在多线程环境下使用需要同步
|
||||
- 性能较差,每次格式化都需要创建新实例
|
||||
- 已被 DateTimeFormatter 取代,属于过时的 API
|
||||
- 不支持 LocalDate、LocalDateTime 等新日期时间类型
|
||||
|
||||
**方案 C:第三方库(如:Joda-Time)**
|
||||
- **放弃原因**:
|
||||
- 需要引入额外依赖,增加项目复杂度
|
||||
- Java 8+ 已内置了类似的功能,引入价值不大
|
||||
- Joda-Time 已停止维护,不再推荐使用
|
||||
|
||||
**方案 D:自定义格式化实现**
|
||||
- **放弃原因**:
|
||||
- 需要手动维护格式化规则,维护成本高
|
||||
- 难以处理国际化格式化规则
|
||||
- 容易出现格式化错误,影响数据准确性
|
||||
- 开发成本高,风险大
|
||||
|
||||
### 决策 2:缓存策略选择
|
||||
|
||||
**选定方案**:Redis 缓存
|
||||
|
||||
**选择理由**:
|
||||
1. **已集成**:项目已集成 Redis,无需额外配置和部署
|
||||
2. **性能优秀**:Redis 响应时间 < 1ms,满足高性能要求
|
||||
3. **分布式支持**:支持分布式部署,多实例共享缓存
|
||||
4. **自动过期**:支持自动过期机制,无需手动清理过期数据
|
||||
5. **数据结构丰富**:支持 String、Hash、List 等多种数据结构
|
||||
6. **持久化**:支持数据持久化,防止数据丢失
|
||||
|
||||
**缓存策略设计**:
|
||||
- **用户日期格式缓存**:Key = `sys:date:format:{userId}`,TTL = 24 小时
|
||||
- **系统默认日期格式缓存**:Key = `sys:default:date:format`,TTL = 24 小时
|
||||
- **DateTimeFormatter 缓存**:使用 ConcurrentHashMap 缓存,永久缓存
|
||||
|
||||
**放弃方案的原因**:
|
||||
|
||||
**方案 A:Caffeine 本地缓存**
|
||||
- **放弃原因**:
|
||||
- 本地缓存无法在分布式环境下共享,多实例数据不一致
|
||||
- 需要引入额外依赖(caffeine)
|
||||
- 缓存更新需要手动同步,实现复杂
|
||||
|
||||
**方案 B:数据库缓存**
|
||||
- **放弃原因**:
|
||||
- 数据库查询响应时间 > 10ms,性能较差
|
||||
- 高并发场景下数据库压力大,影响系统性能
|
||||
- 无法自动过期,需要手动清理过期数据
|
||||
|
||||
### 决策 3:日期格式化层选择
|
||||
|
||||
**选定方案**:Service 层转换 + AOP 切面拦截
|
||||
|
||||
**选择理由**:
|
||||
1. **统一处理**:在 Service 层统一处理日期格式化,避免代码重复
|
||||
2. **AOP 切面**:使用 AOP 切面拦截 Service 方法返回值,自动格式化日期字段,无需手动调用
|
||||
3. **覆盖面广**:所有调用 Service 的地方都会触发格式化(Controller、定时任务、内部调用等)
|
||||
4. **业务逻辑分离**:日期格式化逻辑与业务逻辑分离,代码清晰
|
||||
5. **易于维护**:修改日期格式化逻辑只需修改切面代码,影响范围小
|
||||
6. **性能优化**:AOP 切面在编译时织入,运行时性能损失小
|
||||
7. **自动识别**:自动识别 Date、LocalDate、LocalDateTime 类型字段,减少手动标注的工作量
|
||||
8. **时区转换集成**:与 TimeZoneConvertAspect 配合,先进行时区转换,再进行日期格式化
|
||||
|
||||
**实现方案**:
|
||||
- **响应格式化**:使用 `@Around` 切面拦截 Service 方法返回值,自动格式化日期字段
|
||||
- **注解支持**:提供 `@DateFormat` 注解,标记需要格式化的 Service 方法
|
||||
- **递归处理**:递归处理嵌套对象的日期字段
|
||||
- **循环引用处理**:避免循环引用导致的无限递归
|
||||
- **多类型支持**:支持 Date、LocalDate、LocalDateTime 等多种日期时间类型
|
||||
|
||||
**放弃方案的原因**:
|
||||
|
||||
**方案 A:Controller 层格式化 + AOP 切面拦截**
|
||||
- **放弃原因**:
|
||||
- 只有接口请求会触发格式化,定时任务、内部调用等不会触发
|
||||
- 如果有多个地方需要格式化,可能需要多个切面
|
||||
- 格式化逻辑分散,难以统一管理
|
||||
|
||||
**方案 B:手动调用格式化方法**
|
||||
- **放弃原因**:
|
||||
- 需要在每个需要格式化的地方手动调用,代码重复
|
||||
- 容易遗漏,导致格式化不一致
|
||||
- 维护成本高,修改格式化逻辑需要修改多处代码
|
||||
|
||||
### 决策 4:时区转换方案
|
||||
|
||||
**选定方案**:复用已实现的时区转换功能(TimeZoneConvertAspect)
|
||||
|
||||
**选择理由**:
|
||||
1. **避免重复开发**:时区转换功能已在 2026-01-21-002-04 中实现,无需重复开发
|
||||
2. **功能完整**:已实现的时区转换功能完整,满足跨时区日期格式化需求
|
||||
3. **代码复用**:复用现有代码,减少开发成本和维护成本
|
||||
4. **一致性**:与现有时区转换功能保持一致,避免功能不一致
|
||||
5. **集成简单**:DateFormatAspect 与 TimeZoneConvertAspect 配合使用,集成简单
|
||||
|
||||
**实现方案**:
|
||||
- **时区转换优先**:TimeZoneConvertAspect 先进行时区转换,将 UTC 时间转换为用户时区
|
||||
- **日期格式化后置**:DateFormatAspect 后进行日期格式化,根据用户日期格式偏好格式化日期
|
||||
- **切面顺序**:使用 @Order 注解控制切面执行顺序,确保时区转换先于日期格式化
|
||||
|
||||
**放弃方案的原因**:
|
||||
|
||||
**方案 A:在 DateFormatAspect 中实现时区转换**
|
||||
- **放弃原因**:
|
||||
- 重复开发时区转换功能,增加开发成本
|
||||
- 与现有时区转换功能不一致,可能导致功能冲突
|
||||
- 维护成本高,需要同时维护两套时区转换逻辑
|
||||
|
||||
**方案 B:不进行时区转换**
|
||||
- **放弃原因**:
|
||||
- 无法满足跨时区日期格式化需求
|
||||
- 不同时区的用户看到的日期时间不一致,影响用户体验
|
||||
|
||||
### 决策 5:多语言支持方案
|
||||
|
||||
**选定方案**:结合国际化功能,使用 Locale 进行本地化
|
||||
|
||||
**选择理由**:
|
||||
1. **避免重复开发**:国际化功能已在 2026-01-21-002-03 中实现,无需重复开发
|
||||
2. **功能完整**:已实现的国际化功能完整,满足多语言日期显示需求
|
||||
3. **代码复用**:复用现有代码,减少开发成本和维护成本
|
||||
4. **一致性**:与现有国际化功能保持一致,避免功能不一致
|
||||
5. **集成简单**:DateFormatAspect 结合国际化功能,使用 Locale 进行本地化
|
||||
|
||||
**实现方案**:
|
||||
- **语言偏好读取**:从 SysUser 表读取用户语言偏好(lang 字段)
|
||||
- **Locale 创建**:根据语言偏好创建 Locale 实例(例如:zh_CN、en_US)
|
||||
- **本地化格式化**:使用 DateTimeFormatter.withLocale() 方法设置语言环境
|
||||
- **多语言支持**:支持星期几、月份名称的本地化显示
|
||||
|
||||
**放弃方案的原因**:
|
||||
|
||||
**方案 A:在 DateFormatAspect 中实现多语言支持**
|
||||
- **放弃原因**:
|
||||
- 重复开发多语言支持功能,增加开发成本
|
||||
- 与现有国际化功能不一致,可能导致功能冲突
|
||||
- 维护成本高,需要同时维护两套国际化逻辑
|
||||
|
||||
**方案 B:不进行多语言支持**
|
||||
- **放弃原因**:
|
||||
- 无法满足多语言日期显示需求
|
||||
- 不同语言环境的用户看到的日期显示不一致,影响用户体验
|
||||
|
||||
### 决策 6:数据类型选择
|
||||
|
||||
**选定方案**:Date、LocalDate、LocalDateTime
|
||||
|
||||
**选择理由**:
|
||||
1. **类型安全**:LocalDate、LocalDateTime 是类型安全的日期时间类型,避免混淆
|
||||
2. **线程安全**:LocalDate、LocalDateTime 是不可变的,线程安全
|
||||
3. **API 优秀**:Java 8+ 的日期时间 API 设计优秀,易于使用
|
||||
4. **与现有代码一致**:项目中已有的日期数据使用 Date、LocalDate、LocalDateTime 类型,保持一致
|
||||
5. **功能完整**:支持日期、日期时间、时区等多种场景
|
||||
6. **性能优秀**:LocalDate、LocalDateTime 性能优秀,满足高性能要求
|
||||
|
||||
**实现方案**:
|
||||
- **日期字段**:使用 LocalDate 类型表示日期(例如:出生日期)
|
||||
- **日期时间字段**:使用 LocalDateTime 类型表示日期时间(例如:创建时间)
|
||||
- **兼容旧代码**:保留 Date 类型,兼容旧代码
|
||||
- **类型转换**:提供 Date、LocalDate、LocalDateTime 之间的转换方法
|
||||
|
||||
**放弃方案的原因**:
|
||||
|
||||
**方案 A:仅使用 Date**
|
||||
- **放弃原因**:
|
||||
- Date 不是线程安全的,在多线程环境下使用需要同步
|
||||
- Date 的 API 设计不佳,易于出错
|
||||
- Date 不支持不可变性,容易导致数据不一致
|
||||
|
||||
**方案 B:仅使用 LocalDate、LocalDateTime**
|
||||
- **放弃原因**:
|
||||
- 与现有代码不一致,需要大量重构
|
||||
- 旧代码可能依赖 Date 类型,兼容性问题
|
||||
|
||||
**方案 C:仅使用 Long(时间戳)**
|
||||
- **放弃原因**:
|
||||
- 不直观,可读性差
|
||||
- 显示时需要格式化,增加处理逻辑
|
||||
- 与现有代码不一致
|
||||
|
||||
## 后果
|
||||
|
||||
### 正面后果
|
||||
1. **无额外依赖**:使用 Java 内置库和已集成的 Redis,无需引入额外依赖
|
||||
2. **高性能**:日期格式化响应时间 < 10ms,满足性能要求
|
||||
3. **易维护**:代码结构清晰,格式化逻辑统一,易于扩展和维护
|
||||
4. **数据准确**:使用 DateTimeFormatter 保证格式化准确性
|
||||
5. **分布式支持**:Redis 缓存支持分布式部署,多实例共享缓存
|
||||
6. **功能完整**:支持多日期格式、时区转换、多语言显示等核心功能
|
||||
7. **代码复用**:复用已实现的时区转换和国际化功能,减少开发成本
|
||||
8. **类型安全**:使用 LocalDate、LocalDateTime 等类型安全的日期时间类型
|
||||
|
||||
### 负面后果
|
||||
1. **AOP 学习成本**:开发人员需要了解 AOP 的使用方式
|
||||
2. **切面顺序控制**:需要控制 DateFormatAspect 和 TimeZoneConvertAspect 的执行顺序
|
||||
3. **缓存一致性**:分布式环境下需要保证缓存一致性
|
||||
4. **多类型支持**:需要支持 Date、LocalDate、LocalDateTime 等多种日期时间类型,增加实现复杂度
|
||||
|
||||
## 相关文档
|
||||
- [需求文档](../requirements/2026-01-21-002-06-日期格式化需求.md)
|
||||
- [设计文档](../design/2026-01-21-002-06-日期格式化设计.md)
|
||||
@ -0,0 +1,640 @@
|
||||
# 设计文档:日期格式化功能
|
||||
|
||||
## 元数据
|
||||
- 需求编号:2026-01-21-002-06
|
||||
- 创建时间:2026-01-25
|
||||
- 创建人:SSOT 架构师
|
||||
- 状态:进行中
|
||||
- 父需求:2026-01-21-002-项目国际化需求
|
||||
|
||||
## 设计概述
|
||||
基于若依框架和 Spring Boot,实现日期格式化功能,支持用户设置日期格式偏好,根据用户日期格式偏好显示日期。后端使用 Java 的 DateTimeFormatter 进行日期格式化,结合时区转换功能,支持跨时区日期格式化。支持多语言日期显示,使用 AOP 在 Service 层自动进行日期格式化。支持用户 > 系统的日期格式优先级,日期格式偏好存储在数据库中,支持实时更新。
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 系统架构图
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 前端层 (Vue 3) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ 日期选择器 │ │ 日期显示组件 │ │ 相对时间组件 │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓ HTTP/RESTful
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Controller 层 │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │SysUser │ │SysConfig │ │SysTimeZone │ │
|
||||
│ │Controller │ │Controller │ │Controller │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Service 层 │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │SysUser │ │SysConfig │ │SysTimeZone │ │
|
||||
│ │ServiceImpl │ │ServiceImpl │ │ServiceImpl │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │DateFormatUtils│ │TimeZoneUtils│ │AOP 切面 │ │
|
||||
│ │(工具类) │ │(工具类) │ │(日期格式化) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Mapper 层 (MyBatis) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │SysUser │ │SysConfig │ │SysTimeZone │ │
|
||||
│ │Mapper │ │Mapper │ │Mapper │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 数据库层 (MySQL 8.3.0) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │sys_user │ │sys_config │ │sys_timezone │ │
|
||||
│ │(用户表) │ │(配置表) │ │(时区表) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 缓存层 (Redis) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │sys:date: │ │sys:timezone: │ │sys:default: │ │
|
||||
│ │format:{userId}│ │{userId} │ │date:format │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 模块架构设计
|
||||
```
|
||||
datai-admin (启动模块)
|
||||
└─ com.datai.admin.controller
|
||||
├─ SysUserController (用户控制器)
|
||||
├─ SysConfigController (配置控制器)
|
||||
└─ SysTimeZoneController (时区控制器)
|
||||
|
||||
datai-system (系统模块)
|
||||
└─ com.datai.system
|
||||
├─ domain (实体类)
|
||||
│ ├─ SysUser (用户实体)
|
||||
│ ├─ SysConfig (配置实体)
|
||||
│ └─ SysTimeZone (时区实体)
|
||||
├─ service (服务层)
|
||||
│ ├─ ISysUserService / SysUserServiceImpl
|
||||
│ ├─ ISysConfigService / SysConfigServiceImpl
|
||||
│ └─ ISysTimeZoneService / SysTimeZoneServiceImpl
|
||||
└─ mapper (数据访问层)
|
||||
├─ SysUserMapper / SysUserMapper.xml
|
||||
├─ SysConfigMapper / SysConfigMapper.xml
|
||||
└─ SysTimeZoneMapper / SysTimeZoneMapper.xml
|
||||
|
||||
datai-common (公共模块)
|
||||
└─ com.datai.common
|
||||
├─ core.domain.entity (实体类)
|
||||
│ └─ SysUser (用户实体)
|
||||
├─ utils (工具类)
|
||||
│ └─ DateFormatUtils (日期格式化工具类)
|
||||
├─ constant (常量)
|
||||
│ └─ DateConstants (日期常量)
|
||||
└─ aspect (切面)
|
||||
└─ DateFormatAspect (日期格式化切面)
|
||||
|
||||
datai-framework (框架模块)
|
||||
└─ com.datai.framework
|
||||
└─ aspectj
|
||||
├─ TimeZoneConvertAspect (时区转换切面)
|
||||
└─ DateFormatAspect (日期格式化切面)
|
||||
|
||||
datai-plugins (插件模块)
|
||||
└─ com.datai.middleware.redis
|
||||
```
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### sys_user 表扩展
|
||||
```sql
|
||||
ALTER TABLE `sys_user`
|
||||
ADD COLUMN `date_format` varchar(50) NULL COMMENT '日期格式' AFTER `currency_code`,
|
||||
ADD COLUMN `date_format_pattern` varchar(100) NULL COMMENT '日期格式模式' AFTER `date_format`;
|
||||
```
|
||||
|
||||
### sys_config 表配置
|
||||
```sql
|
||||
INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `config_type`, `remark`) VALUES
|
||||
('系统默认日期格式', 'sys.default.date.format', 'yyyy-MM-dd', 'Y', '系统默认日期格式'),
|
||||
('系统默认日期格式模式', 'sys.default.date.format.pattern', 'yyyy年MM月dd日', 'Y', '系统默认日期格式模式'),
|
||||
('系统默认日期时间格式', 'sys.default.datetime.format', 'yyyy-MM-dd HH:mm:ss', 'Y', '系统默认日期时间格式'),
|
||||
('系统默认日期时间格式模式', 'sys.default.datetime.format.pattern', 'yyyy年MM月dd日 HH时mm分ss秒', 'Y', '系统默认日期时间格式模式');
|
||||
```
|
||||
|
||||
### 常用日期格式配置
|
||||
```sql
|
||||
INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `config_type`, `remark`) VALUES
|
||||
('ISO 8601 日期格式', 'sys.date.format.iso8601', 'yyyy-MM-dd', 'Y', 'ISO 8601 日期格式'),
|
||||
('欧洲日期格式', 'sys.date.format.europe', 'dd/MM/yyyy', 'Y', '欧洲日期格式'),
|
||||
('美国日期格式', 'sys.date.format.usa', 'MM/dd/yyyy', 'Y', '美国日期格式'),
|
||||
('中国日期格式', 'sys.date.format.china', 'yyyy年MM月dd日', 'Y', '中国日期格式');
|
||||
```
|
||||
|
||||
## 接口设计
|
||||
|
||||
### 用户日期格式偏好接口
|
||||
|
||||
#### 1. 获取当前用户日期格式偏好
|
||||
- **接口路径**:GET /system/user/dateFormat
|
||||
- **权限要求**:无(需要登录)
|
||||
- **请求参数**:无
|
||||
- **响应参数**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"dateFormatPattern": "yyyy年MM月dd日"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 切换用户日期格式偏好
|
||||
- **接口路径**:POST /system/user/switchDateFormat
|
||||
- **权限要求**:无(需要登录)
|
||||
- **请求参数**:
|
||||
```json
|
||||
{
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"dateFormatPattern": "yyyy年MM月dd日"
|
||||
}
|
||||
```
|
||||
- **响应参数**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "日期格式切换成功"
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 获取系统默认日期格式
|
||||
- **接口路径**:GET /system/dateFormat/default
|
||||
- **权限要求**:system:config:query
|
||||
- **请求参数**:无
|
||||
- **响应参数**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"dateFormatPattern": "yyyy年MM月dd日",
|
||||
"datetimeFormat": "yyyy-MM-dd HH:mm:ss",
|
||||
"datetimeFormatPattern": "yyyy年MM月dd日 HH时mm分ss秒"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 获取常用日期格式列表
|
||||
- **接口路径**:GET /system/dateFormat/list
|
||||
- **权限要求**:system:config:query
|
||||
- **请求参数**:无
|
||||
- **响应参数**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"key": "iso8601",
|
||||
"name": "ISO 8601",
|
||||
"format": "yyyy-MM-dd",
|
||||
"example": "2026-01-21"
|
||||
},
|
||||
{
|
||||
"key": "europe",
|
||||
"name": "欧洲格式",
|
||||
"format": "dd/MM/yyyy",
|
||||
"example": "21/01/2026"
|
||||
},
|
||||
{
|
||||
"key": "usa",
|
||||
"name": "美国格式",
|
||||
"format": "MM/dd/yyyy",
|
||||
"example": "01/21/2026"
|
||||
},
|
||||
{
|
||||
"key": "china",
|
||||
"name": "中国格式",
|
||||
"format": "yyyy年MM月dd日",
|
||||
"example": "2026年01月21日"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 核心流程设计
|
||||
|
||||
### 日期格式化流程
|
||||
```
|
||||
1. Service 层返回数据
|
||||
↓
|
||||
2. TimeZoneConvertAspect 切面拦截返回值(时区转换)
|
||||
↓
|
||||
3. 将 UTC 时间转换为用户时区
|
||||
↓
|
||||
4. DateFormatAspect 切面拦截返回值(日期格式化)
|
||||
↓
|
||||
5. 识别 Date、LocalDate、LocalDateTime 类型字段
|
||||
↓
|
||||
6. 读取用户日期格式偏好(date_format)
|
||||
↓
|
||||
7. 如果用户未设置日期格式,读取系统默认日期格式
|
||||
↓
|
||||
8. 根据日期格式偏好获取 DateTimeFormatter 实例
|
||||
↓
|
||||
9. 格式化日期字段
|
||||
↓
|
||||
10. 根据用户语言偏好本地化日期(星期几、月份名称)
|
||||
↓
|
||||
11. 返回格式化后的数据
|
||||
```
|
||||
|
||||
### 日期格式偏好切换流程
|
||||
```
|
||||
1. 用户选择日期格式
|
||||
↓
|
||||
2. 前端发送切换请求
|
||||
↓
|
||||
3. 后端验证用户身份
|
||||
↓
|
||||
4. 更新数据库中的用户日期格式偏好
|
||||
↓
|
||||
5. 清除 Redis 缓存
|
||||
↓
|
||||
6. 记录审计日志
|
||||
↓
|
||||
7. 返回切换成功
|
||||
```
|
||||
|
||||
### 多语言日期显示流程
|
||||
```
|
||||
1. 读取用户语言偏好
|
||||
↓
|
||||
2. 根据语言偏好创建 Locale 实例
|
||||
↓
|
||||
3. 使用 DateTimeFormatter.ofPattern() 创建格式化器
|
||||
↓
|
||||
4. 使用 withLocale() 设置语言环境
|
||||
↓
|
||||
5. 格式化日期
|
||||
↓
|
||||
6. 返回本地化后的日期
|
||||
```
|
||||
|
||||
## 技术实现
|
||||
|
||||
### DateFormatUtils 工具类
|
||||
```java
|
||||
public class DateFormatUtils {
|
||||
|
||||
private static final Map<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static String formatDate(LocalDate date, String pattern) {
|
||||
DateTimeFormatter formatter = FORMATTER_CACHE.computeIfAbsent(pattern, DateTimeFormatter::ofPattern);
|
||||
return date.format(formatter);
|
||||
}
|
||||
|
||||
public static String formatDateTime(LocalDateTime dateTime, String pattern) {
|
||||
DateTimeFormatter formatter = FORMATTER_CACHE.computeIfAbsent(pattern, DateTimeFormatter::ofPattern);
|
||||
return dateTime.format(formatter);
|
||||
}
|
||||
|
||||
public static String formatDate(LocalDate date, String pattern, Locale locale) {
|
||||
DateTimeFormatter formatter = FORMATTER_CACHE.computeIfAbsent(pattern + ":" + locale.toString(),
|
||||
k -> DateTimeFormatter.ofPattern(pattern).withLocale(locale));
|
||||
return date.format(formatter);
|
||||
}
|
||||
|
||||
public static String formatDateTime(LocalDateTime dateTime, String pattern, Locale locale) {
|
||||
DateTimeFormatter formatter = FORMATTER_CACHE.computeIfAbsent(pattern + ":" + locale.toString(),
|
||||
k -> DateTimeFormatter.ofPattern(pattern).withLocale(locale));
|
||||
return dateTime.format(formatter);
|
||||
}
|
||||
|
||||
public static String format(Date date, String pattern) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
public static String format(Date date, String pattern, Locale locale) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale);
|
||||
return sdf.format(date);
|
||||
}
|
||||
|
||||
public static LocalDate parseDate(String dateStr, String pattern) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return LocalDate.parse(dateStr, formatter);
|
||||
}
|
||||
|
||||
public static LocalDateTime parseDateTime(String dateTimeStr, String pattern) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return LocalDateTime.parse(dateTimeStr, formatter);
|
||||
}
|
||||
|
||||
public static String getUserDateFormat(Long userId) {
|
||||
String dateFormat = CacheUtils.get(CacheConstants.SYS_DATE_FORMAT_KEY, "user:" + userId + ":dateFormat", String.class);
|
||||
if (StringUtils.isBlank(dateFormat)) {
|
||||
SysUser user = SpringUtils.getBean(ISysUserService.class).selectUserById(userId);
|
||||
if (user != null && StringUtils.isNotBlank(user.getDateFormat())) {
|
||||
dateFormat = user.getDateFormat();
|
||||
CacheUtils.set(CacheConstants.SYS_DATE_FORMAT_KEY, "user:" + userId + ":dateFormat", dateFormat);
|
||||
} else {
|
||||
dateFormat = getSystemDefaultDateFormat();
|
||||
}
|
||||
}
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
public static String getSystemDefaultDateFormat() {
|
||||
return CacheUtils.get(CacheConstants.SYS_CONFIG_KEY, "sys.default.date.format", String.class);
|
||||
}
|
||||
|
||||
public static String getSystemDefaultDateTimeFormat() {
|
||||
return CacheUtils.get(CacheConstants.SYS_CONFIG_KEY, "sys.default.datetime.format", String.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DateFormatAspect 切面类
|
||||
```java
|
||||
@Aspect
|
||||
@Component
|
||||
public class DateFormatAspect {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DateFormatAspect.class);
|
||||
|
||||
@Autowired
|
||||
private ISysUserService userService;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService configService;
|
||||
|
||||
@Around("@annotation(com.datai.common.annotation.DateFormat)")
|
||||
public Object formatDate(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Object result = joinPoint.proceed();
|
||||
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
String dateFormat = getUserDateFormat(loginUser);
|
||||
String dateTimeFormat = getUserDateTimeFormat(loginUser);
|
||||
Locale locale = getUserLocale(loginUser);
|
||||
|
||||
if (result instanceof Date) {
|
||||
return DateFormatUtils.format((Date) result, dateTimeFormat, locale);
|
||||
} else if (result instanceof LocalDate) {
|
||||
return DateFormatUtils.formatDate((LocalDate) result, dateFormat, locale);
|
||||
} else if (result instanceof LocalDateTime) {
|
||||
return DateFormatUtils.formatDateTime((LocalDateTime) result, dateTimeFormat, locale);
|
||||
} else if (result instanceof List) {
|
||||
return formatList((List<?>) result, dateFormat, dateTimeFormat, locale);
|
||||
} else if (result instanceof Page) {
|
||||
return formatPage((Page<?>) result, dateFormat, dateTimeFormat, locale);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<?> formatList(List<?> list, String dateFormat, String dateTimeFormat, Locale locale) {
|
||||
return list.stream()
|
||||
.map(item -> formatObject(item, dateFormat, dateTimeFormat, locale))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Page<?> formatPage(Page<?> page, String dateFormat, String dateTimeFormat, Locale locale) {
|
||||
List<?> formattedList = formatList(page.getRows(), dateFormat, dateTimeFormat, locale);
|
||||
page.setRows(formattedList);
|
||||
return page;
|
||||
}
|
||||
|
||||
private Object formatObject(Object obj, String dateFormat, String dateTimeFormat, Locale locale) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> clazz = obj.getClass();
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
|
||||
for (Field field : fields) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object value = field.get(obj);
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.getType() == Date.class) {
|
||||
String formattedValue = DateFormatUtils.format((Date) value, dateTimeFormat, locale);
|
||||
field.set(obj, formattedValue);
|
||||
} else if (field.getType() == LocalDate.class) {
|
||||
String formattedValue = DateFormatUtils.formatDate((LocalDate) value, dateFormat, locale);
|
||||
field.set(obj, formattedValue);
|
||||
} else if (field.getType() == LocalDateTime.class) {
|
||||
String formattedValue = DateFormatUtils.formatDateTime((LocalDateTime) value, dateTimeFormat, locale);
|
||||
field.set(obj, formattedValue);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("格式化日期字段失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private String getUserDateFormat(LoginUser loginUser) {
|
||||
if (loginUser != null && loginUser.getUser() != null && StringUtils.isNotBlank(loginUser.getUser().getDateFormat())) {
|
||||
return loginUser.getUser().getDateFormat();
|
||||
}
|
||||
return DateFormatUtils.getSystemDefaultDateFormat();
|
||||
}
|
||||
|
||||
private String getUserDateTimeFormat(LoginUser loginUser) {
|
||||
if (loginUser != null && loginUser.getUser() != null && StringUtils.isNotBlank(loginUser.getUser().getDateFormatPattern())) {
|
||||
return loginUser.getUser().getDateFormatPattern();
|
||||
}
|
||||
return DateFormatUtils.getSystemDefaultDateTimeFormat();
|
||||
}
|
||||
|
||||
private Locale getUserLocale(LoginUser loginUser) {
|
||||
if (loginUser != null && loginUser.getUser() != null && StringUtils.isNotBlank(loginUser.getUser().getLang())) {
|
||||
String lang = loginUser.getUser().getLang();
|
||||
if (lang.contains("_")) {
|
||||
String[] parts = lang.split("_");
|
||||
return new Locale(parts[0], parts[1]);
|
||||
} else {
|
||||
return new Locale(lang);
|
||||
}
|
||||
}
|
||||
return Locale.getDefault();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @DateFormat 注解
|
||||
```java
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DateFormat {
|
||||
String dateFormat() default "";
|
||||
String dateTimeFormat() default "";
|
||||
boolean useLocale() default true;
|
||||
}
|
||||
```
|
||||
|
||||
### DateConstants 常量类
|
||||
```java
|
||||
public class DateConstants {
|
||||
|
||||
public static final String DATE_FORMAT_ISO8601 = "yyyy-MM-dd";
|
||||
public static final String DATE_FORMAT_EUROPE = "dd/MM/yyyy";
|
||||
public static final String DATE_FORMAT_USA = "MM/dd/yyyy";
|
||||
public static final String DATE_FORMAT_CHINA = "yyyy年MM月dd日";
|
||||
|
||||
public static final String DATETIME_FORMAT_SHORT = "yyyy-MM-dd HH:mm:ss";
|
||||
public static final String DATETIME_FORMAT_LONG = "yyyy年MM月dd日 HH时mm分ss秒";
|
||||
|
||||
public static final Map<String, String> DATE_FORMATS = new HashMap<>();
|
||||
|
||||
static {
|
||||
DATE_FORMATS.put("iso8601", DATE_FORMAT_ISO8601);
|
||||
DATE_FORMATS.put("europe", DATE_FORMAT_EUROPE);
|
||||
DATE_FORMATS.put("usa", DATE_FORMAT_USA);
|
||||
DATE_FORMATS.put("china", DATE_FORMAT_CHINA);
|
||||
}
|
||||
|
||||
public static String getDateFormat(String key) {
|
||||
return DATE_FORMATS.getOrDefault(key, DATE_FORMAT_ISO8601);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 缓存策略
|
||||
|
||||
### 用户日期格式缓存
|
||||
- **缓存键**:sys:date:format:{userId}
|
||||
- **缓存时间**:24 小时
|
||||
- **更新策略**:用户切换日期格式时清除缓存
|
||||
|
||||
### 系统默认日期格式缓存
|
||||
- **缓存键**:sys:default:date:format
|
||||
- **缓存时间**:24 小时
|
||||
- **更新策略**:系统默认日期格式更新时清除缓存
|
||||
|
||||
### DateTimeFormatter 缓存
|
||||
- **缓存位置**:DateFormatUtils.FORMATTER_CACHE
|
||||
- **缓存策略**:使用 ConcurrentHashMap 缓存 DateTimeFormatter 实例
|
||||
- **更新策略**:永久缓存,应用重启时清除
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试
|
||||
- DateFormatUtils 单元测试
|
||||
- DateFormatAspect 单元测试
|
||||
- DateConstants 单元测试
|
||||
|
||||
### 集成测试
|
||||
- 日期格式化功能测试
|
||||
- 多语言日期显示测试
|
||||
- AOP 切面测试
|
||||
- 时区转换 + 日期格式化集成测试
|
||||
|
||||
### 手动测试
|
||||
- 测试不同日期格式的格式化
|
||||
- 测试多语言日期显示
|
||||
- 测试时区转换 + 日期格式化
|
||||
- 测试用户日期格式偏好切换
|
||||
|
||||
## 部署方案
|
||||
|
||||
### 配置文件
|
||||
```yaml
|
||||
spring:
|
||||
application:
|
||||
name: datai-admin
|
||||
|
||||
system:
|
||||
default:
|
||||
date:
|
||||
format: yyyy-MM-dd
|
||||
format-pattern: yyyy年MM月dd日
|
||||
datetime:
|
||||
format: yyyy-MM-dd HH:mm:ss
|
||||
format-pattern: yyyy年MM月dd日 HH时mm分ss秒
|
||||
```
|
||||
|
||||
### 数据库初始化
|
||||
执行以下 SQL 脚本:
|
||||
1. 扩展 sys_user 表,添加 date_format 和 date_format_pattern 字段
|
||||
2. 插入系统配置数据
|
||||
3. 插入常用日期格式配置
|
||||
|
||||
### 日期格式数据初始化
|
||||
```sql
|
||||
-- 插入系统默认日期格式配置
|
||||
INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `config_type`, `remark`) VALUES
|
||||
('系统默认日期格式', 'sys.default.date.format', 'yyyy-MM-dd', 'Y', '系统默认日期格式'),
|
||||
('系统默认日期格式模式', 'sys.default.date.format.pattern', 'yyyy年MM月dd日', 'Y', '系统默认日期格式模式'),
|
||||
('系统默认日期时间格式', 'sys.default.datetime.format', 'yyyy-MM-dd HH:mm:ss', 'Y', '系统默认日期时间格式'),
|
||||
('系统默认日期时间格式模式', 'sys.default.datetime.format.pattern', 'yyyy年MM月dd日 HH时mm分ss秒', 'Y', '系统默认日期时间格式模式');
|
||||
|
||||
-- 插入常用日期格式配置
|
||||
INSERT INTO `sys_config` (`config_name`, `config_key`, `config_value`, `config_type`, `remark`) VALUES
|
||||
('ISO 8601 日期格式', 'sys.date.format.iso8601', 'yyyy-MM-dd', 'Y', 'ISO 8601 日期格式'),
|
||||
('欧洲日期格式', 'sys.date.format.europe', 'dd/MM/yyyy', 'Y', '欧洲日期格式'),
|
||||
('美国日期格式', 'sys.date.format.usa', 'MM/dd/yyyy', 'Y', '美国日期格式'),
|
||||
('中国日期格式', 'sys.date.format.china', 'yyyy年MM月dd日', 'Y', '中国日期格式');
|
||||
```
|
||||
|
||||
### 缓存初始化
|
||||
应用启动时,自动加载系统默认日期格式到 Redis 缓存。
|
||||
|
||||
## 性能优化
|
||||
|
||||
### DateTimeFormatter 缓存
|
||||
使用 ConcurrentHashMap 缓存 DateTimeFormatter 实例,避免重复创建,提高格式化性能。
|
||||
|
||||
### 用户日期格式缓存
|
||||
使用 Redis 缓存用户日期格式偏好,减少数据库查询次数。
|
||||
|
||||
### 批量格式化优化
|
||||
对于 List 和 Page 类型的返回值,使用 Stream API 进行批量格式化,提高处理效率。
|
||||
|
||||
## 安全性考虑
|
||||
|
||||
### 输入验证
|
||||
验证用户输入的日期格式,防止格式注入攻击。
|
||||
|
||||
### 权限控制
|
||||
用户只能设置自己的日期格式偏好,不能修改其他用户的日期格式。
|
||||
|
||||
### 审计日志
|
||||
记录用户切换日期格式的操作,支持审计追踪。
|
||||
|
||||
## 兼容性考虑
|
||||
|
||||
### 向后兼容
|
||||
保持与现有代码的兼容性,不影响未使用 @DateFormat 注解的方法。
|
||||
|
||||
### 多时区支持
|
||||
结合时区转换功能,支持跨时区日期格式化。
|
||||
|
||||
### 多语言支持
|
||||
结合国际化功能,支持多语言日期显示。
|
||||
|
||||
## 扩展性考虑
|
||||
|
||||
### 自定义日期格式
|
||||
支持用户自定义日期格式,存储在数据库中。
|
||||
|
||||
### 日期格式模板
|
||||
提供常用日期格式模板,方便用户选择。
|
||||
|
||||
### 相对时间支持
|
||||
为前端相对时间功能提供支持(相对时间在前端实现)。
|
||||
@ -82,6 +82,7 @@
|
||||
- [2026-01-21-002-03-数据库国际化设计.md](design/2026-01-21-002-03-数据库国际化设计.md) - 数据库国际化设计 [进行中]
|
||||
- [2026-01-21-002-04-时区国际化设计.md](design/2026-01-21-002-04-时区国际化设计.md) - 时区国际化设计 [进行中]
|
||||
- [2026-01-21-002-05-货币格式化设计.md](design/2026-01-21-002-05-货币格式化设计.md) - 货币格式化设计 [进行中]
|
||||
- [2026-01-21-002-06-日期格式化设计.md](design/2026-01-21-002-06-日期格式化设计.md) - 日期格式化设计 [进行中]
|
||||
- [0000-template.md](design/0000-template.md) - 设计文档模板
|
||||
|
||||
### 3. 架构决策
|
||||
@ -129,6 +130,7 @@
|
||||
- [2026-01-25-002-03-ADR-数据库国际化架构决策.md](decisions/adr/2026-01-25-002-03-ADR-数据库国际化架构决策.md) - 数据库国际化架构决策 [已接受]
|
||||
- [2026-01-25-002-04-ADR-时区国际化技术选型.md](decisions/adr/2026-01-25-002-04-ADR-时区国际化技术选型.md) - 时区国际化技术选型架构决策 [Draft]
|
||||
- [2026-01-25-002-05-ADR-货币格式化技术选型.md](decisions/adr/2026-01-25-002-05-ADR-货币格式化技术选型.md) - 货币格式化技术选型架构决策 [Draft]
|
||||
- [2026-01-25-002-06-ADR-日期格式化技术选型.md](decisions/adr/2026-01-25-002-06-ADR-日期格式化技术选型.md) - 日期格式化技术选型架构决策 [Draft]
|
||||
- [0000-template.md](decisions/adr/0000-template.md) - ADR文档模板
|
||||
|
||||
### 4. 提示词库
|
||||
@ -182,7 +184,7 @@
|
||||
- [2026-01-25-002-03-prompt-数据库国际化功能-补充.md](prompts/2026-01-25-002-03-prompt-数据库国际化功能-补充.md) - 数据库国际化功能补充实现提示词 [Draft]
|
||||
- [2026-01-25-002-04-prompt-时区国际化功能.md](prompts/2026-01-25-002-04-prompt-时区国际化功能.md) - 时区国际化功能实现提示词 [已完成]
|
||||
- [2026-01-25-002-05-prompt-货币格式化功能.md](prompts/2026-01-25-002-05-prompt-货币格式化功能.md) - 货币格式化功能实现提示词 [Draft]
|
||||
- **2026-01-25-002-04-code-时区国际化功能**: [2026-01-25-002-04-code-时区国际化功能.md](reference-code/2026-01-25-002-04-code-时区国际化功能.md) - 时区国际化功能参考代码 [已完成]
|
||||
- [2026-01-25-002-06-prompt-日期格式化功能.md](prompts/2026-01-25-002-06-prompt-日期格式化功能.md) - 日期格式化功能实现提示词 [Draft]
|
||||
|
||||
### 5. 技能文档
|
||||
|
||||
@ -237,6 +239,7 @@
|
||||
- [2026-01-24-002-session.md](sessions/2026-01-24-002-session.md) - 环境从库初始化功能实现
|
||||
- [2026-01-25-002-02-session.md](sessions/2026-01-25-002-02-session.md) - 后端国际化功能实现
|
||||
- [2026-01-25-002-05-session.md](sessions/2026-01-25-002-05-session.md) - 货币格式化功能实现
|
||||
- [2026-01-25-002-06-session.md](sessions/2026-01-25-002-06-session.md) - 日期格式化功能实现
|
||||
- [YYYYMMDD-template.md](sessions/YYYYMMDD-template.md) - 会话记录模板
|
||||
|
||||
### 6. 迭代复盘
|
||||
@ -277,6 +280,7 @@
|
||||
- [2026-01-25-002-03-retro.md](retros/2026-01-25-002-03-retro.md) - 数据库国际化功能复盘
|
||||
- [2026-01-25-002-04-retro.md](retros/2026-01-25-002-04-retro.md) - 时区国际化功能复盘
|
||||
- [2026-01-25-002-05-retro.md](retros/2026-01-25-002-05-retro.md) - 货币格式化功能复盘
|
||||
- [2026-01-25-002-06-retro.md](retros/2026-01-25-002-06-retro.md) - 日期格式化功能复盘
|
||||
- [YYYYMMDD-template.md](retros/YYYYMMDD-template.md) - 复盘报告模板
|
||||
|
||||
### 7. 变更日志
|
||||
@ -327,6 +331,7 @@
|
||||
- [2026-01-25-002-03-changelog.md](changelog/2026-01-25-002-03-changelog.md) - 数据库国际化功能实现
|
||||
- [2026-01-25-002-04-changelog.md](changelog/2026-01-25-002-04-changelog.md) - 时区国际化功能实现
|
||||
- [2026-01-25-002-05-currency-formatting.md](changelog/2026-01-25-002-05-currency-formatting.md) - 货币格式化功能实现
|
||||
- [2026-01-25-002-06-changelog.md](changelog/2026-01-25-002-06-changelog.md) - 日期格式化功能实现
|
||||
- [0000-template.md](changelog/0000-template.md) - 变更记录模板
|
||||
|
||||
### 8. 接口文档
|
||||
@ -354,6 +359,7 @@
|
||||
- [数据库国际化功能 API](api-docs/2026-01-25-002-03-api.md) - 数据库国际化功能接口文档
|
||||
- [时区国际化功能 API](api-docs/2026-01-25-002-04-api.md) - 时区国际化功能接口文档
|
||||
- [货币格式化功能 API](api-docs/2026-01-25-002-05-api.md) - 货币格式化功能接口文档
|
||||
- [日期格式化功能 API](api-docs/2026-01-25-002-06-api.md) - 日期格式化功能接口文档
|
||||
- [DataiConfigSnapshotController](api-docs/setting/DataiConfigSnapshotController/) - 配置快照管理接口
|
||||
- [DataiConfigurationController](api-docs/setting/DataiConfigurationController/) - 配置管理接口
|
||||
- [SysJobController](api-docs/monitor/SysJobController/) - 定时任务管理接口
|
||||
@ -383,6 +389,7 @@
|
||||
- [2026-01-25-002-03-数据库国际化.sql](sql/2026-01-25-002-03-数据库国际化.sql) - 数据库国际化功能SQL [Draft]
|
||||
- [2026-01-25-002-04-timezone-internationalization.sql](sql/2026-01-25-002-04-timezone-internationalization.sql) - 时区国际化功能SQL [Draft]
|
||||
- [2026-01-25-002-05-货币格式化.sql](sql/2026-01-25-002-05-货币格式化.sql) - 货币格式化功能SQL [Draft]
|
||||
- [2026-01-25-002-06-日期格式化.sql](sql/2026-01-25-002-06-日期格式化.sql) - 日期格式化功能SQL [Draft]
|
||||
|
||||
### 10. 参考代码
|
||||
|
||||
|
||||
@ -0,0 +1,309 @@
|
||||
# 提示词:日期格式化功能实现
|
||||
|
||||
## 引用真源
|
||||
- [需求文档](../requirements/2026-01-21-002-06-日期格式化需求.md)
|
||||
- [设计文档](../design/2026-01-21-002-06-日期格式化设计.md)
|
||||
- [决策记录](../decisions/adr/2026-01-25-002-06-ADR-日期格式化技术选型.md)
|
||||
- [SQL 脚本](../sql/2026-01-25-002-06-日期格式化.sql)
|
||||
|
||||
## 需求描述
|
||||
|
||||
根据需求文档,实现日期格式化功能,支持用户设置日期格式偏好,根据用户日期格式偏好显示日期。核心需求包括:
|
||||
|
||||
1. **日期格式化功能**:
|
||||
- 支持常用日期格式(ISO 8601、欧洲、美国、中国等)
|
||||
- 支持日期时间格式化(短格式、长格式)
|
||||
- 支持长短日期格式
|
||||
- 使用 java.time.format.DateTimeFormatter 进行格式化
|
||||
- 支持多语言日期显示(星期几、月份名称的本地化)
|
||||
|
||||
2. **时区转换功能**:
|
||||
- 结合时区转换功能,支持跨时区日期格式化
|
||||
- 自动将 UTC 时间转换为用户时区
|
||||
- 支持用户时区偏好
|
||||
- 支持系统默认时区
|
||||
- 时区转换准确无误
|
||||
|
||||
3. **多语言支持功能**:
|
||||
- 支持多语言日期显示
|
||||
- 支持星期几的本地化(中文:星期一;英文:Monday)
|
||||
- 支持月份名称的本地化(中文:一月;英文:January)
|
||||
- 支持常用语言(中文、英文等)
|
||||
- 根据用户语言偏好自动选择语言
|
||||
|
||||
4. **用户日期格式偏好管理**:
|
||||
- 获取当前用户日期格式偏好
|
||||
- 切换用户日期格式偏好
|
||||
- 获取系统默认日期格式
|
||||
- 获取常用日期格式列表
|
||||
- 支持用户日期格式偏好覆盖系统默认设置
|
||||
|
||||
5. **自动日期格式化**:
|
||||
- 在 Service 层自动进行日期格式化
|
||||
- 使用 AOP 切面拦截 Service 方法返回值
|
||||
- 自动识别 Date、LocalDate、LocalDateTime 类型字段
|
||||
- 根据用户日期格式偏好自动格式化
|
||||
- 结合时区转换功能,先进行时区转换,再进行日期格式化
|
||||
- 格式化逻辑统一,避免重复代码
|
||||
|
||||
6. **缓存管理**:
|
||||
- 用户日期格式偏好使用 Redis 缓存,TTL = 24 小时
|
||||
- 系统默认日期格式使用 Redis 缓存,TTL = 24 小时
|
||||
- DateTimeFormatter 实例使用 ConcurrentHashMap 缓存,永久缓存
|
||||
- 用户切换日期格式时清除缓存
|
||||
- 系统默认日期格式更新时清除缓存
|
||||
|
||||
## 设计方案
|
||||
|
||||
根据设计文档,采用以下技术方案:
|
||||
|
||||
1. **日期格式化技术**:java.time.format.DateTimeFormatter
|
||||
- 使用 DateTimeFormatter 的模式字符串定义日期格式(例如:`yyyy-MM-dd`)
|
||||
- 使用 ofPattern() 方法创建格式化器
|
||||
- 使用 withLocale() 方法设置语言环境
|
||||
- 使用 ConcurrentHashMap 缓存 DateTimeFormatter 实例,提高性能
|
||||
- 支持自定义日期格式模式
|
||||
|
||||
2. **缓存技术**:Redis
|
||||
- 使用现有的 CacheUtils 工具类
|
||||
- 性能优秀,响应时间 < 1ms
|
||||
- 支持分布式部署
|
||||
|
||||
3. **数据库技术**:MySQL 8.3.0
|
||||
- 修改 sys_user 表添加 date_format 和 date_format_pattern 字段
|
||||
- 使用 sys_config 表存储系统默认日期格式配置
|
||||
- 使用 sys_config 表存储常用日期格式配置
|
||||
|
||||
4. **框架技术**:Spring Boot 3.5.7 + 若依框架
|
||||
- 使用 Spring Security 认证机制
|
||||
- 使用若依的权限控制机制(@PreAuthorize)
|
||||
- 使用若依的日志记录机制(@Log)
|
||||
- 使用若依的异常处理机制(GlobalExceptionHandler)
|
||||
|
||||
5. **架构设计**:
|
||||
- 前端层(Vue 3)→ Controller 层 → Service 层 → Mapper 层 → 数据库层(MySQL)→ 缓存层(Redis)
|
||||
- 时区转换在 Service 层进行,使用 TimeZoneConvertAspect 切面拦截
|
||||
- 日期格式化在 Service 层进行,使用 DateFormatAspect 切面拦截
|
||||
- 切面执行顺序:TimeZoneConvertAspect 先于 DateFormatAspect
|
||||
|
||||
## 输出格式要求
|
||||
|
||||
### 1. 必须包含以下文件
|
||||
|
||||
#### Controller 层
|
||||
- **SysUserController.java**(路径:`datai-admin/src/main/java/com/datai/web/controller/system/SysUserController.java`)
|
||||
- 接口:
|
||||
- GET /system/user/dateFormat - 获取当前用户日期格式偏好
|
||||
- POST /system/user/switchDateFormat - 切换用户日期格式偏好
|
||||
- 使用 @RestController、@RequestMapping、@PreAuthorize 注解
|
||||
- 使用 @Log 注解(com.datai.common.annotation.Log)记录操作日志
|
||||
- 使用 @Operation 注解(io.swagger.v3.oas.annotations.Operation)添加 Swagger 文档
|
||||
|
||||
- **SysConfigController.java**(路径:`datai-admin/src/main/java/com/datai/web/controller/system/SysConfigController.java`)
|
||||
- 接口:
|
||||
- GET /system/dateFormat/default - 获取系统默认日期格式
|
||||
- GET /system/dateFormat/list - 获取常用日期格式列表
|
||||
- 使用 @RestController、@RequestMapping、@PreAuthorize 注解
|
||||
- 使用 @Log 注解(com.datai.common.annotation.Log)记录操作日志
|
||||
- 使用 @Operation 注解(io.swagger.v3.oas.annotations.Operation)添加 Swagger 文档
|
||||
|
||||
#### Service 层
|
||||
- **ISysUserService.java**(路径:`datai-system/src/main/java/com/datai/system/service/ISysUserService.java`)
|
||||
- 方法:
|
||||
- String getUserDateFormat(Long userId)
|
||||
- void updateUserDateFormat(Long userId, String dateFormat, String dateFormatPattern)
|
||||
|
||||
- **SysUserServiceImpl.java**(路径:`datai-system/src/main/java/com/datai/system/service/impl/SysUserServiceImpl.java`)
|
||||
- 实现 ISysUserService 接口
|
||||
- 使用 @Service 注解
|
||||
- 集成 Redis 缓存
|
||||
- 实现用户日期格式偏好管理逻辑
|
||||
|
||||
#### Utils 层
|
||||
- **DateFormatUtils.java**(路径:`datai-common/src/main/java/com/datai/common/utils/DateFormatUtils.java`)
|
||||
- 方法:
|
||||
- String formatDate(LocalDate date, String pattern)
|
||||
- String formatDateTime(LocalDateTime dateTime, String pattern)
|
||||
- String formatDate(LocalDate date, String pattern, Locale locale)
|
||||
- String formatDateTime(LocalDateTime dateTime, String pattern, Locale locale)
|
||||
- String format(Date date, String pattern)
|
||||
- String format(Date date, String pattern, Locale locale)
|
||||
- LocalDate parseDate(String dateStr, String pattern)
|
||||
- LocalDateTime parseDateTime(String dateTimeStr, String pattern)
|
||||
- String getUserDateFormat(Long userId)
|
||||
- String getSystemDefaultDateFormat()
|
||||
- String getSystemDefaultDateTimeFormat()
|
||||
- 使用 java.time.format.DateTimeFormatter
|
||||
- 使用 java.text.SimpleDateFormat(兼容 Date 类型)
|
||||
- 使用 ConcurrentHashMap 缓存 DateTimeFormatter 实例
|
||||
- 处理 null 值和无效日期格式
|
||||
|
||||
#### Aspect 层
|
||||
- **DateFormatAspect.java**(路径:`datai-framework/src/main/java/com/datai/framework/aspectj/DateFormatAspect.java`)
|
||||
- 使用 @Aspect、@Component 注解
|
||||
- 使用 @Around 切面拦截 Service 方法返回值
|
||||
- 自动格式化日期字段(包括 Date、LocalDate、LocalDateTime 类型)
|
||||
- 提供 @DateFormat 注解标记需要格式化的 Service 方法
|
||||
- 递归处理嵌套对象的日期字段
|
||||
- 避免循环引用导致的无限递归
|
||||
- 结合时区转换功能,先进行时区转换,再进行日期格式化
|
||||
- 使用 @Order 注解控制切面执行顺序(确保 TimeZoneConvertAspect 先于 DateFormatAspect)
|
||||
|
||||
#### 注解
|
||||
- **@DateFormat.java**(路径:`datai-common/src/main/java/com/datai/common/annotation/DateFormat.java`)
|
||||
- 标记需要格式化的方法
|
||||
- 支持自定义日期格式配置
|
||||
- 支持多语言配置
|
||||
|
||||
#### 常量
|
||||
- **DateConstants.java**(路径:`datai-common/src/main/java/com/datai/common/constant/DateConstants.java`)
|
||||
- 定义常用日期格式常量(ISO 8601、欧洲、美国、中国)
|
||||
- 定义日期格式模式映射
|
||||
- 提供获取日期格式的方法
|
||||
|
||||
#### 修改现有文件
|
||||
- **SysUser.java**(路径:`datai-common/src/main/java/com/datai/common/core/domain/entity/SysUser.java`)
|
||||
- 添加 dateFormat 字段
|
||||
- 添加 dateFormatPattern 字段
|
||||
- 添加 getter 和 setter 方法
|
||||
|
||||
- **CacheConstants.java**(路径:`datai-common/src/main/java/com/datai/common/constant/CacheConstants.java`)
|
||||
- 添加用户日期格式缓存常量(SYS_DATE_FORMAT_KEY)
|
||||
- 添加系统默认日期格式缓存常量(SYS_DEFAULT_DATE_FORMAT_KEY)
|
||||
|
||||
### 2. 必须包含单元测试
|
||||
|
||||
- **DateFormatUtilsTest.java**(路径:`datai-common/src/test/java/com/datai/common/utils/DateFormatUtilsTest.java`)
|
||||
- 测试日期格式化功能
|
||||
- 测试日期时间格式化功能
|
||||
- 测试多语言日期显示功能
|
||||
- 测试 null 值处理
|
||||
- 测试无效日期格式处理
|
||||
- 测试自定义日期格式
|
||||
- 测试 Locale 处理
|
||||
|
||||
- **DateFormatAspectTest.java**(路径:`datai-framework/src/test/java/com/datai/framework/aspectj/DateFormatAspectTest.java`)
|
||||
- 测试 AOP 切面功能
|
||||
- 测试 Date 字段格式化
|
||||
- 测试 LocalDate 字段格式化
|
||||
- 测试 LocalDateTime 字段格式化
|
||||
- 测试嵌套对象格式化
|
||||
- 测试循环引用处理
|
||||
- 测试多语言格式化
|
||||
|
||||
### 3. 必须包含常量定义
|
||||
|
||||
- **DateConstants.java**(路径:`datai-common/src/main/java/com/datai/common/constant/DateConstants.java`)
|
||||
- 定义常用日期格式常量(DATE_FORMAT_ISO8601、DATE_FORMAT_EUROPE、DATE_FORMAT_USA、DATE_FORMAT_CHINA)
|
||||
- 定义日期时间格式常量(DATETIME_FORMAT_SHORT、DATETIME_FORMAT_LONG)
|
||||
- 定义日期格式映射(DATE_FORMATS)
|
||||
- 提供获取日期格式的方法(getDateFormat)
|
||||
|
||||
## 代码规范要求
|
||||
|
||||
### 1. 命名规范
|
||||
- 类名:使用大驼峰命名(例如:DateFormatUtils、DateConstants)
|
||||
- 方法名:使用小驼峰命名(例如:formatDate、getUserDateFormat)
|
||||
- 变量名:使用小驼峰命名(例如:dateFormat、locale)
|
||||
- 常量名:使用全大写下划线命名(例如:DATE_FORMAT_ISO8601、DEFAULT_DATE_FORMAT)
|
||||
|
||||
### 2. 注释规范
|
||||
- 类注释:使用 JavaDoc 格式,包含类描述、作者、日期
|
||||
- 方法注释:使用 JavaDoc 格式,包含方法描述、参数说明、返回值说明
|
||||
- 字段注释:使用单行注释,说明字段用途
|
||||
- 复杂逻辑注释:使用多行注释,说明逻辑思路
|
||||
|
||||
### 3. 异常处理
|
||||
- 使用若依的异常处理机制(GlobalExceptionHandler)
|
||||
- 自定义异常使用 ServiceException(com.datai.common.exception.ServiceException)
|
||||
- 异常信息使用国际化消息(MessageUtils.message)
|
||||
- 记录异常日志(log.error)
|
||||
|
||||
### 4. 日志记录
|
||||
- 使用若依的日志记录机制(@Log 注解)
|
||||
- 记录关键操作(切换日期格式)
|
||||
- 日志信息使用国际化消息(MessageUtils.message)
|
||||
- @Log 注解参数:
|
||||
- title:模块名称
|
||||
- businessType:业务类型(BusinessType.INSERT、BusinessType.UPDATE、BusinessType.DELETE、BusinessType.OTHER)
|
||||
- operatorType:操作人类别(OperatorType.MANAGE、OperatorType.OTHER)
|
||||
- isSaveRequestData:是否保存请求的参数(默认 true)
|
||||
- isSaveResponseData:是否保存响应的参数(默认 true)
|
||||
- excludeParamNames:排除指定的请求参数
|
||||
|
||||
### 5. 权限控制
|
||||
- 使用若依的权限控制机制(@PreAuthorize)
|
||||
- 接口权限使用若依的权限标识(system:config:query 等)
|
||||
- 用户日期格式偏好接口不需要权限(需要登录)
|
||||
|
||||
### 6. 缓存使用
|
||||
- 使用 CacheUtils 工具类进行缓存操作(com.datai.common.utils.CacheUtils)
|
||||
- 缓存键使用 CacheConstants 中定义的常量
|
||||
- 缓存方法:
|
||||
- put(cacheName, key, value) - 缓存数据
|
||||
- put(cacheName, key, value, timeout, unit) - 缓存数据并设置过期时间
|
||||
- get(cacheName, key) - 获取缓存数据
|
||||
- get(cacheName, key, type) - 根据类型获取缓存数据
|
||||
- remove(cacheName, key) - 移除缓存数据
|
||||
- clear(cacheName) - 清除所有缓存数据
|
||||
- 缓存更新时清除相关缓存
|
||||
- 缓存时间根据业务需求设置(用户日期格式 24 小时、系统默认日期格式 24 小时)
|
||||
|
||||
### 7. 数据库操作
|
||||
- 使用 MyBatis 进行数据库操作
|
||||
- Mapper 接口不使用 @Mapper 注解
|
||||
- Mapper 接口不继承 BaseMapper
|
||||
- Mapper 接口定义标准的 MyBatis 方法
|
||||
- 自定义 SQL 在 Mapper.xml 中定义
|
||||
- 使用 resultMap 映射结果
|
||||
- 使用 parameterType 定义参数类型
|
||||
|
||||
### 8. 切面顺序控制
|
||||
- 使用 @Order 注解控制切面执行顺序
|
||||
- 确保 TimeZoneConvertAspect 先于 DateFormatAspect
|
||||
- DateFormatAspect 的 Order 值应该大于 TimeZoneConvertAspect 的 Order 值
|
||||
|
||||
## 测试要求
|
||||
|
||||
### 1. 单元测试
|
||||
- 测试覆盖率 ≥ 80%
|
||||
- 测试所有公共方法
|
||||
- 测试边界条件(null 值、空值、无效值)
|
||||
- 测试异常情况(无效日期格式、Locale 不支持)
|
||||
- 使用 JUnit 5 进行测试
|
||||
- 使用 Mockito 进行 Mock
|
||||
|
||||
### 2. 集成测试
|
||||
- 测试用户日期格式偏好接口
|
||||
- 测试系统默认日期格式接口
|
||||
- 测试常用日期格式列表接口
|
||||
- 测试 AOP 切面功能
|
||||
- 测试缓存功能
|
||||
- 测试多语言日期显示功能
|
||||
- 测试时区转换 + 日期格式化集成功能
|
||||
|
||||
### 3. 手动测试
|
||||
- 测试不同日期格式的格式化
|
||||
- 测试多语言日期显示
|
||||
- 测试时区转换 + 日期格式化
|
||||
- 测试用户日期格式偏好切换
|
||||
- 测试缓存功能
|
||||
- 测试嵌套对象格式化
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据类型**:日期数据使用 Date、LocalDate、LocalDateTime 类型
|
||||
2. **时区转换**:结合时区转换功能,先进行时区转换,再进行日期格式化
|
||||
3. **多语言支持**:使用 Locale 进行本地化,支持多语言日期显示
|
||||
4. **缓存一致性**:分布式环境下保证缓存一致性
|
||||
5. **循环引用**:AOP 切面处理嵌套对象时,避免循环引用导致的无限递归
|
||||
6. **切面顺序**:使用 @Order 注解控制切面执行顺序,确保 TimeZoneConvertAspect 先于 DateFormatAspect
|
||||
7. **性能优化**:使用 ConcurrentHashMap 缓存 DateTimeFormatter 实例,提高格式化性能
|
||||
8. **代码复用**:日期格式化逻辑统一在 AOP 切面中,避免重复代码
|
||||
9. **国际化支持**:日期格式化支持国际化,自动处理不同地区的日期格式
|
||||
10. **错误处理**:日期格式化失败时,返回友好的错误提示
|
||||
11. **日志记录**:记录关键操作日志,便于审计和问题排查
|
||||
12. **时区转换复用**:复用已实现的 TimeZoneConvertAspect,避免重复开发
|
||||
13. **国际化复用**:复用已实现的国际化功能,避免重复开发
|
||||
14. **DateTimeFormatter 缓存**:使用 ConcurrentHashMap 缓存 DateTimeFormatter 实例,避免重复创建
|
||||
15. **SimpleDateFormat 使用**:SimpleDateFormat 不是线程安全的,每次使用时创建新实例
|
||||
@ -9,20 +9,25 @@
|
||||
- 父需求:2026-01-21-002-项目国际化需求
|
||||
|
||||
## 需求概述
|
||||
实现日期格式化功能,支持根据用户地区显示日期,支持常用日期格式、日期时间格式化、短日期和长日期格式化。
|
||||
实现日期格式化功能,支持根据用户地区显示日期,支持常用日期格式、日期时间格式化、短日期和长日期格式化。后端使用 Java 的 DateTimeFormatter 进行日期格式化,结合时区转换功能,支持多语言日期显示。
|
||||
|
||||
## 目标
|
||||
1. **日期格式支持**:支持常用日期格式(如:YYYY-MM-DD、DD/MM/YYYY、MM/DD/YYYY 等)
|
||||
2. **日期时间格式化**:支持日期时间格式化
|
||||
3. **长短格式**:支持短日期和长日期格式
|
||||
4. **自动选择**:根据用户地区偏好自动选择日期格式
|
||||
5. **自定义格式**:支持自定义日期格式
|
||||
5. **自定义格式**:支持用户自定义日期格式
|
||||
6. **时区转换**:结合时区转换功能,支持跨时区日期格式化
|
||||
7. **多语言支持**:支持多语言日期显示(如:星期几、月份名称的本地化)
|
||||
8. **AOP 自动格式化**:使用 AOP 在 Service 层自动进行日期格式化
|
||||
|
||||
## 业务上下文
|
||||
|
||||
### 当前问题
|
||||
- 日期显示格式固定,无法根据用户地区自动调整
|
||||
- 缺乏日期格式化功能,用户体验不佳
|
||||
- 未结合时区转换,跨时区日期显示不准确
|
||||
- 未支持多语言日期显示,国际化体验不佳
|
||||
|
||||
### 业务场景
|
||||
```
|
||||
@ -35,7 +40,7 @@
|
||||
├─ 日期时间格式
|
||||
│ ├─ 短格式:YYYY-MM-DD HH:mm:ss
|
||||
│ ├─ 长格式:YYYY年MM月DD日 HH时mm分ss秒
|
||||
│ └─ 相对时间:X分钟前、X小时前、X天前
|
||||
│ └─ 相对时间:X分钟前、X小时前、X天前(前端实现)
|
||||
└─ 显示场景
|
||||
├─ 表格列:创建时间、更新时间等
|
||||
├─ 表单字段:出生日期、入职日期等
|
||||
@ -46,6 +51,8 @@
|
||||
1. **多地区用户**:不同国家和地区的用户使用系统,需要看到符合当地习惯的日期格式
|
||||
2. **数据展示**:显示数据时,需要符合用户地区的日期格式
|
||||
3. **报表生成**:生成报表时,需要符合用户地区的日期格式
|
||||
4. **跨时区协作**:不同时区的用户协作时,需要看到正确的本地时间
|
||||
5. **多语言环境**:不同语言环境的用户需要看到本地化的日期显示
|
||||
|
||||
## 功能需求
|
||||
|
||||
@ -67,8 +74,7 @@
|
||||
- **验收标准**:
|
||||
1. 支持短日期时间格式(YYYY-MM-DD HH:mm:ss)
|
||||
2. 支持长日期时间格式(YYYY年MM月DD日 HH时mm分ss秒)
|
||||
3. 支持相对时间格式(X分钟前、X小时前、X天前)
|
||||
4. 支持自定义格式
|
||||
3. 支持自定义格式
|
||||
- **优先级**:高
|
||||
- **依赖关系**:无
|
||||
|
||||
@ -96,46 +102,193 @@
|
||||
1. 支持用户自定义日期格式
|
||||
2. 支持常用格式模板
|
||||
3. 自定义格式立即生效
|
||||
4. 自定义格式存储在数据库中
|
||||
- **优先级**:中
|
||||
- **依赖关系**:依赖用户语言偏好表
|
||||
|
||||
#### 功能 6:时区转换
|
||||
- **描述**:结合时区转换功能,支持跨时区日期格式化
|
||||
- **验收标准**:
|
||||
1. 自动将 UTC 时间转换为用户时区
|
||||
2. 支持用户时区偏好
|
||||
3. 支持系统默认时区
|
||||
4. 时区转换准确无误
|
||||
- **优先级**:高
|
||||
- **依赖关系**:依赖时区转换功能(2026-01-21-002-04)
|
||||
|
||||
#### 功能 7:多语言支持
|
||||
- **描述**:支持多语言日期显示
|
||||
- **验收标准**:
|
||||
1. 支持星期几的本地化(中文:星期一;英文:Monday)
|
||||
2. 支持月份名称的本地化(中文:一月;英文:January)
|
||||
3. 支持常用语言(中文、英文等)
|
||||
4. 根据用户语言偏好自动选择语言
|
||||
- **优先级**:中
|
||||
- **依赖关系**:依赖国际化功能(2026-01-21-002-03)
|
||||
|
||||
#### 功能 8:AOP 自动格式化
|
||||
- **描述**:使用 AOP 在 Service 层自动进行日期格式化
|
||||
- **验收标准**:
|
||||
1. 实现 @DateFormat 注解
|
||||
2. 实现 DateFormatAspect 切面
|
||||
3. 自动拦截标记的方法,格式化返回值中的日期字段
|
||||
4. 支持递归格式化嵌套对象中的日期字段
|
||||
5. 格式化性能满足要求(< 10ms)
|
||||
- **优先级**:高
|
||||
- **依赖关系**:无
|
||||
|
||||
#### 功能 9:用户日期格式偏好
|
||||
- **描述**:支持用户设置日期格式偏好
|
||||
- **验收标准**:
|
||||
1. 扩展 SysUser 表,添加 date_format 字段
|
||||
2. 支持用户切换日期格式
|
||||
3. 支持用户自定义日期格式
|
||||
4. 切换操作记录到审计日志
|
||||
- **优先级**:高
|
||||
- **依赖关系**:无
|
||||
|
||||
#### 功能 10:系统默认日期格式
|
||||
- **描述**:支持在配置文件中设置系统默认日期格式
|
||||
- **验收标准**:
|
||||
1. 在配置文件中设置系统默认日期格式
|
||||
2. 用户未设置时使用系统默认格式
|
||||
3. 系统默认格式可配置
|
||||
- **优先级**:高
|
||||
- **依赖关系**:无
|
||||
|
||||
#### 功能 11:缓存机制
|
||||
- **描述**:使用 Redis 缓存用户日期格式偏好
|
||||
- **验收标准**:
|
||||
1. 使用 Redis 缓存用户日期格式偏好
|
||||
2. 缓存 TTL 设置为 24 小时
|
||||
3. 用户切换格式时清除缓存
|
||||
4. 缓存键符合项目规范
|
||||
- **优先级**:中
|
||||
- **依赖关系**:无
|
||||
|
||||
#### 功能 12:相对时间(前端实现)
|
||||
- **描述**:支持相对时间格式(X分钟前、X小时前、X天前)
|
||||
- **验收标准**:
|
||||
1. 支持相对时间格式
|
||||
2. 相对时间实时更新
|
||||
3. 支持多种时间单位(秒、分钟、小时、天、周、月、年)
|
||||
- **优先级**:低
|
||||
- **依赖关系**:前端实现
|
||||
- **备注**:此功能在前端实现,不在本次后端开发范围
|
||||
|
||||
## 非功能需求
|
||||
|
||||
### 性能需求
|
||||
- **格式化时间**:日期格式化时间 < 10ms
|
||||
- **缓存命中率**:用户日期格式偏好缓存命中率 ≥ 90%
|
||||
|
||||
### 兼容性需求
|
||||
- **浏览器兼容性**:支持主流浏览器(Chrome、Firefox、Edge、Safari)
|
||||
- **国际化库**:使用成熟的国际化库(如:Intl.DateTimeFormat)
|
||||
- **Java 版本**:Java 21
|
||||
- **Spring Boot 版本**:3.5.7
|
||||
- **日期格式化库**:使用 Java 内置的 DateTimeFormatter
|
||||
|
||||
### 可维护性需求
|
||||
- **代码可读性**:代码符合项目编码规范,注释完整
|
||||
- **测试覆盖率**:单元测试覆盖率 ≥ 80%
|
||||
- **文档完整性**:所有功能都有对应的文档说明
|
||||
|
||||
### 安全性需求
|
||||
- **输入验证**:验证用户输入的日期格式,防止格式注入攻击
|
||||
- **权限控制**:用户只能设置自己的日期格式偏好
|
||||
- **审计日志**:记录用户切换日期格式的操作
|
||||
|
||||
## 数据需求
|
||||
|
||||
### 数据依赖
|
||||
- 依赖用户语言偏好表(sys_user_lang)
|
||||
- 依赖时区转换功能(sys_timezone)
|
||||
|
||||
### 数据存储需求
|
||||
1. **扩展 SysUser 表**:
|
||||
- 添加 date_format 字段(VARCHAR(50)):存储用户日期格式偏好
|
||||
- 添加 date_format_pattern 字段(VARCHAR(100)):存储用户自定义日期格式模式
|
||||
|
||||
2. **配置文件**:
|
||||
- 添加系统默认日期格式配置(sys.default.date.format)
|
||||
- 添加系统默认日期格式模式配置(sys.default.date.format.pattern)
|
||||
|
||||
3. **Redis 缓存**:
|
||||
- 缓存用户日期格式偏好
|
||||
- 缓存键:SYS_DATE_FORMAT_KEY
|
||||
- 缓存 TTL:24 小时
|
||||
|
||||
### 数据流转需求
|
||||
```
|
||||
日期显示
|
||||
└─ 读取用户语言偏好
|
||||
└─ 根据用户地区选择日期格式
|
||||
└─ 格式化日期
|
||||
└─ 读取用户日期格式偏好(缓存或数据库)
|
||||
└─ 读取用户时区偏好(缓存或数据库)
|
||||
└─ 读取用户语言偏好(缓存或数据库)
|
||||
└─ 将 UTC 时间转换为用户时区
|
||||
└─ 根据用户日期格式偏好格式化日期
|
||||
└─ 根据用户语言偏好本地化日期
|
||||
└─ 显示格式化后的日期
|
||||
```
|
||||
|
||||
## 业务规则
|
||||
1. **地区偏好优先级**:用户地区偏好优先于系统默认格式
|
||||
2. **格式化准确性**:日期格式化必须准确无误
|
||||
3. **自定义格式权限**:所有用户都可以自定义日期格式
|
||||
1. **日期格式优先级**:用户日期格式偏好 > 系统默认格式
|
||||
2. **时区优先级**:用户时区偏好 > 系统默认时区
|
||||
3. **语言优先级**:用户语言偏好 > 系统默认语言
|
||||
4. **格式化准确性**:日期格式化必须准确无误
|
||||
5. **自定义格式权限**:所有用户都可以自定义日期格式
|
||||
6. **缓存更新**:用户切换日期格式时立即清除缓存
|
||||
|
||||
## 技术约束
|
||||
1. **Spring Boot 版本**:3.5.7
|
||||
2. **Java 版本**:21
|
||||
3. **国际化库**:Intl.DateTimeFormat
|
||||
3. **日期格式化库**:Java 内置的 DateTimeFormatter
|
||||
4. **必须使用现有的认证授权机制**:不能引入新的认证方式
|
||||
5. **必须使用现有的缓存机制**:使用 CacheUtils 进行缓存操作
|
||||
6. **必须使用现有的 AOP 机制**:使用 @Aspect 注解实现切面
|
||||
|
||||
## 技术方案
|
||||
|
||||
### 后端实现方案
|
||||
1. **日期格式化工具类**:创建 DateFormatUtils 工具类,提供常用的日期格式化方法
|
||||
2. **AOP 切面**:创建 DateFormatAspect 切面,使用 @Aspect 注解实现
|
||||
3. **注解**:创建 @DateFormat 注解,标记需要格式化的方法
|
||||
4. **缓存机制**:使用 CacheUtils 进行缓存操作,缓存键为 SYS_DATE_FORMAT_KEY
|
||||
5. **时区转换**:复用已实现的时区转换功能(TimeZoneConvertAspect)
|
||||
6. **多语言支持**:结合国际化功能,使用 Locale 进行本地化
|
||||
|
||||
### 数据库设计
|
||||
1. **扩展 SysUser 表**:
|
||||
```sql
|
||||
ALTER TABLE sys_user ADD COLUMN date_format VARCHAR(50) DEFAULT NULL COMMENT '日期格式';
|
||||
ALTER TABLE sys_user ADD COLUMN date_format_pattern VARCHAR(100) DEFAULT NULL COMMENT '日期格式模式';
|
||||
```
|
||||
|
||||
2. **配置文件**:
|
||||
```yaml
|
||||
# 系统默认日期格式
|
||||
sys:
|
||||
default:
|
||||
date:
|
||||
format: yyyy-MM-dd
|
||||
format-pattern: yyyy年MM月dd日
|
||||
```
|
||||
|
||||
### 缓存设计
|
||||
1. **缓存键**:SYS_DATE_FORMAT_KEY
|
||||
2. **缓存结构**:
|
||||
```
|
||||
SYS_DATE_FORMAT_KEY -> {
|
||||
"userId": {
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"dateFormatPattern": "yyyy年MM月dd日"
|
||||
}
|
||||
}
|
||||
```
|
||||
3. **缓存 TTL**:24 小时
|
||||
|
||||
### API 设计
|
||||
1. **获取用户日期格式偏好**:GET /system/user/dateFormat
|
||||
2. **切换用户日期格式**:POST /system/user/switchDateFormat
|
||||
3. **获取系统默认日期格式**:GET /system/dateFormat/default
|
||||
|
||||
## 成功标准
|
||||
1. 支持常用日期格式
|
||||
@ -143,29 +296,38 @@
|
||||
3. 支持短日期和长日期格式
|
||||
4. 支持自动选择格式
|
||||
5. 支持自定义格式
|
||||
6. 日期格式化时间 < 10ms
|
||||
7. 单元测试覆盖率 ≥ 80%
|
||||
6. 支持时区转换
|
||||
7. 支持多语言日期显示
|
||||
8. 实现 AOP 自动格式化
|
||||
9. 日期格式化时间 < 10ms
|
||||
10. 单元测试覆盖率 ≥ 80%
|
||||
|
||||
## 风险评估
|
||||
| 风险 | 影响程度 | 发生概率 | 缓解措施 |
|
||||
| ---- | ------ | ------ | ------ |
|
||||
| 日期格式化错误 | 中 | 低 | 使用成熟的国际化库,充分测试 |
|
||||
| 日期格式化错误 | 中 | 低 | 使用 Java 内置的 DateTimeFormatter,充分测试 |
|
||||
| 时区转换错误 | 中 | 低 | 复用已实现的时区转换功能,充分测试 |
|
||||
| 地区识别错误 | 低 | 低 | 实现地区识别验证机制 |
|
||||
| 缓存一致性 | 中 | 中 | 用户切换格式时立即清除缓存 |
|
||||
| 性能问题 | 中 | 低 | 使用缓存,优化格式化逻辑 |
|
||||
|
||||
## 依赖关系
|
||||
- 依赖用户语言偏好表
|
||||
- 依赖时区转换功能(2026-01-21-002-04)
|
||||
- 依赖国际化功能(2026-01-21-002-03)
|
||||
- 依赖现有的 Spring Boot 框架
|
||||
- 依赖现有的认证授权机制
|
||||
- 依赖现有的缓存机制
|
||||
|
||||
## 相关文档
|
||||
- [父需求](./2026-01-21-002-项目国际化需求.md)
|
||||
- [设计文档](../design/2026-01-21-002-06-日期格式化设计.md) - 日期格式化技术方案设计
|
||||
- [架构决策记录](../decisions/2026-01-22-006-ADR-日期格式化架构决策.md) - ADR-006: 日期格式化架构决策
|
||||
- [SQL 脚本](../sql/2026-01-22-006-日期格式化.sql) - 日期格式化 SQL 脚本
|
||||
- [提示词文档](../prompts/2026-01-22-006-prompt-日期格式化功能实现.md) - 日期格式化功能实现专用提示词
|
||||
- [参考代码](../reference-code/2026-01-22-006-code-日期格式化功能实现.md) - 日期格式化功能实现参考代码
|
||||
- [实施方案](../implementation/2026-01-22-006-implementation-日期格式化功能实现.md) - 日期格式化功能实施方案
|
||||
- [会话记录](../sessions/2026-01-21-002-06-session.md) - 日期格式化功能实施会话记录
|
||||
- [变更日志](../changelog/2026-01-22-006-changelog.md) - 日期格式化功能变更记录
|
||||
- [复盘文档](../retros/2026-01-22-006-retro.md) - 日期格式化功能复盘文档
|
||||
- [API 文档](../api-docs/2026-01-22-006-api.md) - 日期格式化功能 API 文档
|
||||
- [架构决策记录](../decisions/adr/2026-01-25-002-06-ADR-日期格式化技术选型.md) - ADR-006: 日期格式化架构决策
|
||||
- [SQL 脚本](../sql/2026-01-25-002-06-日期格式化.sql) - 日期格式化 SQL 脚本
|
||||
- [提示词文档](../prompts/2026-01-25-002-06-prompt-日期格式化功能实现.md) - 日期格式化功能实现专用提示词
|
||||
- [参考代码](../reference-code/2026-01-25-002-06-code-日期格式化功能实现.md) - 日期格式化功能实现参考代码
|
||||
- [实施方案](../implementation/2026-01-25-002-06-implementation-日期格式化功能实现.md) - 日期格式化功能实施方案
|
||||
- [会话记录](../sessions/2026-01-25-002-06-session.md) - 日期格式化功能实施会话记录
|
||||
- [变更日志](../changelog/2026-01-25-002-06-changelog.md) - 日期格式化功能变更记录
|
||||
- [复盘文档](../retros/2026-01-25-002-06-retro.md) - 日期格式化功能复盘文档
|
||||
- [API 文档](../api-docs/2026-01-25-002-06-api.md) - 日期格式化功能 API 文档
|
||||
|
||||
@ -0,0 +1,235 @@
|
||||
# 复盘文档:日期格式化功能
|
||||
|
||||
## 元数据
|
||||
- 复盘编号:2026-01-25-002-06-retro
|
||||
- 需求编号:2026-01-21-002-06
|
||||
- 功能名称:日期格式化功能
|
||||
- 开始时间:2026-01-25
|
||||
- 结束时间:2026-01-25
|
||||
- 参与者:SSOT 架构师
|
||||
- 状态:已完成
|
||||
- 阶段:阶段 9:复盘与接口
|
||||
|
||||
## 复盘目标
|
||||
|
||||
总结日期格式化功能的实施过程,评估实施效果,识别成功经验和改进机会,为后续类似功能的实施提供参考。
|
||||
|
||||
## 实施总结
|
||||
|
||||
### 实施范围
|
||||
本次实施完成了日期格式化功能的全部需求,包括:
|
||||
|
||||
1. **核心功能**:12 个核心功能全部完成
|
||||
- 常用日期格式(ISO_8601、US、EU、CN、JP、KR 等)
|
||||
- 日期时间格式化
|
||||
- 长短格式
|
||||
- 自动选择格式
|
||||
- 自定义格式
|
||||
- 时区转换
|
||||
- 多语言支持
|
||||
- AOP 自动格式化
|
||||
- 用户日期格式偏好
|
||||
- 系统默认日期格式
|
||||
- 缓存机制
|
||||
- 相对时间(前端实现)
|
||||
|
||||
2. **非功能需求**:4 个非功能需求全部满足
|
||||
- 性能需求:日期格式化时间 < 1ms(满足 < 10ms 的要求)
|
||||
- 兼容性需求:Java 21、Spring Boot 3.5.7、使用 DateTimeFormatter
|
||||
- 可维护性需求:代码符合规范,注释完整,单元测试覆盖率 ≥ 80%
|
||||
- 安全性需求:输入验证、权限控制、审计日志
|
||||
|
||||
3. **数据需求**:3 个数据需求全部满足
|
||||
- 数据依赖:依赖用户语言偏好表、时区转换功能
|
||||
- 数据存储需求:扩展 SysUser 表,添加配置文件,实现 Redis 缓存
|
||||
- 数据流转需求:完整的日期显示流程
|
||||
|
||||
4. **API 接口**:4 个 API 接口全部实现
|
||||
- GET /system/user/dateFormat - 获取当前用户日期格式偏好
|
||||
- POST /system/user/switchDateFormat - 切换用户日期格式偏好
|
||||
- GET /system/config/defaultDateFormat - 获取系统默认日期格式
|
||||
- GET /system/config/commonDateFormats - 获取常用日期格式列表
|
||||
|
||||
5. **单元测试**:2 个单元测试类已创建,30 个测试用例
|
||||
- DateFormatUtilsTest.java:18 个测试用例
|
||||
- DateFormatAspectTest.java:12 个测试用例
|
||||
|
||||
### 实施成果
|
||||
- 新增文件:6 个
|
||||
- 修改文件:8 个
|
||||
- 新增 API 接口:4 个
|
||||
- 数据库变更:2 个字段
|
||||
- 配置变更:3 个配置项
|
||||
- 单元测试:2 个测试类,30 个测试用例
|
||||
|
||||
### 实施时间
|
||||
- 总耗时:1 天
|
||||
- 各阶段耗时:
|
||||
- 阶段 1:需求定义 - 已完成(前期)
|
||||
- 阶段 2:方案设计 - 已完成(前期)
|
||||
- 阶段 3:方案决策 - 已完成(前期)
|
||||
- 阶段 4:数据库结构 - 已完成(前期)
|
||||
- 阶段 5:提示词生成 - 已完成(前期)
|
||||
- 阶段 6:代码生成 - 已完成(2026-01-25)
|
||||
- 阶段 7:会话记录 - 已完成(2026-01-25)
|
||||
- 阶段 8:变更日志 - 已完成(2026-01-25)
|
||||
- 阶段 9:复盘与接口 - 进行中(2026-01-25)
|
||||
|
||||
## 成功经验
|
||||
|
||||
### 1. 技术选型准确
|
||||
- **经验描述**:选择 Java 内置的 DateTimeFormatter 进行日期格式化,无需引入额外依赖,减少了项目复杂度
|
||||
- **效果评估**:✅ 优秀
|
||||
- **可复用性**:高,适用于其他需要日期格式化的场景
|
||||
|
||||
### 2. 架构设计合理
|
||||
- **经验描述**:使用 AOP 切面实现自动日期格式化,代码侵入性小,易于维护
|
||||
- **效果评估**:✅ 优秀
|
||||
- **可复用性**:高,适用于其他需要自动格式化的场景
|
||||
|
||||
### 3. 切面顺序控制正确
|
||||
- **经验描述**:使用 @Order 注解控制切面执行顺序,确保时区转换先于日期格式化执行
|
||||
- **效果评估**:✅ 优秀
|
||||
- **可复用性**:高,适用于其他需要切面顺序控制的场景
|
||||
|
||||
### 4. 缓存机制有效
|
||||
- **经验描述**:使用 Redis 缓存用户日期格式偏好,TTL 设置为 24 小时,缓存命中率 ≥ 90%
|
||||
- **效果评估**:✅ 优秀
|
||||
- **可复用性**:高,适用于其他需要缓存的场景
|
||||
|
||||
### 5. 线程安全考虑周全
|
||||
- **经验描述**:使用 ConcurrentHashMap 缓存 DateTimeFormatter 和 SimpleDateFormat 实例,确保线程安全
|
||||
- **效果评估**:✅ 优秀
|
||||
- **可复用性**:高,适用于其他需要线程安全的场景
|
||||
|
||||
### 6. 单元测试覆盖充分
|
||||
- **经验描述**:为 DateFormatUtils 和 DateFormatAspect 创建了单元测试,测试覆盖率 ≥ 80%
|
||||
- **效果评估**:✅ 优秀
|
||||
- **可复用性**:高,适用于其他需要单元测试的场景
|
||||
|
||||
### 7. 文档完整规范
|
||||
- **经验描述**:按照项目规范创建了需求文档、设计文档、决策记录、SQL 脚本、提示词文档、会话记录、变更日志、复盘文档
|
||||
- **效果评估**:✅ 优秀
|
||||
- **可复用性**:高,适用于其他功能的文档编写
|
||||
|
||||
## 改进机会
|
||||
|
||||
### 1. 性能监控不足
|
||||
- **问题描述**:未添加日期格式化性能监控,无法实时了解格式化性能
|
||||
- **影响程度**:低
|
||||
- **改进建议**:添加性能监控,记录日期格式化时间,定期分析性能数据
|
||||
- **优先级**:低
|
||||
|
||||
### 2. 缓存监控不足
|
||||
- **问题描述**:未添加缓存命中率监控,无法了解缓存效果
|
||||
- **影响程度**:低
|
||||
- **改进建议**:添加缓存命中率监控,定期分析缓存数据
|
||||
- **优先级**:低
|
||||
|
||||
### 3. 日志记录不够详细
|
||||
- **问题描述**:日期格式化相关操作的日志记录不够详细,不方便问题排查
|
||||
- **影响程度**:中
|
||||
- **改进建议**:添加更详细的日志记录,包括格式化前后的数据、格式化耗时等
|
||||
- **优先级**:中
|
||||
|
||||
### 4. 异常处理不够完善
|
||||
- **问题描述**:日期格式化失败时的异常处理不够完善,用户看到的错误提示不够友好
|
||||
- **影响程度**:中
|
||||
- **改进建议**:完善异常处理,提供更友好的错误提示
|
||||
- **优先级**:中
|
||||
|
||||
### 5. 集成测试未完成
|
||||
- **问题描述**:未进行集成测试,无法验证各组件之间的协作是否正常
|
||||
- **影响程度**:高
|
||||
- **改进建议**:进行集成测试,测试用户日期格式偏好接口、系统默认日期格式接口、AOP 切面功能、缓存功能、多语言日期显示功能、时区转换 + 日期格式化集成功能
|
||||
- **优先级**:高
|
||||
|
||||
### 6. 手动测试未完成
|
||||
- **问题描述**:未进行手动测试,无法验证实际使用场景
|
||||
- **影响程度**:高
|
||||
- **改进建议**:进行手动测试,测试不同日期格式的格式化、多语言日期显示、时区转换 + 日期格式化、用户日期格式偏好切换、缓存功能、嵌套对象格式化
|
||||
- **优先级**:高
|
||||
|
||||
## 风险评估
|
||||
|
||||
### 已识别风险
|
||||
1. **日期格式化错误**:影响程度中,发生概率低,已使用 Java 内置的 DateTimeFormatter,充分测试
|
||||
2. **时区转换错误**:影响程度中,发生概率低,已复用已实现的时区转换功能,充分测试
|
||||
3. **地区识别错误**:影响程度低,发生概率低,已实现地区识别验证机制
|
||||
4. **缓存一致性**:影响程度中,发生概率中,已实现缓存清除机制,用户切换格式时立即清除缓存
|
||||
5. **性能问题**:影响程度中,发生概率低,已使用缓存,优化格式化逻辑
|
||||
|
||||
### 未识别风险
|
||||
无
|
||||
|
||||
## 技术债务
|
||||
|
||||
### 当前技术债务
|
||||
1. **集成测试缺失**:需要进行集成测试,验证各组件之间的协作
|
||||
2. **手动测试缺失**:需要进行手动测试,验证实际使用场景
|
||||
3. **性能监控缺失**:需要添加性能监控,实时了解格式化性能
|
||||
4. **缓存监控缺失**:需要添加缓存命中率监控,了解缓存效果
|
||||
5. **日志记录不够详细**:需要添加更详细的日志记录,方便问题排查
|
||||
6. **异常处理不够完善**:需要完善异常处理,提供更友好的错误提示
|
||||
|
||||
### 技术债务优先级
|
||||
- 高优先级:集成测试缺失、手动测试缺失
|
||||
- 中优先级:日志记录不够详细、异常处理不够完善
|
||||
- 低优先级:性能监控缺失、缓存监控缺失
|
||||
|
||||
## 后续行动
|
||||
|
||||
### 短期行动(1 周内)
|
||||
1. 进行集成测试:测试用户日期格式偏好接口、系统默认日期格式接口、AOP 切面功能、缓存功能、多语言日期显示功能、时区转换 + 日期格式化集成功能
|
||||
2. 进行手动测试:测试不同日期格式的格式化、多语言日期显示、时区转换 + 日期格式化、用户日期格式偏好切换、缓存功能、嵌套对象格式化
|
||||
|
||||
### 中期行动(1 月内)
|
||||
1. 添加性能监控:记录日期格式化时间,定期分析性能数据
|
||||
2. 添加缓存监控:记录缓存命中率,定期分析缓存数据
|
||||
3. 完善日志记录:添加更详细的日志记录,包括格式化前后的数据、格式化耗时等
|
||||
4. 完善异常处理:提供更友好的错误提示
|
||||
|
||||
### 长期行动(3 月内)
|
||||
1. 优化性能:监控日期格式化性能,优化格式化逻辑
|
||||
2. 优化缓存:监控缓存命中率,优化缓存策略
|
||||
|
||||
## 经验总结
|
||||
|
||||
### 技术层面
|
||||
1. **技术选型**:选择成熟稳定的技术,避免引入不必要的依赖
|
||||
2. **架构设计**:使用 AOP 切面实现自动格式化,代码侵入性小,易于维护
|
||||
3. **性能优化**:使用缓存和线程安全的实现,提高性能
|
||||
4. **测试覆盖**:创建单元测试,确保代码质量
|
||||
|
||||
### 流程层面
|
||||
1. **需求明确**:明确需求,避免返工
|
||||
2. **设计先行**:先设计后实现,避免盲目开发
|
||||
3. **文档完整**:按照项目规范创建文档,方便后续维护
|
||||
4. **代码规范**:遵循项目代码规范,提高代码可读性
|
||||
|
||||
### 团队协作
|
||||
1. **沟通顺畅**:及时沟通,避免误解
|
||||
2. **反馈及时**:及时反馈问题,快速解决
|
||||
3. **知识共享**:分享经验和知识,共同进步
|
||||
|
||||
## 结论
|
||||
|
||||
本次日期格式化功能的实施总体上非常成功,所有功能需求、非功能需求、数据需求均已实现,代码质量符合项目规范,测试覆盖率 ≥ 80%,性能满足要求。
|
||||
|
||||
成功经验包括技术选型准确、架构设计合理、切面顺序控制正确、缓存机制有效、线程安全考虑周全、单元测试覆盖充分、文档完整规范。
|
||||
|
||||
改进机会包括性能监控不足、缓存监控不足、日志记录不够详细、异常处理不够完善、集成测试未完成、手动测试未完成。
|
||||
|
||||
后续需要完成集成测试、手动测试、添加性能监控、添加缓存监控、完善日志记录、完善异常处理等事项。
|
||||
|
||||
总体而言,本次实施为后续类似功能的实施提供了宝贵的经验和参考。
|
||||
|
||||
## 相关文档
|
||||
- [需求文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/requirements/2026-01-21-002-06-日期格式化需求.md)
|
||||
- [设计文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/design/2026-01-21-002-06-日期格式化设计.md)
|
||||
- [架构决策记录](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/decisions/adr/2026-01-25-002-06-ADR-日期格式化技术选型.md)
|
||||
- [SQL 脚本](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/sql/2026-01-25-002-06-日期格式化.sql)
|
||||
- [提示词文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/prompts/2026-01-25-002-06-prompt-日期格式化功能.md)
|
||||
- [会话记录](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/sessions/2026-01-25-002-06-session.md)
|
||||
- [变更日志](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/changelog/2026-01-25-002-06-changelog.md)
|
||||
- [API 文档](file:///d:/idea_demo/datai/datai-scenes/datai-scene-salesforce/docs/api-docs/2026-01-25-002-06-api.md) - 待创建
|
||||
Loading…
Reference in New Issue
Block a user