# 服务器端 Service 类规则文档 ## 1. 概述 本规则文档定义了 Contract-Manager 项目服务器端(server模块)Service 类的设计规范、实现标准和最佳实践。所有服务器端 Service 类必须严格遵循本规则,以确保代码的一致性、可维护性和性能。 ## 2. 目录结构 Service 类按业务领域组织,位于 `server/src/main/java/com/ecep/contract/ds/{业务领域}/service/` 目录下。其中 `{业务领域}` 对应具体的业务模块,如 `customer`、`contract`、`company`、`project`、`other` 等。 **示例:** - 客户分类服务:`server/src/main/java/com/ecep/contract/ds/customer/service/CustomerCatalogService.java` - 员工服务:`server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java` ## 3. 命名规范 - **类名**:采用驼峰命名法,首字母大写,以 `Service` 结尾,表示这是一个服务类。 **示例**:`CustomerCatalogService`、`EmployeeService` ## 4. 接口实现 所有业务领域的 Service 类必须实现以下三个核心接口: ### 4.1 IEntityService 提供实体类的基本 CRUD 操作。泛型 `T` 表示实体类类型。 **主要方法:** - `T getById(Integer id)`:根据 ID 查询实体对象 - `Page findAll(Specification spec, Pageable pageable)`:根据条件和分页参数查询实体列表 - `Specification getSpecification(String searchText)`:构建搜索条件 - `void delete(T entity)`:删除实体 - `T save(T entity)`:保存实体 ### 4.2 QueryService 提供 VO 对象的查询能力。泛型 `Vo` 表示视图对象类型。 **主要方法:** - `Vo findById(Integer id)`:根据 ID 查询 VO 对象 - `Page findAll(JsonNode paramsNode, Pageable pageable)`:根据 JSON 查询参数和分页条件查询 VO 列表 - `default long count(JsonNode paramsNode)`:根据查询参数统计数据总数 ### 4.3 VoableService 提供从 VO 对象更新实体对象的能力。泛型 `M` 表示实体类类型,`Vo` 表示视图对象类型。 **主要方法:** - `void updateByVo(M model, Vo vo)`:根据 VO 对象更新实体对象 **实现示例:** ```java @Lazy @Service @CacheConfig(cacheNames = "customer-catalog") public class CustomerCatalogService implements IEntityService, QueryService, VoableService { // 实现方法... } ``` ## 5. 注解规范 Service 类必须使用以下注解: ### 5.1 类级别注解 - `@Service`:标记这是一个 Spring 服务类,使其能够被自动扫描和管理 - `@Lazy`:延迟加载服务类,提高应用启动性能 - `@CacheConfig(cacheNames = "缓存名称")`:配置缓存名称,缓存名称通常与业务领域相关 **示例**:`@CacheConfig(cacheNames = "customer-catalog")`、`@CacheConfig(cacheNames = "employee")` ### 5.2 方法级别注解 #### 5.2.1 查询方法注解 - `@Cacheable(key = "缓存键")`:将查询结果缓存起来,下次相同查询可以直接从缓存获取 **示例**:`@Cacheable(key = "#p0")`(使用方法第一个参数作为缓存键)、`@Cacheable(key = "'code-'+#p0")`(使用前缀+参数值作为缓存键) #### 5.2.2 数据修改方法注解 - `@Caching(evict = { ... })`:在保存或删除操作时清除相关缓存 **示例**: ```java @Caching(evict = { @CacheEvict(key = "#p0.id"), @CacheEvict(key = "'code-'+#p0.code"), @CacheEvict(key = "'name-'+#p0.name"), @CacheEvict(key = "'all'") }) ``` - `@Transactional`:标记方法需要在事务中执行,确保数据一致性 **示例**:用于包含多个数据操作的复杂业务方法 ## 6. 缓存策略 Service 类必须遵循以下缓存策略: ### 6.1 查询缓存 - 所有 `findById`、`findByCode`、`findByName` 等单条查询方法都应使用 `@Cacheable` 注解缓存结果 - 缓存键设计应具有唯一性和可读性,通常包含参数值和适当的前缀 - 列表查询(如 `findAll`)可以考虑使用 `@Cacheable`,但需谨慎管理缓存失效 ### 6.2 缓存清理 - 所有 `save`、`delete` 等修改数据的方法都应使用 `@Caching` 和 `@CacheEvict` 注解清理相关缓存 - 清理缓存时应考虑所有可能影响的查询,确保缓存数据的一致性 ## 7. 方法实现规范 ### 7.1 IEntityService 方法实现 - `getById`:直接调用 Repository 的 `findById` 方法,返回实体对象 ```java @Override public CustomerCatalog getById(Integer id) { return repository.findById(id).orElse(null); } ``` - `findAll(Specification, Pageable)`:直接调用 Repository 的 `findAll` 方法,返回实体分页对象 ```java @Override public Page findAll(Specification spec, Pageable pageable) { return repository.findAll(spec, pageable); } ``` - `save/delete`:调用 Repository 的相应方法,并添加缓存清理注解 ```java @Caching(evict = { ... }) @Override public CustomerCatalog save(CustomerCatalog catalog) { return repository.save(catalog); } ``` ### 7.2 QueryService 方法实现 - `findById`:调用 Repository 的 `findById` 方法,然后调用实体类的 `toVo` 方法转换为 VO 对象 ```java @Cacheable(key = "#p0") @Override public CustomerCatalogVo findById(Integer id) { return repository.findById(id).map(CustomerCatalog::toVo).orElse(null); } ``` - `findAll(JsonNode, Pageable)`:构建 Specification,调用 IEntityService 的 `findAll` 方法,然后使用 Stream API 的 `map` 方法将结果转换为 VO 对象 ```java @Override public Page findAll(JsonNode paramsNode, Pageable pageable) { Specification spec = null; if (paramsNode.has(ServiceConstant.KEY_SEARCH_TEXT)) { spec = getSpecification(paramsNode.get(ServiceConstant.KEY_SEARCH_TEXT).asText()); } // 字段等值查询 spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "code", "name", "description"); return repository.findAll(spec, pageable).map(CustomerCatalog::toVo); } ``` ### 7.3 VoableService 方法实现 - `updateByVo`:将 VO 对象的属性逐个复制到实体对象,实现 VO 到实体的转换 ```java @Override public void updateByVo(CustomerCatalog model, CustomerCatalogVo vo) { // 参数校验 if (model == null) { throw new ServiceException("实体对象不能为空"); } if (vo == null) { throw new ServiceException("VO对象不能为空"); } // 映射基本属性 model.setCode(vo.getCode()); model.setName(vo.getName()); model.setDescription(vo.getDescription()); } ``` ## 8. 依赖注入规范 - Service 类应使用 `@Autowired` 注解注入所需的 Repository 和其他依赖 - 为了避免循环依赖,所有注入的依赖都应使用 `@Lazy` 注解标记为延迟加载 ```java @Lazy @Autowired private CustomerCatalogRepository repository; ``` ## 9. 查询条件构建 - 使用 `SpecificationUtils` 工具类辅助构建查询条件 - 实现 `getSpecification(String searchText)` 方法,提供基于搜索文本的模糊查询能力 ```java @Override public Specification getSpecification(String searchText) { if (!StringUtils.hasText(searchText)) { return null; } String likeText = "%" + searchText + "%"; return (root, query, builder) -> { return builder.or( builder.like(root.get("code"), likeText), builder.like(root.get("name"), likeText), builder.like(root.get("description"), likeText)); }; } ``` ## 10. 异常处理 - Service 类应适当处理异常,特别是对输入参数的校验 - 对于业务逻辑异常,应抛出 `ServiceException` ```java if (model == null) { throw new ServiceException("实体对象不能为空"); } ``` ## 11. 最佳实践 1. **VO优先原则**:QueryService 接口应使用 VO 类型作为泛型参数,返回 VO 对象而不是实体对象 2. **缓存粒度**:缓存键应设计得足够细粒度,避免缓存过大或频繁失效 3. **事务管理**:包含多个数据操作的方法应使用 `@Transactional` 注解确保事务一致性 4. **延迟加载**:所有依赖都应使用 `@Lazy` 注解,避免循环依赖问题 5. **参数校验**:方法开始时应进行参数校验,确保输入数据的合法性 6. **文档注释**:关键方法应有清晰的 JavaDoc 注释,说明其功能、参数和返回值 7. **代码复用**:尽量使用 SpecificationUtils 等工具类辅助构建查询条件,提高代码复用性 ## 12. 不符合规范的Service类示例 以下是不符合规范的Service类实现,应避免: ```java // 错误:QueryService泛型参数使用实体类型而非VO类型 @Service @CacheConfig(cacheNames = "company") public class CompanyService extends EntityService implements IEntityService, QueryService, VoableService { // 实现方法... } // 错误:未使用缓存注解 @Service public class ExampleService implements IEntityService, QueryService, VoableService { // 未使用@Cacheable、@CacheEvict等缓存注解 } ``` ## 13. 总结 本规则文档定义了服务器端 Service 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。