# 客户端 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` 表示 VO 类型(实现了 IdentityEntity 接口) - `TV` 表示 ViewModel 类型(实现了 IdentityViewModel 接口) - `QueryService` 实现了 `ViewModelService` 接口 - `ViewModelService` 继承了 `IEntityService` 接口 - 特定场景下可以不继承 `QueryService`,直接实现所需接口或创建独立服务类 ```java @Service @CacheConfig(cacheNames = "company") public class CompanyService extends QueryService { // 业务方法实现 } ``` ## 4. 注解使用 - **@Service**:标记为 Spring 服务组件,使其可被自动发现和注入 - **@CacheConfig**:配置缓存名称,通常与服务类名对应 - **@Cacheable**:标记方法结果可缓存,需指定缓存键(key) - **@CacheEvict**:标记方法执行后清除缓存,可指定缓存键或清除所有 - **@Caching**:组合多个缓存操作(如同时清除多个缓存条目) - **@Autowired**:用于自动注入依赖的其他服务 ```java @Service @CacheConfig(cacheNames = "contract") public class ContractService extends QueryService { @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 并提供详细错误信息 ```java @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` 接口定义的核心方法: - `findById(Integer id)`:根据 ID 查询实体 - `save(T entity)`:保存实体 - `delete(T entity)`:删除实体 - `findAll()`:查询所有实体 - `findAll(Map params, Pageable pageable)`:条件分页查询 - `getStringConverter()`:获取类型转换器 - 通常通过继承 `QueryService` 来复用这些基础方法的实现 - 可根据业务需求重写或扩展基础方法 ## 8. 业务方法规范 - 业务方法应与服务端对应,保持方法名和参数一致 - 方法命名应清晰表达其功能,如 `findByName`, `findByCode` - 复杂业务逻辑应封装为独立方法 - 参数校验应在方法开始处进行 - 返回值类型应明确,避免使用过于泛化的类型 ## 9. 类型转换器 - 实现 `getStringConverter()` 方法,返回对应的 StringConverter 实例 - 通常创建专用的 Converter 类,如 `CustomerCatalogStringConverter` - 转换器实例应作为服务类的成员变量,避免重复创建 ```java public class CustomerCatalogService extends QueryService { private final CustomerCatalogStringConverter stringConverter = new CustomerCatalogStringConverter(this); @Override public StringConverter getStringConverter() { return stringConverter; } } ``` ## 10. 错误处理 - 远程调用异常应捕获并包装为更具描述性的 RuntimeException - 提供详细的错误信息,包括调用的方法名和参数 - 对于可预期的业务异常,可添加专门的处理逻辑 - 不推荐使用 printStackTrace(),应使用日志记录异常 ## 11. 工具方法和辅助功能 - 通用功能可封装为工具方法 - 配置相关操作可通过 `SysConfService` 实现 - 文件路径处理应使用 `File` 类和相关工具方法 - 日期时间处理应使用 Java 8+ 的日期时间 API ## 12. 最佳实践 - 遵循单一职责原则,每个服务类专注于一个业务领域 - 优先使用继承和接口实现来复用代码 - 合理使用缓存提高性能,但注意缓存一致性 - 异步调用应正确处理异常和超时情况 - 服务类之间的依赖应通过 `@Autowired` 注入,避免硬编码 - 方法实现应简洁明了,复杂逻辑应拆分 - 为重要方法添加 JavaDoc 注释,说明其功能和参数含义