# Mock Gateway 本项目是 **独立 Git 仓库** 的本地 Mock 网关,用于模拟 eWallet 后端依赖的 Bally/EDIP 接口,避免在 `Backend` 主代码中持续保留绕过逻辑。 ## 已内置的 Mock 接口 - `POST /edip/v1/token/get` - `POST /edip/v1/token/refresh` - `POST /ewl/bal/v1/cardpin/validate` - `POST /ewl/promotion/v1/promotion/all` - `POST /ewl/bal/v1/promotion/all` (兼容旧地址) - `POST /ewl/promotion/v1/promotion/backdate/all` - `POST /ewl/bal/v1/promotion/backdate/all` (兼容旧地址) - `POST /ewl/mkt-rm/v1/team/hierarchy` - `POST /ewl/bal/v1/prize/redeem` - `POST /ewl/bal/v1/point/void` - `POST /ewl/promotion/v1/redeemedpromotion/detail` - `POST /ewl/bal/v1/redeemedpromotion/detail` (兼容旧地址) - `POST /ewl/promotion/v1/playerpromotion/redeem` - `POST /ewl/bal/v1/playerpromotion/redeem` (兼容旧地址) - `POST /ewl/promotion/v1/redeemedpromotion/void` - `POST /ewl/bal/v1/redeemedpromotion/void` (兼容旧地址) - `POST /ewl/edip/v1/member/profile` - `POST /ewl/bal/v1/player/image` - `POST /ewl/bal/v1/player/earn` - `POST /api/v1/prize-redemptions/sync` - `GET /healthz` ## 已内置的 LDAP Mock(新增) - 协议:LDAPv3 - 默认监听:`ldap://0.0.0.0:1389` - 支持能力:`Simple Bind` + `Search` 固定行为(与对接约定一致): 1. manager bind DN:`cn=s-cicd-app,OU=ServiceAccount,dc=macausjm-glp,dc=com` 2. 用户搜索:base `dc=macausjm-glp,dc=com` + filter `(sAMAccountName={username})` 3. 组搜索:base `OU=SecurityGroups,OU=GLPUsers,dc=macausjm-glp,dc=com` + filter `(member={userDn})` 4. 用户条目属性:`sAMAccountName` / `cn` / `mail` / `memberOf` 5. 组条目属性:`cn` / `member` 并且支持用户 DN + 密码直接 bind 成功(基于内置默认用户或 `MOCK_LDAP_USERS_JSON` 配置用户)。 另外默认开启“任意用户名/密码放行”联调模式(`MOCK_LDAP_ALLOW_ANY_USER_MODE=true`): - 前端输入任意账号密码都可通过后端 LDAP 认证 - `(sAMAccountName={username})` 会按 `{username}` 动态返回用户条目 - `(member={userDn})` 会返回组条目(默认 `SG-APP-EWALLET-SUPER-ADMIN`) 推荐做法:通过 `MOCK_LDAP_USERS_JSON` / `MOCK_LDAP_GROUPS_JSON` 在 `Mock_Gateway` 侧配置用户与用户组,不要再在 `Backend` 代码里硬编码开发账号或组别。 ## 本地启动 ```bash cd Mock_Gateway npm install cp .env.example .env npm run start ``` 开发联调建议使用自动重启模式(监听 `src/` 与 `.env` 变更): ```bash npm run dev ``` 默认监听:`http://127.0.0.1:19090` LDAP 默认监听:`ldap://0.0.0.0:1389` ## Promotion 完整链路 mock 默认的 `playerpromotion/redeem` 仍然只是静态回包。 如果你要在本地继续联调 promotion 首打、redeemed history、reprint 这一整条链路,可以开启下面这组桥接配置,让 `Mock_Gateway` 在 promotion redeem 成功后继续: 1. 登录本地 `Backend` 2. 调用真实 `POST /api/v1/prize-redemptions` 3. 把同一笔 `redemptionId` 记录进 mock 的 `redeemedpromotion/detail` 建议同时满足以下前提: - 本地 `Backend` 已启动 - 本地 `Backend` 已接到当前 `Mock_Gateway` 的 LDAP mock - 测试会员在本地 wallet 里对 `MOCK_PROMOTION_REDEMPTION_SOURCE_BUCKET` 有足够余额 推荐配置: ```bash MOCK_PROMOTION_REDEMPTION_BRIDGE_ENABLED=true MOCK_PROMOTION_REDEMPTION_BRIDGE_BASE_URL=http://127.0.0.1:8080 MOCK_PROMOTION_REDEMPTION_BRIDGE_LOGIN_PASSWORD=mock-password MOCK_PROMOTION_REDEMPTION_BRIDGE_TIMEOUT_MS=30000 MOCK_PROMOTION_REDEMPTION_INITIATE_SOURCE=PROMOTION_MODULE MOCK_PROMOTION_REDEMPTION_SOURCE_BUCKET=m-comp ``` 说明: - `MOCK_PROMOTION_REDEMPTION_BRIDGE_LOGIN_USERNAME` 留空时,默认复用本次 redeem 请求里的 `loginId` - `MOCK_PROMOTION_REDEMPTION_SOURCE_BUCKET` 默认用 `m-comp` 作为 source wallet bucket - 桥接成功后,`/ewl/bal/v1/redeemedpromotion/detail` 会优先返回本次实际生成的 mock redeemed record ### Promotion issue-to-wallet 控制 当前 `Mock_Gateway` 已支持两层控制 promotion redeem -> 本地 `prize-redemptions` 的 `issueTargetToWallet`: 1. 显式覆盖,优先级最高 2. 现有推断,作为兜底 显式覆盖支持两种写法: - 请求级: - `data.issueTargetToWallet: [true, false, ...]`,按 `outcomeId` 顺序逐项覆盖;同一个 `outcomeId` 重复出现且每次选择不同 `Open Item / Auto Settle` 时,必须走这个数组 - `data.issueTargetToWalletByOutcomeId: { "11330011": true, "11330012": false }`,按 outcomeId 精确覆盖 - fixture 级: - `outcomeList[].mockIssueTargetToWallet` - `outcomeList[].prize.mockIssueTargetToWallet` 当以上显式覆盖都没有提供时,才回退到现有推断规则: - `gisPrizeIdentifier[index]` 非空 -> `issueTargetToWallet=false` - 否则 `deliveryMode=EXTERNAL_ONLY` -> `false` - 其他情况 -> `true` 这意味着: - 对 `USER_CHOICE` prize,如需稳定复现同一个 outcome 在 openitem / autosettle 之间切换,推荐直接在 mock 请求或 fixture 中提供显式覆盖,不要只依赖 `deliveryMode` - 如果一个请求里允许同一个 `outcomeId` 重复出现且每次选择不同,优先使用 `issueTargetToWallet` 数组;`issueTargetToWalletByOutcomeId` 只适合一个 outcomeId 对应一个固定值的场景 - 如果调用方需要,也可以通过 body 的 `issueTargetToWalletByOutcomeId` / `issueTargetToWallet`,或自定义 header `X-Ewallet-Mock-Issue-Target-To-Wallet-By-Outcome-Id` 给 mock 传入显式覆盖;未提供时仍按当前默认推断执行 - 旧链路不受影响;未提供覆盖时仍保持当前默认行为 ### UI 点测同一个 outcome 的本地预设 如果不改 Backend,但希望在 UI 上对同一个 outcome 反复点测 `Open Item / Auto Settle`,可以先写入 mock runtime override: ```bash curl -s -X POST http://127.0.0.1:19090/mock-api/promotion-redeem/fulfillment-overrides \ -H 'Content-Type: application/json' \ -d '{ "accountNum": "80010991", "promotionId": 11330, "promotionRuleId": 1133001, "outcomeId": 11330011, "issueTargetToWallet": false }' ``` 说明: - `issueTargetToWallet=true` 表示本次同一个 outcome 走 `Open Item` - `issueTargetToWallet=false` 表示本次同一个 outcome 走 `Auto Settle` - 写入后,直接在 UI 点同一个 outcome 即可;mock 会优先使用这份 runtime override - override 默认会持久化到 `./runtime/promotion-redeem-fulfillment-overrides.json`,重启 `Mock_Gateway` 后仍会保留 - 可通过 `GET /mock-api/promotion-redeem/fulfillment-overrides` 查看当前预设 - 可通过 `DELETE /mock-api/promotion-redeem/fulfillment-overrides?accountNum=80010991&promotionId=11330&promotionRuleId=1133001&outcomeId=11330011` 清除单条预设 ## 与 Backend 集成 在 Backend 启动环境中设置: ```bash MOCK_GATEWAY_BASE_URL=http://127.0.0.1:19090 BALLY_BASE_URL=${MOCK_GATEWAY_BASE_URL} EDIP_BASE_URL=${MOCK_GATEWAY_BASE_URL} BALLY_REDEEM_BASE_URL=${MOCK_GATEWAY_BASE_URL} BALLY_PROMOTION_BASE_URL=${MOCK_GATEWAY_BASE_URL} # 建议关闭旧的本地 member 直返 mock,改走网关 BALLY_MEMBER_QUERY_LOCAL_MOCK_ENABLED=false ``` ## Task12085 本地故障注入 当前 `Mock_Gateway` 已支持直接注入 Member Profile 两类关键故障: 1. `503` / 服务不可用 ```bash MOCK_MEMBER_PROFILE_FORCE_STATUS_CODE=503 MOCK_MEMBER_PROFILE_FORCE_ERROR_CODE=MOCK_503 MOCK_MEMBER_PROFILE_FORCE_ERROR_MSG='Mock member profile unavailable' ``` 2. 响应超时 ```bash MOCK_MEMBER_PROFILE_DELAY_MS=15000 ``` 建议: - `MOCK_MEMBER_PROFILE_DELAY_MS` 需要大于 Backend 当前 member profile 调用的阻塞超时,才能稳定触发超时 - 如果你只想打 member profile 故障,保持 `MOCK_EDIP_FORCE_ERROR_CODE` 为空,让 token 继续正常返回 - 测完后把这些变量清空并重启 `Mock_Gateway` ## 快速验证 ```bash curl -s http://127.0.0.1:19090/healthz curl -s -X POST http://127.0.0.1:19090/edip/v1/token/get \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"account":"a","password":"b","key":"k","secret":"s"}}' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/cardpin/validate \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"accountNum":"810006843","pin":"1234"}}' curl -s -X POST http://127.0.0.1:19090/ewl/promotion/v1/promotion/all \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"accountNum":"800048718"}}' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/promotion/all \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"accountNum":"800048718"}}' curl -s -X POST http://127.0.0.1:19090/ewl/mkt-rm/v1/team/hierarchy \ -H 'Content-Type: application/json' \ -d '{"schema":{"terminalCode":"MOCK-PC"},"data":{"userName":"mock.user","staffId":"M000001"}}' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/prize/redeem \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"accountNum":"800048718","prizeCode":"MOCK"}}' curl -s -X POST http://127.0.0.1:19090/api/v1/prize-redemptions/sync \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"correlationId":"mock-correlation-id"}}' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/point/void \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"transId":"mock-redeem-id"}}' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/redeemedpromotion/detail \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"accountNum":"800048718"}}' curl -s -X POST http://127.0.0.1:19090/ewl/promotion/v1/playerpromotion/redeem \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"accountNum":"800048718","promotionId":100002}}' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/playerpromotion/redeem \ -H 'Content-Type: application/json' \ -d '{ "schema": {}, "data": { "accountNum": "80010991", "promotionId": 11330, "promotionRuleId": 1133001, "outcomeId": [11330011, 11330012, 11330013], "issueTargetToWalletByOutcomeId": { "11330011": true, "11330012": false, "11330013": false } } }' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/playerpromotion/redeem \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"accountNum":"800048718","promotionId":100002}}' curl -s -X POST http://127.0.0.1:19090/ewl/promotion/v1/redeemedpromotion/void \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"redeemedId":<上一步 detail 响应里的 redemptionId>}}' curl -s -X POST http://127.0.0.1:19090/ewl/bal/v1/redeemedpromotion/void \ -H 'Content-Type: application/json' \ -d '{"schema":{},"data":{"redeemedId":<上一步 detail 响应里的 redemptionId>}}' # LDAP manager bind(默认 manager 密码可留空,便于 Backend 只改 LDAP_URL) ldapsearch -x -H ldap://127.0.0.1:1389 \ -D "cn=s-cicd-app,OU=ServiceAccount,dc=macausjm-glp,dc=com" \ -w "any-password" \ -b "dc=macausjm-glp,dc=com" \ "(sAMAccountName=mock.user)" sAMAccountName cn mail memberOf # LDAP user bind(DN + 密码) ldapsearch -x -H ldap://127.0.0.1:1389 \ -D "cn=mock.user,OU=GLPUsers,dc=macausjm-glp,dc=com" \ -w "mock-user-password" \ -b "OU=SecurityGroups,OU=GLPUsers,dc=macausjm-glp,dc=com" \ "(member=cn=mock.user,OU=GLPUsers,dc=macausjm-glp,dc=com)" cn member # 建议把默认组名设置成 Backend 角色表已有 code(如 `SG-APP-EWALLET-SUPER-ADMIN`),更容易看到非空 permissions ``` ## 页面数据模板(JSON) - `fixtures/token.get.json`:`/edip/v1/token/get` 成功响应模板 - `fixtures/member.profile.json`:`/ewl/edip/v1/member/profile` 成功响应模板 - `fixtures/promotions.capture.800048718.json`:`/ewl/promotion/v1/promotion/all` 抓包模板 - `fixtures/promotions.capture..json`:按会员号隔离的 promotion fixture;存在同名文件时,mock 会优先对该会员返回该文件内容 - promotion fixture 可额外带 `outcomeList[].mockIssueTargetToWallet` 或 `outcomeList[].prize.mockIssueTargetToWallet`,用于稳定指定某个 outcome 走 openitem / autosettle 当前已补一条专用于完整链路测试的会员: - `80010991` - promotionId:`10991` - promotion:`LP-MOCK-REPRINT-CHAIN` - ruleId:`1099101` - outcomeId:`10991011` - targetPrizeCode:`lp-ee-nsuwr-5pts-20-fb` - 另有 3-outcome 的 `USER_CHOICE` 测试 promotion: - promotionId:`11330` - promotion:`LP-MOCK-USER-CHOICE-3-OUTCOMES` - ruleId:`1133001` - outcomeId:`11330011` -> `it-test-fb-$1` - outcomeId:`11330012` -> `it-test-nnpc-$1` - outcomeId:`11330013` -> `it-test-ebo-$1` - 这 3 个 outcome 都可在 UI 上分别切 `Open Item / Auto Settle`,用于本地验证 promotion redeem -> prize-redemptions -> redeemed detail 整条链路 说明: - 以上模板用于维护响应“静态骨架” - 动态字段仍由网关运行时覆盖(如 `accessToken`、`refreshToken`、`accountNum`、`playerId`、以及 LDAP 用户相关字段) ## 代码结构(已拆分) - `src/server.js`:仅保留启动、路由装配、404 与 LDAP 启动 - `src/ldap/createMockLdapService.js`:LDAP 相关逻辑独立维护 - `src/context/createMockContext.js`:共享配置、fixture 加载、通用工具函数 - `src/routes/**`:每个接口路径一个独立文件(便于新增与变更) ## 常用环境变量 - `MOCK_PROMOTION_CAPTURE_FILE`:promotion 抓包文件路径(默认读取仓库内 `./fixtures/promotions.capture.800048718.json`) - `MOCK_PROMOTION_MAX_ITEMS`:promotion 返回上限(eligible/entitled 各自限制,默认 `30`) - `MOCK_TASK_10766_ENABLED`:开启 Task 10766 复现场景(默认 `false`) - `MOCK_TASK_10766_ACCOUNT_NUM`:Task 10766 固定 mock 会员号(默认 `80010766`) - `MOCK_TASK_10766_PROMOTION_CAPTURE_FILE`:Task 10766 promotion fixture(默认 `./fixtures/promotions.capture.10766.json`) - `MOCK_TASK_10766_MEMBER_EBONUS`:Task 10766 会员资料接口返回的 eBonus 数值(默认 `8888`) - `MOCK_TASK_10766_REDEEM_ERROR_CODE` / `MOCK_TASK_10766_REDEEM_ERROR_MSG` / `MOCK_TASK_10766_REDEEM_RESULT`:Task 10766 的 `playerpromotion/redeem` 定向失败控制 - `MOCK_TASK_10852_ENABLED`:开启 Task 10852 多 outcome promotion 复现场景(默认 `false`) - `MOCK_TASK_10852_ACCOUNT_NUM`:Task 10852 固定 mock 会员号(默认 `80010852`) - `MOCK_TASK_10852_PROMOTION_CAPTURE_FILE`:Task 10852 promotion fixture,默认使用隔离的 entitled 双 outcome 抓包 `./fixtures/promotions.capture.10852.json` - `MOCK_TASK_10852_MEMBER_EBONUS`:Task 10852 会员资料接口返回的 eBonus 数值(默认 `188`) - `MOCK_TOKEN_GET_FIXTURE_FILE`:`/edip/v1/token/get` JSON 模板文件路径(默认 `./fixtures/token.get.json`) - `MOCK_MEMBER_PROFILE_FIXTURE_FILE`:`/ewl/edip/v1/member/profile` JSON 模板文件路径(默认 `./fixtures/member.profile.json`) - `MOCK_MEMBER_PROFILE_FORCE_STATUS_CODE`:`/ewl/edip/v1/member/profile` 强制返回指定 HTTP 状态码,例如 `503` - `MOCK_MEMBER_PROFILE_FORCE_ERROR_CODE`:配合 `MOCK_MEMBER_PROFILE_FORCE_STATUS_CODE` 返回错误码 - `MOCK_MEMBER_PROFILE_FORCE_ERROR_MSG`:配合 `MOCK_MEMBER_PROFILE_FORCE_STATUS_CODE` 返回错误信息 - `MOCK_MEMBER_PROFILE_DELAY_MS`:`/ewl/edip/v1/member/profile` 延迟响应毫秒数,可用于本地模拟超时 - `MOCK_TEAM_HIERARCHY_STATUS`:`team/hierarchy` 的 `status`(默认 `true`) - `MOCK_TEAM_HIERARCHY_ERROR_CODE` / `MOCK_TEAM_HIERARCHY_MESSAGE`:`team/hierarchy` 错误模拟 - `MOCK_TEAM_HIERARCHY_ROLE` / `MOCK_TEAM_HIERARCHY_DEPARTMENT` / `MOCK_TEAM_HIERARCHY_TAGS`:`team/hierarchy` 返回字段 - `MOCK_PRIZE_REDEEM_ERROR_CODE` / `MOCK_PRIZE_REDEEM_ERROR_MSG` / `MOCK_PRIZE_REDEEM_VALUE`:`prize/redeem` 返回控制(`MOCK_PRIZE_REDEEM_VALUE` 留空时自动生成交易号) - `MOCK_POINT_VOID_ERROR_CODE` / `MOCK_POINT_VOID_ERROR_MSG` / `MOCK_POINT_VOID_RESULT`:`point/void` 返回控制 - `MOCK_REDEEMED_PROMOTION_DETAIL_ERROR_CODE` / `MOCK_REDEEMED_PROMOTION_DETAIL_ERROR_MSG`:`redeemedpromotion/detail` 返回控制 - `MOCK_REDEEMED_PROMOTION_DETAIL_INCLUDE_DEFAULT_RECORD`:是否返回默认一条 `outcomeList`(默认 `true`) - `MOCK_REDEEMED_PROMOTION_DETAIL_VOIDED`:默认记录是否标记已作废(默认 `false`) - `MOCK_PLAYERPROMOTION_REDEEM_ERROR_CODE` / `MOCK_PLAYERPROMOTION_REDEEM_ERROR_MSG` / `MOCK_PLAYERPROMOTION_REDEEM_RESULT`:`playerpromotion/redeem` 返回控制 - `MOCK_PROMOTION_REDEMPTION_BRIDGE_ENABLED`:开启 promotion redeem -> 本地 `prize-redemptions` 桥接(默认 `false`) - `MOCK_PROMOTION_REDEMPTION_BRIDGE_BASE_URL`:本地 Backend 基地址(默认 `http://127.0.0.1:8080`) - `MOCK_PROMOTION_REDEMPTION_BRIDGE_LOGIN_USERNAME` / `MOCK_PROMOTION_REDEMPTION_BRIDGE_LOGIN_PASSWORD`:桥接调用 Backend 登录用账号;用户名留空时默认复用本次 redeem 的 `loginId` - `MOCK_PROMOTION_REDEMPTION_BRIDGE_TIMEOUT_MS`:桥接调用超时毫秒数(默认 `30000`);本地完整链路联调建议至少保留 `30000` - `MOCK_PROMOTION_REDEMPTION_INITIATE_SOURCE`:桥接到 `prize-redemptions` 时写入的 `initiateSource`(默认 `PROMOTION_MODULE`) - `MOCK_PROMOTION_REDEMPTION_SOURCE_BUCKET`:桥接到 `prize-redemptions` 时使用的 source wallet bucket(默认 `m-comp`) - `MOCK_PROMOTION_REDEMPTION_ID_START`:mock redeemed history 的起始 `redemptionId`;留空时默认用进程启动时的 Unix 秒级时间戳作为 seed,且会限制在 Backend `int` 可解析范围内,避免 Mock_Gateway 重启后重复复用旧 `redemptionId` - `MOCK_REDEEMEDPROMOTION_VOID_ERROR_CODE` / `MOCK_REDEEMEDPROMOTION_VOID_ERROR_MSG` / `MOCK_REDEEMEDPROMOTION_VOID_RESULT`:`redeemedpromotion/void` 返回控制 - `MOCK_PRIZE_REDEMPTIONS_SYNC_SUCCESS` / `MOCK_PRIZE_REDEMPTIONS_SYNC_MESSAGE`:`/api/v1/prize-redemptions/sync` 返回控制 - `MOCK_LDAP_ENABLED`:是否启用 LDAP mock(默认 `true`) - `MOCK_LDAP_HOST`:LDAP 监听地址(默认 `0.0.0.0`) - `MOCK_LDAP_PORT`:LDAP 监听端口(默认 `1389`) - `MOCK_LDAP_BASE_DN`:LDAP 基础 DN(默认 `dc=macausjm-glp,dc=com`) - `MOCK_LDAP_MANAGER_DN`:manager bind DN(默认 `cn=s-cicd-app,OU=ServiceAccount,dc=macausjm-glp,dc=com`) - `MOCK_LDAP_GROUP_BASE_DN`:组搜索 base(默认 `OU=SecurityGroups,OU=GLPUsers,dc=macausjm-glp,dc=com`) - `MOCK_LDAP_ALLOW_ANY_USER_MODE`:任意用户名/密码放行模式(默认 `true`) - `MOCK_LDAP_ALLOW_ANY_USER_OU`:动态用户 DN 的 OU 段(默认 `OU=GLPUsers`) - `MOCK_LDAP_ALLOW_ANY_USER_GROUP_CN`:动态用户默认组名(默认 `SG-APP-EWALLET-SUPER-ADMIN`) - `MOCK_LDAP_ALLOW_ANY_USER_MAIL_DOMAIN`:动态用户邮箱域名(默认 `macausjm-glp.com`) - `MOCK_LDAP_MANAGER_PASSWORD`:manager bind 密码;留空时允许任意密码(便于仅改 `LDAP_URL` 联调) - `MOCK_LDAP_DEFAULT_USER_*`:默认单用户 mock 配置 - `MOCK_LDAP_USERS_JSON`:JSON 数组,多用户覆盖配置 ## Task 10766 复现场景 打开 `.env`,加入或覆盖以下配置: ```bash MOCK_TASK_10766_ENABLED=true MOCK_TASK_10766_ACCOUNT_NUM=80010766 MOCK_TASK_10766_PROMOTION_CAPTURE_FILE=./fixtures/promotions.capture.10766.json MOCK_TASK_10766_MEMBER_EBONUS=8888 MOCK_TASK_10766_REDEEM_ERROR_CODE=EWALLET_HTTP_ERROR MOCK_TASK_10766_REDEEM_ERROR_MSG=ewallet connection failed: 404 NOT_FOUND, {"success":false,"message":"Prize definition not found"} MOCK_TASK_10766_REDEEM_RESULT=false ``` 复现方式: 1. 启动 `Mock_Gateway` 2. 前后端联调继续指向本地 mock 网关 3. 在 `Wallet` 页面搜索会员 `80010766` 4. 切到 `Promotion` 5. 搜索 `cny` 6. 选择 `CNY Gift Set Earn & Get-2026 / GS-8pts-LCH Gift Set Abalone + Scallop` 7. 点击 `Redeem` 并完成确认 预期: - 会员资料接口返回 `eBonus=8888` - promotion 列表固定返回 Task 10766 专用 fixture - `POST /ewl/promotion/v1/playerpromotion/redeem` 固定返回 `Prize definition not found` 场景 - 可稳定复现 10766 对应的 promotion redeem 失败明细展示问题 ## Task 10852 复现场景 打开 `.env`,加入或覆盖以下配置: ```bash MOCK_TASK_10852_ENABLED=true MOCK_TASK_10852_ACCOUNT_NUM=80010852 MOCK_TASK_10852_PROMOTION_CAPTURE_FILE=./fixtures/promotions.capture.10852.json MOCK_TASK_10852_MEMBER_EBONUS=188 ``` 复现方式: 1. 启动 `Mock_Gateway` 2. 前后端联调继续指向本地 mock 网关 3. 在 `Wallet` 页面搜索会员 `80010852` 4. 切到 `Promotion` 5. 搜索 `cny` 6. 选择 `CNY Gift Set Earn & Get-2026` 7. 勾选两个 outcome 后继续兑换流程 预期: - 会员资料接口返回 `eBonus=188` - `POST /ewl/promotion/v1/promotion/all` 固定返回可点击的 entitled 双 outcome promotion - 前端会稳定走到 `targetPrizes` 多项场景,便于验证 10852 的最小修复 ## 按会员号自动注入 Promotion Fixture 除了现有的 Task 10766 / 10852 专用开关外,`Mock_Gateway` 现在还支持按会员号自动加载 fixture: - 文件命名:`fixtures/promotions.capture..json` - 命中规则:当 `/ewl/promotion/v1/promotion/all` 的 `accountNum` 与文件名中的会员号一致时,自动返回该 fixture - backdate 文件命名:`fixtures/promotions.backdate..json` - backdate 命中规则:当 `/ewl/promotion/v1/promotion/backdate/all` 的 `accountNum` 与文件名中的会员号一致时,自动返回该 fixture - 优先级:Task 10766 / 10852 专用场景优先,其次才是按会员号自动加载的 fixture 示例: - `fixtures/promotions.capture.800038008.json` - `fixtures/promotions.capture.80010852.json` - `fixtures/promotions.backdate.800038008.json` 适用场景: - 需要保留某个会员的真实采样 promotion 规则 - 想快速补充新的 `minOutcome / maxOutcome / outcomeList / prize` 组合 - 不希望影响默认共享 mock 数据 ## 使用本地测试开发网关的时候可以使用以下会员号 按 `Mock_Gateway` 当前代码中的“定向逻辑 / 定向 fixture / 固定路由”统计,特别会员号共有 `5` 个(不含默认模板/示例号): 1. `800038008` - backdate 固定会员号。 `/ewl/promotion/v1/promotion/entitledBackDate` 和 `/ewl/promotion/v1/promotion/entitledBackDate/all` 会固定返回这个会员号的 backdate promotion;仓库里也有它的普通 promotion fixture 和 backdate fixture。 2. `80010766` - Task 10766 专用复现号。 开启 `MOCK_TASK_10766_ENABLED=true` 后,会员资料固定返回 `eBonus=8888`,promotion 列表走专用 fixture,`playerpromotion/redeem` 固定复现 `Prize definition not found` 失败场景。 3. `80010852` - Task 10852 专用复现号。 开启 `MOCK_TASK_10852_ENABLED=true` 后,会员资料固定返回 `eBonus=188`,promotion 列表走专用 fixture,并强制提供可点击的 entitled 双 outcome 场景,用于验证多 outcome redeem。 4. `80010991` - 完整链路 / 同 outcome 切换场景号。 命中专用 `promotions.capture.80010991.json`,内含单 outcome reprint chain 和 3-outcome `USER_CHOICE` promotion;也用于 runtime fulfillment override,方便在 UI 上反复验证同一个 outcome 的 `Open Item / Auto Settle` 切换。 5. `100000059` - redeemed promotion detail 专用号。 命中 `award.promotions.100000059.json`,用于 `redeemedpromotion/detail` 返回账户级已兑 promotion 明细。 以下号码也出现在 `Mock_Gateway` 中,但更适合视为默认模板 / 示例数据,不计入上面的“特别会员号”: 1. `800048718` - 默认 promotion 抓包模板来源。 没有专属定向逻辑,未命中其他专用场景时,普通账号会回落到这份默认 promotion fixture。 2. `800049052` - 默认 redeemed promotions 示例号。 只在 `redeemedpromotion/detail` 没传账号且没有账户级记录时,作为默认示例数据中的 `customerId` 出现。 800048718 老会员----换新账号----910700001