[feat] 富文本图片替换初步实现

This commit is contained in:
Kris 2025-10-10 11:21:43 +08:00
parent 33d23213dd
commit 2457665b14
7 changed files with 355 additions and 273 deletions

View File

@ -104,6 +104,13 @@ public class DataField implements Serializable {
@ApiModelProperty(value = "是否更新")
private Boolean isUpdateable;
/**
* 是否富文本字段
*/
@TableField("isHtmlFormatted")
@ApiModelProperty(value = "是否富文本字段")
private Boolean isHtmlFormatted;
/**
* 是否为空目标库
*/

View File

@ -0,0 +1,68 @@
package com.celnet.datadump.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 富文本替换记录表
* </p>
*
* @author Lingma
* @since 2025-10-10
*/
@Getter
@Setter
@TableName("richText_log")
@ApiModel(value = "RichTextLog对象", description = "富文本替换记录表")
public class RichTextLog implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("主键ID")
@TableId(value = "ID", type = IdType.AUTO)
private Long id;
@ApiModelProperty("对象API名称")
@TableField("api")
private String api;
@ApiModelProperty("字段API名称")
@TableField("field")
private String field;
@ApiModelProperty("记录ID")
@TableField("record_id")
private String recordId;
@ApiModelProperty("记录OwnerID")
@TableField("record_owner_id")
private String recordOwnerId;
@ApiModelProperty("原始富文本内容")
@TableField("original_rich_text")
private String originalRichText;
@ApiModelProperty("原图片链接")
@TableField("original_image_url")
private String originalImageUrl;
@ApiModelProperty("新图片链接")
@TableField("new_image_url")
private String newImageUrl;
@ApiModelProperty("新文件IDContentDocumentId")
@TableField("new_file_id")
private String newFileId;
@ApiModelProperty("创建时间")
@TableField(value = "created_time", fill = FieldFill.INSERT)
private Date createdTime;
}

View File

@ -0,0 +1,18 @@
package com.celnet.datadump.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.celnet.datadump.entity.RichTextLog;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* 富文本替换记录表 Mapper 接口
* </p>
*
* @author Lingma
* @since 2025-10-10
*/
@Mapper
public interface RichTextLogMapper extends BaseMapper<RichTextLog> {
}

View File

@ -0,0 +1,31 @@
package com.celnet.datadump.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.celnet.datadump.entity.RichTextLog;
/**
* <p>
* 富文本替换记录表 服务类
* </p>
*
* @author Lingma
* @since 2025-10-10
*/
public interface RichTextLogService extends IService<RichTextLog> {
/**
* 记录富文本图片替换日志
* @param api 对象API名称
* @param field 字段API名称
* @param recordId 记录ID
* @param recordOwnerId 记录OwnerID
* @param originalRichText 原始富文本内容
* @param originalImageUrl 原图片链接
* @param newImageUrl 新图片链接
* @param newFileId 新文件IDContentDocumentId
*/
void logRichTextImageReplace(String api, String field, String recordId, String recordOwnerId,
String originalRichText, String originalImageUrl,
String newImageUrl, String newFileId);
}

View File

