feat(contract): 新增合同余额功能及重构文件管理
重构合同文件管理逻辑,增加错误处理和日志记录 新增ContractBalance实体、Repository和VO类 完善Voable接口文档和实现规范 更新项目架构文档和数据库设计 修复SmbFileService的连接问题 移动合同相关TabSkin类到contract包 添加合同文件重建任务的WebSocket支持
This commit is contained in:
@@ -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, TV>`
|
||||
- `T` 表示 VO 类型(实现了 IdentityEntity 接口)
|
||||
- `TV` 表示 ViewModel 类型(实现了 IdentityViewModel<T> 接口)
|
||||
- `QueryService` 实现了 `ViewModelService<T, TV>` 接口
|
||||
- `ViewModelService` 继承了 `IEntityService<T>` 接口
|
||||
- 特定场景下可以不继承 `QueryService`,直接实现所需接口或创建独立服务类
|
||||
---
|
||||
|
||||
## 🏗️ 基础架构
|
||||
|
||||
### 1. 继承层次结构
|
||||
|
||||
```java
|
||||
// Service 接口定义
|
||||
public interface IEntityService<T> {
|
||||
T findById(Integer id);
|
||||
T save(T entity);
|
||||
void delete(T entity);
|
||||
List<T> findAll();
|
||||
Page<T> findAll(Map<String, Object> params, Pageable pageable);
|
||||
StringConverter<T> getStringConverter();
|
||||
}
|
||||
|
||||
// 基础 Service 实现
|
||||
public abstract class QueryService<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
implements ViewModelService<T, TV> {
|
||||
// 核心实现
|
||||
}
|
||||
|
||||
// 具体业务 Service
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "company")
|
||||
public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
|
||||
// 业务方法实现
|
||||
@CacheConfig(cacheNames = "business")
|
||||
public class XxxService extends QueryService<XxxVo, XxxViewModel> {
|
||||
// 业务特定实现
|
||||
}
|
||||
```
|
||||
|
||||
## 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<ContractVo, ContractViewModel> {
|
||||
@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<XxxVo, XxxViewModel> {
|
||||
// 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<T>` 接口定义的核心方法:
|
||||
- `findById(Integer id)`:根据 ID 查询实体
|
||||
- `save(T entity)`:保存实体
|
||||
- `delete(T entity)`:删除实体
|
||||
- `findAll()`:查询所有实体
|
||||
- `findAll(Map<String, Object> params, Pageable pageable)`:条件分页查询
|
||||
- `getStringConverter()`:获取类型转换器
|
||||
- 通常通过继承 `QueryService` 来复用这些基础方法的实现
|
||||
- 可根据业务需求重写或扩展基础方法
|
||||
|
||||
## 8. 业务方法规范
|
||||
- 业务方法应与服务端对应,保持方法名和参数一致
|
||||
- 方法命名应清晰表达其功能,如 `findByName`, `findByCode`
|
||||
- 复杂业务逻辑应封装为独立方法
|
||||
- 参数校验应在方法开始处进行
|
||||
- 返回值类型应明确,避免使用过于泛化的类型
|
||||
|
||||
## 9. 类型转换器
|
||||
- 实现 `getStringConverter()` 方法,返回对应的 StringConverter 实例
|
||||
- 通常创建专用的 Converter 类,如 `CustomerCatalogStringConverter`
|
||||
- 转换器实例应作为服务类的成员变量,避免重复创建
|
||||
### 2. 复杂对象处理
|
||||
|
||||
```java
|
||||
public class CustomerCatalogService extends QueryService<CustomerCatalogVo, CustomerCatalogViewModel> {
|
||||
private final CustomerCatalogStringConverter stringConverter = new CustomerCatalogStringConverter(this);
|
||||
|
||||
@Override
|
||||
public StringConverter<CustomerCatalogVo> getStringConverter() {
|
||||
return stringConverter;
|
||||
public List<XxxDetailVo> 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<XxxDetailVo> 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 注释,说明其功能和参数含义
|
||||
### 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<XxxVo> 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<XxxVo> 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<String, Object> 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
|
||||
Reference in New Issue
Block a user