feat(callback/app): 回调探测端点 /admin/callback/probe/{id}——签 flow.ping 未知事件码走真实管道同步一发(信封同构 CallbackDispatcher,结果落 flw_callback_log 一次性行+resp_body 响应摘要列);应用辅助统计 /admin/app/stats(appKey→本校回调注册数,应用设置页对接深度列)
build / build-push (push) Failing after 14s
build / build-push (push) Failing after 14s
This commit is contained in:
@@ -211,6 +211,105 @@ public class CallbackPushService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调探测(2026-10-11 管理端增强):以该注册的 secret 按现行信封签一条未知事件码 flow.ping,
|
||||
* 走真实推送管道同步一发——接收方按规范 §6.4 对未知事件码"记日志后 200",不破坏业务数据。
|
||||
* 探测是一次性行为:结果落 flw_callback_log(成功=1 / 失败=2,永不进重试队列),
|
||||
* 响应体摘要落 resp_body。返回 {success, httpStatus, elapsedMs, respSnippet, error}。
|
||||
*/
|
||||
public java.util.Map<String, Object> probe(Long schId, FlwCallbackReg reg) {
|
||||
String eventId = UUID.randomUUID().toString().replace("-", "");
|
||||
String body = probePayload(schId, reg, eventId);
|
||||
long start = System.currentTimeMillis();
|
||||
String ts = String.valueOf(System.currentTimeMillis());
|
||||
String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
|
||||
Integer httpStatus = null;
|
||||
String respSnippet = null;
|
||||
String error = null;
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("X-Flw-EventId", eventId);
|
||||
headers.set("X-Flw-Timestamp", ts);
|
||||
headers.set("X-Flw-Nonce", nonce);
|
||||
headers.set("X-Flw-Signature", sign(reg.getSecret(), ts, nonce, body));
|
||||
RestTemplate template = CallbackAppSigner.signedTemplate(
|
||||
appIdentityProvider.getIfAvailable(), reg.getBasePath(), 5000, 10000);
|
||||
ResponseEntity<String> resp = template.exchange(reg.getCallbackUrl(), HttpMethod.POST,
|
||||
new HttpEntity<>(body, headers), String.class);
|
||||
httpStatus = resp.getStatusCodeValue();
|
||||
respSnippet = resp.getBody() == null ? null : truncate(resp.getBody());
|
||||
} catch (Exception e) {
|
||||
error = rootMessage(e);
|
||||
}
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
boolean ok = httpStatus != null && httpStatus >= 200 && httpStatus < 300;
|
||||
persistProbeLog(schId, reg, eventId, body, ok, httpStatus, respSnippet, error);
|
||||
log.info("[flow-center] 回调探测:reg={} url={} ok={} http={} elapsed={}ms",
|
||||
reg.getId(), reg.getCallbackUrl(), ok, httpStatus, elapsed);
|
||||
java.util.Map<String, Object> out = new java.util.HashMap<>();
|
||||
out.put("success", ok);
|
||||
out.put("httpStatus", httpStatus);
|
||||
out.put("elapsedMs", elapsed);
|
||||
out.put("respSnippet", respSnippet);
|
||||
out.put("error", error);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 探测信封:与 CallbackDispatcher 现行事件同构,event=flow.ping(未知码),instanceId=0 哨兵 */
|
||||
private String probePayload(Long schId, FlwCallbackReg reg, String eventId) {
|
||||
java.util.Map<String, Object> payload = new java.util.HashMap<>();
|
||||
payload.put("eventId", eventId);
|
||||
payload.put("event", "flow.ping");
|
||||
payload.put("schId", schId);
|
||||
payload.put("flowGuid", reg.getFlowGuid());
|
||||
payload.put("instanceId", 0L);
|
||||
payload.put("instanceVersion", 0);
|
||||
payload.put("bizType", "probe");
|
||||
payload.put("bizId", String.valueOf(reg.getId()));
|
||||
payload.put("bizTitle", "回调探测 flow.ping");
|
||||
payload.put("occurredTime", LocalDateTime.now().format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
payload.put("taskId", null);
|
||||
payload.put("operatorUserId", null);
|
||||
payload.put("operatorName", null);
|
||||
java.util.Map<String, Object> data = new java.util.HashMap<>();
|
||||
data.put("probe", true);
|
||||
data.put("message", "flow-center 回调连通性探测(未知事件码,按规范 §6.4 记日志后 200 即可)");
|
||||
payload.put("data", data);
|
||||
try {
|
||||
return new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(payload);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("探测信封序列化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 探测结果落日志行(一次性行为:成功=1 / 失败=2,status≠PENDING 永不被重试扫描捞起) */
|
||||
private void persistProbeLog(Long schId, FlwCallbackReg reg, String eventId, String body,
|
||||
boolean ok, Integer httpStatus, String respSnippet, String error) {
|
||||
try {
|
||||
FlwCallbackLog row = new FlwCallbackLog();
|
||||
row.setSchId(schId);
|
||||
row.setRegId(reg.getId());
|
||||
row.setEventId(eventId);
|
||||
row.setEvent("flow.ping");
|
||||
row.setInstanceId(0L);
|
||||
row.setBizType("probe");
|
||||
row.setBizId(String.valueOf(reg.getId()));
|
||||
row.setPayloadJson(body);
|
||||
row.setRespBody(respSnippet);
|
||||
row.setStatus(ok ? FlwEnums.CallbackLogStatus.SUCCESS.getCode()
|
||||
: FlwEnums.CallbackLogStatus.DEAD.getCode());
|
||||
row.setRetryCount(0);
|
||||
row.setNextRetryTime(LocalDateTime.now());
|
||||
row.setLastHttpStatus(httpStatus == null ? null : String.valueOf(httpStatus));
|
||||
row.setLastError(error == null ? null : truncate(error));
|
||||
row.setPushTime(LocalDateTime.now());
|
||||
logRepository.save(row);
|
||||
} catch (Exception e) {
|
||||
log.warn("[flow-center] 探测日志落库失败:reg={} {}", reg.getId(), e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static String sign(String secret, String timestamp, String nonce, String body) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
|
||||
@@ -91,6 +91,12 @@ public class FlowAdminController {
|
||||
Boolean.parseBoolean(String.valueOf(body.get("enabled"))));
|
||||
}
|
||||
|
||||
/** 应用辅助统计(2026-10-11 增强):appKey → 回调注册数(本校),应用设置页"对接深度"列 */
|
||||
@GetMapping("/app/stats")
|
||||
public Map<String, Long> appStats(BoaUser user) {
|
||||
return appAdminService.stats(adminGuard.requireAdmin(user, null));
|
||||
}
|
||||
|
||||
@GetMapping("/def/get")
|
||||
public FlwDef defGet(BoaUser user, @RequestParam("id") Long id) {
|
||||
return defAdminService.get(adminGuard.requireAdmin(user, null), id);
|
||||
@@ -302,6 +308,16 @@ public class FlowAdminController {
|
||||
return java.util.Collections.singletonMap("secret", secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调探测(2026-10-11 管理端增强):对该注册同步推送一条 flow.ping(未知事件码,接收方按
|
||||
* 规范 §6.4 记日志后 200),返回 {success, httpStatus, elapsedMs, respSnippet, error};
|
||||
* 结果同时落推送日志一行(event=flow.ping)。
|
||||
*/
|
||||
@PostMapping("/callback/probe/{id}")
|
||||
public Map<String, Object> probe(BoaUser user, @PathVariable("id") Long id) {
|
||||
return callbackAdminService.probe(adminGuard.requireAdmin(user, null), id);
|
||||
}
|
||||
|
||||
@GetMapping("/callback/logs")
|
||||
public BasePageResult<FlwCallbackLog> logs(BoaUser user,
|
||||
@RequestParam(value = "status", required = false) Integer status,
|
||||
|
||||
@@ -76,6 +76,10 @@ public class FlwCallbackLog extends BaseEntity {
|
||||
@Column(name = "last_error", length = 1000)
|
||||
private String lastError;
|
||||
|
||||
/** 响应体摘要(探测/probe 落,截 1000;常规推送不落——2xx 即成功不解析响应体) */
|
||||
@Column(name = "resp_body", columnDefinition = "TEXT")
|
||||
private String respBody;
|
||||
|
||||
/** 成功时间 */
|
||||
@Column(name = "push_time")
|
||||
private LocalDateTime pushTime;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.yunhai.flowcenter.service;
|
||||
|
||||
import com.yunhai.flowcenter.entity.FlwApp;
|
||||
import com.yunhai.flowcenter.repository.FlwAppRepository;
|
||||
import com.yunhai.flowcenter.repository.FlwCallbackRegRepository;
|
||||
import com.yunhai.platform.openapi.base.OpenApiAppClient;
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
@@ -30,6 +31,19 @@ public class FlowAppAdminService {
|
||||
|
||||
private final FlwAppRepository appRepository;
|
||||
private final OpenApiAppClient appClient;
|
||||
private final FlwCallbackRegRepository callbackRegRepository;
|
||||
|
||||
/**
|
||||
* 应用维度辅助统计(2026-10-11 应用管理增强):key=appKey,value=回调注册数(本校范围)。
|
||||
* 应用设置页展示"对接深度"用;不区分注册启停(停用注册同样计入,管理员感知总量)。
|
||||
*/
|
||||
public Map<String, Long> stats(Long schId) {
|
||||
Map<String, Long> out = new HashMap<>();
|
||||
for (com.yunhai.flowcenter.entity.FlwCallbackReg reg : callbackRegRepository.findBySchIdOrderByUpdateTimeDesc(schId)) {
|
||||
out.merge(reg.getAppKey(), 1L, Long::sum);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表(管理端应用设置页)。附带一次静默学校池同步:UserBase 不可达/学校未开通任何应用时
|
||||
|
||||
@@ -55,6 +55,15 @@ public class FlowCallbackAdminService {
|
||||
return secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调探测(2026-10-11 管理端增强):对指定注册签发一条 flow.ping 未知事件码走真实管道
|
||||
* 同步一发,验证接收方可达性与验签配置。停用注册同样允许探测(管理员排障需要)。
|
||||
*/
|
||||
public java.util.Map<String, Object> probe(Long schId, Long regId) {
|
||||
FlwCallbackReg reg = requireReg(schId, regId);
|
||||
return pushService.probe(schId, reg);
|
||||
}
|
||||
|
||||
/** 推送日志分页(状态/事件/办件筛选) */
|
||||
public BasePageResult<FlwCallbackLog> pageLogs(Long schId, Integer status, String event,
|
||||
Long instanceId, Integer page, Integer size) {
|
||||
|
||||
Reference in New Issue
Block a user