From cdf0976f2ec4dbb6704761ff004f8e2a4bb5f9d8 Mon Sep 17 00:00:00 2001 From: Tiga Date: Tue, 27 Oct 2026 15:51:05 +0800 Subject: [PATCH] =?UTF-8?q?sms=E6=A8=A1=E6=8B=9F=E7=9A=84=E8=B7=AF?= =?UTF-8?q?=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/ewl/msgw/contentSend.js | 46 +++++++---- src/routes/ewl/msgw/smsErrorState.js | 77 +++++++++++++++---- .../mockApi/smsContentSendErrorOverride.js | 38 +++++---- 3 files changed, 117 insertions(+), 44 deletions(-) diff --git a/src/routes/ewl/msgw/contentSend.js b/src/routes/ewl/msgw/contentSend.js index e162b38..79a7f1e 100644 --- a/src/routes/ewl/msgw/contentSend.js +++ b/src/routes/ewl/msgw/contentSend.js @@ -1,36 +1,52 @@ // Message Gateway 内容发送接口 Mock:POST /ewl/msgw/v1/messages/content/send // 后端短信已从旧 SMS Hub(/SMSToken + /SMSRequest/Post)切换到该接口,认证复用 EDIP Token。 // 响应严格按真实网关信封:HTTP 200 + status/errorCode,成功时 smsid 为全小写字段、errorCode 为空字符串。 -const { getForcedErrorCode, getForcedErrorMsg } = require('./smsErrorState') +// 支持三类故障注入(见 smsErrorState.js):HTTP 状态码、业务错误码、收件人级失败状态、缺失 smsid。 +const { getFaultInjection } = require('./smsErrorState') function registerMessageGatewayContentSendRoute({ app }) { app.post('/ewl/msgw/v1/messages/content/send', (req, res) => { const data = req.body?.data || {} const recipients = Array.isArray(data.recipients) ? data.recipients : [] + const fault = getFaultInjection() - // 错误注入:优先取运行时开关(/mock-api/sms/content-send-error),回落环境变量。 - // 8923 属供应商暂不可用(后端应重试),8907 属号码非法(后端不应重试) - const forcedErrorCode = getForcedErrorCode() - if (forcedErrorCode) { - return res.status(200).json({ + // ① HTTP 层故障:以指定状态码响应,用于验证 4xx 不重试、5xx/429 重试 + if (fault.httpStatus) { + return res.status(fault.httpStatus).json({ status: false, - errorCode: forcedErrorCode, - message: getForcedErrorMsg(), + errorCode: fault.errorCode || '', + message: fault.errorMsg, data: null, }) } + // ② 业务错误码:HTTP 200 + errorCode,用于验证 8901-8910 不重试、8911-8930 重试 + if (fault.errorCode) { + return res.status(200).json({ + status: false, + errorCode: fault.errorCode, + message: fault.errorMsg, + data: null, + }) + } + + // ③ 成功响应:可选注入收件人级失败状态(smsStatus=FAILED)与缺失 smsid + const recipientStatus = fault.recipientStatus || 'QUEUED' + const responseData = { + smsStatus: recipients.map((r) => ({ + recipient: r?.phone || '', + status: recipientStatus, + })), + } + if (!fault.suppressSmsId) { + responseData.smsid = `mock-sms-${Date.now()}` + } + return res.status(200).json({ status: true, errorCode: '', message: '', - data: { - smsid: `mock-sms-${Date.now()}`, - smsStatus: recipients.map((r) => ({ - recipient: r?.phone || '', - status: 'QUEUED', - })), - }, + data: responseData, }) }) } diff --git a/src/routes/ewl/msgw/smsErrorState.js b/src/routes/ewl/msgw/smsErrorState.js index 7c61583..800b069 100644 --- a/src/routes/ewl/msgw/smsErrorState.js +++ b/src/routes/ewl/msgw/smsErrorState.js @@ -1,25 +1,76 @@ -// Message Gateway 内容发送 Mock 的运行时错误码开关。 -// 目的:联调时可在进程内切换 errorCode(如 8923 可重试、8907 不可重试),不必反复重启 Mock 进程。 -// 初值取环境变量 MOCK_SMS_FORCE_ERROR_CODE,之后由 /mock-api/sms/content-send-error 覆盖。 +// Message Gateway 内容发送 Mock 的运行时故障注入开关。 +// 目的:联调时可在进程内切换故障形态(错误码 / HTTP 状态码 / 收件人级失败 / 缺失 smsid), +// 不必反复重启 Mock 进程。初值取环境变量,之后由 /mock-api/sms/content-send-error 覆盖。 const state = { forcedErrorCode: (process.env.MOCK_SMS_FORCE_ERROR_CODE || '').trim(), forcedErrorMsg: process.env.MOCK_SMS_FORCE_ERROR_MSG || 'Mock message gateway error', + // 非 0 时直接以该 HTTP 状态码响应(用于验证 4xx 不重试、5xx/429 重试) + forcedHttpStatus: Number(process.env.MOCK_SMS_FORCE_HTTP_STATUS || 0), + // 非空时把每个收件人的 status 替换成该值(用于验证 smsStatus=FAILED 的收件人级失败) + forcedRecipientStatus: (process.env.MOCK_SMS_FORCE_RECIPIENT_STATUS || '').trim(), + // 为真时成功响应里不返回 smsid(用于验证成功但无网关消息 ID 的审计标注) + suppressSmsId: String(process.env.MOCK_SMS_SUPPRESS_SMS_ID || '').toLowerCase() === 'true', } -function setForcedErrorCode(errorCode, errorMsg) { - state.forcedErrorCode = errorCode == null ? '' : String(errorCode).trim() - if (errorMsg != null && String(errorMsg).trim() !== '') { - state.forcedErrorMsg = String(errorMsg).trim() +function normalizeHttpStatus(value) { + const status = Number(value || 0) + return Number.isInteger(status) && status >= 100 && status <= 599 ? status : 0 +} + +/** 覆盖故障注入配置;未提供的字段保持不变,传 null/空串表示清除该项 */ +function setFaultInjection(overrides = {}) { + if (overrides.errorCode !== undefined) { + state.forcedErrorCode = overrides.errorCode == null ? '' : String(overrides.errorCode).trim() } - return { errorCode: state.forcedErrorCode, errorMsg: state.forcedErrorMsg } + if (overrides.errorMsg !== undefined && String(overrides.errorMsg || '').trim() !== '') { + state.forcedErrorMsg = String(overrides.errorMsg).trim() + } + if (overrides.httpStatus !== undefined) { + state.forcedHttpStatus = normalizeHttpStatus(overrides.httpStatus) + } + if (overrides.recipientStatus !== undefined) { + state.forcedRecipientStatus = + overrides.recipientStatus == null ? '' : String(overrides.recipientStatus).trim() + } + if (overrides.noSmsId !== undefined) { + state.suppressSmsId = Boolean(overrides.noSmsId) + } + return currentFaultInjection() } -function getForcedErrorCode() { - return state.forcedErrorCode +function currentFaultInjection() { + return { + errorCode: state.forcedErrorCode, + errorMsg: state.forcedErrorMsg, + httpStatus: state.forcedHttpStatus, + recipientStatus: state.forcedRecipientStatus, + noSmsId: state.suppressSmsId, + } } -function getForcedErrorMsg() { - return state.forcedErrorMsg +/** 清除全部故障注入,回到真实成功响应 */ +function clearFaultInjection() { + state.forcedErrorCode = '' + state.forcedErrorMsg = 'Mock message gateway error' + state.forcedHttpStatus = 0 + state.forcedRecipientStatus = '' + state.suppressSmsId = false + return currentFaultInjection() } -module.exports = { setForcedErrorCode, getForcedErrorCode, getForcedErrorMsg } +function getFaultInjection() { + return { + errorCode: state.forcedErrorCode, + errorMsg: state.forcedErrorMsg, + httpStatus: state.forcedHttpStatus, + recipientStatus: state.forcedRecipientStatus, + suppressSmsId: state.suppressSmsId, + } +} + +module.exports = { + setFaultInjection, + clearFaultInjection, + currentFaultInjection, + getFaultInjection, +} diff --git a/src/routes/mockApi/smsContentSendErrorOverride.js b/src/routes/mockApi/smsContentSendErrorOverride.js index 2d6e1d1..c676f92 100644 --- a/src/routes/mockApi/smsContentSendErrorOverride.js +++ b/src/routes/mockApi/smsContentSendErrorOverride.js @@ -1,6 +1,15 @@ -// Message Gateway 内容发送 Mock 的错误码开关节点(仅联调用)。 -// 用途:进程内切换 errorCode,验后端重试语义(8911-8930 应重试,8901-8910 不应重试),避免反复重启 Mock。 -const { setForcedErrorCode, getForcedErrorCode, getForcedErrorMsg } = require('../ewl/msgw/smsErrorState') +// Message Gateway 内容发送 Mock 的故障注入开关节点(仅联调用)。 +// 用途:进程内切换故障形态,验证后端的成功/错误处理与重试语义,避免反复重启 Mock。 +// POST {"errorCode":"8923"} 注入业务错误码(8911-8930 应重试,8901-8910 不应重试) +// POST {"httpStatus":401} 注入 HTTP 状态码(4xx 不应重试,5xx/429 应重试) +// POST {"recipientStatus":"FAILED"} 注入收件人级失败(整体 200 但 smsStatus=FAILED) +// POST {"noSmsId":true} 成功响应中不返回 smsid +// DELETE 清除全部注入 +const { + setFaultInjection, + clearFaultInjection, + currentFaultInjection, +} = require('../ewl/msgw/smsErrorState') function registerSmsContentSendErrorOverrideRoute({ app }) { const route = '/mock-api/sms/content-send-error' @@ -8,31 +17,28 @@ function registerSmsContentSendErrorOverrideRoute({ app }) { app.get(route, (req, res) => res.status(200).json({ success: true, - message: 'SMS content send error override retrieved successfully.', - data: { errorCode: getForcedErrorCode(), errorMsg: getForcedErrorMsg() }, + message: 'SMS content send fault injection retrieved successfully.', + data: currentFaultInjection(), }), ) app.post(route, (req, res) => { const body = req.body && typeof req.body === 'object' ? req.body : {} - const override = setForcedErrorCode(body.errorCode ?? '', body.errorMsg) return res.status(200).json({ success: true, - message: 'SMS content send error override saved successfully.', - data: override, + message: 'SMS content send fault injection saved successfully.', + data: setFaultInjection(body), }) }) - app.delete(route, (req, res) => { - const cleared = setForcedErrorCode('', null) - - return res.status(200).json({ + app.delete(route, (req, res) => + res.status(200).json({ success: true, - message: 'SMS content send error override cleared successfully.', - data: cleared, - }) - }) + message: 'SMS content send fault injection cleared successfully.', + data: clearFaultInjection(), + }), + ) } module.exports = registerSmsContentSendErrorOverrideRoute