Files
contract-manager/.trae/rules/server_repository_rules.md
songqq 02afa189f8 feat(contract): 新增合同余额功能及重构文件管理
重构合同文件管理逻辑,增加错误处理和日志记录
新增ContractBalance实体、Repository和VO类
完善Voable接口文档和实现规范
更新项目架构文档和数据库设计
修复SmbFileService的连接问题
移动合同相关TabSkin类到contract包
添加合同文件重建任务的WebSocket支持
2025-11-19 00:50:16 +08:00

460 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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实现应参考此文档规范。特别注意避免文档中提到的常见错误。*