Files
contract-manager/.trae/rules/server_service_rules.md
songqq 0dcc9236a8 feat: 实现节假日服务功能并更新项目版本
refactor(CloudRkManagerWindowController): 简化任务初始化逻辑
feat(HolidayService): 添加节假日调整和检查功能
feat(HolidayTable): 实现Voable接口并添加toVo方法
feat(HolidayTableRepository): 添加节假日查询方法
docs: 添加服务器端Service类规则文档
build: 更新项目版本至0.0.101-SNAPSHOT
2025-10-10 10:21:51 +08:00

9.6 KiB
Raw Blame History

服务器端 Service 类规则文档

1. 概述

本规则文档定义了 Contract-Manager 项目服务器端server模块Service 类的设计规范、实现标准和最佳实践。所有服务器端 Service 类必须严格遵循本规则,以确保代码的一致性、可维护性和性能。

2. 目录结构

Service 类按业务领域组织,位于 server/src/main/java/com/ecep/contract/ds/{业务领域}/service/ 目录下。其中 {业务领域} 对应具体的业务模块,如 customercontractcompanyprojectother 等。

示例:

  • 客户分类服务: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 结尾,表示这是一个服务类。 示例CustomerCatalogServiceEmployeeService

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 查询缓存

  • 所有 findByIdfindByCodefindByName 等单条查询方法都应使用 @Cacheable 注解缓存结果
  • 缓存键设计应具有唯一性和可读性,通常包含参数值和适当的前缀
  • 列表查询(如 findAll)可以考虑使用 @Cacheable,但需谨慎管理缓存失效

6.2 缓存清理

  • 所有 savedelete 等修改数据的方法都应使用 @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 类应适当处理异常,特别是对输入参数的校验
  • 对于业务逻辑异常,应抛出 ServiceException
    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类实现应避免

// 错误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 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。