docs: 添加数据库结构文档和任务说明文档

添加多个数据库表结构SQL文件,包括供应商评分、企业报告等JSON数据模板
添加任务说明文档,包括枚举类型本地化实现方案、VO创建指南和异步任务监控方案
删除无用的CustomerFileTypeLocalRepository.java文件
This commit is contained in:
2025-09-24 00:11:05 +08:00
parent 9561ad99e6
commit 7b023fd07b
16 changed files with 0 additions and 20 deletions

116
docs/task/create_enum.md Normal file
View File

@@ -0,0 +1,116 @@
按枚举型的实现本地化
# 枚举类型清单
- common/src/main/java/com/ecep/contract/VendorType.java
- common/src/main/java/com/ecep/contract/VendorFileType.java
- common/src/main/java/com/ecep/contract/ProjectFileType.java
- common/src/main/java/com/ecep/contract/CompanyFileType.java
- common/src/main/java/com/ecep/contract/ContractFileType.java
- common/src/main/java/com/ecep/contract/CustomerFIleType.java
# Server 模块
## Repository
- server/src/main/java/com/ecep/contract/ds/vendor/repository/VendorTypeLocalRepository.java
- server/src/main/java/com/ecep/contract/ds/vendor/repository/VendorFileTypeLocalRepository.java
- server/src/main/java/com/ecep/contract/ds/project/repository/ProjectFileTypeLocalRepository.java
- server/src/main/java/com/ecep/contract/ds/company/repository/CompanyFileTypeLocalRepository.java
- server/src/main/java/com/ecep/contract/ds/contract/repository/ContractFileTypeLocalRepository.java
- server/src/main/java/com/ecep/contract/ds/customer/repository/CustomerFileTypeLocalRepository.java
## Service
- server/src/main/java/com/ecep/contract/ds/vendor/service/VendorTypeService.java
- server/src/main/java/com/ecep/contract/ds/vendor/service/VendorFileTypeService.java
- server/src/main/java/com/ecep/contract/ds/project/service/ProjectFileTypeService.java
- server/src/main/java/com/ecep/contract/ds/company/service/CompanyFileTypeService.java
- server/src/main/java/com/ecep/contract/ds/contract/service/ContractFileTypeService.java
- server/src/main/java/com/ecep/contract/ds/customer/service/CustomerFileTypeService.java
# Client 模块
## View Model
- 所在目录: D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\vm
- extends EnumViewModel<枚举类, Vo>
### 清单
- client\src\main\java\com\ecep\contract\vm\VendorTypeLocalViewModel.java
- client\src/main/java/com/ecep/contract/vm/VendorFileTypeLocalViewModel.java
- client\src/main/java/com/ecep/contract/vm/ProjectFileTypeLocalViewModel.java
- client\src/main/java/com/ecep/contract/vm/CompanyFileTypeLocalViewModel.java
- client\src/main/java/com/ecep/contract/vm/ContractFileTypeLocalViewModel.java
- client\src/main/java/com/ecep/contract/vm/CustomerFileTypeLocalViewModel.java
## StringConverter
所在目录 client/src/main/java/com/ecep/contract/converter/
package com.ecep.contract.converter;
### 清单
- client/src/main/java/com/ecep/contract/converter/VendorTypeStringConverter.java
- client/src/main/java/com/ecep/contract/converter/VendorFileTypeStringConverter.java
- client/src/main/java/com/ecep/contract/converter/ProjectFileTypeStringConverter.java
- client/src/main/java/com/ecep/contract/converter/CompanyFileTypeStringConverter.java
- client/src/main/java/com/ecep/contract/converter/ContractFileTypeStringConverter.java
- client/src/main/java/com/ecep/contract/converter/CustomerFileTypeStringConverter.java
## TableCell 表格单元格渲染
### 要求
- 需实现 forTableColumn 静态方法,用于创建表格列的单元格工厂。
- 表格列的单元格工厂需要接收一个 Service 实例,用于从数据库中获取枚举类型的本地化信息。
- 表格列的单元格工厂需要实现 Callback 接口,用于创建单元格实例。
- 表格列的单元格工厂需要在 forTableColumn 方法中接收 Service 实例,用于从数据库中获取枚举类型的本地化信息。
### 清单
- client/src/main/java/com/ecep/contract/controller/table/cell/VendorTypeTableCell.java
- client/src/main/java/com/ecep/contract/controller/table/cell/VendorFileTypeTableCell.java
- client/src/main/java/com/ecep/contract/controller/table/cell/ProjectFileTypeTableCell.java
- client/src/main/java/com/ecep/contract/controller/table/cell/CompanyFileTypeTableCell.java
- client/src/main/java/com/ecep/contract/controller/table/cell/ContractFileTypeTableCell.java
- client/src/main/java/com/ecep/contract/controller/table/cell/CustomerFileTypeTableCell.java
## Service
需要定义注解 @CacheConfig
需要实现 findByLocaleAndValue 方法
- client/src/main/java/com/ecep/contract/service/VendorTypeService.java
- client/src/main/java/com/ecep/contract/service/VendorFileTypeService.java
- client/src/main/java/com/ecep/contract/service/ProjectFileTypeService.java
- client/src/main/java/com/ecep/contract/service/CompanyFileTypeService.java
- client/src/main/java/com/ecep/contract/service/ContractFileTypeService.java
- client/src/main/java/com/ecep/contract/service/CompanyCustomerFileTypeService.java
# Common 模块
## Entity 类, 枚举类型的本地化
- common/src/main/java/com/ecep/contract/model/VendorTypeLocal.java
- common/src/main/java/com/ecep/contract/model/VendorFileTypeLocal.java
- common/src/main/java/com/ecep/contract/model/ProjectFileTypeLocal.java
- common/src/main/java/com/ecep/contract/model/CompanyFileTypeLocal.java
- common/src/main/java/com/ecep/contract/model/ContractFileTypeLocal.java
- common/src/main/java/com/ecep/contract/model/CustomerFileTypeLocal.java
### SQL
每个Entity类创建一个对应的SQL文件文件名格式为Entity类名 + .sql例如VendorTypeLocal.sql
存储在 docs/db/ 目录下
数据表名已经定义在 Entity 类的 @Table 注解中
字段名已经定义在 Entity 类的 @Column 注解中,包括父类的字段
## VOView Object
- common/src/main/java/com/ecep/contract/vo/VendorTypeLocalVo.java
- common/src/main/java/com/ecep/contract/vo/VendorFileTypeLocalVo.java
- common/src/main/java/com/ecep/contract/vo/ProjectFileTypeLocalVo.java
- common/src/main/java/com/ecep/contract/vo/CompanyFileTypeLocalVo.java
- common/src/main/java/com/ecep/contract/vo/ContractFileTypeLocalVo.java
- common/src/main/java/com/ecep/contract/vo/CustomerFileTypeLocalVo.java
# 其他
本文中的所有文件在项目路径下

