[feat] bulk api查询数据

This commit is contained in:
Kris 2025-09-09 11:10:53 +08:00
parent 468aba2515
commit 36c2caad22
21 changed files with 2013 additions and 274 deletions

View File

@ -0,0 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="data-dump城博测试" type="docker-deploy" factoryName="dockerfile" server-name="城博测试Docekr">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="data-dump" />
<option name="buildCliOptions" value="--platform=linux/amd64" />
<option name="containerName" value="data-dump" />
<option name="commandLineOptions" value="-p 90:80 -v /usr/local/data-dump/file:/data-dump/file -v /usr/local/data-dump/logs:/data-dump/logs --restart=always --network=host" />
<option name="sourceFilePath" value="Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="data-dump康蒂思" type="docker-deploy" factoryName="dockerfile" server-name="康蒂思docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="data-dump" />
<option name="buildCliOptions" value="--platform=linux/amd64" />
<option name="containerName" value="data-dump" />
<option name="commandLineOptions" value="-p 90:80 -v /usr/local/data-dump/file:/data-dump/file -v /usr/local/data-dump/logs:/data-dump/logs --restart=always --network=host" />
<option name="sourceFilePath" value="Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1 @@
<result-list xmlns="http://www.force.com/2009/06/asyncapi/dataload"><result>752e2000002qkjy</result></result-list>
Can't render this file because it contains an unexpected character in line 1 and column 20.

View File

@ -36,6 +36,13 @@
</properties>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>

View File

