# 客户端 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, TV>`
  - `T` 表示 VO 类型（实现了 IdentityEntity 接口）
  - `TV` 表示 ViewModel 类型（实现了 IdentityViewModel<T> 接口）
- `QueryService` 实现了 `ViewModelService<T, TV>` 接口
- `ViewModelService` 继承了 `IEntityService<T>` 接口
- 特定场景下可以不继承 `QueryService`，直接实现所需接口或创建独立服务类

```java
@Service
@CacheConfig(cacheNames = "company")
public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
    // 业务方法实现
}
```

## 4. 注解使用
- **@Service**：标记为 Spring 服务组件，使其可被自动发现和注入
- **@CacheConfig**：配置缓存名称，通常与服务类名对应
- **@Cacheable**：标记方法结果可缓存，需指定缓存键（key）
- **@CacheEvict**：标记方法执行后清除缓存，可指定缓存键或清除所有
- **@Caching**：组合多个缓存操作（如同时清除多个缓存条目）
- **@Autowired**：用于自动注入依赖的其他服务

```java
@Service
@CacheConfig(cacheNames = "contract")
public class ContractService extends QueryService<ContractVo, ContractViewModel> {
    @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<T>` 接口定义的核心方法：
  - `findById(Integer id)`：根据 ID 查询实体
  - `save(T entity)`：保存实体
  - `delete(T entity)`：删除实体
  - `findAll()`：查询所有实体
  - `findAll(Map<String, Object> params, Pageable pageable)`：条件分页查询
  - `getStringConverter()`：获取类型转换器
- 通常通过继承 `QueryService` 来复用这些基础方法的实现
- 可根据业务需求重写或扩展基础方法

## 8. 业务方法规范
- 业务方法应与服务端对应，保持方法名和参数一致
- 方法命名应清晰表达其功能，如 `findByName`, `findByCode`
- 复杂业务逻辑应封装为独立方法
- 参数校验应在方法开始处进行
- 返回值类型应明确，避免使用过于泛化的类型

## 9. 类型转换器
- 实现 `getStringConverter()` 方法，返回对应的 StringConverter 实例
- 通常创建专用的 Converter 类，如 `CustomerCatalogStringConverter`
- 转换器实例应作为服务类的成员变量，避免重复创建

```java
public class CustomerCatalogService extends QueryService<CustomerCatalogVo, CustomerCatalogViewModel> {
    private final CustomerCatalogStringConverter stringConverter = new CustomerCatalogStringConverter(this);
    
    @Override
    public StringConverter<CustomerCatalogVo> getStringConverter() {
        return stringConverter;
    }
}
```

## 10. 错误处理
- 远程调用异常应捕获并包装为更具描述性的 RuntimeException
- 提供详细的错误信息，包括调用的方法名和参数
- 对于可预期的业务异常，可添加专门的处理逻辑
- 不推荐使用 printStackTrace()，应使用日志记录异常

## 11. 工具方法和辅助功能
- 通用功能可封装为工具方法
- 配置相关操作可通过 `SysConfService` 实现
- 文件路径处理应使用 `File` 类和相关工具方法
- 日期时间处理应使用 Java 8+ 的日期时间 API

## 12. 最佳实践
- 遵循单一职责原则，每个服务类专注于一个业务领域
- 优先使用继承和接口实现来复用代码
- 合理使用缓存提高性能，但注意缓存一致性
- 异步调用应正确处理异常和超时情况
- 服务类之间的依赖应通过 `@Autowired` 注入，避免硬编码
- 方法实现应简洁明了，复杂逻辑应拆分
- 为重要方法添加 JavaDoc 注释，说明其功能和参数含义