@ -979,10 +979,7 @@ public class CommonServiceImpl implements CommonService {
String blobField = null;
List<DataPicklist> dataPicklists = Lists.newArrayList();
for (Field field : dsr.getFields()) {
if ("textarea".equalsIgnoreCase(field.getType().toString()) && field.isHtmlFormatted()) {
log.info("检测到富文本字段: {}API: {}", field.getName(), apiName);
}
// 过滤字段
if (Const.TABLE_FILTERS.contains(field.getName())) {
continue;
@ -1016,6 +1013,15 @@ public class CommonServiceImpl implements CommonService {
} else {
log.info("referenceTo too long set null, api:{}, field:{}, reference to:{}", apiName, field.getName(), StringUtils.join(field.getReferenceTo(), ","));
}
if ("textarea".equalsIgnoreCase(field.getType().toString()) && field.isHtmlFormatted()) {
log.info("检测到富文本字段: {}API: {}", field.getName(), apiName);
dataField.setIsHtmlFormatted(true);
Map<String, Object> htmlMap = Maps.newHashMap();
htmlMap.put("type", "text");
htmlMap.put("comment", field.getLabel()+ "备份字段");
htmlMap.put("name", field.getName()+ "_back");
list.add(htmlMap);
}
if (field.getReferenceTo().length > 1){
if (field.getName().contains("Id") && !"OwnerId".equals(field.getName()) && !"UserOrGroupId".equals(field.getName())){
LinkConfig linkConfig = new LinkConfig();
@ -1337,6 +1343,7 @@ public class CommonServiceImpl implements CommonService {
String blobField = null;
List<DataPicklist> dataPicklists = Lists.newArrayList();
for (Field field : dsr.getFields()) {
// 过滤字段
if (Const.TABLE_FILTERS.contains(field.getName())) {
continue;
@ -1356,48 +1363,47 @@ public class CommonServiceImpl implements CommonService {
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);
dataField.setReferenceTo(field.getReferenceTo() != null && field.getReferenceTo().length > 0 ? field.getReferenceTo()[0] : null);
dataField.setLeftIndex(field.getLeftSideReferenceTo() != null && field.getLeftSideReferenceTo().length > 0 ? field.getLeftSideReferenceTo()[0] : null);
dataField.setRightIndex(field.getRightSideReferenceTo() != null && field.getRightSideReferenceTo().length > 0 ? field.getRightSideReferenceTo()[0] : null);
dataField.setOnList(field.isListVisible());
dataField.setSfType(sfType);
dataField.setCreateable(field.isCreateable());
dataField.setUpdateable(field.isUpdateable());
dataField.setHtmlFormatted(field.isHtmlFormatted());
dataField.setNillable(field.isNillable());
dataField.setDefaultedOnCreate(field.isDefaultedOnCreate());
fieldList.add(dataField);
fields.add(field.getName());
// 如果是富文本字段添加备份字段
if (field.isHtmlFormatted()) {
String backupFieldName = field.getName() + "_back";
Map<String, Object> backupMap = Maps.newHashMap();
backupMap.put("type", DataUtil.fieldTypeToMysql(field));
backupMap.put("comment", field.getLabel() + " 备份");
backupMap.put("name", backupFieldName);
list.add(backupMap);
DataField backupDataField = new DataField();
backupDataField.setApi(apiName);
backupDataField.setField(backupFieldName);
backupDataField.setName(field.getLabel() + " 备份");
backupDataField.setReferenceTo(field.getReferenceTo() != null && field.getReferenceTo().length > 0 ? field.getReferenceTo()[0] : null);
backupDataField.setLeftIndex(field.getLeftSideReferenceTo() != null && field.getLeftSideReferenceTo().length > 0 ? field.getLeftSideReferenceTo()[0] : null);
backupDataField.setRightIndex(field.getRightSideReferenceTo() != null && field.getRightSideReferenceTo().length > 0 ? field.getRightSideReferenceTo()[0] : null);
backupDataField.setOnList(field.isListVisible());
backupDataField.setSfType(sfType);
backupDataField.setCreateable(field.isCreateable());
backupDataField.setUpdateable(field.isUpdateable());
backupDataField.setHtmlFormatted(field.isHtmlFormatted());
backupDataField.setNillable(field.isNillable());
backupDataField.setDefaultedOnCreate(field.isDefaultedOnCreate());
fieldList.add(backupDataField);
fields.add(backupFieldName);
}
}
// 存在ownerId字段
String extraField = "";

View File

@ -93,6 +93,9 @@ public class DataImportNewServiceImpl implements DataImportNewService {
@Autowired
private DataLogService dataLogService;
@Autowired
private RichTextLogService richTextLogService;
private static final String TEMP_FILE_PATH = "verify/";
static {
@ -2637,85 +2640,99 @@ public class DataImportNewServiceImpl implements DataImportNewService {
@Override
public ReturnT<String> richTextImageReplace(SalesforceParam param) throws Exception {
log.info("richTextImageReplace execute start ..................");
String beginDateStr = null;
String endDateStr = null;
if (param.getType() != 1){
Date beginDate = param.getBeginCreateDate();
Date endDate = param.getEndCreateDate();
if (param.getBeginCreateDate() != null && param.getEndCreateDate() != null){
beginDateStr = DateUtil.format(beginDate, "yyyy-MM-dd HH:mm:ss");
endDateStr = DateUtil.format(endDate, "yyyy-MM-dd HH:mm:ss");
}
}
// 获取连接
PartnerConnection sourceConnection = salesforceConnect.createConnect();
PartnerConnection targetConnection = salesforceTargetConnect.createConnect();
String api = param.getApi();
String field = null;
if (StringUtils.isBlank(api)) {
return new ReturnT<>(500, "api不能为空");
QueryWrapper<DataObject> qw = new QueryWrapper<>();
if (StringUtils.isNotBlank(param.getApi())) {
List<String> apis = DataUtil.toIdList(param.getApi());
qw.in("name", apis);
}
qw.eq("data_work", true);
List<DataObject> objectList = dataObjectService.list(qw);
if (StringUtils.isBlank(field)) {
return new ReturnT<>(500, "field不能为空");
}
for (DataObject dataObject : objectList) {
String api = dataObject.getName();
QueryWrapper<DataField> qw1 = new QueryWrapper<>();
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%'";
String beginDateStr = null;
String endDateStr = null;
Date beginDate = param.getBeginCreateDate();
Date endDate = param.getEndCreateDate();
if (param.getBeginCreateDate() != null && param.getEndCreateDate() != null){
beginDateStr = DateUtil.format(beginDate, "yyyy-MM-dd HH:mm:ss");
endDateStr = DateUtil.format(endDate, "yyyy-MM-dd HH:mm:ss");
}
if (StringUtils.isNotEmpty(beginDateStr) && StringUtils.isNotEmpty(endDateStr)) {
sqlCondition += " and CreatedDate >= '" + beginDateStr + "' and CreatedDate < '" + endDateStr + "'";
}
String sqlCondition = "new_id is not null and " + field + " like '%<img%refid%'";
if (StringUtils.isNotEmpty(beginDateStr) && StringUtils.isNotEmpty(endDateStr)) {
sqlCondition += " and CreatedDate >= '" + beginDateStr + "' and CreatedDate < '" + endDateStr + "'";
}
// 获取记录总数
Integer count = customMapper.countBySQL(api, "where " + sqlCondition);
log.info("对象 {} 字段 {} 需要处理的富文本记录总数: {}", api, field, count);
// 获取记录总数
Integer count = customMapper.countBySQL(api, "where " + sqlCondition);
log.info("需要处理的富文本记录总数: {}", count);
if (count == 0) {
log.info("对象 {} 字段 {} 没有需要处理的富文本记录", api, field);
continue;
}
if (count == 0) {
log.info("没有需要处理的富文本记录");
return ReturnT.SUCCESS;
}
// 分批处理每批200条记录
int pageSize = 200;
int totalPages = (count + pageSize - 1) / pageSize;
// 分批处理每批200条记录
int pageSize = 200;
int totalPages = (count + pageSize - 1) / pageSize;
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);
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);
log.info("处理对象 {} 字段 {} 第 {}/{} 页,本页记录数: {}", api, field, (page + 1), totalPages, records.size());
log.info("处理第 {}/{} 页,本页记录数: {}", (page + 1), totalPages, records.size());
for (Map<String, Object> record : records) {
try {
String sourceId = (String) record.get("Id");
String targetId = (String) record.get("new_id");
String richTextContent = (String) record.get(field);
for (Map<String, Object> record : records) {
try {
String sourceId = (String) record.get("Id");
String targetId = (String) record.get("new_id");
String richTextContent = (String) record.get(field);
if (StringUtils.isBlank(richTextContent) || !richTextContent.contains("refid")) {
continue;
}
if (StringUtils.isBlank(richTextContent) || !richTextContent.contains("refid")) {
continue;
}
// 处理富文本中的图片
String updatedContent = processRichTextImages(richTextContent, sourceId, api, field, sourceConnection, targetConnection);
// 如果内容有变化则更新目标组织中的记录
if (!richTextContent.equals(updatedContent)) {
// 更新目标组织中的记录
SObject sObject = new SObject();
sObject.setType(api);
sObject.setId(targetId);
sObject.setField(field, updatedContent);
// 同时更新备份字段
sObject.setField(field + "_back", richTextContent);
// 处理富文本中的图片
String updatedContent = null;
// 如果内容有变化则更新目标组织中的记录
if (!richTextContent.equals(updatedContent)) {
// 更新目标组织中的记录
SObject sObject = new SObject();
sObject.setType(api);
sObject.setId(targetId);
sObject.setField(field, updatedContent);
SaveResult[] results = targetConnection.update(new SObject[]{sObject});
if (results != null && results.length > 0 && results[0].getSuccess()) {
log.info("成功更新记录 {} 的富文本内容", targetId);
} else {
log.error("更新记录 {} 的富文本内容失败: {}", targetId,
results != null && results.length > 0 ? JSON.toJSONString(results[0].getErrors()) : "未知错误");
SaveResult[] results = targetConnection.update(new SObject[]{sObject});
if (results != null && results.length > 0 && results[0].getSuccess()) {
log.info("成功更新记录 {} 的富文本内容", targetId);
} else {
log.error("更新记录 {} 的富文本内容失败: {}", targetId,
results != null && results.length > 0 ? JSON.toJSONString(results[0].getErrors()) : "未知错误");
}
}
} catch (Exception e) {
log.error("处理记录 {} 的富文本内容时发生错误: {}", record.get("Id"), e.getMessage(), e);
}
}
} catch (Exception e) {
log.error("处理记录 {} 的富文本内容时发生错误: {}", record.get("Id"), e.getMessage(), e);
}
}
}
@ -2724,105 +2741,6 @@ public class DataImportNewServiceImpl implements DataImportNewService {
return ReturnT.SUCCESS;
}
/**
* 处理富文本中的图片
* @param richTextContent 原始富文本内容
* @param recordId 记录ID
* @param api 对象API名称
* @param field 字段名
* @param sourceConnection 源组织连接
* @param targetConnection 目标组织连接
* @return 处理后的富文本内容
* @throws Exception 异常
*/
// private String processRichTextImages(String richTextContent, String recordId, String api, String field,
// PartnerConnection sourceConnection, PartnerConnection targetConnection) throws Exception {
// // 使用正则表达式匹配<img>标签
// Pattern imgPattern = Pattern.compile("<img[^>]*refid=\\\"([^\"]*)\\\"[^>]*/?>", Pattern.CASE_INSENSITIVE);
// Matcher matcher = imgPattern.matcher(richTextContent);
//
// StringBuffer result = new StringBuffer();
// boolean hasReplacements = false;
//
// while (matcher.find()) {
// String imgTag = matcher.group(0);
// String refId = matcher.group(1);
//
// try {
// // 从源组织下载图片
// String sourceSessionId = sourceConnection.getSessionHeader().getSessionId();
// String sourceInstanceUrl = sourceConnection.getConfig().getServiceEndpoint().replace("/services/Soap/u/50.0", "");
//
// String imageUrl = sourceInstanceUrl + String.format(Const.SF_RICH_TEXT_FILE_URL, api, recordId, field, refId);
//
// // 构建请求头
// Map<String, String> headers = new HashMap<>();
// headers.put("Authorization", "Bearer " + sourceSessionId);
//
// // 下载图片
// Response response = HttpUtil.doGet(imageUrl, null, headers);
// if (response.code() == 200 && response.body() != null) {
// // 将图片转换为Base64
// byte[] imageBytes = response.body().bytes();
// String base64Image = Base64.getEncoder().encodeToString(imageBytes);
//
// // 在目标组织中上传图片
// String targetSessionId = targetConnection.getSessionHeader().getSessionId();
// String targetInstanceUrl = targetConnection.getConfig().getServiceEndpoint().replace("/services/Soap/u/50.0", "");
//
// // 创建ContentVersion来存储图片
// JSONObject contentVersion = new JSONObject();
// contentVersion.put("Title", "RichTextImage_" + refId);
// contentVersion.put("PathOnClient", "image.jpg");
// contentVersion.put("VersionData", base64Image);
//
// // 上传到目标组织
// String uploadUrl = targetInstanceUrl + "/services/data/v55.0/sobjects/ContentVersion/";
// Map<String, String> targetHeaders = new HashMap<>();
// targetHeaders.put("Authorization", "Bearer " + targetSessionId);
// targetHeaders.put("Content-Type", "application/json");
//
// Response uploadResponse = HttpUtil.doPost(uploadUrl, contentVersion.toJSONString(), targetHeaders);
// if (uploadResponse.code() == 201 || uploadResponse.code() == 200) {
// // 获取上传后的ContentVersion ID
// String responseContent = uploadResponse.body().string();
// JSONObject jsonResponse = JSON.parseObject(responseContent);
// String contentVersionId = jsonResponse.getString("id");
//
// // 获取ContentDocumentId
// String contentDocumentId = getDocumentId(targetConnection, contentVersionId);
//
// // 生成新的图片URL
// String newImgUrl = targetInstanceUrl + "/sfc/servlet.shepherd/document/download/" + contentDocumentId;
// String newImgTag = imgTag.replaceFirst("src\\s*=\\s*\"[^"]*\"", "src=\"" + newImgUrl + "\"")
// .replaceFirst("refid\\s*=\\s*\"[^"]*\"", "");
//
// matcher.appendReplacement(result, Matcher.quoteReplacement(newImgTag));
// hasReplacements = true;
//
// log.info("成功处理图片 refId: {}", refId);
// } else {
// log.error("上传图片到目标组织失败refId: {}, 状态码: {}, 响应: {}",
// refId, uploadResponse.code(), uploadResponse.message());
// matcher.appendReplacement(result, Matcher.quoteReplacement(imgTag));
// }
// } else {
// log.error("从源组织下载图片失败refId: {}, 状态码: {}", refId, response.code());
// matcher.appendReplacement(result, Matcher.quoteReplacement(imgTag));
// }
// } catch (Exception e) {
// log.error("处理图片 refId: {} 时发生错误: {}", refId, e.getMessage(), e);
// matcher.appendReplacement(result, Matcher.quoteReplacement(imgTag));
// }
// }
//
// matcher.appendTail(result);
//
// // 如果有替换则返回新内容否则返回原始内容
// return hasReplacements ? result.toString() : richTextContent;
// }
//
/**
* 处理富文本中的图片
* @param richTextContent 原始富文本内容
@ -2836,89 +2754,88 @@ public class DataImportNewServiceImpl implements DataImportNewService {
*/
private String processRichTextImages(String richTextContent, String recordId, String api, String field,
PartnerConnection sourceConnection, PartnerConnection targetConnection) throws Exception {
// 使用正则表达式匹配<img>标签
Pattern imgPattern = Pattern.compile("<img[^>]*refid=\\\"([^\"]*)\\\"[^>]*/?>", Pattern.CASE_INSENSITIVE);
// 匹配富文本中包含refid的img标签
Pattern imgPattern = Pattern.compile("<img[^>]*refid=\"([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE);
Matcher matcher = imgPattern.matcher(richTextContent);
StringBuffer result = new StringBuffer();
boolean hasReplacements = false;
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);
String updatedImgTag = imgTag.replace("refid=\"" + refId + "\"", "refid=\"" + newRefId + "\"");
matcher.appendReplacement(updatedContent, Matcher.quoteReplacement(updatedImgTag));
continue;
}
try {
// 从源组织下载图片
String sourceSessionId = sourceConnection.getSessionHeader().getSessionId();
String sourceInstanceUrl = sourceConnection.getConfig().getServiceEndpoint().replace("/services/Soap/u/50.0", "");
String imageUrl = sourceInstanceUrl + String.format(Const.SF_RICH_TEXT_FILE_URL, api, recordId, field, refId);
// 构建请求头
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + sourceSessionId);
// 下载图片
Response response = HttpUtil.doGet(imageUrl, null, headers);
if (response.code() == 200 && response.body() != null) {
// 将图片转换为Base64
byte[] imageBytes = response.body().bytes();
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
// 在目标组织中上传图片
String targetSessionId = targetConnection.getSessionHeader().getSessionId();
String targetInstanceUrl = targetConnection.getConfig().getServiceEndpoint().replace("/services/Soap/u/50.0", "");
// 创建ContentVersion来存储图片
JSONObject contentVersion = new JSONObject();
contentVersion.put("Title", "RichTextImage_" + refId);
contentVersion.put("PathOnClient", "image.jpg");
contentVersion.put("VersionData", base64Image);
// 上传到目标组织
String uploadUrl = targetInstanceUrl + "/services/data/v55.0/sobjects/ContentVersion/";
Map<String, String> targetHeaders = new HashMap<>();
targetHeaders.put("Authorization", "Bearer " + targetSessionId);
targetHeaders.put("Content-Type", "application/json");
Response uploadResponse = HttpUtil.doPost(uploadUrl, contentVersion.toJSONString(), targetHeaders);
if (uploadResponse.code() == 201 || uploadResponse.code() == 200) {
// 获取上传后的ContentVersion ID
String responseContent = uploadResponse.body().string();
JSONObject jsonResponse = null;
String contentVersionId = jsonResponse.getString("id");
// 获取ContentDocumentId
String contentDocumentId = getDocumentId(targetConnection, contentVersionId);
// 生成新的图片URL
String newImgUrl = targetInstanceUrl + "/sfc/servlet.shepherd/document/download/" + contentDocumentId;
String newImgTag = imgTag.replaceFirst("src\\s*=\\s*\"[^\"]*\"", "src=\"" + newImgUrl + "\"")
.replaceFirst("refid\\s*=\\s*\"[^\"]*\"", "");
matcher.appendReplacement(result, Matcher.quoteReplacement(newImgTag));
hasReplacements = true;
log.info("成功处理图片 refId: {}", refId);
} else {
log.error("上传图片到目标组织失败refId: {}, 状态码: {}, 响应: {}",
refId, uploadResponse.code(), uploadResponse.message());
matcher.appendReplacement(result, Matcher.quoteReplacement(imgTag));
// 查询源组织中的ContentDocumentLink记录
String cdlQuery = "SELECT Id, ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = '" + recordId + "' 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;
}
}
} else {
log.error("从源组织下载图片失败refId: {}, 状态码: {}", refId, response.code());
matcher.appendReplacement(result, Matcher.quoteReplacement(imgTag));
}
// 如果找不到对应关系保留原始标签
matcher.appendReplacement(updatedContent, Matcher.quoteReplacement(imgTag));
} catch (Exception e) {
log.error("处理图片 refId: {} 时发生错误: {}", refId, e.getMessage(), e);
matcher.appendReplacement(result, Matcher.quoteReplacement(imgTag));
log.error("处理富文本图片时发生错误refId: {}", refId, e);
matcher.appendReplacement(updatedContent, Matcher.quoteReplacement(imgTag));
}
}
matcher.appendTail(result);
// 如果有替换则返回新内容否则返回原始内容
return hasReplacements ? result.toString() : richTextContent;
matcher.appendTail(updatedContent);
return updatedContent.toString();
}
/**
* 获取ContentVersion ID
* @param connection Salesforce连接
* @param contentDocumentId ContentDocument ID
* @return ContentVersion ID
* @throws Exception 异常
*/
private String getContentVersionId(PartnerConnection connection, String contentDocumentId) throws Exception {
String query = "SELECT Id FROM ContentVersion WHERE ContentDocumentId = '" + contentDocumentId + "' ORDER BY CreatedDate DESC LIMIT 1";
QueryResult result = connection.query(query);
if (result.getRecords() != null && result.getRecords().length > 0) {
return (String) result.getRecords()[0].getField("Id");
}
return null;
}
/**
* 获取目标组织中的ContentDocumentId
* @param connection 目标组织连接
* @param contentVersionId ContentVersion ID
* @return 目标组织中的ContentDocumentId
* @throws Exception 异常
*/
private String getTargetContentDocumentId(PartnerConnection connection, String contentVersionId) throws Exception {
String query = "SELECT ContentDocumentId FROM ContentVersion WHERE Id = '" + contentVersionId + "'";
QueryResult result = connection.query(query);
if (result.getRecords() != null && result.getRecords().length > 0) {
return (String) result.getRecords()[0].getField("ContentDocumentId");
}
return null;
}
/**

View File

@ -0,0 +1,35 @@
package com.celnet.datadump.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.celnet.datadump.entity.RichTextLog;
import com.celnet.datadump.mapper.RichTextLogMapper;
import com.celnet.datadump.service.RichTextLogService;
import org.springframework.stereotype.Service;
/**
* <p>
* 富文本替换记录表 服务实现类
* </p>
*
* @author Lingma
* @since 2025-10-10
*/
@Service
public class RichTextLogServiceImpl extends ServiceImpl<RichTextLogMapper, RichTextLog> implements RichTextLogService {
@Override
public void logRichTextImageReplace(String api, String field, String recordId, String recordOwnerId,
String originalRichText, String originalImageUrl,
String newImageUrl, String newFileId) {
RichTextLog log = new RichTextLog();
log.setApi(api);
log.setField(field);
log.setRecordId(recordId);
log.setRecordOwnerId(recordOwnerId);
log.setOriginalRichText(originalRichText);
log.setOriginalImageUrl(originalImageUrl);
log.setNewImageUrl(newImageUrl);
log.setNewFileId(newFileId);
this.save(log);
}
}