# 服务器端 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<T>

提供实体类的基本 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` 表示视图对象类型。

**主要方法：**
- `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 对象更新实体对象

**实现示例：**
```java
@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 = { ... })`：在保存或删除操作时清除相关缓存
  **示例**：
  ```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<T>, Pageable)`：直接调用 Repository 的 `findAll` 方法，返回实体分页对象
  ```java
  @Override
  public Page<CustomerCatalog> findAll(Specification<CustomerCatalog> 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<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 到实体的转换
  ```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<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`
  ```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<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 类的完整规范，包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则，以确保代码的一致性、可维护性和性能。