Appearance
DynamicTaskScheduler 动态定时任务调度器使用文档
一、概述
DynamicTaskScheduler 是一个基于 Spring 的动态定时任务调度器,支持运行时动态添加、更新和删除定时任务。它使用 Cron 表达式实现灵活的任务调度,适用于需要动态管理定时任务的业务场景。
核心特性
- 动态任务管理: 支持运行时添加、更新、删除定时任务
- 多种执行模式: 支持单次执行、定时执行(按周、按月、按季度)
- Cron 表达式生成: 自动根据业务参数生成标准 Cron 表达式
- 线程安全: 基于 ConcurrentHashMap 实现,支持并发操作
- 下次执行时间计算: 自动计算并返回任务的下次执行时间
- 任务取消: 支持优雅地取消正在运行或待执行的任务
二、快速开始
2.1 基本使用
java
@Service
public class ReportTaskService {
@Resource
private DynamicTaskScheduler dynamicTaskScheduler;
@Resource
private AuditReportService auditReportService;
/**
* 创建定时任务
*/
public LocalDateTime createTask() {
// 1. 准备任务配置
TimingTaskBaseDTO task = new TimingTaskBaseDTO();
task.setTaskName("audit_report_task_001"); // 任务名称(唯一标识)
task.setExecuteType("2"); // 执行类型:2=定时执行
task.setPeriodType("1"); // 周期类型:1=按月
task.setPeriodVal("15"); // 周期值:每月15号
// 2. 创建任务执行器
Runnable taskRunnable = new AuditReportTaskRunnable(
auditReportService,
taskConfigId
);
// 3. 调度任务,返回下次执行时间
LocalDateTime nextExecuteTime = dynamicTaskScheduler.scheduleTask(task, taskRunnable);
System.out.println("任务已创建,下次执行时间: " + nextExecuteTime);
return nextExecuteTime;
}
}2.2 创建任务执行器
java
/**
* 审计报告定时任务执行器
*/
public class AuditReportTaskRunnable implements Runnable {
private final Long taskConfigId;
private final AuditReportService auditReportService;
public AuditReportTaskRunnable(AuditReportService auditReportService, Long taskConfigId) {
this.taskConfigId = taskConfigId;
this.auditReportService = auditReportService;
}
@Override
public void run() {
try {
// 执行业务逻辑
auditReportService.generateReport(taskConfigId);
} catch (Exception e) {
// 异常处理
log.error("定时任务执行失败: taskConfigId={}", taskConfigId, e);
}
}
}三、任务配置详解
3.1 TimingTaskBaseDTO 参数说明
java
@Data
public class TimingTaskBaseDTO {
/**
* 任务名称(必填)
* 作为任务的唯一标识,用于后续更新、删除操作
*/
private String taskName;
/**
* 执行类型(必填)
* - "1": 单次执行 - 在指定时间执行一次后自动停止
* - "2": 定时执行 - 按照周期规则持续执行
*/
private String executeType;
/**
* 执行时间(单次执行时必填)
* 指定任务的执行时间点
*/
private LocalDateTime executeDate;
/**
* 周期类型(定时执行时必填)
* - "0": 按周执行
* - "1": 按月执行
* - "2": 按季度执行
*/
private String periodType;
/**
* 周期值(定时执行时必填)
* 格式根据周期类型不同而不同,详见下文
*/
private String periodVal;
}3.2 执行类型说明
3.2.1 单次执行 (executeType = "1")
任务在指定时间执行一次后自动停止。
示例:在 2024年12月31日 23:59 执行一次
java
TimingTaskBaseDTO task = new TimingTaskBaseDTO();
task.setTaskName("year_end_report");
task.setExecuteType("1");
task.setExecuteDate(LocalDateTime.of(2024, 12, 31, 23, 59, 0));
// 生成的Cron表达式: 0 59 23 31 12 ?生成的 Cron 表达式格式:
0 [分钟] [小时] [日期] [月份] ?3.2.2 定时执行 (executeType = "2")
任务按照指定的周期规则持续执行。
需要配置:
periodType: 周期类型periodVal: 周期值
3.3 周期类型详解
3.3.1 按周执行 (periodType = "0")
周期值格式: 1-7 (数字,表示星期几)
- 1 = 星期一
- 2 = 星期二
- 3 = 星期三
- 4 = 星期四
- 5 = 星期五
- 6 = 星期六
- 7 = 星期日
示例1:每周一执行
java
task.setExecuteType("2");
task.setPeriodType("0");
task.setPeriodVal("1");
// 生成的Cron表达式: 0 0 0 * * 1
// 含义: 每周一 00:00:00 执行示例2:每周五执行
java
task.setPeriodType("0");
task.setPeriodVal("5");
// 生成的Cron表达式: 0 0 0 * * 5
// 含义: 每周五 00:00:00 执行3.3.2 按月执行 (periodType = "1")
周期值格式: 1-31 (数字,表示月份中的日期)
示例1:每月15号执行
java
task.setExecuteType("2");
task.setPeriodType("1");
task.setPeriodVal("15");
// 生成的Cron表达式: 0 0 0 15 * ?
// 含义: 每月15号 00:00:00 执行示例2:每月1号执行(月初报表)
java
task.setPeriodType("1");
task.setPeriodVal("1");
// 生成的Cron表达式: 0 0 0 1 * ?
// 含义: 每月1号 00:00:00 执行示例3:每月最后一天执行
java
task.setPeriodType("1");
task.setPeriodVal("31");
// 注意:对于只有30天的月份,会在30号执行
// 对于2月,会在28或29号执行3.3.3 按季度执行 (periodType = "2")
周期值格式: 季度中的月份,日期
- 季度中的月份: 1-3 (1=季度第一个月, 2=季度第二个月, 3=季度第三个月)
- 日期: 1-31
季度划分:
- 第一季度: 1月、2月、3月
- 第二季度: 4月、5月、6月
- 第三季度: 7月、8月、9月
- 第四季度: 10月、11月、12月
示例1:每季度首月15号执行
java
task.setExecuteType("2");
task.setPeriodType("2");
task.setPeriodVal("1,15");
// 生成的Cron表达式: 0 0 0 15 1,4,7,10 ?
// 含义: 1月15日、4月15日、7月15日、10月15日 00:00:00 执行示例2:每季度末月最后一天执行
java
task.setPeriodType("2");
task.setPeriodVal("3,31");
// 生成的Cron表达式: 0 0 0 31 3,6,9,12 ?
// 含义: 3月31日、6月30日、9月30日、12月31日 执行示例3:每季度第二个月10号执行
java
task.setPeriodType("2");
task.setPeriodVal("2,10");
// 生成的Cron表达式: 0 0 0 10 2,5,8,11 ?
// 含义: 2月10日、5月10日、8月10日、11月10日 00:00:00 执行四、完整使用案例
4.1 审计报告定时任务管理
以 AuditReportTaskConfigServiceImpl 为例,展示完整的任务管理流程。
4.1.1 创建任务
java
@Service
public class AuditReportTaskConfigServiceImpl implements AuditReportTaskConfigService {
@Resource
private DynamicTaskScheduler dynamicTaskScheduler;
@Resource
private AuditReportService auditReportService;
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveTaskConfig(AuditReportTaskConfigEntity taskConfig) {
// 1. 保存任务配置到数据库
save(taskConfig);
// 2. 启动定时任务
startTask(taskConfig);
return true;
}
/**
* 启动定时任务
*/
private void startTask(AuditReportTaskConfigEntity taskConfig) {
// 构建任务名称
String taskName = "AUDIT_REPORT_TASK_" + taskConfig.getTaskName();
// 获取任务配置参数
String executeType = taskConfig.getExecuteType();
LocalDateTime executeDate = taskConfig.getExecuteDate();
String periodType = taskConfig.getPeriodType();
String periodVal = taskConfig.getPeriodVal();
// 创建任务DTO
TimingTaskBaseDTO task = new TimingTaskBaseDTO(
taskName,
executeType,
executeDate,
periodType,
periodVal
);
// 只有单次执行或定时执行才启动任务
if ("1".equals(executeType) || "2".equals(executeType)) {
// 创建任务执行器
AuditReportTaskRunnable runnable = new AuditReportTaskRunnable(
auditReportService,
taskConfig.getId()
);
// 调度任务并获取下次执行时间
LocalDateTime nextExecuteTime = dynamicTaskScheduler.scheduleTask(task, runnable);
// 保存下次执行时间
taskConfig.setNextTaskDate(nextExecuteTime);
updateById(taskConfig);
log.info("定时任务已启动: taskName={}, nextExecuteTime={}",
taskName, nextExecuteTime);
}
}
}4.1.2 更新任务
java
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateTaskConfig(AuditReportTaskConfigEntity taskConfig) {
// 1. 更新数据库
updateById(taskConfig);
// 2. 根据任务状态决定启动或关闭
if ("1".equals(taskConfig.getTaskStatus())) {
// 任务状态为开启,先取消旧任务再启动新任务
startTask(taskConfig);
} else {
// 任务状态为关闭,取消任务
closeTask(taskConfig);
}
return true;
}
/**
* 关闭任务
*/
private void closeTask(AuditReportTaskConfigEntity taskConfig) {
String taskName = "AUDIT_REPORT_TASK_" + taskConfig.getTaskName();
TimingTaskBaseDTO task = new TimingTaskBaseDTO();
task.setTaskName(taskName);
// 取消任务
dynamicTaskScheduler.removeTask(task);
log.info("定时任务已关闭: taskName={}", taskName);
}4.1.3 删除任务
java
@Override
public Boolean deleteTask(Long id) {
// 1. 获取任务配置
AuditReportTaskConfigEntity taskConfig = getById(id);
// 2. 关闭定时任务
closeTask(taskConfig);
// 3. 删除数据库记录
removeById(id);
return true;
}4.1.4 提醒任务(复杂场景)
java
/**
* 启动提醒任务
* 在主任务执行前一定时间发送提醒通知
*/
private void startRemindTask(AuditReportTaskConfigEntity taskConfig) {
// 1. 计算提醒时间
LocalDateTime remindDateTime = calculateRemindTime(taskConfig);
// 2. 创建提醒任务
String remindTaskName = taskConfig.getTaskName() + "_REMIND";
TimingTaskBaseDTO remindTask = new TimingTaskBaseDTO();
remindTask.setTaskName(remindTaskName);
remindTask.setExecuteType("1"); // 单次执行
remindTask.setExecuteDate(remindDateTime);
// 3. 创建提醒任务执行器
AuditReportTaskRemindRunnable runnable = new AuditReportTaskRemindRunnable(
remindTask,
dynamicTaskScheduler
);
// 4. 调度提醒任务
dynamicTaskScheduler.scheduleTask(remindTask, runnable);
log.info("提醒任务已启动: taskName={}, remindTime={}",
remindTaskName, remindDateTime);
}
/**
* 计算提醒时间
*/
private LocalDateTime calculateRemindTime(AuditReportTaskConfigEntity taskConfig) {
LocalDate taskDate = taskConfig.getNextTaskDate().toLocalDate();
LocalDate now = LocalDate.now();
Period period = Period.between(now, taskDate);
int days = period.getDays();
LocalDate remindDate;
if (days >= 180) {
// 距离执行时间>=180天,提前30天提醒
remindDate = taskDate.minusDays(30);
} else if (days >= 30) {
// 距离执行时间>=30天,提前7天提醒
remindDate = taskDate.minusDays(7);
} else {
// 距离执行时间<30天,提前2天提醒
remindDate = taskDate.minusDays(2);
}
// 设置提醒时间为下午3点
return remindDate.atTime(15, 0, 0);
}4.2 应用启动时自动加载任务
java
@Component
public class TaskInitializer {
@Resource
private DynamicTaskScheduler dynamicTaskScheduler;
@Resource
private AuditReportTaskConfigService reportTaskConfigService;
@Resource
private AuditReportService auditReportService;
/**
* 应用启动后自动加载所有开启状态的任务
*/
@PostConstruct
public void initEnabledTasks() {
// 1. 查询所有开启状态的任务
List<AuditReportTaskConfigEntity> reportList =
reportTaskConfigService.getOpenStatusTask();
// 2. 逐个启动任务
reportList.forEach(taskConfig -> {
String taskName = "AUDIT_REPORT_TASK_" + taskConfig.getTaskName();
String executeType = taskConfig.getExecuteType();
// 只启动单次执行和定时执行的任务
if ("1".equals(executeType) || "2".equals(executeType)) {
// 创建任务DTO
TimingTaskBaseDTO task = new TimingTaskBaseDTO(
taskName,
executeType,
taskConfig.getExecuteDate(),
taskConfig.getPeriodType(),
taskConfig.getPeriodVal()
);
// 创建任务执行器
AuditReportTaskRunnable runnable = new AuditReportTaskRunnable(
auditReportService,
taskConfig.getId()
);
// 调度任务
dynamicTaskScheduler.scheduleTask(task, runnable);
// 更新关联数据
reportTaskConfigService.uploadReportLinkData(taskConfig.getId());
log.info("已加载定时任务: {}", taskName);
}
});
log.info("定时任务初始化完成,共加载 {} 个任务", reportList.size());
}
}五、高级功能
5.1 计算下次执行时间
java
@Service
public class TaskScheduleService {
@Resource
private DynamicTaskScheduler dynamicTaskScheduler;
/**
* 获取任务的下次执行时间
*/
public LocalDateTime getNextExecuteTime(AuditReportTaskConfigEntity taskConfig) {
// 生成Cron表达式
String cronExpression = DynamicTaskScheduler.generateCronExpression(
taskConfig.getExecuteType(),
taskConfig.getExecuteDate(),
taskConfig.getPeriodType(),
taskConfig.getPeriodVal()
);
if (cronExpression == null) {
return null;
}
// 计算下次执行时间
return dynamicTaskScheduler.getCronByData(cronExpression);
}
}5.2 手动生成 Cron 表达式
java
// 按周执行:每周三
String cron1 = DynamicTaskScheduler.generateCronExpression(
"2", // 定时执行
null, // 无需执行时间
"0", // 按周
"3" // 周三
);
System.out.println(cron1); // 输出: 0 0 0 * * 3
// 按月执行:每月10号
String cron2 = DynamicTaskScheduler.generateCronExpression(
"2", // 定时执行
null,
"1", // 按月
"10" // 10号
);
System.out.println(cron2); // 输出: 0 0 0 10 * ?
// 单次执行:2024年6月30日 18:00
LocalDateTime executeTime = LocalDateTime.of(2024, 6, 30, 18, 0, 0);
String cron3 = DynamicTaskScheduler.generateCronExpression(
"1", // 单次执行
executeTime,
null,
null
);
System.out.println(cron3); // 输出: 0 0 18 30 6 ?5.3 任务状态管理
java
@Service
public class TaskStatusService {
@Resource
private DynamicTaskScheduler dynamicTaskScheduler;
/**
* 启用任务
*/
public void enableTask(Long taskId) {
AuditReportTaskConfigEntity taskConfig = getById(taskId);
// 更新状态为启用
taskConfig.setTaskStatus("1");
updateById(taskConfig);
// 启动任务
startTask(taskConfig);
}
/**
* 禁用任务
*/
public void disableTask(Long taskId) {
AuditReportTaskConfigEntity taskConfig = getById(taskId);
// 更新状态为禁用
taskConfig.setTaskStatus("0");
updateById(taskConfig);
// 停止任务
closeTask(taskConfig);
}
/**
* 暂停任务(保留配置,停止执行)
*/
public void pauseTask(Long taskId) {
AuditReportTaskConfigEntity taskConfig = getById(taskId);
TimingTaskBaseDTO task = new TimingTaskBaseDTO();
task.setTaskName("AUDIT_REPORT_TASK_" + taskConfig.getTaskName());
dynamicTaskScheduler.removeTask(task);
log.info("任务已暂停: {}", taskConfig.getTaskName());
}
/**
* 恢复任务
*/
public void resumeTask(Long taskId) {
AuditReportTaskConfigEntity taskConfig = getById(taskId);
startTask(taskConfig);
log.info("任务已恢复: {}", taskConfig.getTaskName());
}
}六、Cron 表达式参考
6.1 Cron 表达式格式
秒 分 时 日 月 周 [年]字段说明:
- 秒: 0-59
- 分: 0-59
- 时: 0-23
- 日: 1-31
- 月: 1-12 或 JAN-DEC
- 周: 1-7 或 SUN-SAT (1=星期一, 7=星期日)
- 年: 可选,1970-2099
6.2 特殊字符
*: 所有值?: 不指定值(日和周字段互斥使用)-: 范围,如 10-12,: 列举,如 1,4,7,10/: 步长,如 0/15 (每15分钟)L: 最后,如 5L (本月最后一个星期五)W: 工作日,如 15W (最接近15号的工作日)#: 第几个,如 6#3 (本月第3个星期六)
6.3 常用 Cron 表达式示例
0 0 0 * * ? 每天午夜执行
0 0 12 * * ? 每天中午12点执行
0 0 9-17 * * ? 每天9点到17点每小时执行
0 0/30 * * * ? 每30分钟执行
0 0 0 1 * ? 每月1号午夜执行
0 0 0 ? * 1 每周一午夜执行
0 0 0 1 1 ? 每年1月1日午夜执行
0 0 0 L * ? 每月最后一天午夜执行七、注意事项
7.1 任务名称唯一性
- 任务名称必须唯一,作为任务的唯一标识
- 如果使用相同名称添加新任务,会自动取消旧任务
java
// 第一次调用
TimingTaskBaseDTO task1 = new TimingTaskBaseDTO("report_task", "1", executeDate, null, null);
dynamicTaskScheduler.scheduleTask(task1, runnable1);
// 第二次调用相同名称,会先取消task1,再启动新任务
TimingTaskBaseDTO task2 = new TimingTaskBaseDTO("report_task", "2", null, "1", "15");
dynamicTaskScheduler.scheduleTask(task2, runnable2); // task1 被自动取消7.2 线程安全
DynamicTaskScheduler使用ConcurrentHashMap存储任务,支持并发操作- 可以在多线程环境下安全地添加、删除任务
7.3 时区问题
- Cron 表达式使用服务器本地时区
- 如果需要特定时区,需在应用配置中设置
yaml
# application.yml
spring:
task:
scheduling:
pool:
size: 10
# 设置时区
jackson:
time-zone: Asia/Shanghai7.4 任务执行异常处理
- 如果任务执行抛出异常,不会影响下次调度
- 建议在 Runnable 实现中添加异常处理
java
@Override
public void run() {
try {
// 业务逻辑
executeBusinessLogic();
} catch (Exception e) {
// 记录日志但不抛出异常
log.error("定时任务执行失败", e);
// 可选:发送告警通知
sendAlertNotification(e);
}
}7.5 单次任务的特殊处理
- 单次任务执行完后不会自动移除,需要手动取消
- 建议在业务逻辑中执行完后调用
removeTask
java
public class OneTimeTaskRunnable implements Runnable {
private final DynamicTaskScheduler scheduler;
private final TimingTaskBaseDTO taskConfig;
@Override
public void run() {
try {
// 执行业务逻辑
executeBusinessLogic();
} finally {
// 执行完后自动移除任务
scheduler.removeTask(taskConfig);
}
}
}7.6 季度任务的月份计算
- 季度任务会自动计算对应的月份
- 例如:
periodVal="2,10"表示每季度第二个月10号- 实际执行时间:2月10日、5月10日、8月10日、11月10日
7.7 性能优化建议
- 合理设置线程池大小
yaml
spring:
task:
scheduling:
pool:
size: 20 # 根据任务数量调整- 避免长时间运行的任务
java
// 不推荐
public void run() {
// 执行耗时很长的操作
processHugeDataSet(); // 可能运行几小时
}
// 推荐
public void run() {
// 提交到异步线程池执行
asyncExecutor.submit(() -> processHugeDataSet());
}- 批量操作时避免频繁调用
java
// 不推荐
for (TaskConfig config : configs) {
scheduler.scheduleTask(config, runnable);
}
// 推荐
configs.forEach(config -> {
scheduler.scheduleTask(config, runnable);
});八、故障排查
8.1 任务未按预期执行
检查步骤:
- 验证 Cron 表达式
java
String cron = DynamicTaskScheduler.generateCronExpression(...);
System.out.println("生成的Cron表达式: " + cron);
LocalDateTime nextTime = scheduler.getCronByData(cron);
System.out.println("下次执行时间: " + nextTime);- 检查任务是否已注册
java
// 可以在DynamicTaskScheduler中添加调试方法
public Set<String> getAllTaskNames() {
return taskMap.keySet();
}- 查看日志
yaml
logging:
level:
org.springframework.scheduling: DEBUG
com.pig4cloud.pigx.audit.infrastructure.scheduler: DEBUG8.2 任务重复执行
原因:
- 任务名称重复
- 应用重启时重复注册
解决方案:
java
@PostConstruct
public void init() {
// 启动前先清理可能存在的任务
TimingTaskBaseDTO task = new TimingTaskBaseDTO();
task.setTaskName(taskName);
scheduler.removeTask(task);
// 再注册新任务
scheduler.scheduleTask(task, runnable);
}8.3 任务取消失败
原因:
- 任务名称不匹配
- 任务已经执行完成
解决方案:
java
public void removeTask(TimingTaskBaseDTO task) {
if (taskMap.containsKey(task.getTaskName())) {
ScheduledFuture<?> future = taskMap.get(task.getTaskName());
boolean cancelled = future.cancel(true); // true表示中断正在执行的任务
taskMap.remove(task.getTaskName());
log.info("任务已取消: {}, 取消结果: {}", task.getTaskName(), cancelled);
} else {
log.warn("任务不存在: {}", task.getTaskName());
}
}九、完整配置示例
9.1 Spring Boot 配置
yaml
# application.yml
spring:
task:
scheduling:
# 线程池配置
pool:
size: 20 # 线程池大小
thread-name-prefix: "task-" # 线程名称前缀
shutdown:
await-termination: true # 关闭时等待任务完成
await-termination-period: 60 # 等待超时时间(秒)
# 日志配置
logging:
level:
com.pig4cloud.pigx.audit.infrastructure.scheduler: INFO
org.springframework.scheduling: WARN9.2 Bean 配置
java
@Configuration
public class SchedulerConfig {
/**
* 配置线程池任务调度器
*/
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(20);
scheduler.setThreadNamePrefix("dynamic-task-");
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(60);
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
scheduler.initialize();
return scheduler;
}
}十、接口参考
10.1 DynamicTaskScheduler 主要方法
java
/**
* 添加/更新定时任务
* @param task 任务配置
* @param runnable 任务执行器
* @return 下次执行时间
*/
public LocalDateTime scheduleTask(TimingTaskBaseDTO task, Runnable runnable)
/**
* 取消任务
* @param task 任务配置
*/
public void removeTask(TimingTaskBaseDTO task)
/**
* 生成Cron表达式(静态方法)
* @param executeType 执行类型
* @param executeDate 执行时间
* @param periodType 周期类型
* @param periodVal 周期值
* @return Cron表达式
*/
public static String generateCronExpression(
String executeType,
LocalDateTime executeDate,
String periodType,
String periodVal
)
/**
* 根据Cron表达式计算下次执行时间
* @param cronExpression Cron表达式
* @return 下次执行时间
*/
public LocalDateTime getCronByData(String cronExpression)十一、最佳实践总结
任务命名规范
- 使用统一的前缀:
SERVICE_NAME_TASK_业务描述 - 例如:
AUDIT_REPORT_TASK_MONTHLY,DATA_SYNC_TASK_DAILY
- 使用统一的前缀:
任务执行器设计
- 实现
Runnable接口 - 添加完善的异常处理
- 记录详细的执行日志
- 考虑幂等性设计
- 实现
任务配置持久化
- 将任务配置保存到数据库
- 支持页面化管理
- 应用启动时自动加载
监控和告警
- 记录任务执行时长
- 任务失败时发送告警
- 统计任务执行成功率
优雅停机
- 配置
await-termination: true - 设置合理的等待时间
- 任务中定期检查中断标志
- 配置
十二、版本历史
v1.0.0 (2024-11)
- ✅ 初始版本发布
- ✅ 支持单次执行、定时执行
- ✅ 支持按周、按月、按季度调度
- ✅ 自动生成 Cron 表达式
- ✅ 线程安全的任务管理
十三、技术支持
如遇到问题,请按以下步骤处理:
- 查看本文档的"故障排查"章节
- 检查日志输出
- 验证 Cron 表达式是否正确
- 联系框架维护人员
维护团队: 审计系统开发组