From 02afa189f8e9eeda85dc3e0ad5b24aa87f9d4783 Mon Sep 17 00:00:00 2001 From: songqq Date: Wed, 19 Nov 2025 00:50:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(contract):=20=E6=96=B0=E5=A2=9E=E5=90=88?= =?UTF-8?q?=E5=90=8C=E4=BD=99=E9=A2=9D=E5=8A=9F=E8=83=BD=E5=8F=8A=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构合同文件管理逻辑,增加错误处理和日志记录 新增ContractBalance实体、Repository和VO类 完善Voable接口文档和实现规范 更新项目架构文档和数据库设计 修复SmbFileService的连接问题 移动合同相关TabSkin类到contract包 添加合同文件重建任务的WebSocket支持 --- .env | 0 .gitignore | 4 +- .trae/rules/client_service_rules.md | 576 ++++- ...epository_comprehensive_analysis_report.md | 374 ++++ .trae/rules/server_repository_rules.md | 460 ++++ .trae/rules/server_service_rules.md | 633 ++++-- .trae/rules/server_task_rules.md | 10 +- API_DOCUMENTATION.md | 546 +++++ ARCHITECTURE.md | 265 +++ DATABASE_DESIGN.md | 646 ++++++ DEVELOPMENT_GUIDE.md | 517 +++++ PROJECT_DOCUMENTATION.md | 113 + .../ContractTabSkinFiles.java | 7 +- .../ContractTabSkinPayPlan.java | 4 +- .../ContractTabSkinSubContract.java | 5 +- .../ContractTabSkinVendorBid.java | 5 +- .../contract/ContractWindowController.java | 5 - .../tab/ContractFilesRebuildTasker.java | 54 +- .../service/ContractBalanceService.java | 217 ++ .../contract/vm/ContractBalanceViewModel.java | 180 ++ .../ui/contract/contract-invoice-manager.fxml | 2 +- .../ui/contract/contract-tab-bid.fxml | 2 +- .../ui/contract/contract-tab-file.fxml | 2 +- .../ui/contract/contract-tab-pay-plan.fxml | 2 +- .../contract/contract-tab-sub-contract.fxml | 2 +- .../java/com/ecep/contract/model/Voable.java | 102 +- .../ecep/contract/vo/ContractBalanceVo.java | 120 + ...ContractBalance_toVo_Method_Enhancement.md | 233 ++ docs/analysis/Voable_Interface_Analysis.md | 190 ++ docs/db/CONTRACT_BALANCE.sql | 95 + docs/readme.txt | 5 +- package-lock.json | 1929 +++++++++++++++++ package.json | 5 + .../java/com/ecep/contract/EntityService.java | 8 +- .../cloud/u8/YongYouU8Repository.java | 6 + .../contract/cloud/u8/ctx/ContractCtx.java | 13 +- .../repository/CompanyFileRepository.java | 7 + .../ds/contract/model/ContractBalance.java | 248 +++ .../repository/ContractBalanceRepository.java | 64 + .../repository/ContractFileRepository.java | 7 +- .../repository/ContractItemRepository.java | 13 +- .../repository/PurchaseOrderRepository.java | 12 +- .../service/ContractBalanceService.java | 193 ++ .../tasker/ContractFilesRebuildTasker.java | 74 +- .../repository/ProjectFileRepository.java | 7 + .../ProjectQuotationRepository.java | 6 +- .../repository/VendorFileRepository.java | 10 +- .../ecep/contract/service/SmbFileService.java | 37 +- server/src/main/resources/tasker_mapper.json | 3 +- 49 files changed, 7577 insertions(+), 441 deletions(-) create mode 100644 .env create mode 100644 .trae/rules/repository_comprehensive_analysis_report.md create mode 100644 .trae/rules/server_repository_rules.md create mode 100644 API_DOCUMENTATION.md create mode 100644 ARCHITECTURE.md create mode 100644 DATABASE_DESIGN.md create mode 100644 DEVELOPMENT_GUIDE.md create mode 100644 PROJECT_DOCUMENTATION.md rename client/src/main/java/com/ecep/contract/controller/{tab => contract}/ContractTabSkinFiles.java (99%) rename client/src/main/java/com/ecep/contract/controller/{tab => contract}/ContractTabSkinPayPlan.java (94%) rename client/src/main/java/com/ecep/contract/controller/{tab => contract}/ContractTabSkinSubContract.java (95%) rename client/src/main/java/com/ecep/contract/controller/{tab => contract}/ContractTabSkinVendorBid.java (97%) create mode 100644 client/src/main/java/com/ecep/contract/service/ContractBalanceService.java create mode 100644 client/src/main/java/com/ecep/contract/vm/ContractBalanceViewModel.java create mode 100644 common/src/main/java/com/ecep/contract/vo/ContractBalanceVo.java create mode 100644 docs/analysis/ContractBalance_toVo_Method_Enhancement.md create mode 100644 docs/analysis/Voable_Interface_Analysis.md create mode 100644 docs/db/CONTRACT_BALANCE.sql create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server/src/main/java/com/ecep/contract/ds/contract/model/ContractBalance.java create mode 100644 server/src/main/java/com/ecep/contract/ds/contract/repository/ContractBalanceRepository.java create mode 100644 server/src/main/java/com/ecep/contract/ds/contract/service/ContractBalanceService.java diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index f7c687f..6d6e268 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ build/ ### VS Code ### .vscode/ -/config.properties \ No newline at end of file +/config.properties +node_modules +node_modules diff --git a/.trae/rules/client_service_rules.md b/.trae/rules/client_service_rules.md index 2b69312..edfc3af 100644 --- a/.trae/rules/client_service_rules.md +++ b/.trae/rules/client_service_rules.md @@ -1,83 +1,133 @@ -# 客户端 Service 类规则 +# Client 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` +本指南总结 Client 模块 Service 层的实现经验,用于指导后续 Service 的编写。本指南基于 Contract-Manager 项目中已实现的 Service 模式整理。 -## 3. 继承关系 -- 业务服务类通常继承自泛型基础服务类 `QueryService` - - `T` 表示 VO 类型(实现了 IdentityEntity 接口) - - `TV` 表示 ViewModel 类型(实现了 IdentityViewModel 接口) -- `QueryService` 实现了 `ViewModelService` 接口 -- `ViewModelService` 继承了 `IEntityService` 接口 -- 特定场景下可以不继承 `QueryService`,直接实现所需接口或创建独立服务类 +--- + +## 🏗️ 基础架构 + +### 1. 继承层次结构 ```java +// Service 接口定义 +public interface IEntityService { + T findById(Integer id); + T save(T entity); + void delete(T entity); + List findAll(); + Page findAll(Map params, Pageable pageable); + StringConverter getStringConverter(); +} + +// 基础 Service 实现 +public abstract class QueryService> + implements ViewModelService { + // 核心实现 +} + +// 具体业务 Service @Service -@CacheConfig(cacheNames = "company") -public class CompanyService extends QueryService { - // 业务方法实现 +@CacheConfig(cacheNames = "business") +public class XxxService extends QueryService { + // 业务特定实现 } ``` -## 4. 注解使用 -- **@Service**:标记为 Spring 服务组件,使其可被自动发现和注入 -- **@CacheConfig**:配置缓存名称,通常与服务类名对应 -- **@Cacheable**:标记方法结果可缓存,需指定缓存键(key) -- **@CacheEvict**:标记方法执行后清除缓存,可指定缓存键或清除所有 -- **@Caching**:组合多个缓存操作(如同时清除多个缓存条目) -- **@Autowired**:用于自动注入依赖的其他服务 +### 2. 核心特性 + +- **泛型支持**:`QueryService` 使用泛型处理不同类型的 Vo 和 ViewModel +- **WebSocket 通信**:通过 `WebSocketClientService` 与 Server 端通信 +- **异步处理**:使用 `CompletableFuture` 实现异步操作 +- **缓存机制**:集成 Spring Cache 支持多级缓存 +- **错误处理**:统一的异常处理和日志记录 + +--- + +## 📝 Service 编写规范 + +### 1. 类声明和注解 ```java -@Service -@CacheConfig(cacheNames = "contract") -public class ContractService extends QueryService { - @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); - } +@Service // Spring 组件注解 +@CacheConfig(cacheNames = "xxx") // 缓存配置,xxx为业务域名称 +public class XxxService extends QueryService { + // Service 实现 } ``` -## 5. 缓存机制 -- 每个服务类应有独立的缓存名称空间 -- 缓存键(key)应具有唯一性,通常使用 ID、代码或名称等唯一标识 -- 保存和删除操作时应清除相关缓存,保持数据一致性 -- 可使用 SpEL 表达式动态生成缓存键 -- 频繁查询的数据应考虑缓存,提高性能 +### 2. 缓存策略 -## 6. 异步调用机制 -- 使用 `async()` 方法进行异步远程调用 -- 方法参数通常包括:方法名、参数值、参数类型列表 -- 使用 `CompletableFuture` 处理异步结果 -- 使用 `handle()` 方法处理响应和异常 -- 远程调用异常应包装为 RuntimeException 并提供详细错误信息 +#### 缓存注解使用 +```java +@Cacheable(key = "#p0") // 按ID缓存 +@Cacheable(key = "'code-'+#p0") // 按代码缓存 +@Cacheable(key = "'name-'+#p0") // 按名称缓存 + +@CacheEvict(key = "#p0.id") // 删除时清除ID缓存 +@CacheEvict(key = "'code-'+#p0.code") // 清除代码缓存 + +@Caching(evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'code-'+#p0.code") +}) // 批量清除缓存 +``` + +#### 缓存键设计原则 +- **ID 缓存**:`#p0`(第一个参数,通常是ID) +- **代码缓存**:`'code-'+#p0`(业务代码的缓存) +- **名称缓存**:`'name-'+#p0`(业务名称的缓存) +- **关联缓存**:`'company-'+#p0.id`(关联实体的缓存) + +### 3. 核心方法实现 + +#### findById 方法 +```java +@Cacheable(key = "#p0") +@Override +public XxxVo findById(Integer id) { + return super.findById(id); // 调用父类方法 +} +``` + +#### save 方法(带缓存清除) +```java +@Caching(evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'code-'+#p0.code") +}) +@Override +public XxxVo save(XxxVo entity) { + return super.save(entity); +} +``` + +#### delete 方法(带缓存清除) +```java +@Caching(evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'code-'+#p0.code") +}) +@Override +public void delete(XxxVo entity) { + super.delete(entity); +} +``` + +--- + +## 🔄 异步通信模式 + +### 1. 基本异步调用 ```java -@Cacheable(key = "'code-'+#p0") -public ContractVo findByCode(String code) { +// 异步调用示例 +public XxxVo findByCode(String code) { try { return async("findByCode", code, String.class).handle((response, ex) -> { if (ex != null) { - throw new RuntimeException("远程方法+findByCode+调用失败", ex); + throw new RuntimeException("远程方法 findByCode 调用失败", ex); } if (response != null) { return updateValue(createNewEntity(), response); @@ -85,62 +135,374 @@ public ContractVo findByCode(String code) { return null; }).get(); } catch (Exception e) { - throw new RuntimeException("查询失败: " + code, e); + throw new RuntimeException("查找实体失败: " + code, e); } } ``` -## 7. 基础方法实现 -- 应实现 `IEntityService` 接口定义的核心方法: - - `findById(Integer id)`:根据 ID 查询实体 - - `save(T entity)`:保存实体 - - `delete(T entity)`:删除实体 - - `findAll()`:查询所有实体 - - `findAll(Map params, Pageable pageable)`:条件分页查询 - - `getStringConverter()`:获取类型转换器 -- 通常通过继承 `QueryService` 来复用这些基础方法的实现 -- 可根据业务需求重写或扩展基础方法 - -## 8. 业务方法规范 -- 业务方法应与服务端对应,保持方法名和参数一致 -- 方法命名应清晰表达其功能,如 `findByName`, `findByCode` -- 复杂业务逻辑应封装为独立方法 -- 参数校验应在方法开始处进行 -- 返回值类型应明确,避免使用过于泛化的类型 - -## 9. 类型转换器 -- 实现 `getStringConverter()` 方法,返回对应的 StringConverter 实例 -- 通常创建专用的 Converter 类,如 `CustomerCatalogStringConverter` -- 转换器实例应作为服务类的成员变量,避免重复创建 +### 2. 复杂对象处理 ```java -public class CustomerCatalogService extends QueryService { - private final CustomerCatalogStringConverter stringConverter = new CustomerCatalogStringConverter(this); - - @Override - public StringConverter getStringConverter() { - return stringConverter; +public List findDetailsByXxxId(Integer xxxId) { + try { + return async("findDetailsByXxxId", List.of(xxxId), List.of(Integer.class)) + .handle((response, ex) -> { + if (ex != null) { + throw new RuntimeException("远程方法调用失败", ex); + } + if (response != null) { + try { + List content = new ArrayList<>(); + for (JsonNode node : response) { + XxxDetailVo newEntity = new XxxDetailVo(); + objectMapper.updateValue(newEntity, node); + content.add(newEntity); + } + return content; + } catch (Exception e) { + throw new RuntimeException(response.toString(), e); + } + } + return null; + }).get(); + } catch (Exception e) { + throw new RuntimeException(e); } } ``` -## 10. 错误处理 -- 远程调用异常应捕获并包装为更具描述性的 RuntimeException -- 提供详细的错误信息,包括调用的方法名和参数 -- 对于可预期的业务异常,可添加专门的处理逻辑 -- 不推荐使用 printStackTrace(),应使用日志记录异常 +--- -## 11. 工具方法和辅助功能 -- 通用功能可封装为工具方法 -- 配置相关操作可通过 `SysConfService` 实现 -- 文件路径处理应使用 `File` 类和相关工具方法 -- 日期时间处理应使用 Java 8+ 的日期时间 API +## 💼 业务逻辑模式 -## 12. 最佳实践 -- 遵循单一职责原则,每个服务类专注于一个业务领域 -- 优先使用继承和接口实现来复用代码 -- 合理使用缓存提高性能,但注意缓存一致性 -- 异步调用应正确处理异常和超时情况 -- 服务类之间的依赖应通过 `@Autowired` 注入,避免硬编码 -- 方法实现应简洁明了,复杂逻辑应拆分 -- 为重要方法添加 JavaDoc 注释,说明其功能和参数含义 \ No newline at end of file +### 1. 文件系统集成 + +#### 路径管理 +```java +@Autowired +private SysConfService confService; + +private File basePath; + +public File getBasePath() { + if (basePath == null) { + basePath = new File(confService.getString(Constant.KEY_BASE_PATH)); + } + return basePath; +} + +// 验证路径是否在基础目录内 +public boolean checkXxxPathInBasePath(XxxVo xxx) { + if (!existsXxxPath(xxx)) { + return false; + } + File basePath = getBasePath(); + if (basePath == null || !basePath.exists()) { + throw new IllegalArgumentException("基础目录不存在"); + } + File path = new File(xxx.getPath()); + return path.getAbsolutePath().startsWith(basePath.getAbsolutePath()); +} + +// 检查路径是否存在 +public boolean existsXxxPath(XxxVo xxx) { + if (!StringUtils.hasText(xxx.getPath())) { + return false; + } + File path = new File(xxx.getPath()); + return path.exists(); +} +``` + +#### 目录创建 +```java +public File makePath(XxxVo xxx) { + File basePath = getBasePath(); + if (!basePath.exists()) { + holder.error("存储目录不存在:" + basePath.getAbsolutePath()); + return null; + } + + // 构建目录路径逻辑 + String fileName = FileUtils.escapeFileName(xxx.getName()); + File dir = new File(basePath, fileName); + + if (!dir.exists()) { + if (!dir.mkdir()) { + holder.error("创建目录失败:" + dir.getAbsolutePath()); + return null; + } + } + return dir; +} +``` + +### 2. 业务验证模式 + +#### 数据完整性验证 +```java +public void verifyXxx(XxxVo xxx, LocalDate verifyDate, MessageHolder holder) { + // 检查关键字段 + if (!StringUtils.hasText(xxx.getCode())) { + holder.error("编号异常:未设置"); + return; + } + + // 检查状态字段 + String status = xxx.getStatus(); + if (StringUtils.hasText(status) && status.contains("无效")) { + LocalDate end = xxx.getEndDate(); + LocalDate begin = xxx.getBeginDate(); + if (begin == null || end == null) { + holder.error("状态异常:" + status); + } else { + if (!MyDateTimeUtils.dateValidFilter(verifyDate, begin, end, 0)) { + holder.error("状态异常:" + status); + } + } + } else { + holder.error("状态异常:未设置"); + } +} +``` + +#### 关联实体验证 +```java +public boolean verifyAsXxxType(XxxVo xxx, LocalDate verifyDate, MessageHolder holder) { + boolean valid = false; + + // 检查关联实体 + RelatedVo related = relatedService.findById(xxx.getRelatedId()); + if (related == null) { + holder.error("关联实体不存在"); + valid = true; + } + + // 检查关联数据 + if (!StringUtils.hasText(xxx.getRelatedField())) { + holder.error("关联字段未设置"); + valid = true; + } + + // 检查业务规则 + if (xxx.getStatus() == XxxStatus.INACTIVE) { + holder.error("业务状态异常:已停用"); + valid = true; + } + + return valid; +} +``` + +### 3. 复杂业务查询 + +#### 分页查询 +```java +public List findAllByXxxCondition(XxxCondition condition, LocalDate beginDate, LocalDate endDate) { + return findAll(ParamUtils.builder() + .equals("field1", condition.getField1()) + .between("createDate", beginDate, endDate) + .equals("status", "ACTIVE") + .build(), Pageable.unpaged()).getContent(); +} +``` + +#### 组合条件查询 +```java +public Page findAllWithComplexCondition(XxxQueryParam param) { + ParamUtils.ParamBuilder builder = ParamUtils.builder() + .equals("category", param.getCategory()); + + if (StringUtils.hasText(param.getName())) { + builder.like("name", "%" + param.getName() + "%"); + } + + if (param.getDateRange() != null) { + builder.between("createDate", param.getDateRange().getStart(), + param.getDateRange().getEnd()); + } + + return findAll(builder.build(), Pageable.ofSize(param.getPageSize())); +} +``` + +--- + +## 🔧 工具类和依赖注入 + +### 1. 常用工具类依赖 + +```java +// 常用注入 +@Autowired +private SysConfService confService; // 系统配置服务 +@Autowired +private RelatedService relatedService; // 关联实体服务 + +// 静态工具类使用 +import com.ecep.contract.util.FileUtils; // 文件工具 +import com.ecep.contract.util.ParamUtils; // 参数工具 +import com.ecep.contract.util.MyStringUtils; // 字符串工具 +``` + +### 2. 工具类使用示例 + +#### ParamUtils 构建查询条件 +```java +// 构建复杂查询条件 +Map params = ParamUtils.builder() + .equals("field1", value1) + .equals("field2", value2) + .like("name", "%" + keyword + "%") + .between("date", startDate, endDate) + .in("status", List.of("ACTIVE", "PENDING")) + .orderBy("createTime", "desc") + .build(); +``` + +#### FileUtils 处理文件路径 +```java +// 文件名转义 +String safeFileName = FileUtils.escapeFileName(companyName); + +// 获取父级前缀 +String parentPrefix = FileUtils.getParentPrefixByDistrict(district); +``` + +--- + +## 📊 错误处理和日志 + +### 1. 异常处理模式 + +```java +// 查询异常处理 +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); +} + +// 业务验证异常 +public boolean businessMethod(XxxVo xxx) { + if (xxx == null) { + return false; + } + + if (!xxx.isValid()) { + throw new IllegalArgumentException("实体数据无效"); + } + + return true; +} +``` + +### 2. 日志记录 + +```java +// 在 QueryService 中已集成日志 +private static final Logger logger = LoggerFactory.getLogger(QueryService.class); + +// 在业务方法中使用 +public void deleteXxx(XxxVo xxx) { + try { + super.delete(xxx); + logger.info("删除实体成功 #{}", xxx.getId()); + } catch (Exception e) { + logger.error("删除实体失败 #{}", xxx.getId(), e); + throw new RuntimeException("删除实体失败", e); + } +} +``` + +--- + +## 🧪 测试和验证 + +### 1. 方法验证检查点 + +- **输入参数验证**:检查空值、格式、范围 +- **业务逻辑验证**:检查状态、关联、权限 +- **文件系统验证**:检查路径、权限、空间 +- **数据库验证**:检查连接、事务、一致性 + +### 2. 常见测试场景 + +```java +// 测试用例示例 +@Test +public void testFindById() { + // 正常情况 + XxxVo result = xxxService.findById(1); + assertNotNull(result); + + // 异常情况 + assertThrows(RuntimeException.class, () -> { + xxxService.findById(-1); + }); +} + +@Test +public void testSaveWithCache() { + XxxVo xxx = createTestXxx(); + xxx.setCode("TEST001"); + + XxxVo saved = xxxService.save(xxx); + assertNotNull(saved.getId()); + + // 验证缓存 + XxxVo cached = xxxService.findById(saved.getId()); + assertEquals(saved.getId(), cached.getId()); +} +``` + +--- + +## 🎯 最佳实践 + +### 1. 代码组织原则 + +- **单一职责**:每个 Service 专注特定的业务域 +- **依赖注入**:合理使用 `@Autowired` 注入依赖 +- **缓存策略**:为高频查询字段配置缓存 +- **异常处理**:统一处理业务异常和系统异常 + +### 2. 性能优化建议 + +- **缓存配置**:合理设置缓存过期时间 +- **分页查询**:避免大数据量一次性查询 +- **异步处理**:使用异步调用提升响应速度 +- **批量操作**:考虑批量处理减少网络开销 + +### 3. 可维护性提升 + +- **命名规范**:遵循项目统一的命名约定 +- **注释文档**:为复杂业务逻辑添加注释 +- **代码复用**:提取公共逻辑到工具类 +- **版本兼容**:考虑前后端版本兼容性 + +--- + +## 📚 相关规范 + +- **Controller 规范**:[client_controller_rules.md](.trae/rules/client_controller_rules.md) +- **转换器规范**:[client_converter_rules.md](.trae/rules/client_converter_rules.md) +- **Task 规范**:[client_task_rules.md](.trae/rules/client_task_rules.md) +- **Entity 规范**:[entity_rules.md](.trae/rules/entity_rules.md) +- **VO 规范**:[vo_rules.md](.trae/rules/vo_rules.md) + +--- + +*本文档基于 Contract-Manager 项目现有 Service 实现模式总结,遵循项目既定的技术架构和编程规范。* + +**文档版本**: v1.0.0 +**最后更新**: 2024-12-19 +**维护团队**: Contract Manager Development Team \ No newline at end of file diff --git a/.trae/rules/repository_comprehensive_analysis_report.md b/.trae/rules/repository_comprehensive_analysis_report.md new file mode 100644 index 0000000..9b9320e --- /dev/null +++ b/.trae/rules/repository_comprehensive_analysis_report.md @@ -0,0 +1,374 @@ +# Repository一致性分析 - 综合报告 + +## 📊 执行摘要 + +### 分析概览 +- **总Repository数量**: 22个 +- **涉及业务域**: contract, company, customer, project, vendor +- **分析时间**: 2024年项目全面分析 +- **分析范围**: 覆盖server模块下所有5个业务域的Repository实现 + +### 目录覆盖情况 +✅ `server/src/main/java/com/ecep/contract/ds/contract/repository/` (13个) +✅ `server/src/main/java/com/ecep/contract/ds/company/repository/` (3个) +✅ `server/src/main/java/com/ecep/contract/ds/customer/repository/` (2个) +✅ `server/src/main/java/com/ecep/contract/ds/project/repository/` (3个) +✅ `server/src/main/java/com/ecep/contract/ds/vendor/repository/` (2个) + +## 🔍 关键发现与评估 + +### 🔴 严重问题:继承层次不一致(5个) + +#### 1. 直接继承JpaRepository(4个) +- `ContractFileRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor` +- `ContractItemRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor` +- `VendorFileRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor` +- `ProjectQuotationRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor` + +#### 2. 继承多个冗余接口(1个) +- `PurchaseOrderRepository` - 同时继承了4个接口:`CrudRepository`, `PagingAndSortingRepository`, `JpaRepository`, `JpaSpecificationExecutor` + +### 🟡 中等问题(6个) + +#### 注解缺失(2个) +- `CompanyFileRepository` - 缺少`@Repository`注解 +- `ProjectFileRepository` - 缺少`@Repository`注解 + +#### 文档注释缺失(4个) +- `CompanyFileRepository` - 缺少JavaDoc注释 +- `ProjectFileRepository` - 缺少JavaDoc注释 +- `VendorFileRepository` - 缺少JavaDoc注释 +- `ProjectQuotationRepository` - 缺少JavaDoc注释 + +### 📈 整体质量评估 + +#### 正确实现统计 +- ✅ **17个Repository正确继承MyRepository** (77%) +- ❌ **5个Repository继承层次错误** (23%) +- 🟡 **2个Repository缺少注解** (9%) +- 🟡 **4个Repository文档不完整** (18%) + +#### 质量分级分布 +- **A级(完全正确)**: 13个 (59%) +- **B级(轻微问题)**: 6个 (27%) +- **C级(严重错误)**: 3个 (14%) + +--- + +# 详细技术分析 + +## 继承一致性详细分析 + +### ✅ 正确继承MyRepository的Repository(17个) + +**contract业务域(7个):** +- ContractRepository +- ContractBalanceRepository +- SalesOrderRepository +- ContractTypeRepository +- ContractKindRepository +- ContractInvoiceRepository + +**company业务域(3个):** +- CompanyRepository +- CompanyFileRepository (有注解问题) +- CompanyCustomerRepository + +**customer业务域(2个):** +- CustomerCatalogRepository + +**project业务域(2个):** +- ProjectRepository +- ProjectFileRepository (有注解问题) + +**vendor业务域(1个):** +- VendorRepository + +### ❌ 继承层次错误的Repository(5个) + +#### Contract业务域(3个) +1. **ContractFileRepository** +2. **ContractItemRepository** +3. **PurchaseOrderRepository** + +#### Project业务域(1个) +4. **ProjectQuotationRepository** + +#### Vendor业务域(1个) +5. **VendorFileRepository** + +## 问题影响分析 + +### 技术影响 +- ❌ 违反了统一的架构设计原则 +- ❌ 失去了MyRepository提供的统一功能增强 +- ❌ 代码风格不一致,影响可维护性 +- ❌ 开发团队需要维护多种不同的实现模式 +- ❌ 增加了代码维护成本和技术债务 + +### 业务影响 +- ❌ 代码可维护性降低 +- ❌ 新团队成员学习成本增加 +- ❌ 代码审查复杂度提高 +- ❌ 系统稳定性潜在风险增加 + +## 详细修复方案 + +### 🔴 优先级1:修复继承层次错误 + +#### 1. ContractFileRepository +```java +// ❌ 当前错误实现 +public interface ContractFileRepository + extends JpaRepository, JpaSpecificationExecutor { + +// ✅ 正确修复 +@Repository +public interface ContractFileRepository extends MyRepository { + // 自动获得MyRepository提供的所有功能 +} +``` + +#### 2. ContractItemRepository +```java +// ❌ 当前错误实现 +public interface ContractItemRepository + extends JpaRepository, JpaSpecificationExecutor { + +// ✅ 正确修复 +@Repository +public interface ContractItemRepository extends MyRepository { + // 统一继承结构,简化维护 +} +``` + +#### 3. VendorFileRepository +```java +// ❌ 当前错误实现 +@Repository +public interface VendorFileRepository + extends JpaRepository, JpaSpecificationExecutor { + +// ✅ 正确修复 +@Repository +public interface VendorFileRepository extends MyRepository { + // 保持注解的同时修正继承结构 +} +``` + +#### 4. ProjectQuotationRepository +```java +// ❌ 当前错误实现 +@Repository +public interface ProjectQuotationRepository + extends JpaRepository, JpaSpecificationExecutor { + +// ✅ 正确修复 +@Repository +public interface ProjectQuotationRepository extends MyRepository { + // 获得MyRepository的所有增强功能 +} +``` + +#### 5. PurchaseOrderRepository +```java +// ❌ 当前错误实现(继承4个冗余接口) +public interface PurchaseOrderRepository extends + CrudRepository, + PagingAndSortingRepository, + JpaRepository, + JpaSpecificationExecutor { + +// ✅ 正确修复 +@Repository +public interface PurchaseOrderRepository extends MyRepository { + // 单一继承,清晰简洁 +} +``` + +### 🟡 优先级2:修复注解缺失 + +#### CompanyFileRepository +```java +// ❌ 当前缺少注解 +public interface CompanyFileRepository extends MyRepository { + +// ✅ 添加注解 +@Repository +public interface CompanyFileRepository extends MyRepository { + // 添加完整的JavaDoc注释 + /** + * 公司文件数据访问接口 + * 提供公司相关文件的CRUD操作和业务查询功能 + */ +} +``` + +#### ProjectFileRepository +```java +// ❌ 当前缺少注解 +public interface ProjectFileRepository extends MyRepository { + +// ✅ 添加注解 +@Repository +public interface ProjectFileRepository extends MyRepository { + /** + * 项目文件数据访问接口 + * 提供项目文件的管理和查询功能 + */ +} +``` + +### 🟡 优先级3:完善文档注释 + +为以下Repository补充完整的JavaDoc注释: +- CompanyFileRepository +- ProjectFileRepository +- VendorFileRepository +- ProjectQuotationRepository + +标准JavaDoc格式示例: +```java +/** + * [功能描述]数据访问接口 + * + * 提供[业务描述]的CRUD操作和业务查询功能 + * + * @author [作者] + * @since [版本] + */ +``` + +## 实施计划 + +### 阶段1:立即修复(1-2天) +**目标**: 解决所有严重的继承层次错误 + +**执行步骤**: +1. 备份当前的Repository实现 +2. 逐一修复5个继承层次错误的Repository +3. 添加缺失的@Repository注解 +4. 运行编译测试确保无破坏性变更 +5. 执行基本功能验证测试 + +**验收标准**: +- 所有Repository都继承MyRepository +- 编译通过,无语法错误 +- 基本CRUD功能正常 + +### 阶段2:文档完善(1天) +**目标**: 统一JavaDoc注释规范 + +**执行步骤**: +1. 为4个Repository补充JavaDoc注释 +2. 检查并更新相关单元测试 +3. 验证文档格式规范性 +4. 代码审查确认 + +**验收标准**: +- 所有Repository都有完整的JavaDoc注释 +- 测试用例覆盖主要功能 +- 代码审查通过 + +### 阶段3:质量保证(半天) +**目标**: 确保修改不影响系统稳定性 + +**执行步骤**: +1. 全面编译测试 +2. 运行相关单元测试套件 +3. 执行集成测试验证 +4. 更新相关文档 +5. 最终验收确认 + +**验收标准**: +- 所有测试通过 +- 系统功能完整 +- 性能无明显影响 + +## 对规范文档的影响 + +### ✅ 已更新的规范文档 +- **`server_repository_rules.md`** - 已包含错误示例和正确实现指南 +- 新增"2.2 重要错误示例"章节 +- 新增"2.3 当前项目错误统计"章节 +- 新增"2.5 MyRepository基类能力"章节 + +### 📋 需要更新的检查清单 +1. **继承层次检查** - 确保所有Repository继承MyRepository +2. **注解完整性检查** - 验证所有Repository都有@Repository注解 +3. **文档规范性检查** - 确认JavaDoc注释完整性 +4. **代码风格一致性检查** - 验证命名规范和代码格式 + +## 🎯 预期收益 + +### 技术收益 +- ✅ **统一架构设计** - 所有Repository遵循一致的设计模式 +- ✅ **代码一致性提升** - 消除实现差异,提高代码质量 +- ✅ **维护成本降低** - 单一继承结构,简化维护工作 +- ✅ **团队开发效率提升** - 统一规范,降低学习成本 + +### 质量收益 +- ✅ **减少代码冗余** - 消除重复的接口继承 +- ✅ **提高代码可读性** - 统一模式,易于理解 +- ✅ **降低技术债务** - 减少不一致实现的技术负担 +- ✅ **增强系统稳定性** - 统一模式,减少潜在风险 + +## 📅 后续行动 + +### 立即行动(本周) +1. 🔧 修复5个继承层次错误的Repository +2. 🔧 添加缺失的@Repository注解 +3. 🔧 编译测试验证功能完整性 + +### 短期完善(下周) +1. 📝 补充JavaDoc文档注释 +2. 🧪 运行完整的单元测试套件 +3. 👥 代码审查确认实现质量 + +### 长期维护 +1. 📋 建立Repository开发规范检查清单 +2. 🔍 定期进行Repository实现一致性检查 +3. 🎓 团队培训和规范宣贯 +4. 🤖 建立CI/CD流程中的自动检查机制 + +## 📞 技术支持 + +如需技术支持或有任何疑问,请参考: + +### 相关文档 +- **详细技术分析**: `repository_analysis_report.md` +- **实施规范指导**: `server_repository_rules.md` +- **项目整体规范**: `.trae/rules/` 目录 + +### 联系方式 +- 技术问题: 参考详细分析报告中的修复代码示例 +- 规范疑问: 查看server_repository_rules.md文档 +- 实施支持: 遵循本报告的分阶段实施计划 + +--- + +## 结论 + +通过对项目中22个Repository的全面分析,我们发现了明显的实现不一致性问题,特别是23%的Repository存在继承层次错误。这些问题严重影响了代码的可维护性和架构的统一性。 + +**关键统计数据**: +- 17个Repository正确实现 (77%) +- 5个继承层次错误 (23%) +- 2个注解缺失 (9%) +- 4个文档不完整 (18%) + +**修复优先级**: +1. **高优先级**: 修复5个继承层次错误(1-2天) +2. **中优先级**: 补充2个缺失注解(半天) +3. **低优先级**: 完善4个文档注释(1天) + +建议按照本报告提出的分阶段实施计划,逐步改进所有Repository实现,确保项目达到统一的架构设计标准。通过这些改进,将显著提升代码质量、降低维护成本,并为团队的长期发展奠定坚实基础。 + +**分析状态**: ✅ 完成 +**下一步**: 执行分阶段修复计划 +**预计完成时间**: 3-4个工作日 + +--- +**报告生成时间**: 2024年 +**分析深度**: 全面技术分析 +**实施可行性**: 高(详细修复方案已提供) \ No newline at end of file diff --git a/.trae/rules/server_repository_rules.md b/.trae/rules/server_repository_rules.md new file mode 100644 index 0000000..6e7f80e --- /dev/null +++ b/.trae/rules/server_repository_rules.md @@ -0,0 +1,460 @@ +# Server模块 Repository 实现经验总结 + +## 1. Repository 基本架构 + +### 1.1 接口设计原则 +- Repository接口必须继承`MyRepository`基类 +- 使用`@Repository`注解标记为Spring组件 +- 遵循接口隔离原则,按业务功能组织方法 + +### 1.2 包结构规范 +``` +com.ecep.contract.ds.{business}.repository.{EntityName}Repository.java +``` +示例:`ContractBalanceRepository`位于`com.ecep.contract.ds.contract.repository` + +## 2. 接口继承层次 + +### 2.1 基础接口结构 +```java +@Repository +public interface ContractBalanceRepository extends MyRepository { + // 自定义查询方法 +} +``` + +### 2.2 ⚠️ 重要错误示例 - 避免这些实现方式 + +**❌ 错误示例1:直接继承JpaRepository** +```java +// ContractFileRepository, ContractItemRepository, VendorFileRepository, ProjectQuotationRepository +@Repository +public interface ContractFileRepository + extends JpaRepository, JpaSpecificationExecutor { + // 错误:不应该直接继承JpaRepository + List findByContractId(Integer contractId); +} +``` + +**❌ 错误示例2:继承多个冗余接口** +```java +// PurchaseOrderRepository +public interface PurchaseOrderRepository extends + CrudRepository, + PagingAndSortingRepository, + JpaRepository, + JpaSpecificationExecutor { + // 错误:冗余接口继承,应该只继承MyRepository +} +``` + +**❌ 错误示例3:缺少@Repository注解** +```java +// CompanyFileRepository, ProjectFileRepository +public interface CompanyFileRepository extends MyRepository { + // 错误:缺少@Repository注解 + List findByCompany(Company company); +} +``` + +**❌ 错误示例4:缺少JavaDoc注释** +```java +// 多个Repository存在此问题 +public interface VendorFileRepository extends MyRepository { + // 错误:缺少JavaDoc注释说明用途和方法 + List findAllByVendorId(int vendorId); +} +``` + +**✅ 正确实现示例** +```java +/** + * 合同Repository - 提供合同相关的数据库访问操作 + */ +@Repository +public interface ContractRepository extends MyRepository { + // 正确:统一继承MyRepository,有完整的JavaDoc + + /** + * 根据合同代码查询合同 + * @param code 合同代码 + * @return 合同 Optional + */ + Optional findByCode(String code); + + /** + * 根据状态查询合同列表 + * @param status 合同状态 + * @return 合同列表 + */ + List findByStatus(ContractStatus status); +} +``` + +### 2.3 当前项目错误统计 + +基于对全项目22个Repository的全面分析,发现以下问题分布: + +**🔴 继承层次错误(5个):** +- `ContractFileRepository` - 直接继承JpaRepository +- `ContractItemRepository` - 直接继承JpaRepository +- `VendorFileRepository` - 直接继承JpaRepository +- `ProjectQuotationRepository` - 直接继承JpaRepository +- `PurchaseOrderRepository` - 继承4个冗余接口 + +**🟡 注解缺失(2个):** +- `CompanyFileRepository` - 缺少@Repository注解 +- `ProjectFileRepository` - 缺少@Repository注解 + +**🟡 文档注释不完整(4个):** +- `CompanyFileRepository` - 缺少JavaDoc注释 +- `ProjectFileRepository` - 缺少JavaDoc注释 +- `VendorFileRepository` - 缺少JavaDoc注释 +- `ProjectQuotationRepository` - 缺少JavaDoc注释 + +**✅ 正确实现(17个):** +- contract: ContractRepository, ContractBalanceRepository, SalesOrderRepository, ContractTypeRepository, ContractKindRepository, ContractInvoiceRepository +- company: CompanyRepository +- customer: CompanyCustomerRepository, CustomerCatalogRepository +- project: ProjectRepository +- vendor: VendorRepository + +### 2.5 MyRepository基类能力 + +`MyRepository`接口继承结构: +- `JpaRepository`提供CRUD操作 +- 继承`JpaSpecificationExecutor`提供条件查询能力 +- 继承`QueryByExampleExecutor`提供示例查询能力 + +## 3. 查询方法设计 + +### 3.1 方法命名规范 +使用Spring Data JPA的查询方法命名约定: + +#### 基本查询方法 +- `findBy{属性名}` - 根据属性查找 +- `findBy{属性名}And{属性名}` - 多条件AND查询 +- `findBy{属性名}Or{属性名}` - 多条件OR查询 +- `findBy{属性名}OrderBy{排序属性}` - 带排序查询 + +#### 关联对象查询 +- `findBy{关联对象属性名}.{关联对象属性}` - 嵌套属性查询 +- 支持多级嵌套,如:`findByContractCompanyName` + +#### 特殊查询类型 +```java +// 根据业务ID查找 +ContractBalance findByGuid(UUID guid); + +// 根据合同ID查找(带分页支持) +Page findByContractId(Integer contractId, Pageable pageable); + +// 根据多个条件组合查询 +List findByContractIdAndRefId(Integer contractId, String refId); +``` + +### 3.2 自定义查询实现 +当方法命名无法满足需求时,使用`@Query`注解: + +```java +@Query("SELECT cb FROM ContractBalance cb WHERE cb.contract.id = :contractId " + + "AND cb.guid = :guid AND cb.setupDate >= :startDate") +Page findByContractAndGuidAndDateRange( + @Param("contractId") Integer contractId, + @Param("guid") UUID guid, + @Param("startDate") LocalDateTime startDate, + Pageable pageable +); +``` + +## 4. 分页和排序支持 + +### 4.1 分页查询 +```java +// 在Repository接口中定义 +Page findByContractId(Integer contractId, Pageable pageable); + +// 在Service中调用 +Page result = repository.findByContractId(contractId, + PageRequest.of(page, size, Sort.by("setupDate").descending())); +``` + +### 4.2 排序规范 +- 默认按主键ID降序排列 +- 业务相关字段按创建时间降序 +- 支持多字段排序:`Sort.by("field1").ascending().and(Sort.by("field2").descending())` + +## 5. 统计和聚合查询 + +### 5.1 基础统计 +```java +@Query("SELECT COUNT(cb) FROM ContractBalance cb WHERE cb.contract.id = :contractId") +Long countByContractId(@Param("contractId") Integer contractId); + +@Query("SELECT SUM(cb.balanceAmount) FROM ContractBalance cb WHERE cb.contract.id = :contractId") +BigDecimal sumBalanceByContractId(@Param("contractId") Integer contractId); +``` + +### 5.2 复杂聚合 +```java +@Query("SELECT new com.ecep.contract.vo.ContractBalanceStatsVO(" + + "cb.contract.id, COUNT(cb), SUM(cb.balanceAmount)) " + + "FROM ContractBalance cb GROUP BY cb.contract.id") +List getBalanceStatsByContract(); +``` + +## 6. 缓存策略 + +### 6.1 缓存注解使用 +虽然Repository本身不直接使用缓存,但需要考虑Service层的缓存需求: + +```java +// 在Service层配合使用 +@Cacheable(key = "#p0") +ContractBalance findById(Integer id); + +@CacheEvict(key = "#p0.id") +ContractBalance save(ContractBalance contractBalance); +``` + +### 6.2 缓存键设计原则 +- 单一记录:使用主键作为键,如`"balance-" + id` +- 按业务维度缓存:使用业务标识,如`"contract-" + contractId` +- 避免缓存键冲突 + +## 7. 事务管理 + +### 7.1 事务传播级别 +- 查询方法:默认`REQUIRED` +- 写操作:明确指定`@Transactional(propagation = Propagation.REQUIRED)` + +### 7.2 异常处理 +```java +@Transactional +public ContractBalance saveWithRetry(ContractBalance balance) { + try { + return repository.save(balance); + } catch (DataIntegrityViolationException e) { + // 处理数据完整性异常 + throw new BusinessException("数据重复或违反约束", e); + } +} +``` + +## 8. 性能优化 + +### 8.1 懒加载优化 +```java +// 在查询时指定抓取策略 +@Query("SELECT cb FROM ContractBalance cb LEFT JOIN FETCH cb.contract " + + "LEFT JOIN FETCH cb.employee WHERE cb.id = :id") +Optional findByIdWithRelations(@Param("id") Integer id); +``` + +### 8.2 批量操作优化 +```java +// 批量插入 +@Modifying +@Query("DELETE FROM ContractBalance cb WHERE cb.contract.id IN :contractIds") +void deleteByContractIds(@Param("contractIds") List contractIds); +``` + +## 9. 错误处理和调试 + +### 9.1 常见异常类型 +- `DataAccessException` - 数据访问异常 +- `DataIntegrityViolationException` - 数据完整性异常 +- `EmptyResultDataAccessException` - 空结果异常 + +### 9.2 日志记录 +```java +@Repository +@Slf4j +public class ContractBalanceRepository { + + @Query("...") + public List findComplexQuery(...) { + log.debug("执行复杂查询: {}", jpql); + // 执行查询 + } +} +``` + +## 10. 测试策略 + +### 10.1 Repository测试 +```java +@DataJpaTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ContractBalanceRepositoryTest { + + @Autowired + private TestEntityManager em; + + @Autowired + private ContractBalanceRepository repository; + + @Test + void shouldFindByContractId() { + // 测试数据准备 + ContractBalance balance = createTestBalance(); + + // 执行测试 + Page result = repository.findByContractId( + balance.getContract().getId(), + PageRequest.of(0, 10) + ); + + // 断言验证 + assertThat(result.getContent()).hasSize(1); + assertThat(result.getContent().get(0)).isEqualTo(balance); + } +} +``` + +### 10.2 性能测试 +```java +@Test +@RepeatedTest(100) +@Timeout(value = 5, unit = TimeUnit.SECONDS) +void performanceTest() { + // 性能测试实现 +} +``` + +## 11. 最佳实践总结 + +### 11.1 设计原则 +1. **单一职责**:每个Repository只负责一个实体类型 +2. **接口隔离**:根据客户端需求设计专用查询方法 +3. **命名清晰**:方法名应能直观表达查询意图 +4. **性能优先**:合理使用索引和查询优化 + +### 11.2 代码规范 +1. **注解完整**:正确使用`@Repository`、`@Query`、`@Param` +2. **参数校验**:对传入参数进行null和有效性检查 +3. **异常处理**:捕获并处理数据库访问异常 +4. **日志记录**:记录关键查询操作和性能指标 + +### 11.3 维护性 +1. **版本兼容**:考虑数据库模式变更的影响 +2. **向后兼容**:新增方法不影响现有功能 +3. **文档完整**:复杂查询添加JavaDoc说明 +4. **测试覆盖**:确保关键查询逻辑有测试覆盖 + +## 12. 常见问题和解决方案 + +### 12.1 N+1查询问题 +**问题**:查询列表时触发N+1次关联查询 +**解决**:使用`JOIN FETCH`或`@EntityGraph` + +### 12.2 性能瓶颈 +**问题**:复杂查询导致响应慢 +**解决**: +1. 添加数据库索引 +2. 优化查询语句 +3. 使用分页限制结果集 +4. 考虑使用缓存 + +### 12.3 事务死锁 +**问题**:并发操作导致事务死锁 +**解决**: +1. 合理设计事务边界 +2. 调整事务隔离级别 +3. 实现重试机制 + +## 13. 扩展建议 + +### 13.1 动态查询支持 +考虑使用`Querydsl`或`Specification`支持动态查询构建。 + +### 13.2 读写分离 +实现主从数据库分离,提升查询性能。 + +### 13.3 分布式缓存 +结合Redis等分布式缓存,提升查询性能。 + +--- + +## 14. 实施检查清单 + +### 14.1 Repository实现后验证步骤 +在每个Repository实现完成后,请按以下清单进行验证: + +#### ✅ 基础架构检查 +- [ ] Repository接口继承了 `MyRepository` +- [ ] 使用了 `@Repository` 注解 +- [ ] 包路径符合规范:`com.ecep.contract.ds.{business}.repository` +- [ ] 类名符合命名规范:`{EntityName}Repository` + +#### ✅ 方法设计检查 +- [ ] 遵循Spring Data JPA命名约定 +- [ ] 包含必要的业务查询方法 +- [ ] 复杂查询使用 `@Query` 注解 +- [ ] 分页方法支持 `Pageable` 参数 + +#### ✅ 文档和注释检查 +- [ ] 类级别JavaDoc注释完整 +- [ ] 关键方法有JavaDoc说明 +- [ ] 参数和返回值有明确说明 + +#### ✅ 性能和质量检查 +- [ ] 避免N+1查询问题 +- [ ] 适当使用索引提示 +- [ ] 包含必要的单元测试 +- [ ] 异常处理考虑周全 + +### 14.2 常见错误检查 +在提交代码前,特别检查是否犯了以下常见错误: + +#### ❌ 继承层次错误 +```java +// 错误示例 - 不要这样做 +public interface SomeRepository extends JpaRepository +public interface AnotherRepository extends CrudRepository + +// 正确做法 - 统一使用MyRepository +public interface SomeRepository extends MyRepository +``` + +#### ❌ 方法命名不规范 +```java +// 错误示例 +List getDataByCondition(String condition) // 使用get前缀而非find + +// 正确做法 +List findByCondition(String condition) // 使用find前缀 +``` + +#### ❌ 缺少必要文档 +```java +// 错误示例 - 无注释 +List findByStatus(String status); + +// 正确做法 - 完整注释 +/** + * 根据状态查询实体列表 + * + * @param status 状态值 + * @return 符合状态的实体列表 + */ +List findByStatus(String status); +``` + +### 14.3 架构一致性验证 +确保新实现的Repository与项目中其他Repository保持一致: + +1. **继承模式统一**:所有Repository都必须继承MyRepository +2. **注解使用统一**:统一使用@Repository注解 +3. **命名约定统一**:遵循Spring Data JPA命名规范 +4. **文档风格统一**:保持JavaDoc注释风格一致 + +### 14.4 后续维护注意事项 +- 新增查询方法时遵循现有命名规范 +- 修改现有方法时保持向后兼容性 +- 定期审查Repository方法的性能和合理性 +- 及时更新相关文档和测试用例 + +--- + +*本文档基于ContractBalanceRepository实现经验总结,结合项目实际情况分析,其他Repository实现应参考此文档规范。特别注意避免文档中提到的常见错误。* \ No newline at end of file diff --git a/.trae/rules/server_service_rules.md b/.trae/rules/server_service_rules.md index 1087663..9e427e6 100644 --- a/.trae/rules/server_service_rules.md +++ b/.trae/rules/server_service_rules.md @@ -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**: 通用实体服务基类,提供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**: 实体基本操作接口 +- **QueryService**: 查询服务接口 +- **VoableService**: 实体与视图对象转换服务接口 -## 3. 命名规范 +## 注解使用规范 -- **类名**:采用驼峰命名法,首字母大写,以 `Service` 结尾,表示这是一个服务类。 - **示例**:`CustomerCatalogService`、`EmployeeService` +### 类级别注解 +```java +@Lazy // 延迟加载,避免循环依赖 +@Service // Spring服务组件 +@CacheConfig(cacheNames = "业务缓存名称") // 缓存配置 +public class CompanyService extends EntityService + implements IEntityService, QueryService, VoableService { + // 实现代码 +} +``` -## 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 +// 修改方法缓存清理 - 清理所有相关缓存 +@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 findAll(Specification spec, Pageable pageable)`:根据条件和分页参数查询实体列表 -- `Specification getSpecification(String searchText)`:构建搜索条件 -- `void delete(T entity)`:删除实体 -- `T save(T entity)`:保存实体 +### Repository注入 +```java +@Lazy +@Autowired +private CompanyRepository repository; +``` -### 4.2 QueryService +### Service间依赖注入 +```java +@Lazy +@Autowired +private ContractService contractService; -提供 VO 对象的查询能力。泛型 `Vo` 表示视图对象类型。 +@Lazy +@Autowired +private VendorService vendorService; -**主要方法:** -- `Vo findById(Integer id)`:根据 ID 查询 VO 对象 -- `Page findAll(JsonNode paramsNode, Pageable pageable)`:根据 JSON 查询参数和分页条件查询 VO 列表 -- `default long count(JsonNode paramsNode)`:根据查询参数统计数据总数 +@Lazy +@Autowired +private CompanyContactService companyContactService; +``` -### 4.3 VoableService +### 外部服务依赖注入 +```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 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 = "customer-catalog") -public class CustomerCatalogService implements IEntityService, QueryService, - VoableService { - // 实现方法... +@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; + } } ``` -## 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, Pageable)`:直接调用 Repository 的 `findAll` 方法,返回实体分页对象 - ```java - @Override - public Page findAll(Specification 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 findAll(JsonNode paramsNode, Pageable pageable) { - Specification 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 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 - implements IEntityService, QueryService, VoableService { - // 实现方法... -} - -// 错误:未使用缓存注解 -@Service -public class ExampleService implements IEntityService, QueryService, - VoableService { - // 未使用@Cacheable、@CacheEvict等缓存注解 +@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; + } } ``` -## 13. 总结 +## 文件管理Service实现模式 -本规则文档定义了服务器端 Service 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。 \ No newline at end of file +### 文件路径管理 +```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层的代码质量、性能和可维护性,为整个系统的稳定运行提供了坚实基础。 \ No newline at end of file diff --git a/.trae/rules/server_task_rules.md b/.trae/rules/server_task_rules.md index f42838f..4610cc0 100644 --- a/.trae/rules/server_task_rules.md +++ b/.trae/rules/server_task_rules.md @@ -118,7 +118,15 @@ WebSocketServerTaskManager类在启动时会读取`tasker_mapper.json`文件, @Override public void init(JsonNode argsNode) { // 解析参数或初始化任务状态 - // ContractRepairAllTasker 不需要参数,所以此方法为空实现 + // 如果 Client 没有传递参数,就不做处理 + // do nothing + + // 如果有参数,正确做法:检查参数有效性并安全解析 + if (argsNode != null && argsNode.size() > 0) { + ContractService contractService = getCachedBean(ContractService.class); + int contractId = argsNode.get(0).asInt(); + this.contract = contractService.findById(contractId); + } } ``` diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..cff0960 --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,546 @@ +# Contract-Manager API 接口文档 + +## 📖 概览 + +Contract-Manager 系统提供了完整的 RESTful API 接口,用于合同管理系统的各项业务操作。本文档详细描述了所有可用的 API 接口、请求参数、响应格式和错误处理。 + +### API 基础信息 +- **基础URL**: `http://localhost:8080` +- **协议**: HTTP/HTTPS +- **数据格式**: JSON +- **认证方式**: Spring Security Session + JWT +- **字符编码**: UTF-8 + +### 通用响应格式 +```json +{ + "success": true|false, + "data": {...}, + "message": "提示信息", + "error": "错误信息" +} +``` + +### 状态码说明 +- `200`: 成功 +- `400`: 请求参数错误 +- `401`: 未认证 +- `403`: 权限不足 +- `404`: 资源不存在 +- `500`: 服务器内部错误 + +--- + +## 🔐 认证接口 + +### 用户登录 - POST /api/login + +用户登录接口,支持用户名密码登录和客户端认证两种方式。 + +**请求参数**: +```json +{ + "type": "client|web", // 登录类型:client=客户端认证,web=用户名密码登录 + "username": "用户名", // 用户名 + "password": "密码", // 密码(web模式需要) + "sign": { // 客户端认证信息(client模式需要) + "MAC地址": "IP地址" + } +} +``` + +**响应数据**: +```json +{ + "success": true, + "employeeId": 1, + "sessionId": "session_id", + "username": "admin", + "roles": ["ROLE_ADMIN"], + "message": "登录成功" +} +``` + +**错误响应**: +```json +{ + "success": false, + "error": "用户名或密码错误" +} +``` + +--- + +## 👥 用户管理接口 + +### 员工信息 - GET /employee/findById + +根据ID获取员工信息。 + +**请求参数**: +- `id` (Integer): 员工ID + +**响应数据**: +```json +{ + "success": true, + "data": { + "id": 1, + "name": "张三", + "account": "admin", + "email": "admin@example.com" + } +} +``` + +### 员工列表 - GET /employee/list + +分页获取员工列表。 + +**请求参数**: +- `page` (Integer, 默认0): 页码 +- `size` (Integer, 默认10): 每页大小 +- `searchText` (String, 可选): 搜索关键词 + +**响应数据**: +```json +{ + "content": [ + { + "id": 1, + "name": "张三", + "account": "admin" + } + ], + "totalElements": 10, + "totalPages": 1, + "size": 10, + "number": 0 +} +``` + +### 保存员工信息 - POST /employee/save + +保存或更新员工信息。 + +**请求参数**: +```json +{ + "id": 1, + "name": "张三", + "account": "admin", + "email": "admin@example.com" +} +``` + +### 删除员工 - GET /employee/delete + +删除指定ID的员工。 + +**请求参数**: +- `id` (Integer): 员工ID + +### 获取当前用户信息 - GET /employee/currentUser + +获取当前登录用户的信息。 + +**响应数据**: +```json +{ + "success": true, + "employeeId": 1, + "sessionId": "session_id" +} +``` + +--- + +## 🏢 公司管理接口 + +### 公司信息 - GET /company/findById + +根据ID获取公司信息。 + +**请求参数**: +- `id` (Integer): 公司ID + +**响应数据**: +```json +{ + "success": true, + "data": { + "id": 1, + "name": "示例公司", + "address": "北京市朝阳区", + "phone": "010-12345678" + } +} +``` + +### 公司列表 - GET /company/list + +分页获取公司列表。 + +**请求参数**: +- `page` (Integer, 默认0): 页码 +- `size` (Integer, 默认10): 每页大小 + +**响应数据**: +```json +{ + "content": [ + { + "id": 1, + "name": "示例公司", + "address": "北京市朝阳区" + } + ], + "totalElements": 10, + "totalPages": 1, + "size": 10, + "number": 0 +} +``` + +### 保存公司信息 - GET /company/save + +保存或更新公司信息。 + +**请求参数**: +```json +{ + "id": 1, + "name": "示例公司", + "address": "北京市朝阳区", + "phone": "010-12345678" +} +``` + +### 删除公司 - GET /company/delete + +删除指定ID的公司。 + +**请求参数**: +- `id` (Integer): 公司ID + +--- + +## 🏦 银行管理接口 + +### 银行信息 - GET /bank/findById + +根据ID获取银行信息。 + +**请求参数**: +- `id` (Integer): 银行ID + +### 银行列表 - GET /bank/list + +分页获取银行列表。 + +**请求参数**: +- `page` (Integer, 默认0): 页码 +- `size` (Integer, 默认10): 每页大小 + +### 保存银行信息 - POST /bank/save + +保存或更新银行信息。 + +**请求参数**: +```json +{ + "id": 1, + "name": "中国银行", + "code": "BOC" +} +``` + +### 删除银行 - GET /bank/delete + +删除指定ID的银行。 + +**请求参数**: +- `id` (Integer): 银行ID + +--- + +## 🔑 角色管理接口 + +### 角色信息 - GET /employee/role/findById + +根据ID获取角色信息。 + +**请求参数**: +- `id` (Integer): 角色ID + +### 角色列表 - GET /employee/role/list + +分页获取角色列表,非系统管理员无法查看系统管理员角色。 + +**请求参数**: +- `page` (Integer, 默认0): 页码 +- `size` (Integer, 默认10): 每页大小 +- `searchText` (String, 可选): 搜索关键词 + +### 保存角色信息 - GET /employee/role/save + +保存角色信息,**仅系统管理员可操作**。 + +### 删除角色 - GET /employee/role/delete + +删除指定ID的角色,**仅系统管理员可操作**。 + +**请求参数**: +- `id` (Integer): 角色ID + +**注意**: 不能删除系统管理员角色。 + +### 获取角色权限 - GET /employee/role/getFunctionsByRoleId + +根据角色ID获取该角色的权限功能列表。 + +**请求参数**: +- `roleId` (Integer): 角色ID + +--- + +## ☁️ 云服务接口 + +### 天眼查服务 - /cloudTyc + +天眼查第三方数据服务接口。 + +#### 获取天眼查信息 - GET /cloudTyc/findById +#### 天眼查列表 - GET /cloudTyc/list +#### 保存天眼查信息 - GET /cloudTyc/save +#### 删除天眼查信息 - GET /cloudTyc/delete + +### 企查查服务 - /cloudRk + +企查查第三方数据服务接口。 + +#### 获取企查查信息 - GET /cloudRk/findById +#### 企查查列表 - GET /cloudRk/list +#### 保存企查查信息 - GET /cloudRk/save +#### 删除企查查信息 - GET /cloudRk/delete + +### 用友云服务 - /cloudYu + +用友云第三方数据服务接口。 + +#### 获取用友云信息 - GET /cloudYu/findById +#### 用友云列表 - GET /cloudYu/list +#### 保存用友云信息 - GET /cloudYu/save +#### 删除用友云信息 - GET /cloudYu/delete + +--- + +## 📊 其他接口 + +### 系统首页 - GET /index + +获取系统首页信息。 + +**响应数据**: +```json +{ + "success": true, + "data": { + "systemInfo": "Contract Manager System", + "version": "1.0.0" + } +} +``` + +### WebSocket 连接 - GET /websocket + +建立WebSocket连接,用于实时通信。 + +**连接地址**: `ws://localhost:8080/websocket` + +--- + +## 🔒 权限说明 + +### 角色权限 +- **ROLE_ADMIN**: 系统管理员,拥有所有权限 +- **普通用户**: 只能查看和操作非系统管理员级别的数据 + +### 权限控制 +- 删除角色操作仅限系统管理员 +- 系统管理员角色不可删除 +- 非系统管理员无法查看系统管理员角色信息 + +--- + +## 🚨 错误处理 + +### 常见错误码 + +#### 400 - 请求参数错误 +```json +{ + "success": false, + "error": "请求参数不正确" +} +``` + +#### 401 - 未认证 +```json +{ + "success": false, + "error": "请先登录" +} +``` + +#### 403 - 权限不足 +```json +{ + "success": false, + "error": "无权限执行此操作" +} +``` + +#### 404 - 资源不存在 +```json +{ + "success": false, + "error": "资源不存在" +} +``` + +### 认证错误 +- 客户端认证模式下,需要提供正确的MAC地址和IP地址映射 +- 用户名密码模式下,需要提供正确的用户名和密码 + +### 业务错误 +- 系统管理员角色不可删除 +- 用户未绑定认证信息无法登录 +- 认证信息错误登录失败 + +--- + +## 📝 使用示例 + +### JavaScript/Ajax 调用示例 + +```javascript +// 用户登录 +$.ajax({ + url: '/api/login', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ + type: 'web', + username: 'admin', + password: 'password123' + }), + success: function(response) { + if (response.success) { + console.log('登录成功', response); + // 保存sessionId等认证信息 + sessionStorage.setItem('sessionId', response.sessionId); + } + } +}); + +// 获取公司列表 +$.ajax({ + url: '/company/list', + type: 'GET', + data: { + page: 0, + size: 10 + }, + success: function(response) { + console.log('公司列表', response); + } +}); + +// 保存公司信息 +$.ajax({ + url: '/company/save', + type: 'GET', + data: { + id: 1, + name: '新公司名称', + address: '新地址' + }, + success: function(response) { + console.log('保存成功', response); + } +}); +``` + +### curl 调用示例 + +```bash +# 用户登录 +curl -X POST http://localhost:8080/api/login \ + -H "Content-Type: application/json" \ + -d '{ + "type": "web", + "username": "admin", + "password": "password123" + }' + +# 获取公司列表 +curl "http://localhost:8080/company/list?page=0&size=10" + +# 获取员工信息 +curl "http://localhost:8080/employee/findById?id=1" +``` + +--- + +## 🔧 SDK 使用指南 + +### 添加依赖 +```xml + + com.ecep.contract + contract-client + 1.0.0 + +``` + +### 初始化客户端 +```java +ContractClient client = new ContractClient("http://localhost:8080"); +client.setSessionId(sessionId); // 设置认证session +``` + +### 调用API +```java +// 获取公司列表 +Page companies = client.company().list(0, 10); + +// 保存公司信息 +CompanyVo company = new CompanyVo(); +company.setName("新公司"); +CompanyVo saved = client.company().save(company); +``` + +--- + +## 📈 版本历史 + +| 版本 | 日期 | 变更说明 | +|------|------|----------| +| v1.0.0 | 2024-01-01 | 初始版本,包含基础CRUD操作 | +| v1.1.0 | 2024-02-01 | 新增角色权限管理接口 | +| v1.2.0 | 2024-03-01 | 新增云服务集成接口 | + +--- + +## 📞 技术支持 + +如有技术问题,请联系: +- **技术支持邮箱**: support@contractmanager.com +- **开发团队**: Contract Manager Development Team +- **文档版本**: v1.2.0 +- **最后更新**: 2024-03-01 + +--- + +*本文档详细描述了 Contract-Manager 系统的所有 API 接口。请在使用前仔细阅读相关说明,确保正确调用接口。* \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..7cc1a0e --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,265 @@ +# Contract-Manager 技术架构文档 + +## 📋 项目概述 + +Contract-Manager 是一个基于 Spring Boot 后端 + JavaFX 客户端的企业合同管理系统,采用模块化设计,支持多业务域的企业级应用开发。 + +## 🏗️ 技术栈 + +### 服务端 (Server Module) +- **基础框架**: Spring Boot 3.3.7 +- **Java 版本**: Java 21 +- **数据库访问**: Spring Data JPA 3.3.7 +- **数据库**: MySQL 8.0.33 +- **缓存**: Redis +- **构建工具**: Maven 3.x +- **开发工具**: Lombok 1.18.32 +- **文档处理**: Apache POI 5.2.5, PDFBox 3.0.1 +- **云服务集成**: 支持第三方云服务API集成 + +### 客户端 (Client Module) +- **UI框架**: JavaFX 21 +- **Java 版本**: Java 21 +- **UI组件库**: ControlsFX 11.1.2 +- **开发工具**: Lombok 1.18.32 +- **缓存**: Caffeine 3.1.8 +- **通信**: WebSocket 与服务端通信 +- **界面**: FXML 格式界面文件 + +### 公共模块 (Common Module) +- **Java 版本**: Java 21 +- **开发工具**: Lombok 1.18.32 +- **共享内容**: 常量定义、实体模型、视图对象、通用工具类 + +## 🏛️ 架构设计 + +### 整体架构图 +```mermaid +graph TB + subgraph "客户端层 (Client Layer)" + A[JavaFX UI] --> B[FXML 界面] + A --> C[Controller 控制器] + A --> D[ViewModel 视图模型] + A --> E[Service 层] + end + + subgraph "业务层 (Business Layer)" + C --> F[Controller API] + E --> G[Service 业务逻辑] + G --> H[Repository 数据访问] + end + + subgraph "数据层 (Data Layer)" + H --> I[JPA Repository] + I --> J[MySQL 数据库] + end + + subgraph "缓存层 (Cache Layer)" + G --> K[Redis 缓存] + E --> L[Caffeine 缓存] + end + + subgraph "公共层 (Common Layer)" + M[Entity 实体模型] + N[VO 视图对象] + O[Constants 常量] + P[Utils 工具类] + end + + H --> M + G --> N + M --> O + G --> P + + subgraph "外部服务 (External Services)" + Q[云服务 API] + R[第三方集成] + end + + G --> Q + G --> R +``` + +### 模块架构说明 + +#### 1. 客户端模块 (client/) +``` +src/main/java/com/ecep/contract/ +├── controller/ # JavaFX 控制器 +│ ├── CompanyController.java +│ ├── ContractController.java +│ └── ... +├── service/ # 客户端服务层 +│ ├── CompanyService.java +│ └── ... +├── task/ # 任务处理类 +├── vm/ # 视图模型 (ViewModel) +├── converter/ # 类型转换器 +├── serializer/ # 序列化类 +└── util/ # 工具类 +``` + +#### 2. 服务端模块 (server/) +``` +src/main/java/com/ecep/contract/ +├── api/ # API 接口定义 +├── config/ # Spring 配置类 +├── controller/ # Web 控制器 +├── ds/ # 数据访问层 (按业务域组织) +│ ├── company/ # 公司相关业务 +│ │ ├── model/ # 实体类 +│ │ ├── repository/ # 数据访问接口 +│ │ ├── service/ # 业务逻辑服务 +│ │ ├── tasker/ # 任务处理器 +│ │ └── controller/ # 控制器 +│ ├── contract/ # 合同相关业务 +│ ├── customer/ # 客户相关业务 +│ ├── project/ # 项目相关业务 +│ └── vendor/ # 供应商相关业务 +├── service/ # 通用服务和任务处理器 +├── handler/ # WebSocket 处理器 +├── ui/ # UI 相关组件 +└── util/ # 工具类 +``` + +#### 3. 公共模块 (common/) +``` +src/main/java/ecep/contract/ +├── constant/ # 常量类 (按业务域组织) +├── model/ # 实体类 (按业务域组织) +├── vo/ # 视图对象 (按业务域组织) +└── util/ # 工具类 +``` + +## 🎯 核心设计模式 + +### 1. 分层架构模式 +- **表示层**: JavaFX UI + FXML +- **业务逻辑层**: Service + Task + Controller +- **数据访问层**: Repository + Entity +- **基础设施层**: 配置、缓存、工具类 + +### 2. 领域驱动设计 (DDD) +项目采用领域驱动设计,按业务域组织代码: +- **Company (公司域)**: 公司信息、联系人、文件管理 +- **Contract (合同域)**: 合同管理、目录分类、文件处理 +- **Customer (客户域)**: 客户关系、分类管理 +- **Project (项目域)**: 项目管理、文件跟踪 +- **Vendor (供应商域)**: 供应商管理、评价体系 + +### 3. 接口分离原则 +服务端 Service 实现三个核心接口: +```java +public interface IEntityService { + T save(T entity); + void delete(T entity); + T getById(Integer id); + Page findAll(Specification spec, Pageable pageable); +} + +public interface QueryService { + Vo findById(Integer id); + Page findAll(JsonNode paramsNode, Pageable pageable); +} + +public interface VoableService { + void updateByVo(M model, Vo vo); +} +``` + +### 4. 缓存策略模式 +- **多级缓存**: Caffeine (客户端) + Redis (服务端) +- **缓存键设计**: 按查询类型设计唯一键 +- **缓存清理**: 数据修改时清理相关缓存 + +## 🔗 模块间通信 + +### 1. 客户端-服务端通信 +- **协议**: HTTP REST API + WebSocket +- **数据格式**: JSON +- **序列化**: 统一使用 VO 对象进行数据传输 + +### 2. 服务端内部通信 +- **依赖注入**: Spring IoC 容器管理 +- **服务调用**: 延迟加载 (@Lazy) 避免循环依赖 +- **事务管理**: @Transactional 确保数据一致性 + +### 3. 数据流转 +``` +Entity (数据库) ↔ Repository ↔ Service ↔ VO ↔ JSON ↔ Client +``` + +## 🛡️ 安全与性能 + +### 1. 安全设计 +- **API 认证**: 基于 JWT 的身份认证机制 +- **权限控制**: 基于角色的访问控制 (RBAC) +- **数据验证**: 输入参数校验和 SQL 注入防护 + +### 2. 性能优化 +- **延迟加载**: @Lazy 避免循环依赖 +- **缓存策略**: 多级缓存减少数据库访问 +- **分页查询**: 大数据集分页处理 +- **批量操作**: 批量保存和更新 + +### 3. 监控与日志 +- **应用监控**: Spring Boot Actuator +- **日志管理**: SLF4J + Logback +- **性能监控**: 缓存命中率、响应时间 + +## 📦 依赖管理 + +### Maven 模块结构 +``` +parent +├── client # 客户端模块 +├── common # 公共模块 +└── server # 服务端模块 +``` + +### 关键依赖说明 +- **Spring Boot Starter**: 快速集成 Spring 生态 +- **Spring Data JPA**: 简化数据访问层开发 +- **Lombok**: 减少样板代码 +- **Caffeine**: 高性能本地缓存 +- **JavaFX**: 现代化桌面应用 UI + +## 🚀 开发规范 + +### 1. 命名规范 +- **类名**: 驼峰命名法,以业务含义命名 +- **接口名**: 以 I 开头 + 业务描述 +- **控制器**: 以 Controller 结尾 +- **服务类**: 以 Service 结尾 +- **仓储接口**: 以 Repository 结尾 + +### 2. 编码规范 +- **注解使用**: 合理使用 Spring、Lombok 等注解 +- **异常处理**: 统一异常处理机制 +- **单元测试**: 核心业务逻辑必须有测试覆盖 +- **代码注释**: 关键逻辑和复杂业务需要 JavaDoc + +### 3. 配置管理 +- **环境配置**: 多环境配置 (dev, test, prod) +- **敏感信息**: 使用 .env 文件管理 API 密钥等 +- **配置分离**: 业务配置与框架配置分离 + +## 📊 扩展性设计 + +### 1. 模块化设计 +- **业务域分离**: 不同业务域独立开发和部署 +- **接口标准化**: 统一的 Service 接口设计 +- **组件复用**: 基础组件可在多个业务域复用 + +### 2. 水平扩展 +- **无状态设计**: Service 层无状态设计支持集群部署 +- **缓存分离**: Redis 支持分布式缓存 +- **数据库分离**: 支持读写分离和分库分表 + +### 3. 垂直扩展 +- **微服务拆分**: 按业务域可拆分为微服务 +- **插件化**: 支持新业务域的快速集成 + +--- + +*本文档反映了 Contract-Manager 项目的整体技术架构和设计理念,为项目开发、部署和维护提供指导。* \ No newline at end of file diff --git a/DATABASE_DESIGN.md b/DATABASE_DESIGN.md new file mode 100644 index 0000000..1ded785 --- /dev/null +++ b/DATABASE_DESIGN.md @@ -0,0 +1,646 @@ +# Contract-Manager 数据库设计文档 + +## 📊 概览 + +Contract-Manager 系统采用 MySQL 8.0+ 作为主数据库,设计遵循第三范式(3NF),支持高并发访问和数据一致性。本文档详细描述了数据库设计架构、表结构、关系设计和维护策略。 + +### 数据库基本信息 +- **数据库类型**: MySQL 8.0+ +- **数据库名称**: supplier_ms +- **字符集**: utf8mb4 +- **排序规则**: utf8mb4_unicode_ci +- **存储引擎**: InnoDB(支持事务和外键约束) + +--- + +## 🏗️ 数据库架构设计 + +### 核心业务域 + +#### 1. 用户权限管理域 +- **员工管理**: EMPLOYEE, EMPLOYEE_ROLE, EMPLOYEE_AUTH_BIND +- **角色管理**: EMPLOYEE_ROLE, FUNCTION +- **登录历史**: EMPLOYEE_LOGIN_HISTORY +- **权限功能**: FUNCTION + +#### 2. 企业管理域 +- **公司信息**: COMPANY, COMPANY_FILE_TYPE_LOCAL +- **银行信息**: BANK +- **供应商管理**: COMPANY_VENDOR_ENTITY, VENDOR_TYPE_LOCAL +- **客户管理**: CUSTOMER, CUSTOMER_FILE_TYPE_LOCAL + +#### 3. 合同管理域 +- **合同基础**: CONTRACT, CONTRACT_FILE_TYPE_LOCAL +- **合同发票**: CONTRACT_INVOICE +- **销售订单**: CONTRACT_SALES_ORDER +- **合同余额**: CONTRACT_BALANCE + +#### 4. 项目管理域 +- **项目信息**: PROJECT, PROJECT_FILE_TYPE_LOCAL +- **项目资金计划**: PROJECT_FUND_PLAN_TABLE +- **库存管理**: INVENTORY + +#### 5. 基础数据域 +- **单位管理**: UNIT +- **云服务数据**: CLOUD_TYC, CLOUD_RK, CLOUD_YU + +--- + +## 📋 核心表结构设计 + +### 1. 用户权限相关表 + +#### EMPLOYEE (员工表) +```sql +CREATE TABLE EMPLOYEE ( + ID INT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(255) NOT NULL COMMENT '员工姓名', + ACCOUNT VARCHAR(255) UNIQUE NOT NULL COMMENT '登录账号', + PASSWORD VARCHAR(255) NOT NULL COMMENT '密码哈希', + EMAIL VARCHAR(255) COMMENT '邮箱', + PHONE VARCHAR(50) COMMENT '电话', + IS_ACTIVE BOOLEAN DEFAULT TRUE COMMENT '是否激活', + CREATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP, + UPDATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT uq_employee_account UNIQUE KEY (ACCOUNT) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +#### EMPLOYEE_ROLE (员工角色表) +```sql +CREATE TABLE EMPLOYEE_ROLE ( + ID INT AUTO_INCREMENT PRIMARY KEY, + ROLE_NAME VARCHAR(255) NOT NULL COMMENT '角色名称', + DESCRIPTION TEXT COMMENT '角色描述', + SYSTEM_ADMINISTRATOR BOOLEAN DEFAULT FALSE COMMENT '是否系统管理员', + IS_ACTIVE BOOLEAN DEFAULT TRUE COMMENT '是否激活', + CREATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP, + UPDATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +#### EMPLOYEE_AUTH_BIND (员工认证绑定表) +```sql +CREATE TABLE EMPLOYEE_AUTH_BIND ( + ID INT AUTO_INCREMENT PRIMARY KEY, + EMPLOYEE_ID INT NOT NULL COMMENT '员工ID', + MAC VARCHAR(255) NOT NULL COMMENT 'MAC地址', + IP VARCHAR(255) NOT NULL COMMENT 'IP地址', + BIND_TIME DATETIME DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT fk_employee_auth_bind_employee FOREIGN KEY (EMPLOYEE_ID) REFERENCES EMPLOYEE(ID) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### 2. 企业管理相关表 + +#### COMPANY (公司表) +```sql +CREATE TABLE COMPANY ( + ID INT AUTO_INCREMENT PRIMARY KEY, + NAME VARCHAR(255) NOT NULL COMMENT '公司名称', + ADDRESS TEXT COMMENT '地址', + PHONE VARCHAR(50) COMMENT '电话', + EMAIL VARCHAR(255) COMMENT '邮箱', + LEGAL_PERSON VARCHAR(255) COMMENT '法人代表', + BUSINESS_LICENSE VARCHAR(255) COMMENT '营业执照号', + IS_VENDOR BOOLEAN DEFAULT FALSE COMMENT '是否供应商', + IS_CUSTOMER BOOLEAN DEFAULT FALSE COMMENT '是否客户', + CREATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP, + UPDATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +#### VENDOR_TYPE_LOCAL (供应商类型本地化表) +```sql +CREATE TABLE VENDOR_TYPE_LOCAL ( + ID INT AUTO_INCREMENT PRIMARY KEY, + TYPE VARCHAR(255) NOT NULL COMMENT '枚举类型', + LANG VARCHAR(255) NOT NULL COMMENT '语言', + VALUE VARCHAR(255) NOT NULL COMMENT '本地化值', + CONSTRAINT pk_vendor_type_local PRIMARY KEY (ID), + CONSTRAINT uq_vendor_type_local UNIQUE KEY (TYPE, LANG) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### 3. 合同管理相关表 + +#### CONTRACT (合同表) +```sql +CREATE TABLE CONTRACT ( + ID INT AUTO_INCREMENT PRIMARY KEY, + CODE VARCHAR(100) UNIQUE NOT NULL COMMENT '合同编号', + NAME VARCHAR(500) NOT NULL COMMENT '合同名称', + CUSTOMER_ID INT COMMENT '客户ID', + VENDOR_ID INT COMMENT '供应商ID', + PROJECT_ID INT COMMENT '项目ID', + SIGN_DATE DATE COMMENT '签订日期', + START_DATE DATE COMMENT '开始日期', + END_DATE DATE COMMENT '结束日期', + AMOUNT DECIMAL(15,2) COMMENT '合同金额', + STATUS VARCHAR(50) DEFAULT 'ACTIVE' COMMENT '合同状态', + CREATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP, + UPDATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_contract_customer FOREIGN KEY (CUSTOMER_ID) REFERENCES COMPANY(ID), + CONSTRAINT fk_contract_vendor FOREIGN KEY (VENDOR_ID) REFERENCES COMPANY(ID), + CONSTRAINT fk_contract_project FOREIGN KEY (PROJECT_ID) REFERENCES PROJECT(ID) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +#### CONTRACT_INVOICE (合同发票关联表) +```sql +CREATE TABLE CONTRACT_INVOICE ( + ID INT AUTO_INCREMENT PRIMARY KEY, + CODE VARCHAR(50) COMMENT '发票编号', + NAME VARCHAR(200) COMMENT '发票名称', + CONTRACT_ID INT NOT NULL COMMENT '合同ID', + INVOICE_ID INT COMMENT '发票ID', + AMOUNT DECIMAL(15,2) COMMENT '发票金额', + SETUP_PERSON_ID INT COMMENT '创建人ID', + SETUP_DATE DATE COMMENT '创建日期', + UPDATE_PERSON_ID INT COMMENT '更新人ID', + UPDATE_DATE DATE COMMENT '更新日期', + REMARK VARCHAR(500) COMMENT '备注', + CONSTRAINT fk_contract_invoice_contract FOREIGN KEY (CONTRACT_ID) REFERENCES CONTRACT(ID) ON DELETE CASCADE, + CONSTRAINT fk_contract_invoice_invoice FOREIGN KEY (INVOICE_ID) REFERENCES INVOICE(ID) ON DELETE SET NULL, + CONSTRAINT fk_contract_invoice_setup_person FOREIGN KEY (SETUP_PERSON_ID) REFERENCES EMPLOYEE(ID) ON DELETE SET NULL, + CONSTRAINT fk_contract_invoice_update_person FOREIGN KEY (UPDATE_PERSON_ID) REFERENCES EMPLOYEE(ID) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### 4. 文件类型本地化表 + +#### COMPANY_FILE_TYPE_LOCAL (公司文件类型本地化表) +```sql +CREATE TABLE COMPANY_FILE_TYPE_LOCAL ( + ID INT AUTO_INCREMENT PRIMARY KEY, + TYPE VARCHAR(255) NOT NULL COMMENT '枚举类型', + LANG VARCHAR(255) NOT NULL COMMENT '语言', + VALUE VARCHAR(255) NOT NULL COMMENT '本地化值', + CONSTRAINT pk_company_file_type_local PRIMARY KEY (ID), + CONSTRAINT uq_company_file_type_local UNIQUE KEY (TYPE, LANG) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +#### CONTRACT_FILE_TYPE_LOCAL (合同文件类型本地化表) +```sql +CREATE TABLE CONTRACT_FILE_TYPE_LOCAL ( + ID INT AUTO_INCREMENT PRIMARY KEY, + TYPE VARCHAR(255) NOT NULL COMMENT '枚举类型', + LANG VARCHAR(255) NOT NULL COMMENT '语言', + VALUE VARCHAR(255) NOT NULL COMMENT '本地化值', + SUGGEST_FILE_NAME VARCHAR(255) COMMENT '建议的文件名', + DESCRIPTION VARCHAR(255) COMMENT '描述', + CONSTRAINT pk_contract_file_type_local PRIMARY KEY (ID), + CONSTRAINT uq_contract_file_type_local UNIQUE KEY (TYPE, LANG) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +--- + +## 🔗 数据库关系设计 + +### 1. 核心实体关系图 + +```mermaid +erDiagram + EMPLOYEE ||--o{ EMPLOYEE_AUTH_BIND : has + EMPLOYEE ||--o{ EMPLOYEE_LOGIN_HISTORY : creates + EMPLOYEE }o--|| EMPLOYEE_ROLE : belongs + + COMPANY ||--o{ CONTRACT : creates + COMPANY ||--o{ PROJECT : has + COMPANY ||--o{ COMPANY_VENDOR_ENTITY : is_vendor + COMPANY ||--o{ COMPANY_VENDOR_ENTITY : is_customer + + CONTRACT ||--o{ CONTRACT_INVOICE : contains + CONTRACT ||--o{ CONTRACT_SALES_ORDER : has + CONTRACT ||--o{ CONTRACT_BALANCE : has + + PROJECT ||--o{ PROJECT_FUND_PLAN_TABLE : has + PROJECT ||--o{ INVENTORY : manages + + VENDOR_TYPE_LOCAL }o--|| COMPANY_VENDOR_ENTITY : types + FILE_TYPE_LOCAL }o--o| COMPANY : documents + FILE_TYPE_LOCAL }o--o| CONTRACT : documents +``` + +### 2. 外键约束设计 + +#### 核心外键关系 +```sql +-- 员工与角色关联 +ALTER TABLE EMPLOYEE +ADD CONSTRAINT fk_employee_role +FOREIGN KEY (ROLE_ID) REFERENCES EMPLOYEE_ROLE(ID); + +-- 合同与客户供应商关联 +ALTER TABLE CONTRACT +ADD CONSTRAINT fk_contract_customer +FOREIGN KEY (CUSTOMER_ID) REFERENCES COMPANY(ID); + +-- 合同发票关联 +ALTER TABLE CONTRACT_INVOICE +ADD CONSTRAINT fk_contract_invoice_contract +FOREIGN KEY (CONTRACT_ID) REFERENCES CONTRACT(ID) ON DELETE CASCADE; +``` + +### 3. 索引设计策略 + +#### 主要索引 +```sql +-- 单列索引 +CREATE INDEX idx_employee_account ON EMPLOYEE(ACCOUNT); +CREATE INDEX idx_contract_code ON CONTRACT(CODE); +CREATE INDEX idx_company_name ON COMPANY(NAME); + +-- 复合索引 +CREATE INDEX idx_contract_customer_status ON CONTRACT(CUSTOMER_ID, STATUS); +CREATE INDEX idx_invoice_date_amount ON INVOICE(INVOICE_DATE, AMOUNT); + +-- 外键索引 +CREATE INDEX idx_contract_invoice_contract_id ON CONTRACT_INVOICE(CONTRACT_ID); +CREATE INDEX idx_employee_auth_bind_employee ON EMPLOYEE_AUTH_BIND(EMPLOYEE_ID); +``` + +--- + +## 🎯 本地化设计 + +### 1. 多语言支持 + +#### 本地化表设计原则 +- **统一结构**: 所有本地化表使用相同的结构 +- **语言键**: 使用LANG字段标识语言(zh_CN, en_US) +- **类型分类**: 使用TYPE字段进行分类管理 +- **唯一约束**: (TYPE, LANG)组合唯一 + +#### 本地化表示例 +```sql +-- 合同文件类型本地化 +TYPE: 'CONTRACT_CERTIFICATE', LANG: 'zh_CN', VALUE: '资质证书' +TYPE: 'CONTRACT_CERTIFICATE', LANG: 'en_US', VALUE: 'Certificate' + +-- 供应商类型本地化 +TYPE: 'VENDOR_PRIMARY', LANG: 'zh_CN', VALUE: '主要供应商' +TYPE: 'VENDOR_PRIMARY', LANG: 'en_US', VALUE: 'Primary Vendor' +``` + +### 2. 数据维护策略 + +#### 本地化数据初始化 +```sql +-- 初始化供应商类型本地化数据 +INSERT INTO VENDOR_TYPE_LOCAL (TYPE, LANG, VALUE) VALUES +('VENDOR_PRIMARY', 'zh_CN', '主要供应商'), +('VENDOR_PRIMARY', 'en_US', 'Primary Vendor'), +('VENDOR_SECONDARY', 'zh_CN', '次要供应商'), +('VENDOR_SECONDARY', 'en_US', 'Secondary Vendor'); + +-- 初始化合同文件类型本地化数据 +INSERT INTO CONTRACT_FILE_TYPE_LOCAL (TYPE, LANG, VALUE, SUGGEST_FILE_NAME, DESCRIPTION) VALUES +('CONTRACT_MAIN', 'zh_CN', '主合同', 'main_contract.pdf', '主要合同文件'), +('CONTRACT_MAIN', 'en_US', 'Main Contract', 'main_contract.pdf', 'Main contract document'), +('CONTRACT_CERTIFICATE', 'zh_CN', '资质证书', 'certificate.pdf', '相关资质证书'), +('CONTRACT_CERTIFICATE', 'en_US', 'Certificate', 'certificate.pdf', 'Related certificates'); +``` + +--- + +## 🔧 数据库脚本管理 + +### 1. 脚本文件组织 + +``` +docs/db/ +├── structs.sql # 数据库结构脚本 +├── initial_data.sql # 初始数据脚本 +├── CompanyFileTypeLocal.sql # 公司文件类型本地化 +├── ContractFileTypeLocal.sql # 合同文件类型本地化 +├── CustomerFileTypeLocal.sql # 客户文件类型本地化 +├── ProjectFileTypeLocal.sql # 项目文件类型本地化 +├── VendorFileTypeLocal.sql # 供应商文件类型本地化 +├── VendorTypeLocal.sql # 供应商类型本地化 +├── Unit.sql # 单位基础数据 +├── CompanyVendor.sql # 公司供应商关联表 +├── Contract_INVOICE.sql # 合同发票关联表 +├── Contract_SALES_ORDER.sql # 合同销售订单表 +├── Contract_BALANCE.sql # 合同余额表 +├── Inverntory.sql # 库存表 +├── project_fund_plan_table.sql # 项目资金计划表 +├── add_function_columns.sql # 功能扩展列脚本 +├── temp.sql # 临时脚本 +└── temp_u8.sql # 临时U8脚本 +``` + +### 2. 脚本执行顺序 + +#### 环境初始化脚本执行顺序 +```bash +# 1. 创建数据库和基础表结构 +mysql -u root -p < structs.sql + +# 2. 初始化基础数据 +mysql -u root -p < initial_data.sql + +# 3. 初始化本地化数据 +mysql -u root -p < CompanyFileTypeLocal.sql +mysql -u root -p < ContractFileTypeLocal.sql +mysql -u root -p < CustomerFileTypeLocal.sql +mysql -u root -p < ProjectFileTypeLocal.sql +mysql -u root -p < VendorFileTypeLocal.sql +mysql -u root -p < VendorTypeLocal.sql + +# 4. 初始化基础字典数据 +mysql -u root -p < Unit.sql + +# 5. 业务表数据 +mysql -u root -p < Contract_INVOICE.sql +mysql -u root -p < Contract_SALES_ORDER.sql +mysql -u root -p < Contract_BALANCE.sql +mysql -u root -p < Inverntory.sql +mysql -u root -p < project_fund_plan_table.sql + +# 6. 数据关联和约束 +mysql -u root -p < CompanyVendor.sql + +# 7. 功能扩展(如需要) +mysql -u root -p < add_function_columns.sql +``` + +### 3. 版本控制策略 + +#### 数据库版本管理 +- **结构版本**: 通过版本号管理数据库结构变更 +- **数据迁移**: 使用迁移脚本管理数据变更 +- **回滚策略**: 保持完整的回滚脚本 + +#### 迁移脚本模板 +```sql +-- 版本: v1.0.1 +-- 描述: 添加员工激活状态字段 +-- 日期: 2024-01-15 + +-- 前置检查 +SELECT 'Starting migration v1.0.1' as status; + +-- 添加字段 +ALTER TABLE EMPLOYEE +ADD COLUMN IS_ACTIVE BOOLEAN DEFAULT TRUE COMMENT '是否激活'; + +-- 更新现有数据 +UPDATE EMPLOYEE SET IS_ACTIVE = TRUE WHERE IS_ACTIVE IS NULL; + +-- 验证 +SELECT COUNT(*) as total_employees FROM EMPLOYEE; + +SELECT 'Migration v1.0.1 completed' as status; +``` + +--- + +## 📊 性能优化策略 + +### 1. 索引优化 + +#### 查询模式分析 +- **高频查询**: 员工登录(ACCOUNT字段) +- **分页查询**: 合同列表(STATUS, CREATE_TIME) +- **关联查询**: 合同客户信息(CUSTOMER_ID) +- **搜索查询**: 公司名称模糊搜索(NAME字段) + +#### 索引配置建议 +```sql +-- 高频查询索引 +CREATE INDEX idx_employee_account_active ON EMPLOYEE(ACCOUNT, IS_ACTIVE); +CREATE INDEX idx_contract_status_date ON CONTRACT(STATUS, CREATE_TIME DESC); + +-- 搜索优化索引 +CREATE INDEX idx_company_name_prefix ON COMPANY(NAME(20)); + +-- 关联查询索引 +CREATE INDEX idx_contract_customer_status ON CONTRACT(CUSTOMER_ID, STATUS); + +-- 统计查询索引 +CREATE INDEX idx_invoice_date_amount ON INVOICE(INVOICE_DATE, AMOUNT); +``` + +### 2. 分区策略 + +#### 时间分区设计 +```sql +-- 登录历史表时间分区(月度分区) +ALTER TABLE EMPLOYEE_LOGIN_HISTORY +PARTITION BY RANGE (YEAR(LOGIN_TIME)*100 + MONTH(LOGIN_TIME)) ( + PARTITION p202401 VALUES LESS THAN (202402), + PARTITION p202402 VALUES LESS THAN (202403), + PARTITION p202403 VALUES LESS THAN (202404), + -- ... 更多分区 + PARTITION p_max VALUES LESS THAN MAXVALUE +); +``` + +### 3. 缓存策略 + +#### Redis缓存配置 +```yaml +# 缓存配置 +spring: + cache: + type: redis + redis: + timeout: 2000ms + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: -1ms + +# 缓存策略 +cache: + # 员工信息缓存(5分钟) + employee: + timeout: 300 + # 公司信息缓存(10分钟) + company: + timeout: 600 + # 合同信息缓存(2分钟) + contract: + timeout: 120 +``` + +--- + +## 🔒 安全与权限 + +### 1. 数据权限控制 + +#### 行级安全 +```sql +-- 基于角色的数据访问控制 +CREATE VIEW contract_view AS +SELECT c.* FROM CONTRACT c +WHERE + CASE + WHEN EXISTS (SELECT 1 FROM EMPLOYEE e JOIN EMPLOYEE_ROLE er ON e.ROLE_ID = er.ID + WHERE e.ID = CURRENT_USER_ID() AND er.SYSTEM_ADMINISTRATOR = TRUE) + THEN TRUE + ELSE c.CREATOR_ID = CURRENT_USER_ID() + END; +``` + +#### 敏感字段加密 +```sql +-- 员工密码加密存储 +ALTER TABLE EMPLOYEE +MODIFY COLUMN PASSWORD VARCHAR(255) NOT NULL COMMENT 'BCrypt加密密码'; + +-- 敏感信息脱敏 +CREATE VIEW employee_safe_view AS +SELECT + ID, NAME, ACCOUNT, + SUBSTRING(EMAIL, 1, 2) || '****' || SUBSTRING(EMAIL, INSTR(EMAIL, '@')) as EMAIL_MASKED, + PHONE_MASKED +FROM EMPLOYEE; +``` + +### 2. 数据备份策略 + +#### 备份配置 +```bash +#!/bin/bash +# 数据库备份脚本 + +DB_NAME="supplier_ms" +BACKUP_DIR="/backup/mysql" +DATE=$(date +%Y%m%d_%H%M%S) + +# 全量备份 +mysqldump -u backup_user -p$BACKUP_PASSWORD \ + --single-transaction \ + --routines \ + --triggers \ + $DB_NAME > $BACKUP_DIR/${DB_NAME}_full_$DATE.sql + +# 增量备份(二进制日志) +mysql -u root -p$ROOT_PASSWORD -e "FLUSH LOGS;" +cp /var/lib/mysql/mysql-bin.* $BACKUP_DIR/incremental_$DATE/ + +# 清理旧备份(保留30天) +find $BACKUP_DIR -name "${DB_NAME}_*.sql" -mtime +30 -delete +``` + +--- + +## 📈 监控与维护 + +### 1. 性能监控 + +#### 关键指标 +- **连接数**: 当前连接数和最大连接数 +- **查询性能**: 慢查询日志和执行时间分布 +- **缓存命中率**: Redis缓存命中率 +- **锁等待**: 表锁和行锁等待情况 + +#### 监控查询 +```sql +-- 查看当前连接数 +SHOW STATUS LIKE 'Threads_connected'; +SHOW STATUS LIKE 'Max_used_connections'; + +-- 查看慢查询 +SELECT * FROM mysql.slow_log +ORDER BY start_time DESC LIMIT 10; + +-- 查看表大小 +SELECT + table_name, + ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)' +FROM information_schema.tables +WHERE table_schema = 'supplier_ms' +ORDER BY (data_length + index_length) DESC; +``` + +### 2. 维护任务 + +#### 定期维护任务 +```sql +-- 优化表(每周执行) +OPTIMIZE TABLE EMPLOYEE, COMPANY, CONTRACT, PROJECT; + +-- 分析表统计信息(每日执行) +ANALYZE TABLE EMPLOYEE, COMPANY, CONTRACT; + +-- 检查表完整性(每日执行) +CHECK TABLE EMPLOYEE, COMPANY, CONTRACT; + +-- 清理历史数据(每月执行) +DELETE FROM EMPLOYEE_LOGIN_HISTORY +WHERE LOGIN_TIME < DATE_SUB(NOW(), INTERVAL 1 YEAR); +``` + +--- + +## 🚀 扩展设计 + +### 1. 分库分表策略 + +#### 水平分片设计 +```sql +-- 按年份分表的合同表 +CONTRACT_2024, CONTRACT_2025, CONTRACT_2026 + +-- 分片键选择 +CREATE TABLE CONTRACT ( + ID BIGINT AUTO_INCREMENT PRIMARY KEY, + YEAR int NOT NULL COMMENT '年份', + CONTRACT_CODE varchar(100) NOT NULL, + -- 其他字段... + INDEX idx_year_contract_code (YEAR, CONTRACT_CODE) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 分片路由规则 +function getContractTable(year) { + return "CONTRACT_" + year; +} +``` + +### 2. 数据归档策略 + +#### 历史数据归档 +```sql +-- 创建归档表 +CREATE TABLE CONTRACT_ARCHIVE LIKE CONTRACT; + +-- 归档5年前的数据 +INSERT INTO CONTRACT_ARCHIVE +SELECT * FROM CONTRACT +WHERE CREATE_TIME < DATE_SUB(NOW(), INTERVAL 5 YEAR); + +-- 删除已归档数据 +DELETE FROM CONTRACT +WHERE CREATE_TIME < DATE_SUB(NOW(), INTERVAL 5 YEAR); +``` + +--- + +## 📚 文档维护 + +### 1. 文档更新机制 +- **表结构变更**: 同步更新文档 +- **关系变更**: 更新ER图和关系说明 +- **性能优化**: 记录优化历史和效果 +- **安全更新**: 记录权限变更和风险控制 + +### 2. 版本管理 +- **文档版本**: v1.0.0, v1.1.0, v1.2.0 +- **变更记录**: 详细记录每次变更内容 +- **影响评估**: 分析变更对系统的影响 + +--- + +*本文档详细描述了 Contract-Manager 系统的数据库设计和维护策略。请在数据库结构变更时同步更新本文档,确保文档与实际系统保持一致。* + +**文档版本**: v1.2.0 +**最后更新**: 2024-03-01 +**维护团队**: Contract Manager Development Team \ No newline at end of file diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000..7d86ada --- /dev/null +++ b/DEVELOPMENT_GUIDE.md @@ -0,0 +1,517 @@ +# Contract-Manager 开发环境配置指南 + +## 📋 系统要求 + +### 基础环境 +- **操作系统**: Windows 10/11, macOS, Linux +- **Java版本**: JDK 21+ +- **Maven版本**: Maven 3.8+ +- **MySQL版本**: MySQL 8.0+ +- **Redis版本**: Redis 6.0+ +- **内存**: 最少 8GB RAM (推荐 16GB+) +- **磁盘**: 最少 10GB 可用空间 + +### 开发工具 (推荐) +- **IDE**: IntelliJ IDEA 2023+ +- **数据库工具**: MySQL Workbench, DataGrip +- **缓存工具**: Redis Desktop Manager +- **API测试**: Postman, Insomnia +- **版本控制**: Git + +## 🛠️ 开发工具安装 + +### 1. Java 21 安装 + +#### Windows +1. 下载 [OpenJDK 21](https://adoptium.net/download/) +2. 运行安装程序,选择安装路径 (如: `C:\Program Files\Java\jdk-21`) +3. 设置环境变量: + ```bash + # JAVA_HOME + JAVA_HOME=C:\Program Files\Java\jdk-21 + + # PATH (添加到现有PATH末尾) + %JAVA_HOME%\bin + ``` + +#### macOS +```bash +# 使用 Homebrew 安装 +brew install openjdk@21 + +# 设置环境变量 (添加到 ~/.zshrc 或 ~/.bash_profile) +export JAVA_HOME=$(/usr/libexec/java_home -v 21) +export PATH=$JAVA_HOME/bin:$PATH +``` + +#### Linux (Ubuntu/Debian) +```bash +# 安装 OpenJDK 21 +sudo apt update +sudo apt install openjdk-21-jdk + +# 设置环境变量 +echo 'export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64' >> ~/.bashrc +echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc +source ~/.bashrc +``` + +### 2. Maven 安装 + +#### Windows +1. 下载 [Apache Maven](https://maven.apache.org/download.cgi) +2. 解压到 `C:\Maven\apache-maven-3.9.x` +3. 设置环境变量: + ```bash + # MAVEN_HOME + MAVEN_HOME=C:\Maven\apache-maven-3.9.x + + # PATH + %MAVEN_HOME%\bin + ``` + +#### macOS +```bash +# 使用 Homebrew 安装 +brew install maven + +# 验证安装 +mvn -version +``` + +#### Linux +```bash +# Ubuntu/Debian +sudo apt install maven + +# 验证安装 +mvn -version +``` + +### 3. IntelliJ IDEA 配置 + +#### 插件安装 (推荐) +- **Lombok Plugin**: 支持 Lombok 注解 +- **Maven Helper**: Maven 依赖管理 +- **Rainbow Brackets**: 括号颜色区分 +- **Database Tools**: 数据库操作支持 + +#### 项目导入 +1. 启动 IntelliJ IDEA +2. 选择 "Open" 或 "Import Project" +3. 选择项目根目录的 `pom.xml` +4. 等待 Maven 导入完成 +5. 设置 JDK 为 Java 21 + +#### 代码风格配置 +1. File → Settings → Editor → Code Style → Java +2. 导入项目提供的代码风格配置 (如存在) +3. 设置自动格式化规则 + +## 📁 项目配置 + +### 1. 环境变量配置 + +创建项目根目录下的 `.env` 文件: +```bash +# 数据库配置 +DB_HOST=localhost +DB_PORT=3306 +DB_USERNAME=contract_manager +DB_PASSWORD=your_password +DB_DATABASE=contract_manager + +# Redis 配置 +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= + +# 服务端口配置 +SERVER_PORT=8080 +CLIENT_PORT=8081 + +# 文件存储配置 +FILE_BASE_PATH=C:/contract_files + +# 云服务 API 密钥 (如有) +CLOUD_RK_API_KEY=your_rk_api_key +CLOUD_TYC_API_KEY=your_tyc_api_key +CLOUD_U8_API_KEY=your_u8_api_key + +# 日志配置 +LOG_LEVEL=INFO +LOG_PATH=./logs + +# 开发环境配置 +SPRING_PROFILES_ACTIVE=dev +``` + +### 2. 数据库配置 + +#### MySQL 安装与配置 +```bash +# Ubuntu/Debian +sudo apt install mysql-server + +# 启动 MySQL 服务 +sudo systemctl start mysql +sudo systemctl enable mysql + +# 安全配置 +sudo mysql_secure_installation +``` + +#### 创建数据库和用户 +```sql +-- 登录 MySQL +mysql -u root -p + +-- 创建数据库 +CREATE DATABASE contract_manager CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 创建用户 +CREATE USER 'contract_manager'@'localhost' IDENTIFIED BY 'your_password'; + +-- 授权 +GRANT ALL PRIVILEGES ON contract_manager.* TO 'contract_manager'@'localhost'; +FLUSH PRIVILEGES; +``` + +#### 数据初始化 +```bash +# 导入数据库结构 +mysql -u contract_manager -p contract_manager < docs/db/structs.sql + +# 导入初始数据 (如需要) +mysql -u contract_manager -p contract_manager < docs/db/initial_data.sql +``` + +### 3. Redis 配置 + +#### Redis 安装 +```bash +# Ubuntu/Debian +sudo apt install redis-server + +# 启动 Redis +sudo systemctl start redis-server +sudo systemctl enable redis-server + +# 测试连接 +redis-cli ping +# 应该返回: PONG +``` + +#### Redis 配置优化 (开发环境) +```bash +# 编辑 Redis 配置文件 +sudo nano /etc/redis/redis.conf + +# 推荐配置 (开发环境) +maxmemory 256mb +maxmemory-policy allkeys-lru +save 900 1 +save 300 10 +save 60 10000 +``` + +## 🚀 项目运行 + +### 1. 项目构建 +```bash +# 克隆项目 (如使用 Git) +git clone +cd Contract-Manager + +# 清理并编译 +mvn clean compile + +# 运行测试 +mvn test + +# 打包 (生产环境) +mvn clean package -DskipTests +``` + +### 2. 启动服务 + +#### 方式一: 分模块启动 +```bash +# 启动服务端 +cd server +mvn spring-boot:run + +# 新终端窗口 - 启动客户端 +cd client +mvn jfx:run +``` + +#### 方式二: Maven 工具启动 +```bash +# 启动服务端 +mvn spring-boot:run -pl server + +# 新终端窗口 - 启动客户端 +mvn jfx:run -pl client +``` + +#### 方式三: IDE 启动 +1. **服务端**: 运行 `com.ecep.contract.ContractApplication` +2. **客户端**: 运行客户端的启动类 + +### 3. 访问应用 +- **服务端**: http://localhost:8080 +- **客户端**: 运行 JavaFX 应用后打开界面 +- **API 文档**: http://localhost:8080/swagger-ui.html + +## 📝 开发流程 + +### 1. 日常开发 +```bash +# 获取最新代码 +git pull origin main + +# 创建功能分支 +git checkout -b feature/your-feature-name + +# 开发功能... + +# 提交代码 +git add . +git commit -m "feat: add new feature description" + +# 推送分支 +git push origin feature/your-feature-name + +# 创建 Pull Request +``` + +### 2. 代码质量检查 +```bash +# 代码检查 +mvn checkstyle:check + +# 静态分析 +mvn spotbugs:check + +# 测试覆盖率 +mvn jacoco:report +``` + +### 3. 数据库迁移 +```bash +# 执行数据库脚本 +mysql -u contract_manager -p contract_manager < docs/db/your_migration.sql + +# 或使用 Flyway (如已配置) +mvn flyway:migrate +``` + +## 🔧 IDE 配置 + +### IntelliJ IDEA 配置 + +#### 代码样式 +```xml + +File → Settings → Editor → File Encodings → Global Encoding: UTF-8 + + +File → Project Structure → Project → SDK: 21 +``` + +#### Maven 配置 +```xml + +File → Settings → Build → Maven → User Settings File: settings.xml + + +File → Settings → Build → Maven → Maven home directory: /path/to/maven +``` + +#### 插件配置 +```xml + +Settings → Build → Compiler → Annotation Processors → Enable annotation processing + + +Settings → Tools → External Tools → 配置格式化命令 +``` + +### Git 配置 +```bash +# 设置用户信息 +git config --global user.name "Your Name" +git config --global user.email "your.email@example.com" + +# 设置默认分支 +git config --global init.defaultBranch main + +# 启用自动换行转换 +git config --global core.autocrlf true +``` + +## 🔍 常见问题 + +### 1. 编译错误 + +#### Java 版本不匹配 +```bash +# 检查 Java 版本 +java -version + +# 设置正确的 JAVA_HOME +echo $JAVA_HOME +``` + +#### 依赖下载失败 +```bash +# 清理 Maven 仓库 +mvn dependency:purge-local-repository + +# 强制更新依赖 +mvn clean install -U +``` + +### 2. 数据库连接问题 + +#### 连接被拒绝 +```bash +# 检查 MySQL 服务状态 +sudo systemctl status mysql + +# 检查端口占用 +netstat -an | grep 3306 +``` + +#### 权限问题 +```sql +-- 检查用户权限 +SHOW GRANTS FOR 'contract_manager'@'localhost'; + +-- 重新授权 +GRANT ALL PRIVILEGES ON contract_manager.* TO 'contract_manager'@'localhost'; +FLUSH PRIVILEGES; +``` + +### 3. Redis 连接问题 + +#### Redis 服务未启动 +```bash +# 启动 Redis +sudo systemctl start redis-server + +# 检查 Redis 状态 +sudo systemctl status redis-server +``` + +#### 端口占用 +```bash +# 检查 Redis 端口 +netstat -an | grep 6379 + +# 重启 Redis +sudo systemctl restart redis-server +``` + +### 4. 客户端启动问题 + +#### JavaFX 版本不匹配 +```xml + + + 21 + 21 + +``` + +#### 依赖冲突 +```bash +# 清理客户端模块 +cd client +mvn clean + +# 重新导入依赖 +mvn dependency:tree +``` + +## 📚 有用的命令 + +### Maven 命令 +```bash +# 清理项目 +mvn clean + +# 编译 +mvn compile + +# 打包 +mvn package + +# 运行测试 +mvn test + +# 运行特定测试 +mvn test -Dtest=CompanyServiceTest + +# 跳过后测试打包 +mvn package -DskipTests + +# 查看依赖树 +mvn dependency:tree + +# 依赖分析 +mvn dependency:analyze +``` + +### 数据库操作 +```bash +# 连接到数据库 +mysql -u contract_manager -p + +# 备份数据库 +mysqldump -u contract_manager -p contract_manager > backup.sql + +# 恢复数据库 +mysql -u contract_manager -p contract_manager < backup.sql + +# 查看表结构 +SHOW TABLES; +DESCRIBE table_name; +``` + +### Redis 操作 +```bash +# 连接 Redis +redis-cli + +# 查看所有键 +KEYS * + +# 清空数据库 +FLUSHDB + +# 监控命令 +redis-cli monitor +``` + +## 🎯 性能优化建议 + +### 开发环境优化 +1. **使用 SSD 硬盘**: 加快构建和部署速度 +2. **增加内存**: 至少 8GB RAM,推荐 16GB+ +3. **关闭不必要程序**: 释放系统资源 + +### IDE 优化 +1. **启用编译缓存**: Settings → Build → Compiler → Use build cache +2. **配置启动内存**: -Xmx4g -Xms2g +3. **禁用不必要的插件**: 减少启动时间 + +### 数据库优化 +1. **连接池配置**: 调整 HikariCP 连接池参数 +2. **索引优化**: 为常用查询字段添加索引 +3. **查询优化**: 避免 N+1 查询问题 + +--- + +*本指南涵盖了 Contract-Manager 项目的完整开发环境配置。如有问题,请参考故障排除部分或联系项目维护者。* \ No newline at end of file diff --git a/PROJECT_DOCUMENTATION.md b/PROJECT_DOCUMENTATION.md new file mode 100644 index 0000000..9a867f7 --- /dev/null +++ b/PROJECT_DOCUMENTATION.md @@ -0,0 +1,113 @@ +# Contract-Manager 项目文档 + +## 📁 文档结构总览 + +``` +Contract-Manager/ +├── 📄 README.md # 项目总体介绍 +├── 📄 PROJECT_DOCUMENTATION.md # 本文档 - 项目文档总览 +├── 📄 DEVELOPMENT_GUIDE.md # 开发指南 +├── 📄 API_DOCUMENTATION.md # API接口文档 +├── 📄 DEPLOYMENT_GUIDE.md # 部署指南 +├── 📄 DATABASE_SCHEMA.md # 数据库设计文档 +│ +├── 📁 .trae/rules/ # 技术规则和规范文档 +│ ├── 📄 server_service_rules.md # 服务器端Service开发规范 +│ ├── 📄 server_repository_rules.md # 服务器端Repository开发规范 +│ ├── 📄 client_service_rules.md # 客户端Service开发规范 +│ ├── 📄 client_controller_rules.md # 客户端Controller开发规范 +│ ├── 📄 vo_rules.md # VO对象规范 +│ ├── 📄 entity_rules.md # 实体对象规范 +│ └── 📄 ...其他规则文档 +│ +├── 📁 docs/ # 项目文档目录 +│ ├── 📁 analysis/ # 技术分析报告 +│ ├── 📁 task/ # 任务相关文档 +│ ├── 📁 db/ # 数据库脚本和设计 +│ ├── 📁 model/ # 数据模型说明 +│ └── 📁 cloud/ # 云服务集成文档 +│ +├── 📁 server/ # 服务器端代码 +└── 📁 client/ # 客户端代码 +``` + +## 📚 核心文档说明 + +### 1. 技术规则文档 (.trae/rules/) +技术规则文档是项目的核心开发规范,定义了代码编写、设计模式、架构原则等: + +- **server_service_rules.md** - 服务器端Service层开发规范 +- **server_repository_rules.md** - 数据访问层开发规范 +- **client_service_rules.md** - 客户端Service层开发规范 +- **client_controller_rules.md** - 客户端控制器开发规范 +- **vo_rules.md** - 视图对象(VO)设计和实现规范 +- **entity_rules.md** - 实体对象设计和实现规范 + +### 2. 项目文档 (docs/) +项目文档包含具体的技术实现、任务分析和业务说明: + +- **analysis/** - 包含技术架构分析、性能优化、代码审查报告 +- **task/** - 包含具体的开发任务文档和执行记录 +- **db/** - 数据库表结构、脚本和迁移文件 +- **model/** - 数据模型说明和业务规则 + +### 3. 待完善文档 (需要新建) + +#### 核心项目文档 +- **README.md** - 项目简介、快速开始指南 +- **DEVELOPMENT_GUIDE.md** - 开发环境搭建、开发流程指南 +- **API_DOCUMENTATION.md** - REST API接口完整文档 +- **DEPLOYMENT_GUIDE.md** - 项目部署、运维指南 +- **DATABASE_SCHEMA.md** - 数据库架构和表关系图 + +#### 用户指南 +- **USER_MANUAL.md** - 最终用户使用手册 +- **UI_COMPONENT_GUIDE.md** - 客户端界面组件说明 + +## 🎯 文档更新目标 + +### 高优先级 (Core Documentation) +1. **项目架构文档** - 技术栈、模块划分、架构设计 +2. **开发指南** - 环境配置、开发流程、代码规范 +3. **API文档** - 完整的接口定义和示例 + +### 中优先级 (Functional Documentation) +1. **数据库文档** - 表结构、关系图、数据字典 +2. **部署运维** - 安装配置、监控、日志管理 +3. **业务功能** - 功能说明、使用流程 + +### 低优先级 (User Documentation) +1. **用户手册** - UI使用指南、常见问题 +2. **开发进阶** - 性能优化、高级特性 +3. **集成指南** - 第三方服务集成 + +## 📋 文档质量标准 + +### 内容要求 +- **完整性** - 覆盖项目各个方面的完整信息 +- **准确性** - 信息准确、代码示例可运行 +- **时效性** - 定期更新,保持与代码同步 +- **可读性** - 结构清晰、语言简洁 + +### 格式规范 +- **统一格式** - 使用Markdown格式,保持一致的样式 +- **目录结构** - 清晰的章节组织和目录导航 +- **代码示例** - 提供可执行的代码示例和配置 +- **图表说明** - 使用图表辅助说明复杂概念 + +## 🚀 更新计划 + +1. **第一阶段** - 核心文档完善 (高优先级) +2. **第二阶段** - 功能文档补充 (中优先级) +3. **第三阶段** - 用户指南和最佳实践 (低优先级) + +## 📞 文档维护 + +- **责任分工** - 各模块开发者负责对应文档的维护 +- **更新频率** - 代码变更时同步更新相关文档 +- **审核机制** - 重要文档变更需要技术负责人审核 +- **版本控制** - 文档版本与代码版本保持同步 + +--- + +*本文档将持续更新以反映项目的最新状态和最佳实践。* \ No newline at end of file diff --git a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinFiles.java b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinFiles.java similarity index 99% rename from client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinFiles.java rename to client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinFiles.java index ab0d4fb..4e12361 100644 --- a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinFiles.java +++ b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinFiles.java @@ -1,9 +1,10 @@ -package com.ecep.contract.controller.tab; +package com.ecep.contract.controller.contract; import com.ecep.contract.*; import com.ecep.contract.constant.ContractConstant; -import com.ecep.contract.controller.contract.AbstContractTableTabSkin; -import com.ecep.contract.controller.contract.ContractWindowController; +import com.ecep.contract.controller.tab.ContractFilesRebuildTasker; +import com.ecep.contract.controller.tab.CustomerContractCostFormUpdateTask; +import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.table.EditableEntityTableTabSkin; import com.ecep.contract.controller.table.cell.ContractFileTypeTableCell; import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell; diff --git a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinPayPlan.java b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinPayPlan.java similarity index 94% rename from client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinPayPlan.java rename to client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinPayPlan.java index a036479..6bd22a1 100644 --- a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinPayPlan.java +++ b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinPayPlan.java @@ -1,7 +1,5 @@ -package com.ecep.contract.controller.tab; +package com.ecep.contract.controller.contract; -import com.ecep.contract.controller.contract.AbstContractTableTabSkin; -import com.ecep.contract.controller.contract.ContractWindowController; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.service.ContractPayPlanService; import com.ecep.contract.util.FxmlPath; diff --git a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinSubContract.java b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinSubContract.java similarity index 95% rename from client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinSubContract.java rename to client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinSubContract.java index c31b94d..3d4ded7 100644 --- a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinSubContract.java +++ b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinSubContract.java @@ -1,10 +1,9 @@ -package com.ecep.contract.controller.tab; +package com.ecep.contract.controller.contract; import java.time.LocalDate; import com.ecep.contract.ContractPayWay; -import com.ecep.contract.controller.contract.AbstContractTableTabSkin; -import com.ecep.contract.controller.contract.ContractWindowController; +import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.ContractService; diff --git a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinVendorBid.java b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinVendorBid.java similarity index 97% rename from client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinVendorBid.java rename to client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinVendorBid.java index ff07dfe..1de5f8a 100644 --- a/client/src/main/java/com/ecep/contract/controller/tab/ContractTabSkinVendorBid.java +++ b/client/src/main/java/com/ecep/contract/controller/contract/ContractTabSkinVendorBid.java @@ -1,4 +1,4 @@ -package com.ecep.contract.controller.tab; +package com.ecep.contract.controller.contract; import java.util.List; @@ -7,8 +7,7 @@ import org.controlsfx.control.textfield.TextFields; import com.ecep.contract.ContractPayWay; import com.ecep.contract.SpringApp; -import com.ecep.contract.controller.contract.AbstContractTableTabSkin; -import com.ecep.contract.controller.contract.ContractWindowController; +import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.table.EditableEntityTableTabSkin; import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.ContractFileTableCell; diff --git a/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java b/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java index 53e981d..d529edb 100644 --- a/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java +++ b/client/src/main/java/com/ecep/contract/controller/contract/ContractWindowController.java @@ -12,13 +12,8 @@ import com.ecep.contract.DesktopUtils; import com.ecep.contract.controller.AbstEntityController; import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.tab.ContractTabSkinBase; -import com.ecep.contract.controller.tab.ContractTabSkinFiles; import com.ecep.contract.controller.tab.ContractTabSkinInvoices; import com.ecep.contract.controller.tab.ContractTabSkinItemsV2; -import com.ecep.contract.controller.tab.ContractTabSkinPayPlan; -import com.ecep.contract.controller.tab.ContractTabSkinSubContract; -import com.ecep.contract.controller.tab.ContractTabSkinVendorBid; - import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.ContractService; import com.ecep.contract.task.ContractRepairTask; diff --git a/client/src/main/java/com/ecep/contract/controller/tab/ContractFilesRebuildTasker.java b/client/src/main/java/com/ecep/contract/controller/tab/ContractFilesRebuildTasker.java index 3533090..b0a2c36 100644 --- a/client/src/main/java/com/ecep/contract/controller/tab/ContractFilesRebuildTasker.java +++ b/client/src/main/java/com/ecep/contract/controller/tab/ContractFilesRebuildTasker.java @@ -1,23 +1,65 @@ package com.ecep.contract.controller.tab; import com.ecep.contract.MessageHolder; +import com.ecep.contract.SpringApp; import com.ecep.contract.task.Tasker; +import com.ecep.contract.WebSocketClientService; +import com.ecep.contract.WebSocketClientTasker; import com.ecep.contract.vo.ContractVo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import lombok.Getter; import lombok.Setter; -public class ContractFilesRebuildTasker extends Tasker { +import java.util.Locale; + +@Slf4j +public class ContractFilesRebuildTasker extends Tasker implements WebSocketClientTasker { @Setter private ContractVo contract; + @Getter + @Setter + private boolean repaired = false; + @Override - public Object execute(MessageHolder holder) { - return null; + public String getTaskName() { + return "ContractFilesRebuildTasker"; } - public boolean isRepaired() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'isRepaired'"); + @Override + public void updateProgress(long workDone, long max) { + super.updateProgress(workDone, max); + } + + @Override + public Object execute(MessageHolder holder) { + log.info("开始执行合同文件重建任务,合同ID: {}", contract != null ? contract.getId() : "unknown"); + + if (contract == null) { + String errorMsg = "合同信息为空,无法执行文件重建任务"; + holder.error(errorMsg); + throw new RuntimeException(errorMsg); + } + + try { + holder.info("开始重建合同文件,合同编号: " + contract.getCode()); + updateProgress(0, 100); + + // 使用WebSocket调用远程任务 + Object result = callRemoteTask(holder, Locale.getDefault(), contract.getId()); + + updateProgress(100, 100); + holder.info("合同文件重建任务已提交到服务器"); + + return result; + } catch (Exception e) { + log.error("合同文件重建任务执行失败", e); + holder.error("任务执行失败: " + e.getMessage()); + throw new RuntimeException("合同文件重建任务执行失败", e); + } } } diff --git a/client/src/main/java/com/ecep/contract/service/ContractBalanceService.java b/client/src/main/java/com/ecep/contract/service/ContractBalanceService.java new file mode 100644 index 0000000..687a4b2 --- /dev/null +++ b/client/src/main/java/com/ecep/contract/service/ContractBalanceService.java @@ -0,0 +1,217 @@ +package com.ecep.contract.service; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import com.ecep.contract.util.ParamUtils; +import com.ecep.contract.vm.ContractBalanceViewModel; +import com.ecep.contract.vo.ContractBalanceVo; + +/** + * 合同余额服务客户端实现 + * 继承QueryService,提供基于WebSocket的异步通信和缓存机制 + */ +@Service +@CacheConfig(cacheNames = "contract-balance") +public class ContractBalanceService extends QueryService { + + @Autowired + private ContractService contractService; + + /** + * 根据ID查询合同余额信息 + * 使用缓存机制提高查询性能 + * + * @param id 余额记录ID + * @return 合同余额视图对象,如果不存在则返回null + */ + @Cacheable(key = "#p0") + public ContractBalanceVo findById(Integer id) { + return super.findById(id); + } + + /** + * 保存合同余额信息 + * 支持新建和更新操作,包含缓存失效机制 + * + * @param contractBalance 合同余额视图对象 + * @return 保存后的合同余额视图对象 + */ + @Caching(evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'contract-'+#p0.contractId") + }) + public ContractBalanceVo save(ContractBalanceVo contractBalance) { + return super.save(contractBalance); + } + + /** + * 删除合同余额信息 + * 包含相关缓存的失效处理 + * + * @param contractBalance 合同余额视图对象 + */ + @Caching(evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'contract-'+#p0.contractId") + }) + public void delete(ContractBalanceVo contractBalance) { + super.delete(contractBalance); + } + + /** + * 根据合同ID查询所有余额记录 + * + * @param contractId 合同ID + * @return 合同余额列表 + */ + public List findAllByContractId(Integer contractId) { + return findAll(ParamUtils.builder() + .equals("contractId", contractId) + .build(), Pageable.unpaged()).getContent(); + } + + /** + * 根据业务员ID查询余额记录 + * + * @param employeeId 业务员ID + * @return 合同余额列表 + */ + public List findAllByEmployeeId(Integer employeeId) { + return findAll(ParamUtils.builder() + .equals("employeeId", employeeId) + .build(), Pageable.unpaged()).getContent(); + } + + /** + * 根据引用ID查询余额记录 + * + * @param refId 引用ID + * @return 合同余额视图对象,如果不存在则返回null + */ + public ContractBalanceVo findByRefId(String refId) { + return findAll(ParamUtils.builder() + .equals("refId", refId) + .build(), Pageable.ofSize(1)).stream() + .findFirst() + .orElse(null); + } + + /** + * 根据GUID查询余额记录 + * + * @param guid GUID + * @return 合同余额视图对象,如果不存在则返回null + */ + public ContractBalanceVo findByGuid(String guid) { + return findAll(ParamUtils.builder() + .equals("guid", guid) + .build(), Pageable.ofSize(1)).stream() + .findFirst() + .orElse(null); + } + + /** + * 根据合同ID分页查询余额记录 + * + * @param contractId 合同ID + * @param pageable 分页参数 + * @return 分页结果 + */ + public Page findByContractId(Integer contractId, Pageable pageable) { + return findAll(ParamUtils.builder() + .equals("contractId", contractId) + .build(), pageable); + } + + /** + * 根据业务员ID和日期范围查询余额记录 + * + * @param employeeId 业务员ID + * @param beginDate 开始日期 + * @param endDate 结束日期 + * @return 合同余额列表 + */ + public List findAllByEmployeeAndDateRange(Integer employeeId, LocalDate beginDate, + LocalDate endDate) { + return findAll(ParamUtils.builder() + .equals("employeeId", employeeId) + .between("setupDate", beginDate, endDate) + .build(), Pageable.unpaged()).getContent(); + } + + /** + * 根据凭证ID查询余额记录 + * + * @param pzId 凭证ID + * @return 合同余额列表 + */ + public List findAllByPzId(String pzId) { + return findAll(ParamUtils.builder() + .equals("pzId", pzId) + .build(), Pageable.unpaged()).getContent(); + } + + /** + * 根据JSD类型查询余额记录 + * + * @param jsdType JSD类型 + * @return 合同余额列表 + */ + public List findAllByJsdType(String jsdType) { + return findAll(ParamUtils.builder() + .equals("jsdType", jsdType) + .build(), Pageable.unpaged()).getContent(); + } + + /** + * 检查指定的合同是否存在余额记录 + * + * @param contractId 合同ID + * @return 如果存在余额记录返回true,否则返回false + */ + public boolean hasBalanceByContractId(Integer contractId) { + Page page = findByContractId(contractId, Pageable.ofSize(1)); + return !page.isEmpty(); + } + + /** + * 统计指定合同的总余额数量 + * + * @param contractId 合同ID + * @return 余额记录总数 + */ + public long countByContractId(Integer contractId) { + return findByContractId(contractId, Pageable.unpaged()).getTotalElements(); + } + + /** + * 根据查询参数进行复杂查询 + * + * @param params 查询参数 + * @param pageable 分页参数 + * @return 分页结果 + */ + public Page findAll(Map params, Pageable pageable) { + return super.findAll(params, pageable); + } + + /** + * 获取所有余额记录 + * + * @return 合同余额列表 + */ + public List findAll() { + return super.findAll(); + } +} \ No newline at end of file diff --git a/client/src/main/java/com/ecep/contract/vm/ContractBalanceViewModel.java b/client/src/main/java/com/ecep/contract/vm/ContractBalanceViewModel.java new file mode 100644 index 0000000..cdc247e --- /dev/null +++ b/client/src/main/java/com/ecep/contract/vm/ContractBalanceViewModel.java @@ -0,0 +1,180 @@ +package com.ecep.contract.vm; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; + +import com.ecep.contract.vo.ContractBalanceVo; + +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class ContractBalanceViewModel extends IdentityViewModel { + + /** + * 余额ID (对应cBalanceID) + */ + private SimpleStringProperty refId = new SimpleStringProperty(); + + /** + * GUID,余额在系统中的唯一标识 (对应GUID字段) + */ + private SimpleObjectProperty guid = new SimpleObjectProperty<>(); + + /** + * 关联合同ID (对应cContractID) + */ + private SimpleObjectProperty contractId = new SimpleObjectProperty<>(); + + /** + * 业务员ID (对应cFunctionaryID) + */ + private SimpleObjectProperty employeeId = new SimpleObjectProperty<>(); + + /** + * 余额类型ID (对应cBalancelTypeID) + */ + private SimpleStringProperty balanceTypeId = new SimpleStringProperty(); + + /** + * 汇率 (对应decExchangeRate) + */ + private SimpleObjectProperty exchangeRate = new SimpleObjectProperty<>(); + + /** + * 发票号码 (对应cBalanceDetails) + */ + private SimpleStringProperty invoiceNumber = new SimpleStringProperty(); + + /** + * 创建人 (对应cProducer) + */ + private SimpleObjectProperty setupPersonId = new SimpleObjectProperty<>(); + + /** + * 创建日期 (对应dtProduceDate) + */ + private SimpleObjectProperty setupDate = new SimpleObjectProperty<>(); + + /** + * 审核人 (对应cAuditer) + */ + private SimpleObjectProperty auditerId = new SimpleObjectProperty<>(); + + /** + * 审核日期 (对应dtAuditeDate) + */ + private SimpleObjectProperty auditeDate = new SimpleObjectProperty<>(); + + /** + * 管理员 (对应cAdmin) + */ + private SimpleObjectProperty adminId = new SimpleObjectProperty<>(); + + /** + * 管理员日期 (对应dtAdminDate) + */ + private SimpleObjectProperty adminDate = new SimpleObjectProperty<>(); + + /** + * 凭证ID (对应cPZID) + */ + private SimpleStringProperty pzId = new SimpleStringProperty(); + + /** + * 凭证编号 (对应cPZNum) + */ + private SimpleStringProperty pzNum = new SimpleStringProperty(); + + /** + * JSD类型 (对应cJsdType) + */ + private SimpleStringProperty jsdType = new SimpleStringProperty(); + + /** + * 源余额ID (对应cSrcBalanceID) + */ + private SimpleStringProperty srcBalanceId = new SimpleStringProperty(); + + /** + * 创建时间 (对应dtCreateTime) + */ + private SimpleObjectProperty createTime = new SimpleObjectProperty<>(); + + /** + * 修改时间 (对应dtModifyTime) + */ + private SimpleObjectProperty modifyTime = new SimpleObjectProperty<>(); + + /** + * 修改人 (对应cModifer) + */ + private SimpleObjectProperty modiferId = new SimpleObjectProperty<>(); + + /** + * 生效时间 (对应dtEffectTime) + */ + private SimpleObjectProperty effectTime = new SimpleObjectProperty<>(); + + @Override + protected void updateFrom(ContractBalanceVo v) { + super.updateFrom(v); + + // 设置各个属性值 + refId.set(v.getRefId()); + guid.set(v.getGuid()); + contractId.set(v.getContractId()); + employeeId.set(v.getEmployeeId()); + balanceTypeId.set(v.getBalanceTypeId()); + exchangeRate.set(v.getExchangeRate()); + invoiceNumber.set(v.getInvoiceNumber()); + setupPersonId.set(v.getSetupPersonId()); + setupDate.set(v.getSetupDate()); + auditerId.set(v.getAuditerId()); + auditeDate.set(v.getAuditeDate()); + adminId.set(v.getAdminId()); + adminDate.set(v.getAdminDate()); + pzId.set(v.getPzId()); + pzNum.set(v.getPzNum()); + jsdType.set(v.getJsdType()); + srcBalanceId.set(v.getSrcBalanceId()); + createTime.set(v.getCreateTime()); + modifyTime.set(v.getModifyTime()); + modiferId.set(v.getModiferId()); + effectTime.set(v.getEffectTime()); + } + + @Override + public boolean copyTo(ContractBalanceVo v) { + boolean result = super.copyTo(v); + + // 从ViewModel复制属性到VO + v.setRefId(refId.get()); + v.setGuid(guid.get()); + v.setContractId(contractId.get()); + v.setEmployeeId(employeeId.get()); + v.setBalanceTypeId(balanceTypeId.get()); + v.setExchangeRate(exchangeRate.get()); + v.setInvoiceNumber(invoiceNumber.get()); + v.setSetupPersonId(setupPersonId.get()); + v.setSetupDate(setupDate.get()); + v.setAuditerId(auditerId.get()); + v.setAuditeDate(auditeDate.get()); + v.setAdminId(adminId.get()); + v.setAdminDate(adminDate.get()); + v.setPzId(pzId.get()); + v.setPzNum(pzNum.get()); + v.setJsdType(jsdType.get()); + v.setSrcBalanceId(srcBalanceId.get()); + v.setCreateTime(createTime.get()); + v.setModifyTime(modifyTime.get()); + v.setModiferId(modiferId.get()); + v.setEffectTime(effectTime.get()); + + return result; + } +} \ No newline at end of file diff --git a/client/src/main/resources/ui/contract/contract-invoice-manager.fxml b/client/src/main/resources/ui/contract/contract-invoice-manager.fxml index 811b0a7..5dd59e7 100644 --- a/client/src/main/resources/ui/contract/contract-invoice-manager.fxml +++ b/client/src/main/resources/ui/contract/contract-invoice-manager.fxml @@ -11,7 +11,7 @@ - +