{ "info": { "name": "黄金矿工 API 场景测试", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "description": "按当前后端代码更新:共 48 个请求(47 POST、1 GET),包含黄金矿工玩家接口、通关、原生/客服支付、奖励授权与保存确认、到期补发、普通/临时期管理及 activityConfig/list。08 公共日程无需账号;01 可单独 Runner;其他组按场景逐条执行,不要运行整个 Collection。详见 gold-miner.README.md。" }, "auth": { "type": "noauth" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need(['baseUrl']);", "if (!/^https?:\\/\\//.test(pm.environment.get('baseUrl')) || /[<>]/.test(pm.environment.get('baseUrl'))) stop('请填写实际部署的 baseUrl');", "const context=JSON.stringify([pm.environment.get('baseUrl'),pm.environment.get('uid')]);", "const previous=pm.environment.get('gmContext');", "if (previous && previous !== context) {", " pm.environment.unset('gmInfo');", " pm.environment.unset('gmActivitySchedule');", " pm.environment.unset('expectedListedPeriodId');", " pm.environment.unset('periodId');", " pm.environment.unset('productId');", " pm.environment.unset('priceFen');", " pm.environment.unset('firstTaskId');", " pm.environment.unset('secondTaskId');", " pm.environment.unset('currentMainLevel');", " pm.environment.unset('gmWinBefore');", " pm.environment.unset('gmWinBody');", " pm.environment.unset('gmWinAfter');", " pm.environment.unset('legacySessionFrom');", " pm.environment.unset('allowCustomerMessage');", " pm.environment.unset('outTradeNo');", " pm.environment.unset('orderPeriodId');", " pm.environment.unset('gmGrant');", " pm.environment.unset('grantId');", " pm.environment.unset('coinBeforeDelivery');", " pm.environment.unset('gmSavedGrantId');", " pm.environment.unset('settlementCursor');", " pm.environment.unset('expectedStatus');", " pm.environment.unset('expectedProgressWins');", " pm.environment.unset('claimPeriodId');", " pm.environment.unset('claimTaskId');", " pm.environment.unset('coinAfterDelivery');", " pm.environment.unset('settlementId');", " pm.environment.unset('expectedTargetPeriodId');", " pm.environment.unset('mainLevelAfterWin');", " pm.environment.unset('endlessSequence');", " pm.environment.unset('expiredPeriodId');", " pm.environment.unset('openid');", " pm.environment.set('gmContext',context);", " stop('已切换应用/账号,旧测试上下文已清理。请从第 01 组重新查询');", "}", "pm.environment.set('gmContext',context);" ] } } ], "item": [ { "name": "01 基础查询与鉴权(可单独 Runner)", "description": "本组不提交通关或发货。info 可能初始化玩家期记录并激活已有权益;补发查询本身不执行结算。支持普通周四周期和 goldMiner:test: 临时期,expectedStatus 留空时只校验结构。", "item": [ { "name": "01 活动信息并保存当期变量", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"info\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:action=info、uid、token。返回 serverTime/status/periodId/tasks;有效且已达资格时另含 startsAt/endsAt/configVersion/priceFen/productId/progressWins/maxTarget。status 为 unavailable/locked/purchasable/purchase_disabled/unlocked。任务字段:taskId/targetWins/itemsSnapshot/claimStatus/completed/progress;claimStatus 为 locked/claimable/issuing/claimed。测试期时间可为任意窗口,不能按固定四天推算。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "pm.test('活动状态结构', () => {", " const d = r.data;", " pm.expect(['unavailable','locked','purchasable','purchase_disabled','unlocked'].includes(d.status)).to.eql(true);", " pm.expect(Number.isSafeInteger(d.serverTime)).to.eql(true);", " for (const key of ['availability','qualified','entitlement','purchasable','currency','nextStartsAt','nextPeriodId','claimedThrough']) pm.expect(Object.prototype.hasOwnProperty.call(d,key)).to.eql(false);", " if (pm.environment.get('expectedStatus')) pm.expect(d.status).to.eql(pm.environment.get('expectedStatus'));", " if (['unavailable','locked'].includes(d.status)) { pm.expect(d.tasks).to.eql([]); return; }", " pm.expect(Number.isSafeInteger(d.startsAt) && Number.isSafeInteger(d.endsAt) && d.startsAt < d.endsAt).to.eql(true);", " pm.expect(d.serverTime >= d.startsAt && d.serverTime < d.endsAt).to.eql(true);", " if (d.periodId.startsWith('goldMiner:test:')) {", " pm.expect(/^goldMiner:test:[A-Za-z0-9_-]{1,60}$/.test(d.periodId)).to.eql(true);", " } else {", " pm.expect(/^goldMiner:\\d{4}-\\d{2}-\\d{2}$/.test(d.periodId)).to.eql(true);", " pm.expect(d.endsAt - d.startsAt).to.eql(4 * 86400000);", " pm.expect(new Date(d.startsAt + 8 * 3600000).getUTCDay()).to.eql(4);", " pm.expect(new Date(d.startsAt + 8 * 3600000).getUTCHours()).to.eql(0);", " }", " pm.expect(Number.isSafeInteger(d.progressWins) && d.progressWins >= 0).to.eql(true);", " pm.expect(d.tasks.length > 0).to.eql(true);", " pm.expect(d.maxTarget).to.eql(d.tasks[d.tasks.length - 1].targetWins);", " let lastTarget = 0;", " d.tasks.forEach((t, i) => {", " pm.expect(Object.keys(t).sort()).to.eql(['taskId','targetWins','itemsSnapshot','claimStatus','completed','progress'].sort());", " pm.expect(t.targetWins > lastTarget).to.eql(true); lastTarget = t.targetWins;", " pm.expect(t.progress).to.eql(Math.min(d.progressWins, t.targetWins));", " pm.expect(t.completed).to.eql(d.progressWins >= t.targetWins);", " if (d.status !== 'unlocked') pm.expect(t.claimStatus).to.eql(t.completed ? 'pending_unlock' : 'locked');", " else if (!t.completed) pm.expect(t.claimStatus).to.eql('locked');", " else pm.expect(['claimable','issuing','claimed'].includes(t.claimStatus)).to.eql(true);", " });", " if (String(pm.environment.get('expectedProgressWins') ?? '') !== '') pm.expect(d.progressWins).to.eql(Number(pm.environment.get('expectedProgressWins')));", "});", "if (r.code === 1) {", " pm.environment.set('gmInfo', JSON.stringify(r.data));", " if (['purchasable','purchase_disabled','unlocked'].includes(r.data.status)) {", " for (const key of ['periodId','productId','priceFen']) pm.environment.set(key, r.data[key]);", " pm.environment.set('firstTaskId', r.data.tasks[0].taskId);", " pm.environment.set('secondTaskId', r.data.tasks[1]?.taskId || '');", " } else {", " for (const key of ['periodId','productId','priceFen','firstTaskId','secondTaskId']) pm.environment.unset(key);", " if (r.data.status === 'locked') pm.environment.set('periodId',r.data.periodId);", " }", "}" ] } } ] }, { "name": "02 读取主线存档", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"read\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userLevel", "host": [ "{{baseUrl}}" ], "path": [ "userLevel" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "if (r.code === 1) pm.environment.set('currentMainLevel', r.data.levelAmount);" ] } } ] }, { "name": "03 缺少 token", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"info\",\n \"uid\": \"{{uid}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:UNAUTHORIZED', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('UNAUTHORIZED'); });" ] } } ] }, { "name": "04 不支持的 IAA 账号域", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"info\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"gameName\": \"iaa\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:UNAUTHORIZED', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('UNAUTHORIZED'); });" ] } } ] }, { "name": "05 非法 action", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"invalid_action\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:INVALID_ACTION', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('INVALID_ACTION'); });" ] } } ] }, { "name": "06 非法分页游标", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"settlements\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"afterId\": 42\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token,afterId 可选分页游标;前端不传 limit,后端固定每页扫描 20 期。返回 status=pending_delivery/settling/no_pending_rewards、items、nextCursor;每项含 settlementId/periodId/rewards,奖励含 taskId/grantId/items。可发奖励优先返回,不受其他期结算阻塞;no_pending_rewards 表示该玩家当前环境下所有已结束期均无待发或待结算记录。\n\n" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:INVALID_INPUT', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('INVALID_INPUT'); });" ] } } ] }, { "name": "07 非法期 ID", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"claim\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"invalid-period\",\n \"taskId\": \"task_1\",\n \"requestId\": \"invalid-period-test\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/requestId。返回 grantId/taskId/items/claimStatus/claimedThrough/requiresClientDelivery。仅有效期内可申请;授权不加币。到期返回 PERIOD_SETTLEMENT_REQUIRED,转补发查询。\n\n" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:INVALID_PERIOD', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('INVALID_PERIOD'); });" ] } } ] } ] }, { "name": "02 通关进度(按场景手动)", "description": "先读活动和主线存档。每次新胜利前重新读取活动信息。新胜利请求生成 eventId,重试请求复用完整原始载荷;不要以新胜利请求代替重试。", "item": [ { "name": "01 主线新胜利", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmWinBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userLevel", "host": [ "{{baseUrl}}" ], "path": [ "userLevel" ] }, "description": "mainLevelAfterWin=currentMainLevel+1;默认预期 counted。资格关需设 qualification、刚读取存档为 40,且活动已配置开放。封顶场景设 capped。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([]);", "", "need(['uid','token','periodId','currentMainLevel','gmInfo']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.periodId !== pm.environment.get('periodId')) stop('请重新查询本期活动信息');", "const expected = pm.environment.get('expectedProgressResult');", "if (!['counted','qualification','capped'].includes(expected)) stop('expectedProgressResult 仅支持 counted/qualification/capped');", "const level = integer('mainLevelAfterWin', 1);", "const oldLevel = integer('currentMainLevel', 0);", "const mode = 'main';", "if (mode === 'main' && level !== oldLevel + 1) stop('主线只测试顺序过关:mainLevelAfterWin 必须为刚读取的 currentMainLevel + 1');", "if (mode === 'endless' && level !== oldLevel) stop('无尽测试须保留当前主线存档');", "if (expected === 'qualification' && (mode !== 'main' || level !== 41)) stop('资格关用例要求主线第 41 关');", "if (expected !== 'qualification' && !['purchasable','purchase_disabled','unlocked'].includes(info.status)) stop('请先准备有资格且处于活动期的账号');", "const input = {periodId: pm.environment.get('periodId'), eventId: 'win-' + pm.variables.replaceIn('{{$guid}}'), outcome:'win', mode};", "if (mode === 'main') input.clearedMainLevel = level;", "else input.endlessSequence = integer('endlessSequence', 1);", "const data = {action:'save', uid:pm.environment.get('uid'), token:pm.environment.get('token'), levelAmount:level, goldMiner:input};", "if (mode === 'endless') data.isWuXian = 'true';", "pm.environment.set('gmWinBefore', info.progressWins || 0);", "pm.environment.set('gmWinBody', JSON.stringify(data));", "pm.environment.unset('gmWinAfter');" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "const d = r.data?.goldMiner;", "pm.test('活动子结果与计数', () => {", " pm.expect(d.code).to.eql(1);", " const before = Number(pm.environment.get('gmWinBefore'));", " const expected = pm.environment.get('expectedProgressResult');", " pm.expect(d.activityCounted).to.eql(expected === 'counted');", " pm.expect(d.progressWins).to.eql(before + (expected === 'counted' ? 1 : 0));", " if (expected === 'qualification') pm.expect(d.reason).to.eql('qualification_level');", " else pm.expect(d.duplicate).to.eql(false);", " if (expected === 'capped') pm.expect(d.progressWins).to.eql(JSON.parse(pm.environment.get('gmInfo')).maxTarget);", "});", "if (r.code === 1 && d?.code === 1) {", " pm.environment.set('gmWinAfter', d.progressWins);", " pm.environment.set('currentMainLevel', r.data.levelAmount);", "}" ] } } ] }, { "name": "02 无尽新胜利", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmWinBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userLevel", "host": [ "{{baseUrl}}" ], "path": [ "userLevel" ] }, "description": "mainLevelAfterWin=currentMainLevel;endlessSequence 使用此账号本期从未上报的唯一序号。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([]);", "", "need(['uid','token','periodId','currentMainLevel','gmInfo']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.periodId !== pm.environment.get('periodId')) stop('请重新查询本期活动信息');", "const expected = pm.environment.get('expectedProgressResult');", "if (!['counted','qualification','capped'].includes(expected)) stop('expectedProgressResult 仅支持 counted/qualification/capped');", "const level = integer('mainLevelAfterWin', 1);", "const oldLevel = integer('currentMainLevel', 0);", "const mode = 'endless';", "if (mode === 'main' && level !== oldLevel + 1) stop('主线只测试顺序过关:mainLevelAfterWin 必须为刚读取的 currentMainLevel + 1');", "if (mode === 'endless' && level !== oldLevel) stop('无尽测试须保留当前主线存档');", "if (expected === 'qualification' && (mode !== 'main' || level !== 41)) stop('资格关用例要求主线第 41 关');", "if (expected !== 'qualification' && !['purchasable','purchase_disabled','unlocked'].includes(info.status)) stop('请先准备有资格且处于活动期的账号');", "const input = {periodId: pm.environment.get('periodId'), eventId: 'win-' + pm.variables.replaceIn('{{$guid}}'), outcome:'win', mode};", "if (mode === 'main') input.clearedMainLevel = level;", "else input.endlessSequence = integer('endlessSequence', 1);", "const data = {action:'save', uid:pm.environment.get('uid'), token:pm.environment.get('token'), levelAmount:level, goldMiner:input};", "if (mode === 'endless') data.isWuXian = 'true';", "pm.environment.set('gmWinBefore', info.progressWins || 0);", "pm.environment.set('gmWinBody', JSON.stringify(data));", "pm.environment.unset('gmWinAfter');" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "const d = r.data?.goldMiner;", "pm.test('活动子结果与计数', () => {", " pm.expect(d.code).to.eql(1);", " const before = Number(pm.environment.get('gmWinBefore'));", " const expected = pm.environment.get('expectedProgressResult');", " pm.expect(d.activityCounted).to.eql(expected === 'counted');", " pm.expect(d.progressWins).to.eql(before + (expected === 'counted' ? 1 : 0));", " if (expected === 'qualification') pm.expect(d.reason).to.eql('qualification_level');", " else pm.expect(d.duplicate).to.eql(false);", " if (expected === 'capped') pm.expect(d.progressWins).to.eql(JSON.parse(pm.environment.get('gmInfo')).maxTarget);", "});", "if (r.code === 1 && d?.code === 1) {", " pm.environment.set('gmWinAfter', d.progressWins);", " pm.environment.set('currentMainLevel', r.data.levelAmount);", "}" ] } } ] }, { "name": "03 原事件重试不重复累计", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmRetryBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userLevel", "host": [ "{{baseUrl}}" ], "path": [ "userLevel" ] }, "description": "仅紧接已计数的胜利;资格关和封顶未记录去重条目,不适用此用例。无尽重试会再次影响旧 addLevel 统计,只用独立测试账号。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "need(['gmWinBody','gmWinAfter']);", "const saved = JSON.parse(pm.environment.get('gmWinBody'));", "saved.token = pm.environment.get('token');", "pm.variables.set('gmRetryBody', JSON.stringify(saved));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('同一次胜利只计一次', () => { const d = r.data.goldMiner; pm.expect(d.code).to.eql(1); pm.expect(d.duplicate).to.eql(true); pm.expect(d.activityCounted).to.eql(false); pm.expect(d.progressWins).to.eql(Number(pm.environment.get('gmWinAfter'))); });" ] } } ] }, { "name": "04 同一事件 ID 改变通关载荷", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmConflictBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userLevel", "host": [ "{{baseUrl}}" ], "path": [ "userLevel" ] }, "description": "仅对已计数原事件。活动拒绝发生在普通存档之后;本请求保留原 levelAmount,避免推进主线存档。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "need(['gmWinBody','gmWinAfter']);", "const saved = JSON.parse(pm.environment.get('gmWinBody'));", "saved.token = pm.environment.get('token');", "pm.variables.set('gmRetryBody', JSON.stringify(saved));", "if (saved.goldMiner.mode === 'main') saved.goldMiner.clearedMainLevel += 1; else saved.goldMiner.endlessSequence += 1;", "pm.variables.set('gmConflictBody', JSON.stringify(saved));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('活动子结果拒绝冲突', () => { pm.expect(r.data.goldMiner.code).to.eql(0); pm.expect(r.data.goldMiner.errorCode).to.eql('EVENT_PAYLOAD_CONFLICT'); });" ] } } ] }, { "name": "05 结束后提交新的胜利", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmExpiredBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userLevel", "host": [ "{{baseUrl}}" ], "path": [ "userLevel" ] }, "description": "expiredPeriodId 为已结束的普通期或 goldMiner:test: 临时期;endlessSequence 为该旧期未记录序号。普通存档仍会执行,单独检查 data.goldMiner.errorCode=PERIOD_ENDED。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([]);", "need(['uid','token','expiredPeriodId','currentMainLevel']);", "const data = {action:'save',uid:pm.environment.get('uid'),token:pm.environment.get('token'),levelAmount:integer('currentMainLevel',1),isWuXian:'true',goldMiner:{periodId:pm.environment.get('expiredPeriodId'),eventId:'late-'+pm.variables.replaceIn('{{$guid}}'),outcome:'win',mode:'endless',endlessSequence:integer('endlessSequence',1)}};", "pm.variables.set('gmExpiredBody',JSON.stringify(data));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('不补记过期进度', () => { pm.expect(r.data.goldMiner.code).to.eql(0); pm.expect(r.data.goldMiner.errorCode).to.eql('PERIOD_ENDED'); });" ] } } ] } ] }, { "name": "03 原生支付与查单(手动)", "description": "Android/iOS 创建入口按平台二选一;同一期另一平台也应复用原订单。签名请求本身不扣款;真实支付在客户端进行。", "item": [ { "name": "01 Android 创建订单(含篡改价格参数)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"itemid\": \"{{productId}}\",\n \"createRequestId\": \"{{createRequestId}}\",\n \"itemPrice\": 1,\n \"itemCount\": 999\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/orderPaySig", "host": [ "{{baseUrl}}" ], "path": [ "wx", "orderPaySig" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "need(['gmInfo','productId','priceFen']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.status !== 'purchasable' || info.productId !== pm.environment.get('productId')) stop('请先查询可购买的本期活动');", "pm.variables.set('createRequestId','buy-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "pm.test('商品及金额取服务端快照', () => {", " const sign = JSON.parse(r.data.signData);", " pm.expect(sign.productId).to.eql(pm.environment.get('productId'));", " pm.expect(sign.buyQuantity).to.eql(1);", " pm.expect(sign.goodsPrice).to.eql(Number(pm.environment.get('priceFen')));", " pm.expect(sign.outTradeNo).to.eql(r.data.outTradeNo);", " pm.expect(typeof r.data.paySig).to.eql('string');", " pm.expect(typeof r.data.signature).to.eql('string');", "});", "if (r.code === 1) { pm.environment.set('outTradeNo',r.data.outTradeNo); pm.environment.set('orderPeriodId',JSON.parse(pm.environment.get('gmInfo')).periodId); }" ] } } ] }, { "name": "02 原生 iOS 创建订单(含篡改价格参数)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"itemid\": \"{{productId}}\",\n \"createRequestId\": \"{{createRequestId}}\",\n \"itemPrice\": 1,\n \"itemCount\": 999\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/iosorderPaySig", "host": [ "{{baseUrl}}" ], "path": [ "wx", "iosorderPaySig" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "need(['gmInfo','productId','priceFen']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.status !== 'purchasable' || info.productId !== pm.environment.get('productId')) stop('请先查询可购买的本期活动');", "pm.variables.set('createRequestId','buy-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "pm.test('商品及金额取服务端快照', () => {", " const sign = JSON.parse(r.data.signData);", " pm.expect(sign.productId).to.eql(pm.environment.get('productId'));", " pm.expect(sign.buyQuantity).to.eql(1);", " pm.expect(sign.goodsPrice).to.eql(Number(pm.environment.get('priceFen')));", " pm.expect(sign.outTradeNo).to.eql(r.data.outTradeNo);", " pm.expect(typeof r.data.paySig).to.eql('string');", " pm.expect(typeof r.data.signature).to.eql('string');", "});", "if (r.code === 1) { pm.environment.set('outTradeNo',r.data.outTradeNo); pm.environment.set('orderPeriodId',JSON.parse(pm.environment.get('gmInfo')).periodId); }" ] } } ] }, { "name": "03 换 requestId 仍复用同一期订单", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"itemid\": \"{{productId}}\",\n \"createRequestId\": \"{{createRequestId}}\",\n \"itemPrice\": 1,\n \"itemCount\": 999\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/orderPaySig", "host": [ "{{baseUrl}}" ], "path": [ "wx", "orderPaySig" ] }, "description": "仅紧接同一期下单,支付前执行;outTradeNo 必须来自本期首次创建。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"outTradeNo\", \"uid\", \"token\"]);", "need(['gmInfo','productId','priceFen']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.status !== 'purchasable' || info.productId !== pm.environment.get('productId')) stop('请先查询可购买的本期活动');", "pm.variables.set('createRequestId','buy-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('复用原订单', () => pm.expect(r.data.outTradeNo).to.eql(pm.environment.get('outTradeNo')));" ] } } ] }, { "name": "查单/通用领取:wx/getPayInfo", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"outTradeNo\": \"{{outTradeNo}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/getPayInfo", "host": [ "{{baseUrl}}" ], "path": [ "wx", "getPayInfo" ] }, "description": "默认 pending;客户端真实支付并收到可信通知后设 paid,明确填写 expectedFulfillmentRoute 和 expectedTargetPeriodId。不会由此请求伪造支付。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"outTradeNo\", \"uid\", \"token\"]);", "if (!['pending','paid'].includes(pm.environment.get('expectedPaymentState'))) stop('expectedPaymentState 只能为 pending/paid');", "if (pm.environment.get('expectedPaymentState') === 'paid') need(['expectedFulfillmentRoute','expectedTargetPeriodId']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "", "const expected = pm.environment.get('expectedPaymentState');", "pm.test('查单匹配指定状态', () => {", " if (expected === 'pending') { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('PAYMENT_PENDING'); return; }", " pm.expect(r.code).to.eql(1); pm.expect(r.data.pay_state).to.eql(2);", " pm.expect(r.data.rewardDelivery).to.eql('goldMiner.claim');", " pm.expect(r.data.goldMiner.fulfillmentStatus).to.eql('fulfilled');", " pm.expect(r.data.goldMiner.fulfillmentRoute).to.eql(pm.environment.get('expectedFulfillmentRoute'));", " pm.expect(r.data.goldMiner.targetPeriodId).to.eql(pm.environment.get('expectedTargetPeriodId'));", "});" ] } } ] }, { "name": "查单/通用领取:wx/iosgetPayInfo", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"outTradeNo\": \"{{outTradeNo}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/iosgetPayInfo", "host": [ "{{baseUrl}}" ], "path": [ "wx", "iosgetPayInfo" ] }, "description": "默认 pending;客户端真实支付并收到可信通知后设 paid,明确填写 expectedFulfillmentRoute 和 expectedTargetPeriodId。不会由此请求伪造支付。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"outTradeNo\", \"uid\", \"token\"]);", "if (!['pending','paid'].includes(pm.environment.get('expectedPaymentState'))) stop('expectedPaymentState 只能为 pending/paid');", "if (pm.environment.get('expectedPaymentState') === 'paid') need(['expectedFulfillmentRoute','expectedTargetPeriodId']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "", "const expected = pm.environment.get('expectedPaymentState');", "pm.test('查单匹配指定状态', () => {", " if (expected === 'pending') { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('PAYMENT_PENDING'); return; }", " pm.expect(r.code).to.eql(1); pm.expect(r.data.pay_state).to.eql(2);", " pm.expect(r.data.rewardDelivery).to.eql('goldMiner.claim');", " pm.expect(r.data.goldMiner.fulfillmentStatus).to.eql('fulfilled');", " pm.expect(r.data.goldMiner.fulfillmentRoute).to.eql(pm.environment.get('expectedFulfillmentRoute'));", " pm.expect(r.data.goldMiner.targetPeriodId).to.eql(pm.environment.get('expectedTargetPeriodId'));", "});" ] } } ] }, { "name": "查单/通用领取:wx/getOrderReward", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"outTradeNo\": \"{{outTradeNo}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/getOrderReward", "host": [ "{{baseUrl}}" ], "path": [ "wx", "getOrderReward" ] }, "description": "默认 pending;客户端真实支付并收到可信通知后设 paid,明确填写 expectedFulfillmentRoute 和 expectedTargetPeriodId。不会由此请求伪造支付。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"outTradeNo\", \"uid\", \"token\"]);", "if (!['pending','paid'].includes(pm.environment.get('expectedPaymentState'))) stop('expectedPaymentState 只能为 pending/paid');", "if (pm.environment.get('expectedPaymentState') === 'paid') need(['expectedFulfillmentRoute','expectedTargetPeriodId']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "", "const expected = pm.environment.get('expectedPaymentState');", "pm.test('查单匹配指定状态', () => {", " if (expected === 'pending') { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('PAYMENT_PENDING'); return; }", " pm.expect(r.code).to.eql(1); pm.expect(r.data.pay_state).to.eql(2);", " pm.expect(r.data.rewardDelivery).to.eql('goldMiner.claim');", " pm.expect(r.data.goldMiner.fulfillmentStatus).to.eql('fulfilled');", " pm.expect(r.data.goldMiner.fulfillmentRoute).to.eql(pm.environment.get('expectedFulfillmentRoute'));", " pm.expect(r.data.goldMiner.targetPeriodId).to.eql(pm.environment.get('expectedTargetPeriodId'));", "});" ] } } ] }, { "name": "无效签名通知不得解锁", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmCallbackBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/payCallBack", "host": [ "{{baseUrl}}" ], "path": [ "wx", "payCallBack" ] }, "description": "用尚未支付的测试订单,openid 须为所属账号。发送后以 pending 查单确认仍未支付;正向验签必须用支付平台通知。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([]);", "need(['outTradeNo','productId','priceFen','openid']);", "const payload = JSON.stringify({OutTradeNo:pm.environment.get('outTradeNo'),OpenId:pm.environment.get('openid'),Env:0,GoodsInfo:{ProductId:pm.environment.get('productId'),Quantity:1,OrigPrice:integer('priceFen',1)}});", "pm.variables.set('gmCallbackBody',JSON.stringify({Event:'minigame_game_pay_goods_deliver_notify',MiniGame:{Payload:payload,PayEventSig:'0'.repeat(64),IsMock:false}}));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('通知被拒绝', () => { pm.expect(typeof r.ErrCode).to.eql('number'); pm.expect(r.ErrCode === 0).to.eql(false); });" ] } } ] } ] }, { "name": "04 任意档位领取与客户端保存(逐条手动)", "description": "领取目标单独使用 claimPeriodId/claimTaskId,info 不会改写它们。领过的任务不得重新加币;确认幂等用同一 grantId。", "item": [ { "name": "01 获取奖励授权", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"claim\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{claimPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"requestId\": \"{{claimRequestId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/requestId。返回 grantId/taskId/items/claimStatus/claimedThrough/requiresClientDelivery。仅有效期内可申请;授权不加币。到期返回 PERIOD_SETTLEMENT_REQUIRED,转补发查询。\n\n普通领奖仅活动有效期内调用;历史补发使用 05 文件夹清单授权和发货确认。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"claimPeriodId\", \"claimTaskId\", \"uid\", \"token\"]);", "pm.variables.set('claimRequestId','claim-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "pm.test('奖励授权结构', () => {", " pm.expect(r.data.taskId).to.eql(pm.environment.get('claimTaskId'));", " pm.expect(typeof r.data.grantId).to.eql('string');", " pm.expect(r.data.items.length > 0).to.eql(true);", " pm.expect(['issuing','claimed'].includes(r.data.claimStatus)).to.eql(true);", " pm.expect(r.data.requiresClientDelivery).to.eql(r.data.claimStatus !== 'claimed');", "});", "if (r.code === 1) {", " const old = pm.environment.get('gmGrant') ? JSON.parse(pm.environment.get('gmGrant')) : null;", " const current = {...r.data,periodId:pm.environment.get('claimPeriodId'),taskId:pm.environment.get('claimTaskId')};", " if (!old || old.grantId !== current.grantId) { pm.environment.unset('gmSavedGrantId'); pm.environment.unset('coinAfterDelivery'); }", " pm.environment.set('gmGrant',JSON.stringify(current));", " pm.environment.set('grantId',current.grantId);", "}" ] } } ] }, { "name": "02 重复领取返回原凭证", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"claim\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{claimPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"requestId\": \"{{claimRequestId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/requestId。返回 grantId/taskId/items/claimStatus/claimedThrough/requiresClientDelivery。仅有效期内可申请;授权不加币。到期返回 PERIOD_SETTLEMENT_REQUIRED,转补发查询。\n\n" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"claimPeriodId\", \"claimTaskId\", \"uid\", \"token\"]);", "", "need(['gmGrant','grantId','claimPeriodId','claimTaskId']);", "const grant = JSON.parse(pm.environment.get('gmGrant'));", "if (grant.grantId !== pm.environment.get('grantId') || grant.periodId !== pm.environment.get('claimPeriodId') || grant.taskId !== pm.environment.get('claimTaskId')) stop('凭证与所选期/任务不一致,请先领取所选任务');", "pm.variables.set('claimRequestId','claim-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('重复领取无新奖励凭证', () => { const prior = JSON.parse(pm.environment.get('gmGrant')); pm.expect(r.data.grantId).to.eql(prior.grantId); pm.expect(r.data.items).to.eql(prior.items); pm.expect(r.data.claimStatus).to.eql(prior.claimStatus); pm.expect(r.data.requiresClientDelivery).to.eql(prior.claimStatus !== 'claimed'); });" ] } } ] }, { "name": "03 读取金币总余额", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"read\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userCoin", "host": [ "{{baseUrl}}" ], "path": [ "userCoin" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('余额为非负数', () => pm.expect(Number.isFinite(r.data.coinAmount) && r.data.coinAmount >= 0).to.eql(true));", "if (r.code === 1) pm.environment.set('coinBeforeDelivery',r.data.coinAmount);" ] } } ] }, { "name": "04 模拟客户端发货后保存总余额", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"save\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"coinAmount\": \"{{coinAfterDelivery}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/userCoin", "host": [ "{{baseUrl}}" ], "path": [ "userCoin" ] }, "description": "仅独立测试账号、无其他设备写入时使用。领取后读余额,再手填 coinAfterDelivery;保存的是总余额。超时后先读服务端并核对原余额及凭证,不能重新相加。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "", "need(['gmGrant','grantId','claimPeriodId','claimTaskId']);", "const grant = JSON.parse(pm.environment.get('gmGrant'));", "if (grant.grantId !== pm.environment.get('grantId') || grant.periodId !== pm.environment.get('claimPeriodId') || grant.taskId !== pm.environment.get('claimTaskId')) stop('凭证与所选期/任务不一致,请先领取所选任务');", "", "if (grant.claimStatus !== 'issuing' || !grant.requiresClientDelivery) stop('该凭证已完成,不得再次加币');", "if (pm.environment.get('gmSavedGrantId') === grant.grantId) stop('此凭证已保存成功,只需确认领取');", "const before = integer('coinBeforeDelivery');", "const after = integer('coinAfterDelivery');", "if (!grant.items.every(i => i.type === 'coin')) stop('当前测试仅支持金币发货');", "const amount = grant.items.reduce((sum,i) => sum+i.count,0);", "if (after !== before + amount) stop('coinAfterDelivery 应为刚读取的总余额加本次奖励;请人工填写');" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('保存指定总余额', () => pm.expect(r.data.coinAmount).to.eql(Number(pm.environment.get('coinAfterDelivery'))));", "if (r.code === 1 && r.data.coinAmount === Number(pm.environment.get('coinAfterDelivery'))) pm.environment.set('gmSavedGrantId',pm.environment.get('grantId'));" ] } } ] }, { "name": "05 保存后确认领取(可原请求重试)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"confirm_delivery\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{claimPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"grantId\": \"{{grantId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/grantId。返回 grantId/claimStatus/claimedThrough。仅在客户端已保存奖励后确认,重复确认幂等。\n\n完成后可重复确认、重复授权,再查询活动验证当前档 claimed 和下一档状态。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "", "need(['gmGrant','grantId','claimPeriodId','claimTaskId']);", "const grant = JSON.parse(pm.environment.get('gmGrant'));", "if (grant.grantId !== pm.environment.get('grantId') || grant.periodId !== pm.environment.get('claimPeriodId') || grant.taskId !== pm.environment.get('claimTaskId')) stop('凭证与所选期/任务不一致,请先领取所选任务');", "", "if (pm.environment.get('gmSavedGrantId') !== grant.grantId) stop('请先完成本凭证的金币保存');" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "pm.test('确认同一凭证', () => { pm.expect(r.data.grantId).to.eql(pm.environment.get('grantId')); pm.expect(r.data.claimStatus).to.eql('claimed'); pm.expect(Number.isSafeInteger(r.data.claimedThrough) && r.data.claimedThrough >= 0).to.eql(true); });", "if (r.code === 1) { const grant = JSON.parse(pm.environment.get('gmGrant')); grant.claimStatus='claimed'; grant.requiresClientDelivery=false; grant.claimedThrough=r.data.claimedThrough; pm.environment.set('gmGrant',JSON.stringify(grant)); }" ] } } ] }, { "name": "06 未付费不能领", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"claim\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{claimPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"requestId\": \"{{claimRequestId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/requestId。返回 grantId/taskId/items/claimStatus/claimedThrough/requiresClientDelivery。仅有效期内可申请;授权不加币。到期返回 PERIOD_SETTLEMENT_REQUIRED,转补发查询。\n\n未付费且任务尚未授权的已有玩家期记录。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"claimPeriodId\", \"claimTaskId\", \"uid\", \"token\"]);", "pm.variables.set('claimRequestId','claim-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:NOT_PAID', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('NOT_PAID'); });" ] } } ] }, { "name": "07 未达标不能领", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"claim\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{claimPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"requestId\": \"{{claimRequestId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/requestId。返回 grantId/taskId/items/claimStatus/claimedThrough/requiresClientDelivery。仅有效期内可申请;授权不加币。到期返回 PERIOD_SETTLEMENT_REQUIRED,转补发查询。\n\n已付费,所选任务未达标。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"claimPeriodId\", \"claimTaskId\", \"uid\", \"token\"]);", "pm.variables.set('claimRequestId','claim-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:TARGET_NOT_REACHED', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('TARGET_NOT_REACHED'); });" ] } } ] }, { "name": "08 跳过前一档领取已达标任务", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"claim\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{claimPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"requestId\": \"{{claimRequestId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/requestId。返回 grantId/taskId/items/claimStatus/claimedThrough/requiresClientDelivery。仅有效期内可申请;授权不加币。到期返回 PERIOD_SETTLEMENT_REQUIRED,转补发查询。\n\n已付费,所选后续任务已达标,但前一档未确认保存。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"claimPeriodId\", \"claimTaskId\", \"uid\", \"token\"]);", "pm.variables.set('claimRequestId','claim-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "", "pm.test('奖励授权结构', () => {", " pm.expect(r.data.taskId).to.eql(pm.environment.get('claimTaskId'));", " pm.expect(typeof r.data.grantId).to.eql('string');", " pm.expect(r.data.items.length > 0).to.eql(true);", " pm.expect(['issuing','claimed'].includes(r.data.claimStatus)).to.eql(true);", " pm.expect(r.data.requiresClientDelivery).to.eql(r.data.claimStatus !== 'claimed');", "});", "if (r.code === 1) {", " const old = pm.environment.get('gmGrant') ? JSON.parse(pm.environment.get('gmGrant')) : null;", " const current = {...r.data,periodId:pm.environment.get('claimPeriodId'),taskId:pm.environment.get('claimTaskId')};", " if (!old || old.grantId !== current.grantId) { pm.environment.unset('gmSavedGrantId'); pm.environment.unset('coinAfterDelivery'); }", " pm.environment.set('gmGrant',JSON.stringify(current));", " pm.environment.set('grantId',current.grantId);", "}" ] } } ] }, { "name": "09 错误发货凭证不能确认", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"confirm_delivery\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{claimPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"grantId\": \"postman-invalid-grant\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/grantId。返回 grantId/claimStatus/claimedThrough。仅在客户端已保存奖励后确认,重复确认幂等。\n\n所选任务须存在于该账号的玩家期记录。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"claimPeriodId\", \"claimTaskId\", \"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:INVALID_GRANT', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('INVALID_GRANT'); });" ] } } ] } ] }, { "name": "05 到期补发(手动)", "description": "查询所有历史已结束期的未确认奖励;后台 jobs/支付恢复负责结算。优先处理 items,有 nextCursor 即翻页,包括 settling 响应。扫描结束后从第一页复查;settling 时稍后重查,no_pending_rewards 时结束。05-05 选原 grantId,04-03/04 保存金币后使用 05-06 确认补发;ack_settlement 仅兼容展示确认。", "item": [ { "name": "01 清单第一页", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"settlements\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token,afterId 可选分页游标;前端不传 limit,后端固定每页扫描 20 期。返回 status=pending_delivery/settling/no_pending_rewards、items、nextCursor;每项含 settlementId/periodId/rewards,奖励含 taskId/grantId/items。可发奖励优先返回,不受其他期结算阻塞;no_pending_rewards 表示该玩家当前环境下所有已结束期均无待发或待结算记录。\n\n只查询后台已结算的未确认奖励。先处理可发奖励,有 nextCursor 即继续翻页;扫描结束后从第一页复查,settling 时稍后重查,no_pending_rewards 时结束。需部署 goldMiner/jobs 及每分钟触发器。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('补发清单结构', () => {", " pm.expect(['settling','pending_delivery','no_pending_rewards'].includes(r.data.status)).to.eql(true);", " pm.expect(Array.isArray(r.data.items)).to.eql(true);", " pm.expect(r.data.nextCursor === null || typeof r.data.nextCursor === 'string').to.eql(true);", " if (r.data.status === 'settling') pm.expect(r.data.items.length).to.eql(0);", " if (r.data.status === 'no_pending_rewards') { pm.expect(r.data.items.length).to.eql(0); pm.expect(r.data.nextCursor).to.eql(null); }", " r.data.items.forEach(s => {", " pm.expect(typeof s.settlementId).to.eql('string'); pm.expect(typeof s.periodId).to.eql('string');", " pm.expect(Array.isArray(s.rewards) && s.rewards.length > 0).to.eql(true);", " s.rewards.forEach(g => { pm.expect(typeof g.grantId).to.eql('string'); pm.expect(typeof g.taskId).to.eql('string'); pm.expect(Array.isArray(g.items)).to.eql(true); });", " });", "});", "if (r.code === 1) pm.environment.set('settlementCursor',r.data.nextCursor || '');" ] } } ] }, { "name": "02 下一页(空 items 也可能有下一页)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"settlements\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"afterId\": \"{{settlementCursor}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token,afterId 可选分页游标;前端不传 limit,后端固定每页扫描 20 期。返回 status=pending_delivery/settling/no_pending_rewards、items、nextCursor;每项含 settlementId/periodId/rewards,奖励含 taskId/grantId/items。可发奖励优先返回,不受其他期结算阻塞;no_pending_rewards 表示该玩家当前环境下所有已结束期均无待发或待结算记录。\n\n只查询后台已结算的未确认奖励。先处理可发奖励,有 nextCursor 即继续翻页;扫描结束后从第一页复查,settling 时稍后重查,no_pending_rewards 时结束。需部署 goldMiner/jobs 及每分钟触发器。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"settlementCursor\", \"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('补发清单结构', () => {", " pm.expect(['settling','pending_delivery','no_pending_rewards'].includes(r.data.status)).to.eql(true);", " pm.expect(Array.isArray(r.data.items)).to.eql(true);", " pm.expect(r.data.nextCursor === null || typeof r.data.nextCursor === 'string').to.eql(true);", " if (r.data.status === 'settling') pm.expect(r.data.items.length).to.eql(0);", " if (r.data.status === 'no_pending_rewards') { pm.expect(r.data.items.length).to.eql(0); pm.expect(r.data.nextCursor).to.eql(null); }", " r.data.items.forEach(s => {", " pm.expect(typeof s.settlementId).to.eql('string'); pm.expect(typeof s.periodId).to.eql('string');", " pm.expect(Array.isArray(s.rewards) && s.rewards.length > 0).to.eql(true);", " s.rewards.forEach(g => { pm.expect(typeof g.grantId).to.eql('string'); pm.expect(typeof g.taskId).to.eql('string'); pm.expect(Array.isArray(g.items)).to.eql(true); });", " });", "});", "if (r.code === 1) pm.environment.set('settlementCursor',r.data.nextCursor || '');" ] } } ] }, { "name": "03 未完成发货不能确认清单", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"ack_settlement\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"settlementId\": \"{{settlementId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/settlementId。返回 settlementId/acknowledged。仅已全部确认发货时成功;这是兼容展示确认,不代替 confirm_settlement_delivery。\n\n清单有未确认奖励时,旧 ack_settlement 仍不能提前确认展示。此请求不是发货确认。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"settlementId\", \"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('拒绝:DELIVERY_PENDING', () => { pm.expect(r.code).to.eql(0); pm.expect(r.errorCode).to.eql('DELIVERY_PENDING'); });" ] } } ] }, { "name": "04 完成后确认清单(可重复)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"ack_settlement\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"settlementId\": \"{{settlementId}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/settlementId。返回 settlementId/acknowledged。仅已全部确认发货时成功;这是兼容展示确认,不代替 confirm_settlement_delivery。\n\n全部奖励确认后可选调用的旧展示确认,不是发货确认;新流程不要求调用。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"settlementId\", \"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('确认选定清单', () => { pm.expect(r.data.settlementId).to.eql(pm.environment.get('settlementId')); pm.expect(r.data.acknowledged).to.eql(true); });" ] } } ] }, { "name": "05 查询并选择首个待补发奖励(不调用 claim)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"settlements\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token,afterId 可选分页游标;前端不传 limit,后端固定每页扫描 20 期。返回 status=pending_delivery/settling/no_pending_rewards、items、nextCursor;每项含 settlementId/periodId/rewards,奖励含 taskId/grantId/items。可发奖励优先返回,不受其他期结算阻塞;no_pending_rewards 表示该玩家当前环境下所有已结束期均无待发或待结算记录。\n\n选择第一页首项;随后执行 04 文件夹的 03 读余额、04 保存余额,再执行本文件夹 06 确认。其他页奖励按返回标识选择,不走 claim。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('补发清单结构', () => {", " pm.expect(['settling','pending_delivery','no_pending_rewards'].includes(r.data.status)).to.eql(true);", " pm.expect(Array.isArray(r.data.items)).to.eql(true);", " pm.expect(r.data.nextCursor === null || typeof r.data.nextCursor === 'string').to.eql(true);", " if (r.data.status === 'settling') pm.expect(r.data.items.length).to.eql(0);", " if (r.data.status === 'no_pending_rewards') { pm.expect(r.data.items.length).to.eql(0); pm.expect(r.data.nextCursor).to.eql(null); }", " r.data.items.forEach(s => {", " pm.expect(typeof s.settlementId).to.eql('string'); pm.expect(typeof s.periodId).to.eql('string');", " pm.expect(Array.isArray(s.rewards) && s.rewards.length > 0).to.eql(true);", " s.rewards.forEach(g => { pm.expect(typeof g.grantId).to.eql('string'); pm.expect(typeof g.taskId).to.eql('string'); pm.expect(Array.isArray(g.items)).to.eql(true); });", " });", "});", "if (r.code === 1) pm.environment.set('settlementCursor',r.data.nextCursor || '');", "", "if (r.code === 1 && r.data.status === 'pending_delivery' && r.data.items.length) {", " const s=r.data.items[0], g=s.rewards[0];", " pm.environment.set('settlementId',s.settlementId);", " pm.environment.set('claimPeriodId',s.periodId); pm.environment.set('claimTaskId',g.taskId);", " pm.environment.set('grantId',g.grantId);", " pm.environment.set('gmGrant',JSON.stringify({...g,periodId:s.periodId,claimStatus:'issuing',requiresClientDelivery:true}));", "}" ] } } ] }, { "name": "06 保存金币后确认补发(重复安全)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"confirm_settlement_delivery\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"settlementId\": \"{{settlementId}}\",\n \"grantIds\": [\n \"{{grantId}}\"\n ]\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/settlementId/grantIds(1~100 个不重复授权 ID)。返回 settlementId/confirmedGrantIds/deliveryStatus(partial/client_saved)。本用例逐项确认,须先完成对应奖励保存。\n\n只在所选 grantId 的金币确定已保存后执行。接口支持批量 grantIds,手工联调默认逐项,沿用既有金币保存保护。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "", "need(['gmGrant','grantId','claimPeriodId','claimTaskId']);", "const grant = JSON.parse(pm.environment.get('gmGrant'));", "if (grant.grantId !== pm.environment.get('grantId') || grant.periodId !== pm.environment.get('claimPeriodId') || grant.taskId !== pm.environment.get('claimTaskId')) stop('凭证与所选期/任务不一致,请先领取所选任务');", "", "if (pm.environment.get('gmSavedGrantId') !== grant.grantId) stop('请先完成本凭证的金币保存');", "need(['settlementId']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r=pm.response.json();", "pm.test('业务成功',()=>pm.expect(r.code).to.eql(1));", "pm.test('补发确认匹配授权',()=>{", " pm.expect(r.data.settlementId).to.eql(pm.environment.get('settlementId'));", " pm.expect(r.data.confirmedGrantIds).to.eql([pm.environment.get('grantId')]);", " pm.expect(['partial','client_saved'].includes(r.data.deliveryStatus)).to.eql(true);", "});" ] } } ] }, { "name": "07 到期后普通领奖被拒绝", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"claim\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"periodId\": \"{{expiredPeriodId}}\",\n \"taskId\": \"{{claimTaskId}}\",\n \"requestId\": \"expired-claim\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "请求:uid/token/periodId/taskId/requestId。返回 grantId/taskId/items/claimStatus/claimedThrough/requiresClientDelivery。仅有效期内可申请;授权不加币。到期返回 PERIOD_SETTLEMENT_REQUIRED,转补发查询。\n\n手填 expiredPeriodId 和该期有效 claimTaskId;到期后普通领奖必须拒绝,含已授权任务的重复请求。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"uid\", \"token\"]);", "need(['expiredPeriodId','claimTaskId']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r=pm.response.json();", "pm.test('过期只走补发',()=>{pm.expect(r.code).to.eql(0);pm.expect(r.errorCode).to.eql('PERIOD_SETTLEMENT_REQUIRED');});" ] } } ] } ] }, { "name": "06 管理操作(手动)", "description": "adminToken 对应服务端 ADMIN_TOKEN。逐条执行:索引、未来周配置、购买开关、测试临时期。publish_test_period 额外要求 PAYMENT_APP_ENV=test 和 GOLD_MINER_TEST_PERIODS_ENABLED=true。", "item": [ { "name": "01 创建活动索引", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"setup_indexes\",\n \"adminToken\": \"{{adminToken}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/admin", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "admin" ] }, "description": "请求:action=setup_indexes、adminToken(ADMIN_TOKEN)。返回 code/msg。只准备现有设计需要的索引,不启用活动。\n\n" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"adminToken\"]);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));" ] } } ] }, { "name": "02 发布未来期配置", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmAdminBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/admin", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "admin" ] }, "description": "configJson 格式见 README/API 文档,生效期必须为未来周四;同 configVersion 不重复发布。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"adminToken\", \"configJson\"]);", "let config;", "try { config=JSON.parse(pm.environment.get('configJson')); } catch { stop('configJson 必须是填写完成的配置 JSON'); }", "const offers = config?.vipTiers === undefined ? [config] : config.vipTiers; if (!Array.isArray(offers) || !offers.length || (config.vipTiers && offers.length !== 6) || offers.some(o => !o || !Number.isSafeInteger(o.priceFen) || o.priceFen <= 0 || !Array.isArray(o.tasks) || !o.tasks.length)) stop('请填写正式确定的价格和任务;禁止发布空草稿');", "pm.variables.set('gmAdminBody',JSON.stringify({action:'publish',adminToken:pm.environment.get('adminToken'),config}));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('配置发布成功', () => { const config=JSON.parse(pm.environment.get('configJson')); pm.expect(r.data.configVersion).to.eql(config.configVersion); pm.expect(r.data.effectiveFromPeriodId).to.eql(config.effectiveFromPeriodId); if (config.vipTiers) pm.expect(r.data.vipTiers).to.eql(config.vipTiers); else { pm.expect(r.data.priceFen).to.eql(config.priceFen); pm.expect(r.data.tasks).to.eql(config.tasks); } });" ] } } ] }, { "name": "03 修改已有期购买开关", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{gmAdminBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/admin", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "admin" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need([\"adminToken\", \"adminPeriodId\", \"purchaseEnabled\"]);", "const value=pm.environment.get('purchaseEnabled');", "if (!['true','false'].includes(value)) stop('purchaseEnabled 必须为 true 或 false');", "pm.variables.set('gmAdminBody',JSON.stringify({action:'set_purchasable',adminToken:pm.environment.get('adminToken'),periodId:pm.environment.get('adminPeriodId'),purchaseEnabled:value==='true'}));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));" ] } } ] }, { "name": "04 发布测试临时期(默认立即开启 30 分钟)", "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "const get = key => String(pm.environment.get(key) ?? '').trim();", "const baseUrl = get('baseUrl');", "if (!/^https?:\\/\\/[^\\s/?#]+(?::\\d+)?(?:\\/[^\\s?#]*)?$/.test(baseUrl) || baseUrl.endsWith('/') || /[<>]/.test(baseUrl)) stop('请填写 baseUrl(部署域名,无末尾 /)');", "const adminToken = pm.environment.get('adminToken');", "if (typeof adminToken !== 'string' || !adminToken.trim()) stop('请填写测试服 ADMIN_TOKEN 对应的 adminToken');", "function timestamp(value, label) {", " if (!/^\\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) <= 0 || !Number.isFinite(new Date(Number(value)).getTime())) stop(label + ' 必须为有效的 Unix 毫秒整数');", " return Number(value);", "}", "let config;", "try { config = JSON.parse(get('tempConfig')); } catch { stop('tempConfig 必须为 JSON 配置对象'); }", "if (!config || typeof config !== 'object' || Array.isArray(config)) stop('tempConfig 必须为 JSON 配置对象');", "let periodId = get('tempPeriodId');", "if (periodId && !/^goldMiner:test:[A-Za-z0-9_-]{1,60}$/.test(periodId)) stop('tempPeriodId 格式为 goldMiner:test:自定义标识');", "const startsAt = get('tempStartsAt') ? timestamp(get('tempStartsAt'), 'tempStartsAt') : Date.now();", "let endsAt;", "if (get('tempEndsAt')) endsAt = timestamp(get('tempEndsAt'), 'tempEndsAt');", "else {", " const minutes = Number(get('tempDurationMinutes'));", " if (!Number.isSafeInteger(minutes) || minutes <= 0) stop('tempDurationMinutes 必须为正整数分钟');", " endsAt = timestamp(String(startsAt + minutes * 60000), '计算出的结束时间');", "}", "if (endsAt <= startsAt) stop('tempEndsAt 必须晚于 tempStartsAt');", "const nextPeriodId = get('tempNextPeriodId');", "if (nextPeriodId && !/^goldMiner:(?:test:[A-Za-z0-9_-]{1,60}|\\d{4}-\\d{2}-\\d{2})$/.test(nextPeriodId)) stop('tempNextPeriodId 格式错误');", "const previousBase = get('tempDraftBaseUrl');", "if (previousBase && previousBase !== baseUrl) stop('baseUrl 已切换,请使用另一份环境,或清空 tempDraftBaseUrl/tempPeriodId/tempStartsAt/tempEndsAt 后重新生成');", "if (!periodId) periodId = 'goldMiner:test:' + pm.variables.replaceIn('{{$guid}}');", "// Only non-secret identifiers/times are persisted. Retrying Send reuses the same period.", "pm.environment.set('tempPeriodId', periodId);", "pm.environment.set('tempStartsAt', String(startsAt));", "pm.environment.set('tempEndsAt', String(endsAt));", "pm.environment.set('tempDraftBaseUrl', baseUrl);", "const body = { action: 'publish_test_period', adminToken, periodId, startsAt, endsAt, config };", "if (nextPeriodId) body.nextPeriodId = nextPeriodId;", "// Local scope avoids storing a second copy of the administrator secret in the environment.", "pm.variables.set('testPeriodRequestBody', JSON.stringify(body));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('HTTP 200', function () { pm.response.to.have.status(200); });", "const response = pm.response.json();", "const sent = JSON.parse(pm.variables.get('testPeriodRequestBody'));", "pm.test('临时期发布成功(失败时查看响应 errorCode/msg)', function () { pm.expect(response.code).to.eql(1); });", "if (response.code === 1 && response.data) {", " const data = response.data;", " pm.test('期 ID、开始和结束时间与请求一致', function () {", " pm.expect(data.periodId).to.eql(sent.periodId);", " pm.expect(data.startsAt).to.eql(sent.startsAt);", " pm.expect(data.endsAt).to.eql(sent.endsAt);", " });", " pm.test('奖励配置和商品快照与请求一致', function () {", " for (const key of ['configVersion', 'unlockPassedLevel', 'timezone', 'currency', ...(sent.config.vipTiers ? ['vipTiers'] : ['productId', 'priceFen', 'tasks'])]) {", " pm.expect(data.configSnapshot[key]).to.eql(sent.config[key]);", " }", " pm.expect(data.configSnapshot.effectiveFromPeriodId).to.eql(sent.periodId);", " pm.expect(typeof data.configHash).to.eql('string');", " });", " pm.test('顺延目标不早于本期结束', function () {", " pm.expect(Number.isSafeInteger(data.nextStartsAt) && data.nextStartsAt >= sent.endsAt).to.eql(true);", " if (sent.nextPeriodId) pm.expect(data.nextPeriodId).to.eql(sent.nextPeriodId);", " });", "}" ] } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{testPeriodRequestBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/admin", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "admin" ] }, "description": "参数在配套 Environment 中配置。tempPeriodId/tempStartsAt/tempEndsAt 留空时首次自动生成并保存。tempConfig 为完整业务配置 JSON,可修改门槛、价格及任务。tempNextPeriodId 可留空。已发布期不可修改;新建期时使用新的期 ID 及不重叠的时间。接口对应服务端 publish_test_period 动作,没有单独的 /test-period 路由。" }, "response": [] } ] }, { "name": "07 旧 iOS 客服渠道", "item": [ { "name": "01 客服渠道预下单(服务端定价)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"create_order\",\n \"channel\": \"legacy_ios\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"itemid\": \"{{productId}}\",\n \"createRequestId\": \"{{createRequestId}}\",\n \"itemPrice\": 1,\n \"itemCount\": 999\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "需服务端启用 GOLD_MINER_LEGACY_ENABLED。与原生下单使用不同账号或不同期;同一期不切换渠道。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need(['uid','token','gmInfo','productId','priceFen']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.status !== 'purchasable') stop('请先查询可购买的当期活动');", "pm.variables.set('createRequestId','legacy-'+pm.variables.replaceIn('{{$guid}}'));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('旧渠道订单使用服务端快照', () => {", " pm.expect(r.data.channel).to.eql('legacy_ios');", " pm.expect(r.data.quantity).to.eql(1);", " pm.expect(r.data.priceFen).to.eql(Number(pm.environment.get('priceFen')));", " pm.expect(r.data.productId).to.eql(pm.environment.get('productId'));", " pm.expect(r.data.periodId).to.eql(JSON.parse(pm.environment.get('gmInfo')).periodId);", " const session = JSON.parse(r.data.sessionFrom);", " pm.expect(session.outTradeNo).to.eql(r.data.outTradeNo);", " pm.expect(session.propName).to.eql(r.data.productId);", " pm.expect(typeof session.goldMinerTicket).to.eql('string');", "});", "if (r.code === 1) { pm.environment.set('outTradeNo',r.data.outTradeNo); pm.environment.set('orderPeriodId',r.data.periodId); pm.environment.set('legacySessionFrom',r.data.sessionFrom); }" ] } } ] }, { "name": "02 重复预下单复用原订单", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"create_order\",\n \"channel\": \"legacy_ios\",\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"itemid\": \"{{productId}}\",\n \"createRequestId\": \"{{createRequestId}}\",\n \"itemPrice\": 1,\n \"itemCount\": 999\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/index", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "index" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need(['uid','token','gmInfo','productId','priceFen']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.status !== 'purchasable') stop('请先查询可购买的当期活动');", "pm.variables.set('createRequestId','legacy-'+pm.variables.replaceIn('{{$guid}}'));", "need(['outTradeNo','legacySessionFrom']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('旧渠道订单使用服务端快照', () => {", " pm.expect(r.data.channel).to.eql('legacy_ios');", " pm.expect(r.data.quantity).to.eql(1);", " pm.expect(r.data.priceFen).to.eql(Number(pm.environment.get('priceFen')));", " pm.expect(r.data.productId).to.eql(pm.environment.get('productId'));", " pm.expect(r.data.periodId).to.eql(JSON.parse(pm.environment.get('gmInfo')).periodId);", " const session = JSON.parse(r.data.sessionFrom);", " pm.expect(session.outTradeNo).to.eql(r.data.outTradeNo);", " pm.expect(session.propName).to.eql(r.data.productId);", " pm.expect(typeof session.goldMinerTicket).to.eql('string');", "});", "pm.test('复用原订单', () => pm.expect(r.data.outTradeNo).to.eql(pm.environment.get('outTradeNo')));", "if (r.code === 1) pm.environment.set('legacySessionFrom',r.data.sessionFrom);" ] } } ] }, { "name": "03 客服回调发送支付链接(手动开启)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{{legacyCustomerBody}}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/KeFuInfo", "host": [ "{{baseUrl}}" ], "path": [ "wx", "KeFuInfo" ] }, "description": "需测试账号在微信有效客服会话窗口内,服务端已有 wxaccess_token;此请求会真实发送消息,勿在 Runner 中批量执行。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need(['openid','legacySessionFrom']);", "if (pm.environment.get('allowCustomerMessage') !== 'true') stop('此请求会向测试账号发送客服消息;确认后填写 allowCustomerMessage=true');", "pm.variables.set('legacyCustomerBody',JSON.stringify({Event:'user_enter_tempsession',FromUserName:pm.environment.get('openid'),SessionFrom:pm.environment.get('legacySessionFrom')}));" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('业务成功', () => pm.expect(r.code).to.eql(1));", "pm.test('仍为原订单', () => pm.expect(r.data.outTradeNo).to.eql(pm.environment.get('outTradeNo')));" ] } } ] }, { "name": "04 登录态转单可重复调用", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"outTradeNo\": \"{{outTradeNo}}\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/checkIos", "host": [ "{{baseUrl}}" ], "path": [ "wx", "checkIos" ] }, "description": "创建订单后即可调用;只校验/关联快照,不确认付款。支付页使用签名 time 票据而非登录态。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need(['uid','token','outTradeNo']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('转单明确成功', () => pm.expect(pm.response.json()).to.eql(true));" ] } } ] }, { "name": "05 无效转单票据拒绝", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"openid\": \"{{openid}}\",\n \"outTradeNo\": \"{{outTradeNo}}\",\n \"time\": \"invalid-ticket\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/checkIos", "host": [ "{{baseUrl}}" ], "path": [ "wx", "checkIos" ] }, "description": "" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need(['openid','outTradeNo']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('未授权转单拒绝', () => pm.expect(pm.response.json()).to.eql(false));" ] } } ] }, { "name": "06 无签名商户通知拒绝", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"event_type\": \"TRANSACTION.SUCCESS\",\n \"resource\": {}\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/goldMiner/merchantNotify", "host": [ "{{baseUrl}}" ], "path": [ "goldMiner", "merchantNotify" ] }, "description": "之后再次查单,确认未解锁。不提供伪造成功通知或支付密钥。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('HTTP 500', () => pm.response.to.have.status(500));", "pm.test('无签名通知拒绝', () => pm.expect(pm.response.json().code).to.eql('FAIL'));" ] } } ] }, { "name": "07 旧渠道订单禁止再创建原生订单", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"uid\": \"{{uid}}\",\n \"token\": \"{{token}}\",\n \"itemid\": \"{{productId}}\",\n \"createRequestId\": \"{{createRequestId}}\",\n \"itemPrice\": 1,\n \"itemCount\": 999\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/wx/orderPaySig", "host": [ "{{baseUrl}}" ], "path": [ "wx", "orderPaySig" ] }, "description": "仅在旧渠道订单尚未付款时执行;原生支付签名配置也需已就绪。" }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "function stop(message) { console.error(message); pm.execution.skipRequest(); throw new Error(message); }", "function need(keys) { for (const key of keys) if (String(pm.environment.get(key) ?? '').trim() === '') stop('请先填写/取得变量:' + key); }", "function integer(key, min = 0) { need([key]); const value = Number(pm.environment.get(key)); if (!Number.isSafeInteger(value) || value < min) stop(key + ' 必须为有效整数'); return value; }", "", "need(['uid','token','gmInfo','productId','priceFen']);", "const info = JSON.parse(pm.environment.get('gmInfo'));", "if (info.status !== 'purchasable') stop('请先查询可购买的当期活动');", "pm.variables.set('createRequestId','legacy-'+pm.variables.replaceIn('{{$guid}}'));", "need(['outTradeNo','legacySessionFrom']);" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('渠道冲突', () => pm.expect(r.errorCode).to.eql('PAYMENT_CHANNEL_CONFLICT'));" ] } } ] } ] }, { "name": "08 公共活动日程(无需登录,可单独 Runner)", "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。", "item": [ { "name": "01 公共活动日程(GET,无需登录)", "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('公共日程查询成功', () => pm.expect(r.code).to.eql(1));", "let valid = false;", "if (r.code === 1) pm.test('公共日程字段、时间窗口和排序', () => {", " const d = r.data;", " pm.expect(Object.keys(d).sort()).to.eql(['schemaVersion','serverNow','windowEndsAt','refreshAt','activities'].sort());", " pm.expect(d.schemaVersion).to.eql(1);", " pm.expect(Number.isSafeInteger(d.serverNow)).to.eql(true);", " pm.expect(d.windowEndsAt).to.eql(d.serverNow + 7 * 86400000);", " pm.expect(Array.isArray(d.activities)).to.eql(true);", " const seen = new Set();", " let refreshAt = d.serverNow + 30000;", " d.activities.forEach(a => {", " pm.expect(['goldMiner','cloudRise'].includes(a.activityId)).to.eql(true);", " const fields = ['activityId','periodId','configVersion','startsAt','endsAt','unlockLevel','phase'];", " if (a.activityId === 'goldMiner') fields.push('purchaseEnabled');", " pm.expect(Object.keys(a).sort()).to.eql(fields.sort());", " pm.expect(typeof a.periodId === 'string' && !!a.periodId && typeof a.configVersion === 'string' && !!a.configVersion).to.eql(true);", " pm.expect(Number.isSafeInteger(a.startsAt) && Number.isSafeInteger(a.endsAt) && a.startsAt < a.endsAt).to.eql(true);", " pm.expect(a.startsAt < d.windowEndsAt && a.endsAt > d.serverNow).to.eql(true);", " pm.expect(Number.isSafeInteger(a.unlockLevel) && a.unlockLevel >= 0).to.eql(true);", " pm.expect(a.phase).to.eql(a.startsAt <= d.serverNow ? 'active' : 'upcoming');", " if (a.activityId === 'goldMiner') pm.expect(typeof a.purchaseEnabled).to.eql('boolean');", " const key = JSON.stringify([a.activityId,a.periodId]);", " pm.expect(seen.has(key)).to.eql(false);seen.add(key);", " for (const boundary of [a.startsAt,a.endsAt]) if (boundary > d.serverNow) refreshAt = Math.min(refreshAt,boundary);", " });", " pm.expect(d.refreshAt).to.eql(refreshAt);", " const sorted = [...d.activities].sort((a,b) => a.activityId < b.activityId ? -1 : a.activityId > b.activityId ? 1", " : b.startsAt-a.startsAt || (a.periodId < b.periodId ? -1 : a.periodId > b.periodId ? 1 : 0));", " pm.expect(d.activities).to.eql(sorted);", " const expected = pm.environment.get('expectedListedPeriodId');", " if (expected) pm.expect(d.activities.some(a=>a.activityId==='goldMiner' && a.periodId===expected)).to.eql(true);", " valid = true;", "});", "// Cache only a valid success. Empty activities is valid; errors must preserve the previous cache.", "if (valid) pm.environment.set('gmActivitySchedule',JSON.stringify(r.data));" ] } } ], "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。" }, "response": [ { "name": "成功:临时期日程", "originalRequest": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。" }, "status": "OK", "code": 200, "_postman_previewlanguage": "json", "header": [ { "key": "Content-Type", "value": "application/json" } ], "cookie": [], "body": "{\n \"code\": 1,\n \"msg\": \"ok\",\n \"data\": {\n \"schemaVersion\": 1,\n \"serverNow\": 1789956000000,\n \"windowEndsAt\": 1790560800000,\n \"refreshAt\": 1789956030000,\n \"activities\": [\n {\n \"activityId\": \"goldMiner\",\n \"periodId\": \"goldMiner:test:20260921_1000\",\n \"configVersion\": \"temporaryV1\",\n \"startsAt\": 1789956000000,\n \"endsAt\": 1789959600000,\n \"unlockLevel\": 41,\n \"phase\": \"active\",\n \"purchaseEnabled\": true\n }\n ]\n }\n}" }, { "name": "成功:无开放或预告活动", "originalRequest": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。" }, "status": "OK", "code": 200, "_postman_previewlanguage": "json", "header": [ { "key": "Content-Type", "value": "application/json" } ], "cookie": [], "body": "{\n \"code\": 1,\n \"msg\": \"ok\",\n \"data\": {\n \"schemaVersion\": 1,\n \"serverNow\": 1789956000000,\n \"windowEndsAt\": 1790560800000,\n \"refreshAt\": 1789956030000,\n \"activities\": []\n }\n}" }, { "name": "查询失败:保留旧缓存", "originalRequest": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。" }, "status": "OK", "code": 200, "_postman_previewlanguage": "json", "header": [ { "key": "Content-Type", "value": "application/json" } ], "cookie": [], "body": "{\n \"code\": 0,\n \"msg\": \"活动信息暂时无法获取,请稍后重试\",\n \"data\": null\n}" } ] }, { "name": "02 公共活动日程(POST,无需登录)", "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const r = pm.response.json();", "pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.test('公共日程查询成功', () => pm.expect(r.code).to.eql(1));", "let valid = false;", "if (r.code === 1) pm.test('公共日程字段、时间窗口和排序', () => {", " const d = r.data;", " pm.expect(Object.keys(d).sort()).to.eql(['schemaVersion','serverNow','windowEndsAt','refreshAt','activities'].sort());", " pm.expect(d.schemaVersion).to.eql(1);", " pm.expect(Number.isSafeInteger(d.serverNow)).to.eql(true);", " pm.expect(d.windowEndsAt).to.eql(d.serverNow + 7 * 86400000);", " pm.expect(Array.isArray(d.activities)).to.eql(true);", " const seen = new Set();", " let refreshAt = d.serverNow + 30000;", " d.activities.forEach(a => {", " pm.expect(['goldMiner','cloudRise'].includes(a.activityId)).to.eql(true);", " const fields = ['activityId','periodId','configVersion','startsAt','endsAt','unlockLevel','phase'];", " if (a.activityId === 'goldMiner') fields.push('purchaseEnabled');", " pm.expect(Object.keys(a).sort()).to.eql(fields.sort());", " pm.expect(typeof a.periodId === 'string' && !!a.periodId && typeof a.configVersion === 'string' && !!a.configVersion).to.eql(true);", " pm.expect(Number.isSafeInteger(a.startsAt) && Number.isSafeInteger(a.endsAt) && a.startsAt < a.endsAt).to.eql(true);", " pm.expect(a.startsAt < d.windowEndsAt && a.endsAt > d.serverNow).to.eql(true);", " pm.expect(Number.isSafeInteger(a.unlockLevel) && a.unlockLevel >= 0).to.eql(true);", " pm.expect(a.phase).to.eql(a.startsAt <= d.serverNow ? 'active' : 'upcoming');", " if (a.activityId === 'goldMiner') pm.expect(typeof a.purchaseEnabled).to.eql('boolean');", " const key = JSON.stringify([a.activityId,a.periodId]);", " pm.expect(seen.has(key)).to.eql(false);seen.add(key);", " for (const boundary of [a.startsAt,a.endsAt]) if (boundary > d.serverNow) refreshAt = Math.min(refreshAt,boundary);", " });", " pm.expect(d.refreshAt).to.eql(refreshAt);", " const sorted = [...d.activities].sort((a,b) => a.activityId < b.activityId ? -1 : a.activityId > b.activityId ? 1", " : b.startsAt-a.startsAt || (a.periodId < b.periodId ? -1 : a.periodId > b.periodId ? 1 : 0));", " pm.expect(d.activities).to.eql(sorted);", " const expected = pm.environment.get('expectedListedPeriodId');", " if (expected) pm.expect(d.activities.some(a=>a.activityId==='goldMiner' && a.periodId===expected)).to.eql(true);", " valid = true;", "});", "// Cache only a valid success. Empty activities is valid; errors must preserve the previous cache.", "if (valid) pm.environment.set('gmActivitySchedule',JSON.stringify(r.data));" ] } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。", "body": { "mode": "raw", "raw": "{}", "options": { "raw": { "language": "json" } } } }, "response": [ { "name": "成功:临时期日程", "originalRequest": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。", "body": { "mode": "raw", "raw": "{}", "options": { "raw": { "language": "json" } } } }, "status": "OK", "code": 200, "_postman_previewlanguage": "json", "header": [ { "key": "Content-Type", "value": "application/json" } ], "cookie": [], "body": "{\n \"code\": 1,\n \"msg\": \"ok\",\n \"data\": {\n \"schemaVersion\": 1,\n \"serverNow\": 1789956000000,\n \"windowEndsAt\": 1790560800000,\n \"refreshAt\": 1789956030000,\n \"activities\": [\n {\n \"activityId\": \"goldMiner\",\n \"periodId\": \"goldMiner:test:20260921_1000\",\n \"configVersion\": \"temporaryV1\",\n \"startsAt\": 1789956000000,\n \"endsAt\": 1789959600000,\n \"unlockLevel\": 41,\n \"phase\": \"active\",\n \"purchaseEnabled\": true\n }\n ]\n }\n}" }, { "name": "成功:无开放或预告活动", "originalRequest": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。", "body": { "mode": "raw", "raw": "{}", "options": { "raw": { "language": "json" } } } }, "status": "OK", "code": 200, "_postman_previewlanguage": "json", "header": [ { "key": "Content-Type", "value": "application/json" } ], "cookie": [], "body": "{\n \"code\": 1,\n \"msg\": \"ok\",\n \"data\": {\n \"schemaVersion\": 1,\n \"serverNow\": 1789956000000,\n \"windowEndsAt\": 1790560800000,\n \"refreshAt\": 1789956030000,\n \"activities\": []\n }\n}" }, { "name": "查询失败:保留旧缓存", "originalRequest": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "url": { "raw": "{{baseUrl}}/activityConfig/list", "host": [ "{{baseUrl}}" ], "path": [ "activityConfig", "list" ] }, "description": "公开只读,无需 uid/token/adminToken,不传 action。GET 无请求体;POST 请求体为 {}。返回 schemaVersion、serverNow、windowEndsAt、refreshAt、activities。活动条目为 activityId/periodId/configVersion/startsAt/endsAt/unlockLevel/phase;黄金矿工另含 purchaseEnabled。phase=active/upcoming。含当前和未来七天的黄金矿工/百人赛日程;不表示玩家已达资格或已付费。成功空列表是正常结果;code=0/data=null 表示查询失败,保留缓存并重试。脚本只保存 gmActivitySchedule,不覆盖玩家 info 的 periodId 或奖励状态。可选 expectedListedPeriodId 断言指定黄金矿工期存在(必须落在查询窗口内)。", "body": { "mode": "raw", "raw": "{}", "options": { "raw": { "language": "json" } } } }, "status": "OK", "code": 200, "_postman_previewlanguage": "json", "header": [ { "key": "Content-Type", "value": "application/json" } ], "cookie": [], "body": "{\n \"code\": 0,\n \"msg\": \"活动信息暂时无法获取,请稍后重试\",\n \"data\": null\n}" } ] } ] } ] }