Compare commits
27 Commits
235269f86f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 643338f4b0 | |||
| 880671a5a9 | |||
| 4e738bea3c | |||
| 3cf3a717be | |||
| e8c8305f40 | |||
| be63ff62a4 | |||
| 18057a657e | |||
| db07befffe | |||
| 94030a5a39 | |||
| 7e59f2c17e | |||
| c8b0d57f22 | |||
| 6eebdb1744 | |||
| 9dc90507cb | |||
| 0c26d329c6 | |||
| e3661630fe | |||
| 5d6fb961b6 | |||
| 72edb07798 | |||
| 330418cfd6 | |||
| c10bd369c0 | |||
| f0e85c5a18 | |||
| a784438e97 | |||
| 02afa189f8 | |||
| 87290f15b0 | |||
| e761990ebf | |||
| 1cb0edbd07 | |||
| dd49c3927a | |||
| 7d4961dae4 |
1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
||||
PLAYWRIGHT_MCP_EXTENSION_TOKEN=TB7T39NhEbruyS7L-E7RXIGYk39PVK7eu1h-WP8M1Cg
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -36,3 +36,5 @@ build/
|
||||
.vscode/
|
||||
|
||||
/config.properties
|
||||
node_modules
|
||||
node_modules
|
||||
|
||||
@@ -155,3 +155,69 @@ public class [业务]WindowController extends AbstEntityController<[Vo类型], [
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 13. TableCell 工厂模式规范
|
||||
|
||||
### 13.1 工厂方法命名
|
||||
|
||||
- TableCell类应提供静态工厂方法`forTableColumn`,用于创建单元格工厂
|
||||
- 方法命名必须为`forTableColumn`,保持一致性
|
||||
|
||||
### 13.2 工厂方法参数
|
||||
|
||||
- 工厂方法应接收服务层参数,如`BankService`
|
||||
- 参数类型应与TableCell构造函数所需的服务类型一致
|
||||
|
||||
### 13.3 工厂方法返回类型
|
||||
|
||||
- 返回类型应为`Callback<javafx.scene.control.TableColumn<V, T>, javafx.scene.control.TableCell<V, T>>`
|
||||
- 其中`V`为ViewModel类型,`T`为单元格值类型
|
||||
|
||||
### 13.4 工厂方法实现
|
||||
|
||||
- 在工厂方法内部,创建并返回TableCell实例
|
||||
- 使用泛型参数确保类型安全
|
||||
|
||||
### 13.5 使用方式
|
||||
|
||||
- 在设置表格列的单元格工厂时,应调用TableCell的静态工厂方法
|
||||
- 避免直接使用`new TableCell<>(service)`的方式创建实例
|
||||
|
||||
### 13.6 示例代码
|
||||
|
||||
```java
|
||||
/**
|
||||
* 银行单元格
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> {
|
||||
/**
|
||||
* 创建单元格工厂
|
||||
*
|
||||
* @param bankService 银行服务
|
||||
* @return 单元格工厂
|
||||
*/
|
||||
public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
|
||||
BankService bankService) {
|
||||
return param -> new BankTableCell<V>(bankService);
|
||||
}
|
||||
|
||||
public BankTableCell(BankService service) {
|
||||
setService(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BankService getServiceBean() {
|
||||
return getBean(BankService.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.7 使用示例
|
||||
|
||||
```java
|
||||
// 推荐方式:使用工厂方法
|
||||
column.setCellFactory(BankTableCell.forTableColumn(getBankService()));
|
||||
|
||||
// 不推荐方式:直接实例化
|
||||
// column.setCellFactory(param -> new BankTableCell<>(getBankService()));
|
||||
@@ -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
|
||||
@@ -0,0 +1,419 @@
|
||||
# 客户端Tasker实现WebSocketClientTasker接口规范
|
||||
|
||||
## 概述
|
||||
|
||||
本文档基于 `ContractRepairAllTasker` 实现 `WebSocketClientTasker` 接口的经验,总结了客户端Tasker类升级为支持WebSocket通信的最佳实践和规范。
|
||||
|
||||
## WebSocketClientTasker接口介绍
|
||||
|
||||
`WebSocketClientTasker` 接口定义了通过WebSocket与服务器通信的任务的通用方法,包括任务名称、消息更新、进度更新等核心功能。
|
||||
|
||||
### 核心方法
|
||||
|
||||
1. **getTaskName()** - 获取任务名称,用于在WebSocket通信中标识任务
|
||||
2. **updateMessage(Level, String)** - 更新任务执行过程中的消息
|
||||
3. **updateTitle(String)** - 更新任务标题
|
||||
4. **updateProgress(long, long)** - 更新任务进度
|
||||
5. **cancelTask()** - 取消任务执行(默认实现为空)
|
||||
6. **callRemoteTask(MessageHolder, Locale, Object...)** - 调用远程WebSocket任务
|
||||
7. **callRemoteTaskAsync(MessageHolder, Locale, Object...)** - 异步调用远程WebSocket任务
|
||||
8. **generateTaskId()** - 生成唯一的任务ID
|
||||
|
||||
### 典型实现模式概览
|
||||
|
||||
通过分析项目中的17个实现类,我们发现了以下典型实现模式:
|
||||
|
||||
1. **标准实现**:继承Tasker<Object>并实现WebSocketClientTasker
|
||||
2. **属性注入**:使用@Setter注解或手动设置属性传递任务参数
|
||||
3. **Spring Bean获取**:通过SpringApp.getBean()获取服务实例
|
||||
4. **消息更新**:简洁的消息更新方式
|
||||
5. **参数传递**:通过callRemoteTask的可变参数传递任务所需数据
|
||||
|
||||
## Tasker实现WebSocketClientTasker最佳实践
|
||||
|
||||
### 1. 类定义和继承
|
||||
|
||||
```java
|
||||
/**
|
||||
* 任务类描述
|
||||
* 用于通过WebSocket与服务器通信执行具体操作
|
||||
*/
|
||||
public class 任务类名 extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(任务类名.class);
|
||||
|
||||
// 实现方法
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 参数传递模式
|
||||
|
||||
#### 2.1 使用@Setter注解注入参数
|
||||
|
||||
```java
|
||||
/**
|
||||
* 更新供应商评价表任务
|
||||
*/
|
||||
public class CompanyVendorEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyVendorEvaluationFormUpdateTask.class);
|
||||
|
||||
@Setter
|
||||
private VendorVo vendor;
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("更新供应商评价表");
|
||||
return callRemoteTask(holder, getLocale(), vendor.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 使用@Getter和@Setter注解
|
||||
|
||||
```java
|
||||
/**
|
||||
* 客户文件重建任务类
|
||||
*/
|
||||
public class CustomerRebuildFilesTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
@Getter
|
||||
@Setter
|
||||
private CustomerVo companyCustomer;
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("重建客户文件");
|
||||
return callRemoteTask(holder, getLocale(), companyCustomer.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 必要方法实现
|
||||
|
||||
#### 2.1 getTaskName()
|
||||
|
||||
```java
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "Task名称"; // 必须与服务器端对应Tasker类名匹配
|
||||
}
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
- 任务名称必须与服务器端对应的Tasker注册名(tasker_mapper.json中的key)保持一致
|
||||
- 名称应简洁明了,反映任务的核心功能
|
||||
|
||||
#### 2.2 updateProgress()
|
||||
updateProgress 方法重载为public,用于外部调用更新进度
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total); // 调用父类方法更新进度
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 updateTitle()
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void updateTitle(String title) {
|
||||
super.updateTitle(title); // 使用Tasker的updateTitle方法更新标题
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 execute() 方法重写
|
||||
|
||||
**标准实现**:
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
logger.info("开始执行任务描述");
|
||||
updateTitle("任务标题");
|
||||
|
||||
// 调用远程任务,可选传入参数
|
||||
Object result = callRemoteTask(holder, getLocale(), 可选参数...);
|
||||
|
||||
logger.info("任务执行完成");
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**简洁实现**(适用于简单任务):
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("更新供应商评价表");
|
||||
return callRemoteTask(holder, getLocale(), vendor.getId());
|
||||
}
|
||||
```
|
||||
|
||||
**带消息更新的实现**:
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 设置任务标题
|
||||
updateTitle("全量库存同步任务");
|
||||
|
||||
// 更新任务消息
|
||||
updateMessage("开始执行全量库存同步...");
|
||||
|
||||
// 调用远程WebSocket任务
|
||||
return callRemoteTask(holder, getLocale());
|
||||
}
|
||||
```
|
||||
|
||||
**关键步骤**:
|
||||
1. 记录任务开始日志(可选)
|
||||
2. 设置任务标题
|
||||
3. 可选:添加任务开始消息
|
||||
4. 调用远程任务执行核心逻辑,传入必要参数
|
||||
5. 记录任务完成日志(可选)
|
||||
6. 返回执行结果
|
||||
|
||||
### 3. 日志记录
|
||||
|
||||
```java
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// 在类中定义
|
||||
private static final Logger logger = LoggerFactory.getLogger(任务类名.class);
|
||||
|
||||
// 在关键位置使用日志
|
||||
logger.info("任务开始");
|
||||
logger.warn("警告信息");
|
||||
logger.error("错误信息", exception);
|
||||
```
|
||||
|
||||
**日志使用建议**:
|
||||
- 复杂任务建议记录详细日志
|
||||
- 简单任务可以简化或省略日志记录
|
||||
- 确保异常情况下有适当的错误日志记录
|
||||
|
||||
### 4. 异常处理
|
||||
|
||||
在`execute`方法中应妥善处理可能的异常,并通过MessageHolder通知用户:
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
try {
|
||||
// 任务执行逻辑
|
||||
} catch (Exception e) {
|
||||
logger.error("任务执行失败", e);
|
||||
holder.addMessage(Level.SEVERE, "任务执行失败: " + e.getMessage());
|
||||
throw e; // 向上抛出异常,让框架处理
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**异常处理策略**:
|
||||
- 对于简单任务,可以依赖框架的异常处理机制
|
||||
- 对于复杂任务,建议添加自定义的异常处理逻辑
|
||||
- 确保异常信息对用户友好且具有足够的调试信息
|
||||
|
||||
## 完整实现示例
|
||||
|
||||
### 示例1:简单任务实现
|
||||
|
||||
```java
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
|
||||
/**
|
||||
* 合同修复任务类
|
||||
* 用于通过WebSocket与服务器通信执行合同数据修复操作
|
||||
*/
|
||||
public class ContractRepairAllTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContractRepairAllTasker.class);
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTitle(String title) {
|
||||
// 使用Tasker的updateTitle方法更新标题
|
||||
super.updateTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "ContractRepairAllTask"; // 与服务器端对应Tasker类名匹配
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
logger.info("开始执行合同修复任务");
|
||||
updateTitle("合同数据修复");
|
||||
Object result = callRemoteTask(holder, getLocale());
|
||||
logger.info("合同修复任务执行完成");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:带参数的任务实现
|
||||
|
||||
```java
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.vo.VendorVo;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 更新供应商评价表
|
||||
*/
|
||||
public class CompanyVendorEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyVendorEvaluationFormUpdateTask.class);
|
||||
@Setter
|
||||
private VendorVo vendor;
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "CompanyVendorEvaluationFormUpdateTask"; // 与服务器端对应Tasker类名匹配
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("更新供应商评价表");
|
||||
return callRemoteTask(holder, getLocale(), vendor.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例3:使用Spring Bean的任务实现
|
||||
|
||||
```java
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.service.YongYouU8Service;
|
||||
|
||||
/**
|
||||
* 合同同步任务
|
||||
*/
|
||||
public class ContractSyncTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContractSyncTask.class);
|
||||
|
||||
private YongYouU8Service yongYouU8Service;
|
||||
|
||||
private YongYouU8Service getYongYouU8Service() {
|
||||
if (yongYouU8Service == null) {
|
||||
yongYouU8Service = SpringApp.getBean(YongYouU8Service.class);
|
||||
}
|
||||
return yongYouU8Service;
|
||||
}
|
||||
|
||||
public String getTaskName() {
|
||||
return "ContractSyncTask"; // 与服务器端对应Tasker类名匹配
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("用友U8系统-同步合同");
|
||||
return callRemoteTask(holder, getLocale());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项和最佳实践
|
||||
|
||||
### 1. 命名规范
|
||||
- 任务类名应采用驼峰命名法,以`Tasker`结尾或描述性名称如`Task`
|
||||
- getTaskName()返回的名称应与服务器端对应Tasker类名完全匹配
|
||||
- 类注释应清晰描述任务的功能和用途
|
||||
|
||||
### 2. 继承关系
|
||||
- 必须同时继承Tasker类并实现WebSocketClientTasker接口
|
||||
- Tasker泛型参数通常为Object
|
||||
- 确保正确导入所有必要的包
|
||||
|
||||
### 3. 参数处理
|
||||
- 对于需要参数的任务,使用@Setter注解简化属性设置
|
||||
- 对于需要在多处使用的参数,考虑添加@Getter注解
|
||||
- 确保参数验证(如果必要)
|
||||
|
||||
### 4. Spring Bean获取
|
||||
- 使用SpringApp.getBean()获取所需的服务实例
|
||||
- 考虑使用懒加载模式,避免不必要的Bean初始化
|
||||
|
||||
### 5. 消息和进度更新
|
||||
- 使用updateTitle()设置有意义的任务标题
|
||||
- 通过MessageHolder或updateMessage()记录详细的执行消息
|
||||
- 确保进度更新反映真实的执行进度
|
||||
|
||||
### 6. 异常处理
|
||||
- 在关键操作处添加try-catch块
|
||||
- 记录异常日志并通知用户
|
||||
- 适当向上抛出异常以确保框架能正确处理
|
||||
|
||||
### 7. 日志级别使用
|
||||
- INFO: 记录正常的操作流程
|
||||
- WARNING: 记录可能的问题,但不影响继续执行
|
||||
- ERROR: 记录严重错误,通常会终止执行
|
||||
|
||||
### 8. 远程调用参数
|
||||
- 确保传入的参数类型与服务器端Tasker期望的一致
|
||||
- 对于不需要参数的任务,可以不传入额外参数
|
||||
- 对于需要多个参数的任务,确保参数顺序正确
|
||||
|
||||
### 9. 代码风格
|
||||
- 保持代码简洁明了
|
||||
- 遵循项目的代码格式化规范
|
||||
- 添加必要的注释说明核心逻辑
|
||||
|
||||
### 10. 实现策略选择
|
||||
- 简单任务:使用简洁的实现方式,省略不必要的日志
|
||||
- 复杂任务:添加详细的日志记录和异常处理
|
||||
- 有特定需求的任务:根据需要重写接口中的其他方法
|
||||
|
||||
## 与服务器端交互流程
|
||||
|
||||
1. 客户端Tasker通过callRemoteTask()方法提交任务
|
||||
2. WebSocketClientService负责建立与服务器的连接并发送任务信息
|
||||
3. 服务器接收到任务后,创建对应的Tasker实例并执行
|
||||
4. 执行过程中的消息、进度等通过WebSocket实时返回给客户端
|
||||
5. 客户端Tasker通过updateMessage()、updateProgress()等方法更新UI
|
||||
|
||||
## 扩展和自定义
|
||||
|
||||
如需为特定任务提供自定义功能,可以:
|
||||
|
||||
1. 重写cancelTask()方法实现任务取消逻辑
|
||||
2. 根据需要添加额外的字段和方法
|
||||
3. 扩展execute()方法实现更复杂的任务流程
|
||||
|
||||
## 总结
|
||||
|
||||
通过分析项目中的17个WebSocketClientTasker实现类,我们总结了客户端Tasker实现的多种模式和最佳实践。这些实现从简单到复杂,涵盖了各种使用场景,为后续Tasker的编写提供了全面的参考。
|
||||
|
||||
客户端Tasker类实现WebSocketClientTasker接口是实现与服务器实时通信的关键步骤。通过遵循本文档中的规范和最佳实践,可以确保任务执行的可靠性、进度的实时更新和良好的用户体验。
|
||||
|
||||
在实际开发中,应根据任务的复杂度和具体需求,选择合适的实现模式和策略,同时保持代码的一致性和可维护性。
|
||||
374
.trae/rules/repository_comprehensive_analysis_report.md
Normal file
374
.trae/rules/repository_comprehensive_analysis_report.md
Normal file
@@ -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<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface ContractFileRepository extends MyRepository<ContractFile, Integer> {
|
||||
// 自动获得MyRepository提供的所有功能
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. ContractItemRepository
|
||||
```java
|
||||
// ❌ 当前错误实现
|
||||
public interface ContractItemRepository
|
||||
extends JpaRepository<ContractItem, Integer>, JpaSpecificationExecutor<ContractItem> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface ContractItemRepository extends MyRepository<ContractItem, Integer> {
|
||||
// 统一继承结构,简化维护
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. VendorFileRepository
|
||||
```java
|
||||
// ❌ 当前错误实现
|
||||
@Repository
|
||||
public interface VendorFileRepository
|
||||
extends JpaRepository<VendorFile, Integer>, JpaSpecificationExecutor<VendorFile> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
|
||||
// 保持注解的同时修正继承结构
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. ProjectQuotationRepository
|
||||
```java
|
||||
// ❌ 当前错误实现
|
||||
@Repository
|
||||
public interface ProjectQuotationRepository
|
||||
extends JpaRepository<ProjectQuotation, Integer>, JpaSpecificationExecutor<ProjectQuotation> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface ProjectQuotationRepository extends MyRepository<ProjectQuotation, Integer> {
|
||||
// 获得MyRepository的所有增强功能
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. PurchaseOrderRepository
|
||||
```java
|
||||
// ❌ 当前错误实现(继承4个冗余接口)
|
||||
public interface PurchaseOrderRepository extends
|
||||
CrudRepository<PurchaseOrder, Integer>,
|
||||
PagingAndSortingRepository<PurchaseOrder, Integer>,
|
||||
JpaRepository<PurchaseOrder, Integer>,
|
||||
JpaSpecificationExecutor<PurchaseOrder> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface PurchaseOrderRepository extends MyRepository<PurchaseOrder, Integer> {
|
||||
// 单一继承,清晰简洁
|
||||
}
|
||||
```
|
||||
|
||||
### 🟡 优先级2:修复注解缺失
|
||||
|
||||
#### CompanyFileRepository
|
||||
```java
|
||||
// ❌ 当前缺少注解
|
||||
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
|
||||
|
||||
// ✅ 添加注解
|
||||
@Repository
|
||||
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
|
||||
// 添加完整的JavaDoc注释
|
||||
/**
|
||||
* 公司文件数据访问接口
|
||||
* 提供公司相关文件的CRUD操作和业务查询功能
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
#### ProjectFileRepository
|
||||
```java
|
||||
// ❌ 当前缺少注解
|
||||
public interface ProjectFileRepository extends MyRepository<ProjectFile, Integer> {
|
||||
|
||||
// ✅ 添加注解
|
||||
@Repository
|
||||
public interface ProjectFileRepository extends MyRepository<ProjectFile, Integer> {
|
||||
/**
|
||||
* 项目文件数据访问接口
|
||||
* 提供项目文件的管理和查询功能
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
### 🟡 优先级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年
|
||||
**分析深度**: 全面技术分析
|
||||
**实施可行性**: 高(详细修复方案已提供)
|
||||
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实现应参考此文档规范。特别注意避免文档中提到的常见错误。*
|
||||
@@ -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<M, Vo, ID>**: 通用实体服务基类,提供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<T>**: 实体基本操作接口
|
||||
- **QueryService<Vo>**: 查询服务接口
|
||||
- **VoableService<M, Vo>**: 实体与视图对象转换服务接口
|
||||
|
||||
## 3. 命名规范
|
||||
## 注解使用规范
|
||||
|
||||
- **类名**:采用驼峰命名法,首字母大写,以 `Service` 结尾,表示这是一个服务类。
|
||||
**示例**:`CustomerCatalogService`、`EmployeeService`
|
||||
### 类级别注解
|
||||
```java
|
||||
@Lazy // 延迟加载,避免循环依赖
|
||||
@Service // Spring服务组件
|
||||
@CacheConfig(cacheNames = "业务缓存名称") // 缓存配置
|
||||
public class CompanyService extends EntityService<Company, CompanyVo, Integer>
|
||||
implements IEntityService<Company>, QueryService<CompanyVo>, VoableService<Company, CompanyVo> {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
## 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<T>
|
||||
// 修改方法缓存清理 - 清理所有相关缓存
|
||||
@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<T> findAll(Specification<T> spec, Pageable pageable)`:根据条件和分页参数查询实体列表
|
||||
- `Specification<T> getSpecification(String searchText)`:构建搜索条件
|
||||
- `void delete(T entity)`:删除实体
|
||||
- `T save(T entity)`:保存实体
|
||||
### Repository注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyRepository repository;
|
||||
```
|
||||
|
||||
### 4.2 QueryService<Vo>
|
||||
### Service间依赖注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private ContractService contractService;
|
||||
|
||||
提供 VO 对象的查询能力。泛型 `Vo` 表示视图对象类型。
|
||||
@Lazy
|
||||
@Autowired
|
||||
private VendorService vendorService;
|
||||
|
||||
**主要方法:**
|
||||
- `Vo findById(Integer id)`:根据 ID 查询 VO 对象
|
||||
- `Page<Vo> findAll(JsonNode paramsNode, Pageable pageable)`:根据 JSON 查询参数和分页条件查询 VO 列表
|
||||
- `default long count(JsonNode paramsNode)`:根据查询参数统计数据总数
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyContactService companyContactService;
|
||||
```
|
||||
|
||||
### 4.3 VoableService<M, Vo>
|
||||
### 外部服务依赖注入
|
||||
```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<CompanyVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
Specification<Company> 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<Company> 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<Vendor> getSpecification(String searchText) {
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用公司关联查询
|
||||
Specification<Vendor> nameSpec = (root, query, builder) -> {
|
||||
Path<Company> 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<VendorEntity> 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<CustomerCatalog>, QueryService<CustomerCatalogVo>,
|
||||
VoableService<CustomerCatalog, CustomerCatalogVo> {
|
||||
// 实现方法...
|
||||
@CacheConfig(cacheNames = "contract")
|
||||
public class ContractService extends EntityService<Contract, ContractVo, Integer>
|
||||
implements IEntityService<Contract>, QueryService<ContractVo>, VoableService<Contract, ContractVo> {
|
||||
|
||||
@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<Contract> 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<T>, Pageable)`:直接调用 Repository 的 `findAll` 方法,返回实体分页对象
|
||||
```java
|
||||
@Override
|
||||
public Page<CustomerCatalog> findAll(Specification<CustomerCatalog> 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<CustomerCatalogVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
Specification<CustomerCatalog> 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<CustomerCatalog> 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<Company, Integer>
|
||||
implements IEntityService<Company>, QueryService<Company>, VoableService<Company, CompanyVo> {
|
||||
// 实现方法...
|
||||
}
|
||||
@CacheConfig(cacheNames = "company-customer")
|
||||
public class CustomerService extends CompanyBasicService
|
||||
implements IEntityService<CompanyCustomer>, QueryService<CustomerVo>,
|
||||
VoableService<CompanyCustomer, CustomerVo> {
|
||||
|
||||
// 错误:未使用缓存注解
|
||||
@Service
|
||||
public class ExampleService implements IEntityService<Example>, QueryService<ExampleVo>,
|
||||
VoableService<Example, ExampleVo> {
|
||||
// 未使用@Cacheable、@CacheEvict等缓存注解
|
||||
// 提供公司关联查询
|
||||
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<CompanyCustomerFile> dbFiles = companyCustomerFileService.findAllByCustomer(companyCustomer);
|
||||
Map<String, CompanyCustomerFile> map = new HashMap<>();
|
||||
|
||||
boolean modified = fetchDbFiles(dbFiles, map, holder::info);
|
||||
|
||||
List<File> 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 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。
|
||||
### 文件路径管理
|
||||
```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<T>进行分页查询,避免一次性加载大量数据:
|
||||
```java
|
||||
public Page<Project> findAll(Specification<Project> spec, Pageable pageable) {
|
||||
return projectRepository.findAll(spec, pageable);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 批量操作
|
||||
对于大量数据操作,考虑使用批量处理:
|
||||
```java
|
||||
public void saveAll(List<CompanyCustomerFile> 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<Company> companies = repository.findAllByUniscid(uniscid);
|
||||
if (companies.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (companies.size() == 1) {
|
||||
return companies.getFirst();
|
||||
} else {
|
||||
List<Company> 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层的代码质量、性能和可维护性,为整个系统的稳定运行提供了坚实基础。
|
||||
460
.trae/rules/server_task_rules.md
Normal file
460
.trae/rules/server_task_rules.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# WebSocketServerTasker 接口实现规范
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档总结了实现 `WebSocketServerTasker` 接口的标准做法和最佳实践,基于对项目中已有实现类的分析。该接口用于服务器端实现支持WebSocket通信的任务处理器,实现异步任务的执行和状态监控。
|
||||
|
||||
## 2. WebSocketServerTasker 接口介绍
|
||||
|
||||
`WebSocketServerTasker` 接口位于 `com.ecep.contract.service.tasker` 包下,继承自 `java.util.concurrent.Callable<Object>`,定义了以下核心方法:
|
||||
|
||||
```java
|
||||
public interface WebSocketServerTasker extends Callable<Object> {
|
||||
// 初始化任务,处理传入的参数
|
||||
void init(JsonNode argsNode);
|
||||
|
||||
// 设置会话信息(默认实现为空)
|
||||
default void setSession(SessionInfo session) {}
|
||||
|
||||
// 设置消息处理函数
|
||||
void setMessageHandler(Predicate<Message> messageHandler);
|
||||
|
||||
// 设置标题处理函数
|
||||
void setTitleHandler(Predicate<String> titleHandler);
|
||||
|
||||
// 设置属性处理函数
|
||||
void setPropertyHandler(BiConsumer<String, Object> propertyHandler);
|
||||
|
||||
// 设置进度处理函数
|
||||
void setProgressHandler(BiConsumer<Long, Long> progressHandler);
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 基础实现模式
|
||||
|
||||
### 3.1 推荐实现方式
|
||||
|
||||
通过分析项目中的实现类,推荐以下标准实现模式:
|
||||
|
||||
1. **继承 Tasker<Object> 并实现 WebSocketServerTasker 接口**
|
||||
|
||||
```java
|
||||
public class YourTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
2. **在 Tasker 基类中已实现的方法**
|
||||
|
||||
`Tasker` 类已经提供了 `WebSocketServerTasker` 接口中大部分方法的实现,包括:
|
||||
- `setMessageHandler`
|
||||
- `setTitleHandler`
|
||||
- `setPropertyHandler`
|
||||
- `setProgressHandler`
|
||||
- `call()` 方法(实现了 `Callable` 接口)
|
||||
|
||||
这使得实现类只需要关注特定业务逻辑的实现。
|
||||
|
||||
## 3.2 WebSocketServerTasker 注册配置
|
||||
|
||||
要使WebSocketServerTasker能够被客户端通过WebSocket调用,必须在`tasker_mapper.json`配置文件中进行注册。
|
||||
|
||||
### 3.2.1 注册配置文件
|
||||
|
||||
配置文件位置:`server/src/main/resources/tasker_mapper.json`
|
||||
|
||||
### 3.2.2 注册格式
|
||||
|
||||
注册格式为JSON对象,包含一个`tasks`字段,该字段是一个键值对映射,其中:
|
||||
- **键**:任务名称(客户端通过此名称调用任务)
|
||||
- **值**:任务类的完全限定名
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": {
|
||||
"任务名称": "任务类的完全限定名",
|
||||
"ContractVerifyTasker": "com.ecep.contract.service.tasker.ContractVerifyTasker"
|
||||
},
|
||||
"descriptions": "任务注册信息, 客户端的任务可以通过 WebSocket 调用"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2.3 注册示例
|
||||
|
||||
假设我们创建了一个名为`CustomTasker`的新任务类,其完全限定名为`com.ecep.contract.service.tasker.CustomTasker`,则注册方式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": {
|
||||
"CustomTasker": "com.ecep.contract.service.tasker.CustomTasker",
|
||||
"OtherTasker": "..."
|
||||
},
|
||||
"descriptions": "任务注册信息, 客户端的任务可以通过 WebSocket 调用"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2.4 注册机制说明
|
||||
|
||||
WebSocketServerTaskManager类在启动时会读取`tasker_mapper.json`文件,初始化任务类映射表。当客户端请求执行任务时,系统会:
|
||||
|
||||
1. 根据客户端提供的任务名称从映射表中查找对应的任务类
|
||||
2. 使用反射机制实例化任务对象
|
||||
3. 设置WebSocket会话和各类处理器
|
||||
4. 执行任务并通过WebSocket返回结果
|
||||
|
||||
## 4. ContractRepairAllTasker 升级经验
|
||||
|
||||
通过分析 `ContractRepairAllTasker` 的实现,我们总结了以下升级经验:
|
||||
|
||||
### 4.1 升级步骤
|
||||
|
||||
1. **继承适当的基础类**:根据业务需求选择继承 `Tasker<Object>` 或其他特定的抽象类(如 `AbstContractRepairTasker`)
|
||||
|
||||
2. **实现 WebSocketServerTasker 接口**:声明实现该接口,自动继承接口定义的方法
|
||||
|
||||
3. **实现 init 方法**:处理任务初始化和参数解析
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 解析参数或初始化任务状态
|
||||
// 如果 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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **实现或重写 execute 方法**:提供具体的业务逻辑实现
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 调用父类初始化
|
||||
super.execute(holder);
|
||||
updateTitle("同步修复所有合同");
|
||||
// 执行具体业务逻辑
|
||||
repair(holder);
|
||||
return null; // 通常返回 null 或处理结果
|
||||
}
|
||||
```
|
||||
|
||||
5. **使用更新方法提供状态反馈**:在执行过程中使用 `updateTitle`、`updateProgress` 等方法提供实时反馈
|
||||
|
||||
```java
|
||||
updateProgress(counter.incrementAndGet(), total);
|
||||
```
|
||||
|
||||
6. **支持任务取消**:定期检查 `isCancelled()` 方法,支持任务的取消操作
|
||||
|
||||
```java
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 关键经验
|
||||
|
||||
1. **消息处理**:通过 `MessageHolder` 处理和记录执行过程中的消息,方便调试和错误追踪
|
||||
|
||||
2. **进度更新**:对于批量操作,使用计数器和总数定期更新进度,提供良好的用户体验
|
||||
|
||||
3. **异常处理**:在循环处理中捕获异常并记录,确保单个项目的失败不会导致整个任务失败
|
||||
|
||||
4. **分页处理**:对于大量数据的处理,使用分页查询避免内存溢出
|
||||
|
||||
## 5. 共同模式和最佳实践
|
||||
|
||||
通过分析项目中的17个实现类,我们总结了以下共同模式和最佳实践:
|
||||
|
||||
### 5.1 共同模式
|
||||
|
||||
1. **继承结构**:所有实现类都继承 `Tasker<Object>` 并实现 `WebSocketServerTasker` 接口
|
||||
|
||||
2. **核心方法实现**:
|
||||
- 实现 `init(JsonNode argsNode)` 方法处理参数
|
||||
- 实现或重写 `execute(MessageHolder holder)` 方法实现业务逻辑
|
||||
|
||||
3. **状态更新**:使用 `updateTitle`、`updateMessage`、`updateProgress` 等方法更新任务状态
|
||||
|
||||
4. **Bean获取**:使用 `getCachedBean` 方法获取Spring管理的Bean
|
||||
|
||||
```java
|
||||
ContractService contractService = getCachedBean(ContractService.class);
|
||||
```
|
||||
|
||||
### 5.2 最佳实践
|
||||
|
||||
1. **参数验证**:在 `init` 方法中验证和转换输入参数
|
||||
|
||||
2. **进度反馈**:对于长时间运行的任务,提供合理的进度更新
|
||||
|
||||
3. **异常处理**:捕获并处理特定异常,提供有意义的错误信息
|
||||
|
||||
4. **取消支持**:实现任务取消机制,定期检查 `isCancelled()` 状态
|
||||
|
||||
5. **资源清理**:在任务结束或取消时清理资源
|
||||
|
||||
6. **日志记录**:使用 `slf4j` 记录关键操作和异常信息
|
||||
|
||||
7. **属性传递**:使用 `updateProperty` 方法传递任务执行结果或状态
|
||||
|
||||
```java
|
||||
updateProperty("passed", passed);
|
||||
```
|
||||
|
||||
## 6. 完整实现示例
|
||||
|
||||
### 6.1 简单任务实现示例
|
||||
|
||||
```java
|
||||
package com.ecep.contract.service.tasker;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.ui.Tasker;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class SimpleTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
@Getter
|
||||
@Setter
|
||||
private int entityId;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean success = false;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 解析参数
|
||||
this.entityId = argsNode.get(0).asInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 更新标题
|
||||
updateTitle("执行简单任务:" + entityId);
|
||||
|
||||
try {
|
||||
// 更新进度
|
||||
updateProgress(1, 3);
|
||||
|
||||
// 执行业务逻辑
|
||||
ContractService service = getCachedBean(ContractService.class);
|
||||
updateProgress(2, 3);
|
||||
|
||||
// 处理结果
|
||||
success = true;
|
||||
holder.info("任务执行成功");
|
||||
|
||||
// 更新最终进度
|
||||
updateProgress(3, 3);
|
||||
|
||||
// 更新结果属性
|
||||
updateProperty("success", success);
|
||||
} catch (Exception e) {
|
||||
holder.error("任务执行失败: " + e.getMessage());
|
||||
success = false;
|
||||
updateProperty("success", success);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 批量处理任务实现示例
|
||||
|
||||
```java
|
||||
package com.ecep.contract.service.tasker;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.ds.contract.model.Contract;
|
||||
import com.ecep.contract.ui.Tasker;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class BatchProcessTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
private ContractService contractService;
|
||||
private List<Integer> entityIds;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 初始化服务引用
|
||||
this.contractService = getCachedBean(ContractService.class);
|
||||
|
||||
// 解析实体ID列表
|
||||
entityIds = new ArrayList<>();
|
||||
for (JsonNode node : argsNode) {
|
||||
entityIds.add(node.asInt());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("批量处理实体任务");
|
||||
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
long total = entityIds.size();
|
||||
|
||||
for (Integer id : entityIds) {
|
||||
// 检查是否取消
|
||||
if (isCancelled()) {
|
||||
holder.info("任务已取消");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
MessageHolder subHolder = holder.sub("处理 ID: " + id + " > ");
|
||||
|
||||
// 执行业务逻辑
|
||||
Contract entity = contractService.getById(id);
|
||||
// 处理实体...
|
||||
|
||||
subHolder.info("处理成功");
|
||||
} catch (Exception e) {
|
||||
holder.error("处理 ID: " + id + " 失败: " + e.getMessage());
|
||||
// 记录异常但继续处理下一个
|
||||
}
|
||||
|
||||
// 更新进度
|
||||
updateProgress(counter.incrementAndGet(), total);
|
||||
}
|
||||
|
||||
updateTitle("批量处理完成");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 带有依赖的任务实现示例
|
||||
|
||||
```java
|
||||
package com.ecep.contract.service.tasker;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.ds.customer.service.CustomerService;
|
||||
import com.ecep.contract.ui.Tasker;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class DependentTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
@Getter
|
||||
@Setter
|
||||
private int contractId;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean processed = false;
|
||||
|
||||
private ContractService contractService;
|
||||
private CustomerService customerService;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 解析参数
|
||||
this.contractId = argsNode.get(0).asInt();
|
||||
|
||||
// 获取服务依赖
|
||||
this.contractService = getCachedBean(ContractService.class);
|
||||
this.customerService = getCachedBean(CustomerService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("执行带有服务依赖的任务");
|
||||
|
||||
updateProgress(1, 5);
|
||||
|
||||
// 使用多个服务协同完成任务
|
||||
try {
|
||||
// 步骤1: 获取合同信息
|
||||
var contract = contractService.getById(contractId);
|
||||
updateProgress(2, 5);
|
||||
|
||||
// 步骤2: 获取相关客户信息
|
||||
var customer = customerService.getById(contract.getCustomerId());
|
||||
updateProgress(3, 5);
|
||||
|
||||
// 步骤3: 执行业务逻辑
|
||||
// ...
|
||||
updateProgress(4, 5);
|
||||
|
||||
processed = true;
|
||||
holder.info("任务处理成功");
|
||||
|
||||
// 更新结果属性
|
||||
updateProperty("processed", processed);
|
||||
} catch (Exception e) {
|
||||
holder.error("任务处理失败: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
|
||||
updateProgress(5, 5);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 最佳实践总结
|
||||
|
||||
1. **继承 Tasker<Object> 并实现 WebSocketServerTasker 接口**:利用已有的基础实现
|
||||
|
||||
2. **合理实现 init 方法**:处理参数初始化,避免在 execute 中重复解析
|
||||
|
||||
3. **实现清晰的 execute 逻辑**:
|
||||
- 开始时设置任务标题
|
||||
- 过程中定期更新进度
|
||||
- 提供详细的消息反馈
|
||||
- 结束时更新最终状态
|
||||
|
||||
4. **使用 Spring Bean**:通过 `getCachedBean` 获取需要的服务依赖
|
||||
|
||||
5. **支持任务取消**:在关键循环中检查 `isCancelled()` 状态
|
||||
|
||||
6. **异常处理**:捕获异常并记录,提供有意义的错误信息
|
||||
|
||||
7. **进度反馈**:对于长时间运行的任务,提供合理的进度更新频率
|
||||
|
||||
8. **属性传递**:使用 `updateProperty` 方法传递任务结果给调用方
|
||||
|
||||
9. **日志记录**:使用 slf4j 记录关键操作和异常
|
||||
|
||||
10. **资源管理**:确保在任务完成或取消时释放相关资源
|
||||
|
||||
## 8. 注意事项
|
||||
|
||||
1. **不要在构造函数中获取 Spring Bean**:使用 `init` 方法或懒加载方式获取
|
||||
|
||||
2. **避免阻塞操作**:长时间阻塞的操作应考虑异步处理
|
||||
|
||||
3. **线程安全**:注意多线程环境下的并发访问问题
|
||||
|
||||
4. **内存管理**:对于处理大量数据的任务,注意内存使用,避免OOM
|
||||
|
||||
5. **超时处理**:考虑设置合理的超时机制
|
||||
|
||||
6. **事务管理**:根据需要考虑事务的范围和传播行为
|
||||
|
||||
7. **任务注册**:创建新的WebSocketServerTasker后,必须在`tasker_mapper.json`文件中注册,否则客户端将无法调用
|
||||
|
||||
8. **任务名称唯一性**:确保任务名称在`tasker_mapper.json`中唯一,避免名称冲突
|
||||
|
||||
9. **类路径正确**:注册时确保使用正确的类完全限定名,否则会导致实例化失败
|
||||
|
||||
10. **重启服务**:修改`tasker_mapper.json`后,需要重启服务才能使新的注册生效
|
||||
|
||||
通过遵循这些规范和最佳实践,可以确保实现的 `WebSocketServerTasker` 类具有良好的可维护性、可扩展性和用户体验。
|
||||
182
.trae/rules/tasker_implementation_guide.md
Normal file
182
.trae/rules/tasker_implementation_guide.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Tasker实现规范指南
|
||||
|
||||
## 1. Tasker架构概述
|
||||
|
||||
Tasker是系统中用于执行异步任务的核心框架,主要用于处理耗时操作并提供实时进度反馈。Tasker架构包括:
|
||||
|
||||
- **Tasker基类**:提供任务执行的基础框架和通信功能
|
||||
- **WebSocketServerTasker接口**:定义WebSocket通信任务的规范
|
||||
- **MessageHolder**:用于在任务执行过程中传递消息和更新进度
|
||||
|
||||
## 2. 服务器端Tasker实现规范
|
||||
|
||||
### 2.1 基本结构
|
||||
|
||||
服务器端Tasker实现应遵循以下结构:
|
||||
|
||||
```java
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class YourTaskNameTask extends Tasker<Object> implements WebSocketServerTasker {
|
||||
// 服务依赖
|
||||
@Setter
|
||||
private YourService service;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 初始化任务标题
|
||||
updateTitle("任务标题");
|
||||
// 初始化参数
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 实现业务逻辑
|
||||
// 使用holder.info()、holder.error()等方法反馈消息
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 关键注意事项
|
||||
|
||||
1. **继承与实现**:
|
||||
- 必须继承`Tasker<Object>`基类
|
||||
- 必须实现`WebSocketServerTasker`接口
|
||||
|
||||
2. **方法实现**:
|
||||
- **不要重复实现**Tasker基类已提供的handler设置方法:`setMessageHandler`、`setTitleHandler`、`setPropertyHandler`、`setProgressHandler`
|
||||
- 必须实现`init`方法,设置任务标题和初始化参数
|
||||
- 必须实现`execute`方法,实现具体业务逻辑
|
||||
|
||||
3. **进度和消息反馈**:
|
||||
- 使用`updateProgress`方法提供进度更新
|
||||
- 使用`MessageHolder`的`info`、`error`、`debug`等方法提供消息反馈
|
||||
|
||||
4. **任务取消**:
|
||||
- 在适当的地方检查`isCancelled()`状态,支持任务取消
|
||||
|
||||
5. **注册要求**:
|
||||
- 任务类必须在`tasker_mapper.json`中注册
|
||||
|
||||
## 3. 客户端Tasker实现规范
|
||||
|
||||
客户端Tasker实现应遵循:
|
||||
|
||||
```java
|
||||
public class YourTaskNameTask extends Tasker<Object> {
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 客户端任务逻辑
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 通信规范
|
||||
|
||||
### 4.1 命名规范
|
||||
|
||||
- 客户端和服务器端对应的Tasker类应使用**相同的类名**
|
||||
- 示例:客户端的`ContractSyncAllTask`对应服务器端的`ContractSyncAllTask`
|
||||
|
||||
### 4.2 消息传递
|
||||
|
||||
- 使用`MessageHolder`进行消息传递
|
||||
- 支持不同级别的消息:INFO、ERROR、DEBUG
|
||||
|
||||
## 5. 最佳实践
|
||||
|
||||
1. **任务拆分**:
|
||||
- 将大型任务拆分为多个小任务,提高可维护性
|
||||
|
||||
2. **异常处理**:
|
||||
- 捕获并正确处理异常,使用`holder.error()`提供详细错误信息
|
||||
|
||||
3. **资源管理**:
|
||||
- 确保及时释放资源,特别是在任务取消的情况下
|
||||
|
||||
4. **日志记录**:
|
||||
- 使用`holder.debug()`记录调试信息
|
||||
- 使用`holder.info()`记录进度信息
|
||||
|
||||
5. **状态检查**:
|
||||
- 定期检查`isCancelled()`状态,确保任务可以被及时取消
|
||||
|
||||
## 6. 代码示例
|
||||
|
||||
### 6.1 服务器端Tasker示例
|
||||
|
||||
```java
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ExampleSyncTask extends Tasker<Object> implements WebSocketServerTasker {
|
||||
@Setter
|
||||
private ExampleService exampleService;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
updateTitle("示例同步任务");
|
||||
// 从argsNode初始化参数
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
try {
|
||||
// 获取总记录数
|
||||
long total = exampleService.getTotalCount();
|
||||
holder.info("开始同步数据,共" + total + "条记录");
|
||||
|
||||
// 分批处理
|
||||
long processed = 0;
|
||||
List<ExampleEntity> entities = exampleService.getAllEntities();
|
||||
|
||||
for (ExampleEntity entity : entities) {
|
||||
// 检查是否取消
|
||||
if (isCancelled()) {
|
||||
holder.info("任务已取消");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 处理单个实体
|
||||
exampleService.processEntity(entity);
|
||||
processed++;
|
||||
|
||||
// 更新进度
|
||||
updateProgress(processed, total);
|
||||
if (processed % 100 == 0) {
|
||||
holder.info("已处理" + processed + "/" + total + "条记录");
|
||||
}
|
||||
}
|
||||
|
||||
holder.info("同步完成,共处理" + processed + "条记录");
|
||||
return processed;
|
||||
} catch (Exception e) {
|
||||
holder.error("同步失败: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 常见错误和避免方法
|
||||
|
||||
1. **重复实现handler方法**:
|
||||
- 错误:在子类中重复实现setMessageHandler等方法
|
||||
- 解决:删除子类中的这些方法,直接使用Tasker基类提供的实现
|
||||
|
||||
2. **缺少任务注册**:
|
||||
- 错误:任务类没有在tasker_mapper.json中注册
|
||||
- 解决:确保所有服务器端Tasker类都在配置文件中正确注册
|
||||
|
||||
3. **类名不匹配**:
|
||||
- 错误:客户端和服务器端Tasker类名不一致
|
||||
- 解决:确保两端使用相同的类名
|
||||
|
||||
4. **进度更新不正确**:
|
||||
- 错误:进度计算错误或没有更新
|
||||
- 解决:正确计算和更新进度,提高用户体验
|
||||
|
||||
5. **资源泄漏**:
|
||||
- 错误:未及时关闭数据库连接等资源
|
||||
- 解决:使用try-with-resources或在finally块中关闭资源
|
||||
@@ -178,8 +178,11 @@ Contract-Manager/
|
||||
2. 配置连接到服务端的WebSocket地址
|
||||
3. 打包为可执行jar或使用jpackage创建安装包
|
||||
|
||||
## 开发环境要求
|
||||
## Tasker实现规范指南
|
||||
|
||||
有关Tasker框架的详细实现规范,请参阅 [Tasker实现规范指南](docs/task/tasker_implementation_guide.md)。
|
||||
|
||||
## 开发环境要求
|
||||
- JDK 21
|
||||
- Maven 3.6+
|
||||
- MySQL 8.0+
|
||||
@@ -193,6 +196,3 @@ Contract-Manager/
|
||||
3. 增强系统安全机制
|
||||
4. 改进用户界面体验
|
||||
5. 扩展更多云端服务集成
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<parent>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>Contract-Manager</artifactId>
|
||||
<version>0.0.126-SNAPSHOT</version>
|
||||
<version>0.0.135-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>client</artifactId>
|
||||
<version>0.0.126-SNAPSHOT</version>
|
||||
<version>0.0.135-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
@@ -22,7 +22,7 @@
|
||||
<dependency>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>0.0.126-SNAPSHOT</version>
|
||||
<version>0.0.135-SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -53,7 +53,6 @@ public class WebSocketClientService {
|
||||
@Getter
|
||||
@Setter
|
||||
private long readTimeout = 30000;
|
||||
private String webSocketUrl = "ws://localhost:8080/ws";
|
||||
private boolean isActive = false; // 标记连接是否活跃
|
||||
private ScheduledFuture<?> heartbeatTask; // 心跳任务
|
||||
private ScheduledFuture<?> reconnectFuture; // 修改类型为CompletableFuture<Void>
|
||||
@@ -207,6 +206,12 @@ public class WebSocketClientService {
|
||||
send(objectMapper.writeValueAsString(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocketServerCallbackManage#onMessage 负责接收处理
|
||||
*
|
||||
* @param msg
|
||||
* @return
|
||||
*/
|
||||
public CompletableFuture<JsonNode> send(SimpleMessage msg) {
|
||||
CompletableFuture<JsonNode> future = new CompletableFuture<>();
|
||||
try {
|
||||
@@ -248,6 +253,8 @@ public class WebSocketClientService {
|
||||
|
||||
try {
|
||||
// 构建WebSocket请求,包含认证信息
|
||||
var myProperties = SpringApp.getBean(MyProperties.class);
|
||||
String webSocketUrl = "ws://" + myProperties.getServerHost() + ":" + myProperties.getServerPort() + "/ws";
|
||||
Request request = new Request.Builder()
|
||||
.url(webSocketUrl)
|
||||
.build();
|
||||
@@ -352,14 +359,6 @@ public class WebSocketClientService {
|
||||
return online;
|
||||
}
|
||||
|
||||
public void withSession(Consumer<WebSocketClientSession> sessionConsumer) {
|
||||
WebSocketClientSession session = createSession();
|
||||
try {
|
||||
sessionConsumer.accept(session);
|
||||
} finally {
|
||||
// closeSession(session);vvvv
|
||||
}
|
||||
}
|
||||
|
||||
public void closeSession(WebSocketClientSession session) {
|
||||
if (session != null) {
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import com.ecep.contract.constant.WebSocketConstant;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import com.ecep.contract.constant.WebSocketConstant;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -27,6 +24,8 @@ public class WebSocketClientSession {
|
||||
*/
|
||||
@Getter
|
||||
private final String sessionId = UUID.randomUUID().toString();
|
||||
@Getter
|
||||
private boolean done = false;
|
||||
|
||||
private WebSocketClientTasker tasker;
|
||||
|
||||
@@ -69,6 +68,8 @@ public class WebSocketClientSession {
|
||||
handleAsStart(args);
|
||||
} else if (type.equals("done")) {
|
||||
handleAsDone(args);
|
||||
done = true;
|
||||
close();
|
||||
} else {
|
||||
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString());
|
||||
}
|
||||
@@ -83,7 +84,6 @@ public class WebSocketClientSession {
|
||||
|
||||
private void handleAsDone(JsonNode args) {
|
||||
tasker.updateMessage(java.util.logging.Level.INFO, "任务完成");
|
||||
close();
|
||||
}
|
||||
|
||||
private void handleAsProgress(JsonNode args) {
|
||||
|
||||
@@ -5,16 +5,21 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* WebSocket客户端任务接口
|
||||
* 定义了所有通过WebSocket与服务器通信的任务的通用方法
|
||||
* 包括任务名称、更新消息、更新标题、更新进度等操作
|
||||
* <p>
|
||||
* 所有通过WebSocket与服务器通信的任务类都应实现此接口, 文档参考 .trace/rules/client_task_rules.md
|
||||
*/
|
||||
public interface WebSocketClientTasker {
|
||||
/**
|
||||
* s
|
||||
* 获取任务名称
|
||||
* 任务名称用于唯一标识任务, 服务器端会根据任务名称来调用对应的任务处理函数
|
||||
*
|
||||
* @return 任务名称
|
||||
*/
|
||||
@@ -22,14 +27,16 @@ public interface WebSocketClientTasker {
|
||||
|
||||
/**
|
||||
* 更新任务执行过程中的消息
|
||||
* 客户端可以通过此方法向用户展示任务执行过程中的重要信息或错误提示
|
||||
*
|
||||
* @param level 消息级别
|
||||
* @param message 消息内容
|
||||
* @param level 消息级别, 用于区分不同类型的消息, 如INFO, WARNING, SEVERE等
|
||||
* @param message 消息内容, 可以是任意字符串, 用于展示给用户
|
||||
*/
|
||||
void updateMessage(Level level, String message);
|
||||
|
||||
/**
|
||||
* 更新任务标题
|
||||
* 客户端可以通过此方法向用户展示任务的当前执行状态或重要信息
|
||||
*
|
||||
* @param title 任务标题
|
||||
*/
|
||||
@@ -37,6 +44,7 @@ public interface WebSocketClientTasker {
|
||||
|
||||
/**
|
||||
* 更新任务进度
|
||||
* 客户端可以通过此方法向用户展示任务的执行进度, 如文件上传进度、数据库操作进度等
|
||||
*
|
||||
* @param current 当前进度
|
||||
* @param total 总进度
|
||||
@@ -49,10 +57,14 @@ public interface WebSocketClientTasker {
|
||||
*/
|
||||
default void cancelTask() {
|
||||
// 默认实现为空,由具体任务重写
|
||||
// 需要获取到 session
|
||||
// 发送 cancel 指令
|
||||
// 关闭 session.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用远程WebSocket任务
|
||||
* 客户端可以通过此方法向服务器提交任务, 并等待服务器返回任务执行结果
|
||||
*
|
||||
* @param holder 消息持有者,用于记录任务执行过程中的消息
|
||||
* @param locale 语言环境
|
||||
@@ -69,16 +81,25 @@ public interface WebSocketClientTasker {
|
||||
return null;
|
||||
}
|
||||
|
||||
webSocketService.withSession(session -> {
|
||||
try {
|
||||
session.submitTask(this, locale, args);
|
||||
holder.info("已提交任务到服务器: " + getTaskName());
|
||||
} catch (JsonProcessingException e) {
|
||||
String errorMsg = "任务提交失败: " + e.getMessage();
|
||||
holder.warn(errorMsg);
|
||||
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
WebSocketClientSession session = webSocketService.createSession();
|
||||
try {
|
||||
session.submitTask(this, locale, args);
|
||||
holder.info("已提交任务到服务器: " + getTaskName());
|
||||
} catch (JsonProcessingException e) {
|
||||
String errorMsg = "任务提交失败: " + e.getMessage();
|
||||
holder.warn(errorMsg);
|
||||
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// while (!session.isDone()) {
|
||||
// // 使用TimeUnit
|
||||
// try {
|
||||
// TimeUnit.SECONDS.sleep(1);
|
||||
// } catch (InterruptedException e) {
|
||||
// Thread.currentThread().interrupt();
|
||||
// }
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,48 +45,55 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
protected CompletableFuture<T> loadedFuture;
|
||||
private final ObservableList<TabSkin> tabSkins = FXCollections.observableArrayList();
|
||||
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
// 载数据
|
||||
initializeData();
|
||||
// 注册 skin
|
||||
registerTabSkins();
|
||||
// 初始化保存按钮
|
||||
initializeSaveBtn();
|
||||
// 安装 skin
|
||||
installTabSkins();
|
||||
}
|
||||
|
||||
protected void initializeSaveBtn() {
|
||||
if (saveBtn != null) {
|
||||
saveBtn.disableProperty().bind(createTabSkinChangedBindings().not());
|
||||
saveBtn.setOnAction(event -> saveTabSkins());
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeData() {
|
||||
ViewModelService<T, TV> service = getViewModelService();
|
||||
|
||||
if (service instanceof QueryService<T, TV> queryService) {
|
||||
setStatus("读取...");
|
||||
loadedFuture = queryService.asyncFindById(viewModel.getId().get());
|
||||
loadedFuture.thenAccept(entity -> {
|
||||
// fixed, bind change if new view model create
|
||||
if (viewModel != null) {
|
||||
viewModel.bindListener();
|
||||
}
|
||||
setStatus();
|
||||
// BaseViewModel.updateInFxApplicationThread(entity, viewModel);
|
||||
});
|
||||
loadedFuture.exceptionally(ex -> {
|
||||
handleException("载入失败,#" + viewModel.getId().get(), ex);
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
loadedFuture = CompletableFuture.supplyAsync(() -> {
|
||||
T entity = getViewModelService().findById(viewModel.getId().get());
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
setStatus();
|
||||
viewModel.update(entity);
|
||||
});
|
||||
viewModel.bindListener();
|
||||
return entity;
|
||||
return getViewModelService().findById(viewModel.getId().get());
|
||||
});
|
||||
}
|
||||
|
||||
registerTabSkins();
|
||||
if (saveBtn != null) {
|
||||
saveBtn.disableProperty().bind(createTabSkinChangedBindings().not());
|
||||
saveBtn.setOnAction(event -> saveTabSkins());
|
||||
}
|
||||
loadedFuture.thenAccept(entity -> {
|
||||
// fixed, bind change if new view model create
|
||||
if (viewModel != null) {
|
||||
updateViewModel(entity);
|
||||
viewModel.bindListener();
|
||||
}
|
||||
setStatus();
|
||||
});
|
||||
loadedFuture.exceptionally(ex -> {
|
||||
handleException("载入失败,#" + viewModel.getId().get(), ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
installTabSkins();
|
||||
protected void updateViewModel(T entity) {
|
||||
BaseViewModel.updateInFxApplicationThread(entity, viewModel);
|
||||
}
|
||||
|
||||
public T getEntity() {
|
||||
@@ -108,6 +115,19 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
return saved;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
T entity = getEntity();
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
if (viewModel.copyTo(entity)) {
|
||||
save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 skin
|
||||
*/
|
||||
protected void registerTabSkins() {
|
||||
}
|
||||
|
||||
@@ -119,6 +139,9 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装 skin
|
||||
*/
|
||||
protected void installTabSkins() {
|
||||
for (TabSkin tabSkin : tabSkins) {
|
||||
try {
|
||||
@@ -229,10 +252,10 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(list
|
||||
.stream()
|
||||
.map(RefreshableSkin::refresh)
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(CompletableFuture<?>[]::new))
|
||||
.stream()
|
||||
.map(RefreshableSkin::refresh)
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(CompletableFuture<?>[]::new))
|
||||
.whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
future.completeExceptionally(ex);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ecep.contract.controller.bank;
|
||||
|
||||
import org.controlsfx.control.SearchableComboBox;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
@@ -34,7 +35,6 @@ public class BankManagerWindowController
|
||||
}
|
||||
|
||||
public void onCreateNewAction(ActionEvent event) {
|
||||
|
||||
}
|
||||
|
||||
public void onReBuildFilesAction(ActionEvent event) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.ManagerSkin;
|
||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.YongYouU8Service;
|
||||
@@ -15,6 +16,7 @@ import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
|
||||
public class YongYouU8ManagerSkin
|
||||
extends
|
||||
@@ -36,10 +38,9 @@ public class YongYouU8ManagerSkin
|
||||
@Override
|
||||
public void initializeTable() {
|
||||
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
|
||||
controller.companyColumn.setCellFactory(param -> new CompanyTableCell<>(getCompanyService()));
|
||||
|
||||
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId());
|
||||
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
|
||||
controller.companyColumn.setCellFactory( CompanyTableCell.forTableColumn(getCompanyService()));
|
||||
|
||||
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
|
||||
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
@@ -47,7 +48,14 @@ public class YongYouU8ManagerSkin
|
||||
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
|
||||
controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getVendorCode());
|
||||
controller.cloudVendorUpdateDateColumn.setCellValueFactory(param -> param.getValue().getVendorUpdateDate());
|
||||
|
||||
controller.cloudCustomerUpdateDateColumn.setCellValueFactory(param -> param.getValue().getCustomerUpdateDate());
|
||||
|
||||
controller.activeColumn.setCellValueFactory(param -> param.getValue().getActive());
|
||||
controller.activeColumn.setCellFactory(CheckBoxTableCell.forTableColumn(controller.activeColumn));
|
||||
|
||||
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getExceptionMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,7 +81,7 @@ public class YongYouU8ManagerSkin
|
||||
}
|
||||
for (CloudYuInfoViewModel selectedItem : selectedItems) {
|
||||
CloudYuVo yongYouU8Vo = getU8Service().findById(selectedItem.getId().get());
|
||||
selectedItem.getCustomerCode().set("");
|
||||
selectedItem.getExceptionMessage().set("");
|
||||
if (selectedItem.copyTo(yongYouU8Vo)) {
|
||||
CloudYuVo saved = getU8Service().save(yongYouU8Vo);
|
||||
selectedItem.update(saved);
|
||||
|
||||
@@ -37,8 +37,10 @@ public class YongYouU8ManagerWindowController
|
||||
public TableColumn<CloudYuInfoViewModel, Number> idColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, LocalDateTime> latestUpdateColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, Integer> companyColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, String> cloudIdColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, LocalDateTime> cloudLatestColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudVendorUpdateDateColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudCustomerUpdateDateColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, Boolean> activeColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, String> descriptionColumn;
|
||||
|
||||
@Override
|
||||
@@ -91,27 +93,32 @@ public class YongYouU8ManagerWindowController
|
||||
|
||||
public void onContractGroupSyncAction(ActionEvent event) {
|
||||
ContractGroupSyncTask task = new ContractGroupSyncTask();
|
||||
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
UITools.showTaskDialogAndWait("合同组数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onContractTypeSyncAction(ActionEvent event) {
|
||||
ContractTypeSyncTask task = new ContractTypeSyncTask();
|
||||
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onContractKindSyncAction(ActionEvent event) {
|
||||
ContractKindSyncTask task = new ContractKindSyncTask();
|
||||
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onVendorClassSyncAction(ActionEvent event) {
|
||||
VendorClassSyncTask task = new VendorClassSyncTask();
|
||||
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onCustomerClassSyncAction(ActionEvent event) {
|
||||
CustomerClassSyncTask task = new CustomerClassSyncTask();
|
||||
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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;
|
||||
import com.ecep.contract.model.ContractFileTypeLocal;
|
||||
import com.ecep.contract.service.ContractFileService;
|
||||
import com.ecep.contract.service.ContractFileTypeService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
@@ -21,7 +21,6 @@ import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.StringConverter;
|
||||
import lombok.Setter;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
@@ -94,21 +93,6 @@ public class ContractTabSkinFiles
|
||||
return controller.fileTab;
|
||||
}
|
||||
|
||||
static class ContractFileTypeLocalStringConverter extends StringConverter<ContractFileTypeLocal> {
|
||||
@Override
|
||||
public String toString(ContractFileTypeLocal local) {
|
||||
if (local == null) {
|
||||
return "-";
|
||||
}
|
||||
return local.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractFileTypeLocal fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
TableView<ContractFileViewModel> table = getTableView();
|
||||
@@ -134,6 +118,7 @@ public class ContractTabSkinFiles
|
||||
.setCellFactory(ContractFileTypeTableCell.forTableColumn(getCachedBean(ContractFileTypeService.class)));
|
||||
fileTable_typeColumn.setEditable(false);
|
||||
|
||||
|
||||
/* 文件名编辑器 */
|
||||
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFileName());
|
||||
fileTable_filePathColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
@@ -193,6 +178,34 @@ public class ContractTabSkinFiles
|
||||
createVendorContractRequestByTemplateUpdateMenuItem(),
|
||||
createVendorContractApplyByTemplateUpdateMenuItem());
|
||||
|
||||
|
||||
runAsync(() -> {
|
||||
getCachedBean(ContractFileTypeService.class).findAll(getLocale()).forEach((k, v) -> {
|
||||
if (isCustomer && !k.isSupportCustomer()) {
|
||||
return;
|
||||
}
|
||||
if (isVendor && !k.isSupportVendor()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
MenuItem item = new MenuItem();
|
||||
item.setText(v.getValue());
|
||||
item.getProperties().put("typeLocal", v);
|
||||
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
|
||||
fileTable_menu_change_type.getItems().add(item);
|
||||
}
|
||||
if (StringUtils.hasText(v.getSuggestFileName())) {
|
||||
MenuItem item = new MenuItem();
|
||||
item.setText(v.getValue());
|
||||
item.getProperties().put("typeLocal", v);
|
||||
item.getProperties().put("rename", true);
|
||||
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
|
||||
fileTable_menu_change_type_and_name.getItems().add(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
super.initializeTab();
|
||||
}
|
||||
|
||||
@@ -540,13 +553,13 @@ public class ContractTabSkinFiles
|
||||
//
|
||||
fileTable_menu_change_type.setVisible(true);
|
||||
for (MenuItem item : fileTable_menu_change_type.getItems()) {
|
||||
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal");
|
||||
ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
|
||||
item.setVisible(typeLocal.getType() != selectedItem.getType().get());
|
||||
}
|
||||
|
||||
fileTable_menu_change_type_and_name.setVisible(true);
|
||||
for (MenuItem item : fileTable_menu_change_type.getItems()) {
|
||||
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal");
|
||||
ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
|
||||
item.setVisible(typeLocal.getType() != selectedItem.getType().get());
|
||||
}
|
||||
|
||||
@@ -559,7 +572,7 @@ public class ContractTabSkinFiles
|
||||
if (selectedItems == null || selectedItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal");
|
||||
ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
|
||||
if (typeLocal == null) {
|
||||
return;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -12,6 +12,9 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import org.controlsfx.control.PopOver;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -50,13 +53,6 @@ import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckMenuItem;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Modality;
|
||||
@@ -79,6 +75,7 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
return super.show(loader, owner, modality);
|
||||
}
|
||||
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class MessageExt extends Message {
|
||||
@@ -90,12 +87,16 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Model implements MessageHolder {
|
||||
public static class Model {
|
||||
private SimpleStringProperty code = new SimpleStringProperty();
|
||||
private SimpleStringProperty name = new SimpleStringProperty();
|
||||
private SimpleObjectProperty<Integer> employee = new SimpleObjectProperty<>();
|
||||
private SimpleObjectProperty<LocalDate> setupDate = new SimpleObjectProperty<>();
|
||||
private SimpleListProperty<MessageExt> messages = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
}
|
||||
|
||||
static class MessageHolderImpl implements MessageHolder {
|
||||
List<MessageExt> messages = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void addMessage(Level level, String message) {
|
||||
@@ -261,6 +262,8 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
long total = contractService.count(params);
|
||||
setStatus("合同:" + total + " 条");
|
||||
|
||||
MessageHolderImpl messageHolder = new MessageHolderImpl();
|
||||
|
||||
while (true) {
|
||||
if (isCloseRequested()) {
|
||||
break;
|
||||
@@ -268,6 +271,7 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
|
||||
Page<ContractVo> page = contractService.findAll(params, pageRequest);
|
||||
for (ContractVo contract : page) {
|
||||
messageHolder.messages.clear();
|
||||
if (isCloseRequested()) {
|
||||
break;
|
||||
}
|
||||
@@ -285,11 +289,11 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
model.getName().set(contract.getName());
|
||||
model.getSetupDate().set(contract.getSetupDate());
|
||||
|
||||
comm.verify(contract, model);
|
||||
comm.verify(contract, messageHolder);
|
||||
setStatus("合同验证进度:" + counter.get() + " / " + total);
|
||||
// 移除中间消息
|
||||
if (!model.getMessages().isEmpty()) {
|
||||
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue());
|
||||
if (!messageHolder.messages.isEmpty()) {
|
||||
model.getMessages().setAll(messageHolder.messages.stream().filter(msg -> msg.getLevel().intValue() > Level.INFO.intValue()).limit(50).toList());
|
||||
}
|
||||
if (model.getMessages().isEmpty()) {
|
||||
if (onlyShowVerifiedChecker.isSelected()) {
|
||||
@@ -325,6 +329,7 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
}
|
||||
runAsync(() -> {
|
||||
ContractVo contract = null;
|
||||
MessageHolderImpl messageHolder = new MessageHolderImpl();
|
||||
try {
|
||||
contract = contractService.findByCode(contractCode);
|
||||
} catch (Exception e) {
|
||||
@@ -336,17 +341,18 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
}
|
||||
model.getMessages().clear();
|
||||
try {
|
||||
comm.verify(contract, model);
|
||||
// 移除中间消息
|
||||
if (!model.getMessages().isEmpty()) {
|
||||
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue());
|
||||
}
|
||||
comm.verify(contract, messageHolder);
|
||||
} catch (Exception e) {
|
||||
logger.error(model.getCode().get(), e);
|
||||
model.error(e.getMessage());
|
||||
messageHolder.error(e.getMessage());
|
||||
}
|
||||
|
||||
if (model.getMessages().isEmpty()) {
|
||||
// 移除中间消息
|
||||
if (!messageHolder.messages.isEmpty()) {
|
||||
model.getMessages().setAll(messageHolder.messages.stream().filter(msg -> msg.getLevel().intValue() > Level.INFO.intValue()).limit(50).toList());
|
||||
}
|
||||
|
||||
if (messageHolder.messages.isEmpty()) {
|
||||
Platform.runLater(() -> {
|
||||
viewTableDataSet.remove(model);
|
||||
});
|
||||
@@ -380,6 +386,38 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
ContractWindowController.show(contract, viewTable.getScene().getWindow());
|
||||
}
|
||||
|
||||
|
||||
public void onShowVerifyStatusAction(ActionEvent event) {
|
||||
|
||||
// 在新新窗口中显示 状态消息 Model# messages
|
||||
|
||||
Model selectedItem = viewTable.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListView<MessageExt> listView = new ListView<>();
|
||||
listView.setItems(selectedItem.messages);
|
||||
listView.setCellFactory(v -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(MessageExt item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(item.getMessage());
|
||||
setGraphic(new Label(item.getPrefix()));
|
||||
}
|
||||
}
|
||||
});
|
||||
PopOver popOver = new PopOver(listView);
|
||||
popOver.setArrowLocation(PopOver.ArrowLocation.TOP_LEFT);
|
||||
MenuItem menuItem = (MenuItem) event.getSource();
|
||||
Node node = viewTable.lookup(".table-row-cell:selected");
|
||||
popOver.show(node);
|
||||
}
|
||||
|
||||
public void onExportVerifyResultAsFileAction(ActionEvent e) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("导出核验结果");
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javafx.scene.control.*;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -12,13 +13,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;
|
||||
@@ -31,13 +27,6 @@ import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
@@ -49,6 +38,8 @@ import javafx.stage.WindowEvent;
|
||||
public class ContractWindowController
|
||||
extends AbstEntityController<ContractVo, ContractViewModel> {
|
||||
|
||||
|
||||
|
||||
public static void show(ContractVo contract, Window owner) {
|
||||
show(ContractViewModel.from(contract), owner);
|
||||
}
|
||||
@@ -74,6 +65,7 @@ public class ContractWindowController
|
||||
public Button openRelativeCompanyVendorBtn;
|
||||
|
||||
public TextField nameField;
|
||||
public CheckBox contractNameLockedCk;
|
||||
public TextField guidField;
|
||||
public TextField codeField;
|
||||
public TextField parentCodeField;
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.service.CompanyCustomerFileTypeService;
|
||||
import com.ecep.contract.service.ViewModelService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vo.CustomerFileTypeLocalVo;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
@@ -15,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
@@ -29,11 +45,9 @@ import com.ecep.contract.vm.CustomerFileViewModel;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -50,29 +64,30 @@ import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
public class CompanyCustomerEvaluationFormFileWindowController extends BaseController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerEvaluationFormFileWindowController.class);
|
||||
@FxmlPath("/ui/company/customer/customer_evaluation_form.fxml")
|
||||
public class CompanyCustomerEvaluationFormFileWindowController
|
||||
extends AbstEntityController<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> {
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(CompanyCustomerEvaluationFormFileWindowController.class);
|
||||
|
||||
public static void show(CustomerFileViewModel item, Window window) {
|
||||
show(CompanyCustomerEvaluationFormFileWindowController.class, window, controller -> {
|
||||
controller.fileViewModel = item;
|
||||
});
|
||||
}
|
||||
|
||||
public static void show(CompanyCustomerEvaluationFormFileVo saved, Window window) {
|
||||
show(CompanyCustomerEvaluationFormFileViewModel.from(saved), window);
|
||||
}
|
||||
|
||||
public static void show(CompanyCustomerEvaluationFormFileViewModel viewModel, Window window) {
|
||||
String key = viewModel.getClass().getName() + "-" + viewModel.getId().get();
|
||||
if (toFront(key)) {
|
||||
return;
|
||||
}
|
||||
FxmlUtils.newLoaderAsyncWithRunLater("/ui/company/customer/customer_evaluation_form.fxml", null, loader -> {
|
||||
CompanyCustomerEvaluationFormFileWindowController controller = loader.getController();
|
||||
controller.viewModel = viewModel;
|
||||
controller.show(loader, window, Modality.NONE, key);
|
||||
});
|
||||
show(CompanyCustomerEvaluationFormFileWindowController.class, viewModel, window);
|
||||
}
|
||||
|
||||
|
||||
public Label idField;
|
||||
public TextField filePathField;
|
||||
public CheckBox validField;
|
||||
@@ -91,64 +106,66 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
public ScrollPane leftPane;
|
||||
public Label totalCreditScoreLabel;
|
||||
|
||||
private CompanyCustomerEvaluationFormFileViewModel viewModel;
|
||||
|
||||
private final SimpleStringProperty catalogProperty = new SimpleStringProperty("");
|
||||
private final SimpleStringProperty levelProperty = new SimpleStringProperty("");
|
||||
private final SimpleIntegerProperty score1Property = new SimpleIntegerProperty(-1);
|
||||
private final SimpleIntegerProperty score2Property = new SimpleIntegerProperty(-1);
|
||||
private final SimpleIntegerProperty score3Property = new SimpleIntegerProperty(-1);
|
||||
private final SimpleIntegerProperty score4Property = new SimpleIntegerProperty(-1);
|
||||
private final SimpleIntegerProperty score5Property = new SimpleIntegerProperty(-1);
|
||||
private final SimpleIntegerProperty creditLevelProperty = new SimpleIntegerProperty(-1);
|
||||
private final SimpleIntegerProperty totalCreditScoreProperty = new SimpleIntegerProperty(-1);
|
||||
|
||||
private SimpleObjectProperty<Image> imageProperty = new SimpleObjectProperty<>();
|
||||
private CustomerFileViewModel fileViewModel;
|
||||
|
||||
private CompletableFuture<CompanyCustomerEvaluationFormFileVo> loadedFuture;
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyCustomerFileService companyCustomerFileService;
|
||||
private SimpleBooleanProperty changed = new SimpleBooleanProperty(false);
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyCustomerEvaluationFormFileService evaluationFormFileService;
|
||||
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
stage.setFullScreen(false);
|
||||
// Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
|
||||
//
|
||||
// stage.setX(screenBounds.getMinX());
|
||||
// stage.setY(screenBounds.getMinY());
|
||||
// stage.setWidth(screenBounds.getWidth());
|
||||
// stage.setHeight(screenBounds.getHeight());
|
||||
//
|
||||
// stage.isMaximized();
|
||||
stage.setMaximized(true);
|
||||
getTitle().set("客户评估表单");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("onShown");
|
||||
}
|
||||
getTitle().set("客户评估表单");
|
||||
}
|
||||
|
||||
initializePane();
|
||||
@Override
|
||||
public ViewModelService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> getViewModelService() {
|
||||
return evaluationFormFileService;
|
||||
}
|
||||
|
||||
loadedFuture = CompletableFuture.supplyAsync(() -> {
|
||||
int id = viewModel.getId().get();
|
||||
CustomerFileVo customerFile = companyCustomerFileService.findById(id);
|
||||
CompanyCustomerEvaluationFormFileVo formFile = evaluationFormFileService.findByCustomerFile(customerFile);
|
||||
Platform.runLater(() -> update(formFile));
|
||||
return formFile;
|
||||
@Override
|
||||
protected void initializeData() {
|
||||
CompanyCustomerEvaluationFormFileViewModel viewModel = new CompanyCustomerEvaluationFormFileViewModel();
|
||||
setViewModel(viewModel);
|
||||
runAsync(() -> {
|
||||
CompanyCustomerEvaluationFormFileVo item = getCachedBean(CompanyCustomerEvaluationFormFileService.class)
|
||||
.findByCustomerFile(fileViewModel.getId().get());
|
||||
viewModel.getId().set(item.getId());
|
||||
updateViewModel(item);
|
||||
super.initializeData();
|
||||
Platform.runLater(this::initializePane);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateViewModel(CompanyCustomerEvaluationFormFileVo entity) {
|
||||
super.updateViewModel(entity);
|
||||
changed.set(false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BooleanBinding createTabSkinChangedBindings() {
|
||||
return viewModel.getChanged().or(fileViewModel.getChanged()).or(changed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveTabSkins() {
|
||||
save();
|
||||
changed.setValue(false);
|
||||
}
|
||||
|
||||
BiConsumer<ToggleGroup, String> stringRadioGroupUpdater = (group, newValue) -> {
|
||||
if (newValue != null) {
|
||||
for (Toggle toggle : group.getToggles()) {
|
||||
@@ -164,14 +181,15 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
return;
|
||||
}
|
||||
}
|
||||
changed.set(true);
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
group.selectToggle(null);
|
||||
// Toggle first = group.getToggles().getFirst();
|
||||
// first.setSelected(true);
|
||||
// first.setSelected(false);
|
||||
// RadioButton btn = (RadioButton) first;
|
||||
// btn.setText(newValue);
|
||||
// Toggle first = group.getToggles().getFirst();
|
||||
// first.setSelected(true);
|
||||
// first.setSelected(false);
|
||||
// RadioButton btn = (RadioButton) first;
|
||||
// btn.setText(newValue);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -182,9 +200,11 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
}
|
||||
String data = (String) toggle.getUserData();
|
||||
property.set(data);
|
||||
changed.set(true);
|
||||
};
|
||||
|
||||
BiConsumer<ToggleGroup, Number> numberRadioGroupUpdater = (group, newValue) -> {
|
||||
System.out.println("group = " + group + ", newValue = " + newValue);
|
||||
String value = String.valueOf(newValue);
|
||||
for (Toggle toggle : group.getToggles()) {
|
||||
String data = (String) toggle.getUserData();
|
||||
@@ -195,12 +215,13 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
group.selectToggle(null);
|
||||
// Toggle first = group.getToggles().getFirst();
|
||||
// first.setSelected(true);
|
||||
// first.setSelected(false);
|
||||
// RadioButton btn = (RadioButton) first;
|
||||
// btn.setText(String.valueOf(newValue));
|
||||
// Toggle first = group.getToggles().getFirst();
|
||||
// first.setSelected(true);
|
||||
// first.setSelected(false);
|
||||
// RadioButton btn = (RadioButton) first;
|
||||
// btn.setText(String.valueOf(newValue));
|
||||
});
|
||||
changed.set(true);
|
||||
};
|
||||
|
||||
BiConsumer<SimpleIntegerProperty, Toggle> numberPropertyUpdater = (property, toggle) -> {
|
||||
@@ -210,9 +231,9 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
}
|
||||
String data = (String) toggle.getUserData();
|
||||
property.set(Integer.parseInt(data));
|
||||
changed.set(true);
|
||||
};
|
||||
|
||||
|
||||
int totalScoreToLevel(int score) {
|
||||
if (score >= 200) {
|
||||
return 4;
|
||||
@@ -227,63 +248,73 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
}
|
||||
|
||||
boolean calcValid() {
|
||||
if (creditLevelProperty.get() <= 0) {
|
||||
if (viewModel.getCreditLevel().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.hasText(catalogProperty.get())) {
|
||||
if (!StringUtils.hasText(viewModel.getCatalog().get())) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.hasText(levelProperty.get())) {
|
||||
if (!StringUtils.hasText(viewModel.getLevel().get())) {
|
||||
return false;
|
||||
}
|
||||
if (score1Property.get() <= 0) {
|
||||
|
||||
if (viewModel.getScore1().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (score2Property.get() <= 0) {
|
||||
if (viewModel.getScore2().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (score3Property.get() <= 0) {
|
||||
if (viewModel.getScore3().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (score4Property.get() <= 0) {
|
||||
if (viewModel.getScore4().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (score5Property.get() <= 0) {
|
||||
if (viewModel.getScore5().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (creditLevelProperty.get() <= 0) {
|
||||
if (viewModel.getCreditLevel().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initializePane() {
|
||||
setStatus("");
|
||||
idField.textProperty().bind(viewModel.getId().asString());
|
||||
// filePathField.textProperty().bind(viewModel.getFilePath());
|
||||
// editFilePathField.textProperty().bind(viewModel.getEditFilePath());
|
||||
// signDateField.valueProperty().bindBidirectional(viewModel.getSignDate());
|
||||
// validField.selectedProperty().bindBidirectional(viewModel.getValid());
|
||||
|
||||
initializeRadioGroup(catalog, catalogProperty);
|
||||
initializeRadioGroup(level, levelProperty);
|
||||
|
||||
initializeRadioGroup(score1, score1Property);
|
||||
initializeRadioGroup(score2, score2Property);
|
||||
initializeRadioGroup(score3, score3Property);
|
||||
initializeRadioGroup(score4, score4Property);
|
||||
initializeRadioGroup(score5, score5Property);
|
||||
|
||||
creditLevelProperty.addListener((observable, oldValue, newValue) -> {
|
||||
numberRadioGroupUpdater.accept(creditLevel, newValue);
|
||||
filePathField.textProperty().bind(fileViewModel.getFilePath());
|
||||
editFilePathField.textProperty().bind(fileViewModel.getEditFilePath());
|
||||
String pattern = "yyyy-MM-dd";
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
signDateField.setConverter(new LocalDateStringConverter(formatter, null));
|
||||
signDateField.valueProperty().bindBidirectional(fileViewModel.getSignDate());
|
||||
signDateField.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
changed.set(true);
|
||||
});
|
||||
|
||||
SimpleIntegerProperty[] scores = new SimpleIntegerProperty[]{score1Property, score2Property, score3Property, score4Property, score5Property};
|
||||
initializeRadioGroup(catalog, viewModel.getCatalog());
|
||||
initializeRadioGroup(level, viewModel.getLevel());
|
||||
|
||||
initializeRadioGroup(score1, viewModel.getScore1());
|
||||
initializeRadioGroup(score2, viewModel.getScore2());
|
||||
initializeRadioGroup(score3, viewModel.getScore3());
|
||||
initializeRadioGroup(score4, viewModel.getScore4());
|
||||
initializeRadioGroup(score5, viewModel.getScore5());
|
||||
|
||||
// 信用等级
|
||||
viewModel.getCreditLevel().addListener((observable, oldValue, newValue) -> {
|
||||
numberRadioGroupUpdater.accept(creditLevel, newValue);
|
||||
});
|
||||
numberRadioGroupUpdater.accept(creditLevel, viewModel.getCreditLevel().get());
|
||||
|
||||
SimpleIntegerProperty[] scores = new SimpleIntegerProperty[]{viewModel.getScore1(), viewModel.getScore2(),
|
||||
viewModel.getScore3(), viewModel.getScore4(), viewModel.getScore5()};
|
||||
totalCreditScoreProperty.bind(Bindings.createIntegerBinding(() -> {
|
||||
int total = 0;
|
||||
for (SimpleIntegerProperty score : scores) {
|
||||
total += score.get();
|
||||
}
|
||||
creditLevelProperty.set(totalScoreToLevel(total));
|
||||
viewModel.getCreditLevel().set(totalScoreToLevel(total));
|
||||
return total;
|
||||
}, scores));
|
||||
|
||||
@@ -291,70 +322,205 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
return "合计总分:" + score;
|
||||
}));
|
||||
|
||||
Bindings.createBooleanBinding(() -> {
|
||||
boolean valid = calcValid();
|
||||
// viewModel.getValid().set(valid);
|
||||
return valid;
|
||||
}, catalogProperty, levelProperty, score1Property, score2Property, score3Property, score4Property, score5Property, creditLevelProperty).addListener(((observable, oldValue, newValue) -> {
|
||||
logger.info("valid:{}", newValue);
|
||||
}));
|
||||
Bindings.createBooleanBinding(this::calcValid, viewModel.getCatalog(), viewModel.getLevel(),
|
||||
viewModel.getScore1(), viewModel.getScore2(), viewModel.getScore3(), viewModel.getScore4(),
|
||||
viewModel.getScore5(), viewModel.getCreditLevel())
|
||||
.addListener((observable, oldValue, newValue) -> {
|
||||
fileViewModel.getValid().set(newValue);
|
||||
changed.set(true);
|
||||
});
|
||||
validField.selectedProperty().bindBidirectional(fileViewModel.getValid());
|
||||
validField.setSelected(fileViewModel.getValid().getValue());
|
||||
|
||||
|
||||
imageView.imageProperty().bind(viewModel.getFilePath().map(path -> {
|
||||
if (FileUtils.withExtensions(path, FileUtils.PDF)) {
|
||||
File pdfFile = new File(path);
|
||||
try (PDDocument pdDocument = Loader.loadPDF(pdfFile)) {
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);
|
||||
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);
|
||||
|
||||
// 获取 BufferedImage 的宽度和高度
|
||||
int width = bufferedImage.getWidth();
|
||||
int height = bufferedImage.getHeight();
|
||||
WritableImage writableImage = new WritableImage(width, height);
|
||||
PixelWriter pixelWriter = writableImage.getPixelWriter();
|
||||
// 将 BufferedImage 的像素数据复制到 WritableImage
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int argb = bufferedImage.getRGB(x, y);
|
||||
pixelWriter.setArgb(x, y, argb);
|
||||
}
|
||||
}
|
||||
return writableImage;
|
||||
} catch (Exception e) {
|
||||
setStatus(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
File file = new File(path);
|
||||
Image image = new Image(file.toURI().toString());
|
||||
return image;
|
||||
}
|
||||
}));
|
||||
fileViewModel.getFilePath().addListener((observable, oldValue, newValue) -> {
|
||||
File file = new File(newValue);
|
||||
loadFile(file);
|
||||
});
|
||||
if (StringUtils.hasText(fileViewModel.getFilePath().get())) {
|
||||
loadFile(new File(fileViewModel.getFilePath().get()));
|
||||
}
|
||||
|
||||
|
||||
leftPane.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
imageView.setFitWidth(leftPane.getWidth());
|
||||
imageView.setFitHeight(leftPane.getHeight());
|
||||
imageView.setFitHeight(-1);
|
||||
});
|
||||
});
|
||||
|
||||
imageView.setFitWidth(leftPane.getWidth());
|
||||
imageView.setFitHeight(leftPane.getHeight());
|
||||
imageView.setFitHeight(-1);
|
||||
|
||||
imageView.setOnScroll(event -> {
|
||||
System.out.println("event = " + event);
|
||||
System.out.println("event.getDeltaY() = " + event.getDeltaY());
|
||||
Bounds bounds = imageView.getBoundsInLocal();
|
||||
// Bounds latestBounds = (Bounds) imageView.getProperties().get("latestBounds");
|
||||
// if (latestBounds != null) {
|
||||
// double latestBoundsWidth = latestBounds.getWidth();
|
||||
// }
|
||||
// if (bounds.getWidth() < leftPane.getWidth()) {
|
||||
imageView.setFitWidth(bounds.getWidth() + event.getDeltaY());
|
||||
// } else {
|
||||
imageView.setFitHeight(bounds.getHeight() + event.getDeltaY());
|
||||
// }
|
||||
imageView.setFitHeight(-1);
|
||||
event.consume();
|
||||
});
|
||||
|
||||
imageView.setOnMouseClicked(event -> {
|
||||
System.out.println("imageView.getFitWidth() = " + imageView.getFitWidth());
|
||||
System.out.println("imageView.getFitHeight() = " + imageView.getFitHeight());
|
||||
|
||||
System.out.println("leftPane.getWidth() = " + leftPane.getWidth());
|
||||
System.out.println("leftPane.getViewportBounds().getWidth() = " + leftPane.getViewportBounds().getWidth());
|
||||
|
||||
if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
|
||||
Image image = imageView.getImage();
|
||||
if (image != null) {
|
||||
System.out.println("image.getWidth() = " + image.getWidth());
|
||||
if (image.getWidth() > imageView.getFitWidth()) {
|
||||
imageView.setFitWidth(image.getWidth());
|
||||
} else {
|
||||
imageView.setFitWidth(leftPane.getWidth());
|
||||
}
|
||||
imageView.setFitHeight(-1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadFile(File file) {
|
||||
setStatus("文件" + file.getAbsolutePath() + " 加载中...");
|
||||
if (FileUtils.withExtensions(file.getName(), FileUtils.PDF)) {
|
||||
loadPdf(file);
|
||||
return;
|
||||
}
|
||||
Image image = new Image(file.toURI().toString(), true);
|
||||
imageView.setImage(image);
|
||||
}
|
||||
|
||||
private void loadPdf(File pdfFile) {
|
||||
// 绘制文字, 等待加载
|
||||
// 创建画布并绘制备用占位文字
|
||||
javafx.scene.canvas.Canvas canvas = new javafx.scene.canvas.Canvas(leftPane.getWidth(), leftPane.getHeight());
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
gc.setFill(javafx.scene.paint.Color.RED);
|
||||
var h1 = javafx.scene.text.Font.font("Microsoft YaHei", FontWeight.BOLD, 24);
|
||||
var h2 = javafx.scene.text.Font.font("Microsoft YaHei", FontWeight.NORMAL, 18);
|
||||
gc.setFont(h1);
|
||||
gc.fillText(fileViewModel.getType().get().name(), 50, 100);
|
||||
|
||||
Runnable updateImage = () -> {
|
||||
WritableImage writableImage = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
|
||||
// 将画布内容转为图像
|
||||
Platform.runLater(() -> {
|
||||
canvas.snapshot(null, writableImage);
|
||||
imageView.setImage(writableImage);
|
||||
});
|
||||
};
|
||||
|
||||
runAsync(() -> {
|
||||
CustomerFileTypeLocalVo localVo = getCachedBean(CompanyCustomerFileTypeService.class).findByLocaleAndType(getLocale(), fileViewModel.getType().get());
|
||||
gc.setFill(Color.WHITE);
|
||||
// 覆盖 fileViewModel.getType() 文字
|
||||
gc.fillRect(0, 55, canvas.getWidth(), 55);
|
||||
|
||||
// 绘制 文件类型
|
||||
gc.setFill(javafx.scene.paint.Color.RED);
|
||||
gc.setFont(h1);
|
||||
gc.fillText(localVo.getValue(), 50, 100);
|
||||
|
||||
updateImage.run();
|
||||
});
|
||||
|
||||
gc.setStroke(javafx.scene.paint.Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.strokeText("正在加载文件..." + pdfFile.getName(), 50, 150);
|
||||
updateImage.run();
|
||||
|
||||
runAsync(() -> {
|
||||
|
||||
//FileSystemUtils.
|
||||
long fileSize = pdfFile.length();
|
||||
byte[] bytes = new byte[0];
|
||||
try (java.io.FileInputStream fis = new java.io.FileInputStream(pdfFile);
|
||||
java.io.BufferedInputStream bis = new java.io.BufferedInputStream(fis)) {
|
||||
|
||||
bytes = new byte[(int) fileSize];
|
||||
int totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
byte[] buffer = new byte[8192]; // 8KB buffer
|
||||
|
||||
while ((bytesRead = bis.read(buffer)) != -1) {
|
||||
System.arraycopy(buffer, 0, bytes, totalBytesRead, bytesRead);
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
// 更新进度
|
||||
double progress = (double) totalBytesRead / fileSize * 100;
|
||||
final String status = String.format("正在加载文件... %s (%.1f%%)",
|
||||
pdfFile.getName(), progress);
|
||||
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 200, canvas.getWidth(), 80);
|
||||
|
||||
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.fillText(status, 50, 250);
|
||||
|
||||
|
||||
// 绘制进度条背景
|
||||
gc.setFill(Color.LIGHTGRAY);
|
||||
gc.fillRect(50, 270, 400, 20);
|
||||
|
||||
// 绘制进度条
|
||||
gc.setFill(Color.GREEN);
|
||||
gc.fillRect(50, 270, 400 * (totalBytesRead / (double) fileSize), 20);
|
||||
|
||||
// 绘制进度条边框
|
||||
gc.setStroke(Color.BLACK);
|
||||
gc.setLineWidth(1);
|
||||
gc.strokeRect(50, 270, 400, 20);
|
||||
|
||||
|
||||
updateImage.run();
|
||||
}
|
||||
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.fillText("Loading file: " + pdfFile.getName() + ", size: " + bytes.length + " bytes", 50, 320);
|
||||
updateImage.run();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try (PDDocument pdDocument = Loader.loadPDF(bytes)) {
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.fillText("PDF has " + pdDocument.getNumberOfPages() + " pages", 50, 380);
|
||||
updateImage.run();
|
||||
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);
|
||||
|
||||
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);
|
||||
|
||||
// 获取 BufferedImage 的宽度和高度
|
||||
int width = bufferedImage.getWidth();
|
||||
int height = bufferedImage.getHeight();
|
||||
canvas.resize(width, height);
|
||||
GraphicsContext graphic = canvas.getGraphicsContext2D();
|
||||
|
||||
WritableImage writableImage1 = new WritableImage(width, height);
|
||||
|
||||
PixelWriter pixelWriter = writableImage1.getPixelWriter();
|
||||
// 将 BufferedImage 的像素数据复制到 WritableImage
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int argb = bufferedImage.getRGB(x, y);
|
||||
pixelWriter.setArgb(x, y, argb);
|
||||
}
|
||||
}
|
||||
setStatus();
|
||||
Platform.runLater(() -> {
|
||||
imageView.setImage(writableImage1);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
setStatus(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -365,6 +531,7 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
stringPropertyUpdater.accept(property, newValue);
|
||||
});
|
||||
stringRadioGroupUpdater.accept(toggleGroup, property.getValue());
|
||||
}
|
||||
|
||||
private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleIntegerProperty property) {
|
||||
@@ -375,21 +542,8 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
|
||||
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
numberPropertyUpdater.accept(property, newValue);
|
||||
});
|
||||
numberRadioGroupUpdater.accept(toggleGroup, property.getValue());
|
||||
}
|
||||
|
||||
private void update(CompanyCustomerEvaluationFormFileVo formFile) {
|
||||
|
||||
viewModel.update(formFile);
|
||||
|
||||
// formFile.getScoreTemplateVersion();
|
||||
|
||||
catalogProperty.set(formFile.getCatalog());
|
||||
levelProperty.set(formFile.getLevel());
|
||||
score1Property.set(formFile.getScore1());
|
||||
score2Property.set(formFile.getScore2());
|
||||
score3Property.set(formFile.getScore3());
|
||||
score4Property.set(formFile.getScore4());
|
||||
score5Property.set(formFile.getScore5());
|
||||
creditLevelProperty.set(formFile.getCreditLevel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,9 +161,7 @@ public class CustomerTabSkinFile
|
||||
// 文件不是 Excel 文件时,打开编辑UI
|
||||
if (!FileUtils.withExtensions(item.getFilePath().get(), FileUtils.XLS,
|
||||
FileUtils.XLSX)) {
|
||||
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getEvaluationFormFileService()
|
||||
.findByCustomerFile(item.getId().get());
|
||||
CompanyCustomerEvaluationFormFileWindowController.show(evaluationFormFile,
|
||||
CompanyCustomerEvaluationFormFileWindowController.show(item,
|
||||
controller.root.getScene().getWindow());
|
||||
return;
|
||||
}
|
||||
@@ -211,10 +209,7 @@ public class CustomerTabSkinFile
|
||||
model.update(saved);
|
||||
dataSet.add(model);
|
||||
|
||||
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getCachedBean(
|
||||
CompanyCustomerEvaluationFormFileService.class).findByCustomerFile(saved);
|
||||
|
||||
CompanyCustomerEvaluationFormFileWindowController.show(evaluationFormFile,
|
||||
CompanyCustomerEvaluationFormFileWindowController.show(model,
|
||||
getTableView().getScene().getWindow());
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -47,7 +47,7 @@ public class EmployeeManagerSkin
|
||||
controller.accountColumn.setCellValueFactory(param -> param.getValue().getAccount());
|
||||
|
||||
controller.departmentColumn.setCellValueFactory(param -> param.getValue().getDepartment());
|
||||
controller.departmentColumn.setCellFactory(param -> new DepartmentTableCell<>(getDepartmentService()));
|
||||
controller.departmentColumn.setCellFactory(DepartmentTableCell.forTableColumn(getDepartmentService()));
|
||||
|
||||
controller.emailColumn.setCellValueFactory(param -> param.getValue().getEmail());
|
||||
controller.createdColumn.setCellValueFactory(param -> param.getValue().getCreated());
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.ecep.contract.controller.employee;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
@@ -48,7 +50,7 @@ public class EmployeeTabSkinRole
|
||||
|
||||
private void initializeListView() {
|
||||
// 非系统内置账户
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
Map<String, Object> params = ParamUtils.builder().build();
|
||||
List<EmployeeRoleVo> roles = getEmployeeRoleService().findAll(params, Pageable.ofSize(500)).getContent();
|
||||
|
||||
controller.rolesField.getSourceItems().setAll(roles);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ecep.contract.controller.permission;
|
||||
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.service.FunctionService;
|
||||
import com.ecep.contract.service.PermissionService;
|
||||
import com.ecep.contract.vm.FunctionViewModel;
|
||||
import com.ecep.contract.vo.FunctionVo;
|
||||
|
||||
public abstract class AbstEmployeeFunctionBasedTabSkin
|
||||
extends AbstEntityBasedTabSkin<EmployeeFunctionWindowController, FunctionVo, FunctionViewModel> {
|
||||
|
||||
public AbstEmployeeFunctionBasedTabSkin(EmployeeFunctionWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
FunctionService getFunctionService() {
|
||||
return getCachedBean(FunctionService.class);
|
||||
}
|
||||
|
||||
public PermissionService getPermissionService() {
|
||||
return controller.permissionService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ecep.contract.controller.permission;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
|
||||
import javafx.scene.control.Tab;
|
||||
|
||||
public class EmployeeFunctionTabSkinBase extends AbstEmployeeFunctionBasedTabSkin implements TabSkin {
|
||||
|
||||
public EmployeeFunctionTabSkinBase(EmployeeFunctionWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tab getTab() {
|
||||
return controller.baseInfoTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
|
||||
controller.keyField.textProperty().bindBidirectional(viewModel.getKey());
|
||||
controller.controllerField.textProperty().bindBidirectional(viewModel.getController());
|
||||
controller.iconField.textProperty().bindBidirectional(viewModel.getIcon());
|
||||
controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
|
||||
controller.activeField.selectedProperty().bindBidirectional(viewModel.getActive());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.ecep.contract.controller.permission;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.service.FunctionService;
|
||||
import com.ecep.contract.service.PermissionService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.FunctionViewModel;
|
||||
import com.ecep.contract.vo.FunctionVo;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/employee/function.fxml")
|
||||
public class EmployeeFunctionWindowController extends AbstEntityController<FunctionVo, FunctionViewModel> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EmployeeFunctionWindowController.class);
|
||||
|
||||
|
||||
public static void show(FunctionViewModel viewModel, Window window) {
|
||||
show(EmployeeFunctionWindowController.class, viewModel, window);
|
||||
}
|
||||
|
||||
public BorderPane root;
|
||||
public TabPane tabPane;
|
||||
|
||||
/*
|
||||
* 基本信息标签页
|
||||
*/
|
||||
public Tab baseInfoTab;
|
||||
public TextField nameField;
|
||||
public TextField keyField;
|
||||
public TextField controllerField;
|
||||
public TextField iconField;
|
||||
public TextField descriptionField;
|
||||
|
||||
@FXML
|
||||
public CheckBox activeField;
|
||||
public Label versionLabel;
|
||||
|
||||
/*
|
||||
* 权限标签页
|
||||
*/
|
||||
public Tab permissionTab;
|
||||
|
||||
@Autowired
|
||||
PermissionService permissionService;
|
||||
@Autowired
|
||||
FunctionService functionService;
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
getTitle().bind(viewModel.getName().map(name -> "[" + viewModel.getId().get() + "] " + name + " 功能详情"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTabSkins() {
|
||||
registerTabSkin(baseInfoTab, tab -> new EmployeeFunctionTabSkinBase(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionService getViewModelService() {
|
||||
return functionService;
|
||||
}
|
||||
}
|
||||
@@ -61,8 +61,6 @@ public class EmployeeFunctionsManagerWindowController
|
||||
public void onCreateNewAction(ActionEvent event) {
|
||||
}
|
||||
|
||||
public void onReBuildFilesAction(ActionEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionService getViewModelService() {
|
||||
|
||||
@@ -54,9 +54,6 @@ public class EmployeeRoleManagerWindowController
|
||||
public void onCreateNewAction(ActionEvent event) {
|
||||
}
|
||||
|
||||
public void onReBuildFilesAction(ActionEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmployeeRoleService getViewModelService() {
|
||||
return employeeRoleService;
|
||||
|
||||
@@ -50,7 +50,7 @@ public class EmployeeRoleTabSkinFunctions extends AbstEmployeeRoleBasedTabSkin {
|
||||
}
|
||||
|
||||
private void loadSelectedRoles() {
|
||||
List<FunctionVo> selectedRoles = getRoleService().getFunctionsByRoleId(viewModel.getId().get());
|
||||
List<FunctionVo> selectedRoles = getRoleService().getFunctionsByRole(getEntity());
|
||||
if (selectedRoles != null) {
|
||||
functionsField.getTargetItems().setAll(selectedRoles);
|
||||
}
|
||||
@@ -100,8 +100,7 @@ public class EmployeeRoleTabSkinFunctions extends AbstEmployeeRoleBasedTabSkin {
|
||||
|
||||
private void saveRoles(ActionEvent event) {
|
||||
EmployeeRoleVo entity = getEntity();
|
||||
entity.setFunctions(functionsField.getTargetItems());
|
||||
save(entity);
|
||||
getRoleService().saveRoleFunctions(entity, functionsField.getTargetItems());
|
||||
loadSelectedRoles();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,9 @@ public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSk
|
||||
|
||||
controller.saleTypeField.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Predicate<DeliverySignMethodVo> predicate = p -> {
|
||||
if (newValue == null) {
|
||||
return true;
|
||||
}
|
||||
return p == null || Objects.equals(p.getSaleTypeId(), newValue.getId());
|
||||
};
|
||||
|
||||
|
||||
@@ -88,8 +88,23 @@ public class ProjectCostWindowController
|
||||
|
||||
@Override
|
||||
protected void registerTabSkins() {
|
||||
|
||||
registerTabSkin(baseInfoTab, tab -> new ProjectCostTabSkinBase(this));
|
||||
registerTabSkin(itemTab, tab -> new ProjectCostTabSkinItems(this));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateViewModel(ProjectCostVo entity) {
|
||||
super.updateViewModel(entity);
|
||||
var tabPane = baseInfoTab.getTabPane();
|
||||
if (entity.getVersion() > 0) {
|
||||
if (!tabPane.getTabs().contains(approvalTab)) {
|
||||
tabPane.getTabs().add(approvalTab);
|
||||
}
|
||||
} else {
|
||||
tabPane.getTabs().remove(approvalTab);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package com.ecep.contract.controller.project.quotation;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.controller.customer.CompanyCustomerEvaluationFormFileWindowController;
|
||||
@@ -11,21 +7,13 @@ import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.converter.CompanyStringConverter;
|
||||
import com.ecep.contract.converter.EmployeeStringConverter;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
|
||||
import com.ecep.contract.service.CompanyCustomerFileService;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.ProjectQuotationService;
|
||||
import com.ecep.contract.service.ProjectService;
|
||||
import com.ecep.contract.service.*;
|
||||
import com.ecep.contract.util.ProxyUtils;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.ProjectQuotationViewModel;
|
||||
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import com.ecep.contract.vo.ProjectQuotationVo;
|
||||
import com.ecep.contract.vo.ProjectVo;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Tab;
|
||||
@@ -37,36 +25,22 @@ import javafx.util.converter.LocalDateTimeStringConverter;
|
||||
import javafx.util.converter.NumberStringConverter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* 项目报价单
|
||||
*/
|
||||
public class ProjectQuotationTabSkinBase
|
||||
extends AbstEntityBasedTabSkin<ProjectQuotationWindowController, ProjectQuotationVo, ProjectQuotationViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
@Setter
|
||||
private LocalDateTimeStringConverter localDateTimeStringConverter;
|
||||
@Setter
|
||||
private ProjectQuotationService projectQuotationService;
|
||||
@Setter
|
||||
private LocalDateStringConverter localDateStringConverter;
|
||||
@Setter
|
||||
private EmployeeStringConverter employeeStringConverter;
|
||||
@Setter
|
||||
private CompanyStringConverter companyStringConverter;
|
||||
@Setter
|
||||
private CompanyService companyService;
|
||||
@Setter
|
||||
private CustomerService customerService;
|
||||
@Setter
|
||||
private CompanyCustomerFileService customerFileService;
|
||||
@Setter
|
||||
private CompanyCustomerEvaluationFormFileService evaluationFormFileService;
|
||||
@Setter
|
||||
private ProjectService projectService;
|
||||
|
||||
private ProjectService getProjectService() {
|
||||
if (projectService == null) {
|
||||
projectService = getBean(ProjectService.class);
|
||||
}
|
||||
return projectService;
|
||||
return getCachedBean(ProjectService.class);
|
||||
}
|
||||
|
||||
public ProjectQuotationTabSkinBase(ProjectQuotationWindowController controller) {
|
||||
@@ -133,7 +107,10 @@ public class ProjectQuotationTabSkinBase
|
||||
new NumberStringConverter(getLocale()));
|
||||
|
||||
UITools.autoCompletion(controller.evaluationFileField, viewModel.getEvaluationFile(),
|
||||
getEvaluationFormFileService());
|
||||
getEvaluationFormFileService(), getEvaluationFormFileService().getStringConverter(), searchText -> {
|
||||
var project = getProjectService().findById(getEntity().getProject());
|
||||
return getEvaluationFormFileService().searchByCompany(project.getCustomerId(), searchText);
|
||||
});
|
||||
|
||||
controller.authorizationFileField.textProperty().bind(viewModel.getAuthorizationFile().map(File::getName));
|
||||
|
||||
@@ -220,7 +197,7 @@ public class ProjectQuotationTabSkinBase
|
||||
}
|
||||
|
||||
private void evaluationFileAutoCompletion(TextField textField,
|
||||
SimpleObjectProperty<CompanyCustomerEvaluationFormFileVo> property) {
|
||||
SimpleObjectProperty<CompanyCustomerEvaluationFormFileVo> property) {
|
||||
// 直接使用Vo类,不再需要手动转换
|
||||
}
|
||||
|
||||
|
||||
@@ -62,17 +62,13 @@ public class CompanyTabSkinBankAccount
|
||||
bankAccountSearchBtn.setOnAction(this::onTableRefreshAction);
|
||||
|
||||
bankAccountTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
|
||||
bankAccountTable_bankColumn.setCellValueFactory(param -> param.getValue().getBankId());
|
||||
bankAccountTable_bankColumn.setCellFactory(param -> new BankTableCell<>(getBankService()));
|
||||
bankAccountTable_bankColumn.setCellFactory(BankTableCell.forTableColumn(getBankService()));
|
||||
|
||||
bankAccountTable_openingBankColumn.setCellValueFactory(param -> param.getValue().getOpeningBank());
|
||||
bankAccountTable_accountColumn.setCellValueFactory(param -> param.getValue().getAccount());
|
||||
|
||||
|
||||
bankAccountTable_menu_refresh.setOnAction(this::onTableRefreshAction);
|
||||
bankAccountTable_menu_add.setOnAction(this::onTableAddAction);
|
||||
bankAccountTable_menu_del.setOnAction(this::onTableDeleteAction);
|
||||
|
||||
super.initializeTab();
|
||||
}
|
||||
|
||||
|
||||
@@ -164,18 +164,18 @@ public class CompanyTabSkinContract
|
||||
contractTable_startDateColumn.setCellValueFactory(param -> param.getValue().getStartDate());
|
||||
contractTable_endDateColumn.setCellValueFactory(param -> param.getValue().getEndDate());
|
||||
contractTable_setupPersonColumn.setCellValueFactory(param -> param.getValue().getSetupPerson());
|
||||
contractTable_setupPersonColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService()));
|
||||
contractTable_setupPersonColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
|
||||
contractTable_setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate());
|
||||
// contractTable_setupDateColumn.setSortable(true);
|
||||
// contractTable_setupDateColumn.setSortType(TableColumn.SortType.DESCENDING);
|
||||
contractTable_inurePersonColumn.setCellValueFactory(param -> param.getValue().getInurePerson());
|
||||
contractTable_inurePersonColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService()));
|
||||
contractTable_inurePersonColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
|
||||
contractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate());
|
||||
|
||||
contractTable_varyPersonColumn.setCellValueFactory(param -> param.getValue().getVaryPerson());
|
||||
contractTable_varyPersonColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService()));
|
||||
contractTable_varyPersonColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
|
||||
contractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate());
|
||||
contractTable_createdColumn.setCellValueFactory(param -> param.getValue().getCreated());
|
||||
|
||||
@@ -6,8 +6,12 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.ecep.contract.task.CompanyMergeClientTasker;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vo.CompanyOldNameVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -138,53 +142,20 @@ public class CompanyTabSkinOldName
|
||||
}
|
||||
|
||||
private void onTableMergeAction(ActionEvent event) {
|
||||
CompanyVo updater = getParent();
|
||||
HashSet<String> nameSet = new HashSet<>();
|
||||
nameSet.add(updater.getName());
|
||||
// 收集所有曾用名
|
||||
List<String> nameList = dataSet.stream()
|
||||
.map(viewModel -> viewModel.getName().get())
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
List<CompanyOldNameViewModel> removes = new ArrayList<>();
|
||||
for (CompanyOldNameViewModel viewModel : dataSet) {
|
||||
// 创建独立的WebSocket客户端任务器
|
||||
CompanyMergeClientTasker task = new CompanyMergeClientTasker();
|
||||
|
||||
if (!nameSet.add(viewModel.getName().get())) {
|
||||
// fixed 曾用名中有重复时,删除重复的
|
||||
deleteRow(viewModel);
|
||||
removes.add(viewModel);
|
||||
}
|
||||
}
|
||||
if (!removes.isEmpty()) {
|
||||
Platform.runLater(() -> {
|
||||
dataSet.removeAll(removes);
|
||||
});
|
||||
setStatus("移除重复的曾用名" + removes.size());
|
||||
}
|
||||
task.setCompany(getParent());
|
||||
task.setNameList(nameList);
|
||||
UITools.showTaskDialogAndWait("合并曾用名", task, null);
|
||||
|
||||
int size = nameSet.size();
|
||||
int count = 1;
|
||||
int merge = 0;
|
||||
for (String name : nameSet) {
|
||||
controller.setRightStatus(count + "/" + size);
|
||||
if (StringUtils.hasText(name)) {
|
||||
List<CompanyVo> list = getParentService().findAllByName(name);
|
||||
for (CompanyVo company : list) {
|
||||
// fixed 曾用名中有可能有 updater 的名字,会导致自己删除自己
|
||||
if (Objects.equals(company.getId(), updater.getId())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
getCompanyService().merge(company, updater);
|
||||
setStatus("并户 " + company.getName() + "[" + company.getId() + "] 到当前公司");
|
||||
merge++;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("合并 " + company.getName() + " -> " + updater.getName() + " 失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
count++;
|
||||
}
|
||||
if (merge == 0) {
|
||||
setStatus("没有需要并户的公司");
|
||||
}
|
||||
controller.setRightStatus("");
|
||||
loadTableDataSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,7 +172,7 @@ public class CompanyTabSkinOldName
|
||||
return;
|
||||
}
|
||||
String path = viewModel.getPath().get();
|
||||
if (StringUtils.hasText(path)) {
|
||||
if (org.springframework.util.StringUtils.hasText(path)) {
|
||||
if (item.startsWith(path)) {
|
||||
item = "~" + item.substring(path.length());
|
||||
}
|
||||
|
||||
@@ -97,8 +97,10 @@ public class CompanyTabSkinOther
|
||||
// Yu //
|
||||
public TitledPane yuCloudPane;
|
||||
public TextField cloudYuIdField;
|
||||
public TextField cloudYuCloudIdField;
|
||||
public TextField cloudYuLatestField;
|
||||
public TextField cloudYuVendorUpdateDateField;
|
||||
public TextField cloudYuCustomerUpdateDateField;
|
||||
public CheckBox cloudYuActiveField;
|
||||
public Label cloudYuVersionLabel;
|
||||
public Button yuCloudPaneSaveButton;
|
||||
|
||||
@@ -417,8 +419,10 @@ public class CompanyTabSkinOther
|
||||
}
|
||||
|
||||
cloudYuIdField.textProperty().bind(yuCloudInfoViewModel.getId().asString());
|
||||
cloudYuCloudIdField.textProperty().bindBidirectional(yuCloudInfoViewModel.getCloudId());
|
||||
cloudYuLatestField.textProperty().bind(yuCloudInfoViewModel.getLatestUpdate().map(MyDateTimeUtils::format));
|
||||
cloudYuVendorUpdateDateField.textProperty().bind(yuCloudInfoViewModel.getVendorUpdateDate().map(MyDateTimeUtils::format));
|
||||
cloudYuCustomerUpdateDateField.textProperty().bind(yuCloudInfoViewModel.getCustomerUpdateDate().map(MyDateTimeUtils::format));
|
||||
cloudYuActiveField.selectedProperty().bindBidirectional(yuCloudInfoViewModel.getActive());
|
||||
cloudYuVersionLabel.textProperty().bind(yuCloudInfoViewModel.getVersion().asString("Ver:%s"));
|
||||
|
||||
Button button = yuCloudPaneSaveButton;
|
||||
@@ -436,6 +440,34 @@ public class CompanyTabSkinOther
|
||||
button.setDisable(false);
|
||||
});
|
||||
});
|
||||
|
||||
DelayOnceExecutor saveExecutor = new DelayOnceExecutor(() -> {
|
||||
save(yuCloudInfoViewModel);
|
||||
cloudYuActiveField.setBorder(null);
|
||||
}, 2, TimeUnit.SECONDS).exception(e -> logger.error(e.getMessage(), e));
|
||||
cloudYuActiveField.setOnMouseClicked(event -> {
|
||||
cloudYuActiveField.setBorder(Border.stroke(Color.RED));
|
||||
saveExecutor.tick();
|
||||
});
|
||||
}
|
||||
|
||||
public void save(CloudYuInfoViewModel viewModel) {
|
||||
int infoId = viewModel.getId().get();
|
||||
YongYouU8Service service = getYongYouU8Service();
|
||||
CloudYuVo cloudYu = service.findById(infoId);
|
||||
if (cloudYu == null) {
|
||||
throw new RuntimeException("CloudTyc not found");
|
||||
}
|
||||
if (viewModel.copyTo(cloudYu)) {
|
||||
CloudYuVo saved = service.save(cloudYu);
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
viewModel.update(saved);
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
viewModel.update(saved);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onCloudYuUpdateButtonClicked(ActionEvent event) {
|
||||
@@ -516,9 +548,11 @@ public class CompanyTabSkinOther
|
||||
CompanyExtendInfoViewModel viewModel = extendInfoViewModel;
|
||||
CompanyExtendInfoService service = getExtendInfoService();
|
||||
CompanyExtendInfoVo extendInfo = service.findByCompany(company);
|
||||
Platform.runLater(() -> {
|
||||
viewModel.update(extendInfo);
|
||||
});
|
||||
if (extendInfo != null) {
|
||||
Platform.runLater(() -> {
|
||||
viewModel.update(extendInfo);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CloudRkService getCloudRkService() {
|
||||
|
||||
@@ -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<Object> {
|
||||
import java.util.Locale;
|
||||
|
||||
@Slf4j
|
||||
public class ContractFilesRebuildTasker extends Tasker<Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,9 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin {
|
||||
|
||||
controller.payWayField.textProperty().bind(viewModel.getPayWay().map(ContractPayWay::getText));
|
||||
|
||||
controller.nameField.textProperty().bind(viewModel.getName());
|
||||
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
|
||||
controller.nameField.editableProperty().bind(viewModel.getNameLocked());
|
||||
controller.contractNameLockedCk.selectedProperty().bindBidirectional(viewModel.getNameLocked());
|
||||
controller.codeField.textProperty().bind(viewModel.getCode());
|
||||
|
||||
controller.parentCodeField.textProperty().bindBidirectional(viewModel.getParentCode());
|
||||
@@ -351,17 +353,20 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin {
|
||||
}
|
||||
if (initialDirectory == null) {
|
||||
if (entity.getPayWay() == ContractPayWay.RECEIVE) {
|
||||
// 根据项目设置初始目录
|
||||
ProjectVo project = getProjectService().findById(entity.getProject());
|
||||
if (project != null) {
|
||||
// 根据项目销售方式设置初始目录
|
||||
ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId());
|
||||
if (saleType != null) {
|
||||
File dir = new File(saleType.getPath());
|
||||
if (saleType.isStoreByYear()) {
|
||||
dir = new File(dir, "20" + project.getCodeYear());
|
||||
Integer projectId = entity.getProject();
|
||||
if (projectId != null) {
|
||||
// 根据项目设置初始目录
|
||||
ProjectVo project = getProjectService().findById(projectId);
|
||||
if (project != null) {
|
||||
// 根据项目销售方式设置初始目录
|
||||
ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId());
|
||||
if (saleType != null) {
|
||||
File dir = new File(saleType.getPath());
|
||||
if (saleType.isStoreByYear()) {
|
||||
dir = new File(dir, "20" + project.getCodeYear());
|
||||
}
|
||||
initialDirectory = dir;
|
||||
}
|
||||
initialDirectory = dir;
|
||||
}
|
||||
}
|
||||
} else if (entity.getPayWay() == ContractPayWay.PAY) {
|
||||
|
||||
@@ -1,20 +1,39 @@
|
||||
package com.ecep.contract.controller.table.cell;
|
||||
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.service.BankService;
|
||||
import com.ecep.contract.vo.BankVo;
|
||||
|
||||
import javafx.util.Callback;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import static com.ecep.contract.SpringApp.getBean;
|
||||
|
||||
/**
|
||||
* 银行单元格
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> {
|
||||
/**
|
||||
* 创建单元格工厂
|
||||
*
|
||||
* @param bankService 银行服务
|
||||
* @return 单元格工厂
|
||||
*/
|
||||
public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
|
||||
BankService bankService) {
|
||||
return param -> new BankTableCell<V>(bankService);
|
||||
}
|
||||
|
||||
public BankTableCell(BankService service) {
|
||||
setService(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BankService getServiceBean() {
|
||||
return SpringApp.getBean(BankService.class);
|
||||
return getBean(BankService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(BankVo entity) {
|
||||
return getService().getStringConverter().toString(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,15 @@ import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.service.DepartmentService;
|
||||
import com.ecep.contract.vo.DepartmentVo;
|
||||
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.util.Callback;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
public class DepartmentTableCell<T> extends AsyncUpdateTableCell<T, Integer, DepartmentVo> {
|
||||
|
||||
public DepartmentTableCell(DepartmentService service) {
|
||||
setService(service);
|
||||
}
|
||||
@@ -17,4 +22,20 @@ public class DepartmentTableCell<T> extends AsyncUpdateTableCell<T, Integer, Dep
|
||||
return SpringApp.getBean(DepartmentService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为表格列创建 DepartmentTableCell 的工厂方法
|
||||
*
|
||||
* @param service 部门服务
|
||||
* @param <T> 表格行数据类型
|
||||
* @return 表格列的回调函数
|
||||
*/
|
||||
public static <T> Callback<TableColumn<T, Integer>, TableCell<T, Integer>> forTableColumn(DepartmentService service) {
|
||||
return column -> new DepartmentTableCell<>(service);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String format(DepartmentVo entity) {
|
||||
return getService().getStringConverter().toString(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ public class VendorTypeTableCell<T> extends AsyncUpdateTableCell<T, VendorType,
|
||||
protected VendorTypeLocalVo initialize() {
|
||||
VendorType item = getItem();
|
||||
VendorTypeLocalVo localVo = getServiceBean().findByType(item);
|
||||
System.out.println("item = " + item + ", localVo = " + getServiceBean().getStringConverter().toString(localVo));
|
||||
return localVo;
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ public class VendorApprovedListTabSkinVendors
|
||||
task.setEveryYearMinContracts(qualifiedVendorEveryYearMinContractsSpinner.getValue());
|
||||
|
||||
UITools.showTaskDialogAndWait("导入供方", task, null);
|
||||
loadTableDataSet();
|
||||
}
|
||||
|
||||
public void onVendorTableRefreshAction(ActionEvent event) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.ecep.contract.constant.CompanyVendorConstant;
|
||||
import com.ecep.contract.service.*;
|
||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
@@ -119,7 +120,8 @@ public class VendorApprovedListVendorExportTask extends Tasker<Object> {
|
||||
}
|
||||
|
||||
private File getVendorApprovedListTemplate() {
|
||||
return getVendorService().getVendorApprovedListTemplate();
|
||||
String path = getConfService().getString(CompanyVendorConstant.KEY_APPROVED_LIST_TEMPLATE);
|
||||
return new File(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -95,6 +95,7 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
|
||||
}
|
||||
|
||||
service = getBean(VendorApprovedService.class);
|
||||
// 检索供方
|
||||
VendorService vendorService = getBean(VendorService.class);
|
||||
Page<VendorVo> page = vendorService.findAll(
|
||||
ParamUtils.builder()
|
||||
@@ -136,7 +137,17 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
|
||||
// 明确 company 实例
|
||||
CompanyVo company = initializedVendorCompany(vendor);
|
||||
if (company == null) {
|
||||
// 无效
|
||||
// 无效, 删除异常数据
|
||||
holder.error("供方(#" + vendor.getId() + ")无对应的公司消息");
|
||||
List<VendorApprovedItemVo> items = getItemService().findAllByListAndVendor(approvedList, vendor);
|
||||
if (items != null && !items.isEmpty()) {
|
||||
// 删除
|
||||
MessageHolder subHolder = holder.sub(" - ");
|
||||
items.forEach(item -> {
|
||||
getItemService().delete(item);
|
||||
subHolder.info("删除 #" + item.getId() + ", " + item.getVendorName());
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -144,15 +155,15 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
|
||||
|
||||
VendorType vendorType = vendor.getType();
|
||||
if (vendorType == null) {
|
||||
subHolder.error("供方的分类为空");
|
||||
return;
|
||||
subHolder.debug("供方分类未设置");
|
||||
}
|
||||
|
||||
// 确认供方的developDate 是否在供方名录的发布日期之后
|
||||
LocalDate developDate = vendor.getDevelopDate();
|
||||
if (developDate == null) {
|
||||
subHolder.error("供方的开发日期为空");
|
||||
subHolder.error("开发日期未设置");
|
||||
} else if (developDate.isAfter(approvedList.getPublishDate())) {
|
||||
subHolder.info("开发日期在供方名录发布之后, 跳过...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,108 +171,13 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
|
||||
List<VendorApprovedItemVo> items = getItemService().findAllByListAndVendor(approvedList, vendor);
|
||||
if (items == null || items.isEmpty()) {
|
||||
// 供方不在供方名录中时,新建一个
|
||||
VendorApprovedItemVo item = new VendorApprovedItemVo();
|
||||
item.setListId(approvedList.getId());
|
||||
item.setVendorId(vendor.getId());
|
||||
|
||||
// 当前供应商分类是不合格供应商时
|
||||
if (vendorType == VendorType.UNQUALIFIED) {
|
||||
// 检索查看供方的评价表, 看与发布日期期间是否有评价表
|
||||
if (!checkAsQualifiedVendorByEvaluationFormFiles(vendor, item, subHolder)) {
|
||||
// 不合同供方,跳过
|
||||
if (logUnqualifiedVendor) {
|
||||
subHolder.info("供方是不合格供方, 不纳入名录");
|
||||
}
|
||||
// 上一年度的供方目录中是否有此供应商
|
||||
return;
|
||||
}
|
||||
item.setDescription("");
|
||||
}
|
||||
// 当前供应商分类是合格供方时
|
||||
else if (vendorType == VendorType.TYPICALLY) {
|
||||
// 协议经销商,认定为合格供方
|
||||
if (vendor.isProtocolProvider()) {
|
||||
item.setType(VendorType.QUALIFIED);
|
||||
item.setDescription("协议经销商");
|
||||
}
|
||||
// 非协议经销商
|
||||
else {
|
||||
// 查看供方的合同,看近3年期间是否有合同
|
||||
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
|
||||
if (contracts.isEmpty()) {
|
||||
// 没有合同,应该归入不合格供应商
|
||||
// 保持一般供应商分类,后期流程处理是否变更分类
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
if (logTypicallyVendorNoThreeYearContract) {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
|
||||
}
|
||||
item.setDescription(STR_MEET_UNQUALIFIED);
|
||||
} else {
|
||||
// 检查近3年期间是否都有合同
|
||||
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
|
||||
// 每年都有合同,合同数是否符合合格供方标准
|
||||
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
|
||||
// 保持一般供应商分类,后期流程处理是否变更分类
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
subHolder.info("供方近" + vendorContractMinusYear + "年每年都有合作, 符合合格供方标准");
|
||||
item.setDescription(STR_MEET_QUALIFIED);
|
||||
} else {
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
|
||||
item.setDescription(STR_MEET_TYPICALLY);
|
||||
}
|
||||
} else {
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
item.setDescription("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 当前供应商分类是合格供方时
|
||||
else if (vendorType == VendorType.QUALIFIED) {
|
||||
// 查看供方的合同,看近3年期间是否有合同
|
||||
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
|
||||
item.setType(vendorType);
|
||||
if (contracts.isEmpty()) {
|
||||
if (logTypicallyVendorNoThreeYearContract) {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
|
||||
}
|
||||
item.setDescription(STR_MEET_UNQUALIFIED);
|
||||
} else {
|
||||
// 检查近3年期间是否都有合同
|
||||
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
|
||||
// 每年都有合同,合同数是否符合合格供方标准
|
||||
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
|
||||
item.setDescription("");
|
||||
} else {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
|
||||
item.setDescription(STR_MEET_TYPICALLY);
|
||||
}
|
||||
} else {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年非每年都有合作");
|
||||
item.setDescription("");
|
||||
}
|
||||
}
|
||||
}
|
||||
// 未知分类时
|
||||
else {
|
||||
item.setDescription("未知供方分类");
|
||||
}
|
||||
|
||||
// 匹配的历史名称
|
||||
updateVendorNameWithOldName(vendor, item);
|
||||
getItemService().save(item);
|
||||
syncWhenNoItem(vendor, company, subHolder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.size() == 1) {
|
||||
VendorApprovedItemVo first = items.getFirst();
|
||||
if (!StringUtils.hasText(first.getVendorName())) {
|
||||
updateVendorNameWithOldName(vendor, first);
|
||||
}
|
||||
updateItem(vendor, first, subHolder);
|
||||
return;
|
||||
}
|
||||
VendorApprovedItemVo first = items.getFirst();
|
||||
syncItem(vendor, company, first, subHolder);
|
||||
|
||||
|
||||
for (int i = 1; i < items.size(); i++) {
|
||||
VendorApprovedItemVo item = items.get(i);
|
||||
@@ -270,11 +186,123 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVendorNameWithOldName(VendorVo vendor, VendorApprovedItemVo item) {
|
||||
CompanyVo company = initializedVendorCompany(vendor);
|
||||
if (company == null) {
|
||||
/**
|
||||
* 当没有匹配的供方名录项时
|
||||
*
|
||||
* @param vendor
|
||||
* @param company
|
||||
* @param subHolder
|
||||
*/
|
||||
private void syncWhenNoItem(VendorVo vendor, CompanyVo company, MessageHolder subHolder) {
|
||||
VendorType vendorType = vendor.getType();
|
||||
if (vendorType == null) {
|
||||
subHolder.debug("供方分类未设置");
|
||||
}
|
||||
|
||||
VendorApprovedItemVo item = new VendorApprovedItemVo();
|
||||
item.setListId(approvedList.getId());
|
||||
item.setVendorId(vendor.getId());
|
||||
|
||||
// 当前供应商分类是不合格供应商时
|
||||
if (vendorType == VendorType.UNQUALIFIED) {
|
||||
// 检索查看供方的评价表, 看与发布日期期间是否有评价表
|
||||
if (!checkAsQualifiedVendorByEvaluationFormFiles(vendor, item, subHolder)) {
|
||||
// 不合同供方,跳过
|
||||
if (logUnqualifiedVendor) {
|
||||
subHolder.info("供方是不合格供方, 不纳入名录");
|
||||
}
|
||||
// 上一年度的供方目录中是否有此供应商
|
||||
return;
|
||||
}
|
||||
item.setDescription("");
|
||||
}
|
||||
// 当前供应商分类是合格供方时
|
||||
else if (vendorType == VendorType.TYPICALLY) {
|
||||
// 协议经销商,认定为合格供方
|
||||
if (vendor.isProtocolProvider()) {
|
||||
item.setType(VendorType.QUALIFIED);
|
||||
item.setDescription("协议经销商");
|
||||
}
|
||||
// 非协议经销商
|
||||
else {
|
||||
// 查看供方的合同,看近3年期间是否有合同
|
||||
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
|
||||
if (contracts.isEmpty()) {
|
||||
// 没有合同,应该归入不合格供应商
|
||||
// 保持一般供应商分类,后期流程处理是否变更分类
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
if (logTypicallyVendorNoThreeYearContract) {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
|
||||
}
|
||||
item.setDescription(STR_MEET_UNQUALIFIED + "(缺合同1)");
|
||||
} else {
|
||||
// 检查近3年期间是否都有合同
|
||||
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
|
||||
// 每年都有合同,合同数是否符合合格供方标准
|
||||
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
|
||||
// 保持一般供应商分类,后期流程处理是否变更分类
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
subHolder.info("供方近" + vendorContractMinusYear + "年每年都有合作, 符合合格供方标准");
|
||||
item.setDescription(STR_MEET_QUALIFIED);
|
||||
} else {
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
|
||||
item.setDescription(STR_MEET_TYPICALLY);
|
||||
}
|
||||
} else {
|
||||
item.setType(VendorType.TYPICALLY);
|
||||
item.setDescription("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 当前供应商分类是合格供方时
|
||||
else if (vendorType == VendorType.QUALIFIED) {
|
||||
// 查看供方的合同,看近3年期间是否有合同
|
||||
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
|
||||
item.setType(vendorType);
|
||||
if (contracts.isEmpty()) {
|
||||
if (logTypicallyVendorNoThreeYearContract) {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
|
||||
}
|
||||
item.setDescription(STR_MEET_UNQUALIFIED + "(缺合同2)");
|
||||
} else {
|
||||
// 检查近3年期间是否都有合同
|
||||
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
|
||||
// 每年都有合同,合同数是否符合合格供方标准
|
||||
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
|
||||
item.setDescription("");
|
||||
} else {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
|
||||
item.setDescription(STR_MEET_TYPICALLY);
|
||||
}
|
||||
} else {
|
||||
subHolder.warn("供方近" + vendorContractMinusYear + "年非每年都有合作");
|
||||
item.setDescription("");
|
||||
}
|
||||
}
|
||||
}
|
||||
// 未知分类时
|
||||
else {
|
||||
item.setDescription("未知供方分类");
|
||||
}
|
||||
|
||||
// 匹配的历史名称
|
||||
updateVendorNameWithOldName(company, item);
|
||||
getItemService().save(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配历史名称,当前供方名称为空时
|
||||
* @param company
|
||||
* @param item
|
||||
*/
|
||||
private void updateVendorNameWithOldName(CompanyVo company, VendorApprovedItemVo item) {
|
||||
if (StringUtils.hasText(item.getVendorName())) {
|
||||
// 已经有供方名称时,不更新
|
||||
return;
|
||||
}
|
||||
|
||||
CompanyOldNameVo companyOldName = getCompanyOldNameService().findMatchByDate(company,
|
||||
approvedList.getPublishDate());
|
||||
if (companyOldName != null) {
|
||||
@@ -288,11 +316,12 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
|
||||
@Setter
|
||||
private boolean logUnqualifiedVendorRemove = true;
|
||||
|
||||
private void updateItem(
|
||||
VendorVo vendor, VendorApprovedItemVo item, MessageHolder holder) {
|
||||
private void syncItem(
|
||||
VendorVo vendor, CompanyVo company, VendorApprovedItemVo item, MessageHolder holder) {
|
||||
VendorType t1 = item.getType();
|
||||
VendorType vendorType = vendor.getType();
|
||||
VendorApprovedItemService itemService = getItemService();
|
||||
updateVendorNameWithOldName(company, item);
|
||||
if (t1 != vendorType) {
|
||||
holder.warn("注意分类不一致, " + t1 + ", " + vendorType + ".");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ecep.contract.controller.vendor.approved_list;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.ecep.contract.task.VendorApprovedListMakePathTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -104,15 +105,13 @@ public class VendorApprovedListWindowController
|
||||
}
|
||||
|
||||
public void onApprovedListCreatePathAction(ActionEvent event) {
|
||||
VendorApprovedListMakePathTask task = new VendorApprovedListMakePathTask();
|
||||
task.setApprovedList(getEntity());
|
||||
UITools.showTaskDialogAndWait("创建目录", task, null);
|
||||
|
||||
int id = viewModel.getId().get();
|
||||
VendorApprovedVo list = service.findById(id);
|
||||
|
||||
if (service.makePathAbsent(list)) {
|
||||
VendorApprovedVo saved = service.save(list);
|
||||
viewModel.update(saved);
|
||||
} else {
|
||||
setStatus("目录存在或创建失败");
|
||||
}
|
||||
viewModel.update(list);
|
||||
}
|
||||
|
||||
public void onApprovedListChangePathAction(ActionEvent event) {
|
||||
@@ -123,8 +122,4 @@ public class VendorApprovedListWindowController
|
||||
task.setApprovedList(getEntity());
|
||||
UITools.showTaskDialogAndWait("导出供方", task, null);
|
||||
}
|
||||
|
||||
private void save(ActionEvent event) {
|
||||
saveTabSkins();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.ecep.contract.converter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
|
||||
import com.ecep.contract.service.CompanyCustomerFileService;
|
||||
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
|
||||
import com.ecep.contract.vo.CustomerFileVo;
|
||||
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
/**
|
||||
* CompanyCustomerEvaluationFormFileVo的StringConverter实现,用于JavaFX控件中的显示和转换
|
||||
*/
|
||||
public class CompanyCustomerEvaluationFormFileStringConverter
|
||||
extends StringConverter<CompanyCustomerEvaluationFormFileVo> {
|
||||
private final CompanyCustomerEvaluationFormFileService service;
|
||||
private CompanyCustomerFileService customerFileService;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param service CompanyCustomerEvaluationFormFileService实例
|
||||
*/
|
||||
public CompanyCustomerEvaluationFormFileStringConverter(CompanyCustomerEvaluationFormFileService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
private CompanyCustomerFileService getCustomerFileService() {
|
||||
if (customerFileService == null) {
|
||||
customerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
|
||||
}
|
||||
return customerFileService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将CompanyCustomerEvaluationFormFileVo对象转换为字符串
|
||||
*
|
||||
* @param formFile CompanyCustomerEvaluationFormFileVo对象
|
||||
* @return 转换后的字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString(CompanyCustomerEvaluationFormFileVo formFile) {
|
||||
if (formFile == null || formFile.getCustomerFile() == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
CustomerFileVo customerFile = getCustomerFileService().findById(formFile.getCustomerFile());
|
||||
if (customerFile == null || !StringUtils.hasText(customerFile.getFilePath())) {
|
||||
return "#" + formFile.getCustomerFile();
|
||||
}
|
||||
File file = new File(customerFile.getFilePath());
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串转换为CompanyCustomerEvaluationFormFileVo对象
|
||||
*
|
||||
* @param string 字符串
|
||||
* @return 转换后的CompanyCustomerEvaluationFormFileVo对象
|
||||
*/
|
||||
@Override
|
||||
public CompanyCustomerEvaluationFormFileVo fromString(String string) {
|
||||
// 由于文件名称可能不唯一,这里返回null
|
||||
// 实际使用时应该通过ID或其他唯一标识来查找
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.ecep.contract.converter;
|
||||
|
||||
import com.ecep.contract.service.DeliverySignMethodService;
|
||||
import com.ecep.contract.vo.DeliverySignMethodVo;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
/**
|
||||
* DeliverySignMethodVo的StringConverter实现,用于JavaFX控件中的显示和转换
|
||||
*/
|
||||
public class DeliverySignMethodStringConverter extends StringConverter<DeliverySignMethodVo> {
|
||||
private final DeliverySignMethodService service;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param service DeliverySignMethodService实例
|
||||
*/
|
||||
public DeliverySignMethodStringConverter(DeliverySignMethodService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将DeliverySignMethodVo对象转换为字符串
|
||||
*
|
||||
* @param method DeliverySignMethodVo对象
|
||||
* @return 转换后的字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString(DeliverySignMethodVo method) {
|
||||
return method == null ? "" : method.getCode() + " " + method.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串转换为DeliverySignMethodVo对象
|
||||
*
|
||||
* @param string 字符串
|
||||
* @return 转换后的DeliverySignMethodVo对象
|
||||
*/
|
||||
@Override
|
||||
public DeliverySignMethodVo fromString(String string) {
|
||||
if (string == null || string.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// 查找所有方法,然后通过名称匹配
|
||||
return service.findByName(string);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,37 @@
|
||||
package com.ecep.contract.converter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.service.DepartmentService;
|
||||
import com.ecep.contract.vo.DepartmentVo;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
public class DepartmentStringConverter extends EntityStringConverter<DepartmentVo> {
|
||||
@Lazy
|
||||
@Autowired
|
||||
/**
|
||||
* 部门字符串转换器
|
||||
*/
|
||||
public class DepartmentStringConverter extends StringConverter<DepartmentVo> {
|
||||
private DepartmentService service;
|
||||
|
||||
public DepartmentStringConverter() {
|
||||
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
setInitialized(department -> service.findById(department.getId()));
|
||||
setSuggestion(service::search);
|
||||
public DepartmentStringConverter(DepartmentService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(DepartmentVo department) {
|
||||
if (department == null) {
|
||||
return "-";
|
||||
}
|
||||
return department.getCode() + " " + department.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DepartmentVo fromString(String string) {
|
||||
if (service == null || string == null || string.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return service.findByCode(string.trim());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,12 +22,12 @@ public class ProjectTypeStringConverter extends StringConverter<ProjectTypeVo> {
|
||||
/**
|
||||
* 将ProjectTypeVo对象转换为字符串
|
||||
*
|
||||
* @param object ProjectTypeVo对象
|
||||
* @param type ProjectTypeVo对象
|
||||
* @return 转换后的字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString(ProjectTypeVo object) {
|
||||
return object == null ? "" : object.getName();
|
||||
public String toString(ProjectTypeVo type) {
|
||||
return type == null ? "" : type.getCode() + " " + type.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,9 @@ public class VendorCatalogStringConverter extends StringConverter<VendorCatalogV
|
||||
|
||||
@Override
|
||||
public String toString(VendorCatalogVo object) {
|
||||
if (object == null) {
|
||||
return "-";
|
||||
}
|
||||
return object.getName();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
package com.ecep.contract.converter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import com.ecep.contract.service.VendorGroupService;
|
||||
import com.ecep.contract.vo.VendorGroupVo;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
public class VendorGroupStringConverter extends EntityStringConverter<VendorGroupVo> {
|
||||
@Lazy
|
||||
@Autowired
|
||||
public class VendorGroupStringConverter extends StringConverter<VendorGroupVo> {
|
||||
private VendorGroupService service;
|
||||
|
||||
public VendorGroupStringConverter() {
|
||||
|
||||
public VendorGroupStringConverter(VendorGroupService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
setInitialized(group -> service.findById(group.getId()));
|
||||
setSuggestion(service::search);
|
||||
@Override
|
||||
public String toString(VendorGroupVo object) {
|
||||
return object == null ? "" : object.getCode() + " " + object.getName();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public VendorGroupVo fromString(String string) {
|
||||
return service.findByCode(string);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.controlsfx.control.TaskProgressView;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -21,6 +23,8 @@ import lombok.Data;
|
||||
|
||||
@Service
|
||||
public class CloudRkService extends QueryService<CloudRkVo, CloudRkViewModel> {
|
||||
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class EntInfo {
|
||||
@@ -69,6 +73,10 @@ public class CloudRkService extends QueryService<CloudRkVo, CloudRkViewModel> {
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Page<CloudRkVo> findAllByCompany(CompanyVo company) {
|
||||
return findAll(ParamUtils.builder().equals("company", company.getId()).build(), Pageable.unpaged());
|
||||
}
|
||||
|
||||
public boolean checkBlackListUpdateElapse(CompanyVo company, CloudRkVo cloudRk) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'checkBlackListUpdateElapse'");
|
||||
@@ -78,4 +86,12 @@ public class CloudRkService extends QueryService<CloudRkVo, CloudRkViewModel> {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'updateBlackList'");
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
List<CloudRkVo> list = findAllByCompany(from).getContent();
|
||||
for (CloudRkVo item : list) {
|
||||
item.setCompanyId(to.getId());
|
||||
save(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -77,4 +79,16 @@ public class CloudTycService extends QueryService<CloudTycVo, CloudTycInfoViewMo
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Page<CloudTycVo> findAllByCompany(CompanyVo company) {
|
||||
return findAll(ParamUtils.builder().equals("company", company.getId()).build(), Pageable.unpaged());
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
List<CloudTycVo> list = findAllByCompany(from).getContent();
|
||||
for (CloudTycVo item : list) {
|
||||
item.setCompanyId(to.getId());
|
||||
save(item);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -32,4 +33,21 @@ public class CompanyContactService extends QueryService<CompanyContactVo, Compan
|
||||
return page.getContent().getFirst();
|
||||
}
|
||||
|
||||
public List<CompanyContactVo> findAllByCompany(CompanyVo company, LocalDate beginDate, LocalDate endDate) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("company", company.getId())
|
||||
.between("setupDate", beginDate, endDate)
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
List<CompanyContactVo> contacts = findAllByCompany(from, LocalDate.MIN, LocalDate.MAX);
|
||||
if (contacts == null || contacts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (CompanyContactVo contact : contacts) {
|
||||
contact.setCompanyId(to.getId());
|
||||
save(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,12 @@ public class CompanyCustomerEntityService extends QueryService<CompanyCustomerEn
|
||||
.equals("customer", customer.getId()).build(), Pageable.unpaged())
|
||||
.getContent();
|
||||
}
|
||||
|
||||
public void mergeTo(CustomerVo from, CustomerVo to) {
|
||||
List<CompanyCustomerEntityVo> fromEntities = findAllByCustomer(from);
|
||||
for (CompanyCustomerEntityVo fromEntity : fromEntities) {
|
||||
fromEntity.setCustomerId(to.getId());
|
||||
save(fromEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import com.ecep.contract.converter.CompanyCustomerEvaluationFormFileStringConverter;
|
||||
import javafx.util.StringConverter;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -14,6 +16,8 @@ import java.util.List;
|
||||
public class CompanyCustomerEvaluationFormFileService
|
||||
extends QueryService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> {
|
||||
|
||||
private final StringConverter<CompanyCustomerEvaluationFormFileVo> stringConverter = new CompanyCustomerEvaluationFormFileStringConverter(this);
|
||||
|
||||
/**
|
||||
* 根据ID查找客户评估表文件
|
||||
*/
|
||||
@@ -25,18 +29,17 @@ public class CompanyCustomerEvaluationFormFileService
|
||||
* 根据客户文件查找评估表文件
|
||||
*/
|
||||
public CompanyCustomerEvaluationFormFileVo findByCustomerFile(CustomerFileVo customerFile) {
|
||||
return findByCustomerFile(customerFile.getId());
|
||||
return findOneByProperty("customerFile", customerFile.getId());
|
||||
}
|
||||
|
||||
public CompanyCustomerEvaluationFormFileVo findByCustomerFile(Integer customerFileId) {
|
||||
List<CompanyCustomerEvaluationFormFileVo> page = findAll(ParamUtils.builder()
|
||||
.equals("customerFile", customerFileId)
|
||||
.build(), Pageable.ofSize(1))
|
||||
.getContent();
|
||||
if (page.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.getFirst();
|
||||
return findOneByProperty("customerFile", customerFileId);
|
||||
}
|
||||
|
||||
public List<CompanyCustomerEvaluationFormFileVo> searchByCompany(Integer companyId, String searchText) {
|
||||
ParamUtils.Builder params = getSpecification(searchText);
|
||||
params.equals("company", companyId);
|
||||
return findAll(params.build(), Pageable.ofSize(10)).getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,4 +49,10 @@ public class CompanyCustomerEvaluationFormFileService
|
||||
return super.save(formFile);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public StringConverter<CompanyCustomerEvaluationFormFileVo> getStringConverter() {
|
||||
return stringConverter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -121,4 +121,12 @@ public class CompanyCustomerFileService extends QueryService<CustomerFileVo, Cus
|
||||
public CustomerFileVo save(CustomerFileVo entity) {
|
||||
return super.save(entity);
|
||||
}
|
||||
|
||||
public void mergeTo(CustomerVo from, CustomerVo to) {
|
||||
List<CustomerFileVo> fromFiles = findAllByCustomer(from);
|
||||
for (CustomerFileVo fromFile : fromFiles) {
|
||||
fromFile.setCustomer(to.getId());
|
||||
save(fromFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import javafx.util.StringConverter;
|
||||
@CacheConfig(cacheNames = "company-customer-file-type")
|
||||
public class CompanyCustomerFileTypeService
|
||||
extends QueryService<CustomerFileTypeLocalVo, CompanyCustomerFileTypeLocalViewModel> {
|
||||
private final StringConverter<CustomerFileTypeLocalVo> stringConverter = new CustomerFileTypeStringConverter(this);
|
||||
private final CustomerFileTypeStringConverter stringConverter = new CustomerFileTypeStringConverter(this);
|
||||
|
||||
@Cacheable(key = "#p0")
|
||||
@Override
|
||||
@@ -39,13 +39,13 @@ public class CompanyCustomerFileTypeService
|
||||
return super.findAll();
|
||||
}
|
||||
|
||||
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") })
|
||||
@Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
|
||||
@Override
|
||||
public CustomerFileTypeLocalVo save(CustomerFileTypeLocalVo entity) {
|
||||
return super.save(entity);
|
||||
}
|
||||
|
||||
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") })
|
||||
@Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
|
||||
@Override
|
||||
public void delete(CustomerFileTypeLocalVo entity) {
|
||||
super.delete(entity);
|
||||
@@ -53,8 +53,7 @@ public class CompanyCustomerFileTypeService
|
||||
|
||||
@Cacheable
|
||||
public Map<CustomerFileType, CustomerFileTypeLocalVo> findAll(Locale locale) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("lang", locale.toLanguageTag());
|
||||
Map<String, Object> params = ParamUtils.builder().equals("lang", locale.toLanguageTag()).build();
|
||||
return findAll(params, Pageable.unpaged()).stream()
|
||||
.collect(Collectors.toMap(CustomerFileTypeLocalVo::getType, Function.identity()));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.CompanyInvoiceInfoViewModel;
|
||||
import com.ecep.contract.vo.CompanyInvoiceInfoVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
@@ -15,9 +16,8 @@ import java.util.Map;
|
||||
public class CompanyInvoiceInfoService extends QueryService<CompanyInvoiceInfoVo, CompanyInvoiceInfoViewModel> {
|
||||
|
||||
public List<CompanyInvoiceInfoVo> searchByCompany(CompanyVo company, String searchText) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("company", company);
|
||||
params.put("searchText", searchText);
|
||||
Map<String, Object> params = ParamUtils.builder().equals("company", company.getId())
|
||||
.search(searchText).build();
|
||||
return findAll(params, Pageable.unpaged()).getContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import com.ecep.contract.util.MyStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -70,24 +71,29 @@ public class CompanyOldNameService extends QueryService<CompanyOldNameVo, Compan
|
||||
}
|
||||
|
||||
public CompanyOldNameVo findMatchByDate(CompanyVo company, LocalDate localDate) {
|
||||
Page<CompanyOldNameVo> page = findAll(ParamUtils.builder()
|
||||
.equals("ambiguity", false)
|
||||
findAll(ParamUtils.builder()
|
||||
.equals("company", company.getId())
|
||||
.and(b -> b.isNotNull("beginDate").greaterThan("beginDate", localDate))
|
||||
.and(b -> b.isNotNull("endDate").lessThan("endDate", localDate))
|
||||
.build(), Pageable.ofSize(1));
|
||||
if (page.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.getContent().getFirst();
|
||||
.equals("ambiguity", true)
|
||||
.isNotNull("beginDate")
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<CompanyOldNameVo> findAllByCompanyAndName(CompanyVo company, String oldName) {
|
||||
return findAll(ParamUtils.builder().equals("company", company.getId()).equals("oldName", oldName).build(),
|
||||
return findAll(ParamUtils.builder().equals("company", company.getId()).equals("name", oldName).build(),
|
||||
Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public List<CompanyOldNameVo> findAllByCompany(IdentityEntity company) {
|
||||
return findAll(ParamUtils.equal("company", company.getId()), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
List<CompanyOldNameVo> fromOldNames = findAllByCompany(from);
|
||||
for (CompanyOldNameVo fromOldName : fromOldNames) {
|
||||
fromOldName.setMemo(MyStringUtils.appendIfAbsent(fromOldName.getMemo(), "转自 " + from.getId()));
|
||||
fromOldName.setCompanyId(to.getId());
|
||||
save(fromOldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
@@ -57,14 +59,26 @@ public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
|
||||
}
|
||||
|
||||
public List<CompanyVo> findAllByName(String name) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("name", name);
|
||||
Map<String, Object> params = ParamUtils.builder().equals("name", name).build();
|
||||
return findAll(params, Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public void merge(CompanyVo company, CompanyVo updater) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'merge'");
|
||||
public void merge(CompanyVo from, CompanyVo to) {
|
||||
|
||||
SpringApp.getBean(CloudRkService.class).mergeTo(from, to);
|
||||
SpringApp.getBean(CloudTycService.class).mergeTo(from, to);
|
||||
SpringApp.getBean(YongYouU8Service.class).mergeTo(from, to);
|
||||
|
||||
SpringApp.getBean(CompanyOldNameService.class).mergeTo(from, to);
|
||||
SpringApp.getBean(CompanyContactService.class).mergeTo(from, to);
|
||||
|
||||
// 供应商和客户
|
||||
SpringApp.getBean(VendorService.class).mergeTo(from, to);
|
||||
SpringApp.getBean(CustomerService.class).mergeTo(from, to);
|
||||
|
||||
SpringApp.getBean(ContractService.class).mergeTo(from, to);
|
||||
SpringApp.getBean(CompanyContactService.class).mergeTo(from, to);
|
||||
delete(from);
|
||||
}
|
||||
|
||||
public CompanyVo createNewCompany(String newCompanyName) {
|
||||
|
||||
@@ -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<ContractBalanceVo, ContractBalanceViewModel> {
|
||||
|
||||
@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<ContractBalanceVo> findAllByContractId(Integer contractId) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("contractId", contractId)
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据业务员ID查询余额记录
|
||||
*
|
||||
* @param employeeId 业务员ID
|
||||
* @return 合同余额列表
|
||||
*/
|
||||
public List<ContractBalanceVo> 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<ContractBalanceVo> 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<ContractBalanceVo> 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<ContractBalanceVo> findAllByPzId(String pzId) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("pzId", pzId)
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据JSD类型查询余额记录
|
||||
*
|
||||
* @param jsdType JSD类型
|
||||
* @return 合同余额列表
|
||||
*/
|
||||
public List<ContractBalanceVo> 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<ContractBalanceVo> 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<ContractBalanceVo> findAll(Map<String, Object> params, Pageable pageable) {
|
||||
return super.findAll(params, pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有余额记录
|
||||
*
|
||||
* @return 合同余额列表
|
||||
*/
|
||||
public List<ContractBalanceVo> findAll() {
|
||||
return super.findAll();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,12 @@ import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
@Service
|
||||
public class ContractFileService extends QueryService<ContractFileVo, ContractFileViewModel> {
|
||||
|
||||
@Override
|
||||
public ContractFileVo findById(Integer id) {
|
||||
return super.findById(id);
|
||||
}
|
||||
|
||||
public List<ContractFileVo> findAllByContract(ContractVo contract) {
|
||||
return findAll(ParamUtils.equal("contract", contract.getId()), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ecep.contract.service;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -9,6 +10,7 @@ import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.CachePut;
|
||||
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;
|
||||
|
||||
@@ -37,13 +39,13 @@ public class ContractFileTypeService extends QueryService<ContractFileTypeLocalV
|
||||
return super.findAll();
|
||||
}
|
||||
|
||||
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") })
|
||||
@Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
|
||||
@Override
|
||||
public ContractFileTypeLocalVo save(ContractFileTypeLocalVo entity) {
|
||||
return super.save(entity);
|
||||
}
|
||||
|
||||
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") })
|
||||
@Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
|
||||
@Override
|
||||
public void delete(ContractFileTypeLocalVo entity) {
|
||||
super.delete(entity);
|
||||
@@ -55,6 +57,10 @@ public class ContractFileTypeService extends QueryService<ContractFileTypeLocalV
|
||||
.collect(Collectors.toMap(ContractFileTypeLocalVo::getType, Function.identity()));
|
||||
}
|
||||
|
||||
public CompletableFuture<Page<ContractFileTypeLocalVo>> asyncFindAll(Locale locale) {
|
||||
return asyncFindAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).build(), Pageable.unpaged());
|
||||
}
|
||||
|
||||
@Cacheable
|
||||
public ContractFileTypeLocalVo findByType(Locale locale, ContractFileType type) {
|
||||
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("type", type).build(), Pageable.ofSize(1)).stream().findFirst().orElse(null);
|
||||
|
||||
@@ -5,6 +5,9 @@ import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.util.MyStringUtils;
|
||||
import com.ecep.contract.vo.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
@@ -20,10 +23,6 @@ import com.ecep.contract.constant.ContractConstant;
|
||||
import com.ecep.contract.util.ContractUtils;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.ContractViewModel;
|
||||
import com.ecep.contract.vo.ContractFileVo;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
import com.ecep.contract.vo.ProjectVo;
|
||||
import com.ecep.contract.vo.VendorVo;
|
||||
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "contract")
|
||||
@@ -158,10 +157,24 @@ public class ContractService extends QueryService<ContractVo, ContractViewModel>
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public List<ContractVo> findAllByCompanyCustomer(CustomerVo customer, LocalDate beginDate, LocalDate endDate) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("company", customer.getCompanyId())
|
||||
.between("setupDate", beginDate, endDate)
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public List<ContractVo> findAllByCompany(CompanyVo company, LocalDate beginDate, LocalDate endDate) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("company", company.getId())
|
||||
.between("setupDate", beginDate, endDate)
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public List<ContractVo> findAllSalesByProject(ProjectVo project) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("parentCode", "")
|
||||
.equals("project", project.getId()).build(),
|
||||
.equals("parentCode", "")
|
||||
.equals("project", project.getId()).build(),
|
||||
Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
@@ -183,4 +196,18 @@ public class ContractService extends QueryService<ContractVo, ContractViewModel>
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'syncContractFile'");
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
|
||||
List<ContractVo> contracts = findAllByCompany(from, LocalDate.MIN, LocalDate.MAX);
|
||||
if (contracts == null || contracts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ContractVo contract : contracts) {
|
||||
contract.setDescription(MyStringUtils.appendIfAbsent(contract.getDescription(), "转自 " + from.getId()));
|
||||
contract.setCompanyId(to.getId());
|
||||
save(contract);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import org.springframework.stereotype.Service;
|
||||
import com.ecep.contract.vm.ContractTypeViewModel;
|
||||
import com.ecep.contract.vo.ContractTypeVo;
|
||||
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "contract-type")
|
||||
public class ContractTypeService extends QueryService<ContractTypeVo, ContractTypeViewModel> {
|
||||
|
||||
@@ -30,11 +30,7 @@ public class CustomerService extends QueryService<CustomerVo, CompanyCustomerVie
|
||||
}
|
||||
|
||||
public CustomerVo findByCompany(CompanyVo company) {
|
||||
Page<CustomerVo> page = findAll(ParamUtils.equal("company", company.getId()), Pageable.ofSize(1));
|
||||
if (page.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.getContent().getFirst();
|
||||
return findOneByProperty("company", company.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,4 +136,43 @@ public class CustomerService extends QueryService<CustomerVo, CompanyCustomerVie
|
||||
// 替换文件名中的非法字符
|
||||
return fileName.replaceAll("[/\\:*?\"<>|]", "_");
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
CustomerVo fromCustomer = findByCompany(from);
|
||||
if (fromCustomer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CustomerVo toCustomer = findByCompany(to);
|
||||
if (toCustomer == null) {
|
||||
// 直接修改关联
|
||||
fromCustomer.setCompanyId(to.getId());
|
||||
save(fromCustomer);
|
||||
return;
|
||||
}
|
||||
|
||||
mergeTo(fromCustomer, toCustomer);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并客户
|
||||
* 1. 删除源客户对象
|
||||
* 2. 删除源客户对象关联的文件
|
||||
* 3. 修改目标客户对象的关联公司
|
||||
* 4. 修改目标客户对象的关联文件
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
public void mergeTo(CustomerVo from, CustomerVo to) {
|
||||
// file
|
||||
CompanyCustomerFileService companyCustomerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
|
||||
companyCustomerFileService.mergeTo(from, to);
|
||||
// entity
|
||||
CompanyCustomerEntityService companyCustomerEntityService = SpringApp.getBean(CompanyCustomerEntityService.class);
|
||||
companyCustomerEntityService.mergeTo(from, to);
|
||||
// 删除源客户对象
|
||||
delete(from);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
@@ -7,16 +9,18 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.converter.DeliverySignMethodStringConverter;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.DeliverySignMethodViewModel;
|
||||
import com.ecep.contract.vo.DeliverySignMethodVo;
|
||||
import com.ecep.contract.vo.ProjectSaleTypeVo;
|
||||
|
||||
import java.util.List;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "delivery-sign-method")
|
||||
public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo, DeliverySignMethodViewModel> {
|
||||
private final StringConverter<DeliverySignMethodVo> stringConverter = new DeliverySignMethodStringConverter(this);
|
||||
|
||||
@Cacheable(key = "#id")
|
||||
@Override
|
||||
@@ -24,6 +28,14 @@ public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo
|
||||
return super.findById(id);
|
||||
}
|
||||
|
||||
public DeliverySignMethodVo findByCode(String code) {
|
||||
return findOneByProperty("code", code);
|
||||
}
|
||||
|
||||
public DeliverySignMethodVo findByName(String name) {
|
||||
return findOneByProperty("name", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据销售类型和编码查询发货方式
|
||||
*
|
||||
@@ -32,15 +44,16 @@ public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo
|
||||
* @return
|
||||
*/
|
||||
public DeliverySignMethodVo findBySaleTypeAndCode(ProjectSaleTypeVo saleType, String code) {
|
||||
Page<DeliverySignMethodVo> page = findAll(ParamUtils.builder()
|
||||
.equals("saleType", saleType.getId()).build(), Pageable.unpaged());
|
||||
var builder = ParamUtils.builder();
|
||||
builder.equals("saleType", saleType.getId());
|
||||
builder.equals("code", code);
|
||||
Page<DeliverySignMethodVo> page = findAll(builder.build(), Pageable.unpaged());
|
||||
if (page.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.stream().filter(v -> v.getCode().equals(code)).findFirst().orElse(null);
|
||||
return page.stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
|
||||
@Cacheable(key = "'all'")
|
||||
@Override
|
||||
public List<DeliverySignMethodVo> findAll() {
|
||||
@@ -58,4 +71,9 @@ public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo
|
||||
public void delete(DeliverySignMethodVo entity) {
|
||||
super.delete(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringConverter<DeliverySignMethodVo> getStringConverter() {
|
||||
return stringConverter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,81 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.converter.DepartmentStringConverter;
|
||||
import com.ecep.contract.vm.DepartmentViewModel;
|
||||
import com.ecep.contract.vo.DepartmentVo;
|
||||
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "department")
|
||||
public class DepartmentService extends QueryService<DepartmentVo, DepartmentViewModel> {
|
||||
|
||||
private final DepartmentStringConverter stringConverter = new DepartmentStringConverter(this);
|
||||
|
||||
@Cacheable(key = "#p0")
|
||||
@Override
|
||||
public DepartmentVo findById(Integer id) {
|
||||
return super.findById(id);
|
||||
}
|
||||
|
||||
public DepartmentVo findByName(String name) {
|
||||
try {
|
||||
return async("findByName", name, String.class).handle((response, ex) -> {
|
||||
DepartmentVo newEntity = createNewEntity();
|
||||
return updateValue(newEntity, response);
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("查询实体失败" + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
public DepartmentVo findByCode(String code) {
|
||||
try {
|
||||
return async("findByCode", code, String.class).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法+findByCode+调用失败", ex);
|
||||
}
|
||||
DepartmentVo newEntity = createNewEntity();
|
||||
return updateValue(newEntity, response);
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("查询实体失败" + code, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Cacheable(key = "'departments'")
|
||||
@Override
|
||||
public List<DepartmentVo> findAll() {
|
||||
return super.findAll();
|
||||
}
|
||||
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"), @CacheEvict(key = "'departments'"),
|
||||
})
|
||||
@Override
|
||||
public void delete(DepartmentVo entity) {
|
||||
super.delete(entity);
|
||||
}
|
||||
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"), @CacheEvict(key = "'departments'"),
|
||||
})
|
||||
@Override
|
||||
public DepartmentVo save(DepartmentVo entity) {
|
||||
return super.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringConverter<DepartmentVo> getStringConverter() {
|
||||
return stringConverter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.ecep.contract.vo.DepartmentVo;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.vm.EmployeeRoleViewModel;
|
||||
@@ -11,11 +15,38 @@ import com.ecep.contract.vo.FunctionVo;
|
||||
@Service
|
||||
public class EmployeeRoleService extends QueryService<EmployeeRoleVo, EmployeeRoleViewModel> {
|
||||
|
||||
public List<FunctionVo> getFunctionsByRoleId(int roleId) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getFunctionsByRoleId'");
|
||||
public List<FunctionVo> getFunctionsByRole(EmployeeRoleVo role) {
|
||||
try {
|
||||
return async("getFunctionsByRoleId", role.getId(), Integer.class).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法+getFunctionsByRoleId+调用失败", ex);
|
||||
}
|
||||
List<FunctionVo> list = new ArrayList<>();
|
||||
try {
|
||||
objectMapper.readerForUpdating(list)
|
||||
.forType(objectMapper.getTypeFactory().constructCollectionType(List.class, FunctionVo.class))
|
||||
.readValue(response);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return list;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("查询实体失败, Function#" + role.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void saveRoleFunctions(EmployeeRoleVo role, List<FunctionVo> functions) {
|
||||
try {
|
||||
async("saveRoleFunctions", new Object[]{role.getId(), functions.stream().mapToInt(FunctionVo::getId).toArray()}, new Object[]{Integer.class, Integer[].class}).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法+saveRoleFunctions+调用失败", ex);
|
||||
}
|
||||
return null;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("保存角色的功能失败, 角色#" + role.getId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +51,9 @@ public class ProjectIndustryService extends QueryService<ProjectIndustryVo, Proj
|
||||
super.delete(entity);
|
||||
}
|
||||
|
||||
|
||||
public StringConverter<ProjectIndustryVo> getStringConverter() {
|
||||
return stringConverter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ public class ProjectSaleTypeService extends QueryService<ProjectSaleTypeVo, Proj
|
||||
}
|
||||
|
||||
public ProjectSaleTypeVo findByCode(String code) {
|
||||
return findAll(ParamUtils.builder().equals("code", code).build(), Pageable.ofSize(1)).getContent().getFirst();
|
||||
return findOneByProperty("code", code);
|
||||
}
|
||||
|
||||
public ProjectSaleTypeVo findByName(String name) {
|
||||
return findAll(ParamUtils.builder().equals("name", name).build(), Pageable.ofSize(1)).getContent().getFirst();
|
||||
return findOneByProperty("name", name);
|
||||
}
|
||||
|
||||
@Caching(evict = {@CacheEvict(key = "#p0.id")})
|
||||
|
||||
@@ -13,11 +13,7 @@ import javafx.util.StringConverter;
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "project-type")
|
||||
public class ProjectTypeService extends QueryService<ProjectTypeVo, ProjectTypeViewModel> {
|
||||
private final StringConverter<ProjectTypeVo> stringConverter;
|
||||
|
||||
public ProjectTypeService() {
|
||||
this.stringConverter = new ProjectTypeStringConverter(this);
|
||||
}
|
||||
private final StringConverter<ProjectTypeVo> stringConverter = new ProjectTypeStringConverter(this);
|
||||
|
||||
@Cacheable(key = "#p0")
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ecep.contract.service;
|
||||
import com.ecep.contract.PageArgument;
|
||||
import com.ecep.contract.PageContent;
|
||||
import com.ecep.contract.WebSocketClientService;
|
||||
import com.ecep.contract.constant.ServiceConstant;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.model.NamedEntity;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
@@ -67,7 +68,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
||||
@Override
|
||||
public T save(T entity) {
|
||||
try {
|
||||
return async("save", entity, entity.getClass().getName()).handle((response, ex) -> {
|
||||
return async(ServiceConstant.SAVE_METHOD_NAME, entity, entity.getClass().getName()).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("保存实体失败", ex);
|
||||
}
|
||||
@@ -88,7 +89,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
||||
@Override
|
||||
public void delete(T entity) {
|
||||
try {
|
||||
async("delete", entity, entity.getClass().getName()).handle((response, ex) -> {
|
||||
async(ServiceConstant.DELETE_METHOD_NAME, entity, entity.getClass().getName()).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("删除实体失败", ex);
|
||||
}
|
||||
@@ -117,7 +118,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
||||
}
|
||||
|
||||
public CompletableFuture<T> asyncFindById(Integer id) {
|
||||
return async("findById", id, Integer.class).handle((response, ex) -> {
|
||||
return async(ServiceConstant.FIND_BY_ID_METHOD_NAME, id, Integer.class).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("查询实体失败", ex);
|
||||
}
|
||||
@@ -149,7 +150,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
||||
*/
|
||||
public CompletableFuture<Page<T>> asyncFindAll(Map<String, Object> params, Pageable pageable) {
|
||||
// 调用async方法发送WebSocket请求,获取异步响应结果
|
||||
return async("findAll", params, PageArgument.of(pageable)).handle((response, ex) -> {
|
||||
return async(ServiceConstant.FIND_ALL_METHOD_NAME, params, PageArgument.of(pageable)).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法+findAll+调用失败", ex);
|
||||
}
|
||||
@@ -188,8 +189,9 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
||||
}
|
||||
|
||||
public T findOneByProperty(String propertyName, Object propertyValue) {
|
||||
return findAll(ParamUtils.builder().equals(propertyName, propertyValue).build(), Pageable.ofSize(1)).stream()
|
||||
.findFirst().orElse(null);
|
||||
ParamUtils.Builder paramBuilder = ParamUtils.builder().equals(propertyName, propertyValue);
|
||||
Page<T> page = findAll(paramBuilder.build(), Pageable.ofSize(1));
|
||||
return page.stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,7 +203,7 @@ public class QueryService<T extends IdentityEntity, TV extends IdentityViewModel
|
||||
public CompletableFuture<Long> asyncCount(Map<String, Object> params) {
|
||||
// 调用async方法执行名为"count"的异步操作,传入参数params
|
||||
// 使用handle方法处理异步操作的结果或异常
|
||||
return async("count", params).handle((response, ex) -> {
|
||||
return async(ServiceConstant.COUNT_METHOD_NAME, params).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法+count+调用失败", ex);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ public class SysConfService {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法+findById+调用失败", ex);
|
||||
}
|
||||
if (response == null || response.isNull()) {
|
||||
return null;
|
||||
}
|
||||
SysConf newEntity = new SysConf();
|
||||
try {
|
||||
objectMapper.updateValue(newEntity, response);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
@@ -12,13 +14,14 @@ public class VendorApprovedFileService
|
||||
extends QueryService<VendorApprovedFileVo, CompanyVendorApprovedFileViewModel> {
|
||||
|
||||
public VendorApprovedFileVo findByName(VendorApprovedVo approvedList, String name) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'findByName'");
|
||||
return findOneByProperty(approvedList, "fileName", name);
|
||||
}
|
||||
|
||||
public boolean reBuildingFiles(VendorApprovedVo list, MessageHolder holder) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'reBuildingFiles'");
|
||||
public VendorApprovedFileVo findOneByProperty(VendorApprovedVo list, String propertyName, Object propertyValue) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("list", list.getId())
|
||||
.equals(propertyName, propertyValue)
|
||||
.build(), Pageable.ofSize(1)).stream()
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ecep.contract.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.ecep.contract.vo.VendorApprovedFileVo;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -23,4 +24,12 @@ public class VendorApprovedItemService
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public VendorApprovedItemVo findOneByProperty(VendorApprovedVo list, String propertyName, Object propertyValue) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("list", list.getId())
|
||||
.equals(propertyName, propertyValue)
|
||||
.build(), Pageable.ofSize(1)).stream()
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,23 +6,23 @@ import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.vm.CompanyVendorApprovedListViewModel;
|
||||
import com.ecep.contract.vo.VendorApprovedVo;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Service
|
||||
public class VendorApprovedService
|
||||
extends QueryService<VendorApprovedVo, CompanyVendorApprovedListViewModel> {
|
||||
|
||||
public boolean makePathAbsent(VendorApprovedVo list) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'makePathAbsent'");
|
||||
}
|
||||
|
||||
public boolean reBuildingFiles(VendorApprovedVo list, MessageHolder holder) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'reBuildingFiles'");
|
||||
}
|
||||
|
||||
public boolean existPath(VendorApprovedVo entity) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'existPath'");
|
||||
try {
|
||||
return async("existPath", entity.getId(), Integer.class).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法+existPath+调用失败", ex);
|
||||
}
|
||||
return response != null && response.isBoolean() && response.asBoolean();
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,4 +49,12 @@ public class VendorEntityService extends QueryService<VendorEntityVo, CompanyVen
|
||||
// 确保返回正确的服务名称
|
||||
return "vendorEntityService";
|
||||
}
|
||||
|
||||
public void mergeTo(VendorVo from, VendorVo to) {
|
||||
List<VendorEntityVo> fromEntities = findByVendor(from);
|
||||
for (VendorEntityVo fromEntity : fromEntities) {
|
||||
fromEntity.setVendorId(to.getId());
|
||||
save(fromEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,33 +97,13 @@ public class VendorFileService extends QueryService<VendorFileVo, CompanyVendorF
|
||||
}
|
||||
|
||||
public void verify(VendorVo companyVendor, LocalDate verifyDate, MessageHolder holder) {
|
||||
// 查询所有评价表
|
||||
List<VendorFileVo> files = findAllByVendorAndType(companyVendor, VendorFileType.EvaluationForm);
|
||||
if (files == null || files.isEmpty()) {
|
||||
holder.error("未见供应商评价");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检索 验证日期最近一年内的有效评价表,日期宽限7天
|
||||
LocalDate begin = verifyDate.plusYears(-1);
|
||||
VendorFileVo vendorFile = files.stream()
|
||||
.filter(v -> v.getSignDate() != null && v.isValid())
|
||||
.filter(v -> MyDateTimeUtils.dateValidFilter(v.getSignDate(), begin, verifyDate, 7))
|
||||
.findFirst().orElse(null);
|
||||
if (vendorFile == null) {
|
||||
// 检索最后一个有效评价表
|
||||
VendorFileVo latestFile = files.stream()
|
||||
.filter(v -> v.getSignDate() != null && v.isValid())
|
||||
.max(Comparator.comparing(VendorFileVo::getSignDate))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
if (latestFile == null) {
|
||||
holder.error("未匹配的供应商评价");
|
||||
return;
|
||||
}
|
||||
// 提示评价表已过期
|
||||
holder.error("供应商评价已过期:" + latestFile.getSignDate() + ", 检测日期:" + verifyDate);
|
||||
}
|
||||
public List<VendorFileVo> findAllByVendor(VendorVo vendor) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("vendor", vendor.getId())
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public List<VendorFileVo> findAllByVendorAndType(VendorVo vendor, VendorFileType type) {
|
||||
@@ -132,4 +112,12 @@ public class VendorFileService extends QueryService<VendorFileVo, CompanyVendorF
|
||||
.equals("type", type.name())
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
|
||||
public void mergeTo(VendorVo from, VendorVo to) {
|
||||
List<VendorFileVo> fromFiles = findAllByVendor(from);
|
||||
for (VendorFileVo fromFile : fromFiles) {
|
||||
fromFile.setVendorId(to.getId());
|
||||
save(fromFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import com.ecep.contract.converter.VendorGroupStringConverter;
|
||||
import com.ecep.contract.model.VendorGroup;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.VendorGroupViewModel;
|
||||
import com.ecep.contract.vo.VendorGroupVo;
|
||||
import javafx.util.StringConverter;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
@@ -15,6 +17,8 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "vendor-group")
|
||||
public class VendorGroupService extends QueryService<VendorGroupVo, VendorGroupViewModel> {
|
||||
|
||||
private VendorGroupStringConverter stringConverter = new VendorGroupStringConverter(this);
|
||||
@Cacheable(key = "#id")
|
||||
@Override
|
||||
public VendorGroupVo findById(Integer id) {
|
||||
@@ -53,4 +57,9 @@ public class VendorGroupService extends QueryService<VendorGroupVo, VendorGroupV
|
||||
public void delete(VendorGroupVo entity) {
|
||||
super.delete(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VendorGroupStringConverter getStringConverter() {
|
||||
return stringConverter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
|
||||
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 org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.VendorType;
|
||||
@@ -21,11 +8,20 @@ import com.ecep.contract.model.VendorCatalog;
|
||||
import com.ecep.contract.util.CompanyUtils;
|
||||
import com.ecep.contract.util.FileUtils;
|
||||
import com.ecep.contract.util.MyStringUtils;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.CompanyVendorViewModel;
|
||||
import com.ecep.contract.vo.VendorVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
import com.ecep.contract.vo.VendorVo;
|
||||
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.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "vendor")
|
||||
@@ -47,22 +43,9 @@ public class VendorService extends QueryService<VendorVo, CompanyVendorViewModel
|
||||
return basePath;
|
||||
}
|
||||
|
||||
public File getVendorApprovedListTemplate() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getVendorApprovedListTemplate'");
|
||||
}
|
||||
|
||||
public VendorCatalog findCatalogById(Integer id) {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'findCatalogById'");
|
||||
}
|
||||
|
||||
@Cacheable(key = "'company-'+#p0.id")
|
||||
public VendorVo findByCompany(CompanyVo company) {
|
||||
Page<VendorVo> page = findAll(ParamUtils.equal("company", company.getId()), Pageable.ofSize(1));
|
||||
if (page.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.getContent().getFirst();
|
||||
return findOneByProperty("company", company.getId());
|
||||
}
|
||||
|
||||
public void verify(ContractVo contract, MessageHolder holder) {
|
||||
@@ -189,4 +172,32 @@ public class VendorService extends QueryService<VendorVo, CompanyVendorViewModel
|
||||
public void delete(VendorVo entity) {
|
||||
super.delete(entity);
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
VendorVo fromCustomer = findByCompany(from);
|
||||
if (fromCustomer == null) {
|
||||
return;
|
||||
}
|
||||
VendorVo toCustomer = findByCompany(to);
|
||||
if (toCustomer == null) {
|
||||
// 直接修改关联
|
||||
fromCustomer.setCompanyId(to.getId());
|
||||
save(fromCustomer);
|
||||
return;
|
||||
}
|
||||
|
||||
mergeTo(fromCustomer, toCustomer);
|
||||
}
|
||||
|
||||
public void mergeTo(VendorVo from, VendorVo to) {
|
||||
// file
|
||||
VendorFileService companyVendorFileService = SpringApp.getBean(VendorFileService.class);
|
||||
companyVendorFileService.mergeTo(from, to);
|
||||
// entity
|
||||
VendorEntityService companyCustomerEntityService = SpringApp.getBean(VendorEntityService.class);
|
||||
companyCustomerEntityService.mergeTo(from, to);
|
||||
// 删除源客户对象
|
||||
delete(from);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.controlsfx.control.TaskProgressView;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -78,4 +81,15 @@ public class YongYouU8Service extends QueryService<CloudYuVo, CloudYuInfoViewMod
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Page<CloudYuVo> findAllByCompany(CompanyVo company) {
|
||||
return findAll(ParamUtils.builder().equals("company", company.getId()).build(), Pageable.unpaged());
|
||||
}
|
||||
|
||||
public void mergeTo(CompanyVo from, CompanyVo to) {
|
||||
List<CloudYuVo> list = findAllByCompany(from).getContent();
|
||||
for (CloudYuVo item : list) {
|
||||
item.setCompanyId(to.getId());
|
||||
save(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import javafx.application.Platform;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* 公司合并客户端任务器
|
||||
* 通过WebSocket连接与服务器端通信,执行公司合并操作
|
||||
*/
|
||||
|
||||
public class CompanyMergeClientTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private CompanyVo company;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private List<String> nameList;
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "CompanyMergeTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
return callRemoteTask(holder, getLocale(), company.getId(), nameList.toArray());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,37 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.vo.ContractGroupVo;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import lombok.Setter;
|
||||
|
||||
public class ContractFilesRebuildAllTasker extends Tasker<Object>{
|
||||
@Slf4j
|
||||
public class ContractFilesRebuildAllTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
@Setter
|
||||
private ContractService contractService;
|
||||
|
||||
@Setter
|
||||
private ContractGroupVo group;
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "ContractFilesRebuildAllTasker";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long workDone, long max) {
|
||||
super.updateProgress(workDone, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'execute'");
|
||||
updateTitle("重建合同组 " + group.getName() + " 的所有文件");
|
||||
log.info("开始重建合同组文件: {}", group.getName());
|
||||
|
||||
// 调用远程任务
|
||||
return callRemoteTask(holder, getLocale(), group.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
|
||||
public class ContractGroupSyncTask extends Tasker<Object>{
|
||||
public class ContractGroupSyncTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
@Override
|
||||
public Object execute(MessageHolder holder) {
|
||||
return null;
|
||||
public String getTaskName() {
|
||||
return "ContractGroupSyncTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(MessageHolder holder) {
|
||||
return callRemoteTask(holder, getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
|
||||
public class ContractKindSyncTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
public static final String TASK_NAME = "ContractKindSyncTask";
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return TASK_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long workDone, long max) {
|
||||
super.updateProgress(workDone, max);
|
||||
}
|
||||
|
||||
public class ContractKindSyncTask extends Tasker<Object> {
|
||||
@Override
|
||||
public Object execute(MessageHolder holder) {
|
||||
return null;
|
||||
return callRemoteTask(holder, getLocale());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ContractRepairAllTasker extends Tasker<Object>{
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
|
||||
/**
|
||||
* 合同修复任务类
|
||||
* 用于通过WebSocket与服务器通信执行合同数据修复操作
|
||||
*/
|
||||
public class ContractRepairAllTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContractRepairAllTasker.class);
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "ContractRepairAllTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'execute'");
|
||||
logger.info("开始执行合同修复任务");
|
||||
updateTitle("合同数据修复");
|
||||
Object result = callRemoteTask(holder, getLocale());
|
||||
|
||||
logger.info("合同修复任务执行完成");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ public class ContractRepairTask extends Tasker<Object> implements WebSocketClien
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
double d = (double) current / total;
|
||||
super.updateProgress(d, 1);
|
||||
System.out.println("current = " + d + ", total = " + total);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* 合同全量同步任务
|
||||
* 通过WebSocket与服务器进行通信,实现合同数据的全量同步
|
||||
*/
|
||||
public class ContractSyncAllTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final String TASK_NAME = "ContractSyncAllTask";
|
||||
|
||||
public class ContractSyncAllTask extends Tasker<Object> {
|
||||
@Override
|
||||
public Object execute(MessageHolder holder) {
|
||||
return null;
|
||||
public String getTaskName() {
|
||||
return TASK_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(MessageHolder holder) {
|
||||
// 更新任务状态信息
|
||||
updateTitle("开始合同全量同步任务");
|
||||
holder.info("准备连接服务器进行合同数据同步...");
|
||||
|
||||
try {
|
||||
// 调用远程任务
|
||||
Object result = callRemoteTask(holder, Locale.getDefault());
|
||||
holder.info("合同全量同步任务完成");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
holder.error("同步失败: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.service.YongYouU8Service;
|
||||
|
||||
/**
|
||||
* 合同同步任务
|
||||
@@ -14,15 +12,6 @@ import com.ecep.contract.service.YongYouU8Service;
|
||||
public class ContractSyncTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContractSyncTask.class);
|
||||
|
||||
private YongYouU8Service yongYouU8Service;
|
||||
|
||||
private YongYouU8Service getYongYouU8Service() {
|
||||
if (yongYouU8Service == null) {
|
||||
yongYouU8Service = SpringApp.getBean(YongYouU8Service.class);
|
||||
}
|
||||
return yongYouU8Service;
|
||||
}
|
||||
|
||||
public String getTaskName() {
|
||||
return "ContractSyncTask";
|
||||
}
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
|
||||
/**
|
||||
* 合同类型同步任务
|
||||
*/
|
||||
public class ContractTypeSyncTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "ContractTypeSyncTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
public class ContractTypeSyncTask extends Tasker<Object> {
|
||||
@Override
|
||||
public Object execute(MessageHolder holder) {
|
||||
return null;
|
||||
// 调用远程WebSocket任务
|
||||
return callRemoteTask(holder, Locale.getDefault());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +373,10 @@ public class ContractVerifyComm implements BeanContext {
|
||||
}
|
||||
|
||||
CompanyVo company = getCompanyService().findById(bidVendor.getCompanyId());
|
||||
ContractFileVo contractFile = fileService.findById(bidVendor.getQuotationSheetFileId());
|
||||
ContractFileVo contractFile = null;
|
||||
if (bidVendor.getQuotationSheetFileId() != null) {
|
||||
contractFile = fileService.findById(bidVendor.getQuotationSheetFileId());
|
||||
}
|
||||
// 报价表文件不存在
|
||||
if (contractFile == null) {
|
||||
if (requireQuotation && bidVendor.getCompanyId().equals(contract.getCompanyId())) {
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import java.util.Locale;
|
||||
|
||||
public class CustomerClassSyncTask extends Tasker<Object> {
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
|
||||
public class CustomerClassSyncTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
public static final String TASK_NAME = "CustomerClassSyncTask";
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomerClassSyncTask.class);
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return TASK_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'execute'");
|
||||
updateTitle("用友U8系统-同步客户分类");
|
||||
return callRemoteTask(holder, Locale.getDefault());
|
||||
}
|
||||
|
||||
// 显式重写 updateProgress 以解决方法隐藏冲突
|
||||
@Override
|
||||
public void updateProgress(long workDone, long max) {
|
||||
super.updateProgress(workDone, max);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user