refactor(CloudRkManagerWindowController): 简化任务初始化逻辑 feat(HolidayService): 添加节假日调整和检查功能 feat(HolidayTable): 实现Voable接口并添加toVo方法 feat(HolidayTableRepository): 添加节假日查询方法 docs: 添加服务器端Service类规则文档 build: 更新项目版本至0.0.101-SNAPSHOT
260 lines
9.6 KiB
Markdown
260 lines
9.6 KiB
Markdown
# 服务器端 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 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。 |