291
docs/task/create_vo.md Normal file
View File

@@ -0,0 +1,291 @@
更新Vo
# 任务逻辑
根据 model 创建、更新 Vo先检查Vo是否存在如果不存在则创建Vo否则根据要求更新Vo
更新结果保存到 D:\idea-workspace\Contract-Manager\docs\create_vo.md
Model和Vo的对应关系记录在 create_vo.md 结果记录中如果未找到对应的Vo从结果记录中查看是否有对应关系
## model
- 所在目录: D:\idea-workspace\Contract-Manager\common\src\main\java\com\ecep\contract\model
- 注解为 @Entity 的 .java 文件
- 请根据所在目录找到的Model更新本文件中的 Entity 类清单
如果 model 不在 Entity 类清单中,从清单中移除
- Model 需要继承 com.ecep.contract.model.Voable 接口的类,并且实现 toVo 方法
- 以 Local 结尾的 Model 类,并且前面部分名称正好是一个枚举类,则需继承自 BaseEnumEntity
### 参考
- D:\idea-workspace\Contract-Manager\common\src\main\java\com\ecep\contract\model\Bank.java
## ViewModel VM
- 所在目录: D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\vm
- 继承 IdentityViewModel<VO>
- 包含一个 名为 from 的静态方法
public static BankViewModel from(BankVo v) {
BankViewModel vm = new BankViewModel();
vm.update(v);
return vm;
}
- 继承实现 void updateFrom(Vo v) 方法
- 继承实现 void copyTo(Vo v) 方法
- 关联对象 使用 ObjectProperty<Integer>,不使用 IntegerProperty
- IdentityViewModel 不需要调整
- 如果Vo的属性是 boolean使用 BooleanProperty
### 参考
- D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\vm\BankViewModel.java
- D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\vm\CompanyBankAccountViewModel.java
## vo
- 所在目录: D:\idea-workspace\Contract-Manager\common\src\main\java\com\ecep\contract\vo
### 参考
- D:\idea-workspace\Contract-Manager\common\src\main\java\com\ecep\contract\vo\BankVo.java
## Service (client 模块)
- 所在目录: D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\service
- 继承 QueryService<Vo, VM>
- 指定 @Service 注解
- 如果 指定了 @CacheConfig 注解,则需要实现 findById、findAll、save和delete方法并且方法上实现 Cache 相关注解,参考 D:
\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\service\BankService.java
- find** 方法,则通过调用父类的 findAllparams, pageable) 方法实现,如下代码
`
public List<ContractVo> findAllByCompanyVendor(CompanyVendorVo vendor, LocalDate beginDate, LocalDate endDate) {
return findAll(ParamUtils.builder()
.equals("company", vendor.getCompanyId())
.between("setupDate", beginDate, endDate)
.build(), Pageable.unpaged()).getContent();
}
`
- 查询条件中的 .equals("company", vendor.getCompanyId()) 中的company 是对应 model 中对应的字段的名,不对 vo 中对应字段的名
- 如果方法内没有具体的实现,也用如上代码实现
## Service (Server 模块)
## Controller
- ManagerWindowController
- 继承 AbstManagerWindowController<VO, VM, ManagerSkin>
- 实现 createDefaultSkin 方法 返回 ManagerSkin
- 实现 getViewModelService 方法 返回 对应的 Service
- 需要给定以下注解 @Lazy@Scope("prototype")、@Component@FxmlPath("/ui/bank-manager.fxml")
### 参考
- D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\controller\bank\BankManagerWindowController.java
- D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\controller\bank\BankManagerSkin.java
## TabSkin
EditableEntityTableTabSkin<Vo, VM>
## TableCell
- 所在目录: D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\controller\table\cell
- TableCell<T> extends AsyncUpdateTableCell<T, VO>
- TableVIew 使用 TableCell
- bankAccountTable_bankColumn.setCellValueFactory(param -> param.getValue().getBankId());
- bankAccountTable_bankColumn.setCellFactory(param -> new BankTableCell<>(getBankService()));
### 参考
D:\idea-workspace\Contract-Manager\client\src\main\java\com\ecep\contract\controller\table\cell\BankTableCell.java
## Combo
## FXML UI 界面
- 所在目录: D:\idea-workspace\Contract-Manager\client\src\main\resources\ui
## 其他要求
- 检索到的Model先记录在 create_vo.md
- 主键名称为id类型都是 Integer
- 布尔类型的属性使用 boolean,不要使用 Boolean初始值为false
- 注解为@Embeddable@MappedSuperclass的Model不需要更新Vo
- 更新结果以 ModelVo (状态) 保存, Model 和 VO 的名称后期后修改,可能会不一一对应
# Entity 类清单
Bank
CloudRk
CloudTyc
CloudYu
Company
CompanyBankAccount
CompanyBlackReason
CompanyContact
CompanyContract
CompanyCustomer
CompanyCustomerEntity
CompanyCustomerEvaluationFormFile
CompanyCustomerFile
CompanyCustomerFileTypeLocal
CompanyExtendInfo
CompanyFile
CompanyFileTypeLocal
CompanyInvoiceInfo
CompanyOldName
CompanyVendor
CompanyVendorApprovedFile
CompanyVendorApprovedItem
CompanyVendorApprovedList
CompanyVendorEntity
CompanyVendorFile
Contract
ContractBidVendor
ContractCatalog
ContractFile
ContractFileTypeLocal
ContractGroup
ContractItem
ContractKind
ContractPayPlan
ContractType
CustomerCatalog
CustomerSatisfactionSurvey
DeliverySignMethod
ExtendVendorInfo
HolidayTable
Inventory
InventoryCatalog
InventoryHistoryPrice
Invoice
ProductType
ProductUsage
Project
ProjectBid
ProjectCost
ProjectCostItem
ProjectFile
ProjectFileTypeLocal
ProjectFundPlan
ProjectIndustry
ProjectQuotation
ProjectSaleType
ProjectSaleTypeRequireFileType
ProjectType
PurchaseBillVoucher
PurchaseBillVoucherItem
PurchaseOrder
PurchaseOrderItem
PurchaseReceipt
PurchaseSettlementVoucher
PurchaseSettlementVoucherItem
SalesBillVoucher
SalesBillVoucherItem
SalesOrder
SalesOrderInvoice
SalesOrderItem
SysConf
Unit
VendorCatalog
VendorFileTypeLocal
VendorGroup
VendorGroupRequireFileType
VendorTypeLocal
VolumeSize
# 结果记录
EmployeeAuthBind: EmployeeAuthBindVo (已创建)
EmployeeLoginHistory: EmployeeLoginHistoryVo (已创建)
ProjectSaleTypeRequireFileType: ProjectSaleTypeRequireFileTypeVo (已创建)
ProjectFundPlan: ProjectFundPlanVo (已创建)
SalesOrderInvoice: SalesOrderInvoiceVo (已创建)
SalesBillVoucherItem: SalesBillVoucherItemVo (已创建)
SalesBillVoucher: SalesBillVoucherVo (已创建)
PurchaseSettlementVoucherItem: PurchaseSettlementVoucherItemVo (已创建)
PurchaseSettlementVoucher: PurchaseSettlementVoucherVo (已创建)
PurchaseBillVoucherItem: PurchaseBillVoucherItemVo (已创建)
PurchaseBillVoucher: PurchaseBillVoucherVo (已创建)
CompanyOldName: CompanyOldNameVo (已创建)
ContractCatalog: ContractCatalogVo (已创建)
CompanyBlackReason: CompanyBlackReasonVo (已更新)
CompanyContract: CompanyContractVo (已更新)
CompanyCustomer: CompanyCustomerVo (已更新)
CompanyCustomerEntity: CompanyCustomerEntityVo (已创建)
CompanyCustomerEvaluationFormFile: CompanyCustomerEvaluationFormFileVo (已更新)
CompanyCustomerFile: CompanyCustomerFileVo (已更新)
CompanyCustomerFileTypeLocal: CompanyCustomerFileTypeLocalVo (已更新)
CompanyExtendInfo: CompanyExtendInfoVo (已更新)
CompanyFileTypeLocal: CompanyFileTypeLocalVo (已更新)
CompanyVendor: CompanyVendorVo (已更新)
CompanyVendorApprovedFile: CompanyVendorApprovedFileVo (已更新)
CompanyVendorApprovedItem: CompanyVendorApprovedItemVo (已更新)
CompanyVendorApprovedList: CompanyVendorApprovedListVo (已更新)
CompanyVendorEntity: CompanyVendorEntityVo (已更新)
CompanyVendorFile: CompanyVendorFileVo (已更新)
CustomerSatisfactionSurvey: CustomerSatisfactionSurveyVo (已更新)
EmployeeRole: EmployeeRoleVo (已更新)
ProductType: ProductTypeVo (已更新)
PurchaseOrder: PurchaseOrderVo (已更新)
CloudRk: CloudRkVo (已更新)
CloudTyc: CloudTycVo (已更新)
CloudYu: CloudYuVo (已更新)
ProjectFile: ProjectFileVo (已更新)
CompanyContact: CompanyContactVo (已更新)
ContractType: ContractTypeVo (已更新)
ContractKind: ContractKindVo (已更新)
ContractPayPlan: ContractPayPlanVo (已更新)
CustomerCatalog: CustomerCatalogVo (已更新)
Department: DepartmentVo (已更新)
Function: FunctionVo (已更新)
ProjectType: ProjectTypeVo (已更新)
ProductType: ProductTypeVo (已更新)
ProductUsage: ProductUsageVo (已更新)
Price: PriceVo (已更新)
ContractGroup: ContractGroupVo (已更新)
DeliverySignMethod: DeliverySignMethodVo (已更新)
Employee: EmployeeVo (已更新)
Inventory: InventoryVo (已更新)
InventoryCatalog: InventoryCatalogVo (已更新)
Project: ProjectVo (已更新)
ProjectIndustry: ProjectIndustryVo (已更新)
ProjectSaleType: ProjectSaleTypeVo (已更新)
CompanyBankAccount: CompanyBankAccountVo (已更新)
CompanyInvoiceInfo: CompanyInvoiceInfoVo (已更新)
CompanyOldName: CompanyOldNameVo (已更新)
Company: CompanyVo (已更新)
Contract: ContractVo (已更新)
PurchaseReceipt: PurchaseReceiptVo (已更新)
ContractItem: ContractItemVo (已检查)
VolumeSize: VolumeSizeVo (已检查)
Bank: BankVo (已检查)
Contract: ContractVo (已检查)
ContractBidVendor: ContractBidVendorVo (已检查)
ContractCatalog: ContractCatalogVo (已检查)
ContractFile: ContractFileVo (已检查)
ContractFileTypeLocal: ContractFileTypeLocalVo (已检查)
Employee: EmployeeVo (已检查)
Department: DepartmentVo (已检查)
Inventory: InventoryVo (已更新)
InventoryCatalog: InventoryCatalogVo (已检查)
Project: ProjectVo (已检查)
ProjectIndustry: ProjectIndustryVo (已检查)
ProjectSaleType: ProjectSaleTypeVo (已检查)
Company: CompanyVo (已检查)
Invoice: InvoiceVo (已更新)
PurchaseOrder: PurchaseOrderVo (已更新)
SalesOrder: SalesOrderVo (已更新)
ProjectBid: ProjectBidVo (已更新)
ProjectQuotation: ProjectQuotationVo (已更新)
CompanyBankAccount: CompanyBankAccountVo (已检查)
ExtendVendorInfo: ExtendVendorInfoVo (已更新)
ProjectCost: ProjectCostVo (已更新)
VendorGroup: VendorGroupVo (已更新)
VendorGroupRequireFileType: VendorGroupRequireFileTypeVo (已更新)
ProjectCostItem: ProjectCostItemVo (已更新)
ProjectFileTypeLocal: ProjectFileTypeLocalVo (已更新)
VendorTypeLocal: VendorTypeLocalVo (已更新)
VendorFileTypeLocal: VendorFileTypeLocalVo (已更新)

