diff --git a/README.md b/README.md
index 6747a8b0..d56dc933 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,8 @@
本人的其他两个推荐搭配的项目
-1. [DataI-App-Geek: 这是若依极客生态的小程序版本 (gitee.com)](https://gitee.com/geek-xd/geek-uniapp-vue3-uview-plus-uchart)
-2. [DataI-Vue3-Geek: 这是若依极客生态的Vue3版本 (gitee.com)](https://gitee.com/geek-xd/ruo-yi-vue3-geek)
+1. [DataI-App-Geek: 这是集成系统生态的小程序版本 (gitee.com)](https://gitee.com/geek-xd/geek-uniapp-vue3-uview-plus-uchart)
+2. [DataI-Vue3-Geek: 这是集成系统生态的Vue3版本 (gitee.com)](https://gitee.com/geek-xd/ruo-yi-vue3-geek)
与本项目同为一个作者开发,兼容性最好,学习成本最低。
diff --git a/datai-admin/src/main/java/com/datai/DataIApplication.java b/datai-admin/src/main/java/com/datai/DataIApplication.java
index 9a1daaa4..013cf3cf 100644
--- a/datai-admin/src/main/java/com/datai/DataIApplication.java
+++ b/datai-admin/src/main/java/com/datai/DataIApplication.java
@@ -17,19 +17,9 @@ import org.springframework.core.env.Environment;
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class DataIApplication {
public static void main(String[] args) throws UnknownHostException {
- // System.setProperty("spring.devtools.restart.enabled", "false");
+ System.setProperty("spring.devtools.restart.enabled", "false");
ConfigurableApplicationContext application = SpringApplication.run(DataIApplication.class, args);
- System.out.println("(♥◠‿◠)ノ゙ 若依极客启动成功 ლ(´ڡ`ლ)゙ \n" +
- " .-------. ____ __ \n" +
- " | _ _ \\ \\ \\ / / \n" +
- " | ( ' ) | \\ _. / ' \n" +
- " |(_ o _) / _( )_ .' \n" +
- " | (_,_).' __ ___(_ o _)' " + " ____ _ " + "\n" +
- " | |\\ \\ | || |(_,_)' " + " / ___| ___ ___| | __ " + "\n" +
- " | | \\ `' /| `-' / " + "| | _ / _ \\/ _ \\ |/ / " + "\n" +
- " | | \\ / \\ / " + " | |_| | __/ __/ < " + "\n" +
- " ''-' `'-' `-..-' " + "\\____|\\___|\\___|_|\\_\\");
-
+ System.out.println("(♥◠‿◠)ノ゙ 集成系统启动成功 ლ(´ڡ`ლ)゙ \n");
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java
index ba051d3f..e06836d2 100644
--- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java
+++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/controller/DataiIntegrationObjectController.java
@@ -24,10 +24,12 @@ import com.datai.common.enums.BusinessType;
import com.datai.integration.model.domain.DataiIntegrationObject;
import com.datai.integration.model.dto.DataiIntegrationObjectDto;
import com.datai.integration.service.IDataiIntegrationObjectService;
+import com.datai.integration.service.IDataiIntegrationFieldService;
import com.datai.common.utils.poi.ExcelUtil;
import com.datai.common.core.page.TableDataInfo;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
+import lombok.extern.slf4j.Slf4j;
/**
* 对象同步控制Controller
@@ -38,6 +40,7 @@ import io.swagger.v3.oas.annotations.Operation;
@RestController
@RequestMapping("/integration/object")
@Tag(name = "【对象同步控制】管理")
+@Slf4j
public class DataiIntegrationObjectController extends BaseController
{
@Autowired
@@ -214,4 +217,45 @@ public class DataiIntegrationObjectController extends BaseController
return error((String) statistics.get("message"));
}
}
+
+ /**
+ * 同步单对象数据到本地数据库
+ *
+ * 该方法用于触发指定对象的单次数据同步操作,将Salesforce对象的数据同步到本地数据库
+ * 同步操作包括全量同步和增量同步两种模式,具体模式由对象的配置决定
+ *
+ * @param id 对象ID,用于标识需要同步的Salesforce对象
+ * @return AjaxResult 同步结果,包含成功/失败状态、同步数据量、耗时等信息
+ * - 成功时返回:success=true, message="对象数据同步成功", 以及详细的同步信息
+ * - 失败时返回:success=false, message=错误信息
+ */
+ @Operation(summary = "同步单对象数据到本地数据库")
+ @PreAuthorize("@ss.hasPermi('integration:object:syncData')")
+ @Log(title = "对象同步控制", businessType = BusinessType.UPDATE)
+ @PostMapping("/{id}/syncData")
+ public AjaxResult syncObjectData(@PathVariable("id") Integer id)
+ {
+ if (id == null) {
+ log.error("对象ID为空,无法同步数据");
+ return error("对象ID不能为空");
+ }
+
+ try {
+ log.info("开始同步对象数据,对象ID: {}", id);
+
+ Map result = dataiIntegrationObjectService.syncSingleObjectData(id);
+
+ if ((Boolean) result.get("success")) {
+ log.info("对象数据同步成功,对象ID: {}", id);
+ return success(result);
+ } else {
+ log.error("对象数据同步失败,对象ID: {}, 错误信息: {}", id, result.get("message"));
+ return error((String) result.get("message"));
+ }
+
+ } catch (Exception e) {
+ log.error("同步对象数据时发生异常,对象ID: {}", id, e);
+ return error("同步对象数据时发生异常: " + e.getMessage());
+ }
+ }
}
diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/mapper/DataiIntegrationObjectMapper.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/mapper/DataiIntegrationObjectMapper.java
index ed405eb5..46a09477 100644
--- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/mapper/DataiIntegrationObjectMapper.java
+++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/mapper/DataiIntegrationObjectMapper.java
@@ -20,6 +20,14 @@ public interface DataiIntegrationObjectMapper
*/
public DataiIntegrationObject selectDataiIntegrationObjectById(Integer id);
+ /**
+ * 根据API查询对象同步控制
+ *
+ * @param api 对象API
+ * @return 对象同步控制
+ */
+ public DataiIntegrationObject selectDataiIntegrationObjectByApi(String api);
+
/**
* 查询对象同步控制列表
*
diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java
index 847d2307..8806f10c 100644
--- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java
+++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/IDataiIntegrationObjectService.java
@@ -108,4 +108,12 @@ public interface IDataiIntegrationObjectService
* @return 统计信息
*/
public Map getObjectStatistics();
+
+ /**
+ * 同步单对象数据到本地数据库
+ *
+ * @param id 对象ID
+ * @return 同步结果
+ */
+ public Map syncSingleObjectData(Integer id);
}
diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java
index 5b5a0ba5..119cb5aa 100644
--- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java
+++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationBatchServiceImpl.java
@@ -1,5 +1,6 @@
package com.datai.integration.service.impl;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
@@ -21,6 +22,7 @@ import com.datai.common.core.domain.model.LoginUser;
import com.datai.integration.model.domain.DataiIntegrationBatchHistory;
import com.datai.integration.service.IDataiIntegrationBatchHistoryService;
import com.datai.integration.service.IDataiIntegrationFieldService;
+import com.datai.integration.service.IDataiIntegrationSyncLogService;
import com.datai.integration.mapper.CustomMapper;
import com.datai.integration.factory.impl.SOAPConnectionFactory;
import com.sforce.soap.partner.DescribeSObjectResult;
@@ -56,6 +58,9 @@ public class DataiIntegrationBatchServiceImpl implements IDataiIntegrationBatchS
@Autowired
private SOAPConnectionFactory soapConnectionFactory;
+ @Autowired
+ private IDataiIntegrationSyncLogService dataiIntegrationSyncLogService;
+
/**
* 查询数据批次
*
@@ -338,7 +343,18 @@ public class DataiIntegrationBatchServiceImpl implements IDataiIntegrationBatchS
return false;
}
+ DataiIntegrationBatch batch = null;
+ DataiIntegrationBatchHistory batchHistory = new DataiIntegrationBatchHistory();
+ long startTime = System.currentTimeMillis();
+ LocalDateTime syncStartTime = LocalDateTime.now();
+
try {
+ batch = selectDataiIntegrationBatchById(Integer.parseInt(batchId));
+ if (batch == null) {
+ log.error("批次不存在,批次ID: {}", batchId);
+ return false;
+ }
+
PartnerConnection connection = retryOperation(() -> soapConnectionFactory.getConnection(), 3, 1000);
log.info("成功获取Salesforce SOAP连接");
@@ -355,14 +371,83 @@ public class DataiIntegrationBatchServiceImpl implements IDataiIntegrationBatchS
int totalCount = executeQueryAndProcessData(connection, param, fieldList);
log.info("对象 {} 批次 {} 数据同步完成,共处理 {} 条记录", objectApi, batchId, totalCount);
+
+ long endTime = System.currentTimeMillis();
+ long duration = endTime - startTime;
+ LocalDateTime syncEndTime = LocalDateTime.now();
+
+ batchHistory.setApi(objectApi);
+ batchHistory.setLabel(batch.getLabel());
+ batchHistory.setBatchId(Integer.parseInt(batchId));
+ batchHistory.setBatchField(batch.getBatchField());
+ batchHistory.setSyncNum(totalCount);
+ batchHistory.setSyncType(batch.getSyncType());
+ batchHistory.setSyncStatus(true);
+ batchHistory.setStartTime(syncStartTime);
+ batchHistory.setEndTime(syncEndTime);
+ batchHistory.setCost(duration);
+ batchHistory.setSyncStartTime(syncStartTime);
+ batchHistory.setSyncEndTime(syncEndTime);
+ batchHistoryService.insertDataiIntegrationBatchHistory(batchHistory);
+ log.info("批次 {} 历史记录已保存,同步数据量: {}, 耗时: {}ms", batchId, totalCount, duration);
+ insertSyncLog(objectApi, batch.getLabel(), batch.getSyncType(), true, null, duration);
+
return true;
} catch (Exception e) {
log.error("同步Salesforce对象指定批次数据失败,对象API: {}, 批次ID: {}", objectApi, batchId, e);
+
+ long endTime = System.currentTimeMillis();
+ long duration = endTime - startTime;
+ LocalDateTime syncEndTime = LocalDateTime.now();
+
+ batchHistory.setApi(objectApi);
+ batchHistory.setLabel(batch != null ? batch.getLabel() : "");
+ batchHistory.setBatchId(Integer.parseInt(batchId));
+ batchHistory.setBatchField(batch != null ? batch.getBatchField() : "");
+ batchHistory.setSyncNum(0);
+ batchHistory.setSyncType(batch != null ? batch.getSyncType() : "");
+ batchHistory.setSyncStatus(false);
+ batchHistory.setStartTime(syncStartTime);
+ batchHistory.setEndTime(syncEndTime);
+ batchHistory.setCost(duration);
+ batchHistory.setSyncStartTime(syncStartTime);
+ batchHistory.setSyncEndTime(syncEndTime);
+ batchHistoryService.insertDataiIntegrationBatchHistory(batchHistory);
+ log.info("批次 {} 失败历史记录已保存,耗时: {}ms", batchId, duration);
+
+ insertSyncLog(objectApi, batch != null ? batch.getLabel() : "", batch != null ? batch.getSyncType() : "", false, e.getMessage(), duration);
+
return false;
}
}
+ private void insertSyncLog(String objectApi, String label, String syncType, boolean success, String errorMessage, long duration) {
+ try {
+ com.datai.integration.model.domain.DataiIntegrationSyncLog syncLog = new com.datai.integration.model.domain.DataiIntegrationSyncLog();
+ syncLog.setObjectApi(objectApi);
+ syncLog.setOperationType("FULL".equals(syncType) ? "全量同步" : "增量同步");
+ syncLog.setOperationStatus(success ? "成功" : "失败");
+ syncLog.setErrorMessage(errorMessage);
+ syncLog.setExecutionTime(new java.math.BigDecimal(duration / 1000.0));
+ syncLog.setCreateTime(DateUtils.getNowDate());
+
+ try {
+ LoginUser loginUser = SecurityUtils.getLoginUser();
+ if (loginUser != null && loginUser.getDeptId() != null) {
+ syncLog.setDeptId(loginUser.getDeptId());
+ }
+ } catch (Exception ex) {
+ log.warn("获取部门ID失败: {}", ex.getMessage());
+ }
+
+ dataiIntegrationSyncLogService.insertDataiIntegrationSyncLog(syncLog);
+ log.info("同步日志已记录,对象API: {}, 操作类型: {}, 状态: {}", objectApi, syncLog.getOperationType(), syncLog.getOperationStatus());
+ } catch (Exception e) {
+ log.error("插入同步日志失败: {}", e.getMessage(), e);
+ }
+ }
+
/**
* 重试操作
*
diff --git a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java
index 2360778d..511d3c70 100644
--- a/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java
+++ b/datai-scenes/datai-scene-salesforce/datai-salesforce-integration/src/main/java/com/datai/integration/service/impl/DataiIntegrationObjectServiceImpl.java
@@ -1,10 +1,9 @@
package com.datai.integration.service.impl;
+import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
import com.datai.common.core.domain.model.LoginUser;
import com.datai.common.utils.DateUtils;
@@ -12,12 +11,20 @@ import com.datai.common.utils.SecurityUtils;
import com.datai.integration.factory.impl.SOAPConnectionFactory;
import com.datai.integration.mapper.CustomMapper;
import com.datai.integration.mapper.DataiIntegrationObjectMapper;
+import com.datai.integration.model.domain.DataiIntegrationBatch;
import com.datai.integration.model.domain.DataiIntegrationObject;
+import com.datai.integration.service.IDataiIntegrationBatchService;
+import com.datai.integration.service.IDataiIntegrationFieldService;
import com.datai.integration.service.IDataiIntegrationObjectService;
+import com.datai.salesforce.common.param.SalesforceParam;
+import com.datai.setting.future.SalesforceExecutor;
import com.sforce.soap.partner.DescribeSObjectResult;
import com.sforce.soap.partner.Field;
import com.sforce.soap.partner.PartnerConnection;
+import com.sforce.soap.partner.QueryResult;
+import com.sforce.soap.partner.sobject.SObject;
import com.sforce.ws.ConnectionException;
+import org.apache.commons.lang3.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -42,6 +49,15 @@ public class DataiIntegrationObjectServiceImpl implements IDataiIntegrationObjec
@Autowired
private SOAPConnectionFactory soapConnectionFactory;
+ @Autowired
+ private IDataiIntegrationFieldService dataiIntegrationFieldService;
+
+ @Autowired
+ private IDataiIntegrationBatchService dataiIntegrationBatchService;
+
+ @Autowired
+ private SalesforceExecutor salesforceExecutor;
+
/**
* 查询对象同步控制
*
@@ -305,6 +321,40 @@ public class DataiIntegrationObjectServiceImpl implements IDataiIntegrationObjec
return result;
}
+ /**
+ * 将Salesforce数据类型转换为MySQL数据类型
+ *
+ * 该方法负责将Salesforce对象字段的数据类型映射为MySQL数据库对应的数据类型,
+ * 用于自动创建本地数据表时确定字段的SQL数据类型定义。
+ *
+ * 类型映射规则:
+ * - id → VARCHAR(18):Salesforce的ID字段为18位字符串
+ * - string/email/url/phone → VARCHAR(255):文本类型字段
+ * - textarea → TEXT:长文本字段
+ * - boolean → TINYINT(1):布尔值,MySQL中用0和1表示
+ * - int → INT:整数字段
+ * - double → DOUBLE:双精度浮点数
+ * - currency → DECIMAL(18,4):货币类型,保留4位小数
+ * - date → DATE:日期类型
+ * - datetime → DATETIME:日期时间类型
+ * - time → TIME:时间类型
+ * - percent → DECIMAL(10,2):百分比,保留2位小数
+ * - reference → VARCHAR(18):引用字段,存储其他对象的ID
+ * - picklist/multipicklist → VARCHAR(255):下拉列表字段
+ * - combobox → VARCHAR(255):组合框字段
+ * - base64 → LONGBLOB:二进制大对象,用于存储附件等
+ * - address → TEXT:地址字段
+ * - location → VARCHAR(255):位置字段
+ * - 其他未知类型 → VARCHAR(255):默认类型
+ *
+ * 使用场景:
+ * - 自动创建数据表时确定字段类型
+ * - 动态生成DDL语句
+ * - 确保数据类型兼容性
+ *
+ * @param typeStr Salesforce字段类型字符串,如"string"、"int"等
+ * @return 对应的MySQL数据类型字符串,如"VARCHAR(255)"、"INT"等
+ */
private String convertSalesforceTypeToMySQL(String typeStr) {
if (typeStr == null || typeStr.isEmpty()) {
return "VARCHAR(255)";
@@ -579,4 +629,825 @@ public class DataiIntegrationObjectServiceImpl implements IDataiIntegrationObjec
return statistics;
}
+
+ /**
+ * 同步单个Salesforce对象的数据到本地数据库
+ *
+ * @param id 对象ID,用于标识需要同步的Salesforce对象
+ * @return Map 同步结果,包含success、message、objectId、objectApi、totalCount、duration、syncType、lastFullSyncDate等字段
+ * @throws ConnectionException 当获取Salesforce连接失败时抛出
+ * @throws RuntimeException 当同步过程中发生其他异常时抛出
+ */
+ @Override
+ public Map syncSingleObjectData(Integer id)
+ {
+ Map result = new HashMap<>();
+ long startTime = System.currentTimeMillis();
+ DataiIntegrationObject object = null;
+
+ try {
+ if (id == null) {
+ log.error("对象ID不能为空");
+ result.put("success", false);
+ result.put("message", "对象ID不能为空");
+ return result;
+ }
+
+ log.info("开始同步单对象数据,对象ID: {}", id);
+
+ object = dataiIntegrationObjectMapper.selectDataiIntegrationObjectById(id);
+ if (object == null) {
+ log.error("对象不存在,对象ID: {}", id);
+ result.put("success", false);
+ result.put("message", "对象不存在");
+ return result;
+ }
+
+ if (object.getApi() == null || object.getApi().trim().isEmpty()) {
+ log.error("对象API为空,对象ID: {}", id);
+ result.put("success", false);
+ result.put("message", "对象API不能为空");
+ return result;
+ }
+
+ if (!object.getIsWork()) {
+ log.warn("对象未启用同步,对象ID: {}, 对象API: {}", id, object.getApi());
+ result.put("success", false);
+ result.put("message", "对象未启用同步");
+ return result;
+ }
+
+ String objectApi = object.getApi().trim();
+
+ log.info("准备同步对象数据,对象API: {}", objectApi);
+
+ LocalDateTime lastFullSyncDate = object.getLastFullSyncDate();
+ Map syncResult;
+
+ if (lastFullSyncDate == null) {
+ log.info("对象 {} 的全量拉取时间为空,执行全量数据拉取", objectApi);
+ syncResult = syncFullData(object, startTime);
+ } else {
+ log.info("对象 {} 的全量拉取时间不为空({}),执行增量数据拉取", objectApi, lastFullSyncDate);
+ syncResult = syncIncrementalData(object, startTime);
+ }
+
+ result.putAll(syncResult);
+
+ } catch (Exception e) {
+ log.error("同步单对象数据失败,对象ID: {}", id, e);
+ if (object != null) {
+ object.setErrorMessage("同步单对象数据失败: " + e.getMessage());
+ object.setSyncStatus(false);
+ object.setUpdateTime(DateUtils.getNowDate());
+ updateDataiIntegrationObject(object);
+ }
+ result.put("success", false);
+ result.put("message", "同步单对象数据失败: " + e.getMessage());
+ }
+
+ return result;
+ }
+
+ /**
+ * 全量数据拉取
+ * 查询所有批次并使用多线程并行拉取
+ *
+ * @param object 对象信息
+ * @param startTime 开始时间
+ * @return 同步结果
+ */
+ private Map syncFullData(DataiIntegrationObject object, long startTime) {
+ Map result = new HashMap<>();
+ String objectApi = object.getApi().trim();
+
+ try {
+ DataiIntegrationBatch queryBatch = new DataiIntegrationBatch();
+ queryBatch.setApi(objectApi);
+ queryBatch.setSyncType("FULL");
+ List batches = dataiIntegrationBatchService.selectDataiIntegrationBatchList(queryBatch);
+
+ if (batches.isEmpty()) {
+ log.warn("对象 {} 没有找到全量同步批次,无法执行全量数据拉取", objectApi);
+ result.put("success", false);
+ result.put("message", "没有找到全量同步批次");
+ return result;
+ }
+
+ log.info("对象 {} 共找到 {} 个全量同步批次,准备使用多线程并行拉取", objectApi, batches.size());
+
+ List> futures = new ArrayList<>();
+ List
- *
+ *
+ * 特性:
+ * 1. 支持任务优先级排序(基于ComparableFutureTask)
+ * 2. 自动将父线程的Spring Security上下文传递到子线程
+ * 3. 可配置的线程池参数(核心线程数、最大线程数、队列容量等)
+ * 4. 支持优雅关闭和强制关闭
+ *
*/
@Service
@Slf4j
@@ -180,6 +186,10 @@ public class SalesforceExecutor {
*
* 将任务提交到线程池执行,支持优先级设置。
*
+ *
+ * 注意:该方法会自动将当前线程的Spring Security上下文传递到子线程中,
+ * 确保子线程可以访问父线程的安全信息(如登录用户信息)。
+ *
*
* @param runnable 要执行的任务
* @param batch 批次号(优先级相同时,值越小优先级越高)