feat(service): 实现国际化支持并优化Service层
重构文件类型相关Service以支持国际化查询 添加findOneByLang辅助方法统一查询逻辑 实现StringConverter支持UI控件显示 优化缓存配置和查询性能 新增UnitStringConverter和CustomerCatalogStringConverter 完善文档和测试用例
This commit is contained in:
@@ -24,11 +24,13 @@ Model和Vo的对应关系记录在 create_vo.md 结果记录中,如果未找
|
||||
- 所在目录: D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\vm
|
||||
- 继承 IdentityViewModel<VO>
|
||||
- 包含一个 名为 from 的静态方法
|
||||
public static BankViewModel from(BankVo v) {
|
||||
```java
|
||||
public static BankViewModel from(BankVo v) {
|
||||
BankViewModel vm = new BankViewModel();
|
||||
vm.update(v);
|
||||
return vm;
|
||||
}
|
||||
}
|
||||
```
|
||||
- 继承实现 void updateFrom(Vo v) 方法
|
||||
- 继承实现 void copyTo(Vo v) 方法
|
||||
- 关联对象 使用 ObjectProperty<Integer>,不使用 IntegerProperty
|
||||
|
||||
129
docs/task/enum_service_internationalization_analysis.md
Normal file
129
docs/task/enum_service_internationalization_analysis.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 继承自BaseEnumEntity的Vo类对应Service国际化支持分析报告
|
||||
|
||||
## 概述
|
||||
|
||||
本报告分析了`d:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\service`目录下,所有处理继承自`BaseEnumEntity`的Vo类的Service实现,检查它们是否符合`service_layer_rules.md`文档中关于国际化支持的要求。
|
||||
|
||||
## 检查的Service列表
|
||||
|
||||
通过分析,以下是处理继承自`BaseEnumEntity`的Vo类的Service:
|
||||
|
||||
1. ContractFileTypeService - 处理ContractFileTypeLocalVo
|
||||
2. ProjectFileTypeService - 处理ProjectFileTypeLocalVo
|
||||
3. VendorFileTypeService - 处理VendorFileTypeLocalVo
|
||||
4. CompanyFileTypeService - 处理CompanyFileTypeLocalVo
|
||||
5. CompanyCustomerFileTypeService - 处理CustomerFileTypeLocalVo
|
||||
6. VendorTypeService - 处理VendorTypeLocalVo
|
||||
7. UnitService - 处理UnitVo(注:经过详细检查,发现UnitVo并不继承自BaseEnumEntity)
|
||||
|
||||
## 国际化支持实现情况分析
|
||||
|
||||
### 1. 符合国际化支持要求的Service
|
||||
|
||||
以下Service完全符合`service_layer_rules.md`文档中关于国际化支持的要求:
|
||||
|
||||
#### CompanyFileTypeService
|
||||
- ✅ 实现了`findAll(Locale)`方法并添加了`@Cacheable`注解
|
||||
- ✅ 返回`Map<CompanyFileType, CompanyFileTypeLocalVo>`格式数据
|
||||
- ✅ 提供了`findOneByLang`私有辅助方法,用于构建参数、分页查询和结果提取
|
||||
- ✅ 实现了`findByLocaleAndValue`和`findByLocaleAndType`方法
|
||||
- ✅ 使用了`@CacheConfig`配置缓存
|
||||
- ✅ 实现了`getStringConverter`方法
|
||||
|
||||
#### CompanyCustomerFileTypeService
|
||||
- ✅ 实现了`findAll(Locale)`方法并添加了`@Cacheable`注解
|
||||
- ✅ 返回`Map<CustomerFileType, CustomerFileTypeLocalVo>`格式数据
|
||||
- ✅ 提供了`findOneByLang`私有辅助方法
|
||||
- ✅ 实现了`findByLocaleAndValue`和`findByLocaleAndType`方法
|
||||
- ✅ 使用了`@CacheConfig`配置缓存
|
||||
- ✅ 实现了`getStringConverter`方法
|
||||
|
||||
### 2. 已完成国际化支持优化的Service
|
||||
|
||||
以下Service在本任务中已完成国际化支持优化,现在完全符合要求:
|
||||
|
||||
#### ContractFileTypeService
|
||||
- ✅ 实现了`findAll(Locale)`方法并添加了`@Cacheable`注解
|
||||
- ✅ 返回`Map<ContractFileType, ContractFileTypeLocalVo>`格式数据
|
||||
- ✅ 提供了`findOneByLang`私有辅助方法
|
||||
- ✅ 实现了`findByLocaleAndValue`和`findByLocaleAndType`方法
|
||||
- ✅ 使用了`@CacheConfig`配置缓存
|
||||
- ✅ 实现了`getStringConverter`方法
|
||||
|
||||
#### ProjectFileTypeService
|
||||
- ✅ 实现了`findAll(Locale)`方法并添加了`@Cacheable`注解
|
||||
- ✅ 返回`Map<ProjectFileType, ProjectFileTypeLocalVo>`格式数据
|
||||
- ✅ 提供了`findOneByLang`私有辅助方法
|
||||
- ✅ 实现了`findByLocaleAndValue`和`findByLocaleAndType`方法
|
||||
- ✅ 使用了`@CacheConfig`配置缓存
|
||||
- ✅ 实现了`getStringConverter`方法
|
||||
|
||||
#### VendorFileTypeService
|
||||
- ✅ 实现了`findAll(Locale)`方法并添加了`@Cacheable`注解
|
||||
- ✅ 返回`Map<VendorFileType, VendorFileTypeLocalVo>`格式数据
|
||||
- ✅ 提供了`findOneByLang`私有辅助方法
|
||||
- ✅ 实现了`findByLocaleAndValue`和`findByLocaleAndType`方法
|
||||
- ✅ 使用了`@CacheConfig`配置缓存
|
||||
- ✅ 实现了`getStringConverter`方法
|
||||
|
||||
#### VendorTypeService
|
||||
- ✅ 实现了`findAll(Locale)`方法并添加了`@Cacheable`注解
|
||||
- ✅ 返回`Map<VendorType, VendorTypeLocalVo>`格式数据
|
||||
- ✅ 提供了`findOneByLang`私有辅助方法
|
||||
- ✅ 实现了`findByLocaleAndValue`和`findByLocaleAndType`方法
|
||||
- ✅ 使用了`@CacheConfig`配置缓存
|
||||
- ✅ 实现了`getStringConverter`方法
|
||||
|
||||
### 3. 特殊情况:UnitService
|
||||
|
||||
经过详细检查,发现UnitVo并不继承自BaseEnumEntity,而是一个普通的数据实体。因此,它不需要实现BaseEnumEntity相关的国际化支持方法。
|
||||
|
||||
针对UnitService,我们进行了以下优化:
|
||||
- ✅ 实现了`findByName`方法,用于根据名称查找UnitVo对象
|
||||
- ✅ 实现了`getStringConverter`方法,提供UnitVo的StringConverter支持
|
||||
- ✅ 创建了`UnitStringConverter`类,支持UnitVo在JavaFX控件中的显示和转换
|
||||
- 使用了`@CacheConfig`配置缓存
|
||||
|
||||
## 实现的修改
|
||||
|
||||
在本任务中,我们对以下Service进行了修改:
|
||||
|
||||
1. **ContractFileTypeService**:
|
||||
- 添加了`findOneByLang`私有辅助方法
|
||||
- 添加了`findByLocaleAndValue`方法(重构为使用findOneByLang)
|
||||
- 添加了`findByLocaleAndType`方法
|
||||
|
||||
2. **ProjectFileTypeService**:
|
||||
- 添加了`findOneByLang`私有辅助方法
|
||||
- 添加了`findByLocaleAndValue`方法(重构为使用findOneByLang)
|
||||
- 添加了`findByLocaleAndType`方法
|
||||
|
||||
3. **VendorFileTypeService**:
|
||||
- 添加了`findOneByLang`私有辅助方法
|
||||
- 添加了`findByLocaleAndValue`方法(重构为使用findOneByLang)
|
||||
- 添加了`findByLocaleAndType`方法
|
||||
|
||||
4. **VendorTypeService**:
|
||||
- 添加了`findAll(Locale)`方法
|
||||
- 添加了`findOneByLang`私有辅助方法
|
||||
- 添加了`findByLocaleAndValue`方法(重构为使用findOneByLang)
|
||||
- 添加了`findByLocaleAndType`方法
|
||||
|
||||
5. **UnitService**:
|
||||
- 添加了`findByName`方法
|
||||
- 实现了`getStringConverter`方法
|
||||
|
||||
6. **新增类**:
|
||||
- 创建了`UnitStringConverter`类,支持UnitVo的字符串转换
|
||||
|
||||
## 结论
|
||||
|
||||
通过本任务的优化,所有处理继承自`BaseEnumEntity`的Vo类的Service现在都完全符合`service_layer_rules.md`文档中的国际化支持要求。对于特殊情况UnitService,我们也根据其实际需求进行了适当的优化。
|
||||
|
||||
这些优化确保了:
|
||||
1. 所有相关Service都实现了统一的国际化支持方法
|
||||
2. 查询方法都正确配置了缓存,提高了系统性能
|
||||
3. 代码结构更加一致,提高了可维护性
|
||||
4. 为UI控件提供了良好的字符串转换支持
|
||||
|
||||
经过这些改进,系统的国际化支持更加完善,为用户提供了更好的多语言体验。
|
||||
180
docs/task/service_compliance_analysis.md
Normal file
180
docs/task/service_compliance_analysis.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Contract-Manager 项目 Service 层合规性分析报告
|
||||
|
||||
## 概述
|
||||
本报告对 Contract-Manager 项目中 `client\src\main\java\com\ecep\contract\service` 目录下的 Service 实现进行分析,评估它们是否符合 `docs\task\service_layer_rules.md` 文档中定义的规则和最佳实践。
|
||||
|
||||
## 合规性评估
|
||||
|
||||
### 1. 基础架构
|
||||
|
||||
#### 1.1 继承体系
|
||||
- **符合**: 大多数业务 Service 都继承自泛型抽象类 `QueryService<T extends IdentityEntity, TV extends IdentityViewModel<T>>`
|
||||
- 例如: `CompanyFileTypeService`, `ContractService`, `CompanyService`, `ProjectService`, `VendorService`
|
||||
- **不符合**: `SysConfService` 没有继承 `QueryService`,而是直接实现了自己的异步调用方法
|
||||
|
||||
#### 1.2 核心接口实现
|
||||
- **符合**: 继承 `QueryService` 的 Service 间接实现了 `IEntityService<T>` 和 `ViewModelService<T, TV>` 接口
|
||||
|
||||
### 2. 缓存管理
|
||||
|
||||
#### 2.1 缓存注解使用
|
||||
- **符合**: 许多 Service 使用了 `@CacheConfig` 和相关缓存注解
|
||||
- `@CacheConfig(cacheNames = "缓存名称")`: `ContractFileTypeService`, `ProjectFileTypeService`, `ContractService`, `CompanyService`, `SysConfService`
|
||||
- `@Cacheable`: 用于 `findById`, `findAll`, `findByCode` 等查询方法
|
||||
- `@CachePut`/`@CacheEvict`: 用于 `save`, `delete` 等修改方法
|
||||
- `@Caching`: 用于组合多个缓存操作
|
||||
- **不符合**: 部分 Service 未使用缓存注解
|
||||
- `VendorService`, `ProjectService` 没有配置缓存
|
||||
|
||||
#### 2.2 缓存键规则
|
||||
- **符合**: 缓存键的使用基本符合规则
|
||||
- `#p0`: 用于方法参数,如 `@Cacheable(key = "#p0")`
|
||||
- `'all'`: 用于所有数据,如 `@Cacheable(key = "'all'")`
|
||||
- 组合键: 如 `@Cacheable(key = "'code-'+#p0")`
|
||||
|
||||
### 3. 数据操作模式
|
||||
|
||||
#### 3.1 基础 CRUD 操作
|
||||
- **符合**: 所有继承 `QueryService` 的 Service 都获得了基础 CRUD 操作
|
||||
- `findById(Integer id)`: 根据 ID 查询单条数据
|
||||
- `save(T entity)`: 保存实体
|
||||
- `delete(T entity)`: 删除实体
|
||||
- `findAll()`: 查询所有数据
|
||||
- `findAll(Map<String, Object> params, Pageable pageable)`: 带参数和分页的查询
|
||||
|
||||
#### 3.2 异步操作
|
||||
- **符合**: 大多数 Service 使用 `QueryService` 提供的异步机制
|
||||
- 通过 `async(String method, Object... params)` 方法实现异步调用
|
||||
- 返回 `CompletableFuture<JsonNode>` 对象
|
||||
- **不符合**: `SysConfService` 实现了自己的异步方法,如 `asyncFindById`, `asyncFindAll`
|
||||
|
||||
#### 3.3 业务查询方法
|
||||
- **符合**: 业务 Service 通常添加特定的查询方法
|
||||
- `findByCode(String code)`: `ContractService`, `CompanyService`, `ProjectService`
|
||||
- `findByName(String name)`: `ContractService`, `CompanyService`, `ProjectService`
|
||||
- `findAllByName(String name)`: `CompanyService`
|
||||
|
||||
#### 3.4 参数构建
|
||||
- **符合**: 普遍使用 `ParamUtils` 工具类构建查询参数
|
||||
- `ParamUtils.builder().equals("字段名", 值).build()`
|
||||
- `ParamUtils.equal("字段名", 值)`
|
||||
|
||||
### 4. 国际化支持
|
||||
|
||||
#### 4.1 多语言查询
|
||||
- **符合**: 文件类型相关 Service 提供了基于 Locale 的查询方法
|
||||
- `findAll(Locale locale)`: `ContractFileTypeService`, `ProjectFileTypeService`, `CompanyFileTypeService`, `CompanyCustomerFileTypeService`
|
||||
- 通过 `locale.toLanguageTag()` 转换为语言标签进行查询
|
||||
- 返回结果通常转换为 Map,方便按类型查找
|
||||
- **不适用**: 对于不需要国际化的业务 Service,没有提供这些方法
|
||||
|
||||
#### 4.2 辅助方法
|
||||
- **符合**: 文件类型相关 Service 实现了辅助方法
|
||||
- `findOneByLang(Locale locale, String key, Object value)`: 私有辅助方法
|
||||
- `findByLocaleAndValue(Locale locale, String string)`: 按语言和值查询
|
||||
- `findByLocaleAndType(Locale locale, EnumType type)`: 按语言和枚举类型查询
|
||||
- **不适用**: 对于不需要国际化的业务 Service,没有提供这些方法
|
||||
|
||||
### 5. 类型转换
|
||||
|
||||
#### 5.1 StringConverter 实现
|
||||
- **符合**: 实体类型 Service 实现了 `getStringConverter()` 方法
|
||||
- 返回 `StringConverter<T>` 实例
|
||||
- 创建专门的 Converter 类,如 `ContractFileTypeStringConverter`, `ProjectFileTypeStringConverter`
|
||||
- **不符合**: 业务对象 Service 如 `ContractService`, `CompanyService`, `VendorService`, `ProjectService` 没有实现 `getStringConverter()` 方法
|
||||
|
||||
### 6. 业务特性
|
||||
|
||||
#### 6.1 文件路径管理
|
||||
- **符合**: 部分 Service 管理文件路径
|
||||
- `getBasePath()` 方法: `ContractService`, `CompanyService`, `VendorService`, `ProjectService`
|
||||
- 通过 `SysConfService` 获取配置的基础路径
|
||||
- 路径存储为 `File` 对象
|
||||
|
||||
#### 6.2 业务验证
|
||||
- **符合**: 包含业务特定的验证逻辑
|
||||
- `verifyEnterpriseStatus(CompanyVo company, LocalDate verifyDate, MessageHolder holder)`: `CompanyService`
|
||||
- `verify(ContractVo contract, MessageHolder holder)`: `VendorService`
|
||||
- 使用 `MessageHolder` 存储验证结果和错误信息
|
||||
|
||||
#### 6.3 Spring Bean 获取
|
||||
- **符合**: 使用 `SpringApp.getBean(ServiceClass.class)` 获取其他 Service 实例
|
||||
- 例如 `VendorService` 中获取 `SysConfService`,`ProjectService` 中获取 `ProjectSaleTypeService`
|
||||
|
||||
### 7. 代码规范
|
||||
|
||||
#### 7.1 类命名
|
||||
- **符合**: 服务类名以 `Service` 结尾
|
||||
- `ContractService`, `CompanyService`, `ProjectService` 等
|
||||
- 实现类直接使用业务名称+Service,无 I 前缀
|
||||
|
||||
#### 7.2 注释规范
|
||||
- **部分符合**: 大部分类和方法有 Javadoc 注释
|
||||
- 例如 `ContractService.getBasePath()` 方法有清晰的注释
|
||||
- **不符合**: 部分方法缺少注释
|
||||
- 例如 `VendorService.findByCompany()` 方法没有注释
|
||||
- 许多未实现的方法只有 TODO 注释
|
||||
|
||||
#### 7.3 异常处理
|
||||
- **符合**: 使用 `RuntimeException` 包装异常
|
||||
- 异常消息清晰描述错误情况
|
||||
- **不符合**: `SysConfService` 中有些异常处理直接打印堆栈,没有抛出自定义异常
|
||||
|
||||
### 8. 特定业务 Service 模式
|
||||
|
||||
#### 8.1 实体类型 Service
|
||||
- **符合**: 如 `ContractFileTypeService`, `ProjectFileTypeService` 等
|
||||
- 管理枚举类型的本地化数据
|
||||
- 提供缓存管理
|
||||
- 实现 StringConverter
|
||||
- 提供基于 Locale 的查询方法
|
||||
|
||||
#### 8.2 业务对象 Service
|
||||
- **符合**: 如 `ContractService`, `CompanyService`, `ProjectService` 等
|
||||
- 管理核心业务实体
|
||||
- 实现特定的业务逻辑
|
||||
- 处理文件和路径
|
||||
- 提供业务验证
|
||||
|
||||
#### 8.3 关联关系管理
|
||||
- **符合**: Service 之间通过依赖注入或 `SpringApp.getBean()` 进行协作
|
||||
- 处理实体之间的关联关系
|
||||
|
||||
### 9. 特殊实现模式
|
||||
|
||||
#### 9.1 自定义 BeanName
|
||||
- **符合**: 部分 Service 重写 `getBeanName()` 方法
|
||||
- 例如 `CompanyService` 指定 WebSocket 通信的 Bean 名称
|
||||
|
||||
#### 9.2 未实现方法
|
||||
- **符合**: 使用 `throw new UnsupportedOperationException("Unimplemented method 'methodName'")` 标记未实现的方法
|
||||
- 例如 `CompanyService.merge()`, `VendorService.getVendorApprovedListTemplate()` 等
|
||||
|
||||
#### 9.3 分页查询
|
||||
- **符合**: 大量使用 `Pageable.ofSize(1)` 进行单条记录的精确查询
|
||||
- 使用 `stream().findFirst().orElse(null)` 提取结果
|
||||
- 例如 `ProjectService.findByName()`, `ProjectService.findByCode()`
|
||||
|
||||
## 不符合规范的 Service 列表
|
||||
|
||||
| Service 类名 | 不符合项 | 建议改进 |
|
||||
|------------|---------|---------|
|
||||
| `SysConfService` | 未继承 `QueryService`,实现了自己的异步方法 | 重构为继承 `QueryService`,使用标准的异步方法 |
|
||||
| `VendorService` | 未使用缓存注解 | 添加 `@CacheConfig` 和相关缓存注解 |
|
||||
| `ProjectService` | 未使用缓存注解 | 添加 `@CacheConfig` 和相关缓存注解 |
|
||||
| 业务对象 Service | 没有实现 `getStringConverter()` 方法 | 如果需要类型转换,实现该方法 |
|
||||
| 多个 Service | 部分方法缺少注释 | 完善 Javadoc 注释 |
|
||||
| `SysConfService` | 异常处理不统一 | 统一使用 `RuntimeException` 包装异常 |
|
||||
|
||||
## 总结
|
||||
|
||||
Contract-Manager 项目的 Service 层实现整体上符合 `service_layer_rules.md` 文档中定义的规则和最佳实践。大多数 Service 遵循了基础架构、缓存管理、数据操作模式等规范,特别是实体类型 Service 的实现非常一致。
|
||||
|
||||
主要的改进点包括:
|
||||
1. 统一 Service 的继承体系,确保所有 Service 都继承自 `QueryService`
|
||||
2. 为所有 Service 添加适当的缓存管理
|
||||
3. 完善方法注释,提高代码可读性
|
||||
4. 统一异常处理机制
|
||||
5. 根据业务需求,为需要类型转换的 Service 实现 `getStringConverter()` 方法
|
||||
|
||||
通过这些改进,可以进一步提高项目的代码一致性、可维护性和性能。
|
||||
150
docs/task/service_layer_rules.md
Normal file
150
docs/task/service_layer_rules.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Contract-Manager 项目 Service 层规则与逻辑总结
|
||||
|
||||
## 1. 基础架构
|
||||
|
||||
### 1.1 继承体系
|
||||
- 所有业务Service都继承自泛型抽象类 `QueryService<T extends IdentityEntity, TV extends IdentityViewModel<T>>`
|
||||
- `QueryService` 实现了 `IEntityService<T>` 和 `ViewModelService<T, TV>` 接口
|
||||
- 泛型参数说明:
|
||||
- `T`: 实体类型(通常为Vo类)
|
||||
- `TV`: 视图模型类型
|
||||
|
||||
### 1.2 核心接口
|
||||
`IEntityService<T>` 定义了基础的实体操作方法:
|
||||
```java
|
||||
public interface IEntityService<T> {
|
||||
T findById(Integer id);
|
||||
T save(T entity);
|
||||
void delete(T entity);
|
||||
List<T> findAll();
|
||||
Page<T> findAll(Map<String, Object> params, Pageable pageable);
|
||||
StringConverter<T> getStringConverter();
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 缓存管理
|
||||
|
||||
### 2.1 缓存注解使用
|
||||
- 使用 `@CacheConfig(cacheNames = "缓存名称")` 设置缓存名称
|
||||
- 使用 `@Cacheable` 注解标记查询方法,如 `findById`、`findAll`
|
||||
- 使用 `@CachePut` 注解标记更新方法,如 `save`、`delete`
|
||||
- 使用 `@CacheEvict` 注解标记清除缓存的方法
|
||||
- 使用 `@Caching` 组合多个缓存操作
|
||||
|
||||
### 2.2 缓存键规则
|
||||
- `#p0`: 表示方法的第一个参数
|
||||
- `'all'`: 表示所有数据的缓存键
|
||||
- `'code-'+#p0`: 组合键,如 `'code-'+#p0.code` 用于按编码缓存
|
||||
|
||||
## 3. 数据操作模式
|
||||
|
||||
### 3.1 基础CRUD操作
|
||||
所有Service通过继承`QueryService`获得以下基础操作:
|
||||
- `findById(Integer id)`: 根据ID查询单条数据
|
||||
- `save(T entity)`: 保存实体
|
||||
- `delete(T entity)`: 删除实体
|
||||
- `findAll()`: 查询所有数据
|
||||
- `findAll(Map<String, Object> params, Pageable pageable)`: 带参数和分页的查询
|
||||
|
||||
### 3.2 异步操作
|
||||
- 通过 `async(String method, Object... params)` 方法实现异步调用
|
||||
- 返回 `CompletableFuture<JsonNode>` 对象
|
||||
- 有专门的异步查询方法,如 `asyncFindById`、`asyncFindAll`、`asyncCount`
|
||||
|
||||
### 3.3 业务查询方法
|
||||
业务Service通常会添加特定的查询方法:
|
||||
- `findByCode(String code)`: 按编码查询
|
||||
- `findByName(String name)`: 按名称查询
|
||||
- `findAllByName(String name)`: 按名称查询所有匹配项
|
||||
|
||||
### 3.4 参数构建
|
||||
- 使用 `ParamUtils` 工具类构建查询参数
|
||||
- 常用方法:`ParamUtils.builder().equals("字段名", 值).build()`
|
||||
|
||||
## 4. 国际化支持
|
||||
|
||||
### 4.1 多语言查询
|
||||
- 所有继承自`BaseEnumEntity`的Vo类对应的Service都必须实现国际化支持
|
||||
- 提供基于Locale的查询方法:`findAll(Locale locale)`
|
||||
- 实现方式:通过 `locale.toLanguageTag()` 转换为语言标签进行查询
|
||||
- 返回结果通常转换为Map<EnumType, EntityType>,以枚举类型为键,方便按类型查找
|
||||
- 方法上应添加`@Cacheable`注解进行缓存
|
||||
|
||||
### 4.2 辅助方法
|
||||
- 必须实现`findOneByLang(Locale locale, String key, Object value)`私有辅助方法,根据语言和参数查找单个对象
|
||||
- 实现方式:使用`ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals(key, value).build()`构建参数
|
||||
- 使用`Pageable.ofSize(1)`进行单条记录的精确查询
|
||||
- 使用`stream().findFirst().orElse(null)`提取结果
|
||||
- 必须实现`findByLocaleAndValue(Locale locale, String string)`方法,按语言和值查询
|
||||
- 调用`findOneByLang(locale, "value", string)`实现
|
||||
- 必须实现`findByLocaleAndType(Locale locale, EnumType type)`方法,按语言和枚举类型查询
|
||||
- 调用`findOneByLang(locale, "type", type)`实现
|
||||
|
||||
## 5. 类型转换
|
||||
|
||||
### 5.1 StringConverter实现
|
||||
- 所有Service都实现 `getStringConverter()` 方法,返回 `StringConverter<T>` 实例
|
||||
- 通常会创建专门的Converter类,如 `CompanyFileTypeStringConverter`、`CustomerFileTypeStringConverter`
|
||||
- Converter类通常接收对应的Service作为构造参数
|
||||
|
||||
## 6. 业务特性
|
||||
|
||||
### 6.1 文件路径管理
|
||||
- 部分Service管理文件路径,如 `getBasePath()` 方法
|
||||
- 通常通过 `SysConfService` 获取配置的基础路径
|
||||
- 路径存储为 `File` 对象
|
||||
|
||||
### 6.2 业务验证
|
||||
- 包含业务特定的验证逻辑,如 `verifyEnterpriseStatus`、`verify` 方法
|
||||
- 使用 `MessageHolder` 存储验证结果和错误信息
|
||||
|
||||
### 6.3 Spring Bean获取
|
||||
- 使用 `SpringApp.getBean(ServiceClass.class)` 获取其他Service实例
|
||||
- 用于Service之间的协作调用
|
||||
|
||||
## 7. 代码规范
|
||||
|
||||
### 7.1 类命名
|
||||
- 服务类名以 `Service` 结尾,如 `ContractService`、`CompanyService`
|
||||
- 实现类直接使用业务名称+Service,无I前缀
|
||||
|
||||
### 7.2 注释规范
|
||||
- 方法和类应有清晰的Javadoc注释
|
||||
- 私有辅助方法也应有注释说明其用途
|
||||
|
||||
### 7.3 异常处理
|
||||
- 使用 `RuntimeException` 包装所有异常
|
||||
- 异常消息应清晰描述错误情况
|
||||
- 部分Service使用日志记录错误信息
|
||||
|
||||
## 8. 特定业务Service模式
|
||||
|
||||
### 8.1 实体类型Service
|
||||
如 `CompanyFileTypeService`、`CompanyCustomerFileTypeService` 等:
|
||||
- 管理枚举类型的本地化数据
|
||||
- 提供缓存管理
|
||||
- 实现StringConverter
|
||||
- 提供基于Locale的查询方法
|
||||
|
||||
### 8.2 业务对象Service
|
||||
如 `ContractService`、`CompanyService`、`ProjectService` 等:
|
||||
- 管理核心业务实体
|
||||
- 实现特定的业务逻辑
|
||||
- 处理文件和路径
|
||||
- 提供业务验证
|
||||
|
||||
### 8.3 关联关系管理
|
||||
- Service之间通过依赖注入或 `SpringApp.getBean()` 进行协作
|
||||
- 处理实体之间的关联关系,如 `Contract` 与 `Company`、`Vendor` 等
|
||||
|
||||
## 9. 特殊实现模式
|
||||
|
||||
### 9.1 自定义BeanName
|
||||
部分Service重写 `getBeanName()` 方法,指定WebSocket通信的Bean名称
|
||||
|
||||
### 9.2 未实现方法
|
||||
使用 `throw new UnsupportedOperationException("Unimplemented method 'methodName'")` 标记未实现的方法
|
||||
|
||||
### 9.3 分页查询
|
||||
- 大量使用 `Pageable.ofSize(1)` 进行单条记录的精确查询
|
||||
- 使用 `stream().findFirst().orElse(null)` 提取结果
|
||||
124
docs/task/string_converter_implementation_guide.md
Normal file
124
docs/task/string_converter_implementation_guide.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# StringConverter 创建规则与实现逻辑
|
||||
|
||||
## 目的
|
||||
StringConverter 在 Contract-Manager 项目中用于:
|
||||
- 在UI组件(如ComboBox)中显示对象的可读字符串表示
|
||||
- 从用户输入的字符串中还原对应的对象实例
|
||||
- 支持表单数据的绑定和转换
|
||||
|
||||
## 创建规则
|
||||
|
||||
### 1. 文件位置
|
||||
StringConverter 类应位于 `com.ecep.contract.converter` 包下
|
||||
|
||||
### 2. 命名规范
|
||||
- 类名格式:`[EntityName]StringConverter`
|
||||
- 例如:`CustomerCatalogStringConverter`、`VendorCatalogStringConverter`
|
||||
|
||||
### 3. 继承关系
|
||||
- 实现 `javafx.util.StringConverter<T>` 接口,其中T为实体的VO类型
|
||||
- 例如:`StringConverter<CustomerCatalogVo>`
|
||||
|
||||
### 4. 构造函数
|
||||
- 通常需要注入对应的Service实例
|
||||
- 构造函数接收Service作为参数
|
||||
- 例如:`public CustomerCatalogStringConverter(CustomerCatalogService service)`
|
||||
|
||||
### 5. 核心方法实现
|
||||
|
||||
#### toString(T object)
|
||||
- 将对象转换为可读的字符串表示
|
||||
- 通常返回对象的名称或关键标识
|
||||
- 处理null情况
|
||||
|
||||
#### fromString(String string)
|
||||
- 从字符串还原对象实例
|
||||
- 通常调用Service的findByName或findByCode方法
|
||||
|
||||
## 与Service类的关联
|
||||
|
||||
### 1. 在Service类中创建Converter实例
|
||||
```java
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "[entity-name]-cache")
|
||||
public class [EntityName]Service extends QueryService<[EntityName]Vo, [EntityName]ViewModel> {
|
||||
|
||||
private final [EntityName]StringConverter stringConverter = new [EntityName]StringConverter(this);
|
||||
|
||||
// 实现findByName方法供StringConverter使用
|
||||
public [EntityName]Vo findByName(String name) {
|
||||
return findAll(ParamUtils.builder().equals("name", name).build(), Pageable.ofSize(1))
|
||||
.stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringConverter<[EntityName]Vo> getStringConverter() {
|
||||
return stringConverter;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 缓存配置
|
||||
Service类通常需要添加缓存注解以提高性能:
|
||||
- `@Cacheable(key = "#id")` 用于findById方法
|
||||
- `@Cacheable(key = "'all'")` 用于findAll方法
|
||||
- `@Caching(evict = {@CacheEvict(key = "'all'"), @CacheEvict(key = "#entity.id")})` 用于save和delete方法
|
||||
|
||||
## 示例实现
|
||||
|
||||
### CustomerCatalogStringConverter 示例
|
||||
```java
|
||||
package com.ecep.contract.converter;
|
||||
|
||||
import com.ecep.contract.service.CustomerCatalogService;
|
||||
import com.ecep.contract.vo.CustomerCatalogVo;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
public class CustomerCatalogStringConverter extends StringConverter<CustomerCatalogVo> {
|
||||
private final CustomerCatalogService service;
|
||||
|
||||
public CustomerCatalogStringConverter(CustomerCatalogService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(CustomerCatalogVo object) {
|
||||
return object == null ? "" : object.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomerCatalogVo fromString(String string) {
|
||||
return service.findByName(string);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 在UI组件中的使用
|
||||
StringConverter 主要用于:
|
||||
- ComboBox的数据绑定和显示
|
||||
- 表单字段的字符串转换
|
||||
- 表格单元格的格式化显示
|
||||
|
||||
### 获取StringConverter实例
|
||||
```java
|
||||
// 方式1:直接从Service获取
|
||||
StringConverter<CustomerCatalogVo> converter = customerCatalogService.getStringConverter();
|
||||
|
||||
// 方式2:通过Spring容器获取
|
||||
StringConverter<CustomerCatalogVo> converter = context.getBean(CustomerCatalogStringConverter.class);
|
||||
```
|
||||
|
||||
## 常见模式
|
||||
|
||||
1. **基础模式**:直接继承StringConverter,实现toString和fromString方法
|
||||
2. **组件模式**:继承EntityStringConverter基类,设置初始化、建议和字符串转换方法
|
||||
3. **枚举模式**:针对枚举类型的特殊处理
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保Service类实现了必要的find方法(如findByName)
|
||||
2. 处理null值情况,避免空指针异常
|
||||
3. 考虑缓存策略,提高频繁转换场景下的性能
|
||||
4. 保持Converter与Service的同步更新,确保字段变更时转换逻辑也相应更新
|
||||
220
docs/task/table_cell_implementation_guide.md
Normal file
220
docs/task/table_cell_implementation_guide.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# TableCell 实现规则与模式指南
|
||||
|
||||
## 1. 目的与作用
|
||||
|
||||
TableCell 是 JavaFX TableView 组件中的关键元素,负责表格中单个单元格的渲染和交互。在 Contract-Manager 项目中,TableCell 主要用于:
|
||||
|
||||
- 显示各种类型的数据(数字、日期、实体引用等)
|
||||
- 提供自定义的显示格式和样式
|
||||
- 支持异步加载关联数据
|
||||
- 实现单元格内编辑功能
|
||||
|
||||
## 2. 文件结构与命名规范
|
||||
|
||||
- **文件位置**:`client/src/main/java/com/ecep/contract/controller/table/cell/`
|
||||
- **命名规范**:使用 PascalCase(驼峰命名法,首字母大写),以 `TableCell` 结尾
|
||||
- 示例:`CompanyTableCell.java`、`LocalDateFieldTableCell.java`
|
||||
|
||||
## 3. TableCell 分类与实现模式
|
||||
|
||||
### 3.1 基础 TableCell
|
||||
|
||||
直接继承 JavaFX 的 `TableCell` 类,适用于简单的数据显示。
|
||||
|
||||
**实现规则**:
|
||||
- 继承 `TableCell<S, T>`,其中 S 是表格行数据类型,T 是单元格数据类型
|
||||
- 实现 `updateItem(T item, boolean empty)` 方法
|
||||
- 提供静态的 `forTableColumn()` 工厂方法
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
public class NumberTableCell<S> extends TableCell<S, Number> {
|
||||
private final NumberStringConverter numberStringConverter;
|
||||
|
||||
public NumberTableCell(NumberStringConverter numberStringConverter) {
|
||||
this.numberStringConverter = numberStringConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Number item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(numberStringConverter.toString(item));
|
||||
}
|
||||
}
|
||||
|
||||
public static <S> Callback<TableColumn<S, Number>, TableCell<S, Number>> forTableColumn(NumberStringConverter numberStringConverter) {
|
||||
return param -> new NumberTableCell<>(numberStringConverter);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 异步更新 TableCell
|
||||
|
||||
继承 `AsyncUpdateTableCell`,用于异步加载并显示关联的实体数据。
|
||||
|
||||
**实现规则**:
|
||||
- 继承 `AsyncUpdateTableCell<V, K, T>`,其中 V 是表格行数据类型,K 是 ID 类型,T 是实体类型
|
||||
- 提供构造函数接收服务对象,或实现 `getServiceBean()` 方法从 Spring 容器获取服务
|
||||
- 可选:重写 `initialize()` 方法自定义实体加载逻辑
|
||||
- 可选:重写 `format()` 方法自定义实体显示格式
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
@NoArgsConstructor
|
||||
public class CompanyTableCell<V> extends AsyncUpdateTableCell<V, Integer, CompanyVo> {
|
||||
public CompanyTableCell(CompanyService companyService) {
|
||||
setService(companyService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompanyService getServiceBean() {
|
||||
return SpringApp.getBean(CompanyService.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 可编辑 TableCell
|
||||
|
||||
实现单元格内编辑功能,如日期选择、文本编辑等。
|
||||
|
||||
**实现规则**:
|
||||
- 继承 `TableCell` 或其子类
|
||||
- 实现 `startEdit()`、`cancelEdit()` 方法
|
||||
- 在 `updateItem()` 方法中处理编辑状态的显示
|
||||
- 提供编辑控件和事件处理
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
public class LocalDateFieldTableCell<S> extends TableCell<S, LocalDate> {
|
||||
private final DatePicker datePicker;
|
||||
|
||||
// 构造函数、converter 属性等
|
||||
|
||||
@Override
|
||||
public void startEdit() {
|
||||
if (isEmpty()) {
|
||||
return;
|
||||
}
|
||||
super.startEdit();
|
||||
setGraphic(datePicker);
|
||||
datePicker.setConverter(getConverter());
|
||||
datePicker.setValue(getItem());
|
||||
datePicker.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEdit() {
|
||||
super.cancelEdit();
|
||||
LocalDate item = getItem();
|
||||
if (item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(getConverter().toString(item));
|
||||
}
|
||||
setGraphic(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(LocalDate item, boolean empty) {
|
||||
// 实现更新逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. AsyncUpdateTableCell 核心功能
|
||||
|
||||
`AsyncUpdateTableCell` 是项目中最常用的基类,提供以下核心功能:
|
||||
|
||||
### 4.1 异步加载机制
|
||||
|
||||
- 显示占位符(`#id`)直到数据加载完成
|
||||
- 使用线程池执行异步任务
|
||||
- 自动取消不再需要的异步任务
|
||||
- 确保在 JavaFX 应用线程更新 UI
|
||||
|
||||
### 4.2 实体显示格式化
|
||||
|
||||
- 默认使用 `toString()` 方法格式化实体
|
||||
- 对 `NamedEntity` 和 `BasedEntity` 有特殊处理
|
||||
- 允许子类重写 `format()` 方法自定义格式
|
||||
|
||||
### 4.3 服务获取方式
|
||||
|
||||
- 通过构造函数注入服务
|
||||
- 或通过 `getServiceBean()` 方法从 Spring 容器获取
|
||||
|
||||
## 5. 特殊情况处理
|
||||
|
||||
### 5.1 非 Integer 类型 ID
|
||||
|
||||
当实体 ID 不是 Integer 类型时,需要重写 `initialize()` 方法:
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected ContractFileTypeLocalVo initialize() {
|
||||
ContractFileType item = getItem();
|
||||
ContractFileTypeLocalVo localVo = getServiceBean().findByType(
|
||||
Desktop.instance.getActiveEmployee().localeProperty().get(), item);
|
||||
return localVo;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 自定义显示格式
|
||||
|
||||
当需要自定义实体的显示格式时,重写 `format()` 方法:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public String format(ContractFileTypeLocalVo entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return entity.getValue();
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 工厂方法模式
|
||||
|
||||
所有 TableCell 类都应提供静态的 `forTableColumn()` 工厂方法,用于创建单元格工厂回调:
|
||||
|
||||
- 无参数版本:`public static <S> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn()`
|
||||
- 带服务参数版本:`public static <S> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn(ServiceType service)`
|
||||
|
||||
## 7. 代码规范
|
||||
|
||||
- 使用 Lombok 注解(如 `@NoArgsConstructor`)简化代码
|
||||
- 添加适当的 JavaDoc 注释
|
||||
- 遵循 JavaFX 最佳实践
|
||||
- 处理空值和异常情况
|
||||
- 确保线程安全,UI 更新在 JavaFX 应用线程执行
|
||||
|
||||
## 8. 实现检查清单
|
||||
|
||||
创建新的 TableCell 时,请检查以下项目:
|
||||
|
||||
- [ ] 类名符合 `*TableCell` 命名规范
|
||||
- [ ] 放置在正确的包路径下
|
||||
- [ ] 选择合适的基类(TableCell、AsyncUpdateTableCell)
|
||||
- [ ] 实现必要的构造函数
|
||||
- [ ] 实现或重写 `updateItem()` 方法
|
||||
- [ ] 提供静态的 `forTableColumn()` 工厂方法
|
||||
- [ ] 对于异步更新类型,实现 `getServiceBean()` 或提供服务注入
|
||||
- [ ] 添加适当的注释
|
||||
- [ ] 处理空值和异常情况
|
||||
|
||||
## 9. 示例实现
|
||||
|
||||
### 9.1 简单数据类型 TableCell
|
||||
|
||||
参考 `NumberTableCell.java`、`LocalDateFieldTableCell.java`
|
||||
|
||||
### 9.2 实体引用 TableCell
|
||||
|
||||
参考 `CompanyTableCell.java`、`ContractTableCell.java`
|
||||
|
||||
### 9.3 特殊处理 TableCell
|
||||
|
||||
参考 `ContractFileTypeTableCell.java`
|
||||
Reference in New Issue
Block a user