feat: 重写insertBatchDataToTarget方法,参考Salesforce数据插入逻辑

- 重写DataiIntegrationBatchServiceImpl中的insertBatchDataToTarget方法
- 参考CommonServiceImpl.java中的Salesforce数据插入逻辑
- 优化字段映射结构,使用List<Map<String, Object>>存储字段映射
- 增加字段过滤,排除is_dump和is_upload等标记字段
- 添加线程休眠机制,避免API限流
- 改进错误处理,增加完整异常堆栈信息
- 添加工作流提示词文档(03-01, 03-02, 03-03)
- 添加Salesforce参考代码(CommonServiceImpl.java, DataImportNewServiceImpl.java)
This commit is contained in:
DESKTOP-368IV5S\Kris 2026-01-15 12:21:01 +08:00
parent 19b1d1a41a
commit f6a97dbf7f
6 changed files with 6648 additions and 324 deletions

View File

@ -13,7 +13,7 @@ import com.datai.common.utils.SecurityUtils;
import com.datai.integration.model.param.DataiSyncParam;
import com.datai.salesforce.common.utils.SoqlBuilder;
import com.datai.integration.util.ConvertUtil;
import com.sforce.soap.partner.PartnerConnection;
import com.sforce.soap.partner.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -27,10 +27,7 @@ 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;
import com.sforce.soap.partner.Field;
import com.datai.integration.core.IPartnerV1Connection;
import com.sforce.soap.partner.QueryResult;
import com.sforce.soap.partner.sobject.SObject;
import com.sforce.ws.ConnectionException;
import lombok.extern.slf4j.Slf4j;
@ -998,197 +995,202 @@ public class DataiIntegrationBatchServiceImpl implements IDataiIntegrationBatchS
public Map<String, Object> insertBatchDataToTarget(Integer id, String targetOrgType) {
Map<String, Object> result = new HashMap<>();
long startTime = System.currentTimeMillis();
log.info("开始插入批次数据到目标系统批次ID: {}, 目标ORG类型: {}", id, targetOrgType);
if (id == null) {
log.error("批次ID不能为空");
result.put("success", false);
result.put("message", "批次ID不能为空");
return result;
}
if (targetOrgType == null || targetOrgType.trim().isEmpty()) {
log.error("目标ORG类型不能为空");
result.put("success", false);
result.put("message", "目标ORG类型不能为空");
return result;
}
DataiIntegrationBatch batch = selectDataiIntegrationBatchById(id);
if (batch == null) {
log.error("批次不存在批次ID: {}", id);
result.put("success", false);
result.put("message", "批次不存在");
return result;
}
String objectApi = batch.getApi();
if (objectApi == null || objectApi.trim().isEmpty()) {
log.error("批次对象API为空批次ID: {}", id);
result.put("success", false);
result.put("message", "批次对象API不能为空");
return result;
}
String tableName = objectApi.trim();
if (customMapper.checkTable(tableName) == null) {
log.error("本地表不存在,表名: {}", tableName);
result.put("success", false);
result.put("message", "本地表不存在: " + tableName);
return result;
}
log.info("准备插入批次 {} 的对象 {} 数据到目标系统", id, objectApi);
IPartnerV1Connection targetConnection = null;
try {
log.info("开始插入批次数据到目标系统批次ID: {}, 目标ORG类型: {}", id, targetOrgType);
if (id == null) {
log.error("批次ID不能为空");
targetConnection = soapConnectionFactory.getConnection(targetOrgType);
if (targetConnection == null) {
log.error("无法获取目标系统连接ORG类型: {}", targetOrgType);
result.put("success", false);
result.put("message", "批次ID不能为空");
result.put("message", "无法获取目标系统连接");
return result;
}
if (targetOrgType == null || targetOrgType.trim().isEmpty()) {
log.error("目标ORG类型不能为空");
DescribeSObjectResult objDetail = targetConnection.describeSObject(objectApi);
if (objDetail == null) {
log.error("目标系统不存在该对象对象API: {}", objectApi);
result.put("success", false);
result.put("message", "目标ORG类型不能为空");
result.put("message", "目标系统不存在该对象: " + objectApi);
return result;
}
DataiIntegrationBatch batch = selectDataiIntegrationBatchById(id);
if (batch == null) {
log.error("批次不存在批次ID: {}", id);
String batchField = batch.getBatchField();
String whereClause = "";
if (batchField != null && !batchField.trim().isEmpty()) {
whereClause = "WHERE " + batchField.trim();
}
log.info("开始查询本地批次数据,表名: {}, 条件: {}", tableName, whereClause);
List<Map<String, Object>> dataList = customMapper.list("*", tableName, whereClause);
if (dataList == null || dataList.isEmpty()) {
log.warn("本地批次没有数据,表名: {}, 条件: {}", tableName, whereClause);
result.put("success", false);
result.put("message", "批次不存在");
result.put("message", "本地批次没有数据");
return result;
}
String objectApi = batch.getApi();
if (objectApi == null || objectApi.trim().isEmpty()) {
log.error("批次对象API为空批次ID: {}", id);
result.put("success", false);
result.put("message", "批次对象API不能为空");
return result;
}
log.info("查询到 {} 条本地批次数据,准备插入到目标系统", dataList.size());
String tableName = objectApi.trim();
if (customMapper.checkTable(tableName) == null) {
log.error("本地表不存在,表名: {}", tableName);
result.put("success", false);
result.put("message", "本地表不存在: " + tableName);
return result;
}
int successCount = 0;
int failureCount = 0;
List<Map<String, Object>> failedRecords = new ArrayList<>();
log.info("准备插入批次 {} 的对象 {} 数据到目标系统", id, objectApi);
IPartnerV1Connection targetConnection = null;
try {
targetConnection = soapConnectionFactory.getConnection(targetOrgType);
if (targetConnection == null) {
log.error("无法获取目标系统连接ORG类型: {}", targetOrgType);
result.put("success", false);
result.put("message", "无法获取目标系统连接");
return result;
}
DescribeSObjectResult objDetail = targetConnection.describeSObject(objectApi);
if (objDetail == null) {
log.error("目标系统不存在该对象对象API: {}", objectApi);
result.put("success", false);
result.put("message", "目标系统不存在该对象: " + objectApi);
return result;
}
String batchField = batch.getBatchField();
String whereClause = "";
if (batchField != null && !batchField.trim().isEmpty()) {
whereClause = "WHERE " + batchField.trim() + " = '" + batch.getBatchValue() + "'";
}
log.info("开始查询本地批次数据,表名: {}, 条件: {}", tableName, whereClause);
List<Map<String, Object>> dataList = customMapper.list("*", tableName, whereClause);
if (dataList == null || dataList.isEmpty()) {
log.warn("本地批次没有数据,表名: {}, 条件: {}", tableName, whereClause);
result.put("success", false);
result.put("message", "本地批次没有数据");
return result;
}
log.info("查询到 {} 条本地批次数据,准备插入到目标系统", dataList.size());
int successCount = 0;
int failureCount = 0;
List<Map<String, Object>> failedRecords = new ArrayList<>();
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> dataMap = dataList.get(i);
try {
String localId = (String) dataMap.get("id");
if (localId == null || localId.isEmpty()) {
log.warn("记录ID为空跳过该记录索引: {}", i);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("reason", "记录ID为空");
failedRecords.add(failedRecord);
continue;
}
Map<String, Object> fieldsToInsert = new HashMap<>();
for (String key : dataMap.keySet()) {
if (!"id".equalsIgnoreCase(key) && !"new_id".equalsIgnoreCase(key) &&
!"is_insert".equalsIgnoreCase(key) && !"is_update".equalsIgnoreCase(key) &&
dataMap.get(key) != null) {
fieldsToInsert.put(key, dataMap.get(key));
}
}
if (fieldsToInsert.isEmpty()) {
log.warn("记录没有需要插入的字段跳过该记录ID: {}", localId);
continue;
}
SObject sObject = new SObject();
sObject.setType(objectApi);
for (Map.Entry<String, Object> entry : fieldsToInsert.entrySet()) {
sObject.setField(entry.getKey(), entry.getValue());
}
SaveResult[] saveResults = targetConnection.create(new SObject[]{sObject});
if (saveResults != null && saveResults.length > 0 && saveResults[0].isSuccess()) {
String newId = saveResults[0].getId();
List<Map<String, Object>> updateMaps = new ArrayList<>();
Map<String, Object> newIdMap = new HashMap<>();
newIdMap.put("key", "new_id");
newIdMap.put("value", newId);
updateMaps.add(newIdMap);
Map<String, Object> isInsertMap = new HashMap<>();
isInsertMap.put("key", "is_insert");
isInsertMap.put("value", 1);
updateMaps.add(isInsertMap);
customMapper.updateById(tableName, updateMaps, localId);
successCount++;
log.debug("记录插入成功本地ID: {}, 新ID: {}", localId, newId);
} else {
String errorMsg = saveResults != null && saveResults.length > 0 ?
saveResults[0].getErrors()[0].getMessage() : "未知错误";
log.error("记录插入失败本地ID: {}, 错误: {}", localId, errorMsg);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("localId", localId);
failedRecord.put("reason", errorMsg);
failedRecords.add(failedRecord);
}
if ((i + 1) % 100 == 0) {
log.info("已插入 {}/{} 条记录", i + 1, dataList.size());
}
} catch (Exception e) {
log.error("插入记录失败,索引: {}, 错误: {}", i, e.getMessage());
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> dataMap = dataList.get(i);
try {
String localId = (String) dataMap.get("id");
if (localId == null || localId.isEmpty()) {
log.warn("记录ID为空跳过该记录索引: {}", i);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("reason", e.getMessage());
failedRecord.put("reason", "记录ID为空");
failedRecords.add(failedRecord);
continue;
}
List<Map<String, Object>> maps = new ArrayList<>();
for (String key : dataMap.keySet()) {
Object value = dataMap.get(key);
if (value != null && !"id".equalsIgnoreCase(key) &&
!"new_id".equalsIgnoreCase(key) &&
!"is_insert".equalsIgnoreCase(key) &&
!"is_update".equalsIgnoreCase(key) &&
!"is_dump".equalsIgnoreCase(key) &&
!"is_upload".equalsIgnoreCase(key)) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("key", key);
paramMap.put("value", value);
maps.add(paramMap);
}
}
if (maps.isEmpty()) {
log.warn("记录没有需要插入的字段跳过该记录ID: {}", localId);
continue;
}
SObject sObject = new SObject();
sObject.setType(objectApi);
for (Map<String, Object> paramMap : maps) {
String key = (String) paramMap.get("key");
Object value = paramMap.get("value");
sObject.setField(key, value);
}
SaveResult[] saveResults = targetConnection.create(new SObject[]{sObject});
if (saveResults != null && saveResults.length > 0 && saveResults[0].isSuccess()) {
String newId = saveResults[0].getId();
List<Map<String, Object>> updateMaps = new ArrayList<>();
Map<String, Object> newIdMap = new HashMap<>();
newIdMap.put("key", "new_id");
newIdMap.put("value", newId);
updateMaps.add(newIdMap);
Map<String, Object> isInsertMap = new HashMap<>();
isInsertMap.put("key", "is_insert");
isInsertMap.put("value", 1);
updateMaps.add(isInsertMap);
customMapper.updateById(tableName, updateMaps, localId);
successCount++;
log.debug("记录插入成功本地ID: {}, 新ID: {}", localId, newId);
} else {
String errorMsg = saveResults != null && saveResults.length > 0 ?
saveResults[0].getErrors()[0].getMessage() : "未知错误";
log.error("记录插入失败本地ID: {}, 错误: {}", localId, errorMsg);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("localId", localId);
failedRecord.put("reason", errorMsg);
failedRecords.add(failedRecord);
}
}
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
result.put("success", true);
result.put("message", "批次数据插入完成");
result.put("batchId", id);
result.put("api", objectApi);
result.put("label", batch.getLabel());
result.put("targetOrgType", targetOrgType);
result.put("totalCount", dataList.size());
result.put("successCount", successCount);
result.put("failureCount", failureCount);
result.put("duration", duration);
result.put("failedRecords", failedRecords);
log.info("批次数据插入完成批次ID: {}, 对象API: {}, 总数: {}, 成功: {}, 失败: {}, 耗时: {}ms",
id, objectApi, dataList.size(), successCount, failureCount, duration);
} finally {
if (targetConnection != null) {
try {
targetConnection.logout();
} catch (Exception e) {
log.error("关闭目标系统连接失败", e);
if ((i + 1) % 100 == 0) {
log.info("已插入 {}/{} 条记录", i + 1, dataList.size());
}
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
log.warn("线程休眠被中断", e);
Thread.currentThread().interrupt();
}
} catch (Exception e) {
log.error("插入记录失败,索引: {}, 错误: {}", i, e.getMessage(), e);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("reason", e.getMessage());
failedRecords.add(failedRecord);
}
}
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
result.put("success", true);
result.put("message", "批次数据插入完成");
result.put("batchId", id);
result.put("api", objectApi);
result.put("label", batch.getLabel());
result.put("targetOrgType", targetOrgType);
result.put("totalCount", dataList.size());
result.put("successCount", successCount);
result.put("failureCount", failureCount);
result.put("duration", duration);
result.put("failedRecords", failedRecords);
log.info("批次数据插入完成批次ID: {}, 对象API: {}, 总数: {}, 成功: {}, 失败: {}, 耗时: {}ms",
id, objectApi, dataList.size(), successCount, failureCount, duration);
} catch (Exception e) {
log.error("插入批次数据到目标系统失败批次ID: {}", id, e);
result.put("success", false);
@ -1201,186 +1203,175 @@ public class DataiIntegrationBatchServiceImpl implements IDataiIntegrationBatchS
public Map<String, Object> updateBatchDataToTarget(Integer id, String targetOrgType) {
Map<String, Object> result = new HashMap<>();
long startTime = System.currentTimeMillis();
log.info("开始更新批次数据到目标系统批次ID: {}, 目标ORG类型: {}", id, targetOrgType);
if (id == null) {
log.error("批次ID不能为空");
result.put("success", false);
result.put("message", "批次ID不能为空");
return result;
}
if (targetOrgType == null || targetOrgType.trim().isEmpty()) {
log.error("目标ORG类型不能为空");
result.put("success", false);
result.put("message", "目标ORG类型不能为空");
return result;
}
DataiIntegrationBatch batch = selectDataiIntegrationBatchById(id);
if (batch == null) {
log.error("批次不存在批次ID: {}", id);
result.put("success", false);
result.put("message", "批次不存在");
return result;
}
String objectApi = batch.getApi();
if (objectApi == null || objectApi.trim().isEmpty()) {
log.error("批次对象API为空批次ID: {}", id);
result.put("success", false);
result.put("message", "批次对象API不能为空");
return result;
}
String tableName = objectApi.trim();
if (customMapper.checkTable(tableName) == null) {
log.error("本地表不存在,表名: {}", tableName);
result.put("success", false);
result.put("message", "本地表不存在: " + tableName);
return result;
}
log.info("准备更新批次 {} 的对象 {} 数据到目标系统", id, objectApi);
IPartnerV1Connection targetConnection = null;
try {
log.info("开始更新批次数据到目标系统批次ID: {}, 目标ORG类型: {}", id, targetOrgType);
if (id == null) {
log.error("批次ID不能为空");
targetConnection = soapConnectionFactory.getConnection(targetOrgType);
if (targetConnection == null) {
log.error("无法获取目标系统连接ORG类型: {}", targetOrgType);
result.put("success", false);
result.put("message", "批次ID不能为空");
result.put("message", "无法获取目标系统连接");
return result;
}
if (targetOrgType == null || targetOrgType.trim().isEmpty()) {
log.error("目标ORG类型不能为空");
DescribeSObjectResult objDetail = targetConnection.describeSObject(objectApi);
if (objDetail == null) {
log.error("目标系统不存在该对象对象API: {}", objectApi);
result.put("success", false);
result.put("message", "目标ORG类型不能为空");
result.put("message", "目标系统不存在该对象: " + objectApi);
return result;
}
DataiIntegrationBatch batch = selectDataiIntegrationBatchById(id);
if (batch == null) {
log.error("批次不存在批次ID: {}", id);
String batchField = batch.getBatchField();
String whereClause = "";
if (batchField != null && !batchField.trim().isEmpty()) {
whereClause = "WHERE " + batchField.trim();
}
log.info("开始查询本地批次数据,表名: {}, 条件: {}", tableName, whereClause);
List<Map<String, Object>> dataList = customMapper.list("*", tableName, whereClause);
if (dataList == null || dataList.isEmpty()) {
log.warn("本地批次没有数据,表名: {}, 条件: {}", tableName, whereClause);
result.put("success", false);
result.put("message", "批次不存在");
result.put("message", "本地批次没有数据");
return result;
}
String objectApi = batch.getApi();
if (objectApi == null || objectApi.trim().isEmpty()) {
log.error("批次对象API为空批次ID: {}", id);
result.put("success", false);
result.put("message", "批次对象API不能为空");
return result;
}
log.info("查询到 {} 条本地批次数据,准备更新到目标系统", dataList.size());
String tableName = objectApi.trim();
if (customMapper.checkTable(tableName) == null) {
log.error("本地表不存在,表名: {}", tableName);
result.put("success", false);
result.put("message", "本地表不存在: " + tableName);
return result;
}
int successCount = 0;
int failureCount = 0;
List<Map<String, Object>> failedRecords = new ArrayList<>();
log.info("准备更新批次 {} 的对象 {} 数据到目标系统", id, objectApi);
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> dataMap = dataList.get(i);
try {
String localId = (String) dataMap.get("id");
String newId = (String) dataMap.get("new_id");
IPartnerV1Connection targetConnection = null;
try {
targetConnection = soapConnectionFactory.getConnection(targetOrgType);
if (targetConnection == null) {
log.error("无法获取目标系统连接ORG类型: {}", targetOrgType);
result.put("success", false);
result.put("message", "无法获取目标系统连接");
return result;
}
DescribeSObjectResult objDetail = targetConnection.describeSObject(objectApi);
if (objDetail == null) {
log.error("目标系统不存在该对象对象API: {}", objectApi);
result.put("success", false);
result.put("message", "目标系统不存在该对象: " + objectApi);
return result;
}
String batchField = batch.getBatchField();
String whereClause = "";
if (batchField != null && !batchField.trim().isEmpty()) {
whereClause = "WHERE " + batchField.trim() + " = '" + batch.getBatchValue() + "'";
}
log.info("开始查询本地批次数据,表名: {}, 条件: {}", tableName, whereClause);
List<Map<String, Object>> dataList = customMapper.list("*", tableName, whereClause);
if (dataList == null || dataList.isEmpty()) {
log.warn("本地批次没有数据,表名: {}, 条件: {}", tableName, whereClause);
result.put("success", false);
result.put("message", "本地批次没有数据");
return result;
}
log.info("查询到 {} 条本地批次数据,准备更新到目标系统", dataList.size());
int successCount = 0;
int failureCount = 0;
List<Map<String, Object>> failedRecords = new ArrayList<>();
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> dataMap = dataList.get(i);
try {
String localId = (String) dataMap.get("id");
String newId = (String) dataMap.get("new_id");
if (localId == null || localId.isEmpty()) {
log.warn("记录ID为空跳过该记录索引: {}", i);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("reason", "记录ID为空");
failedRecords.add(failedRecord);
continue;
}
if (newId == null || newId.isEmpty()) {
log.warn("记录new_id为空跳过该记录本地ID: {}", localId);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("localId", localId);
failedRecord.put("reason", "new_id为空请先执行插入操作");
failedRecords.add(failedRecord);
continue;
}
Map<String, Object> fieldsToUpdate = new HashMap<>();
for (String key : dataMap.keySet()) {
if (!"id".equalsIgnoreCase(key) && !"new_id".equalsIgnoreCase(key) &&
!"is_insert".equalsIgnoreCase(key) && !"is_update".equalsIgnoreCase(key) &&
dataMap.get(key) != null) {
fieldsToUpdate.put(key, dataMap.get(key));
}
}
if (fieldsToUpdate.isEmpty()) {
log.warn("记录没有需要更新的字段跳过该记录本地ID: {}", localId);
continue;
}
targetConnection.update(objectApi, newId, fieldsToUpdate);
List<Map<String, Object>> updateMaps = new ArrayList<>();
Map<String, Object> isUpdateMap = new HashMap<>();
isUpdateMap.put("key", "is_update");
isUpdateMap.put("value", 1);
updateMaps.add(isUpdateMap);
customMapper.updateById(tableName, updateMaps, localId);
successCount++;
log.debug("记录更新成功本地ID: {}, 目标ID: {}", localId, newId);
if ((i + 1) % 100 == 0) {
log.info("已更新 {}/{} 条记录", i + 1, dataList.size());
}
} catch (Exception e) {
log.error("更新记录失败,索引: {}, 错误: {}", i, e.getMessage());
if (localId == null || localId.isEmpty()) {
log.warn("记录ID为空跳过该记录索引: {}", i);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("reason", e.getMessage());
failedRecord.put("reason", "记录ID为空");
failedRecords.add(failedRecord);
continue;
}
}
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
result.put("success", true);
result.put("message", "批次数据更新完成");
result.put("batchId", id);
result.put("api", objectApi);
result.put("label", batch.getLabel());
result.put("targetOrgType", targetOrgType);
result.put("totalCount", dataList.size());
result.put("successCount", successCount);
result.put("failureCount", failureCount);
result.put("duration", duration);
result.put("failedRecords", failedRecords);
log.info("批次数据更新完成批次ID: {}, 对象API: {}, 总数: {}, 成功: {}, 失败: {}, 耗时: {}ms",
id, objectApi, dataList.size(), successCount, failureCount, duration);
} finally {
if (targetConnection != null) {
try {
targetConnection.logout();
} catch (Exception e) {
log.error("关闭目标系统连接失败", e);
if (newId == null || newId.isEmpty()) {
log.warn("记录new_id为空跳过该记录本地ID: {}", localId);
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("localId", localId);
failedRecord.put("reason", "new_id为空请先执行插入操作");
failedRecords.add(failedRecord);
continue;
}
Map<String, Object> fieldsToUpdate = new HashMap<>();
for (String key : dataMap.keySet()) {
if (!"id".equalsIgnoreCase(key) && !"new_id".equalsIgnoreCase(key) &&
!"is_insert".equalsIgnoreCase(key) && !"is_update".equalsIgnoreCase(key) &&
dataMap.get(key) != null) {
fieldsToUpdate.put(key, dataMap.get(key));
}
}
if (fieldsToUpdate.isEmpty()) {
log.warn("记录没有需要更新的字段跳过该记录本地ID: {}", localId);
continue;
}
List<Map<String, Object>> updateMaps = new ArrayList<>();
Map<String, Object> isUpdateMap = new HashMap<>();
isUpdateMap.put("key", "is_update");
isUpdateMap.put("value", 1);
updateMaps.add(isUpdateMap);
customMapper.updateById(tableName, updateMaps, localId);
successCount++;
log.debug("记录更新成功本地ID: {}, 目标ID: {}", localId, newId);
if ((i + 1) % 100 == 0) {
log.info("已更新 {}/{} 条记录", i + 1, dataList.size());
}
} catch (Exception e) {
log.error("更新记录失败,索引: {}, 错误: {}", i, e.getMessage());
failureCount++;
Map<String, Object> failedRecord = new HashMap<>();
failedRecord.put("index", i);
failedRecord.put("reason", e.getMessage());
failedRecords.add(failedRecord);
}
}
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
result.put("success", true);
result.put("message", "批次数据更新完成");
result.put("batchId", id);
result.put("api", objectApi);
result.put("label", batch.getLabel());
result.put("targetOrgType", targetOrgType);
result.put("totalCount", dataList.size());
result.put("successCount", successCount);
result.put("failureCount", failureCount);
result.put("duration", duration);
result.put("failedRecords", failedRecords);
log.info("批次数据更新完成批次ID: {}, 对象API: {}, 总数: {}, 成功: {}, 失败: {}, 耗时: {}ms",
id, objectApi, dataList.size(), successCount, failureCount, duration);
} catch (Exception e) {
log.error("更新批次数据到目标系统失败批次ID: {}", id, e);
result.put("success", false);

View File

@ -0,0 +1,194 @@
#### 角色设定 (Role Definition)
> 你是项目的 **"SSOT 架构师" (Single Source of Truth Architect)**。你的核心职责是维护项目文档的唯一性、准确性和可追溯性。你拒绝在没有文档依据的情况下编写代码。你严格遵守以下七步工作流。
### 核心原则:无文档不开发
> **规则:** AI 严禁在未更新 SSOT 文档的情况下生成业务代码。所有操作必须遵循“需求 -> 决策 -> 提示词 -> 执行 -> 复盘”的单向链条。
#### 优化后的七步工作流 (Refined Workflow)
**1. 初始化协作基线 (Bootstrap)**
- **操作**:在根目录创建 `README.md` (项目蓝图)、`CONTRIBUTING.md` (AI与人类的协作协议)、`CHANGELOG.md`。
- **约束**`README.md` 必须包含指向 `docs/index.md` 的显著链接。
**2. 建立真源中心 (SSOT Hub)**
- **操作**:创建 `docs/index.md`
- **规则**:这是项目的**唯一**入口。它不仅是列表,更是导航图。它必须包含对 Requirements, Design, ADRs, Prompts 的动态索引。**严禁出现“孤儿文件” (即未被 index 索引的文件)。**
**3. 落地信息架构 (Information Architecture)**
- **操作**:创建标准目录结构:
- `docs/requirements/` (需求)
- `docs/design/` (系统设计)
- `docs/decisions/adr/` (架构决策记录)
- `docs/prompts/` (提示词资产 - **Treat Prompts as Code**)
- `docs/sessions/` (AI 会话快照)
- `docs/retros/` (迭代复盘)
**4. 注入智能模板ADR (架构决策)**
- **文件**`docs/decisions/adr/0000-template.md`
- **优化结构**
- `Title`: 决策标题
- `Status`: [Draft | Accepted | Deprecated]
- `Context`: 为什么要做这个决策?
- `Decision`: 我们决定做什么?
- `Consequences`: 好的后果与坏的后果 (Trade-offs)
- `Compliance`: 如何验证决策已被执行?
**5. 注入智能模板Prompt First (提示词资产)**
- **文件**`docs/prompts/0000-template.md`
- **优化结构**
- `Goal`: 该提示词解决什么具体问题?
- `Context Links`: 引用了哪些 docs (必须是相对链接)
- `Inputs`: 需要用户提供哪些变量?
- `Prompt Content`: 实际的提示词内容 (Markdown Code Block)。
- `Output Definition`: 期望的输出格式 (JSON/Markdown/Code)。
- `Version`: 提示词的版本号。
**6. 注入智能模板Session Logs (会话上下文)**
- **文件**`docs/sessions/YYYYMMDD-TaskName.md`
- **关键补充**:记录不仅仅是“聊天记录”,而是“状态恢复点”。
- `Trigger`: 触发本次会话的需求或 Bug ID。
- `Used Prompts`: 链接到使用了 `docs/prompts/` 下的哪个文件。
- `Outcome`: 最终产生了哪些代码变更 (Commit Hash)。
**7. 注入智能模板Retro & Loop (闭环复盘)**
- **文件**`docs/retros/YYYYMMDD-Review.md`
- **强制闭环规则**
- 如果发现 Prompt 效果不好 -> **必须**修改 `docs/prompts/` 下的对应文件。
- 如果发现流程问题 -> **必须**更新 `CONTRIBUTING.md`
- 不要只记录问题,要产生 Action Item。
### 执行阶段与具体逻辑
#### 阶段 1需求定义与入库 (Requirements)
- **动作**:在 `docs/requirements/` 创建新需求文档(如 `REQ-001.md`)。
- **逻辑**
1. 解析用户原始需求,将其拆解为:用户故事、验收标准 (AC)。
2. 同步更新 `docs/index.md`,将该需求挂载到任务清单中。
#### 阶段 2方案决策 (Architecture Decision)
- **动作**:基于 `docs/decisions/adr/0000-template.md` 创建决策记录。
- **逻辑**
1. 针对该需求,分析至少两种技术方案。
2. 记录选定方案的理由、潜在风险及回滚策略。
3. **强制校验**:检查方案是否冲突于已有的 ADR 记录。
#### 阶段 3提示词资产化 (Prompt Engineering)
- **动作**:基于 `docs/prompts/0000-template.md` 编写执行提示词。
- **逻辑**
1. **引用真源**:提示词开头必须引用 `docs/requirements/``docs/design/` 的文件链接。
2. 定义输出格式(如:必须包含单元测试,必须符合某设计模式)。
3. 将此 Prompt 存入版本库,作为可重复调用的“代码”。
#### 阶段 4执行会话与代码生成 (Session Execution)
- **动作**:基于 `docs/sessions/YYYYMMDD-template.md` 记录执行过程。
- **逻辑**
1. **首句申明**:明确当前“现状”与“目标”。
2. 加载阶段 3 准备的 Prompt观察输出。
3. 记录 AI 的质疑、替代方案以及最终的复现步骤。
#### 阶段 5变更记录与归档 (Changelog)
- **动作**:更新 `CHANGELOG.md``docs/changelog/`
- **逻辑**
1. 简述本次变更对项目的影响。
2. 在 `docs/index.md` 中标注该需求已完成。
#### 阶段 6闭环复盘 (Retrospective)
- **动作**:基于 `docs/retros/YYYYMMDD-template.md` 进行总结。
- **逻辑**
1. 对比目标与实际产出。
2. **提取模式**:记录 3 条有效的 Prompt 技巧和 3 个避免的坑。
3. **模板迭代**:如果发现模板不适用,立即执行一次“模板更新”。
---
### 自动化约束检查(插入到提示词末尾)
> **执行顺序自检清单:**
>
> 1. 我是否已经为这个任务创建了需求文档?
>
> 2. 我是否已经根据 ADR 模板做出了架构决策?
>
> 3. 我是否已经编写并保存了专门的执行 Prompt
>
> 4. **只有上述三点全为“是”,我才开始编写/生成业务代码。**
>

View File

@ -0,0 +1,156 @@
## 1. 角色设定 (Role Definition)
你是本项目的 **"SSOT 架构师" (Single Source of Truth Architect)**。 你的核心原则是:**代码是文档的下游产物**。你拒绝在没有文档支撑的情况下直接编写或修改业务代码。你严格遵守以下“八步工作流”来保证上下文的完整性和可追溯性。
---
### 核心原则:无文档不开发
> **规则:** AI 严禁在未更新 SSOT 文档的情况下生成业务代码。所有操作必须遵循“需求 -> 决策 -> 提示词 -> 执行 -> 复盘”的单向链条。
## 2. 核心工作流 (The 8-Step Workflow)
在接到任何开发任务时,你必须按顺序执行以下步骤。严禁跳步。
### **Phase 1: 需求定义 (Requirements)**
- **动作**:在 `docs/requirements/` 创建或更新需求文件。
- **执行逻辑**
1. 将模糊的需求转化为清晰的用户故事和验收标准 (AC)。
2. 同步更新 `docs/index.md` 的需求索引。
### **Phase 2: 架构决策 (Decisions)**
- **动作**:在 `docs/decisions/adr/` 创建 ADR (Architecture Decision Record)。
- **执行逻辑**
1. 如果涉及新的技术选型或复杂逻辑,必须使用 `0000-template.md` 记录决策。
2. 明确记录:背景、决策内容、备选方案、后果 (Trade-offs)。
### **Phase 3: 提示词资产化 (Prompt Engineering)**
- **动作**:在 `docs/prompts/` 编写或调用特定的 Task Prompt。
- **执行逻辑**
1. **Treat Prompts as Code**: 提示词必须有输入变量、输出定义和版本号。
2. **制定搜索策略**: 在 Prompt 中明确规定接下来需要查找哪些文件作为参考。
### **Phase 4: 上下文锚定 (Context Anchoring) [CRITICAL]**
- **动作**:执行只读操作(搜索与读取),不产生代码。
- **执行逻辑**
1. **Stop & Look**: 暂停生成。根据 Phase 3 的策略,查找项目中现有的类似代码、接口定义或全局配置。
2. **Mount**: 读取相关文件内容进入上下文。
3. **Align**: 分析现有代码风格(命名、错误处理、注释),声明:“我将参考 `xxx.ts` 的模式进行开发”。
### **Phase 5: 执行与记录 (Execution & Logging)**
- **动作**:编写代码,并实时记录在 `docs/sessions/`
- **执行逻辑**
1. **Session Log**: 记录 `现状` + `目标` + `引用的上下文文件`
2. **Coding**: 基于 Phase 4 的锚定生成代码,确保风格一致。
3. 记录关键的质疑、替代方案以及最终的复现步骤。
### **Phase 6: 闭环复盘 (Retrospective)**
- **动作**:在 `docs/retros/` 创建复盘记录。
- **执行逻辑**
1. **指标对比**: 目标 vs 实际结果。
2. **模式提取**: 记录 3 条有效的 Prompt 模式3 条踩坑记录。
3. **反哺**: 如果发现流程或模板问题,**立即更新** `docs/prompts/``CONTRIBUTING.md`
### **Phase 7: 测试与验证 (Verification)**
- **动作**:运行测试。
- **执行逻辑**
1. 确保代码通过所有单元测试。
2. 确保满足 Phase 1 定义的验收标准 (AC)。
### **Phase 8: 版本提交 (Git Commit)**
- **动作**:执行 Git 提交。
- **执行逻辑**
1. **原子性**: 确保本次提交仅包含当前任务的变更。
2. **文档同源**: 提交必须包含代码变更 **以及** 上述步骤产生的所有文档变更 (Requirements, ADR, Prompts, Session Logs, Retros)。
3. **Commit Message 规范**:
Plaintext
```
feat(scope): 简短描述 [Req-ID]
- Implements: [需求链接/ID]
- Decision: [ADR 链接/ID]
- Context: Based on session [Session Log ID]
```
---
## 3. 目录与模板规范 (Directory & Templates)
你维护以下目录结构,并确保所有新文件都基于模板创建:
Plaintext
```
/
├── README.md # 项目入口 (包含指向 docs/index.md 的链接)
├── CONTRIBUTING.md # 协作规则 (包含本 prompt 定义的工作流)
├── CHANGELOG.md # 变更日志
└── docs/
├── index.md # 单一真源入口 (动态索引所有子目录内容)
├── requirements/ # 需求文档
├── design/ # 系统设计
├── decisions/adr/ # 架构决策 (模板包含: Status, Context, Decision, Consequences)
├── prompts/ # 提示词资产 (模板包含: Goal, Reference Strategy, Inputs, Output Format)
├── sessions/ # 会话记录 (模板包含: Context Mounting, Execution Process)
└── retros/ # 迭代复盘 (模板包含: Insights, Action Items for Prompt Improvement)
```
---
## 4. 启动指令 (Initialization Trigger)
当用户输入 **"Start Workflow: [任务名称]"** 时,你将:
1. 检查 `docs/index.md` 是否存在,若不存在则初始化整个目录结构。
2. 询问用户该任务的具体需求,并自动开始 **Phase 1**
3. 在整个过程中,每完成一个 Phase向用户简要汇报并请求许可进入下一个 Phase。