View File

@@ -0,0 +1,885 @@
# 异步任务监控实现方案
## 现状分析
通过分析项目代码,我们发现当前系统的异步任务管理存在以下特点和不足:
1. **任务调度机制**系统使用JavaFX的`Task`和Spring的`ScheduledExecutorService`进行异步任务调度
2. **任务基类**`Tasker`类作为所有任务的基类,实现了基本的任务生命周期管理
3. **进度展示**`UITools.showTaskDialogAndWait()`方法提供了简单的任务进度对话框
4. **异步编程**:项目广泛使用`CompletableFuture`进行异步操作
5. **主要不足**
- 缺乏统一的任务监控中心
- 任务执行状态缺乏集中管理
- 大部分任务没有超时处理机制
- 任务执行历史无法追溯
- 缺少任务失败后的重试策略
## 实现方案
### 1. 任务监控核心组件
#### 1.1 任务监控中心
```java
package com.ecep.contract.manager.ui.task;
import com.ecep.contract.manager.ui.Tasker;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 任务监控中心 - 统一管理所有异步任务
*/
@Component
public class TaskMonitorCenter {
private static final Logger logger = LoggerFactory.getLogger(TaskMonitorCenter.class);
// 正在运行的任务列表
@Getter
private final ObservableList<MonitoredTask<?>> activeTasks = FXCollections.observableArrayList();
// 任务历史记录
@Getter
private final ObservableList<TaskHistory> taskHistory = FXCollections.observableArrayList();
// 任务ID映射
private final Map<String, MonitoredTask<?>> taskMap = new ConcurrentHashMap<>();
// 任务计数器
private final AtomicInteger taskCounter = new AtomicInteger(0);
/**
* 注册并启动任务
*/
public <T> MonitoredTask<T> registerTask(Tasker<T> task) {
return registerTask(task, null);
}
/**
* 注册并启动任务,支持设置超时时间
*/
public <T> MonitoredTask<T> registerTask(Tasker<T> task, Long timeoutSeconds) {
String taskId = generateTaskId();
MonitoredTask<T> monitoredTask = new MonitoredTask<>(task, taskId);
// 设置超时处理
if (timeoutSeconds != null && timeoutSeconds > 0) {
monitoredTask.setTimeout(timeoutSeconds);
}
// 添加任务状态变更监听器
monitoredTask.setOnSucceeded(event -> {
logger.info("任务[{}]执行成功: {}", taskId, task.getTitle());
recordHistory(monitoredTask, TaskStatus.SUCCEEDED);
removeActiveTask(monitoredTask);
});
monitoredTask.setOnFailed(event -> {
logger.error("任务[{}]执行失败: {}", taskId, task.getTitle(), task.getException());
recordHistory(monitoredTask, TaskStatus.FAILED);
removeActiveTask(monitoredTask);
});
monitoredTask.setOnCancelled(event -> {
logger.info("任务[{}]被取消: {}", taskId, task.getTitle());
recordHistory(monitoredTask, TaskStatus.CANCELLED);
removeActiveTask(monitoredTask);
});
// 保存任务引用并启动
taskMap.put(taskId, monitoredTask);
activeTasks.add(monitoredTask);
monitoredTask.start();
logger.info("任务[{}]已注册并启动: {}", taskId, task.getTitle());
return monitoredTask;
}
/**
* 取消任务
*/
public boolean cancelTask(String taskId) {
MonitoredTask<?> task = taskMap.get(taskId);
if (task != null && task.isRunning()) {
task.cancel();
return true;
}
return false;
}
/**
* 取消所有任务
*/
public void cancelAllTasks() {
for (MonitoredTask<?> task : new ArrayList<>(activeTasks)) {
task.cancel();
}
}
/**
* 从活动列表中移除任务
*/
private void removeActiveTask(MonitoredTask<?> task) {
Platform.runLater(() -> activeTasks.remove(task));
taskMap.remove(task.getTaskId());
}
/**
* 记录任务历史
*/
private void recordHistory(MonitoredTask<?> task, TaskStatus status) {
TaskHistory history = new TaskHistory(
task.getTaskId(),
task.getTitle(),
status,
task.getStartTime(),
System.currentTimeMillis()
);
if (task.getException() != null) {
history.setErrorMessage(task.getException().getMessage());
}
Platform.runLater(() -> {
taskHistory.add(0, history);
// 只保留最近100条历史记录
if (taskHistory.size() > 100) {
taskHistory.remove(taskHistory.size() - 1);
}
});
}
/**
* 生成任务ID
*/
private String generateTaskId() {
return "task-" + UUID.randomUUID().toString().substring(0, 8) + "-" + taskCounter.incrementAndGet();
}
}
```
#### 1.2 监控任务包装类
```java
package com.ecep.contract.manager.ui.task;
import com.ecep.contract.manager.ui.Tasker;
import javafx.concurrent.Task;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 被监控的任务包装类
*/
public class MonitoredTask<T> extends Task<T> {
@Getter
private final Tasker<T> delegate;
@Getter
private final String taskId;
@Getter
private final long startTime;
@Setter
private Long timeoutSeconds;
private ScheduledExecutorService timeoutExecutor;
private final AtomicBoolean timeoutHandled = new AtomicBoolean(false);
public MonitoredTask(Tasker<T> delegate, String taskId) {
this.delegate = delegate;
this.taskId = taskId;
this.startTime = System.currentTimeMillis();
// 绑定属性
titleProperty().bind(delegate.titleProperty());
messageProperty().bind(delegate.messageProperty());
progressProperty().bind(delegate.progressProperty());
}
@Override
protected T call() throws Exception {
// 设置超时监控
if (timeoutSeconds != null) {
setupTimeoutMonitor();
}
try {
return delegate.call();
} finally {
// 取消超时监控
if (timeoutExecutor != null) {
timeoutExecutor.shutdownNow();
}
}
}
/**
* 设置超时监控
*/
private void setupTimeoutMonitor() {
timeoutExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r, "task-timeout-monitor-" + taskId);
thread.setDaemon(true);
return thread;
});
timeoutExecutor.schedule(() -> {
if (isRunning() && timeoutHandled.compareAndSet(false, true)) {
updateMessage("任务执行超时,正在取消...");
cancel();
}
}, timeoutSeconds, TimeUnit.SECONDS);
}
@Override
public boolean cancel() {
boolean cancelled = super.cancel();
if (cancelled) {
delegate.cancel();
}
return cancelled;
}
/**
* 启动任务
*/
public void start() {
Thread thread = new Thread(this, "task-" + taskId);
thread.setDaemon(true);
thread.start();
}
/**
* 获取任务执行耗时
*/
public long getExecutionTime() {
return System.currentTimeMillis() - startTime;
}
}
```
#### 1.3 任务状态和历史记录类
```java
package com.ecep.contract.manager.ui.task;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 任务状态枚举
*/
public enum TaskStatus {
SCHEDULED("已调度"),
RUNNING("运行中"),
SUCCEEDED("成功"),
FAILED("失败"),
CANCELLED("已取消"),
TIMED_OUT("超时");
private final String description;
TaskStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
/**
* 任务历史记录
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TaskHistory {
private String taskId;
private String title;
private TaskStatus status;
private long startTime;
private long endTime;
private String errorMessage;
/**
* 获取任务执行耗时(毫秒)
*/
public long getExecutionTime() {
return endTime - startTime;
}
/**
* 获取格式化的执行耗时
*/
public String getFormattedExecutionTime() {
long ms = getExecutionTime();
if (ms < 1000) {
return ms + "ms";
} else if (ms < 60000) {
return String.format("%.2fs", ms / 1000.0);
} else {
return String.format("%dm %.2fs", ms / 60000, (ms % 60000) / 1000.0);
}
}
}
```
### 2. 任务监控界面
```java
package com.ecep.contract.manager.ui.task;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ui.Tasker;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 任务监控界面控制器
*/
@Component
public class TaskMonitorViewController {
@FXML
private TableView<MonitoredTask<?>> activeTasksTable;
@FXML
private TableColumn<MonitoredTask<?>, String> activeTaskIdColumn;
@FXML
private TableColumn<MonitoredTask<?>, String> activeTaskTitleColumn;
@FXML
private TableColumn<MonitoredTask<?>, ProgressBar> activeTaskProgressColumn;
@FXML
private TableColumn<MonitoredTask<?>, String> activeTaskStatusColumn;
@FXML
private TableView<TaskHistory> historyTasksTable;
@FXML
private TableColumn<TaskHistory, String> historyTaskIdColumn;
@FXML
private TableColumn<TaskHistory, String> historyTaskTitleColumn;
@FXML
private TableColumn<TaskHistory, String> historyTaskStatusColumn;
@FXML
private TableColumn<TaskHistory, String> historyTaskStartTimeColumn;
@FXML
private TableColumn<TaskHistory, String> historyTaskExecutionTimeColumn;
@FXML
private Button cancelTaskButton;
@FXML
private Button clearHistoryButton;
private final TaskMonitorCenter taskMonitorCenter;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public TaskMonitorViewController() {
this.taskMonitorCenter = SpringApp.getBean(TaskMonitorCenter.class);
}
@FXML
public void initialize() {
// 初始化活动任务表格
activeTaskIdColumn.setCellValueFactory(new PropertyValueFactory<>("taskId"));
activeTaskTitleColumn.setCellValueFactory(new PropertyValueFactory<>("title"));
// 进度条列
activeTaskProgressColumn.setCellFactory(column -> new TableCell<>() {
private final ProgressBar progressBar = new ProgressBar();
@Override
protected void updateItem(ProgressBar item, boolean empty) {
super.updateItem(item, empty);
if (empty || getTableRow().getItem() == null) {
setGraphic(null);
} else {
MonitoredTask<?> task = getTableRow().getItem();
progressBar.progressProperty().bind(task.progressProperty());
setGraphic(progressBar);
}
}
});
// 状态列
activeTaskStatusColumn.setCellValueFactory(cellData -> {
MonitoredTask<?> task = cellData.getValue();
String status;
if (task.isRunning()) {
status = "运行中";
} else if (task.isCancelled()) {
status = "已取消";
} else if (task.isFailed()) {
status = "失败";
} else if (task.isSucceeded()) {
status = "成功";
} else {
status = "等待中";
}
return new SimpleStringProperty(status);
});
// 初始化历史任务表格
historyTaskIdColumn.setCellValueFactory(new PropertyValueFactory<>("taskId"));
historyTaskTitleColumn.setCellValueFactory(new PropertyValueFactory<>("title"));
historyTaskStatusColumn.setCellValueFactory(cellData ->
new SimpleStringProperty(cellData.getValue().getStatus().getDescription()));
historyTaskStartTimeColumn.setCellValueFactory(cellData ->
new SimpleStringProperty(dateFormat.format(new Date(cellData.getValue().getStartTime()))));
historyTaskExecutionTimeColumn.setCellValueFactory(cellData ->
new SimpleStringProperty(cellData.getValue().getFormattedExecutionTime()));
// 绑定数据
activeTasksTable.setItems(taskMonitorCenter.getActiveTasks());
historyTasksTable.setItems(taskMonitorCenter.getTaskHistory());
// 添加任务双击事件(显示详情)
historyTasksTable.setRowFactory(tv -> {
TableRow<TaskHistory> row = new TableRow<>();
row.setOnMouseClicked(event -> {
if (event.getClickCount() == 2 && !row.isEmpty()) {
TaskHistory history = row.getItem();
showTaskDetails(history);
}
});
return row;
});
// 更新按钮状态
updateButtonStates();
activeTasksTable.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) ->
updateButtonStates());
}
/**
* 更新按钮状态
*/
private void updateButtonStates() {
cancelTaskButton.setDisable(activeTasksTable.getSelectionModel().getSelectedItem() == null);
}
/**
* 取消选中的任务
*/
@FXML
private void onCancelTask() {
MonitoredTask<?> selectedTask = activeTasksTable.getSelectionModel().getSelectedItem();
if (selectedTask != null) {
taskMonitorCenter.cancelTask(selectedTask.getTaskId());
}
}
/**
* 清空历史记录
*/
@FXML
private void onClearHistory() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("确认操作");
alert.setHeaderText("清空任务历史");
alert.setContentText("确定要清空所有任务历史记录吗?此操作不可恢复。");
alert.showAndWait().ifPresent(response -> {
if (response == ButtonType.OK) {
taskMonitorCenter.getTaskHistory().clear();
}
});
}
/**
* 显示任务详情
*/
private void showTaskDetails(TaskHistory history) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("任务详情");
alert.setHeaderText(history.getTitle());
StringBuilder content = new StringBuilder();
content.append("任务ID: " + history.getTaskId() + "\n");
content.append("状态: " + history.getStatus().getDescription() + "\n");
content.append("开始时间: " + dateFormat.format(new Date(history.getStartTime())) + "\n");
content.append("结束时间: " + dateFormat.format(new Date(history.getEndTime())) + "\n");
content.append("执行耗时: " + history.getFormattedExecutionTime() + "\n");
if (history.getErrorMessage() != null) {
content.append("\n错误信息: " + history.getErrorMessage());
alert.getDialogPane().setExpandableContent(new TextArea(history.getErrorMessage()));
}
alert.setContentText(content.toString());
alert.showAndWait();
}
/**
* 显示任务监控窗口
*/
@SneakyThrows(IOException.class)
public static void show() {
FXMLLoader loader = new FXMLLoader(TaskMonitorViewController.class.getResource("/ui/task/TaskMonitorView.fxml"));
// 设置Spring为控制器工厂
loader.setControllerFactory(SpringApp::getBean);
Parent root = loader.load();
Stage stage = new Stage();
stage.setTitle("任务监控中心");
stage.setScene(new Scene(root, 800, 600));
stage.initModality(Modality.NONE); // 非模态窗口
stage.show();
}
}
```
### 3. 集成到现有系统
#### 3.1 修改UITools类
```java
package com.ecep.contract.manager.util;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ui.Message;
import com.ecep.contract.manager.ui.Tasker;
import com.ecep.contract.manager.ui.task.TaskMonitorCenter;
import com.ecep.contract.manager.ui.task.MonitoredTask;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import java.util.Optional;
import java.util.function.Consumer;
public class UITools {
// 其他方法保持不变...
/**
* 显示一个对话框, 并等待用户关闭对话框
*
* @param title 对话框标题
* @param task 任务
* @param init 初始化
*/
public static void showTaskDialogAndWait(String title, Task<?> task, Consumer<Consumer<Message>> init) {
Dialog<Message> dialog = createDialog();
dialog.getDialogPane().getScene().getStylesheets().add("/ui/dialog.css");
dialog.setTitle(title);
DialogPane dialogPane = dialog.getDialogPane();
VBox box = (VBox) dialogPane.getContent();
Label headerLabel = (Label) box.lookup("#header");
ListView<Message> listView = (ListView<Message>) box.lookup("#list-view");
listView.setCellFactory(param -> new Tasker.MessageListCell());
headerLabel.textProperty().bind(task.titleProperty());
Consumer<Message> consumer = message -> {
if (Platform.isFxApplicationThread()) {
listView.getItems().add(message);
} else {
Platform.runLater(() -> listView.getItems().add(message));
}
};
if (task instanceof Tasker<?> tasker) {
tasker.setMessageHandler(consumer);
} else {
task.messageProperty().addListener((observable, oldValue, newValue) -> {
consumer.accept(Message.info(newValue));
});
}
// 加一个进度条
ProgressBar progressBar = new ProgressBar(0);
progressBar.setPrefHeight(12);
progressBar.setPrefWidth(200);
progressBar.prefWidthProperty().bind(box.widthProperty().subtract(10));
progressBar.setMinHeight(12);
progressBar.setVisible(true);
VBox.setMargin(progressBar, new Insets(6, 0, 6, 0));
Platform.runLater(() -> {
box.getChildren().add(progressBar);
});
progressBar.visibleProperty().bind(task.runningProperty());
progressBar.progressProperty().bind(task.progressProperty());
// 使用任务监控中心启动任务
if (task instanceof Tasker<?> tasker) {
// 注册到监控中心但不在这里启动因为showAndWait会阻塞UI线程
TaskMonitorCenter monitorCenter = SpringApp.getBean(TaskMonitorCenter.class);
MonitoredTask<?> monitoredTask = monitorCenter.registerTask(tasker);
}
if (init != null) {
init.accept(consumer);
}
dialog.showAndWait();
if (task.isRunning()) {
task.cancel();
}
}
// 其他方法保持不变...
}
```
#### 3.2 修改SpringApp配置
```java
package com.ecep.contract.manager;
import com.ecep.contract.manager.ui.task.TaskMonitorCenter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.DomainScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.concurrent.ScheduledExecutorService;
@SpringBootApplication
@EnableJpaRepositories(basePackages = {"com.ecep.contract.manager.ds"})
@DomainScan(basePackages = {"com.ecep.contract.manager.ds"})
@EnableScheduling
@EnableAsync
public class SpringApp {
private static ConfigurableApplicationContext context;
// 其他方法保持不变...
public static void main(String[] args) {
context = SpringApplication.run(SpringApp.class, args);
// 初始化任务监控中心
TaskMonitorCenter monitorCenter = context.getBean(TaskMonitorCenter.class);
// 可以在这里添加一些初始化代码
}
// 其他方法保持不变...
}
```
### 4. 超时处理机制
```java
package com.ecep.contract.manager.util;
import java.util.concurrent.*;
import java.util.function.Supplier;
/**
* 异步任务工具类 - 提供超时处理等增强功能
*/
public class AsyncUtils {
/**
* 执行带超时的CompletableFuture任务
*/
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, long timeout, TimeUnit unit) {
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier);
return withTimeout(future, timeout, unit);
}
/**
* 执行带超时的CompletableFuture任务并指定线程池
*/
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, Executor executor, long timeout, TimeUnit unit) {
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier, executor);
return withTimeout(future, timeout, unit);
}
/**
* 为已有的CompletableFuture添加超时处理
*/
public static <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future, long timeout, TimeUnit unit) {
CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r, "timeout-scheduler");
thread.setDaemon(true);
return thread;
});
scheduler.schedule(() -> {
TimeoutException timeoutException = new TimeoutException("Task timed out after " + timeout + " " + unit.name());
timeoutFuture.completeExceptionally(timeoutException);
}, timeout, unit);
return future.applyToEither(timeoutFuture, t -> t)
.whenComplete((t, ex) -> scheduler.shutdownNow());
}
/**
* 带重试机制的CompletableFuture任务
*/
public static <T> CompletableFuture<T> supplyAsyncWithRetry(Supplier<T> supplier, int maxRetries, long delayBetweenRetries, TimeUnit unit) {
return supplyAsyncWithRetry(supplier, maxRetries, delayBetweenRetries, unit, null);
}
/**
* 带重试机制的CompletableFuture任务并指定哪些异常需要重试
*/
public static <T> CompletableFuture<T> supplyAsyncWithRetry(
Supplier<T> supplier,
int maxRetries,
long delayBetweenRetries,
TimeUnit unit,
Class<? extends Exception>[] retryableExceptions) {
CompletableFuture<T> resultFuture = new CompletableFuture<>();
// 执行任务的方法
executeWithRetry(supplier, resultFuture, 0, maxRetries, delayBetweenRetries, unit, retryableExceptions);
return resultFuture;
}
private static <T> void executeWithRetry(
Supplier<T> supplier,
CompletableFuture<T> resultFuture,
int currentAttempt,
int maxRetries,
long delayBetweenRetries,
TimeUnit unit,
Class<? extends Exception>[] retryableExceptions) {
CompletableFuture.supplyAsync(supplier)
.thenAccept(resultFuture::complete)
.exceptionally(ex -> {
if (currentAttempt < maxRetries && shouldRetry(ex, retryableExceptions)) {
// 延迟后重试
try {
Thread.sleep(unit.toMillis(delayBetweenRetries));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
resultFuture.completeExceptionally(ie);
return null;
}
// 递归调用,增加尝试次数
executeWithRetry(supplier, resultFuture, currentAttempt + 1,
maxRetries, delayBetweenRetries, unit, retryableExceptions);
} else {
// 达到最大重试次数或异常不可重试完成Future
resultFuture.completeExceptionally(ex);
}
return null;
});
}
private static boolean shouldRetry(Throwable ex, Class<? extends Exception>[] retryableExceptions) {
// 如果未指定重试异常类型,则所有异常都重试
if (retryableExceptions == null || retryableExceptions.length == 0) {
return true;
}
// 检查异常是否属于可重试类型
for (Class<? extends Exception> exceptionClass : retryableExceptions) {
if (exceptionClass.isInstance(ex.getCause())) {
return true;
}
}
return false;
}
}
```
## 使用示例
### 1. 基本任务监控
```java
// 在任何需要执行异步任务的地方
Tasker<?> task = new ContractSyncTask();
TaskMonitorCenter monitorCenter = SpringApp.getBean(TaskMonitorCenter.class);
// 注册任务设置30秒超时
monitorCenter.registerTask(task, 30L);
// 显示任务监控窗口
TaskMonitorViewController.show();
```
### 2. 带超时处理的CompletableFuture
```java
// 使用AsyncUtils工具类执行带超时的异步任务
CompletableFuture<String> future = AsyncUtils.supplyAsyncWithTimeout(() -> {
// 执行耗时操作
Thread.sleep(5000);
return "任务完成";
}, 3, TimeUnit.SECONDS);
// 处理结果
future.thenAccept(result -> System.out.println("结果: " + result))
.exceptionally(ex -> {
if (ex.getCause() instanceof TimeoutException) {
System.out.println("任务执行超时");
} else {
System.out.println("任务执行异常: " + ex.getMessage());
}
return null;
});
```
### 3. 带重试机制的任务
```java
// 使用AsyncUtils工具类执行带重试的异步任务
CompletableFuture<String> future = AsyncUtils.supplyAsyncWithRetry(() -> {
// 模拟一个可能失败的操作
if (Math.random() > 0.7) {
throw new RuntimeException("随机失败");
}
return "操作成功";
}, 3, 1, TimeUnit.SECONDS, new Class[]{RuntimeException.class});
// 处理结果
future.thenAccept(result -> System.out.println("结果: " + result))
.exceptionally(ex -> {
System.out.println("多次重试后仍然失败: " + ex.getMessage());
return null;
});
```
## 实现要点总结
1. **统一任务管理**:通过`TaskMonitorCenter`集中管理所有异步任务,提供注册、监控和取消功能
2. **完整生命周期监控**:跟踪任务从创建、运行到完成/失败/取消的整个生命周期
3. **超时处理机制**:为任务设置超时时间,防止任务无限期阻塞
4. **历史记录追踪**:保存任务执行历史,方便问题排查和性能分析
5. **可视化界面**:提供直观的任务监控界面,展示任务进度和状态
6. **重试策略**:针对失败任务提供可配置的重试机制
7. **与现有系统集成**:无缝集成到现有任务调度机制中
这套实现方案可以显著提升系统的稳定性和可维护性,防止长时间运行的任务占用系统资源,并为用户提供更好的任务执行状态反馈。