feat(contract): 新增合同余额功能及重构文件管理
重构合同文件管理逻辑,增加错误处理和日志记录 新增ContractBalance实体、Repository和VO类 完善Voable接口文档和实现规范 更新项目架构文档和数据库设计 修复SmbFileService的连接问题 移动合同相关TabSkin类到contract包 添加合同文件重建任务的WebSocket支持
This commit is contained in:
460
.trae/rules/server_repository_rules.md
Normal file
460
.trae/rules/server_repository_rules.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# Server模块 Repository 实现经验总结
|
||||
|
||||
## 1. Repository 基本架构
|
||||
|
||||
### 1.1 接口设计原则
|
||||
- Repository接口必须继承`MyRepository<T, ID>`基类
|
||||
- 使用`@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<ContractBalance, Integer> {
|
||||
// 自定义查询方法
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 ⚠️ 重要错误示例 - 避免这些实现方式
|
||||
|
||||
**❌ 错误示例1:直接继承JpaRepository**
|
||||
```java
|
||||
// ContractFileRepository, ContractItemRepository, VendorFileRepository, ProjectQuotationRepository
|
||||
@Repository
|
||||
public interface ContractFileRepository
|
||||
extends JpaRepository<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
|
||||
// 错误:不应该直接继承JpaRepository
|
||||
List<ContractFile> findByContractId(Integer contractId);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ 错误示例2:继承多个冗余接口**
|
||||
```java
|
||||
// PurchaseOrderRepository
|
||||
public interface PurchaseOrderRepository extends
|
||||
CrudRepository<PurchaseOrder, Integer>,
|
||||
PagingAndSortingRepository<PurchaseOrder, Integer>,
|
||||
JpaRepository<PurchaseOrder, Integer>,
|
||||
JpaSpecificationExecutor<PurchaseOrder> {
|
||||
// 错误:冗余接口继承,应该只继承MyRepository
|
||||
}
|
||||
```
|
||||
|
||||
**❌ 错误示例3:缺少@Repository注解**
|
||||
```java
|
||||
// CompanyFileRepository, ProjectFileRepository
|
||||
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
|
||||
// 错误:缺少@Repository注解
|
||||
List<CompanyFile> findByCompany(Company company);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ 错误示例4:缺少JavaDoc注释**
|
||||
```java
|
||||
// 多个Repository存在此问题
|
||||
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
|
||||
// 错误:缺少JavaDoc注释说明用途和方法
|
||||
List<VendorFile> findAllByVendorId(int vendorId);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ 正确实现示例**
|
||||
```java
|
||||
/**
|
||||
* 合同Repository - 提供合同相关的数据库访问操作
|
||||
*/
|
||||
@Repository
|
||||
public interface ContractRepository extends MyRepository<Contract, Integer> {
|
||||
// 正确:统一继承MyRepository,有完整的JavaDoc
|
||||
|
||||
/**
|
||||
* 根据合同代码查询合同
|
||||
* @param code 合同代码
|
||||
* @return 合同 Optional
|
||||
*/
|
||||
Optional<Contract> findByCode(String code);
|
||||
|
||||
/**
|
||||
* 根据状态查询合同列表
|
||||
* @param status 合同状态
|
||||
* @return 合同列表
|
||||
*/
|
||||
List<Contract> 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<ContractBalance> findByContractId(Integer contractId, Pageable pageable);
|
||||
|
||||
// 根据多个条件组合查询
|
||||
List<ContractBalance> 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<ContractBalance> findByContractAndGuidAndDateRange(
|
||||
@Param("contractId") Integer contractId,
|
||||
@Param("guid") UUID guid,
|
||||
@Param("startDate") LocalDateTime startDate,
|
||||
Pageable pageable
|
||||
);
|
||||
```
|
||||
|
||||
## 4. 分页和排序支持
|
||||
|
||||
### 4.1 分页查询
|
||||
```java
|
||||
// 在Repository接口中定义
|
||||
Page<ContractBalance> findByContractId(Integer contractId, Pageable pageable);
|
||||
|
||||
// 在Service中调用
|
||||
Page<ContractBalance> 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<ContractBalanceStatsVO> 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<ContractBalance> 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<Integer> contractIds);
|
||||
```
|
||||
|
||||
## 9. 错误处理和调试
|
||||
|
||||
### 9.1 常见异常类型
|
||||
- `DataAccessException` - 数据访问异常
|
||||
- `DataIntegrityViolationException` - 数据完整性异常
|
||||
- `EmptyResultDataAccessException` - 空结果异常
|
||||
|
||||
### 9.2 日志记录
|
||||
```java
|
||||
@Repository
|
||||
@Slf4j
|
||||
public class ContractBalanceRepository {
|
||||
|
||||
@Query("...")
|
||||
public List<ContractBalance> 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<ContractBalance> 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<T, ID>`
|
||||
- [ ] 使用了 `@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<Entity, Integer>
|
||||
public interface AnotherRepository extends CrudRepository<Entity, Integer>
|
||||
|
||||
// 正确做法 - 统一使用MyRepository
|
||||
public interface SomeRepository extends MyRepository<Entity, Integer>
|
||||
```
|
||||
|
||||
#### ❌ 方法命名不规范
|
||||
```java
|
||||
// 错误示例
|
||||
List<Entity> getDataByCondition(String condition) // 使用get前缀而非find
|
||||
|
||||
// 正确做法
|
||||
List<Entity> findByCondition(String condition) // 使用find前缀
|
||||
```
|
||||
|
||||
#### ❌ 缺少必要文档
|
||||
```java
|
||||
// 错误示例 - 无注释
|
||||
List<Entity> findByStatus(String status);
|
||||
|
||||
// 正确做法 - 完整注释
|
||||
/**
|
||||
* 根据状态查询实体列表
|
||||
*
|
||||
* @param status 状态值
|
||||
* @return 符合状态的实体列表
|
||||
*/
|
||||
List<Entity> findByStatus(String status);
|
||||
```
|
||||
|
||||
### 14.3 架构一致性验证
|
||||
确保新实现的Repository与项目中其他Repository保持一致:
|
||||
|
||||
1. **继承模式统一**:所有Repository都必须继承MyRepository
|
||||
2. **注解使用统一**:统一使用@Repository注解
|
||||
3. **命名约定统一**:遵循Spring Data JPA命名规范
|
||||
4. **文档风格统一**:保持JavaDoc注释风格一致
|
||||
|
||||
### 14.4 后续维护注意事项
|
||||
- 新增查询方法时遵循现有命名规范
|
||||
- 修改现有方法时保持向后兼容性
|
||||
- 定期审查Repository方法的性能和合理性
|
||||
- 及时更新相关文档和测试用例
|
||||
|
||||
---
|
||||
|
||||
*本文档基于ContractBalanceRepository实现经验总结,结合项目实际情况分析,其他Repository实现应参考此文档规范。特别注意避免文档中提到的常见错误。*
|
||||
Reference in New Issue
Block a user