【feat】 提交元数据变更拉取任务
This commit is contained in:
parent
da9ba42769
commit
547b2c6436
@ -89,6 +89,19 @@ public class BaseController
|
||||
return rspData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应请求分页数据
|
||||
*/
|
||||
protected TableDataInfo getDataTableByPage(List<?> list,long total)
|
||||
{
|
||||
TableDataInfo rspData = new TableDataInfo();
|
||||
rspData.setCode(HttpStatus.SUCCESS);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setRows(list);
|
||||
rspData.setTotal(total);
|
||||
return rspData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回成功
|
||||
*/
|
||||
|
||||
@ -55,7 +55,7 @@ public class ${ClassName}Controller extends BaseController
|
||||
startPage();
|
||||
List<${ClassName}> list = ${className}Service.select${ClassName}List(${ClassName}Dto.toObj(${className}Dto));
|
||||
List<${ClassName}Vo> voList = list.stream().map(${ClassName}Vo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTable(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
#elseif($table.tree)
|
||||
public AjaxResult list(${ClassName}Dto ${className}Dto)
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.auth.model.vo.DataiSfLoginHistoryVo;
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -52,7 +53,7 @@ public class DataiSfLoginHistoryController extends BaseController
|
||||
startPage();
|
||||
List<DataiSfLoginHistory> list = dataiSfLoginHistoryService.selectDataiSfLoginHistoryList(DataiSfLoginHistoryDto.toObj(dataiSfLoginHistoryDto));
|
||||
List<DataiSfLoginHistoryVo> voList = list.stream().map(DataiSfLoginHistoryVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.auth.model.vo.DataiSfLoginSessionVo;
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -52,7 +53,7 @@ public class DataiSfLoginSessionController extends BaseController
|
||||
startPage();
|
||||
List<DataiSfLoginSession> list = dataiSfLoginSessionService.selectDataiSfLoginSessionList(DataiSfLoginSessionDto.toObj(dataiSfLoginSessionDto));
|
||||
List<DataiSfLoginSessionVo> voList = list.stream().map(DataiSfLoginSessionVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -20,7 +20,8 @@ public class SalesforceLoginResult implements Serializable {
|
||||
private String refreshToken; // 仅在 OAuth 流程中存在
|
||||
private String tokenType = "Bearer";
|
||||
private long expiresIn; // 过期时间(秒)
|
||||
private long loginTimestamp = System.currentTimeMillis(); // 记录登录时间点
|
||||
private long loginTimestamp; // 记录登录时间点
|
||||
private long expirationTimestamp; // 记录过期时间点(登录时间 + 过期时长)
|
||||
|
||||
/** 环境信息 */
|
||||
private String instanceUrl; // API 访问基础地址
|
||||
@ -49,9 +50,10 @@ public class SalesforceLoginResult implements Serializable {
|
||||
* 提前 5 分钟判断为过期,以预留网络传输时间
|
||||
*/
|
||||
public boolean isSessionExpired() {
|
||||
if (expiresIn <= 0) return false; // 如果永不过期
|
||||
long bufferMillis = 300 * 1000; // 5分钟缓冲
|
||||
return System.currentTimeMillis() > (loginTimestamp + (expiresIn * 1000) - bufferMillis);
|
||||
if (expiresIn <= 0) return false;
|
||||
if (expirationTimestamp <= 0) return false;
|
||||
long bufferMillis = 300 * 1000;
|
||||
return System.currentTimeMillis() > (expirationTimestamp - bufferMillis);
|
||||
}
|
||||
|
||||
// --- 增强的 Getter 和 Setter ---
|
||||
@ -110,6 +112,12 @@ public class SalesforceLoginResult implements Serializable {
|
||||
public String getTimeZone() { return timeZone; }
|
||||
public void setTimeZone(String timeZone) { this.timeZone = timeZone; }
|
||||
|
||||
public long getLoginTimestamp() { return loginTimestamp; }
|
||||
public void setLoginTimestamp(long loginTimestamp) { this.loginTimestamp = loginTimestamp; }
|
||||
|
||||
public long getExpirationTimestamp() { return expirationTimestamp; }
|
||||
public void setExpirationTimestamp(long expirationTimestamp) { this.expirationTimestamp = expirationTimestamp; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SalesforceLoginResult{" +
|
||||
|
||||
@ -550,6 +550,12 @@ public class LegacyCredentialLoginStrategy implements LoginStrategy {
|
||||
|
||||
// 4. 后置处理:设置默认过期时间和 Token 类型
|
||||
fillDefaultExpiration(result);
|
||||
|
||||
// 5. 设置登录时间戳和过期时间点
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
result.setLoginTimestamp(currentTimestamp);
|
||||
result.setExpirationTimestamp(currentTimestamp + (result.getExpiresIn() * 1000));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -617,7 +623,7 @@ public class LegacyCredentialLoginStrategy implements LoginStrategy {
|
||||
result.setTokenType("Bearer");
|
||||
}
|
||||
|
||||
long expiresIn = 7200L; // 默认值
|
||||
long expiresIn = 6900L; // 默认值
|
||||
|
||||
if (result.getExpiresIn() == 0) {
|
||||
try {
|
||||
@ -627,7 +633,7 @@ public class LegacyCredentialLoginStrategy implements LoginStrategy {
|
||||
.map(c -> CacheUtils.get(salesforceConfigCacheManager.getEnvironmentCacheKey(), "salesforce.session.timeout", String.class))
|
||||
.filter(StringUtils::isNumeric) // 确保是数字
|
||||
.map(Long::parseLong)
|
||||
.orElse(7200L);
|
||||
.orElse(6900L);
|
||||
} catch (Exception e) {
|
||||
logger.warn("读取Salesforce会话超时配置失败,使用默认值: 7200", e);
|
||||
}
|
||||
|
||||
@ -340,6 +340,11 @@ public class OAuth2LoginStrategy implements LoginStrategy {
|
||||
result.setRefreshToken(jsonResponse.get("refresh_token").asText());
|
||||
}
|
||||
|
||||
// 设置登录时间戳和过期时间点
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
result.setLoginTimestamp(currentTimestamp);
|
||||
result.setExpirationTimestamp(currentTimestamp + (result.getExpiresIn() * 1000));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -377,6 +382,11 @@ public class OAuth2LoginStrategy implements LoginStrategy {
|
||||
result.setOrganizationId(jsonResponse.get("organization_id").asText());
|
||||
}
|
||||
|
||||
// 设置登录时间戳和过期时间点
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
result.setLoginTimestamp(currentTimestamp);
|
||||
result.setExpirationTimestamp(currentTimestamp + (result.getExpiresIn() * 1000));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -461,6 +471,11 @@ public class OAuth2LoginStrategy implements LoginStrategy {
|
||||
STATE_CODE_VERIFIER_MAP.remove(state);
|
||||
STATE_TIMESTAMP_MAP.remove(state);
|
||||
|
||||
// 设置登录时间戳和过期时间点
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
result.setLoginTimestamp(currentTimestamp);
|
||||
result.setExpirationTimestamp(currentTimestamp + (result.getExpiresIn() * 1000));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,11 @@ public class SalesforceCliLoginStrategy implements LoginStrategy {
|
||||
// 3. 解析CLI输出,获取Session信息
|
||||
result = parseCliOutput(output);
|
||||
result.setSuccess(true);
|
||||
|
||||
// 4. 设置登录时间戳和过期时间点
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
result.setLoginTimestamp(currentTimestamp);
|
||||
result.setExpirationTimestamp(currentTimestamp + (result.getExpiresIn() * 1000));
|
||||
} catch (Exception e) {
|
||||
logger.error("Salesforce CLI登录失败: {}", e.getMessage(), e);
|
||||
result.setSuccess(false);
|
||||
|
||||
@ -80,6 +80,11 @@ public class SessionIdLoginStrategy implements LoginStrategy {
|
||||
result.setTokenType("Bearer");
|
||||
result.setExpiresIn(getSessionTimeout(config));
|
||||
|
||||
// 设置登录时间戳和过期时间点
|
||||
long currentTimestamp = System.currentTimeMillis();
|
||||
result.setLoginTimestamp(currentTimestamp);
|
||||
result.setExpirationTimestamp(currentTimestamp + (result.getExpiresIn() * 1000));
|
||||
|
||||
logger.info("Session ID登录成功,用户ID: {}", userInfo.get("user_id"));
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationApiCallLogVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -56,7 +57,7 @@ public class DataiIntegrationApiCallLogController extends BaseController
|
||||
List<DataiIntegrationApiCallLogVo> voList = list.stream()
|
||||
.map(DataiIntegrationApiCallLogVo::objToVo)
|
||||
.collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationBatchVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -56,7 +57,7 @@ public class DataiIntegrationBatchController extends BaseController
|
||||
List<DataiIntegrationBatchVo> voList = list.stream()
|
||||
.map(DataiIntegrationBatchVo::objToVo)
|
||||
.collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationBatchHistoryVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -57,7 +58,7 @@ public class DataiIntegrationBatchHistoryController extends BaseController
|
||||
List<DataiIntegrationBatchHistoryVo> voList = list.stream()
|
||||
.map(DataiIntegrationBatchHistoryVo::objToVo)
|
||||
.collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.datai.integration.controller;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationFieldVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -55,7 +56,7 @@ public class DataiIntegrationFieldController extends BaseController
|
||||
List<DataiIntegrationFieldVo> voList = list.stream()
|
||||
.map(DataiIntegrationFieldVo::objToVo)
|
||||
.collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.datai.integration.controller;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationFilterLookupVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -53,7 +54,7 @@ public class DataiIntegrationFilterLookupController extends BaseController
|
||||
DataiIntegrationFilterLookup dataiIntegrationFilterLookup = DataiIntegrationFilterLookupDto.toObj(dataiIntegrationFilterLookupDto);
|
||||
List<DataiIntegrationFilterLookup> list = dataiIntegrationFilterLookupService.selectDataiIntegrationFilterLookupList(dataiIntegrationFilterLookup);
|
||||
List<DataiIntegrationFilterLookupVo> voList = list.stream().map(DataiIntegrationFilterLookupVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.datai.integration.controller;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.domain.DataiIntegrationMetadataChange;
|
||||
import com.datai.integration.model.vo.DataiIntegrationMetadataChangeVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@ -55,7 +56,7 @@ public class DataiIntegrationMetadataChangeController extends BaseController
|
||||
DataiIntegrationMetadataChange dataiIntegrationMetadataChange = DataiIntegrationMetadataChangeDto.toObj(dataiIntegrationMetadataChangeDto);
|
||||
List<DataiIntegrationMetadataChange> list = dataiIntegrationMetadataChangeService.selectDataiIntegrationMetadataChangeList(dataiIntegrationMetadataChange);
|
||||
List<DataiIntegrationMetadataChangeVo> voList = list.stream().map(DataiIntegrationMetadataChangeVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,7 +71,7 @@ public class DataiIntegrationMetadataChangeController extends BaseController
|
||||
DataiIntegrationMetadataChange dataiIntegrationMetadataChange = DataiIntegrationMetadataChangeDto.toObj(dataiIntegrationMetadataChangeDto);
|
||||
List<DataiIntegrationMetadataChange> list = dataiIntegrationMetadataChangeService.selectUnsyncedMetadataChangeList(dataiIntegrationMetadataChange);
|
||||
List<DataiIntegrationMetadataChangeVo> voList = list.stream().map(DataiIntegrationMetadataChangeVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,12 +234,21 @@ public class DataiIntegrationMetadataChangeController extends BaseController
|
||||
/**
|
||||
* 全对象元数据变更拉取
|
||||
* 从Salesforce拉取所有对象的元数据变更信息并记录到元数据变更表中
|
||||
* 该方法会:
|
||||
* 1. 连接到Salesforce获取所有对象的元数据
|
||||
* 2. 比较现有数据库中的对象元数据
|
||||
* 3. 记录对象级别的变更(新增、修改、删除)
|
||||
* 4. 记录字段级别的变更(新增、修改、删除)
|
||||
* 5. 检测并记录已从Salesforce中删除的对象
|
||||
*
|
||||
* 表的变更新增需要满足以下任一条件:
|
||||
* - isQueryable (可查询)
|
||||
* - isCreateable (可创建)
|
||||
* - isUpdateable (可更新)
|
||||
* - isDeletable (可删除)
|
||||
* 字段的变更新增无限制
|
||||
*
|
||||
* @return 包含拉取结果的AjaxResult,包含对象变更数量和字段变更数量
|
||||
*/
|
||||
@Operation(summary = "全对象元数据变更拉取")
|
||||
@PreAuthorize("@ss.hasPermi('integration:change:pullAll')")
|
||||
@ -247,9 +257,11 @@ public class DataiIntegrationMetadataChangeController extends BaseController
|
||||
public AjaxResult pullAllMetadataChanges()
|
||||
{
|
||||
try {
|
||||
// 调用服务层方法执行全对象元数据变更拉取
|
||||
Map<String, Object> result = dataiIntegrationMetadataChangeService.pullAllMetadataChanges();
|
||||
return success(result);
|
||||
} catch (Exception e) {
|
||||
// 记录拉取过程中发生的异常
|
||||
log.error("全对象元数据变更拉取时发生异常", e);
|
||||
return error("全对象元数据变更拉取失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationObjectVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -54,7 +55,7 @@ public class DataiIntegrationObjectController extends BaseController
|
||||
DataiIntegrationObject dataiIntegrationObject = DataiIntegrationObjectDto.toObj(dataiIntegrationObjectDto);
|
||||
List<DataiIntegrationObject> list = dataiIntegrationObjectService.selectDataiIntegrationObjectList(dataiIntegrationObject);
|
||||
List<DataiIntegrationObjectVo> voList = list.stream().map(DataiIntegrationObjectVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.datai.integration.controller;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationPicklistVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -53,7 +54,7 @@ public class DataiIntegrationPicklistController extends BaseController
|
||||
DataiIntegrationPicklist dataiIntegrationPicklist = DataiIntegrationPicklistDto.toObj(dataiIntegrationPicklistDto);
|
||||
List<DataiIntegrationPicklist> list = dataiIntegrationPicklistService.selectDataiIntegrationPicklistList(dataiIntegrationPicklist);
|
||||
List<DataiIntegrationPicklistVo> voList = list.stream().map(DataiIntegrationPicklistVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.datai.integration.controller;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.integration.model.vo.DataiIntegrationRateLimitVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -53,7 +54,7 @@ public class DataiIntegrationRateLimitController extends BaseController
|
||||
DataiIntegrationRateLimit dataiIntegrationRateLimit = DataiIntegrationRateLimitDto.toObj(dataiIntegrationRateLimitDto);
|
||||
List<DataiIntegrationRateLimit> list = dataiIntegrationRateLimitService.selectDataiIntegrationRateLimitList(dataiIntegrationRateLimit);
|
||||
List<DataiIntegrationRateLimitVo> voList = list.stream().map(DataiIntegrationRateLimitVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.datai.integration.core;
|
||||
|
||||
import com.datai.auth.model.domain.DataiSfLoginHistory;
|
||||
import com.datai.auth.service.IDataiSfLoginHistoryService;
|
||||
import com.datai.common.core.domain.AjaxResult;
|
||||
import com.datai.salesforce.common.constant.SalesforceConfigConstants;
|
||||
import com.datai.auth.model.domain.SalesforceLoginResult;
|
||||
import com.datai.auth.service.ISalesforceLoginService;
|
||||
@ -21,6 +24,9 @@ public class SessionManager {
|
||||
@Autowired
|
||||
private ISalesforceLoginService salesforceLoginService;
|
||||
|
||||
@Autowired
|
||||
private IDataiSfLoginHistoryService dataiSfLoginHistoryService;
|
||||
|
||||
/**
|
||||
* 获取当前Salesforce会话信息
|
||||
* 如果会话无效,则自动尝试重新登录
|
||||
@ -34,20 +40,13 @@ public class SessionManager {
|
||||
try {
|
||||
SalesforceLoginResult loginResult = CacheUtils.get(SalesforceConfigConstants.CACHE_NAME, SalesforceConfigConstants.CURRENT_RESULT, SalesforceLoginResult.class);
|
||||
|
||||
if (loginResult != null && loginResult.isSuccess() && !loginResult.isSessionExpired()) {
|
||||
if (isLoginResultValid(loginResult)) {
|
||||
log.info("获取会话信息成功,访问令牌前缀: {}",
|
||||
loginResult.getSessionId() != null ? loginResult.getSessionId().substring(0, Math.min(10, loginResult.getSessionId().length())) : "null");
|
||||
return loginResult;
|
||||
}
|
||||
|
||||
if (loginResult == null) {
|
||||
log.warn("会话信息为null,尝试自动重新登录");
|
||||
} else if (!loginResult.isSuccess()) {
|
||||
log.warn("登录状态为失败,尝试自动重新登录,错误信息: {}", loginResult.getErrorMessage());
|
||||
} else if (loginResult.isSessionExpired()) {
|
||||
log.warn("Session已过期,尝试自动重新登录");
|
||||
}
|
||||
|
||||
logSessionStatus(loginResult);
|
||||
return autoLogin();
|
||||
|
||||
} catch (Exception e) {
|
||||
@ -66,7 +65,15 @@ public class SessionManager {
|
||||
log.info("开始执行自动重新登录");
|
||||
|
||||
try {
|
||||
SalesforceLoginResult result = salesforceLoginService.autoLogin(null);
|
||||
DataiSfLoginHistory latestLoginHistory = dataiSfLoginHistoryService.selectLatestSuccessLoginHistory();
|
||||
|
||||
if (latestLoginHistory == null) {
|
||||
throw new RuntimeException("未找到登录历史记录" );
|
||||
}
|
||||
|
||||
Long historyId = latestLoginHistory.getId();
|
||||
|
||||
SalesforceLoginResult result = salesforceLoginService.autoLogin(historyId);
|
||||
|
||||
if (result == null || !result.isSuccess()) {
|
||||
String errorMsg = result != null ? result.getErrorMessage() : "登录结果为空";
|
||||
@ -126,36 +133,39 @@ public class SessionManager {
|
||||
log.info("检查当前Salesforce会话信息是否有效");
|
||||
|
||||
try {
|
||||
SalesforceLoginResult loginResult = CacheUtils.get(SalesforceConfigConstants.CACHE_NAME, SalesforceConfigConstants.CURRENT_RESULT, SalesforceLoginResult.class);
|
||||
|
||||
if (loginResult == null) {
|
||||
log.warn("会话信息为null,会话无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loginResult.isSuccess()) {
|
||||
log.warn("登录状态为失败,会话无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loginResult.getSessionId() == null || loginResult.getSessionId().trim().isEmpty()) {
|
||||
log.warn("Session ID为空,会话无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loginResult.isSessionExpired()) {
|
||||
log.warn("Session已过期,会话无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("会话信息有效,Session ID前缀: {}",
|
||||
loginResult.getSessionId() != null ?
|
||||
loginResult.getSessionId().substring(0, Math.min(10, loginResult.getSessionId().length())) : "null");
|
||||
return true;
|
||||
SalesforceLoginResult loginResult = getCurrentLoginResult();
|
||||
return isLoginResultValid(loginResult);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查会话信息有效性时发生异常: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断登录结果是否有效
|
||||
*
|
||||
* @param loginResult 登录结果
|
||||
* @return 如果有效返回true,否则返回false
|
||||
*/
|
||||
private boolean isLoginResultValid(SalesforceLoginResult loginResult) {
|
||||
return loginResult != null
|
||||
&& loginResult.isSuccess()
|
||||
&& !loginResult.isSessionExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录会话状态日志
|
||||
*
|
||||
* @param loginResult 登录结果
|
||||
*/
|
||||
private void logSessionStatus(SalesforceLoginResult loginResult) {
|
||||
if (loginResult == null) {
|
||||
log.warn("会话信息为null,尝试自动重新登录");
|
||||
} else if (!loginResult.isSuccess()) {
|
||||
log.warn("登录状态为失败,尝试自动重新登录,错误信息: {}", loginResult.getErrorMessage());
|
||||
} else if (loginResult.isSessionExpired()) {
|
||||
log.warn("Session已过期,尝试自动重新登录");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,10 +30,18 @@ public abstract class AbstractConnectionFactory<T> implements ISalesforceConnect
|
||||
String configKey = getConfigKey();
|
||||
|
||||
T connection = connectionCache.get(configKey);
|
||||
|
||||
if (connection != null && !sessionManager.isSessionValid()) {
|
||||
log.warn("检测到Session已过期,清除缓存的{}连接", getConnectionType());
|
||||
connectionCache.remove(configKey);
|
||||
connection = null;
|
||||
}
|
||||
|
||||
if (connection == null) {
|
||||
lock.lock();
|
||||
try {
|
||||
connection = connectionCache.get(configKey);
|
||||
|
||||
if (connection == null) {
|
||||
connection = createConnection();
|
||||
connectionCache.put(configKey, connection);
|
||||
|
||||
@ -692,17 +692,21 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
|
||||
@Override
|
||||
public Map<String, Object> pullAllMetadataChanges() {
|
||||
// 初始化返回结果
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
log.info("开始执行全对象元数据变更拉取");
|
||||
|
||||
// 建立与Salesforce的连接,使用重试机制确保连接成功
|
||||
PartnerConnection connection = retryOperation(() -> soapConnectionFactory.getConnection(), 3, 1000);
|
||||
log.info("成功获取Salesforce SOAP连接");
|
||||
|
||||
// 获取Salesforce中所有对象的全局描述信息
|
||||
DescribeGlobalResult globalDescribe = connection.describeGlobal();
|
||||
DescribeGlobalSObjectResult[] sObjects = globalDescribe.getSobjects();
|
||||
|
||||
// 检查是否获取到任何对象
|
||||
if (sObjects == null || sObjects.length == 0) {
|
||||
log.warn("未获取到任何Salesforce对象");
|
||||
result.put("success", true);
|
||||
@ -714,68 +718,93 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
|
||||
log.info("从Salesforce获取到 {} 个对象", sObjects.length);
|
||||
|
||||
// 用于存储已同步的对象API名称,以便后续检测删除的对象
|
||||
Set<String> syncedObjectApis = new HashSet<>();
|
||||
int objectChangeCount = 0;
|
||||
int fieldChangeCount = 0;
|
||||
int objectChangeCount = 0; // 记录对象变更数量
|
||||
int fieldChangeCount = 0; // 记录字段变更数量
|
||||
|
||||
// 遍历所有Salesforce对象
|
||||
for (DescribeGlobalSObjectResult sObject : sObjects) {
|
||||
try {
|
||||
String objectApi = sObject.getName();
|
||||
|
||||
// 检查对象是否需要同步(满足查询、创建、更新或删除任一条件)
|
||||
if (shouldSyncObject(sObject)) {
|
||||
// 获取对象的详细描述信息
|
||||
DescribeSObjectResult objDetail = connection.describeSObject(objectApi);
|
||||
|
||||
// 查询数据库中是否已存在该对象
|
||||
DataiIntegrationObject queryObject = new DataiIntegrationObject();
|
||||
queryObject.setApi(objectApi);
|
||||
List<DataiIntegrationObject> existingObjects = dataiIntegrationObjectService.selectDataiIntegrationObjectList(queryObject);
|
||||
|
||||
// 构建当前对象的元数据信息
|
||||
DataiIntegrationObject newObject = buildObjectMetadata(objDetail);
|
||||
boolean hasFieldChange = false;
|
||||
boolean hasFieldChange = false; // 标记该对象是否有字段变更
|
||||
|
||||
// 判断对象是否为新增
|
||||
if (existingObjects.isEmpty()) {
|
||||
// 新增对象到数据库
|
||||
dataiIntegrationObjectService.insertDataiIntegrationObject(newObject);
|
||||
// 记录对象新增变更
|
||||
recordObjectChange(newObject, null, "INSERT");
|
||||
objectChangeCount++;
|
||||
log.info("新增对象并记录变更: {}", objectApi);
|
||||
// 重新查询以获取新插入对象的ID
|
||||
existingObjects = dataiIntegrationObjectService.selectDataiIntegrationObjectList(queryObject);
|
||||
if (!existingObjects.isEmpty()) {
|
||||
newObject.setId(existingObjects.get(0).getId());
|
||||
}
|
||||
} else {
|
||||
DataiIntegrationObject existingObject = existingObjects.get(0);
|
||||
newObject.setId(existingObject.getId());
|
||||
List<String> changedFields = compareObjects(existingObject, newObject);
|
||||
if (!changedFields.isEmpty()) {
|
||||
recordObjectChange(newObject, existingObject, "UPDATE");
|
||||
objectChangeCount++;
|
||||
log.debug("记录对象更新: {} - 变更: {}", objectApi, String.join(", ", changedFields));
|
||||
}
|
||||
// 将对象API添加到已同步集合中
|
||||
syncedObjectApis.add(objectApi);
|
||||
// 对象新增后直接跳过字段比较
|
||||
continue;
|
||||
}
|
||||
|
||||
// 对象已存在,比较新旧对象的差异
|
||||
DataiIntegrationObject existingObject = existingObjects.get(0);
|
||||
newObject.setId(existingObject.getId());
|
||||
List<String> changedFields = compareObjects(existingObject, newObject);
|
||||
if (!changedFields.isEmpty()) {
|
||||
// 记录对象更新变更
|
||||
recordObjectChange(newObject, existingObject, "UPDATE");
|
||||
objectChangeCount++;
|
||||
log.debug("记录对象更新: {} - 变更: {}", objectApi, String.join(", ", changedFields));
|
||||
}
|
||||
|
||||
// 将对象API添加到已同步集合中
|
||||
syncedObjectApis.add(objectApi);
|
||||
|
||||
// 查询数据库中该对象的现有字段
|
||||
DataiIntegrationField queryField = new DataiIntegrationField();
|
||||
queryField.setApi(objectApi);
|
||||
List<DataiIntegrationField> existingFields = dataiIntegrationFieldService.selectDataiIntegrationFieldList(queryField);
|
||||
|
||||
// 将现有字段转换为Map,便于快速查找
|
||||
Map<String, DataiIntegrationField> existingFieldMap = new HashMap<>();
|
||||
for (DataiIntegrationField existingField : existingFields) {
|
||||
existingFieldMap.put(existingField.getField(), existingField);
|
||||
}
|
||||
|
||||
// 遍历Salesforce中的所有字段
|
||||
for (Field field : objDetail.getFields()) {
|
||||
// 构建当前字段的元数据信息
|
||||
DataiIntegrationField newField = buildFieldMetadata(objectApi, field);
|
||||
// 检查字段是否已存在
|
||||
DataiIntegrationField existingField = existingFieldMap.get(field.getName());
|
||||
|
||||
if (existingField == null) {
|
||||
// 字段为新增
|
||||
recordFieldChange(objectApi, newObject.getLabel(), newField.getField(),
|
||||
newField.getLabel(), null, "INSERT", "新增字段", newObject.getIsCustom());
|
||||
fieldChangeCount++;
|
||||
hasFieldChange = true;
|
||||
log.debug("记录字段新增: {}.{}", objectApi, field.getName());
|
||||
} else {
|
||||
// 字段已存在,比较新旧字段的差异
|
||||
List<String> changedFieldProps = compareFields(existingField, newField);
|
||||
if (!changedFieldProps.isEmpty()) {
|
||||
// 记录字段更新变更
|
||||
recordFieldChange(objectApi, newObject.getLabel(), newField.getField(),
|
||||
newField.getLabel(), "字段属性变更: " + String.join(", ", changedFieldProps),
|
||||
"UPDATE", "字段属性更新", newObject.getIsCustom());
|
||||
@ -787,6 +816,7 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有已删除的字段(在数据库中存在但在Salesforce中不存在)
|
||||
for (DataiIntegrationField existingField : existingFields) {
|
||||
boolean fieldExists = false;
|
||||
for (Field field : objDetail.getFields()) {
|
||||
@ -796,6 +826,7 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
}
|
||||
}
|
||||
if (!fieldExists) {
|
||||
// 记录字段删除变更
|
||||
recordFieldChange(objectApi, newObject.getLabel(), existingField.getField(),
|
||||
existingField.getLabel(), "字段已从Salesforce中删除",
|
||||
"DELETE", "字段删除", newObject.getIsCustom());
|
||||
@ -805,23 +836,27 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
}
|
||||
}
|
||||
|
||||
// 如果该对象有字段变更,禁用其增量更新状态
|
||||
if (hasFieldChange && newObject.getId() != null) {
|
||||
DataiIntegrationObject updateObject = new DataiIntegrationObject();
|
||||
updateObject.setId(newObject.getId());
|
||||
updateObject.setIsIncremental(false);
|
||||
updateObject.setIsIncremental(false); // 禁用增量更新
|
||||
dataiIntegrationObjectService.updateDataiIntegrationObject(updateObject);
|
||||
log.info("检测到字段变更,已禁用对象 {} 的增量更新状态", objectApi);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录处理单个对象时的错误,但继续处理其他对象
|
||||
log.error("处理对象 {} 时出错: {}", sObject.getName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查并记录已从Salesforce中删除的对象
|
||||
checkDeletedObjectsForMetadata(syncedObjectApis);
|
||||
|
||||
log.info("全对象元数据变更拉取完成,对象变更: {} 个,字段变更: {} 个", objectChangeCount, fieldChangeCount);
|
||||
|
||||
// 设置返回结果
|
||||
result.put("success", true);
|
||||
result.put("objectChangeCount", objectChangeCount);
|
||||
result.put("fieldChangeCount", fieldChangeCount);
|
||||
@ -829,6 +864,7 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
// 记录整个拉取过程中的异常
|
||||
log.error("全对象元数据变更拉取时发生异常", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "全对象元数据变更拉取失败: " + e.getMessage());
|
||||
@ -836,114 +872,218 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否需要同步
|
||||
* 只有满足以下任一条件的对象才需要同步:
|
||||
* - 可查询 (isQueryable)
|
||||
* - 可创建 (isCreateable)
|
||||
* - 可更新 (isUpdateable)
|
||||
* - 可删除 (isDeletable)
|
||||
*
|
||||
* @param sObject Salesforce对象描述信息
|
||||
* @return 如果对象需要同步返回true,否则返回false
|
||||
*/
|
||||
private boolean shouldSyncObject(DescribeGlobalSObjectResult sObject) {
|
||||
return sObject.isQueryable() || sObject.isCreateable() || sObject.isUpdateable() || sObject.isDeletable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Salesforce对象描述信息构建对象元数据实体
|
||||
* 将Salesforce的DescribeSObjectResult转换为DataiIntegrationObject实体
|
||||
*
|
||||
* @param objDetail Salesforce对象的详细描述信息
|
||||
* @return 构建的DataiIntegrationObject实体,如果构建失败返回null
|
||||
*/
|
||||
private DataiIntegrationObject buildObjectMetadata(DescribeSObjectResult objDetail) {
|
||||
try {
|
||||
// 创建对象元数据实体
|
||||
DataiIntegrationObject object = new DataiIntegrationObject();
|
||||
// 设置对象API名称
|
||||
object.setApi(objDetail.getName());
|
||||
// 设置对象标签名称
|
||||
object.setLabel(objDetail.getLabel());
|
||||
// 设置对象复数标签名称
|
||||
object.setLabelPlural(objDetail.getLabelPlural());
|
||||
// 设置对象键前缀
|
||||
object.setKeyPrefix(objDetail.getKeyPrefix());
|
||||
// 设置是否为自定义对象
|
||||
object.setIsCustom(objDetail.isCustom());
|
||||
// 设置是否为自定义设置对象
|
||||
object.setIsCustomSetting(objDetail.isCustomSetting());
|
||||
// 设置是否可查询
|
||||
object.setIsQueryable(objDetail.isQueryable());
|
||||
// 设置是否可创建
|
||||
object.setIsCreateable(objDetail.isCreateable());
|
||||
// 设置是否可更新
|
||||
object.setIsUpdateable(objDetail.isUpdateable());
|
||||
// 设置是否可删除
|
||||
object.setIsDeletable(objDetail.isDeletable());
|
||||
// 设置是否可复制
|
||||
object.setIsReplicateable(objDetail.isReplicateable());
|
||||
// 设置是否可检索
|
||||
object.setIsRetrieveable(objDetail.isRetrieveable());
|
||||
// 设置是否可搜索
|
||||
object.setIsSearchable(objDetail.isSearchable());
|
||||
object.setIsWork(true);
|
||||
object.setIsIncremental(true);
|
||||
// 设置是否工作状态(启用)
|
||||
object.setIsWork(false);
|
||||
// 设置是否启用增量更新
|
||||
object.setIsIncremental(false);
|
||||
// 设置最后同步时间
|
||||
object.setLastSyncDate(LocalDateTime.now());
|
||||
return object;
|
||||
} catch (Exception e) {
|
||||
// 记录构建对象元数据时的错误
|
||||
log.error("构建对象 {} 元数据时出错: {}", objDetail.getName(), e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Salesforce字段描述信息构建字段元数据实体
|
||||
* 将Salesforce的Field对象转换为DataiIntegrationField实体
|
||||
*
|
||||
* @param objectApi 所属对象的API名称
|
||||
* @param field Salesforce字段描述信息
|
||||
* @return 构建的DataiIntegrationField实体
|
||||
*/
|
||||
private DataiIntegrationField buildFieldMetadata(String objectApi, Field field) {
|
||||
// 创建字段元数据实体
|
||||
DataiIntegrationField fieldEntity = new DataiIntegrationField();
|
||||
// 设置所属对象API名称
|
||||
fieldEntity.setApi(objectApi);
|
||||
// 设置字段名称
|
||||
fieldEntity.setField(field.getName());
|
||||
// 设置字段标签
|
||||
fieldEntity.setLabel(field.getLabel());
|
||||
// 设置是否可创建
|
||||
fieldEntity.setIsCreateable(field.isCreateable());
|
||||
// 设置是否可为空
|
||||
fieldEntity.setIsNillable(field.isNillable());
|
||||
// 设置是否可更新
|
||||
fieldEntity.setIsUpdateable(field.isUpdateable());
|
||||
// 设置是否在创建时有默认值
|
||||
fieldEntity.setIsDefaultedOnCreate(field.isDefaultedOnCreate());
|
||||
// 设置是否唯一
|
||||
fieldEntity.setIsUnique(field.isUnique());
|
||||
// 设置是否可过滤
|
||||
fieldEntity.setIsFilterable(field.isFilterable());
|
||||
// 设置是否可排序
|
||||
fieldEntity.setIsSortable(field.isSortable());
|
||||
// 设置是否可聚合
|
||||
fieldEntity.setIsAggregatable(field.isAggregatable());
|
||||
// 设置是否可分组
|
||||
fieldEntity.setIsGroupable(field.isGroupable());
|
||||
// 设置是否为多态外键
|
||||
fieldEntity.setIsPolymorphicForeignKey(field.isPolymorphicForeignKey());
|
||||
// 设置多态外键类型字段
|
||||
fieldEntity.setPolymorphicForeignField(field.getName() + "_type");
|
||||
// 设置是否为外部ID
|
||||
fieldEntity.setIsExternalId(field.isExternalId());
|
||||
// 设置是否为自定义字段
|
||||
fieldEntity.setIsCustom(field.isCustom());
|
||||
// 设置是否为计算字段
|
||||
fieldEntity.setIsCalculated(field.isCalculated());
|
||||
// 设置是否为自动编号字段
|
||||
fieldEntity.setIsAutoNumber(field.isAutoNumber());
|
||||
// 设置是否区分大小写
|
||||
fieldEntity.setIsCaseSensitive(field.isCaseSensitive());
|
||||
// 设置是否为加密字段
|
||||
fieldEntity.setIsEncrypted(field.isEncrypted());
|
||||
// 设置是否为HTML格式化字段
|
||||
fieldEntity.setIsHtmlFormatted(field.isHtmlFormatted());
|
||||
// 设置是否为ID查找字段
|
||||
fieldEntity.setIsIdLookup(field.isIdLookup());
|
||||
// 设置是否为权限字段
|
||||
fieldEntity.setIsPermissionable(field.isPermissionable());
|
||||
// 设置是否为受限选择列表
|
||||
fieldEntity.setIsRestrictedPicklist(field.isRestrictedPicklist());
|
||||
// 设置是否为受限删除
|
||||
fieldEntity.setIsRestrictedDelete(field.isRestrictedDelete());
|
||||
// 设置写入是否需要主记录读取权限
|
||||
fieldEntity.setIsWriteRequiresMasterRead(field.isWriteRequiresMasterRead());
|
||||
// 设置字段数据类型
|
||||
fieldEntity.setFieldDataType(field.getType() != null ? field.getType().toString() : null);
|
||||
// 设置字段长度
|
||||
fieldEntity.setFieldLength(field.getLength());
|
||||
// 设置字段精度
|
||||
fieldEntity.setFieldPrecision(field.getPrecision());
|
||||
// 设置字段小数位数
|
||||
fieldEntity.setFieldScale(field.getScale());
|
||||
// 设置字段字节长度
|
||||
fieldEntity.setFieldByteLength(field.getByteLength());
|
||||
// 设置默认值公式
|
||||
fieldEntity.setDefaultValue(field.getDefaultValueFormula());
|
||||
// 设置计算公式
|
||||
fieldEntity.setCalculatedFormula(field.getCalculatedFormula());
|
||||
// 设置内联帮助文本
|
||||
fieldEntity.setInlineHelpText(field.getInlineHelpText());
|
||||
// 设置关系名称
|
||||
fieldEntity.setRelationshipName(field.getRelationshipName());
|
||||
// 设置关系顺序
|
||||
fieldEntity.setRelationshipOrder(field.getRelationshipOrder());
|
||||
// 设置引用目标字段
|
||||
fieldEntity.setReferenceTargetField(field.getReferenceTo() != null && field.getReferenceTo().length > 0 ? field.getReferenceTo()[0] : null);
|
||||
|
||||
// 如果字段有引用目标,则设置引用目标
|
||||
if (field.getReferenceTo() != null && field.getReferenceTo().length > 0) {
|
||||
// 设置引用目标(多个引用目标用逗号分隔)
|
||||
fieldEntity.setReferenceTo(String.join(",", field.getReferenceTo()));
|
||||
// 设置第一个引用目标字段
|
||||
fieldEntity.setReferenceTargetField(field.getReferenceTo()[0]);
|
||||
}
|
||||
|
||||
return fieldEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个对象元数据的差异
|
||||
* 检查新旧对象元数据之间的不同属性,并返回有变化的属性列表
|
||||
*
|
||||
* @param oldObject 旧的对象元数据
|
||||
* @param newObject 新的对象元数据
|
||||
* @return 有变化的属性名称列表
|
||||
*/
|
||||
private List<String> compareObjects(DataiIntegrationObject oldObject, DataiIntegrationObject newObject) {
|
||||
// 初始化变更字段列表
|
||||
List<String> changedFields = new ArrayList<>();
|
||||
|
||||
// 比较标签名称
|
||||
if (!Objects.equals(oldObject.getLabel(), newObject.getLabel())) {
|
||||
changedFields.add("label");
|
||||
}
|
||||
// 比较复数标签名称
|
||||
if (!Objects.equals(oldObject.getLabelPlural(), newObject.getLabelPlural())) {
|
||||
changedFields.add("labelPlural");
|
||||
}
|
||||
// 比较键前缀
|
||||
if (!Objects.equals(oldObject.getKeyPrefix(), newObject.getKeyPrefix())) {
|
||||
changedFields.add("keyPrefix");
|
||||
}
|
||||
// 比较是否可查询状态
|
||||
if (!Objects.equals(oldObject.getIsQueryable(), newObject.getIsQueryable())) {
|
||||
changedFields.add("isQueryable");
|
||||
}
|
||||
// 比较是否可创建状态
|
||||
if (!Objects.equals(oldObject.getIsCreateable(), newObject.getIsCreateable())) {
|
||||
changedFields.add("isCreateable");
|
||||
}
|
||||
// 比较是否可更新状态
|
||||
if (!Objects.equals(oldObject.getIsUpdateable(), newObject.getIsUpdateable())) {
|
||||
changedFields.add("isUpdateable");
|
||||
}
|
||||
// 比较是否可删除状态
|
||||
if (!Objects.equals(oldObject.getIsDeletable(), newObject.getIsDeletable())) {
|
||||
changedFields.add("isDeletable");
|
||||
}
|
||||
// 比较是否可复制状态
|
||||
if (!Objects.equals(oldObject.getIsReplicateable(), newObject.getIsReplicateable())) {
|
||||
changedFields.add("isReplicateable");
|
||||
}
|
||||
// 比较是否可检索状态
|
||||
if (!Objects.equals(oldObject.getIsRetrieveable(), newObject.getIsRetrieveable())) {
|
||||
changedFields.add("isRetrieveable");
|
||||
}
|
||||
// 比较是否可搜索状态
|
||||
if (!Objects.equals(oldObject.getIsSearchable(), newObject.getIsSearchable())) {
|
||||
changedFields.add("isSearchable");
|
||||
}
|
||||
@ -951,105 +1091,147 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
return changedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个字段元数据的差异
|
||||
* 检查新旧字段元数据之间的不同属性,并返回有变化的属性列表
|
||||
*
|
||||
* @param oldField 旧的字段元数据
|
||||
* @param newField 新的字段元数据
|
||||
* @return 有变化的属性名称列表
|
||||
*/
|
||||
private List<String> compareFields(DataiIntegrationField oldField, DataiIntegrationField newField) {
|
||||
// 初始化变更字段列表
|
||||
List<String> changedFields = new ArrayList<>();
|
||||
|
||||
// 比较标签名称
|
||||
if (!Objects.equals(oldField.getLabel(), newField.getLabel())) {
|
||||
changedFields.add("label");
|
||||
}
|
||||
// 比较是否可创建状态
|
||||
if (!Objects.equals(oldField.getIsCreateable(), newField.getIsCreateable())) {
|
||||
changedFields.add("isCreateable");
|
||||
}
|
||||
// 比较是否可为空状态
|
||||
if (!Objects.equals(oldField.getIsNillable(), newField.getIsNillable())) {
|
||||
changedFields.add("isNillable");
|
||||
}
|
||||
// 比较是否可更新状态
|
||||
if (!Objects.equals(oldField.getIsUpdateable(), newField.getIsUpdateable())) {
|
||||
changedFields.add("isUpdateable");
|
||||
}
|
||||
// 比较是否在创建时有默认值状态
|
||||
if (!Objects.equals(oldField.getIsDefaultedOnCreate(), newField.getIsDefaultedOnCreate())) {
|
||||
changedFields.add("isDefaultedOnCreate");
|
||||
}
|
||||
// 比较是否唯一状态
|
||||
if (!Objects.equals(oldField.getIsUnique(), newField.getIsUnique())) {
|
||||
changedFields.add("isUnique");
|
||||
}
|
||||
// 比较是否可过滤状态
|
||||
if (!Objects.equals(oldField.getIsFilterable(), newField.getIsFilterable())) {
|
||||
changedFields.add("isFilterable");
|
||||
}
|
||||
// 比较是否可排序状态
|
||||
if (!Objects.equals(oldField.getIsSortable(), newField.getIsSortable())) {
|
||||
changedFields.add("isSortable");
|
||||
}
|
||||
// 比较是否可聚合状态
|
||||
if (!Objects.equals(oldField.getIsAggregatable(), newField.getIsAggregatable())) {
|
||||
changedFields.add("isAggregatable");
|
||||
}
|
||||
// 比较是否可分组状态
|
||||
if (!Objects.equals(oldField.getIsGroupable(), newField.getIsGroupable())) {
|
||||
changedFields.add("isGroupable");
|
||||
}
|
||||
// 比较是否为多态外键状态
|
||||
if (!Objects.equals(oldField.getIsPolymorphicForeignKey(), newField.getIsPolymorphicForeignKey())) {
|
||||
changedFields.add("isPolymorphicForeignKey");
|
||||
}
|
||||
// 比较是否为外部ID状态
|
||||
if (!Objects.equals(oldField.getIsExternalId(), newField.getIsExternalId())) {
|
||||
changedFields.add("isExternalId");
|
||||
}
|
||||
// 比较是否为自定义字段状态
|
||||
if (!Objects.equals(oldField.getIsCustom(), newField.getIsCustom())) {
|
||||
changedFields.add("isCustom");
|
||||
}
|
||||
// 比较是否为计算字段状态
|
||||
if (!Objects.equals(oldField.getIsCalculated(), newField.getIsCalculated())) {
|
||||
changedFields.add("isCalculated");
|
||||
}
|
||||
// 比较是否为自动编号字段状态
|
||||
if (!Objects.equals(oldField.getIsAutoNumber(), newField.getIsAutoNumber())) {
|
||||
changedFields.add("isAutoNumber");
|
||||
}
|
||||
// 比较是否区分大小写状态
|
||||
if (!Objects.equals(oldField.getIsCaseSensitive(), newField.getIsCaseSensitive())) {
|
||||
changedFields.add("isCaseSensitive");
|
||||
}
|
||||
// 比较是否为加密字段状态
|
||||
if (!Objects.equals(oldField.getIsEncrypted(), newField.getIsEncrypted())) {
|
||||
changedFields.add("isEncrypted");
|
||||
}
|
||||
// 比较是否为HTML格式化字段状态
|
||||
if (!Objects.equals(oldField.getIsHtmlFormatted(), newField.getIsHtmlFormatted())) {
|
||||
changedFields.add("isHtmlFormatted");
|
||||
}
|
||||
// 比较是否为ID查找字段状态
|
||||
if (!Objects.equals(oldField.getIsIdLookup(), newField.getIsIdLookup())) {
|
||||
changedFields.add("isIdLookup");
|
||||
}
|
||||
// 比较是否为权限字段状态
|
||||
if (!Objects.equals(oldField.getIsPermissionable(), newField.getIsPermissionable())) {
|
||||
changedFields.add("isPermissionable");
|
||||
}
|
||||
// 比较是否为受限选择列表状态
|
||||
if (!Objects.equals(oldField.getIsRestrictedPicklist(), newField.getIsRestrictedPicklist())) {
|
||||
changedFields.add("isRestrictedPicklist");
|
||||
}
|
||||
// 比较是否为受限删除状态
|
||||
if (!Objects.equals(oldField.getIsRestrictedDelete(), newField.getIsRestrictedDelete())) {
|
||||
changedFields.add("isRestrictedDelete");
|
||||
}
|
||||
// 比较写入是否需要主记录读取权限状态
|
||||
if (!Objects.equals(oldField.getIsWriteRequiresMasterRead(), newField.getIsWriteRequiresMasterRead())) {
|
||||
changedFields.add("isWriteRequiresMasterRead");
|
||||
}
|
||||
// 比较字段数据类型
|
||||
if (!Objects.equals(oldField.getFieldDataType(), newField.getFieldDataType())) {
|
||||
changedFields.add("fieldDataType");
|
||||
}
|
||||
// 比较字段长度
|
||||
if (!Objects.equals(oldField.getFieldLength(), newField.getFieldLength())) {
|
||||
changedFields.add("fieldLength");
|
||||
}
|
||||
// 比较字段精度
|
||||
if (!Objects.equals(oldField.getFieldPrecision(), newField.getFieldPrecision())) {
|
||||
changedFields.add("fieldPrecision");
|
||||
}
|
||||
// 比较字段小数位数
|
||||
if (!Objects.equals(oldField.getFieldScale(), newField.getFieldScale())) {
|
||||
changedFields.add("fieldScale");
|
||||
}
|
||||
// 比较字段字节长度
|
||||
if (!Objects.equals(oldField.getFieldByteLength(), newField.getFieldByteLength())) {
|
||||
changedFields.add("fieldByteLength");
|
||||
}
|
||||
// 比较默认值公式
|
||||
if (!Objects.equals(oldField.getDefaultValue(), newField.getDefaultValue())) {
|
||||
changedFields.add("defaultValue");
|
||||
}
|
||||
// 比较计算公式
|
||||
if (!Objects.equals(oldField.getCalculatedFormula(), newField.getCalculatedFormula())) {
|
||||
changedFields.add("calculatedFormula");
|
||||
}
|
||||
// 比较内联帮助文本
|
||||
if (!Objects.equals(oldField.getInlineHelpText(), newField.getInlineHelpText())) {
|
||||
changedFields.add("inlineHelpText");
|
||||
}
|
||||
// 比较关系名称
|
||||
if (!Objects.equals(oldField.getRelationshipName(), newField.getRelationshipName())) {
|
||||
changedFields.add("relationshipName");
|
||||
}
|
||||
// 比较引用目标
|
||||
if (!Objects.equals(oldField.getReferenceTo(), newField.getReferenceTo())) {
|
||||
changedFields.add("referenceTo");
|
||||
}
|
||||
@ -1057,102 +1239,189 @@ public class DataiIntegrationMetadataChangeServiceImpl implements IDataiIntegrat
|
||||
return changedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录对象元数据变更
|
||||
* 将对象级别的变更(新增、修改、删除)记录到元数据变更表中
|
||||
*
|
||||
* @param newObject 新的对象元数据
|
||||
* @param oldObject 旧的对象元数据(新增操作时为null)
|
||||
* @param operationType 操作类型(INSERT、UPDATE、DELETE)
|
||||
*/
|
||||
private void recordObjectChange(DataiIntegrationObject newObject, DataiIntegrationObject oldObject, String operationType) {
|
||||
try {
|
||||
// 创建元数据变更记录实体
|
||||
DataiIntegrationMetadataChange metadataChange = new DataiIntegrationMetadataChange();
|
||||
// 设置变更类型为对象
|
||||
metadataChange.setChangeType("OBJECT");
|
||||
// 设置操作类型(INSERT、UPDATE、DELETE)
|
||||
metadataChange.setOperationType(operationType);
|
||||
// 设置对象API名称
|
||||
metadataChange.setObjectApi(newObject.getApi());
|
||||
// 设置对象标签名称
|
||||
metadataChange.setObjectLabel(newObject.getLabel());
|
||||
// 设置变更时间
|
||||
metadataChange.setChangeTime(LocalDateTime.now());
|
||||
// 设置同步状态为未同步
|
||||
metadataChange.setSyncStatus(false);
|
||||
// 设置是否为自定义对象
|
||||
metadataChange.setIsCustom(newObject.getIsCustom());
|
||||
|
||||
// 如果是更新操作(oldObject不为null),则比较新旧对象的差异
|
||||
if (oldObject != null) {
|
||||
List<String> changedFields = compareObjects(oldObject, newObject);
|
||||
if (!changedFields.isEmpty()) {
|
||||
// 如果有具体变更字段,则记录详细变更信息
|
||||
metadataChange.setChangeReason("对象属性变更: " + String.join(", ", changedFields));
|
||||
} else {
|
||||
// 如果没有具体变更字段,则记录通用更新信息
|
||||
metadataChange.setChangeReason("对象属性更新");
|
||||
}
|
||||
} else {
|
||||
// 如果是新增操作,则记录为新增对象
|
||||
metadataChange.setChangeReason("新增对象");
|
||||
}
|
||||
// 设置变更用户为系统
|
||||
metadataChange.setChangeUser("SYSTEM");
|
||||
|
||||
// 插入元数据变更记录
|
||||
insertDataiIntegrationMetadataChange(metadataChange);
|
||||
log.debug("记录对象变更成功: {} - {}", newObject.getApi(), operationType);
|
||||
} catch (Exception e) {
|
||||
// 记录记录对象变更时的错误
|
||||
log.error("记录对象变更失败: {} - {}", newObject.getApi(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录字段元数据变更
|
||||
* 将字段级别的变更(新增、修改、删除)记录到元数据变更表中
|
||||
*
|
||||
* @param objectApi 所属对象的API名称
|
||||
* @param objectLabel 所属对象的标签名称
|
||||
* @param fieldApi 字段API名称
|
||||
* @param fieldLabel 字段标签名称
|
||||
* @param changeReason 变更原因(可为null,使用defaultReason)
|
||||
* @param operationType 操作类型(INSERT、UPDATE、DELETE)
|
||||
* @param defaultReason 默认变更原因
|
||||
* @param isCustom 是否为自定义对象
|
||||
*/
|
||||
private void recordFieldChange(String objectApi, String objectLabel, String fieldApi, String fieldLabel,
|
||||
String changeReason, String operationType, String defaultReason, Boolean isCustom) {
|
||||
try {
|
||||
// 创建元数据变更记录实体
|
||||
DataiIntegrationMetadataChange metadataChange = new DataiIntegrationMetadataChange();
|
||||
// 设置变更类型为字段
|
||||
metadataChange.setChangeType("FIELD");
|
||||
// 设置操作类型(INSERT、UPDATE、DELETE)
|
||||
metadataChange.setOperationType(operationType);
|
||||
// 设置对象API名称
|
||||
metadataChange.setObjectApi(objectApi);
|
||||
// 设置对象标签名称
|
||||
metadataChange.setObjectLabel(objectLabel);
|
||||
// 设置字段API名称
|
||||
metadataChange.setFieldApi(fieldApi);
|
||||
// 设置字段标签名称
|
||||
metadataChange.setFieldLabel(fieldLabel);
|
||||
// 设置变更时间
|
||||
metadataChange.setChangeTime(LocalDateTime.now());
|
||||
// 设置同步状态为未同步
|
||||
metadataChange.setSyncStatus(false);
|
||||
// 设置是否为自定义对象
|
||||
metadataChange.setIsCustom(isCustom);
|
||||
// 设置变更原因,如果changeReason不为null则使用它,否则使用默认原因
|
||||
metadataChange.setChangeReason(changeReason != null ? changeReason : defaultReason);
|
||||
// 设置变更用户为系统
|
||||
metadataChange.setChangeUser("SYSTEM");
|
||||
|
||||
// 插入元数据变更记录
|
||||
insertDataiIntegrationMetadataChange(metadataChange);
|
||||
log.debug("记录字段变更成功: {}.{} - {}", objectApi, fieldApi, operationType);
|
||||
} catch (Exception e) {
|
||||
// 记录记录字段变更时的错误
|
||||
log.error("记录字段变更失败: {}.{} - {}", objectApi, fieldApi, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并记录已从Salesforce中删除的对象
|
||||
* 通过比较数据库中存储的对象与从Salesforce获取的对象列表,
|
||||
* 识别出已从Salesforce中删除的对象并记录相应的删除变更
|
||||
*
|
||||
* @param syncedObjectApis 从Salesforce获取到的已同步对象API名称集合
|
||||
*/
|
||||
private void checkDeletedObjectsForMetadata(Set<String> syncedObjectApis) {
|
||||
try {
|
||||
// 查询数据库中所有对象
|
||||
DataiIntegrationObject queryObject = new DataiIntegrationObject();
|
||||
queryObject.setIsWork(true);
|
||||
List<DataiIntegrationObject> allObjects = dataiIntegrationObjectService.selectDataiIntegrationObjectList(queryObject);
|
||||
|
||||
// 遍历数据库中的所有对象,检查哪些对象已不在Salesforce中
|
||||
for (DataiIntegrationObject object : allObjects) {
|
||||
if (!syncedObjectApis.contains(object.getApi())) {
|
||||
// 创建元数据变更记录,标记为删除操作
|
||||
DataiIntegrationMetadataChange metadataChange = new DataiIntegrationMetadataChange();
|
||||
// 设置变更类型为对象
|
||||
metadataChange.setChangeType("OBJECT");
|
||||
// 设置操作类型为删除
|
||||
metadataChange.setOperationType("DELETE");
|
||||
// 设置对象API名称
|
||||
metadataChange.setObjectApi(object.getApi());
|
||||
// 设置对象标签名称
|
||||
metadataChange.setObjectLabel(object.getLabel());
|
||||
// 设置变更时间
|
||||
metadataChange.setChangeTime(LocalDateTime.now());
|
||||
// 设置同步状态为未同步
|
||||
metadataChange.setSyncStatus(false);
|
||||
// 设置是否为自定义对象
|
||||
metadataChange.setIsCustom(object.getIsCustom());
|
||||
// 设置变更原因
|
||||
metadataChange.setChangeReason("对象已从Salesforce中删除");
|
||||
// 设置变更用户为系统
|
||||
metadataChange.setChangeUser("SYSTEM");
|
||||
|
||||
// 插入元数据变更记录
|
||||
insertDataiIntegrationMetadataChange(metadataChange);
|
||||
log.warn("检测到对象已删除: {}", object.getApi());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录检查已删除对象时的错误
|
||||
log.error("检查已删除对象时出错: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试操作执行
|
||||
* 对指定的操作执行重试机制,在操作失败时进行重试,直到达到最大重试次数
|
||||
*
|
||||
* @param <T> 操作返回值类型
|
||||
* @param operation 需要执行的操作(Supplier函数式接口)
|
||||
* @param maxRetries 最大重试次数
|
||||
* @param delayMs 每次重试前的延迟时间(毫秒)
|
||||
* @return 操作执行结果
|
||||
*/
|
||||
private <T> T retryOperation(java.util.function.Supplier<T> operation, int maxRetries, long delayMs) {
|
||||
// 初始化重试计数器
|
||||
int retries = 0;
|
||||
// 无限循环直到操作成功或达到最大重试次数
|
||||
while (true) {
|
||||
try {
|
||||
// 执行操作并返回结果
|
||||
return operation.get();
|
||||
} catch (Exception e) {
|
||||
// 增加重试计数
|
||||
retries++;
|
||||
if (retries >= maxRetries) {
|
||||
// 如果达到最大重试次数,记录错误并抛出异常
|
||||
log.error("操作失败,已达到最大重试次数: {}", maxRetries, e);
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
// 记录重试日志并延迟指定时间
|
||||
log.warn("操作失败,将在 {} 毫秒后重试,当前重试次数: {}", delayMs, retries);
|
||||
Thread.sleep(delayMs);
|
||||
} catch (InterruptedException ie) {
|
||||
// 如果线程在延迟期间被中断,恢复中断状态并抛出运行时异常
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("重试延迟时线程被中断", ie);
|
||||
throw new RuntimeException(ie);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package com.datai.integration.task;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.datai.integration.model.domain.DataiIntegrationMetadataChange;
|
||||
import com.datai.integration.model.param.DataiSyncParam;
|
||||
import com.datai.integration.service.IDataiIntegrationMetadataChangeService;
|
||||
import com.datai.salesforce.common.utils.ApiListUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -15,19 +17,9 @@ public class SalesforceSyncTask {
|
||||
|
||||
@Autowired
|
||||
private ISalesforceDataPullService salesforceDataPullService;
|
||||
|
||||
public void syncAllData(String paramStr) {
|
||||
DataiSyncParam dataiSyncParam = new DataiSyncParam();
|
||||
|
||||
if (StringUtils.isNotBlank(paramStr)) {
|
||||
dataiSyncParam = JSON.parseObject(paramStr, DataiSyncParam.class);
|
||||
}
|
||||
|
||||
List<String> strings = ApiListUtils.splitApiList(dataiSyncParam.getApi());
|
||||
|
||||
salesforceDataPullService.syncObjectsData(strings);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private IDataiIntegrationMetadataChangeService dataiIntegrationMetadataChangeService;
|
||||
|
||||
public void syncByObjectStructures(String paramStr) {
|
||||
|
||||
@ -41,4 +33,8 @@ public class SalesforceSyncTask {
|
||||
|
||||
salesforceDataPullService.syncObjectStructures(strings);
|
||||
}
|
||||
|
||||
public void pullAllMetadataChanges() {
|
||||
dataiIntegrationMetadataChangeService.pullAllMetadataChanges();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,245 @@
|
||||
# 全对象元数据变更拉取接口文档
|
||||
|
||||
## 1. 接口概述
|
||||
|
||||
### 1.1 接口名称
|
||||
全对象元数据变更拉取
|
||||
|
||||
### 1.2 接口描述
|
||||
从Salesforce拉取所有对象的元数据变更信息并记录到元数据变更表中。该接口会自动检测对象和字段的新增、修改、删除操作,并将变更记录到数据库中。
|
||||
|
||||
### 1.3 接口地址
|
||||
```
|
||||
POST /integration/change/pullAll
|
||||
```
|
||||
|
||||
### 1.4 请求方式
|
||||
POST
|
||||
|
||||
### 1.5 权限要求
|
||||
需要 `integration:change:pullAll` 权限
|
||||
|
||||
### 1.6 日志记录
|
||||
操作类型:OTHER(其他)
|
||||
日志标题:全对象元数据变更拉取
|
||||
|
||||
## 2. 请求参数
|
||||
|
||||
### 2.1 请求头
|
||||
```
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
### 2.2 请求体
|
||||
该接口无需请求体参数
|
||||
|
||||
### 2.3 请求示例
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/integration/change/pullAll" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer {your_token}"
|
||||
```
|
||||
|
||||
## 3. 响应格式
|
||||
|
||||
### 3.1 响应结构
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"success": true,
|
||||
"objectChangeCount": 5,
|
||||
"fieldChangeCount": 23,
|
||||
"message": "全对象元数据变更拉取完成,对象变更: 5 个,字段变更: 23 个"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 响应字段说明
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| code | Integer | 响应状态码,200表示成功 |
|
||||
| msg | String | 响应消息 |
|
||||
| data | Object | 响应数据对象 |
|
||||
| data.success | Boolean | 操作是否成功 |
|
||||
| data.objectChangeCount | Integer | 对象变更数量 |
|
||||
| data.fieldChangeCount | Integer | 字段变更数量 |
|
||||
| data.message | String | 详细消息说明 |
|
||||
|
||||
### 3.3 成功响应示例
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"success": true,
|
||||
"objectChangeCount": 3,
|
||||
"fieldChangeCount": 15,
|
||||
"message": "全对象元数据变更拉取完成,对象变更: 3 个,字段变更: 15 个"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 失败响应示例
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "全对象元数据变更拉取失败: 无法连接到Salesforce",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 业务逻辑说明
|
||||
|
||||
### 4.1 处理流程
|
||||
1. **建立连接**:使用重试机制(最多3次,每次间隔1秒)建立与Salesforce的SOAP连接
|
||||
2. **获取对象列表**:从Salesforce获取所有对象的全局描述信息
|
||||
3. **对象过滤**:只同步满足以下任一条件的对象:
|
||||
- isQueryable(可查询)
|
||||
- isCreateable(可创建)
|
||||
- isUpdateable(可更新)
|
||||
- isDeletable(可删除)
|
||||
4. **对象变更检测**:
|
||||
- 新增对象:插入数据库并记录变更
|
||||
- 修改对象:比较差异并记录变更
|
||||
- 删除对象:检测并记录已删除的对象
|
||||
5. **字段变更检测**:
|
||||
- 新增字段:记录字段新增变更
|
||||
- 修改字段:比较字段属性差异并记录变更
|
||||
- 删除字段:检测并记录已删除的字段
|
||||
6. **增量更新处理**:如果检测到字段变更,自动禁用该对象的增量更新状态
|
||||
7. **统计结果**:返回对象变更数量和字段变更数量
|
||||
|
||||
### 4.2 对象同步条件
|
||||
只有满足以下任一条件的对象才会被同步:
|
||||
- 可查询(isQueryable = true)
|
||||
- 可创建(isCreateable = true)
|
||||
- 可更新(isUpdateable = true)
|
||||
- 可删除(isDeletable = true)
|
||||
|
||||
### 4.3 字段同步条件
|
||||
所有字段都会被同步,无特殊限制
|
||||
|
||||
### 4.4 变更记录规则
|
||||
- **对象变更**:记录到元数据变更表,变更类型为 "OBJECT"
|
||||
- **字段变更**:记录到元数据变更表,变更类型为 "FIELD"
|
||||
- **操作类型**:INSERT(新增)、UPDATE(修改)、DELETE(删除)
|
||||
- **同步状态**:默认为 false(未同步)
|
||||
|
||||
## 5. 错误处理
|
||||
|
||||
### 5.1 常见错误码
|
||||
|
||||
| 错误码 | 说明 | 处理建议 |
|
||||
|--------|------|----------|
|
||||
| 401 | 未授权 | 检查token是否有效 |
|
||||
| 403 | 权限不足 | 确认用户是否具有 `integration:change:pullAll` 权限 |
|
||||
| 500 | 服务器内部错误 | 查看服务器日志,检查Salesforce连接配置 |
|
||||
|
||||
### 5.2 常见错误场景
|
||||
|
||||
#### 5.2.1 Salesforce连接失败
|
||||
```
|
||||
错误信息:全对象元数据变更拉取失败: 无法连接到Salesforce
|
||||
原因:SOAP连接配置错误或网络问题
|
||||
解决方案:检查SOAPConnectionFactory配置和网络连接
|
||||
```
|
||||
|
||||
#### 5.2.2 权限不足
|
||||
```
|
||||
错误信息:您没有权限执行此操作
|
||||
原因:用户缺少 `integration:change:pullAll` 权限
|
||||
解决方案:联系管理员分配相应权限
|
||||
```
|
||||
|
||||
#### 5.2.3 未获取到对象
|
||||
```
|
||||
错误信息:未获取到任何Salesforce对象
|
||||
原因:Salesforce中无可访问对象或API权限不足
|
||||
解决方案:检查Salesforce API权限配置
|
||||
```
|
||||
|
||||
## 6. 使用注意事项
|
||||
|
||||
### 6.1 性能考虑
|
||||
- 该接口会遍历Salesforce中的所有对象和字段,可能需要较长时间
|
||||
- 建议在业务低峰期调用
|
||||
- 避免频繁调用,建议按需触发
|
||||
|
||||
### 6.2 数据一致性
|
||||
- 接口执行过程中会记录详细的变更日志
|
||||
- 如果执行失败,已记录的变更不会回滚
|
||||
- 建议在执行前备份重要数据
|
||||
|
||||
### 6.3 并发控制
|
||||
- 不建议同时调用多个拉取接口
|
||||
- 确保前一次拉取完成后再执行下一次拉取
|
||||
|
||||
### 6.4 监控建议
|
||||
- 监控接口执行时间
|
||||
- 监控变更数量,异常增长可能需要人工介入
|
||||
- 定期检查同步状态,及时处理未同步的变更
|
||||
|
||||
## 7. 相关接口
|
||||
|
||||
### 7.1 查询元数据变更列表
|
||||
```
|
||||
GET /integration/change/list
|
||||
```
|
||||
|
||||
### 7.2 查询未同步的元数据变更
|
||||
```
|
||||
GET /integration/change/unsynced
|
||||
```
|
||||
|
||||
### 7.3 同步元数据变更到本地数据库
|
||||
```
|
||||
POST /integration/change/{id}/sync
|
||||
```
|
||||
|
||||
### 7.4 批量同步元数据变更
|
||||
```
|
||||
POST /integration/change/syncBatch
|
||||
```
|
||||
|
||||
### 7.5 获取变更统计信息
|
||||
```
|
||||
GET /integration/change/statistics
|
||||
```
|
||||
|
||||
## 8. 附录
|
||||
|
||||
### 8.1 元数据变更表结构
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| id | Long | 主键ID |
|
||||
| changeType | String | 变更类型(OBJECT/FIELD) |
|
||||
| operationType | String | 操作类型(INSERT/UPDATE/DELETE) |
|
||||
| objectApi | String | 对象API名称 |
|
||||
| objectLabel | String | 对象标签名称 |
|
||||
| fieldApi | String | 字段API名称(字段变更时) |
|
||||
| fieldLabel | String | 字段标签名称(字段变更时) |
|
||||
| changeReason | String | 变更原因 |
|
||||
| changeTime | LocalDateTime | 变更时间 |
|
||||
| syncStatus | Boolean | 同步状态(false-未同步,true-已同步) |
|
||||
| isCustom | Boolean | 是否为自定义对象 |
|
||||
| changeUser | String | 变更用户 |
|
||||
|
||||
### 8.2 版本历史
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| 1.0 | 2025-12-27 | 初始版本 |
|
||||
|
||||
### 8.3 联系方式
|
||||
如有问题,请联系技术支持团队。
|
||||
|
||||
---
|
||||
|
||||
**文档生成时间**:2025-12-27
|
||||
**最后更新时间**:2025-12-27
|
||||
**文档版本**:1.0
|
||||
@ -3,6 +3,7 @@ package com.datai.setting.controller;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.setting.model.dto.DataiConfigAuditLogDto;
|
||||
import com.datai.setting.model.vo.DataiConfigAuditLogVo;
|
||||
import com.datai.setting.utils.AuditLogObjectNameResolver;
|
||||
@ -63,7 +64,7 @@ public class DataiConfigAuditLogController extends BaseController
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.datai.setting.controller;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.setting.model.vo.DataiConfigEnvironmentVo;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -55,7 +56,7 @@ public class DataiConfigEnvironmentController extends BaseController
|
||||
DataiConfigEnvironment dataiConfigEnvironment = DataiConfigEnvironmentDto.toObj(dataiConfigEnvironmentDto);
|
||||
List<DataiConfigEnvironment> list = dataiConfigEnvironmentService.selectDataiConfigEnvironmentList(dataiConfigEnvironment);
|
||||
List<DataiConfigEnvironmentVo> voList = list.stream().map(DataiConfigEnvironmentVo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.setting.model.dto.DataiConfigSnapshotDto;
|
||||
import com.datai.setting.model.vo.DataiConfigSnapshotVo;
|
||||
import com.datai.setting.service.IDataiConfigEnvironmentService;
|
||||
@ -80,7 +81,7 @@ public class DataiConfigSnapshotController extends BaseController
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.datai.common.utils.PageUtils;
|
||||
import com.datai.setting.model.vo.DataiConfigurationVo;
|
||||
import com.datai.setting.service.IDataiConfigEnvironmentService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@ -80,7 +81,7 @@ public class DataiConfigurationController extends BaseController
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return getDataTable(voList);
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user