Merge branch 'dev' into 'test'

Dev

See merge request jygk/dsc!24
This commit is contained in:
朝锦 杨
2026-01-29 00:47:19 +00:00
43 changed files with 1523 additions and 88 deletions

View File

@@ -3,6 +3,7 @@ package com.zt.plat.module.system.controller.admin.integration.iwork;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.tenant.core.aop.TenantIgnore;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*;
import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService;
import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService;
import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService;
import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService;
@@ -19,6 +20,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.prepost.PreAuthorize;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* 提供统一 iWork 流程能力的管理端接口。
@@ -34,6 +37,7 @@ public class IWorkIntegrationController {
private final IWorkIntegrationService integrationService;
private final IWorkOrgRestService orgRestService;
private final IWorkSyncService syncService;
private final IWorkCallbackLogService callbackLogService;
@PostMapping("/auth/register")
@Operation(summary = "注册 iWork 凭证,获取服务端公钥与 secret")
@@ -87,6 +91,39 @@ public class IWorkIntegrationController {
return success(integrationService.voidWorkflow(reqVO));
}
@PreAuthorize("@ss.hasPermission('system:iwork:log:query')")
@PostMapping("/log/page")
@Operation(summary = "iWork 回调日志分页查询")
public CommonResult<com.zt.plat.framework.common.pojo.PageResult<IWorkCallbackLogRespVO>> pageLogs(@Valid @RequestBody IWorkCallbackLogPageReqVO reqVO) {
com.zt.plat.framework.common.pojo.PageResult<com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO> page = callbackLogService.page(reqVO);
java.util.List<IWorkCallbackLogRespVO> mapped = new java.util.ArrayList<>();
for (com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO log : page.getList()) {
IWorkCallbackLogRespVO vo = new IWorkCallbackLogRespVO();
vo.setId(log.getId());
vo.setRequestId(log.getRequestId());
vo.setBusinessCode(log.getBusinessCode());
vo.setBizCallbackKey(log.getBizCallbackKey());
vo.setStatus(log.getStatus());
vo.setRetryCount(log.getRetryCount());
vo.setMaxRetry(log.getMaxRetry());
vo.setLastErrorMessage(log.getLastErrorMessage());
vo.setRawCallback(log.getRawCallback());
vo.setLastCallbackTime(log.getLastCallbackTime());
vo.setCreateTime(log.getCreateTime());
vo.setUpdateTime(log.getUpdateTime());
mapped.add(vo);
}
return success(new com.zt.plat.framework.common.pojo.PageResult<>(mapped, page.getTotal(), page.getSummary()));
}
@PreAuthorize("@ss.hasPermission('system:iwork:log:retry')")
@PostMapping("/log/retry")
@Operation(summary = "iWork 回调手工重试")
public CommonResult<Boolean> retry(@Valid @RequestBody IWorkWorkflowVoidReqVO reqVO) {
callbackLogService.resetAndDispatch(reqVO.getRequestId());
return success(true);
}
// ----------------- 人力组织接口 -----------------
@PostMapping("/hr/subcompany/page")

View File

@@ -0,0 +1,39 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - iWork 用印回调日志分页查询")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkCallbackLogPageReqVO extends PageParam {
@Schema(description = "requestId")
private String requestId;
@Schema(description = "业务单号")
private String businessCode;
@Schema(description = "业务回调标识")
private String bizCallbackKey;
@Schema(description = "状态")
private Integer status;
@Schema(description = "创建时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "最后回调时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastCallbackTime;
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "iWork 用印回调日志响应 VO")
@Data
public class IWorkCallbackLogRespVO {
private Long id;
private String requestId;
private String businessCode;
private String bizCallbackKey;
private Integer status;
private Integer retryCount;
private Integer maxRetry;
private String lastErrorMessage;
private String rawCallback;
private LocalDateTime lastCallbackTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@@ -2,12 +2,21 @@ package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(name = "IWorkFileCallbackReqVO", description = "iWork 文件回调请求 VO")
@Data
public class IWorkFileCallbackReqVO {
@Schema(description = "iWork requestId唯一标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "requestId 不能为空")
private String requestId;
@Schema(description = "业务回调标识 bizCallbackKey≤255 字符", example = "seal-flow-callback")
@Size(max = 255, message = "bizCallbackKey 长度不能超过 255 字符")
private String bizCallbackKey;
@Schema(description = "文件下载 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://example.com/file.pdf")
@NotBlank(message = "文件 URL 不能为空")
private String fileUrl;
@@ -19,6 +28,9 @@ public class IWorkFileCallbackReqVO {
@Schema(description = "文件名称,可选", example = "合同附件.pdf")
private String fileName;
@Schema(description = "OA 单点下载使用的 ssoToken可选", example = "6102A7C13F09DD6B1AF06CDA0E479AC8...")
private String ssoToken;
@Schema(description = "业务回调状态/结果码,可选")
private String status;
@Schema(description = "原始回调文本(可选,若不传则使用 payload 或请求体序列化)")
private String rawBody;
}

View File

@@ -14,6 +14,6 @@ public class IWorkOaTokenReqVO {
@NotBlank(message = "loginId 不能为空")
private String loginId;
@Schema(description = "应用 appid未填则使用配置默认", example = "a17ca6ca-88b0-463e-bffa-7995086bf225")
@Schema(description = "应用 appid已固定使用配置值,无需传递", example = "")
private String appId;
}

View File

@@ -0,0 +1,26 @@
package com.zt.plat.module.system.controller.admin.integration.iwork.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Schema(description = "iWork 流程回调请求")
@Data
public class IWorkWorkflowCallbackReqVO {
@Schema(description = "iWork requestId唯一标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "requestId 不能为空")
private String requestId;
@Schema(description = "业务单号 (ywxtdjbh)")
private String businessCode;
@Schema(description = "业务回调标识 bizCallbackKey")
private String bizCallbackKey;
@Schema(description = "回调状态/结果码")
private String status;
@Schema(description = "原始回调文本(可截断存储)")
private String rawBody;
}

View File

@@ -34,9 +34,9 @@ public class IWorkWorkflowCreateReqVO extends IWorkBaseReqVO {
@Schema(description = "用印材料附件 URL必填")
private String xyywjUrl;
@Schema(description = "用印材料附件文件名(必填")
private String xyywjFileName;
@Schema(description = "业务回调标识回调分发使用≤255 字符")
private String bizCallbackKey;
@Schema(description = "用印事项")
private String yysx;

View File

@@ -0,0 +1,72 @@
package com.zt.plat.module.system.dal.dataobject.iwork;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
/**
* iWork 用印流程回调日志。
*/
@TableName("system_iwork_seal_log")
@KeySequence("system_iwork_seal_log_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IWorkSealLogDO extends BaseDO {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* iWork 返回的请求编号,唯一业务标识。
*/
private String requestId;
/**
* 业务单号ywxtdjbh
*/
private String businessCode;
/**
* 业务回调标识。
*/
private String bizCallbackKey;
/**
* 状态枚举,参考 IWorkCallbackStatusEnum。
*/
private Integer status;
/**
* 已执行的自动/手工重试次数。
*/
private Integer retryCount;
/**
* 最大重试次数(快照)。
*/
private Integer maxRetry;
/**
* 最后一次错误信息。
*/
private String lastErrorMessage;
/**
* 回调原始负载(截断)。
*/
private String rawCallback;
/**
* 最近一次回调时间。
*/
private LocalDateTime lastCallbackTime;
}

View File

@@ -0,0 +1,27 @@
package com.zt.plat.module.system.dal.mysql.iwork;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IWorkSealLogMapper extends BaseMapperX<IWorkSealLogDO> {
default IWorkSealLogDO selectByRequestId(String requestId) {
return selectOne(IWorkSealLogDO::getRequestId, requestId);
}
default PageResult<IWorkSealLogDO> selectPage(IWorkCallbackLogPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IWorkSealLogDO>()
.eqIfPresent(IWorkSealLogDO::getRequestId, reqVO.getRequestId())
.eqIfPresent(IWorkSealLogDO::getBusinessCode, reqVO.getBusinessCode())
.eqIfPresent(IWorkSealLogDO::getBizCallbackKey, reqVO.getBizCallbackKey())
.eqIfPresent(IWorkSealLogDO::getStatus, reqVO.getStatus())
.betweenIfPresent(IWorkSealLogDO::getCreateTime, reqVO.getCreateTime())
.betweenIfPresent(IWorkSealLogDO::getLastCallbackTime, reqVO.getLastCallbackTime())
.orderByDesc(IWorkSealLogDO::getId));
}
}

View File

@@ -42,6 +42,7 @@ public class IWorkProperties {
private final Client client = new Client();
private final OrgRest org = new OrgRest();
private final Workflow workflow = new Workflow();
private final Callback callback = new Callback();
private final Oa oa = new Oa();
@Data
@@ -127,6 +128,26 @@ public class IWorkProperties {
private String sealWorkflowId;
}
@Data
public static class Callback {
/**
* 业务回调重试配置。
*/
private final Retry retry = new Retry();
}
@Data
public static class Retry {
/**
* 最大重试次数,默认 3。
*/
private int maxAttempts = 3;
/**
* 重试延迟(秒),默认 5。
*/
private int delaySeconds = 5;
}
@Data
public static class Oa {
/**

View File

@@ -0,0 +1,66 @@
package com.zt.plat.module.system.mq.iwork;
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackResultMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 监听业务模块回传的处理结果,并在失败时由 system 模块负责重试投递原始回调消息。
*/
@Slf4j
@Component
@RequiredArgsConstructor
@RocketMQMessageListener(topic = IWorkBizCallbackResultMessage.TOPIC, consumerGroup = IWorkBizCallbackResultMessage.TOPIC + "_CONSUMER")
public class IWorkBizCallbackListener implements RocketMQListener<IWorkBizCallbackResultMessage>, InitializingBean {
private final IWorkCallbackLogService logService;
private final IWorkProperties properties;
private final IWorkBizCallbackProducer producer;
private ScheduledExecutorService scheduler;
@Override
public void afterPropertiesSet() {
scheduler = Executors.newScheduledThreadPool(1, r -> new Thread(r, "iwork-callback-retry"));
}
@Override
public void onMessage(IWorkBizCallbackResultMessage message) {
String key = message.getBizCallbackKey();
if (message.isSuccess()) {
logService.markSuccess(message.getRequestId());
return;
}
int attempt = message.getAttempt() + 1;
logService.incrementRetry(message.getRequestId());
int maxAttempts = message.getMaxAttempts() > 0 ? message.getMaxAttempts() : properties.getCallback().getRetry().getMaxAttempts();
if (attempt < maxAttempts) {
logService.markFailure(message.getRequestId(), message.getErrorMessage(), true, maxAttempts);
IWorkBizCallbackMessage next = IWorkBizCallbackMessage.builder()
.requestId(message.getRequestId())
.bizCallbackKey(key)
.payload(message.getPayload())
.attempt(attempt)
.maxAttempts(maxAttempts)
.build();
int delay = properties.getCallback().getRetry().getDelaySeconds();
scheduler.schedule(() -> producer.send(next), delay, TimeUnit.SECONDS);
} else {
logService.markFailure(message.getRequestId(), message.getErrorMessage(), false, maxAttempts);
}
}
}

View File

@@ -0,0 +1,18 @@
package com.zt.plat.module.system.mq.iwork;
import lombok.RequiredArgsConstructor;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class IWorkBizCallbackProducer {
private final RocketMQTemplate rocketMQTemplate;
public void send(IWorkBizCallbackMessage message) {
// 使用 tag=bizCallbackKey方便业务侧按 key 订阅
String destination = IWorkBizCallbackMessage.TOPIC + ":" + message.getBizCallbackKey();
rocketMQTemplate.syncSend(destination, message);
}
}

View File

@@ -0,0 +1,21 @@
package com.zt.plat.module.system.service.integration.iwork;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
public interface IWorkCallbackLogService {
IWorkSealLogDO upsertOnCallback(IWorkWorkflowCallbackReqVO reqVO, int maxRetry, String rawBody);
void markSuccess(String requestId);
void markFailure(String requestId, String error, boolean retrying, int maxRetry);
void incrementRetry(String requestId);
PageResult<IWorkSealLogDO> page(IWorkCallbackLogPageReqVO reqVO);
void resetAndDispatch(String requestId);
}

View File

@@ -0,0 +1,16 @@
package com.zt.plat.module.system.service.integration.iwork.callback;
public interface IWorkBizCallbackHandler {
/**
* 返回 handler 能处理的 bizCallbackKey。
*/
String getBizCallbackKey();
/**
* 处理业务回调。
* @param requestId iWork requestId
* @param payload 回调负载
*/
void handle(String requestId, Object payload) throws Exception;
}

View File

@@ -0,0 +1,20 @@
package com.zt.plat.module.system.service.integration.iwork.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum IWorkCallbackStatusEnum {
CREATE_PENDING(0),
CREATE_SUCCESS(1),
CREATE_FAILED(2),
CALLBACK_PENDING(3),
CALLBACK_SUCCESS(4),
CALLBACK_FAILED(5),
CALLBACK_RETRYING(6),
CALLBACK_RETRY_FAILED(7);
private final int status;
}

View File

@@ -0,0 +1,130 @@
package com.zt.plat.module.system.service.integration.iwork.impl;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.json.JsonUtils;
import com.zt.plat.framework.common.util.object.ObjectUtils;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
import com.zt.plat.module.system.dal.mysql.iwork.IWorkSealLogMapper;
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer;
import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService;
import com.zt.plat.module.system.service.integration.iwork.enums.IWorkCallbackStatusEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class IWorkCallbackLogServiceImpl implements IWorkCallbackLogService {
private static final int RAW_MAX = 2000;
private final IWorkSealLogMapper logMapper;
private final IWorkBizCallbackProducer producer;
private final IWorkProperties properties;
@Override
@Transactional(rollbackFor = Exception.class)
public IWorkSealLogDO upsertOnCallback(IWorkWorkflowCallbackReqVO reqVO, int maxRetry, String rawBody) {
IWorkSealLogDO existing = logMapper.selectByRequestId(reqVO.getRequestId());
IWorkSealLogDO log = Optional.ofNullable(existing).orElseGet(IWorkSealLogDO::new);
log.setRequestId(reqVO.getRequestId());
log.setBusinessCode(reqVO.getBusinessCode());
log.setBizCallbackKey(reqVO.getBizCallbackKey());
log.setStatus(IWorkCallbackStatusEnum.CALLBACK_PENDING.getStatus());
log.setRetryCount(ObjectUtils.defaultIfNull(log.getRetryCount(), 0));
log.setMaxRetry(maxRetry);
log.setRawCallback(truncate(rawBody));
log.setLastCallbackTime(LocalDateTime.now());
if (log.getId() == null) {
logMapper.insert(log);
} else {
logMapper.updateById(log);
}
return log;
}
@Override
public void markSuccess(String requestId) {
IWorkSealLogDO log = new IWorkSealLogDO();
log.setRequestId(requestId);
log.setStatus(IWorkCallbackStatusEnum.CALLBACK_SUCCESS.getStatus());
log.setLastErrorMessage(null);
log.setLastCallbackTime(LocalDateTime.now());
logMapper.update(log, new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<IWorkSealLogDO>()
.eq(IWorkSealLogDO::getRequestId, requestId));
}
@Override
public void markFailure(String requestId, String error, boolean retrying, int maxRetry) {
IWorkSealLogDO log = new IWorkSealLogDO();
log.setRequestId(requestId);
log.setStatus(retrying ? IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus() : IWorkCallbackStatusEnum.CALLBACK_FAILED.getStatus());
log.setLastErrorMessage(error);
log.setLastCallbackTime(LocalDateTime.now());
log.setMaxRetry(maxRetry);
logMapper.update(log, new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<IWorkSealLogDO>()
.eq(IWorkSealLogDO::getRequestId, requestId));
}
@Override
public void incrementRetry(String requestId) {
IWorkSealLogDO db = logMapper.selectByRequestId(requestId);
if (db == null) {
return;
}
IWorkSealLogDO log = new IWorkSealLogDO();
log.setId(db.getId());
log.setRetryCount(ObjectUtils.defaultIfNull(db.getRetryCount(), 0) + 1);
log.setLastCallbackTime(LocalDateTime.now());
logMapper.updateById(log);
}
@Override
public PageResult<IWorkSealLogDO> page(IWorkCallbackLogPageReqVO reqVO) {
return logMapper.selectPage(reqVO);
}
@Override
public void resetAndDispatch(String requestId) {
IWorkSealLogDO db = logMapper.selectByRequestId(requestId);
if (db == null) {
return;
}
IWorkSealLogDO log = new IWorkSealLogDO();
log.setId(db.getId());
log.setRetryCount(0);
log.setStatus(IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus());
log.setLastCallbackTime(LocalDateTime.now());
logMapper.updateById(log);
int maxAttempts = properties.getCallback().getRetry().getMaxAttempts();
Object payload;
try {
payload = JsonUtils.parseObject(db.getRawCallback(), Object.class);
} catch (Exception ex) {
payload = db.getRawCallback();
}
producer.send(IWorkBizCallbackMessage.builder()
.requestId(db.getRequestId())
.bizCallbackKey(db.getBizCallbackKey())
.payload(payload)
.attempt(0)
.maxAttempts(maxAttempts)
.build());
}
private String truncate(String raw) {
if (!StringUtils.hasText(raw)) {
return raw;
}
return raw.length() > RAW_MAX ? raw.substring(0, RAW_MAX) : raw;
}
}

View File

@@ -182,7 +182,7 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
}
AtomicReference<Long> attachmentIdRef = new AtomicReference<>();
TenantUtils.execute(tenantId, () -> attachmentIdRef.set(saveCallbackAttachment(fileUrl, reqVO.getFileName(), referenceBusinessFile, reqVO.getSsoToken())));
TenantUtils.execute(tenantId, () -> attachmentIdRef.set(saveCallbackAttachment(fileUrl, reqVO.getFileName(), referenceBusinessFile)));
return attachmentIdRef.get();
}
@@ -251,14 +251,14 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
return executeOaRequest(request);
}
private Long saveCallbackAttachment(String fileUrl, String overrideFileName, BusinessFileRespDTO referenceBusinessFile, String ssoToken) {
private Long saveCallbackAttachment(String fileUrl, String overrideFileName, BusinessFileRespDTO referenceBusinessFile) {
Long businessId = referenceBusinessFile.getBusinessId();
FileCreateReqDTO fileCreateReqDTO = new FileCreateReqDTO();
fileCreateReqDTO.setName(resolveFileName(overrideFileName, fileUrl));
fileCreateReqDTO.setDirectory(null);
fileCreateReqDTO.setType(null);
fileCreateReqDTO.setContent(downloadFileBytes(fileUrl, ssoToken));
fileCreateReqDTO.setContent(downloadFileBytes(fileUrl));
CommonResult<FileRespDTO> fileResult = fileApi.createFileWithReturn(fileCreateReqDTO);
if (fileResult == null || !fileResult.isSuccess() || fileResult.getData() == null) {
@@ -297,10 +297,8 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
return businessFile;
}
private byte[] downloadFileBytes(String fileUrl, String ssoToken) {
// 如果回调已提供 ssoToken按需拼接后下载 OA 附件
String finalUrl = appendSsoTokenIfNeeded(fileUrl, ssoToken);
private byte[] downloadFileBytes(String fileUrl) {
String finalUrl = fileUrl;
OkHttpClient client = okHttpClient();
Request request = new Request.Builder().url(finalUrl).get().build();
try (Response response = client.newCall(request).execute()) {
@@ -317,22 +315,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService {
}
}
private String appendSsoTokenIfNeeded(String fileUrl, String ssoToken) {
// 未提供 token 或 URL 为空,直接返回原链接
if (!StringUtils.hasText(ssoToken) || !StringUtils.hasText(fileUrl)) {
return fileUrl;
}
// 已包含 ssoToken不区分大小写则不重复添加
String lower = fileUrl.toLowerCase();
if (lower.contains("ssotoken=")) {
return fileUrl;
}
// 简单拼接查询参数
return fileUrl.contains("?")
? fileUrl + "&ssoToken=" + ssoToken
: fileUrl + "?ssoToken=" + ssoToken;
}
private String resolveFileName(String overrideName, String fileUrl) {
if (StringUtils.hasText(overrideName)) {
return overrideName;

View File

@@ -0,0 +1,63 @@
package com.zt.plat.module.system.service.integration.iwork.impl;
import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO;
import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO;
import com.zt.plat.module.system.dal.mysql.iwork.IWorkSealLogMapper;
import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties;
import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
class IWorkCallbackLogServiceImplTest {
private IWorkSealLogMapper mapper;
private IWorkBizCallbackProducer producer;
private IWorkProperties properties;
private IWorkCallbackLogServiceImpl service;
@BeforeEach
void setup() {
mapper = mock(IWorkSealLogMapper.class);
producer = mock(IWorkBizCallbackProducer.class);
properties = new IWorkProperties();
service = new IWorkCallbackLogServiceImpl(mapper, producer, properties);
}
@Test
void upsertOnCallback_shouldTruncateRaw() {
String longRaw = "x".repeat(2100);
IWorkWorkflowCallbackReqVO req = new IWorkWorkflowCallbackReqVO();
req.setRequestId("REQ-1");
req.setBizCallbackKey("key");
ArgumentCaptor<IWorkSealLogDO> captor = ArgumentCaptor.forClass(IWorkSealLogDO.class);
when(mapper.selectByRequestId("REQ-1")).thenReturn(null);
service.upsertOnCallback(req, 3, longRaw);
verify(mapper).insert(captor.capture());
IWorkSealLogDO saved = captor.getValue();
assertThat(saved.getRawCallback()).hasSize(2000);
assertThat(saved.getMaxRetry()).isEqualTo(3);
}
@Test
void incrementRetry_shouldIncreaseCount() {
IWorkSealLogDO existing = new IWorkSealLogDO();
existing.setId(1L);
existing.setRequestId("REQ-2");
existing.setRetryCount(1);
when(mapper.selectByRequestId("REQ-2")).thenReturn(existing);
service.incrementRetry("REQ-2");
ArgumentCaptor<IWorkSealLogDO> captor = ArgumentCaptor.forClass(IWorkSealLogDO.class);
verify(mapper).updateById(captor.capture());
assertThat(captor.getValue().getRetryCount()).isEqualTo(2);
}
}