# 服务器端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）
    └── ...
```

## 核心基类和接口体系

### 主要基类
- **EntityService<M, Vo, ID>**: 通用实体服务基类，提供CRUD操作的标准实现
- **CompanyBasicService**: 专门处理公司相关业务的基础服务类，支持公司关联查询

### 核心接口
- **IEntityService<T>**: 实体基本操作接口
- **QueryService<Vo>**: 查询服务接口  
- **VoableService<M, Vo>**: 实体与视图对象转换服务接口

## 注解使用规范

### 类级别注解
```java
@Lazy  // 延迟加载，避免循环依赖
@Service  // Spring服务组件
@CacheConfig(cacheNames = "业务缓存名称")  // 缓存配置
public class CompanyService extends EntityService<Company, CompanyVo, Integer>
        implements IEntityService<Company>, QueryService<CompanyVo>, VoableService<Company, CompanyVo> {
    // 实现代码
}
```

### 方法级别注解
```java
// 查询方法缓存 - 使用参数作为缓存键
@Cacheable(key = "#p0")  // ID查询
public CompanyVo findById(Integer id) {
    return repository.findById(id).map(Company::toVo).orElse(null);
}

@Cacheable(key = "'name-'+#p0")  // 名称查询，带前缀
public CompanyVo findByName(String name) {
    return repository.findFirstByName(name).map(Company::toVo).orElse(null);
}

// 修改方法缓存清理 - 清理所有相关缓存
@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);
}
```

## 依赖注入规范

### Repository注入
```java
@Lazy
@Autowired
private CompanyRepository repository;
```

### Service间依赖注入
```java
@Lazy
@Autowired
private ContractService contractService;

@Lazy
@Autowired
private VendorService vendorService;

@Lazy
@Autowired
private CompanyContactService companyContactService;
```

### 外部服务依赖注入
```java
@Lazy
@Autowired
private CloudRkService cloudRkService;

@Lazy
@Autowired
private CloudTycService cloudTycService;

@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 = "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;
    }
}
```

### 2. 继承CompanyBasicService的Service
```java
@Lazy
@Service
@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;
    }
}
```

## 文件管理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层的代码质量、性能和可维护性，为整个系统的稳定运行提供了坚实基础。