feat(contract): 新增合同余额功能及重构文件管理
重构合同文件管理逻辑,增加错误处理和日志记录 新增ContractBalance实体、Repository和VO类 完善Voable接口文档和实现规范 更新项目架构文档和数据库设计 修复SmbFileService的连接问题 移动合同相关TabSkin类到contract包 添加合同文件重建任务的WebSocket支持
This commit is contained in:
@@ -1,260 +1,435 @@
|
||||
# 服务器端 Service 类规则文档
|
||||
# 服务器端Service设计规范
|
||||
|
||||
## 1. 概述
|
||||
## 目录结构
|
||||
|
||||
本规则文档定义了 Contract-Manager 项目服务器端(server模块)Service 类的设计规范、实现标准和最佳实践。所有服务器端 Service 类必须严格遵循本规则,以确保代码的一致性、可维护性和性能。
|
||||
每个业务域下的service目录结构示例:
|
||||
```
|
||||
ds/
|
||||
├── company/service/
|
||||
│ ├── CompanyService.java # 主业务服务
|
||||
│ ├── CompanyContactService.java # 联系人服务
|
||||
│ ├── CompanyFileService.java # 文件管理服务
|
||||
│ ├── CompanyOldNameService.java # 曾用名服务
|
||||
│ └── ...
|
||||
├── contract/service/
|
||||
│ ├── ContractService.java # 主业务服务
|
||||
│ ├── ContractCatalogService.java # 分类目录服务
|
||||
│ └── ...
|
||||
├── customer/service/
|
||||
│ ├── CustomerService.java # 主业务服务(继承CompanyBasicService)
|
||||
│ └── ...
|
||||
├── project/service/
|
||||
│ ├── ProjectService.java # 主业务服务
|
||||
│ ├── ProjectFileService.java # 文件管理服务
|
||||
│ └── ...
|
||||
└── vendor/service/
|
||||
├── VendorService.java # 主业务服务(继承CompanyBasicService)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 2. 目录结构
|
||||
## 核心基类和接口体系
|
||||
|
||||
Service 类按业务领域组织,位于 `server/src/main/java/com/ecep/contract/ds/{业务领域}/service/` 目录下。其中 `{业务领域}` 对应具体的业务模块,如 `customer`、`contract`、`company`、`project`、`other` 等。
|
||||
### 主要基类
|
||||
- **EntityService<M, Vo, ID>**: 通用实体服务基类,提供CRUD操作的标准实现
|
||||
- **CompanyBasicService**: 专门处理公司相关业务的基础服务类,支持公司关联查询
|
||||
|
||||
**示例:**
|
||||
- 客户分类服务:`server/src/main/java/com/ecep/contract/ds/customer/service/CustomerCatalogService.java`
|
||||
- 员工服务:`server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java`
|
||||
### 核心接口
|
||||
- **IEntityService<T>**: 实体基本操作接口
|
||||
- **QueryService<Vo>**: 查询服务接口
|
||||
- **VoableService<M, Vo>**: 实体与视图对象转换服务接口
|
||||
|
||||
## 3. 命名规范
|
||||
## 注解使用规范
|
||||
|
||||
- **类名**:采用驼峰命名法,首字母大写,以 `Service` 结尾,表示这是一个服务类。
|
||||
**示例**:`CustomerCatalogService`、`EmployeeService`
|
||||
### 类级别注解
|
||||
```java
|
||||
@Lazy // 延迟加载,避免循环依赖
|
||||
@Service // Spring服务组件
|
||||
@CacheConfig(cacheNames = "业务缓存名称") // 缓存配置
|
||||
public class CompanyService extends EntityService<Company, CompanyVo, Integer>
|
||||
implements IEntityService<Company>, QueryService<CompanyVo>, VoableService<Company, CompanyVo> {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 接口实现
|
||||
### 方法级别注解
|
||||
```java
|
||||
// 查询方法缓存 - 使用参数作为缓存键
|
||||
@Cacheable(key = "#p0") // ID查询
|
||||
public CompanyVo findById(Integer id) {
|
||||
return repository.findById(id).map(Company::toVo).orElse(null);
|
||||
}
|
||||
|
||||
所有业务领域的 Service 类必须实现以下三个核心接口:
|
||||
@Cacheable(key = "'name-'+#p0") // 名称查询,带前缀
|
||||
public CompanyVo findByName(String name) {
|
||||
return repository.findFirstByName(name).map(Company::toVo).orElse(null);
|
||||
}
|
||||
|
||||
### 4.1 IEntityService<T>
|
||||
// 修改方法缓存清理 - 清理所有相关缓存
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"),
|
||||
@CacheEvict(key = "'name-'+#p0.name"),
|
||||
@CacheEvict(key = "'code-'+#p0.code")
|
||||
})
|
||||
public Contract save(Contract contract) {
|
||||
return contractRepository.save(contract);
|
||||
}
|
||||
```
|
||||
|
||||
提供实体类的基本 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)`:保存实体
|
||||
### Repository注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyRepository repository;
|
||||
```
|
||||
|
||||
### 4.2 QueryService<Vo>
|
||||
### Service间依赖注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private ContractService contractService;
|
||||
|
||||
提供 VO 对象的查询能力。泛型 `Vo` 表示视图对象类型。
|
||||
@Lazy
|
||||
@Autowired
|
||||
private VendorService vendorService;
|
||||
|
||||
**主要方法:**
|
||||
- `Vo findById(Integer id)`:根据 ID 查询 VO 对象
|
||||
- `Page<Vo> findAll(JsonNode paramsNode, Pageable pageable)`:根据 JSON 查询参数和分页条件查询 VO 列表
|
||||
- `default long count(JsonNode paramsNode)`:根据查询参数统计数据总数
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyContactService companyContactService;
|
||||
```
|
||||
|
||||
### 4.3 VoableService<M, Vo>
|
||||
### 外部服务依赖注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CloudRkService cloudRkService;
|
||||
|
||||
提供从 VO 对象更新实体对象的能力。泛型 `M` 表示实体类类型,`Vo` 表示视图对象类型。
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CloudTycService cloudTycService;
|
||||
|
||||
**主要方法:**
|
||||
- `void updateByVo(M model, Vo vo)`:根据 VO 对象更新实体对象
|
||||
@Autowired(required = false) // 可选依赖
|
||||
private YongYouU8Service yongYouU8Service;
|
||||
```
|
||||
|
||||
**实现示例:**
|
||||
## 查询实现模式
|
||||
|
||||
### 标准查询实现
|
||||
```java
|
||||
@Override
|
||||
public Page<CompanyVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
Specification<Company> spec = null;
|
||||
|
||||
// 搜索文本查询
|
||||
if (paramsNode.has("searchText")) {
|
||||
spec = getSpecification(paramsNode.get("searchText").asText());
|
||||
}
|
||||
|
||||
// 字段等值查询
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "name", "uniscid", "abbName");
|
||||
|
||||
// 分页查询并转换为VO
|
||||
return findAll(spec, pageable).map(Company::toVo);
|
||||
}
|
||||
```
|
||||
|
||||
### 复杂查询条件构建
|
||||
```java
|
||||
@Override
|
||||
public Specification<Company> getSpecification(String searchText) {
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (root, query, builder) -> {
|
||||
return builder.or(
|
||||
builder.like(root.get("name"), "%" + searchText + "%"),
|
||||
builder.like(root.get("code"), "%" + searchText + "%"),
|
||||
builder.like(root.get("description"), "%" + searchText + "%"));
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### CompanyBasicService继承模式
|
||||
继承CompanyBasicService的Service(如CustomerService、VendorService)提供公司关联查询:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public Specification<Vendor> getSpecification(String searchText) {
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用公司关联查询
|
||||
Specification<Vendor> nameSpec = (root, query, builder) -> {
|
||||
Path<Company> company = root.get("company");
|
||||
return companyService.buildSearchPredicate(searchText, company, query, builder);
|
||||
};
|
||||
|
||||
// 数字ID查询
|
||||
if (MyStringUtils.isAllDigit(searchText)) {
|
||||
try {
|
||||
int id = Integer.parseInt(searchText);
|
||||
nameSpec = SpecificationUtils.or(nameSpec, (root, query, builder) -> {
|
||||
return builder.equal(root.get("id"), id);
|
||||
});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// 实体搜索
|
||||
List<VendorEntity> searched = vendorEntityService.search(searchText);
|
||||
if (!searched.isEmpty()) {
|
||||
nameSpec = SpecificationUtils.or(nameSpec, (root, query, builder) -> {
|
||||
return builder.in(root.get("id")).value(searched.stream()
|
||||
.map(VendorEntity::getVendor)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Vendor::getId)
|
||||
.collect(Collectors.toSet()));
|
||||
});
|
||||
}
|
||||
|
||||
return nameSpec;
|
||||
}
|
||||
```
|
||||
|
||||
## 业务逻辑实现模式
|
||||
|
||||
### 1. 主业务Service(继承EntityService)
|
||||
```java
|
||||
@Lazy
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "customer-catalog")
|
||||
public class CustomerCatalogService implements IEntityService<CustomerCatalog>, QueryService<CustomerCatalogVo>,
|
||||
VoableService<CustomerCatalog, CustomerCatalogVo> {
|
||||
// 实现方法...
|
||||
@CacheConfig(cacheNames = "contract")
|
||||
public class ContractService extends EntityService<Contract, ContractVo, Integer>
|
||||
implements IEntityService<Contract>, QueryService<ContractVo>, VoableService<Contract, ContractVo> {
|
||||
|
||||
@Override
|
||||
protected ContractRepository getRepository() {
|
||||
return contractRepository;
|
||||
}
|
||||
|
||||
@Cacheable(key = "#p0")
|
||||
public ContractVo findById(Integer id) {
|
||||
return getRepository().findById(id).map(Contract::toVo).orElse(null);
|
||||
}
|
||||
|
||||
// 业务特定方法
|
||||
public List<Contract> findAllByCompany(Company company) {
|
||||
return contractRepository.findAllByCompanyId(company.getId());
|
||||
}
|
||||
|
||||
public File getContractCatalogPath(ContractCatalogVo catalog, ContractVo contract) {
|
||||
// 文件路径处理逻辑
|
||||
String parent = catalog.getParent();
|
||||
File dir = getBasePath();
|
||||
// ... 路径构建逻辑
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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类实现,应避免:
|
||||
|
||||
### 2. 继承CompanyBasicService的Service
|
||||
```java
|
||||
// 错误:QueryService泛型参数使用实体类型而非VO类型
|
||||
@Lazy
|
||||
@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等缓存注解
|
||||
@CacheConfig(cacheNames = "company-customer")
|
||||
public class CustomerService extends CompanyBasicService
|
||||
implements IEntityService<CompanyCustomer>, QueryService<CustomerVo>,
|
||||
VoableService<CompanyCustomer, CustomerVo> {
|
||||
|
||||
// 提供公司关联查询
|
||||
public CompanyCustomer findByCompany(Company company) {
|
||||
return repository.findByCompany(company).orElse(null);
|
||||
}
|
||||
|
||||
public CustomerVo findByCompany(CompanyVo company) {
|
||||
return repository.findByCompanyId(company.getId()).map(CompanyCustomer::toVo).orElse(null);
|
||||
}
|
||||
|
||||
// 文件重建业务逻辑
|
||||
public boolean reBuildingFiles(CompanyCustomer companyCustomer, MessageHolder holder) {
|
||||
List<CompanyCustomerFile> dbFiles = companyCustomerFileService.findAllByCustomer(companyCustomer);
|
||||
Map<String, CompanyCustomerFile> map = new HashMap<>();
|
||||
|
||||
boolean modified = fetchDbFiles(dbFiles, map, holder::info);
|
||||
|
||||
List<File> needMoveToCompanyPath = new ArrayList<>();
|
||||
fetchFiles(companyCustomer.getPath(), needMoveToCompanyPath, retrieveFiles, map, holder::info);
|
||||
|
||||
moveFileToCompany(companyCustomer.getCompany(), needMoveToCompanyPath);
|
||||
|
||||
holder.info("导入 " + retrieveFiles.size() + " 个文件");
|
||||
|
||||
if (!retrieveFiles.isEmpty()) {
|
||||
retrieveFiles.forEach(v -> v.setCustomer(companyCustomer));
|
||||
companyCustomerFileService.saveAll(retrieveFiles);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 13. 总结
|
||||
## 文件管理Service实现模式
|
||||
|
||||
本规则文档定义了服务器端 Service 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。
|
||||
### 文件路径管理
|
||||
```java
|
||||
public File getBasePath() {
|
||||
return new File(confService.getString(ContractConstant.KEY_BASE_PATH));
|
||||
}
|
||||
|
||||
public File getContractCatalogPath(ContractCatalogVo catalog, ContractVo contract) {
|
||||
String parent = catalog.getParent();
|
||||
File dir = getBasePath();
|
||||
|
||||
if (StringUtils.hasText(parent)) {
|
||||
dir = new File(dir, parent);
|
||||
if (!dir.exists() && !dir.mkdir()) {
|
||||
System.out.println("unable make directory = " + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
dir = new File(dir, catalog.getPath());
|
||||
if (!dir.exists() && !dir.mkdir()) {
|
||||
System.out.println("unable make directory = " + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (catalog.isUseYear()) {
|
||||
String code = contract.getCode();
|
||||
String catalogCode = catalog.getCode();
|
||||
int length = catalogCode.length();
|
||||
String yearCode = code.substring(length, length + 2);
|
||||
dir = new File(dir, "20" + yearCode);
|
||||
if (!dir.exists() && !dir.mkdir()) {
|
||||
System.out.println("unable make directory = " + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
```
|
||||
|
||||
## 缓存策略最佳实践
|
||||
|
||||
### 缓存键设计原则
|
||||
- **ID查询**: `@Cacheable(key = "#p0")`
|
||||
- **字符串查询**: `@Cacheable(key = "'name-'+#p0")`
|
||||
- **复合键查询**: `@Cacheable(key = "'code-year-'+#p0+'-'+#p1")`
|
||||
|
||||
### 缓存清理策略
|
||||
```java
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"), // 主键缓存
|
||||
@CacheEvict(key = "'name-'+#p0.name"), // 名称缓存
|
||||
@CacheEvict(key = "'code-'+#p0.code"), // 编码缓存
|
||||
@CacheEvict(key = "'guid-'+#p0.guid") // GUID缓存
|
||||
})
|
||||
public Contract save(Contract contract) {
|
||||
return contractRepository.save(contract);
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 延迟加载
|
||||
所有Service依赖都应使用@Lazy注解,避免循环依赖和启动时的性能问题。
|
||||
|
||||
### 2. 缓存粒度
|
||||
- 单条查询使用细粒度缓存键
|
||||
- 避免缓存大量数据的列表查询
|
||||
- 合理设置缓存过期策略
|
||||
|
||||
### 3. 分页查询
|
||||
使用Page<T>进行分页查询,避免一次性加载大量数据:
|
||||
```java
|
||||
public Page<Project> findAll(Specification<Project> spec, Pageable pageable) {
|
||||
return projectRepository.findAll(spec, pageable);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 批量操作
|
||||
对于大量数据操作,考虑使用批量处理:
|
||||
```java
|
||||
public void saveAll(List<CompanyCustomerFile> files) {
|
||||
companyCustomerFileService.saveAll(retrieveFiles);
|
||||
}
|
||||
```
|
||||
|
||||
## 异常处理规范
|
||||
|
||||
### 参数校验
|
||||
```java
|
||||
public void updateByVo(CustomerCatalog model, CustomerCatalogVo vo) {
|
||||
if (model == null) {
|
||||
throw new ServiceException("实体对象不能为空");
|
||||
}
|
||||
if (vo == null) {
|
||||
throw new ServiceException("VO对象不能为空");
|
||||
}
|
||||
// ... 业务逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 业务异常处理
|
||||
```java
|
||||
public Company findAndRemoveDuplicateCompanyByUniscid(String uniscid) {
|
||||
List<Company> companies = repository.findAllByUniscid(uniscid);
|
||||
if (companies.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (companies.size() == 1) {
|
||||
return companies.getFirst();
|
||||
} else {
|
||||
List<Company> result = removeDuplicatesByUniscid(companies);
|
||||
if (!result.isEmpty()) {
|
||||
return result.getFirst();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## 工具类使用规范
|
||||
|
||||
### SpecificationUtils使用
|
||||
```java
|
||||
// 字段等值查询
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "name", "code", "status");
|
||||
|
||||
// 参数关联查询
|
||||
spec = SpecificationUtils.andParam(spec, paramsNode, "company", "catalog", "type");
|
||||
|
||||
// 搜索文本组合
|
||||
spec = SpecificationUtils.andWith(searchText, this::buildSearchSpecification);
|
||||
```
|
||||
|
||||
### 字符串工具使用CompanyBasicService
|
||||
```java
|
||||
// 全数字判断
|
||||
if (MyStringUtils.isAllDigit(searchText)) {
|
||||
// 数字处理逻辑
|
||||
}
|
||||
|
||||
// 空值检查
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践总结
|
||||
|
||||
1. **接口实现完整性**: 所有Service应实现三个核心接口,确保功能一致性
|
||||
2. **缓存策略一致性**: 遵循统一的缓存键设计和清理策略
|
||||
3. **依赖注入规范**: 使用@Lazy避免循环依赖,清晰管理Service间依赖关系
|
||||
4. **查询性能优化**: 合理使用SpecificationUtils构建查询条件,支持分页查询
|
||||
5. **异常处理统一**: 统一的异常处理方式,提高代码健壮性
|
||||
6. **代码复用**: 继承合适的基类,复用通用逻辑
|
||||
7. **文档注释**: 关键方法和复杂业务逻辑应有清晰的JavaDoc注释
|
||||
8. **性能监控**: 关注缓存命中率,适时调整缓存策略
|
||||
|
||||
这套规范确保了Service层的代码质量、性能和可维护性,为整个系统的稳定运行提供了坚实基础。
|
||||
Reference in New Issue
Block a user