重构模型类包结构,将模型类按功能模块划分到不同的子包中。优化序列化处理,为VO类添加serialVersionUID并实现Serializable接口。移除部分冗余的serialVersionUID字段,简化模型类代码。同时修复UITools中空值处理的问题,并更新pom版本至0.0.100-SNAPSHOT。 - 将模型类按功能模块划分到ds子包中 - 为VO类添加序列化支持 - 移除冗余的serialVersionUID字段 - 修复UITools空值处理问题 - 更新项目版本号
5.7 KiB
5.7 KiB
客户端 Service 类规则
1. 目录结构
- 所有客户端 Service 类位于
client/src/main/java/com/ecep/contract/service/目录下 - 按业务领域组织,直接放置在 service 包下,不进行子包划分
- 服务类命名与实体类一一对应
2. 命名规范
- 服务类命名格式为:
[实体名称]Service.java - 例如:
CompanyService.java、ContractService.java、ProjectService.java - 基础服务接口命名为:
IEntityService.java、ViewModelService.java - 泛型基础服务类命名为:
QueryService.java
3. 继承关系
- 业务服务类通常继承自泛型基础服务类
QueryService<T, TV>T表示 VO 类型(实现了 IdentityEntity 接口)TV表示 ViewModel 类型(实现了 IdentityViewModel 接口)
QueryService实现了ViewModelService<T, TV>接口ViewModelService继承了IEntityService<T>接口- 特定场景下可以不继承
QueryService,直接实现所需接口或创建独立服务类
@Service
@CacheConfig(cacheNames = "company")
public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
// 业务方法实现
}
4. 注解使用
- @Service:标记为 Spring 服务组件,使其可被自动发现和注入
- @CacheConfig:配置缓存名称,通常与服务类名对应
- @Cacheable:标记方法结果可缓存,需指定缓存键(key)
- @CacheEvict:标记方法执行后清除缓存,可指定缓存键或清除所有
- @Caching:组合多个缓存操作(如同时清除多个缓存条目)
- @Autowired:用于自动注入依赖的其他服务
@Service
@CacheConfig(cacheNames = "contract")
public class ContractService extends QueryService<ContractVo, ContractViewModel> {
@Autowired
private SysConfService confService;
@Cacheable(key = "#p0")
public ContractVo findById(Integer id) {
return super.findById(id);
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code")
})
public ContractVo save(ContractVo contract) {
return super.save(contract);
}
}
5. 缓存机制
- 每个服务类应有独立的缓存名称空间
- 缓存键(key)应具有唯一性,通常使用 ID、代码或名称等唯一标识
- 保存和删除操作时应清除相关缓存,保持数据一致性
- 可使用 SpEL 表达式动态生成缓存键
- 频繁查询的数据应考虑缓存,提高性能
6. 异步调用机制
- 使用
async()方法进行异步远程调用 - 方法参数通常包括:方法名、参数值、参数类型列表
- 使用
CompletableFuture处理异步结果 - 使用
handle()方法处理响应和异常 - 远程调用异常应包装为 RuntimeException 并提供详细错误信息
@Cacheable(key = "'code-'+#p0")
public ContractVo findByCode(String code) {
try {
return async("findByCode", code, String.class).handle((response, ex) -> {
if (ex != null) {
throw new RuntimeException("远程方法+findByCode+调用失败", ex);
}
if (response != null) {
return updateValue(createNewEntity(), response);
}
return null;
}).get();
} catch (Exception e) {
throw new RuntimeException("查询失败: " + code, e);
}
}
7. 基础方法实现
- 应实现
IEntityService<T>接口定义的核心方法:findById(Integer id):根据 ID 查询实体save(T entity):保存实体delete(T entity):删除实体findAll():查询所有实体findAll(Map<String, Object> params, Pageable pageable):条件分页查询getStringConverter():获取类型转换器
- 通常通过继承
QueryService来复用这些基础方法的实现 - 可根据业务需求重写或扩展基础方法
8. 业务方法规范
- 业务方法应与服务端对应,保持方法名和参数一致
- 方法命名应清晰表达其功能,如
findByName,findByCode - 复杂业务逻辑应封装为独立方法
- 参数校验应在方法开始处进行
- 返回值类型应明确,避免使用过于泛化的类型
9. 类型转换器
- 实现
getStringConverter()方法,返回对应的 StringConverter 实例 - 通常创建专用的 Converter 类,如
CustomerCatalogStringConverter - 转换器实例应作为服务类的成员变量,避免重复创建
public class CustomerCatalogService extends QueryService<CustomerCatalogVo, CustomerCatalogViewModel> {
private final CustomerCatalogStringConverter stringConverter = new CustomerCatalogStringConverter(this);
@Override
public StringConverter<CustomerCatalogVo> getStringConverter() {
return stringConverter;
}
}
10. 错误处理
- 远程调用异常应捕获并包装为更具描述性的 RuntimeException
- 提供详细的错误信息,包括调用的方法名和参数
- 对于可预期的业务异常,可添加专门的处理逻辑
- 不推荐使用 printStackTrace(),应使用日志记录异常
11. 工具方法和辅助功能
- 通用功能可封装为工具方法
- 配置相关操作可通过
SysConfService实现 - 文件路径处理应使用
File类和相关工具方法 - 日期时间处理应使用 Java 8+ 的日期时间 API
12. 最佳实践
- 遵循单一职责原则,每个服务类专注于一个业务领域
- 优先使用继承和接口实现来复用代码
- 合理使用缓存提高性能,但注意缓存一致性
- 异步调用应正确处理异常和超时情况
- 服务类之间的依赖应通过
@Autowired注入,避免硬编码 - 方法实现应简洁明了,复杂逻辑应拆分
- 为重要方法添加 JavaDoc 注释,说明其功能和参数含义