@ -48,6 +48,8 @@ public class JobController {
private DataImportBatchService dataImportBatchService;
@Autowired
private DataImportNewService dataImportNewService;
@Autowired
private CommonBatchService commonBatchService;
@PostMapping("/fileTransform")
@ApiOperation("附件解析")
@ -118,15 +120,20 @@ public class JobController {
/**
* 创建表结构
*
* @param param 参数
* @return result
*/
@PostMapping("/createApi")
@ApiOperation("创建表结构")
public Result createApi(@RequestBody SalesforceParam param) throws Exception {
if (param.getType() == null) {
return Result.fail("参数缺失!");
public Result createApi(String paramStr) throws Exception {
log.info("dataImportBatchJob execute start ..................");
SalesforceParam param = new SalesforceParam();
if (StringUtils.isNotBlank(paramStr)) {
param = JSON.parseObject(paramStr, SalesforceParam.class);
}
param.setType(1);
// 参数转换
param.setBeginCreateDate(param.getBeginDate());
param.setEndCreateDate(param.getEndDate());
ReturnT<String> returnT = commonService.createApi(param);
if (returnT.getCode() == ReturnT.SUCCESS_CODE) {
return Result.success();
@ -224,6 +231,33 @@ public class JobController {
return dataImportBatchService.immigrationBatch(param);
}
/**
* bulk批量大数据生成newSFID
* @param paramStr
* @author kris
* @return
* @throws Exception
*/
@PostMapping("/dataDumpManualBatchJob")
@ApiOperation("存量任务(大数据量)")
@LogServiceAnnotation(operateType = OperateTypeConstant.TYPE_INSERT, remark = "存量任务(大数据量)")
public ReturnT<String> dataDumpManualBatchJob(String paramStr) throws Exception {
log.info("dataImportBatchJob execute start ..................");
SalesforceParam param = new SalesforceParam();
try {
if (StringUtils.isNotBlank(paramStr)) {
param = JSON.parseObject(paramStr, SalesforceParam.class);
}
} catch (Throwable throwable) {
return new ReturnT<>(500, "参数解析失败!");
}
param.setType(1);
// 参数转换
param.setBeginCreateDate(param.getBeginDate());
param.setEndCreateDate(param.getEndDate());
return commonBatchService.dumpBatch(param);
}
/**
* bulk批量更新大数据量数据
* @param paramStr

View File

@ -91,7 +91,7 @@ public class DataDumpNewJob {
}
/**
* 存量任务
* 存量任务数据
*
* @param paramStr 参数json
* @return result

View File

@ -57,6 +57,47 @@ public interface CustomMapper {
*/
public void createTable(@Param("tableName") String tableName, @Param("tableComment") String tableComment, @Param("maps") List<Map<String, Object>> maps, @Param("index") List<Map<String, Object>> index);
/**
* 创建RANGE分区表
*
* @param tableName 表名
* @param tableComment 表注释
* @param maps 字段定义列表
* @param index 索引定义列表
* @param partitionKey 分区键
* @param partitions 分区定义列表
*/
void createRangePartitionTable(@Param("tableName") String tableName,
@Param("tableComment") String tableComment,
@Param("maps") List<Map<String, Object>> maps,
@Param("index") List<Map<String, Object>> index,
@Param("partitionKey") String partitionKey,
@Param("partitions") List<Map<String, Object>> partitions);
/**
* 在指定分区插入单条记录
*
* @param tableName 表名
* @param partitionName 分区名
* @param maps 插入字段映射
*/
void saveToPartition(@Param("tableName") String tableName,
@Param("partitionName") String partitionName,
@Param("maps") List<Map<String, Object>> maps);
/**
* 在指定分区根据ID更新记录
*
* @param tableName 表名
* @param partitionName 分区名
* @param maps 更新字段映射
* @param id 记录ID
*/
void updateToPartition(@Param("tableName") String tableName,
@Param("partitionName") String partitionName,
@Param("maps") List<Map<String, Object>> maps,
@Param("id") String id);
/**
* 创建字段
* @param tableName

View File

@ -27,6 +27,8 @@ public interface CommonService {
void checkApi(String apiName, Boolean createBatch) throws Exception;
void checkPartitionedApi(String apiName, Boolean createBatch) throws Exception;
/**
* 创建表
* @param param 参数

View File

@ -1,45 +1,62 @@
package com.celnet.datadump.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.celnet.datadump.config.SalesforceConnect;
import com.celnet.datadump.config.SalesforceExecutor;
import com.celnet.datadump.entity.DataBatch;
import com.celnet.datadump.entity.DataBatchHistory;
import com.celnet.datadump.entity.DataObject;
import com.celnet.datadump.entity.DataReport;
import com.celnet.datadump.entity.*;
import com.celnet.datadump.global.Const;
import com.celnet.datadump.mapper.CustomMapper;
import com.celnet.datadump.param.DataDumpParam;
import com.celnet.datadump.param.DataDumpSpecialParam;
import com.celnet.datadump.param.SalesforceParam;
import com.celnet.datadump.service.*;
import com.celnet.datadump.util.BulkUtil;
import com.celnet.datadump.util.DataUtil;
import com.celnet.datadump.util.EmailUtil;
import com.celnet.datadump.util.SqlUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sforce.async.BulkConnection;
import com.opencsv.CSVReaderHeaderAware;
import com.opencsv.exceptions.CsvValidationException;
import com.sforce.async.*;
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.parser.XmlInputStream;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.log.XxlJobLogger;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.Header;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.apache.commons.lang.CharEncoding.UTF_8;
@Service
@Slf4j
public class CommonBatchServiceImpl implements CommonBatchService {
@ -53,185 +70,617 @@ public class CommonBatchServiceImpl implements CommonBatchService {
@Autowired
private CommonService commonService;
@Autowired
private DataFieldService dataFieldService;
@Autowired
private SalesforceConnect salesforceConnect;
@Autowired
private DataFieldService dataFieldService;
private CustomMapper customMapper;
@Autowired
private DataLogService dataLogService;
@Autowired
private DataDumpSpecialService dataDumpSpecialService;
@Autowired
private DataBatchHistoryService dataBatchHistoryService;
@Override
public ReturnT<String> dumpBatch(SalesforceParam param) throws Exception {
// List<Future<?>> futures = Lists.newArrayList();
// try {
// if (StringUtils.isNotBlank(param.getApi())) {
// // 手动任务
// ReturnT<String> result = manualBatchDump(param, futures);
// if (result != null) {
// return result;
// }
// } else {
// // 自动任务
// autoDump(param, futures);
// }
// return ReturnT.SUCCESS;
// } catch (Throwable throwable) {
// salesforceExecutor.remove(futures.toArray(new Future<?>[]{}));
// log.error("dump error", throwable);
// throw throwable;
// }
List<Future<?>> futures = Lists.newArrayList();
try {
if (StringUtils.isNotBlank(param.getApi())) {
// 手动任务
ReturnT<String> result = manualBatchDump(param, futures);
if (result != null) {
return result;
}
}
return ReturnT.SUCCESS;
} catch (Throwable throwable) {
salesforceExecutor.remove(futures.toArray(new Future<?>[]{}));
log.error("dump error", throwable);
throw throwable;
}
}
private ReturnT<String> manualBatchDump(SalesforceParam param, List<Future<?>> futures) throws InterruptedException {
List<String> apis;
apis = DataUtil.toIdList(param.getApi());
String join = StringUtils.join(apis, ",");
log.info("dump apis: {}", join);
XxlJobLogger.log("dump apis: {}", join);
// 根据参数获取sql
for (String api : apis) {
DataObject update = new DataObject();
TimeUnit.MILLISECONDS.sleep(1);
try {
commonService.checkApi(api, true);
if (!dataFieldService.hasCreatedDate(api)) {
DataDumpSpecialParam dataDumpSpecialParam = new DataDumpSpecialParam();
dataDumpSpecialParam.setApi(api);
Future<?> future = dataDumpSpecialService.getData(dataDumpSpecialParam, salesforceConnect.createConnect());
// 等待当前所有线程执行完成
salesforceExecutor.waitForFutures(future);
continue;
}
param.setApi(api);
List<SalesforceParam> salesforceParams = null;
update.setName(api);
update.setDataLock(1);
dataObjectService.updateById(update);
QueryWrapper<DataBatch> dbQw = new QueryWrapper<>();
dbQw.eq("name", api)
.isNull("first_sync_date");
if (param.getBeginDate() != null && param.getEndDate() != null) {
dbQw.eq("sync_start_date", param.getBeginDate());
dbQw.eq("sync_end_date", param.getEndDate());
}
List<DataBatch> list = dataBatchService.list(dbQw);
AtomicInteger batch = new AtomicInteger(1);
if (CollectionUtils.isNotEmpty(list)) {
salesforceParams = list.stream().map(t -> {
SalesforceParam salesforceParam = param.clone();
salesforceParam.setApi(t.getName());
salesforceParam.setBeginCreateDate(t.getSyncStartDate());
salesforceParam.setEndCreateDate(t.getSyncEndDate());
salesforceParam.setBatch(batch.getAndIncrement());
return salesforceParam;
}).collect(Collectors.toList());
} else {
salesforceParams = DataUtil.splitTask(param);
}
// 手动任务优先执行
for (SalesforceParam salesforceParam : salesforceParams) {
Future<?> future = salesforceExecutor.execute(() -> {
try {
dumpBatchData(salesforceParam);
} catch (Throwable throwable) {
log.error("salesforceExecutor error", throwable);
throw new RuntimeException(throwable);
}
}, salesforceParam.getBatch(), 1);
futures.add(future);
}
// 等待当前所有线程执行完成
salesforceExecutor.waitForFutures(futures.toArray(new Future<?>[]{}));
update.setDataWork(0);
} catch (Throwable e) {
log.error("manualDump error", e);
throw new RuntimeException(e);
} finally {
update.setName(api);
update.setDataLock(0);
dataObjectService.updateById(update);
}
}
return ReturnT.SUCCESS;
}
// private ReturnT<String> manualBatchDump(SalesforceParam param, List<Future<?>> futures) throws InterruptedException {
// List<String> apis = DataUtil.toIdList(param.getApi());
// String join = StringUtils.join(apis, ",");
// log.info("dump apis: {}", join);
//
// // 根据参数获取sql
// for (String api : apis) {
// DataObject update = new DataObject();
// TimeUnit.MILLISECONDS.sleep(1);
// try {
// // 判断当前表是否存在
// commonService.checkApi(api, true);
//
//// if (!hasCreatedDate(api)) {
//// DataDumpSpecialParam dataDumpSpecialParam = new DataDumpSpecialParam();
//// dataDumpSpecialParam.setApi(api);
//// Future<?> future = dataDumpSpecialService.getData(dataDumpSpecialParam, salesforceConnect.createConnect());
//// // 等待当前所有线程执行完成
//// salesforceExecutor.waitForFutures(future);
//// continue;
//// }
//
// List<SalesforceParam> salesforceParams = null;
// update.setName(api);
// update.setDataLock(1);
// dataObjectService.updateById(update);
// QueryWrapper<DataBatch> dbQw = new QueryWrapper<>();
// dbQw.eq("name", api)
// .isNull("first_sync_date");
//
// List<DataBatch> list = dataBatchService.list(dbQw);
// AtomicInteger batch = new AtomicInteger(1);
// if (CollectionUtils.isNotEmpty(list)) {
// salesforceParams = list.stream().map(t -> {
// SalesforceParam salesforceParam = param.clone();
// salesforceParam.setApi(t.getName());
// salesforceParam.setBeginCreateDate(t.getSyncStartDate());
// salesforceParam.setEndCreateDate(t.getSyncEndDate());
// salesforceParam.setBatch(batch.getAndIncrement());
// return salesforceParam;
// }).collect(Collectors.toList());
// } else {
// salesforceParams = DataUtil.splitTask(param);
// }
// // 手动任务优先执行
// for (SalesforceParam salesforceParam : salesforceParams) {
// Future<?> future = salesforceExecutor.execute(() -> {
// try {
// dumpBatchData(salesforceParam);
// } catch (Throwable throwable) {
// log.error("salesforceExecutor error", throwable);
// throw new RuntimeException(throwable);
// }
// }, salesforceParam.getBatch(), 1);
// futures.add(future);
// }
//
// // 等待当前所有线程执行完成
// salesforceExecutor.waitForFutures(futures.toArray(new Future<?>[]{}));
//
// update.setDataWork(0);
// } catch (Throwable e) {
// log.error("manualDump error", e);
// throw new RuntimeException(e);
// } finally {
// update.setName(api);
// update.setDataLock(0);
// dataObjectService.updateById(update);
// }
// }
// return null;
// }
//
//
// private void dumpBatchData(SalesforceParam param) throws Throwable {
// String api = param.getApi();
// BulkConnection bulkConnect = null;
private BulkConnection dumpBatchData(SalesforceParam param) throws Throwable {
String api = param.getApi();
BulkConnection bulkConnect = null;
try {
DataBatchHistory dataBatchHistory = new DataBatchHistory();
dataBatchHistory.setName(api);
dataBatchHistory.setStartDate(new Date());
dataBatchHistory.setSyncStartDate(param.getBeginCreateDate());
if (param.getEndCreateDate() != null) {
dataBatchHistory.setSyncEndDate(DateUtils.addSeconds(param.getEndCreateDate(), -1));
}
dataBatchHistory.setBatch(param.getBatch());
if (param.getBeginCreateDate() != null && param.getEndCreateDate() != null) {
log.info("NO.{} dump {}, date: {} ~ {} start", param.getBatch(),
param.getApi(),
DateFormatUtils.format(param.getBeginCreateDate(), "yyyy-MM-dd HH:mm:ss"),
DateFormatUtils.format(param.getEndCreateDate(), "yyyy-MM-dd HH:mm:ss"));
}
bulkConnect = salesforceConnect.createBulkConnect();
// 存在isDeleted 只查询IsDeleted为false的
if (dataFieldService.hasDeleted(param.getApi())) {
param.setIsDeleted(false);
} else {
// 不存在 过滤
param.setIsDeleted(null);
}
int sfData = getAllBulkV1SfData(param, bulkConnect);
dataBatchHistory.setDbNum(sfData);
dataBatchHistory.setSfNum(sfData);
updateDataBatch(param, dataBatchHistory);
}catch (InterruptedException e){
return bulkConnect;
} catch (Throwable throwable) {
log.error("dataDumpJob error api:{}", api, throwable);
String type = param.getType() == 1 ? "存量" : "增量";
String format = String.format("%s数据迁移 error, api name: %s, \nparam: %s, \ncause:\n%s", type, api, JSON.toJSONString(param, DataDumpParam.getFilter()), throwable);
EmailUtil.send("DataDump ERROR", format);
throw throwable;
}
return bulkConnect;
}
private int getAllBulkV1SfData(SalesforceParam param, BulkConnection bulkConnect) throws Throwable {
boolean hasMore = true;
QueryWrapper<DataField> wrapper = new QueryWrapper<>();
String api = param.getApi();
wrapper.eq("api", api);
List<DataField> list = dataFieldService.list(wrapper);
Map<String, Object> map = Maps.newHashMap();
String dateName = param.getType() == 1 ? Const.CREATED_DATE : param.getUpdateField();
int count = 0;
Date lastCreatedDate = null;
String maxId = null;
List<String> fields = Lists.newArrayList();
for (DataField field : list) {
if (!"base64".equalsIgnoreCase(field.getSfType()) && field.getSfType() != null){
fields.add(field.getField());
}
}
DataObject dataObject = dataObjectService.getById(api);
if (dataObject != null && StringUtils.isNotBlank(dataObject.getExtraField())) {
fields.addAll(Arrays.asList(StringUtils.split(dataObject.getExtraField().replaceAll(StringUtils.SPACE, StringUtils.EMPTY), ",")));
}
param.setSelect(StringUtils.join(fields, ","));
log.info("开始处理API: {}, 类型: {}", api, param.getType() == 1 ? "按创建时间" : "按修改时间");
while (hasMore) {
int sfData = 0;
param.setMaxId(maxId);
param.setLimit(10000);
// 获取创建时间
param.setTimestamp(lastCreatedDate);
map.put("param", param);
String sql;
JobInfo job = null;
try {
if (param.getType() == 1) {
// type 1 按创建时间纬度
sql = SqlUtil.showSql("com.celnet.datadump.mapper.SalesforceMapper.list", map);
} else {
// type 2 按修改时间纬度
sql = SqlUtil.showSql("com.celnet.datadump.mapper.SalesforceMapper.listByModifyTime", map);
}
log.info("query sql: {}", sql);
XxlJobLogger.log("query sql: {}", sql);
job = BulkUtil.createJob(bulkConnect, api, OperationEnum.queryAll);
log.info("创建Bulk作业成功, 作业ID: {}", job.getId());
BatchInfo batchInfo = bulkConnect.createBatchFromStream(job, new ByteArrayInputStream(sql.getBytes(StandardCharsets.UTF_8)));
log.info("创建批次成功, 批次ID: {}", batchInfo.getId());
int completionOne = BulkUtil.awaitCompletionOne(bulkConnect, job, batchInfo);
log.info("批次处理完成, 处理记录数: {}", completionOne);
if (completionOne == 0){
log.info("无更多数据, 结束处理");
BulkUtil.closeJob(bulkConnect, job.getId());
break;
}
List<Map<String, Object>> batchRecords = new ArrayList<>();
QueryResultList queryResultList = bulkConnect.getQueryResultList(job.getId(), batchInfo.getId());
log.info("获取查询结果列表, 结果数量: {}", queryResultList.getResult().length);
for (final String resultId : queryResultList.getResult()) {
InputStream resultStream = bulkConnect.getQueryResultStream(job.getId(), batchInfo.getId(), resultId);
CSVReader rdr = new CSVReader(resultStream);
List<String> headers = rdr.nextRecord();
log.debug("处理结果ID: {}, 表头数量: {}", resultId, headers.size());
// 批量处理记录
List<String> record;
while ((record = rdr.nextRecord()) != null) {
Map<String, Object> recordMap = new HashMap<>();
for (int i = 0; i < headers.size() && i < record.size(); i++) {
recordMap.put(headers.get(i), record.get(i));
}
batchRecords.add(recordMap);
}
}
if (!batchRecords.isEmpty()){
log.info("开始保存或更新数据, 记录数: {}", batchRecords.size());
sfData = saveOrUpdate(api, batchRecords, true);
count += sfData;
maxId = batchRecords.get(batchRecords.size() - 1).get("Id").toString();
lastCreatedDate = DateUtils.addHours(DateUtils.parseDate((String) batchRecords.get(batchRecords.size() - 1).get("CreatedDate"), Const.SF_DATE_FORMAT), 8);
log.info("处理结果: api:{}, sf数量:{}, 当前批次最大Id:{}, 当前批次最后创建时间:{}", api, sfData, maxId, lastCreatedDate);
} else {
log.info("批次无数据记录");
}
BulkUtil.closeJob(bulkConnect, job.getId());
log.info("关闭作业成功, 作业ID: {}", job.getId());
}catch (InterruptedException interruptedException){
return 0;
} catch (Exception e) {
log.error("处理查询结果时异常: ", e);
throw new AsyncApiException("处理查询结果时异常: " + e.getMessage(), AsyncExceptionCode.Unknown);
}finally {
if (sfData != 10000){
hasMore = false;
log.info("当前批次数据不足10000条, 结束循环");
}
}
}
log.info("API: {} 数据处理完成, 总处理记录数: {}", api, count);
return count;
}
/**
* 处理查询结果
*
* @throws AsyncApiException 异步API异常
*/
// private int processQueryV1Results(BulkConnection connection, JobInfo job, String api) throws AsyncApiException {
// int totalProcessedRecords = 0;
// int batch = 0;
// try {
// DataBatchHistory dataBatchHistory = new DataBatchHistory();
// dataBatchHistory.setName(api);
// dataBatchHistory.setStartDate(new Date());
// dataBatchHistory.setSyncStartDate(param.getBeginCreateDate());
// if (param.getEndCreateDate() != null) {
// dataBatchHistory.setSyncEndDate(DateUtils.addSeconds(param.getEndCreateDate(), -1));
// }
// dataBatchHistory.setBatch(param.getBatch());
// if (param.getBeginCreateDate() != null && param.getEndCreateDate() != null) {
// log.info("NO.{} dump {}, date: {} ~ {} start", param.getBatch(),
// param.getApi(),
// DateFormatUtils.format(param.getBeginCreateDate(), "yyyy-MM-dd HH:mm:ss"),
// DateFormatUtils.format(param.getEndCreateDate(), "yyyy-MM-dd HH:mm:ss"));
// }
// bulkConnect = salesforceConnect.createBulkConnect();
// // 存在isDeleted 只查询IsDeleted为false的
// if (dataFieldService.hasDeleted(param.getApi())) {
// param.setIsDeleted(false);
// } else {
// // 不存在 过滤
// param.setIsDeleted(null);
// }
// // 若count数量过多 可能导致超时出不来结果 对该任务做进一步拆分
// int failCount = 0;
// boolean isSuccess = false;
// while (failCount <= Const.MAX_FAIL_COUNT) {
// try {
// // 不能 count Id
// if (!"FeedItem".equals(api) && !"ContentFolderMember ".equals(api)){
// dataBatchHistory.setSfNum(commonService.countSfNum(connect, param));
// }
// isSuccess = true;
// break;
// } catch (Throwable throwable) {
// failCount++;
// }
// }
// // 不成功 做任务拆分
// if (!isSuccess) {
// if (splitTask(param)) {
// return connect;
// }
// }
// getAllSfData(param, connect, dataReport);
// updateDataBatch(param, dataBatchHistory);
// } catch (Throwable throwable) {
// log.error("dataDumpJob error api:{}", api, throwable);
// String type = param.getType() == 1 ? "存量" : "增量";
// String format = String.format("%s数据迁移 error, api name: %s, \nparam: %s, \ncause:\n%s", type, api, JSON.toJSONString(param, DataDumpParam.getFilter()), throwable);
// EmailUtil.send("DataDump ERROR", format);
// throw throwable;
// }
// return connect;
// }
// BatchInfoList batchInfoList = connection.getBatchInfoList(job.getId());
// BatchInfo[] batchInfos = batchInfoList.getBatchInfo();
//
// /**
// * 统计salesforce数据量
// *
// * @param connect connect
// * @param param 参数
// * @return sf统计数量
// */
// public Integer countSfNum(PartnerConnection connect, SalesforceParam param) throws Exception {
// Map<String, Object> map = Maps.newHashMap();
// map.put("param", param);
// String sql = SqlUtil.showSql("com.celnet.datadump.mapper.SalesforceMapper.count", map);
// log.info("count sql: {}", sql);
// XxlJobLogger.log("count sql: {}", sql);
// QueryResult queryResult = connect.queryAll(sql);
// SObject record = queryResult.getRecords()[0];
// Integer num = (Integer) record.getField("num");
// log.info("count sf num: {}", num);
// XxlJobLogger.log("count sf num: {}", num);
// return num;
// for (BatchInfo batchInfo : batchInfos) {
// if (batchInfo.getState() == BatchStateEnum.Completed) {
//
// InputStream resultStream = connection.getBatchResultStream(job.getId(), batchInfo.getId());
//
// // 获取批次结果
// CSVReader rdr = new CSVReader(resultStream);
// List<String> headers = rdr.nextRecord();
//
// // 批量处理记录
// List<Map<String, String>> batchRecords = new ArrayList<>();
// List<String> record;
// while ((record = rdr.nextRecord()) != null) {
// Map<String, String> recordMap = new HashMap<>();
// for (int i = 0; i < headers.size() && i < record.size(); i++) {
// recordMap.put(headers.get(i), record.get(i));
// }
// batchRecords.add(recordMap);
// }
// DataLog dataLog = new DataLog(api, null, new Date(), null, "Bulk数据拉取拉取第" + batch + "批数据", "InsertDB");
//// totalProcessedRecords += processBatchRecords(job.getObject(), batchRecords);
// dataLog.setEndTime(new Date());
// dataLogService.save(dataLog);
//
// } else {
// log.error("批次 {} 的批量 {} 状态异常: {}", job.getId(), batchInfo.getId(), batchInfo.getState());
// }
// }
// } catch (Exception e) {
// throw new AsyncApiException("处理查询结果时异常: " + e.getMessage(), AsyncExceptionCode.Unknown);
// }
// return totalProcessedRecords;
// }
private int getAllSfData(SalesforceParam param, BulkConnection bulkConnect) throws Throwable {
String v2JobId = null;
try {
String api = param.getApi();
Map<String, Object> map = Maps.newHashMap();
// 获取对象字段信息
PartnerConnection partnerConnection = salesforceConnect.createConnect();
DescribeSObjectResult dsr = partnerConnection.describeSObject(api);
Field[] dsrFields = dsr.getFields();
List<String> fields = Lists.newArrayList();
for (Field field : dsrFields) {
// 不查询文件
if ("base64".equalsIgnoreCase(field.getType().toString())) {
continue;
}
fields.add(field.getName());
}
DataObject dataObject = dataObjectService.getById(api);
if (dataObject != null && StringUtils.isNotBlank(dataObject.getExtraField())) {
fields.addAll(Arrays.asList(StringUtils.split(dataObject.getExtraField().replaceAll(StringUtils.SPACE, StringUtils.EMPTY), ",")));
}
param.setSelect(StringUtils.join(fields, ","));
map.put("param", param);
String sql;
if (param.getType() == 1) {
// type 1 按创建时间纬度
sql = SqlUtil.showSql("com.celnet.datadump.mapper.SalesforceMapper.list", map);
} else {
// type 2 按修改时间纬度
sql = SqlUtil.showSql("com.celnet.datadump.mapper.SalesforceMapper.listByModifyTime", map);
}
log.info("query sql: {}", sql);
XxlJobLogger.log("query sql: {}", sql);
// job = createBulkJob(bulkConnect, api, sql);
// 创建Bulk API作业
// job = BulkUtil.createQueryJob1(api,sql,bulkConnect,job);
//
// List<BatchInfo> batchInfoList = new ArrayList<>();
//
// BatchInfo batch = bulkConnect.createBatchFromStream(job, new ByteArrayInputStream(sql.getBytes(StandardCharsets.UTF_8)));
//
// batchInfoList.add( batch);
String restEndpoint = bulkConnect.getConfig().getRestEndpoint();
String url = restEndpoint.substring(0, restEndpoint.indexOf("services/"));
v2JobId = BulkUtil.createBulkV2Job(url,sql, bulkConnect);
BulkUtil.waitForBulkV2JobCompletion(url,v2JobId, bulkConnect);
// 处理查询结果
return processQueryResults(url,bulkConnect, v2JobId,null, api);
}catch (Exception e){
log.error("getAllSfData error", e);
}
return 0;
}
private JobInfo createBulkJob(BulkConnection connection, String objectName, String query) throws AsyncApiException {
JobInfo job = new JobInfo();
job.setObject(objectName);
job.setOperation(OperationEnum.queryAll);
job.setContentType(ContentType.CSV);
job = connection.createJob(job);
// 关闭作业
job.setState(JobStateEnum.Closed);
return job;
}
/**
* 处理查询结果
*
* @param connection Bulk连接
* @throws AsyncApiException 异步API异常
*/
private int processQueryResults(String url,BulkConnection connection, String jobId ,String locator,String api) throws AsyncApiException {
int totalProcessedRecords = 0;
try {
String newLocator = null;
int numberOfRecords = 0;
boolean hasMoreRecords = true;
while (hasMoreRecords) {
CloseableHttpClient httpClient = HttpClients.createDefault();
String resultsUrl = url + "services/data/v56.0/jobs/query/" + jobId + "/results";
if (locator != null && !locator.isEmpty()) {
resultsUrl += "?locator=" + java.net.URLEncoder.encode(locator, "UTF-8");
}
HttpGet httpGet = new HttpGet(resultsUrl);
httpGet.setHeader("Authorization", "Bearer " + connection.getConfig().getSessionId());
httpGet.setHeader("Content-Type", "application/json");
httpGet.setHeader("Accept", "text/csv");
httpGet.setHeader("X-PrettyPrint", "1");
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
Header locatorHeader = response.getFirstHeader("Sforce-Locator");
Header numRecordsHeader = response.getFirstHeader("Sforce-NumberOfRecords");
newLocator = (locatorHeader != null) ? locatorHeader.getValue() : null;
numberOfRecords = (numRecordsHeader != null) ? Integer.parseInt(numRecordsHeader.getValue()) : 0;
totalProcessedRecords += numberOfRecords;
String csvResponse = EntityUtils.toString(response.getEntity());
// writeExtractionForServerStream(csvResponse,api);
// 检查是否还有更多记录
if (numberOfRecords == 0 || newLocator == null || newLocator.isEmpty()) {
hasMoreRecords = false;
} else {
locator = newLocator; // 设置下一次迭代的locator
}
} else {
String errorBody = EntityUtils.toString(response.getEntity());
throw new RuntimeException("Failed to get query job results, status code: " + statusCode + ", error: " + errorBody);
}
} finally {
response.close();
httpClient.close();
}
}
}catch (Exception e){
log.error("processQueryResults error", e);
}
return totalProcessedRecords;
}
// public int writeExtractionForServerStream(String csvData,String api) throws IOException {
//
//// int totalProcessedRecords = 0;
//// try {
//// CSVReader rdr = new CSVReader(new StringReader(csvData));
//// rdr.setMaxCharsInFile(Integer.MAX_VALUE);
//// rdr.setMaxRowsInFile(Integer.MAX_VALUE);
//// List<String> record ;
//// List<Map<String, String>> batchRecords = new ArrayList<>();
//// while ((record = rdr.nextRecord()) != null) {
//// Map<String, String> recordMap = new HashMap<>();
//// for (int i = 0; i < record.size(); i++) {
//// recordMap.put(record.get(i), record.get(i));
//// }
//// batchRecords.add(recordMap);
//// }
//
//// totalProcessedRecords += processBatchRecords(api, batchRecords);
//
//// }catch (Exception e){
//// throw new IOException("处理结果时异常: " + e.getMessage());
//// }
//// return totalProcessedRecords;
// }
/**
* 插入或更新数据
*
* @param api 表名
* @param insert 不存在是否插入
* @return 处理的记录数
*/
public Integer saveOrUpdate(String api,List<Map<String, Object>> batchRecords, boolean insert) throws Throwable {
// 根据id查询数据库 取出已存在数据的id
List<String> ids = batchRecords.stream()
.map(record -> record.get("Id").toString())
.filter(Objects::nonNull)
.collect(Collectors.toList());
DataObject one = dataObjectService.getById(api);
QueryWrapper<DataField> dbQw = new QueryWrapper<>();
dbQw.eq("api", api);
List<DataField> listed = dataFieldService.list(dbQw);
List<String> existsIds = customMapper.getIds(api, ids);
int processedCount = 0;
for ( Map<String, Object> jsonObject : batchRecords) {
try {
// update
String id = jsonObject.get(Const.ID).toString();
List<Map<String, Object>> maps = Lists.newArrayList();
for (DataField field : listed) {
if (StringUtils.isNotEmpty(field.getSfType())) {
// ContentVersion表插入更新时把title中的/替换为-
if ("ContentVersion".equals(one.getName()) && "Title".equals(field.getField())){
String title = StringUtils.replace(String.valueOf(jsonObject.get("Title")), "/", "-");
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", "Title");
paramMap.put("value", title);
maps.add(paramMap);
continue;
}
// Document,Attachment插入更新时把Name中的/替换为-
if (("Document".equals(one.getName()) || "Attachment".equals(one.getName())) && "Name".equals(field.getField())){
String name = StringUtils.replace(String.valueOf(jsonObject.get("Name")), "/", "-");
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", "Name");
paramMap.put("value", name);
maps.add(paramMap);
continue;
}
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", field.getField());
paramMap.put("value",DataUtil.convertMysqlValue(field.getSfType(),jsonObject.get(field.getField())));
maps.add(paramMap);
}
}
// 附件表 插入更新时把is_dump置为false
if (StringUtils.isNotBlank(one.getBlobField())) {
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", "is_dump");
paramMap.put("value", false);
Map<String, Object> paramMap2 = Maps.newHashMap();
paramMap2.put("key", "is_upload");
paramMap2.put("value", false);
maps.add(paramMap);
maps.add(paramMap2);
}
Map<String, Object> paramMap3 = Maps.newHashMap();
paramMap3.put("key", "is_update");
paramMap3.put("value", 0);
maps.add(paramMap3);
if (existsIds.contains(id)) {
customMapper.updateById(api, maps, id);
} else {
if (insert) {
customMapper.save(api, maps);
}
}
processedCount++;
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
throw e;
} catch (Throwable throwable) {
if (throwable.toString().contains("interrupt")) {
log.error("may interrupt error:", throwable);
throw new InterruptedException();
}
throwable.addSuppressed(new Exception("saveOrUpdate error data:" + JSON.toJSONString(jsonObject)));
throw throwable;
}
}
return processedCount;
}
/**
* 批次存在 且首次时间为0或者为空 赋值保存执行记录
*
* @param param 参数
* @param dataBatchHistory 执行记录
*/
private void updateDataBatch(SalesforceParam param, DataBatchHistory dataBatchHistory) {
if (dataBatchHistory == null) {
return;
}
dataBatchHistory.setEndDate(new Date());
Integer num = customMapper.count(param);
dataBatchHistory.setDbNum(num);
if (dataBatchHistory.getSfNum() != null) {
dataBatchHistory.setSyncStatus(dataBatchHistory.getSfNum().equals(dataBatchHistory.getDbNum()) ? 1 : 0);
}
dataBatchHistory.setCost(DataUtil.calTime(dataBatchHistory.getEndDate(), dataBatchHistory.getStartDate()));
QueryWrapper<DataBatch> qw = new QueryWrapper<>();
qw.eq("name", param.getApi())
.eq("sync_start_date", param.getBeginCreateDate())
.eq("sync_end_date", param.getEndCreateDate())
.isNull("first_sync_date");
DataBatch dataBatch = dataBatchService.getOne(qw);
if (dataBatch == null) {
return;
}
dataBatch.setFirstSfNum(dataBatchHistory.getSfNum());
dataBatch.setFirstDbNum(dataBatchHistory.getDbNum());
dataBatch.setFirstSyncDate(dataBatchHistory.getStartDate());
dataBatch.setSyncStatus(dataBatchHistory.getSyncStatus());
dataBatchService.update(dataBatch, qw);
log.info("count db num: {}", num);
XxlJobLogger.log("count db num: {}", num);
dataBatchHistoryService.save(dataBatchHistory);
log.info("dataDumpJob done api:{}, count:{}", dataBatchHistory.getName(), num);
}
}

View File

@ -721,7 +721,10 @@ public class CommonServiceImpl implements CommonService {
objects = DataUtil.toJsonArray(records, dsrFields);
// 获取最大修改时间和等于该修改时间的数据id
{
// 获取当前批次最后一条记录的时间戳用于下一批次的查询条件
Date maxDate = objects.getJSONObject(objects.size() - 1).getDate(dateName);
// 在当前批次中找出所有具有相同时间戳的记录并获取其中ID最大的那条记录的ID
// 这个ID将作为下一批次查询的起始位置避免重复处理相同时间戳的数据
maxId = objects.stream()
.map(t -> (JSONObject) t)
.filter(t -> maxDate.equals(t.getDate(dateName)))
@ -742,6 +745,7 @@ public class CommonServiceImpl implements CommonService {
XxlJobLogger.log("dump success count: {}, timestamp: {}", count, format);
failCount = 0;
objects = null;
batch ++;
} catch (InterruptedException e) {
throw e;
} catch (Throwable throwable) {
@ -870,7 +874,7 @@ public class CommonServiceImpl implements CommonService {
DataObject one = dataObjectService.getById(api);
QueryWrapper<DataField> dbQw = new QueryWrapper<>();
dbQw.eq("api", api);
List<DataField> listed = dataFieldService.list(dbQw);
List<String> list = dataFieldService.list(dbQw).stream().map(DataField::getField).collect(Collectors.toList());
List<String> existsIds = customMapper.getIds(api, ids);
for (int i = 0; i < objects.size(); i++) {
JSONObject jsonObject = objects.getJSONObject(i);
@ -878,10 +882,10 @@ public class CommonServiceImpl implements CommonService {
// update
String id = jsonObject.getString(Const.ID);
List<Map<String, Object>> maps = Lists.newArrayList();
for (DataField field : listed) {
if (StringUtils.isNotEmpty(field.getSfType())) {
for (String key : list) {
if (fields.stream().anyMatch(key::equalsIgnoreCase)) {
// ContentVersion表插入更新时把title中的/替换为-
if ("ContentVersion".equals(one.getName()) && "Title".equals(field.getField())){
if ("ContentVersion".equals(one.getName()) && "Title".equals(key)){
String title = StringUtils.replace(String.valueOf(jsonObject.get("Title")), "/", "-");
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", "Title");
@ -891,7 +895,7 @@ public class CommonServiceImpl implements CommonService {
}
// Document,Attachment插入更新时把Name中的/替换为-
if (("Document".equals(one.getName()) || "Attachment".equals(one.getName())) && "Name".equals(field.getField())){
if (("Document".equals(one.getName()) || "Attachment".equals(one.getName())) && "Name".equals(key)){
String name = StringUtils.replace(String.valueOf(jsonObject.get("Name")), "/", "-");
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", "Name");
@ -899,30 +903,20 @@ public class CommonServiceImpl implements CommonService {
maps.add(paramMap);
continue;
}
if ("is_dump".equals( key) || "is_upload".equals( key)){
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", key);
paramMap.put("value", false);
maps.add(paramMap);
continue;
}
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", field.getField());
paramMap.put("value", jsonObject.get(field.getField()));
paramMap.put("key", key);
paramMap.put("value", jsonObject.get(key));
maps.add(paramMap);
}
}
// 附件表 插入更新时把is_dump置为false
if (StringUtils.isNotBlank(one.getBlobField())) {
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("key", "is_dump");
paramMap.put("value", false);
Map<String, Object> paramMap2 = Maps.newHashMap();
paramMap2.put("key", "is_upload");
paramMap2.put("value", false);
maps.add(paramMap);
maps.add(paramMap2);
}
Map<String, Object> paramMap3 = Maps.newHashMap();
paramMap3.put("key", "is_update");
paramMap3.put("value", 0);
maps.add(paramMap3);
if (existsIds.contains(id)) {
customMapper.updateById(api, maps, id);
@ -946,6 +940,7 @@ public class CommonServiceImpl implements CommonService {
return existsIds.size();
}
private final ReentrantLock reentrantLock = new ReentrantLock();
/**
@ -957,7 +952,6 @@ public class CommonServiceImpl implements CommonService {
@Override
public void checkApi(String apiName, Boolean createBatch) throws Exception {
// 加个锁 避免重复执行创建api
reentrantLock.lock();
log.info("check api:{}", apiName);
QueryWrapper<MetaclassConfig> queryWrapper = new QueryWrapper<>();
@ -1183,8 +1177,288 @@ public class CommonServiceImpl implements CommonService {
update.setKeyPrefix(dsr.getKeyPrefix());
dataObjectService.saveOrUpdate(update);
}
} finally {
reentrantLock.unlock();
}catch (Exception e){
EmailUtil.send("DataDump ERROR", e.getMessage());
}
}
/**
* 创建分区表结构
* @param apiName
* @param createBatch
* @throws Exception
*/
public void checkPartitionedApi(String apiName, Boolean createBatch) throws Exception {
// 加个锁 避免重复执行创建api
log.info("check api:{}", apiName);
QueryWrapper<MetaclassConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name",apiName);
long count = metaclassConfigService.count(queryWrapper);
try {
boolean hasCreatedDate = false;
PartnerConnection connection = salesforceConnect.createConnect();
DataObject dataObject = dataObjectService.getById(apiName);
Date now = new Date();
if (StringUtils.isBlank(customMapper.checkTable(apiName))) {
log.info("api:{} doesn't exist create", apiName);
// 构建字段
List<Map<String, Object>> list = Lists.newArrayList();
DescribeSObjectResult dsr = connection.describeSObject(apiName);
String label = dsr.getLabel();
List<DataField> fieldList = Lists.newArrayList();
List<String> fields = Lists.newArrayList();
String blobField = null;
List<DataPicklist> dataPicklists = Lists.newArrayList();
for (Field field : dsr.getFields()) {
// 过滤字段
if (Const.TABLE_FILTERS.contains(field.getName())) {
continue;
}
if (Const.CREATED_DATE.equalsIgnoreCase(field.getName())) {
hasCreatedDate = true;
}
Map<String, Object> map = Maps.newHashMap();
String sfType = field.getType().toString();
if ("base64".equalsIgnoreCase(sfType)) {
blobField = field.getName();
}
map.put("type", DataUtil.fieldTypeToMysql(field));
// 英文' 转换为 \'
map.put("comment", field.getLabel().replaceAll("'", "\\\\'"));
map.put("name", field.getName());
list.add(map);
DataField dataField = new DataField();
dataField.setApi(apiName);
dataField.setSfType(sfType);
dataField.setField(field.getName());
dataField.setName(field.getLabel());
dataField.setIsCreateable(field.getCreateable());
dataField.setIsUpdateable(field.getUpdateable());
dataField.setIsNillable(field.getNillable());
dataField.setIsDefaultedOnCreate(field.getDefaultedOnCreate());
String join = null;
// 会有非常多映射关系的字段在 这里置空不管
if (field.getReferenceTo().length <= 3) {
join = StringUtils.join(field.getReferenceTo(), ",");
} else {
log.info("referenceTo too long set null, api:{}, field:{}, reference to:{}", apiName, field.getName(), StringUtils.join(field.getReferenceTo(), ","));
}
if (field.getReferenceTo().length > 1){
if (field.getName().contains("Id") && !"OwnerId".equals(field.getName()) && !"UserOrGroupId".equals(field.getName())){
LinkConfig linkConfig = new LinkConfig();
linkConfig.setApi(apiName);
linkConfig.setLabel(label);
linkConfig.setField(field.getName());
linkConfig.setLinkField(StringUtils.replace(field.getName(), "Id", "_Type"));
linkConfigService.save(linkConfig);
}
}
// picklist保存到picklist表
if ("picklist".equalsIgnoreCase(sfType)) {
join = "data_picklist";
PicklistEntry[] picklistValues = field.getPicklistValues();
if (ArrayUtils.isNotEmpty(picklistValues)) {
for (PicklistEntry picklistValue : picklistValues) {
DataPicklist dataPicklist = new DataPicklist();
dataPicklist.setApi(apiName);
dataPicklist.setField(field.getName());
dataPicklist.setLabel(picklistValue.getLabel());
dataPicklist.setValue(picklistValue.getValue());
dataPicklists.add(dataPicklist);
}
}
}
dataField.setReferenceTo(join);
fieldList.add(dataField);
fields.add(field.getName());
}
// 存在ownerId字段
String extraField = "";
if (dataObject != null && StringUtils.isNotBlank(dataObject.getExtraField())) {
extraField = dataObject.getExtraField();
}
if (fields.stream().anyMatch(Const.OWNER_ID::equalsIgnoreCase)) {
if (StringUtils.isNotBlank(extraField)) {
extraField += ",Owner.Type";
} else {
extraField = "Owner.Type";
}
}
// 是否存在额外字段 配置上表
if (StringUtils.isNotBlank(extraField)) {
Set<String> extras = Arrays.stream(StringUtils.split(extraField.replaceAll(StringUtils.SPACE, StringUtils.EMPTY), ",")).collect(Collectors.toSet());
for (String extra : extras) {
String fieldName = extra.replaceAll("\\.", "_");
DataField dataField = new DataField();
dataField.setApi(apiName);
dataField.setField(fieldName);
dataField.setName(extra);
fieldList.add(dataField);
fields.add(fieldName);
Map<String, Object> map = Maps.newHashMap();
map.put("type", "varchar(255)");
map.put("comment", extra);
map.put("name", fieldName);
list.add(map);
}
}
// 存在附件的表 作特殊处理 构建两个字段
if (StringUtils.isNotBlank(blobField)) {
fileExtraFieldBuild(apiName, list, fieldList, fields);
}
log.info("api {} not exist, create..", apiName);
//新增一个用来存储新sfid的字段
Map<String, Object> map = Maps.newHashMap();
map.put("type", "varchar(255)");
map.put("comment", "新sfid");
map.put("name", "new_id");
list.add(map);
fields.add("new_id");
// 构建索引
List<Map<String, Object>> index = Lists.newArrayList();
for (String tableIndex : Const.TABLE_INDEX) {
if (!fields.contains(tableIndex)) {
continue;
}
Map<String, Object> createDateMap = Maps.newHashMap();
createDateMap.put("field", tableIndex);
createDateMap.put("name", String.format("IDX_%s_%s", apiName, tableIndex));
index.add(createDateMap);
}
// 构建分区结构按object上的时间字段构建分区
List<Map<String, Object>> partitions = new ArrayList<>();
// 查找对象上的时间字段作为分区键
String partitionKey = "CreatedDate"; // 默认使用创建时间字段
for (Field field : dsr.getFields()) {
if ("datetime".equalsIgnoreCase(field.getType().toString()) ||
"date".equalsIgnoreCase(field.getType().toString())) {
partitionKey = field.getName();
break;
}
}
// 添加默认分区按照日期进行分区
Date defaultBeginDate = DataUtil.DEFAULT_BEGIN_DATE;
Calendar calendar = Calendar.getInstance();
int currentYear = calendar.get(Calendar.YEAR);
Calendar startCalendar = Calendar.getInstance();
startCalendar.setTime(defaultBeginDate);
int startYear = startCalendar.get(Calendar.YEAR);
// 创建从开始年份到当前年份+1的分区每个年份一个分区
for (int year = startYear; year <= currentYear + 1; year++) {
Map<String, Object> partition = new HashMap<>();
partition.put("name", "p" + year);
partition.put("value", String.format("'%s-01-01 00:00:00'", year + 1)); // datetime类型的值
partitions.add(partition);
}
// 添加一个未来分区
Map<String, Object> futurePartition = new HashMap<>();
futurePartition.put("name", "p" + (currentYear + 2));
futurePartition.put("value", "'9999-12-31 23:59:59'");
partitions.add(futurePartition);
//DeleteEvent 回收站新增是否删除数据内同字段
if ("DeleteEvent".equals(apiName)){
Map<String, Object> map1 = Maps.newHashMap();
map1.put("type", "tinyint(1) DEFAULT 0");
map1.put("comment", "是否删除");
map1.put("name", "is_delete");
list.add(map1);
Map<String, Object> map2 = Maps.newHashMap();
map2.put("type", "text");
map2.put("comment", "完整数据");
map2.put("name", "all_data");
list.add(map2);
}
//是否更新
Map<String, Object> map3 = Maps.newHashMap();
map3.put("type", "tinyint(1) DEFAULT 0");
map3.put("comment", "是否更新");
map3.put("name", "is_update");
list.add(map3);
//是否更新
Map<String, Object> map4 = Maps.newHashMap();
map4.put("type", "text");
map4.put("comment", "更新错误信息");
map4.put("name", "error_message");
list.add(map4);
// 创建RANGE分区表按年份分区
customMapper.createRangePartitionTable(
apiName,
label,
list,
index,
partitionKey, // 使用动态查找的分区键
partitions
);
// 生成字段映射
QueryWrapper<DataField> delfieldQw = new QueryWrapper<>();
delfieldQw.eq("api", apiName);
dataFieldService.remove(delfieldQw);
dataFieldService.saveBatch(fieldList);
// 生成picklist
QueryWrapper<DataPicklist> deldQw = new QueryWrapper<>();
deldQw.eq("api", apiName);
dataPicklistService.remove(deldQw);
dataPicklistService.saveBatch(dataPicklists);
// 生成batch
Date startDate = DataUtil.DEFAULT_BEGIN_DATE;
// 取当天零点 作为最大时间
Date endCreateDate = DateUtils.parseDate(DateFormatUtils.format(now, "yyyy-MM-dd"), "yyyy-MM-dd");
Date lastDay = null;
QueryWrapper<DataBatch> delQw = new QueryWrapper<>();
delQw.eq("name", apiName);
dataBatchService.remove(delQw);
if (createBatch && Const.BATCH_FILTERS.stream().noneMatch(t -> apiName.indexOf(t) > 0)) {
// 匹配的不生成
DataBatch one = new DataBatch();
one.setFirstDbNum(0);
one.setFirstSfNum(0);
one.setDbNum(0);
one.setSfNum(0);
one.setName(apiName);
one.setLabel(label);
if (hasCreatedDate) {
List<Map<String, Object>> bachList = customMapper.list("code,value","system_config","code ='"+BATCH_TYPE+"'");
String batchType = bachList.get(0).get("value").toString();
do {
lastDay = getLastDay(batchType, endCreateDate, startDate);
DataBatch dataBatch = one.clone();
dataBatch.setSyncStartDate(startDate);
dataBatch.setSyncEndDate(lastDay);
dataBatchService.save(dataBatch);
startDate = lastDay;
} while (lastDay.compareTo(endCreateDate) < 0);
} else {
// 没创建时间 开始结束时间为空
dataBatchService.save(one);
}
}
DataObject update = new DataObject();
update.setLabel(label);
update.setName(apiName);
update.setLastUpdateDate(endCreateDate);
update.setBlobField(blobField);
if (count>0 || apiName.endsWith("Share")){
update.setIsEditable(false);
}
update.setKeyPrefix(dsr.getKeyPrefix());
dataObjectService.saveOrUpdate(update);
}
}catch (Exception e){
EmailUtil.send("DataDump ERROR", e.getMessage());
}
}

View File

@ -9,6 +9,7 @@ import com.celnet.datadump.config.SalesforceExecutor;
import com.celnet.datadump.entity.DataBatch;
import com.celnet.datadump.entity.DataLog;
import com.celnet.datadump.entity.DataObject;
import com.celnet.datadump.entity.DataPicklist;
import com.celnet.datadump.global.Const;
import com.celnet.datadump.param.DataDumpSpecialParam;
import com.celnet.datadump.param.SalesforceParam;
@ -34,10 +35,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -66,6 +64,8 @@ public class DataDumpSpecialServiceImpl implements DataDumpSpecialService {
private SalesforceExecutor salesforceExecutor;
@Autowired
private DataLogService dataLogService;
@Autowired
private DataPicklistServiceImpl dataPicklistServiceImpl;
@Override
public ReturnT<String> dataDumpSpecial(DataDumpSpecialParam param) throws Throwable {

View File

@ -410,12 +410,14 @@ public class DataImportBatchServiceImpl implements DataImportBatchService {
}
}
account.put("old_owner_id__c", data.get(j-1).get("OwnerId"));
account.put("old_sfdc_id__c", data.get(j-1).get("Id"));
if (dataObject.getIsEditable()){
account.put("old_owner_id__c", data.get(j-1).get("OwnerId"));
account.put("old_sfdc_id__c", data.get(j-1).get("Id"));
}
ids[j-1] = data.get(j-1).get("Id").toString();
insertList.add(account);
if (i*5000+j == count){
if (i*5000+j >= count){
break;
}
}

View File

@ -809,6 +809,7 @@ public class DataImportNewServiceImpl implements DataImportNewService {
List<String> fieldsToNull = new ArrayList<>();
SObject account = new SObject();
account.setType(api);
String message = null;
//给对象赋值
for (DataField dataField : list) {
String field = dataField.getField();
@ -843,8 +844,7 @@ public class DataImportNewServiceImpl implements DataImportNewService {
if (m != null && !m.isEmpty()) {
account.setField(field, m.get("new_id"));
}else {
String message = "对象类型:" + api + "的数据:"+ map.get("Id") +"的引用对象:" + reference_to + "的数据:"+ map.get(field) +"不存在!";
EmailUtil.send("DataDump ERROR", message);
message = "对象类型:" + api + "的数据:"+ map.get("Id") +"的引用对象:" + reference_to + "的数据:"+ map.get(field) +"不存在!";
log.info(message);
break;
}
@ -859,6 +859,9 @@ public class DataImportNewServiceImpl implements DataImportNewService {
}
}
}
if (message != null){
continue;
}
if (!fieldsToNull.isEmpty()){
String[] fieldsToNullArray = fieldsToNull.toArray(new String[0]);
@ -888,13 +891,11 @@ public class DataImportNewServiceImpl implements DataImportNewService {
maps.add(linkMap);
Map<String, Object> linkMap1 = new HashMap<>();
linkMap1.put("key", "error_message");
linkMap1.put("value", saveResult.getErrors());
linkMap1.put("value", JSON.toJSONString(saveResult.getErrors()));
maps.add(linkMap1);
customMapper.updateByNewId(maps,api, saveResult.getId());
Map<String, String> map = returnErrorAccountsDetails(accounts, list, saveResult.getId());
String format = String.format("数据更新 error, api name: %s, \nparam: %s, \ncause:\n%s, \n数据实体类:\n%s", api, com.alibaba.fastjson2.JSON.toJSONString(param, DataDumpParam.getFilter()), JSON.toJSONString(saveResult),JSON.toJSONString(map));
String format = String.format("数据更新 error, api name: %s, \ncause:\n%s", api, JSON.toJSONString(saveResult));
log.info(format);
return;
}else {
List<Map<String, Object>> maps = new ArrayList<>();
Map<String, Object> linkMap = new HashMap<>();
@ -949,32 +950,15 @@ public class DataImportNewServiceImpl implements DataImportNewService {
String beginDateStr = null;
String endDateStr = null;
String updateDateField = dataFieldService.returnUpdateDateField(api);
if (api.endsWith("Share")){
sql = "where RowCause = 'Manual' and new_id is null ";
sql2 = "RowCause = 'Manual' and new_id is null limit 10000";
sql = "where RowCause = 'Manual' and is_update = 0 and new_id is not null and "+updateDateField+" >= '" + beginDateStr + "' ";
sql2 = "RowCause = 'Manual' and is_update = 0 and new_id is not null and "+updateDateField+" >= '" + beginDateStr + "' order by Id asc limit ";
}else {
sql = "where new_id is null ";
sql2 = "new_id is null limit 10000";
sql = "where new_id is not null and is_update = 0 and "+updateDateField+" >= '" + beginDateStr + "' ";
sql2 = "new_id is not null and is_update = 0 and "+updateDateField+" >= '" + beginDateStr + "' order by Id asc limit ";
}
if (1 == 1) {
if (api.endsWith("Share")){
sql = "where RowCause = 'Manual' and new_id is not null and CreatedDate >= '" + beginDateStr + "' and CreatedDate < '" + endDateStr + "'";
sql2 = "RowCause = 'Manual' and new_id is not null and CreatedDate >= '" + beginDateStr + "' and CreatedDate < '" + endDateStr + "' order by Id asc limit ";
}else {
sql = "where new_id is not null and CreatedDate >= '" + beginDateStr + "' and CreatedDate < '" + endDateStr + "'";
sql2 = "new_id is not null and CreatedDate >= '" + beginDateStr + "' and CreatedDate < '" + endDateStr + "' order by Id asc limit ";
}
}else {
String updateDateField = dataFieldService.returnUpdateDateField(api);
if (api.endsWith("Share")){
sql = "where RowCause = 'Manual' and new_id is not null and "+updateDateField+" >= '" + beginDateStr + "' ";
sql2 = "RowCause = 'Manual' and new_id is not null and "+updateDateField+" >= '" + beginDateStr + "' order by Id asc limit ";
}else {
sql = "where new_id is not null and "+updateDateField+" >= '" + beginDateStr + "' ";
sql2 = "new_id is not null and "+updateDateField+" >= '" + beginDateStr + "' order by Id asc limit ";
}
}
//表内数据总量
Integer count = customMapper.countBySQL(api, sql);
@ -997,8 +981,6 @@ public class DataImportNewServiceImpl implements DataImportNewService {
}
int targetCount = 0;
int num = count%10000 == 0 ? count/10000 : (count/10000) + 1;
log.info("总Update数据 count:{}-开始时间:{}-结束时间:{}-api:{}", count, beginDateStr, endDateStr, api);
@ -1089,12 +1071,25 @@ public class DataImportNewServiceImpl implements DataImportNewService {
SaveResult[] saveResults = partnerConnection.update(accounts);
for (SaveResult saveResult : saveResults) {
if (!saveResult.getSuccess()) {
Map<String, String> map = returnErrorAccountsDetails(accounts, list, saveResult.getId());
String format = String.format("数据更新 error, api name: %s, \n cause:\n%s, \n数据实体类:\n%s", api, com.alibaba.fastjson2.JSON.toJSONString(DataDumpParam.getFilter()), JSON.toJSONString(saveResult),JSON.toJSONString(map));
EmailUtil.send("DataDump ERROR", format);
return;
List<Map<String, Object>> maps = new ArrayList<>();
Map<String, Object> linkMap = new HashMap<>();
linkMap.put("key", "is_update");
linkMap.put("value", 2);
maps.add(linkMap);
Map<String, Object> linkMap1 = new HashMap<>();
linkMap1.put("key", "error_message");
linkMap1.put("value", JSON.toJSONString(saveResult.getErrors()));
maps.add(linkMap1);
customMapper.updateByNewId(maps,api, saveResult.getId());
String format = String.format("数据更新 error, api name: %s, \ncause:\n%s", api, JSON.toJSONString(saveResult));
log.info(format);
}else {
targetCount ++;
List<Map<String, Object>> maps = new ArrayList<>();
Map<String, Object> linkMap = new HashMap<>();
linkMap.put("key", "is_update");
linkMap.put("value", 1);
maps.add(linkMap);
customMapper.updateByNewId(maps,api, saveResult.getId());
}
}
@ -2976,6 +2971,7 @@ public class DataImportNewServiceImpl implements DataImportNewService {
for (int j = 1; j <= sized; j++) {
SObject account = new SObject();
account.setType(api);
String message = null;
//给对象赋值
for (DataField dataField : list) {
String field = dataField.getField();
@ -3008,7 +3004,7 @@ public class DataImportNewServiceImpl implements DataImportNewService {
if (m != null && !m.isEmpty()) {
account.setField(field, m.get("new_id"));
}else {
String message = "对象类型:" + api + "的数据:"+ data.get(j - 1).get("Id") +"的引用对象:" + reference_to + "的数据:"+ data.get(j - 1).get(field) +"不存在!";
message = "对象类型:" + api + "的数据:"+ data.get(j - 1).get("Id") +"的引用对象:" + reference_to + "的数据:"+ data.get(j - 1).get(field) +"不存在!";
log.info(message);
break;
}
@ -3021,6 +3017,9 @@ public class DataImportNewServiceImpl implements DataImportNewService {
}
}
}
if (message!=null){
continue;
}
if (dataObject.getIsEditable()){
account.setField("old_owner_id__c", data.get(j - 1).get("OwnerId"));
account.setField("old_sfdc_id__c", data.get(j - 1).get("Id"));
@ -3057,6 +3056,12 @@ public class DataImportNewServiceImpl implements DataImportNewService {
customMapper.updateById(api, maps, ids[index]);
targetCount ++;
}else{
List<Map<String, Object>> maps = new ArrayList<>();
Map<String, Object> linkMap1 = new HashMap<>();
linkMap1.put("key", "error_message");
linkMap1.put("value", JSON.toJSONString(saveResult.getErrors()));
maps.add(linkMap1);
customMapper.updateById(api, maps, ids[index]);
log.error("Id:{},saveResults: {}",ids[index], JSON.toJSONString(saveResult));
}
index++;
@ -3195,6 +3200,7 @@ public class DataImportNewServiceImpl implements DataImportNewService {
for (int j = 1; j <= sized; j++) {
SObject account = new SObject();
account.setType(api);
String message = null;
//给对象赋值
for (DataField dataField : list) {
String field = dataField.getField();
@ -3227,7 +3233,7 @@ public class DataImportNewServiceImpl implements DataImportNewService {
if (m != null && !m.isEmpty()) {
account.setField(field, m.get("new_id"));
}else {
String message = "对象类型:" + api + "的数据:"+ data.get(j - 1).get("Id") +"的引用对象:" + reference_to + "的数据:"+ data.get(j - 1).get(field) +"不存在!";
message = "对象类型:" + api + "的数据:"+ data.get(j - 1).get("Id") +"的引用对象:" + reference_to + "的数据:"+ data.get(j - 1).get(field) +"不存在!";
log.info(message);
break;
}
@ -3240,6 +3246,9 @@ public class DataImportNewServiceImpl implements DataImportNewService {
}
}
}
if (message != null){
continue;
}
if (dataObject.getIsEditable()){
account.setField("old_owner_id__c", data.get(j - 1).get("OwnerId"));
account.setField("old_sfdc_id__c", data.get(j - 1).get("Id"));

View File

@ -325,6 +325,7 @@ public class DataImportServiceImpl implements DataImportService {
try {
for (int j = 1; j <= sized; j++) {
String message = null;
SObject account = new SObject();
account.setType(api);
//找出sf对象必填字段并且给默认值
@ -351,8 +352,11 @@ public class DataImportServiceImpl implements DataImportService {
Map<String, Object> CreatedByIdMap = customMapper.getById("new_id", "User", mapList.get(j-1).get("CreatedById").toString());
if(CreatedByIdMap != null ){
account.setField("CreatedById", CreatedByIdMap.get("new_id"));
}else {
message = "CreatedById:" + mapList.get(j-1).get("CreatedById") + "不存在";
log.info( message);
break;
}
continue;
}
if (dataField.getIsCreateable() != null && dataField.getIsCreateable() && !dataField.getIsNillable() && !dataField.getIsDefaultedOnCreate()) {
@ -365,11 +369,12 @@ public class DataImportServiceImpl implements DataImportService {
if (mapList.get(j - 1).get(linkfield)!=null){
reference = mapList.get(j - 1).get(linkfield).toString();
}else {
log.info("对象类型:" + api + "的数据:"+ mapList.get(j - 1).get("Id") +"的关联类型不存在!");
message ="对象类型:" + api + "的数据:"+ mapList.get(j - 1).get("Id") +"的关联类型不存在!";
log.info(message);
}
}
if (reference == null){
continue;
break;
}
List<Map<String, Object>> referenceMap = customMapper.list("new_id", reference, "new_id is not null limit 1");
@ -396,6 +401,10 @@ public class DataImportServiceImpl implements DataImportService {
}
}
if (message != null){
continue;
}
if (dataObject.getIsEditable()){
account.setField("old_owner_id__c", mapList.get(j - 1).get("OwnerId"));
account.setField("old_sfdc_id__c", mapList.get(j - 1).get("Id"));
@ -432,7 +441,13 @@ public class DataImportServiceImpl implements DataImportService {
maps.add(m);
customMapper.updateById(api, maps, ids[index]);
}else{
log.error("-------------saveResults: {}", JSON.toJSONString(saveResult));
List<Map<String, Object>> maps = new ArrayList<>();
Map<String, Object> linkMap1 = new HashMap<>();
linkMap1.put("key", "error_message");
linkMap1.put("value", JSON.toJSONString(saveResult.getErrors()));
maps.add(linkMap1);
customMapper.updateById(api, maps, ids[index]);
log.error("Id:{},saveResults: {}",ids[index], JSON.toJSONString(saveResult));
}
index++;
}

View File

@ -42,6 +42,7 @@ import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import springfox.documentation.spring.web.json.Json;
import java.io.*;
import java.net.URLEncoder;
@ -558,6 +559,7 @@ public class FileServiceImpl implements FileService {
break;
}
} else {
log.info("文件上传错误,返回:" + JSON.toJSONString(response));
throw new RuntimeException();
}
}

View File

@ -1,21 +1,47 @@
package com.celnet.datadump.util;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sforce.async.*;
import com.sforce.soap.partner.PartnerConnection;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;
import com.sforce.ws.bind.CalendarCodec;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import static com.sforce.async.BulkConnection.JSON_CONTENT_TYPE;
public class BulkUtil {
private static final String URI_STEM_QUERY = "query/";
private static final String URI_STEM_INGEST = "ingest/";
private static final String AUTH_HEADER = "Authorization";
private static final String REQUEST_CONTENT_TYPE_HEADER = "Content-Type";
private static final String ACCEPT_CONTENT_TYPES_HEADER = "ACCEPT";
private static final String AUTH_HEADER_VALUE_PREFIX = "Bearer ";
private static final String UTF_8 = StandardCharsets.UTF_8.name();
private static final String INGEST_RESULTS_SUCCESSFUL = "successfulResults";
private static final String INGEST_RESULTS_UNSUCCESSFUL = "failedResults";
private static final String INGEST_RECORDS_UNPROCESSED = "unprocessedrecords";
public static final String SFORCE_CALL_OPTIONS_HEADER = "Sforce-Call-Options";
public static void closeJob(BulkConnection connection, String jobId)
throws AsyncApiException {
JobInfo job = new JobInfo();
@ -24,6 +50,33 @@ public class BulkUtil {
connection.updateJob(job);
}
public static int awaitCompletionOne(BulkConnection connection, JobInfo job,
BatchInfo bi)
throws AsyncApiException {
long sleepTime = 0L;
int numberRecordsProcessed = 0;
Set<String> incomplete = new HashSet<String>();
incomplete.add(bi.getId());
while (!incomplete.isEmpty()) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
System.out.println("Awaiting results..." + incomplete.size());
sleepTime = 10000L;
BatchInfo[] statusList =
connection.getBatchInfoList(job.getId()).getBatchInfo();
for (BatchInfo b : statusList) {
if (b.getState() == BatchStateEnum.Completed
|| b.getState() == BatchStateEnum.Failed) {
if (incomplete.remove(b.getId())) {
System.out.println("BATCH STATUS:\n" + b);
}
}
numberRecordsProcessed += b.getNumberRecordsProcessed();
}
}
return numberRecordsProcessed;
}
/**
* Wait for a job to complete by polling the Bulk API.
@ -32,7 +85,6 @@ public class BulkUtil {
* BulkConnection used to check results.
* @param job
* The job awaiting completion.
* @param batchInfoList
* List of batches for this job.
* @throws AsyncApiException
*/
@ -83,6 +135,123 @@ public class BulkUtil {
return job;
}
/**
* 创建Bulk API 2.0查询作业
** @param soqlQuery SOQL查询语句
* @return 作业ID
* @throws Exception 创建作业时发生错误
*/
public static String createBulkV2Job(String url,String soqlQuery,BulkConnection connection) throws Exception {
// 构建创建查询作业的 JSON 请求体
String jsonRequest = String.format("{\"operation\":\"query\", \"query\":\"%s\"}", soqlQuery.replace("\"", "\\\""));
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url + "services/data/v56.0/jobs/query");
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Authorization", "Bearer " + connection.getConfig().getSessionId());
httpPost.setEntity(new StringEntity(jsonRequest, "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
// 从响应体中解析出 jobId
String responseBody = EntityUtils.toString(response.getEntity());
// 简单的JSON解析提取id实际项目中建议使用JSON库如Jackson
String jobId = responseBody.split("\"id\":\"")[1].split("\"")[0];
System.out.println("Bulk API 2.0 Query job created with ID: " + jobId);
return jobId;
} else {
String errorBody = EntityUtils.toString(response.getEntity());
throw new RuntimeException("Failed to create Bulk API 2.0 query job, status code: " + statusCode + ", error: " + errorBody);
}
} finally {
response.close();
httpClient.close();
}
}
/**
* Wait for a Bulk API 2.0 job to complete by polling the job status.
*
* @param jobId The ID of the job to check.
* @param connection BulkConnection used to get session info.
* @throws Exception If an error occurs during the polling process.
*/
public static void waitForBulkV2JobCompletion(String url, String jobId, BulkConnection connection) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
String jobStatusUrl = url + "services/data/v56.0/jobs/query/" + jobId;
boolean jobCompleted = false;
while (!jobCompleted) {
HttpGet httpGet = new HttpGet(jobStatusUrl);
httpGet.setHeader("Authorization", "Bearer " + connection.getConfig().getSessionId());
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
String responseBody = EntityUtils.toString(response.getEntity());
// 简单解析响应体中的状态字段实际项目中建议使用JSON库如Jackson
if (responseBody.contains("\"state\":\"JobComplete\"")) {
System.out.println("Job completed successfully.");
jobCompleted = true;
} else if (responseBody.contains("\"state\":\"Failed\"") || responseBody.contains("\"state\":\"Aborted\"")) {
throw new RuntimeException("Job failed or was aborted. Response: " + responseBody);
} else {
System.out.println("Job is still running. Waiting...");
Thread.sleep(10000); // 等待10秒后再次检查
}
} else {
String errorBody = EntityUtils.toString(response.getEntity());
throw new RuntimeException("Failed to get job status, status code: " + statusCode + ", error: " + errorBody);
}
} finally {
response.close();
}
}
httpClient.close();
}
/**
* 获取 Bulk API 2.0 查询作业的结果
*
* @param url Salesforce实例URL
* @param jobId 作业ID
* @param connection BulkConnection used to get session info.
* @return 查询结果的字符串内容
* @throws Exception 如果在获取结果过程中发生错误
*/
public static String getBulkV2QueryJobResults(String url, String jobId, BulkConnection connection) throws Exception {
CloseableHttpClient httpClient = HttpClients.createDefault();
String resultsUrl = url + "/services/data/v65.0/jobs/query/" + jobId + "/results" ;
HttpPost httpGet = new HttpPost(resultsUrl);
httpGet.setHeader("Authorization", "Bearer " + connection.getConfig().getSessionId());
httpGet.setHeader("Content-Type", "application/json");
httpGet.setHeader("Accept", "text/csv");
httpGet.setHeader("X-PrettyPrint", "1");
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
Header locatorHeader = response.getFirstHeader("Sforce-Locator");
Header numRecordsHeader = response.getFirstHeader("Sforce-NumberOfRecords");
String newLocator = (locatorHeader != null) ? locatorHeader.getValue() : null;
int numberOfRecords = (numRecordsHeader != null) ? Integer.parseInt(numRecordsHeader.getValue()) : 0;
return EntityUtils.toString(response.getEntity());
} else {
String errorBody = EntityUtils.toString(response.getEntity());
throw new RuntimeException("Failed to get query job results, status code: " + statusCode + ", error: " + errorBody);
}
} finally {
response.close();
httpClient.close();
}
}
/**
@ -118,6 +287,102 @@ public class BulkUtil {
return connection;
}
public static JobInfo createQueryJob(String api, String soqlQuery,BulkConnection connection) throws Exception {
JobInfo job = new JobInfo();
job.setOperation(OperationEnum.queryAll);
ContentType jobContentType = ContentType.CSV;
job.setContentType(jobContentType);
ConcurrencyMode jobConcurrencyMode = ConcurrencyMode.Parallel;
job.setConcurrencyMode(jobConcurrencyMode);
job.setObject(soqlQuery);
String endpoint = connection.getConfig().getRestEndpoint() + "job/";
HashMap<String, String> headers = getHeaders(JSON_CONTENT_TYPE, JSON_CONTENT_TYPE, connection.getConfig().getSessionId());
HashMap<String, Object> requestBodyMap = new HashMap<String, Object>();
requestBodyMap.put("operation", job.getOperation().toString());
requestBodyMap.put("object", job.getObject());
requestBodyMap.put("contentType", job.getContentType().toString());
requestBodyMap.put("lineEnding", "LF");
requestBodyMap.put("concurrencyMode", job.getConcurrencyMode().toString());
HttpTransportImpl transport = HttpTransportImpl.getInstance();
OutputStream out = transport.connect(endpoint, headers, true, HttpTransportInterface.SupportedHttpMethodType.POST);
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(CalendarCodec.getDateFormat());
String requestContent = mapper.writeValueAsString(requestBodyMap);
out.write(requestContent.getBytes(UTF_8));
out.close();
InputStream in = transport.getContent();
boolean successfulRequest = transport.isSuccessful();
if (!successfulRequest) {
throw new AsyncApiException("Failed to create query job", AsyncExceptionCode.Unknown);
}
ObjectMapper responseMapper = new ObjectMapper();
responseMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
responseMapper.setTimeZone(TimeZone.getTimeZone("GMT"));
return responseMapper.readValue(in, JobInfo.class);
}
public static JobInfo createQueryJob1(String api, String soqlQuery,BulkConnection connection) throws Exception {
JobInfo job = new JobInfo();
InputStream in = null;
job.setOperation(OperationEnum.queryAll);
ContentType jobContentType = ContentType.CSV;
job.setContentType(jobContentType);
ConcurrencyMode jobConcurrencyMode = ConcurrencyMode.Parallel;
job.setConcurrencyMode(jobConcurrencyMode);
job.setObject(soqlQuery);
String endpoint =connection.getConfig().getRestEndpoint()+ "job/";
String requestURL = constructRequestURL(endpoint, job.getId());
HashMap<String, String> headers = null;
headers = getHeaders(JSON_CONTENT_TYPE, JSON_CONTENT_TYPE,connection.getConfig().getSessionId());
HashMap<String, Object> requestBodyMap = new HashMap<String, Object>();
ContentType type = job.getContentType();
requestBodyMap.put("object", job.getObject());
requestBodyMap.put("contentType", type.toString());
requestBodyMap.put("lineEnding", "LF");
OutputStream out;
HttpTransportImpl transport = HttpTransportImpl.getInstance();
out = transport.connect(requestURL, headers, true, HttpTransportInterface.SupportedHttpMethodType.POST);
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(CalendarCodec.getDateFormat());
String requestContent = mapper.writeValueAsString(requestBodyMap);
out.write(requestContent.getBytes(UTF_8));
out.close();
in = transport.getContent();
boolean successfulRequest = true;
successfulRequest = transport.isSuccessful();
JobInfo result = null;
ObjectMapper mapper1 = new ObjectMapper();
mapper1.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
// By default, ObjectMapper generates Calendar instances with UTC TimeZone.
// Here, override that to "GMT" to better match the behavior of the WSC XML parser.
mapper1.setTimeZone(TimeZone.getTimeZone("GMT"));
return mapper1.readValue(in, JobInfo.class);
}
public static HashMap<String, String> getHeaders(String requestContentType, String acceptContentType,String seesionId) {
HashMap<String, String> newMap = new HashMap<String, String>();
String authHeaderValue = AUTH_HEADER_VALUE_PREFIX + seesionId;
newMap.put(REQUEST_CONTENT_TYPE_HEADER, requestContentType);
newMap.put(ACCEPT_CONTENT_TYPES_HEADER, acceptContentType);
newMap.put(AUTH_HEADER, authHeaderValue);
newMap.put(SFORCE_CALL_OPTIONS_HEADER, "client=data-dump, batchSize=5000");
return newMap;
}
private static String constructRequestURL(String urlString,String jobId) {
urlString += "query/" + jobId + "/";
return urlString;
}
/**
* Create and upload batches using a CSV file.
* The file into the appropriate size batch files.

View File

@ -11,6 +11,7 @@ import com.celnet.datadump.param.SalesforceParam;
import com.celnet.datadump.global.SystemConfigCode;
import com.celnet.datadump.service.SystemConfigService;
import com.google.common.collect.Lists;
import com.sforce.async.CSVReader;
import com.sforce.soap.partner.Field;
import com.sforce.soap.partner.sobject.SObject;
import com.sforce.ws.bind.XmlObject;
@ -28,7 +29,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
@ -102,6 +106,9 @@ public class DataUtil {
case "currency":
case "percent":
int scale = field.getScale();
if (scale < 4) {
scale = 4;
}
result = "double(" + 18 + "," + scale + ")";
break;
case "base64":
@ -370,6 +377,48 @@ public class DataUtil {
return jsonObject;
}
/**
* 根据字段类型将值转换为MySQL类型并返回
*
* @param type 字段类型
* @param value 字段值
* @return 转换后的值
* @throws ParseException 日期解析异常
*/
public static Object convertMysqlValue( String type, Object value) throws ParseException {
if (value == null) {
return null;
}
switch (type) {
case "date":
return DateUtils.parseDate((String) value, "yyyy-MM-dd");
case "datetime":
return SF_DATE_FORMAT.parse((String) value);
case "time":
return SF_TIME_FORMAT.parse((String) value);
case "boolean":
return BooleanUtils.toBoolean((String) value);
case "long":
case "int":
return Integer.parseInt((String) value);
case "double":
case "currency":
case "percent":
return Double.parseDouble((String) value);
case "base64":
// 文件不处理 统一存oss或者server
return null;
default:
if (value instanceof GregorianCalendar) {
return DateFormatUtils.format(((GregorianCalendar) value), "yyyy-MM-dd HH:mm:ss");
} else if (value instanceof Time) {
return DateFormatUtils.format((((Time) value).getTimeInMillis()), "HH:mm:ss");
} else {
return String.valueOf(value);
}
}
}
/**
* sObject 数组转 JSONArray
@ -537,4 +586,26 @@ public class DataUtil {
}
}
public static JSONArray toJsonObjectArray(InputStream resultStream) throws IOException {
JSONArray jsonArray = new JSONArray();
try{
CSVReader reader = new CSVReader(resultStream);
CSVReader rdr = new CSVReader(resultStream);
List<String> headers = rdr.nextRecord();
List<String> record;
while ((record = reader.nextRecord()) != null) {
JSONObject jsonObject = new JSONObject();
for (int i = 0; i < headers.size() && i < record.size(); i++) {
jsonObject.put(headers.get(i), record.get(i));
}
jsonArray.add(jsonObject);
}
}catch (Exception e){
log.error("exception message", e);
}
return jsonArray;
}
}

View File

@ -0,0 +1,429 @@
package com.celnet.datadump.util;
import com.sforce.async.AsyncApiException;
import com.sforce.ws.ConnectorConfig;
import com.sforce.ws.tools.VersionInfo;
import com.sforce.ws.transport.LimitingOutputStream;
import com.sforce.ws.transport.MessageHandlerOutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.ProxyAuthenticationStrategy;
import org.apache.http.message.BasicNameValuePair;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* This class implements the Transport interface for WSC with HttpClient in order to properly work
* with NTLM proxies. The existing JdkHttpTransport in WSC does not work with NTLM proxies when
* compiled on Java 1.6
*
* @author Jeff Lai
* @since 25.0.2
*/
public class HttpTransportImpl implements HttpTransportInterface {
private static final String AUTH_HEADER_VALUE_PREFIX = "Bearer ";
private static final String AUTH_HEADER_FOR_JSON = "Authorization";
private static final String AUTH_HEADER_FOR_XML = "X-SFDC-Session";
private static final String USER_AGENT_HEADER = "User-Agent";
private static ConnectorConfig currentConnectorConfig = null;
private boolean successful;
private HttpRequestBase httpMethod = null;
private OutputStream output;
private ByteArrayOutputStream entityByteOut;
private static CloseableHttpClient currentHttpClient = null;
private static long serverInvocationCount = 0;
private HttpResponse httpResponse;
private static final HttpTransportImpl singletonTransportInstance = new HttpTransportImpl();
@Override
public synchronized void setConfig(ConnectorConfig newConfig) {
if (!canReuseHttpClient(currentConnectorConfig, newConfig)
&& currentHttpClient != null) {
closeHttpClient();
}
currentConnectorConfig = newConfig;
if (currentConnectorConfig != null && currentHttpClient == null) {
try {
initializeHttpClient();
} catch (UnknownHostException e) {
}
}
}
private boolean canReuseHttpClient(ConnectorConfig config1, ConnectorConfig config2) {
if (config1 == config2) {
return true;
} else if (config1 == null || config2 == null) {
// one of the configs is null, other isn't. They can't be equal.
return false;
}
InetSocketAddress proxy1Address = (InetSocketAddress)config1.getProxy().address();
InetSocketAddress proxy2Address = (InetSocketAddress)config2.getProxy().address();
if ((proxy1Address == null && proxy2Address != null)
|| (proxy1Address != null && proxy2Address == null)) {
return false;
} else if (proxy1Address != null && proxy2Address != null) {
String field1, field2;
field1 = config1.getProxyUsername() == null ? "" : config1.getProxyUsername();
field2 = config2.getProxyUsername() == null ? "" : config2.getProxyUsername();
if (field1.compareTo(field2) != 0) {
return false;
}
field1 = config1.getProxyPassword() == null ? "" : config1.getProxyPassword();
field2 = config2.getProxyPassword() == null ? "" : config2.getProxyPassword();
if (field1.compareTo(field2) != 0) {
return false;
}
field1 = config1.getNtlmDomain() == null ? "" : config1.getNtlmDomain();
field2 = config2.getNtlmDomain() == null ? "" : config2.getNtlmDomain();
if (field1.compareTo(field2) != 0) {
return false;
}
field1 = proxy1Address.getHostName() == null ? "" : proxy1Address.getHostName();
field2 = proxy2Address.getHostName() == null ? "" : proxy2Address.getHostName();
if (field1.compareTo(field2) != 0) {
return false;
}
int intField1 = proxy1Address.getPort();
int intField2 = proxy2Address.getPort();
if (intField1 != intField2) {
return false;
}
}
return true;
}
private synchronized void initializeHttpClient() throws UnknownHostException {
closeHttpClient();
httpMethod = null;
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder = httpClientBuilder.useSystemProperties();
if (currentConnectorConfig != null
&& currentConnectorConfig.getProxy() != null
&& currentConnectorConfig.getProxy().address() != null) {
String proxyUser = currentConnectorConfig.getProxyUsername() == null ? "" : currentConnectorConfig.getProxyUsername();
String proxyPassword = currentConnectorConfig.getProxyPassword() == null ? "" : currentConnectorConfig.getProxyPassword();
InetSocketAddress proxyAddress = (InetSocketAddress) currentConnectorConfig.getProxy().address();
HttpHost proxyHost = new HttpHost(proxyAddress.getHostName(), proxyAddress.getPort(), "http");
httpClientBuilder.setProxy(proxyHost);
CredentialsProvider credentialsprovider = new BasicCredentialsProvider();
AuthScope scope = new AuthScope(proxyAddress.getHostName(), proxyAddress.getPort(), null, null);
Credentials credentials;
credentials = new UsernamePasswordCredentials(proxyUser, proxyPassword);
// based on answer at https://stackoverflow.com/questions/6962047/apache-httpclient-4-1-proxy-authentication
credentialsprovider.setCredentials(scope, credentials);
httpClientBuilder.setDefaultCredentialsProvider(credentialsprovider);
httpClientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
currentHttpClient = httpClientBuilder.build();
}
if (currentHttpClient == null) {
currentHttpClient = httpClientBuilder.build();
}
}
@Override
public synchronized InputStream getContent() throws IOException {
serverInvocationCount++;
if (this.httpMethod instanceof HttpEntityEnclosingRequestBase
&& ((HttpEntityEnclosingRequestBase)this.httpMethod).getEntity() == null) {
byte[] entityBytes = entityByteOut.toByteArray();
HttpEntity entity = new ByteArrayEntity(entityBytes);
currentConnectorConfig.setUseChunkedPost(false);
((HttpEntityEnclosingRequestBase)this.httpMethod).setEntity(entity);
}
InputStream input = new ByteArrayInputStream(new byte[1]);
HttpClientContext context = HttpClientContext.create();
RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(currentConnectorConfig.useChunkedPost()).build();
context.setRequestConfig(config);
if (currentConnectorConfig.getNtlmDomain() != null && !currentConnectorConfig.getNtlmDomain().equals("")) {
// need to send a HEAD request to trigger NTLM authentication
try (CloseableHttpResponse ignored = currentHttpClient.execute(new HttpHead("http://salesforce.com"))) {
} catch (Exception ex) {
throw ex;
}
}
try (CloseableHttpResponse response = currentHttpClient.execute(this.httpMethod, context)) {
successful = true;
httpResponse = response;
if (response.getStatusLine().getStatusCode() > 399) {
successful = false;
if (response.getStatusLine().getStatusCode() == 407) {
throw new RuntimeException(response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase());
}
}
// copy input stream data into a new input stream because releasing the connection will close the input stream
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
if (response.getEntity() != null) {
try (InputStream inStream = response.getEntity().getContent()) {
IOUtils.copy(inStream, bOut);
input = new ByteArrayInputStream(bOut.toByteArray());
if (response.containsHeader("Content-Encoding") && response.getHeaders("Content-Encoding")[0].getValue().equals("gzip")) {
input = new GZIPInputStream(input);
}
}
}
bOut.close();
}
return input;
}
public HttpResponse getHttpResponse() {
return this.httpResponse;
}
@Override
public boolean isSuccessful() {
return successful;
}
@Override
public OutputStream connect(String url, String soapAction) throws IOException {
if (soapAction == null) {
soapAction = "";
}
HashMap<String, String> header = new HashMap<String, String>();
header.put("SOAPAction", "\"" + soapAction + "\"");
header.put("Content-Type", "text/xml; charset=" + StandardCharsets.UTF_8.name());
header.put("Accept", "text/xml");
return connect(url, header);
}
@Override
public OutputStream connect(String endpoint, HashMap<String, String> httpHeaders) throws IOException {
return connect(endpoint, httpHeaders, true);
}
@Override
public OutputStream connect(String endpoint, HashMap<String, String> httpHeaders, boolean enableCompression) throws IOException {
return connect(endpoint, httpHeaders, enableCompression, SupportedHttpMethodType.POST);
}
@Override
public OutputStream connect(String endpoint, HashMap<String, String> httpHeaders, boolean enableCompression,
SupportedHttpMethodType httpMethod) throws IOException {
return doConnect(endpoint, httpHeaders, enableCompression, httpMethod, null, null);
}
@Override
public void connect(String endpoint, HashMap<String, String> httpHeaders, boolean enableCompression,
SupportedHttpMethodType httpMethod, InputStream contentInputStream, String contentEncoding)
throws IOException {
doConnect(endpoint, httpHeaders, enableCompression, httpMethod, contentInputStream, contentEncoding);
}
public static long getServerInvocationCount() {
return serverInvocationCount;
}
public static void resetServerInvocationCount() {
serverInvocationCount = 0;
}
private OutputStream doConnect(String endpoint,
HashMap<String, String> httpHeaders,
boolean enableCompression,
SupportedHttpMethodType httpMethodType,
InputStream requestInputStream,
String contentTypeStr) throws IOException {
configureHttpMethod(endpoint, httpHeaders, enableCompression, httpMethodType,
requestInputStream, contentTypeStr);
if (requestInputStream != null) {
// Request content is in an input stream.
// Caller won't be using an output stream to write request content to.
return null;
}
entityByteOut = new ByteArrayOutputStream();
output = entityByteOut;
if (currentConnectorConfig.getMaxRequestSize() > 0) {
output = new LimitingOutputStream(currentConnectorConfig.getMaxRequestSize(), output);
}
if (enableCompression && currentConnectorConfig.isCompression()) {
output = new GZIPOutputStream(output);
}
if (currentConnectorConfig.isTraceMessage()) {
output = currentConnectorConfig.teeOutputStream(output);
}
if (currentConnectorConfig.hasMessageHandlers()) {
URL url = new URL(endpoint);
output = new MessageHandlerOutputStream(currentConnectorConfig, url, output);
}
return output;
}
private void configureHttpMethod(
String endpoint,
HashMap<String, String> httpHeaders,
boolean enableCompression,
SupportedHttpMethodType httpMethodType,
InputStream requestInputStream,
String contentTypeStr
) throws IOException {
switch (httpMethodType) {
case GET :
this.httpMethod = new HttpGet(endpoint);
break;
case PATCH :
this.httpMethod = new HttpPatch(endpoint);
break;
case PUT :
this.httpMethod = new HttpPut(endpoint);
break;
case DELETE :
this.httpMethod = new HttpDelete(endpoint);
break;
default:
this.httpMethod = new HttpPost(endpoint);
}
if (httpHeaders != null) {
for (String name : httpHeaders.keySet()) {
this.httpMethod.addHeader(name, httpHeaders.get(name));
}
}
Map<String, String> connectorHeaders = currentConnectorConfig.getHeaders();
if (connectorHeaders != null) {
for (String name : connectorHeaders.keySet()) {
if (httpHeaders == null || !httpHeaders.containsKey(name)) {
this.httpMethod.addHeader(name, connectorHeaders.get(name));
}
}
}
setAuthAndClientHeadersForHttpMethod();
if (enableCompression && currentConnectorConfig.isCompression()) {
this.httpMethod.addHeader("Content-Encoding", "gzip");
this.httpMethod.addHeader("Accept-Encoding", "gzip");
}
if (requestInputStream != null) {
// caller has pre-specified input stream
ContentType contentType = ContentType.DEFAULT_TEXT;
if (contentTypeStr != null) {
contentType = ContentType.create(contentTypeStr);
}
BufferedHttpEntity entity = new BufferedHttpEntity(new InputStreamEntity(requestInputStream, contentType));
currentConnectorConfig.setUseChunkedPost(true);
if (this.httpMethod instanceof HttpEntityEnclosingRequestBase) {
((HttpEntityEnclosingRequestBase)this.httpMethod).setEntity(entity);
}
}
}
public InputStream simplePost(
String endpoint,
HashMap<String, String> httpHeaders,
BasicNameValuePair[] inputs) throws IOException {
configureHttpMethod(endpoint, httpHeaders,
false, SupportedHttpMethodType.POST, null, null);
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(Arrays.asList(inputs));
if (this.httpMethod instanceof HttpPost) {
((HttpPost)this.httpMethod).setEntity(entity);
return this.getContent();
}
return null;
}
private void setAuthAndClientHeadersForHttpMethod() {
if (this.httpMethod != null
&& currentConnectorConfig.getSessionId() != null
) {
String authSessionId = currentConnectorConfig.getSessionId();
Header authHeaderValForXML = this.httpMethod.getFirstHeader(AUTH_HEADER_FOR_XML);
Header authHeaderValForJSON = this.httpMethod.getFirstHeader(AUTH_HEADER_FOR_JSON);
if (authHeaderValForXML != null) {
authSessionId = authHeaderValForXML.getValue();
} else if (authHeaderValForJSON != null) {
authSessionId = authHeaderValForJSON.getValue();
}
if (authHeaderValForXML == null) {
this.httpMethod.addHeader(AUTH_HEADER_FOR_XML, AUTH_HEADER_VALUE_PREFIX + authSessionId);
}
if (authHeaderValForJSON == null) {
this.httpMethod.addHeader(AUTH_HEADER_FOR_JSON, AUTH_HEADER_VALUE_PREFIX + authSessionId);
}
}
Header userAgentHeaderVal = this.httpMethod.getFirstHeader(USER_AGENT_HEADER);
if (userAgentHeaderVal == null) {
this.httpMethod.addHeader(USER_AGENT_HEADER, VersionInfo.info());
}
}
public static void closeHttpClient() {
if (currentHttpClient != null) {
try {
currentHttpClient.close();
} catch (IOException ex) {
// do nothing
}
currentHttpClient = null;
}
}
public InputStream httpGet(String urlStr) throws IOException, AsyncApiException{
InputStream in = null;
connect(urlStr, null, false, SupportedHttpMethodType.GET, null, null);
in = getContent();
return in;
}
public static HttpTransportImpl getInstance() {
return singletonTransportInstance;
}
}

View File

@ -0,0 +1,37 @@
package com.celnet.datadump.util;
import com.sforce.async.AsyncApiException;
import com.sforce.ws.transport.Transport;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
/**
* This interface defines a Transport.
*
* @author http://cheenath.com
* @version 1.0
* @since 1.0 Nov 30, 2005
*/
public interface HttpTransportInterface extends Transport {
enum SupportedHttpMethodType {
PUT,
POST,
PATCH,
DELETE,
GET
}
int PROXY_AUTHENTICATION_REQUIRED = 407;
OutputStream connect(String endpoint, HashMap<String, String> httpHeaders, boolean enableCompression,
SupportedHttpMethodType httpMethod) throws IOException;
void connect(String endpoint, HashMap<String, String> httpHeaders, boolean enableCompression,
SupportedHttpMethodType httpMethod, InputStream contentInputStream, String contentEncoding) throws IOException;
InputStream httpGet(String urlStr) throws IOException, AsyncApiException;
HttpResponse getHttpResponse();
}

View File

@ -35,6 +35,32 @@
) COMMENT = '${tableComment}';
</update>
<!-- 创建RANGE分区表 -->
<update id="createRangePartitionTable" statementType="STATEMENT">
CREATE TABLE `${tableName}` (
<foreach item="map" collection="maps">
`${map.name}` ${map.type} comment '${map.comment}',
</foreach>
<foreach item="map" collection="index">
<choose>
<when test="map.field == 'new_id'">
UNIQUE INDEX `${map.name}`(`${map.field}`),
</when>
<otherwise>
INDEX `${map.name}`(`${map.field}`),
</otherwise>
</choose>
</foreach>
PRIMARY KEY (`id`,`CreatedDate`)
) COMMENT = '${tableComment}'
PARTITION BY RANGE COLUMNS(`${partitionKey}`)
(
<foreach item="partition" collection="partitions" separator=",">
PARTITION ${partition.name} VALUES LESS THAN (${partition.value})
</foreach>
)
</update>
<update id="createField">
ALTER TABLE `${tableName}` ADD COLUMN `${fieldName}` varchar(255)
</update>
@ -51,6 +77,27 @@
</foreach>
</select>
<!-- 在指定分区插入单条记录 -->
<insert id="saveToPartition" statementType="STATEMENT">
INSERT INTO `${tableName}` PARTITION (${partitionName})
<foreach item="map" collection="maps" open="(" separator="," close=")">
`${map.key}`
</foreach>
VALUES
<foreach item="map" collection="maps" open="(" separator="," close=")">
#{map.value}
</foreach>
</insert>
<!-- 在指定分区根据ID更新记录 -->
<update id="updateToPartition" statementType="STATEMENT">
UPDATE `${tableName}` PARTITION (${partitionName})
SET
<foreach item="map" collection="maps" separator=",">
`${map.key}` = #{map.value}
</foreach>
WHERE id = #{id}
</update>
<update id="updateById">
UPDATE
@ -78,13 +125,14 @@
</update>
<update id="updateByNewId">
UPDATE `${tableName}`
SET new_id = #{newId}
<where>
<foreach item="map" collection="maps" separator=" AND ">
${map.key} = #{map.value}
</foreach>
</where>
UPDATE
`${tableName}`
SET
<foreach item="map" collection="maps" separator=" AND ">
${map.key} = #{map.value}
</foreach>
WHERE
new_id = #{newId}
</update>
<insert id="save">

View File

@ -26,7 +26,12 @@
</if>
<!-- 判断 param.api 是否以 "Share" 结尾 -->
<if test="param.api != null and param.api.endsWith('Share')">
AND RowCause = 'Manual'
AND RowCause NOT IN (
'Owner', 'Rule', 'Territory', 'Team',
'ImplicitChild', 'ImplicitParent', 'TerritoryRule',
'ImplicitCallCenter', 'PortalRole', 'Portal',
'ImplicitPerson', 'ImplicitGrant'
)
</if>
<if test="param.timestamp != null">
<choose>
@ -70,7 +75,12 @@
</if>
<!-- 判断 param.api 是否以 "Share" 结尾 -->
<if test="param.api != null and param.api.endsWith('Share')">
AND RowCause = 'Manual'
AND RowCause NOT IN (
'Owner', 'Rule', 'Territory', 'Team',
'ImplicitChild', 'ImplicitParent', 'TerritoryRule',
'ImplicitCallCenter', 'PortalRole', 'Portal',
'ImplicitPerson', 'ImplicitGrant'
)
</if>
<if test="param.timestamp != null">
<choose>
@ -111,7 +121,12 @@
</if>
<!-- 判断 param.api 是否以 "Share" 结尾 -->
<if test="param.api != null and param.api.endsWith('Share')">
AND RowCause = 'Manual'
AND RowCause NOT IN (
'Owner', 'Rule', 'Territory', 'Team',
'ImplicitChild', 'ImplicitParent', 'TerritoryRule',
'ImplicitCallCenter', 'PortalRole', 'Portal',
'ImplicitPerson', 'ImplicitGrant'
)
</if>
<if test="param.endCreateDate != null">
AND CreatedDate &lt; #{param.endCreateDate}
@ -136,7 +151,12 @@
</if>
<!-- 判断 param.api 是否以 "Share" 结尾 -->
<if test="param.api != null and param.api.endsWith('Share')">
AND RowCause = 'Manual'
AND RowCause NOT IN (
'Owner', 'Rule', 'Territory', 'Team',
'ImplicitChild', 'ImplicitParent', 'TerritoryRule',
'ImplicitCallCenter', 'PortalRole', 'Portal',
'ImplicitPerson', 'ImplicitGrant'
)
</if>
</if>
</where>
@ -162,7 +182,12 @@
</if>
<!-- 判断 param.api 是否以 "Share" 结尾 -->
<if test="param.api != null and param.api.endsWith('Share')">
AND RowCause = 'Manual'
AND RowCause NOT IN (
'Owner', 'Rule', 'Territory', 'Team',
'ImplicitChild', 'ImplicitParent', 'TerritoryRule',
'ImplicitCallCenter', 'PortalRole', 'Portal',
'ImplicitPerson', 'ImplicitGrant'
)
</if>
<if test="param.beginModifyDate != null and !param.api.endsWith('Share')">
AND SystemModstamp >= #{param.beginModifyDate}