# 服务器端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**: 通用实体服务基类,提供CRUD操作的标准实现 - **CompanyBasicService**: 专门处理公司相关业务的基础服务类,支持公司关联查询 ### 核心接口 - **IEntityService**: 实体基本操作接口 - **QueryService**: 查询服务接口 - **VoableService**: 实体与视图对象转换服务接口 ## 注解使用规范 ### 类级别注解 ```java @Lazy // 延迟加载,避免循环依赖 @Service // Spring服务组件 @CacheConfig(cacheNames = "业务缓存名称") // 缓存配置 public class CompanyService extends EntityService implements IEntityService, QueryService, VoableService { // 实现代码 } ``` ### 方法级别注解 ```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 findAll(JsonNode paramsNode, Pageable pageable) { Specification 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 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 getSpecification(String searchText) { if (!StringUtils.hasText(searchText)) { return null; } // 使用公司关联查询 Specification nameSpec = (root, query, builder) -> { Path 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 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 implements IEntityService, QueryService, VoableService { @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 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, QueryService, VoableService { // 提供公司关联查询 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 dbFiles = companyCustomerFileService.findAllByCustomer(companyCustomer); Map map = new HashMap<>(); boolean modified = fetchDbFiles(dbFiles, map, holder::info); List 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进行分页查询,避免一次性加载大量数据: ```java public Page findAll(Specification spec, Pageable pageable) { return projectRepository.findAll(spec, pageable); } ``` ### 4. 批量操作 对于大量数据操作,考虑使用批量处理: ```java public void saveAll(List 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 companies = repository.findAllByUniscid(uniscid); if (companies.isEmpty()) { return null; } if (companies.size() == 1) { return companies.getFirst(); } else { List 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层的代码质量、性能和可维护性,为整个系统的稳定运行提供了坚实基础。