From 84d6fbd4014361e16de97f56cdf73cbc1ae96e83 Mon Sep 17 00:00:00 2001 From: Kris <2893855659@qq.com> Date: Fri, 10 Oct 2025 16:26:42 +0800 Subject: [PATCH] =?UTF-8?q?[feat]=20=E5=AF=8C=E6=96=87=E6=9C=AC=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=9B=BF=E6=8D=A2=5Fv2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/DataImportNewServiceImpl.java | 188 +++++++++++++++--- 1 file changed, 155 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/celnet/datadump/service/impl/DataImportNewServiceImpl.java b/src/main/java/com/celnet/datadump/service/impl/DataImportNewServiceImpl.java index 0a95bea..23718eb 100644 --- a/src/main/java/com/celnet/datadump/service/impl/DataImportNewServiceImpl.java +++ b/src/main/java/com/celnet/datadump/service/impl/DataImportNewServiceImpl.java @@ -31,6 +31,7 @@ import com.xxl.job.core.util.DateUtil; import lombok.extern.slf4j.Slf4j; import okhttp3.Response; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.Get; import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -56,6 +57,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; + +import java.util.Base64; + @Service @Slf4j public class DataImportNewServiceImpl implements DataImportNewService { @@ -964,7 +968,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { .eq("sync_end_date", endDate) .set("sf_update_num", targetCount); dataBatchService.update(updateQw2); - + log.info("数据更新完成,API: {},总更新数: {},开始时间: {},结束时间: {}", api, targetCount, beginDateStr, endDateStr); } @@ -2413,7 +2417,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { String sql = ""; if (!param.getIds().isEmpty()){ - sql = "where Record in (" + param.getIds().stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")) + ")"; + sql = "where Record in (" + param.getIds().stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")) + ")"; }else { Date beginDate = param.getBeginCreateDate(); Date endDate = param.getEndCreateDate(); @@ -2671,7 +2675,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { qw1.eq("api", api); qw1.eq("isHtmlFormatted", true); List fields = dataFieldService.list(qw1); - + for (DataField dataField : fields) { String field = dataField.getField(); String sqlCondition = "new_id is not null and " + field + " like '%> records = customMapper.list("Id, new_id, " + field, api, - sqlCondition + " order by Id limit " + (page * pageSize) + "," + pageSize); + sqlCondition + " order by Id limit " + (page * pageSize) + "," + pageSize); log.info("处理对象 {} 字段 {} 第 {}/{} 页,本页记录数: {}", api, field, (page + 1), totalPages, records.size()); @@ -2715,7 +2719,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { // 处理富文本中的图片 String updatedContent = processRichTextImages(richTextContent, sourceId, api, field, sourceConnection, targetConnection); - + // 如果内容有变化,则更新目标组织中的记录 if (!richTextContent.equals(updatedContent)) { @@ -2782,7 +2786,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { /** * 处理富文本中的图片 * @param richTextContent 原始富文本内容 - * @param recordId 记录ID + * @param sourceRecordId 源记录ID * @param api 对象API名称 * @param field 字段名 * @param sourceConnection 源组织连接 @@ -2790,20 +2794,20 @@ public class DataImportNewServiceImpl implements DataImportNewService { * @return 处理后的富文本内容 * @throws Exception 异常 */ - private String processRichTextImages(String richTextContent, String recordId, String api, String field, - PartnerConnection sourceConnection, PartnerConnection targetConnection) throws Exception { + private String processRichTextImages(String richTextContent, String sourceRecordId, String api, String field, + PartnerConnection sourceConnection, PartnerConnection targetConnection) throws Exception { // 匹配富文本中包含refid的img标签 - Pattern imgPattern = Pattern.compile("]*refid=\"([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE); + Pattern imgPattern = Pattern.compile("]*refid=\"([^\"]+)\"[^>]*/?>", Pattern.CASE_INSENSITIVE); Matcher matcher = imgPattern.matcher(richTextContent); - + StringBuffer updatedContent = new StringBuffer(); Map imageIdMap = new ConcurrentHashMap<>(); - + // 遍历所有匹配的img标签 while (matcher.find()) { String imgTag = matcher.group(0); String refId = matcher.group(1); - + // 检查是否已经处理过这个refId if (imageIdMap.containsKey(refId)) { String newRefId = imageIdMap.get(refId); @@ -2811,27 +2815,61 @@ public class DataImportNewServiceImpl implements DataImportNewService { matcher.appendReplacement(updatedContent, Matcher.quoteReplacement(updatedImgTag)); continue; } - + try { // 查询源组织中的ContentDocumentLink记录 - String cdlQuery = "SELECT Id, ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = '" + recordId + "' AND ContentDocumentId = '" + refId + "'"; + String cdlQuery = "SELECT Id, ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = '" + sourceRecordId + "' AND ContentDocumentId = '" + refId + "'"; QueryResult cdlResult = sourceConnection.query(cdlQuery); - + if (cdlResult.getRecords() != null && cdlResult.getRecords().length > 0) { - // 获取ContentVersion ID - String contentVersionId = getContentVersionId(sourceConnection, refId); - if (contentVersionId != null) { - // 获取目标组织中对应的ContentDocumentId - String newContentDocumentId = getTargetContentDocumentId(targetConnection, contentVersionId); - if (newContentDocumentId != null) { - imageIdMap.put(refId, newContentDocumentId); - String updatedImgTag = imgTag.replace("refid=\"" + refId + "\"", "refid=\"" + newContentDocumentId + "\""); - matcher.appendReplacement(updatedContent, Matcher.quoteReplacement(updatedImgTag)); - continue; + // 构建源图片下载URL (参考Python脚本中的imageUrl构建方式) + String downloadUrl = sourceConnection.getConfig().getServiceEndpoint() + + String.format("/services/data/v55.0/sobjects/%s/%s/richTextImageFields/%s/%s", + api, sourceRecordId, field, refId); + + // 下载源图片文件 + InputStream inputStream = downloadImageFile(sourceConnection, downloadUrl); + if (inputStream != null) { + // 获取图片的alt属性作为标题 + String title = "image"; + Pattern altPattern = Pattern.compile("alt=\"([^\"]*)\"", Pattern.CASE_INSENSITIVE); + Matcher altMatcher = altPattern.matcher(imgTag); + if (altMatcher.find()) { + title = altMatcher.group(1); + } + + // 上传图片到目标组织 + String newContentVersionId = uploadImageFile(targetConnection, inputStream, title); + if (newContentVersionId != null) { + // 获取新的ContentDocumentId + String newContentDocumentId = getDocumentId(targetConnection, newContentVersionId); + if (newContentDocumentId != null) { + imageIdMap.put(refId, newContentDocumentId); + + // 构建新的img标签 (参考Python脚本中的richTextUrl构建方式) + String newImageUrl = targetConnection.getConfig().getServiceEndpoint() + + "/sfc/servlet.shepherd/version/download/" + newContentVersionId; + String updatedImgTag = ""; + + matcher.appendReplacement(updatedContent, Matcher.quoteReplacement(updatedImgTag)); + + // 记录替换日志 + richTextLogService.logRichTextImageReplace( + api, + field, + newContentDocumentId, + null, // recordOwnerId 可以从记录中获取,但此处简化处理 + richTextContent, + refId, // 原图片链接 + newImageUrl, // 新图片链接 + newContentDocumentId // 新文件ID + ); + continue; + } } } } - + // 如果找不到对应关系,保留原始标签 matcher.appendReplacement(updatedContent, Matcher.quoteReplacement(imgTag)); } catch (Exception e) { @@ -2840,10 +2878,94 @@ public class DataImportNewServiceImpl implements DataImportNewService { } } matcher.appendTail(updatedContent); - + return updatedContent.toString(); } - + + /** + * 从源组织下载图片文件 + * @param connection 源组织连接 + * @param downloadUrl 下载URL + * @return 图片文件数据 + * @throws Exception 异常 + */ + private InputStream downloadImageFile(PartnerConnection connection, String downloadUrl) throws Exception { + InputStream inputStream = null; + try { + String token = connection.getSessionHeader().getSessionId(); + + Map headers = Maps.newHashMap(); + headers.put("Authorization", "Bearer " + token); + headers.put("connection", "keep-alive"); + + Response response = HttpUtil.doGet(downloadUrl, null, headers); + assert response.body() != null; + inputStream = response.body().byteStream(); + } catch (Exception e) { + log.error("下载图片文件时发生错误,URL: {}", downloadUrl, e); + return null; + } + return inputStream; + } + + /** + * 上传图片文件到目标组织 + * @param connection 目标组织连接 + * @param inputStream 文件数据 + * @param title 文件标题 + * @return 新的ContentVersion ID + * @throws Exception 异常 + */ + private String uploadImageFile(PartnerConnection connection, InputStream inputStream, String title) throws Exception { + String newId = null; + try { + int failCount = 0; + + String token = connection.getSessionHeader().getSessionId(); + + String uploadUrl = connection.getConfig().getServiceEndpoint() + + "/services/data/v55.0/sobjects/ContentVersion"; + + HttpPost httpPost = new HttpPost(uploadUrl); + + Map headers = Maps.newHashMap(); + headers.put("Authorization", "Bearer " + token); + headers.put("connection", "keep-alive"); + + + com.alibaba.fastjson.JSONObject credentialsJsonParam = new com.alibaba.fastjson.JSONObject(); + credentialsJsonParam.put("title", title); + credentialsJsonParam.put("pathOnClient", title + ".jpg"); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("entity_content", credentialsJsonParam.toJSONString(), ContentType.APPLICATION_JSON); + builder.addBinaryBody("VersionData", inputStream, ContentType.APPLICATION_OCTET_STREAM, title + ".jpg"); + HttpEntity entity = builder.build(); + httpPost.setEntity(entity); + + CloseableHttpClient httpClient = HttpClients.createDefault(); + + CloseableHttpResponse response = null; + String respContent = null; + while (failCount < Const.MAX_FAIL_COUNT) { + response = httpClient.execute(httpPost); + if (response != null && response.getStatusLine() != null && response.getStatusLine().getStatusCode() < 400 && response.getEntity() != null) { + HttpEntity he = response.getEntity(); + respContent = EntityUtils.toString(he, "UTF-8"); + newId = String.valueOf(com.alibaba.fastjson.JSONObject.parseObject(respContent).get("id")); + break; + } else { + failCount++; + log.error("富文本图片替换图片文件上传失败,返回体信息:" + JSON.toJSONString(response)); + } + } + } catch (Exception e) { + log.error("上传图片文件时发生错误,标题: {}", title, e); + return null; + } + return newId; + } + /** * 获取ContentVersion ID * @param connection Salesforce连接 @@ -2859,7 +2981,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { } return null; } - + /** * 获取目标组织中的ContentDocumentId * @param connection 目标组织连接 @@ -2984,7 +3106,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { JSONObject jsonObject = objects.getJSONObject(i); String id = jsonObject.getString(Const.ID); List> maps = buildUpdateMaps(api, jsonObject, stringList); - + if (maps!=null && !maps.isEmpty()) { log.info("准备更新记录,API: {}, ID: {}, 匹配字段信息: {}", api, id, maps); customMapper.updateNewIdByFields(api, maps, id); @@ -2999,7 +3121,7 @@ public class DataImportNewServiceImpl implements DataImportNewService { for (String key : stringList) { Map paramMap = null; - + switch (api) { case "AccountContactRelation": paramMap = handleAccountContactRelation(key, jsonObject); @@ -3036,12 +3158,12 @@ public class DataImportNewServiceImpl implements DataImportNewService { log.info("普通字段处理,API: {}, 字段: {}, 值: {}", api, key, jsonObject.getString(key)); break; } - + if (!paramMap.isEmpty()) { maps.add(paramMap); } } - + return maps; }