[feat] 富文本图片替换_v2

This commit is contained in:
Kris 2025-10-10 16:26:42 +08:00
parent 3402971c49
commit 84d6fbd401

View File

@ -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<DataField> fields = dataFieldService.list(qw1);
for (DataField dataField : fields) {
String field = dataField.getField();
String sqlCondition = "new_id is not null and " + field + " like '%<img%refid%'";
@ -2696,7 +2700,7 @@ public class DataImportNewServiceImpl implements DataImportNewService {
for (int page = 0; page < totalPages; page++) {
List<Map<String, Object>> 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("<img[^>]*refid=\"([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE);
Pattern imgPattern = Pattern.compile("<img[^>]*refid=\"([^\"]+)\"[^>]*/?>", Pattern.CASE_INSENSITIVE);
Matcher matcher = imgPattern.matcher(richTextContent);
StringBuffer updatedContent = new StringBuffer();
Map<String, String> 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 = "<img src=\"" + newImageUrl + "\" refid=\"" + newContentDocumentId + "\"/>";
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<String, String> 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<String, String> 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<Map<String, Object>> 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<String, Object> 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;
}