diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/callback/CallbackInboxController.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/callback/CallbackInboxController.java index d7cec99..42de0ba 100644 --- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/callback/CallbackInboxController.java +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/callback/CallbackInboxController.java @@ -1,6 +1,9 @@ package cn.craftlabs.platform.api.callback; +import cn.craftlabs.platform.api.service.CallbackEventIngestService; import cn.craftlabs.platform.api.service.CallbackInboxService; +import cn.craftlabs.platform.api.web.dto.CallbackEventIngestRequest; +import cn.craftlabs.platform.api.web.dto.CallbackEventIngestResponse; import cn.craftlabs.platform.api.web.dto.CallbackInboxLinkPatchRequest; import cn.craftlabs.platform.api.web.dto.CallbackInboxResponse; import cn.craftlabs.platform.api.web.dto.CallbackInboxStatusPatchRequest; @@ -10,15 +13,17 @@ import cn.craftlabs.platform.api.web.dto.PageResponse; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RestController; import java.time.OffsetDateTime; @@ -30,9 +35,11 @@ import java.time.OffsetDateTime; public class CallbackInboxController { private final CallbackInboxService callbackInboxService; + private final CallbackEventIngestService callbackEventIngestService; - public CallbackInboxController(CallbackInboxService callbackInboxService) { + public CallbackInboxController(CallbackInboxService callbackInboxService, CallbackEventIngestService callbackEventIngestService) { this.callbackInboxService = callbackInboxService; + this.callbackEventIngestService = callbackEventIngestService; } @GetMapping @@ -77,6 +84,14 @@ public class CallbackInboxController { return callbackInboxService.patchLink(id, request); } + /** M5-F10:模拟投递(仅测试环境),手动 POST 模拟 Callback 事件。 */ + @PostMapping("/simulate") + public CallbackEventIngestResponse simulate( + @Valid @RequestBody CallbackEventIngestRequest request, + @RequestHeader(value = "Idempotency-Key", required = false) String idempotencyKey) { + return callbackEventIngestService.ingest(request, idempotencyKey); + } + /** I8:代理 OPS 调用 Webhook,将关联收据的 {@code DEAD} 出库重新入队。 */ @PostMapping("/{id}/replay-webhook-delivery") public CallbackWebhookReplayResponse replayWebhookDelivery(@PathVariable("id") long id) { diff --git a/web/delivery-platform-ui/src/api/platform.js b/web/delivery-platform-ui/src/api/platform.js index 4908446..0c9d811 100644 --- a/web/delivery-platform-ui/src/api/platform.js +++ b/web/delivery-platform-ui/src/api/platform.js @@ -280,6 +280,14 @@ export function patchCallbackInboxLink(id, body) { return axios.patch(`/api/v1/callback-inbox/${id}/link`, body); } +/** + * M5-F10:模拟投递(仅测试环境)。 + * @param {Record} body + */ +export function simulateCallback(body) { + return axios.post('/api/v1/callback-inbox/simulate', body); +} + /** * I8:将 Webhook 侧 DEAD 出库按收据 ID 重新入队(需平台配置 LICENSE_WEBHOOK_*)。 * @param {string | number} id — callback inbox id diff --git a/web/delivery-platform-ui/src/views/CallbackInboxView.vue b/web/delivery-platform-ui/src/views/CallbackInboxView.vue index 11bb81f..f247501 100644 --- a/web/delivery-platform-ui/src/views/CallbackInboxView.vue +++ b/web/delivery-platform-ui/src/views/CallbackInboxView.vue @@ -15,6 +15,7 @@ 查询 批量重试 + 模拟投递 @@ -52,6 +53,29 @@ @size-change="onSizeChange" /> + + + + + + + + + + + + + + + + + + + + @@ -60,7 +84,7 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { ElMessage } from "element-plus"; import { useAuthStore } from "../stores/auth"; -import { listCallbackInbox, replayCallbackWebhookDelivery } from "../api/platform"; +import { listCallbackInbox, replayCallbackWebhookDelivery, simulateCallback } from "../api/platform"; import { apiErrorMessage } from "../utils/apiErrorMessage"; const auth = useAuthStore(); @@ -76,6 +100,9 @@ const filterEventType = ref(""); const filterSnCode = ref(""); const filterProjectId = ref(""); const selectedCallbacks = ref([]); +const simDialogVisible = ref(false); +const simulating = ref(false); +const simForm = ref({ eventType: 'sn:post_activate', snCode: '', rawPayload: '{}' }); function handleSelectionChange(val) { selectedCallbacks.value = val; @@ -150,6 +177,27 @@ async function load() { } } +async function handleSimulate() { + simulating.value = true; + try { + await simulateCallback({ + sourceSystem: 'SIMULATOR', + externalMessageId: `sim-${Date.now()}`, + schemaVersion: '1.0', + eventType: simForm.value.eventType, + rawPayload: JSON.parse(simForm.value.rawPayload), + receivedAt: new Date().toISOString(), + }); + ElMessage.success('模拟事件已投递'); + simDialogVisible.value = false; + load(); + } catch (e) { + ElMessage.error(apiErrorMessage(e, '模拟投递失败')); + } finally { + simulating.value = false; + } +} + function goDetail(id) { router.push({ name: "callback-inbox-detail", params: { id: String(id) } }); }