- FlowCenterClient:apply/getInstanceByBiz/registerCallback 由 ad-hoc TargetSpec
改走 YschFlowOpenApi 门面(ApplyBody/CallbackRegBody/ApplyResultVo/CallbackRegVo
继承 SDK DTO);instance 走 raw() 预绑视图保留 Map 全字段透传。
- CmsSiteDirectoryImpl:DIRECTORY_KEY 包可见;目录未配且 svcchall.cms.base-url
缺失时构造期 fail-fast(原 @Value 无默认值会直接炸启动,无明确提示)。
- FlowCenterClient:svchall.flow.base-url 默认值去掉 + 同款 fail-fast(enabled=false 不校验)。
- application.yml:新增 yh.open.services.{flowcenter,cms},删 svchall.cms.base-url /
svchall.flow.base-url —— 地址唯一来源=服务目录。
- 测试:新增 FlowCenterClientFacadeTest(6 例,冻结门面/归一目标/回落/fail-fast);
CmsSiteDirectoryImplTest 加目录命名目标 + fail-fast 用例。112 测试全绿。
This commit is contained in:
@@ -471,11 +471,35 @@ public final class SvcModels {
|
||||
private Integer pageSize = 10;
|
||||
}
|
||||
|
||||
// ===== 流程(管理端回显) =====
|
||||
// ===== 流程(管理端回显 + flow-center 门面出入参) =====
|
||||
|
||||
/** 字段单一事实源=SDK 门面 DTO(可直接作 YschFlowOpenApi 泛型出入参) */
|
||||
@Data
|
||||
@lombok.EqualsAndHashCode(callSuper = true)
|
||||
public static class FlowSummaryVo extends com.yunhai.platform.openapi.flow.model.FlowSummary {
|
||||
}
|
||||
|
||||
/** 代发起请求体(门面 {@code YschFlowOpenApi.apply} 的 B 绑定;字段继承 SDK FlowApplyBody) */
|
||||
@Data
|
||||
@lombok.EqualsAndHashCode(callSuper = true)
|
||||
public static class ApplyBody extends com.yunhai.platform.openapi.flow.model.FlowApplyBody {
|
||||
}
|
||||
|
||||
/** 代发起响应(门面 R 绑定;{@code duplicated=true} 表示命中 biz uk 幂等返回既有办件) */
|
||||
@Data
|
||||
@lombok.EqualsAndHashCode(callSuper = true)
|
||||
public static class ApplyResultVo extends com.yunhai.platform.openapi.flow.model.FlowApplyResult {
|
||||
}
|
||||
|
||||
/** 回调注册请求体(门面 {@code YschFlowOpenApi.registerCallback} 的 B 绑定) */
|
||||
@Data
|
||||
@lombok.EqualsAndHashCode(callSuper = true)
|
||||
public static class CallbackRegBody extends com.yunhai.platform.openapi.flow.model.FlowCallbackRegBody {
|
||||
}
|
||||
|
||||
/** 回调注册响应(下发的 secret 用于验 X-Flw-Signature) */
|
||||
@Data
|
||||
@lombok.EqualsAndHashCode(callSuper = true)
|
||||
public static class CallbackRegVo extends com.yunhai.platform.openapi.flow.model.FlowCallbackRegResult {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,21 +43,28 @@ public class CmsSiteDirectoryImpl implements SiteDirectory {
|
||||
.build();
|
||||
|
||||
/** 服务目录命名目标(cms 未晋升枚举,按名直调);目录未配置时为 null=走 legacy ad-hoc spec */
|
||||
private static final String DIRECTORY_KEY = "cms";
|
||||
static final String DIRECTORY_KEY = "cms";
|
||||
|
||||
private static final String LEGACY_KEY = "svchall.cms.base-url";
|
||||
|
||||
private final YschOpenCall openCall;
|
||||
private final TargetSpec cmsSpec;
|
||||
private final boolean directoryResolved;
|
||||
|
||||
public CmsSiteDirectoryImpl(YschOpenCall openCall,
|
||||
@Value("${svchall.cms.base-url}") String cmsBaseUrl) {
|
||||
@Value("${svchall.cms.base-url:}") String cmsBaseUrl) {
|
||||
this.openCall = openCall;
|
||||
this.directoryResolved = openCall.hasTarget(DIRECTORY_KEY);
|
||||
this.directoryResolved = openCall != null && openCall.hasTarget(DIRECTORY_KEY);
|
||||
if (directoryResolved) {
|
||||
this.cmsSpec = null;
|
||||
return;
|
||||
}
|
||||
String base = trimTrailing(cmsBaseUrl == null ? "" : cmsBaseUrl.trim());
|
||||
if (base.isEmpty()) {
|
||||
// 目录未配且 legacy 键也空:构造期 fail-fast——别把"地址没配"伪装成"站点服务不可达"
|
||||
throw new IllegalStateException("CMS 站点目录调用地址未配置:请配置服务目录 yh.open.services."
|
||||
+ DIRECTORY_KEY + "(推荐,容器直连完整 URL)或回落键 " + LEGACY_KEY);
|
||||
}
|
||||
URI uri = URI.create(base);
|
||||
String host = uri.getScheme() + "://" + uri.getRawAuthority();
|
||||
String path = uri.getRawPath() == null ? "" : uri.getRawPath();
|
||||
|
||||
@@ -42,20 +42,27 @@ public class FlowCenterClient implements FlowClient {
|
||||
|
||||
public FlowCenterClient(YschOpenCall openCall, ObjectMapper objectMapper,
|
||||
@Value("${svchall.flow.enabled:false}") boolean enabled,
|
||||
@Value("${svchall.flow.base-url:http://localhost:8085}") String baseUrl) {
|
||||
@Value("${svchall.flow.base-url:}") String baseUrl) {
|
||||
this.openCall = openCall;
|
||||
this.objectMapper = objectMapper;
|
||||
this.enabled = enabled;
|
||||
String base = trimTrailing(baseUrl == null ? "" : baseUrl.trim());
|
||||
URI uri = URI.create(base);
|
||||
String host = uri.getScheme() + "://" + uri.getRawAuthority();
|
||||
String path = uri.getRawPath() == null ? "" : uri.getRawPath();
|
||||
this.flowSpec = TargetSpec.of(host, path);
|
||||
this.directoryResolved = openCall.hasTarget(
|
||||
this.directoryResolved = openCall != null && openCall.hasTarget(
|
||||
com.yunhai.platform.openapi.core.PlatformService.FLOWCENTER.key());
|
||||
if (directoryResolved) {
|
||||
com.yunhai.platform.openapi.flow.YschFlowOpenApi.setCall(openCall);
|
||||
}
|
||||
if (enabled && base.isEmpty() && !directoryResolved) {
|
||||
// 目录未配且 legacy 键也空:构造期 fail-fast——别把"地址没配"伪装成"流程中心不可达"。
|
||||
// enabled=false(显式未接入)时不校验——地址本就不需要。
|
||||
throw new IllegalStateException("流程中心调用地址未配置:请配置服务目录 yh.open.services."
|
||||
+ com.yunhai.platform.openapi.core.PlatformService.FLOWCENTER.key()
|
||||
+ "(推荐,容器直连完整 URL)或回落键 svchall.flow.base-url");
|
||||
}
|
||||
URI uri = URI.create(base);
|
||||
String host = uri.getScheme() + "://" + uri.getRawAuthority();
|
||||
String path = uri.getRawPath() == null ? "" : uri.getRawPath();
|
||||
this.flowSpec = TargetSpec.of(host, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,26 +104,30 @@ public class FlowCenterClient implements FlowClient {
|
||||
String bizUrl, String bizSnapshot, Integer submitterId, String submitterName,
|
||||
String formDataJson) {
|
||||
requireEnabled();
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("flowGuid", flowGuid);
|
||||
body.put("bizType", bizType);
|
||||
body.put("bizId", bizId);
|
||||
body.put("bizTitle", bizTitle);
|
||||
if (bizUrl != null) {
|
||||
body.put("bizUrl", bizUrl);
|
||||
}
|
||||
if (bizSnapshot != null) {
|
||||
body.put("bizSnapshot", bizSnapshot);
|
||||
}
|
||||
body.put("submitterId", submitterId);
|
||||
body.put("submitterName", submitterName);
|
||||
SvcModels.ApplyBody body = new SvcModels.ApplyBody();
|
||||
body.setFlowGuid(flowGuid);
|
||||
body.setBizType(bizType);
|
||||
body.setBizId(bizId);
|
||||
body.setBizTitle(bizTitle);
|
||||
body.setBizUrl(bizUrl);
|
||||
body.setBizSnapshot(bizSnapshot);
|
||||
body.setSubmitterId(submitterId);
|
||||
body.setSubmitterName(submitterName);
|
||||
if (formDataJson != null && !formDataJson.isEmpty()) {
|
||||
try {
|
||||
body.put("formData", objectMapper.readValue(formDataJson, Map.class));
|
||||
body.setFormData(objectMapper.readValue(formDataJson, Map.class));
|
||||
} catch (Exception e) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "表单数据不是合法 JSON 对象");
|
||||
}
|
||||
}
|
||||
if (directoryResolved) {
|
||||
SvcModels.ApplyResultVo result = com.yunhai.platform.openapi.flow.YschFlowOpenApi.apply(
|
||||
schId, body, SvcModels.ApplyResultVo.class);
|
||||
if (result == null || result.getInstanceId() == null) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "流程中心代发起返回异常(缺少 instanceId)");
|
||||
}
|
||||
return new ApplyResult(result.getInstanceId(), result.isDuplicated());
|
||||
}
|
||||
JsonNode data = openCall.post(flowSpec, "/oapi/flow/apply",
|
||||
Collections.singletonMap("schId", String.valueOf(schId)), body, JsonNode.class);
|
||||
if (data == null || data.get("instanceId") == null) {
|
||||
@@ -133,24 +144,38 @@ public class FlowCenterClient implements FlowClient {
|
||||
query.put("schId", String.valueOf(schId));
|
||||
query.put("bizType", bizType);
|
||||
query.put("bizId", bizId);
|
||||
if (directoryResolved) {
|
||||
// 门面 instance(...) 只反序列化 FlowInstance 的字段子集;本方法契约是 Map 全字段透传
|
||||
// (对账/查看守卫要 status/version/assigneeIds/nodeSummary…),故走预绑 flowcenter 的
|
||||
// 泛化视图 raw()——目标名由门面承载,不再回落应用专属地址。
|
||||
return com.yunhai.platform.openapi.flow.YschFlowOpenApi.raw()
|
||||
.get("/oapi/flow/instance", query, Map.class);
|
||||
}
|
||||
return openCall.get(flowSpec, "/oapi/flow/instance", query, Map.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String registerCallback(Long schId, String flowGuid, String callbackUrl, String eventTypes) {
|
||||
requireEnabled();
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("flowGuid", flowGuid);
|
||||
body.put("callbackUrl", callbackUrl);
|
||||
if (eventTypes != null && !eventTypes.isEmpty()) {
|
||||
body.put("eventTypes", eventTypes);
|
||||
}
|
||||
SvcModels.CallbackRegBody body = new SvcModels.CallbackRegBody();
|
||||
body.setFlowGuid(flowGuid);
|
||||
body.setCallbackUrl(callbackUrl);
|
||||
body.setEventTypes(eventTypes);
|
||||
// 申报本服务 context-path(从回调 URL path 段推导,去掉尾部 /callback/flow):
|
||||
// 推送方按它剥离后再签应用签名,与本服务 AppAuthFilter/FlowCallbackVerifyFilter 剥
|
||||
// contextPath 后的 target 对齐——本地裸地址(无 ctx)推导为空串,行为不变。
|
||||
// contextPath 后的 target 对齐——本地裸地址(无 ctx)推导为空串,行为不变(不下发空串)。
|
||||
String basePath = deriveCallbackBasePath(callbackUrl);
|
||||
if (basePath != null && !basePath.isEmpty()) {
|
||||
body.put("basePath", basePath);
|
||||
body.setBasePath(basePath);
|
||||
}
|
||||
if (directoryResolved) {
|
||||
SvcModels.CallbackRegVo result = com.yunhai.platform.openapi.flow.YschFlowOpenApi
|
||||
.registerCallback(schId, body, SvcModels.CallbackRegVo.class);
|
||||
Object secret = result == null ? null : result.getSecret();
|
||||
if (secret == null) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "流程中心回调注册返回异常(缺少 secret)");
|
||||
}
|
||||
return String.valueOf(secret);
|
||||
}
|
||||
Map<?, ?> data = openCall.post(flowSpec, "/oapi/flow/callback/reg",
|
||||
Collections.singletonMap("schId", String.valueOf(schId)), body, Map.class);
|
||||
|
||||
@@ -59,13 +59,17 @@ yh:
|
||||
trusted-apps:
|
||||
100005: 2c7885de594742cc840b8762910b2d2e
|
||||
open:
|
||||
enabled: true
|
||||
# 身份密钥唯一来源 = yh.app-auth.app-key/app-secret(本文件 app-auth 段),open.* 副本已删
|
||||
base-host: http://base.yunhaixingtong.com
|
||||
base-path: /userbase
|
||||
app-key: 100018
|
||||
# 联调态密钥(与 flow-center trusted-apps["100018"] 逐字一致);
|
||||
# 注意旧占位"裸 32 个 0"会被 YAML 按八进制整数解析成 "0",签名时 SignUtil 校验直接失败,必须带字母+引号
|
||||
app-secret: "svc-hall-dev-secret-100018"
|
||||
# 服务目录(子应用配置优化方案 §4.2):跨服务出站唯一地址来源,命名目标走 SDK 门面 /
|
||||
# YschOpenCall。cms 未晋升 PlatformService 枚举(按名直调);flowcenter 已入枚举,此处
|
||||
# 显式覆盖为本地端口。完整 URL 拆 host+ctx,per-target base-path 与提供方 context-path
|
||||
# 对齐 → 签名天然一致。生产改容器直连:flowcenter=http://flow-center-service:10008/flow-center、
|
||||
# cms=http://yschcms-service:10005/yschcms。
|
||||
services:
|
||||
flowcenter: http://localhost:8085
|
||||
cms: http://base.yunhaixingtong.com:10005/yschcms
|
||||
|
||||
# ===== 办事大厅专属配置 =====
|
||||
svchall:
|
||||
@@ -78,17 +82,15 @@ svchall:
|
||||
# 服务部门"容器组"类型(UserBase GroupTypeEnum,默认 7=教师组;口径承袭 CMS cms.svc.item.dept-group-type)
|
||||
dept-group-type: 7
|
||||
cms:
|
||||
# CMS 基址(站点目录 /oapi/site/list 数据源)。必须指向**后端**且带 context-path:
|
||||
# 内网 CMS 挂在 base 域下 /yschcms,但 Caddy 只反代 /yschcms/{front,api,portal},
|
||||
# 没有 /oapi 路由——用 http://base.yunhaixingtong.com/yschcms 打 /oapi/* 会拿到 Caddy 404,
|
||||
# 因此走容器直连端口 10005(实测 /yschcms/oapi/site/list 能到应用)。
|
||||
# 本机起 CMS(Cms/local-debug/start-cms.bat)时改用 http://localhost:8072/cms;
|
||||
# 生产由 projman 注入覆盖。
|
||||
base-url: http://base.yunhaixingtong.com:10005/yschcms
|
||||
# CMS 基址已迁至服务目录 yh.open.services.cms(上方 open 段)。
|
||||
# 务必指向**后端**且带 context-path:内网 CMS 挂在 base 域下 /yschcms,但 Caddy 只反代
|
||||
# /yschcms/{front,api,portal},没有 /oapi 路由——用 http://base.yunhaixingtong.com/yschcms
|
||||
# 打 /oapi/* 会拿到 Caddy 404,因此必须是容器直连端口 10005
|
||||
# (实测 /yschcms/oapi/site/list 能到应用)。
|
||||
flow:
|
||||
# 流程中心 flow-center 基址;2026-09-12 联调置 true(本地 8085 已部署 flow-center M1)
|
||||
# 流程中心 flow-center 基址已迁至服务目录 yh.open.services.flowcenter(上方 open 段)。
|
||||
# enabled=false(本地未部署 flow-center)时全部方法抛业务异常,消费方按需降级。
|
||||
enabled: true
|
||||
base-url: http://localhost:8085
|
||||
# 对账 Job 应急闸(契约冻结"消费方免每日对账":flow-center 持续重试+死信+挂起告警为主链路)。
|
||||
# 默认 false 停用;仅大面积漂移事故应急时置 true 开闸(每日 04:00 跑)
|
||||
reconcile-enabled: false
|
||||
|
||||
Reference in New Issue
Block a user