This commit is contained in:
2026-08-13 20:00:30 +08:00
parent 50251240f4
commit 5bc62a109e
28 changed files with 602 additions and 176 deletions
@@ -0,0 +1,160 @@
const MODULE = 'notifications'
const OPTIONS = { scenario: 'platform-notification-administration', deploymentBlocker: true }
const TEMPLATE_BASE = '/api/platform/v1/notification-templates'
const TASK_BASE = '/api/platform/v1/notification-tasks'
const CREATE_PATH = '/api/platform/v1/notifications'
function id(value) { return value == null ? '' : String(value) }
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
function records(page) { return Array.isArray(page?.records) ? page.records : [] }
function hasTrace(response) { return Boolean(response?.requestId) && Boolean(response?.traceId) }
function isConflict(response) { return response?.http === 200 && ['A0409', '409'].includes(codeOf(response)) }
function query(base, values) {
const params = new URLSearchParams()
for (const [key, value] of Object.entries(values)) if (value !== '' && value != null) params.set(key, String(value))
return `${base}?${params}`
}
export async function run(context) {
const suffix = context.marker.replace(/[^0-9A-Za-z]/g, '').slice(-20).toUpperCase()
const templateCode = `ACPT_NOTIFICATION_${suffix}`.slice(0, 64)
const category = 'ACCEPTANCE'
const templateCreate = {
templateCode,
templateName: `全量验收消息模板-${suffix}`,
category,
titleTemplate: '全量验收通知 {{marker}}',
bodyTemplate: '这是 {{marker}} 的消息中心全量验收定时任务。',
variableWhitelist: ['marker'],
}
const baselineResponse = await context.request('GET', query(TEMPLATE_BASE, { page: 1, size: 20, templateCode }))
const baseline = context.apiSuccess(MODULE, '按 CODE 查询消息模板基线', baselineResponse, `templateCode=${templateCode}`, OPTIONS)
context.assert(MODULE, '消息模板分页结构完整', baselineResponse,
Array.isArray(baseline?.records) && Number(baseline?.page) === 1 && Number(baseline?.size) === 20,
`records=${records(baseline).length}, total=${baseline?.total ?? 'missing'}`, OPTIONS)
const invalidResponse = await context.request('POST', TEMPLATE_BASE, {
...templateCreate,
templateCode: `ACPT_INVALID_${suffix}`.slice(0, 64),
variableWhitelist: [],
})
context.assert(MODULE, '模板占位变量未列入白名单时被拒绝且返回追踪信息', invalidResponse,
invalidResponse.http === 200 && ['A0422', '422'].includes(codeOf(invalidResponse)) && hasTrace(invalidResponse),
`HTTP=${invalidResponse.http}, code=${codeOf(invalidResponse)}, requestId=${invalidResponse.requestId || 'missing'}, traceId=${invalidResponse.traceId || 'missing'}`, OPTIONS)
const createResponse = await context.request('POST', TEMPLATE_BASE, templateCreate)
let template = context.apiSuccess(MODULE, '创建消息模板草稿', createResponse, `templateCode=${templateCode}`, OPTIONS)
const templateId = id(template?.id)
context.assert(MODULE, '新建模板为 DRAFT 且仅支持站内信', createResponse,
Boolean(templateId) && template?.status === 'DRAFT' && template?.channel === 'IN_APP' && Number(template?.version) === 1,
`templateId=${templateId || 'missing'}, status=${template?.status || 'missing'}, channel=${template?.channel || 'missing'}`, OPTIONS)
if (!templateId) return
let taskId = ''
let taskStatus = ''
let finalTemplateStatus = template?.status || 'UNKNOWN'
try {
const duplicateResponse = await context.request('POST', TEMPLATE_BASE, templateCreate)
context.assert(MODULE, '重复模板 CODE 返回冲突并保留追踪信息', duplicateResponse,
isConflict(duplicateResponse) && hasTrace(duplicateResponse),
`code=${codeOf(duplicateResponse)}, requestId=${duplicateResponse.requestId || 'missing'}, traceId=${duplicateResponse.traceId || 'missing'}`, OPTIONS)
const updateResponse = await context.request('PUT', `${TEMPLATE_BASE}/${encodeURIComponent(templateId)}`, {
templateName: `全量验收消息模板已编辑-${suffix}`,
category,
titleTemplate: '全量验收通知已编辑 {{marker}}',
bodyTemplate: '这是 {{marker}} 的消息中心全量验收定时任务,模板已经编辑。',
variableWhitelist: ['marker'],
version: template.version,
})
template = context.apiSuccess(MODULE, '编辑 DRAFT 消息模板', updateResponse, '', OPTIONS)
context.assert(MODULE, '模板编辑结果及版本已持久化', updateResponse,
template?.templateName === `全量验收消息模板已编辑-${suffix}` && Number(template?.version) === 2,
`name=${template?.templateName || 'missing'}, version=${template?.version ?? 'missing'}`, OPTIONS)
const staleUpdate = await context.request('PUT', `${TEMPLATE_BASE}/${encodeURIComponent(templateId)}`, {
templateName: templateCreate.templateName,
category,
titleTemplate: templateCreate.titleTemplate,
bodyTemplate: templateCreate.bodyTemplate,
variableWhitelist: ['marker'],
version: 1,
})
context.assert(MODULE, '旧模板版本编辑被拒绝且返回追踪信息', staleUpdate,
isConflict(staleUpdate) && hasTrace(staleUpdate),
`code=${codeOf(staleUpdate)}, requestId=${staleUpdate.requestId || 'missing'}, traceId=${staleUpdate.traceId || 'missing'}`, OPTIONS)
const activateResponse = await context.request('PUT', `${TEMPLATE_BASE}/${encodeURIComponent(templateId)}/status`, {
targetStatus: 'ACTIVE', version: template?.version,
})
template = context.apiSuccess(MODULE, '激活消息模板', activateResponse, '', OPTIONS)
finalTemplateStatus = template?.status || finalTemplateStatus
context.assert(MODULE, '消息模板激活且版本递增', activateResponse,
template?.status === 'ACTIVE' && Number(template?.version) === 3,
`status=${template?.status || 'missing'}, version=${template?.version ?? 'missing'}`, OPTIONS)
const scheduledAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().slice(0, 19)
const idempotencyKey = `${context.marker}-notification-scheduled`.slice(0, 128)
const taskCreate = {
tenantId: 0,
templateCode,
recipientType: 'PLATFORM_ROLE',
roleCode: 'PLATFORM_ADMIN',
variables: { marker: context.marker },
scheduledAt,
idempotencyKey,
sourceEventId: `${context.marker}-notification-acceptance`.slice(0, 128),
}
const taskResponse = await context.request('POST', CREATE_PATH, taskCreate)
let task = context.apiSuccess(MODULE, '创建未来定时消息任务', taskResponse, `scheduledAt=${scheduledAt}`, OPTIONS)
taskId = id(task?.id); taskStatus = task?.status || ''
context.assert(MODULE, '定时任务创建为 SCHEDULED 且解析平台角色接收人', taskResponse,
Boolean(taskId) && task?.status === 'SCHEDULED' && Number(task?.recipientCount) > 0 && task?.duplicated === false,
`taskId=${taskId || 'missing'}, status=${task?.status || 'missing'}, recipientCount=${task?.recipientCount ?? 'missing'}`, OPTIONS)
const replayResponse = await context.request('POST', CREATE_PATH, taskCreate)
const replay = context.apiSuccess(MODULE, '同幂等键重放定时消息任务', replayResponse, '', OPTIONS)
context.assert(MODULE, '幂等重放返回同一任务且标记 duplicated', replayResponse,
id(replay?.id) === taskId && replay?.duplicated === true,
`original=${taskId || 'missing'}, replay=${id(replay?.id) || 'missing'}, duplicated=${replay?.duplicated ?? 'missing'}`, OPTIONS)
const taskListResponse = await context.request('GET', query(TASK_BASE, { page: 1, size: 20, taskNo: task?.taskNo, category, status: 'SCHEDULED' }))
const taskPage = context.apiSuccess(MODULE, '按任务号和状态查询消息任务', taskListResponse, `taskNo=${task?.taskNo || 'missing'}`, OPTIONS)
context.assert(MODULE, '任务列表命中本轮定时任务', taskListResponse,
records(taskPage).some((row) => id(row?.id) === taskId && row?.status === 'SCHEDULED'),
`taskId=${taskId || 'missing'}, records=${records(taskPage).length}`, OPTIONS)
if (taskId) {
const cancelResponse = await context.request('PUT', `${TASK_BASE}/${encodeURIComponent(taskId)}/cancel`)
task = context.apiSuccess(MODULE, '取消未来定时消息任务', cancelResponse, '', OPTIONS)
taskStatus = task?.status || taskStatus
context.assert(MODULE, '定时消息任务最终为 CANCELLED', cancelResponse,
task?.status === 'CANCELLED', `status=${task?.status || 'missing'}`, OPTIONS)
const retryResponse = await context.request('PUT', `${TASK_BASE}/${encodeURIComponent(taskId)}/retry`)
context.assert(MODULE, '已取消任务禁止重试且返回追踪信息', retryResponse,
isConflict(retryResponse) && hasTrace(retryResponse),
`code=${codeOf(retryResponse)}, requestId=${retryResponse.requestId || 'missing'}, traceId=${retryResponse.traceId || 'missing'}`, OPTIONS)
}
} finally {
if (taskId && taskStatus === 'SCHEDULED') {
const cleanupTask = await context.request('PUT', `${TASK_BASE}/${encodeURIComponent(taskId)}/cancel`)
const cleanedTask = context.apiSuccess(MODULE, '异常路径取消未来定时消息任务', cleanupTask, `taskId=${taskId}`, OPTIONS)
taskStatus = cleanedTask?.status || taskStatus
}
if (template?.status === 'ACTIVE') {
const disableResponse = await context.request('PUT', `${TEMPLATE_BASE}/${encodeURIComponent(templateId)}/status`, {
targetStatus: 'DISABLED', version: template.version,
})
template = context.apiSuccess(MODULE, '停用本轮消息模板', disableResponse, `templateId=${templateId}`, OPTIONS)
finalTemplateStatus = template?.status || finalTemplateStatus
context.assert(MODULE, '消息模板最终为 DISABLED', disableResponse,
template?.status === 'DISABLED', `status=${template?.status || 'missing'}`, OPTIONS)
}
context.createdResources.notifications = {
marker: context.marker, templateId, templateCode, finalTemplateStatus,
taskId: taskId || null, taskStatus: taskStatus || null, retained: true,
}
}
}