refactor(CloudRkManagerWindowController): 简化任务初始化逻辑 feat(HolidayService): 添加节假日调整和检查功能 feat(HolidayTable): 实现Voable接口并添加toVo方法 feat(HolidayTableRepository): 添加节假日查询方法 docs: 添加服务器端Service类规则文档 build: 更新项目版本至0.0.101-SNAPSHOT
9.6 KiB
服务器端 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<T> findAll(Specification<T> spec, Pageable pageable):根据条件和分页参数查询实体列表Specification<T> getSpecification(String searchText):构建搜索条件void delete(T entity):删除实体T save(T entity):保存实体
4.2 QueryService
提供 VO 对象的查询能力。泛型 Vo 表示视图对象类型。
主要方法:
Vo findById(Integer id):根据 ID 查询 VO 对象Page<Vo> findAll(JsonNode paramsNode, Pageable pageable):根据 JSON 查询参数和分页条件查询 VO 列表default long count(JsonNode paramsNode):根据查询参数统计数据总数
4.3 VoableService<M, Vo>
提供从 VO 对象更新实体对象的能力。泛型 M 表示实体类类型,Vo 表示视图对象类型。
主要方法:
void updateByVo(M model, Vo vo):根据 VO 对象更新实体对象
实现示例:
@Lazy
@Service
@CacheConfig(cacheNames = "customer-catalog")
public class CustomerCatalogService implements IEntityService<CustomerCatalog>, QueryService<CustomerCatalogVo>,
VoableService<CustomerCatalog, CustomerCatalogVo> {
// 实现方法...
}
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 = { ... }):在保存或删除操作时清除相关缓存 示例:@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方法,返回实体对象@Override public CustomerCatalog getById(Integer id) { return repository.findById(id).orElse(null); } -
findAll(Specification<T>, Pageable):直接调用 Repository 的findAll方法,返回实体分页对象@Override public Page<CustomerCatalog> findAll(Specification<CustomerCatalog> spec, Pageable pageable) { return repository.findAll(spec, pageable); } -
save/delete:调用 Repository 的相应方法,并添加缓存清理注解@Caching(evict = { ... }) @Override public CustomerCatalog save(CustomerCatalog catalog) { return repository.save(catalog); }
7.2 QueryService 方法实现
-
findById:调用 Repository 的findById方法,然后调用实体类的toVo方法转换为 VO 对象@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 对象@Override public Page<CustomerCatalogVo> findAll(JsonNode paramsNode, Pageable pageable) { Specification<CustomerCatalog> 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 到实体的转换@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注解标记为延迟加载@Lazy @Autowired private CustomerCatalogRepository repository;
9. 查询条件构建
- 使用
SpecificationUtils工具类辅助构建查询条件 - 实现
getSpecification(String searchText)方法,提供基于搜索文本的模糊查询能力@Override public Specification<CustomerCatalog> 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 类应适当处理异常,特别是对输入参数的校验
- 对于业务逻辑异常,应抛出
ServiceExceptionif (model == null) { throw new ServiceException("实体对象不能为空"); }
11. 最佳实践
- VO优先原则:QueryService 接口应使用 VO 类型作为泛型参数,返回 VO 对象而不是实体对象
- 缓存粒度:缓存键应设计得足够细粒度,避免缓存过大或频繁失效
- 事务管理:包含多个数据操作的方法应使用
@Transactional注解确保事务一致性 - 延迟加载:所有依赖都应使用
@Lazy注解,避免循环依赖问题 - 参数校验:方法开始时应进行参数校验,确保输入数据的合法性
- 文档注释:关键方法应有清晰的 JavaDoc 注释,说明其功能、参数和返回值
- 代码复用:尽量使用 SpecificationUtils 等工具类辅助构建查询条件,提高代码复用性
12. 不符合规范的Service类示例
以下是不符合规范的Service类实现,应避免:
// 错误:QueryService泛型参数使用实体类型而非VO类型
@Service
@CacheConfig(cacheNames = "company")
public class CompanyService extends EntityService<Company, Integer>
implements IEntityService<Company>, QueryService<Company>, VoableService<Company, CompanyVo> {
// 实现方法...
}
// 错误:未使用缓存注解
@Service
public class ExampleService implements IEntityService<Example>, QueryService<ExampleVo>,
VoableService<Example, ExampleVo> {
// 未使用@Cacheable、@CacheEvict等缓存注解
}
13. 总结
本规则文档定义了服务器端 Service 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。