build / build-push (push) Failing after 55s
在 b369527 之上增量移植 FL2.0 后端能力,不改既有线级停发/指派预览/流程图入口:
- 部门目录:FlowDepartmentController(/admin/department/{groups,options,resolve,members})
+ UserBaseDepartmentDirectory(先按会话校域解析引用,再读成员;目录不可用 fail-closed)
- 退回重提 D14:POST /oapi/flow/instance/{instanceId}/resubmit,仅原提交人 CAS 路径放行,
OAPI 侧 submitterId 路径继续 fail-closed
- 授权管理:/api/flow/admin/usage-grant/{schoolList,summary}(校域取管理员会话,无 schId 入参)
- 来源主体:解析器派生 ub-app:{applicationInfo.id},不再读取 UserBase 的 sourcePrincipalId 字段
- 回调来源策略:SELF 身份强校验;NULL 来源不广播;复合幂等键 (event_id,reg_id)
- 测试夹具:许可/主体/开通桩按新契约修正,FlowEngineIT/FormalApplyCommandIT/
CallbackDispatchIT/OapiFlowContractIT 共 117 例在真实 MySQL 8 通过
不含:FlSourceAuthorizationTest(其断言的两个 Repository 派生方法属另一实现线)
60 lines
2.5 KiB
Java
60 lines
2.5 KiB
Java
package com.yunhai.flowcenter.service;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.yunhai.platform.apistd.BizException;
|
|
import com.yunhai.platform.openapi.base.OpenApiAppClient;
|
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
/**
|
|
* 调用应用主体解析(D02/D09/D11:appKey 是验签凭据,不是主体)。
|
|
*
|
|
* <p>主体=UserBase {@code application_info} 主键 id(稳定整型主键,appKey 可重置/复用不影响它)。
|
|
* 加 {@link #SOURCE_PREFIX} 前缀落库,与未来可能引入的 UUID 主体空间不撞。
|
|
* 不可达、查无、缺主键一律拒绝,不回退 appKey/数字自增猜测。</p>
|
|
*/
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class FlowSourcePrincipalResolver {
|
|
|
|
/** 来源主体列(varchar(36))的命名空间前缀:标明"id 来自 UserBase 应用注册表主键"。 */
|
|
public static final String SOURCE_PREFIX = "ub-app:";
|
|
|
|
private final OpenApiAppClient appClient;
|
|
|
|
/** 稳定主体字符串;appId 缺失即拒(调用方不得用猜测值补全)。 */
|
|
public static String sourcePrincipalIdOf(Integer appId) {
|
|
if (appId == null || appId <= 0) {
|
|
throw new BizException(ErrorCode.UNAUTHORIZED, "调用应用主体不可解析");
|
|
}
|
|
return SOURCE_PREFIX + appId;
|
|
}
|
|
|
|
public Principal resolve(String authenticatedAppKey) {
|
|
if (authenticatedAppKey == null || authenticatedAppKey.trim().isEmpty()) {
|
|
throw new BizException(ErrorCode.UNAUTHORIZED, "缺少已认证的调用应用");
|
|
}
|
|
JsonNode app;
|
|
try {
|
|
app = appClient.getAppByKey(authenticatedAppKey.trim());
|
|
} catch (RuntimeException unavailable) {
|
|
throw new BizException(ErrorCode.UNAUTHORIZED, "调用应用主体解析不可用,请稍后重试");
|
|
}
|
|
if (app == null || app.isNull() || !app.path("id").isIntegralNumber() || !app.path("id").canConvertToInt()) {
|
|
throw new BizException(ErrorCode.UNAUTHORIZED, "调用应用主体不可解析");
|
|
}
|
|
Integer appId = app.path("id").intValue();
|
|
return new Principal(sourcePrincipalIdOf(appId), appId, authenticatedAppKey.trim(),
|
|
app.path("appCode").isTextual() ? app.path("appCode").textValue() : null);
|
|
}
|
|
|
|
@lombok.RequiredArgsConstructor
|
|
public static class Principal {
|
|
public final String id;
|
|
public final Integer appId;
|
|
public final String appKey;
|
|
public final String appCode;
|
|
}
|
|
}
|