haojiwen 3 weeks ago
commit
d0dcefafaf
100 changed files with 71633 additions and 0 deletions
  1. 14 0
      .gitignore
  2. 111 0
      AGENTS.md
  3. 301 0
      API接口清单.md
  4. 78 0
      Dockerfile
  5. 301 0
      README.md
  6. 1 0
      Swagger3.0.json
  7. 14 0
      code/AI-ONBOARDING.md
  8. 412 0
      code/ARCHITECTURE-RULES.md
  9. 157 0
      code/BUG-LESSONS.md
  10. 20 0
      code/CLAUDE.md
  11. 344 0
      code/DANGER-ZONES.md
  12. 57 0
      code/PERFORMANCE-CHECKLIST.md
  13. 44 0
      code/README.md
  14. 8 0
      code/说明.txt
  15. 148 0
      code_version_md/v4.25-玩家活跃.md
  16. 236 0
      code_version_md/v4.26/v4.26-需求内容.md
  17. 6 0
      crontabs/crontabfile.txt
  18. 315 0
      debug_doc/项目错误经验记录.md
  19. 17 0
      docker-compose.yml
  20. 165 0
      docs/login-flow-mermaid.md
  21. 485 0
      docs/login-flow-plantuml.puml
  22. 27 0
      key/sdkapi.qmgames.cn.key
  23. 59 0
      key/sdkapi.qmgames.cn.pem
  24. 27 0
      key/server.key
  25. 61 0
      key/server.pem
  26. 27 0
      key/t.qmgames.cn.key
  27. 61 0
      key/t.qmgames.cn.pem
  28. 1 0
      logs/.gitkeep
  29. 342 0
      md/项目功能梳理文档.md
  30. 66 0
      md_go_migration_api/00-api-go-migration-index.md
  31. 325 0
      md_go_migration_api/01-api-architecture-and-business-domains.md
  32. 210 0
      md_go_migration_api/02-controller-endpoint-inventory.md
  33. 534 0
      md_go_migration_api/03-core-business-flows.md
  34. 324 0
      md_go_migration_api/04-data-dependencies-and-risks.md
  35. 259 0
      md_go_migration_api/05-go-refactor-blueprint.md
  36. 332 0
      md_go_migration_api/11-observability-stack-guide.md
  37. 345 0
      md_go_migration_api/12-development-roadmap-todo.md
  38. 4 0
      nginx/.gitignore
  39. 49 0
      nginx/default.conf
  40. 40 0
      nginx/https.conf
  41. 101 0
      nginx/nginx.conf
  42. 37 0
      nginx/sdkapi.qmgames.cn.conf
  43. 19 0
      php-fpm/php-ini-overrides.ini
  44. 7 0
      php-fpm/sources.list
  45. 6 0
      php-fpm/up.sh
  46. 364 0
      review_doc/01-基础设施审查.md
  47. 454 0
      review_doc/02-安全审查.md
  48. 547 0
      review_doc/03-架构设计审查.md
  49. 429 0
      review_doc/04-代码质量审查.md
  50. 549 0
      review_doc/05-性能优化审查.md
  51. 299 0
      review_doc/06-依赖管理审查.md
  52. 309 0
      review_doc/07-测试覆盖审查.md
  53. 303 0
      review_doc/08-文档完整性审查.md
  54. 337 0
      review_doc/09-审查总结报告.md
  55. 130 0
      review_doc/审查计划.md
  56. 3 0
      script/complex_summary.sh
  57. 6 0
      script/daily.sh
  58. 3 0
      script/mlbb.sh
  59. 3 0
      script/pay_timeout.sh
  60. 3 0
      script/poly_complex_notice.sh
  61. 3 0
      script/poly_complex_pay.sh
  62. 153 0
      script/statistics_handle.sh
  63. 2 0
      www/.gitignore
  64. 5 0
      www/new_sdk/.codegraph/.gitignore
  65. 143 0
      www/new_sdk/.env
  66. 5 0
      www/new_sdk/.gitignore
  67. 940 0
      www/new_sdk/.understand-anything/.trash-20260630091809/assembled-graph.json
  68. 23611 0
      www/new_sdk/.understand-anything/.trash-20260630091809/batches.json
  69. 31 0
      www/new_sdk/.understand-anything/.trash-20260630091809/fingerprint-input.json
  70. 44 0
      www/new_sdk/.understand-anything/.trash-20260630091809/review.json
  71. 63 0
      www/new_sdk/.understand-anything/.trash-20260630091809/ua-inline-validate.cjs
  72. 980 0
      www/new_sdk/.understand-anything/.trash-20260630093035/assembled-graph.json
  73. 45 0
      www/new_sdk/.understand-anything/.trash-20260630093035/review.json
  74. 10017 0
      www/new_sdk/.understand-anything/.trash-20260630093035/tmp/scan-files.json
  75. 242 0
      www/new_sdk/.understand-anything/.trash-20260630093035/tmp/ua-graph-validate.js
  76. 44 0
      www/new_sdk/.understand-anything/.trash-20260630093035/tmp/ua-review-results.json
  77. 45 0
      www/new_sdk/.understand-anything/.understandignore
  78. 3 0
      www/new_sdk/.understand-anything/config.json
  79. 3712 0
      www/new_sdk/.understand-anything/fingerprints.json
  80. 15106 0
      www/new_sdk/.understand-anything/intermediate/scan-result.json
  81. 980 0
      www/new_sdk/.understand-anything/knowledge-graph.json
  82. 6 0
      www/new_sdk/.understand-anything/meta.json
  83. 113 0
      www/new_sdk/README.md
  84. 1 0
      www/new_sdk/application/.htaccess
  85. 302 0
      www/new_sdk/application/admin/common.php
  86. 148 0
      www/new_sdk/application/admin/controller/Aboutus.php
  87. 527 0
      www/new_sdk/application/admin/controller/Admin.php
  88. 136 0
      www/new_sdk/application/admin/controller/Appkey.php
  89. 462 0
      www/new_sdk/application/admin/controller/Article.php
  90. 80 0
      www/new_sdk/application/admin/controller/Autopack.php
  91. 69 0
      www/new_sdk/application/admin/controller/Autoreply.php
  92. 163 0
      www/new_sdk/application/admin/controller/Bind.php
  93. 129 0
      www/new_sdk/application/admin/controller/Business.php
  94. 1551 0
      www/new_sdk/application/admin/controller/Channel.php
  95. 267 0
      www/new_sdk/application/admin/controller/ChannelAccount.php
  96. 142 0
      www/new_sdk/application/admin/controller/ChannelAccountDet.php
  97. 267 0
      www/new_sdk/application/admin/controller/ChannelApply.php
  98. 378 0
      www/new_sdk/application/admin/controller/ChannelCa.php
  99. 482 0
      www/new_sdk/application/admin/controller/ChannelCps.php
  100. 0 0
      www/new_sdk/application/admin/controller/ChannelDataManager.php

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+*.log
+!.gitkeep
+datadir/*
+.idea
+data
+nginx/default_*.confs
+/www/new_sdk/.vscode
+.vscode
+.agent
+.claude
+.cursor
+.codex
+.omo
+/.codegraph

+ 111 - 0
AGENTS.md

@@ -0,0 +1,111 @@
+# AGENTS.md — new_sdk
+
+## 项目概述
+
+游戏SDK平台 — 基于 ThinkPHP 5.0 的多模块应用,涵盖游戏渠道SDK对接、支付回调、公会管理和官网。PHP 7.0–7.2。
+
+**仓库根目录** (`D:\web\composer\new_sdk\new_sdk`) 只有 `www/`,所有应用代码在 `www/new_sdk/` 下。以下路径均相对于 `www/new_sdk/`。
+
+## 架构
+
+多模块 ThinkPHP 应用,通过域名路由将子域名映射到模块(`application/route.php`):
+
+| 子域名前缀 | 模块 | 用途 |
+|---|---|---|
+| `admin` | `admin/` | 后台管理 |
+| `sdkapi` / `t` | `api/` | 支付回调 + SDK API |
+| `cpsapi` / `sdkcpsapi` | `guildapi/` | 公会API |
+| `mcpsapi` | `mcpsapi/` | MCPS API |
+| `jhgame` | `complex/` | 渠道SDK对接 |
+| `www` | `home/` | 官网 (PC) |
+| `m` | `mobile/` | 手机端 |
+
+核心目录:
+
+- `application/common/` — 共享代码:`model/`(151个模型)、`logic/`、`library/`、`controller/Base.php`
+- `application/service/` — 顶层服务类(PayService、GamePayService 等)
+- `application/crontab/` — 定时任务,注册在 `application/command.php`,通过 `php think <command>` 调用
+- `application/extra/` — 扩展配置:`queue.php`(Redis队列)、`kafka.php`、`operatelog.php`
+- `extend/` — 第三方SDK:alipay、alipay_wap、DouYinGameOpen、LdzfPay、XiTaiYouPay、Obs(华为)、tree
+- `public/` — Web根目录,入口文件 `index.php`
+- `think` — CLI入口(类似 artisan)
+
+## 入口文件
+
+- **Web**: `public/index.php` — 加载 `constants.php` → `constants/<APP_STATUS>.php` → `thinkphp/start.php`
+- **CLI**: `think` — 加载 `public/constants.php` → `public/constants/<APP_STATUS>.php` → `thinkphp/console.php`
+
+## 环境配置
+
+环境变量通过 `.env_dev` / `.env_pro` 文件加载(ThinkPHP 的 `Env::get()`)。`APP_STATUS` 常量(`dev`、`stable` 或默认值)决定使用哪个环境文件和路由配置。
+
+**仓库中缺失**:`public/constants.php` 和 `public/constants/*.php` — 定义 `APP_STATUS` 等平台常量,属于部署配置,已 gitignore。
+
+## 开发命令
+
+```bash
+# 安装依赖
+composer install
+# 如果系统没有 composer,可以用仓库中已提交的 composer.phar
+php composer.phar install
+
+# 运行定时任务(注册在 application/command.php)
+php think <CommandName>
+
+# 示例:
+php think PolyChannelFlowStats
+php think ChannelSettleMonth
+php think AutoPackage
+
+# PHPUnit(tests/ 目录目前不存在)
+vendor/bin/phpunit
+```
+
+没有配置 lint、typecheck 或 codegen 命令,没有 CI 工作流。
+
+## 关键约定
+
+- **PSR-4 自动加载**:`app\` 命名空间映射到 `application/`(composer.json)
+- **模型命名**:`application/common/model/` 下使用 ThinkPHP 约定(如 `app\common\model\Members`)
+- **控制器模式**:每个模块有独立的 `controller/`、`model/`、`validate/`、`view/` 目录
+- **配置加载**:模块级配置通过各模块目录下的 `config.php`;扩展配置在 `application/extra/`
+- **队列**:Redis 驱动(`application/extra/queue.php`),使用 db select 3
+- **Kafka**:用于事件流(`application/extra/kafka.php`、`application/command/KafkaConsumer.php`)
+- **Session**:API 模块使用 Redis 存储
+- **CORS**:`public/index.php` 中完全开放(`Access-Control-Allow-Origin: *`)
+- **响应码**:100=登录失效, 110=参数有误, 120=处理提示, 130=执行异常, 200=正常
+
+## 外部服务
+
+- MySQL 7, Redis 5.2.1
+- 阿里云OSS / 华为云OBS(文件存储)
+- 支付宝(SDK 在 `extend/alipay/`、`extend/alipay_wap/`)
+- 微信支付(`application/common/library/WeixinPay.php`)
+- 钉钉机器人(告警)
+- 阿里云短信
+- 抖音游戏开放平台(`extend/DouYinGameOpen/`)
+- Kafka(事件流)
+- GatewayWorker/WebSocket(`application/common/logic/Websocket.php`)
+
+## 关联仓库
+
+本仓库是五个仓库之一,其余不在本目录内:
+- `new_sdk_cps-admin` — CPS后台
+- `new_sdk_cps-py` — CPS打包(Python,被 `application/common/logic/SubPackage.php` 引用)
+- `new_sdk_ws` — 通知服务(WebSocket)
+- `new_sdk_go` — Excel导出 & 游戏礼包发放(Go,被 `application/common/library/MakeReportGo.php` 引用)
+
+## 注意事项
+
+- `public/constants.php` 和 `public/constants/*.php` 已 gitignore — 没有它们应用无法启动,需要为你的环境创建。
+- `application/common.php` 约2000行全局辅助函数(auth_code、curl_post 等)— 所有模块都在用。
+- `.env_dev` 和 `.env_pro` 包含真实凭据。`.gitignore` 列的是 `.env`,但这两个文件名不在其中。
+- `composer.phar` 已提交到仓库 — 系统没有 composer 时可以直接用。
+- `phpunit.xml` 引用的 `tests/` 目录在仓库中不存在。
+- `application/service/` 中有 `_bak` 文件(GamePayService_bak.php、MemberCoinService_bak.php)。
+
+
+## 禁止操作
+
+- **禁止启动定时任务** — 不要创建、启动或修改任何 cron / scheduled task(包括 crontab、systemd timer、Windows 计划任务、PHP 定时脚本等)。
+- **禁止执行 `php think` 命令** — 不要运行仓库内的任何 `php think <command>`。涉及定时任务、队列消费、数据迁移等操作一律禁止。需要调试或了解某命令行为时,只能阅读源码,不可实际执行。

+ 301 - 0
API接口清单.md

@@ -0,0 +1,301 @@
+# 祈盟SDK 接口清单
+
+> 来源: Apipost OpenAPI 文档 (v1.0.0)
+> 接口总数: 87 个路径
+
+---
+
+## 一、接口总览(按业务模块分类)
+
+### 1. 公共模块
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| GET | `/v2.Captcha/getCaptcha` | 图形验证码 | 多处复用(注册、绑定、忘记密码、新设备判断) |
+| POST | `/v2.CheckUpdate/version.html` | 安卓热更新(进入游戏时) | 客户端小文件更新 |
+| POST | `/v2.game_notify/index_v2` | 游戏公告&强更&分包更新-v2 | 准备弃用 |
+| POST | `/v2.game_notify/index_v3` | 游戏公告&强更&分包更新-v3 | 替代v2 |
+| POST | `/v2.privacy/privacy` | 协议内容获取 | |
+| GET | `http://qm_url.7dgame.cn/` | 获取请求域名地址(正式) | |
+| GET | `http://qm_url.7dgame.cn/dev` | 获取请求域名地址(测试) | |
+| POST | `/game/getChannelOrGame` | 获取渠道和游戏数据(H5) | |
+
+### 2. 注册&登录模块
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v1.login/index.html` | 账户登录 | |
+| POST | `v1.login/checkToken.html` | 自动登录(Token校验) | |
+| POST | `/v1.login/logout.html` | 退出登录 | |
+| POST | `/v1.login/third` | 第三方用户登录 | |
+| POST | `/v1.register/index.html` | 手机号注册 | |
+| POST | `/v1.register/createUsername.html` | 注册生成用户名 | |
+| POST | `/v1.send_sms/index.html` | 发送短信验证码 | 手机注册/忘记密码/绑定手机号共用 |
+| POST | `/v1.is_identity/isVerified.html` | 是否已实名认证 | 登录+充值前调用 |
+| POST | `/v1.Is_Identity/isLoginPopup.html` | 是否实名认证弹窗 | 登录后调用 |
+| POST | `/v2.user/personalIdentity` | 实名认证 | |
+| POST | `/v2.user/userinfo` | 用户详情 | |
+| POST | `/v2.config/getconfig` | 第三方登录参数配置 | |
+
+### 3. 绑定手机号
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.user/getBindPhoneCode` | 获取验证码(绑定/换绑) | |
+| POST | `/v2.user/bindPhone` | 手机号绑定 | |
+
+### 4. 忘记密码
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.forget/resetPwdSendCode` | 发送验证码 | 流程1 |
+| POST | `/v2.forget/resetPwdCheckCode` | 效验验证码 | 流程2 |
+| POST | `/v2.forget/forgetPwdToReset` | 重置密码 | 流程3 |
+
+### 5. 新设备判断
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.user/newImeilValid` | 验证验证码 | |
+| POST | `/v2.user/getNewImeiCode` | 验证码获取 | |
+
+### 6. 我的(用户中心)
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.user/updatePwd` | 密码修改 | |
+| POST | `/v2.user/problem` | 常见问题 | |
+| POST | `/v2.user/getChannelCall` | 专属客服 | |
+| POST | `/v2.AiAssistant/getConfig` | AI助手配置 | |
+
+### 7. 换绑手机号
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.user/unbindingPhoneCode` | 旧手机号-获取验证码 | |
+| POST | `/v2.user/checkUnbindingPhoneCode` | 旧手机号-效验验证码 | |
+| POST | `/v2.user/unbindingPhone` | 换绑-绑定新手机号 | |
+
+### 8. 订单模块
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.order/orderList` | 订单列表 | |
+| POST | `/v2.order/orderInfo` | 订单详情 | |
+| POST | `/v2.order/cancelOrder` | 取消订单 | 充值/支付共用 |
+
+### 9. 充值(平台币)
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.coin/payType` | 支付方式列表 | |
+| POST | `/v2.coin/pay` | 发起充值 | |
+| POST | `/v2.order/getMemberCoinList` | 交易明细列表 | |
+
+### 10. 代金券
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.coupon/getcoupon` | 可领取代金券列表 | |
+| POST | `/v2.coupon/getMyCoupon` | 我的代金券列表 | |
+| POST | `/v2.coupon/exchangeCoupon` | 兑换代金券码 | |
+| POST | `/v2.coupon/receiveCoupon` | 领取代金券 | |
+
+### 11. 礼包
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.Gift/giftList` | 礼包列表 | |
+| POST | `/v2.Gift/getMyGiftList` | 我的礼包列表 | |
+| POST | `/v2.Gift/giftPack` | 领取礼包 | |
+
+### 12. SDK 支付(游戏内购买道具)
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.order/getPayInfo` | 获取支付信息 | |
+| POST | `/v1.pay/index.html` | 发起支付 | |
+| POST | `/v1.pay/querypay.html` | 订单信息查询 | |
+
+### 13. 支付回调(SDK)
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| GET | `/v1.pay_notify/xty_pay.html` | 喜钛游-支付回调 | |
+| POST | `/v1.pay_notify/qzl_pay.html` | 趣智连-支付回调 | |
+| GET | `/index/notidy` | 支付回调示例 | |
+| GET | `/v1.pay_notify/dianhun` | 电魂订单通知 | |
+
+### 14. 角色管理
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v1.role/index.html` | 角色数据上传 | |
+
+### 15. 聚合渠道
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v1.complex_login/getSpecialParam` | 1. 渠道效验(获取用户ID) | |
+| POST | `/v1.complex_login/index.html` | 2. 登录效验 | |
+| POST | `/v1.complex_pay/index.html` | 3. 发起下单 | |
+| POST | `/v2.complex_authentication/check` | 4. 渠道实名认证 | 非必请求,客户端+对接方使用 |
+| POST | `/v2.complex/handle.html` | 5. 角色上传接口 | |
+| POST | `/v2.complex_authentication/query` | (弃用)实名认证结果查询 | |
+
+### 16. 聚合游戏回调-服务端(渠道支付回调)
+| 方法 | 路径 | 渠道 |
+|------|------|------|
+| POST | `/v1.complex_pay_notify/notify/channelqf/congyu/gameidqf/10` | 从语 |
+| POST | `/v1.complex_pay_notify/notify/channelqf/test/gameidqf/10` | 测试渠道 |
+| POST | `/v1.complex_pay_notify/notify/shenqi/gameidqf/10` | 神起 |
+| POST | `/v1.complex_pay_notify/notify/yueyou/gameidqf/10` | 悦游 |
+| POST | `/v1.complex_pay_notify/notify/sanjiu/gameidqf/10` | 三九互娱 |
+| POST | `/v1.complex_pay_notify/notify/youxiyou/gameidqf/10` | 游戏友 |
+| POST | `/v1.complex_pay_notify/notify/channelqf/weilian/gameidqf/20` | 唯恋 |
+| POST | `/v1.complex_pay_notify/notify/channelqf/jlyx/gameidqf/10` | 神龙游戏 |
+| GET | `http://sdkapi.newsdk.com/v1.complex_pay_notify/notify/channelqf/tlyx/gameidqf/10` | 贪乐 |
+
+### 17. 第三方平台
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v2.Third/getDyCode` | 抖音游戏-获取access_token | |
+| POST | `/v2.Third/getDyAuthInfoOpen` | 抖音游戏-查询绑定意愿 | |
+| POST | `/v2.Third/getJumpAuthCallback` | 抖音-游戏内授权后回调 | |
+
+### 18. 抖音游戏平台
+| 方法 | 路径 | 功能 |
+|------|------|------|
+| POST | `/v2.DyGameOpen/getAuthLogin` | 抖音-授权登录 |
+| POST | `/v2.DyGameOpen/getMobileCode` | 抖音-手机号发送验证码 |
+| POST | `/v2.DyGameOpen/getMobileCheck` | 抖音-手机号验证码校验 |
+| POST | `/v2.DyGameOpen/getRoleList` | 抖音-获取角色列表 |
+| POST | `/v2.DyGameOpen/HandleEventNotice` | 抖音-绑定事件通知 |
+
+### 19. 直播平台(数据查询)
+| 方法 | 路径 | 功能 |
+|------|------|------|
+| POST | `/gh/get_role` | 获取角色数据 |
+| POST | `/gh/get_pay_list` | 获取订单列表数据 |
+| POST | `/gh/get_sub_user_list` | 获取注册归属列表数据 |
+| POST | `/gh/get_player_active` | 获取玩家活跃数据列表 |
+| POST | `/gh/channel_data_summary` | 获取渠道数据汇总(弃用) |
+| POST | `/gh/channel_data_summary_v2` | 获取渠道数据汇总V2 |
+| POST | `/gh/get_sub` | 获取游戏账户列表 |
+
+### 20. 服务端效验
+| 方法 | 路径 | 功能 | 备注 |
+|------|------|------|------|
+| POST | `/v1.helper/checkToken.html` | CP登录二次效验 | 服务端+聚合渠道 |
+
+### 21. AI助手
+| 方法 | 路径 | 功能 |
+|------|------|------|
+| POST | `/v2.AiAssistant/getConfig` | AI助手配置 |
+
+### 22. 其他
+| 方法 | 路径 | 功能 |
+|------|------|------|
+| POST | `/v2.user/getYqlChannel` | 获取176的渠道推广信息 |
+
+---
+
+## 二、接口流程说明
+
+### 登录流程
+```
+1. 图形验证码 → /v2.Captcha/getCaptcha
+2. 登录 → /v1.login/index.html
+3. 实名认证检查 → /v1.is_identity/isVerified.html
+4. 实名认证弹窗 → /v1.Is_Identity/isLoginPopup.html
+5. 自动登录(Token刷新) → v1.login/checkToken.html
+```
+
+### 注册流程
+```
+1. 图形验证码 → /v2.Captcha/getCaptcha
+2. 发送短信验证码 → /v1.send_sms/index.html
+3. 手机号注册 → /v1.register/index.html
+4. 生成用户名 → /v1.register/createUsername.html
+```
+
+### 忘记密码流程
+```
+1. 图形验证码 → /v2.Captcha/getCaptcha
+2. 发送验证码 → /v2.forget/resetPwdSendCode
+3. 效验验证码 → /v2.forget/resetPwdCheckCode
+4. 重置密码 → /v2.forget/forgetPwdToReset
+```
+
+### 游戏支付流程
+```
+1. 实名认证检查 → /v1.is_identity/isVerified.html
+2. 获取支付信息 → /v2.order/getPayInfo
+3. 发起支付 → /v1.pay/index.html
+4. 查询订单 → /v1.pay/querypay.html
+5. 支付回调(渠道推送)
+```
+
+### 聚合渠道对接流程
+```
+1. 渠道效验 → /v1.complex_login/getSpecialParam
+2. 登录效验 → /v1.complex_login/index.html
+3. 发起下单 → /v1.complex_pay/index.html
+4. 渠道实名认证(可选)→ /v2.complex_authentication/check
+5. 角色上传 → /v2.complex/handle.html
+6. 支付回调(渠道推送)
+```
+
+### 绑定手机号流程
+```
+1. 图形验证码 → /v2.Captcha/getCaptcha
+2. 获取验证码 → /v2.user/getBindPhoneCode
+3. 绑定手机号 → /v2.user/bindPhone
+```
+
+### 换绑手机号流程
+```
+1. 旧手机号获取验证码 → /v2.user/unbindingPhoneCode
+2. 旧手机号效验验证码 → /v2.user/checkUnbindingPhoneCode
+3. 绑定新手机号 → /v2.user/unbindingPhone
+```
+
+---
+
+## 三、签名规则
+
+```
+1. 去掉 sign 参数
+2. 按 key 升序排列
+3. 拼接 key=value 并追加 appkey
+4. 转换为小写(含罗马数字特殊转换)
+```
+
+---
+
+## 四、公共参数
+
+| 参数 | 说明 | 必填 |
+|------|------|------|
+| device | 设备来源(1=PC, 2=安卓, 3=苹果, 4=H5) | 是 |
+| channel_id | 渠道ID | 是 |
+| appid | 应用ID | 是 |
+| gameid | 游戏ID | 是 |
+| imeil | 设备号 | 是 |
+| version | 版本号 | 是 |
+| token | 登录Token | 否 |
+
+---
+
+## 五、响应码约定
+
+| 响应码 | 含义 |
+|--------|------|
+| 100 | 登录失效 |
+| 110 | 参数有误 |
+| 120 | 处理提示 |
+| 130 | 执行异常 |
+| 200 | 正常 |
+
+---
+
+## 六、外部渠道回调汇总
+
+| 渠道 | 回调路径 |
+|------|---------|
+| 喜钛游 | `/v1.pay_notify/xty_pay.html` |
+| 趣智连 | `/v1.pay_notify/qzl_pay.html` |
+| 电魂 | `/v1.pay_notify/dianhun` |
+| 从语 | `/v1.complex_pay_notify/notify/channelqf/congyu/gameidqf/10` |
+| 神起 | `/v1.complex_pay_notify/notify/shenqi/gameidqf/10` |
+| 悦游 | `/v1.complex_pay_notify/notify/yueyou/gameidqf/10` |
+| 三九互娱 | `/v1.complex_pay_notify/notify/sanjiu/gameidqf/10` |
+| 游戏友 | `/v1.complex_pay_notify/notify/youxiyou/gameidqf/10` |
+| 唯恋 | `/v1.complex_pay_notify/notify/channelqf/weilian/gameidqf/20` |
+| 神龙游戏 | `/v1.complex_pay_notify/notify/channelqf/jlyx/gameidqf/10` |
+| 贪乐 | `/v1.complex_pay_notify/notify/channelqf/tlyx/gameidqf/10` |

+ 78 - 0
Dockerfile

@@ -0,0 +1,78 @@
+FROM php:7.3-fpm-alpine
+
+WORKDIR "/app"
+
+#COPY ./sources.list /etc/apt/sources.list
+
+# 修改镜像源
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
+
+#RUN wget -P /tmp/ http://nginx.org/download/nginx-1.15.11.tar.gz
+#RUN cd /tmp/;tar xzf nginx-1.15.11.tar.gz;cd nginx-1.15.11;sed -i -e 's/1.15.11//g' -e 's/nginx\//WS/g' -e 's/"NGINX"/"WS"/g' src/core/nginx.h
+#RUN cd /tmp/nginx-1.15.11;./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module;make;make install
+
+# 安装依赖,核心扩展,pecl扩展,git,composer,npm工具
+RUN apk update && apk add --no-cache tzdata && apk add nginx && apk add --no-cache --virtual .build-deps \
+        $PHPIZE_DEPS \
+        curl-dev \
+		libzip-dev \
+        libtool \
+        libxml2-dev \
+        sqlite-dev \
+		libmcrypt-dev \
+        freetype-dev \
+        libjpeg-turbo-dev \
+        libpng-dev \
+    && apk add --no-cache \
+        curl \
+        git \
+        mysql-client \
+    # 配置npm中国镜像
+    && pecl install mcrypt-1.0.2 \
+    && docker-php-ext-enable mcrypt \
+    && docker-php-ext-install \
+        bcmath \
+        curl \
+        mbstring \
+        pdo \
+        pdo_mysql \
+        pcntl \
+        tokenizer \
+        xml \
+        zip \
+    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
+    && docker-php-ext-install -j"$(getconf _NPROCESSORS_ONLN)" gd \
+    && pecl install -o -f redis \
+    && rm -rf /tmp/pear \
+    && docker-php-ext-enable redis
+
+# 安装composer并允许root用户运行
+ENV COMPOSER_ALLOW_SUPERUSER=1
+ENV COMPOSER_NO_INTERACTION=1
+ENV COMPOSER_HOME=/usr/local/share/composer
+RUN mkdir -p /usr/local/share/composer \
+	&& curl -o /tmp/composer-setup.php https://getcomposer.org/installer \
+	&& php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --snapshot \
+	&& rm -f /tmp/composer-setup.* \
+    # 配置composer中国全量镜像
+    && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
+
+# Expose port 9000 and start php-fpm server
+#EXPOSE 9000
+
+ENV TZ=Asia/Shanghai
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+COPY . /app
+COPY ./nginx/default.conf /etc/nginx/conf.d/
+COPY ./nginx/https.conf /etc/nginx/conf.d/
+COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
+COPY ./php-fpm/up.sh /usr/local/bin
+COPY ./php-fpm/php-ini-overrides.ini /usr/local/etc/php/conf.d/overrides.ini
+
+RUN mkdir -p /run/nginx
+RUN chmod a+x /usr/local/bin/up.sh
+RUN chmod a+x /app/www/new_sdk/crontab.sh
+RUN crontab ./crontabs/crontabfile.txt
+
+CMD ["/bin/sh", "/usr/local/bin/up.sh", "/app/www/new_sdk/crontab.sh"]

+ 301 - 0
README.md

@@ -0,0 +1,301 @@
+# 祈盟SDK(new_sdk)
+
+> 内部代号:`new_sdk` | 业务名:祈盟SDK(Qimeng SDK)
+> 项目类型:**多租户游戏SDK聚合平台**
+
+为游戏发行商和渠道合作伙伴提供一站式接入与运营能力,核心能力包括:
+
+- 多渠道游戏发行(一次接入对接 45+ 渠道)
+- 多支付方式集成(聚合 10+ 支付渠道)
+- 跨游戏、跨渠道的用户统一管理
+- 数据统计、分析与风控
+- CPS 公会渠道分层管理
+
+---
+
+## 一、技术栈
+
+| 组件        | 版本 / 说明                              |
+| ----------- | ---------------------------------------- |
+| 框架        | ThinkPHP 5.0.7                           |
+| PHP         | 7.0 - 7.2                                |
+| 数据库      | MySQL                                    |
+| 缓存        | Redis                                    |
+| 消息队列    | Kafka / ThinkPHP queue                   |
+| 对象存储    | 阿里云 OSS、华为云 OBS                   |
+| 部署        | Docker + docker-compose + Nginx + php-fpm |
+
+---
+
+## 二、目录结构
+
+项目根目录 `c:\web\composer\qi_meng\new_sdk`:
+
+```
+├── docker-compose.yml / Dockerfile   # 容器化部署
+├── nginx/                            # Nginx 配置
+│   └── default.conf
+├── php-fpm/                          # PHP-FPM 配置
+│   ├── Dockerfile
+│   ├── php-ini-overrides.ini
+│   └── sources.list
+├── crontabs/ script/                 # 定时任务与脚本
+├── logs/                             # 日志目录
+├── key/                              # 密钥
+└── www/new_sdk/                      # 主项目(ThinkPHP5)
+    ├── application/
+    │   ├── admin/      # 后台(100+ 控制器)
+    │   ├── api/        # 对外 API(v1 / v2 / mlbb / complex)
+    │   ├── cps/        # 公会系统
+    │   ├── common/     # 公共模型 / 工厂 / 服务
+    │   └── crontab/    # 定时任务(14+)
+    ├── extend/         # 第三方 SDK(支付宝、抖音游戏开放等)
+    ├── public/         # 入口 + 静态资源 + mlbb/complex 特殊入口
+    ├── doc/ doc_channel/ docs_12/ md/   # 各类文档
+    └── tests/          # 单测
+```
+
+---
+
+## 三、业务模块(12 大块)
+
+| # | 模块       | # | 模块         |
+| - | ---------- | - | ------------ |
+| 1 | 用户体系   | 7 | 客服系统     |
+| 2 | 游戏管理   | 8 | 风控系统     |
+| 3 | 支付处理   | 9 | CPS 公会系统 |
+| 4 | 渠道管理   | 10 | 运营工具   |
+| 5 | 数据统计   | 11 | 自动打包   |
+| 6 | 内容管理   | 12 | MLBB 游戏专项 |
+
+---
+
+## 四、对外域名生态
+
+| 用途             | 生产(`7dgames.cn` / `qmgames.cn`) | 测试(前缀加 `dev`) |
+| ---------------- | ----------------------------------- | -------------------- |
+| 后台             | `sdkadmin`                          | `devsdkadmin`        |
+| 支付回调 + API   | `sdkapi`                            | `devsdkapi`          |
+| 渠道 SDK 对接    | `sdkjhgame`                         | `devsdkjhgame`       |
+| 公会后台         | `sdkcps`                            | `devsdkcps`          |
+| 公会 API         | `sdkcpsapi`                         | `devsdkcpsapi`       |
+| 官网             | `sdkwww`                            | —                    |
+| 渠道包下载       | `sdkdownapp`                        | `devsdkdownapp`      |
+| 导出下载(Go)   | `sdkdownreport`                     | `devsdkdownreport`   |
+| 图片静态资源     | `sdkstatic`                         | `devsdkstatic`       |
+
+- 隐私页:https://sdkapi.qmgames.cn/privacy.html
+- 分包服务器请求域名:http://sdkpack.qmgames.cn/pack
+
+---
+
+## 五、关联仓库
+
+| 仓库                   | 职责                          |
+| ---------------------- | ----------------------------- |
+| `new_sdk`(本仓库)    | 后台 + API + CPS_API          |
+| `new_sdk_cps-admin`    | CPS 公会后台                  |
+| `new_sdk_cps-py`       | CPS 打包(Python)            |
+| `new_sdk_ws`           | 相关通知服务(WebSocket)     |
+| `new_sdk_go`           | Excel 导出 & 发放游戏礼包     |
+
+---
+
+## 六、核心架构
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ 用户接入层   游戏App │ Web端 │ 渠道SDK │ 公会系统           │
+├────────────────────────────────────────────────────────────┤
+│ API 网关层   v1 / v2 / mlbb / complex                       │
+├────────────────────────────────────────────────────────────┤
+│ 业务逻辑层   支付中心 │ 用户中心 │ 渠道适配 │ 统计聚合       │
+├────────────────────────────────────────────────────────────┤
+│ 数据访问层   用户 / 支付 / 游戏 / 统计 模型                  │
+├────────────────────────────────────────────────────────────┤
+│ 基础设施层   MySQL │ Redis │ Kafka │ OSS │ 短信/推送 │ 钉钉 │
+└────────────────────────────────────────────────────────────┘
+```
+
+### 关键流程
+
+- **游戏接入**:接入 → 渠道配置 → SDK 打包 → 分发 → 数据采集
+- **支付**:下单(`nw_pay`)→ 调用支付 SDK → 用户支付 → 异步回调(`nw_pay_cpinfo`)→ 验签 → 发货(`nw_member_coin`)→ 通知游戏服务器
+- **渠道分层**:平台 → B+ 级(总代)→ B 级(分销)→ B- 级(子分销)→ 推广员
+
+### 核心数据表
+
+```
+nw_members / nw_subaccount / nw_bind         用户体系
+nw_channel / nw_channel_ca / nw_channel_frozen  渠道
+cy_game / cy_game_package                    游戏
+nw_pay / nw_member_coin                      支付 & 余额
+nw_complex_summary_*                         统计汇总
+nw_channel_share                             CPS 分成配置
+```
+
+---
+
+## 七、外部依赖
+
+- **支付渠道(10+)**:支付宝(APP/H5/扫码)、微信支付(H5/小程序/扫码)、易宝、联动优势、趣智连、喜钛游、全民付…
+- **云服务**:阿里云 OSS、华为云 OBS、阿里云短信、钉钉告警机器人
+- **第三方 API**:176Park、MLBB、抖音广告追踪
+
+---
+
+## 八、项目现状与重构规划
+
+### 现状特征
+
+- **功能完整**,覆盖游戏 SDK 全链路
+- **业务复杂**,多租户、多渠道、多支付
+- **技术债务**:ThinkPHP 5.0.7 已过时、PHP 7.0-7.2 官方停止维护
+- **代码质量**:缺乏统一规范、无测试覆盖、耦合度高
+
+### 主要痛点
+
+1. `PaymentController` 约 2000 行,逻辑混乱
+2. 45+ 个 Channel SDK 存在大量重复代码
+3. 80+ 个数据模型缺乏统一规范
+4. 无测试覆盖,改一处可能影响多处
+5. 大量同步阻塞调用影响性能
+
+### 重构文档索引(`www/new_sdk/docs_12/`)
+
+```
+01_项目概览.md              全局认知
+02_痛点清单.md              痛点盘点
+03_模块矩阵.md              模块划分
+04_架构设计方案.md          目标架构
+05_重构实施方案.md          落地方案
+06_SDK渠道架构优化方案.md   45+ 渠道 SDK 标准化
+07_极简SDK重构方案.md       SDK 极简版
+08_签名系统终极方案.md      签名体系重构
+09_全部文档总结.md          总结
+README_SDK重构总结.md
+README_决策版.md
+```
+
+---
+
+## 九、环境要求
+
+| 类型     | 配置                   |
+| -------- | ---------------------- |
+| 服务器   | 2 核 4G                |
+| 打包服务器 | 2 核 8G             |
+| 数据库   | 2 核 8G                |
+| Redis    | 2 核 4G                |
+| 带宽     | 5M                     |
+| 开发栈   | PHP + MySQL + Redis + WS + Go + Python |
+
+必备:mysql / redis / docker / git。
+
+### 需要准备的第三方资源
+
+- 域名
+- 阿里云 OSS(或华为云 OBS)
+- 阿里云短信
+- 支付宝支付(wap / 扫码)
+- 微信支付(h5 / 扫码)
+- 钉钉预警机器人(可选,充值异常 / 特殊回调异常)
+
+---
+
+## 十、部署与构建
+
+### 容器化部署(推荐)
+
+```bash
+docker-compose up -d --build
+```
+
+### 目录配置要点
+
+- `nginx/default.conf`:Nginx 站点配置
+- `php-fpm/php-ini-overrides.ini`:PHP 配置覆盖
+- `docker-compose.yml`:容器编排
+- `Dockerfile`:PHP-FPM 镜像构建
+
+### 队列(支付异步回调)
+
+```bash
+# 生产:守护进程(性能最高,改代码需重启)
+php think queue:work --queue asyncCallback --daemon
+
+# 调试:热更新
+php think queue:listen --queue asyncCallback
+
+# 后台挂起
+nohup php think queue:work --queue asyncCallback --daemon > /dev/null 2>&1 &
+```
+
+| 参数     | 说明                                      |
+| -------- | ----------------------------------------- |
+| `delay`  | 任务失败后延迟多少秒重试(默认 0)        |
+| `force`  | 维护模式下仍强制运行                      |
+| `memory` | 允许占用的最大内存(默认 128M)           |
+| `sleep`  | 队列空闲时进程休眠秒数(默认 3)          |
+| `tries`  | 任务失败最大重试次数(默认 0 = 无限重试) |
+
+---
+
+## 十一、API 响应码约定
+
+```
+100 = 登录失效
+110 = 请求参数有误
+120 = 请求处理过程提示
+130 = 执行异常
+200 = 正常
+```
+
+---
+
+## 十二、常用运维命令
+
+```bash
+# 查询某天的日志文件列表及对应时间
+ls runtime/log/apilog/202603/*02.log | awk -F'/' '{print $NF}' \
+  | awk -F'-' '/^[0-9]+-/{cmd="date -d @"$1" +\"%Y-%m-%d %H:%M:%S\""; cmd | getline date; close(cmd); print date,$0}'
+
+# 查询某天日志中是否包含某订单号
+ls runtime/log/apilog/202603/*23.log | while read f; do \
+  grep -q "WL17742374917242139316" "$f" && { \
+    d=$(date -d @"$(basename "$f" | awk -F'-' '{print $1}')" +"%F %T"); \
+    echo "$d $(basename "$f")"; \
+  }; \
+done
+```
+
+---
+
+## 十三、其他代码入口
+
+- **WS 通知管理**
+  - `www/new_sdk/public/static/js/wsbusiness.js`
+  - `www/new_sdk/public/static/js/link_to_wsocket.js`
+
+- **当前仓库缺少 / 需另行对接**
+  - 报表生成服务(`app\common\library\MakeReportGo`,关联 Go 程序)
+  - 渠道打包(`app\common\logic\SubPackage`,关联 Python 程序)
+  - Workerman 相关配置
+
+---
+
+## 十四、上手建议
+
+如果你要上手或推进工作,推荐按此顺序:
+
+1. 读 `www/new_sdk/docs_12/01_项目概览.md` + `02_痛点清单.md` 建立全局认知
+2. 看 `09_全部文档总结.md` 把握重构总体思路
+3. 支付模块重点看 `application/common/factory/Pay.php` 与 `application/api/controller/v1/PayNotify.php`
+4. 渠道 SDK 看 `06_SDK渠道架构优化方案.md` + `extend/` 目录
+5. 风控看根目录 `risk_control_tables.sql` 与 `application/admin/controller/` 下风控相关控制器
+
+---
+
+## 参考
+
+- [ThinkPHP 5 完全开发手册](http://www.kancloud.cn/manual/thinkphp5)

File diff suppressed because it is too large
+ 1 - 0
Swagger3.0.json


+ 14 - 0
code/AI-ONBOARDING.md

@@ -0,0 +1,14 @@
+# AI 上手指南
+
+## 5 分钟上手流程
+
+1. 读 README.md(5 分钟)
+2. 扫 ARCHITECTURE-RULES.md 的"4 条铁律"(2 分钟)
+3. 扫 DANGER-ZONES.md 的 8 个禁区(3 分钟)
+4. 开始写代码
+
+## 写到一半发现"这个设计以前是不是做过"?
+→ 检索 BUG-LESSONS.md(按根因分类,不是按时间)
+
+## 写完代码
+→ 逐项过 PERFORMANCE-CHECKLIST.md

+ 412 - 0
code/ARCHITECTURE-RULES.md

@@ -0,0 +1,412 @@
+# 祈盟SDK 架构硬性规则
+
+> 适用对象:所有 AI 生成的本项目代码
+> 违反这些规则的 PR 一律不通过
+> 最后更新:2026-06-30
+
+---
+
+## R1:批量查询规则
+
+### 必须遵守
+
+- 任何接口接收批量入参时,必须在接口入口校验 `count(params) <= 2000`
+- 超过 2000 条的请求返回 `400 BatchSizeExceeded`
+- 不允许"为了业务方便"放宽这个限制
+- 不允许用 OR 条件拼 > 1000 条的 SQL(必须用 IN 分批 或 临时表 JOIN)
+
+### 推荐实现(Java)
+
+```java
+@PostMapping("/api/v1/sdk/query")
+public Result query(@RequestBody QueryRequest request) {
+    if (request.getUserIds() == null || request.getUserIds().isEmpty()) {
+        return Result.fail("INVALID_PARAMS", "参数不能为空");
+    }
+    if (request.getUserIds().size() > 2000) {
+        return Result.fail("BATCH_SIZE_EXCEEDED", "单次查询最多2000条");
+    }
+    // 去重
+    List<String> dedupIds = request.getUserIds().stream()
+        .distinct()
+        .collect(Collectors.toList());
+    return userDao.findByIds(dedupIds);
+}
+```
+
+### 错误示范(5/26 事故真实代码)
+
+```java
+// 错误:直接拼 OR 条件,2w 条条件打 MySQL
+String sql = "SELECT * FROM user WHERE " +
+    IntStream.range(0, userIds.size())
+        .mapToObj(i -> "(user_id=" + userIds.get(i) + 
+                       " AND game_id='" + gameId + "')")
+        .collect(Collectors.joining(" OR "));
+// 结果:MySQL CPU 90%+ 持续 8 分钟
+```
+
+### 正确实现(IN 分批)
+
+```java
+List<List<String>> batches = Lists.partition(userIds, 1000);
+List<User> result = new ArrayList<>();
+for (List<String> batch : batches) {
+    result.addAll(userDao.findByIds(batch));
+}
+return result;
+```
+
+### 正确实现(临时表 JOIN,性能远优于 2w 个 OR)
+
+```sql
+-- Step 1: 把查询条件写入临时表
+CREATE TEMPORARY TABLE tmp_query (
+    user_id VARCHAR(64),
+    game_id VARCHAR(32),
+    INDEX idx_user_game (user_id, game_id)
+);
+
+-- Step 2: 批量插入
+INSERT INTO tmp_query VALUES (?, ?), (?, ?), ...;
+
+-- Step 3: JOIN 查询(MySQL 优化器能走索引)
+SELECT u.* FROM user u
+JOIN tmp_query t ON u.user_id = t.user_id AND u.game_id = t.game_id;
+
+-- Step 4: 清理
+DROP TEMPORARY TABLE tmp_query;
+```
+
+---
+
+## R2:重试规则
+
+### 必须遵守
+
+- 任何重试必须使用指数退避:`30s -> 1min -> 2min -> 4min -> 5min`(封顶)
+- 超过 5 次后熔断,返回缓存值或默认值
+- 不允许"快速连续重试"
+- 重试触发前必须判断"失败原因"(瞬时故障 vs 持续故障)
+
+### 推荐实现(Resilience4j)
+
+```java
+RetryConfig config = RetryConfig.custom()
+    .maxAttempts(5)
+    .intervalFunction(IntervalFunction.ofExponentialBackoff(
+        30000L,    // 初始 30s
+        2.0,       // 每次翻倍
+        300000L))  // 封顶 5min
+    .retryOnException(e -> e instanceof TransientException)
+    .build();
+
+Retry retry = Retry.of("sdkFetch", config);
+CheckedSupplier<List<Data>> supplier = Retry.decorateCheckedSupplier(
+    retry,
+    () -> fetchData()
+);
+```
+
+### 推荐实现(Python 自研)
+
+```python
+import time
+import random
+
+def retry_with_backoff(func, max_attempts=5):
+    delays = [30, 60, 120, 240, 300]  # 秒
+    for attempt in range(max_attempts):
+        try:
+            return func()
+        except TransientException as e:
+            if attempt == max_attempts - 1:
+                # 最后一次失败,熔断:返回缓存或默认值
+                return get_cached_or_default()
+            delay = delays[attempt] + random.uniform(0, 5)
+            logging.warning(f"重试 {attempt+1}/{max_attempts},"
+                          f"等待 {delay}s,原因:{e}")
+            time.sleep(delay)
+        except PermanentException:
+            # 持续故障(DB挂/参数错),不重试
+            raise
+```
+
+### 错误示范(5/26 事故真实代码)
+
+```python
+# 错误:无退避连续重试,把已死的 DB 反复打死
+for i in range(5):
+    try:
+        fetch_data()
+        break
+    except Exception as e:
+        log.error(f"retry {i}")
+# 5 次重试全部打在已经扛不住的 DB 上
+```
+
+---
+
+## R3:缓存时长规则
+
+### 必须遵守
+
+- 客户端/脚本缓存时长 必须 小于 服务端会话有效期
+- 任何缓存必须有失效校验逻辑(每次使用前检查是否过期)
+- 不允许"假设缓存永远有效"
+- 推荐预留 10%-20% 的安全余量
+
+### 推荐实现(Python 抓取任务)
+
+```python
+# 每次执行前先校验 Cookie 有效性
+def fetch_orders():
+    if not is_cookie_valid():
+        logging.info("Cookie 已失效,重新登录")
+        re_login()
+    return do_fetch()
+```
+
+### 错误示范(6/16 事故真实配置)
+
+```python
+# 错误:客户端缓存 > 服务端会话
+SDK_SESSION_HOURS = 14       # 服务端 PHPSESSID 有效期
+SCRIPT_CACHE_HOURS = 24      # 脚本缓存时长,比服务端还长
+# 结果:第 14h 静默中断,无任何报错
+```
+
+### 正确配置
+
+```python
+SDK_SESSION_HOURS = 14
+SCRIPT_CACHE_HOURS = SDK_SESSION_HOURS - 2  # 预留 2h 余量 = 12h
+SCRIPT_CACHE_HOURS = 12
+```
+
+---
+
+## R4:接口限流规则
+
+### 必须遵守
+
+- 所有对外接口必须配置 QPS 限流(推荐 Nginx/网关层)
+- 默认限流值:1000 QPS(按接口可调)
+- 超过限流返回 `429 Too Many Requests`
+- 限流触发时必须有降级方案(返回缓存值/默认值/友好提示)
+
+### 推荐配置(Nginx)
+
+```nginx
+limit_req_zone $binary_remote_addr zone=sdk_api:10m rate=1000r/s;
+
+server {
+    location /api/v1/sdk/ {
+        limit_req zone=sdk_api burst=2000 nodelay;
+        limit_req_status 429;
+        proxy_pass http://sdk_backend;
+    }
+}
+```
+
+### 推荐配置(Spring Boot 限流)
+
+```java
+// 使用 Resilience4j RateLimiter
+RateLimiterConfig config = RateLimiterConfig.custom()
+    .limitForPeriod(1000)
+    .limitRefreshPeriod(Duration.ofSeconds(1))
+    .timeoutDuration(Duration.ofMillis(500))
+    .build();
+
+RateLimiter rateLimiter = RateLimiter.of("sdkApi", config);
+```
+
+---
+
+## R5:金额存储规则
+
+### 必须遵守
+
+- 金额字段必须使用 `DECIMAL(15,2)` 或 `BIGINT`(以分为单位)
+- 禁止使用 `DOUBLE` 或 `FLOAT` 存储金额
+- 所有金额运算在数据库层完成,不要在应用层用 `double` 计算
+
+### 推荐 DDL
+
+```sql
+-- 方案 A: DECIMAL(适合报表展示)
+amount DECIMAL(15,2) NOT NULL DEFAULT 0 COMMENT '金额(元)'
+
+-- 方案 B: BIGINT(适合高频计算,避免小数运算)
+amount_cents BIGINT NOT NULL DEFAULT 0 COMMENT '金额(分)'
+```
+
+### 错误 DDL(SDK 长期隐患)
+
+```sql
+-- 错误:DOUBLE 精度溢出
+amount DOUBLE(11,2)
+-- 0.1 + 0.2 在 double 下不是 0.3
+-- 累计计算后误差放大
+```
+
+### 推荐应用层写法
+
+```java
+// 错误:double 累加
+double total = 0.0;
+for (Order order : orders) {
+    total += order.getAmount();  // 精度累积误差
+}
+
+// 正确:用 BigDecimal
+BigDecimal total = BigDecimal.ZERO;
+for (Order order : orders) {
+    total = total.add(order.getAmount());
+}
+```
+
+---
+
+## R6:实时统计规则
+
+### 必须遵守
+
+- 直播平台读的"统计类"数据,必须从预聚合表读,不读明细表
+- 预聚合表由凌晨任务(02:00-05:00)更新
+- 实时数据(如"最近 30 分钟活跃")才允许读明细表
+- 不允许"为了实时性"让统计接口查明细表
+
+### 错误的实时统计(v4.24 之前的设计)
+
+```sql
+-- 错误:每次查都全表扫描
+SELECT COUNT(DISTINCT user_id) FROM active_log
+WHERE active_time >= NOW() - INTERVAL 30 MINUTE
+-- 直播平台每秒调一次 → DB 爆炸
+```
+
+### 正确的预聚合表(v4.25 设计)
+
+```sql
+-- 正确:读预聚合表,加 Redis 缓存
+SELECT active_count FROM stat_account_summary
+WHERE stat_date = ? AND game_id = ? AND promoter_id = ?
+
+-- 配合 Redis 缓存(5 分钟 TTL)
+String cacheKey = "stat:" + date + ":" + gameId + ":" + promoterId;
+```
+
+---
+
+## R7:跨部门信息同步规则
+
+### 必须遵守
+
+- 任何"接口模式 vs 抓取模式"的切换必须同步通知产品部和发行部
+- 任何"数据源变更"必须同步通知产品部和发行部
+- 不允许"群里通知一下就完事"
+- 通知必须包含:变更原因、变更时间、影响范围、回滚方案、负责人
+
+### 通知模板
+
+```
+【变更通知】
+
+变更原因:[例如:5/26 抓取任务异常,临时切换为接口模式]
+变更时间:[例如:2026-05-26 20:30]
+变更内容:[例如:订单数据从爬虫抓取切换为 SDK 接口读取]
+影响范围:[例如:订单数据延迟从 1h 变为 5min]
+回滚方案:[例如:执行 rollback.sh,恢复爬虫模式]
+负责人:[姓名 + 联系方式]
+监控验证:[例如:72h 内订单数据完整性 100%]
+
+抄送:技术部 / 产品部 / 发行部 / 客服部
+```
+
+---
+
+## R8:监控告警规则
+
+### 必须遵守
+
+- 所有新接口上线 = 必须有监控(不是"出事后补")
+- 所有新数据源接入 = 必须有告警
+- 所有批量任务 = 必须有完成/失败告警
+- 所有外部依赖(DB/Redis/第三方接口)= 必须有可用性监控
+
+### 推荐监控指标
+
+| 指标 | 阈值 | 告警级别 |
+|------|------|---------|
+| 接口 QPS | > 1000 | P2 |
+| 接口错误率 | > 1% | P1 |
+| 接口 P99 延迟 | > 3s | P1 |
+| MySQL CPU | > 70% | P1 |
+| MySQL 慢查询 | > 100 条/min | P1 |
+| Redis 带宽 | > 800Mbps | P1 |
+| 抓取任务执行时长 | > 1h | P2 |
+| 抓取任务连续失败 | > 3 次 | P0 |
+
+---
+
+## R9:幂等性规则
+
+### 必须遵守
+
+- 所有写操作必须支持幂等(重复执行不产生副作用)
+- 必须使用业务唯一键做幂等控制
+- 不允许"假设上游只会调一次"
+
+### 推荐实现
+
+```sql
+-- 唯一索引 + INSERT IGNORE
+CREATE UNIQUE INDEX uk_order_no ON orders (order_no);
+
+INSERT IGNORE INTO orders (order_no, amount, ...) VALUES (?, ?, ...);
+```
+
+```java
+// 或 ON DUPLICATE KEY UPDATE
+String sql = "INSERT INTO player_active_record (...) " +
+             "VALUES (...) " +
+             "ON DUPLICATE KEY UPDATE active_time = NOW()";
+```
+
+---
+
+## R10:日志规则
+
+### 必须遵守
+
+- 所有关键操作必须有日志(登录、抓取、接口调用、异常)
+- 日志必须包含:时间、用户、接口、参数摘要、结果、耗时
+- 异常日志必须包含完整堆栈
+- 不允许"只 log.info 一行"就算日志
+- 不允许在生产环境删日志代码("方便后续快速排查")
+
+### 推荐日志格式
+
+```java
+log.info("SDK接口调用|userId={}|gameId={}|action={}|result={}|cost={}ms",
+    userId, gameId, action, result, cost);
+```
+
+---
+
+## 附录:规则速查表
+
+| 规则 | 核心约束 | 历史事故 |
+|------|---------|---------|
+| R1 批量查询 | 单次 <= 2000,禁止 OR 拼 SQL | 5/12、5/26 |
+| R2 重试 | 指数退避 30s 起步,5 次熔断 | 5/26 |
+| R3 缓存时长 | 客户端 < 服务端,每次校验 | 6/16 |
+| R4 接口限流 | QPS <= 1000,超限 429 | 5/12、5/26 |
+| R5 金额存储 | DECIMAL(15,2) 或 BIGINT,禁用 DOUBLE | 长期隐患 |
+| R6 实时统计 | 走预聚合表,不查明细 | v4.24 之前 |
+| R7 跨部门通知 | 正式通知,不群里说 | 6/16 |
+| R8 监控告警 | 上线即有,不出事后补 | 5/12、5/26 |
+| R9 幂等性 | 写操作必须幂等 | - |
+| R10 日志 | 关键操作全打日志 | 6/16 |

+ 157 - 0
code/BUG-LESSONS.md

@@ -0,0 +1,157 @@
+
+---
+
+### 文件 3:BUG-LESSONS.md(历次故障复盘 - 按根因分类)
+
+```markdown
+# 祈盟SDK 故障复盘 - 知识沉淀
+
+> 按"根因"分类,不是按时间。
+> 同样的根因在多个时间点出现 → 必须从架构层根治,不是修 Bug。
+
+```
+
+## 类别 1:批量查询无限制(高频根因)
+
+### 案例 1.1:5/12 Redis 带宽打满
+
+**症状**:直播平台 7000 用户查询,Redis 出口带宽 14h 被打满
+
+**根因**:
+- 上游调用方传 7000 个用户,接口直接查 Redis
+- Redis 出口带宽 1Gbps 封顶
+- 查询数据量超过带宽
+
+**修复**(部分根治):
+- ✅ Redis 带宽升级到 5Gbps
+- ✅ 接口加用户数限制 7000
+- ❌ **没根治**:下次别的查询维度(角色/订单/充值)也会爆
+
+**AI 启示**:
+- 看到"批量查询" → 立即检查"是否有限流+分页"
+- 看到"上游传 N 个参数" → 立即想"这个 N 上限在哪?"
+
+### 案例 1.2:5/26 MySQL CPU 爆掉(同根因不同表象)
+
+**症状**:抓取任务失败,连续重试,MySQL CPU 90%+ 持续 8 分钟
+
+**根因**:
+- 抓取脚本传 1 个游戏 ID + 7000 用户 ID
+- 每个用户 200 个角色 → 2w 条 OR 条件 SQL
+- MySQL 无法走索引合并优化 → 全表扫描
+
+**修复**(部分根治):
+- ✅ 接口加 2000 条硬限
+- ✅ 抓取改分批 1000 条
+- ❌ **没根治**:2w 条 OR 条件 SQL 本身没改,**应该改临时表 JOIN**
+
+**AI 启示**:
+- 看到"OR 条件拼 SQL" → **立即**改成"IN 分批 + 服务层聚合"或"临时表 JOIN"
+- 看到"批量查询失败重试" → **立即**想到"重试也会打死 DB"
+
+---
+
+## 类别 2:重试无退避(高频根因)
+
+### 案例 2.1:5/26 MySQL 二次伤害(同事故的另一个根因)
+
+**症状**:第一次请求超时后,**连续 5 次重试**,每次都打 DB
+
+**根因**:
+- 抓取脚本无脑 retry 5 次
+- 每次重试都重新跑同一条 2w 条 SQL
+- 重试相当于"给已死的 DB 浇油"
+
+**修复**(**完全没做**):
+- ❌ 重试机制**至今未加退避**
+- ❌ 没有熔断阈值
+
+**AI 启示**:
+- 看到 `for (int i = 0; i < 5; i++) retry()` → **立即**改为指数退避
+- 看到"重试" → **立即**问"失败原因是瞬时还是持续?"
+
+---
+
+## 类别 3:缓存时长配置冲突(高频根因)
+
+### 案例 3.1:6/16 Cookie 失效导致抓取中断
+
+**症状**:11:40 抓取任务静默中断,无任何报错,订单数据缺失
+
+**根因**:
+- SDK 服务端 PHPSESSID = 14h
+- 直播平台脚本缓存 Cookie = 24h
+- 14h 后服务端 Session 失效,但客户端缓存还在
+- 接口返回登录页,脚本误以为是"空数据"
+
+**修复**(已闭环):
+- ✅ SDK 缓存有效期延长
+- ✅ 直播平台加 Cookie 有效性校验
+- ✅ 失效自动重新登录
+
+**AI 启示**:
+- 看到"客户端缓存" → **立即**问"服务端有效期是多少?客户端缓存 < 服务端有效期吗?"
+- 看到"无校验的脚本任务" → **立即**加"每次执行前先校验缓存有效性"
+
+---
+
+## 类别 4:精度溢出(长期隐患)
+
+### 案例 4.1:SDK 金额存储 double 精度问题(未根治)
+
+**症状**:累计金额、提成计算出现微小误差(0.01 元级别)
+
+**根因**:
+- `amount DOUBLE(11,2)` 存储金额
+- DOUBLE 是浮点数,无法精确表示 0.1/0.2 等值
+- 累计计算后误差放大
+
+**修复**(**没做**):
+- ❌ 长期遗留,**架构治理最低分项**
+- 计划 v4.25 改 `DECIMAL(15,2)`
+
+**AI 启示**:
+- 看到 `DOUBLE` / `FLOAT` 存金额 → **立即**改为 `DECIMAL` 或 `BIGINT`(分)
+- 看到"金额计算" → **立即**问"在数据库算还是在应用层算?"
+
+---
+
+## 类别 5:跨部门信息同步缺失(流程类)
+
+### 案例 5.1:6/16 接口模式切换未通知
+
+**症状**:5/26 上线新接口,5/26 晚用户反馈数据缺失,临时切换为爬虫,**只群里通知,没告知产品/发行**
+
+**根因**:
+- 接口/爬虫切换是重大变更
+- 没有正式的"变更通知流程"
+
+**修复**(部分):
+- ✅ 临时通知了
+- ❌ 没有正式流程
+
+**AI 启示**:
+- 看到"切换数据源/接口" → **立即**触发"通知产品部 + 发行部"流程
+- 看到"群里通知一下" → **立即**升级为"正式邮件 + 工单 + 文档记录"
+
+---
+
+## 类别 6:流程归口不清(架构治理类)
+
+### 案例 6.1:4/13 P1 故障(流程类)
+
+**症状**:需求私对接开发,验收无标准,数据异常无提报模板
+
+**根因**:
+- 没有 P0/P1/P2/P3 判定标准
+- 没有数据异常提报模板
+- 没有测试验收规范
+
+**修复**(部分):
+- ✅ 4 级优先级标准
+- ✅ 数据异常模板
+- ❌ 测试验收规范未落地
+
+**AI 启示**:
+- 看到"需求无验收标准" → **立即**要求"先有验收标准,再写代码"
+- 看到"私对接开发" → **立即**走"需求归口 = 产品部"

+ 20 - 0
code/CLAUDE.md

@@ -0,0 +1,20 @@
+# Claude 上下文
+
+## 项目
+祈盟SDK - Java/Go 后端 + Android/iOS/H5 SDK + 数据统计服务
+
+## 关键技术约束
+- 批量查询上限 2000
+- 重试必须指数退避
+- 缓存时长 < 服务端会话
+- 金额用 DECIMAL(15,2) 或 BIGINT
+
+## 必读文档
+- /README.md
+- /ARCHITECTURE-RULES.md
+- /DANGER-ZONES.md
+- /BUG-LESSONS.md
+- /PERFORMANCE-CHECKLIST.md
+
+## 禁止写法
+见 DANGER-ZONES.md(8 个禁区)

+ 344 - 0
code/DANGER-ZONES.md

@@ -0,0 +1,344 @@
+# 祈盟SDK 代码禁区
+
+> 这些写法在本项目历史中出过事。
+> AI 生成代码时,必须避免以下 8 种模式。
+> 最后更新:2026-06-30
+
+---
+
+## 禁区 1:OR 条件拼 SQL
+
+### 错误写法(永远不要这么写)
+
+```java
+// 错误:直接拼 OR 条件,2w 条条件打 MySQL
+String sql = "SELECT * FROM t WHERE " +
+    conditions.stream()
+        .map(c -> "(a=" + c.a + " AND b='" + c.b + "')")
+        .collect(Collectors.joining(" OR "));
+```
+
+### 事故记录
+
+- 5/26 MySQL CPU 爆掉事故:2w 条 OR 条件,CPU 90%+ 持续 8 分钟
+
+### 正确写法(3 种方案)
+
+```java
+// 方案 A: IN 语法(推荐,最简单)
+String sql = "SELECT * FROM t WHERE a IN (...) AND b = ?";
+
+// 方案 B: 分批 IN(数据量超大时)
+List<List<String>> batches = Lists.partition(userIds, 1000);
+for (List<String> batch : batches) {
+    userDao.findByIds(batch);
+}
+
+// 方案 C: 临时表 JOIN(性能最优)
+"INSERT INTO tmp_query (a, b) VALUES (?, ?), (?, ?), ...";
+"SELECT t.* FROM t JOIN tmp_query USING (a, b)";
+```
+
+---
+
+## 禁区 2:无退避重试
+
+### 错误写法(永远不要这么写)
+
+```java
+// 错误:无退避连续重试
+for (int i = 0; i < 5; i++) {
+    try { fetch(); break; }
+    catch (Exception e) { log.error("retry " + i); }
+}
+```
+
+```python
+# Python 错误写法
+for i in range(5):
+    try:
+        fetch_data()
+        break
+    except Exception as e:
+        log.error(f"retry {i}")
+```
+
+### 事故记录
+
+- 5/26 MySQL 二次伤害:连续 5 次重试,每次都打 DB
+
+### 正确写法
+
+```java
+// Resilience4j
+RetryConfig config = RetryConfig.custom()
+    .maxAttempts(5)
+    .intervalFunction(IntervalFunction.ofExponentialBackoff(30000L, 2.0, 300000L))
+    .retryOnException(e -> e instanceof TransientException)
+    .build();
+```
+
+```python
+# Python 指数退避
+import time
+import random
+
+def retry_with_backoff(func, max_attempts=5):
+    delays = [30, 60, 120, 240, 300]
+    for attempt in range(max_attempts):
+        try:
+            return func()
+        except TransientException:
+            if attempt == max_attempts - 1:
+                return get_cached_or_default()  # 熔断
+            delay = delays[attempt] + random.uniform(0, 5)
+            time.sleep(delay)
+        except PermanentException:
+            raise  # 持续故障不重试
+```
+
+---
+
+## 禁区 3:客户端缓存 > 服务端会话
+
+### 错误写法(永远不要这么写)
+
+```python
+# 错误:客户端缓存时长 > 服务端会话有效期
+SDK_SESSION_HOURS = 14       # 服务端
+SCRIPT_CACHE_HOURS = 24      # 客户端
+# 结果:第 14h 静默中断,无任何报错
+```
+
+### 事故记录
+
+- 6/16 Cookie 失效导致抓取中断
+
+### 正确写法
+
+```python
+# 正确:客户端缓存 < 服务端会话,预留 10%-20% 余量
+SDK_SESSION_HOURS = 14
+SCRIPT_CACHE_HOURS = SDK_SESSION_HOURS - 2  # = 12h
+SCRIPT_CACHE_HOURS = 12
+
+# 正确:每次使用前校验缓存有效性
+def fetch_orders():
+    if not is_cookie_valid():
+        re_login()
+    return do_fetch()
+```
+
+---
+
+## 禁区 4:用 DOUBLE 存金额
+
+### 错误 DDL(永远不要这么写)
+
+```sql
+-- 错误:DOUBLE 精度溢出
+amount DOUBLE(11,2)
+amount FLOAT
+-- 0.1 + 0.2 在 double 下不是精确的 0.3
+-- 累计计算后误差放大
+```
+
+### 事故记录
+
+- SDK 金额存储 double 精度问题(长期未根治,周报标"架构治理最低分")
+
+### 正确 DDL
+
+```sql
+-- 方案 A: DECIMAL(适合报表展示)
+amount DECIMAL(15,2) NOT NULL DEFAULT 0 COMMENT '金额(元)'
+
+-- 方案 B: BIGINT(适合高频计算,避免小数运算)
+amount_cents BIGINT NOT NULL DEFAULT 0 COMMENT '金额(分)'
+```
+
+### 正确应用层写法
+
+```java
+// 错误:double 累加
+double total = 0.0;
+for (Order order : orders) {
+    total += order.getAmount();
+}
+
+// 正确:BigDecimal
+BigDecimal total = BigDecimal.ZERO;
+for (Order order : orders) {
+    total = total.add(order.getAmount());
+}
+```
+
+---
+
+## 禁区 5:实时查明细做统计
+
+### 错误 SQL(永远不要这么写)
+
+```sql
+-- 错误:直播平台查"昨日活跃",每次全表扫描
+SELECT COUNT(DISTINCT user_id) FROM active_log
+WHERE active_time >= ? AND active_time < ?
+-- 直播平台每秒调一次 → DB 爆炸
+```
+
+### 事故记录
+
+- v4.24 之前的设计:实时统计打爆 DB
+
+### 正确 SQL
+
+```sql
+-- 正确:读预聚合表(凌晨任务算好的)
+SELECT active_count FROM stat_account_summary
+WHERE stat_date = ? AND game_id = ? AND promoter_id = ?
+
+-- 配合 Redis 缓存(5 分钟 TTL)
+```
+
+### 适用边界
+
+- 统计类数据(昨日/近 30 天/留存/LTV)→ 走预聚合表
+- 实时数据(最近 30 分钟活跃)→ 才允许读明细表
+- 高频查询(> 10 QPS)→ 必须走预聚合 + 缓存
+
+---
+
+## 禁区 6:相信上游会传正确的数据
+
+### 错误写法(永远不要这么写)
+
+```java
+// 错误:直接用入参,不校验
+public Result query(List<String> userIds) {
+    return userDao.findByIds(userIds);
+}
+```
+
+### 事故记录
+
+- 5/12、5/26 都涉及"上游传了过多数据"
+
+### 正确写法
+
+```java
+// 正确:所有入参必须校验
+public Result query(List<String> userIds) {
+    // 1. 非空校验
+    if (userIds == null || userIds.isEmpty()) {
+        return Result.fail("INVALID_PARAMS", "参数不能为空");
+    }
+    // 2. 上限校验
+    if (userIds.size() > 2000) {
+        return Result.fail("BATCH_SIZE_EXCEEDED", "单次最多2000条");
+    }
+    // 3. 格式校验
+    for (String id : userIds) {
+        if (id == null || id.length() > 64) {
+            return Result.fail("INVALID_PARAMS", "参数格式错误");
+        }
+    }
+    // 4. 去重
+    List<String> dedupIds = userIds.stream()
+        .distinct()
+        .collect(Collectors.toList());
+    return userDao.findByIds(dedupIds);
+}
+```
+
+---
+
+## 禁区 7:群里通知一下就算"跨部门同步"
+
+### 错误做法(永远不要这么做)
+
+```
+错误:在群里 @ 一下就算通知完事
+"@产品 @发行 接口切了,你们看下"
+```
+
+### 事故记录
+
+- 6/16:5/26 上线新接口,5/26 晚用户反馈数据缺失,临时切换为爬虫
+  只群里通知,没告知产品/发行,跨部门信息不同步
+
+### 正确做法(4 步走)
+
+```
+步骤 1: 邮件
+- 收件人:产品部 + 发行部 + 技术部 + 客服部
+- 主题:【变更通知】[具体变更内容]
+- 正文:变更原因 + 时间 + 内容 + 影响 + 回滚 + 负责人
+
+步骤 2: 工单
+- 在 PMO / 飞书项目里建变更单
+- 关联到具体需求/事故
+
+步骤 3: 文档更新
+- 在 Confluence / 飞书更新变更记录
+- 写明前后差异
+
+步骤 4: 72h 跟踪
+- 变更后 72h 内确认无问题
+- 出问题立即回滚并通知
+```
+
+---
+
+## 禁区 8:出事后才补监控
+
+### 错误流程(永远不要这么做)
+
+```
+错误流程:
+5/12 出事 -> 5/13 加 Redis 监控
+5/26 出事 -> 5/27 加 MySQL 监控
+```
+
+### 事故记录
+
+- 5/12 事故后才补 Redis 监控
+- 5/26 事故后才补 MySQL 监控
+- 6/16 事故前没有任何抓取任务监控
+
+### 正确流程(上线即有)
+
+```
+所有新接口上线 = 必须有监控(QPS / 错误率 / 延迟)
+所有新数据源接入 = 必须有告警(可用性 / 数据量 / 时延)
+所有批量任务 = 必须有完成/失败告警
+所有外部依赖 = 必须有可用性监控(DB / Redis / 第三方接口)
+```
+
+### 推荐监控清单
+
+| 监控项 | 阈值 | 告警级别 |
+|--------|------|---------|
+| 接口 QPS | > 1000 | P2 |
+| 接口错误率 | > 1% | P1 |
+| 接口 P99 延迟 | > 3s | P1 |
+| MySQL CPU | > 70% | P1 |
+| MySQL 慢查询 | > 100 条/min | P1 |
+| Redis 带宽 | > 800Mbps | P1 |
+| 抓取任务执行时长 | > 1h | P2 |
+| 抓取任务连续失败 | > 3 次 | P0 |
+| 跨部门数据同步延迟 | > 1h | P1 |
+
+---
+
+## 附录:禁区速查表
+
+| 禁区 | 一句话 | 事故 |
+|------|--------|------|
+| 1 OR 拼 SQL | 用 IN / 临时表 JOIN 替代 | 5/26 |
+| 2 无退避重试 | 30s -> 1min -> 2min -> 4min -> 5min | 5/26 |
+| 3 缓存 > 会话 | 客户端 < 服务端,每次校验 | 6/16 |
+| 4 DOUBLE 存金额 | 用 DECIMAL(15,2) 或 BIGINT | 长期隐患 |
+| 5 实时查明细 | 走预聚合表,不查明细 | v4.24 之前 |
+| 6 相信上游 | 入参必校验:非空 + 上限 + 格式 | 5/12、5/26 |
+| 7 群里通知 | 邮件 + 工单 + 文档 + 72h 跟踪 | 6/16 |
+| 8 出事后补监控 | 上线即有监控和告警 | 5/12、5/26 |

+ 57 - 0
code/PERFORMANCE-CHECKLIST.md

@@ -0,0 +1,57 @@
+# 祈盟SDK 性能自检清单
+
+> 写完代码后,对照这份清单逐项打勾。
+> 任何一项不通过,PR 会被打回。
+
+---
+
+## 批量查询类
+
+- [ ] 入参是否校验 `count ≤ 2000`?
+- [ ] 是否避免 OR 条件拼 > 1000 条 SQL?
+- [ ] 是否使用分页/分批/IN 语法?
+- [ ] 大量数据查询是否走预聚合表而非明细表?
+
+## 重试类
+
+- [ ] 重试是否使用指数退避(30s→1min→2min→4min→5min)?
+- [ ] 重试上限是否 ≤ 5 次?
+- [ ] 失败时是否有降级方案(缓存值/默认值)?
+- [ ] 重试触发前是否判断"失败原因"?(瞬时 vs 持续)
+
+## 缓存类
+
+- [ ] 客户端缓存时长是否 < 服务端会话有效期?
+- [ ] 是否有缓存失效校验逻辑?
+- [ ] 缓存 Key 是否包含版本号/更新时间?(避免脏读)
+
+## 接口类
+
+- [ ] 是否配置了 QPS 限流?
+- [ ] 是否有超时设置(建议 5s)?
+- [ ] 是否有降级方案(限流后返回什么)?
+
+## 数据库类
+
+- [ ] 金额字段是否用 `DECIMAL(15,2)` 或 `BIGINT`?
+- [ ] 金额运算是否在数据库层完成?
+- [ ] 慢 SQL 是否走索引?
+- [ ] 是否避免 `SELECT *`?
+
+## 并发类
+
+- [ ] 是否考虑了并发场景下的幂等性?
+- [ ] 是否有锁竞争/死锁风险?
+- [ ] 连接池大小是否合理?(建议 ≤ 50)
+
+## 跨部门类
+
+- [ ] 接口/数据源切换是否同步了产品部 + 发行部?
+- [ ] 是否更新了对外文档?
+
+## 上线类
+
+- [ ] 是否有监控告警?(不是"出事后补")
+- [ ] 是否有降级开关?
+- [ ] 是否有回滚方案?
+- [ ] 冒烟测试是否覆盖核心场景?

+ 44 - 0
code/README.md

@@ -0,0 +1,44 @@
+# 祈盟SDK - AI 开发上下文必读
+
+> 本文件是所有 AI 在为本项目生成代码前必须阅读的入口。
+> 阅读时间:5 分钟。
+> 最后更新:2026-06-30
+
+## 1. 这是什么
+
+祈盟SDK 是面向直播平台 + 推广员 + 游戏方的中间层,承载:
+- 玩家登录/活跃数据采集
+- 订单/充值数据推送
+- 多端(Android/iOS/H5)统一接入
+- 数据统计 + LTV/留存分析
+
+## 2. 技术栈关键事实
+
+- **后端**:Java + Spring Boot(v4.x 之前)/ Go(v4.25 部分模块)
+- **数据库**:MySQL 8.0(分库分表后单库 8 千万行级)
+- **缓存**:Redis Cluster(出口带宽 1Gbps 封顶)
+- **对外协议**:HTTP REST + WebSocket
+- **采集端**:Android Java / iOS Swift / H5 JS
+
+## 3. 在开发前必读的 4 份文件
+
+| 场景 | 必读文件 |
+|------|---------|
+| 写任何接口前 | `ARCHITECTURE-RULES.md` |
+| 写批量查询/重试/缓存相关代码前 | `DANGER-ZONES.md` |
+| 写完代码后 | `PERFORMANCE-CHECKLIST.md` |
+| 遇到"这个设计为什么是这样"的问题 | `BUG-LESSONS.md` |
+
+## 4. 4 条铁律(违反任何一条,PR 会被拒)
+
+1. **任何批量查询必须分页**(单次 ≤ 2000 条)
+2. **任何重试必须带指数退避**(起始 30s,封顶 5min,5 次后熔断)
+3. **任何缓存时长必须小于服务端会话有效期**
+4. **任何对外接口必须有 QPS 限流**
+
+## 5. 不要做的事
+
+- ❌ 不要用 `double(11,2)` 存储金额(精度溢出)
+- ❌ 不要用 OR 条件拼 > 1000 个的 SQL
+- ❌ 不要相信"上游会传正确的数据"——所有入参都要校验
+- ❌ 不要在接口里直接查明细表做实时统计

+ 8 - 0
code/说明.txt

@@ -0,0 +1,8 @@
+祈盟SDK/
+├── README.md                    ← AI 必读入口(≤ 200 行,5 分钟读完)
+├── ARCHITECTURE-RULES.md        ← 架构硬性规则(AI 生成代码必须遵守)
+├── BUG-LESSONS.md               ← 历次故障复盘(按根因分类,不是按时间)
+├── PERFORMANCE-CHECKLIST.md     ← 性能自检清单(开发完成后对照检查)
+└── DANGER-ZONES.md              ← 代码禁区("这些写法在我们这里出过事")
+
+

+ 148 - 0
code_version_md/v4.25-玩家活跃.md

@@ -0,0 +1,148 @@
+# v4.25 - 玩家活跃记录
+
+## 一、需求概述
+
+24小时监控玩家在线状态,每小时记录一次活跃数据。每个账户、每个游戏、每个角色每小时最多生成一条记录。
+
+> 玩家A从1点在线到3点,实际产生3条数据(1点、2点、3点各一条)。
+
+## 二、方案选型
+
+### 方案对比
+
+| 对比项 | 客户端每小时上报 | 心跳 + 服务端聚合 |
+|---|---|---|
+| iOS 可靠性 | ⚠️ 后台定时器不可靠 | ⚠️ 同样依赖前台定时器 |
+| H5 可靠性 | ⚠️ 切后台暂停JS | ⚠️ 同样依赖页面可见 |
+| 安卓兼容性 | ⚠️ 部分厂商杀后台 | ⚠️ 同样存在此问题 |
+| 实现复杂度 | 低 | 中(多一层Redis + Cron) |
+| 接口调用量 | 低(1次/小时) | 中(12次/小时) |
+| 扩展性 | 仅统计活跃 | 可扩展实时在线、掉线检测 |
+
+### 结论
+
+两种方案都依赖客户端定时器,切后台/被杀后定时器都会暂停,回到前台都会重新启动。核心区别在于漏报窗口:心跳5分钟 vs 每小时上报60分钟。
+
+对于"每小时记录一次活跃"的需求,客户端每小时上报简单直接,够用。
+
+**采用方案:客户端每小时上报。**
+
+## 三、接口设计
+
+### 3.1 活跃上报接口
+
+- **路径**:`POST /api/v2/User/playerActive`
+- **调用方**:客户端定时任务,每小时调用一次
+- **请求参数**:
+
+| 参数 | 类型 | 必填 | 说明 |
+|---|---|---|---|
+| member_id | int | 是 | 玩家账号ID |
+| game_id | int | 是 | 游戏ID |
+| channel_id | int | 是 | 渠道ID(推广员) |
+| server_id | int | 是 | 区服ID |
+| server_name | string | 是 | 区服名 |
+| role_id | int | 是 | 角色ID |
+| role_name | string | 是 | 角色名 |
+
+- **响应示例**:
+
+```json
+{
+    "code": 200,
+    "msg": "上报成功",
+    "data": []
+}
+```
+
+### 3.2 服务端处理逻辑
+
+1. 校验参数完整性
+2. 查询玩家信息(`cy_members`)获取 `member_create_time`
+3. 查询角色信息(`cy_role_info`)获取 `role_create_time`
+4. 通过 `channel_id`(推广员,level=3)查询渠道层级关系,获取子会长、公会账号、商务账号
+5. 通过 `game_id` 查询游戏名
+6. 检查去重:同一 `member_id + game_id + role_id + 小时` 是否已存在记录
+7. 不存在则写入,存在则跳过
+
+## 四、表结构
+
+### 4.1 活跃记录表 `cy_player_active_log`
+
+```sql
+CREATE TABLE `cy_player_active_log` (
+    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+    `member_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '玩家账号ID',
+    `subaccount_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '子账户ID',
+    `game_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '游戏ID',
+    `game_name` varchar(100) NOT NULL DEFAULT '' COMMENT '游戏名',
+    `channel_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '渠道ID(推广员)',
+    `server_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '区服ID',
+    `server_name` varchar(100) NOT NULL DEFAULT '' COMMENT '区服名',
+    `role_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '角色ID',
+    `role_name` varchar(100) NOT NULL DEFAULT '' COMMENT '角色名',
+    `role_create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '角色创建时间(时间戳)',
+    `member_create_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '账户注册时间(时间戳)',
+    `promoter_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '推广员ID',
+    `sub_president_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '子会长ID',
+    `guild_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '公会账号ID(会长)',
+    `business_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '商务账号ID',
+    `create_time` datetime NOT NULL COMMENT '活跃时间(例如:2026-06-30 01:00:00)',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_active` (`member_id`, `game_id`, `role_id`, `create_time`),
+    KEY `idx_game` (`game_id`, `create_time`),
+    KEY `idx_channel` (`channel_id`, `create_time`),
+    KEY `idx_guild` (`guild_id`, `create_time`),
+    KEY `idx_business` (`business_id`, `create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='玩家活跃记录';
+```
+
+### 4.2 字段说明
+
+| 字段 | 说明 |
+|---|---|
+| `member_id` | 来自请求参数,关联 `cy_members` 表 |
+| `subaccount_id` | 来自请求参数或玩家信息 |
+| `game_id` | 来自请求参数 |
+| `game_name` | 通过 `game_id` 查询 `cy_game` 表获取 |
+| `channel_id` | 来自请求参数,即推广员ID(level=3) |
+| `promoter_id` | 等于 `channel_id` |
+| `sub_president_id` | 通过 `channel.parent_id` 向上查 level=2 的渠道 |
+| `guild_id` | 通过 `channel.parent_id` 向上查 level=1 的渠道 |
+| `business_id` | 通过 `channel.parent_id` 向上查 level=0 的渠道 |
+| `create_time` | 服务端当前时间,取整到小时(如 2026-06-30 01:00:00) |
+
+### 4.3 渠道层级关系查询
+
+渠道表 `nw_channel` 通过 `id_path` 或 `parent_id` 维护四级树形结构:
+
+```
+商务(level=0) → 公会/会长(level=1) → 子会长(level=2) → 推广员(level=3)
+```
+
+`id_path` 示例:`,10,20,300,500,`,一次查询即可拿到整条链路。
+
+## 五、数据流向
+
+```
+客户端(每小时定时器)
+    │
+    ▼
+POST /api/v2/User/playerActive
+    │
+    ├─ 查询 cy_members → member_create_time
+    ├─ 查询 cy_role_info → role_create_time
+    ├─ 查询 cy_game → game_name
+    ├─ 查询 nw_channel(向上查层级) → 子会长、公会、商务
+    ├─ 去重检查(uk_active唯一索引)
+    │
+    ▼
+写入 cy_player_active_log
+```
+
+## 六、注意事项
+
+1. **去重**:`uk_active` 唯一索引保证同一玩家+游戏+角色+小时只有一条记录,重复上报用 `INSERT IGNORE` 或 `ON DUPLICATE KEY`
+2. **服务端时间**:`create_time` 使用服务端时间,不依赖客户端时间
+3. **渠道层级查询**:建议缓存渠道层级关系,避免每次请求都查 `nw_channel` 表
+4. **批量查询优化**:如后续需要查询某公会下的活跃数据,通过 `guild_id` 索引直接查,无需再关联渠道表

+ 236 - 0
code_version_md/v4.26/v4.26-需求内容.md

@@ -0,0 +1,236 @@
+# [V4.26] 代金券/礼包等多项优化
+
+- 需求来源:飞书 Wiki《[V4.26] 代金券/礼包等多项优化》
+- 飞书链接:https://bcnwdpujw6rb.feishu.cn/wiki/LxbGwNFDMiJyltk4aH9cqSuan7Y
+- 文档负责人:韩辉
+- 飞书最近修改时间:2026-08-05 18:14
+- 本地整理时间:2026-08-11
+
+## 1、代金券功能调整(三端)
+
+### 1.1 增加代金券可领取时间
+
+需求内容:
+
+- 代金券需要新增“可领取时间”配置,只有在指定时间范围内,SDK 用户才可以领取。
+- 如果当前时间已经超过可领取时间,代金券状态自动变更为“下架”。
+- “下架”在 SDK 客户端表现为不再展示该代金券。
+- 现有“代金券有效期”只负责限制代金券是否过期,不再承担“是否允许领取”的判断。
+
+需要确认:
+
+- 可领取时间是否按“开始时间 + 结束时间”配置。
+- 下架是只影响 SDK 展示,还是后台列表也标记为已下架。
+- 已经领取但未使用的代金券,在超过可领取时间后是否仍可正常使用;按当前描述,应不受领取时间影响,只受券有效期影响。
+
+### 1.2 新增可拆分代金券
+
+需求内容:
+
+- 新增一种代金券类型:可拆分代金券。
+- 用户持有一张可拆分代金券后,可以根据订单金额分多次抵扣。
+- 每次使用只扣除本次订单实际抵扣金额,剩余金额保留在券内。
+- 当代金券余额全部用完后,该券才变更为已使用/不可用。
+
+业务示例:
+
+- 用户有一张 100 元可拆分代金券。
+- 第一笔订单充值 30 元,抵扣 30 元,券剩余 70 元。
+- 第二笔订单充值 50 元,抵扣 50 元,券剩余 20 元。
+- 第三笔订单充值 80 元,最多只能再抵扣 20 元,券剩余 0 元并视为用完。
+
+当前系统差异:
+
+- 现有代金券是“一张券一次用完”的模式。
+- 当前 `cy_coupon_member` 主要通过 `is_use` 标记“未使用 / 使用中 / 已使用”,没有剩余金额字段。
+- 当前订单表只记录 `coupon_member_id` 和 `coupon_amount`,能知道本单用了哪张券、抵了多少钱,但不能完整追踪券余额变化过程。
+
+需要改造:
+
+- 代金券模板表需要区分普通券和可拆分券。
+- 用户券表需要记录券总额和剩余金额。
+- 需要新增券抵扣流水,记录每次订单抵扣金额、抵扣前余额、抵扣后余额、状态。
+- 下单、支付成功、取消订单、退款或回调重复通知时,都要能准确扣减或回退券余额。
+- SDK 客户端需要展示可拆分券剩余金额,并在支付时按余额抵扣。
+
+## 2、游戏管理筛选调整
+
+需求背景:
+
+- SDK 接入的游戏数量较多。
+- 已下架游戏不需要在默认视图中继续干扰运营操作。
+
+需求内容:
+
+- 游戏管理页面的“对接状态”筛选默认值调整为勾选:
+  - 对接中
+  - 上线
+  - 白名单限制
+- “下架”状态默认不勾选。
+
+需要确认:
+
+- 只是修改默认筛选条件,还是同时保存用户上次选择的筛选条件。
+- 已下架游戏是否仍允许通过手动勾选查询出来;按需求描述,应保留手动查询能力。
+
+## 3、充值记录管理 - 订单手动换绑页面增加备注
+
+需求内容:
+
+- 订单手动换绑页面新增备注字段。
+- 备注为选填。
+- 换绑操作提交后,备注需要随换绑记录保存。
+- 换绑列表需要展示该备注,方便后续追溯换绑原因。
+
+需要改造:
+
+- 换绑表单增加备注输入框。
+- 换绑提交接口接收并保存备注。
+- 换绑记录列表增加备注展示。
+- 如果已有换绑日志表,需要确认是在原表加备注字段,还是另存操作日志。
+
+## 4、充值记录管理 - 用户充值列表增加绑定手机号
+
+需求内容:
+
+- 用户充值列表增加“账号绑定手机号”字段。
+- 列表支持按手机号搜索。
+- 导出的充值数据同步增加:
+  - 手机号
+  - 游戏名
+
+需要改造:
+
+- 充值记录查询需要关联账号手机号。
+- 列表页新增手机号列和手机号搜索条件。
+- 导出逻辑同步补充手机号、游戏名字段。
+
+需要确认:
+
+- 搜索的是账号当前绑定手机号,还是订单发生时的历史手机号。
+- 导出中的手机号是否需要脱敏。
+
+## 5、数据页面支持实付金额排序
+
+需求内容:
+
+- 数据页面新增“新增玩家”“实付金额”的排序能力。
+- 排序状态包括:
+  - 默认状态
+  - 金额倒序
+  - 金额正序
+
+涉及页面:
+
+- 数据统计:
+  - 数据汇总
+  - 游戏留存
+  - 区服数据
+  - 区服留存
+- 推广管理:
+  - 推广员数据
+- 聚合统计管理:
+  - 游戏数据汇总
+  - 渠道数据汇总
+  - 游戏单日报表
+  - 游戏留存报表
+  - 游戏区服数据
+
+需要确认:
+
+- “实付金额”的排序口径是只按现金支付金额,还是按扣除代金券后的金额。
+- 新增玩家排序和实付金额排序是否互斥。
+- 排序是否需要支持导出后的文件顺序保持一致。
+
+## 6、数据表增加自定义字段显示
+
+需求背景:
+
+- 部分数据需要给 CP 提供后台截图。
+- 某些字段不适合直接展示给 CP,需要能隐藏。
+
+需求内容:
+
+- 数据表新增自定义字段显示功能。
+- 用户可以根据需要选择显示或隐藏部分字段。
+
+涉及页面:
+
+- 数据统计
+- 充值记录管理
+
+需要确认:
+
+- 字段隐藏只影响页面展示,还是导出也要同步隐藏。
+- 字段配置是否按用户保存,还是每次进入页面都使用默认字段。
+- 是否需要按角色或权限控制可配置字段范围。
+
+## 7、礼包调整(三端)
+
+需求背景:
+
+- 增加礼包发放类型,满足业务部门运营需求。
+
+需求内容:
+
+- SDK 后台礼包功能需要调整。
+- SDK 客户端礼包页面需要同步调整。
+
+需要确认:
+
+- 新增哪些礼包发放类型。
+- 后台配置项、客户端展示样式、领取规则和发放记录如何变化。
+- 是否需要兼容已有礼包类型和历史数据。
+
+## 8、SDK 与巨量包独立配置登录开关
+
+需求背景:
+
+- SDK 包存在提前上架、预下载但限制登录的情况。
+- 当前如果限制登录,会同时影响巨量包。
+- 巨量包无法登录会影响巨量审核。
+
+需求内容:
+
+- 游戏管理中的“封禁登录”选项调整为支持四种状态:
+  - 关
+  - 全部封禁
+  - 封禁 SDK
+  - 封禁巨量
+
+实现方向:
+
+- 兼容现有 `is_login` 字段。
+- 现有 `0` 可继续表示“关”。
+- 现有 `1` 可继续表示“全部封禁”。
+- 新增枚举值表示“封禁 SDK”“封禁巨量”。
+- 登录接口需要能识别当前请求来自 SDK 包还是巨量包,再按配置判断是否允许登录。
+
+需要确认:
+
+- 客户端请求是否会稳定传入区分 SDK/巨量的字段。
+- 如果不能稳定传入,需要通过包配置、appid/appkey 或服务端配置判断来源。
+- 白名单逻辑是否对“封禁 SDK”和“封禁巨量”同时生效。
+
+## 9、【玩家角色信息】调整
+
+需求内容:
+
+- 玩家角色信息列表增加字段:
+  - 最后活跃时间
+  - 登录设备
+- 为直播平台提供接口。
+
+需要确认:
+
+- 最后活跃时间取自最近一次登录、最近一次角色在线,还是最近一次支付/行为日志。
+- 登录设备展示设备类型、设备型号,还是设备唯一标识。
+- 直播平台接口需要返回哪些字段,以及是否只读。
+
+## 当前优先级建议
+
+建议优先梳理和评审以下三项:
+
+1. 可拆分代金券:涉及券余额、订单、支付回调、退款和客户端展示,改动最大。
+2. SDK 与巨量包独立登录开关:涉及线上登录兼容性,需要先确认包来源识别方式。
+3. 自定义字段显示:需要明确是否影响导出和权限,否则后续容易返工。

+ 6 - 0
crontabs/crontabfile.txt

@@ -0,0 +1,6 @@
+0 0 * * * bash /app/script/statistics_handle.sh
+*/30 * * * * bash /app/script/poly_complex_pay.sh
+5-59/30 * * * * bash /app/script/poly_complex_notice.sh
+10 0 * * * bash /app/script/complex_summary.sh
+* * * * * bash /app/script/pay_timeout.sh
+* * * * * sleep 30; bash /app/script/pay_timeout.sh

+ 315 - 0
debug_doc/项目错误经验记录.md

@@ -0,0 +1,315 @@
+# 项目错误经验记录
+
+本文档记录在开发过程中遇到的典型错误及其解决方案,供团队参考避免重复踩坑。
+
+---
+
+## 1. 支付金额计算顺序错误(2026-07-02)
+
+### 问题描述
+在 `GamePayService::orderData()` 方法中,`$payData['pay_amount']` 的计算依赖于 `Pay::calPayAmount()` 方法。当使用专属币(ptb_amt)且存在折扣时,计算结果偏高。
+
+### 错误代码
+```php
+// Pay.php 第166-176行
+$diff = bcsub($amount, $ptb_amount, 2);      // 先扣除专属币
+$raw_amount = bcmul($diff, $discount, 4);    // 再应用折扣
+```
+
+### 正确逻辑
+```php
+$discounted = bcmul($amount, $discount, 2);   // 先应用折扣
+$result = bcsub($discounted, $ptb_amount, 2); // 再扣除专属币
+```
+
+### 影响范围
+- **触发条件**:专属币 > 0 且折扣 < 1
+- **影响接口**:所有使用 `calPayAmount()` 的支付入口
+- **业务影响**:用户实际支付金额比正确金额偏高
+
+### 根本原因
+1. 开发时未理清业务规则:**折扣应作用于原价,专属币抵扣折扣后的金额**
+2. 缺乏单元测试覆盖边界场景
+3. 新旧逻辑迁移时引入错误(旧逻辑只处理折扣,新逻辑增加专属币扣除)
+
+### 解决方案
+调整 `calPayAmount()` 中的计算顺序,确保先折扣后扣除。
+
+### 预防措施
+- 涉及金额计算的修改必须编写单元测试
+- 明确业务规则文档化(折扣、抵扣的优先级)
+- 代码审查时重点关注计算顺序
+
+---
+
+## 2. API调用丢失折扣参数(2026-07-02)
+
+### 问题描述
+在 `api/controller/v1/Pay.php` 中调用 `calPayAmount()` 时未传递 `$discount` 参数,导致折扣配置失效。
+
+### 错误代码
+```php
+// api/controller/v1/Pay.php 第257、260行
+$arrPayAmount = $payLogic->calPayAmount($amount, $ptb_amt);  // 缺少 $discount
+$arrPayAmount = $payLogic->calPayAmount($amount);            // 缺少 $discount
+```
+
+### 影响范围
+- **触发条件**:所有通过API发起的支付请求
+- **业务影响**:游戏配置的折扣活动完全失效
+
+### 根本原因
+1. 方法签名 `$discount = 1` 的默认值掩盖了参数缺失
+2. 调用方未遵循方法契约(应传递完整参数)
+
+### 解决方案
+补充 `$discount` 参数传递:
+```php
+$discount = getDiscount($member_id, $game_id, $channel_id);
+$arrPayAmount = $payLogic->calPayAmount($amount, $ptb_amt, $discount);
+```
+
+### 预防措施
+- 关键业务参数不应有默认值(或默认值为异常值如 `null`)
+- 静态分析工具检查函数调用参数完整性
+- 统一支付入口,避免多处重复逻辑
+
+---
+
+## 3. 调试代码残留(2026-07-02)
+
+### 问题描述
+生产代码中残留 `dump()` 调试语句,影响性能和输出。
+
+### 错误代码
+```php
+// common.php 第639行
+dump($ancestors);
+
+// Pay.php 第550行
+dump($channel);
+```
+
+### 影响
+- 输出额外内容,可能破坏JSON响应
+- 性能损耗(尤其循环中)
+- 暴露内部数据结构
+
+### 预防措施
+- 代码提交前搜索 `dump(`、`var_dump(`、`print_r(`
+- IDE配置保存时自动移除调试语句
+- Code Review 时重点关注
+
+---
+
+## 4. 登录时未添加新设备到常用设备列表(2026-07-02)
+
+### 问题描述
+用户登录时,新的设备号不会被添加到常用设备列表中,数据库中 `nw_subaccount.recent_devices` 字段始终只保存一个设备。
+
+### 错误代码
+```php
+// api/controller/v1/Login.php 第594行
+$memberDevice->addDevice($userinfo['id'], $gameid, $imeil, $device, false, true);
+//                                                                              ↑
+//                                                              $onlyUpdateExisting = true
+```
+
+### 正确逻辑
+```php
+$memberDevice->addDevice($userinfo['id'], $gameid, $imeil, $device, false, false);
+//                                                                              ↑
+//                                                              $onlyUpdateExisting = false
+```
+
+### 影响范围
+- **触发条件**:用户使用新设备登录
+- **影响接口**:`api/controller/v1/Login.php` 登录接口
+- **业务影响**:常用设备列表无法正常维护,设备切换验证功能异常
+
+### 根本原因
+1. `MemberDevice::addDevice()` 的 `$onlyUpdateExisting` 参数含义混淆:
+   - `true` = 仅更新已存在设备,不添加新设备
+   - `false` = 不存在时添加新设备
+2. 登录接口错误地传入 `true`,与注册接口逻辑不一致
+3. 注册接口正确传入 `false`(或使用默认值)
+
+### 解决方案
+将 Login.php 中 `$onlyUpdateExisting` 参数改为 `false`,使登录与注册保持一致,都允许添加新设备。
+
+### 预防措施
+- 关键业务参数应使用有意义的常量或枚举,避免裸露的 `true/false`
+- 相同功能的代码应保持一致的调用方式
+- 代码审查时对比相似场景的实现
+
+---
+
+## 5. GamePayService 中 real_amount 与 pay_amount 概念混淆(2026-07-02)
+
+### 问题描述
+在 `GamePayService::gamePay()` 方法中,`$payData['real_amount']` 和 `$payData['pay_amount']` 两个字段容易混淆,需要明确区分其业务含义和计算逻辑。
+
+### 字段定义对比
+
+| 字段 | 含义 | 考虑代金券 | 考虑币抵扣 | 考虑折扣 |
+|------|------|------------|------------|----------|
+| `pay_amount` | 折扣后应付金额 | ❌ | ❌ | ✅ |
+| `real_amount` | 实际需第三方支付金额 | ✅ | ✅ | ✅ |
+
+### 计算公式
+
+```
+pay_amount  = amount * discount
+real_amount = (amount - coupon_amount - ptb_amount/coin_amount) * discount
+```
+
+### 数据流向
+
+```
+$data['amount'] (原始充值金额)
+       ↓
+   代金券抵扣 → $amount = amount - coupon_amount
+       ↓
+   calPayAmount() / calCoinPayAmount() → $arrPayAmount
+       ↓
+   orderData() → $payData['real_amount'] + $payData['pay_amount']
+```
+
+### 关键代码位置
+
+1. **代金券抵扣**:`GamePayService.php` 第215-235行
+2. **币抵扣计算**:`Pay::calPayAmount()` / `Pay::calCoinPayAmount()`
+3. **orderData赋值**:`GamePayService.php` 第583-619行
+
+### 易错点
+
+1. **`pay_amount` 不考虑代金券和币抵扣**:仅反映折扣因素
+2. **`real_amount` 是最终支付金额**:传给第三方支付渠道的金额
+3. **`real_amount = 0` 时直接标记支付成功**:无需调用第三方支付
+
+### 具体示例
+
+假设:充值金额 `100元`,折扣 `0.9`,代金券 `10元`,专属币 `20元`
+
+```
+原始金额:     100元
+代金券抵扣后: 100 - 10 = 90元
+折扣后应付:   100 * 0.9 = 90元 → pay_amount
+实际支付:     (100 - 10 - 20) * 0.9 = 63元 → real_amount
+```
+
+### 预防措施
+
+- 修改金额计算逻辑前,先明确字段的业务定义
+- 建议在代码注释中标注字段含义和计算公式
+- 涉及金额的接口必须编写单元测试覆盖边界场景
+
+---
+
+*最后更新:2026-07-02*
+---
+
+## 6. 定时任务循环中使用致命错误导致后续任务中断(2026-07-03)
+
+### 问题描述
+在 PolyChannelSmsWarn.php 定时任务脚本中,当处理多个渠道的短信预警时,第一个渠道发送失败后,后续渠道不会被执行。
+
+### 错误代码
+
+#### 问题1:致命错误导致脚本终止
+`php
+// fetchContent 方法
+if(\ === false) {
+    // ❌ E_USER_ERROR 是致命错误,会立即终止整个 PHP 脚本执行
+    trigger_error("[CURL_" . curl_errno(\) . "]: " . curl_error(\), E_USER_ERROR);
+}
+`
+
+#### 问题2:number_format 重复调用
+`php
+// execute 方法中的短信发送逻辑
+\ = number_format(\['total_advance'] - \, 2);
+// 返回值: "-42,299.56"(带逗号的字符串)
+
+\ = ['name' => \['channel_name'], 'money' => number_format(\, 2)];
+//                                                     ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
+//                               对带逗号的字符串再次 number_format 会报错
+//                               报错信息:A non well formed numeric value encountered
+`
+
+### 正确逻辑
+
+#### 修复1:改为抛出异常
+`php
+// fetchContent 方法
+if(\ === false) {
+    \ = "[CURL_" . curl_errno(\) . "]: " . curl_error(\);
+    curl_close(\);
+    // ✅ 抛出异常,可被 try-catch 捕获
+    throw new \RuntimeException(\);
+}
+`
+
+#### 修复2:添加 try-catch 捕获异常
+`php
+foreach ( \ as \ ) {
+    try {
+        // ... 原有的发送逻辑 ...
+        
+    } catch (\Exception \) {
+        // ✅ 记录错误日志,继续执行下一个渠道
+        \->writeln(date('H:i:s')." [\['channel_id']] 处理异常: ".\->getMessage());
+        continue;
+    }
+}
+`
+
+#### 修复3:避免 number_format 重复调用
+`php
+\ = number_format(\['total_advance'] - \, 2);
+
+// ❌ 错误写法:对已经格式化的字符串再次格式化
+// \ = ['name' => \['channel_name'], 'money' => number_format(\, 2)];
+
+// ✅ 正确写法:直接使用已格式化的值
+\ = ['name' => \['channel_name'], 'money' => \];
+`
+
+### 影响范围
+- **触发条件**:定时任务处理多个渠道,且至少有一个渠道发送失败
+- **影响脚本**:pplication/crontab/PolyChannelSmsWarn.php
+- **业务影响**:短信预警通知不完整,可能导致部分渠道余额不足未被及时发现
+
+### 根本原因
+1. **致命错误处理不当**:在循环中使用 E_USER_ERROR 会导致整个脚本终止,无法容错
+2. **number_format 理解偏差**:
+umber_format() 返回的是带千分位逗号的字符串,不能直接再次格式化
+
+### 预防措施
+
+#### 代码层面
+- **循环中禁止使用致命错误**:E_USER_ERROR、xit()、die() 等会导致脚本终止的语句
+- **使用 try-catch 包裹可能失败的操作**:HTTP请求、数据库操作、文件操作等
+- **number_format 返回值是字符串**:如需再次参与计算,应使用原始数值而非格式化后的字符串
+
+#### 测试层面
+- **多场景测试**:测试部分成功、部分失败的混合场景
+- **边界值测试**:测试负数、零、超大数等特殊情况
+
+#### 架构层面
+- **批量任务设计**:单个任务失败不应影响其他任务
+- **日志记录**:记录每个子任务的执行结果,便于排查问题
+
+### 相关文件
+- www/new_sdk/application/crontab/PolyChannelSmsWarn.php
+
+### 关键教训
+
+> **在循环处理多个任务时,永远不要使用会导致脚本终止的错误处理方式(如 E_USER_ERROR),而应该使用 	ry-catch 捕获异常,确保单个失败不会影响其他任务的执行。**
+> 
+> **
+umber_format() 返回的是带千分位的字符串,不是数值类型,不能直接再次格式化或参与需要数值的运算。**
+
+---
+
+*最后更新:2026-07-03*

+ 17 - 0
docker-compose.yml

@@ -0,0 +1,17 @@
+version: "3.1"
+services:
+    new_sdk:
+      build: .
+      container_name: new-sdk
+      working_dir: /app
+      volumes:
+        - .:/app
+      ports:
+        - "66:80"
+        - "67:443"
+      networks:
+        - default
+
+networks:
+  default:
+

+ 165 - 0
docs/login-flow-mermaid.md

@@ -0,0 +1,165 @@
+# API 登录流程图 (Mermaid)
+
+## 1. 整体请求处理流程
+
+```mermaid
+flowchart TD
+    A[客户端请求] --> B[Api._initialize]
+    B --> B1[设备识别]
+    B1 --> B2[AES-128-ECB 解密 body]
+    B2 --> B3{解密成功?}
+    B3 -->|否| B4[尝试明文 input]
+    B3 -->|是| B5[JSON 解析参数]
+    B4 --> B6[签名校验 MD5]
+    B5 --> B6
+    B6 -->|失败| B7[返回 -124 签名拒绝]
+    B6 -->|成功| C{路由分发}
+    C -->|/login/index| D[普通登录]
+    C -->|/login/third| E[第三方登录]
+    C -->|/login/checkToken| F[Token校验]
+    C -->|/login/logout| G[退出登录]
+```
+
+## 2. 普通登录 index() 流程
+
+```mermaid
+flowchart TD
+    A[接收参数] --> B{海外游戏IP限制}
+    B -->|国内IP| B1[返回 -110 禁止登录]
+    B -->|通过| C{参数校验}
+    C -->|缺少| C1[返回 -100]
+    C -->|通过| D{渠道校验}
+    D -->|不存在/禁用| D1[返回 -120]
+    D -->|level≠3| D2[返回 -120 非推广员]
+    D -->|通过| E{游戏校验}
+    E -->|不存在/下架| E1[返回 -120]
+    E -->|登录白名单| E2[检查白名单]
+    E -->|通过| F{渠道封禁判断}
+    F -->|封禁| F1[返回 -120]
+    F -->|通过| G[密码加密验证]
+    G -->|不匹配| G1[返回 -120]
+    G -->|通过| H{账号冻结检查}
+    H -->|主账号冻结| H1[返回 -120]
+    H -->|子账号冻结| H2[返回 -120]
+    H -->|通过| I[风控策略检查]
+    I -->|触发风控| I1[返回 -120 风控冻结]
+    I -->|通过| J[loginProcess]
+```
+
+## 3. 第三方登录 third() 流程
+
+```mermaid
+flowchart TD
+    A[接收参数] --> B{参数校验}
+    B -->|缺少| B1[返回错误]
+    B -->|通过| C{渠道/游戏校验}
+    C -->|失败| C1[返回错误]
+    C -->|通过| D{type_id}
+    D -->|1=微信| E[查 wx_uid]
+    D -->|2=QQ| F[查 qq_uid]
+    D -->|3=抖音| G[查 dy_uid]
+    E --> H{用户存在?}
+    F --> H
+    G --> H
+    H -->|是| I[loginProcess]
+    H -->|否| J[自动生成随机用户名]
+    J --> K[创建账号]
+    K --> L[插入历史记录]
+    L --> M[插入实名扩展]
+    M --> N[loginProcess]
+```
+
+## 4. loginProcess 公共处理流程
+
+```mermaid
+flowchart TD
+    A[loginProcess 入口] --> B{IP封禁检查}
+    B -->|封禁| B1[钉钉告警 + 拒绝]
+    B -->|通过| C{IMEI封禁检查}
+    C -->|封禁| C1[钉钉告警 + 拒绝]
+    C -->|通过| D{渠道封禁检查}
+    D -->|封禁| D1[拒绝登录]
+    D -->|通过| E{防沉迷检查}
+    E -->|未成年人| F{时间窗口判断}
+    F -->|不在允许时段| F1[返回未成年限制]
+    F -->|在允许时段| G[设置 offline_time]
+    E -->|成年人/未实名| H[继续]
+    G --> H
+    H --> I[开启事务]
+    I --> J{首次登录该游戏?}
+    J -->|是| K[创建 MemberChannelGame 绑定]
+    J -->|否| L[更新最后登录时间]
+    K --> M[子账号处理]
+    L --> M
+    M --> N[更新设备记录]
+    N --> O[写入登录日志]
+    O --> P[生成 Token]
+    P --> P1[Token = auth_code 加密]
+    P1 --> P2[Redis: token→imei 15天]
+    P2 --> P3[Redis: token随机码 15天]
+    P3 --> Q{中宣部防沉迷?}
+    Q -->|是| R[上报登录状态]
+    Q -->|否| S[返回 token + 用户信息]
+    R --> S
+```
+
+## 5. Token 校验机制
+
+```mermaid
+flowchart TD
+    A[checkLogin] --> B{是否需要登录校验}
+    B -->|noNeedLogin| C[跳过]
+    B -->|需要| D[获取 token + imeil]
+    D --> E{Redis 获取 token→imei}
+    E -->|不存在| F[返回 -133 登录失效]
+    E -->|存在| G{imei 匹配?}
+    G -->|不匹配| H[返回 -134 异地登录]
+    G -->|匹配| I[解密 token]
+    I --> J[提取 userid/username/gameid/sub_username/随机码]
+    J --> K{Redis 获取随机码}
+    K -->|不存在| L[返回 -137 登录异常]
+    K -->|存在| M{随机码匹配?}
+    M -->|不匹配| N[返回 -138 登录异常]
+    M -->|匹配| O[校验通过 注入用户信息]
+```
+
+## 6. 数据模型关系
+
+```mermaid
+erDiagram
+    Members ||--o{ Subaccount : "1:N 子账号"
+    Members ||--o{ MemberChannelGame : "1:N 游戏绑定"
+    Members ||--o{ Logininfo : "1:N 登录日志"
+    Members ||--o| MembersTwo : "1:1 实名信息"
+    Members ||--o| MemberDevice : "1:N 设备记录"
+    Game ||--o{ Subaccount : "1:N"
+    Game ||--o{ GameBanned : "1:N 封禁"
+    Game ||--o{ GameRestrict : "1:N 防沉迷"
+    Channel ||--o{ MemberChannelGame : "1:N"
+    Subaccount {
+        int id PK
+        int member_id FK
+        int game_id FK
+        string sub_username
+        int channel_id
+        int auth_status
+        string real_name
+        string idcard_num
+    }
+    Members {
+        int id PK
+        string username
+        string password
+        string wx_uid
+        string qq_uid
+        string dy_uid
+        int flag
+        int channel_id
+    }
+    MemberChannelGame {
+        int mcgr_id PK
+        int member_id FK
+        int game_id FK
+        int channel_id FK
+    }
+```

+ 485 - 0
docs/login-flow-plantuml.puml

@@ -0,0 +1,485 @@
+@startuml login-flow
+!theme plain
+skinparam backgroundColor #FEFEFE
+skinparam shadowing false
+skinparam defaultFontName "Microsoft YaHei"
+skinparam defaultFontSize 12
+
+' ============================================================
+' 1. 整体请求处理流程
+' ============================================================
+title 1. 整体请求处理流程
+
+|客户端|
+start
+:发送请求;
+
+|服务端 - Api._initialize|
+:设备识别 (header/body);
+:AES-128-ECB 解密 body;
+
+if (解密成功?) then (是)
+  :JSON 解析参数;
+else (否)
+  :尝试明文 input();
+endif
+
+:MD5 签名校验;
+
+if (签名校验通过?) then (是)
+else (否)
+  :返回 -124 签名拒绝;
+  stop
+endif
+
+switch (路由分发)
+case (/login/index)
+  :普通登录 index();
+case (/login/third)
+  :第三方登录 third();
+case (/login/checkToken)
+  :Token 校验 checkToken();
+case (/login/logout)
+  :退出登录 logout();
+endswitch
+
+stop
+
+@enduml
+
+
+' ============================================================
+' 2. 普通登录 index() 流程
+' ============================================================
+@startuml login-index
+!theme plain
+skinparam backgroundColor #FEFEFE
+skinparam shadowing false
+skinparam defaultFontName "Microsoft YaHei"
+
+title 2. 普通登录 index() 流程
+
+start
+:接收参数\nusername, password, imeil,\ngameid, device, channel_id;
+
+if (海外游戏 && 国内IP?) then (是)
+  :返回 -110 国内用户禁止登录;
+  stop
+endif
+
+if (参数完整?) then (否)
+  :返回 -100 缺少参数;
+  stop
+endif
+
+if (渠道存在且启用?) then (否)
+  :返回 -120 渠道不存在或已禁用;
+  stop
+endif
+
+if (渠道 level=3?) then (否)
+  :返回 -120 非推广员渠道;
+  stop
+endif
+
+if (游戏存在且未下架?) then (否)
+  :返回 -120 游戏信息不存在;
+  stop
+endif
+
+if (游戏开启登录白名单?) then (是)
+  if (用户在白名单中?) then (否)
+    :返回 -120 当前游戏禁止登录;
+    stop
+  endif
+endif
+
+if (渠道被封禁?) then (是)
+  :返回 -120 渠道禁止登录;
+  stop
+endif
+
+:密码加密 auth_code(password, ENCODE, auth_key);
+:查询用户 where username + password;
+
+if (用户存在?) then (否)
+  :返回 -120 账号不存在或密码不正确;
+  stop
+endif
+
+if (主账号冻结 flag=1?) then (是)
+  :返回 -120 账号已冻结;
+  stop
+endif
+
+if (子账号冻结?) then (是)
+  :返回 -120 子账号已冻结;
+  stop
+endif
+
+:FkStrategiesService 风控策略检查;
+
+if (触发风控?) then (是)
+  :返回 -120 风控冻结;
+  stop
+endif
+
+:调用 loginProcess();
+stop
+
+@enduml
+
+
+' ============================================================
+' 3. 第三方登录 third() 流程
+' ============================================================
+@startuml login-third
+!theme plain
+skinparam backgroundColor #FEFEFE
+skinparam shadowing false
+skinparam defaultFontName "Microsoft YaHei"
+
+title 3. 第三方登录 third() 流程
+
+start
+:接收参数\nusername(第三方UID), imeil, gameid,\ndevice, channel_id, type_id;
+
+if (参数校验通过?) then (否)
+  :返回错误;
+  stop
+endif
+
+if (渠道/游戏校验通过?) then (否)
+  :返回错误;
+  stop
+endif
+
+switch (type_id)
+case (1 = 微信)
+  :查询 wx_uid;
+case (2 = QQ)
+  :查询 qq_uid;
+case (3 = 抖音)
+  :查询 dy_uid;
+endswitch
+
+if (用户已存在?) then (是)
+  :调用 loginProcess();
+else (否)
+  :自动生成随机用户名 (11位);
+  :创建账号 (密码固定, fromflag=type);
+  :插入 cy_member_history;
+  :插入 cy_memberstwo 实名扩展;
+  :调用 loginProcess();
+endif
+
+stop
+
+@enduml
+
+
+' ============================================================
+' 4. loginProcess 公共处理流程
+' ============================================================
+@startuml login-process
+!theme plain
+skinparam backgroundColor #FEFEFE
+skinparam shadowing false
+skinparam defaultFontName "Microsoft YaHei"
+
+title 4. loginProcess 公共处理流程
+
+start
+:接收 userinfo, gameid, imeil, device, channelId;
+
+if (IP 被封禁?) then (是)
+  :钉钉告警;
+  :返回 IP禁止登录;
+  stop
+endif
+
+if (IMEI 被封禁?) then (是)
+  :钉钉告警;
+  :返回 设备禁止登录;
+  stop
+endif
+
+if (渠道被封禁?) then (是)
+  :返回 渠道禁止登录;
+  stop
+endif
+
+if (开启防沉迷? isPreventHook==2) then (是)
+  :查询实名信息 MembersTwo + Subaccount;
+
+  if (中宣部实名认证? game_auth_type==1) then (是)
+    :使用子账号的 auth_status 判断;
+  endif
+
+  if (未实名 或 未成年人?) then (是)
+    if (法定节假日?) then (是)
+      if (20:00-21:00?) then (是)
+        :设置 offline_time = 21:00;
+      else (否)
+        :返回 未成年限制;
+        stop
+      endif
+    else (否)
+      if (周五/六/日?) then (是)
+        if (20:00-21:00?) then (是)
+          :设置 offline_time = 21:00;
+        else (否)
+          :返回 未成年限制;
+          stop
+        endif
+      else (否)
+        :返回 未成年限制;
+        stop
+      endif
+    endif
+  endif
+endif
+
+:Db::startTrans() 开启事务;
+
+if (首次登录该游戏?) then (是)
+  :创建 MemberChannelGame 绑定;
+  :插入玩家游戏渠道绑定记录;
+else (否)
+  :更新最后登录时间;
+endif
+
+if (子账号已存在 sub_username?) then (是)
+  :更新子账号时间和设备;
+else (否)
+  :生成 sub_username = userid_APP_STATUS;
+  :创建或更新 Subaccount 记录;
+endif
+
+:MemberDevice::addDevice() 更新设备列表;
+:Logininfo::limitInsert() 写入登录日志;
+:saveDevices() 收集设备信息;
+:更新 Members 最后登录时间;
+:Db::commit() 提交事务;
+
+:生成 Token;
+note right
+  Token = auth_code(
+    userid|username|gameid|
+    sub_username|随机码|sdk,
+    ENCODE, auth_key
+  )
+end note
+
+:Redis 存储;
+note right
+  token → imeil (15天)
+  token|sdk|{userid}|{gameid} → 随机码 (15天)
+end note
+
+if (中宣部防沉迷?) then (是)
+  :Authentication::loginout() 上报登录状态;
+endif
+
+:返回 token, username, sub_username, offline_time;
+stop
+
+@enduml
+
+
+' ============================================================
+' 5. Token 校验机制
+' ============================================================
+@startuml token-check
+!theme plain
+skinparam backgroundColor #FEFEFE
+skinparam shadowing false
+skinparam defaultFontName "Microsoft YaHei"
+
+title 5. Token 校验机制 (checkLogin)
+
+start
+
+if (在 noNeedLogin 列表中?) then (是)
+  :跳过校验;
+  stop
+endif
+
+:获取 token 和 imeil;
+
+if (token 为空?) then (是)
+  :返回 -132 未登录;
+  stop
+endif
+
+:Redis 获取 token→imei;
+
+if (Redis 中存在?) then (否)
+  :返回 -133 登录失效;
+  stop
+endif
+
+if (imei 匹配?) then (否)
+  :返回 -134 异地登录;
+  stop
+endif
+
+:解密 token\nauth_code(token, DECODE, auth_key);
+
+:提取 userid, username, gameid,\nsub_username, 随机码, type;
+
+:Redis 获取 token|{type}|{userid}|{gameid}→随机码;
+
+if (随机码存在?) then (否)
+  :删除 token;
+  :返回 -137 登录异常;
+  stop
+endif
+
+if (随机码匹配?) then (否)
+  :删除 token;
+  :返回 -138 登录异常;
+  stop
+endif
+
+:校验通过;
+:注入 userid, gameid, username, token, sub_username;
+
+stop
+
+@enduml
+
+
+' ============================================================
+' 6. 数据模型关系
+' ============================================================
+@startuml login-er
+!theme plain
+skinparam backgroundColor #FEFEFE
+skinparam shadowing false
+skinparam defaultFontName "Microsoft YaHei"
+
+title 6. 数据模型关系 (ER 图)
+
+entity "Members" as M {
+  *id : int <<PK>>
+  --
+  username : string
+  password : string
+  wx_uid : string
+  qq_uid : string
+  dy_uid : string
+  flag : int
+  channel_id : int
+  reg_time : int
+  login_time : int
+  nickname : string
+}
+
+entity "Subaccount" as S {
+  *id : int <<PK>>
+  --
+  member_id : int <<FK>>
+  game_id : int <<FK>>
+  sub_username : string
+  channel_id : int
+  auth_status : int
+  real_name : string
+  idcard_num : string
+  auth_pi : string
+  flag : int
+  create_time : int
+  update_time : int
+  last_imeil : string
+}
+
+entity "MemberChannelGame" as MCG {
+  *mcgr_id : int <<PK>>
+  --
+  member_id : int <<FK>>
+  game_id : int <<FK>>
+  channel_id : int <<FK>>
+  mcgr_imeil : string
+  mcgr_ip : string
+  mcgr_createtime : int
+  update_time : int
+}
+
+entity "MembersTwo" as MT {
+  *id : int <<PK>>
+  --
+  userid : int <<FK>>
+  username : string
+  realname : string
+  idcard : string
+  create_time : int
+}
+
+entity "Logininfo" as LI {
+  *id : int <<PK>>
+  --
+  userid : int <<FK>>
+  sub_id : int
+  gameid : int
+  imeil : string
+  device : int
+  channel_id : int
+  login_time : int
+  ip : string
+}
+
+entity "Game" as G {
+  *id : int <<PK>>
+  --
+  name : string
+  cooperation_status : int
+  is_login : int
+  game_auth_type : string
+  game_bizid : string
+}
+
+entity "Channel" as C {
+  *id : int <<PK>>
+  --
+  name : string
+  mark : string
+  level : int
+  status : int
+}
+
+entity "GameBanned" as GB {
+  *id : int <<PK>>
+  --
+  game_id : int <<FK>>
+  banned_type : string
+  banned_device : string
+}
+
+entity "GameRestrict" as GR {
+  *id : int <<PK>>
+  --
+  game_id : int <<FK>>
+  restrict_type : string
+  restrict_status : int
+}
+
+entity "MemberDevice" as MD {
+  *id : int <<PK>>
+  --
+  member_id : int <<FK>>
+  game_id : int <<FK>>
+  imeil : string
+  device : int
+}
+
+M ||--o{ S : "1:N 子账号"
+M ||--o{ MCG : "1:N 游戏绑定"
+M ||--o{ LI : "1:N 登录日志"
+M ||--o| MT : "1:1 实名信息"
+M ||--o{ MD : "1:N 设备记录"
+G ||--o{ S : "1:N"
+G ||--o{ GB : "1:N 封禁"
+G ||--o{ GR : "1:N 防沉迷"
+C ||--o{ MCG : "1:N"
+
+@enduml

+ 27 - 0
key/sdkapi.qmgames.cn.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvai7NlVvNolkvOrZKofgoVAbv8qDD5BJvnwcMQS9+bp677BG
+pvHi7vyONQu5aBGKASlXHqUdYdoog69pJcORr3dyFrD2GG8m5iQx9B5TvA21UFBL
+ni3wBw4NUtvotKs3ym7pGI5o2L0NDJePPll/2zymL0BnzPzmCeVNQ9qM1+PZTM9f
+X1DTm12rJJqfzuc6BhP4G+VZUaCX//rlizhRuOcrEg4JVoFfb5AMh/Y5zOkQqykz
+VUT4YB6oe+eA6bgVauqsxGl3QjbAJOdBc5XhgF7/eDbQ+gDio5DiJqxip1jwDFqR
+dv0WA6C6XOXvhl9QXMF93k/d65Lo6XV5+UO8RwIDAQABAoIBABoOkIoKetkXT0vV
+SDjbqjT/eJCFFjSRMlCFZKonaKJiyHkd0tck7HTBJhiLn9F4d2lpcJq5PKww+Nf+
+xYd2sxFUa8xKS8VXoBSGbIaJlB+GEp8SKRF2Zj/R4mq74+DOlT+utT+2cRk2ABD2
+0atTSImqb3qWcqxrIn/BjTgBUtBmNNWmessn5Znd3cYTJUvuZr5WJbnQb+9GvBwc
+nmEaOCoberwiVEENGeSi2FKdQ42xsp6Xg3p5I36Hx04wpNQ28jidXLsiSIayiTNF
+4TIExXNaI0fOInvFXU1nFOd31emchz4AMg6PlzFalAKBvETKb6joh419LM9cPj54
+/Hs7OSECgYEA6Kx9AINqOhDnn1tH80GQ/DiDkoVRoOZAJonDJojmyYJwrjT/U2aP
+ZcMV0YV/F5nciKGjffCGIPe7STeXfX5qARd7vSbf+U3rGQ9GUs0OX3i7OolQN56i
+VY8/Ty1AWfBbr9AYJ2aLdMnGc4hqP5jskLPk9dSy20vBC9iyawZZee0CgYEA0KxI
+WZS+qPUnBA388QrJXv2IF8zDb6RkU8/QaTW0x6nK2XLOVH2cXmL9GnEwydzr/xnz
+vNfR5ESy0LXtcJKpOCmX9Alg5qtPLl5TlZrfjEMEXNFKnSthejxW96oE9TnvAsAy
+8h4LVtJPfZ2d9DStpYVfiDUmcOyrYMxR7fY4uIMCgYEAoJbUm/MX8UXTp+BHs7SQ
+ThTvcsSDh4sDwdWZlNIuf+yAuywnAbgqxvCz8W2zP2XZmY/+mKDW4pA5eppc+6ex
+oFlF9VKMibRmzGAHiISxI+HfT9KQRCNAJc99wbcY0Dqd/aWh1ifnns7ZIsqHkqeI
+t4MCR03ruIH7lqKU2luCjC0CgYAvZqM8rhwtNxr4wdCuVHvvtvm+JkZSyu37V96I
+t6lsHXa1tG2IJcxqaGQEKKgjgqqdFSEXyZKlHrHb1SethbMozdH1nlfDrjBbYRPt
+jPOw4dxQ1T30u9D1ulUrBxT/O3y+jDhGZWq/+yARccdanMjBK63KvAAScMBxSZhm
+zKKPzwKBgQDMWQ3c6IoMi3YqWf/RYGxhO65nqMvtjnEKJTiYN7FDqaPgb0EBMl3C
+oWlgK2wvSgx2Rq5Oafw45yT/NlnRAlp+p/gRmkKjvrv8nDaZNy5rDEYp5DCRo16d
+sxhsfWLZd4tpUA/DWJ0bIvtUymmUdaWXNJZ4lEbVW0Z09MKIQuRdvA==
+-----END RSA PRIVATE KEY-----

+ 59 - 0
key/sdkapi.qmgames.cn.pem

@@ -0,0 +1,59 @@
+-----BEGIN CERTIFICATE-----
+MIIE9DCCA9ygAwIBAgISA4IayGTlnOHcy1hO02KE1aVTMA0GCSqGSIb3DQEBCwUA
+MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
+EwNSMTEwHhcNMjQwNzE3MDAwMjM0WhcNMjQxMDE1MDAwMjMzWjAXMRUwEwYDVQQD
+DAwqLnFtZ2FtZXMuY24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9
+qLs2VW82iWS86tkqh+ChUBu/yoMPkEm+fBwxBL35unrvsEam8eLu/I41C7loEYoB
+KVcepR1h2iiDr2klw5Gvd3IWsPYYbybmJDH0HlO8DbVQUEueLfAHDg1S2+i0qzfK
+bukYjmjYvQ0Ml48+WX/bPKYvQGfM/OYJ5U1D2ozX49lMz19fUNObXaskmp/O5zoG
+E/gb5VlRoJf/+uWLOFG45ysSDglWgV9vkAyH9jnM6RCrKTNVRPhgHqh754DpuBVq
+6qzEaXdCNsAk50FzleGAXv94NtD6AOKjkOImrGKnWPAMWpF2/RYDoLpc5e+GX1Bc
+wX3eT93rkujpdXn5Q7xHAgMBAAGjggIcMIICGDAOBgNVHQ8BAf8EBAMCBaAwHQYD
+VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0O
+BBYEFPIOD0qjTDI5PaT3yM9ciPlLZNxwMB8GA1UdIwQYMBaAFMXPRqTq9MPAemyV
+xC2wXpIvJuO5MFcGCCsGAQUFBwEBBEswSTAiBggrBgEFBQcwAYYWaHR0cDovL3Ix
+MS5vLmxlbmNyLm9yZzAjBggrBgEFBQcwAoYXaHR0cDovL3IxMS5pLmxlbmNyLm9y
+Zy8wIwYDVR0RBBwwGoIMKi5xbWdhbWVzLmNuggpxbWdhbWVzLmNuMBMGA1UdIAQM
+MAowCAYGZ4EMAQIBMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYASLDja9qmRzQP
+5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGQvjZn5gAABAMARzBFAiEA8AUmhHWm
+bW/AM50FRAnR0hXP9S77ewHMzjIHxWXWTFsCIDzvFELWcDyn77ueC1Qy/zQhROEq
+7kgfevRMv6zrMWFGAHYAPxdLT9ciR1iUHWUchL4NEu2QN38fhWrrwb8ohez4ZG4A
+AAGQvjZn6AAABAMARzBFAiBemzp/Vy1fZ0BbW/m6iJ7wiSH/5STksqIB/XmIJTXg
+AAIhAM+5qk+WVUTKEEH4ispiywBII5+wRR8cmRHn/P4vCBJgMA0GCSqGSIb3DQEB
+CwUAA4IBAQBDqw2C8NAMHbIVJapKqQJU3tbzeRWwLL6SRZauqQLrDEh9A1CfJbPN
+l/7Z7pMFj/uZ5/06JaA2jgWlmAj+5jQj9Ku9u9XRcQSa4KwL4+7choQqTBpLwoNK
+Tba6dz2oJtJ7v3EQ2uhL5nfgmd4i1IE8SnKoi3QSAFaB0w16D7kaTLJiug83bYAL
+vESNKdNuEdBHcqO5+vXKcGU6QBjmeOblRCi9NExeSWdCE33x8LjvhrI5IZvrlFCY
+NE+8XBTJ9v/L/8Ob/f+t4GaHOGZqVbB06Z+SHoy69+OOm/sYhF+Vkflb7oMWNfDe
+NudG44+yd9SiCMgI/pb2X9mK2WsEp4mD
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFBjCCAu6gAwIBAgIRAIp9PhPWLzDvI4a9KQdrNPgwDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
+WhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
+RW5jcnlwdDEMMAoGA1UEAxMDUjExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAuoe8XBsAOcvKCs3UZxD5ATylTqVhyybKUvsVAbe5KPUoHu0nsyQYOWcJ
+DAjs4DqwO3cOvfPlOVRBDE6uQdaZdN5R2+97/1i9qLcT9t4x1fJyyXJqC4N0lZxG
+AGQUmfOx2SLZzaiSqhwmej/+71gFewiVgdtxD4774zEJuwm+UE1fj5F2PVqdnoPy
+6cRms+EGZkNIGIBloDcYmpuEMpexsr3E+BUAnSeI++JjF5ZsmydnS8TbKF5pwnnw
+SVzgJFDhxLyhBax7QG0AtMJBP6dYuC/FXJuluwme8f7rsIU5/agK70XEeOtlKsLP
+Xzze41xNG/cLJyuqC0J3U095ah2H2QIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB
+hjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB
+/wIBADAdBgNVHQ4EFgQUxc9GpOr0w8B6bJXELbBeki8m47kwHwYDVR0jBBgwFoAU
+ebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC
+hhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG
+A1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN
+AQELBQADggIBAE7iiV0KAxyQOND1H/lxXPjDj7I3iHpvsCUf7b632IYGjukJhM1y
+v4Hz/MrPU0jtvfZpQtSlET41yBOykh0FX+ou1Nj4ScOt9ZmWnO8m2OG0JAtIIE38
+01S0qcYhyOE2G/93ZCkXufBL713qzXnQv5C/viOykNpKqUgxdKlEC+Hi9i2DcaR1
+e9KUwQUZRhy5j/PEdEglKg3l9dtD4tuTm7kZtB8v32oOjzHTYw+7KdzdZiw/sBtn
+UfhBPORNuay4pJxmY/WrhSMdzFO2q3Gu3MUBcdo27goYKjL9CTF8j/Zz55yctUoV
+aneCWs/ajUX+HypkBTA+c8LGDLnWO2NKq0YD/pnARkAnYGPfUDoHR9gVSp/qRx+Z
+WghiDLZsMwhN1zjtSC0uBWiugF3vTNzYIEFfaPG7Ws3jDrAMMYebQ95JQ+HIBD/R
+PBuHRTBpqKlyDnkSHDHYPiNX3adPoPAcgdF3H2/W0rmoswMWgTlLn1Wu0mrks7/q
+pdWfS6PJ1jty80r2VKsM/Dj3YIDfbjXKdaFU5C+8bhfJGqU3taKauuz0wHVGT3eo
+6FlWkWYtbt4pgdamlwVeZEW+LM7qZEJEsMNPrfC03APKmZsJgpWCDWOKZvkZcvjV
+uYkQ4omYCTX5ohy+knMjdOmdH9c7SpqEWBDC86fiNex+O0XOMEZSa8DA
+-----END CERTIFICATE-----

+ 27 - 0
key/server.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEArX0KnQwo5YGyxkwj5WI3bOoNKhUz5MlB6BT6PBmEPw2R35Nr
+NNKnyMvZxABwirys4lDJxhEDJ8zxpNLp7K6w+/ILw2YdyZj9mRDF2w8eQBEw2P3G
+QkF64cx9bwS24QKcvgd3T9L4bsRdH4m0XETyzZruMaI+MDenmLWWZ9242hopJbjG
+Fn2KzCSgqCq2rjM3JIjmC6gS0YYNIvfOCpDRKjZQ205hA3ZHXAweDlSME5/KQavC
+BGcsTZKOtD/IroZEqT8beE5jRoNndysnkcER1weFO7wevdEjQRiOl/gF27DwWV3A
+BYv4QWedq440izM+L4e4pInkRIDU+rsEhyMY2wIDAQABAoIBADeicDthsWG8IHD/
+7Mj8IKsQOY2DxCL7uWNsCr0oL6UqvFtWwpvra/v0B08YFv8et1BSOQqH3Ruv2NQT
+AQoyETaSbdxJDIdgRP8HhnOT2w8PlDsZ0rDfn8D2W0k1WVI6DLrR2RyjmqCm3vgk
+qE6om7Z8lyMalNfQmTHbFbMUQtnJ36g/MhvaO59KktEQcm/8uXy/H5rlrpbVwYE1
+c7XPRoHDQ6F5SuV1/+ICU+vFovFDz9dIbpZrWBTyVWek/28f9VzdO7YzwKU01gQs
+mNzZEcCwdfX93stGhaYsxjHLdqBMZQXvBpK5CMS25cumggaqG+rg5FJHzlu0Fugw
+rogbC3ECgYEA2cHdd4NOgKegM4mMhpHdKTt/IXX7LXZ81i6f/807EwySFHLR0HrW
+ylujCHQPQbgCmZ8JyMGJKdXdswGZXYXRYrYtYmXyT7QuWdxBseWy4T35YkXbGDmM
+U/RaJP+vBu8P2iAnsKipeI/7LcRcJGpBOTRD7Cz/yDnzrZrnWzLi4ksCgYEAy/Tl
+gQ1RSVrrwWwGPLmNmX3c9Mc2LMWlKaQjaqIVWCxe/SM7pFY1HtS3CwQlejfEw7NG
+S7DyaBPB0HxP7uZs7I+r/9AoKc1hILeC5u5KOOJChlnkc24zAJAkRVNL8c1kHILL
+oReXpbHH6NWlME1trESji1VjhlsloH9yhajwCbECgYA55pSOvLlY+DleuspE1fAq
+kIljSk25bvIOuWMNz3JPxF3rVgR4HNGUCP22t7V+0gDO/DzLxrpEB3vlpx0yguyE
+BNDYgycFDnHsruZA+USxI5t2CAET1el8QVLe16WggFyEsVwcWxyE1DQDnVyqyh4B
+qeYNnIF+qfojRTcTRtqRNQKBgDHcnzcboGHEnppc5HF4E4wQ3zHgefgcIbK6dAYf
+tgQmZNqSAIS+pqHw8wb4bokglTQa4fajPcAABIQJwCjxoHN89FnH8+m0UiiDdLcs
+Tmz1s7nQxC8MI9/hXVytboWg5vapa0roz6QsgGkFwiq64zMYXLs1lu1D30ELp13R
+3U0hAoGBAKei6/UGE5mgxhUKrqkvo42IuVxTxjXWWrkFBuCLwb7PbsORi9VLYY3u
+HlFmuzCTCrBKbUUZSTSKX+2GQvMUZW7022LoDIDsYIShcrIhWLdeTaI0tKoY7XVP
+wcENjbiWGy6bdpujHFiRUPY/2pZ05SRP6CQmw3/Ca2ZLQ1ILUaAe
+-----END RSA PRIVATE KEY-----

+ 61 - 0
key/server.pem

@@ -0,0 +1,61 @@
+-----BEGIN CERTIFICATE-----
+MIIF7jCCBNagAwIBAgIQB+Y7SbRAHDeClExpItMOpDANBgkqhkiG9w0BAQsFADBu
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
+RFYgVExTIENBIC0gRzEwHhcNMjMwNjIzMDAwMDAwWhcNMjQwNjIzMjM1OTU5WjAY
+MRYwFAYDVQQDEw1hcGkueW91eGk5LmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArX0KnQwo5YGyxkwj5WI3bOoNKhUz5MlB6BT6PBmEPw2R35NrNNKn
+yMvZxABwirys4lDJxhEDJ8zxpNLp7K6w+/ILw2YdyZj9mRDF2w8eQBEw2P3GQkF6
+4cx9bwS24QKcvgd3T9L4bsRdH4m0XETyzZruMaI+MDenmLWWZ9242hopJbjGFn2K
+zCSgqCq2rjM3JIjmC6gS0YYNIvfOCpDRKjZQ205hA3ZHXAweDlSME5/KQavCBGcs
+TZKOtD/IroZEqT8beE5jRoNndysnkcER1weFO7wevdEjQRiOl/gF27DwWV3ABYv4
+QWedq440izM+L4e4pInkRIDU+rsEhyMY2wIDAQABo4IC3DCCAtgwHwYDVR0jBBgw
+FoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFA7BVz8eMIUDluLWPSxJ
+v0AXermyMBgGA1UdEQQRMA+CDWFwaS55b3V4aTkuY24wDgYDVR0PAQH/BAQDAgWg
+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA+BgNVHSAENzA1MDMGBmeB
+DAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw
+gYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl
+cnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v
+RW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcxLmNydDAJBgNVHRMEAjAAMIIB
+fQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgDuzdBk1dsazsVct520zROiModGfLzs
+3sNRSFlGcR+1mwAAAYjl+QxFAAAEAwBHMEUCIFZ+gXlvtkJP+TTtAiO9wymGxGzq
+W2SejKWoZU+IKaL1AiEAp0TM5G1zLxC1LuO5o+k5dPrSEMlalZP1z8QaT5KwQHgA
+dQBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZu7+rOdiEcwAAAYjl+QvuAAAEAwBG
+MEQCIGnjryelJ31bpg/Qo+C4rqz1hDQ/zWng4fEyDopE9NlxAiBpWOZOrJzg6vhx
+PTEHufm4aZsn7hbQQytWGqRKVlao5QB2ANq2v2s/tbYin5vCu1xr6HCRcWy7UYSF
+NL2kPTBI1/urAAABiOX5C58AAAQDAEcwRQIhAOebbkdh3XbOFailV8ZuWHlNYSXD
+L26FoA8sl9Z7bbzfAiBEFm0ObQTORzDz+A41/GFoASFwFhaD/DFNZAGEqYYkQjAN
+BgkqhkiG9w0BAQsFAAOCAQEATYsQQHCglFxqrie8XeUkTeQyVcMrzeGLrmD8ORS0
+JXdRJUDPJRCuZQFTGxuF+3ncclDcw0+RgLg4j3MZPeuDWjstSxu+gkSNTImaWBet
+0JvrD31D+9sjNk5DR/ydTJ6H4gTpmXanbh4flwvrahH0k8lE+yA8qQyXoMOCLkag
+Vaw1ud3w+fHqh9CzObvOhMCaTEVVLnHQmTuDRZ/dtngVgffPbECCqQ01CoGclEu+
+7aZEdOX97BrktPwubCN0CLUxVeT2hSVqyrO2a38Fos6QpIS7H7Cv8+7VdISKGNhF
+bjrnWTIaoc3Jd65t2alKm0K4hs63nuesXtS3M2Gn827nhQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
+MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
+oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
+lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
+pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
+yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
+wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
+pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
+BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
+Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
+Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
+/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
+MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
+SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
+M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
+4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
+sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
+rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
+-----END CERTIFICATE-----

+ 27 - 0
key/t.qmgames.cn.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAnw2L0Xps5jcTz/nu7OCDFep2ocIxgJWxHUt+5YODzy7xASz+
+oWBgsqblQOlKJVMfhKx1n0DxFWZKOMQzW6ZZzSx20/XuCwzvnnXYkoTOR3Yzs5VI
+yxqmme8kmKE0p6jQB70HRRvHgAxs68JJZQPb5kUd+Kg0DjVkSvrS6l3ui2AnlncV
+LN2eMf2hY3ch4PjCEgdbb+qSPTq0wzqENYPzhE34a5OIRTHBBFeZoPaCkN1xkwb9
+HQR3khu/ud09ataS46MOCmUl+E3EZ/YefXdI8RNGBvUSXRXI++bIMyETOj3QjDup
+95BH2PDjOp20v5tH7XwQ5wKKB/yBYZrWpyQBrQIDAQABAoIBAA3UvLrbnYqrcXKd
+dlRs9Zk7SVOpsmpPGhbFEOTeeeDTYSvMANyBqAcBEmCBqOvz/YKpCJVvrUdCHOQw
+05J9tcNswWyPL0MQvemWr92V5OY79iUAr1LSH3i1XgZTxmNp/6Ucn/w3fTK6YeJi
+g8Vf/4hXrFKKNfSiWfVXOG4e+vZto/KmGbKqY8WzuV+jFIXmRGAvClhzXOG5lW1B
+DO1sI6sh33DN56yrXgRQaNK7ifWdyojXSMTDfkVqzziPre9pBFHkvsviuc+XW0St
+F3TwQoGqON6tTaKM0Vu+5rp7+wviVVych0y1DoEJb3/uK0hDFclAxmNhhWRj0rDi
+ob+UUfUCgYEA2Vi5M3eLLceQ/WGpxpB6mlWUtydBbtJ7xHIUm6Wf2ulsvVOfWM0T
+zthYt0dHSaZNj/hga2Q4VA/iDOQu1sCISFvG2MV7PSe7jS+oDVzXBzgF+8lP09JP
+TNwcyAWI+CcmBztSeFlIRZGZUJqaUwK8rvQPDJWLi5DmoRsphLRzlWsCgYEAu1bZ
+lPNDU4cmptIfGqzbqqh8DRr9cEV8czi8KIHT9zeU+b/OElXLzRUFtLR/K+1nOMQH
+v8NMBlwKJPXNu6Zx4UlrEXCB+lHBB2DliB6eCSB4ZouHfj7XBQP4yUSyObdZUFk5
+39peUyF6Ix3k9hb2fi/MiILhjgi0+sqJ0WwD80cCgYA0givUFEhqmfEcVfDnSNZl
+3NEd4nz7YhyGnC0AYWYaRBSCNOalVH45lajxNmYxk9zfMFlS+JZnMKMjDCmItmD2
+Yj3K9wLMBoJYHLBT8h783nsLlloTyZZg8jh91mB92y9jxeAptSAxtng8se0gquOc
+vNe56ZMZ97rQJzjDl7jo2wKBgCuHlq8hhzdVZAnHMSsFl1UkKQ7ygokf3Db5q97N
+q+75n0gY5U0lWmGgzN6cQiB23VE3W4WXX4bqxACqjM9ZcYSjhplaxxje/jc0sKDc
+XGRKgy9wpF8VQukLbzd1GuSGUd51nRkCqIAweyzFuXnlBAl2GdRudPKH22WawkND
+EffpAoGBAJlY4PDOWp6zVg65qsTPY5EkNH+UZTAQ4uV2u0QZKRCmx7tRN/BPE5B4
+gzEYdyngPA7sOzzWOd6HxRQ7O505+nzuutGPVvZ7W4tdIW8YJa7jlLK6hzd124h/
+qNJoIT6+FLAEaFKUO1A+eAQfc1ja/RfJkSP9coHhHiJFktBNRPjL
+-----END RSA PRIVATE KEY-----

+ 61 - 0
key/t.qmgames.cn.pem

@@ -0,0 +1,61 @@
+-----BEGIN CERTIFICATE-----
+MIIF7jCCBNagAwIBAgIQDHbA9aeh23rzfAjBaYvbWzANBgkqhkiG9w0BAQsFADBu
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
+RFYgVExTIENBIC0gRzIwHhcNMjMwOTIxMDAwMDAwWhcNMjQwOTIwMjM1OTU5WjAX
+MRUwEwYDVQQDEwx0LnFtZ2FtZXMuY24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCfDYvRemzmNxPP+e7s4IMV6nahwjGAlbEdS37lg4PPLvEBLP6hYGCy
+puVA6UolUx+ErHWfQPEVZko4xDNbplnNLHbT9e4LDO+eddiShM5HdjOzlUjLGqaZ
+7ySYoTSnqNAHvQdFG8eADGzrwkllA9vmRR34qDQONWRK+tLqXe6LYCeWdxUs3Z4x
+/aFjdyHg+MISB1tv6pI9OrTDOoQ1g/OETfhrk4hFMcEEV5mg9oKQ3XGTBv0dBHeS
+G7+53T1q1pLjow4KZSX4TcRn9h59d0jxE0YG9RJdFcj75sgzIRM6PdCMO6n3kEfY
+8OM6nbS/m0ftfBDnAooH/IFhmtanJAGtAgMBAAGjggLdMIIC2TAfBgNVHSMEGDAW
+gBR435GQX+7erPbFdevVTFVT7yRKtjAdBgNVHQ4EFgQUgaIBcqp1qAWa96iLGPqA
+j5HZJjQwFwYDVR0RBBAwDoIMdC5xbWdhbWVzLmNuMD4GA1UdIAQ3MDUwMwYGZ4EM
+AQIBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAO
+BgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGA
+BggrBgEFBQcBAQR0MHIwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0
+LmNvbTBKBggrBgEFBQcwAoY+aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Vu
+Y3J5cHRpb25FdmVyeXdoZXJlRFZUTFNDQS1HMi5jcnQwDAYDVR0TAQH/BAIwADCC
+AXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHUA7s3QZNXbGs7FXLedtM0TojKHRny8
+7N7DUUhZRnEftZsAAAGKt4aSUgAABAMARjBEAiAhAjAKU/JMaPCaGFiko5ZoBlWb
+qPp32R8bdV+u64aHHgIgJ4exFi8HpjlNvY89kG2K2LY8vi6+NZu0cCB3qu5Ri6wA
+dQBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZu7+rOdiEcwAAAYq3hpJlAAAEAwBG
+MEQCIAj4eX9vHOChiHInC5nCEAdrQqV42AwtO4iQxnoIdBZDAiAPvMrk6LMiO/iX
+CV9Yuw59Z3fT9KBvr0tFDBp9JRP/bQB2ANq2v2s/tbYin5vCu1xr6HCRcWy7UYSF
+NL2kPTBI1/urAAABireGkoQAAAQDAEcwRQIhAMgJz2TrVG4BvcgB8WpFX3Y/pahV
+CA4DajdzEIF64obbAiAyr4ZqxcZt2RezG869sVTgB3475jUSPCZzzkyfgUsNzDAN
+BgkqhkiG9w0BAQsFAAOCAQEAXcoknhgWXFkjrG4Nq9Ogk2ifNiTYDFsJY0stWKlB
+NZb1UxpQABHCCPR2rzz9EHeSAJBL+kMf+rtIwJcYeQH5V2w4Sv0xocz3ckRYErk0
+Um0pJbGipg+Kl21repLNUicZWeRFl/6cHqWSFQwu0u/cNXIcllBDN6utOhk/ES6e
+YcLLHdbafaht7+sVT6EP+FpRepAfK805mqBS+ed/jHHtT+pxjX1tCQDBRASwgk+3
+UpPeBpLucrtcAHs+P0nZLtvYKonQaKxgBdxs5aOVJiWkNSBfo0mvHEIqiNiNDOb5
+b06FCZHswCQB+OP75dS27OtxYfViFnRZqQIXXNapwY+UNw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
+MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE
+eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv
+NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch
+QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn
+NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP
+ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI
+lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf
+BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
+Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
+Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
+/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
+MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ
+L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi
+98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe
+xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN
+GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2
+n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg==
+-----END CERTIFICATE-----

+ 1 - 0
logs/.gitkeep

@@ -0,0 +1 @@
+logs/*

+ 342 - 0
md/项目功能梳理文档.md

@@ -0,0 +1,342 @@
+# 祈盟SDK(new_sdk)项目功能梳理文档
+
+## 一、项目概述
+
+**项目名称**:祈盟SDK(Qimeng SDK)  
+**内部代号**:new_sdk  
+**项目类型**:多租户游戏SDK聚合平台  
+**技术栈**:ThinkPHP 5.0 + PHP 7.0-7.2 + MySQL + Redis + Kafka
+
+### 核心定位
+为游戏发行商和渠道合作伙伴提供一站式接入与运营能力,实现:
+- 多渠道游戏发行(一次接入对接 45+ 渠道)
+- 多支付方式集成(聚合 10+ 支付渠道)
+- 跨游戏、跨渠道的用户统一管理
+- 数据统计、分析与风控
+- CPS 公会渠道分层管理
+
+---
+
+## 二、核心业务模块(12大功能模块)
+
+### 1. 用户体系
+- 用户注册/登录(手机号、邮箱、第三方登录)
+- 用户信息管理与中心
+- 身份验证与实名认证
+- 密码找回与重置
+- 短信验证码服务
+- 防沉迷系统对接
+
+### 2. 游戏管理
+- 游戏信息配置与维护
+- 游戏版本管理与更新检测
+- 游戏支付类型配置
+- 游戏公告与通知管理
+- 游戏礼包发放
+
+### 3. 支付处理
+- 支付统一下单接口
+- 多支付渠道集成:
+  - 支付宝(H5/小程序/扫码)
+  - 微信支付(H5/扫码)
+  - 易宝支付
+  - 联动优势
+  - 趣智连
+  - 喜钛游
+  - 全民付
+- 支付异步回调处理(队列机制)
+- 支付记录查询
+- iOS内购对接
+
+### 4. 渠道管理
+- 45+ 游戏渠道SDK对接
+- 渠道登录验证
+- 渠道支付回调处理
+- 渠道参数配置
+- 渠道包管理与分发
+
+### 5. 数据统计
+- 用户数据分析
+- 订单数据统计
+- 支付数据报表
+- 渠道效果分析
+- 数据导出功能(Go服务实现)
+
+### 6. 内容管理
+- 公告管理
+- 轮播图管理
+- 隐私政策页面
+- 静态资源管理(阿里云OSS/华为云OBS)
+
+### 7. 客服系统
+- 用户反馈处理
+- 工单管理
+- 在线客服对接
+
+### 8. 风控系统
+- 异常订单检测
+- 充值异常告警(钉钉机器人)
+- 风险规则配置
+- 黑名单管理
+
+### 9. CPS公会系统
+- 公会账号管理
+- 推广链接生成
+- 分包功能
+- 流水数据查询
+- 佣金结算
+
+### 10. 运营工具
+- 权限管理系统
+- 操作日志记录
+- 系统配置管理
+- 邮件发送服务
+
+### 11. 自动打包
+- 渠道包自动生成(Python服务)
+- 分包任务管理
+- 打包状态监控
+
+### 12. MLBB游戏专项
+- MLBB专属登录接口
+- MLBB支付处理
+- MLBB回调通知
+
+---
+
+## 三、系统架构分层
+
+### 1. 用户接入层
+- 游戏App客户端
+- Web端应用
+- 渠道SDK接入
+- 公会系统接入
+
+### 2. API网关层
+- **V1版本接口**:基础功能接口
+- **V2版本接口**:增强版接口
+- **MLBB接口**:游戏专用接口
+- **Complex接口**:渠道联运对接接口
+
+### 3. 业务逻辑层
+- **admin模块**:总控管理后台(100+控制器)
+- **api模块**:对外API服务
+- **cps模块**:公会系统
+- **common模块**:公共模型/工厂/服务
+- **crontab模块**:定时任务(14+个)
+
+### 4. 数据存储层
+- MySQL数据库
+- Redis缓存
+- 消息队列(Kafka/ThinkPHP queue)
+
+### 5. 外部服务层
+- 阿里云OSS/华为云OBS(对象存储)
+- 阿里云短信服务
+- 钉钉告警机器人
+- 第三方API(176Park、MLBB、抖音广告追踪)
+
+---
+
+## 四、API接口功能清单
+
+### 用户相关接口
+| 接口 | 功能说明 |
+|------|----------|
+| Login | 用户登录 |
+| Register | 用户注册 |
+| PasswordFind | 密码找回 |
+| SendSms | 短信验证码发送 |
+| Checkemail | 邮箱验证 |
+| Email | 邮件服务 |
+| UserCenter | 用户中心信息 |
+| UserCenterAuth | 用户中心认证 |
+| Identity | 身份验证 |
+| IsIdentity | 身份验证状态查询 |
+| IsHwu | 实名认证查询 |
+| HwuXiaofei | 实名消费记录 |
+
+### 支付相关接口
+| 接口 | 功能说明 |
+|------|----------|
+| Pay | 支付统一下单 |
+| PayCoin | 金币支付 |
+| IosPay | iOS内购支付 |
+| PayNotify | 支付异步回调 |
+| PayNotifyCoin | 金币支付回调 |
+| PayRecord | 支付记录查询 |
+| PayReturn | 支付返回处理 |
+| GamePayType | 游戏支付类型查询 |
+
+### 游戏相关接口
+| 接口 | 功能说明 |
+|------|----------|
+| Index | 首页数据 |
+| CheckUpdate | 版本更新检测 |
+| GameNotify | 游戏通知 |
+| Gift | 礼包发放 |
+| Coin | 金币相关 |
+| CoinInstruction | 金币说明 |
+| MemberCoin | 会员金币 |
+
+### 渠道相关接口
+| 接口 | 功能说明 |
+|------|----------|
+| ComplexLogin | 渠道登录 |
+| ComplexPay | 渠道支付 |
+| ComplexPayNotify | 渠道支付回调 |
+| QuickCallback | 快速回调 |
+| SpecialParam | 特殊参数处理 |
+
+### 系统相关接口
+| 接口 | 功能说明 |
+|------|----------|
+| Startup | 系统启动 |
+| Helper | 辅助工具 |
+| Demo | 演示接口 |
+| Test | 测试接口 |
+| TestRedis | Redis测试 |
+| KafkaTest | Kafka测试 |
+| Mobile | 移动端相关 |
+
+---
+
+## 五、定时任务清单
+
+系统包含14+个定时任务,主要包括:
+- 订单状态同步
+- 支付回调重试
+- 数据统计汇总
+- 日志清理
+- 缓存更新
+- 风控规则执行
+
+---
+
+## 六、对外域名生态
+
+### 生产环境
+| 用途 | 域名 |
+|------|------|
+| 后台管理 | sdkadmin.7dgames.cn / sdkadmin.qmgames.cn |
+| 支付回调+API | sdkapi.7dgames.cn / sdkapi.qmgames.cn |
+| 渠道SDK对接 | sdkjhgame.7dgames.cn / sdkjhgame.qmgames.cn |
+| 公会后台 | sdkcps.7dgames.cn / sdkcps.qmgames.cn |
+| 公会API | sdkcpsapi.7dgames.cn / sdkcpsapi.qmgames.cn |
+| 官网 | sdkwww.7dgames.cn / sdkwww.qmgames.cn |
+| 渠道包下载 | sdkdownapp.7dgames.cn / sdkdownapp.qmgames.cn |
+| 导出下载 | sdkdownreport.7dgames.cn / sdkdownreport.qmgames.cn |
+| 图片静态资源 | sdkstatic.7dgames.cn / sdkstatic.qmgames.cn |
+
+### 测试环境
+在生产域名前加 `dev` 前缀(如 devsdkapi.qmgames.cn)
+
+---
+
+## 七、关联项目仓库
+
+| 仓库名称 | 功能职责 |
+|----------|----------|
+| new_sdk(本仓库) | 后台 + API + CPS_API |
+| new_sdk_cps-admin | CPS公会后台(前端分离) |
+| new_sdk_cps-py | CPS打包服务(Python) |
+| new_sdk_ws | 相关通知服务(WebSocket) |
+| new_sdk_go | Excel导出 & 发放游戏礼包(Go) |
+
+---
+
+## 八、部署与运维
+
+### 环境要求
+- 服务器:2核4G
+- 打包服务器:2核8G
+- 数据库:2核8G
+- Redis:2核4G
+- 带宽:5M
+- 开发栈:PHP + MySQL + Redis + WS + Go + Python
+
+### 容器化部署
+```bash
+docker-compose up -d --build
+```
+
+### 队列服务
+```bash
+# 生产环境(守护进程)
+php think queue:work --queue asyncCallback --daemon
+
+# 调试环境(热更新)
+php think queue:listen --queue asyncCallback
+```
+
+---
+
+## 九、API响应码约定
+
+| 响应码 | 含义 |
+|--------|------|
+| 100 | 登录失效 |
+| 110 | 请求参数有误 |
+| 120 | 请求处理过程提示 |
+| 130 | 执行异常 |
+| 200 | 正常 |
+
+---
+
+## 十、第三方服务集成
+
+### 支付渠道
+- 支付宝(H5/小程序/扫码)
+- 微信支付(H5/扫码)
+- 易宝支付
+- 联动优势
+- 趣智连
+- 喜钛游
+- 全民付
+
+### 云服务
+- 阿里云OSS(对象存储)
+- 华为云OBS(对象存储)
+- 阿里云短信服务
+- 钉钉告警机器人
+
+### 第三方API
+- 176Park游戏平台
+- MLBB游戏服务
+- 抖音广告追踪
+
+---
+
+## 十一、项目现状与重构方向
+
+### 现状特征
+- ✅ 功能完整,覆盖游戏SDK全链路
+- ✅ 业务复杂,多租户、多渠道、多支付
+- ⚠️ 技术债务:ThinkPHP 5.0.7已过时,PHP 7.0-7.2官方停止维护
+- ⚠️ 代码质量:缺乏统一规范、无测试覆盖、耦合度高
+
+### 主要痛点
+1. PaymentController约2000行,逻辑混乱
+2. 45+个Channel SDK存在大量重复代码
+3. 80+个数据模型缺乏统一规范
+4. 无测试覆盖,改一处可能影响多处
+5. 大量同步阻塞调用影响性能
+
+### 重构文档索引
+详见 `www/new_sdk/docs_12/` 目录下的系列文档
+
+---
+
+## 十二、快速上手指南
+
+### 推荐阅读顺序
+1. `www/new_sdk/docs_12/01_项目概览.md` + `02_痛点清单.md` - 建立全局认知
+2. `09_全部文档总结.md` - 把握重构总体思路
+3. 支付模块重点:`application/common/factory/Pay.php` 与 `application/api/controller/v1/PayNotify.php`
+4. 渠道SDK:`06_SDK渠道架构优化方案.md` + `extend/` 目录
+5. 风控系统:根目录 `risk_control_tables.sql` 与 `application/admin/controller/` 下风控相关控制器
+
+---
+
+**文档生成时间**:2026年5月27日  
+**文档版本**:v1.0

+ 66 - 0
md_go_migration_api/00-api-go-migration-index.md

@@ -0,0 +1,66 @@
+# api 模块 Go 重构梳理索引
+
+生成时间:2026-06-09
+
+源代码范围:`C:\web\composer\qi_meng\new_sdk\www\new_sdk\application\api`
+
+目标用途:为后续将当前 PHP/ThinkPHP SDK API 重构为 Go 服务提供业务逻辑、接口边界、数据依赖和迁移顺序参考。
+
+## 文档清单
+
+1. `01-api-architecture-and-business-domains.md`
+   - 当前 `api` 模块的入口协议、公共基类逻辑、业务域拆分。
+2. `02-controller-endpoint-inventory.md`
+   - 控制器、服务类、渠道适配器清单,便于迁移时逐个勾兑。
+3. `03-core-business-flows.md`
+   - 登录注册、支付下单、支付回调、聚合渠道、实名防沉迷、MLBB 活动等核心流程。
+4. `04-data-dependencies-and-risks.md`
+   - 数据表、Redis key、外部 SDK/支付渠道、敏感配置和迁移风险。
+5. `05-go-refactor-blueprint.md`
+   - Go 分层结构、接口抽象、迁移阶段和测试策略。
+6. `11-observability-stack-guide.md`
+   - Prometheus、Grafana、Loki、Tempo、OpenTelemetry、Alloy 的用途说明和第一版落地建议。
+7. `12-development-roadmap-todo.md`
+   - GoFrame 重构 5 个版本的开发计划 TODO,包括底座、账户、SDK 协议、支付订单、渠道运营。
+
+## 当前判断
+
+当前项目不是单纯的 API CRUD 服务,而是一个游戏 SDK 中台:
+
+- 客户端请求统一经过 AES 解密、MD5 签名、App 配置校验、Token/IMEI 校验。
+- 账号体系同时承载主账号、子账号、游戏归属渠道、实名状态、设备信息。
+- 支付链路是最大复杂度来源,包含游戏充值、平台币充值、专属币、代金券、混合支付、第三方支付参数生成、支付异步通知、CP 发货通知。
+- `complex` 是聚合/联运渠道适配层,约 50 多个渠道类,接口形式相似但细节差异很大。
+- `v2` 已经在重构部分业务,例如用户中心、订单、平台币、代金券、聚合渠道处理,Go 迁移时应优先吸收这些较新的边界。
+- `api/config.php` 和部分服务代码里混有渠道密钥、证书、回调地址等敏感配置,Go 版本不要照搬到代码仓库,应移入环境变量、密钥管理或配置中心。
+
+## 建议迁移路线
+
+建议使用 strangler pattern 渐进迁移,不建议一次性重写全部接口:
+
+1. 先迁移公共协议层:AES、签名、App 校验、Token 校验、响应加密。
+2. 再迁移低风险读取类接口:版本配置、公告、游戏包信息、礼包列表、订单查询。
+3. 然后迁移账号安全类接口:登录、注册、短信、设备、实名,保留 PHP 旁路比对。
+4. 支付下单和支付回调最后迁移,必须先补齐 golden case、幂等、金额校验和事务测试。
+5. `complex` 渠道适配器单独做 Go 插件式注册表,按渠道逐个迁移,不要重新堆成一个大控制器。
+
+## 迁移时必须保留的兼容点
+
+- 客户端密文协议:默认 `AES-128-ECB`,debug 请求可明文。
+- 签名算法:过滤 `sign`,按 key 升序拼接 `key=value`,追加 `client_appkey`,小写后 MD5。
+- Token 格式:`userid|username|gameid|sub_username|token_random|sdk` 经 `auth_code` 加密。
+- Redis 登录态:`token => imeil`,`token|sdk|userid|gameid => token_random`。
+- 响应结构:`code/msg/time/data/err_code`,生产环境需要加密输出。
+- 支付订单状态:`cy_pay.status`、`cy_paycpinfo.payflag`、`member_coin_pay.status` 的历史语义不能改。
+- CP 回调签名字段和成功判定:当前多数逻辑以 CP 返回字符串 `success` 为成功。
+
+## 迁移优先级概览
+
+| 优先级 | 模块 | 理由 |
+| --- | --- | --- |
+| P0 | 协议层、认证层、配置层 | 所有接口共享,错了会全量不可用 |
+| P0 | 支付订单与回调状态机 | 涉及资金、发货、幂等、退款 |
+| P1 | 登录注册、子账号、实名防沉迷 | 用户入口,强依赖历史数据兼容 |
+| P1 | `complex` 渠道适配 | 渠道多,适合抽象接口后逐个迁移 |
+| P2 | 订单/礼包/代金券/平台币查询 | 可先灰度,风险相对可控 |
+| P2 | MLBB 活动、第三方数据 API | 活动/渠道专项,可按需求迁移 |

+ 325 - 0
md_go_migration_api/01-api-architecture-and-business-domains.md

@@ -0,0 +1,325 @@
+# api 架构与业务域梳理
+
+## 模块定位
+
+`application\api` 是 SDK 侧的对外接口模块,覆盖客户端 SDK、游戏 CP、聚合渠道、支付渠道、推广页和专项活动。
+
+从业务职责看,它不是一个单一 API 模块,而是多个业务中台能力混在同一模块下:
+
+- SDK 客户端协议:加解密、签名、App 校验、Token 校验、统一响应。
+- 玩家账号:注册、登录、第三方登录、子账号、设备、短信、邮箱、找回密码。
+- 游戏关系:游戏详情、渠道包、启动上报、区服角色、版本更新。
+- 支付:游戏充值、平台币充值、代金券、专属币、混合支付、支付通知、CP 发货通知。
+- 聚合渠道:渠道登录、渠道支付、渠道回调、渠道角色/区服数据。
+- 用户中心:手机号/邮箱绑定、密码修改、实名、防沉迷、新设备校验。
+- 活动和渠道专项:MLBB 活动、抖音游戏开放、YQL 数据、聚合报表、风控。
+
+## 目录职责
+
+| 目录 | 当前职责 | Go 迁移建议 |
+| --- | --- | --- |
+| `controller` | HTTP 控制器,包含根控制器、`v1`、`v2`、`mlbb` | 拆到 `internal/http/handler`,按业务域分 handler |
+| `controller\Api.php` | API 公共协议、验签、登录校验、响应加密、设备/区服保存 | 拆成 middleware + auth service + response codec |
+| `service` | 支付通知、聚合通知、道具发放、专项福利、MLBB 礼包 | 拆成 domain service,不要依赖 HTTP 全局输入 |
+| `complex` | 聚合渠道适配器 | 抽象为 `ComplexAdapter` 注册表,按渠道实现 |
+| `library\mlbb` | MLBB 相关 SDK/抽奖算法 | 拆入 `internal/module/mlbb` 或独立活动模块 |
+| `validate` | ThinkPHP 表单校验 | Go 中改为 request DTO + validator |
+| `view` | 推广页、支付成功页、账号安全页等 HTML | 如 Go 只做 API,可迁移到前端/静态服务;若保留页面,另设 web handler |
+
+## 请求生命周期
+
+多数继承 `app\api\controller\Api` 的接口会走以下流程:
+
+1. `initDevice`
+   - 从 header `device` 或 body `device` 识别设备来源。
+   - 当前值含义:`1=PC`、`2=Android`、`3=iOS`、`4=H5`。
+2. 读取请求体
+   - 默认从 `php://input` 读取密文。
+   - 使用 `Env::get('aes_key')` 执行 `AES-128-ECB` 解密。
+   - header `qmapidebug=true` 时允许明文参数。
+3. App 校验
+   - 必须传 `appid`。
+   - 通过 `cy_app` 对应模型 `App` 查询 `id/appkey/client_appkey/gameid`。
+4. 签名校验
+   - 去掉 `sign` 字段。
+   - 参数 key 升序排序。
+   - 按 `key=value` 拼接,追加 `client_appkey`。
+   - 字符串转小写后 MD5。
+   - `api_debug=false` 时签名失败拒绝。
+5. 默认渠道
+   - 如果没有 `channel_id`,使用配置 `initial_channel_id`。
+6. 登录校验
+   - 非白名单控制器/方法需要 `imeil` 和 `token`。
+   - Redis 中校验 `token => imeil`。
+   - `auth_code` 解出 `userid|username|gameid|sub_username|token_random|sdk`。
+   - Redis 再校验 `token|sdk|userid|gameid => token_random`。
+   - 校验通过后把 `userid/gameid/username/token/sub_username/member_channel_id` 注入 `$this->input`。
+7. 响应输出
+   - debug 模式返回明文 JSON。
+   - 生产模式 JSON 再经 AES 加密。
+   - `jsonResult` 会把正数 code 映射成 `1`,非正数映射成 `0`,并保留 `err_code`。
+
+Go 迁移时应把上面 7 步拆为独立中间件,避免 handler 直接操作全局请求。
+
+## 入口和路由
+
+`application\route.php` 中域名别名大致把以下入口映射到 `api`:
+
+- `sdkapi`、`t` 映射到 `api`。
+- `devsdkapi`、`devt` 在开发环境映射到 `api`。
+- 显式路由包括:
+  - `POST mp/get_wxmp_code` -> `Api/ThreePlatform/getWxAuthCode`
+  - `POST mp/get_order_info` -> `Api/ThreePlatform/getOrderInfo`
+  - `POST mp/get_pay` -> `Api/ThreePlatform/getPay`
+  - `GET mp/get_article_list` -> `Api/ThreePlatform/getArticleList`
+  - `GET mp/get_article_info` -> `Api/ThreePlatform/getArticleInfo`
+  - `GET mlbb/:code` -> `Api/Index/mlbbDownload`
+  - `POST /gh/get_role` -> `Api/JhApi/getRole`
+  - `POST /gh/get_sub` -> `Api/JhApi/getSubList`
+  - `POST /gh/channel_data_summary` -> `Api/JhApi/getChannelDataSummary`
+  - `POST /gh/channel_data_summary_v2` -> `Api/JhApi/getChannelDataSummaryV2`
+  - `POST /gh/get_pay_list` -> `Api/JhApi/getPayList`
+  - `POST /gh/get_sub_user_list` -> `Api/JhApi/getSubUserList`
+  - `GET /game/detail` -> `Api/Game/detail`
+
+除了显式路由,ThinkPHP 默认还支持类似 `sdkapi/v1/login/index`、`sdkapi/v2/user/userInfo` 的模块/控制器/方法访问方式。Go 迁移时需要先从 nginx/access log 或客户端 SDK 配置确认真实使用路径。
+
+## 核心业务域
+
+### 1. SDK 协议与认证
+
+主要代码:
+
+- `controller\Api.php`
+- `api\common.php`
+- `api\config.php`
+
+职责:
+
+- AES 解密和响应加密。
+- MD5 请求验签。
+- App 配置校验。
+- Token/IMEI 登录态校验。
+- 请求频率管控。
+- 保存设备、角色、区服信息。
+
+Go 建议:
+
+- `internal/protocol/codec`:AES、JSON、响应封装。
+- `internal/protocol/sign`:客户端签名算法。
+- `internal/auth/session`:Token decode、Redis 校验、用户上下文。
+- `internal/module/game_role`:设备和区服角色保存。
+
+### 2. 账号与登录注册
+
+主要代码:
+
+- `v1\Login.php`
+- `v1\Register.php`
+- `v1\SendSms.php`
+- `v2\User.php`
+- `v2\Forget.php`
+- `v2\Captcha.php`
+- `v1\PasswordFind.php`
+
+职责:
+
+- 账号密码登录、短信登录、QQ/微信/抖音等第三方登录。
+- 注册时校验用户名、密码、游戏、渠道、设备、IP、游戏/渠道冻结状态。
+- 创建主账号 `cy_members`、实名扩展 `cy_memberstwo`、游戏渠道关系 `cy_member_channel_game_rel`、子账号 `nw_subaccount`。
+- 生成 SDK token,并写 Redis 登录态。
+- 账号安全:手机号、邮箱、找回密码、新设备验证、密码修改。
+
+Go 建议:
+
+- `internal/module/account`:主账号、登录注册、密码。
+- `internal/module/session`:token 和 Redis 会话。
+- `internal/module/security`:短信、验证码、设备校验。
+- `internal/module/identity`:实名与防沉迷。
+
+### 3. 游戏、角色、启动和版本
+
+主要代码:
+
+- `Game.php`
+- `v1\Role.php`
+- `v1\Startup.php`
+- `v1\CheckUpdate.php`
+- `v2\GameVersion.php`
+- `v2\GameNotify.php`
+
+职责:
+
+- 游戏详情、推广下载页、渠道包和安装包信息。
+- 客户端启动/设备上报。
+- 区服角色新增/更新。
+- 游戏通知、版本检查、包更新。
+
+Go 建议:
+
+- `internal/module/game`:游戏配置、包信息、版本。
+- `internal/module/role`:角色和区服写入。
+- `internal/module/startup`:设备启动上报。
+
+### 4. 支付、订单和资产
+
+主要代码:
+
+- `v1\Pay.php`
+- `v1\PayNotify.php`
+- `v1\PayNotifyCoin.php`
+- `v2\Coin.php`
+- `v2\Order.php`
+- `service\PayNotifyService.php`
+- `application\service\GamePayService.php`
+- `application\service\MemberCoinService.php`
+- `application\service\PayService.php`
+- `common\logic\Pay.php`
+- `common\logic\PayCallback.php`
+
+职责:
+
+- 游戏充值下单。
+- 平台币充值下单。
+- 专属币、平台币、代金券、混合支付金额计算。
+- 支付方式随机/路由。
+- 第三方支付参数生成。
+- 支付异步回调验签。
+- 订单置为成功、资产扣减/增加、CP 发货回调。
+
+Go 建议:
+
+- `internal/module/payment/order`:订单状态机和事务。
+- `internal/module/payment/provider`:第三方支付适配器。
+- `internal/module/payment/notify`:支付回调验签与归一化。
+- `internal/module/payment/asset`:平台币、专属币、代金券资产变动。
+- `internal/module/payment/callback`:CP 发货通知。
+
+### 5. 聚合渠道和联运适配
+
+主要代码:
+
+- `v1\ComplexLogin.php`
+- `v1\ComplexPay.php`
+- `v1\ComplexPayNotify.php`
+- `v2\Complex.php`
+- `v2\ComplexAuthentication.php`
+- `service\NotifyService.php`
+- `complex\*.php`
+
+职责:
+
+- 渠道登录验签与用户映射。
+- 渠道支付下单和回调验签。
+- 渠道通知数据归一化。
+- 渠道自定义成功/失败响应。
+- 渠道实名接口。
+- 渠道角色/区服数据同步。
+
+Go 建议:
+
+- `internal/module/complex`:聚合领域服务。
+- `internal/module/complex/adapter`:渠道适配器注册表。
+- `internal/module/complex/auth`:渠道实名。
+- 所有渠道统一实现接口,不再动态拼 class name。
+
+### 6. 用户中心、实名和防沉迷
+
+主要代码:
+
+- `v1\UserCenter.php`
+- `v1\UserCenterAuth.php`
+- `v1\Identity.php`
+- `v1\IsIdentity.php`
+- `v1\YhIdentity.php`
+- `v2\User.php`
+- `application\service\CommonService.php`
+
+职责:
+
+- 用户详情、账号安全、手机号/邮箱绑定。
+- 平台实名和中宣部实名。
+- 未成年登录时间限制。
+- 未成年充值限额。
+- 渠道归属保护。
+
+Go 建议:
+
+- `internal/module/user`
+- `internal/module/identity`
+- `internal/module/antiaddiction`
+- `internal/module/channel_belonging`
+
+### 7. 代金券、礼包、福利
+
+主要代码:
+
+- `v2\Coupon.php`
+- `v1\Gift.php`
+- `v2\Gift.php`
+- `service\SpecialService.php`
+- `application\service\WelfareService.php`
+- `common\logic\PayCallback::specialCallback`
+
+职责:
+
+- 代金券领取、兑换、支付可用券查询。
+- 礼包列表、礼包领取、礼包码发放。
+- 支付成功后的福利触发。
+
+Go 建议:
+
+- `internal/module/coupon`
+- `internal/module/gift`
+- `internal/module/welfare`
+
+### 8. MLBB 活动
+
+主要代码:
+
+- `controller\mlbb\Mlbb.php`
+- `controller\mlbb\Login.php`
+- `controller\mlbb\Game.php`
+- `controller\mlbb\Activity.php`
+- `controller\mlbb\User.php`
+- `controller\mlbb\Other.php`
+- `service\mlbb\HandleService.php`
+- `library\mlbb\LotteryService.php`
+
+职责:
+
+- MLBB 独立活动登录态。
+- 活动预约、角色绑定、任务、抽奖、礼包码和代金券发放。
+- 活动表以 `lr_` 前缀为主。
+
+Go 建议:
+
+- 如果活动仍在运营,单独做 `internal/module/mlbb`。
+- 如果活动已结束,只保留数据查询或后台补发能力,不优先迁移。
+
+### 9. 第三方和渠道数据 API
+
+主要代码:
+
+- `ThreePlatform.php`
+- `v2\DyGameOpen.php`
+- `YqlData.php`
+- `JhApi.php`
+- `RiskControl.php`
+- `v2\PropApi.php`
+- `v2\Huge.php`
+
+职责:
+
+- 微信小程序授权/支付。
+- 抖音开放平台授权、手机号、角色列表、事件通知。
+- YQL 数据、券通知、渠道数据。
+- 聚合报表和渠道数据接口。
+- 风控登录/注册检查。
+- 道具、邮件、充值补单类 API。
+
+Go 建议:
+
+- 做成独立外部集成模块:`internal/integration/*`。
+- 先确认哪些接口仍被使用,再决定迁移顺序。
+

+ 210 - 0
md_go_migration_api/02-controller-endpoint-inventory.md

@@ -0,0 +1,210 @@
+# api 控制器、服务与渠道适配器清单
+
+说明:
+
+- 以下清单来自 `application\api` 下 PHP 文件的类与 public 方法抽取。
+- ThinkPHP 默认路由通常可按 `sdkapi/<version>/<controller>/<action>` 或 `sdkapi/<controller>/<action>` 访问,实际线上路径需要结合 nginx/access log 和 SDK 客户端配置确认。
+- `_initialize`、`__construct`、测试方法、历史备份方法不一定是外部正式接口,迁移时需要逐个确认调用量。
+
+## 根控制器
+
+| 文件 | 类 | 继承 | public 方法 |
+| --- | --- | --- | --- |
+| `Api.php` | `Api` | `Base` | `_initialize`, `setInput`, `getGameParam`, `reParam`, `jsonResult`, `jsonResultUnescape`, `jsonResultTrue`, `_empty` |
+| `DyJl.php` | `DyJl` | `Controller` | `__construct`, `createPackage`, `getAppInfo`, `callback`, `refreshToken` |
+| `Error.php` | `Error` | - | `index`, `_empty` |
+| `Game.php` | `Game` | `Api` | `_initialize`, `index`, `detail`, `getChannelOrGame`, `external` |
+| `GamePromotion.php` | `GamePromotion` | `Controller` | `_initialize`, `index`, `indexOld`, `ios`, `abc`, `wlxx`, `download` |
+| `Index.php` | `Index` | `Controller` | `sywpay`, `index`, `mediaTest`, `transpondUrlAction`, `getAgent`, `senCode`, `pay`, `notify`, `wen_test`, `wxPay`, `migrate_hz`, `migrate_sw`, `_empty`, `wxmp`, `mlbbDownload`, `getMlbbCode` |
+| `JhApi.php` | `JhApi` | `Controller` | `__construct`, `getRole`, `getSubList`, `getChannelDataSummary`, `getChannelDataSummaryV2`, `getSubUserList`, `getPayList` |
+| `Privacy.php` | `Privacy` | `Controller` | `privacy` |
+| `RiskControl.php` | `RiskControl` | `Controller` | `__construct`, `login`, `register` |
+| `Test.php` | `Test` | `Api` | `_initialize`, `index`, `getKeyData`, `handleSignData` |
+| `ThreePlatform.php` | `ThreePlatform` | `Api` | `_initialize`, `getWxAuthCode`, `getOrderInfo`, `getPay`, `getWxH5Pay`, `getArticleList`, `getArticleInfo`, `oceanengine` |
+| `YqlData.php` | `YqlData` | `Controller` | `__construct`, `getGameAccount`, `getCouponReceive`, `notifyCoupon`, `notifyChannel`, `getChannelData` |
+
+## v1 控制器
+
+| 文件 | 类 | 继承 | public 方法 |
+| --- | --- | --- | --- |
+| `v1\Checkemail.php` | `Checkemail` | `Controller` | `_initialize`, `checkEmail`, `unBindEmail` |
+| `v1\CheckUpdate.php` | `CheckUpdate` | `Api` | `_initialize`, `checkClientUpdate`, `version`, `setting` |
+| `v1\Coin.php` | `Coin` | `Api` | `_initialize`, `index`, `defaultCoin`, `remain`, `coinInstruction` |
+| `v1\CoinInstruction.php` | `CoinInstruction` | `Api` | `_initialize`, `index` |
+| `v1\ComplexLogin.php` | `ComplexLogin` | `Api` | `_initialize`, `getSpecialParam`, `index` |
+| `v1\ComplexPay.php` | `ComplexPay` | `Api` | `_initialize`, `index` |
+| `v1\ComplexPayNotify.php` | `ComplexPayNotify` | `Base` | `notify` |
+| `v1\CpsNotify.php` | `CpsNotify` | `Api` | `_initialize`, `alipayAop`, `kdh5zhifu` |
+| `v1\Demo.php` | `Demo` | `Controller` | `a`, `zfb`, `post`, `zfbalipays`, `zf`, `index`, `jsonResult`, `alipays`, `alipayAop`, `returnback`, `wxpay`, `xmlToArray`, `postXmlCurl`, `wxnotify`, `wxreturn`, `wxwebpay` |
+| `v1\Email.php` | `Email` | `UserCenter` | `_initialize`, `index`, `resetEmail`, `sendEmail`, `sendEmailCode`, `velidateCode`, `changeEmail`, `sendNewEmailCode`, `chgBindEmail`, `bindEmail` |
+| `v1\GameNotify.php` | `GameNotify` | `Api` | `_initialize`, `index`, `ad` |
+| `v1\GamePayType.php` | `GamePayType` | `Api` | `_initialize`, `index` |
+| `v1\Gift.php` | `Gift` | `UserCenter` | `_initialize`, `index`, `getGiftData`, `getBoxData`, `getBox`, `getGift`, `giftDetail` |
+| `v1\Helper.php` | `Helper` | `Api` | `_initialize`, `checkToken` |
+| `v1\HwuXiaofei.php` | `HwuXiaofei` | `Api` | `_initialize`, `index`, `pay`, `weixinFinish` |
+| `v1\Identity.php` | `Identity` | `UserCenter` | `_initialize`, `index`, `rule`, `Verify`, `checkPlayLogin`, `getHoliday` |
+| `v1\Index.php` | `Index` | `Api` | `index` |
+| `v1\IosPay.php` | `IosPay` | `Api` | `_initialize`, `index`, `notify` |
+| `v1\IsHwu.php` | `IsHwu` | `Api` | `_initialize`, `index` |
+| `v1\IsIdentity.php` | `IsIdentity` | `Api` | `_initialize`, `isVerified`, `isLoginPopup`, `isPayPopup`, `verifyInfo` |
+| `v1\KafkaTest.php` | `KafkaTest` | `Api` | `testProducer`, `sendCustomMessage` |
+| `v1\Login.php` | `Login` | `Api` | `_initialize`, `index`, `loginSms`, `loginQq`, `getHoliday`, `logout`, `checkToken`, `third` |
+| `v1\MemberCoin.php` | `MemberCoin` | `Api` | `getCoin` |
+| `v1\Mobile.php` | `Mobile` | `UserCenter` | `_initialize`, `index`, `resetMobile`, `velidateCode`, `changeMobile`, `unBindMobile`, `sendSmsCode`, `sendNewSmsCode`, `bindMobile` |
+| `v1\Mwhtnotify.php` | `Mwhtnotify` | `Controller` | `index`, `checkSign`, `updateOrder` |
+| `v1\MwNotify.php` | `MwNotify` | `Controller` | `index`, `checkSign`, `updateOrder` |
+| `v1\PasswordFind.php` | `PasswordFind` | `Controller` | `_initialize`, `index`, `checkType`, `mobileCheck`, `emailCheck`, `checkAuth`, `sendEmail`, `sendSmsCode`, `sendEmailCode`, `resetPassword`, `resetByEmail` |
+| `v1\Pay.php` | `Pay` | `Api` | `_initialize`, `index`, `index_bak`, `queryPay`, `weixinOrderQuery`, `swiftpayWeixinwapOrderQuery`, `getAlipayWapParam`, `alipay_wap_notify`, `alipay_wap_return`, `xmlToArray`, `postXmlCurl`, `xzzfbzfpost` |
+| `v1\PayCoin.php` | `PayCoin` | `Api` | `_initialize`, `index` |
+| `v1\PayNotify.php` | `PayNotify` | `Api` | `_initialize`, `alipay`, `alipayAop`, `swiftpay`, `swiftccbwxpay`, `zfbxjzfipaynow`, `ipaynow`, `wxpayh5`, `wxpayh5mihua`, `zfbh5mihua`, `wxpayh5kj`, `zfbh5kj`, `wxpayh5sumpay`, `kdh5zhifu`, `wxwapnotify`, `wxwapreturn`, `qmf_pay`, `notifyVerify`, `ybzf_pay`, `ldzf_wx_pay`, `qzl_pay`, `yyyb_pay`, `xty_pay`, `pay_refund`, `airwallex`, `dianhun` |
+| `v1\PayNotifyCoin.php` | `PayNotifyCoin` | `Api` | `_initialize`, `alipay`, `alipayAop`, `swiftpay`, `swiftccbwxpay`, `zfbxjzfipaynow`, `ipaynow`, `wxpayh5`, `wxpayh5mihua`, `zfbh5mihua`, `wxpayh5kj`, `zfbh5kj`, `wxpayh5sumpay`, `kdh5zhifu`, `wxwapnotify`, `wxwapreturn`, `ybzf_pay`, `ldzf_wx_pay`, `qzl_pay`, `yyyb_pay`, `xty_pay` |
+| `v1\PayRecord.php` | `PayRecord` | `UserCenter` | `_initialize`, `index` |
+| `v1\PayReturn.php` | `PayReturn` | `Controller` | `_initialize`, `pay_success_mihua`, `pay_success_kj`, `pay_success_mihua_zfb`, `pay_success_kj_zfb`, `pay_success_kdh5zhifu`, `pay_success_sumpay`, `pay_success_xjzf`, `pay_success_wxwappay`, `pay_success_airwallex`, `pay_result_test` |
+| `v1\QuickCallback.php` | `QuickCallback` | `Controller` | `index`, `log_write_message`, `updateOrder` |
+| `v1\Register.php` | `Register` | `Api` | `index`, `createUsername`, `createChexkUsername`, `chkChannelConfig` |
+| `v1\Role.php` | `Role` | `Api` | `_initialize`, `index`, `add`, `update` |
+| `v1\SendSms.php` | `SendSms` | `Api` | `_initialize`, `index` |
+| `v1\SpecialParam.php` | `SpecialParam` | `Controller` | `getSpecialParam`, `jsonResultTrue` |
+| `v1\Startup.php` | `Startup` | `Api` | `_initialize`, `index` |
+| `v1\Test.php` | `Test` | - | `index` |
+| `v1\TestRedis.php` | `TestRedis` | - | `index` |
+| `v1\UserCenter.php` | `UserCenter` | `Api` | `_initialize`, `index`, `logout`, `account`, `json`, `checkBind`, `editPassword` |
+| `v1\UserCenterAuth.php` | `UserCenterAuth` | `Api` | `_initialize`, `index`, `identity` |
+| `v1\WanCmsNotify.php` | `WanCmsNotify` | `Controller` | `index`, `updateOrder` |
+| `v1\YhIdentity.php` | `YhIdentity` | `Api` | `_initialize`, `getVerifyInfo` |
+
+## v2 控制器
+
+| 文件 | 类 | 继承 | public 方法 |
+| --- | --- | --- | --- |
+| `v2\AiAssistant.php` | `AiAssistant` | `Api` | `getConfig` |
+| `v2\Captcha.php` | `Captcha` | `Base` | `getCaptcha`, `vCaptcha` |
+| `v2\CheckUpdate.php` | `CheckUpdate` | `Api` | `version` |
+| `v2\Coin.php` | `Coin` | `Api` | `payType`, `pay` |
+| `v2\Complex.php` | `Complex` | `Api` | `_initialize`, `handle` |
+| `v2\ComplexAuthentication.php` | `ComplexAuthentication` | `Api` | `_initialize`, `check`, `query`, `sync` |
+| `v2\Config.php` | `Config` | `Api` | `getConfig` |
+| `v2\Coupon.php` | `Coupon` | `Api` | `getMyCoupon`, `getCoupon`, `receiveCoupon`, `exchangeCoupon`, `getPayCoupon` |
+| `v2\DyGameOpen.php` | `DyGameOpen` | - | `__construct`, `getCommon`, `getAuthLogin`, `getMobileCode`, `getMobileCheck`, `getRoleList`, `handleEventNotice` |
+| `v2\Forget.php` | `Forget` | `Api` | `resetPwdSendCode`, `resetPwdCheckCode`, `forgetPwdToReset`, `resetPassword`, `getPassword` |
+| `v2\GameNotify.php` | `GameNotify` | `Api` | `_initialize`, `index`, `index_v2`, `index_v3`, `getPackage` |
+| `v2\GameVersion.php` | `GameVersion` | `Api` | `getNew`, `getPackage` |
+| `v2\Gift.php` | `Gift` | `Api` | `giftList`, `giftPack`, `getMyGiftList` |
+| `v2\Huge.php` | `Huge` | `Controller` | `api`, `callback`, `qcallback`, `test`, `getMillisecond`, `curlJson` |
+| `v2\Order.php` | `Order` | `Api` | `_initialize`, `orderList`, `orderInfo`, `cancelOrder`, `getMemberCoinList`, `getPayInfo` |
+| `v2\Privacy.php` | `Privacy` | `Api` | `privacy`, `privacy1` |
+| `v2\PropApi.php` | `PropApi` | - | `prop`, `mail`, `recharge` |
+| `v2\SystemMessage.php` | `SystemMessage` | `Api` | `getMessageList`, `getMessageDetail`, `read` |
+| `v2\TestPay.php` | `TestPay` | `PayNotifyCoin` | `test` |
+| `v2\Third.php` | `Third` | `Api` | `getDyCode`, `getDyAuthInfoOpen`, `getJumpAuthCallback` |
+| `v2\User.php` | `User` | `Api` | `userInfo`, `updatePwd`, `getBindPhoneCode`, `getNewImeiCode`, `newImeilValid`, `bindPhone`, `unbindingPhoneCode`, `checkUnbindingPhoneCode`, `unbindingPhone`, `getPassword`, `verifyUserName`, `personalIdentity`, `checkPlayLogin`, `problem`, `heartbeat`, `getYqlChannel`, `getChannelCall` |
+
+## MLBB 控制器
+
+| 文件 | 类 | 继承 | public 方法 |
+| --- | --- | --- | --- |
+| `mlbb\Activity.php` | `Activity` | `Mlbb` | `getInfo`, `claim` |
+| `mlbb\Game.php` | `Game` | `Mlbb` | `reservation`, `getRole`, `bandRole`, `getTask`, `getTaskComplete`, `getLotteryLog`, `handleLottery` |
+| `mlbb\Login.php` | `Login` | `Mlbb` | `_initialize`, `index` |
+| `mlbb\Mlbb.php` | `Mlbb` | `Base` | `_initialize`, `getChannelId` |
+| `mlbb\Other.php` | `Other` | `Mlbb` | `_initialize`, `common`, `importGift`, `getTest` |
+| `mlbb\User.php` | `User` | `Mlbb` | `getInfo` |
+
+## api service
+
+| 文件 | 类 | public 方法 |
+| --- | --- | --- |
+| `NotifyService.php` | `NotifyService` | `notify` |
+| `PayNotifyService.php` | `PayNotifyService` | `updateOrderCoin` |
+| `PropService.php` | `PropService` | `prop221`, `mail221`, `recharge221`, `propXingMai`, `mailXingMai`, `rechargeXingMai` |
+| `SpecialService.php` | `SpecialService` | `welfare`, `welfareRongyao`, `validate` |
+| `mlbb\HandleService.php` | `HandleService` | `importGift`, `getCacheGiftCode`, `addCacheGiftCode`, `getCacheGiftNum`, `getMemberCoupon`, `entendRewards` |
+
+## complex 渠道适配器
+
+### 标准接口
+
+`ComplexInterface.php` 定义:
+
+- `checkLogin($data, $polyChannelGame = [])`
+- `paySign($data, $polyChannelGame = [])`
+- `getData($data)`
+- `getFail($msg = '')`
+- `getSuccess($msg = '')`
+
+不少渠道还实现了可选方法:
+
+- `getSpecialParam`
+- `getOrder`
+- `getRole`
+- `paySignH5`
+- `checkLoginH5`
+- 私有签名方法,如 `signData`、`notifySign`、`paySignHandle`
+
+### 渠道文件
+
+| 文件 | 类 | 主要方法 |
+| --- | --- | --- |
+| `Ailequ.php` | `Ailequ` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Aiyou.php` | `Aiyou` | `checkLogin`, `getData`, `paySign`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Bhy.php` | `Bhy` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Congyu.php` | `Congyu` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Dangle.php` | `Dangle` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Demo.php` | `Demo` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getOrder`, `getRole`, `getSuccess`, `getFail` |
+| `Fiveseven.php` | `Fiveseven` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Gyln.php` | `Gyln` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getOrder`, `getSuccess`, `getFail` |
+| `Haipai.php` | `Haipai` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Haiquyou.php` | `Haiquyou` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Haofan.php` | `Haofan` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Haofanceshi.php` | `Haofanceshi` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Jcyx.php` | `Jcyx` | `getSpecialParam`, `checkLogin`, `getData`, `paySign`, `getFail`, `getSuccess` |
+| `Jiandao.php` | `Jiandao` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Jiuqu.php` | `Jiuqu` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Jiuquxin.php` | `Jiuquxin` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Jlyx.php` | `Jlyx` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail`, `rsaDecode` |
+| `Juheng.php` | `Juheng` | `checkLogin`, `getData`, `paySign`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Jule.php` | `Jule` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Jywan.php` | `Jywan` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Kshy.php` | `Kshy` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getOrder`, `getRole`, `getSuccess`, `getFail` |
+| `Kuaishou.php` | `Kuaishou` | `getSpecialParam`, `checkLogin`, `getOrder`, `getData`, `paySign`, `getFail`, `getSuccess`, `setParam`, `getKeyData`, `handleSignData` |
+| `Ledao.php` | `Ledao` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Ledaoios.php` | `Ledaoios` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Lizi.php` | `Lizi` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Mw.php` | `Mw` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess`, `sign` |
+| `Naozhewan.php` | `Naozhewan` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Onesixnn.php` | `Onesixnn` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Peiqi.php` | `Peiqi` | `getSpecialParam`, `checkLogin`, `getSpecialParamH5`, `checkLoginH5`, `getData`, `paySign`, `paySignH5`, `getSuccess`, `getFail` |
+| `Pili.php` | `Pili` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Qilin.php` | `Qilin` | `checkLogin`, `getData`, `paySign`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Quchen.php` | `Quchen` | `getSpecialParam`, `checkLogin`, `paySign`, `getOrder`, `getData`, `getSuccess`, `getFail` |
+| `Rrtv.php` | `Rrtv` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Sanjiu.php` | `Sanjiu` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Shenqi.php` | `Shenqi` | `checkLogin`, `getData`, `paySign`, `sign`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Simo.php` | `Simo` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Simoh5.php` | `Simoh5` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Test.php` | `Test` | `getSpecialParam`, `checkLogin`, `getData`, `paySign`, `getFail`, `getSuccess` |
+| `Tlyx.php` | `Tlyx` | `getSpecialParam`, `checkLogin`, `getData`, `paySign`, `getFail`, `getSuccess` |
+| `Tss.php` | `Tss` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Twothreethree.php` | `Twothreethree` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Wanxiong.php` | `Wanxiong` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Weilian.php` | `Weilian` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Xingmai.php` | `Xingmai` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess` |
+| `Xinyou.php` | `Xinyou` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Yehua.php` | `Yehua` | `checkLogin`, `paySign`, `getData`, `getFail`, `getSuccess`, `curl_post`, `getSpecialParam` |
+| `Yinhu.php` | `Yinhu` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Yiyou.php` | `Yiyou` | `checkLogin`, `getData`, `paySign`, `getFail`, `getSuccess`, `getSpecialParam` |
+| `Youchen.php` | `Youchen` | `getSpecialParam`, `checkLogin`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Youxiyou.php` | `Youxiyou` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Yueyou.php` | `Yueyou` | `getSpecialParam`, `checkLogin`, `getOrder`, `paySign`, `getData`, `getSuccess`, `getFail` |
+| `Zhaosy.php` | `Zhaosy` | `getSpecialParam`, `checkLogin`, `getSpecialParamH5`, `checkLoginH5`, `getData`, `paySign`, `paySignH5`, `getSuccess`, `getFail` |
+
+## 迁移核对建议
+
+迁移时建议给每个控制器或方法打标签:
+
+- `keep`:确认仍被客户端或渠道使用。
+- `deprecated`:历史或测试接口,保留 PHP 或下线。
+- `merge`:Go 中并入新的领域接口。
+- `adapter`:支付/渠道/第三方适配器。
+- `page`:HTML 页面,不进入纯 API 服务。
+

+ 534 - 0
md_go_migration_api/03-core-business-flows.md

@@ -0,0 +1,534 @@
+# api 核心业务流程
+
+## 1. SDK 请求协议流程
+
+适用范围:多数继承 `app\api\controller\Api` 的接口。
+
+```mermaid
+flowchart TD
+    A["客户端提交密文 body"] --> B["Api::_initialize"]
+    B --> C["读取 header/body device"]
+    C --> D["AES-128-ECB 解密 body"]
+    D --> E{"qmapidebug 或解密失败?"}
+    E -->|debug 明文| F["input() 参数"]
+    E -->|正常密文| G["JSON decode"]
+    F --> H["校验 appid"]
+    G --> H
+    H --> I["查询 App: id/appkey/client_appkey/gameid"]
+    I --> J["MD5 签名校验"]
+    J --> K["补默认 channel_id"]
+    K --> L["checkLogin"]
+    L --> M["业务 handler"]
+    M --> N["jsonResult"]
+    N --> O{"debug?"}
+    O -->|是| P["明文 JSON"]
+    O -->|否| Q["AES 加密 JSON"]
+```
+
+关键兼容点:
+
+- `qmapidebug` 是 header bool 值,开启后不需要密文。
+- 生产响应默认是加密字符串,不是 JSON。
+- `jsonResult` 会把业务 code 映射为 `code=1/0`,原始错误码保留在 `err_code`。
+
+## 2. 登录态校验流程
+
+代码入口:`Api::checkLogin`
+
+白名单逻辑:
+
+- 控制器白名单:`register`、`sendSms`、`CheckUpdate`、`complexLogin`、`startup`、`forget`、`privacy`、`config`、`Third`。
+- 方法白名单由每个控制器的 `$noNeedLogin` 定义。
+
+校验步骤:
+
+1. 校验 `imeil` 必填。
+2. 从参数或 header 读取 `token`。
+3. Redis 查询 `token => imeil`,必须存在且与当前 `imeil` 一致。
+4. 使用 `auth_code(token, DECODE, auth_key)` 解出:
+   - `userid`
+   - `username`
+   - `gameid`
+   - `sub_username`
+   - `token_random`
+   - `type`
+5. Redis 查询 `token|{type}|{userid}|{gameid}`,必须等于 `token_random`。
+6. 注入运行时输入:
+   - `userid`
+   - `gameid`
+   - `username`
+   - `token`
+   - `sub_username`
+   - `member_channel_id = getChannelId(userid, gameid)`
+7. 对 `v2.user/personalIdentity` 进行请求频率限制。
+
+Go 迁移建议:
+
+- 用 request context 保存 `AuthUser`,不要继续改写请求参数 map。
+- Redis key 兼容期必须保持原样。
+- `auth_code` 算法需要先移植并做 golden test,否则旧 token 全部失效。
+
+## 3. 登录流程
+
+主要代码:`v1\Login.php`
+
+### 账号密码登录
+
+入口:`Login::index`
+
+核心步骤:
+
+1. 校验用户名、密码、设备、游戏、渠道、IMEI。
+2. 海外游戏会使用 `IpLimit` 限制国内 IP。
+3. 校验渠道状态:
+   - 渠道存在。
+   - 渠道 `level=3`。
+   - 渠道未冻结登录。
+4. 校验游戏状态:
+   - 游戏存在。
+   - 游戏未下架。
+   - 游戏未禁止登录,或玩家在白名单。
+5. 密码使用 `auth_code(password, ENCODE, auth_key)` 后匹配 `cy_members`。
+6. 检查账号冻结、子账号冻结、风控冻结、IP/IMEI 封禁。
+7. 进入 `loginProcess`。
+
+### loginProcess
+
+职责:
+
+- 检查 IP/IMEI 封禁。
+- 检查渠道禁止登录。
+- 执行防沉迷登录时间限制:
+  - 未成年只允许周五、周六、周日和法定节假日 20:00 到 21:00。
+  - 命中时返回 `offline_time`。
+- 建立或更新玩家游戏渠道关系 `cy_member_channel_game_rel`。
+- 建立或更新子账号 `nw_subaccount`。
+- 记录登录日志 `cy_logininfo`。
+- 保存设备信息。
+- 生成 token 并写 Redis。
+
+Token 生成格式:
+
+```text
+userid|username|gameid|sub_username|token_random|sdk
+```
+
+返回字段通常包含:
+
+- `token`
+- `username`
+- `sub_username`
+- `userid`
+- `gameid`
+- `imeil`
+- `offline_time`
+
+### 第三方登录
+
+入口:
+
+- `Login::loginQq`
+- `Login::third`
+
+第三方登录会按不同类型查找或创建用户:
+
+- 微信:`wx_uid`
+- QQ:`qq_uid`
+- 抖音:`dy_uid`
+
+如果第三方用户不存在,会生成随机账号、写入主账号和相关扩展表,然后复用 `loginProcess`。
+
+## 4. 注册流程
+
+主要代码:`v1\Register.php`
+
+入口:`Register::index`
+
+核心步骤:
+
+1. 收集注册参数:
+   - `username`
+   - `password`
+   - `gameid`
+   - `imeil`
+   - `channel_id`
+   - `dy_channel_id`
+   - `type`
+2. 校验用户名格式、密码长度、游戏和渠道。
+3. 海外游戏 IP 限制。
+4. 校验渠道:
+   - 渠道存在。
+   - 渠道 `level=3`。
+   - 渠道注册未被冻结。
+5. 校验游戏:
+   - 游戏存在并上线。
+   - 游戏未禁止注册。
+   - 特殊游戏必须手机号注册。
+6. 游客注册逻辑:
+   - 如关闭游客模式,直接拒绝。
+   - 以 `imeil` 查询是否已有游客账号,有则直接返回登录信息。
+7. 手机注册逻辑:
+   - 校验短信验证码。
+   - 手机号写入 `mobile`。
+8. 校验重复账号。
+9. 校验空渠道、分包配置、IP/IMEI 封禁。
+10. 执行注册频控:
+    - 每月同 IP 注册上限。
+    - 每月同 IMEI 注册上限。
+11. 写入主账号和扩展记录:
+    - `cy_members`
+    - `cy_member_history`
+    - `cy_memberstwo`
+    - `mw_dy_channel_rel`
+12. 进入注册内的 `loginProcess`:
+    - 建立 `cy_member_channel_game_rel`
+    - 建立 `nw_subaccount`
+    - 保存设备
+    - 生成 token
+    - 写 Redis
+
+Go 迁移注意:
+
+- 注册函数当前把“注册”和“自动登录”合在一起,Go 中建议拆成 `RegisterUser` 和 `CreateSession`。
+- 注册频控函数名拼写为 `regidterHandle`,迁移时注意不要遗漏。
+- `channel_id=EMPTY_CHANNEL_ID` 有特殊开关。
+
+## 5. 游戏充值下单流程
+
+主入口:`v1\Pay::index`
+
+实际核心服务:`application\service\GamePayService::gamePay`
+
+```mermaid
+flowchart TD
+    A["客户端 Pay::index"] --> B["Pay.add 参数校验"]
+    B --> C["GamePayService::gamePay"]
+    C --> D["支付方式场景识别"]
+    D --> E["PayHandle 随机/管控选择真实 paytype"]
+    E --> F["校验用户、子账号、渠道、游戏、CP 回调地址"]
+    F --> G["代金券/专属币/平台币金额计算"]
+    G --> H["重复下单和 attach 去重"]
+    H --> I["防沉迷充值限额"]
+    I --> J["事务写 cy_pay 与 cy_paycpinfo"]
+    J --> K["扣减券/专属币/平台币"]
+    K --> L{"real_amount 是否为 0?"}
+    L -->|是| M["订单直接支付成功"]
+    L -->|否| N["PayService 生成第三方支付参数"]
+    M --> O["返回订单结果"]
+    N --> O
+```
+
+### 下单关键参数
+
+来自 `validate\Pay.php`:
+
+- `gameid`
+- `userid`
+- `appid`
+- `serverid`
+- `servername`
+- `amount`
+- `roleid`
+- `attach`
+- `channel_id`
+- `productname`
+- `paytype`
+- `imeil`
+- `mc_id`
+- `coupon_member_id`
+
+### 支付资产类型
+
+| 字段/标识 | 含义 |
+| --- | --- |
+| `amount` | 游戏充值原始金额 |
+| `real_amount` | 需要第三方支付的现金金额 |
+| `ptb_amt` | 游戏专属币使用金额 |
+| `coin_amt` | 平台币使用金额 |
+| `coupon_member_id` | 代金券实例 ID |
+| `coupon_amount` | 本单抵扣的代金券金额 |
+| `mix-xxx` | 专属币加第三方混合支付 |
+| `coin-xxx` | 平台币加第三方混合支付 |
+| `coupon` | 纯代金券支付 |
+
+### 订单写入
+
+主要表:
+
+- `cy_pay`
+- `cy_paycpinfo`
+- `cy_member_history`
+- `cy_member_channel_game_rel`
+- `nw_subaccount`
+- `member_coin`/`member_zscoin` 相关明细表
+- `cy_coupon_member`
+
+### CP 回调参数
+
+`GamePayService` 创建 CP 发货通知参数时,签名大致包含:
+
+```text
+orderid={orderid}&username={sub_username}&gameid={gameid}&roleid={roleid}&serverid={serverid}&paytype={paytype}&amount={amount}&paytime={time}&attach={attach}&appkey={appkey}
+```
+
+签名方式:MD5。
+
+Go 迁移注意:
+
+- `attach` 在同一 `gameid` 下必须唯一。
+- 支付方式有版本灰度和随机路由,不能只按客户端传入 `paytype` 处理。
+- 金额计算使用 `bc*` 高精度函数,Go 中必须用 decimal,不能用 float64。
+- 下单事务中包含订单、CP 通知、资产扣减或券状态修改,必须整体回滚。
+
+## 6. 支付回调流程
+
+主要入口:
+
+- `v1\PayNotify.php`:游戏充值现金回调。
+- `v1\PayNotifyCoin.php`:平台币充值现金回调。
+- `service\PayNotifyService.php`:平台币订单成功后加余额。
+- `service\NotifyService.php`:聚合渠道支付回调。
+
+### 游戏充值回调
+
+常见方法:
+
+- `alipay`
+- `alipayAop`
+- `wxpayh5`
+- `ybzf_pay`
+- `ldzf_wx_pay`
+- `qzl_pay`
+- `yyyb_pay`
+- `xty_pay`
+- `airwallex`
+- `dianhun`
+
+统一核心:`PayNotify::updateOrder`
+
+流程:
+
+1. 渠道回调入口完成验签。
+2. 归一化得到平台订单号和支付金额。
+3. 查询 `cy_pay`。
+4. 如果已成功且金额一致,返回成功。
+5. 如果金额不一致,记录错误并失败。
+6. 如果待支付:
+   - 开启事务。
+   - 更新 `cy_pay.status=1`、`pay_time`、必要的回调流水号。
+   - 更新 `cy_paycpinfo.payflag=1`。
+   - 增加玩家累计充值。
+   - 更新代金券状态。
+   - 调用 `PayCallback::callBackToCp(orderid)`。
+   - 推送支付预警和渠道通知。
+7. 提交事务。
+
+Go 迁移注意:
+
+- 每个支付渠道成功响应文本不同,例如 `success`、`SUCCESS`、`ok`、XML、JSON。
+- 不要把渠道响应统一成 JSON,否则第三方会重复回调。
+- `updateOrder` 必须幂等。
+- 回调金额单位有“元”和“分”的差异,适配器必须明确标准化。
+
+### 平台币充值回调
+
+核心:`PayNotifyService::updateOrderCoin`
+
+流程:
+
+1. 查询 `member_coin_pay`。
+2. 已成功且金额一致,返回 true。
+3. 金额不一致,记录错误。
+4. 待支付时开启事务:
+   - `member_coin_pay.status=1`
+   - 写 `member_coin_info` 余额流水。
+   - 更新 `cy_members.amount`。
+5. 提交事务。
+
+## 7. 平台币充值流程
+
+入口:`v2\Coin::pay`
+
+核心服务:`application\service\MemberCoinService::payCoin`
+
+流程:
+
+1. 判断账号类型:
+   - 给自己充值。
+   - 给指定账号充值。
+2. 支付方式按版本和配置随机路由。
+3. 创建 `member_coin_pay` 订单,订单号前缀为 `COIN`。
+4. 调用 `PayService` 生成支付参数。
+5. 支付回调进入 `PayNotifyCoin` 或 `PayNotifyService`。
+6. 成功后更新平台币余额和明细。
+
+## 8. 订单查询和取消流程
+
+入口:`v2\Order.php`
+
+能力:
+
+- `orderList`:游戏充值和平台币充值列表。
+- `orderInfo`:订单详情,含 CP 发货状态。
+- `cancelOrder`:取消待支付订单。
+- `getMemberCoinList`:平台币流水。
+- `getPayInfo`:支付相关信息。
+
+状态:
+
+| status | 含义 |
+| --- | --- |
+| `0` | 待支付 |
+| `1` | 支付成功 |
+| `2` | 已取消 |
+| `3` | 超时取消 |
+
+Go 迁移注意:
+
+- 游戏订单来自 `cy_pay`。
+- 平台币订单来自 `member_coin_pay`。
+- 游戏订单列表会按安卓/iOS 绑定游戏做跨端合并。
+
+## 9. 聚合渠道登录和支付
+
+### 聚合渠道登录
+
+主要代码:`v1\ComplexLogin.php`、`complex\*.php`
+
+流程:
+
+1. 客户端或渠道提交渠道标识、游戏 ID、渠道用户信息。
+2. 查询 `nw_complex_channel` 和 `cy_polychannel_game`。
+3. 动态实例化 `app\api\complex\{Channel}`。
+4. 调用适配器 `checkLogin` 校验渠道登录签名。
+5. 归一化渠道用户数据。
+6. 创建或更新 `nw_complex_members`。
+7. 写聚合登录日志。
+8. 返回聚合用户信息。
+
+### 聚合渠道支付
+
+主要代码:
+
+- `v1\ComplexPay.php`
+- `v1\ComplexPayNotify.php`
+- `service\NotifyService.php`
+- `complex\*.php`
+
+支付回调流程:
+
+```mermaid
+flowchart TD
+    A["渠道支付回调"] --> B["NotifyService::notify(channel,input)"]
+    B --> C["查询 complex_channel"]
+    C --> D["查询 polychannel_game"]
+    D --> E["实例化 complex adapter"]
+    E --> F["paySign 或 paySignH5"]
+    F --> G["getData 归一化 orderid/amount/sub_orderid"]
+    G --> H["checkOrder 校验 nw_complex_pay 金额"]
+    H --> I["事务更新 nw_complex_pay 和 cy_paycpinfo"]
+    I --> J["更新 nw_complex_members.total_pay_amount"]
+    J --> K["PayCallback::callBackToCp"]
+    K --> L["adapter getSuccess/getFail"]
+```
+
+Go 迁移注意:
+
+- 适配器返回给渠道的成功/失败响应必须保留渠道原格式。
+- `getData` 的归一化字段至少应统一为 `order_id`、`amount`、`provider_order_id`。
+- H5 渠道存在 `paySignH5` 分支,不能只实现标准 `paySign`。
+
+## 10. 实名与防沉迷
+
+主要代码:
+
+- `v1\Identity.php`
+- `v2\User::personalIdentity`
+- `v2\ComplexAuthentication.php`
+- `application\service\CommonService::authentication`
+
+实名类型:
+
+- 平台实名:写 `cy_memberstwo`。
+- 中宣部实名:按游戏 `game_auth_type` 和 `game_bizid` 调用外部 `authSdk.Authentication`,结果写 `nw_subaccount`。
+
+防沉迷逻辑:
+
+- 登录限制:
+  - 未成年人只允许特定日期 20:00 到 21:00。
+- 充值限制:
+  - 未满 12 岁不能充值。
+  - 12 到 16 岁,单笔 50,月累计 200。
+  - 16 到 18 岁,单笔 100,月累计 400。
+
+Go 迁移注意:
+
+- 身份证年龄计算要与 PHP helper `isMeetAgeByIDCard` 结果一致。
+- 中宣部认证有“查询中、成功、失败”等状态,应保留 `auth_status` 语义。
+- `CommonService::handleBelongToBinding` 会按实名信息调整渠道归属,是支付分成相关逻辑,不能遗漏。
+
+## 11. 代金券流程
+
+主要代码:`v2\Coupon.php`
+
+能力:
+
+- `getMyCoupon`:用户券列表,按未使用、已使用、已失效分组。
+- `getCoupon`:可领取券列表。
+- `receiveCoupon`:领取券。
+- `exchangeCoupon`:兑换码换券。
+- `getPayCoupon`:获取当前订单金额可用券。
+
+领取流程:
+
+1. 查询券模板 `cy_coupon`。
+2. 校验展示状态、有效期、库存。
+3. 查询 `cy_coupon_member` 中未绑定用户的券码。
+4. 开启事务。
+5. 增加模板 `receive_num`。
+6. 把券码绑定给当前 `member_id`。
+7. 按固定有效期或领取后 N 天设置有效期。
+
+支付使用:
+
+- 下单时 `GamePayService` 调用 `CouponMember::getPayCoupon`。
+- 若订单需要现金支付,券状态从 `is_use=1` 改为 `2`。
+- 若纯券支付成功,券状态从 `is_use=1` 改为 `3`。
+
+## 12. MLBB 活动流程
+
+主要代码:`controller\mlbb`
+
+独立登录态:
+
+- Token 格式:`user_id|username|token_random|sdk|mlbb`。
+- Redis key:`token|sdk|mlbb|user_id`。
+- 有独立的 `Mlbb::validateToken`。
+
+核心活动:
+
+- `Login::index`:活动登录,创建 `lr_user_info`。
+- `Game::reservation`:预约。
+- `Game::getRole`:查询 SDK 角色。
+- `Game::bandRole`:绑定角色到活动账户。
+- `Game::getTask` / `getTaskComplete`:任务读取和完成。
+- `Game::handleLottery`:抽奖,消耗次数,发礼包码或代金券。
+- `Activity::getInfo`:冒险团信息。
+- `Activity::claim`:阶段奖励领取。
+- `Other::importGift`:导入礼包码。
+
+主要表:
+
+- `lr_user_info`
+- `lr_user_gathering`
+- `lr_user_task`
+- `lr_user_winning`
+- `lr_prize`
+- `lr_prize_gift`
+- `lr_claim`
+
+Go 迁移建议:
+
+- 该模块活动属性强,建议先确认是否仍在线。
+- 如果已结束,优先保留数据和后台补发能力,不作为主 SDK 迁移关键路径。
+

+ 324 - 0
md_go_migration_api/04-data-dependencies-and-risks.md

@@ -0,0 +1,324 @@
+# api 数据依赖、外部依赖与迁移风险
+
+## 1. 数据表依赖
+
+以下为 `application\api` 直接或间接高频出现的数据表和模型。命名中 `cy_`、`nw_`、`lr_`、`mw_` 混用,Go 迁移时建议统一 repository 命名,避免把表前缀散落到业务代码。
+
+### 账号与实名
+
+| 表/模型 | 用途 |
+| --- | --- |
+| `cy_members` / `Members` | 主账号,用户名、密码、手机号、邮箱、平台币余额、冻结状态 |
+| `cy_memberstwo` / `MembersTwo` | 实名扩展、身份证、真实姓名等 |
+| `cy_member_history` / `MemberHistory` | 账号历史变更、渠道绑定、实名记录 |
+| `nw_subaccount` / `Subaccount` | 游戏子账号,子用户名、游戏、渠道、实名状态、中宣 PI、设备 |
+| `cy_member_channel_game_rel` / `MemberChannelGame` | 玩家、游戏、渠道归属关系 |
+| `cy_logininfo` / `Logininfo` | 登录日志和渠道归属记录 |
+| `nw_devices` | 设备信息 |
+| `nw_startup_devices` | 启动上报设备 |
+| `GameBanned` | 游戏级 IP/IMEI 封禁 |
+| `GameRestrict` | 游戏防沉迷/限制开关 |
+
+### 游戏、渠道和包
+
+| 表/模型 | 用途 |
+| --- | --- |
+| `cy_game` / `Game` | 游戏基础信息、状态、认证类型 |
+| `cy_gameinfo` / `GameInfo` | 游戏扩展配置、平台、AI 开关、注册/登录提示 |
+| `cy_app` / `App` | SDK App 配置,含 `appkey`、`client_appkey` |
+| `nw_channel` / `Channel` | 渠道信息,状态、层级、父渠道 |
+| `ChannelFrozen` | 渠道冻结配置 |
+| `cy_sdkgamelist` / `SdkGameList` | 游戏分包配置 |
+| `nw_game_band` / `gameBand` | 安卓/iOS/H5 游戏绑定关系 |
+| `nw_game_package_upload` | 游戏包上传记录 |
+| `nw_game_package_test` | 测试包信息 |
+| `nw_game_promotion` | 推广页/短链 |
+| `promotion_short_link` | 推广短链 |
+
+### 角色与区服
+
+| 表/模型 | 用途 |
+| --- | --- |
+| `nw_member_game_server` / `MemberGameServer` | 玩家角色、区服、等级、VIP、IMEI |
+| `GameServer` | 游戏区服信息 |
+| `GameServerIp` | 区服 IP 记录 |
+| `GameServerImeil` | 区服 IMEI 记录 |
+
+### 游戏支付
+
+| 表/模型 | 用途 |
+| --- | --- |
+| `cy_pay` / `Pay` | 游戏充值订单 |
+| `cy_paycpinfo` / `PayCpinfo` | CP 发货回调参数、回调状态、超时时间 |
+| `PayType` | 支付方式配置 |
+| `Payrule` | 支付规则 |
+| `PaySetting` | 充值预警配置 |
+| `PayWarningRule` | 充值预警规则 |
+| `MemberZscoin` | 游戏专属币余额 |
+| `MemberZscoinDet` | 游戏专属币流水 |
+| `MemberCoinInfo` | 平台币流水 |
+| `memberCoinPay` / `MemberCoinPay` | 平台币充值订单 |
+
+### 代金券、礼包、福利
+
+| 表/模型 | 用途 |
+| --- | --- |
+| `cy_coupon` / `Coupon` | 代金券模板 |
+| `cy_coupon_member` / `CouponMember` | 用户代金券/券码实例 |
+| `cy_libao` / `libao` | 礼包主表 |
+| `cy_libaoinfo` | 礼包码/礼包详情 |
+| `cy_libaolog` | 礼包领取日志 |
+| `Welfare` | 福利活动 |
+| `WelfareGrant` | 福利发放记录 |
+| `welfareGrantData` | 福利发放明细 |
+| `welfareGiftType` | 福利礼包类型 |
+| `common/WelfareVoluntarily` | 福利主动发放配置 |
+
+### 聚合渠道
+
+| 表/模型 | 用途 |
+| --- | --- |
+| `nw_complex_channel` / `ComplexChannel` | 聚合渠道定义,mark 映射适配器 |
+| `cy_polychannel_game` / `PolychannelGame` | 渠道游戏配置,含渠道验签参数 |
+| `nw_complex_members` / `ComplexMembers` | 聚合用户 |
+| `nw_complex_pay` / `ComplexPay` | 聚合支付订单 |
+| `nw_complex_role` | 聚合角色 |
+| `nw_complex_server` | 聚合区服 |
+| `ComplexMembersAuthentication` | 聚合用户实名状态 |
+
+### MLBB 活动
+
+| 表 | 用途 |
+| --- | --- |
+| `lr_user_info` | 活动用户信息 |
+| `lr_user_gathering` | 冒险团/邀请码 |
+| `lr_user_task` | 活动任务 |
+| `lr_user_winning` | 抽奖中奖记录 |
+| `lr_prize` | 奖品配置 |
+| `lr_prize_gift` | 礼包码库存 |
+| `lr_claim` | 阶段奖励领取 |
+
+### 第三方与专项
+
+| 表/模型 | 用途 |
+| --- | --- |
+| `cy_game_ai_channel` | AI 助手渠道配置 |
+| `cy_game_notify` | 游戏通知配置 |
+| `cy_game_user_white` | 游戏登录白名单 |
+| `cy_setting` / `Setting` | 全局配置 |
+| `CpsProblem` | 用户问题列表 |
+| `tiktokAdLog` | TikTok/抖音相关日志 |
+| `mw_dy_channel_rel` | 抖音渠道映射 |
+| `cy_retaine_game` / `cy_retaine` | 留存统计 |
+
+## 2. Redis 和缓存 key
+
+### 登录态
+
+| key | 用途 | TTL |
+| --- | --- | --- |
+| `{token}` | token 对应登录设备 `imeil` | 30 天 |
+| `token|sdk|{userid}|{gameid}` | SDK token 随机码 | 30 天 |
+| `token|sdk|mlbb|{userid}` | MLBB token 随机码 | 7 天 |
+
+### 注册和频控
+
+| key | 用途 |
+| --- | --- |
+| `register:ip_limit:{yyyy-mm}_{ip}` | 每月 IP 注册次数 |
+| `register:imeil_limit:{yyyy-mm}_{imeil}` | 每月设备注册次数 |
+| `request:frequency:{username}:{route}` | 指定接口频率限制 |
+| `aoyousdk_register_most:gameid:{gameid}channel_id:{channel_id}` | 渠道注册量频控 |
+| `fk_register_time:{username}` | 风控注册时间标记 |
+
+### 支付和订单
+
+| key | 用途 |
+| --- | --- |
+| `pay_duplicate_{member_id}_{gameid}` | 游戏充值重复下单锁 |
+| `pay_duplicate_{userid}_coin` | 平台币充值重复下单锁 |
+| `PayNotify:updateOrder:{orderid}` | 支付回调临时缓存 |
+| `ldzf_order:{order_id}` | 联动优势小程序支付订单临时数据 |
+| `clearable_pay_warning:channelid:{channel_id}gameid:{game_id}member_id:{member_id}ruleid:{rule_id}amount_{amount}` | 充值预警计数 |
+
+### 角色和区服
+
+| key | 用途 |
+| --- | --- |
+| `:Role_add:MemberGameServer:{md5}` | 防止短时间重复插入角色 |
+| `:Role_add:GameServer:{md5}` | 防止短时间重复插入区服 |
+
+### v2 用户安全
+
+| key | 用途 |
+| --- | --- |
+| `v2:User:checkUnbindingPhoneCode:{token}` | 解绑手机号校验 token |
+
+### MLBB
+
+| key | 用途 |
+| --- | --- |
+| `mlbb` 登录失败次数 key | 登录尝试限制 |
+| 礼包码缓存 key | `HandleService` 中缓存礼包码库存,迁移时需按源码再确认具体 key |
+
+## 3. 外部依赖
+
+### 基础设施
+
+- MySQL:核心业务数据。
+- Redis:登录态、频控、订单临时数据、队列。
+- ThinkPHP Queue:`PayCallback` 中 CP 异步回调入队。
+- 日志目录:`LOG_PATH`、`RUNTIME_PATH` 下有大量业务日志。
+
+### 支付渠道和 SDK
+
+当前代码出现的支付/支付 SDK 包括:
+
+- 支付宝官方 AOP/H5/WAP。
+- 微信 H5/WAP。
+- 易宝 `ybzf`。
+- 联动优势 `ldys`。
+- 趣智连 `qzl`。
+- 优亿宝 `yyyb`。
+- 喜钛游 `xty`。
+- 十一玩 `syw`。
+- 米花 `mihua`。
+- 快接 `kj`。
+- Sumpay。
+- 快电/快达 `kdh5`。
+- Airwallex。
+- Swiftpay。
+- iPayNow。
+- 全民付/趣妙付类接口。
+
+Go 迁移建议:
+
+- 统一抽象 `PaymentProvider`,各渠道单独实现。
+- 每个 provider 明确:
+  - 请求金额单位。
+  - 回调金额单位。
+  - 签名算法。
+  - 成功响应文本。
+  - 通知 URL 类型,游戏充值和平台币充值是否不同。
+
+### 实名和风控
+
+- `authSdk.Authentication`:中宣部实名/查询/登出。
+- `IpLimit`:海外游戏 IP 限制。
+- `FkStrategiesService`:风控策略。
+- `RiskControl`:登录/注册风控 API。
+
+### 第三方渠道和数据
+
+- 微信小程序/公众号。
+- 抖音开放平台。
+- 快手相关渠道适配。
+- YQL 数据和券通知。
+- TikTok 推送。
+- DingTalk 机器人告警。
+- 聚合渠道各自登录/支付验签。
+
+## 4. 敏感配置风险
+
+当前风险点:
+
+- `api/config.php` 中存在渠道密钥、私钥或证书类配置片段。
+- 支付渠道配置散落在 `Config::get(...)`、`.env`、业务服务和 vendor SDK 中。
+- DingTalk webhook、YQL sign key、支付商户号、私钥等都不应进入 Go 代码仓库。
+- 部分渠道适配器可能把签名密钥写在代码、配置或数据库 `param` 中。
+
+Go 迁移建议:
+
+1. 区分三类配置:
+   - 非敏感业务配置:游戏开关、版本、渠道开关。
+   - 敏感密钥:支付私钥、AppSecret、webhook token。
+   - 动态配置:支付路由概率、渠道参数、实名配置。
+2. 敏感密钥统一从环境变量、KMS 或配置中心读取。
+3. 为每个 provider 建立 config schema,不允许 handler 直接读任意配置 key。
+4. 迁移前清点 `.env`、`application/config.php`、`application/api/config.php`、`application/extra`。
+
+## 5. 金额和状态风险
+
+### 金额风险
+
+PHP 中大量使用:
+
+- `bcadd`
+- `bcsub`
+- `bcmul`
+- `bccomp`
+- `priceFormat`
+- `formatFenToYuan`
+- `formatYuanToFen`
+
+Go 中不要使用 `float64` 处理金额。建议:
+
+- 金额内部统一用“分”的整数,或使用 decimal 库。
+- 第三方 provider 层负责元/分转换。
+- 订单表如果仍存 decimal 元,repository 层做格式化。
+
+### 状态风险
+
+游戏充值:
+
+- `cy_pay.status=0`:待支付。
+- `cy_pay.status=1`:支付成功。
+- `cy_paycpinfo.payflag=1`:CP 发货已成功。
+
+平台币充值:
+
+- `member_coin_pay.status=0`:待支付。
+- `member_coin_pay.status=1`:支付成功并已加余额。
+
+代金券:
+
+- `coupon_member.is_use=1`:未使用。
+- `is_use=2`:订单占用或使用中。
+- `is_use=3`:已使用完成。
+
+Go 迁移时要补充订单状态枚举,并在事务边界里统一维护。
+
+## 6. 幂等风险
+
+必须保证幂等的流程:
+
+- 登录 token 生成和旧 token 替换。
+- 注册重复提交。
+- 游戏充值下单重复提交。
+- 支付渠道异步回调重复通知。
+- CP 发货回调失败后的异步重试。
+- 平台币充值加余额。
+- 代金券领取和支付占用。
+- MLBB 抽奖扣次数和发奖。
+
+建议 Go 中引入:
+
+- 数据库唯一索引保护:`orderid`、`attach+gameid`、券码、渠道订单号。
+- Redis 短锁只作为体验优化,不作为唯一保障。
+- 回调处理基于数据库状态机幂等。
+- 每个支付回调记录原始 payload 和 normalized notify。
+
+## 7. 历史兼容风险
+
+需要特别保留的历史行为:
+
+- `imeil` 拼写历史遗留,不要直接改成 `imei`。
+- `sub_username` 生成规则通常为 `{userid}_{APP_STATUS}`,部分游戏直接使用 `userid`。
+- `jsonResult` 的 code 映射逻辑不是直接返回原始 code。
+- `channel_id` 传入渠道和玩家归属渠道可能不同,下单时会用归属渠道覆盖。
+- 安卓/iOS/H5 绑定游戏会影响订单列表、渠道归属和登录关系。
+- 某些游戏 ID 有硬编码特殊规则,例如注册方式、支付方式、子账号格式。
+- `complex` 的渠道类名由 `channel_mark` 动态拼接,大小写和 mark 必须兼容。
+
+## 8. 需要补充确认的清单
+
+迁移前建议用线上日志或数据库确认:
+
+- 哪些 `v1` 接口仍有流量。
+- 哪些支付渠道仍在线。
+- 哪些 `complex` 渠道仍在线。
+- `Demo`、`Test`、`Index` 中测试/迁移方法是否可以下线。
+- MLBB 活动是否仍在运营。
+- 支付成功页 `PayReturn` 是否仍由客户端或浏览器跳转使用。
+- HTML view 是否需要迁移到 Go,还是交由前端/静态服务处理。
+

+ 259 - 0
md_go_migration_api/05-go-refactor-blueprint.md

@@ -0,0 +1,259 @@
+# API Go 重构蓝图
+
+本文档面向后续将 `www/new_sdk/application/api` 迁移到 Go 的落地设计,重点是模块边界、接口抽象、迁移顺序和风险控制。
+
+## 1. 推荐目录结构
+
+```text
+cmd/
+  api/
+    main.go
+internal/
+  bootstrap/              # 配置、DB、Redis、日志、依赖装配
+  config/                 # app、game、payment、complex、third-party 配置
+  http/
+    router/               # 路由注册
+    middleware/           # 协议解密、鉴权、会话、限流、日志
+    handler/              # HTTP controller,保持薄层
+    response/             # 统一响应、错误码、SDK 加密输出
+  protocol/
+    codec/                # AES/JSON/兼容 imeil 等字段
+    sign/                 # MD5 sign、appid/key 校验
+    token/                # auth_code/session token 编解码
+  domain/
+    account/              # 登录、注册、账号绑定、实名
+    session/              # token、Redis 会话
+    game/                 # 游戏、区服、角色、版本、开服表
+    payment/              # 订单、支付渠道、回调、平台币
+    complex/              # 联运/渠道适配器
+    coupon/               # 优惠券
+    gift/                 # 礼包、福利、客服中心
+    mlbb/                 # MLBB 独立协议
+    thirdparty/           # 对外数据 API
+  repository/             # MySQL 仓储
+  integration/
+    payment/              # ali、wx、yunshanfu、dinpay、shande 等
+    identity/             # 实名、未成年、防沉迷
+    notify/               # CP 通知、钉钉告警
+    dataapi/              # dy、yql 等外部接口
+pkg/
+  decimal/                # 金额处理封装,避免 float 误差
+```
+
+原则:HTTP 层只负责协议、参数和响应;业务规则沉到 domain service;MySQL/Redis 访问全部经 repository;第三方接口全部经 integration。
+
+## 2. 中间件拆分
+
+当前 `controller\Api` 承担了请求解密、签名、应用校验、返回加密、登录校验等多种职责。Go 版本建议拆成以下中间件:
+
+```text
+RequestIDMiddleware
+RecoverMiddleware
+AccessLogMiddleware
+SDKCodecMiddleware       # AES-128-ECB 解密 data、兼容 imeil、响应加密
+AppAuthMiddleware        # appid、AppKey、MD5 sign 校验
+SessionMiddleware        # auth_code/token 解析、Redis 会话校验
+RateLimitMiddleware      # 按 appid、ip、用户或接口限流
+```
+
+保留兼容点:
+
+- 客户端参数名、路由名、错误码、加密方式先保持不变。
+- `imeil` 这类历史字段不要迁移时强行改名,可在 Go 内部归一成 `imei`。
+- 未登录接口与需登录接口要由路由显式声明,不再依赖控制器父类隐式判断。
+
+## 3. 核心接口设计
+
+### 3.1 SDK 协议
+
+```go
+type Codec interface {
+    DecodeRequest(ctx context.Context, req *http.Request) (*SDKRequest, error)
+    EncodeResponse(ctx context.Context, app AppConfig, body any) (*SDKResponse, error)
+}
+
+type Signer interface {
+    Verify(params map[string]string, appKey string, sign string) error
+}
+
+type SessionVerifier interface {
+    Verify(ctx context.Context, authCode string) (*Session, error)
+    Issue(ctx context.Context, userID int64, appID string) (*Session, error)
+    Revoke(ctx context.Context, authCode string) error
+}
+```
+
+### 3.2 账号域
+
+```go
+type AccountService interface {
+    Login(ctx context.Context, cmd LoginCommand) (*LoginResult, error)
+    Register(ctx context.Context, cmd RegisterCommand) (*RegisterResult, error)
+    QuickRegister(ctx context.Context, cmd QuickRegisterCommand) (*RegisterResult, error)
+    BindAccount(ctx context.Context, cmd BindAccountCommand) error
+    ChangePassword(ctx context.Context, cmd ChangePasswordCommand) error
+    SubmitIdentity(ctx context.Context, cmd IdentityCommand) (*IdentityResult, error)
+}
+```
+
+账号迁移要特别关注同一用户在 `Member`、`User`、token、实名、防沉迷、渠道用户标识之间的映射关系。
+
+### 3.3 支付域
+
+```go
+type PaymentProvider interface {
+    CreatePayment(ctx context.Context, order PaymentOrder) (*PaymentIntent, error)
+    VerifyNotify(ctx context.Context, req NotifyRequest) (*NormalizedNotify, error)
+    SuccessResponse() HTTPResponse
+    FailResponse(reason string) HTTPResponse
+}
+
+type OrderService interface {
+    CreateGameOrder(ctx context.Context, cmd CreateOrderCommand) (*PaymentOrder, error)
+    MarkPaid(ctx context.Context, notify NormalizedNotify) (*PaymentOrder, error)
+    Cancel(ctx context.Context, orderNo string) error
+    GetOrder(ctx context.Context, orderNo string) (*PaymentOrder, error)
+    ListOrders(ctx context.Context, query OrderQuery) ([]PaymentOrder, error)
+}
+```
+
+支付状态机建议固定为:
+
+```text
+created -> paying -> paid -> delivering -> delivered
+created -> canceled
+paid/delivering -> deliver_failed -> delivering -> delivered
+```
+
+回调处理必须满足:
+
+- 按 `order_no` 或第三方交易号做幂等。
+- 金额用整数分或 decimal,禁止使用 float 做最终判断。
+- 更新订单状态、发放资产、通知 CP 要么在一个事务里完成,要么使用事务加 outbox。
+- CP 通知失败不能导致第三方支付回调一直失败,应单独重试和告警。
+
+### 3.4 联运渠道域
+
+```go
+type ComplexAdapter interface {
+    Channel() string
+    CheckLogin(ctx context.Context, cmd ComplexLoginCommand, cfg ComplexGameConfig) (*ComplexLoginResult, error)
+    VerifyPayNotify(ctx context.Context, req ComplexNotifyRequest, cfg ComplexGameConfig) (*NormalizedComplexNotify, error)
+    SuccessResponse() HTTPResponse
+    FailResponse(reason string) HTTPResponse
+}
+
+type SpecialParamProvider interface {
+    SpecialParams(ctx context.Context, cmd SpecialParamCommand, cfg ComplexGameConfig) (map[string]any, error)
+}
+
+type H5PayVerifier interface {
+    VerifyH5Pay(ctx context.Context, req ComplexH5PayRequest, cfg ComplexGameConfig) error
+}
+```
+
+每个 `complex/*.php` 对应一个 Go adapter,先迁移高流量渠道。渠道差异只允许存在 adapter 内,业务层接收统一后的 `NormalizedComplexNotify`。
+
+## 4. 模块边界
+
+| Go 模块 | 承接现有逻辑 | 迁移重点 |
+| --- | --- | --- |
+| `protocol` | `controller\Api`、签名、AES、响应格式 | 兼容老 SDK |
+| `account` | `User`、`V1\User`、实名相关控制器 | token、实名、防沉迷 |
+| `game` | `Game`、`Role`、`Server`、`Version`、`OpenServer` | 游戏、区服、角色缓存 |
+| `payment` | `Pay`、`PayNotify`、`GamePayService`、`PayService` | 订单状态、幂等、金额 |
+| `complex` | `Complex`、`PayNotifyComplex`、`complex/*` | 渠道适配器抽象 |
+| `coupon` | `Coupon`、`MemberCouponService` | 领取、使用、过期 |
+| `gift` | `Gift`、`Welfare`、`Service` | 礼包码、福利、客服 |
+| `mlbb` | `api/controller/mlbb/*`、`service/mlbb/*` | 独立路由和验签 |
+| `thirdparty` | `Third`, `DataApi`, `YqlApi`, `DyApi` | 外部数据接口隔离 |
+
+## 5. 迁移阶段
+
+### 阶段 0:确认流量与契约
+
+- 统计当前线上实际调用接口、渠道回调、支付渠道占比。
+- 为 AES、sign、token、登录、支付回调准备 golden case。
+- 将敏感配置从代码中迁出,统一进入环境变量或配置中心。
+
+### 阶段 1:协议层先行
+
+- 在 Go 中实现 `SDKCodecMiddleware`、`AppAuthMiddleware`、统一错误码。
+- 使用历史请求样本做加解密、验签、响应格式测试。
+- 先不改业务,仅保证 Go 能读懂老 SDK 请求。
+
+### 阶段 2:只读和低风险接口
+
+- 迁移版本、开服表、游戏信息、礼包列表、订单查询等低风险接口。
+- 通过网关或 Nginx 按接口灰度转发。
+- 开启新旧响应对比日志,确认字段兼容。
+
+### 阶段 3:账号与会话
+
+- 迁移登录、注册、token 校验、实名接口。
+- Redis key 先兼容旧格式,后续再逐步改成 Go 命名规范。
+- 账号写入接口需要有回滚方案和完整审计日志。
+
+### 阶段 4:支付核心
+
+- 先迁移订单创建,再迁移支付回调。
+- 每个支付渠道单独上线,保留 PHP 回调兜底。
+- 订单状态机和幂等逻辑必须在 Go 侧统一,禁止各渠道自行更新状态。
+
+### 阶段 5:联运渠道
+
+- 选取 3 到 5 个高流量 `complex` 渠道优先迁移。
+- 每个渠道建立登录和支付回调契约测试。
+- adapter 上线后保留 PHP fallback,观察回调成功率和金额差异。
+
+### 阶段 6:收敛与下线
+
+- 完成全量接口迁移后,冻结 PHP 新需求。
+- 清理重复 Redis key、废弃支付渠道、无流量接口。
+- 将 API 文档、错误码、渠道协议沉淀为 Go 项目内的 contract tests。
+
+## 6. 测试策略
+
+| 测试类型 | 重点 |
+| --- | --- |
+| Golden tests | AES、sign、auth_code、响应格式、历史字段兼容 |
+| Unit tests | 账号规则、订单状态机、金额计算、渠道验签 |
+| Repository tests | MySQL 查询、事务、唯一键、分页 |
+| Integration tests | Redis 会话、支付 provider、实名 provider、CP 通知 |
+| Contract tests | 每个 complex adapter 的登录与支付回调样本 |
+| Shadow tests | 新旧接口响应对比,只记录不影响线上 |
+
+支付和联运回调不建议直接双写。更稳妥的方式是先做回调样本重放和只读影子验证,确认后再灰度切流。
+
+## 7. 运维与可观测性
+
+- 日志字段固定包含 `request_id`、`appid`、`user_id`、`game_id`、`order_no`、`channel`、`route`。
+- 支付回调、CP 通知、实名失败、渠道登录失败要有独立指标。
+- CP 通知使用队列或 outbox 重试,失败进入告警。
+- 第三方接口要设置超时、重试上限、熔断和降级响应。
+- 管理敏感配置时区分普通配置和 secret,代码仓库不再保存私钥、商户密钥、回调 token。
+
+## 8. Go 技术建议
+
+- Web 框架可选 Gin、Chi 或 Hertz;如果团队偏简单稳定,优先 Chi/Gin。
+- ORM 可选 GORM 或 SQLBoiler;支付和订单建议显式事务,避免隐藏更新。
+- Redis 使用 go-redis。
+- 金额使用整数分或 shopspring/decimal。
+- 配置使用 Viper 或自研轻量 loader,但 secret 必须从环境变量或配置中心读取。
+- 错误码用枚举和集中映射,不在 handler 中散落字符串。
+
+## 9. 优先级建议
+
+| 优先级 | 内容 | 原因 |
+| --- | --- | --- |
+| P0 | 协议层、错误码、配置和日志 | 所有接口依赖 |
+| P0 | 支付订单状态机和幂等模型 | 风险最高 |
+| P1 | 登录、注册、会话、实名 | 核心用户链路 |
+| P1 | 高流量支付渠道 | 直接影响收入 |
+| P2 | 高流量 complex adapter | 联运差异大 |
+| P2 | 游戏、礼包、优惠券、客服 | 业务完整性 |
+| P3 | 低流量 Third/Data API | 可按需迁移 |
+
+## 10. 落地判断
+
+这个项目适合迁移到 Go,但不适合一次性重写。推荐采用“协议兼容 + 分域迁移 + 支付谨慎灰度”的方式推进。最关键的不是框架选择,而是把当前隐含在控制器、service、complex adapter 里的规则变成明确的接口、状态机、契约测试和可观测指标。

+ 332 - 0
md_go_migration_api/11-observability-stack-guide.md

@@ -0,0 +1,332 @@
+# 可观测性技术栈说明
+
+本文档用于记录 GoFrame 重构时建议引入的监控、日志、链路追踪和采集技术栈,方便后续回看和落地。
+
+## 1. 一句话理解
+
+| 组件 | 简单理解 | 主要用途 |
+| --- | --- | --- |
+| Prometheus | 看指标 | 采集和存储 QPS、耗时、失败率、队列积压等数字指标 |
+| Loki | 查日志 | 存储和检索应用日志、支付回调日志、异常日志 |
+| Tempo | 查链路 | 查看一次请求从入口到 DB、Redis、外部接口的完整调用过程 |
+| Grafana | 统一看板 | 在一个界面里看指标、日志、链路和告警 |
+| OpenTelemetry | 统一埋点标准 | 规定应用如何输出 metrics、logs、traces |
+| Alloy | 统一采集器 | 收集日志、指标、链路数据,再转发给 Prometheus、Loki、Tempo |
+
+## 2. 整体关系
+
+```text
+GoFrame API / Worker / Nginx / Redis / MySQL
+        |
+        | 产生 metrics / logs / traces
+        v
+Grafana Alloy / OpenTelemetry Collector
+        |
+        +--> Prometheus  存指标
+        +--> Loki        存日志
+        +--> Tempo       存链路
+        |
+        v
+Grafana 统一展示、查询、告警
+```
+
+这套体系要解决的问题不是“做一个漂亮大屏”,而是让线上问题能快速回答:
+
+- 哪个接口慢了?
+- 哪个渠道支付回调失败变多了?
+- Redis Stream 是否积压?
+- CP 通知是否失败?
+- 一笔订单从回调到发货到底卡在哪一步?
+- 某个 `order_no`、`trace_id`、`channel` 相关日志在哪里?
+
+## 3. Prometheus:看指标
+
+Prometheus 负责存储数字型监控指标,适合做趋势、告警和服务健康判断。
+
+在当前项目中建议重点采集:
+
+| 指标 | 示例 |
+| --- | --- |
+| API 请求量 | 每个接口每分钟请求数 |
+| API 耗时 | P50、P95、P99 延迟 |
+| API 错误率 | 按 route、code、appid 聚合 |
+| 支付回调 | 成功数、失败数、验签失败数、重复回调数 |
+| CP 通知 | 成功数、失败数、重试次数 |
+| Redis Stream | pending 数、lag、消费失败数 |
+| MySQL | 连接池使用量、慢查询数量、事务失败数 |
+| 外部接口 | 实名、支付、渠道 SDK 的耗时和失败率 |
+
+典型告警:
+
+```text
+5 分钟内支付回调失败率 > 5%
+Redis Stream pending > 1000
+CP 通知连续失败 > 20 次
+接口 P95 延迟 > 800ms
+某渠道登录失败率突然升高
+```
+
+注意:Prometheus 不适合当订单账本,支付金额、订单状态、发货状态必须以 MySQL 为准。
+
+官方文档:https://prometheus.io/docs/introduction/overview/
+
+## 4. Loki:查日志
+
+Loki 负责日志存储和查询,适合定位具体问题。
+
+GoFrame 日志建议输出 JSON,统一字段:
+
+```json
+{
+  "time": "2026-06-09T14:30:00+08:00",
+  "level": "error",
+  "trace_id": "trace-xxx",
+  "request_id": "req-xxx",
+  "route": "/api/pay/notify",
+  "appid": "10001",
+  "user_id": 123,
+  "order_no": "P20260609xxx",
+  "channel": "wxpay",
+  "error_code": "PAY_SIGN_INVALID",
+  "message": "pay notify verify failed"
+}
+```
+
+建议作为 Loki label 的字段:
+
+```text
+service
+env
+level
+route
+channel
+```
+
+不建议作为 label 的字段:
+
+```text
+order_no
+user_id
+request_id
+trace_id
+mobile
+id_card
+```
+
+这些字段数量太多,放进日志内容里查询即可,避免 Loki label 爆炸。
+
+当前项目最该记录的日志:
+
+- SDK 协议解密失败
+- appid/sign 校验失败
+- 登录失败
+- 注册失败
+- 支付下单失败
+- 支付回调验签失败
+- 支付金额不一致
+- 重复支付回调
+- CP 通知失败
+- complex 渠道登录失败
+- 实名/防沉迷接口失败
+
+官方文档:https://grafana.com/docs/loki/latest/
+
+## 5. Tempo:查链路
+
+Tempo 负责分布式链路追踪。它关心一次请求内部经历了哪些步骤,每一步耗时多少,哪里失败。
+
+支付回调链路示例:
+
+```text
+HTTP POST /pay/notify
+  -> parse request
+  -> verify payment sign
+  -> query order
+  -> check amount
+  -> update order paid
+  -> write member asset log
+  -> XADD Redis Stream
+  -> worker consume
+  -> notify CP
+```
+
+每一步都可以是一个 span。未来排查订单时,可以通过 `trace_id` 找到整条链路。
+
+第一版建议接入的 trace 链路:
+
+- 登录
+- 注册
+- 下单
+- 支付回调
+- CP 通知
+- complex 登录
+- complex 支付回调
+- 实名认证
+
+Tempo 不需要像日志一样保存大量文本,它主要保存 trace/span 信息。
+
+官方文档:https://grafana.com/docs/tempo/latest/
+
+## 6. Grafana:统一看板
+
+Grafana 是统一查询和展示入口。Prometheus、Loki、Tempo 都可以接到 Grafana 里。
+
+建议第一版做这些 Dashboard:
+
+| Dashboard | 内容 |
+| --- | --- |
+| API 总览 | 请求量、错误率、P95/P99、Top 慢接口 |
+| 支付总览 | 下单数、回调数、成功率、失败原因、渠道分布 |
+| CP 通知 | 通知成功率、失败列表、重试次数、积压数 |
+| complex 渠道 | 各渠道登录/支付成功率、失败率、耗时 |
+| Redis Stream | stream 长度、pending、consumer lag |
+| MySQL | 连接池、慢查询、错误数 |
+| 日志检索 | 按 order_no、trace_id、channel 查询 |
+
+Grafana 还可以配置告警,例如:
+
+```text
+支付回调失败率升高 -> 钉钉/企业微信告警
+Redis Stream 积压 -> 告警
+CP 通知失败持续增长 -> 告警
+某渠道登录失败率异常 -> 告警
+```
+
+官方文档:https://grafana.com/docs/grafana/latest/introduction/
+
+## 7. OpenTelemetry:统一埋点标准
+
+OpenTelemetry 是标准,不是存储系统。
+
+它负责定义应用如何输出:
+
+```text
+metrics  指标
+logs     日志
+traces   链路
+```
+
+使用 OpenTelemetry 的好处:
+
+- GoFrame 服务、worker、后续拆出来的服务都用同一套 trace_id。
+- 以后从本地 Grafana 换到云厂商可观测平台,埋点不用大改。
+- 可以统一 HTTP、MySQL、Redis、外部 API 的耗时观测。
+
+GoFrame 重构时建议从一开始就设计这些上下文字段:
+
+```text
+trace_id
+request_id
+appid
+user_id
+game_id
+order_no
+channel
+route
+```
+
+官方文档:https://opentelemetry.io/docs/what-is-opentelemetry/
+
+## 8. Alloy:统一采集器
+
+Grafana Alloy 是采集器,可以理解为“观测数据中转站”。
+
+它可以采集:
+
+- Go 服务暴露的 metrics
+- Go 服务输出的 OTLP traces
+- Docker 容器日志
+- Nginx access/error 日志
+- 本地文件日志
+- MySQL/Redis exporter 指标
+
+然后转发到:
+
+```text
+Prometheus
+Loki
+Tempo
+Grafana Cloud
+```
+
+在当前项目里,Alloy 可以负责:
+
+```text
+采集 GoFrame JSON 日志 -> 发给 Loki
+采集 OpenTelemetry trace -> 发给 Tempo
+采集应用 metrics -> 发给 Prometheus
+采集 Nginx 日志 -> 发给 Loki
+```
+
+官方文档:https://grafana.com/docs/alloy/latest/
+
+## 9. 第一版落地建议
+
+不要一次性把所有东西都做复杂,建议分阶段。
+
+### 阶段 1:先接 Prometheus + Grafana
+
+目标:能看到接口和支付的关键指标。
+
+必须有:
+
+- API 请求量
+- API 错误率
+- API P95/P99
+- 支付回调成功/失败数
+- CP 通知失败数
+- Redis Stream pending 数
+
+### 阶段 2:接 Loki + Alloy
+
+目标:能按订单、渠道、trace_id 查日志。
+
+必须统一日志字段:
+
+```text
+trace_id
+request_id
+route
+appid
+user_id
+order_no
+channel
+error_code
+```
+
+### 阶段 3:接 Tempo + OpenTelemetry
+
+目标:核心链路能追踪。
+
+优先接:
+
+- 支付回调链路
+- CP 通知链路
+- 登录链路
+- complex 渠道链路
+
+## 10. 对当前项目的关键价值
+
+| 当前痛点 | 这套技术栈的帮助 |
+| --- | --- |
+| 支付失败难排查 | 指标看失败率,日志查订单,链路看卡点 |
+| 渠道太多 | 按 channel 聚合成功率、失败率、耗时 |
+| CP 通知不稳定 | Redis Stream 指标 + 日志 + 重试链路 |
+| 老代码逻辑分散 | 通过 trace_id 把一次业务请求串起来 |
+| 问题发现太晚 | Prometheus/Grafana 告警提前发现 |
+| 日志散落 | Loki 统一查询 |
+
+## 11. 最小可用版本
+
+第一版不追求全量完美,建议至少做到:
+
+```text
+Prometheus:接口指标、支付指标、Redis Stream 指标
+Loki:GoFrame JSON 日志、Nginx 日志
+Grafana:API、支付、渠道、队列四个 Dashboard
+OpenTelemetry:生成 trace_id,并在日志里透传
+Tempo:先只接支付回调和 CP 通知链路
+Alloy:统一采集日志和 trace
+```
+
+这样就能覆盖重构初期最核心的风险:接口异常、支付异常、渠道异常、队列积压、发货失败。

+ 345 - 0
md_go_migration_api/12-development-roadmap-todo.md

@@ -0,0 +1,345 @@
+# GoFrame 重构开发计划 TODO
+
+本文档用于规划 `new_sdk` 从当前 PHP/ThinkPHP 项目迁移到 GoFrame 技术栈的阶段路线。计划先排 5 个版本,每个版本都要有明确边界,避免一开始就陷入“大重写”。
+
+## 0. 总体原则
+
+- 先搭底座,再迁业务。
+- 先做可观测、配置、数据库访问、协议兼容,再做复杂支付。
+- 先做模块化单体,不急着拆微服务。
+- 新系统先兼容老 SDK 协议和老数据表,后续再逐步优化字段、状态和接口。
+- 每个版本都必须可运行、可回滚、可验收。
+- 支付、订单、发货、渠道回调必须有幂等和日志,不允许只靠控制器临时逻辑。
+
+## 1. 版本总览
+
+| 版本 | 名称 | 核心目标 | 主要交付 |
+| --- | --- | --- | --- |
+| V0.1 | 技术栈底座版 | 搭建 GoFrame 基础工程和运行环境 | 项目骨架、配置、日志、DB、Redis、Docker、基础监控 |
+| V0.2 | 基础账户体系版 | 跑通前后台基础账号和权限 | 后台管理员、前台用户、登录、Token、RBAC 雏形 |
+| V0.3 | SDK 协议与基础业务版 | 兼容老 SDK API 协议,迁移低风险读取接口 | AES/sign/token、游戏配置、版本、公告、礼包查询 |
+| V0.4 | 支付订单核心版 | 建立订单、支付、回调、发货状态机 | 下单、支付渠道抽象、回调幂等、Redis Stream、CP 通知 |
+| V0.5 | 渠道与运营闭环版 | 迁移 complex 渠道、运营能力和观测闭环 | 渠道适配器、优惠券/礼包、Dashboard、告警、灰度切流 |
+
+## 2. V0.1 技术栈底座版
+
+### 版本目标
+
+搭建一个可运行、可配置、可观测、可部署的 GoFrame 服务底座。这个版本不追求业务完整,只解决工程基础。
+
+V0.1 必须优先采用 GoFrame 官方脚手架和轻量 Docker 镜像策略,避免一开始就做出偏离框架习惯、镜像体积过大、后续维护成本高的工程底座。
+
+### 技术范围
+
+- Go 1.26.x
+- GoFrame v2
+- GoFrame CLI:`gf init`、`gf gen dao`、`gf run`、`gf build`
+- MySQL
+- Redis
+- Redis Stream 预留
+- OpenAPI
+- Docker Compose
+- Nginx
+- Prometheus
+- Grafana
+- Loki
+- Tempo
+- OpenTelemetry
+- Grafana Alloy
+
+### 工程约束
+
+- [ ] 项目必须使用 `gf init` 初始化,不手写一套偏离 GoFrame 官方习惯的目录。
+- [ ] 保留 GoFrame 官方目录结构,再根据业务复杂度补充 `domain`、`adapter`、`provider` 等目录。
+- [ ] 数据库访问层优先使用 `gf gen dao` 生成。
+- [ ] 核心支付、订单、资产流水等高风险 SQL 可以保留手写 SQL 或独立 repository,不强行全部套 DAO。
+- [ ] Docker 必须使用 multi-stage build。
+- [ ] 生产镜像不能直接使用 `golang` 构建镜像。
+- [ ] V0.1 初期允许使用 `alpine` 作为运行镜像,便于排查问题。
+- [ ] 稳定后生产候选优先使用 `distroless/static-debian12`。
+- [ ] 暂不优先使用 `scratch`,避免证书、时区、DNS、调试成本过高。
+- [ ] 镜像内不放源码、不放编译工具、不放 `.env`、不放证书私钥和支付密钥。
+- [ ] 配置通过环境变量、挂载配置或配置中心注入。
+- [ ] Docker Compose 仅用于本地开发和测试,不直接等同于生产部署方案。
+
+### TODO
+
+- [ ] 使用 `gf init` 初始化 GoFrame 项目结构。
+- [ ] 固化 GoFrame CLI 版本和 Go 版本。
+- [ ] 设计目录结构:`api`、`internal/controller`、`internal/service`、`internal/dao`、`internal/model`、`internal/logic`、`internal/pkg`。
+- [ ] 在官方目录基础上预留:`internal/domain`、`internal/adapter`、`internal/provider`。
+- [ ] 接入配置系统,区分 `local`、`dev`、`prod`。
+- [ ] 接入 MySQL,确认连接池、超时、慢查询日志。
+- [ ] 配置 `gf gen dao`,先生成基础 DAO/Entity/Model。
+- [ ] 接入 Redis,确认连接池和 key 前缀。
+- [ ] 设计统一响应结构。
+- [ ] 设计统一错误码结构。
+- [ ] 设计统一日志字段:`trace_id`、`request_id`、`appid`、`user_id`、`order_no`、`channel`、`route`。
+- [ ] 接入 OpenAPI 文档生成。
+- [ ] 编写 multi-stage Dockerfile。
+- [ ] 构建阶段使用 `golang:<version>-alpine` 或官方 Go 镜像。
+- [ ] 运行阶段初期使用 `alpine`,稳定后评估切换 `distroless/static-debian12`。
+- [ ] 确认运行镜像中包含 CA 证书和正确时区。
+- [ ] 编写 Docker Compose:Go 服务、MySQL 可选、Redis、Nginx、Prometheus、Grafana、Loki、Tempo、Alloy。
+- [ ] 接入 Prometheus 基础指标:请求量、耗时、错误数。
+- [ ] 接入 Loki JSON 日志采集。
+- [ ] 接入 OpenTelemetry trace_id 生成和透传。
+- [ ] 编写健康检查接口:`/health`、`/ready`。
+
+### 验收标准
+
+- [ ] 本地 `docker compose up` 后服务可启动。
+- [ ] `/health` 返回正常。
+- [ ] Go 服务可以连接 MySQL 和 Redis。
+- [ ] Grafana 可以看到基础请求指标。
+- [ ] Loki 可以按 `trace_id` 查询日志。
+- [ ] OpenAPI 页面可以访问。
+- [ ] Docker 生产候选镜像不包含 Go 编译器。
+- [ ] Docker 生产候选镜像不包含项目源码。
+- [ ] Docker 镜像体积符合预期,避免直接使用 `golang` runtime。
+- [ ] `gf gen dao` 可以稳定生成数据库访问层。
+
+### 暂不做
+
+- 不迁移支付。
+- 不迁移 complex 渠道。
+- 不改老数据库结构。
+- 不拆微服务。
+
+## 3. V0.2 基础账户体系版
+
+### 版本目标
+
+搭建基础前后台账户体系,为后续管理端、运营端、SDK 用户体系迁移做准备。
+
+这里的“前台账户”和“后台账户”要分清:
+
+- 后台账户:运营/管理人员使用,用于管理游戏、渠道、订单、礼包等。
+- 前台账户:SDK 用户体系,对应玩家登录、注册、实名、子账号等业务。
+
+### TODO
+
+- [ ] 梳理当前后台管理员表、角色表、权限表。
+- [ ] 梳理当前 SDK 用户表、实名表、设备表、子账号表。
+- [ ] 设计后台管理员登录接口。
+- [ ] 设计后台 RBAC 权限模型。
+- [ ] 设计后台菜单、按钮、接口权限的关系。
+- [ ] 设计前台用户基础模型。
+- [ ] 设计用户登录 Token 模型。
+- [ ] 兼容老 Token 或设计 Token 兼容层。
+- [ ] 接入密码加密策略,确认是否兼容老密码算法。
+- [ ] 接入登录日志。
+- [ ] 接入操作日志。
+- [ ] 设计账号封禁、解封、状态变更流程。
+- [ ] 编写账户相关单元测试。
+
+### 交付接口
+
+- [ ] 后台登录。
+- [ ] 后台退出。
+- [ ] 后台当前用户信息。
+- [ ] 后台权限菜单。
+- [ ] 前台用户登录雏形。
+- [ ] 前台用户 Token 校验。
+
+### 验收标准
+
+- [ ] 后台账号可以登录并获取权限。
+- [ ] 后台接口可以通过中间件校验权限。
+- [ ] 前台用户可以完成登录和 Token 校验。
+- [ ] 登录日志和操作日志可查。
+- [ ] Grafana/Loki 能按用户和请求 ID 查到登录链路。
+
+### 暂不做
+
+- 不做完整支付。
+- 不做 complex 渠道登录。
+- 不做复杂运营功能。
+
+## 4. V0.3 SDK 协议与基础业务版
+
+### 版本目标
+
+开始承接老 `application/api` 的基础能力,优先迁移低风险读取接口和 SDK 公共协议层。
+
+### TODO
+
+- [ ] 实现 SDK 请求解析中间件。
+- [ ] 实现 AES 解密兼容。
+- [ ] 实现 MD5 sign 校验兼容。
+- [ ] 实现 appid/appkey 配置校验。
+- [ ] 实现统一响应加密兼容。
+- [ ] 实现 debug 明文请求兼容。
+- [ ] 实现 auth_code/token 解析兼容。
+- [ ] 整理并迁移游戏基础信息接口。
+- [ ] 整理并迁移版本配置接口。
+- [ ] 整理并迁移公告接口。
+- [ ] 整理并迁移礼包列表/福利查询接口。
+- [ ] 整理并迁移区服/角色查询类接口。
+- [ ] 为 AES/sign/token 准备 golden tests。
+- [ ] 为低风险接口做 PHP/Go 响应对比。
+
+### 交付接口
+
+- [ ] SDK 加解密协议。
+- [ ] SDK 签名校验。
+- [ ] 游戏配置读取。
+- [ ] 版本信息读取。
+- [ ] 公告读取。
+- [ ] 礼包/福利读取。
+- [ ] 区服/角色基础查询。
+
+### 验收标准
+
+- [ ] 老 SDK 请求样本在 Go 侧可以正确解析。
+- [ ] Go 响应格式与 PHP 兼容。
+- [ ] AES/sign/token golden tests 通过。
+- [ ] 低风险读取接口可灰度切流。
+- [ ] 日志里能按 `appid`、`route`、`trace_id` 追踪请求。
+
+### 暂不做
+
+- 不迁移真实支付回调。
+- 不迁移复杂渠道适配器。
+- 不调整客户端协议。
+
+## 5. V0.4 支付订单核心版
+
+### 版本目标
+
+建立支付、订单、回调、发货、补单的核心闭环。这个版本是整个重构里风险最高的一版,必须先做状态机和幂等。
+
+### TODO
+
+- [ ] 梳理支付相关表:订单表、CP 通知表、平台币表、优惠券表、流水表。
+- [ ] 明确订单状态机。
+- [ ] 明确支付回调幂等规则。
+- [ ] 明确金额单位,统一使用整数分或 decimal。
+- [ ] 设计支付渠道接口 `PaymentProvider`。
+- [ ] 设计订单服务 `OrderService`。
+- [ ] 设计回调服务 `NotifyService`。
+- [ ] 设计 CP 通知任务。
+- [ ] 接入 Redis Stream,用于 CP 通知、补单、异步任务。
+- [ ] 实现支付下单接口。
+- [ ] 实现 1 到 2 个低风险支付渠道。
+- [ ] 实现支付回调验签。
+- [ ] 实现订单状态更新事务。
+- [ ] 实现重复回调处理。
+- [ ] 实现 CP 通知重试。
+- [ ] 实现补单入口。
+- [ ] 实现支付相关 Dashboard。
+- [ ] 实现支付失败告警。
+
+### 交付接口/能力
+
+- [ ] 创建订单。
+- [ ] 支付参数生成。
+- [ ] 支付异步回调。
+- [ ] 订单查询。
+- [ ] CP 通知。
+- [ ] CP 通知重试。
+- [ ] 补单。
+- [ ] 支付日志和支付指标。
+
+### 验收标准
+
+- [ ] 重复支付回调不会重复发货。
+- [ ] 金额不一致会拒绝并记录告警日志。
+- [ ] CP 通知失败不会导致第三方支付回调无限失败。
+- [ ] Redis Stream pending 可监控。
+- [ ] 支付回调可通过 `order_no` 查日志和 trace。
+- [ ] 支付状态机有单元测试和集成测试。
+
+### 暂不做
+
+- 不一次性迁移所有支付渠道。
+- 不在这个版本重构全部历史订单表。
+- 不改变现有 CP 回调协议。
+
+## 6. V0.5 渠道与运营闭环版
+
+### 版本目标
+
+迁移 complex 联运渠道能力,并补齐运营类功能和观测闭环。这个版本重点是渠道适配器模式,不允许把渠道逻辑重新堆进大控制器。
+
+### TODO
+
+- [ ] 梳理 `complex` 渠道清单。
+- [ ] 按流量和风险给渠道排序。
+- [ ] 设计 `ComplexAdapter` 接口。
+- [ ] 设计渠道登录统一结果模型。
+- [ ] 设计渠道支付回调统一结果模型。
+- [ ] 设计渠道特殊参数扩展点。
+- [ ] 优先迁移 3 到 5 个高价值渠道。
+- [ ] 为每个渠道准备登录样本和支付回调样本。
+- [ ] 实现渠道适配器注册表。
+- [ ] 实现渠道级指标:登录成功率、支付成功率、验签失败率。
+- [ ] 实现渠道级日志字段:`channel`、`game_id`、`appid`、`order_no`。
+- [ ] 迁移优惠券基础功能。
+- [ ] 迁移礼包码基础功能。
+- [ ] 迁移运营查询类接口。
+- [ ] 完善后台运营页面接口。
+- [ ] 配置渠道异常告警。
+- [ ] 设计灰度切流方案。
+
+### 交付接口/能力
+
+- [ ] complex 渠道登录。
+- [ ] complex 渠道支付回调。
+- [ ] 渠道特殊参数接口。
+- [ ] 优惠券领取/使用。
+- [ ] 礼包码领取/核销。
+- [ ] 后台运营查询接口。
+- [ ] 渠道 Dashboard。
+
+### 验收标准
+
+- [ ] 每个迁移渠道都有 contract test。
+- [ ] 渠道失败率可以在 Grafana 按 channel 查看。
+- [ ] 渠道回调可通过 `order_no` 追踪。
+- [ ] 灰度切流时可回退到 PHP。
+- [ ] 运营人员可以通过后台查看核心数据。
+
+### 暂不做
+
+- 不一次性迁移所有 complex 渠道。
+- 不改第三方渠道协议。
+- 不把运营后台做成完整新产品,只做重构需要的核心闭环。
+
+## 7. 版本后续预留
+
+V0.6 之后可以考虑:
+
+- 全量支付渠道迁移。
+- 全量 complex 渠道迁移。
+- 管理后台完整重构。
+- 数据表清理和字段规范化。
+- Redis key 规范化。
+- 服务拆分。
+- Kubernetes 部署。
+- 更完整的风控、防沉迷、实名、数据报表体系。
+
+## 8. 当前最优先 TODO
+
+在真正进入 V0.1 开发前,还需要完成以下前置工作:
+
+- [ ] 生成 `06-database-schema-inventory.md`:全量表结构、字段、索引。
+- [ ] 生成 `07-api-table-access-map.md`:API 代码读写表关系。
+- [ ] 生成 `08-business-domain-data-map.md`:业务域和数据表映射。
+- [ ] 生成 `09-payment-and-order-state-machine.md`:支付订单状态机。
+- [ ] 确认 GoFrame 项目目录放置位置。
+- [ ] 确认是否需要保留 PHP 与 Go 并行灰度。
+- [ ] 确认第一版后台是否使用现有管理后台页面,还是新建 Go 后台 API。
+
+## 9. 推荐执行顺序
+
+```text
+先做文档和数据映射
+  -> V0.1 技术栈底座
+  -> V0.2 账户体系
+  -> V0.3 SDK 协议和基础业务
+  -> V0.4 支付订单核心
+  -> V0.5 渠道和运营闭环
+```
+
+这个顺序的核心原因:支付和渠道是最高风险模块,必须建立在协议、账户、日志、指标、队列、状态机都清楚的基础上。

+ 4 - 0
nginx/.gitignore

@@ -0,0 +1,4 @@
+/default_test.conf
+/default_dev.conf
+/default_pro.conf
+/*.confs

+ 49 - 0
nginx/default.conf

@@ -0,0 +1,49 @@
+server {
+    listen 80;
+	# listen 443 ssl http2;
+    server_name devsdkapi.7dgame.cn devsdkadmin.7dgame.cn devsdkwww.7dgame.cn devsdkjhgame.7dgame.cn devsdkm.7dgame.cn devcpsapi.7dgame.cn devsdkapi.qmgames.cn devadmin.qmgames.cn devwww.qmgames.cn devjhgame.qmgames.cn devm.qmgames.cn devcpsapi.qmgames.cn;
+
+    client_max_body_size 2048m;
+    # real_ip_header X-Forwarded-For;
+
+	# Specifies the main log format.
+
+    # Sets the path, format, and configuration for a buffered log write.
+    #access_log /var/log/nginx/access.log main;.
+
+	if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") {
+	    set $year $1;
+	    set $month $2;
+	    set $day $3;
+	}
+	access_log /app/logs/access.$year-$month-$day-access.log combined;
+    error_log /app/logs/default_error.log;
+    root /app/www/new_sdk/public;
+
+	# ssl_certificate /app/key/server.pem;
+    # ssl_certificate_key /app/key/server.key;
+
+
+    location /upload {
+        try_files $uri $uri/ =404;
+    }
+
+    location / {
+        try_files $uri $uri/ /index.php$is_args$args;
+        index index.php index.html;
+
+        if (!-e $request_filename) {
+            rewrite  ^(.*)$  /index.php?s=/$1  last;
+        }
+    }
+
+    location ~ \.php$ {
+        fastcgi_pass 127.0.0.1:9000;
+        fastcgi_index index.php;
+        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+        fastcgi_param PHP_VALUE "error_log=/var/log/nginx/application_php_errors.log";
+        fastcgi_buffers 16 16k;
+        fastcgi_buffer_size 32k;
+        include fastcgi_params;
+    }
+}

+ 40 - 0
nginx/https.conf

@@ -0,0 +1,40 @@
+server {
+    #listen 80;
+	listen 443 ssl http2;
+    server_name t.qmgames.cn;
+
+    client_max_body_size 108M;
+    
+	if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") {
+	    set $year $1;
+	    set $month $2;
+	    set $day $3;
+	}
+
+    ssl_certificate /app/key/t.qmgames.cn.pem;
+    ssl_certificate_key /app/key/t.qmgames.cn.key;
+
+
+	access_log /app/logs/access.$year-$month-$day-access.log combined;
+    error_log /app/logs/default_error.log;
+    root /app/www/new_sdk/public;
+	
+    location / {
+        #try_files $uri $uri/ /index.php$is_args$args;
+        index index.php index.html;
+
+        if (!-e $request_filename) {
+            rewrite  ^(.*)$  /index.php?s=/$1  last;
+        }
+    }
+ 
+    location ~ \.php$ {
+        fastcgi_pass 127.0.0.1:9000;
+        fastcgi_index index.php;
+        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+        fastcgi_param PHP_VALUE "error_log=/var/log/nginx/application_php_errors.log";
+        fastcgi_buffers 16 16k;
+        fastcgi_buffer_size 32k;
+        include fastcgi_params;
+    }
+}

+ 101 - 0
nginx/nginx.conf

@@ -0,0 +1,101 @@
+user nginx;
+
+# Set number of worker processes automatically based on number of CPU cores.
+worker_processes auto;
+
+# Enables the use of JIT for regular expressions to speed-up their processing.
+pcre_jit on;
+
+# Configures default error logger.
+error_log /var/log/nginx/error.log warn;
+
+# Includes files with directives to load dynamic modules.
+include /etc/nginx/modules/*.conf;
+
+
+events {
+        use epoll;
+    	worker_connections  102400;
+}
+
+http {
+        # Includes mapping of file name extensions to MIME types of responses
+        # and defines the default type.
+        include /etc/nginx/mime.types;
+        default_type application/octet-stream;
+
+        # Name servers used to resolve names of upstream servers into addresses.
+        # It's also needed when using tcpsocket and udpsocket in Lua modules.
+        #resolver 208.67.222.222 208.67.220.220;
+
+        # Don't tell nginx version to clients.
+        server_tokens off;
+
+        # Specifies the maximum accepted body size of a client request, as
+        # indicated by the request header Content-Length. If the stated content
+        # length is greater than this size, then the client receives the HTTP
+        # error code 413. Set to 0 to disable.
+        client_max_body_size 10m;
+
+        # Timeout for keep-alive connections. Server will close connections after
+        # this time.
+        keepalive_timeout 120;
+
+        # Sendfile copies data between one FD and other from within the kernel,
+        # which is more efficient than read() + write().
+        sendfile on;
+
+        # Don't buffer data-sends (disable Nagle algorithm).
+        # Good for sending frequent small bursts of data in real time.
+        tcp_nodelay on;
+
+        # Causes nginx to attempt to send its HTTP response head in one packet,
+        # instead of using partial frames.
+        #tcp_nopush on;
+
+        fastcgi_cache_path /etc/nginx/fastcgi_cache levels=1:2 keys_zone=TEST:10m inactive=5m;
+        fastcgi_temp_path /etc/nginx/temp;
+
+        fastcgi_connect_timeout 300;
+        fastcgi_send_timeout 300;
+        fastcgi_read_timeout 300;
+        fastcgi_buffer_size 64k;
+        fastcgi_buffers 4 64k;
+        fastcgi_busy_buffers_size 128k;
+        fastcgi_temp_file_write_size 128k;
+
+        # Path of the file with Diffie-Hellman parameters for EDH ciphers.
+        #ssl_dhparam /etc/ssl/nginx/dh2048.pem;
+
+        # Specifies that our cipher suits should be preferred over client ciphers.
+        ssl_prefer_server_ciphers on;
+
+        # Enables a shared SSL cache with size that can hold around 8000 sessions.
+        ssl_session_cache shared:SSL:2m;
+
+	server_names_hash_bucket_size 128;
+    	client_header_buffer_size 32k;
+    	large_client_header_buffers 4 32k;
+
+
+        # Enable gzipping of responses.
+        gzip  on;
+        gzip_min_length  1k;
+        gzip_buffers     4 16k;
+        gzip_http_version 1.0;
+        gzip_comp_level 2;
+        gzip_types text/plain application/javascript application/x-javascript text/css text/javascript application/x-httpd-php image/jpeg image/gif image/png application/xml;
+
+        # Set the Vary HTTP header as defined in the RFC 2616.
+        gzip_vary on;
+
+        # Enable checking the existence of precompressed files.
+        #gzip_static on;
+        
+        log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+                '$status $body_bytes_sent "$http_referer" '
+                '"$http_user_agent" "$http_x_forwarded_for"';
+		
+        # Includes virtual hosts configs.
+        include /etc/nginx/conf.d/*.conf;
+}

+ 37 - 0
nginx/sdkapi.qmgames.cn.conf

@@ -0,0 +1,37 @@
+server {
+    listen 443 ssl http2;
+    server_name sdkapi.qmgames.cn;
+
+    client_max_body_size 2048M;
+    
+    if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") {
+        set $year $1;
+        set $month $2;
+        set $day $3;
+    }
+    access_log /app/logs/access.$year-$month-$day-access.log combined;
+    error_log /app/logs/default_error.log;
+    root /app/www/new_sdk/public;
+
+    ssl_certificate /app/key/sdkapi.qmgames.cn.pem;
+    ssl_certificate_key /app/key/sdkapi.qmgames.cn.key;
+
+    location / {
+        #try_files $uri $uri/ /index.php$is_args$args;
+        index index.php index.html;
+
+        if (!-e $request_filename) {
+            rewrite  ^(.*)$  /index.php?s=/$1  last;
+        }
+    }
+ 
+    location ~ \.php$ {
+        fastcgi_pass 127.0.0.1:9000;
+        fastcgi_index index.php;
+        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+        fastcgi_param PHP_VALUE "error_log=/var/log/nginx/application_php_errors.log";
+        fastcgi_buffers 16 16k;
+        fastcgi_buffer_size 32k;
+        include fastcgi_params;
+    }
+}

+ 19 - 0
php-fpm/php-ini-overrides.ini

@@ -0,0 +1,19 @@
+upload_max_filesize = 1024M
+post_max_size = 108M
+expose_php = Off
+max_input_vars = 5000
+memory_limit = 512M
+
+[Date]
+date.timezone = Asia/Shanghai
+
+zend_extension=opcache.so
+[opcache]
+opcache.enable= 0
+opcache.enable_cli=0
+opcache.memory_consumption=128
+opcache.interned_strings_buffer=8
+opcache.revalidate_freq=60
+opcache.fast_shutdown=1
+opcache.validate_timestamps=0
+opcache.file_cache=/tmp/php

+ 7 - 0
php-fpm/sources.list

@@ -0,0 +1,7 @@
+deb http://mirrors.ustc.edu.cn/debian stable main contrib non-free
+# deb-src http://mirrors.ustc.edu.cn/debian stable main contrib non-free
+deb http://mirrors.ustc.edu.cn/debian stable-updates main contrib non-free
+# deb-src http://mirrors.ustc.edu.cn/debian stable-updates main contrib non-free
+
+# deb http://mirrors.ustc.edu.cn/debian stable-proposed-updates main contrib non-free
+# deb-src http://mirrors.ustc.edu.cn/debian stable-proposed-updates main contrib non-free

+ 6 - 0
php-fpm/up.sh

@@ -0,0 +1,6 @@
+#!/bin/sh
+/usr/sbin/crond
+nohup php-fpm > /var/log/php-fpm.log 2>&1 &
+# su www-data -s /bin/sh -c "/app/www/youxi9-api/artisan queue:work --daemon --quiet --queue=check_channel --delay=5 --sleep=5 --tries=5 2>&1" &
+chown www-data -R /app/www/new_sdk &
+nginx -g "daemon off;"

+ 364 - 0
review_doc/01-基础设施审查.md

@@ -0,0 +1,364 @@
+# 第一阶段:基础设施审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | Dockerfile、docker-compose、Nginx、PHP配置、定时任务、启动脚本 |
+| 发现问题数 | **23个** |
+| 高风险问题 | 6个 |
+| 中风险问题 | 10个 |
+| 低风险问题 | 7个 |
+
+---
+
+## 1. Dockerfile 审查
+
+**文件位置**:`Dockerfile`
+
+### 🔴 高风险问题
+
+#### 1.1 PHP 版本已终止支持
+```dockerfile
+FROM php:7.3-fpm-alpine
+```
+- **问题**:PHP 7.3 已于 2021年12月6日 EOL,不再接收安全更新
+- **风险**:已知漏洞无法修复,存在被攻击风险
+- **建议**:升级到 PHP 8.1 或 8.2
+
+#### 1.2 使用 root 用户运行服务
+```dockerfile
+CMD ["/bin/sh", "/usr/local/bin/up.sh", "/app/www/new_sdk/crontab.sh"]
+```
+- **问题**:容器内所有服务以 root 身份运行
+- **风险**:容器逃逸后直接获得 root 权限
+- **建议**:创建专用用户运行应用
+
+#### 1.3 敏感文件可能被 COPY 到镜像
+```dockerfile
+COPY . /app
+```
+- **问题**:会复制所有文件,包括 `.env`、密钥文件等
+- **风险**:敏感信息泄露到镜像层
+- **建议**:使用 `.dockerignore` 排除敏感文件
+
+### 🟡 中风险问题
+
+#### 1.4 Composer 使用 snapshot 版本
+```dockerfile
+&& php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --snapshot
+```
+- **问题**:使用 `--snapshot` 安装非稳定版本
+- **风险**:可能引入未测试的代码
+- **建议**:移除 `--snapshot` 参数
+
+#### 1.5 缺少多阶段构建
+- **问题**:构建依赖和运行时依赖混在一起
+- **风险**:镜像体积过大,攻击面大
+- **建议**:使用多阶段构建分离构建和运行环境
+
+---
+
+## 2. docker-compose.yml 审查
+
+**文件位置**:`docker-compose.yml`
+
+### 🔴 高风险问题
+
+#### 2.1 缺少资源限制
+```yaml
+services:
+    new_sdk:
+      build: .
+```
+- **问题**:没有设置 CPU/内存限制
+- **风险**:单个容器可能耗尽宿主机资源
+- **建议**:添加 `deploy.resources.limits`
+
+### 🟡 中风险问题
+
+#### 2.2 缺少健康检查
+- **问题**:没有 healthcheck 配置
+- **风险**:无法自动检测服务故障
+- **建议**:添加健康检查配置
+
+#### 2.3 缺少重启策略
+- **问题**:没有 restart 策略
+- **风险**:服务崩溃后不会自动恢复
+- **建议**:添加 `restart: unless-stopped`
+
+#### 2.4 非标准端口映射
+```yaml
+ports:
+  - "66:80"
+  - "67:443"
+```
+- **问题**:使用非标准端口
+- **风险**:可能被防火墙阻止,混淆攻击者(优点)
+- **建议**:根据实际部署环境调整
+
+---
+
+## 3. Nginx 配置审查
+
+**文件位置**:`nginx/nginx.conf`, `nginx/default.conf`, `nginx/https.conf`, `nginx/sdkapi.qmgames.cn.conf`
+
+### 🔴 高风险问题
+
+#### 3.1 client_max_body_size 过大
+```nginx
+client_max_body_size 2048M;  # default.conf
+client_max_body_size 2048M;  # sdkapi.qmgames.cn.conf
+```
+- **问题**:允许上传 2GB 文件
+- **风险**:DoS 攻击、磁盘空间耗尽
+- **建议**:根据业务需求设置合理值(如 50M)
+
+#### 3.2 缺少安全响应头
+- **问题**:未配置以下安全头
+  - `X-Frame-Options` - 防止点击劫持
+  - `X-Content-Type-Options` - 防止 MIME 类型嗅探
+  - `X-XSS-Protection` - XSS 过滤
+  - `Content-Security-Policy` - 内容安全策略
+  - `Strict-Transport-Security` - HSTS
+- **建议**:添加所有安全响应头
+
+#### 3.3 SSL 配置不完整
+```nginx
+ssl_certificate /app/key/sdkapi.qmgames.cn.pem;
+ssl_certificate_key /app/key/sdkapi.qmgames.cn.key;
+```
+- **问题**:缺少以下配置:
+  - `ssl_protocols` - 应只允许 TLSv1.2+
+  - `ssl_ciphers` - 应使用强密码套件
+  - `ssl_session_timeout`
+  - `ssl_stapling` - OCSP 装订
+- **建议**:完善 SSL 安全配置
+
+### 🟡 中风险问题
+
+#### 3.4 缺少 Rate Limiting
+- **问题**:没有配置请求频率限制
+- **风险**:API 被暴力破解、DDoS
+- **建议**:添加 `limit_req_zone` 和 `limit_conn_zone`
+
+#### 3.5 default.conf 暴露多个域名
+```nginx
+server_name devsdkapi.7dgame.cn devsdkadmin.7dgame.cn ...;
+```
+- **问题**:开发域名暴露在生产配置中
+- **建议**:分离开发和生产配置
+
+---
+
+## 4. PHP 配置审查
+
+**文件位置**:`php-fpm/php-ini-overrides.ini`
+
+### 🟡 中风险问题
+
+#### 4.1 upload_max_filesize 过大
+```ini
+upload_max_filesize = 1024M
+```
+- **问题**:允许上传 1GB 文件
+- **风险**:配合 Nginx 配置,可能导致资源耗尽
+- **建议**:根据业务需求设置合理值
+
+#### 4.2 OPcache 被禁用
+```ini
+opcache.enable= 0
+```
+- **问题**:PHP OPcache 未启用
+- **风险**:性能损失约 50-70%
+- **建议**:生产环境应启用 OPcache
+
+### ✅ 良好实践
+
+```ini
+expose_php = Off  # 不暴露 PHP 版本信息
+```
+
+---
+
+## 5. 定时任务审查
+
+**文件位置**:`crontabs/crontabfile.txt`
+
+### 🟡 中风险问题
+
+#### 5.1 pay_timeout 任务可能并发冲突
+```cron
+* * * * * bash /app/script/pay_timeout.sh
+* * * * * sleep 30; bash /app/script/pay_timeout.sh
+```
+- **问题**:每分钟执行两次,如果任务执行超过30秒可能并发
+- **风险**:重复处理订单
+- **现状**:脚本中使用了 flock 锁,部分缓解了问题
+- **建议**:确保所有脚本都有锁机制
+
+#### 5.2 daily.sh 是空脚本
+```bash
+#!/bin/sh
+# cd /app/www/new_sdk/
+# su www-data -s /usr/local/bin/php artisan command:jiubidetail >> /app/script/jiubidetail.log
+# sh /app/www/push_websocketstart.sh >>
+```
+- **问题**:脚本内容被注释掉,没有实际功能
+- **建议**:清理无用脚本或恢复功能
+
+---
+
+## 6. 启动脚本审查
+
+**文件位置**:`php-fpm/up.sh`
+
+### 🟡 中风险问题
+
+#### 6.1 PHP-FPM 后台运行
+```bash
+nohup php-fpm > /var/log/php-fpm.log 2>&1 &
+```
+- **问题**:PHP-FPM 以后台进程运行
+- **风险**:进程崩溃无法自动重启
+- **建议**:使用 supervisord 管理进程
+
+#### 6.2 文件权限设置
+```bash
+chown www-data -R /app/www/new_sdk &
+```
+- **问题**:使用 `&` 后台执行 chown
+- **风险**:可能在文件未完全设置好权限时就开始处理请求
+- **建议**:移除 `&`,确保权限设置完成后再启动服务
+
+---
+
+## 7. 入口文件审查
+
+**文件位置**:`www/new_sdk/public/index.php`
+
+### 🔴 高风险问题
+
+#### 7.1 CORS 配置过于宽松
+```php
+header('Access-Control-Allow-Origin: *');
+header("Access-Control-Allow-Methods: *");
+```
+- **问题**:允许任意来源和任意方法访问
+- **风险**:CSRF 攻击、数据泄露
+- **建议**:限制为特定域名
+
+#### 7.2 Socket 超时禁用
+```php
+ini_set('default_socket_timeout', -1);
+```
+- **问题**:禁用了 socket 超时
+- **风险**:可能导致请求永久阻塞
+- **建议**:设置合理的超时时间
+
+---
+
+## 8. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 高风险(需立即修复)
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | PHP 7.3 EOL | Dockerfile:1 |
+| 2 | root 用户运行 | Dockerfile:78 |
+| 3 | 敏感文件 COPY | Dockerfile:66 |
+| 4 | 资源限制缺失 | docker-compose.yml |
+| 5 | 安全响应头缺失 | nginx/*.conf |
+| 6 | CORS 过于宽松 | public/index.php:14-18 |
+
+#### 🟡 中风险(建议尽快修复)
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | Composer snapshot | Dockerfile:55 |
+| 2 | client_max_body_size 过大 | nginx/default.conf:6 |
+| 3 | SSL 配置不完整 | nginx/https.conf |
+| 4 | 缺少 Rate Limiting | nginx/*.conf |
+| 5 | OPcache 禁用 | php-ini-overrides.ini:12 |
+| 6 | upload_max_filesize 过大 | php-ini-overrides.ini:1 |
+| 7 | PHP-FPM 后台运行 | php-fpm/up.sh:3 |
+| 8 | chown 后台执行 | php-fpm/up.sh:5 |
+| 9 | pay_timeout 并发风险 | crontabs/crontabfile.txt:6-7 |
+| 10 | Socket 超时禁用 | public/index.php:20 |
+
+#### 🟢 低风险(建议优化)
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | 缺少健康检查 | docker-compose.yml |
+| 2 | 缺少重启策略 | docker-compose.yml |
+| 3 | 非标准端口 | docker-compose.yml:10-11 |
+| 4 | 开发域名暴露 | nginx/default.conf:4 |
+| 5 | daily.sh 空脚本 | script/daily.sh |
+| 6 | 缺少多阶段构建 | Dockerfile |
+| 7 | 缺少 .dockerignore | 项目根目录 |
+
+---
+
+## 9. 修复优先级建议
+
+### 第一优先级(安全相关)
+1. 限制 CORS 来源域名
+2. 添加 Nginx 安全响应头
+3. 完善 SSL 配置
+4. 创建 .dockerignore 排除敏感文件
+
+### 第二优先级(稳定性相关)
+1. 添加 docker-compose 资源限制
+2. 添加健康检查和重启策略
+3. 修复 PHP-FPM 启动方式
+4. 启用 OPcache
+
+### 第三优先级(长期规划)
+1. 升级 PHP 版本到 8.x
+2. 升级 ThinkPHP 框架版本
+3. 实施多阶段构建
+4. 添加 Rate Limiting
+
+---
+
+## 10. 参考配置
+
+### Nginx 安全头配置示例
+```nginx
+add_header X-Frame-Options "SAMEORIGIN" always;
+add_header X-Content-Type-Options "nosniff" always;
+add_header X-XSS-Protection "1; mode=block" always;
+add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
+```
+
+### SSL 安全配置示例
+```nginx
+ssl_protocols TLSv1.2 TLSv1.3;
+ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
+ssl_prefer_server_ciphers off;
+ssl_session_timeout 1d;
+ssl_session_cache shared:SSL:10m;
+ssl_session_tickets off;
+ssl_stapling on;
+ssl_stapling_verify on;
+```
+
+### .dockerignore 示例
+```
+.env
+.env_*
+.git
+.gitignore
+.idea
+*.md
+key/
+logs/
+runtime/
+vendor/
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 454 - 0
review_doc/02-安全审查.md

@@ -0,0 +1,454 @@
+# 第二阶段:安全审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | SQL注入、XSS、CSRF、认证授权、文件上传、支付安全、会话管理 |
+| 发现问题数 | **47个** |
+| 严重风险 | 12个 |
+| 高风险 | 15个 |
+| 中风险 | 14个 |
+| 低风险 | 6个 |
+
+---
+
+## 1. SQL 注入风险
+
+### 🔴 严重风险
+
+#### 1.1 用户输入直接拼接 SQL
+**文件**:`mcpsapi/controller/ChannelRebind.php:74`
+```php
+$cusername = $this->input('cusername','','trim');
+$whereRaw .= ' origin_cusername="'.$cusername.'" or cusername="'.$cusername.'" ';
+```
+**风险**:用户输入 `$cusername` 直接拼接到 SQL 语句,可直接进行 SQL 注入攻击
+
+**相同问题位置**:
+- `guildapi/controller/ChannelRebind.php:74`
+- `admin/controller/MemberRebind.php:97`
+
+#### 1.2 JOIN 条件直接拼接用户输入
+**文件**:`api/controller/v2/Gift.php:206`
+```php
+->join('cy_libaoinfo a', 'll.infoid=a.id and ll.user_id="' . $this->input['userid'] . '"')
+```
+**相同问题**:`api/controller/v1/Gift.php:108`
+
+#### 1.3 Db::query 使用变量插值
+**文件**:`common/logic/Complex.php:166`
+```php
+$query = Db::query("SELECT SUM(total_amount*divide_point) as total FROM cy_polychannel_game_frozen WHERE `channel_id`={$channel_id} AND `status`=1");
+```
+
+### 🟡 中风险(约 50+ 处)
+
+多个文件使用 `whereRaw` 配合变量插值但未参数绑定:
+- `api/controller/v2/User.php:918`
+- `api/controller/v2/Gift.php:41`
+- `service/WelfareService.php` 多处
+- `admin/controller/Welfare*.php` 多处
+- `guildapi/controller/*.php` 多处
+- `mcpsapi/controller/*.php` 多处
+
+### 修复建议
+```php
+// ❌ 错误写法
+$whereRaw .= ' origin_cusername="'.$cusername.'"';
+
+// ✅ 正确写法
+$whereRaw .= ' origin_cusername=?';
+$bindParams[] = $cusername;
+```
+
+---
+
+## 2. 认证与授权
+
+### 🔴 严重风险
+
+#### 2.1 GuildAPI GET 请求免密登录
+**文件**:`guildapi/controller/Login.php:98-103`
+```php
+}else{
+    $username = trim(input('get.username'));
+    $adminInfo = Db::name('nw_channel_admin')->where(['username' => $username])->find();
+    return $this->getdoLogin($adminInfo);  // 直接登录,无密码验证!
+}
+```
+**风险**:通过 GET 请求仅传 `username` 即可直接登录,无需密码
+
+#### 2.2 权限检查逻辑缺陷 — 未注册规则默认放行
+**文件**:`common/logic/Auth.php:46-48`
+```php
+if ($findAuthRuleCount == 0) {
+    //return false;
+    return true;  // ← 关键缺陷:未配置的路由默认放行
+}
+```
+**风险**:任何未配置到菜单的控制器/操作都无需权限即可访问
+
+#### 2.3 密码使用 MD5 哈希
+**文件**:`common.php:681-684`
+```php
+function mg_password($pw)
+{
+    return md5(Env::get('ADMIN_PASS_PRE') . $pw);
+}
+```
+**风险**:MD5 已不安全,易被彩虹表攻击
+
+### 🔴 高风险
+
+#### 2.4 密码修改存在越权风险(IDOR)
+**文件**:`admin/controller/Login.php:206-253`
+```php
+$id = input('post.id','','intval');  // id 从客户端获取
+$adminModel->where(['id'=>$id])->update(['password'=>mg_password($new_password)]);
+```
+**风险**:未校验是否为当前登录用户,可修改其他管理员密码
+
+#### 2.5 硬编码超级管理员 ID
+**文件**:`common/logic/Auth.php:33`
+```php
+if ($userId == 1) { return true; }  // ID=1 直接跳过权限检查
+```
+**相同位置**:`admin/controller/Rbac.php:33`, `admin/controller/User.php:40`
+
+#### 2.6 API 调试模式可绕过加密和签名
+**文件**:`api/controller/Api.php:65-110`
+```php
+if (!$decryptStr || $api_debug == true) {
+    $this->input = input();  // 直接使用明文参数,绕过 AES 和签名验证
+}
+```
+**风险**:通过请求头 `qmapidebug: true` 可绕过所有安全验证
+
+### 🟡 中风险
+
+#### 2.7 Admin 登录无暴力破解防护
+**文件**:`admin/controller/Login.php:110-121`
+- 无登录失败次数限制
+- 无 IP 限流
+- 无账号锁定机制
+
+#### 2.8 API 签名使用 MD5 + AES-ECB
+**文件**:`api/controller/Api.php:154,204`
+```php
+$mysgin = md5($arg);  // MD5 签名强度不足
+$str = openssl_encrypt($data, 'AES-128-ECB', $key);  // ECB 模式不安全
+```
+
+---
+
+## 3. XSS 防护
+
+### 🔴 严重风险
+
+#### 3.1 Admin 模板大量反射型 XSS
+**位置**:`admin/view/` 下 **652+ 处**
+
+以下模板直接输出未转义的用户输入:
+```html
+<!-- admin/view/pay/list.html:189 -->
+<input value="{$Request.get.orderid}">
+
+<!-- admin/view/pay/list.html:195 -->
+<input value="{$Request.get.username}">
+
+<!-- admin/view/game_discount_v2/index.html:123 -->
+<script>var initGameIds = '{$Request.get.game_id}'</script>
+```
+
+**完整漏洞列表**:
+| 文件 | 行号 | 变量 |
+|------|------|------|
+| `admin/view/pay/list.html` | 134,189,195,201,208 | orderid, username, roleid, rolename, servername |
+| `admin/view/pay/index.html` | 158,164 | orderid, username |
+| `admin/view/member/login_list.html` | 73,80 | username, imeil |
+| `admin/view/member/bind_list.html` | 55,63 | username, mobile |
+| `admin/view/member/index.html` | 163,170 | m_nickname, m_username |
+| `admin/view/game_package/add.html` | 165 | game_id |
+| `admin/view/welfare_grant/index.html` | 81 | username |
+
+### 🟡 中风险
+
+#### 3.2 全局输入过滤为空
+**文件**:`config.php:50`
+```php
+'default_filter' => ''  // 全局过滤为空
+```
+**仅 home/mobile 模块有过滤**:
+```php
+'default_filter' => 'trim,htmlspecialchars,addslashes,strip_tags'
+```
+**admin/api/guildapi/mcpsapi 模块均无输入过滤**
+
+#### 3.3 removeXss() 定义但未使用
+**文件**:`common.php:109-122` — 定义了 HTMLPurifier 过滤函数,但从未被调用
+
+---
+
+## 4. CSRF 防护
+
+### 🔴 严重风险
+
+#### 4.1 完全没有 CSRF 防护
+
+| 检查项 | 状态 |
+|--------|------|
+| CSRF Token 生成/验证 | ❌ 未实现 |
+| 表单 `__token` 隐藏字段 | ❌ 未使用 |
+| ThinkPHP 内置 CSRF 中间件 | ❌ 未启用 |
+| 任何表单中的 CSRF 隐藏字段 | ❌ 完全没有 |
+
+**影响范围**:所有 admin 后台表单均存在跨站请求伪造风险
+
+---
+
+## 5. 文件上传安全
+
+### 🔴 严重风险
+
+#### 5.1 绕过框架直接使用 move_uploaded_file
+**文件**:`admin/controller/GamePackage.php:341`
+```php
+$result = move_uploaded_file($_FILES['data']['tmp_name'], iconv('UTF-8', 'GBK', $filename));
+```
+**风险**:
+- 仅检查后缀名是否为 `['ipa','apk']`
+- 无 MIME 类型校验
+- 无文件内容校验(magic bytes)
+- 目录权限 `0777`(第335行)
+
+**相同问题**:`admin/controller/GameUpload.php:46`
+
+#### 5.2 Base64 图片上传类型校验可绕过
+**文件**:`home/controller/Member.php:1320-1352`
+```php
+if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)){
+    $type = $result[2];  // 仅校验 base64 头部声明,不校验实际内容
+```
+**风险**:
+- 可伪造头部上传恶意文件
+- 文件写入到 `RUNTIME_PATH` 目录
+
+### 🟡 中风险
+
+#### 5.3 上传目录无执行限制
+- `public/.htaccess` 文件为空
+- 上传目录没有 `.htaccess` 禁止 PHP 执行
+- 如果上传了 `.php` 文件可直接执行
+
+#### 5.4 后缀名校验逻辑缺陷
+**文件**:`admin/controller/GamePackage.php:313-316`
+- 未考虑大小写(如 `.APK`)
+- 未考虑双扩展名(如 `.apk.php`)
+
+---
+
+## 6. 支付安全
+
+### 🔴 严重风险
+
+#### 6.1 硬编码支付密钥
+**文件**:`service/PayService.php:575`
+```php
+$params['sign'] = $wxPay->MakeSign($params, '66052a198653943dbc6c8032f1a40a9c');
+```
+**相同问题**:
+- `common/factory/pay/SywPay.php:107,203`
+- `common/logic/PayCallback.php:182`
+
+#### 6.2 SSL 证书验证禁用(全局 51 处)
+```php
+curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
+```
+**位置**:
+- `common.php:373-374,884-885`
+- `service/PayService.php:880-881,914-915`
+- `common/library/WeixinPay.php:85-88`
+
+#### 6.3 金额精度问题
+**文件**:`common.php:1475-1489`
+```php
+function formatFenToYuan($fen, $long = 2) {
+    return number_format($fen / 100, $long, '.', ''); // 浮点数除法可能丢失精度
+}
+```
+
+### 🟡 中风险
+
+#### 6.4 缺少重放攻击防护
+- 回调接口无 nonce/timestamp 验证
+- 无请求频率限制
+
+#### 6.5 订单状态竞态条件
+**文件**:`api/controller/v1/PayNotify.php:950-951`
+```php
+$payInfo = $payModel->where(['orderid' => $orderid])->find();
+if ($payInfo['status'] == 1 && $payInfo['real_amount'] == $real_amount) {
+    return true; // 先查询再更新,存在并发风险
+}
+```
+
+#### 6.6 签名算法安全性低
+- 多处使用 MD5 签名
+- `PayNotify.php:367` - `md5($str . md5($key))`
+
+---
+
+## 7. 其他安全问题
+
+### 🟡 中风险
+
+#### 7.1 Cookie 安全配置缺失
+**文件**:`config.php:286-290`
+```php
+'secure' => false,    // 允许 HTTP 明文传输
+'httponly' => '',      // JS 可读取 Cookie
+```
+
+#### 7.2 CORS 配置过于宽松
+**文件**:`public/index.php:14`, `guildapi/controller/Guild.php:70`
+```php
+header('Access-Control-Allow-Origin: *');  // 允许任意来源
+```
+
+#### 7.3 安全响应头完全缺失
+未设置以下安全头:
+- `Content-Security-Policy`
+- `X-Frame-Options`
+- `X-XSS-Protection`
+- `X-Content-Type-Options`
+- `Strict-Transport-Security`
+
+#### 7.4 默认开启调试模式
+**文件**:`config.php:24`
+```php
+'app_debug' => Env::get('app_debug', true)  // 默认开启
+```
+
+---
+
+## 8. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 严重风险(需立即修复)
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | SQL 注入 - 用户输入直接拼接 | `mcpsapi/controller/ChannelRebind.php:74` |
+| 2 | SQL 注入 - JOIN 条件拼接 | `api/controller/v2/Gift.php:206` |
+| 3 | GuildAPI GET 免密登录 | `guildapi/controller/Login.php:98-103` |
+| 4 | 权限未注册规则默认放行 | `common/logic/Auth.php:46-48` |
+| 5 | 密码使用 MD5 哈希 | `common.php:681-684` |
+| 6 | Admin 模板 652+ 处 XSS | `admin/view/` 多处 |
+| 7 | 完全没有 CSRF 防护 | 全局 |
+| 8 | 文件上传绕过框架 | `admin/controller/GamePackage.php:341` |
+| 9 | 硬编码支付密钥 | `service/PayService.php:575` |
+| 10 | SSL 证书验证禁用(51处) | `common.php` 等 |
+| 11 | API 调试模式绕过验证 | `api/controller/Api.php:65-110` |
+| 12 | Base64 上传校验可绕过 | `home/controller/Member.php:1320` |
+
+#### 🔴 高风险
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | 密码修改 IDOR 越权 | `admin/controller/Login.php:206` |
+| 2 | 硬编码超管 ID | `common/logic/Auth.php:33` |
+| 3 | SQL 注入 - Db::query 变量插值 | `common/logic/Complex.php:166` |
+| 4 | 上传目录 0777 权限 | `admin/controller/GamePackage.php:335` |
+| 5 | 金额精度问题 | `common.php:1475-1489` |
+
+#### 🟡 中风险
+| 序号 | 问题 | 数量 |
+|------|------|------|
+| 1 | whereRaw 未参数绑定 | 约 50+ 处 |
+| 2 | 登录无暴力破解防护 | 1 处 |
+| 3 | API 签名 MD5+ECB | 多处 |
+| 4 | 缺少重放攻击防护 | 全局 |
+| 5 | 订单竞态条件 | 1 处 |
+| 6 | Cookie 配置不安全 | 全局 |
+| 7 | CORS 过于宽松 | 2 处 |
+| 8 | 安全响应头缺失 | 全局 |
+| 9 | 全局输入过滤为空 | 1 处 |
+| 10 | 上传目录无执行限制 | 全局 |
+
+---
+
+## 9. 修复优先级
+
+### 第一优先级(立即修复 - 安全漏洞)
+1. 修复 SQL 注入高风险点(参数绑定)
+2. 删除 GuildAPI GET 免密登录
+3. 修复权限检查逻辑(`return false`)
+4. 修复 Admin 模板 XSS(转义输出)
+5. 添加 CSRF Token
+6. 修复文件上传安全问题
+
+### 第二优先级(尽快修复)
+1. 密码哈希升级为 bcrypt/argon2
+2. 限制 CORS 来源
+3. 启用 SSL 证书验证
+4. 添加安全响应头
+5. 密钥从环境变量读取
+6. 禁用生产环境调试模式
+
+### 第三优先级(计划修复)
+1. 升级签名算法(HMAC-SHA256)
+2. 添加登录暴力破解防护
+3. 添加重放攻击防护
+4. 金额处理改用 bcmath
+5. 完善日志脱敏
+
+---
+
+## 10. 参考代码
+
+### SQL 注入修复示例
+```php
+// ❌ 错误
+$whereRaw .= ' origin_cusername="'.$cusername.'"';
+Db::query("SELECT * WHERE id={$id}");
+
+// ✅ 正确
+$whereRaw .= ' origin_cusername=?';
+$bindParams[] = $cusername;
+Db::query("SELECT * WHERE id=?", [$id]);
+```
+
+### XSS 修复示例
+```html
+<!-- ❌ 错误 -->
+<input value="{$Request.get.username}">
+
+<!-- ✅ 正确 -->
+<input value="{:escape($Request.get.username)}">
+```
+
+### 密码哈希修复示例
+```php
+// ❌ 错误
+return md5($prefix . $password);
+
+// ✅ 正确
+return password_hash($password, PASSWORD_DEFAULT);
+// 验证
+return password_verify($input, $hashedPassword);
+```
+
+### CSRF Token 示例
+```html
+<form>
+    {csrf_field()}
+    <!-- 或 -->
+    <input type="hidden" name="__token" value="{$Request.token}">
+</form>
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 547 - 0
review_doc/03-架构设计审查.md

@@ -0,0 +1,547 @@
+# 第三阶段:架构设计审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | 模块划分、路由设计、数据库设计、多租户隔离、设计模式 |
+| 发现问题数 | **32个** |
+| 高风险 | 8个 |
+| 中风险 | 15个 |
+| 低风险 | 9个 |
+
+---
+
+## 1. 模块架构分析
+
+### 1.1 模块结构总览
+
+| 模块 | 域名映射 | 职责 | 控制器数 | 完整度 |
+|------|----------|------|----------|--------|
+| **admin** | admin.* | 后台管理系统 | 99 | ✅ 完整 |
+| **api** | sdkapi.* | SDK客户端接口 | 79 | ⚠️ 缺Service |
+| **common** | 禁止访问 | 公共基础层 | - | ✅ 完整 |
+| **complex** | jhgame.* | 聚合渠道接入 | 19 | ❌ 缺Model/Service |
+| **guildapi** | cpsapi.* | CPS渠道商API | 28 | ❌ 缺Model/Service |
+| **mcpsapi** | mcpsapi.* | MCPS渠道商API | 16 | ❌ 缺Model/Service |
+| **home** | www.* | PC官网 | 16 | ✅ 完整 |
+| **mobile** | m.* | 移动端官网 | 14 | ✅ 完整 |
+| **service** | - | 独立服务层 | - | ✅ 12个服务 |
+| **crontab** | - | 定时任务 | 3 | ⚠️ 简单 |
+| **command** | - | 命令行入口 | 1 | ⚠️ 简单 |
+
+### 1.2 模块依赖关系
+
+```
+                    ┌─────────────────────────────────────┐
+                    │          common (公共基础层)          │
+                    │  Model(140+) / Logic / Library /     │
+                    │  Service / Factory / Contracts       │
+                    └──────────┬──────────────────────────┘
+                               │
+        ┌──────────┬───────────┼───────────┬──────────┬──────────┐
+        ▼          ▼           ▼           ▼          ▼          ▼
+    ┌──────┐  ┌────────┐  ┌────────┐  ┌────────┐  ┌──────┐  ┌───────┐
+    │admin │  │  api   │  │guildapi│  │mcpsapi │  │ home │  │mobile │
+    └──────┘  └────────┘  └────────┘  └────────┘  └──────┘  └───────┘
+                               │
+                               ▼
+                    ┌──────────────────┐
+                    │    complex       │
+                    │  (53个渠道适配器) │
+                    └──────────────────┘
+```
+
+### 🔴 高风险问题
+
+#### 1.1 guildapi 与 mcpsapi 大量重复代码
+- **问题**:两个模块有 **14+ 个同名控制器**,代码几乎相同
+- **重复控制器**:Account, Alliance, ChannelRebind, ChannelRecharge, ChannelSettle, ChannelTrade, CoinTransfer, Game, Guild, Login, Member 等
+- **影响**:修改一处需同步修改另一处,维护成本高
+- **建议**:提取公共 Guild 基类到 common,差异通过配置实现
+
+#### 1.2 common 模块职责过重(上帝模块)
+- **问题**:
+  - `common/model` 包含 **140+ 个模型**,承载全部业务模型
+  - `common.php` 包含 **1800+ 行**全局函数
+  - 混杂渠道/支付/加密/通知等各种逻辑
+- **影响**:代码难以理解、测试和维护
+- **建议**:按业务域拆分到各模块
+
+#### 1.3 complex 模块只有 Controller
+- **问题**:19 个渠道控制器直接操作数据库,无独立业务层
+- **影响**:渠道接入逻辑散落在 controller 中,难以维护
+- **建议**:增加统一的渠道接入 service,使用策略模式
+
+### 🟡 中风险问题
+
+#### 1.4 api 模块 service 层薄弱
+- **问题**:79 个 controller 但只有 4 个 service 文件
+- **影响**:大量业务逻辑直接写在 controller 中
+- **建议**:将业务逻辑下沉到 service 层
+
+#### 1.5 home 与 mobile 模块高度重复
+- **问题**:两个模块有 **12 个同名控制器**,View 目录结构几乎一致
+- **影响**:维护成本高,修改需同步两处
+- **建议**:使用响应式设计或提取共享 controller/service
+
+#### 1.6 存在 .bak 备份文件和废弃代码
+- **位置**:
+  - `guildapi/ChannelSettle.php.bak`
+  - `guildapi/ChannelWithdraw.php.bak`
+  - `service/GamePayService_bak.php`
+  - `service/MemberCoinService_bak.php`
+- **建议**:清理废弃代码,使用版本控制管理历史
+
+---
+
+## 2. 路由设计分析
+
+### 2.1 子域名映射
+
+| 域名 | 模块 | 用途 |
+|------|------|------|
+| sdkapi.* | api | SDK核心接口 |
+| t.* | api | 推广短链 |
+| cpsapi.* | guildapi | CPS API |
+| mcpsapi.* | mcpsapi | MCPS API |
+| jhgame.* | complex | 聚合游戏 |
+| www.* | home | PC官网 |
+| m.* | mobile | 手机端 |
+| admin.* | admin | 后台管理 |
+| * | home | 默认(通配) |
+
+### 🔴 高风险问题
+
+#### 2.1 测试路由生产环境暴露
+**文件**:`route.php:156-157`
+```php
+Route::get('test_pay', 'Api/Index/pay');    // 测试支付!
+Route::get('notify', 'Api/Index/notify');   // 通知回调!
+```
+- **风险**:无域名约束,任何域名都可访问
+- **建议**:立即移除或添加环境判断
+
+#### 2.2 路由文件中直接查询数据库
+**文件**:`route.php:68,87`
+```php
+$arrAlias = db('nw_promotion_short_link')->where([...])->find();
+```
+- **风险**:数据库异常会导致整个路由系统崩溃
+- **建议**:迁移到中间件或服务层
+
+#### 2.3 `*` 通配域名风险
+**文件**:`route.php:27`
+```php
+'*' => 'home'
+```
+- **风险**:任意子域名都会路由到 home 模块
+- **建议**:移除通配或添加白名单验证
+
+### 🟡 中风险问题
+
+#### 2.4 v1/v2 API 无显式路由
+- **问题**:api 模块 45+ 个 v1 控制器、21 个 v2 控制器,但**没有定义任何显式路由**
+- **影响**:
+  - 访问路径为 `/{controller}/{action}` 格式
+  - 无版本前缀保护
+  - 控制器方法直接暴露为路由
+- **建议**:添加版本前缀路由 + 认证中间件
+
+#### 2.5 路由文件过于臃肿
+- **问题**:445 行全部集中在一个文件
+- **影响**:PC 端 + Mobile 端 + API + 短链逻辑混杂
+- **建议**:按模块拆分为多个路由文件
+
+#### 2.6 路由命名不规范
+**问题示例**:
+```php
+'/tgy'      → "推广页"拼音缩写
+'syxy'      → "使用协议"拼音缩写
+'zhaq'      → "账户安全"拼音缩写
+'kf'/'kc'   → "客服"/"课程"拼音缩写
+```
+- **建议**:统一使用英文命名
+
+#### 2.7 路由冲突
+**问题**:`gift/index` 路由在第 248 行直接覆盖了第 202 行的定义
+
+### 🟢 低风险
+
+#### 2.8 域名判断使用 HTTP_HOST
+- **风险**:存在 Host Header Injection 风险
+- **建议**:使用可信域名白名单
+
+#### 2.9 支付相关接口无域名约束
+- **位置**:`mp/get_wxmp_code`, `mp/get_pay` 等
+- **建议**:添加 domain 限制
+
+---
+
+## 3. 数据库设计分析
+
+### 3.1 模型分类统计
+
+| 分类 | 数量 | 代表模型 |
+|------|------|----------|
+| 游戏相关 | 32 | Game, GameInfo, GameServer |
+| 渠道相关 | 28 | Channel, ChannelGame, ChannelDivide |
+| 用户/会员 | 15 | Members, MemberCoinInfo |
+| 支付/财务 | 18 | Pay, PayCpinfo, CoinTransfer |
+| 福利系统 | 8 | Welfare, WelfareGrant |
+| 系统配置 | 12 | Setting, Admin, Department |
+| 日志记录 | 10 | Loginlog, Operatelog |
+| Complex聚合 | 8 | ComplexMembers, ComplexPay |
+| 其他业务 | 16 | SdkGameList, Libao, Gift |
+
+### 3.2 表前缀分布
+
+```
+cy_* 前缀: 约 95 个表 (游戏核心业务)
+nw_* 前缀: 约 56 个表 (渠道/扩展业务)
+```
+
+### 🔴 高风险问题
+
+#### 3.1 无统一基类
+- **问题**:151 个模型直接继承 `\think\Model`,无自定义基类
+- **影响**:无法统一管理公共行为(软删除、时间戳、日志等)
+- **建议**:创建 BaseModel 统一公共行为
+
+#### 3.2 关联关系严重缺失
+- **现状**:仅 **4 个模型**定义了关联关系
+- **问题**:大量使用手动 JOIN,代码冗余
+- **建议**:为核心模型定义关联关系
+
+```php
+// 当前:手动 JOIN
+->join('cy_gameinfo b', 'a.id=b.game_id')
+
+// 建议:模型关联
+public function gameInfo() {
+    return $this->hasOne('GameInfo', 'game_id');
+}
+```
+
+### 🟡 中风险问题
+
+#### 3.3 软删除实现不一致
+| 方式 | 使用情况 |
+|------|----------|
+| SoftDelete trait | 仅 1 处(已注释) |
+| `isdelete` 字段 | 部分模型 |
+| `flag` 字段 | 部分模型 |
+| 无软删除 | 大部分模型 |
+
+#### 3.4 时间戳处理混乱
+| 方式 | 模型数量 |
+|------|----------|
+| autoWriteTimestamp=true | ~20 |
+| dateFormat=false | ~15 |
+| 手动 getter 转换 | ~30 |
+| 无时间戳处理 | ~86 |
+
+#### 3.5 字段命名不一致
+- `game_id` vs `gameid`
+- `create_time` vs `createtime`
+- `channel_id` vs `channelid`
+
+#### 3.6 表前缀混乱
+- `cy_*` 和 `nw_*` 混用
+- 缺乏统一命名规范
+
+### 3.7 核心表关系图
+
+```
+┌─────────────┐     ┌─────────────┐     ┌─────────────┐
+│   Channel   │────▶│  Members    │────▶│     Pay     │
+│  (渠道表)   │     │  (用户表)   │     │  (支付表)   │
+└─────────────┘     └─────────────┘     └─────────────┘
+       │                   │                   │
+       ▼                   ▼                   ▼
+┌─────────────┐     ┌─────────────┐     ┌─────────────┐
+│ ChannelGame │     │MemberCoinInfo│    │  PayCpinfo  │
+│ (渠道游戏)  │     │ (平台币记录)│     │(支付回调)   │
+└─────────────┘     └─────────────┘     └─────────────┘
+       │
+       ▼
+┌─────────────┐     ┌─────────────┐
+│    Game     │────▶│  GameInfo   │
+│  (游戏表)   │     │ (游戏详情) │
+└─────────────┘     └─────────────┘
+       │
+       ▼
+┌─────────────┐
+│ GameServer  │
+│  (区服表)   │
+└─────────────┘
+```
+
+---
+
+## 4. 多租户隔离分析
+
+### 4.1 隔离字段
+
+```php
+// 渠道ID隔离 - 出现在约 40+ 个表中
+channel_id
+
+// 游戏ID隔离 - 出现在约 50+ 个表中
+game_id / gameid
+```
+
+### 4.2 实现方式
+
+```php
+// 查询时手动添加条件
+->where('channel_id', $channelId)
+->where(['game_id' => $gameid])
+
+// 渠道层级查询
+->where(['channel.id_path' => ['LIKE', '%,' . $channelId . ',%']])
+```
+
+### 4.3 渠道层级结构
+
+```
+Channel表字段:
+- id
+- parent_id (父渠道)
+- level (1=会长, 2=子会长, 3=推广员)
+- id_path (路径: ,1,2,3,)
+```
+
+### 🟡 中风险问题
+
+#### 4.1 多租户隔离依赖手动 WHERE
+- **问题**:每个查询都需要手动添加 `channel_id` 和 `game_id` 条件
+- **风险**:遗漏条件会导致数据泄露
+- **建议**:使用 Trait 或全局作用域自动注入
+
+```php
+// 建议:创建 MultiTenant Trait
+trait MultiTenant {
+    public function scopeChannel($query, $channelId) {
+        return $query->where('channel_id', $channelId);
+    }
+    
+    public function scopeGame($query, $gameId) {
+        return $query->where('game_id', $gameId);
+    }
+}
+```
+
+---
+
+## 5. 设计模式分析
+
+### 5.1 工厂模式 ⭐⭐⭐⭐
+
+**实现位置**:`common/factory/Pay.php`
+
+```
+Pay::createPayment($way) → switch-case 创建支付实例
+├── LdysPay (联动优势)
+├── QzlPay  (趣支付)
+├── YyYbPay (优亿支付)
+├── XtyPay  (喜钛游)
+├── SywPay  (十一玩)
+└── AliPay  (支付宝)
+```
+
+**问题**:
+- 使用 `switch-case` 硬编码,违反开闭原则
+- 新增支付方式需修改工厂类
+
+**建议**:改为注册式工厂
+
+### 5.2 策略模式 ⭐⭐⭐⭐
+
+**实现位置**:`api/complex/ComplexInterface.php` + 50+ 个渠道实现类
+
+```
+ComplexInterface (接口)
+├── checkLogin()   - 登录验证
+├── paySign()      - 签名验证
+├── getData()      - 数据转换
+├── getFail()      - 失败响应
+└── getSuccess()   - 成功响应
+```
+
+**问题**:
+- 接口方法不完整,部分实现类有额外方法
+- 缺少抽象基类,各实现类有重复代码
+
+### 5.3 服务层模式 ⭐⭐⭐
+
+**实现位置**:`service/` 目录(12 个服务类)
+
+| 服务类 | 职责 | 行数 |
+|--------|------|------|
+| GamePayService | 游戏支付业务 | 629 |
+| PayService | 支付渠道对接 | 943 |
+| MemberCoinService | 平台币充值 | - |
+| RetaineService | 留存数据统计 | - |
+
+**问题**:
+- 职责过重:PayService 943 行
+- 代码重复:支付渠道随机选择逻辑重复
+
+### 5.4 异常处理 ⭐⭐
+
+**实现位置**:`common/exception/Http.php`
+
+**问题**:
+- 钉钉通知代码被注释掉
+- 只处理 HTTP 异常,业务异常分散
+- 缺少自定义业务异常类
+
+### 5.5 日志处理 ⭐⭐⭐
+
+**实现位置**:`common.php` 中的 `log_message()` 函数
+
+**使用情况**:418 处调用,按模块分目录存储
+
+**问题**:
+- 每次调用都重新初始化 Log 配置
+- 日志格式不统一
+- 缺少请求 ID 追踪
+
+### 5.6 事件/观察者模式 ⭐⭐
+
+**现状**:
+- `tags.php` 定义了行为钩子,但全部为空数组
+- 支付成功、登录成功等关键节点未使用事件驱动
+
+---
+
+## 6. 设计模式总体评估
+
+| 维度 | 评分 | 说明 |
+|------|------|------|
+| 架构分层 | ⭐⭐⭐ | Controller → Service → Logic → Model,但层次混乱 |
+| 设计模式运用 | ⭐⭐⭐ | 工厂+策略模式较好,但缺少 Repository、Event |
+| 代码复用 | ⭐⭐ | 大量重复代码(支付初始化、渠道选择) |
+| 可测试性 | ⭐ | 直接依赖实例化,无依赖注入 |
+| 异常处理 | ⭐⭐ | 分散处理,无统一错误码 |
+
+---
+
+## 7. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 高风险
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | 测试路由生产环境暴露 | `route.php:156-157` |
+| 2 | 路由文件中直接查询数据库 | `route.php:68,87` |
+| 3 | guildapi 与 mcpsapi 大量重复 | 14+ 个同名控制器 |
+| 4 | common 模块职责过重 | 1800+ 行全局函数 |
+| 5 | complex 模块缺少 Model/Service | 19 个控制器直接操作数据库 |
+| 6 | 无统一模型基类 | 151 个模型各自为政 |
+| 7 | 关联关系严重缺失 | 仅 4 个模型定义关联 |
+| 8 | `*` 通配域名风险 | `route.php:27` |
+
+#### 🟡 中风险
+| 序号 | 问题 | 数量/位置 |
+|------|------|----------|
+| 1 | v1/v2 API 无显式路由 | api 模块 |
+| 2 | 路由文件臃肿 | 445 行 |
+| 3 | 路由命名不规范 | 多处拼音缩写 |
+| 4 | 路由冲突 | `gift/index` |
+| 5 | api 模块 service 层薄弱 | 79 controller / 4 service |
+| 6 | home/mobile 高度重复 | 12 个同名控制器 |
+| 7 | 软删除不一致 | 多种方式并存 |
+| 8 | 时间戳处理混乱 | 4 种方式并存 |
+| 9 | 字段命名不一致 | game_id vs gameid |
+| 10 | 多租户隔离手动 WHERE | 全局 |
+| 11 | 工厂模式违反开闭原则 | Pay.php |
+| 12 | 策略模式缺少抽象基类 | ComplexInterface |
+| 13 | 服务层职责过重 | PayService 943 行 |
+| 14 | 异常处理不统一 | 全局 |
+| 15 | 事件机制未启用 | tags.php |
+
+#### 🟢 低风险
+| 序号 | 问题 |
+|------|------|
+| 1 | 域名判断使用 HTTP_HOST |
+| 2 | 支付接口无域名约束 |
+| 3 | 存在 .bak 备份文件 |
+| 4 | 日志格式不统一 |
+| 5 | 缺少请求 ID 追踪 |
+| 6 | 表前缀混乱 |
+| 7 | 缺少 Repository 层 |
+| 8 | 缺少依赖注入 |
+| 9 | MLBB 下载路由无参数验证 |
+
+---
+
+## 8. 改进建议
+
+### 第一优先级(架构层面)
+1. **合并 guildapi 与 mcpsapi**:提取公共 Guild 基类
+2. **拆分 common.php**:按职责拆分到独立类
+3. **移除测试路由**:删除 `test_pay` 和 `notify` 路由
+4. **路由文件拆分**:按模块拆分为多个文件
+
+### 第二优先级(设计层面)
+5. **创建统一模型基类**:BaseModel 统一公共行为
+6. **定义核心模型关联关系**:Game、Members、Pay 等
+7. **增加 api 的 service 层**:业务逻辑下沉
+8. **启用事件机制**:支付成功、登录成功使用事件驱动
+
+### 第三优先级(规范层面)
+9. **统一命名规范**:字段名、表名、路由名
+10. **统一软删除实现**:使用 SoftDelete trait
+11. **统一时间戳处理**:autoWriteTimestamp
+12. **清理废弃代码**:删除 .bak 文件
+
+### 第四优先级(长期规划)
+13. **引入 Repository 层**:解耦 Model 与业务逻辑
+14. **依赖注入**:使用容器管理依赖
+15. **RESTful API 改造**:新 API 按 RESTful 规范设计
+16. **home/mobile 合并**:使用响应式设计
+
+---
+
+## 9. 参考架构
+
+### 推荐目录结构
+```
+application/
+├── common/
+│   ├── model/          # 仅共享模型(10-20个)
+│   ├── base/           # BaseController, BaseModel, BaseLogic
+│   ├── trait/          # MultiTenant, SoftDelete 等
+│   ├── event/          # 事件定义
+│   └── exception/      # 业务异常类
+├── admin/
+│   ├── controller/
+│   ├── model/          # Admin 专属模型
+│   ├── service/
+│   └── validate/
+├── api/
+│   ├── v1/
+│   │   ├── controller/
+│   │   └── service/
+│   ├── v2/
+│   │   ├── controller/
+│   │   └── service/
+│   └── complex/        # 渠道适配器
+├── guildapi/           # 合并 mcpsapi
+│   ├── controller/
+│   └── service/
+└── route/
+    ├── route.php       # 入口
+    ├── route_api.php
+    ├── route_admin.php
+    ├── route_home.php
+    └── route_guild.php
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 429 - 0
review_doc/04-代码质量审查.md

@@ -0,0 +1,429 @@
+# 第四阶段:代码质量审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | 代码规范、重复代码、复杂度、命名规范、注释质量 |
+| 发现问题数 | **28个** |
+| 高风险 | 8个 |
+| 中风险 | 12个 |
+| 低风险 | 8个 |
+
+---
+
+## 1. 重复代码分析
+
+### 1.1 重复度总览
+
+| 区域 | 重复度估算 | 浪费行数 |
+|------|-----------|----------|
+| 53个渠道适配器 | **~58%** | ~3,200行 |
+| guildapi vs mcpsapi | **~45%** | ~3,000行 |
+| home vs mobile | **~48%** | ~1,900行 |
+| 支付相关 Service | **~35%** | ~400行 |
+| common.php 重复函数 | **100%** | ~800行 |
+| **总计** | | **~9,300行** |
+
+### 🔴 高风险问题
+
+#### 1.1 53个渠道适配器重复度 58%
+
+**位置**:`api/complex/` 目录
+
+**结构性重复**:
+- 所有 52 个适配器都实现相同接口
+- `getSuccess()` / `getFail()` 方法 95% 相同
+- `checkLogin()` / `paySign()` 流程模板 60% 相同
+
+**完全相同的文件对**:
+| 文件对 | 差异行数 |
+|--------|----------|
+| `Demo.php` = `Kshy.php` | 26行 |
+| `Haofan.php` = `Haofanceshi.php` | 4行 |
+| `Jiuqu.php` ≈ `Jiuquxin.php` | 10行 |
+
+**建议**:
+1. 提取抽象基类 `BaseComplexAdapter`
+2. 使用模板方法模式重构 `checkLogin` / `paySign`
+3. 合并相同文件
+
+#### 1.2 guildapi 与 mcpsapi 重复度 45%
+
+**位置**:`guildapi/` 和 `mcpsapi/` 目录
+
+**common.php 对比**:
+- guildapi:265行
+- mcpsapi:267行
+- **相同代码:255行(96%)**
+
+**同名 Controller 对比**(15个):
+| Controller | 差异数 | 相似度 |
+|------------|--------|--------|
+| Vipinfo | 4行 | 99% |
+| ChannelRebind | 13行 | 96% |
+| ChannelWithdraw | 44行 | 82% |
+| Subpackage | 59行 | 74% |
+| Account | 28行 | 97% |
+
+**建议**:
+1. 提取共享代码到 `common/helper.php`
+2. 合并高度相似的 Controller
+3. 使用继承 + 配置区分差异
+
+#### 1.3 home 与 mobile 重复度 48%
+
+**同名 Controller 对比**(12个):
+| Controller | home行数 | mobile行数 | 相似度 |
+|------------|----------|------------|--------|
+| CoinAirwallex | 264 | 264 | 62% |
+| About | 64 | 49 | 58% |
+| Gift | 155 | 161 | 65% |
+| Coin | 250 | 242 | 81% |
+
+**建议**:
+1. 差异小的 Controller 合并
+2. 使用响应式设计减少重复
+
+#### 1.4 common.php 重复函数
+
+**`auth_code()` 函数存在于 4 处**:
+| 文件 | 行号 |
+|------|------|
+| `common.php` | 153 |
+| `home/common.php` | 63 |
+| `guildapi/common.php` | 106 |
+| `mcpsapi/common.php` | 106 |
+
+**其他重复函数**:
+| 函数名 | 出现次数 | 每处行数 |
+|--------|----------|----------|
+| `GetIP` | 3 | 18行 |
+| `getUserIp` | 4 | 12行 |
+| `postXmlCurl` | 4 | 15行 |
+| `xmlToArray` | 5 | 8行 |
+| `getGameIcon` | 3 | 27行 |
+
+**建议**:统一保留一份,其他文件删除
+
+---
+
+## 2. 代码复杂度分析
+
+### 2.1 超长文件(>500行)
+
+| 排名 | 文件 | 行数 |
+|------|------|------|
+| 1 | `admin/controller/Game.php` | **4,130** |
+| 2 | `admin/controller/Member.php` | **2,341** |
+| 3 | `admin/controller/Pay.php` | **1,714** |
+| 4 | `api/controller/v1/Pay.php` | **1,626** |
+| 5 | `common.php` | **1,563** |
+| 6 | `admin/controller/Channel.php` | **1,409** |
+| 7 | `common/library/HuaweiObs.php` | **1,369** |
+| 8 | `home/controller/Member.php` | **1,171** |
+| 9 | `guildapi/controller/Member.php` | **1,157** |
+| 10 | `service/RetaineService.php` | **1,127** |
+
+### 🔴 高风险问题
+
+#### 2.1 Game.php 超过 4000 行
+- **位置**:`admin/controller/Game.php`
+- **问题**:单个控制器包含 4,130 行代码,职责严重过重
+- **建议**:按功能拆分为多个控制器(GameBasic, GamePackage, GamePoint, GameChannel 等)
+
+### 2.2 超长函数(>100行)
+
+| 排名 | 文件 | 函数 | 行数 |
+|------|------|------|------|
+| 1 | `admin/controller/Game.php` | `ajaxGetCompanyGames` | **684** |
+| 2 | `service/GamePayService_bak.php` | `gamePay` | **568** |
+| 3 | `service/GamePayService.php` | `gamePay` | **554** |
+| 4 | `api/controller/v1/Pay.php` | `index_bak` | **491** |
+| 5 | `service/WelfareService.php` | `getUserlistByGrantTypeGameChannelId` | **308** |
+| 6 | `service/MemberCoinService_bak.php` | `payCoin` | **274** |
+| 7 | `admin/controller/Game.php` | `update` | **247** |
+| 8 | `admin/controller/Pay.php` | `_beforeGetParams` | **233** |
+| 9 | `service/MemberCoinService.php` | `payCoin` | **208** |
+| 10 | `admin/controller/Game.php` | `create` | **201** |
+
+### 🟡 中风险问题
+
+#### 2.3 复杂业务函数缺乏拆分
+
+**GamePayService::gamePay()**(554行)应拆分为:
+- 支付初始化
+- 支付方式判断
+- 支付渠道调用
+- 结果处理
+
+**Game::ajaxGetCompanyGames()**(684行)应拆分为:
+- 数据查询
+- 数据处理
+- 结果格式化
+
+---
+
+## 3. 命名规范分析
+
+### 3.1 类名命名规范(PascalCase)
+
+**违规文件**(4个):
+| 文件 | 类名 | 建议 |
+|------|------|------|
+| `crontab/controller/asyncCallback.php` | `asyncCallback` | `AsyncCallback` |
+| `common/model/fkExceptionRecords.php` | `fkExceptionRecords` | `FkExceptionRecords` |
+| `common/model/fkExceptionObjects.php` | `fkExceptionObjects` | `FkExceptionObjects` |
+| `common/library/jssdk.php` | `jssdk` | `JsSdk` |
+
+### 🔴 高风险问题
+
+#### 3.1 方法名使用 snake_case(174+ 处)
+
+**common.php 中的违规方法**:
+```php
+// 当前(不规范)
+auth_code()
+log_message()
+get_top_channel()
+get_union_channel()
+get_channel_level()
+get_game_name()
+mg_password()
+is_mobile_request()
+
+// 建议
+authCode()
+logMessage()
+getTopChannel()
+getUnionChannel()
+getChannelLevel()
+getGameName()
+mgPassword()
+isMobileRequest()
+```
+
+**api/controller/v1/PayNotify.php 中的违规方法**:
+```php
+qmf_pay()        → qmfPay()
+ybzf_pay()       → ybzfPay()
+ldzf_wx_pay()    → ldzfWxPay()
+qzl_pay()        → qzlPay()
+pay_refund()     → payRefund()
+```
+
+**admin/controller/Retaine*.php 中的违规方法**:
+```php
+reg_total()      → regTotal()
+reg_num()        → regNum()
+role_num()       → roleNum()
+one_stay()       → oneStay()
+thirty_stay()    → thirtyStay()
+```
+
+### 🟡 中风险问题
+
+#### 3.2 变量命名使用 snake_case
+
+**common.php 中的违规变量**:
+```php
+$ckey_length     → $ckeyLength
+$cryptkey        → $cryptKey
+$rndkey          → $rndKey
+$cache_key       → $cacheKey
+$request_result  → $requestResult
+```
+
+### 🔴 高风险问题
+
+#### 3.3 拼音缩写命名(100+ 处)
+
+**路由中的拼音缩写**:
+| 路由 | 拼音 | 含义 | 建议 |
+|------|------|------|------|
+| `/syxy` | 使用协议 | 使用协议 | `/agreement` |
+| `/zhaq` | 账号安全 | 账号安全 | `/account-security` |
+| `/cjwt` | 常见问题 | 常见问题 | `/faq` |
+| `/zhss` | 账号申诉 | 账号申诉 | `/appeal` |
+| `/sscx` | 申诉查询 | 申诉查询 | `/appeal-query` |
+| `/wxss` | 微信申诉 | 微信申诉 | `/wechat-appeal` |
+| `/xyzx` | 游戏资讯 | 游戏资讯 | `/game-news` |
+| `/lyzx` | 礼仪资讯 | 礼仪资讯 | `/gift-news` |
+| `/xypc` | 游戏评测 | 游戏评测 | `/game-review` |
+| `/xygl` | 游戏攻略 | 游戏攻略 | `/game-guide` |
+| `/jcsp` | 精彩视频 | 精彩视频 | `/video-highlights` |
+
+**控制器中的拼音命名**:
+| 文件 | 类名 | 建议 |
+|------|------|------|
+| `admin/controller/Ziyuan.php` | `Ziyuan` | `Resources` |
+
+---
+
+## 4. 注释质量分析
+
+### 4.1 注释覆盖率
+
+| 指标 | 数量 | 覆盖率 |
+|------|------|--------|
+| PHP文件总数 | 610 | - |
+| 有文件头注释 | 259 | **42.5%** |
+| 有类注释 | 64 | **10.5%** |
+| 方法总数 | 3,098 | - |
+| 有注释的方法 | 1,695 | **54.7%** |
+| 无任何注释的文件 | 73 | **12.0%** |
+| 复杂方法(>30行) | 761 | - |
+| 复杂方法有注释 | 13 | **1.7%** |
+
+### 🟡 中风险问题
+
+#### 4.1 类注释极度缺失
+- 仅 **10.5%** 的类有注释
+- 大部分 Service、Controller 类无注释
+
+#### 4.2 复杂方法缺乏注释
+- 761 个复杂方法中仅 **13 个(1.7%)** 有注释
+- 关键业务逻辑缺乏说明
+
+#### 4.3 被注释的代码大量遗留
+- **4,149 行**被注释掉的代码
+- 应该删除,使用版本控制管理历史
+
+#### 4.4 TODO/FIXME 积压
+- TODO:69 个
+- FIXME:1 个
+- 自动生成 stub:18 个
+
+### 🟢 良好实践
+
+#### 4.5 方法注释过半
+- 54.7% 的方法有注释
+- 说明开发者有一定的注释意识
+
+---
+
+## 5. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 高风险
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | 渠道适配器重复度 58% | `api/complex/` |
+| 2 | guildapi/mcpsapi 重复度 45% | 两个模块 |
+| 3 | common.php 重复函数 | 4处 `auth_code()` |
+| 4 | Game.php 超过 4000 行 | `admin/controller/Game.php` |
+| 5 | 方法名 snake_case 174+ 处 | 全局 |
+| 6 | 拼音缩写命名 100+ 处 | 路由、控制器 |
+| 7 | 类名小写开头 4 个文件 | 多处 |
+| 8 | GamePayService::gamePay() 554行 | `service/GamePayService.php` |
+
+#### 🟡 中风险
+| 序号 | 问题 | 数量/位置 |
+|------|------|----------|
+| 1 | home/mobile 重复度 48% | 12 个同名控制器 |
+| 2 | 支付 Service 重复度 35% | GamePayService/MemberCoinService |
+| 3 | 超长文件 >500 行 | 10 个文件 |
+| 4 | 超长函数 >100 行 | 32 个函数 |
+| 5 | 变量 snake_case | 大量 |
+| 6 | 类注释覆盖率 10.5% | 全局 |
+| 7 | 复杂方法注释率 1.7% | 761 个复杂方法 |
+| 8 | 被注释代码 4149 行 | 全局 |
+| 9 | TODO/FIXME 70 个 | 全局 |
+| 10 | 文件头注释覆盖率 42.5% | 全局 |
+| 11 | 数据库前缀混用 | cy_ 和 nw_ |
+| 12 | 参数缺少类型提示 | 538 个参数 |
+
+#### 🟢 低风险
+| 序号 | 问题 |
+|------|------|
+| 1 | 方法注释覆盖率 54.7% |
+| 2 | 常量命名规范(UPPER_SNAKE_CASE) |
+| 3 | 文件名小写开头 4 个 |
+| 4 | 返回类型声明缺失 3389 个方法 |
+| 5 | stub 注释 18 个 |
+| 6 | 备份文件未清理 |
+| 7 | 部分函数命名不一致 |
+| 8 | 魔法数字硬编码 |
+
+---
+
+## 6. 改进建议
+
+### 第一优先级(去重)
+1. **重构渠道适配器**:提取 `BaseComplexAdapter` 基类
+2. **合并 guildapi/mcpsapi**:提取公共代码到 common
+3. **删除重复函数**:`auth_code()` 等仅保留一份
+4. **提取支付调度器**:统一 GamePayService 和 MemberCoinService 的支付逻辑
+
+### 第二优先级(降低复杂度)
+5. **拆分 Game.php**:按功能拆分为多个控制器
+6. **拆分超长函数**:`gamePay()`、`ajaxGetCompanyGames()` 等
+7. **提取公共方法**:减少重复代码
+
+### 第三优先级(命名规范)
+8. **统一方法名**:snake_case → camelCase(174+ 处)
+9. **消除拼音缩写**:路由、控制器、变量名(100+ 处)
+10. **修复类名**:4 个小写开头的类名
+
+### 第四优先级(注释完善)
+11. **清理被注释代码**:删除 4,149 行
+12. **添加类注释**:为核心类添加文档
+13. **处理 TODO**:解决 69 个 TODO 项
+14. **添加复杂方法注释**:为 761 个复杂方法添加说明
+
+---
+
+## 7. 参考代码
+
+### 渠道适配器基类示例
+```php
+abstract class BaseComplexAdapter implements ComplexInterface
+{
+    protected $channel;
+    protected $config;
+    
+    abstract protected function buildSignParams($data): array;
+    abstract protected function verifySign($params): bool;
+    abstract protected function parseLoginResult($response): array;
+    
+    public function getSuccess($msg = '')
+    {
+        echo 'success';
+        exit;
+    }
+    
+    public function getFail($msg = '')
+    {
+        echo 'fail:' . $msg;
+        exit;
+    }
+    
+    public function checkLogin($data, $polyChannelGame = [])
+    {
+        $this->validateParams($data);
+        $this->loadConfig($polyChannelGame);
+        $signParams = $this->buildSignParams($data);
+        $response = $this->sendRequest($signParams);
+        return $this->parseLoginResult($response);
+    }
+}
+```
+
+### 方法命名修复示例
+```php
+// ❌ 当前(不规范)
+public function reg_total() { }
+public function reg_num() { }
+public function one_stay() { }
+
+// ✅ 建议
+public function regTotal() { }
+public function regNum() { }
+public function oneStay() { }
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 549 - 0
review_doc/05-性能优化审查.md

@@ -0,0 +1,549 @@
+# 第五阶段:性能优化审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | 数据库查询、缓存策略、队列处理、同步阻塞 |
+| 发现问题数 | **35个** |
+| 高风险 | 10个 |
+| 中风险 | 15个 |
+| 低风险 | 10个 |
+
+---
+
+## 1. 数据库查询性能
+
+### 🔴 高风险问题
+
+#### 1.1 渠道层级遍历 N+1 查询
+
+**位置**:`common.php:437,464,483,510`
+
+```php
+// get_top_channel() - 每次循环执行一次查询
+while ($info = $channelModel->where(['id' => $channelId])->find()) {
+    if ($info['level'] == 1 || ...) return $info;
+    $channelId = $info['parent_id'];
+}
+```
+
+**影响**:
+- 渠道层级最多 4 级,每次调用 4-5 次查询
+- Pay.php 分页列表循环调用,100 条数据 = 500+ 次额外查询
+
+**建议**:使用 `id_path` 字段一次性查询
+
+#### 1.2 Pay 列表循环内调用查询函数
+
+**位置**:`admin/controller/Pay.php:366-376`
+
+```php
+foreach ($list as $key => $item) {
+    $channel = get_top_second_channel_name($list[$key]['channel_id']); // 2-5次查询
+    $list[$key]['union_name'] = get_union_channel($list[$key]['channel_id'])['name']; // 2-5次查询
+}
+```
+
+**影响**:分页 100 条 → 每条 4-10 次查询 → 400-1000 次额外查询
+
+**建议**:批量预查询 + 数组映射
+
+#### 1.3 循环内重复查询(Bug)
+
+**位置**:`api/controller/Index.php:607-629`
+
+```php
+foreach ($child_list as $v) {
+    // 每次循环都查一次!且条件完全相同
+    $promoter_list = Db::name('nw_channel')
+        ->where(['parent_id' => ['in', $child_list_ids], 'level' => 3])
+        ->select();
+}
+```
+
+**建议**:提到循环外
+
+#### 1.4 大表重复聚合查询
+
+**位置**:`admin/controller/Pay.php:382-409`
+
+```php
+// 同一请求中,对 cy_pay 表执行了 3 次几乎相同的聚合查询
+$totalAmount = $PayModel->...->find()['total_amount'];
+$todayTotalAmount = model('Pay')->...->find()['total_amount'];
+$lastDayTotalAmount = model('Pay')->...->find()['total_amount'];
+```
+
+**建议**:合并为单条查询
+
+### 🟡 中风险问题
+
+#### 1.5 缺少 field() 的 SELECT *
+
+**位置**:`admin/controller/Pay.php:1468-1477`
+
+```php
+$pay = Db::name('cy_pay')->where(['orderid' => $orderid])->find();          // SELECT *
+$members = Db::name('cy_members')->where(['id' => $userid])->find();         // SELECT *
+$pyb = Db::name('nw_channel_admin')->where(['id' => $chal_id])->find();      // SELECT *
+```
+
+**影响**:连续 6 次 `SELECT *`,读取不必要的字段
+
+**建议**:使用 `->field('id,orderid,amount')` 指定字段
+
+#### 1.6 LIKE '%xxx%' 前缀通配符
+
+**位置**:`api/controller/Index.php:564`
+
+```php
+->where(['id_path' => ['like', '%'.$qy_child_id_path.'%']])
+```
+
+**影响**:`%` 前缀导致索引失效,全表扫描
+
+**建议**:改用前缀匹配 `LIKE 'xxx%'`
+
+#### 1.7 3+ 表复杂 JOIN
+
+**位置**:`api/controller/JhApi.php:357-362`
+
+```php
+->join('cy_members cm', 'ns.member_id = cm.id', 'left')
+->join('cy_game cg', 'ns.game_id = cg.id', 'left')
+->join('nw_channel nc', 'ns.channel_id = nc.id', 'left')
+->join('nw_channel p1', 'nc.parent_id = p1.id', 'left')
+->join('nw_channel p2', 'p1.parent_id = p2.id', 'left')
+```
+
+**影响**:`nw_channel` 被 JOIN 3 次
+
+**建议**:使用 `id_path` 拆分或冗余父级名称
+
+#### 1.8 limit(10000) 无分页
+
+**位置**:`admin/controller/FkExceptionRecords.php:191-195`
+
+```php
+$data = Db::name('fk_exception_records')
+    ->where($where)
+    ->order('record_id desc')
+    ->limit(10000)
+    ->select();
+```
+
+**建议**:使用分页或流式查询
+
+#### 1.9 3 层嵌套子查询 UNION
+
+**位置**:`admin/controller/Pay.php:904-912`
+
+```php
+$regSql = model('MemberGameServer')->field(...)->group(...)->buildSql();
+$unionSql = model('Pay')->field(...)->group(...)->union([$regSql])->buildSql();
+$unionSqlNew = Db::table($unionSql . ' bb')->field(...)->group(...)->buildSql();
+$list = Db::table($unionSqlNew . ' cc')->paginate(...);
+```
+
+**建议**:使用临时表或预计算汇总数据
+
+#### 1.10 循环内 COUNT 查询
+
+**位置**:`admin/controller/Setting.php:149-151`
+
+```php
+foreach ($channelList as $v) {
+    $hasChildren = Db::name('nw_channel')
+        ->where(['parent_id' => $v['value'], 'status' => 1])
+        ->count() > 0;
+}
+```
+
+**建议**:使用 GROUP BY 批量查询
+
+---
+
+## 2. 缓存策略分析
+
+### 2.1 缓存配置
+
+| 配置项 | 值 |
+|--------|-----|
+| 驱动 | complex (默认 Redis) |
+| 前缀 | `newsdk_` |
+| Redis 过期 | 7天 |
+| 文件缓存过期 | 30天 |
+| Session | Redis,前缀 `think_`,过期 35小时 |
+
+### 2.2 缓存 Key 设计模式
+
+| 类别 | Key 模式 | 过期时间 |
+|------|----------|----------|
+| Token | `{token字符串}` | 15/30天 |
+| 订单号 | `orderid:{orderid}` | 24小时 |
+| 渠道缓存 | `clearable_channel_list_*` | 24小时-30天 |
+| 游戏列表 | `clearable_game_list{md5}` | 300秒 |
+| 配置 | `clearable_get_setting:{name}` | 24小时 |
+| 风控名单 | `risk_control:inspect_list:{type}:{value}` | 7天 |
+
+### 🔴 高风险问题
+
+#### 2.1 缓存穿透风险
+
+**问题**:查不到数据时不缓存空值
+
+```php
+// Setting::getSetting() - 不缓存空值
+$info = self::where(['name' => $name])->value('value');
+if (empty($info)) {
+    return false;  // 不缓存,下次继续查库
+}
+```
+
+**建议**:缓存空值,短过期时间
+
+#### 2.2 缓存击穿风险
+
+**问题**:热点 key 无互斥锁
+
+```php
+// Game::getAllByCondition() - 无并发保护
+$result = Cache::get($cacheKey);
+if (empty($result)) {
+    $result = Db::table(...)->select();  // 高并发下同时查库
+    Cache::set($cacheKey, $result, 300);
+}
+```
+
+**建议**:使用互斥锁
+
+#### 2.3 缓存雪崩风险
+
+**问题**:同类 key 过期时间高度集中
+
+- 渠道缓存全是 24h
+- 游戏列表全是 300s
+- `Cache::clear('channel_cache')` 瞬间删除所有渠道缓存
+
+**建议**:过期时间加随机偏移
+
+### 🟡 中风险问题
+
+#### 2.4 Token 存储不一致
+
+- Register 存 token:过期 30 天
+- Login 存 token:过期 15 天
+- 相同 key 不同过期时间,存在覆盖问题
+
+#### 2.5 直接操作 Redis handler 不一致
+
+- 部分代码使用 `Cache::store('default')->handler()`
+- 部分代码使用 `Cache::set/get`
+- 绕过框架的前缀、序列化、Tag 机制
+
+#### 2.6 缓存 Key 设计缺乏统一规范
+
+存在多种分隔符混用:
+- `token|sdk|{userid}|{gameid}`(竖线)
+- `risk_control:rule:frequency:*`(冒号)
+- `clearable_channel_list_`(下划线)
+
+---
+
+## 3. 队列和异步处理
+
+### 3.1 队列使用现状
+
+| 机制 | 用途 | 位置 |
+|------|------|------|
+| ThinkPHP Queue (Redis) | 支付回调异步通知 CP | `extra/queue.php` |
+| 原生 Redis List | 打包任务、报表生成 | `SubPackage.php` |
+| 原生 Redis ZSet | 抖音广告推送 | `Tiktok.php` |
+| Kafka | 通用消息队列(示例阶段) | `Kafka.php` |
+
+### 🔴 高风险问题
+
+#### 3.1 Redis DB 选择混乱
+
+**位置**:`Tiktok.php:22`
+
+```php
+$this->_redis->select(Env::get('redis.seelect_go', '')); // 拼写错误!
+```
+
+**问题**:配置键 `redis.seelect_go` 拼写错误,可能连接到错误的 DB
+
+#### 3.2 异步回调重试机制缺陷
+
+**位置**:`crontab/controller/asyncCallback.php`
+
+- 第 33 行和第 55 行重复判断 `$job->attempts() > 5`
+- 重试间隔固定 120 秒,无指数退避
+- 回调结果比较未做 trim/大小写统一
+
+#### 3.3 同步回调重试名存实亡
+
+**位置**:`PayCallback.php:78-95`
+
+```php
+private function _doCallback($callback_url, $callback_params, $retry_times = 1, $sleep = 5)
+// retry_times = 1,sleep 被注释掉,实际只请求一次
+```
+
+### 🟡 中风险问题
+
+#### 3.4 原生 Redis 与框架 Queue 并存
+
+- `MakeReport.php`、`Tiktok.php` 等直接 `new \Redis()`
+- 连接不复用,无连接数控制
+- 无统一的队列抽象层
+
+#### 3.5 Kafka 仅处于示例阶段
+
+- 事件处理只有 placeholder
+- 消费者未配置 `enable.auto.commit => false`
+- 无 graceful shutdown
+
+#### 3.6 队列监控完全缺失
+
+- 无队列深度监控
+- 无死信队列
+- 无告警机制
+- 无消费速率统计
+
+#### 3.7 定时任务 N+1 查询
+
+**位置**:`crontab/ChannelClean.php:347-376`
+
+```php
+for($i=1; $i<=$day_limit; $i++){
+    // 每天单独查询一次数据库
+    $temp_ids = Db::table('cy_member_channel_game_rel')...
+}
+```
+
+**建议**:改为单条 SQL 的 `BETWEEN`
+
+---
+
+## 4. 同步阻塞问题
+
+### 🔴 高风险问题
+
+#### 4.1 curl 无超时设置
+
+| 位置 | 函数 | 问题 |
+|------|------|------|
+| `common.php:1290-1296` | `timingCurl()` | **未设置任何超时** |
+| `api/complex/Yehua.php:121-133` | `curlInit()` | **未设置任何超时** |
+| `api/controller/v2/Huge.php:175-188` | `curlJson()` | **未设置任何超时** |
+| `admin/controller/Tasks.php:814-830` | curl 调用 | **未设置任何超时** |
+| `crontab/DealGameChannelDivide.php:156-167` | curl 调用 | **未设置任何超时** |
+
+#### 4.2 curl 超时过长
+
+| 位置 | 超时 | 说明 |
+|------|------|------|
+| `common/library/Sms.php:533-548` | **300秒** | 短信发送不应超过 30 秒 |
+| `common/library/jssdk.php:125-139` | **500秒** | 微信 JSSDK,荒谬超时 |
+| `common.php:365-388` | **60秒** | 通用 HTTP 工具函数 |
+| `home/common.php:391-406` | **60秒** | Web 控制器调用 |
+
+#### 4.3 curl 句柄泄漏
+
+**位置**:`service/PayService.php:922-926`、`api/controller/v1/Pay.php:1885-1889`
+
+```php
+return $xml;  // return 在 curl_close($ch) 之前
+// curl 句柄永远不会关闭
+```
+
+### 🟡 中风险问题
+
+#### 4.4 file_get_contents 访问外部 URL
+
+| 位置 | 说明 |
+|------|------|
+| `api/controller/v1/Login.php:298` | 淘宝 IP 查询,无超时 |
+| `common/model/CscAppeal.php:137` | 淘宝 IP 查询,无超时 |
+| `common.php:1662` | IP 查询,无超时 |
+
+#### 4.5 CONNECTTIMEOUT 与 TIMEOUT 配置不合理
+
+**位置**:`api/common.php:14-52`
+
+```php
+CONNECTTIMEOUT=30, TIMEOUT=5  // 连接超时30秒过长,总超时仅5秒,配置反了
+```
+
+#### 4.6 数据库读写未分离
+
+**位置**:`config.php:327-345`
+
+```php
+'deploy' => 0,           // 集中式
+'rw_separate' => false,  // 读写未分离
+// 从库配置与主库相同,从库形同虚设
+```
+
+#### 4.7 长时间运行任务无保护
+
+**位置**:`crontab/mlbb/TaskHandle.php:52-86`
+
+```php
+do {
+    // 调用外部 API
+    usleep(10000); // 仅 10ms
+} while(true);  // 无限循环
+```
+
+#### 4.8 递归调用无深度限制
+
+**位置**:`common.php:394-408`
+
+```php
+function makeOrderid($head = 'WL') {
+    if ($redis->has($cache_key)) {
+        return makeOrderid(); // 无递归深度保护
+    }
+}
+```
+
+---
+
+## 5. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 高风险
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | 渠道层级遍历 N+1 查询 | `common.php:437` |
+| 2 | Pay 列表循环内调用查询函数 | `admin/controller/Pay.php:366` |
+| 3 | 循环内重复查询(Bug) | `api/controller/Index.php:620` |
+| 4 | 大表重复聚合查询 | `admin/controller/Pay.php:382` |
+| 5 | 缓存穿透风险 | 全局 |
+| 6 | 缓存击穿风险 | 全局 |
+| 7 | 缓存雪崩风险 | 全局 |
+| 8 | Redis DB 选择混乱 | `Tiktok.php:22` |
+| 9 | curl 无超时设置 | 5 处 |
+| 10 | curl 超时过长(300-500秒) | 2 处 |
+
+#### 🟡 中风险
+| 序号 | 问题 | 数量/位置 |
+|------|------|----------|
+| 1 | 缺少 field() 的 SELECT * | 10+ 处 |
+| 2 | LIKE '%xxx%' 前缀通配符 | 2 处 |
+| 3 | 3+ 表复杂 JOIN | 多处 |
+| 4 | limit(10000) 无分页 | 1 处 |
+| 5 | 3 层嵌套子查询 UNION | 1 处 |
+| 6 | 循环内 COUNT 查询 | 1 处 |
+| 7 | Token 存储不一致 | 全局 |
+| 8 | 缓存 Key 设计不规范 | 全局 |
+| 9 | 异步回调重试缺陷 | `asyncCallback.php` |
+| 10 | 同步回调重试名存实亡 | `PayCallback.php` |
+| 11 | 原生 Redis 与框架并存 | 多处 |
+| 12 | Kafka 仅示例阶段 | `Kafka.php` |
+| 13 | 队列监控缺失 | 全局 |
+| 14 | 定时任务 N+1 查询 | `ChannelClean.php` |
+| 15 | curl 句柄泄漏 | 2 处 |
+
+#### 🟢 低风险
+| 序号 | 问题 |
+|------|------|
+| 1 | file_get_contents 访问外部 URL |
+| 2 | CONNECTTIMEOUT 配置不合理 |
+| 3 | 数据库读写未分离 |
+| 4 | 长时间运行任务无保护 |
+| 5 | 递归调用无深度限制 |
+| 6 | 缓存 Key 分隔符混用 |
+| 7 | 直接操作 Redis handler 不一致 |
+| 8 | 定时任务 usleep 过短 |
+| 9 | 密钥文件每次请求读取 |
+| 10 | 文件日志锁竞争 |
+
+---
+
+## 6. 改进建议
+
+### 第一优先级(查询优化)
+1. **修复渠道层级查询**:使用 `id_path` 一次性查询
+2. **Pay 列表批量预查询**:收集所有 ID 后批量查询
+3. **修复循环内重复查询 Bug**:提到循环外
+4. **合并重复聚合查询**:单条 SQL 完成
+
+### 第二优先级(缓存优化)
+5. **防缓存穿透**:缓存空值
+6. **防缓存击穿**:互斥锁
+7. **防缓存雪崩**:过期时间加随机偏移
+8. **统一 Token 存储**:统一过期时间
+
+### 第三优先级(队列优化)
+9. **修复拼写错误**:`redis.seelect_go` → `redis.select_go`
+10. **完善异步回调**:指数退避 + 结果比较容错
+11. **增加死信队列**:失败任务可追溯
+12. **增加队列监控**:深度 + 告警
+
+### 第四优先级(阻塞优化)
+13. **统一 curl 超时**:CONNECTTIMEOUT=5, TIMEOUT=10
+14. **修复 curl 句柄泄漏**:`curl_close` 移到 `return` 前
+15. **替换 file_get_contents**:使用 curl 并设置超时
+16. **启用数据库读写分离**
+
+---
+
+## 7. 参考代码
+
+### 渠道层级查询优化
+```php
+// ❌ 当前:循环查询
+while ($info = $channelModel->where(['id' => $channelId])->find()) {
+    $channelId = $info['parent_id'];
+}
+
+// ✅ 建议:使用 id_path
+$ids = explode(',', trim($channel->id_path, ','));
+$channels = $channelModel->whereIn('id', $ids)->select();
+```
+
+### 缓存穿透防护
+```php
+// ❌ 当前:不缓存空值
+if (empty($info)) return false;
+
+// ✅ 建议:缓存空值
+if (empty($info)) {
+    Cache::set($cache_key, '', 300);
+    return false;
+}
+```
+
+### 缓存击穿防护
+```php
+// ✅ 建议:互斥锁
+$result = Cache::get($cacheKey);
+if (empty($result)) {
+    $lockKey = $cacheKey . ':lock';
+    if (Cache::set($lockKey, 1, 5)) {
+        $result = Db::table(...)->select();
+        Cache::set($cacheKey, $result, 3600);
+        Cache::rm($lockKey);
+    } else {
+        usleep(100000);
+        return $this->getData($cacheKey);
+    }
+}
+```
+
+### curl 超时统一
+```php
+// ✅ 建议
+curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
+curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 299 - 0
review_doc/06-依赖管理审查.md

@@ -0,0 +1,299 @@
+# 第六阶段:依赖管理审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | Composer 依赖、前端库版本、安全漏洞 |
+| 发现问题数 | **18个** |
+| 高风险 | 5个 |
+| 中风险 | 8个 |
+| 低风险 | 5个 |
+
+---
+
+## 1. PHP 依赖分析(Composer)
+
+### 1.1 依赖清单
+
+| 包名 | 版本 | 最新版本 | 状态 |
+|------|------|----------|------|
+| php | >=7.0 | 8.3 | ⚠️ EOL |
+| topthink/framework | ~5.0.0 | 8.x | ⚠️ 严重过时 |
+| topthink/think-queue | 1.1.4 | 3.x | ⚠️ 过时 |
+| phpmailer/phpmailer | v6.0.3 | 6.9.x | ⚠️ 有安全更新 |
+| topthink/think-captcha | v1.* | 3.x | ⚠️ 过时 |
+| guzzlehttp/guzzle | ^6.3 | 7.x | ⚠️ 过时 |
+| overtrue/pinyin | ~3.0 | 4.x | ⚠️ 过时 |
+| workerman/gatewayclient | v3.0.10 | 3.x | ✅ 最新 |
+| monolog/monolog | 1.23.0 | 3.x | ⚠️ 严重过时 |
+| guzzlehttp/psr7 | 1.4.2 | 2.x | ⚠️ 过时 |
+| guzzlehttp/promises | v1.3.1 | 2.x | ⚠️ 过时 |
+| psr/log | 1.0.2 | 3.x | ⚠️ 过时 |
+| topthink/think-helper | v1.0.6 | 3.x | ⚠️ 过时 |
+| huaweicloud/huaweicloud-sdk-php | 3.0.56-rc | 3.x | ⚠️ RC 版本 |
+| hashids/hashids | ^4.1 | 5.x | ⚠️ 过时 |
+| yurunsoft/pay-sdk | ^3.1 | 3.x | ✅ 最新 |
+
+### 🔴 高风险问题
+
+#### 1.1 ThinkPHP 5.0.7 严重过时
+
+**当前版本**:5.0.7(2017年发布)
+**最新版本**:8.x(2024年)
+**问题**:
+- 已停止安全更新
+- 存在多个已知漏洞
+- 不支持 PHP 8.x 新特性
+
+**建议**:
+- 短期:升级到 5.1.x(向后兼容)
+- 长期:升级到 8.x
+
+#### 1.2 PHP 7.0-7.3 已终止支持
+
+**当前要求**:>=7.0
+**EOL 日期**:
+- PHP 7.0:2018年12月
+- PHP 7.1:2019年12月
+- PHP 7.2:2020年11月
+- PHP 7.3:2021年12月
+
+**风险**:已知漏洞无法修复
+
+**建议**:升级到 PHP 8.1+
+
+#### 1.3 Monolog 1.23.0 严重过时
+
+**当前版本**:1.23.0(2018年)
+**最新版本**:3.x
+**问题**:
+- 不支持 PHP 8.x
+- 缺少新特性和性能优化
+
+### 🟡 中风险问题
+
+#### 1.4 PHPMailer 有安全更新
+
+**当前版本**:v6.0.3
+**建议**:升级到 6.9.x 获取安全修复
+
+#### 1.5 Guzzle 6.x 过时
+
+**当前版本**:6.x
+**最新版本**:7.x
+**问题**:
+- 不支持 HTTP/2
+- 缺少新的安全特性
+
+#### 1.6 华为云 SDK 使用 RC 版本
+
+**当前版本**:3.0.56-rc
+**问题**:RC 是预发布版本,可能不稳定
+
+**建议**:升级到稳定版本
+
+#### 1.7 composer.json 依赖重复定义
+
+```json
+"monolog/monolog": "^1.23",  // 第27行
+"monolog/monolog": "1.23.0", // 第31行(重复)
+
+"phpmailer/phpmailer": "^6.0",  // 第22行
+"phpmailer/phpmailer": "v6.0.3", // 第32行(重复)
+
+"workerman/gatewayclient": "~3.0.7",  // 第26行
+"workerman/gatewayclient": "v3.0.10", // 第34行(重复)
+```
+
+**建议**:清理重复定义,保留精确版本
+
+---
+
+## 2. 前端库分析
+
+### 2.1 库清单
+
+| 库名 | 当前版本 | 最新版本 | 状态 |
+|------|----------|----------|------|
+| jQuery | 1.11.3 | 3.7.x | ⚠️ 严重过时 |
+| Layui | v2.9.16 | 2.9.x | ✅ 最新 |
+| Bootstrap | 3.x | 5.x | ⚠️ 过时 |
+| Vue.js | 未知 | 3.x | ⚠️ 需确认 |
+| Vant | 未知 | 4.x | ⚠️ 需确认 |
+| ECharts | 未知 | 5.x | ⚠️ 需确认 |
+| Select2 | 未知 | 4.x | ⚠️ 需确认 |
+| Font Awesome | 4.7.0 | 6.x | ⚠️ 过时 |
+| Viewer.js | 未知 | 1.x | ⚠️ 需确认 |
+| Swiper | 未知 | 11.x | ⚠️ 需确认 |
+| OwlCarousel | 2.3.4 | 2.x | ✅ 最新 |
+| Moment.js | 未知 | - | ⚠️ 已停止维护 |
+
+### 🔴 高风险问题
+
+#### 2.1 jQuery 1.11.3 严重过时
+
+**当前版本**:1.11.3(2015年)
+**最新版本**:3.7.x
+**问题**:
+- 存在多个已知 XSS 漏洞
+- 不支持现代浏览器 API
+- 性能较差
+
+**建议**:升级到 jQuery 3.7.x
+
+#### 2.2 Bootstrap 3.x 过时
+
+**当前版本**:3.x
+**最新版本**:5.x
+**问题**:
+- 不支持 Flexbox/Grid
+- 缺少响应式设计改进
+- 依赖 jQuery
+
+### 🟡 中风险问题
+
+#### 2.3 Moment.js 已停止维护
+
+**状态**:官方已停止维护,推荐使用 Day.js 或 date-fns
+
+#### 2.4 Font Awesome 4.7.0 过时
+
+**当前版本**:4.7.0
+**最新版本**:6.x
+
+#### 2.5 前端库版本不明确
+
+多个库版本号无法从目录名确定,需要检查具体文件:
+- Vue.js
+- Vant
+- ECharts
+- Select2
+- Viewer.js
+- Swiper
+
+---
+
+## 3. 已知安全漏洞
+
+### 3.1 PHP 依赖漏洞
+
+| 包名 | 漏洞类型 | 严重程度 | CVE |
+|------|----------|----------|-----|
+| ThinkPHP 5.0.x | RCE、SQL注入、文件包含 | 严重 | 多个 |
+| PHPMailer <6.0.8 | XSS | 中 | CVE-2020-13625 |
+| Guzzle <6.5.5 | SSRF | 中 | CVE-2022-29248 |
+| jQuery <3.5.0 | XSS | 高 | CVE-2020-11022 |
+
+### 3.2 前端库漏洞
+
+| 库名 | 漏洞类型 | 严重程度 |
+|------|----------|----------|
+| jQuery 1.11.3 | XSS | 高 |
+| Bootstrap 3.x | XSS | 中 |
+
+---
+
+## 4. 依赖冗余分析
+
+### 4.1 未使用的依赖
+
+根据代码分析,以下依赖可能未被使用:
+
+| 包名 | 检查结果 |
+|------|----------|
+| topthink/think-captcha | 未发现调用 |
+| overtrue/pinyin | 仅在 GamePackage.php 中使用 |
+
+### 4.2 功能重复的依赖
+
+| 功能 | 依赖 | 替代方案 |
+|------|------|----------|
+| HTTP 客户端 | guzzlehttp/guzzle | PHP 8.0+ 内置 curl |
+| 日志 | monolog/monolog | ThinkPHP 内置 Log |
+| 邮件 | phpmailer/phpmailer | 内置 mail() 或第三方 SMTP |
+
+---
+
+## 5. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 高风险
+| 序号 | 问题 | 位置 |
+|------|------|------|
+| 1 | ThinkPHP 5.0.7 严重过时 | `composer.json` |
+| 2 | PHP 7.0-7.3 已终止支持 | `composer.json` |
+| 3 | jQuery 1.11.3 严重过时 | `public/static/lib/` |
+| 4 | Monolog 1.23.0 严重过时 | `composer.json` |
+| 5 | Bootstrap 3.x 过时 | `public/static/lib/` |
+
+#### 🟡 中风险
+| 序号 | 问题 |
+|------|------|
+| 1 | PHPMailer 有安全更新 |
+| 2 | Guzzle 6.x 过时 |
+| 3 | 华为云 SDK 使用 RC 版本 |
+| 4 | composer.json 依赖重复定义 |
+| 5 | Moment.js 已停止维护 |
+| 6 | Font Awesome 4.7.0 过时 |
+| 7 | 前端库版本不明确 |
+| 8 | jQuery/Bootstrap 存在 XSS 漏洞 |
+
+#### 🟢 低风险
+| 序号 | 问题 |
+|------|------|
+| 1 | 未使用的依赖 |
+| 2 | 功能重复的依赖 |
+| 3 | 依赖版本约束不精确 |
+| 4 | 缺少 lock 文件说明 |
+| 5 | 缺少 dev 依赖 |
+
+---
+
+## 6. 改进建议
+
+### 第一优先级(安全更新)
+1. **升级 jQuery**:1.11.3 → 3.7.x
+2. **升级 PHPMailer**:6.0.3 → 6.9.x
+3. **升级 Guzzle**:6.x → 7.x(如可能)
+
+### 第二优先级(版本升级)
+4. **升级 ThinkPHP**:5.0 → 5.1(短期)→ 8.x(长期)
+5. **升级 PHP**:7.x → 8.1+
+6. **升级 Monolog**:1.x → 3.x
+
+### 第三优先级(清理优化)
+7. **清理 composer.json 重复定义**
+8. **确认前端库版本**
+9. **移除未使用的依赖**
+10. **替换 Moment.js**:使用 Day.js
+
+### 第四优先级(长期规划)
+11. **升级 Bootstrap**:3.x → 5.x
+12. **升级 Font Awesome**:4.x → 6.x
+13. **引入依赖安全检查工具**
+
+---
+
+## 7. 升级路径建议
+
+### ThinkPHP 升级路径
+```
+5.0.7 → 5.0.24 (最新5.0) → 5.1.x → 6.x → 8.x
+```
+
+### PHP 升级路径
+```
+7.3 → 7.4 → 8.0 → 8.1 (推荐)
+```
+
+### jQuery 升级路径
+```
+1.11.3 → 3.7.1 (直接升级,使用 jQuery Migrate 兼容)
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 309 - 0
review_doc/07-测试覆盖审查.md

@@ -0,0 +1,309 @@
+# 第七阶段:测试覆盖审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | 测试现状、测试覆盖率、测试策略 |
+| 发现问题数 | **8个** |
+| 高风险 | 3个 |
+| 中风险 | 3个 |
+| 低风险 | 2个 |
+
+---
+
+## 1. 测试现状分析
+
+### 1.1 测试文件统计
+
+| 指标 | 数量 |
+|------|------|
+| PHP 文件总数 | 610 |
+| 测试目录文件数 | **1** |
+| PHPUnit 测试类 | **0** |
+| 测试方法数 | **0** |
+| 测试覆盖率 | **0%** |
+
+### 1.2 现有测试文件
+
+**文件**:`tests/lottery_test.php`
+
+**分析**:
+- 不是 PHPUnit 测试,只是一个独立脚本
+- 用于测试抽奖概率模拟
+- 没有断言(assert),只是输出结果
+- 需要手动运行查看结果
+
+```php
+// 当前代码
+$result = $lottery->draw(0, $userHistory, $prizePool);
+echo "批次数:{$sessionCount},每批抽:{$drawPerSession} 次...";
+
+// 缺少断言
+// $this->assertIsArray($result);
+// $this->assertTrue($result['success']);
+```
+
+### 1.3 PHPUnit 配置分析
+
+**文件**:`phpunit.xml`
+
+```xml
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="false">
+    <testsuites>
+        <testsuite name="Application Test Suite">
+            <directory>./tests/</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist>
+            <directory suffix=".php">application/</directory>
+        </whitelist>
+    </filter>
+</phpunit>
+```
+
+**问题**:
+- 配置存在但未使用
+- `whitelist` 配置指向 `application/` 但无测试覆盖
+- PHPUnit 版本可能与 PHP 版本不兼容
+
+---
+
+## 2. 关键路径测试缺失
+
+### 🔴 高风险(无测试覆盖)
+
+#### 2.1 支付流程
+
+**位置**:`api/controller/v1/Pay.php`、`service/GamePayService.php`
+
+**缺失测试**:
+- 支付创建
+- 支付回调验证
+- 订单状态更新
+- 退款处理
+- 金额计算精度
+
+#### 2.2 用户认证
+
+**位置**:`api/controller/v1/Login.php`、`api/controller/v1/Register.php`
+
+**缺失测试**:
+- 登录验证
+- Token 生成/验证
+- 密码加密
+- 会话管理
+
+#### 2.3 渠道适配器
+
+**位置**:`api/complex/`(53个文件)
+
+**缺失测试**:
+- 登录验证逻辑
+- 签名验证
+- 数据转换
+- 错误处理
+
+### 🟡 中风险(无测试覆盖)
+
+#### 2.4 定时任务
+
+**位置**:`crontab/`
+
+**缺失测试**:
+- 支付超时处理
+- 数据统计计算
+- 队列消费逻辑
+
+#### 2.5 数据模型
+
+**位置**:`common/model/`(151个文件)
+
+**缺失测试**:
+- 模型关联
+- 数据验证
+- 软删除
+- 事件回调
+
+#### 2.6 工具函数
+
+**位置**:`common.php`
+
+**缺失测试**:
+- 加密/解密函数
+- 金额格式化
+- 渠道层级查询
+- IP 处理
+
+---
+
+## 3. 测试策略建议
+
+### 3.1 测试金字塔
+
+```
+        /\
+       /  \        E2E 测试 (10%)
+      /    \       - 关键业务流程
+     /------\
+    /        \     集成测试 (20%)
+   /          \    - API 接口测试
+  /            \   - 数据库交互测试
+ /--------------\
+/                \ 单元测试 (70%)
+/                  \ - 工具函数
+/--------------------\- 模型方法
+                      - 服务层方法
+```
+
+### 3.2 优先级排序
+
+| 优先级 | 测试类型 | 范围 | 工作量 |
+|--------|----------|------|--------|
+| P0 | 单元测试 | 支付计算、加密函数、金额处理 | 2天 |
+| P1 | 集成测试 | API 接口、数据库操作 | 5天 |
+| P2 | E2E 测试 | 支付流程、登录流程 | 3天 |
+| P3 | 回归测试 | 全量测试套件 | 持续 |
+
+### 3.3 推荐测试工具
+
+| 工具 | 用途 |
+|------|------|
+| PHPUnit | 单元测试、集成测试 |
+| Mockery | Mock 对象 |
+| Faker | 测试数据生成 |
+| Codeception | 全栈测试 |
+| Postman/Newman | API 测试 |
+
+---
+
+## 4. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 高风险
+| 序号 | 问题 | 影响 |
+|------|------|------|
+| 1 | 测试覆盖率为 0% | 无法保证代码质量 |
+| 2 | 支付流程无测试 | 资金安全风险 |
+| 3 | 认证流程无测试 | 安全风险 |
+
+#### 🟡 中风险
+| 序号 | 问题 |
+|------|------|
+| 1 | 定时任务无测试 |
+| 2 | 数据模型无测试 |
+| 3 | 工具函数无测试 |
+
+#### 🟢 低风险
+| 序号 | 问题 |
+|------|------|
+| 1 | PHPUnit 配置未使用 |
+| 2 | 现有测试非标准格式 |
+
+---
+
+## 5. 改进建议
+
+### 第一优先级(核心测试)
+1. **支付流程测试**:覆盖支付创建、回调、退款
+2. **认证流程测试**:覆盖登录、Token、权限
+3. **金额计算测试**:确保精度正确
+
+### 第二优先级(基础测试)
+4. **工具函数测试**:加密、格式化、转换
+5. **模型方法测试**:关联、验证、事件
+6. **API 接口测试**:请求/响应验证
+
+### 第三优先级(完善测试)
+7. **定时任务测试**:异步逻辑验证
+8. **渠道适配器测试**:第三方对接验证
+9. **回归测试套件**:全量自动化测试
+
+### 第四优先级(CI/CD 集成)
+10. **引入 CI/CD**:自动运行测试
+11. **代码覆盖率报告**:监控覆盖率
+12. **测试驱动开发**:新功能先写测试
+
+---
+
+## 6. 参考测试代码
+
+### PHPUnit 测试示例
+
+```php
+<?php
+namespace tests\unit;
+
+use PHPUnit\Framework\TestCase;
+use app\common\library\PayUtil;
+
+class PayUtilTest extends TestCase
+{
+    public function testFormatFenToYuan()
+    {
+        $this->assertEquals('1.00', PayUtil::formatFenToYuan(100));
+        $this->assertEquals('0.01', PayUtil::formatFenToYuan(1));
+        $this->assertEquals('100.50', PayUtil::formatFenToYuan(10050));
+    }
+    
+    public function testFormatYuanToFen()
+    {
+        $this->assertEquals(100, PayUtil::formatYuanToFen(1.00));
+        $this->assertEquals(1, PayUtil::formatYuanToFen(0.01));
+        $this->assertEquals(10050, PayUtil::formatYuanToFen(100.50));
+    }
+    
+    public function testMakeOrderid()
+    {
+        $orderid = PayUtil::makeOrderid('TEST');
+        $this->assertMatchesRegularExpression('/^TEST\d{18}$/', $orderid);
+    }
+}
+```
+
+### API 测试示例
+
+```php
+<?php
+namespace tests\integration;
+
+use PHPUnit\Framework\TestCase;
+
+class PayApiTest extends TestCase
+{
+    public function testCreateOrder()
+    {
+        $response = $this->post('/api/v1/pay/index', [
+            'userid' => 1,
+            'gameid' => 1,
+            'amount' => 100,
+            'paytype' => 'zfb',
+        ]);
+        
+        $this->assertEquals(200, $response->getStatusCode());
+        $data = json_decode($response->getBody(), true);
+        $this->assertEquals(1, $data['code']);
+        $this->assertArrayHasKey('orderid', $data['data']);
+    }
+    
+    public function testPayCallback()
+    {
+        // 测试支付回调
+    }
+}
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 303 - 0
review_doc/08-文档完整性审查.md

@@ -0,0 +1,303 @@
+# 第八阶段:文档完整性审查报告
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 审查日期 | 2026年5月19日 |
+| 审查范围 | README、API文档、部署文档、代码注释 |
+| 发现问题数 | **12个** |
+| 高风险 | 3个 |
+| 中风险 | 5个 |
+| 低风险 | 4个 |
+
+---
+
+## 1. README 文档分析
+
+### 1.1 README.md 评估
+
+**文件**:`README.md`(301行)
+
+**优点**:
+- ✅ 项目概述清晰
+- ✅ 技术栈说明完整
+- ✅ 目录结构详细
+- ✅ 业务模块列举
+- ✅ 部署说明
+- ✅ API 响应码约定
+- ✅ 上手建议
+
+**缺失内容**:
+- ❌ 贡献指南(CONTRIBUTING.md)
+- ❌ 变更日志(CHANGELOG.md)
+- ❌ 许可证信息
+- ❌ 联系方式
+- ❌ 常见问题(FAQ)
+
+### 1.2 文档准确性检查
+
+| 内容 | README 描述 | 实际情况 | 状态 |
+|------|-------------|----------|------|
+| PHP 版本 | 7.0 - 7.2 | 7.3-fpm-alpine | ⚠️ 不一致 |
+| 数据模型数量 | 80+ | 151 | ⚠️ 不一致 |
+| 控制器数量 | 100+ | 103(仅 admin) | ✅ 基本准确 |
+| 渠道 SDK | 45+ | 53 | ⚠️ 不一致 |
+| 核心数据表 | nw_* | cy_* 和 nw_* 混用 | ⚠️ 不一致 |
+
+---
+
+## 2. API 文档分析
+
+### 2.1 API 文档现状
+
+**问题**:**完全没有 API 文档**
+
+**缺失内容**:
+- ❌ API 接口列表
+- ❌ 请求/响应格式
+- ❌ 参数说明
+- ❌ 认证方式
+- ❌ 错误码说明
+- ❌ 示例代码
+
+### 2.2 API 接口统计
+
+| 模块 | 控制器数 | 接口数(估算) |
+|------|----------|----------------|
+| api/v1 | 45 | ~200+ |
+| api/v2 | 21 | ~100+ |
+| api/mlbb | 6 | ~30+ |
+| guildapi | 28 | ~150+ |
+| mcpsapi | 16 | ~80+ |
+| **总计** | **116** | **~560+** |
+
+### 🔴 高风险问题
+
+#### 2.1 560+ API 接口无文档
+
+**影响**:
+- 新开发者无法快速上手
+- 前后端对接效率低
+- 第三方渠道接入困难
+- 难以进行接口测试
+
+---
+
+## 3. 部署文档分析
+
+### 3.1 部署文档现状
+
+**README 中的部署说明**:
+```bash
+docker-compose up -d --build
+```
+
+**缺失内容**:
+- ❌ 环境变量配置说明
+- ❌ 数据库初始化步骤
+- ❌ Redis 配置说明
+- ❌ SSL 证书配置
+- ❌ 域名配置
+- ❌ 监控配置
+- ❌ 备份策略
+- ❌ 故障排查指南
+
+### 3.2 配置文件文档
+
+| 配置文件 | 文档状态 |
+|----------|----------|
+| `.env` | ❌ 无说明 |
+| `config.php` | ❌ 无说明 |
+| `nginx/*.conf` | ❌ 无说明 |
+| `docker-compose.yml` | ❌ 无说明 |
+| `crontabs/crontabfile.txt` | ❌ 无说明 |
+
+### 🟡 中风险问题
+
+#### 3.1 部署文档不完整
+
+**影响**:
+- 部署过程依赖口口相传
+- 环境配置容易出错
+- 新环境搭建困难
+
+---
+
+## 4. 代码文档分析
+
+### 4.1 代码注释覆盖率
+
+| 指标 | 数量 | 覆盖率 |
+|------|------|--------|
+| PHP 文件总数 | 610 | - |
+| 有文件头注释 | 259 | **42.5%** |
+| 有类注释 | 64 | **10.5%** |
+| 方法总数 | 3,098 | - |
+| 有注释的方法 | 1,695 | **54.7%** |
+
+### 4.2 文档目录分析
+
+**现有文档目录**:
+```
+www/new_sdk/
+├── md/                    # 7 个文档
+├── doc/                   # 文档目录
+├── doc_channel/           # 渠道文档
+├── docs_12/               # 重构文档(10个)
+└── 祈盟SDK-20260512问题复盘-执行计划.md
+```
+
+**md/ 目录内容**:
+| 文件 | 内容 |
+|------|------|
+| 电魂支付回调-服务端.md | 支付回调说明 |
+| 定时任务.txt | 定时任务列表 |
+| 日志记录.md | 日志说明 |
+| 相关事项说明.md | 其他说明 |
+| AiCode.md | AI 代码相关 |
+| nw_sdk.sql | SQL 文件 |
+| v4.18版本开发需求.md | 需求文档 |
+
+### 4.3 文档分散问题
+
+**问题**:文档分散在多个目录,缺乏统一组织
+
+**目录**:
+- `md/` - 7 个文件
+- `doc/` - 未知数量
+- `doc_channel/` - 未知数量
+- `docs_12/` - 10 个文件
+- 根目录 - 2 个文件
+
+---
+
+## 5. 问题汇总
+
+### 按风险等级分类
+
+#### 🔴 高风险
+| 序号 | 问题 | 影响 |
+|------|------|------|
+| 1 | 560+ API 接口无文档 | 前后端对接困难 |
+| 2 | 部署文档不完整 | 环境搭建困难 |
+| 3 | 文档与代码不一致 | 误导开发者 |
+
+#### 🟡 中风险
+| 序号 | 问题 |
+|------|------|
+| 1 | 缺少 API 认证说明 |
+| 2 | 缺少数据库设计文档 |
+| 3 | 缺少架构设计文档 |
+| 4 | 文档分散无组织 |
+| 5 | 缺少故障排查指南 |
+
+#### 🟢 低风险
+| 序号 | 问题 |
+|------|------|
+| 1 | 缺少贡献指南 |
+| 2 | 缺少变更日志 |
+| 3 | 缺少许可证信息 |
+| 4 | 缺少 FAQ |
+
+---
+
+## 6. 改进建议
+
+### 第一优先级(核心文档)
+1. **API 文档**:使用 Swagger/OpenAPI 生成
+2. **部署文档**:完整的环境搭建指南
+3. **数据库文档**:ER 图 + 表结构说明
+
+### 第二优先级(开发文档)
+4. **架构设计文档**:系统架构图 + 模块说明
+5. **开发规范文档**:代码规范 + Git 规范
+6. **测试文档**:测试策略 + 测试用例
+
+### 第三优先级(运维文档)
+7. **运维手册**:监控 + 备份 + 故障排查
+8. **变更日志**:版本更新记录
+9. **贡献指南**:参与开发流程
+
+### 第四优先级(完善文档)
+10. **FAQ**:常见问题解答
+11. **术语表**:业务术语解释
+12. **许可证**:开源协议说明
+
+---
+
+## 7. 文档模板
+
+### API 文档模板
+
+```markdown
+## POST /api/v1/pay/index
+
+创建支付订单
+
+### 请求参数
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| userid | int | 是 | 用户 ID |
+| gameid | int | 是 | 游戏 ID |
+| amount | int | 是 | 金额(分) |
+| paytype | string | 是 | 支付方式 |
+
+### 响应示例
+
+```json
+{
+    "code": 1,
+    "msg": "success",
+    "data": {
+        "orderid": "WL20260519123456789"
+    }
+}
+```
+
+### 错误码
+
+| 错误码 | 说明 |
+|--------|------|
+| 0 | 失败 |
+| 100 | 未登录 |
+| 110 | 参数错误 |
+```
+
+### 部署文档模板
+
+```markdown
+## 环境要求
+
+- Docker 20.10+
+- Docker Compose 2.0+
+- 4GB+ 内存
+
+## 快速开始
+
+1. 克隆代码
+   ```bash
+   git clone <repo>
+   ```
+
+2. 配置环境变量
+   ```bash
+   cp .env.example .env
+   vim .env
+   ```
+
+3. 启动服务
+   ```bash
+   docker-compose up -d
+   ```
+
+4. 初始化数据库
+   ```bash
+   docker exec -it new-sdk php think migrate:run
+   ```
+```
+
+---
+
+*审查报告生成时间:2026年5月19日*

+ 337 - 0
review_doc/09-审查总结报告.md

@@ -0,0 +1,337 @@
+# 第九阶段:审查总结报告
+
+## 项目审查总览
+
+| 项目 | 详情 |
+|------|------|
+| 项目名称 | 祈盟SDK (new_sdk) |
+| 技术栈 | ThinkPHP 5.0.7 + PHP 7.3 + MySQL + Redis |
+| 项目规模 | 610个PHP文件,151个数据模型 |
+| 审查日期 | 2026年5月19日 |
+| 审查阶段 | 8个阶段 |
+| 发现问题总数 | **189个** |
+
+---
+
+## 问题统计
+
+### 按阶段分类
+
+| 阶段 | 高风险 | 中风险 | 低风险 | 合计 |
+|------|--------|--------|--------|------|
+| 01-基础设施审查 | 6 | 10 | 7 | **23** |
+| 02-安全审查 | 12 | 10 | 1 | **23** |
+| 03-架构设计审查 | 8 | 15 | 9 | **32** |
+| 04-代码质量审查 | 8 | 12 | 8 | **28** |
+| 05-性能优化审查 | 10 | 15 | 10 | **35** |
+| 06-依赖管理审查 | 5 | 8 | 5 | **18** |
+| 07-测试覆盖审查 | 3 | 3 | 2 | **8** |
+| 08-文档完整性审查 | 3 | 5 | 4 | **12** |
+| **总计** | **55** | **78** | **46** | **189** |
+
+### 按风险等级分类
+
+#### 🔴 严重风险(55个)- 需立即修复
+
+| 类别 | 数量 | 主要问题 |
+|------|------|----------|
+| 安全漏洞 | 15 | SQL注入、XSS、CSRF、认证绕过 |
+| 架构缺陷 | 12 | 模块重复、职责混乱、耦合严重 |
+| 性能问题 | 10 | N+1查询、缓存穿透、无超时设置 |
+| 代码质量 | 8 | 4000行控制器、554行函数 |
+| 依赖安全 | 5 | 框架过时、PHP EOL |
+| 测试缺失 | 3 | 覆盖率0%、关键路径无测试 |
+| 文档缺失 | 2 | 560+ API无文档 |
+
+#### 🟡 中风险(78个)- 建议尽快修复
+
+| 类别 | 数量 |
+|------|------|
+| 代码规范 | 20 |
+| 性能优化 | 15 |
+| 架构改进 | 15 |
+| 安全加固 | 10 |
+| 依赖升级 | 8 |
+| 文档完善 | 5 |
+| 测试补充 | 3 |
+| 基础设施 | 2 |
+
+#### 🟢 低风险(46个)- 计划修复
+
+| 类别 | 数量 |
+|------|------|
+| 代码规范 | 15 |
+| 文档完善 | 10 |
+| 依赖清理 | 8 |
+| 性能优化 | 8 |
+| 其他 | 5 |
+
+---
+
+## Top 10 关键问题
+
+### 1. 🔴 SQL 注入漏洞(安全)
+
+**位置**:`mcpsapi/controller/ChannelRebind.php:74` 等
+
+**问题**:用户输入直接拼接到 SQL 语句
+
+**影响**:数据库被攻击、数据泄露
+
+**修复**:使用参数绑定
+
+---
+
+### 2. 🔴 权限检查逻辑缺陷(安全)
+
+**位置**:`common/logic/Auth.php:46-48`
+
+**问题**:未注册的路由规则默认放行
+
+**影响**:任意未配置的接口无需权限即可访问
+
+**修复**:`return false` 替代 `return true`
+
+---
+
+### 3. 🔴 GuildAPI GET 免密登录(安全)
+
+**位置**:`guildapi/controller/Login.php:98-103`
+
+**问题**:仅传 username 即可直接登录
+
+**影响**:任意用户可冒充登录
+
+**修复**:删除 GET 登录路径或强制密码验证
+
+---
+
+### 4. 🔴 Admin 模板 652+ 处 XSS(安全)
+
+**位置**:`admin/view/` 目录
+
+**问题**:`{$Request.get.xxx}` 未转义输出
+
+**影响**:反射型 XSS 攻击
+
+**修复**:使用 `{:escape()}` 转义
+
+---
+
+### 5. 🔴 渠道适配器重复度 58%(架构)
+
+**位置**:`api/complex/`(53个文件)
+
+**问题**:~3,200行重复代码
+
+**影响**:维护成本高、修改易遗漏
+
+**修复**:提取抽象基类
+
+---
+
+### 6. 🔴 Pay 列表 N+1 查询(性能)
+
+**位置**:`admin/controller/Pay.php:366`
+
+**问题**:100条数据 = 400-1000次额外查询
+
+**影响**:页面加载缓慢
+
+**修复**:批量预查询 + 数组映射
+
+---
+
+### 7. 🔴 缓存穿透/击穿/雪崩(性能)
+
+**位置**:全局
+
+**问题**:
+- 不缓存空值(穿透)
+- 无互斥锁(击穿)
+- 过期时间集中(雪崩)
+
+**影响**:高并发下数据库压力激增
+
+**修复**:缓存空值 + 互斥锁 + 随机过期
+
+---
+
+### 8. 🔴 curl 无超时设置(性能)
+
+**位置**:5处
+
+**问题**:请求可能永久阻塞
+
+**影响**:PHP-FPM worker 耗尽
+
+**修复**:统一设置 CONNECTTIMEOUT=5, TIMEOUT=10
+
+---
+
+### 9. 🔴 ThinkPHP 5.0.7 严重过时(依赖)
+
+**位置**:`composer.json`
+
+**问题**:2017年版本,已停止安全更新
+
+**影响**:存在已知漏洞
+
+**修复**:升级到 5.1.x(短期)→ 8.x(长期)
+
+---
+
+### 10. 🔴 测试覆盖率 0%(测试)
+
+**位置**:全局
+
+**问题**:无任何单元测试
+
+**影响**:改一处可能影响多处
+
+**修复**:从核心支付流程开始补充测试
+
+---
+
+## 改进路线图
+
+### 第一阶段:安全加固(1-2周)
+
+```
+Week 1-2: 安全漏洞修复
+├── 修复 SQL 注入(参数绑定)
+├── 修复权限检查逻辑
+├── 删除 GuildAPI GET 登录
+├── 修复 XSS(模板转义)
+├── 添加 CSRF Token
+├── 限制 CORS 来源
+└── 添加安全响应头
+```
+
+**预计工作量**:5-7人天
+
+---
+
+### 第二阶段:性能优化(2-3周)
+
+```
+Week 3-5: 性能问题修复
+├── 修复 N+1 查询
+├── 优化缓存策略
+├── 统一 curl 超时
+├── 启用 OPcache
+└── 数据库索引优化
+```
+
+**预计工作量**:8-10人天
+
+---
+
+### 第三阶段:架构重构(1-2月)
+
+```
+Month 2-3: 架构改进
+├── 合并 guildapi/mcpsapi
+├── 重构渠道适配器
+├── 拆分超长控制器
+├── 统一模型基类
+└── 完善 Service 层
+```
+
+**预计工作量**:20-30人天
+
+---
+
+### 第四阶段:质量提升(2-3月)
+
+```
+Month 3-5: 代码质量
+├── 统一命名规范
+├── 清理重复代码
+├── 补充单元测试
+├── 完善文档
+└── 引入 CI/CD
+```
+
+**预计工作量**:30-40人天
+
+---
+
+### 第五阶段:技术升级(3-6月)
+
+```
+Month 6+: 技术栈升级
+├── 升级 PHP 8.1+
+├── 升级 ThinkPHP 8.x
+├── 升级前端框架
+└── 引入现代化工具
+```
+
+**预计工作量**:50-80人天
+
+---
+
+## 工作量估算
+
+| 阶段 | 工作量 | 优先级 | 时间规划 |
+|------|--------|--------|----------|
+| 安全加固 | 5-7人天 | P0 | 立即开始 |
+| 性能优化 | 8-10人天 | P1 | 2周内 |
+| 架构重构 | 20-30人天 | P2 | 1-2月内 |
+| 质量提升 | 30-40人天 | P3 | 3-5月内 |
+| 技术升级 | 50-80人天 | P4 | 6月+ |
+| **总计** | **113-167人天** | | **6-12个月** |
+
+---
+
+## 风险评估
+
+### 当前风险等级:🔴 高风险
+
+| 风险类型 | 风险等级 | 说明 |
+|----------|----------|------|
+| 安全风险 | 🔴 严重 | SQL注入、XSS、认证绕过 |
+| 稳定性风险 | 🔴 高 | 缓存穿透、无超时、N+1查询 |
+| 维护风险 | 🟡 中 | 代码重复、无测试、文档缺失 |
+| 技术风险 | 🟡 中 | 框架过时、依赖漏洞 |
+| 业务风险 | 🟡 中 | 支付精度、订单竞态 |
+
+### 风险缓解建议
+
+1. **立即**:修复安全漏洞(第一阶段)
+2. **短期**:性能优化(第二阶段)
+3. **中期**:架构重构(第三阶段)
+4. **长期**:技术升级(第五阶段)
+
+---
+
+## 审查文档清单
+
+| 序号 | 文件名 | 内容 | 状态 |
+|------|--------|------|------|
+| 0 | 审查计划.md | 审查计划和步骤 | ✅ 已完成 |
+| 1 | 01-基础设施审查.md | Docker、Nginx、PHP配置 | ✅ 已完成 |
+| 2 | 02-安全审查.md | SQL注入、XSS、CSRF、认证、支付 | ✅ 已完成 |
+| 3 | 03-架构设计审查.md | 模块、路由、数据库、设计模式 | ✅ 已完成 |
+| 4 | 04-代码质量审查.md | 规范、重复代码、复杂度、命名 | ✅ 已完成 |
+| 5 | 05-性能优化审查.md | 查询、缓存、队列、同步阻塞 | ✅ 已完成 |
+| 6 | 06-依赖管理审查.md | Composer、前端库、安全漏洞 | ✅ 已完成 |
+| 7 | 07-测试覆盖审查.md | 测试现状、缺失、策略 | ✅ 已完成 |
+| 8 | 08-文档完整性审查.md | README、API文档、部署文档 | ✅ 已完成 |
+| 9 | 09-审查总结报告.md | 问题汇总、路线图、工作量 | ✅ 已完成 |
+
+---
+
+## 致谢
+
+感谢您对项目审查的配合。本审查报告基于代码静态分析,旨在帮助识别潜在问题和改进机会。
+
+**重要提醒**:
+- 本报告不构成安全审计认证
+- 建议在修复安全漏洞后进行专业安全测试
+- 重构前建议先补充核心流程的单元测试
+
+---
+
+*审查报告生成时间:2026年5月19日*
+*审查工具:OpenCode + AI 静态分析*

+ 130 - 0
review_doc/审查计划.md

@@ -0,0 +1,130 @@
+# 祈盟SDK项目审查计划
+
+## 审查概述
+
+| 项目 | 详情 |
+|------|------|
+| 项目名称 | 祈盟SDK (new_sdk) |
+| 技术栈 | ThinkPHP 5.0.7 + PHP 7.3 + MySQL + Redis |
+| 项目规模 | 610个PHP文件,151个数据模型 |
+| 审查深度 | 深度级(逐文件分析,含代码示例和重构方案) |
+| 排除范围 | 所有 . 开头的文件和文件夹 |
+| 审查日期 | 2026年5月19日 |
+
+---
+
+## 审查阶段
+
+### 第一阶段:基础设施审查
+- **输出文件**:`01-基础设施审查.md`
+- **审查内容**:
+  - Dockerfile 安全性和优化
+  - docker-compose 配置合理性
+  - Nginx 配置安全性
+  - PHP 配置优化
+  - 定时任务可靠性
+  - 日志配置
+
+### 第二阶段:安全审查(重点)
+- **输出文件**:`02-安全审查.md`
+- **审查内容**:
+  - SQL注入风险点
+  - XSS防护措施
+  - CSRF防护完整性
+  - 认证授权机制
+  - 文件上传安全
+  - 敏感数据存储
+  - 支付接口安全(10+支付网关)
+  - API接口防护
+  - 会话管理安全
+
+### 第三阶段:架构设计审查
+- **输出文件**:`03-架构设计审查.md`
+- **审查内容**:
+  - 模块划分合理性
+  - 路由设计(8+子域名)
+  - 数据库表设计
+  - 多租户隔离机制
+  - 设计模式使用
+  - 依赖关系分析
+
+### 第四阶段:代码质量审查
+- **输出文件**:`04-代码质量审查.md`
+- **审查内容**:
+  - 代码规范一致性
+  - 重复代码检测(53个渠道适配器)
+  - 圈复杂度分析
+  - 命名规范
+  - 注释质量
+  - 异常处理
+
+### 第五阶段:性能优化审查
+- **输出文件**:`05-性能优化审查.md`
+- **审查内容**:
+  - 数据库查询优化
+  - N+1查询问题
+  - 缓存策略
+  - 队列处理效率
+  - 同步阻塞问题
+  - 大数据量处理
+
+### 第六阶段:依赖管理审查
+- **输出文件**:`06-依赖管理审查.md`
+- **审查内容**:
+  - Composer依赖版本
+  - 已知安全漏洞
+  - 前端库版本
+  - 依赖冗余
+
+### 第七阶段:测试覆盖审查
+- **输出文件**:`07-测试覆盖审查.md`
+- **审查内容**:
+  - 现有测试分析
+  - 关键路径测试缺失
+  - 测试策略建议
+  - 测试工具推荐
+
+### 第八阶段:文档完整性审查
+- **输出文件**:`08-文档完整性审查.md`
+- **审查内容**:
+  - README完整性
+  - API文档
+  - 部署文档
+  - 代码注释
+
+### 第九阶段:总结报告
+- **输出文件**:`09-审查总结报告.md`
+- **内容**:
+  - 问题汇总清单
+  - 风险等级分类
+  - 优先级排序
+  - 改进路线图
+  - 工作量估算
+
+---
+
+## 审查重点(基于项目特点)
+
+1. **支付安全** - 10+支付网关的集成安全性
+2. **多租户隔离** - 游戏/渠道数据隔离是否完善
+3. **53个渠道SDK适配器** - 代码重复度、维护性
+4. **151个数据模型** - 统一规范、关联关系
+5. **定时任务** - 16个定时任务的可靠性和幂等性
+6. **队列处理** - 异步回调的可靠性
+
+---
+
+## 输出文件清单
+
+| 序号 | 文件名 | 状态 |
+|------|--------|------|
+| 0 | 审查计划.md | ✅ 已完成 |
+| 1 | 01-基础设施审查.md | ⏳ 待执行 |
+| 2 | 02-安全审查.md | ⏳ 待执行 |
+| 3 | 03-架构设计审查.md | ⏳ 待执行 |
+| 4 | 04-代码质量审查.md | ⏳ 待执行 |
+| 5 | 05-性能优化审查.md | ⏳ 待执行 |
+| 6 | 06-依赖管理审查.md | ⏳ 待执行 |
+| 7 | 07-测试覆盖审查.md | ⏳ 待执行 |
+| 8 | 08-文档完整性审查.md | ⏳ 待执行 |
+| 9 | 09-审查总结报告.md | ⏳ 待执行 |

+ 3 - 0
script/complex_summary.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+# 聚合渠道统计脚本
+cd /app/www/new_sdk && php think ComplexSummaryDaily >> /app/logs/complex_summary.log

+ 6 - 0
script/daily.sh

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# cd /app/www/new_sdk/
+# su www-data -s /usr/local/bin/php artisan command:jiubidetail >> /app/script/jiubidetail.log
+
+# sh /app/www/push_websocketstart.sh >> 

+ 3 - 0
script/mlbb.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cd /app/www/new_sdk && php think MlbbTaskHandle >> /app/logs/mlbb_three.log

+ 3 - 0
script/pay_timeout.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cd /app/www/new_sdk && /usr/bin/flock -n /tmp/pay_timeout.lock php think PayTimeout >> /app/logs/pay_timeout.log

+ 3 - 0
script/poly_complex_notice.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+# 每小时执行一次
+cd /app/www/new_sdk && php think PolyChannelSmsWarn >> /app/logs/poly_complex_notice.log

+ 3 - 0
script/poly_complex_pay.sh

@@ -0,0 +1,3 @@
+#!/bin/sh
+# 半小时执行一次
+cd /app/www/new_sdk && php think PolyChannelFlowStats >> /app/logs/poly_complex_pay.log

+ 153 - 0
script/statistics_handle.sh

@@ -0,0 +1,153 @@
+#!/usr/bin/env bash
+# 如果不是在 bash 下执行,则尝试用 bash 重新执行,避免用 sh 导致语法错误
+if [ -z "$BASH_VERSION" ]; then
+    if command -v bash >/dev/null 2>&1; then
+        exec bash "$0" "$@"
+    else
+        echo "This script requires bash. Please run with bash." >&2
+        exit 1
+    fi
+fi
+
+# 获取输入参数的时间,默认值为昨天的日期
+if [ -n "$1" ]; then
+    data_time=$1
+else
+    yesterday=$(date +%s)
+    yesterday=$((yesterday - 86400))
+    data_time=$(date -d "@$yesterday" +"%Y-%m-%d")
+fi
+
+# 可选:第二个参数为每批大小,第三个参数为批次间隔(秒)
+BATCH_SIZE=${2:-10}
+BATCH_INTERVAL=${3:-300}
+
+# 日志文件路径(可通过第4个参数覆盖),保持默认兼容性
+# LOG_FILE=${4:-./statistics_handle.log}
+LOG_FILE=${4:-/app/logs/statistics_handle_v2.log}
+
+# 确保日志目录存在(先创建目录,避免后面写日志失败)
+mkdir -p "$(dirname "$LOG_FILE")"
+
+# 写入日期到日志(使用 printf 更可靠)
+printf "\n# DATE: %s - STARTED AT: %s\n" "$data_time" "$(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE"
+
+# 主机地址
+# host_url="http://admin.newsdk.com"
+# host_url="http://devadmin.qmgames.cn"  # 测试
+host_url="http://admin.qmgames.cn"  # 正式
+
+# 定义 URL 列表(每行一个 URL)
+url_list="
+$host_url/retaine_platform/reg_num?day=$data_time
+$host_url/retaine_platform/role_num?day=$data_time
+$host_url/retaine_platform/act_num?day=$data_time
+$host_url/retaine_platform/recharge_num?day=$data_time
+$host_url/retaine_platform/reg_total?day=$data_time
+
+$host_url/retaine_game/reg_num?day=$data_time
+$host_url/retaine_game/role_num?day=$data_time
+$host_url/retaine_game/act_num?day=$data_time
+$host_url/retaine_game/recharge_num?day=$data_time
+$host_url/retaine_game/one_stay?day=$data_time
+$host_url/retaine_game/three_stay?day=$data_time
+$host_url/retaine_game/four_stay?day=$data_time
+$host_url/retaine_game/five_stay?day=$data_time
+$host_url/retaine_game/six_stay?day=$data_time
+$host_url/retaine_game/seven_stay?day=$data_time
+$host_url/retaine_game/fifteen_stay?day=$data_time
+$host_url/retaine_game/thirty_stay?day=$data_time
+$host_url/retaine_game/reg_total?day=$data_time
+
+$host_url/retaine_server/reg_num?day=$data_time
+$host_url/retaine_server/role_num?day=$data_time
+$host_url/retaine_server/act_num?day=$data_time
+$host_url/retaine_server/recharge_num?day=$data_time
+$host_url/retaine_server/one_stay?day=$data_time
+$host_url/retaine_server/three_stay?day=$data_time
+$host_url/retaine_server/four_stay?day=$data_time
+$host_url/retaine_server/five_stay?day=$data_time
+$host_url/retaine_server/six_stay?day=$data_time
+$host_url/retaine_server/seven_stay?day=$data_time
+$host_url/retaine_server/fifteen_stay?day=$data_time
+$host_url/retaine_server/thirty_stay?day=$data_time
+$host_url/retaine_server/reg_total?day=$data_time
+
+$host_url/retaine_game_channel/reg_num?day=$data_time
+$host_url/retaine_game_channel/role_num?day=$data_time
+$host_url/retaine_game_channel/act_num?day=$data_time
+$host_url/retaine_game_channel/recharge_num?day=$data_time
+$host_url/retaine_game_channel/one_stay?day=$data_time
+$host_url/retaine_game_channel/three_stay?day=$data_time
+$host_url/retaine_game_channel/four_stay?day=$data_time
+$host_url/retaine_game_channel/five_stay?day=$data_time
+$host_url/retaine_game_channel/six_stay?day=$data_time
+$host_url/retaine_game_channel/seven_stay?day=$data_time
+$host_url/retaine_game_channel/fifteen_stay?day=$data_time
+$host_url/retaine_game_channel/thirty_stay?day=$data_time
+$host_url/retaine_game_channel/reg_total?day=$data_time
+$host_url/retaine_game_channel/new_act_num?day=$data_time
+$host_url/retaine_game_channel/new_recharge_num?day=$data_time
+
+$host_url/retaine_server_channel/reg_num?day=$data_time
+$host_url/retaine_server_channel/role_num?day=$data_time
+$host_url/retaine_server_channel/act_num?day=$data_time
+$host_url/retaine_server_channel/recharge_num?day=$data_time
+$host_url/retaine_server_channel/one_stay?day=$data_time
+$host_url/retaine_server_channel/three_stay?day=$data_time
+$host_url/retaine_server_channel/four_stay?day=$data_time
+$host_url/retaine_server_channel/five_stay?day=$data_time
+$host_url/retaine_server_channel/six_stay?day=$data_time
+$host_url/retaine_server_channel/seven_stay?day=$data_time
+$host_url/retaine_server_channel/fifteen_stay?day=$data_time
+$host_url/retaine_server_channel/thirty_stay?day=$data_time
+$host_url/retaine_server_channel/reg_total?day=$data_time
+"
+
+# 定义模拟 GET 请求的方法
+simulate_get_request() {
+    url=$1
+    start_time=$(date +%s)
+    response=$(curl -s -G --connect-timeout 10 --max-time 600 "$url")
+    ret=$?
+    end_time=$(date +%s)
+    duration=$((end_time - start_time))
+
+    res="ok"
+    if [ $ret -ne 0 ] || [ "$response" != "ok" ]; then
+        res="error"
+    fi
+
+    echo "## URL: $url - RES: $res - TIME: ${duration}s" >> "$LOG_FILE"
+}
+
+# 循环发送请求,按批次执行,每批执行后休眠 BATCH_INTERVAL 秒
+count=0
+total=0
+# 使用 while read 更可靠地按行读取多行字符串(保留空行跳过)
+while IFS= read -r url; do
+    # 跳过空行
+    if [ -z "$url" ]; then
+        continue
+    fi
+
+    simulate_get_request "$url"
+    count=$((count + 1))
+    total=$((total + 1))
+
+    if [ $count -ge $BATCH_SIZE ]; then
+        echo "### BATCH FINISHED at $(date '+%Y-%m-%d %H:%M:%S'), processed $total urls. Sleeping ${BATCH_INTERVAL}s" >> "$LOG_FILE"
+        count=0
+        sleep "$BATCH_INTERVAL"
+    fi
+done <<EOF
+${url_list}
+EOF
+
+# 如果最后一批未满,也记录并不需要再睡眠
+if [ $count -gt 0 ]; then
+    echo "### FINAL BATCH FINISHED at $(date '+%Y-%m-%d %H:%M:%S'), processed $total urls." >> "$LOG_FILE"
+fi
+
+# 添加结束标记
+echo "## FINISHED #" >> "$LOG_FILE"

+ 2 - 0
www/.gitignore

@@ -0,0 +1,2 @@
+/push_websocket
+/push_websockets

+ 5 - 0
www/new_sdk/.codegraph/.gitignore

@@ -0,0 +1,5 @@
+# CodeGraph data files — local to each machine, not for committing.
+# Ignore everything in .codegraph/ except this file itself, so transient
+# files (the database, daemon.pid, sockets, logs) never show up in git.
+*
+!.gitignore

+ 143 - 0
www/new_sdk/.env

@@ -0,0 +1,143 @@
+app_debug=true
+app_trace=false
+api_debug=true
+
+
+# sdk游戏账户注册的区分标识
+sdk_mark_type=_dev
+pay_handle_version=v3.4.2
+
+# ## 官网 HOME 模块 ##
+# 支付回调域名
+HTTP_HOST_URL=devsdkapi.newsdk.com
+HTTP_HOST_URL_ALL=http://devsdkapi.newsdk.com
+HTTP_HOST_URL_WWW=http://devwww.newsdk.com
+HTTP_HOST_URL_M=http://devm.newsdk.com
+HTTP_HOST_URL_Static=http://static.newsdk.com
+HTTP_HOST_URL_DOWNAPP=https://cdn.newsdk.com
+HTTP_HOST_URL_CPS=http://devcps.newsdk.com
+HTTP_HOST_URL_H5=http://devsdkh5.qmgames.cn
+
+# 上传文件保存场所 = ali:阿里云, local:本地
+UPLOAD_SAVE_PLACE=ali
+# 游戏分包的Bucket名
+PACKAGE_BUCKET=
+# auth_code 加解密密钥
+AUTH_KEY=xxx
+# 后台密码加密前缀
+ADMIN_PASS_PRE=ayadm
+# 母包上传目录
+MUBAO_PATH=mubao/
+# 支付回调域名
+HTTP_HOST_URL=devsdkapi.newsdk.com
+# 客户端加密标识
+AES_KEY=xxxx
+
+[database]
+hostname=xxx
+database=new_sdk
+username=xxx
+password='xxx'
+hostport=3306
+
+[redis]
+host=127.0.0.1
+port=6379
+password=
+prefix=newsdk_
+select=0
+seelect_go=1
+
+[ali_oss]
+access_keysid=xxx
+access_keysecrt=xxx
+access_endpoint=oss-cn-hangzhou.aliyuncs.com
+access_bucket=new-sdk
+
+[hw_obs]
+access_keyid=
+access_keysecret=
+endpoint=
+bucket=
+
+[py]
+#判断游戏母包的地址
+use_ip=http://127.0.0.1:9998
+
+[ws]
+# 注册中心地址
+register_addr=127.0.0.1:3238
+# ws 连接地址
+ws_addr=ws://127.0.0.1:8282/
+
+## 钉钉通知 ##
+[dingtalk]
+server_name=DEV_祈盟SDK
+warning_url='https://oapi.dingtalk.com/robot/send?access_token=xxxx'
+notic_url='https://oapi.dingtalk.com/robot/send?access_token=xxx'
+operat_url='https://oapi.dingtalk.com/robot/send?access_token=xxx'
+notice_weelfar_url='https://oapi.dingtalk.com/robot/send?access_token=xxx'
+
+## 微信公众号 ##
+[wechat]
+appid=''
+appsecret=''
+
+[juliang]
+app_id='xxx'
+secret='xxx'
+
+[mubao]
+down_url=https://sdkdevdown.newsdk.com
+upload_url=http://sdkdevpack.newsdk.com
+
+# 176 的配置
+[yql]
+# 测试
+api_url=http://devapi.176park.com:8081
+# 正式
+#api_url=https://api.176park.com:8081
+api_sign_key='xxx'
+app_url=http://devh5.176park.com/
+app_hash_key='Su)xxx'
+
+# 祈盟聚合
+[qmjh]
+api_sign_key='xxx'
+
+# 聚合渠道
+[complex]
+# 三九互娱配置了商品的游戏
+sj_game_ids=286_kdzz
+
+
+# 请求频率次数限制(3次/60s)
+request_requency_time=60
+request_requency_count=3
+
+[kafka]
+bootstrap_servers=localhost:9092
+group_id=new_sdk_group
+default_topic=new_sdk_topic
+debug=false
+
+# 支付配置-趣智连
+[pay_qzl]
+app_id=xxx
+mch_no=xxx
+pay_key=xxx
+
+# 中宣配置 #
+[zhongxuan]
+app_id=xxx
+secret_key=xxxx
+
+# 飞书通知
+[feishu]
+notice_url=https://open.feishu.cn/open-apis/bot/v2/hook/xxxx
+
+# 默认有效期15天
+[jwt]
+jwt_key=xxxx
+exp_time=1296000
+

+ 5 - 0
www/new_sdk/.gitignore

@@ -0,0 +1,5 @@
+.idea
+runtime
+/md/*
+.DS_Store
+*.pem

+ 940 - 0
www/new_sdk/.understand-anything/.trash-20260630091809/assembled-graph.json

@@ -0,0 +1,940 @@
+{
+  "nodes": [
+    {
+      "id": "document:README.md",
+      "type": "document",
+      "name": "README.md",
+      "filePath": "README.md",
+      "summary": "项目说明文档,描述游戏SDK平台的域名配置、环境要求和相关服务",
+      "tags": [
+        "documentation",
+        "project-overview"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:composer.json",
+      "type": "config",
+      "name": "composer.json",
+      "filePath": "composer.json",
+      "summary": "PHP依赖管理配置文件,定义ThinkPHP 5.0框架及支付、队列等依赖包",
+      "tags": [
+        "php",
+        "dependencies",
+        "composer"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:public/index.php",
+      "type": "file",
+      "name": "index.php",
+      "filePath": "public/index.php",
+      "summary": "应用入口文件,设置CORS头、定义应用路径、加载常量配置和框架引导",
+      "tags": [
+        "entry-point",
+        "php",
+        "bootstrap"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:application/config.php",
+      "type": "config",
+      "name": "config.php",
+      "filePath": "application/config.php",
+      "summary": "应用主配置文件,定义模块设置、URL路由、数据库缓存、模板引擎等核心配置",
+      "tags": [
+        "config",
+        "thinkphp",
+        "application"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/route.php",
+      "type": "file",
+      "name": "route.php",
+      "filePath": "application/route.php",
+      "summary": "路由配置文件,定义域名到模块的映射(admin/api/guildapi/complex/home/mobile),配置PC站和M站路由规则",
+      "tags": [
+        "routing",
+        "thinkphp",
+        "domain-mapping"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/common.php",
+      "type": "file",
+      "name": "common.php",
+      "filePath": "application/common.php",
+      "summary": "公共函数库,包含随机数生成、数组排序、加密解密、HTTP请求、日期处理等通用工具函数",
+      "tags": [
+        "utility",
+        "common",
+        "helper"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/tags.php",
+      "type": "file",
+      "name": "tags.php",
+      "filePath": "application/tags.php",
+      "summary": "行为扩展定义文件,配置应用初始化、路由开始、模块初始化等钩子",
+      "tags": [
+        "hooks",
+        "lifecycle",
+        "thinkphp"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/command.php",
+      "type": "file",
+      "name": "command.php",
+      "filePath": "application/command.php",
+      "summary": "命令行配置文件,注册自定义命令如Kafka消费者",
+      "tags": [
+        "cli",
+        "commands",
+        "kafka"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/admin/common.php",
+      "type": "file",
+      "name": "admin/common.php",
+      "filePath": "application/admin/common.php",
+      "summary": "后台公共函数库,包含权限检查、日志记录、数据格式化等后台专用工具函数",
+      "tags": [
+        "admin",
+        "utility",
+        "helper"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/admin/controller/Admin.php",
+      "type": "file",
+      "name": "Admin.php",
+      "filePath": "application/admin/controller/Admin.php",
+      "summary": "后台公共控制器基类,实现登录验证、权限检查、WebSocket令牌管理",
+      "tags": [
+        "admin",
+        "controller",
+        "base-class",
+        "auth"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/common.php",
+      "type": "file",
+      "name": "api/common.php",
+      "filePath": "application/api/common.php",
+      "summary": "API模块公共函数库,包含API签名验证、参数加密解密、渠道鉴权等",
+      "tags": [
+        "api",
+        "utility",
+        "auth"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/controller/v1/Login.php",
+      "type": "file",
+      "name": "Login.php",
+      "filePath": "application/api/controller/v1/Login.php",
+      "summary": "用户登录控制器,处理账号密码登录、手机验证码登录、第三方登录等",
+      "tags": [
+        "api",
+        "login",
+        "authentication"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/controller/v1/Pay.php",
+      "type": "file",
+      "name": "Pay.php",
+      "filePath": "application/api/controller/v1/Pay.php",
+      "summary": "支付控制器,处理游戏内充值、创建订单、调用支付渠道(支付宝/微信)",
+      "tags": [
+        "api",
+        "payment",
+        "alipay",
+        "wechat"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/api/controller/v1/PayNotify.php",
+      "type": "file",
+      "name": "PayNotify.php",
+      "filePath": "application/api/controller/v1/PayNotify.php",
+      "summary": "支付回调控制器,接收支付宝/微信支付结果通知,更新订单状态",
+      "tags": [
+        "api",
+        "payment",
+        "callback",
+        "webhook"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/api/controller/v1/Index.php",
+      "type": "file",
+      "name": "Index.php",
+      "filePath": "application/api/controller/v1/Index.php",
+      "summary": "API首页控制器,处理游戏列表、游戏详情、推广页面等",
+      "tags": [
+        "api",
+        "game",
+        "index"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/service/PayService.php",
+      "type": "file",
+      "name": "PayService.php",
+      "filePath": "application/service/PayService.php",
+      "summary": "支付服务类,封装支付宝WAP支付、APP支付、扫码支付等支付方式的调用逻辑",
+      "tags": [
+        "service",
+        "payment",
+        "alipay",
+        "core"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/service/GamePayService.php",
+      "type": "file",
+      "name": "GamePayService.php",
+      "filePath": "application/service/GamePayService.php",
+      "summary": "游戏支付服务类,处理游戏充值订单创建、支付渠道选择、回调处理",
+      "tags": [
+        "service",
+        "payment",
+        "game"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/service/CommonService.php",
+      "type": "file",
+      "name": "CommonService.php",
+      "filePath": "application/service/CommonService.php",
+      "summary": "通用服务类,提供数据查询、缓存管理、配置获取等基础服务",
+      "tags": [
+        "service",
+        "common",
+        "utility"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Game.php",
+      "type": "file",
+      "name": "Game.php",
+      "filePath": "application/common/model/Game.php",
+      "summary": "游戏数据模型,定义游戏表结构和关联关系",
+      "tags": [
+        "model",
+        "game",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Members.php",
+      "type": "file",
+      "name": "Members.php",
+      "filePath": "application/common/model/Members.php",
+      "summary": "会员数据模型,定义用户表结构、登录状态、充值记录等关联",
+      "tags": [
+        "model",
+        "user",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Pay.php",
+      "type": "file",
+      "name": "Pay.php",
+      "filePath": "application/common/model/Pay.php",
+      "summary": "支付订单数据模型,定义订单表结构、支付状态、退款记录等",
+      "tags": [
+        "model",
+        "payment",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Channel.php",
+      "type": "file",
+      "name": "Channel.php",
+      "filePath": "application/common/model/Channel.php",
+      "summary": "渠道数据模型,定义渠道表结构、渠道配置、分成比例等",
+      "tags": [
+        "model",
+        "channel",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Admin.php",
+      "type": "file",
+      "name": "Admin.php",
+      "filePath": "application/common/model/Admin.php",
+      "summary": "管理员数据模型,定义后台用户表结构、角色权限关联",
+      "tags": [
+        "model",
+        "admin",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/controller/Base.php",
+      "type": "file",
+      "name": "Base.php",
+      "filePath": "application/common/controller/Base.php",
+      "summary": "基础控制器类,提供跨模块的公共方法如响应输出、日志记录、异常处理",
+      "tags": [
+        "controller",
+        "base-class",
+        "common"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/guildapi/controller",
+      "type": "file",
+      "name": "guildapi/controllers",
+      "filePath": "application/guildapi/controller",
+      "summary": "公会API控制器目录,处理公会后台的频道管理、数据统计、结算等接口",
+      "tags": [
+        "guild",
+        "api",
+        "controller"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/complex/controller",
+      "type": "file",
+      "name": "complex/controllers",
+      "filePath": "application/complex/controller",
+      "summary": "渠道SDK对接控制器目录,处理第三方渠道的登录、支付、数据同步等",
+      "tags": [
+        "complex",
+        "channel",
+        "sdk"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/home/controller",
+      "type": "file",
+      "name": "home/controllers",
+      "filePath": "application/home/controller",
+      "summary": "官网控制器目录,处理PC端官网的游戏展示、用户中心、客服系统等",
+      "tags": [
+        "home",
+        "website",
+        "frontend"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/mobile/controller",
+      "type": "file",
+      "name": "mobile/controllers",
+      "filePath": "application/mobile/controller",
+      "summary": "移动端控制器目录,处理H5端的游戏展示、用户中心、支付等",
+      "tags": [
+        "mobile",
+        "h5",
+        "frontend"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/crontab",
+      "type": "file",
+      "name": "crontab",
+      "filePath": "application/crontab",
+      "summary": "定时任务目录,包含渠道结算、数据统计、支付超时处理等定时任务",
+      "tags": [
+        "cron",
+        "scheduled",
+        "batch"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/command/KafkaConsumer.php",
+      "type": "file",
+      "name": "KafkaConsumer.php",
+      "filePath": "application/command/KafkaConsumer.php",
+      "summary": "Kafka消费者命令,异步处理消息队列中的任务",
+      "tags": [
+        "kafka",
+        "queue",
+        "async"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/alipay",
+      "type": "file",
+      "name": "alipay SDK",
+      "filePath": "extend/alipay",
+      "summary": "支付宝SDK扩展目录,包含支付宝支付接口的PHP实现",
+      "tags": [
+        "alipay",
+        "sdk",
+        "payment"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:extend/DouYinGameOpen",
+      "type": "file",
+      "name": "DouYinGameOpen",
+      "filePath": "extend/DouYinGameOpen",
+      "summary": "抖音游戏开放平台SDK,处理抖音渠道的登录和支付",
+      "tags": [
+        "douyin",
+        "sdk",
+        "channel"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/LdzfPay",
+      "type": "file",
+      "name": "LdzfPay",
+      "filePath": "extend/LdzfPay",
+      "summary": "联动支付SDK扩展,提供联动优势支付渠道的接入",
+      "tags": [
+        "ldzf",
+        "sdk",
+        "payment"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/Obs",
+      "type": "file",
+      "name": "ObsClient",
+      "filePath": "extend/Obs",
+      "summary": "华为云OBS客户端扩展,用于对象存储服务(文件上传下载)",
+      "tags": [
+        "huawei",
+        "obs",
+        "storage"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:thinkphp",
+      "type": "file",
+      "name": "ThinkPHP Framework",
+      "filePath": "thinkphp",
+      "summary": "ThinkPHP 5.0框架核心目录,包含路由、控制器、模型、视图等核心组件",
+      "tags": [
+        "framework",
+        "thinkphp",
+        "core"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:public/static",
+      "type": "file",
+      "name": "static assets",
+      "filePath": "public/static",
+      "summary": "静态资源目录,包含CSS、JavaScript、图片、字体等前端资源",
+      "tags": [
+        "static",
+        "frontend",
+        "assets"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/admin/view",
+      "type": "file",
+      "name": "admin views",
+      "filePath": "application/admin/view",
+      "summary": "后台视图模板目录,包含后台管理界面的HTML模板文件",
+      "tags": [
+        "admin",
+        "view",
+        "template"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:.env_dev",
+      "type": "config",
+      "name": ".env_dev",
+      "filePath": ".env_dev",
+      "summary": "开发环境配置文件,包含数据库连接、Redis配置、第三方服务密钥等",
+      "tags": [
+        "config",
+        "env",
+        "development"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:.env_pro",
+      "type": "config",
+      "name": ".env_pro",
+      "filePath": ".env_pro",
+      "summary": "生产环境配置文件,包含生产数据库、Redis、支付密钥等敏感配置",
+      "tags": [
+        "config",
+        "env",
+        "production"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:public/constants.php",
+      "type": "file",
+      "name": "constants.php",
+      "filePath": "public/constants.php",
+      "summary": "全局常量定义文件,定义域名、路径、状态码等应用常量",
+      "tags": [
+        "constants",
+        "config",
+        "global"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/common/library",
+      "type": "file",
+      "name": "common/library",
+      "filePath": "application/common/library",
+      "summary": "公共类库目录,包含WebSocket、短信发送、文件处理、数据导出等工具类",
+      "tags": [
+        "library",
+        "common",
+        "utility"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/logic",
+      "type": "file",
+      "name": "common/logic",
+      "filePath": "application/common/logic",
+      "summary": "公共业务逻辑目录,包含支付工厂、子包管理、数据同步等业务逻辑",
+      "tags": [
+        "logic",
+        "business",
+        "common"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/validate",
+      "type": "file",
+      "name": "common/validate",
+      "filePath": "application/common/validate",
+      "summary": "公共验证器目录,定义请求参数验证规则",
+      "tags": [
+        "validate",
+        "common",
+        "input"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/extra",
+      "type": "file",
+      "name": "extra configs",
+      "filePath": "application/extra",
+      "summary": "扩展配置目录,包含Kafka配置、队列配置、操作日志配置等",
+      "tags": [
+        "config",
+        "extra",
+        "kafka"
+      ],
+      "complexity": "simple"
+    }
+  ],
+  "edges": [
+    {
+      "source": "file:public/index.php",
+      "target": "config:application/config.php",
+      "type": "loads",
+      "weight": 0.8
+    },
+    {
+      "source": "file:public/index.php",
+      "target": "file:application/route.php",
+      "type": "loads",
+      "weight": 0.7
+    },
+    {
+      "source": "file:public/index.php",
+      "target": "file:thinkphp",
+      "type": "imports",
+      "weight": 0.9
+    },
+    {
+      "source": "file:application/admin/controller/Admin.php",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.9
+    },
+    {
+      "source": "file:application/admin/controller/Admin.php",
+      "target": "file:application/common/model/Admin.php",
+      "type": "uses",
+      "weight": 0.7
+    },
+    {
+      "source": "file:application/api/controller/v1/Pay.php",
+      "target": "file:application/service/PayService.php",
+      "type": "calls",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/api/controller/v1/Pay.php",
+      "target": "file:application/service/GamePayService.php",
+      "type": "calls",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/service/PayService.php",
+      "target": "file:extend/alipay",
+      "type": "imports",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/api/controller/v1/PayNotify.php",
+      "target": "file:application/service/PayService.php",
+      "type": "calls",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/api/controller/v1/PayNotify.php",
+      "target": "file:application/service/GamePayService.php",
+      "type": "calls",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Pay.php",
+      "type": "uses",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Game.php",
+      "type": "uses",
+      "weight": 0.7
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "uses",
+      "weight": 0.7
+    },
+    {
+      "source": "file:application/api/controller/v1/Login.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "uses",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/common/model/Game.php",
+      "target": "file:application/common/model/Channel.php",
+      "type": "related",
+      "weight": 0.6
+    },
+    {
+      "source": "file:application/common/model/Pay.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "related",
+      "weight": 0.7
+    },
+    {
+      "source": "file:application/crontab",
+      "target": "file:application/common/model/Channel.php",
+      "type": "uses",
+      "weight": 0.6
+    },
+    {
+      "source": "file:application/crontab",
+      "target": "file:application/common/model/Pay.php",
+      "type": "uses",
+      "weight": 0.6
+    },
+    {
+      "source": "file:application/command/KafkaConsumer.php",
+      "target": "file:application/extra",
+      "type": "configures",
+      "weight": 0.5
+    },
+    {
+      "source": "file:application/complex/controller",
+      "target": "file:extend/DouYinGameOpen",
+      "type": "imports",
+      "weight": 0.7
+    },
+    {
+      "source": "file:application/home/controller",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.8
+    },
+    {
+      "source": "file:application/mobile/controller",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.8
+    },
+    {
+      "source": "config:.env_dev",
+      "target": "config:application/config.php",
+      "type": "configures",
+      "weight": 0.9
+    },
+    {
+      "source": "config:.env_pro",
+      "target": "config:application/config.php",
+      "type": "configures",
+      "weight": 0.9
+    }
+  ],
+  "layers": [
+    {
+      "id": "layer:entry",
+      "name": "入口层",
+      "description": "应用入口和框架引导,包含index.php和ThinkPHP框架启动",
+      "nodeIds": [
+        "file:public/index.php",
+        "file:thinkphp",
+        "file:public/constants.php"
+      ]
+    },
+    {
+      "id": "layer:config",
+      "name": "配置层",
+      "description": "应用配置、环境变量、路由规则等配置文件",
+      "nodeIds": [
+        "config:application/config.php",
+        "config:.env_dev",
+        "config:.env_pro",
+        "file:application/route.php",
+        "file:application/tags.php",
+        "file:application/command.php",
+        "file:application/extra"
+      ]
+    },
+    {
+      "id": "layer:model",
+      "name": "数据模型层",
+      "description": "数据库表结构定义、ORM模型、数据关联关系",
+      "nodeIds": [
+        "file:application/common/model/Game.php",
+        "file:application/common/model/Members.php",
+        "file:application/common/model/Pay.php",
+        "file:application/common/model/Channel.php",
+        "file:application/common/model/Admin.php"
+      ]
+    },
+    {
+      "id": "layer:service",
+      "name": "服务层",
+      "description": "业务服务类,封装核心业务逻辑如支付、游戏、会员等",
+      "nodeIds": [
+        "file:application/service/PayService.php",
+        "file:application/service/GamePayService.php",
+        "file:application/service/CommonService.php"
+      ]
+    },
+    {
+      "id": "layer:controller",
+      "name": "控制器层",
+      "description": "HTTP请求处理,包含后台管理、API接口、官网、移动端等控制器",
+      "nodeIds": [
+        "file:application/admin/controller/Admin.php",
+        "file:application/api/controller/v1/Login.php",
+        "file:application/api/controller/v1/Pay.php",
+        "file:application/api/controller/v1/PayNotify.php",
+        "file:application/api/controller/v1/Index.php",
+        "file:application/common/controller/Base.php"
+      ]
+    },
+    {
+      "id": "layer:business",
+      "name": "业务模块层",
+      "description": "按业务领域划分的模块,包含公会API、渠道SDK、官网、移动端等",
+      "nodeIds": [
+        "file:application/guildapi/controller",
+        "file:application/complex/controller",
+        "file:application/home/controller",
+        "file:application/mobile/controller"
+      ]
+    },
+    {
+      "id": "layer:common",
+      "name": "公共层",
+      "description": "跨模块共享的工具函数、类库、业务逻辑、验证器",
+      "nodeIds": [
+        "file:application/common.php",
+        "file:application/admin/common.php",
+        "file:application/api/common.php",
+        "file:application/common/library",
+        "file:application/common/logic",
+        "file:application/common/validate"
+      ]
+    },
+    {
+      "id": "layer:extension",
+      "name": "扩展层",
+      "description": "第三方SDK扩展,包含支付宝、抖音、联动支付等支付渠道SDK",
+      "nodeIds": [
+        "file:extend/alipay",
+        "file:extend/DouYinGameOpen",
+        "file:extend/LdzfPay",
+        "file:extend/Obs"
+      ]
+    },
+    {
+      "id": "layer:task",
+      "name": "任务层",
+      "description": "定时任务和异步任务,包含渠道结算、数据统计、消息队列消费",
+      "nodeIds": [
+        "file:application/crontab",
+        "file:application/command/KafkaConsumer.php"
+      ]
+    },
+    {
+      "id": "layer:frontend",
+      "name": "前端层",
+      "description": "前端资源和视图模板,包含静态文件、后台模板、官网页面",
+      "nodeIds": [
+        "file:public/static",
+        "file:application/admin/view"
+      ]
+    },
+    {
+      "id": "layer:docs",
+      "name": "文档层",
+      "description": "项目文档和依赖配置",
+      "nodeIds": [
+        "document:README.md",
+        "config:composer.json"
+      ]
+    }
+  ],
+  "tour": [
+    {
+      "order": 1,
+      "title": "项目概览",
+      "description": "阅读README了解项目背景:这是一个基于ThinkPHP 5.0的游戏SDK平台,包含后台管理、API接口、公会系统、渠道SDK对接等模块",
+      "nodeIds": [
+        "document:README.md"
+      ]
+    },
+    {
+      "order": 2,
+      "title": "应用入口",
+      "description": "从public/index.php开始,了解应用如何启动:设置CORS头、加载常量配置、引入ThinkPHP框架",
+      "nodeIds": [
+        "file:public/index.php"
+      ]
+    },
+    {
+      "order": 3,
+      "title": "路由配置",
+      "description": "查看route.php理解域名路由映射:不同子域名(admin/api/www/m等)对应不同模块,实现多站点共用一套代码",
+      "nodeIds": [
+        "file:application/route.php"
+      ]
+    },
+    {
+      "order": 4,
+      "title": "配置系统",
+      "description": "了解配置层:.env文件存储环境变量,config.php读取环境配置,extra目录存放扩展配置",
+      "nodeIds": [
+        "config:.env_dev",
+        "config:application/config.php",
+        "file:application/extra"
+      ]
+    },
+    {
+      "order": 5,
+      "title": "数据模型",
+      "description": "浏览common/model目录,了解核心数据表:Game(游戏)、Members(会员)、Pay(订单)、Channel(渠道)",
+      "nodeIds": [
+        "file:application/common/model/Game.php",
+        "file:application/common/model/Members.php",
+        "file:application/common/model/Pay.php"
+      ]
+    },
+    {
+      "order": 6,
+      "title": "支付流程",
+      "description": "追踪支付核心链路:Pay控制器 → PayService → 支付宝SDK,理解订单创建和回调处理",
+      "nodeIds": [
+        "file:application/api/controller/v1/Pay.php",
+        "file:application/service/PayService.php",
+        "file:extend/alipay"
+      ]
+    },
+    {
+      "order": 7,
+      "title": "后台管理",
+      "description": "了解后台管理系统:Admin基类控制器实现权限检查,各业务控制器继承实现具体功能",
+      "nodeIds": [
+        "file:application/admin/controller/Admin.php",
+        "file:application/common/controller/Base.php"
+      ]
+    },
+    {
+      "order": 8,
+      "title": "业务模块",
+      "description": "了解多模块架构:api(游戏API)、guildapi(公会API)、complex(渠道SDK)、home(官网)、mobile(移动端)",
+      "nodeIds": [
+        "file:application/guildapi/controller",
+        "file:application/complex/controller",
+        "file:application/home/controller"
+      ]
+    },
+    {
+      "order": 9,
+      "title": "定时任务",
+      "description": "查看crontab目录,了解定时任务:渠道结算、数据统计、支付超时处理等后台批处理",
+      "nodeIds": [
+        "file:application/crontab"
+      ]
+    },
+    {
+      "order": 10,
+      "title": "公共工具",
+      "description": "浏览common.php和common/library,了解项目复用的工具函数和类库",
+      "nodeIds": [
+        "file:application/common.php",
+        "file:application/common/library"
+      ]
+    }
+  ]
+}

File diff suppressed because it is too large
+ 23611 - 0
www/new_sdk/.understand-anything/.trash-20260630091809/batches.json


+ 31 - 0
www/new_sdk/.understand-anything/.trash-20260630091809/fingerprint-input.json

@@ -0,0 +1,31 @@
+{
+  "gitCommitHash": "7d315b54fbf4a43df94cdcfca70d7507c65d9628",
+  "projectRoot": "D:\\web\\composer\\new_sdk\\new_sdk\\www\\new_sdk",
+  "sourceFilePaths": [
+    "public/index.php",
+    "application/config.php",
+    "application/route.php",
+    "application/common.php",
+    "application/tags.php",
+    "application/command.php",
+    "application/admin/common.php",
+    "application/admin/controller/Admin.php",
+    "application/api/common.php",
+    "application/api/controller/v1/Login.php",
+    "application/api/controller/v1/Pay.php",
+    "application/api/controller/v1/PayNotify.php",
+    "application/api/controller/v1/Index.php",
+    "application/service/PayService.php",
+    "application/service/GamePayService.php",
+    "application/service/CommonService.php",
+    "application/common/model/Game.php",
+    "application/common/model/Members.php",
+    "application/common/model/Pay.php",
+    "application/common/model/Channel.php",
+    "application/common/model/Admin.php",
+    "application/common/controller/Base.php",
+    "application/command/KafkaConsumer.php",
+    "composer.json",
+    "README.md"
+  ]
+}

+ 44 - 0
www/new_sdk/.understand-anything/.trash-20260630091809/review.json

@@ -0,0 +1,44 @@
+{
+  "issues": [
+    "Edge[24] target 'file:application/common/model/Setting.php' not found"
+  ],
+  "warnings": [
+    "Node 'document:README.md' has no edges (orphan)",
+    "Node 'config:composer.json' has no edges (orphan)",
+    "Node 'file:application/tags.php' has no edges (orphan)",
+    "Node 'file:application/command.php' has no edges (orphan)",
+    "Node 'file:application/admin/common.php' has no edges (orphan)",
+    "Node 'file:application/api/common.php' has no edges (orphan)",
+    "Node 'file:application/api/controller/v1/Index.php' has no edges (orphan)",
+    "Node 'file:application/service/CommonService.php' has no edges (orphan)",
+    "Node 'file:application/guildapi/controller' has no edges (orphan)",
+    "Node 'file:extend/LdzfPay' has no edges (orphan)",
+    "Node 'file:extend/Obs' has no edges (orphan)",
+    "Node 'file:public/static' has no edges (orphan)",
+    "Node 'file:application/admin/view' has no edges (orphan)",
+    "Node 'file:public/constants.php' has no edges (orphan)",
+    "Node 'file:application/common/library' has no edges (orphan)",
+    "Node 'file:application/common/logic' has no edges (orphan)",
+    "Node 'file:application/common/validate' has no edges (orphan)"
+  ],
+  "stats": {
+    "totalNodes": 44,
+    "totalEdges": 25,
+    "totalLayers": 11,
+    "tourSteps": 10,
+    "nodeTypes": {
+      "document": 1,
+      "config": 4,
+      "file": 39
+    },
+    "edgeTypes": {
+      "loads": 2,
+      "imports": 3,
+      "inherits": 3,
+      "uses": 8,
+      "calls": 4,
+      "related": 2,
+      "configures": 3
+    }
+  }
+}

+ 63 - 0
www/new_sdk/.understand-anything/.trash-20260630091809/ua-inline-validate.cjs

@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const graphPath = process.argv[2];
+const outputPath = process.argv[3];
+try {
+  const graph = JSON.parse(fs.readFileSync(graphPath, 'utf8'));
+  const issues = [], warnings = [];
+  if (!Array.isArray(graph.nodes)) { issues.push('graph.nodes is missing or not an array'); graph.nodes = []; }
+  if (!Array.isArray(graph.edges)) { issues.push('graph.edges is missing or not an array'); graph.edges = []; }
+  const nodeIds = new Set();
+  const seen = new Map();
+  graph.nodes.forEach((n, i) => {
+    if (!n.id) { issues.push(`Node[${i}] missing id`); return; }
+    if (!n.type) issues.push(`Node[${i}] '${n.id}' missing type`);
+    if (!n.name) issues.push(`Node[${i}] '${n.id}' missing name`);
+    if (!n.summary) issues.push(`Node[${i}] '${n.id}' missing summary`);
+    if (!n.tags || !n.tags.length) issues.push(`Node[${i}] '${n.id}' missing tags`);
+    if (seen.has(n.id)) issues.push(`Duplicate node ID '${n.id}' at indices ${seen.get(n.id)} and ${i}`);
+    else seen.set(n.id, i);
+    nodeIds.add(n.id);
+  });
+  graph.edges.forEach((e, i) => {
+    if (!nodeIds.has(e.source)) issues.push(`Edge[${i}] source '${e.source}' not found`);
+    if (!nodeIds.has(e.target)) issues.push(`Edge[${i}] target '${e.target}' not found`);
+  });
+  const fileLevelTypes = new Set(['file', 'config', 'document', 'service', 'pipeline', 'table', 'schema', 'resource', 'endpoint']);
+  const fileNodes = graph.nodes.filter(n => fileLevelTypes.has(n.type)).map(n => n.id);
+  const assigned = new Map();
+  if (!Array.isArray(graph.layers)) { if (graph.layers) warnings.push('graph.layers is not an array'); graph.layers = []; }
+  if (!Array.isArray(graph.tour)) { if (graph.tour) warnings.push('graph.tour is not an array'); graph.tour = []; }
+  graph.layers.forEach(layer => {
+    (layer.nodeIds || []).forEach(id => {
+      if (!nodeIds.has(id)) issues.push(`Layer '${layer.id}' refs missing node '${id}'`);
+      if (assigned.has(id)) issues.push(`Node '${id}' appears in multiple layers`);
+      assigned.set(id, layer.id);
+    });
+  });
+  fileNodes.forEach(id => {
+    if (!assigned.has(id)) issues.push(`File node '${id}' not in any layer`);
+  });
+  graph.tour.forEach((step, i) => {
+    (step.nodeIds || []).forEach(id => {
+      if (!nodeIds.has(id)) issues.push(`Tour step[${i}] refs missing node '${id}'`);
+    });
+  });
+  const withEdges = new Set([
+    ...graph.edges.map(e => e.source),
+    ...graph.edges.map(e => e.target)
+  ]);
+  graph.nodes.forEach(n => {
+    if (!withEdges.has(n.id)) warnings.push(`Node '${n.id}' has no edges (orphan)`);
+  });
+  const stats = {
+    totalNodes: graph.nodes.length,
+    totalEdges: graph.edges.length,
+    totalLayers: graph.layers.length,
+    tourSteps: graph.tour.length,
+    nodeTypes: graph.nodes.reduce((a, n) => { a[n.type] = (a[n.type]||0)+1; return a; }, {}),
+    edgeTypes: graph.edges.reduce((a, e) => { a[e.type] = (a[e.type]||0)+1; return a; }, {})
+  };
+  fs.writeFileSync(outputPath, JSON.stringify({ issues, warnings, stats }, null, 2));
+  process.exit(0);
+} catch (err) { process.stderr.write(err.message + '\n'); process.exit(1); }

+ 980 - 0
www/new_sdk/.understand-anything/.trash-20260630093035/assembled-graph.json

@@ -0,0 +1,980 @@
+{
+  "nodes": [
+    {
+      "id": "document:README.md",
+      "type": "document",
+      "name": "README.md",
+      "filePath": "README.md",
+      "summary": "项目说明文档,描述游戏SDK平台的域名配置、环境要求和相关服务",
+      "tags": [
+        "documentation",
+        "project-overview"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:composer.json",
+      "type": "config",
+      "name": "composer.json",
+      "filePath": "composer.json",
+      "summary": "PHP依赖管理配置文件,定义ThinkPHP 5.0框架及支付、队列等依赖包",
+      "tags": [
+        "php",
+        "dependencies",
+        "composer"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:public/index.php",
+      "type": "file",
+      "name": "index.php",
+      "filePath": "public/index.php",
+      "summary": "应用入口文件,设置CORS头、定义应用路径、加载常量配置和框架引导",
+      "tags": [
+        "entry-point",
+        "php",
+        "bootstrap"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:application/config.php",
+      "type": "config",
+      "name": "config.php",
+      "filePath": "application/config.php",
+      "summary": "应用主配置文件,定义模块设置、URL路由、数据库缓存、模板引擎等核心配置",
+      "tags": [
+        "config",
+        "thinkphp",
+        "application"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/route.php",
+      "type": "file",
+      "name": "route.php",
+      "filePath": "application/route.php",
+      "summary": "路由配置文件,定义域名到模块的映射(admin/api/guildapi/complex/home/mobile),配置PC站和M站路由规则",
+      "tags": [
+        "routing",
+        "thinkphp",
+        "domain-mapping"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/common.php",
+      "type": "file",
+      "name": "common.php",
+      "filePath": "application/common.php",
+      "summary": "公共函数库,包含随机数生成、数组排序、加密解密、HTTP请求、日期处理等通用工具函数",
+      "tags": [
+        "utility",
+        "common",
+        "helper"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/tags.php",
+      "type": "file",
+      "name": "tags.php",
+      "filePath": "application/tags.php",
+      "summary": "行为扩展定义文件,配置应用初始化、路由开始、模块初始化等钩子",
+      "tags": [
+        "hooks",
+        "lifecycle",
+        "thinkphp"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/command.php",
+      "type": "file",
+      "name": "command.php",
+      "filePath": "application/command.php",
+      "summary": "命令行配置文件,注册自定义命令如Kafka消费者",
+      "tags": [
+        "cli",
+        "commands",
+        "kafka"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/admin/common.php",
+      "type": "file",
+      "name": "admin/common.php",
+      "filePath": "application/admin/common.php",
+      "summary": "后台公共函数库,包含权限检查、日志记录、数据格式化等后台专用工具函数",
+      "tags": [
+        "admin",
+        "utility",
+        "helper"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/admin/controller/Admin.php",
+      "type": "file",
+      "name": "Admin.php",
+      "filePath": "application/admin/controller/Admin.php",
+      "summary": "后台公共控制器基类,实现登录验证、权限检查、WebSocket令牌管理",
+      "tags": [
+        "admin",
+        "controller",
+        "base-class",
+        "auth"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/common.php",
+      "type": "file",
+      "name": "api/common.php",
+      "filePath": "application/api/common.php",
+      "summary": "API模块公共函数库,包含API签名验证、参数加密解密、渠道鉴权等",
+      "tags": [
+        "api",
+        "utility",
+        "auth"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/controller/v1/Login.php",
+      "type": "file",
+      "name": "Login.php",
+      "filePath": "application/api/controller/v1/Login.php",
+      "summary": "用户登录控制器,处理账号密码登录、手机验证码登录、第三方登录等",
+      "tags": [
+        "api",
+        "login",
+        "authentication"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/controller/v1/Pay.php",
+      "type": "file",
+      "name": "Pay.php",
+      "filePath": "application/api/controller/v1/Pay.php",
+      "summary": "支付控制器,处理游戏内充值、创建订单、调用支付渠道(支付宝/微信)",
+      "tags": [
+        "api",
+        "payment",
+        "alipay",
+        "wechat"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/api/controller/v1/PayNotify.php",
+      "type": "file",
+      "name": "PayNotify.php",
+      "filePath": "application/api/controller/v1/PayNotify.php",
+      "summary": "支付回调控制器,接收支付宝/微信支付结果通知,更新订单状态",
+      "tags": [
+        "api",
+        "payment",
+        "callback",
+        "webhook"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/api/controller/v1/Index.php",
+      "type": "file",
+      "name": "Index.php",
+      "filePath": "application/api/controller/v1/Index.php",
+      "summary": "API首页控制器,处理游戏列表、游戏详情、推广页面等",
+      "tags": [
+        "api",
+        "game",
+        "index"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/service/PayService.php",
+      "type": "file",
+      "name": "PayService.php",
+      "filePath": "application/service/PayService.php",
+      "summary": "支付服务类,封装支付宝WAP支付、APP支付、扫码支付等支付方式的调用逻辑",
+      "tags": [
+        "service",
+        "payment",
+        "alipay",
+        "core"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/service/GamePayService.php",
+      "type": "file",
+      "name": "GamePayService.php",
+      "filePath": "application/service/GamePayService.php",
+      "summary": "游戏支付服务类,处理游戏充值订单创建、支付渠道选择、回调处理",
+      "tags": [
+        "service",
+        "payment",
+        "game"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/service/CommonService.php",
+      "type": "file",
+      "name": "CommonService.php",
+      "filePath": "application/service/CommonService.php",
+      "summary": "通用服务类,提供数据查询、缓存管理、配置获取等基础服务",
+      "tags": [
+        "service",
+        "common",
+        "utility"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Game.php",
+      "type": "file",
+      "name": "Game.php",
+      "filePath": "application/common/model/Game.php",
+      "summary": "游戏数据模型,定义游戏表结构和关联关系",
+      "tags": [
+        "model",
+        "game",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Members.php",
+      "type": "file",
+      "name": "Members.php",
+      "filePath": "application/common/model/Members.php",
+      "summary": "会员数据模型,定义用户表结构、登录状态、充值记录等关联",
+      "tags": [
+        "model",
+        "user",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Pay.php",
+      "type": "file",
+      "name": "Pay.php",
+      "filePath": "application/common/model/Pay.php",
+      "summary": "支付订单数据模型,定义订单表结构、支付状态、退款记录等",
+      "tags": [
+        "model",
+        "payment",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Channel.php",
+      "type": "file",
+      "name": "Channel.php",
+      "filePath": "application/common/model/Channel.php",
+      "summary": "渠道数据模型,定义渠道表结构、渠道配置、分成比例等",
+      "tags": [
+        "model",
+        "channel",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Admin.php",
+      "type": "file",
+      "name": "Admin.php",
+      "filePath": "application/common/model/Admin.php",
+      "summary": "管理员数据模型,定义后台用户表结构、角色权限关联",
+      "tags": [
+        "model",
+        "admin",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/controller/Base.php",
+      "type": "file",
+      "name": "Base.php",
+      "filePath": "application/common/controller/Base.php",
+      "summary": "基础控制器类,提供跨模块的公共方法如响应输出、日志记录、异常处理",
+      "tags": [
+        "controller",
+        "base-class",
+        "common"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/guildapi/controller",
+      "type": "file",
+      "name": "guildapi/controllers",
+      "filePath": "application/guildapi/controller",
+      "summary": "公会API控制器目录,处理公会后台的频道管理、数据统计、结算等接口",
+      "tags": [
+        "guild",
+        "api",
+        "controller"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/complex/controller",
+      "type": "file",
+      "name": "complex/controllers",
+      "filePath": "application/complex/controller",
+      "summary": "渠道SDK对接控制器目录,处理第三方渠道的登录、支付、数据同步等",
+      "tags": [
+        "complex",
+        "channel",
+        "sdk"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/home/controller",
+      "type": "file",
+      "name": "home/controllers",
+      "filePath": "application/home/controller",
+      "summary": "官网控制器目录,处理PC端官网的游戏展示、用户中心、客服系统等",
+      "tags": [
+        "home",
+        "website",
+        "frontend"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/mobile/controller",
+      "type": "file",
+      "name": "mobile/controllers",
+      "filePath": "application/mobile/controller",
+      "summary": "移动端控制器目录,处理H5端的游戏展示、用户中心、支付等",
+      "tags": [
+        "mobile",
+        "h5",
+        "frontend"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/crontab",
+      "type": "file",
+      "name": "crontab",
+      "filePath": "application/crontab",
+      "summary": "定时任务目录,包含渠道结算、数据统计、支付超时处理等定时任务",
+      "tags": [
+        "cron",
+        "scheduled",
+        "batch"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/command/KafkaConsumer.php",
+      "type": "file",
+      "name": "KafkaConsumer.php",
+      "filePath": "application/command/KafkaConsumer.php",
+      "summary": "Kafka消费者命令,异步处理消息队列中的任务",
+      "tags": [
+        "kafka",
+        "queue",
+        "async"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/alipay",
+      "type": "file",
+      "name": "alipay SDK",
+      "filePath": "extend/alipay",
+      "summary": "支付宝SDK扩展目录,包含支付宝支付接口的PHP实现",
+      "tags": [
+        "alipay",
+        "sdk",
+        "payment"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:extend/DouYinGameOpen",
+      "type": "file",
+      "name": "DouYinGameOpen",
+      "filePath": "extend/DouYinGameOpen",
+      "summary": "抖音游戏开放平台SDK,处理抖音渠道的登录和支付",
+      "tags": [
+        "douyin",
+        "sdk",
+        "channel"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/LdzfPay",
+      "type": "file",
+      "name": "LdzfPay",
+      "filePath": "extend/LdzfPay",
+      "summary": "联动支付SDK扩展,提供联动优势支付渠道的接入",
+      "tags": [
+        "ldzf",
+        "sdk",
+        "payment"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/Obs",
+      "type": "file",
+      "name": "ObsClient",
+      "filePath": "extend/Obs",
+      "summary": "华为云OBS客户端扩展,用于对象存储服务(文件上传下载)",
+      "tags": [
+        "huawei",
+        "obs",
+        "storage"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:thinkphp",
+      "type": "file",
+      "name": "ThinkPHP Framework",
+      "filePath": "thinkphp",
+      "summary": "ThinkPHP 5.0框架核心目录,包含路由、控制器、模型、视图等核心组件",
+      "tags": [
+        "framework",
+        "thinkphp",
+        "core"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:public/static",
+      "type": "file",
+      "name": "static assets",
+      "filePath": "public/static",
+      "summary": "静态资源目录,包含CSS、JavaScript、图片、字体等前端资源",
+      "tags": [
+        "static",
+        "frontend",
+        "assets"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/admin/view",
+      "type": "file",
+      "name": "admin views",
+      "filePath": "application/admin/view",
+      "summary": "后台视图模板目录,包含后台管理界面的HTML模板文件",
+      "tags": [
+        "admin",
+        "view",
+        "template"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:.env_dev",
+      "type": "config",
+      "name": ".env_dev",
+      "filePath": ".env_dev",
+      "summary": "开发环境配置文件,包含数据库连接、Redis配置、第三方服务密钥等",
+      "tags": [
+        "config",
+        "env",
+        "development"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:.env_pro",
+      "type": "config",
+      "name": ".env_pro",
+      "filePath": ".env_pro",
+      "summary": "生产环境配置文件,包含生产数据库、Redis、支付密钥等敏感配置",
+      "tags": [
+        "config",
+        "env",
+        "production"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:public/constants.php",
+      "type": "file",
+      "name": "constants.php",
+      "filePath": "public/constants.php",
+      "summary": "全局常量定义文件,定义域名、路径、状态码等应用常量",
+      "tags": [
+        "constants",
+        "config",
+        "global"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/common/library",
+      "type": "file",
+      "name": "common/library",
+      "filePath": "application/common/library",
+      "summary": "公共类库目录,包含WebSocket、短信发送、文件处理、数据导出等工具类",
+      "tags": [
+        "library",
+        "common",
+        "utility"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/logic",
+      "type": "file",
+      "name": "common/logic",
+      "filePath": "application/common/logic",
+      "summary": "公共业务逻辑目录,包含支付工厂、子包管理、数据同步等业务逻辑",
+      "tags": [
+        "logic",
+        "business",
+        "common"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/validate",
+      "type": "file",
+      "name": "common/validate",
+      "filePath": "application/common/validate",
+      "summary": "公共验证器目录,定义请求参数验证规则",
+      "tags": [
+        "validate",
+        "common",
+        "input"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/extra",
+      "type": "file",
+      "name": "extra configs",
+      "filePath": "application/extra",
+      "summary": "扩展配置目录,包含Kafka配置、队列配置、操作日志配置等",
+      "tags": [
+        "config",
+        "extra",
+        "kafka"
+      ],
+      "complexity": "simple"
+    }
+  ],
+  "project": {
+    "name": "new_sdk",
+    "analyzedAt": "2026-06-30T09:17:43Z",
+    "languages": [
+      "php",
+      "html",
+      "javascript",
+      "css"
+    ],
+    "frameworks": [
+      "ThinkPHP"
+    ],
+    "gitCommitHash": "7d315b54fbf4a43df94cdcfca70d7507c65d9628",
+    "description": "游戏SDK平台 - 基于ThinkPHP 5.0的游戏渠道SDK对接、支付回调、公会管理后台"
+  },
+  "tour": [
+    {
+      "order": 1,
+      "title": "项目概览",
+      "description": "阅读README了解项目背景:这是一个基于ThinkPHP 5.0的游戏SDK平台,包含后台管理、API接口、公会系统、渠道SDK对接等模块",
+      "nodeIds": [
+        "document:README.md"
+      ]
+    },
+    {
+      "order": 2,
+      "title": "应用入口",
+      "description": "从public/index.php开始,了解应用如何启动:设置CORS头、加载常量配置、引入ThinkPHP框架",
+      "nodeIds": [
+        "file:public/index.php"
+      ]
+    },
+    {
+      "order": 3,
+      "title": "路由配置",
+      "description": "查看route.php理解域名路由映射:不同子域名(admin/api/www/m等)对应不同模块,实现多站点共用一套代码",
+      "nodeIds": [
+        "file:application/route.php"
+      ]
+    },
+    {
+      "order": 4,
+      "title": "配置系统",
+      "description": "了解配置层:.env文件存储环境变量,config.php读取环境配置,extra目录存放扩展配置",
+      "nodeIds": [
+        "config:.env_dev",
+        "config:application/config.php",
+        "file:application/extra"
+      ]
+    },
+    {
+      "order": 5,
+      "title": "数据模型",
+      "description": "浏览common/model目录,了解核心数据表:Game(游戏)、Members(会员)、Pay(订单)、Channel(渠道)",
+      "nodeIds": [
+        "file:application/common/model/Game.php",
+        "file:application/common/model/Members.php",
+        "file:application/common/model/Pay.php"
+      ]
+    },
+    {
+      "order": 6,
+      "title": "支付流程",
+      "description": "追踪支付核心链路:Pay控制器 → PayService → 支付宝SDK,理解订单创建和回调处理",
+      "nodeIds": [
+        "file:application/api/controller/v1/Pay.php",
+        "file:application/service/PayService.php",
+        "file:extend/alipay"
+      ]
+    },
+    {
+      "order": 7,
+      "title": "后台管理",
+      "description": "了解后台管理系统:Admin基类控制器实现权限检查,各业务控制器继承实现具体功能",
+      "nodeIds": [
+        "file:application/admin/controller/Admin.php",
+        "file:application/common/controller/Base.php"
+      ]
+    },
+    {
+      "order": 8,
+      "title": "业务模块",
+      "description": "了解多模块架构:api(游戏API)、guildapi(公会API)、complex(渠道SDK)、home(官网)、mobile(移动端)",
+      "nodeIds": [
+        "file:application/guildapi/controller",
+        "file:application/complex/controller",
+        "file:application/home/controller"
+      ]
+    },
+    {
+      "order": 9,
+      "title": "定时任务",
+      "description": "查看crontab目录,了解定时任务:渠道结算、数据统计、支付超时处理等后台批处理",
+      "nodeIds": [
+        "file:application/crontab"
+      ]
+    },
+    {
+      "order": 10,
+      "title": "公共工具",
+      "description": "浏览common.php和common/library,了解项目复用的工具函数和类库",
+      "nodeIds": [
+        "file:application/common.php",
+        "file:application/common/library"
+      ]
+    }
+  ],
+  "layers": [
+    {
+      "id": "layer:entry",
+      "name": "入口层",
+      "description": "应用入口和框架引导,包含index.php和ThinkPHP框架启动",
+      "nodeIds": [
+        "file:public/index.php",
+        "file:thinkphp",
+        "file:public/constants.php"
+      ]
+    },
+    {
+      "id": "layer:config",
+      "name": "配置层",
+      "description": "应用配置、环境变量、路由规则等配置文件",
+      "nodeIds": [
+        "config:application/config.php",
+        "config:.env_dev",
+        "config:.env_pro",
+        "file:application/route.php",
+        "file:application/tags.php",
+        "file:application/command.php",
+        "file:application/extra"
+      ]
+    },
+    {
+      "id": "layer:model",
+      "name": "数据模型层",
+      "description": "数据库表结构定义、ORM模型、数据关联关系",
+      "nodeIds": [
+        "file:application/common/model/Game.php",
+        "file:application/common/model/Members.php",
+        "file:application/common/model/Pay.php",
+        "file:application/common/model/Channel.php",
+        "file:application/common/model/Admin.php"
+      ]
+    },
+    {
+      "id": "layer:service",
+      "name": "服务层",
+      "description": "业务服务类,封装核心业务逻辑如支付、游戏、会员等",
+      "nodeIds": [
+        "file:application/service/PayService.php",
+        "file:application/service/GamePayService.php",
+        "file:application/service/CommonService.php"
+      ]
+    },
+    {
+      "id": "layer:controller",
+      "name": "控制器层",
+      "description": "HTTP请求处理,包含后台管理、API接口、官网、移动端等控制器",
+      "nodeIds": [
+        "file:application/admin/controller/Admin.php",
+        "file:application/api/controller/v1/Login.php",
+        "file:application/api/controller/v1/Pay.php",
+        "file:application/api/controller/v1/PayNotify.php",
+        "file:application/api/controller/v1/Index.php",
+        "file:application/common/controller/Base.php"
+      ]
+    },
+    {
+      "id": "layer:business",
+      "name": "业务模块层",
+      "description": "按业务领域划分的模块,包含公会API、渠道SDK、官网、移动端等",
+      "nodeIds": [
+        "file:application/guildapi/controller",
+        "file:application/complex/controller",
+        "file:application/home/controller",
+        "file:application/mobile/controller"
+      ]
+    },
+    {
+      "id": "layer:common",
+      "name": "公共层",
+      "description": "跨模块共享的工具函数、类库、业务逻辑、验证器",
+      "nodeIds": [
+        "file:application/common.php",
+        "file:application/admin/common.php",
+        "file:application/api/common.php",
+        "file:application/common/library",
+        "file:application/common/logic",
+        "file:application/common/validate"
+      ]
+    },
+    {
+      "id": "layer:extension",
+      "name": "扩展层",
+      "description": "第三方SDK扩展,包含支付宝、抖音、联动支付等支付渠道SDK",
+      "nodeIds": [
+        "file:extend/alipay",
+        "file:extend/DouYinGameOpen",
+        "file:extend/LdzfPay",
+        "file:extend/Obs"
+      ]
+    },
+    {
+      "id": "layer:task",
+      "name": "任务层",
+      "description": "定时任务和异步任务,包含渠道结算、数据统计、消息队列消费",
+      "nodeIds": [
+        "file:application/crontab",
+        "file:application/command/KafkaConsumer.php"
+      ]
+    },
+    {
+      "id": "layer:frontend",
+      "name": "前端层",
+      "description": "前端资源和视图模板,包含静态文件、后台模板、官网页面",
+      "nodeIds": [
+        "file:public/static",
+        "file:application/admin/view"
+      ]
+    },
+    {
+      "id": "layer:docs",
+      "name": "文档层",
+      "description": "项目文档和依赖配置",
+      "nodeIds": [
+        "document:README.md",
+        "config:composer.json"
+      ]
+    }
+  ],
+  "edges": [
+    {
+      "source": "file:public/index.php",
+      "target": "config:application/config.php",
+      "type": "reads_from",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:public/index.php",
+      "target": "file:application/route.php",
+      "type": "reads_from",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:public/index.php",
+      "target": "file:thinkphp",
+      "type": "imports",
+      "weight": 0.9,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/admin/controller/Admin.php",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.9,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/admin/controller/Admin.php",
+      "target": "file:application/common/model/Admin.php",
+      "type": "calls",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/Pay.php",
+      "target": "file:application/service/PayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/Pay.php",
+      "target": "file:application/service/GamePayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/PayService.php",
+      "target": "file:extend/alipay",
+      "type": "imports",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/PayNotify.php",
+      "target": "file:application/service/PayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/PayNotify.php",
+      "target": "file:application/service/GamePayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Pay.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Game.php",
+      "type": "calls",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "calls",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/Login.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/common/model/Game.php",
+      "target": "file:application/common/model/Channel.php",
+      "type": "related",
+      "weight": 0.6,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/common/model/Pay.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "related",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/crontab",
+      "target": "file:application/common/model/Channel.php",
+      "type": "calls",
+      "weight": 0.6,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/crontab",
+      "target": "file:application/common/model/Pay.php",
+      "type": "calls",
+      "weight": 0.6,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/command/KafkaConsumer.php",
+      "target": "file:application/extra",
+      "type": "configures",
+      "weight": 0.5,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/complex/controller",
+      "target": "file:extend/DouYinGameOpen",
+      "type": "imports",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/home/controller",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/mobile/controller",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "config:.env_dev",
+      "target": "config:application/config.php",
+      "type": "configures",
+      "weight": 0.9,
+      "direction": "forward"
+    },
+    {
+      "source": "config:.env_pro",
+      "target": "config:application/config.php",
+      "type": "configures",
+      "weight": 0.9,
+      "direction": "forward"
+    }
+  ],
+  "version": "1.0.0"
+}

+ 45 - 0
www/new_sdk/.understand-anything/.trash-20260630093035/review.json

@@ -0,0 +1,45 @@
+{
+  "approved": true,
+  "scriptCompleted": true,
+  "issues": [],
+  "warnings": [
+    "Orphan node: \"document:README.md\" has no edges",
+    "Orphan node: \"config:composer.json\" has no edges",
+    "Orphan node: \"file:application/common.php\" has no edges",
+    "Orphan node: \"file:application/tags.php\" has no edges",
+    "Orphan node: \"file:application/command.php\" has no edges",
+    "Orphan node: \"file:application/admin/common.php\" has no edges",
+    "Orphan node: \"file:application/api/common.php\" has no edges",
+    "Orphan node: \"file:application/api/controller/v1/Index.php\" has no edges",
+    "Orphan node: \"file:application/service/CommonService.php\" has no edges",
+    "Orphan node: \"file:application/guildapi/controller\" has no edges",
+    "Orphan node: \"file:extend/LdzfPay\" has no edges",
+    "Orphan node: \"file:extend/Obs\" has no edges",
+    "Orphan node: \"file:public/static\" has no edges",
+    "Orphan node: \"file:application/admin/view\" has no edges",
+    "Orphan node: \"file:public/constants.php\" has no edges",
+    "Orphan node: \"file:application/common/library\" has no edges",
+    "Orphan node: \"file:application/common/logic\" has no edges",
+    "Orphan node: \"file:application/common/validate\" has no edges",
+    "Document node \"document:README.md\" has no \"documents\" edge"
+  ],
+  "stats": {
+    "totalNodes": 44,
+    "totalEdges": 24,
+    "totalLayers": 11,
+    "tourSteps": 10,
+    "nodeTypes": {
+      "document": 1,
+      "config": 4,
+      "file": 39
+    },
+    "edgeTypes": {
+      "reads_from": 2,
+      "imports": 3,
+      "inherits": 3,
+      "calls": 11,
+      "related": 2,
+      "configures": 3
+    }
+  }
+}

File diff suppressed because it is too large
+ 10017 - 0
www/new_sdk/.understand-anything/.trash-20260630093035/tmp/scan-files.json


+ 242 - 0
www/new_sdk/.understand-anything/.trash-20260630093035/tmp/ua-graph-validate.js

@@ -0,0 +1,242 @@
+const fs = require('fs');
+const path = require('path');
+
+const VALID_NODE_TYPES = new Set([
+  'file','function','class','module','concept','config','document',
+  'service','table','endpoint','pipeline','schema','resource','domain','flow','step'
+]);
+
+const VALID_EDGE_TYPES = new Set([
+  'imports','exports','contains','inherits','implements','calls','subscribes',
+  'publishes','middleware','reads_from','writes_to','transforms','validates',
+  'depends_on','tested_by','configures','related','similar_to','deploys','serves',
+  'migrates','documents','provisions','routes','defines_schema','triggers',
+  'contains_flow','flow_step','cross_domain'
+]);
+
+const VALID_COMPLEXITY = new Set(['simple','moderate','complex']);
+const VALID_DIRECTION = new Set(['forward','backward','bidirectional']);
+
+const FILE_LEVEL_TYPES = new Set(['file','config','document','service','pipeline','table','schema','resource','endpoint']);
+
+const graphPath = process.argv[2];
+const outPath = process.argv[3];
+
+const issues = [];
+const warnings = [];
+
+function addIssue(msg) { issues.push(msg); }
+function addWarning(msg) { warnings.push(msg); }
+
+try {
+  const raw = fs.readFileSync(graphPath, 'utf8');
+  const graph = JSON.parse(raw);
+
+  const nodes = graph.nodes || [];
+  const edges = graph.edges || [];
+  const layers = graph.layers || [];
+  const tour = graph.tour || [];
+
+  // Build lookup sets
+  const nodeIds = new Set(nodes.map(n => n.id));
+  const nodeIdsInLayers = new Set();
+  const nodeIdsInTour = new Set();
+
+  // Stats
+  const nodeTypeCounts = {};
+  const edgeTypeCounts = {};
+  for (const n of nodes) { nodeTypeCounts[n.type] = (nodeTypeCounts[n.type] || 0) + 1; }
+  for (const e of edges) { edgeTypeCounts[e.type] = (edgeTypeCounts[e.type] || 0) + 1; }
+
+  // ========== Check 1: Schema Validation ==========
+
+  // Nodes
+  for (const n of nodes) {
+    if (!n.id || typeof n.id !== 'string' || n.id.trim() === '') addIssue(`Node missing/empty id: ${JSON.stringify(n)}`);
+    if (!n.type || !VALID_NODE_TYPES.has(n.type)) addIssue(`Node "${n.id}" has invalid type "${n.type}"`);
+    if (!n.name || typeof n.name !== 'string' || n.name.trim() === '') addIssue(`Node "${n.id}" missing/empty name`);
+    if (!n.summary || typeof n.summary !== 'string' || n.summary.trim() === '') addIssue(`Node "${n.id}" missing/empty summary`);
+    if (n.summary && n.name && n.summary.trim() === n.name.trim()) addWarning(`Node "${n.id}" summary equals name`);
+    if (!Array.isArray(n.tags) || n.tags.length === 0) addIssue(`Node "${n.id}" missing/empty tags`);
+    else {
+      for (const t of n.tags) {
+        if (typeof t !== 'string') addIssue(`Node "${n.id}" has non-string tag`);
+      }
+    }
+    if (!n.complexity || !VALID_COMPLEXITY.has(n.complexity)) addIssue(`Node "${n.id}" has invalid complexity "${n.complexity}"`);
+
+    // ID prefix check (Check 9)
+    if (n.id && n.type) {
+      const prefix = n.id.split(':')[0];
+      if (prefix !== n.type) addWarning(`Node "${n.id}" type "${n.type}" does not match ID prefix "${prefix}"`);
+    }
+  }
+
+  // Edges
+  for (let i = 0; i < edges.length; i++) {
+    const e = edges[i];
+    if (!e.source || typeof e.source !== 'string') addIssue(`Edge[${i}] missing/empty source`);
+    if (!e.target || typeof e.target !== 'string') addIssue(`Edge[${i}] missing/empty target`);
+    if (!e.type || !VALID_EDGE_TYPES.has(e.type)) addIssue(`Edge[${i}] (${e.source} -> ${e.target}) has invalid type "${e.type}"`);
+    if (!e.direction || !VALID_DIRECTION.has(e.direction)) addIssue(`Edge[${i}] (${e.source} -> ${e.target}) has invalid direction "${e.direction}"`);
+    if (typeof e.weight !== 'number' || e.weight < 0 || e.weight > 1) addIssue(`Edge[${i}] (${e.source} -> ${e.target}) has invalid weight "${e.weight}"`);
+  }
+
+  // ========== Check 2: Referential Integrity ==========
+
+  for (let i = 0; i < edges.length; i++) {
+    const e = edges[i];
+    if (e.source && !nodeIds.has(e.source)) addIssue(`Edge[${i}] source "${e.source}" references non-existent node`);
+    if (e.target && !nodeIds.has(e.target)) addIssue(`Edge[${i}] target "${e.target}" references non-existent node`);
+  }
+
+  for (const layer of layers) {
+    if (Array.isArray(layer.nodeIds)) {
+      for (const nid of layer.nodeIds) {
+        nodeIdsInLayers.add(nid);
+        if (!nodeIds.has(nid)) addIssue(`Layer "${layer.id}" nodeIds references non-existent node "${nid}"`);
+      }
+    }
+  }
+
+  for (const step of tour) {
+    if (Array.isArray(step.nodeIds)) {
+      for (const nid of step.nodeIds) {
+        nodeIdsInTour.add(nid);
+        if (!nodeIds.has(nid)) addIssue(`Tour step ${step.order} references non-existent node "${nid}"`);
+      }
+    }
+  }
+
+  // ========== Check 3: Completeness ==========
+
+  if (nodes.length < 1) addIssue('No nodes in graph');
+  if (edges.length < 1) addIssue('No edges in graph');
+  if (layers.length < 1) addIssue('No layers in graph');
+  if (tour.length < 1) addIssue('No tour steps in graph');
+
+  // ========== Check 4: Layer Coverage ==========
+
+  const fileLevelNodes = nodes.filter(n => FILE_LEVEL_TYPES.has(n.type));
+  const fileLevelNodeIds = new Set(fileLevelNodes.map(n => n.id));
+  const coveredInLayers = new Set();
+
+  for (const layer of layers) {
+    if (!Array.isArray(layer.nodeIds) || layer.nodeIds.length === 0) {
+      addIssue(`Layer "${layer.id}" has empty nodeIds`);
+    }
+    for (const nid of (layer.nodeIds || [])) {
+      if (fileLevelNodeIds.has(nid)) {
+        if (coveredInLayers.has(nid)) {
+          addIssue(`File-level node "${nid}" appears in multiple layers`);
+        } else {
+          coveredInLayers.add(nid);
+        }
+      }
+    }
+  }
+
+  for (const fnid of fileLevelNodeIds) {
+    if (!coveredInLayers.has(fnid)) {
+      addIssue(`File-level node "${fnid}" is not in any layer`);
+    }
+  }
+
+  // ========== Check 5: Uniqueness ==========
+
+  const seenIds = new Set();
+  for (const n of nodes) {
+    if (seenIds.has(n.id)) addIssue(`Duplicate node ID: "${n.id}"`);
+    seenIds.add(n.id);
+  }
+
+  // ========== Check 6: Tour Validation ==========
+
+  if (tour.length < 5 || tour.length > 15) {
+    addWarning(`Tour has ${tour.length} steps (expected 5-15)`);
+  }
+  for (let i = 0; i < tour.length; i++) {
+    const step = tour[i];
+    if (step.order !== i + 1) addWarning(`Tour step index ${i} has order ${step.order}, expected ${i + 1}`);
+    if (!Array.isArray(step.nodeIds) || step.nodeIds.length === 0) addWarning(`Tour step ${step.order} has no nodeIds`);
+  }
+
+  // ========== Check 7: Quality Checks ==========
+
+  // Build edge connectivity set
+  const connectedNodes = new Set();
+  for (const e of edges) {
+    connectedNodes.add(e.source);
+    connectedNodes.add(e.target);
+  }
+
+  for (const n of nodes) {
+    if (!connectedNodes.has(n.id)) addWarning(`Orphan node: "${n.id}" has no edges`);
+  }
+
+  for (let i = 0; i < edges.length; i++) {
+    const e = edges[i];
+    if (e.source === e.target) addWarning(`Self-referencing edge[${i}]: "${e.source}"`);
+  }
+
+  // ========== Check 8: Non-Code Node Quality ==========
+
+  // Build edge index by source and target
+  const edgesBySource = {};
+  const edgesByTarget = {};
+  for (const e of edges) {
+    if (!edgesBySource[e.source]) edgesBySource[e.source] = [];
+    edgesBySource[e.source].push(e);
+    if (!edgesByTarget[e.target]) edgesByTarget[e.target] = [];
+    edgesByTarget[e.target].push(e);
+  }
+
+  function nodeHasEdgeType(nodeId, edgeType) {
+    const outEdges = edgesBySource[nodeId] || [];
+    const inEdges = edgesByTarget[nodeId] || [];
+    return outEdges.some(e => e.type === edgeType) || inEdges.some(e => e.type === edgeType);
+  }
+
+  for (const n of nodes) {
+    const allEdges = [...(edgesBySource[n.id] || []), ...(edgesByTarget[n.id] || [])];
+    if (n.type === 'document' && !nodeHasEdgeType(n.id, 'documents')) {
+      addWarning(`Document node "${n.id}" has no "documents" edge`);
+    }
+    if (n.type === 'service' && !(nodeHasEdgeType(n.id, 'deploys') || nodeHasEdgeType(n.id, 'depends_on'))) {
+      addWarning(`Service node "${n.id}" has no "deploys" or "depends_on" edge`);
+    }
+    if (n.type === 'pipeline' && !nodeHasEdgeType(n.id, 'triggers')) {
+      addWarning(`Pipeline node "${n.id}" has no "triggers" edge`);
+    }
+    if (n.type === 'table' && !(nodeHasEdgeType(n.id, 'migrates') || nodeHasEdgeType(n.id, 'defines_schema'))) {
+      addWarning(`Table node "${n.id}" has no "migrates" or "defines_schema" edge`);
+    }
+    if (n.type === 'schema' && !nodeHasEdgeType(n.id, 'defines_schema')) {
+      addWarning(`Schema node "${n.id}" has no "defines_schema" edge`);
+    }
+  }
+
+  // ========== Output ==========
+
+  const result = {
+    scriptCompleted: true,
+    issues,
+    warnings,
+    stats: {
+      totalNodes: nodes.length,
+      totalEdges: edges.length,
+      totalLayers: layers.length,
+      tourSteps: tour.length,
+      nodeTypes: nodeTypeCounts,
+      edgeTypes: edgeTypeCounts
+    }
+  };
+
+  fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf8');
+  console.log(JSON.stringify(result, null, 2));
+  process.exit(0);
+
+} catch (err) {
+  console.error('Script crash:', err.message);
+  process.exit(1);
+}

+ 44 - 0
www/new_sdk/.understand-anything/.trash-20260630093035/tmp/ua-review-results.json

@@ -0,0 +1,44 @@
+{
+  "scriptCompleted": true,
+  "issues": [],
+  "warnings": [
+    "Orphan node: \"document:README.md\" has no edges",
+    "Orphan node: \"config:composer.json\" has no edges",
+    "Orphan node: \"file:application/common.php\" has no edges",
+    "Orphan node: \"file:application/tags.php\" has no edges",
+    "Orphan node: \"file:application/command.php\" has no edges",
+    "Orphan node: \"file:application/admin/common.php\" has no edges",
+    "Orphan node: \"file:application/api/common.php\" has no edges",
+    "Orphan node: \"file:application/api/controller/v1/Index.php\" has no edges",
+    "Orphan node: \"file:application/service/CommonService.php\" has no edges",
+    "Orphan node: \"file:application/guildapi/controller\" has no edges",
+    "Orphan node: \"file:extend/LdzfPay\" has no edges",
+    "Orphan node: \"file:extend/Obs\" has no edges",
+    "Orphan node: \"file:public/static\" has no edges",
+    "Orphan node: \"file:application/admin/view\" has no edges",
+    "Orphan node: \"file:public/constants.php\" has no edges",
+    "Orphan node: \"file:application/common/library\" has no edges",
+    "Orphan node: \"file:application/common/logic\" has no edges",
+    "Orphan node: \"file:application/common/validate\" has no edges",
+    "Document node \"document:README.md\" has no \"documents\" edge"
+  ],
+  "stats": {
+    "totalNodes": 44,
+    "totalEdges": 24,
+    "totalLayers": 11,
+    "tourSteps": 10,
+    "nodeTypes": {
+      "document": 1,
+      "config": 4,
+      "file": 39
+    },
+    "edgeTypes": {
+      "reads_from": 2,
+      "imports": 3,
+      "inherits": 3,
+      "calls": 11,
+      "related": 2,
+      "configures": 3
+    }
+  }
+}

+ 45 - 0
www/new_sdk/.understand-anything/.understandignore

@@ -0,0 +1,45 @@
+# .understandignore — patterns for files/dirs to exclude from analysis
+# Syntax: same as .gitignore (globs, # comments, ! negation, trailing / for dirs)
+# Lines below are suggestions — uncomment to activate.
+# Use ! prefix to force-include something excluded by defaults.
+#
+# Built-in defaults (always excluded unless negated):
+#   node_modules/, .git/, dist/, build/, obj/, *.lock, *.min.js, etc.
+#
+
+# --- From .gitignore (uncomment to exclude) ---
+
+.env
+.env_dev
+.env_pro
+runtime
+/dev.env
+/pro.env
+/test.env
+/vendor
+/md/*
+.DS_Store
+*.pem
+
+# --- Privacy / sensitive files ---
+**/*privacy*.html
+**/cert/**
+
+# --- Test file patterns (uncomment to exclude) ---
+
+# JS / TS
+# *.test.*
+# *.spec.*
+# *.snap
+# C# / .NET
+# **/*Tests.cs
+# **/*Test.cs
+# **/*Fixture.cs
+# **/*.Tests.csproj
+# Java / Kotlin
+# **/src/test/**
+# **/*Test.java
+# **/*IT.java
+# **/*Spec.kt
+# Go
+# **/*_test.go

+ 3 - 0
www/new_sdk/.understand-anything/config.json

@@ -0,0 +1,3 @@
+{
+  "outputLanguage": "zh"
+}

File diff suppressed because it is too large
+ 3712 - 0
www/new_sdk/.understand-anything/fingerprints.json


File diff suppressed because it is too large
+ 15106 - 0
www/new_sdk/.understand-anything/intermediate/scan-result.json


+ 980 - 0
www/new_sdk/.understand-anything/knowledge-graph.json

@@ -0,0 +1,980 @@
+{
+  "nodes": [
+    {
+      "id": "document:README.md",
+      "type": "document",
+      "name": "README.md",
+      "filePath": "README.md",
+      "summary": "项目说明文档,描述游戏SDK平台的域名配置、环境要求和相关服务",
+      "tags": [
+        "documentation",
+        "project-overview"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:composer.json",
+      "type": "config",
+      "name": "composer.json",
+      "filePath": "composer.json",
+      "summary": "PHP依赖管理配置文件,定义ThinkPHP 5.0框架及支付、队列等依赖包",
+      "tags": [
+        "php",
+        "dependencies",
+        "composer"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:public/index.php",
+      "type": "file",
+      "name": "index.php",
+      "filePath": "public/index.php",
+      "summary": "应用入口文件,设置CORS头、定义应用路径、加载常量配置和框架引导",
+      "tags": [
+        "entry-point",
+        "php",
+        "bootstrap"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:application/config.php",
+      "type": "config",
+      "name": "config.php",
+      "filePath": "application/config.php",
+      "summary": "应用主配置文件,定义模块设置、URL路由、数据库缓存、模板引擎等核心配置",
+      "tags": [
+        "config",
+        "thinkphp",
+        "application"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/route.php",
+      "type": "file",
+      "name": "route.php",
+      "filePath": "application/route.php",
+      "summary": "路由配置文件,定义域名到模块的映射(admin/api/guildapi/complex/home/mobile),配置PC站和M站路由规则",
+      "tags": [
+        "routing",
+        "thinkphp",
+        "domain-mapping"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/common.php",
+      "type": "file",
+      "name": "common.php",
+      "filePath": "application/common.php",
+      "summary": "公共函数库,包含随机数生成、数组排序、加密解密、HTTP请求、日期处理等通用工具函数",
+      "tags": [
+        "utility",
+        "common",
+        "helper"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/tags.php",
+      "type": "file",
+      "name": "tags.php",
+      "filePath": "application/tags.php",
+      "summary": "行为扩展定义文件,配置应用初始化、路由开始、模块初始化等钩子",
+      "tags": [
+        "hooks",
+        "lifecycle",
+        "thinkphp"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/command.php",
+      "type": "file",
+      "name": "command.php",
+      "filePath": "application/command.php",
+      "summary": "命令行配置文件,注册自定义命令如Kafka消费者",
+      "tags": [
+        "cli",
+        "commands",
+        "kafka"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/admin/common.php",
+      "type": "file",
+      "name": "admin/common.php",
+      "filePath": "application/admin/common.php",
+      "summary": "后台公共函数库,包含权限检查、日志记录、数据格式化等后台专用工具函数",
+      "tags": [
+        "admin",
+        "utility",
+        "helper"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/admin/controller/Admin.php",
+      "type": "file",
+      "name": "Admin.php",
+      "filePath": "application/admin/controller/Admin.php",
+      "summary": "后台公共控制器基类,实现登录验证、权限检查、WebSocket令牌管理",
+      "tags": [
+        "admin",
+        "controller",
+        "base-class",
+        "auth"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/common.php",
+      "type": "file",
+      "name": "api/common.php",
+      "filePath": "application/api/common.php",
+      "summary": "API模块公共函数库,包含API签名验证、参数加密解密、渠道鉴权等",
+      "tags": [
+        "api",
+        "utility",
+        "auth"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/controller/v1/Login.php",
+      "type": "file",
+      "name": "Login.php",
+      "filePath": "application/api/controller/v1/Login.php",
+      "summary": "用户登录控制器,处理账号密码登录、手机验证码登录、第三方登录等",
+      "tags": [
+        "api",
+        "login",
+        "authentication"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/api/controller/v1/Pay.php",
+      "type": "file",
+      "name": "Pay.php",
+      "filePath": "application/api/controller/v1/Pay.php",
+      "summary": "支付控制器,处理游戏内充值、创建订单、调用支付渠道(支付宝/微信)",
+      "tags": [
+        "api",
+        "payment",
+        "alipay",
+        "wechat"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/api/controller/v1/PayNotify.php",
+      "type": "file",
+      "name": "PayNotify.php",
+      "filePath": "application/api/controller/v1/PayNotify.php",
+      "summary": "支付回调控制器,接收支付宝/微信支付结果通知,更新订单状态",
+      "tags": [
+        "api",
+        "payment",
+        "callback",
+        "webhook"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/api/controller/v1/Index.php",
+      "type": "file",
+      "name": "Index.php",
+      "filePath": "application/api/controller/v1/Index.php",
+      "summary": "API首页控制器,处理游戏列表、游戏详情、推广页面等",
+      "tags": [
+        "api",
+        "game",
+        "index"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/service/PayService.php",
+      "type": "file",
+      "name": "PayService.php",
+      "filePath": "application/service/PayService.php",
+      "summary": "支付服务类,封装支付宝WAP支付、APP支付、扫码支付等支付方式的调用逻辑",
+      "tags": [
+        "service",
+        "payment",
+        "alipay",
+        "core"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/service/GamePayService.php",
+      "type": "file",
+      "name": "GamePayService.php",
+      "filePath": "application/service/GamePayService.php",
+      "summary": "游戏支付服务类,处理游戏充值订单创建、支付渠道选择、回调处理",
+      "tags": [
+        "service",
+        "payment",
+        "game"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/service/CommonService.php",
+      "type": "file",
+      "name": "CommonService.php",
+      "filePath": "application/service/CommonService.php",
+      "summary": "通用服务类,提供数据查询、缓存管理、配置获取等基础服务",
+      "tags": [
+        "service",
+        "common",
+        "utility"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Game.php",
+      "type": "file",
+      "name": "Game.php",
+      "filePath": "application/common/model/Game.php",
+      "summary": "游戏数据模型,定义游戏表结构和关联关系",
+      "tags": [
+        "model",
+        "game",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Members.php",
+      "type": "file",
+      "name": "Members.php",
+      "filePath": "application/common/model/Members.php",
+      "summary": "会员数据模型,定义用户表结构、登录状态、充值记录等关联",
+      "tags": [
+        "model",
+        "user",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Pay.php",
+      "type": "file",
+      "name": "Pay.php",
+      "filePath": "application/common/model/Pay.php",
+      "summary": "支付订单数据模型,定义订单表结构、支付状态、退款记录等",
+      "tags": [
+        "model",
+        "payment",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Channel.php",
+      "type": "file",
+      "name": "Channel.php",
+      "filePath": "application/common/model/Channel.php",
+      "summary": "渠道数据模型,定义渠道表结构、渠道配置、分成比例等",
+      "tags": [
+        "model",
+        "channel",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/model/Admin.php",
+      "type": "file",
+      "name": "Admin.php",
+      "filePath": "application/common/model/Admin.php",
+      "summary": "管理员数据模型,定义后台用户表结构、角色权限关联",
+      "tags": [
+        "model",
+        "admin",
+        "database"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/controller/Base.php",
+      "type": "file",
+      "name": "Base.php",
+      "filePath": "application/common/controller/Base.php",
+      "summary": "基础控制器类,提供跨模块的公共方法如响应输出、日志记录、异常处理",
+      "tags": [
+        "controller",
+        "base-class",
+        "common"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/guildapi/controller",
+      "type": "file",
+      "name": "guildapi/controllers",
+      "filePath": "application/guildapi/controller",
+      "summary": "公会API控制器目录,处理公会后台的频道管理、数据统计、结算等接口",
+      "tags": [
+        "guild",
+        "api",
+        "controller"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/complex/controller",
+      "type": "file",
+      "name": "complex/controllers",
+      "filePath": "application/complex/controller",
+      "summary": "渠道SDK对接控制器目录,处理第三方渠道的登录、支付、数据同步等",
+      "tags": [
+        "complex",
+        "channel",
+        "sdk"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:application/home/controller",
+      "type": "file",
+      "name": "home/controllers",
+      "filePath": "application/home/controller",
+      "summary": "官网控制器目录,处理PC端官网的游戏展示、用户中心、客服系统等",
+      "tags": [
+        "home",
+        "website",
+        "frontend"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/mobile/controller",
+      "type": "file",
+      "name": "mobile/controllers",
+      "filePath": "application/mobile/controller",
+      "summary": "移动端控制器目录,处理H5端的游戏展示、用户中心、支付等",
+      "tags": [
+        "mobile",
+        "h5",
+        "frontend"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/crontab",
+      "type": "file",
+      "name": "crontab",
+      "filePath": "application/crontab",
+      "summary": "定时任务目录,包含渠道结算、数据统计、支付超时处理等定时任务",
+      "tags": [
+        "cron",
+        "scheduled",
+        "batch"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/command/KafkaConsumer.php",
+      "type": "file",
+      "name": "KafkaConsumer.php",
+      "filePath": "application/command/KafkaConsumer.php",
+      "summary": "Kafka消费者命令,异步处理消息队列中的任务",
+      "tags": [
+        "kafka",
+        "queue",
+        "async"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/alipay",
+      "type": "file",
+      "name": "alipay SDK",
+      "filePath": "extend/alipay",
+      "summary": "支付宝SDK扩展目录,包含支付宝支付接口的PHP实现",
+      "tags": [
+        "alipay",
+        "sdk",
+        "payment"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:extend/DouYinGameOpen",
+      "type": "file",
+      "name": "DouYinGameOpen",
+      "filePath": "extend/DouYinGameOpen",
+      "summary": "抖音游戏开放平台SDK,处理抖音渠道的登录和支付",
+      "tags": [
+        "douyin",
+        "sdk",
+        "channel"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/LdzfPay",
+      "type": "file",
+      "name": "LdzfPay",
+      "filePath": "extend/LdzfPay",
+      "summary": "联动支付SDK扩展,提供联动优势支付渠道的接入",
+      "tags": [
+        "ldzf",
+        "sdk",
+        "payment"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:extend/Obs",
+      "type": "file",
+      "name": "ObsClient",
+      "filePath": "extend/Obs",
+      "summary": "华为云OBS客户端扩展,用于对象存储服务(文件上传下载)",
+      "tags": [
+        "huawei",
+        "obs",
+        "storage"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:thinkphp",
+      "type": "file",
+      "name": "ThinkPHP Framework",
+      "filePath": "thinkphp",
+      "summary": "ThinkPHP 5.0框架核心目录,包含路由、控制器、模型、视图等核心组件",
+      "tags": [
+        "framework",
+        "thinkphp",
+        "core"
+      ],
+      "complexity": "complex"
+    },
+    {
+      "id": "file:public/static",
+      "type": "file",
+      "name": "static assets",
+      "filePath": "public/static",
+      "summary": "静态资源目录,包含CSS、JavaScript、图片、字体等前端资源",
+      "tags": [
+        "static",
+        "frontend",
+        "assets"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/admin/view",
+      "type": "file",
+      "name": "admin views",
+      "filePath": "application/admin/view",
+      "summary": "后台视图模板目录,包含后台管理界面的HTML模板文件",
+      "tags": [
+        "admin",
+        "view",
+        "template"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:.env_dev",
+      "type": "config",
+      "name": ".env_dev",
+      "filePath": ".env_dev",
+      "summary": "开发环境配置文件,包含数据库连接、Redis配置、第三方服务密钥等",
+      "tags": [
+        "config",
+        "env",
+        "development"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "config:.env_pro",
+      "type": "config",
+      "name": ".env_pro",
+      "filePath": ".env_pro",
+      "summary": "生产环境配置文件,包含生产数据库、Redis、支付密钥等敏感配置",
+      "tags": [
+        "config",
+        "env",
+        "production"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:public/constants.php",
+      "type": "file",
+      "name": "constants.php",
+      "filePath": "public/constants.php",
+      "summary": "全局常量定义文件,定义域名、路径、状态码等应用常量",
+      "tags": [
+        "constants",
+        "config",
+        "global"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/common/library",
+      "type": "file",
+      "name": "common/library",
+      "filePath": "application/common/library",
+      "summary": "公共类库目录,包含WebSocket、短信发送、文件处理、数据导出等工具类",
+      "tags": [
+        "library",
+        "common",
+        "utility"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/logic",
+      "type": "file",
+      "name": "common/logic",
+      "filePath": "application/common/logic",
+      "summary": "公共业务逻辑目录,包含支付工厂、子包管理、数据同步等业务逻辑",
+      "tags": [
+        "logic",
+        "business",
+        "common"
+      ],
+      "complexity": "moderate"
+    },
+    {
+      "id": "file:application/common/validate",
+      "type": "file",
+      "name": "common/validate",
+      "filePath": "application/common/validate",
+      "summary": "公共验证器目录,定义请求参数验证规则",
+      "tags": [
+        "validate",
+        "common",
+        "input"
+      ],
+      "complexity": "simple"
+    },
+    {
+      "id": "file:application/extra",
+      "type": "file",
+      "name": "extra configs",
+      "filePath": "application/extra",
+      "summary": "扩展配置目录,包含Kafka配置、队列配置、操作日志配置等",
+      "tags": [
+        "config",
+        "extra",
+        "kafka"
+      ],
+      "complexity": "simple"
+    }
+  ],
+  "project": {
+    "name": "new_sdk",
+    "analyzedAt": "2026-06-30T09:17:43Z",
+    "languages": [
+      "php",
+      "html",
+      "javascript",
+      "css"
+    ],
+    "frameworks": [
+      "ThinkPHP"
+    ],
+    "gitCommitHash": "7d315b54fbf4a43df94cdcfca70d7507c65d9628",
+    "description": "游戏SDK平台 - 基于ThinkPHP 5.0的游戏渠道SDK对接、支付回调、公会管理后台"
+  },
+  "tour": [
+    {
+      "order": 1,
+      "title": "项目概览",
+      "description": "阅读README了解项目背景:这是一个基于ThinkPHP 5.0的游戏SDK平台,包含后台管理、API接口、公会系统、渠道SDK对接等模块",
+      "nodeIds": [
+        "document:README.md"
+      ]
+    },
+    {
+      "order": 2,
+      "title": "应用入口",
+      "description": "从public/index.php开始,了解应用如何启动:设置CORS头、加载常量配置、引入ThinkPHP框架",
+      "nodeIds": [
+        "file:public/index.php"
+      ]
+    },
+    {
+      "order": 3,
+      "title": "路由配置",
+      "description": "查看route.php理解域名路由映射:不同子域名(admin/api/www/m等)对应不同模块,实现多站点共用一套代码",
+      "nodeIds": [
+        "file:application/route.php"
+      ]
+    },
+    {
+      "order": 4,
+      "title": "配置系统",
+      "description": "了解配置层:.env文件存储环境变量,config.php读取环境配置,extra目录存放扩展配置",
+      "nodeIds": [
+        "config:.env_dev",
+        "config:application/config.php",
+        "file:application/extra"
+      ]
+    },
+    {
+      "order": 5,
+      "title": "数据模型",
+      "description": "浏览common/model目录,了解核心数据表:Game(游戏)、Members(会员)、Pay(订单)、Channel(渠道)",
+      "nodeIds": [
+        "file:application/common/model/Game.php",
+        "file:application/common/model/Members.php",
+        "file:application/common/model/Pay.php"
+      ]
+    },
+    {
+      "order": 6,
+      "title": "支付流程",
+      "description": "追踪支付核心链路:Pay控制器 → PayService → 支付宝SDK,理解订单创建和回调处理",
+      "nodeIds": [
+        "file:application/api/controller/v1/Pay.php",
+        "file:application/service/PayService.php",
+        "file:extend/alipay"
+      ]
+    },
+    {
+      "order": 7,
+      "title": "后台管理",
+      "description": "了解后台管理系统:Admin基类控制器实现权限检查,各业务控制器继承实现具体功能",
+      "nodeIds": [
+        "file:application/admin/controller/Admin.php",
+        "file:application/common/controller/Base.php"
+      ]
+    },
+    {
+      "order": 8,
+      "title": "业务模块",
+      "description": "了解多模块架构:api(游戏API)、guildapi(公会API)、complex(渠道SDK)、home(官网)、mobile(移动端)",
+      "nodeIds": [
+        "file:application/guildapi/controller",
+        "file:application/complex/controller",
+        "file:application/home/controller"
+      ]
+    },
+    {
+      "order": 9,
+      "title": "定时任务",
+      "description": "查看crontab目录,了解定时任务:渠道结算、数据统计、支付超时处理等后台批处理",
+      "nodeIds": [
+        "file:application/crontab"
+      ]
+    },
+    {
+      "order": 10,
+      "title": "公共工具",
+      "description": "浏览common.php和common/library,了解项目复用的工具函数和类库",
+      "nodeIds": [
+        "file:application/common.php",
+        "file:application/common/library"
+      ]
+    }
+  ],
+  "layers": [
+    {
+      "id": "layer:entry",
+      "name": "入口层",
+      "description": "应用入口和框架引导,包含index.php和ThinkPHP框架启动",
+      "nodeIds": [
+        "file:public/index.php",
+        "file:thinkphp",
+        "file:public/constants.php"
+      ]
+    },
+    {
+      "id": "layer:config",
+      "name": "配置层",
+      "description": "应用配置、环境变量、路由规则等配置文件",
+      "nodeIds": [
+        "config:application/config.php",
+        "config:.env_dev",
+        "config:.env_pro",
+        "file:application/route.php",
+        "file:application/tags.php",
+        "file:application/command.php",
+        "file:application/extra"
+      ]
+    },
+    {
+      "id": "layer:model",
+      "name": "数据模型层",
+      "description": "数据库表结构定义、ORM模型、数据关联关系",
+      "nodeIds": [
+        "file:application/common/model/Game.php",
+        "file:application/common/model/Members.php",
+        "file:application/common/model/Pay.php",
+        "file:application/common/model/Channel.php",
+        "file:application/common/model/Admin.php"
+      ]
+    },
+    {
+      "id": "layer:service",
+      "name": "服务层",
+      "description": "业务服务类,封装核心业务逻辑如支付、游戏、会员等",
+      "nodeIds": [
+        "file:application/service/PayService.php",
+        "file:application/service/GamePayService.php",
+        "file:application/service/CommonService.php"
+      ]
+    },
+    {
+      "id": "layer:controller",
+      "name": "控制器层",
+      "description": "HTTP请求处理,包含后台管理、API接口、官网、移动端等控制器",
+      "nodeIds": [
+        "file:application/admin/controller/Admin.php",
+        "file:application/api/controller/v1/Login.php",
+        "file:application/api/controller/v1/Pay.php",
+        "file:application/api/controller/v1/PayNotify.php",
+        "file:application/api/controller/v1/Index.php",
+        "file:application/common/controller/Base.php"
+      ]
+    },
+    {
+      "id": "layer:business",
+      "name": "业务模块层",
+      "description": "按业务领域划分的模块,包含公会API、渠道SDK、官网、移动端等",
+      "nodeIds": [
+        "file:application/guildapi/controller",
+        "file:application/complex/controller",
+        "file:application/home/controller",
+        "file:application/mobile/controller"
+      ]
+    },
+    {
+      "id": "layer:common",
+      "name": "公共层",
+      "description": "跨模块共享的工具函数、类库、业务逻辑、验证器",
+      "nodeIds": [
+        "file:application/common.php",
+        "file:application/admin/common.php",
+        "file:application/api/common.php",
+        "file:application/common/library",
+        "file:application/common/logic",
+        "file:application/common/validate"
+      ]
+    },
+    {
+      "id": "layer:extension",
+      "name": "扩展层",
+      "description": "第三方SDK扩展,包含支付宝、抖音、联动支付等支付渠道SDK",
+      "nodeIds": [
+        "file:extend/alipay",
+        "file:extend/DouYinGameOpen",
+        "file:extend/LdzfPay",
+        "file:extend/Obs"
+      ]
+    },
+    {
+      "id": "layer:task",
+      "name": "任务层",
+      "description": "定时任务和异步任务,包含渠道结算、数据统计、消息队列消费",
+      "nodeIds": [
+        "file:application/crontab",
+        "file:application/command/KafkaConsumer.php"
+      ]
+    },
+    {
+      "id": "layer:frontend",
+      "name": "前端层",
+      "description": "前端资源和视图模板,包含静态文件、后台模板、官网页面",
+      "nodeIds": [
+        "file:public/static",
+        "file:application/admin/view"
+      ]
+    },
+    {
+      "id": "layer:docs",
+      "name": "文档层",
+      "description": "项目文档和依赖配置",
+      "nodeIds": [
+        "document:README.md",
+        "config:composer.json"
+      ]
+    }
+  ],
+  "edges": [
+    {
+      "source": "file:public/index.php",
+      "target": "config:application/config.php",
+      "type": "reads_from",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:public/index.php",
+      "target": "file:application/route.php",
+      "type": "reads_from",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:public/index.php",
+      "target": "file:thinkphp",
+      "type": "imports",
+      "weight": 0.9,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/admin/controller/Admin.php",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.9,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/admin/controller/Admin.php",
+      "target": "file:application/common/model/Admin.php",
+      "type": "calls",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/Pay.php",
+      "target": "file:application/service/PayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/Pay.php",
+      "target": "file:application/service/GamePayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/PayService.php",
+      "target": "file:extend/alipay",
+      "type": "imports",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/PayNotify.php",
+      "target": "file:application/service/PayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/PayNotify.php",
+      "target": "file:application/service/GamePayService.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Pay.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Game.php",
+      "type": "calls",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/service/GamePayService.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "calls",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/api/controller/v1/Login.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "calls",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/common/model/Game.php",
+      "target": "file:application/common/model/Channel.php",
+      "type": "related",
+      "weight": 0.6,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/common/model/Pay.php",
+      "target": "file:application/common/model/Members.php",
+      "type": "related",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/crontab",
+      "target": "file:application/common/model/Channel.php",
+      "type": "calls",
+      "weight": 0.6,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/crontab",
+      "target": "file:application/common/model/Pay.php",
+      "type": "calls",
+      "weight": 0.6,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/command/KafkaConsumer.php",
+      "target": "file:application/extra",
+      "type": "configures",
+      "weight": 0.5,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/complex/controller",
+      "target": "file:extend/DouYinGameOpen",
+      "type": "imports",
+      "weight": 0.7,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/home/controller",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "file:application/mobile/controller",
+      "target": "file:application/common/controller/Base.php",
+      "type": "inherits",
+      "weight": 0.8,
+      "direction": "forward"
+    },
+    {
+      "source": "config:.env_dev",
+      "target": "config:application/config.php",
+      "type": "configures",
+      "weight": 0.9,
+      "direction": "forward"
+    },
+    {
+      "source": "config:.env_pro",
+      "target": "config:application/config.php",
+      "type": "configures",
+      "weight": 0.9,
+      "direction": "forward"
+    }
+  ],
+  "version": "1.0.0"
+}

+ 6 - 0
www/new_sdk/.understand-anything/meta.json

@@ -0,0 +1,6 @@
+{
+  "lastAnalyzedAt": "2026-06-30T09:30:30Z",
+  "version": "1.0.0",
+  "gitCommitHash": "7d315b54fbf4a43df94cdcfca70d7507c65d9628",
+  "analyzedFiles": 2508
+}

+ 113 - 0
www/new_sdk/README.md

@@ -0,0 +1,113 @@
+ThinkPHP 5.0
+详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5)
+php 正常7.0-7.2 版本都可以
+
+## 相关域名
+ - admin.46yxs.com - 后台
+ - sdkapi.46yxs.com - 支付回调+
+ - cps.46yxs.com - 公会后台
+ - cpsapi.46yxs.com - 公会API
+ <!-- - mcpsapi.46yxs.com - mcpsapi - (无) -->
+ - jhgame.46yxs.com - complex - 渠道SDK对接
+ <!-- - m.46yxs.com - mobile - 手机端(无) -->
+ - www.46yxs.com - home - 官网
+
+
+## 相关地址
+- 隐私详情页:https://sdkapi.qmgames.cn/privacy.html
+
+## 域名
+> 当前域名地址: 7dgame.cn
+> 测试域名前加: dev
+
+devsdkdownapp.7dgame.cn
+devsdkstatic.7dgame.cn
+- 分包服务器请求域名地址:http://sdkpack.qmgames.cn/pack
+
+### 测试
+sdkwww - 官网
+devsdkadmin - 后台
+devsdkapi - 支付回调+
+devsdkcps - 公会后台
+devsdkcpsapi - 公会API
+devsdkjhgame - 渠道SDK对接
+devsdkdownapp - 渠道包下载
+devsdkdownreport - 导出数据下载地址(go相关服务)
+devsdkstatic - 图片地址
+
+
+server_name sdkwww.7dgames.cn devsdkadmin.7dgames.cn devsdkapi.7dgames.cn devsdkcps.7dgames.cn devsdkcpsapi.7dgames.cn devsdkjhgame.7dgames.cn devsdkdownapp.7dgames.cn devsdkdownreport.7dgames.cn;
+
+### 正式
+sdkwww - 官网
+sdkadmin - 后台
+sdkapi - 支付回调+
+sdkcps - 公会后台
+sdkcpsapi - 公会API
+sdkjhgame - 渠道SDK对接
+sdkdownapp - 渠道包下载
+sdkdownreport - 导出数据下载地址(go相关服务)
+sdkstatic - 图片地址
+
+
+
+## 所需环境
+- mysql
+- redis
+- docker
+- git
+
+## 项目仓库
+- new_sdk - 后台+API+CPS_API
+- new_sdk_cps-admin - cps后台
+- new_sdk_cps-py - cps打包
+- new_sdk_ws - 相关通知服务
+- new_sdk_go - excel导出&发放游戏礼包
+
+
+
+### 缺少的东西
+- 报表生成服务类go 关联的go程序 (app\common\library\MakeReportGo)
+- 渠道打包 关联的python程序 (app\common\logic\SubPackage)
+- workerman 没有相关配置文件
+
+## 需要的数据以及第三方
+- 域名
+- 阿里云oss(华为云obs 也可)
+- 预警钉钉机器人(可不用)
+    - 充值异常
+    - 特殊回调异常
+- 短信 阿里云
+- 支付宝 支付
+    - wap 支付
+    - 扫码支付
+- 微信 支付
+    - h5 支付
+    - 扫码支付
+
+### 服务器环境
+#### 配置: 
+- 服务器: 2核4G
+- 打包服务器: 2核8G
+- 数据库: 2核8G
+- redis: 2核4G
+- 带宽: 5M
+
+> 开发环境:php+mysql+redis+ws+go+python
+
+- php 7.0-7.2
+- redis 5.2.1
+- mysql 7
+
+
+## H5
+
+
+### code码定义
+```
+100 = 登录失效
+110 = 请求参数有误
+120 = 请求处理过程提示
+130 = 执行异常
+200 = 正常
+```

+ 1 - 0
www/new_sdk/application/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 302 - 0
www/new_sdk/application/admin/common.php

@@ -0,0 +1,302 @@
+<?php
+use think\Env;
+
+/**
+ * 检查权限
+ * @param $userId  int        要检查权限的用户 ID
+ * @param $name string|array  需要验证的规则列表,支持逗号分隔的权限规则或索引数组
+ * @param $relation string    如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
+ * @return boolean            通过验证返回true;失败返回false
+ */
+function cmf_auth_check($userId, $name = null, $relation = 'or')
+{
+    if (empty($userId)) {
+        return false;
+    }
+
+    if ($userId == 1) {
+        return true;
+    }
+
+    $authObj = new \app\common\logic\Auth();
+    if (empty($name)) {
+        $request = request();
+        $module = $request->module();
+        $controller = $request->controller();
+        $action = $request->action();
+        $name = strtolower($module . "/" . $controller . "/" . $action);
+    }
+    return $authObj->check($userId, $name, $relation);
+}
+
+/**
+ * 获取当前登录的管理员ID
+ * @return int
+ */
+function mg_get_current_admin_id()
+{
+    return session('ADMIN_ID');
+}
+
+/**
+ * 获取游戏名称
+ */
+function get_game_nickname($id)
+{
+    $info = \Think\Db::name('cy_game')->field('name')->where(['id' => $id])->find();
+    return $info['name'];
+}
+
+
+/**
+ * 获取当前渠道名称
+ */
+function get_current_channel_name($id)
+{
+    $info = \Think\Db::name('nw_channel')->field('name')->where(['id' => $id])->find();
+    return $info['name'];
+}
+
+
+/**
+ * 根据权限是否显示UI
+ *
+ * @param bool $show
+ * @param string $rule_name
+ * @return bool
+ */
+function authUI($rule_name = '', $show = false)
+{
+    return $show ?: cmf_auth_check(session('ADMIN_ID'), $rule_name);
+}
+
+/**
+ * 获取渠道
+ */
+function get_channel_arr($channelId)
+{
+    $result = [];
+    $level = 1;
+    $channelModel = \think\Db::name('nw_channel');
+
+    while ($info = $channelModel->field('name,parent_id')->where(['id' => $channelId])->find()) {
+
+        $info['level'] = $level;
+        $result[] = $info;
+        if ($info['parent_id'] == 0) {
+            return array_reverse($result);
+        }
+
+        $channelId = $info['parent_id'];
+        $level++;
+    }
+}
+
+/**
+ * AES加密
+ * @param string $str 需加密的字符串
+ * @param string $key 秘钥
+ *
+ * @return string
+ */
+function aesEncode($str, $key)
+{
+    $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+    $ivStr = strtoupper(md5($key));
+    $iv = substr($ivStr, 0, 16);
+    mcrypt_generic_init($td, $key, $iv);
+    $b = mcrypt_generic($td, padPkcs5($str, 16));
+    mcrypt_generic_deinit($td);
+    mcrypt_module_close($td);
+
+    return bin2hex($b);
+}
+
+/**
+ * AES解密
+ * @param string $str 需解密的字符串
+ * @param string $key 秘钥
+ *
+ * @return string
+ */
+function aesDecode($str, $key)
+{
+    $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+    $ivStr = strtoupper(md5($key));
+    $iv = substr($ivStr, 0, 16);
+    mcrypt_generic_init($td, $key, $iv);
+    $a = unpadPkcs5(mdecrypt_generic($td, hex2bin($str)));
+    mcrypt_generic_deinit($td);
+    mcrypt_module_close($td);
+
+    return $a;
+}
+
+function padPkcs5($text, $blocksize)
+{
+    $pad = $blocksize - (strlen($text) % $blocksize);
+    return $text . str_repeat(chr($pad), $pad);
+}
+
+function unpadPkcs5($text)
+{
+    $pad = ord($text{strlen($text) - 1});
+    if ($pad > strlen($text)) {
+        return false;
+    }
+    if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
+        return false;
+    }
+
+    return substr($text, 0, -1 * $pad);
+}
+
+/**
+ * 当传递的value为空时,返回设置的默认值
+ * @param mixed $value
+ * @param mixed $default
+ * @return mixed
+ */
+function setDefault($value, $default = '---')
+{
+    if (empty($value)) {
+        return $default;
+    } else {
+        return $value;
+    }
+}
+
+/**
+ * 获取一级渠道列表
+ */
+function getChannelByTop()
+{
+    $parent_id = config('TOP_CHANNEL_ID');
+    $channel_list = model('Common/Channel')->where('parent_id', "{$parent_id}")->select();
+    $result = [];
+    foreach ($channel_list as $v) {
+        $result[$v['id']] = $v['name'];
+    }
+    return $result;
+}
+
+/**
+ * 获取日期查询条件
+ * @param int $start_time 开始时间
+ * @param int $end_time 结束时间
+ * @param bool $isdefault 是否默认今天日期
+ * @return
+ */
+function getTimeCondition($start_time, $end_time, $isdefault = true)
+{
+
+    $time = [];
+    //开始时间和结束时间不为空时
+    if ($start_time != '' && $end_time != '') {
+        $time = [
+            ['>=', strtotime($start_time)],
+            ['<=', strtotime($end_time . ' 23:59:59')],
+        ];
+    } //开始时间不为空时
+    elseif ($start_time != '') {
+        $time = ['>=', strtotime($start_time)];
+    } //结束时间不为空时
+    elseif ($end_time != '') {
+        $time = ['<=', strtotime($end_time . ' 23:59:59')];
+    } else {
+
+        if ($isdefault) {
+            $start_time = $end_time = date('Y-m-d', time());
+
+            $time = [
+                ['>=', strtotime($start_time)],
+                ['<=', strtotime($end_time . ' 23:59:59')],
+            ];
+        }
+    }
+
+    return $time;
+}
+
+
+/**
+ * 获取日期查询条件
+ * @param int $start_time 开始时间
+ * @param int $end_time 结束时间
+ * @param bool $isdefault 是否默认今天日期
+ * @return
+ */
+function getDateTimeCondition($start_time, $end_time, $isdefault = true)
+{
+
+    $time = [];
+    //开始时间和结束时间不为空时
+    if ($start_time != '' && $end_time != '') {
+        $time = [
+            ['>=', $start_time],
+            ['<=', $end_time],
+        ];
+    } //开始时间不为空时
+    elseif ($start_time != '') {
+        $time = ['>=', $start_time];
+    } //结束时间不为空时
+    elseif ($end_time != '') {
+        $time = ['<=', $end_time];
+    } else {
+        if ($isdefault) {
+            $start_time = $end_time = date('Y-m-d', time());
+            $time = [
+                ['>=',$start_time],
+                ['<=', $end_time],
+            ];
+        }
+    }
+
+    return $time;
+}
+
+// 获取env配置参数
+function getEnvs($name, $default = ''){
+    return Env::get($name, $default);
+}
+
+/**
+ * 数组键增加前缀
+ *
+ * @param array  $array 处理的数组
+ * @param string $prefix 新增的前缀
+ *
+ * @return array
+ */
+function addPrefixToKeys(array $array, string $prefix): array {
+    // 为所有键添加前缀
+    $prefixedKeys = array_map(function($key) use ($prefix) {
+        return $prefix . $key;
+    }, array_keys($array));
+    
+    // 使用新键名和原值创建新数组
+    return array_combine($prefixedKeys, $array);
+}
+
+/**
+ * 获取未来时间戳与当前时间之间的剩余天数
+ *
+ * @param int $futureTimestamp 目标时间戳(秒)
+ * @return int|string 剩余天数;如果时间已过或无效,返回空字符串
+ */
+function getRemainingDays(int $futureTimestamp) {
+    // 当前时间戳
+    $now = time();
+
+    // 判断目标时间是否早于当前时间
+    if ($futureTimestamp <= $now) {
+        return "-";
+    }
+
+    // 计算剩余秒数,并换算为天数(向上取整)
+    $remainingSeconds = $futureTimestamp - $now;
+    $remainingDays = (int)ceil($remainingSeconds / (60 * 60 * 24));
+
+    return $remainingDays;
+}
+

+ 148 - 0
www/new_sdk/application/admin/controller/Aboutus.php

@@ -0,0 +1,148 @@
+<?php
+/**
+ *关于我们管理
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2018/11/19
+ * Time: 11:50
+ */
+namespace app\admin\controller;
+
+class Aboutus extends Admin
+{
+    protected function _initialize()
+    {
+        parent::_initialize(); // TODO: Change the autogenerated stub
+        $this->aboutusModel = model('Aboutus');
+    }
+
+    /**
+     *关于管理列表
+     */
+    public function index()
+    {
+        $order = $this->request->param('order');
+        $orderStr = null;
+        $orderUrl = url('index',['order'=>'desc']);
+
+        if ($order == 'desc'){
+            $orderUrl = url('index',['order'=>'asc']);
+            $orderStr = 'order desc';
+        }elseif ($order == 'asc'){
+            $orderStr = 'order asc';
+            $orderUrl = url('index',['order'=>'desc']);
+        }
+
+        $list = $this->aboutusModel->getList($orderStr);
+        $this->assign('list',$list);
+        $this->assign('page',$list);
+        $this->assign('orderUrl', $orderUrl);
+        return $this->fetch();
+    }
+    /*
+     * 关于管理添加
+     */
+    public function addAbout()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->getAboutusParam();
+
+            $result = $this->validate($data, [
+                ['title', 'require', '标题不能为空'],
+                ['name', 'require', '路径参数不能为空'],
+                ['content', 'require', '内容不能为空'],
+                ['order', 'require|integer|gt:0', '序列号不能为空|序列号必须为正整数'],
+            ]);
+
+            if (true !== $result) {
+                $this->error($result);
+            }
+
+            if ($this->isShowName($data['name'])) {
+                $this->error('路径参数已经存在');
+            }
+
+            $data['create_time'] = time();
+
+            if ($this->aboutusModel->allowField(true)->save($data)) {
+                $this->aboutusModel->delCache(); // 删除缓存
+                $this->success('添加成功',url('index'));
+            }
+            $this->error($this->aboutusModel->getError() ?: '添加失败');
+        }
+        return $this->fetch('add_about');
+    }
+    /**
+     *关于管理编辑
+     */
+    public function editAbout()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if ($this->request->isPost()) {
+            $data = $this->getAboutusParam();
+
+            $result = $this->validate($data, [
+                ['title', 'require', '标题不能为空'],
+                ['name', 'require', '路径参数不能为空'],
+                ['content', 'require', '内容不能为空'],
+                ['order', 'require|integer|gt:0', '序列号不能为空|序列号必须为正整数'],
+            ]);
+            if (true !== $result) {
+                $this->error($result);
+            }
+
+            if ($this->isShowName($data['name'],$id)) {
+                $this->error('路径参数已经存在');
+            }
+
+            if ($this->aboutusModel->allowField(true)->save($data, ['id' => $id]) !== false ) {
+                $this->aboutusModel->delCache(); // 删除缓存
+                $this->success('编辑成功',url('index'));
+            }
+            $this->error($this->aboutusModel->getError() ?: '编辑失败');
+        }
+
+        $data = $this->aboutusModel->where('id',$id)->find();
+        $this->assign('data', $data);
+        return $this->fetch('edit_about');
+    }
+
+    public function getAboutusParam()
+    {
+        $data = [
+            'title'=> input('post.title'),
+            'name'=> input('post.name'),
+            'content'=> input('post.content'),
+            'order'=> input('post.order'),
+        ];
+        return $data;
+    }
+    /*
+     * 删除
+     */
+    public function delAboutus()
+    {
+        $id = $this->request->param('id', 0, 'intval');
+        if (empty($id) || !($data = $this->aboutusModel->find($id))) {
+            $this->error('参数错误,不存在你要删除的内容');
+        }
+        if ($data->delete()) {
+            $this->aboutusModel->delCache(); // 删除缓存
+            $this->success('删除成功!');
+        }
+        $this->error('删除失败');
+    }
+
+    // 判断路径参数是否存在
+    private function isShowName($name,$id = 0)
+    {
+        $condition = [
+            'name'  => $name,
+            'id'    => ['<>',$id],
+        ];
+
+        $count = $this->aboutusModel->where($condition)->count();
+
+        return $count ? true : false;
+    }
+}

+ 527 - 0
www/new_sdk/application/admin/controller/Admin.php

@@ -0,0 +1,527 @@
+<?php
+
+/**
+ * 后台的公共控制器类
+ *
+ */
+
+namespace app\admin\controller;
+
+use app\common\controller\Base;
+use app\common\logic\Websocket;
+use app\common\model\Admin as AdminModel;
+use think\Db;
+use think\Session;
+
+class Admin extends Base
+{
+    // 不验证权限的action
+    protected $noCheckAuth = [];
+
+    protected $admin_info;      //后台登录用户资料
+
+    protected $current_node;
+
+    /**
+     * 初始化操作
+     */
+    protected function _initialize()
+    {
+        parent::_initialize();
+        $session_admin_id = session('ADMIN_ID');
+        log_message('Admin@input: sid='.session_id() .' - '. json_encode(input()), 'log', LOG_PATH . 'adminlog/');
+
+        if (!empty($session_admin_id)) {
+            if (!$this->checkAccess($session_admin_id)) {
+                $this->error("您没有访问权限!");
+            }
+
+            // 登陆账户的session到期后,重置token
+            if (!session('WS_CONNECT_KEY')) {
+                $ws = new Websocket;
+                $ws->resettingToken(session_id(), $session_admin_id);
+            }
+        } else {
+            if ($this->request->isPost()) {
+                $this->error("您还没有登录!", '/login/index');
+            } else {
+                return $this->redirect('/login/index');
+            }
+        }
+
+        $this->current_node = strtolower($this->request->module() . '/' . $this->request->controller() . '/' . $this->request->action());
+    }
+
+    /**
+     *  检查后台用户访问权限
+     * @param int $userId 后台用户id
+     * @return boolean 检查通过返回true
+     */
+    private function checkAccess($userId)
+    {
+        // 如果用户id是1,则无需判断
+        if ($userId == 1) {
+            return true;
+        }
+
+        $module     = $this->request->module();
+        $controller = $this->request->controller();
+        $action     = $this->request->action();
+        $rule       = $module . $controller . $action;
+
+        // action检测
+        $actionCheck = in_array(strtolower($action),
+            array_map(function ($val){
+                return strtolower($val);
+                }, $this->noCheckAuth)
+        );
+
+        $notRequire = ["adminIndexindex", "adminMainindex"];
+        if (!in_array($rule, $notRequire) &&
+            ! $actionCheck) {
+            return cmf_auth_check($userId);
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 返回 json
+     * @access protected
+     * @param mixed $data 要返回的数据
+     * @param int $code 返回的 code
+     * @param mixed $msg 提示信息
+     * @return void
+     * @throws HttpResponseException
+     */
+    public function jsonResult($data, $code = 0, $msg = '')
+    {
+        $this->result($data, $code, $msg, 'json');
+    }
+
+    /**
+     * 初始化nw前缀 model
+     */
+    public function nwModel($dbName)
+    {
+        return Db::name('nw_' . $dbName);
+    }
+
+    /**
+     * 初始化cy前缀 model
+     */
+    public function cyModel($dbName)
+    {
+        return Db::name('cy_' . $dbName);
+    }
+
+    /**
+     * 发送短信验证码
+     */
+    public function sendSmsCode()
+    {
+        if (request()->isAjax()) {
+
+            $action = input('post.action', '', 'trim');
+
+            switch ($action) {
+                case 'coinSend':           //发币时短信验证
+
+                    $mobile = (new AdminModel)->where(['id' => session('ADMIN_ID')])->value('mobile');
+
+                    break;
+
+                default:
+
+                    $mobile = input('post.mobile', '', 'trim');
+
+                    break;
+            }
+
+            $result = (new \app\common\library\Sms)->sendCode($mobile);
+
+            if ($result['status']) {
+                $this->success('发送成功,请查收');
+            } else {
+                $this->error($result['msg']);
+            }
+        } else {
+            $this->error('非法请求');
+        }
+    }
+
+    protected function insertLog($node = '', $content = '', $type = 0, $admin_id = '', $username = '')
+    {
+        if (empty($admin_id)) {
+            $admin_id = session('ADMIN_ID') ?: 1;
+        }
+        if (empty($username)) {
+            $username = session('USERNAME') ?: '';
+        }
+        
+        return \app\common\logic\Log::insertOperateLog($node, $content, $type, $admin_id, $username);
+    }
+
+    /**
+     *  排序 排序字段为list_orders数组 POST 排序字段为:list_order
+     */
+    protected function listOrders($model ,$pk = 'id')
+    {
+        if (!is_object($model)) {
+            return false;
+        }
+
+        if ($pk !== 'id') {
+            $pk  = $model->pk; //获取主键名称
+        }
+
+        $ids = $this->request->post("list_orders/a");
+
+        if (!empty($ids)) {
+            foreach ($ids as $key => $r) {
+                $data['list_order'] = $r;
+                $model->where([$pk => $key])->update($data);
+            }
+
+        }
+
+        return true;
+    }
+	/**
+	 * 下载成exel表格
+	 *
+	 * @param array $list 数组
+	 *
+	 * @return null
+	 */
+	protected function downloadexls($list,$title,$type,$list_more=array()) {
+        Vendor('PHPExcel.PHPExcel');//调用类库,路径是基于vendor文件夹的
+        Vendor('PHPExcel.PHPExcel.Worksheet.Drawing');
+        Vendor('PHPExcel.PHPExcel.Writer.Excel2007');
+        $objExcel = new \PHPExcel();
+        //set document Property
+        $objWriter = \PHPExcel_IOFactory::createWriter($objExcel, 'Excel2007');
+
+		if ($type == 'finance') {
+            $objActSheet = $objExcel->getActiveSheet();
+            $key = ord("A");
+            $letter =explode(',',"A,B,C,D,E,F,G,H,I");
+            $arrHeader =  array('开始时间','结束时间','游戏名称','渠道(包括子渠道)','结算类型','补点起止时间','消费金额','补点(%)','补点金额');
+            //填充表头信息
+            $lenth =  count($arrHeader);
+            for($i = 0;$i < $lenth;$i++) {
+                $objActSheet->setCellValue("$letter[$i]1","$arrHeader[$i]");
+            };
+            //填充表格信息
+            foreach($list as $k=>$v){
+                $k +=2;
+                $objActSheet->setCellValue('A'.$k,$v['starttime']);
+                $objActSheet->setCellValue('B'.$k, $v['endtime']);
+                $objActSheet->setCellValue('C'.$k, $v['game_name']);
+                $objActSheet->setCellValue('D'.$k, $v['channel_name']);
+                $objActSheet->setCellValue('E'.$k, $v['settle_type']);
+                $objActSheet->setCellValue('F'.$k, $v['begin_date'].'~'.$v['end_date']);
+                $objActSheet->setCellValue('G'.$k, floatval($v['total_amount']));
+                $objActSheet->setCellValue('H'.$k, $v['point']);
+                $objActSheet->setCellValue('I'.$k, floatval($v['point_amt']));
+                // 表格高度
+                $objActSheet->getRowDimension($k)->setRowHeight(20);
+            }
+		}
+		else if($type=='queryChildChannel'){
+            $objActSheet = $objExcel->getActiveSheet();
+            $key = ord("A");
+            $letter =explode(',',"A,B");
+            $arrHeader =  array('计数','渠道名称');
+            //填充表头信息
+            $lenth =  count($arrHeader);
+            for($i = 0;$i < $lenth;$i++) {
+                $objActSheet->setCellValue("$letter[$i]1","$arrHeader[$i]");
+            };
+            //填充表格信息
+            foreach($list as $k=>$v){
+                $k +=2;
+                $objActSheet->setCellValue('A'.$k,$k-1);
+                $objActSheet->setCellValue('B'.$k, $v['name']);
+                // 表格高度
+                $objActSheet->getRowDimension($k)->setRowHeight(20);
+            }
+		}
+		else if($type=='listGame'){
+            $objActSheet = $objExcel->getActiveSheet();
+            $key = ord("A");
+            $letter =explode(',',"A,B,C,D,E,F");
+            $arrHeader =  array('游戏ID','游戏名称','游戏原名','渠道名称','创建时间','备注');
+            //填充表头信息
+            $lenth =  count($arrHeader);
+            for($i = 0;$i < $lenth;$i++) {
+                $objActSheet->setCellValue("$letter[$i]1","$arrHeader[$i]");
+            };
+            //填充表格信息
+            foreach($list as $k=>$v){
+                $k +=2;
+                $objActSheet->setCellValue('A'.$k, $v['game_id']);
+                $objActSheet->setCellValue('B'.$k, $v['game_name']);
+				$objActSheet->setCellValue('C'.$k, $v['origin_name']);
+                $objActSheet->setCellValue('D'.$k, $v['channel_name']);
+                $objActSheet->setCellValue('E'.$k, $v['create_time']);
+                $objActSheet->setCellValue('F'.$k, $v['remark']);
+                // 表格高度
+                $objActSheet->getRowDimension($k)->setRowHeight(20);
+            }
+
+		}
+		else if($type=='listGameFrozen'){
+			//聚合游戏冻结配置
+            $objActSheet = $objExcel->getActiveSheet();
+            $key = ord("A");
+            $letter =explode(',',"A,B,C,D,E,F,G,H,I,J,K,L");
+            $arrHeader =  array('游戏名称','游戏原名','预付状态','当前流水',"折扣", "折扣后流水", '祈盟分成点位','扣除预付款','禁止新增','禁止登录','禁止充值','时间');
+            //填充表头信息
+            $lenth =  count($arrHeader);
+            for($i = 0;$i < $lenth;$i++) {
+                $objActSheet->setCellValue("$letter[$i]1","$arrHeader[$i]");
+            };
+            //填充表格信息
+            foreach($list as $k=>$v){
+				$index = $k + 2;
+                $objActSheet->setCellValue('A'.$index, $v['game_name']);
+                $objActSheet->setCellValue('B'.$index, $v['game_name']);
+                $objActSheet->setCellValue('C'.$index, $v['status'] ? '开启' : '关闭');
+                $objActSheet->setCellValue('D'.$index, $v['total_amount']);
+                $objActSheet->setCellValue('E'.$index, $v['discount']??'--');
+                $objActSheet->setCellValue('F'.$index, $v['discount_amount']??0);
+                $objActSheet->setCellValue('G'.$index, $v['divide_point'] ?? '--');
+                $objActSheet->setCellValue('H'.$index, $v['divide_point'] ? $v['total_amount']*$v['divide_point']/10 : 0);
+                $objActSheet->setCellValue('I'.$index, $v['depf_register'] ? '是' : '否');
+                $objActSheet->setCellValue('J'.$index, $v['depf_login'] ? '是' : '否');
+                $objActSheet->setCellValue('K'.$index, $v['depf_consume'] ? '是' : '否');
+                $objActSheet->setCellValue('L'.$index, $v['create_time']);
+                // 表格高度
+                $objActSheet->getRowDimension($index)->setRowHeight(20);
+            }
+				
+			$index = $index+2;
+			$objActSheet->setCellValue('B' . $index, '预付状态未开启的游戏流水,不计入渠道总流水(实际总流水)。');
+			$index = $index+1;
+			$objActSheet->setCellValue('B' . $index, '总流水:');
+			$objActSheet->setCellValue('C' . $index, $list_more['count_total_amount']);
+			$index = $index+1;
+			$objActSheet->setCellValue('B' . $index, '实际总流水:');
+			$objActSheet->setCellValue('C' . $index, $list_more['count_real_amount']);
+			$index = $index+1;
+			$objActSheet->setCellValue('B' . $index, '累计预付款:');
+			$objActSheet->setCellValue('C' . $index, $list_more['total_advance']);
+			$index = $index+1;
+			$objActSheet->setCellValue('B' . $index, '当前余额:');
+			$objActSheet->setCellValue('C' . $index, $list_more['balance']);
+			$index = $index+1;
+			$objActSheet->setCellValue('B' . $index, '最后预付款:');
+			$objActSheet->setCellValue('C' . $index, $list_more['last_advance']);
+		}
+		else if($type=='channelWithdraw'){
+			//聚合游戏冻结配置
+            $objActSheet = $objExcel->getActiveSheet();
+            $key = ord("A");
+            $letter =explode(',',"A,B,C,D,E,F,G,H,I");
+            $arrHeader =  array('订单编号','公会名称','会长银行开户名','银行名称','会长银行开户行','会长银行卡号','会长支付宝','提现金额(元)','审核时间');
+            //填充表头信息
+            $lenth =  count($arrHeader);
+            for($i = 0;$i < $lenth;$i++) {
+                $objActSheet->setCellValue("$letter[$i]1","$arrHeader[$i]");
+            };
+            //填充表格信息
+            foreach($list as $k=>$v){
+				$index = $k + 2;
+                $objActSheet->setCellValueExplicit('A'.$index, $v['orderid'],'s');
+                $objActSheet->setCellValueExplicit('B'.$index, $v['channel_name'],'s');
+                $objActSheet->setCellValueExplicit('C'.$index, $v['real_name'],'s');
+                $objActSheet->setCellValueExplicit('D'.$index, $v['bank_name'],'s');
+                $objActSheet->setCellValueExplicit('E'.$index, $v['bank_open_name'],'s');
+                $objActSheet->setCellValueExplicit('F'.$index, $v['bank_number'],'s');
+                $objActSheet->setCellValueExplicit('G'.$index, $v['zfb_account'],'s');
+                $objActSheet->setCellValue('H'.$index, floatval($v['withdraw_amt']));
+                $objActSheet->setCellValue('I'.$index, date("Y-m-d H:i:s",$v['audit_time']));
+                // 表格高度
+                $objActSheet->getRowDimension($index)->setRowHeight(20);
+            }
+				
+			$index = $index+3;
+			$objActSheet->setCellValue('G' . $index, '汇总:');
+			$objActSheet->setCellValue('H' . $index, $list_more['total_withdraw_amt']);
+			$index = $index+3;
+			$objActSheet->setCellValue('A' . $index, '制表人:');
+			$objActSheet->setCellValue('C' . $index, '财务专员:');
+			$objActSheet->setCellValue('E' . $index, '财务经理:');
+		}
+		else if($type=='settleMonthData'){
+			//聚合游戏冻结配置
+            $objActSheet = $objExcel->getActiveSheet();
+            $key = ord("A");
+            $letter =explode(',',"A,B,C,D,E,F,G");
+            $arrHeader =  array('会长ID','会长账号','未结算订单总额','结算中订单总额','未提现余额','提现中总额','汇总金额');
+            //填充表头信息
+            $lenth =  count($arrHeader);
+            for($i = 0;$i < $lenth;$i++) {
+                $objActSheet->setCellValue("$letter[$i]1","$arrHeader[$i]");
+            };
+            //填充表格信息
+            foreach($list as $k=>$v){
+				$index = $k + 2;
+                $objActSheet->setCellValueExplicit('A'.$index, $v['channel_id'],'s');
+                $objActSheet->setCellValueExplicit('B'.$index, $v['channel_name'],'s');
+                $objActSheet->setCellValue('C'.$index, floatval($v['unsettle_amt']));
+                $objActSheet->setCellValue('D'.$index, floatval($v['settle_apply_amt']));
+                $objActSheet->setCellValue('E'.$index, floatval($v['unwithdraw_amt']));
+				$objActSheet->setCellValue('F'.$index, floatval($v['withdraw_apply_amt']));
+                $objActSheet->setCellValue('G'.$index, floatval($v['total_amt']));
+                // 表格高度
+                $objActSheet->getRowDimension($index)->setRowHeight(20);
+            }
+		}
+		else if($type=='channelExtraPoint'){
+			//聚合游戏冻结配置
+            $objActSheet = $objExcel->getActiveSheet();
+			$objActSheet->setCellValue('A1', '月补点汇总表');
+			$objActSheet->mergeCells('A1:K1');
+			$objActSheet->getStyle('A1:K1')->getAlignment()->setHorizontal('center');
+			$objActSheet->setCellValue('A2', '序列');
+			$objActSheet->mergeCells('A2'.':'.'A3');
+			$objActSheet->getStyle('A2:A3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('B2', '时间');
+			$objActSheet->mergeCells('B2'.':'.'B3');
+			$objActSheet->getStyle('B2:B3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('C2', '游戏名');
+			$objActSheet->mergeCells('C2'.':'.'C3');
+			$objActSheet->getStyle('C2:C3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('D2', '公会名');
+			$objActSheet->mergeCells('D2'.':'.'D3');
+			$objActSheet->getStyle('D2:D3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('E2', '充值流水');
+			$objActSheet->mergeCells('E2:G2');
+			$objActSheet->getStyle('E2:G2')->getAlignment()->setHorizontal('center');
+			$objActSheet->setCellValue('E3', '充值流水');
+			$objActSheet->setCellValue('F3', '点位(阶梯)');
+			$objActSheet->setCellValue('G3', '公会所得金额');
+			$objActSheet->setCellValue('H2', '收款名称');
+			$objActSheet->mergeCells('H2'.':'.'H3');
+			$objActSheet->getStyle('H2:H3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('I2', '支付宝账号');
+			$objActSheet->mergeCells('I2'.':'.'I3');
+			$objActSheet->getStyle('I2:I3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('J2', '银行账号');
+			$objActSheet->mergeCells('J2'.':'.'J3');
+			$objActSheet->getStyle('J2:J3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('K2', '银行名称');
+			$objActSheet->mergeCells('K2'.':'.'K3');
+			$objActSheet->getStyle('K2:K3')->getAlignment()->setVertical('center');
+			$objActSheet->setCellValue('L2', '开户行');
+			$objActSheet->mergeCells('L2'.':'.'L3');
+			$objActSheet->getStyle('L2:L3')->getAlignment()->setVertical('center');
+
+            //填充表格信息
+			$index = 4;
+			$channelRank = 1;
+			$totalPayAmt = $totalDivideAmt = 0;
+            foreach($list_more as $k=>$v){
+				$totalPayAmt += $v['channel_pay_amt'];
+				$totalDivideAmt += $v['channel_divide_amt'];
+				$channelCnt = $v['channel_game_cnt'];
+				$channelIndex = 1;
+				foreach($list[$v['channel_id']] as $k2=>$v2){
+					if($channelIndex==1){
+						$objActSheet->setCellValue('A'.$index, $channelRank);
+						$objActSheet->getStyle('A'.$index)->getAlignment()->setHorizontal('center');
+						$objActSheet->mergeCells('A'.$index.':A'.($index+$channelCnt));
+						$objActSheet->getStyle('A'.$index.':A'.($index+$channelCnt))->getAlignment()->setVertical('center');
+					}
+					$objActSheet->setCellValueExplicit('B'.$index, $v2['start_date'].' ~ '.$v2['end_date'],'s');
+					$objActSheet->setCellValueExplicit('C'.$index, $v2['game_name'],'s');
+					$objActSheet->setCellValueExplicit('D'.$index, $v2['channel_name'],'s');
+					$objActSheet->setCellValue('E'.$index, floatval($v2['pay_amt']));
+					$objActSheet->setCellValueExplicit('F'.$index, $v2['rate']."%",'s');
+					$objActSheet->getStyle('F'.$index)->getAlignment()->setHorizontal('center');
+					$objActSheet->setCellValue('G'.$index, floatval($v2['divide_amt']));
+					
+					if($channelIndex==1){
+						$objActSheet->setCellValueExplicit('H'.$index, $v2['real_name'],'s');
+						$objActSheet->mergeCells('H'.$index.':H'.($index+$channelCnt-1));
+						$objActSheet->getStyle('H'.$index.':H'.($index+$channelCnt-1))->getAlignment()->setVertical('center');
+
+						$objActSheet->setCellValueExplicit('I'.$index, $v2['zfb_account'],'s');
+						$objActSheet->mergeCells('I'.$index.':I'.($index+$channelCnt-1));
+						$objActSheet->getStyle('I'.$index.':I'.($index+$channelCnt-1))->getAlignment()->setVertical('center');
+
+						$objActSheet->setCellValueExplicit('J'.$index, $v2['bank_number'],'s');
+						$objActSheet->mergeCells('J'.$index.':J'.($index+$channelCnt-1));
+						$objActSheet->getStyle('J'.$index.':J'.($index+$channelCnt-1))->getAlignment()->setVertical('center');
+
+						$objActSheet->setCellValueExplicit('K'.$index, $v2['bank_name'],'s');
+						$objActSheet->mergeCells('K'.$index.':K'.($index+$channelCnt-1));
+						$objActSheet->getStyle('K'.$index.':K'.($index+$channelCnt-1))->getAlignment()->setVertical('center');
+
+						$objActSheet->setCellValueExplicit('L'.$index, $v2['bank_open_name'],'s');
+						$objActSheet->mergeCells('L'.$index.':L'.($index+$channelCnt-1));
+						$objActSheet->getStyle('L'.$index.':L'.($index+$channelCnt-1))->getAlignment()->setVertical('center');
+
+						$objActSheet->getRowDimension($k)->setRowHeight(20);
+					}
+					$index++;
+					$channelIndex++;
+				}
+				$objActSheet->setCellValueExplicit('B'.$index, '合计','s');
+				$objActSheet->mergeCells('B'.$index.':D'.$index);
+				$objActSheet->getStyle('B'.$index.':D'.$index)->getAlignment()->setHorizontal('center');
+
+				$objActSheet->setCellValue('E'.$index, $v['channel_pay_amt']);
+				$objActSheet->setCellValueExplicit('F'.$index, '-','s');
+				$objActSheet->setCellValue('G'.$index, $v['channel_divide_amt']);
+				$objActSheet->setCellValueExplicit('H'.$index, '','s');
+				$objActSheet->setCellValueExplicit('I'.$index, '','s');
+				$objActSheet->setCellValueExplicit('J'.$index, '','s');
+				$objActSheet->setCellValueExplicit('K'.$index, '','s');
+				$objActSheet->getRowDimension($k)->setRowHeight(20);
+				$index++;
+				$channelRank++;
+            }
+				
+			$objActSheet->setCellValueExplicit('A' . $index, '', 's');
+			$objActSheet->setCellValueExplicit('B'.$index, '总计','s');
+			$objActSheet->mergeCells('B'.$index.':D'.$index);
+			$objActSheet->getStyle('B'.$index.':D'.$index)->getAlignment()->setHorizontal('center');
+			$objActSheet->setCellValue('E' . $index, $totalPayAmt);
+			$objActSheet->setCellValueExplicit('F' . $index, '','s');
+			$objActSheet->setCellValue('G' . $index, $totalDivideAmt);
+		}
+
+		if(trim($title)){
+			$outfile = trim($title).".xls";
+		}
+		else{
+			$outfile = date('YmdHis').".xls";
+		}
+		try {
+			if (ob_get_level() > 0) {
+				ob_end_clean();
+			}
+			header("Content-Type: application/force-download");
+			header("Content-Type: application/octet-stream");
+			header("Content-Type: application/download");
+			header('Content-Disposition:inline;filename="'.urlencode($outfile).'"');
+			header("Content-Transfer-Encoding: binary");
+			header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
+			header("Pragma: no-cache");
+			$objWriter->save('php://output');
+		} catch (\Exception $e) {
+			// log_message('Export error: ' . $e->getMessage(), 'error', LOG_PATH . 'export/');
+			$this->error('导出失败:' . $e->getMessage());
+		}
+		exit;
+	}
+
+    // 空方法
+    public function _empty()
+    {
+        return $this->redirect('/index/index');
+    }
+}

+ 136 - 0
www/new_sdk/application/admin/controller/Appkey.php

@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * SDK密钥管理
+ * Date: 2018/1/22
+ * Time: 10:18
+ */
+
+namespace app\admin\controller;
+
+
+use think\Loader;
+use think\Db;
+class Appkey extends Admin
+{
+    protected $gameList;
+
+    public function _initialize()
+    {
+        parent::_initialize(); // TODO: Change the autogenerated stub
+
+        $this->gameList = $gameList = model('Common/Game')->getAllByCondition('id,name');
+
+        $tmpSelfGameList = model('Common/Game')->getAllByCondition('id,name', [],'','self');
+		$selfGameList = array();
+		foreach ($tmpSelfGameList as $game) {
+              $selfGameList[ $game['id']] = $game;
+        }
+		$this->selfGameList  = $selfGameList;
+    }
+
+    /**
+     *SDK密钥列表
+     */
+    public function index()
+    {
+        $condition = [];
+        $gameId    = $this->request->param('gameid', '', 'intval');
+        if (!empty($gameId)) {
+            $condition ['gameid'] = $gameId;
+
+        }
+        $appList = model('Common/App')->alias('a')->join('cy_game g', 'a.gameid=g.id', 'left')->where($condition)->where("g.is_default = 0")->field('a.id,a.appid,a.app_package,a.appkey,a.client_appkey,a.create_time,a.beizhu,a.gameid,g.is_default')->order('id desc')->paginate(20);
+        $data     = $appList->toArray();
+        $gameid   = array_column($data['data'], 'gameid');
+        $gameid   = array_unique($gameid);
+        $gameName = model('game')->whereIn('id', $gameid)->column('id,name');
+
+        $this->assign('game_list', $this->selfGameList);
+        $this->assign('game_name', $gameName);
+        $this->assign('appList', $appList);
+        $this->assign('page', $appList->render());
+        return $this->fetch();
+    }
+
+
+    /**
+     *增
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->post();
+            if (true !== ($res = $this->validate($data, 'appkey'))) {
+                $this->error($res);
+            }
+
+            $data['appkey']         = md5($data['appid'] . $data['gameid'] . time());
+            $data['client_appkey']  = md5(random(10).$data['appid']);       //客户端appkey
+//
+//            if (!$gameInfo = model("game")->field('initial')->where(['id' => $data['gameid']])->find()) {
+//                $this->error('游戏信息不存在');
+//            }
+//            $data['appid'] = $gameInfo['initial'];
+            $appModel = model('Common/App');
+            if ($appModel->save($data)) {
+                $this->success('添加成功',url('index'));
+            }
+            $this->error($appModel->getError() ?: '添加失败');
+        }
+        $this->assign('game_list', $this->selfGameList);
+        return $this->fetch();
+    }
+
+
+    /**
+     *删
+     */
+    public function delete()
+    {
+        $id = $this->request->param('id', '', 'intval');
+        if (empty($id)) {
+            $this->error('参数错误!');
+        }
+        $appModel = model('Common/App');
+        if (!$appModel->find($id)) {
+            $this->error('参数错误,不存在该数据');
+        }
+        if ($appModel->where('id', '=', $id)->delete()) {
+            $this->success('删除成功',url('index'));
+        }
+        $error = $appModel->getError();
+        $this->error($error ?: '删除失败');
+    }
+
+
+    /**
+     *编辑SDK密钥
+     */
+    public function edit()
+    {
+        $id       = $this->request->param('id', '', 'intval');
+        $appModel = model('Common/App');
+        if (!$data = $appModel->find($id)) {
+            $this->error('参数错误,不存在该数据');
+        }
+        if (empty($id)) {
+            $this->error('参数错误!');
+        }
+        if ($this->request->isPost()) {
+            $data           = $this->request->post();
+            $appKeyValidate = Loader::validate('appkey');
+            if (!$res = $appKeyValidate->check($data)) {
+                $this->error($appKeyValidate->getError());
+            }
+            if ($appModel->save($data, ['id' => $id]) !== false) {
+                $this->success('修改成功',url('index'));
+            }
+            $this->error($appModel->getError() ?: '修改失败');
+        }
+        $this->assign('game_list', $this->selfGameList);
+        $this->assign('data', $data);
+        return $this->fetch();
+    }
+
+}

+ 462 - 0
www/new_sdk/application/admin/controller/Article.php

@@ -0,0 +1,462 @@
+<?php
+/**
+ * 新闻列表控制器
+ */
+namespace app\admin\controller;
+
+use think\Db;
+use app\common\library\FileUpload;
+use think\Config;
+
+class Article extends Admin
+{
+    protected function _initialize()
+    {
+        parent::_initialize();
+        $this->articleModel = Db::name('cy_article');
+        $this->contentModel = Db::name('cy_content');
+    }
+
+
+    public function index()
+    {
+        $where = [];
+
+        $start_time = input('request.start_time');
+        
+        // 类型
+        if (input('request.type') != '') {
+            $where['a.type'] = ['=', input('request.type')];
+        }
+
+        // 标题
+        if (input('request.title') != '') {
+            $where['a.title'] = ['like', '%' . input('request.title') .'%'];
+        }
+
+        // 显示位置
+        if (input('request.position') != '') {
+            $where['a.position'] = ['like', '%' . input('request.position') .'%'];
+        }
+        
+        
+        // 游戏
+        if (input('request.gameid') != '') {
+            $where['a.gameid'] = input('request.gameid');
+        }
+        
+        //开始时间和结束时间不为空时
+        if ($start_time != '' && input('request.end_time') != '') {
+            $where['a.create_time'] = [
+                ['>=', strtotime($start_time)],
+                ['<=', strtotime(input('request.end_time').' 23:59:59')],
+            ];
+        } //开始时间不为空时
+        elseif ($start_time!= '') {
+            $where['a.create_time'] = ['>=', strtotime($start_time)];
+        } //结束时间不为空时
+        elseif (input('request.end_time') != '') {
+            $where['a.create_time'] = ['<=', strtotime(input('request.end_time').' 23:59:59')];
+        }
+
+        $where['a.isdelete'] = 0;
+
+        $param = input('get.');
+
+        $article= $this->articleModel
+            ->alias('a')
+            ->join('cy_game g', 'a.gameid=g.id', 'left')
+            ->field('a.*,g.id as gameid2,g.name as game_name')
+            ->where($where)
+            ->order('a.id desc')->paginate(10, false, array('query' => $param));
+
+        $gameList = Db::name('cy_game')->field('id,name')->order('name asc')->select();
+
+        $this->assign('game_list', $gameList);
+        $this->assign('article', $article);
+        $this->assign('page', $article->render());
+
+        return $this->fetch();
+    }
+
+
+    public function add()
+    {
+        if (request()->isPost()) {
+            
+            $fileUpload = new FileUpload();
+            
+            //新闻图片
+            $image_url      = $fileUpload->set('allowExt', 'jpg,jpeg,png,gif')->set('maxsize', 1024000)->set('dir','image/news/pic/')->upload(request()->file('image'));
+            $image_error    = $fileUpload->getError();
+            
+            $data = [
+                'title'         => input('post.title'),
+                'gameid'        => input('post.gameid',0,'int'),
+                'zhiding'       => input('post.top'),
+                'type'          => input('post.type'),
+                'content'       => input('post.content'),
+                'create_time'   => NOW_TIMESTAMP
+            ];
+
+            $result = $this->validate($data, [
+                ['title', 'require|max:100', '标题不能为空|标题不能超过100个字符'],
+                ['gameid', 'require', '游戏不能为空'],
+                ['zhiding', 'require', '是否置顶不能为空'],
+                ['type', 'require|integer', '类型不能为空|类型必须为整型'],
+                ['content', 'require', '内容不能为空'],
+            ]);
+
+            if (true !== $result) {
+                $this->error($result);
+            } 
+            //新闻图片上传出错时
+            elseif(!empty(request()->file('image')) && empty($image_url))
+            {
+                $this->error('新闻图片'.$image_error);
+            }
+            else {
+                unset($data['content']);
+                
+                if ( !empty(input('post.position/a'))) {
+                    $data['position'] = implode('',input('post.position/a'));
+                }
+                
+                //有上传新闻图片时
+                if(!empty($image_url))
+                    $data['image'] = $image_url;
+
+				if(in_array($data['type'],[1,2,3,4,5]) && empty($image_url)){
+					$this->error('请选择新闻图片');
+				}
+                
+                $id = $this->articleModel->insertGetId($data);
+
+                if (empty($id)) {
+                    $this->error('添加失败!');
+                }
+                
+                $this->contentModel->insert(['id'=>$id,'content'=>input('post.content')]);
+
+                $this->success('新闻添加成功!', 'article/index');
+            }
+        }
+
+        $gameList = Db::name('cy_game')->field('id,name')->order('name asc')->select();
+
+        $this->assign('game_list', $gameList);
+        return $this->fetch();
+    }
+
+
+    public function edit($id)
+    {
+        $id = (int)$id;
+
+        if (empty($id)) {
+            $this->error('资讯ID不能为空');
+        }
+
+        $articleInfo = $this->articleModel->where(['id' => $id])->find();
+        if (empty($articleInfo)) {
+            $this->error('资讯信息不能为空! ');
+        }
+
+        if (request()->isPost()) {
+            
+            $fileUpload = new FileUpload();
+            
+            //新闻图片
+            $image_url      = $fileUpload->set('allowExt', 'jpg,jpeg,png,gif')->set('maxsize', 1024000)->set('dir','image/news/pic')->upload(request()->file('image'));
+            $image_error    = $fileUpload->getError();
+            
+            $data = [
+                'title'         => input('post.title'),
+                'gameid'        => input('post.gameid',0,'int'),
+                'zhiding'       => input('post.top'),
+                'type'          => input('post.type'),
+                'content'       => input('post.content'),
+            ];
+
+            $result = $this->validate($data, [
+                ['title', 'require|max:100', '标题不能为空|标题不能超过100个字符'],
+                ['gameid', 'require', '游戏不能为空'],
+                ['zhiding', 'require', '是否置顶不能为空'],
+                ['type', 'require|integer', '类型不能为空|类型必须为整型'],
+                ['content', 'require', '内容不能为空'],
+            ]);
+
+            if (true !== $result) {
+                $this->error($result);
+            } 
+            //新闻图片上传出错时
+            elseif(!empty(request()->file('image')) && empty($image_url))
+            {
+                $this->error('新闻图片'.$image_error);
+            }
+            else {
+                unset($data['content']);
+                
+                if ( !empty(input('post.position/a'))) {
+                    $data['position'] = implode('',input('post.position/a'));
+                }else{
+                    $data['position'] = null;
+                }
+                
+                //有上传新闻图片时
+                if(!empty($image_url))
+                    $data['image'] = $image_url;
+                
+				if(in_array($data['type'],[1,2,3,4,5]) && empty($image_url) && !$articleInfo['image']){
+					$this->error('请选择新闻图片');
+				}
+
+                $this->articleModel->where(['id'=>$id])->update($data);
+                
+                $this->contentModel->where(['id'=>$id])->update(['content'=>input('post.content')]);
+
+                model('GameActivityRead')->where('game_article_id',$id)->delete();
+
+                $this->success('新闻编辑成功!', 'article/index');
+            }
+        }
+        
+        $content = $this->contentModel->where(['id' => $id])->value('content');
+
+        $gameList = Db::name('cy_game')->field('id,name')->order('name asc')->select();
+
+        $this->assign('articleInfo', $articleInfo);
+        $this->assign('content', $content);
+        $this->assign('game_list', $gameList);
+
+        return $this->fetch();
+    }
+
+
+    public function del()
+    {
+        $id = input('id', 0, 'intval');
+        if(empty($id)) $this->error('记录ID不能为空');
+
+        $ret = $this->articleModel->where([
+            'id' => $id,
+        ])->setField('isdelete', 1);
+
+        if (!$ret) {
+            $this->error('删除错误!');
+        }
+
+        $this->success('删除成功', url('index'));
+    }
+
+    /**
+     * 微信公众号-文章列表
+     */
+    public function wxArticleList(){
+
+        $where = [];
+        // 标题
+        if (input('request.title') != '') {
+            $where['title'] = ['like', '%' . input('request.title') .'%'];
+        }
+
+        $start          = $this->request->param('start_time');
+        $end            = $this->request->param('end_time');
+        //开始时间和结束时间不为空时
+        if ($start != '' && $end != '') {
+            $where['create_time'] = [
+                ['>=', strtotime($start)],
+                ['<=', strtotime($end . ' 23:59:59')],
+            ];
+        } //开始时间不为空时
+        elseif ($start != '') {
+            $where['create_time'] = ['>=', strtotime($start)];
+        } //结束时间不为空时
+        elseif ($end != '') {
+            $where['create_time'] = ['<=', strtotime($end . ' 23:59:59')];
+        }
+
+        $wechatDb = Config::get('db_config_wechat.database'); // 微信公众号数据库
+
+
+        $where['is_del'] = 0;
+
+        $infoList = Db::table($wechatDb . '.nw_wx_article')
+            ->field('*')
+            ->where($where)
+            ->order('create_time', 'desc')
+            ->paginate(10, false, ['query' => input('get.')]);
+
+        $this->assign('article', $infoList);
+        $this->assign('page', $infoList->render());
+        $this->assign('wxlink',Config::get('WX_ARTICLE_LINK'));
+
+        return $this->fetch('wx_article_list');
+    }
+
+    /**
+     * 微信公众号-文章 添加
+     */
+    public function wxArticleAdd(){
+        if (request()->isPost()){
+            $fileUpload = new FileUpload();
+
+            //新闻图片
+            $image_url      = $fileUpload->set('allowExt', 'jpg,jpeg,png,gif')->set('maxsize', 1024000 *3)->set('dir','image/wechat/pic/')->upload(request()->file('image'));
+            $image_error    = $fileUpload->getError();
+
+            $data = [
+                'title'         => input('post.title'),
+                'releaser'         => input('post.releaser') ? input('post.releaser') : '祈盟网络游戏',
+                'content'       => input('post.content'),
+                'create_time'   => NOW_TIMESTAMP
+            ];
+
+            $result = $this->validate($data, [
+                ['title', 'require', '标题不能为空'],
+                ['releaser', 'require', '发布人不能为空'],
+                ['content', 'require', '内容不能为空'],
+            ]);
+
+            if (true !== $result) {
+                $this->error($result);
+            }
+            //新闻图片上传出错时
+            elseif(!empty(request()->file('image')) && empty($image_url))
+            {
+                $this->error('新闻图片'.$image_error);
+            }
+            //有上传新闻图片时
+            if(!empty($image_url))
+                $data['image'] = $image_url;
+
+            $wechatDb = Config::get('db_config_wechat.database'); // 微信公众号数据库
+
+
+            $shortLink = strtolower(random(8));
+
+            while (Db::table($wechatDb.'.nw_wx_article')->field('id')->where(['short_link' => $shortLink])->find()) {
+                $shortLink = strtolower(random(8));
+            }
+            $data['short_link'] = $shortLink;
+
+            Db::table($wechatDb.'.nw_wx_article')->insert($data);
+
+            $this->success('文章添加成功!', 'article/wxArticleList');
+        }
+
+        return $this->fetch('wx_article_add');
+    }
+
+    /**
+     * 微信公众号-文章 删除
+     */
+    public function wxArticleDel()
+    {
+        $id         = input('id' , 0 , 'intval');
+        $isRelation = input('check' , '' , 'trim');
+        if(empty($id)) $this->error('记录ID不能为空');
+
+        $wechatDb = Config::get('db_config_wechat.database'); // 微信公众号数据库
+
+        // 检查是否有关联分享礼包
+        if ($isRelation == 'isRelation'){
+
+            $isExsit = Db::table($wechatDb . '.nw_wx_sharelibao')->where(['article_id' => $id,'is_del'=>0])->find();
+
+            if (!empty($isExsit)) {
+                $msg = '该文章已关联分享礼包“'.$isExsit['name'].'”,删除后该礼包也将被自动删除,是否确认删除?';
+            }else{
+                $msg = '该文章未关联分享礼包,是否确认删除?';
+            }
+            $this->success($msg);
+        }
+
+        if ($isRelation != 'sureDel')  $this->error('参数错误!');
+
+        Db::startTrans();
+        try{
+
+            Db::table($wechatDb . '.nw_wx_article')->where(['id' => $id])->setField('is_del', 1);
+            Db::table($wechatDb . '.nw_wx_sharelibao')->where(['article_id' => $id])->setField('is_del', 1);
+
+            Db::commit();
+
+        }catch (\Exception $e) {
+            // 回滚事务
+            Db::rollback();
+
+            $this->error('操作失败:'.$e->getMessage());
+        }
+
+        $this->success('删除成功', url('wxArticleList'));
+        
+    }
+
+    /**
+     * 微信公众号-文章 编辑
+     */
+    public function wxArticleEdit()
+    {
+        $id = input('id', 0, 'intval');
+
+        if (empty($id)) {
+            $this->error('ID不能为空');
+        }
+        $wechatDb = Config::get('db_config_wechat.database'); // 微信公众号数据库
+
+        $articleInfo = Db::table($wechatDb . '.nw_wx_article')->where(['id' => $id])->find();
+        if (empty($articleInfo)) {
+            $this->error('资讯信息不能为空! ');
+        }
+
+        if (request()->isPost()) {
+
+            $fileUpload = new FileUpload();
+
+            //新闻图片
+            $image_url      = $fileUpload->set('allowExt', 'jpg,jpeg,png,gif')->set('maxsize', 1024000 *3)->set('dir','image/news/pic/')->upload(request()->file('image'));
+            $image_error    = $fileUpload->getError();
+
+
+            $data = [
+                'title'         => input('post.title'),
+                'releaser'       => input('post.releaser'),
+                'content'       => input('post.content'),
+                'update_time'       => time(),
+                'update_id'       => session('ADMIN_ID'),
+            ];
+
+            $result = $this->validate($data, [
+                ['title', 'require', '标题不能为空'],
+                ['content', 'require', '内容不能为空']
+            ]);
+
+            if (true !== $result) {
+                $this->error($result);
+            }//新闻图片上传出错时
+            elseif(!empty(request()->file('image')) && empty($image_url))
+            {
+                $this->error('新闻图片'.$image_error);
+            }
+
+            $addIMG = input('post.addIMG');
+            if ($addIMG == 2){
+                $data['image'] = '';
+            }
+
+            //有上传新闻图片时
+            if(!empty($image_url) && $addIMG == 1)
+                $data['image'] = $image_url;
+
+            Db::table($wechatDb . '.nw_wx_article')->where(['id'=>$id])->update($data);
+
+            $this->success('编辑成功!', 'article/wxArticleList');
+        }
+
+        $this->assign('info', $articleInfo);
+        return $this->fetch('wx_article_edit');
+    }
+
+
+}

+ 80 - 0
www/new_sdk/application/admin/controller/Autopack.php

@@ -0,0 +1,80 @@
+<?php
+/**
+ * 聚合sdk自动打包脚本获取客户端参数
+ * User: Administrator
+ * Date: 2020/4/3
+ * Time: 17:32
+ */
+
+namespace app\admin\controller;
+
+use think\Request;
+use app\common\model\Polychannel;
+use app\common\model\PolychannelGame;
+
+class Autopack extends Admin{
+
+    protected $_gameKey = 'ab6D4b2eMjfKfI5A76DFbSflYH9VxCEuQ6Q';
+    protected $_debug   = false;
+
+    public function _initialize()
+    {
+
+    }
+
+    /**
+     * 根据游戏拼音获取游戏信息
+     */
+    public function getGameInfo()
+    {
+        if (!$this->request->isPost()) $this->jsonResult('',0,'请求发生错误');;
+
+        $param = Request::instance()->param();
+        if ( !isset($param['pinyin']) || !isset($param['channel']) || !isset($param['sign'])) {
+            $this->jsonResult('',0,'参数错误');
+        }
+
+
+        $sign = md5(strtolower($param['channel']. $param['pinyin'] . $this->_gameKey));
+        if ($sign != $param['sign']) {
+            $this->jsonResult('',0,'参数不合法');
+        }
+
+        if (!$this->_debug){
+            $polychannelGameModel = new PolychannelGame;
+            $polychannelModel     = new Polychannel;
+
+            $info = $polychannelGameModel->table('cy_polychannel_game cg,nw_channel c,cy_game g')
+                ->field('cg.*,g.name game_name,c.name channel_name')
+                ->where('cg.channel_id=c.id and g.id=cg.game_id')
+                ->where(['g.pinyin'=>$param['pinyin'],'c.mark'=>$param['channel']])
+                ->find();
+
+            if(empty($info)){
+                $this->jsonResult('',0,'聚合渠道游戏信息不存在');
+            }
+
+            //参数字段
+            $param_field_arr = $polychannelModel->field('field_client')->where(['channel_id'=>$info['channel_id']])->find();
+            if (empty($param_field_arr)) {
+                $this->jsonResult('',0,'游戏信息不存在');
+            }
+
+
+            $field_client = $param_field_arr['field_client'];
+            $field_client = (!empty($field_client) ?  explode(';',$field_client) : $field_client);
+            $arrParamClient = (!empty($info['param_client']) ?  unserialize($info['param_client']) : $info['param_client']);
+        }else{
+            $field_client = 'channel;gameid;sign';
+            $field_client = (!empty($field_client) ?  explode(';',$field_client) : $field_client);
+            $arrParamClient = [$param['channel'],$param['pinyin'],$param['sign']];
+        }
+
+        $data = [];
+        foreach ($field_client as $k =>$v){
+            $data[$v] = $arrParamClient[$k];
+        }
+
+        $this->jsonResult($data,1);
+    }
+}

+ 69 - 0
www/new_sdk/application/admin/controller/Autoreply.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace app\admin\controller;
+
+use think\Config;
+use think\Db;
+
+
+class Autoreply extends Admin
+{
+
+    const DEFAULTMSG = 'defaultmsg'; // 收到消息未匹配关键词时回复
+    const SUBSCRIBE  = 'subscribe';  // 关注回复
+
+    protected function _initialize()
+    {
+
+        $this->model = model('WxAutoreplyMsg');
+
+        parent::_initialize();
+    }
+
+    public function index()
+    {
+
+        $defaultmsg = $this->model->where('reply_type',self::DEFAULTMSG)->value('content');
+        $subscribe = $this->model->where('reply_type',self::SUBSCRIBE)->value('content');
+        $this->assign('defaultmsg',$defaultmsg);
+        $this->assign('subscribe',$subscribe);
+
+        return $this->fetch();
+    }
+
+
+    public function add()
+    {
+        if ( request()->isPost()) {
+            $post = input('post.');
+
+            // 判断是否新增
+            $info = $this->model->where('reply_type',$post['reply_type'])->find();
+
+            $data = [
+                'content' => $post[$post['reply_type']]
+            ];
+
+            if ( empty($info)) {
+                $data['reply_type'] = $post['reply_type'];
+                $result = $this->model->insert($data);
+            }else{
+                $result = $this->model->where('reply_type',$post['reply_type'])->update($data);
+            }
+
+            if ( $result !== false) {
+                $this->success('更新成功');
+            }else{
+                $this->error('更新失败');
+            }
+
+        }
+
+    }
+
+
+
+
+
+
+}

+ 163 - 0
www/new_sdk/application/admin/controller/Bind.php

@@ -0,0 +1,163 @@
+<?php
+
+namespace app\admin\controller;
+
+class Bind extends Admin
+{
+    public function index()
+    {
+        $where = [];
+
+
+        $orderid = $this->request->get('orderid', '', 'trim');
+
+        if (isset($orderid) && !empty($orderid)) {
+            $where['orderid'] = $orderid;
+        }
+        $order = 'id desc';
+
+        $couponModel = model('Bind');
+        $list = $couponModel
+            ->where($where)
+            ->order($order)
+            ->paginate(10, false, ['query' => input('get.')]);
+
+        $this->assign('list', $list);
+        $this->assign('page', $list->render());
+        return $this->fetch();
+    }
+
+    public function add()
+    {
+
+        if ($this->request->isPost()) {
+            $data = $this->request->post();
+
+            $ids = explode("\r\n", $data['ids']);
+            $ids = array_map('filterAndTrimInput', $ids);
+            $ids = array_unique(array_filter($ids));
+            $pay = model('Pay')->alias('a')->whereIn('a.orderid', $ids)
+                ->join('nw_game_channel_divide_settle b', 'a.settle_id=b.id', 'left')
+                ->where('a.channel_id', $data['old_channel_id'])
+                ->where('a.status', 1)->column('a.id,a.channel_id,a.settle_id,a.orderid,b.first_audit_status,b.second_audit_status', 'a.orderid');
+
+            if (!$channel = model('Channel')->where('id', $data['new_channel_id'])->where('level', 3)->where('status', 1)->find()) {
+                $this->error('记录不存在');
+            }
+
+
+            $arr = [];
+            $orderids = [];
+            foreach ($ids as $k => $v) {
+                if (isset($pay[$v])) {
+                    if ($pay[$v]['settle_id'] > 0) {
+                        if ($pay[$v]['first_audit_status'] == 1 && $pay[$v]['second_audit_status'] == 1) {
+                            $arr[] = [
+                                'orderid' => $v,
+                                'name' => '',
+                                'state' => '<span style="color: red">订单已结算无法换绑</span>'
+                            ];
+                        } else {
+                            $arr[] = [
+                                'orderid' => $v,
+                                'name' => '',
+                                'state' => '<span style="color: red">订单已申请结算,请先拒绝结算后再换绑</span>'
+                            ];
+                        }
+                    } else {
+                        $orderids[] = $v;
+                        $arr[] = [
+                            'orderid' => $v,
+                            'name' => $channel['name'],
+                            'state' => '<span style="color: #0bb20c">可换绑</span>'
+                        ];
+                    }
+
+                } else {
+                    $arr[] = [
+                        'orderid' => $v,
+                        'name' => '',
+                        'state' => '<span style="color: red">订单不存在</span>'
+                    ];
+                }
+            }
+
+            $this->assign('orderids', implode(',', $orderids));
+            $this->assign('channel', $channel);
+            $this->assign('list', $arr);
+            $this->assign('old_channel_id', $data['old_channel_id']);
+            return $this->fetch('confirm');
+        }
+
+        $channel = model('Channel')->where('level', 3)->where('status', 1)
+            ->field('id,name')
+            ->order('id desc')->select();
+        $this->assign('channel', $channel);
+        return $this->fetch();
+    }
+
+
+    public function confirm()
+    {
+        $data = $this->request->post();
+
+        if (!$data['orderids']) {
+            $this->error('订单不存在');
+        }
+        if (!$data['new_channel_id']) {
+            $this->error('新推广关系不存在');
+        }
+
+        $channel = model('Channel')->where('id', $data['new_channel_id'])->where('level', 3)->where('status', 1)->find();
+        if (!$channel) {
+            $this->error('推广关系不存在');
+        }
+        $old_channel = model('Channel')->where('id', $data['old_channel_id'])->where('level', 3)->where('status', 1)->find();
+        if (!$old_channel) {
+            $this->error('推广关系不存在');
+        }
+
+        $orderids = array_map('filterAndTrimInput', explode(',', $data['orderids']));
+        $orderids = array_unique(array_filter($orderids));
+        $pay = model('Pay')->alias('a')->whereIn('a.orderid', $orderids)
+            ->where('a.channel_id', $data['old_channel_id'])
+            ->where('a.status', 1)->column('a.id,a.channel_id,a.settle_id,a.orderid', 'a.orderid');
+
+        if (!$pay) {
+            $this->error('订单不存在');
+        }
+        $bind = [];
+        $bind_order = [];
+        foreach ($pay as $k => $v) {
+            if ($v['settle_id'] == 0) {
+                $bind_order[] = $v['orderid'];
+                $bind[] = [
+                    'orderid' => $v['orderid'],
+                    'old_channel_id' => $v['channel_id'],
+                    'new_channel_id' => $data['new_channel_id'],
+                    'old_channel_name' => $old_channel['name'],
+                    'new_channel_name' => $channel['name'],
+                    'create_time' => time(),
+                    'admin_name' => session('USERNAME')
+                ];
+            }
+        }
+        if(!$bind_order){
+            $this->error('订单不存在');
+        }
+
+        model('Common/Pay')->startTrans();
+        if (!model('Common/Pay')->whereIn('orderid', $bind_order)
+            ->where('channel_id', $data['old_channel_id'])->update(['channel_id' => $data['new_channel_id']])) {
+            model('Common/Pay')->rollback();
+            $this->error('绑定失败');
+        }
+        if (model('common/Bind')->insertAll($bind)) {
+            model('Common/Pay')->commit();
+            $this->success('绑定成功', url('index'));
+        } else {
+            model('Common/Pay')->rollback();
+            $this->error('绑定失败');
+        }
+    }
+}

+ 129 - 0
www/new_sdk/application/admin/controller/Business.php

@@ -0,0 +1,129 @@
+<?php
+
+namespace app\admin\controller;
+
+
+use think\Loader;
+use think\Db;
+
+class Business extends Admin
+{
+    protected $gameList;
+
+    public function _initialize()
+    {
+        parent::_initialize(); // TODO: Change the autogenerated stub
+
+    }
+    public function index(){
+        $where = [];
+        $order = 'id desc';
+
+        $adminList = model('common/Admin')->alias('a')->join('nw_admin_role_user b','a.id=b.user_id')->field('a.id,a.username')->where(['b.role_id'=>['in',[12,17,18]]])->order('a.id desc')->column('a.username','a.id');
+        $channelList = model('common/Channel')->where(['level'=>0])->order('id desc')->column('name','id');
+        $business = model('common/Business');
+        $list = $business
+            ->order($order)
+            ->paginate(10, false, ['query' => input('get.')])->each(function($item,$key) use ($channelList){
+
+                $channel = explode(',',$item['channel_ids']);
+                $tmp = [];
+                foreach ($channel as $k=>$v){
+                    $tmp[] = $channelList[$v];
+                }
+                $item['channel'] = implode(',',$tmp);
+                return $item;
+        });
+
+        $this->assign('adminList', $adminList);
+        $this->assign('list', $list);
+        $this->assign('page', $list->render());
+        return $this->fetch();
+    }
+
+    public function add(){
+        if ($this->request->isPost()) {
+            $data = $this->request->post();
+            if(!$data['select']){
+                $this->error('请选择游戏');
+            }
+            if(model('common/Business')->where(['admin_id'=>$data['admin_id']])->find()){
+                $this->error('用户已存在');
+            }
+            $data['channel_ids'] = $data['select'];
+            unset($data['select']);
+            $data['update_time'] = $data['create_time'] = time();
+            $m = model('common/Business')->insertGetId($data);
+
+            if (!empty($m)) {
+                $this->success('添加成功', url('index'));
+            }
+            $this->error('添加失败');
+        }
+
+        $adminList = model('common/Admin')->alias('a')->join('nw_admin_role_user b','a.id=b.user_id')->where(['b.role_id'=>['in',[12,17,18]],'a.status'=>1])->field('a.id,a.username')->order('a.id desc')->select();
+
+        $channelList = model('common/Channel')->where(['level'=>0,'status'=>1])->field('id,name')->order('id desc')->select();
+        $this->assign('admin_list', $adminList);
+        $this->assign('channel_list', $channelList);
+        return $this->fetch();
+    }
+
+    public function edit(){
+        $id = $this->request->param('id', '', 'intval');
+        if (!$data = model('common/Business')->find($id)) {
+            $this->error('参数错误,不存在该数据');
+        }
+
+        if ($this->request->isPost()) {
+
+            if(model('common/Business')->where('admin_id','=',$data['admin_id'])->where('admin_id','<>',$data['admin_id'])->find()){
+                $this->error('用户已存在');
+            }
+
+            $data = $this->request->post();
+            if(!$data['select']){
+                $this->error('请选择游戏');
+            }
+            $data['channel_ids'] = $data['select'];
+            unset($data['select']);
+            $data['update_time'] = $data['create_time'] = time();
+
+
+            if (model('common/Business')->where(['id' => $id])->update($data)) {
+                $this->success('修改成功', url('index'));
+            } else {
+                $this->error(model('common/Business')->getError() ?: '修改失败');
+            }
+            $this->error('修改失败');
+        }
+
+        $this->assign('data', $data);
+        $adminList = model('common/Admin')->alias('a')->join('nw_admin_role_user b','a.id=b.user_id')->where(['b.role_id'=>['in',[12,17,18]],'a.status'=>1])->field('a.id,a.username')->order('a.id desc')->select();
+
+        $channelList = model('common/Channel')->where(['level'=>0,'status'=>1])->field('id,name')->order('id desc')->select();
+        $this->assign('admin_list', $adminList);
+        $this->assign('channel_list', $channelList);
+        return $this->fetch();
+    }
+
+    /**
+     *删
+     */
+    public function delete()
+    {
+        $id = $this->request->param('id', '', 'intval');
+        if (empty($id)) {
+            $this->error('参数错误!');
+        }
+        $appModel =model('common/Business');
+        if (!$appModel->find($id)) {
+            $this->error('参数错误,不存在该数据');
+        }
+        if ($appModel->where('id', '=', $id)->delete()) {
+            $this->success('删除成功', url('index'));
+        }
+        $error = $appModel->getError();
+        $this->error($error ?: '删除失败');
+    }
+}

File diff suppressed because it is too large
+ 1551 - 0
www/new_sdk/application/admin/controller/Channel.php


+ 267 - 0
www/new_sdk/application/admin/controller/ChannelAccount.php

@@ -0,0 +1,267 @@
+<?php
+/**
+ * 前台注册用户管理控制器
+ */
+
+namespace app\admin\controller;
+
+use app\common\library\MakeReportGo;
+use think\Db;
+use app\common\logic\Member as MemberService;
+//use app\common\library\MakeReport;
+use app\common\library\FileUpload;
+use think\Exception;
+
+class ChannelAccount extends Admin
+{
+    protected $where;
+    protected $start_time;
+    protected $end_time;
+
+    /**
+     * 不进行父类的登录验证,所以增加构造方法重写了父类的初始化方法
+     */
+    protected function _initialize()
+    {
+        parent::_initialize();
+
+        $this->where        = [];
+    }
+
+    /**
+     * 公会账户余额
+     */
+    public function index()
+    {
+        $this->indexWhere();
+
+		if (request()->isAjax() && input('download')) {
+
+
+			$sql = model("Channel")->field('id,name,level,status,amount_coin,amount,js_amount,cps_settle_period,mcps_settle_period')
+					->where($this->where)
+					->order('id desc')
+					->fetchSql(true)->select();
+		//	echo $sql;
+            if((new MakeReportGo())->addTask('admin.channelAccountIndex',$sql,session_id())){
+                $this->success('报表生成的任务已经提交, 报表生成完成后,会及时通知您,请耐心稍等');
+            }
+            else{
+                $this->error('报表生成任务不可重复提交,如遇到无法导出情况,建议修改查询条件解除当前状态,提交重新生成报表任务!');
+            }
+		}
+
+        $param = input('get.');
+        $list  = model("Channel")->field('id,name,level,status,amount,amount_coin,js_amount,cps_settle_period,mcps_settle_period')
+					->where($this->where)
+					->order('id desc')
+					->paginate(10, false, array('query' => $param));
+
+        $data     = $list->toArray()['data'];
+
+        $this->assign('list', $data);
+        $this->assign('total', $list->total());     //总条数
+        $this->assign('start_time', $this->start_time);
+        $this->assign('end_time', $this->end_time);
+        $this->assign('page', $list->render());
+
+        $business = model('common/Business')->getChannelIds(session('ADMIN_ID'));
+        $channelWhere = [];
+        if( $business != -1){
+            if($business && $business != -2){
+                $channelWhere['id'] = ['in',$business];
+            }else{
+                $channelWhere['id'] = -1;
+            }
+        }
+
+        $uniom = model('Channel')->where(['level'=>0])->where($channelWhere)->field('id,name')->select();
+        $this->assign('uniom', $uniom);          //顶级渠道列表
+
+        return $this->fetch();
+    }
+
+    // 用户信息汇总查询条件
+    public function indexWhere()
+    {
+        $where = array();
+        $userid           = input('request.userid',0,'intval');
+        $username         = input('request.username');
+        $status           = input('request.status');
+		$bplus_channel_id = input('request.bplus_channel_id',0,'intval');
+
+		$where['level']   = 1;
+        //用户ID
+        if ($userid) {
+            $where['id'] = $userid;
+        }
+        //用户名
+        if ($username != '') {
+            $where['name'] = $username;
+        }
+        $business = model('common/Business')->getChannelIds(session('ADMIN_ID'));
+        if( $business != -1){
+            if($bplus_channel_id){
+                if(!in_array($bplus_channel_id,$business)){
+                    $where['parent_id'] = -1;
+                }
+            }else{
+                if($business && $business != -2){
+                    $where['parent_id']  = implode(',',$business);
+                }else{
+                    $where['parent_id']  = -1;
+                }
+            }
+        }else{
+            //商务账号
+            if ($bplus_channel_id) {
+                $where['parent_id'] = $bplus_channel_id;
+            }
+        }
+
+
+
+        //账户状态
+        if ($status != '') {
+            $where['status'] = $status;
+        }
+
+        $this->where      = $where;
+
+    }
+
+    /**
+     * 渠道账户明细列表
+     */
+    public function detList()
+    {
+        $where = $this->_getDetListCondition();
+		//var_dump($where);
+
+		if (request()->isAjax() && input('download')) {
+
+
+			$sql = model("ChannelAccountDet")->alias('d')
+					->join('nw_channel channel', 'd.channel_id = channel.id','left')
+					->field("d.id,d.channel_id,d.channel_name,d.change_amount,d.change_amount,d.account_type,d.type,d.out_orderid,d.create_time,channel.level,channel.status,channel.amount,channel.js_amount")
+					->where($where)
+					->order('d.create_time desc')
+					->fetchSql(true)->select();
+		//	echo $sql;
+
+            if((new MakeReportGo())->addTask('admin.channelAccountDetList',$sql,session_id())){
+                $this->success('报表生成的任务已经提交, 报表生成完成后,会及时通知您,请耐心稍等');
+            }
+            else{
+                $this->error('报表生成任务不可重复提交,如遇到无法导出情况,建议修改查询条件解除当前状态,提交重新生成报表任务!');
+            }
+		}
+
+        $list = model("ChannelAccountDet")->alias('d')
+			->join('nw_channel channel', 'd.channel_id = channel.id','left')
+            ->field("d.id,d.channel_id,d.channel_name,d.change_amount,d.change_amount,d.account_type,d.type,d.out_orderid,d.create_time,channel.level,channel.status,channel.amount,channel.js_amount")
+            ->where($where)
+            ->order('d.create_time desc')
+            ->paginate(10, false, ['query' => input('get.')]);
+
+        $data = $list->toArray()['data'];
+		$this->assign('list', $data);
+
+		$showTotal = $incomeAmt = $payoutAmt = 0;
+		if((isset($where['d.channel_id'])&&$where['d.channel_id'] || isset($where['d.channel_name'])&&$where['d.channel_name']) && (isset($where['d.account_type'])&&$where['d.account_type'])){
+			$showTotal = 1;
+			$incomeAmt = model("ChannelAccountDet")->alias('d')->where($where)->where(['change_amount'=>['egt',0]])->sum('change_amount');
+			$payoutAmt = model("ChannelAccountDet")->alias('d')->where($where)->where(['change_amount'=>['lt',0]])->sum('change_amount');
+		}
+        $this->assign('showTotal',$showTotal);
+        $this->assign('incomeAmt',$incomeAmt);
+        $this->assign('payoutAmt',$payoutAmt);
+        $this->assign('total', $list->total());     //总条数
+        $this->assign('page', $list->render());
+        $this->assign('start_time', $this->start_time);
+        $this->assign('end_time', $this->end_time);
+
+        return $this->fetch('det_list');
+    }
+
+    /**
+     * 账户变动明细  条件查询
+     * @return array
+     */
+    protected function _getDetListCondition()
+    {
+        $start_time       = input('request.start_time', '', 'trim');
+        $end_time         = input('request.end_time', '', 'trim');
+        $channel_name     = input('request.channel_name', '', 'trim');
+		$channel_id		  = input('request.channel_id', 0, 'intval');
+		$type			  = input('request.type', '', 'trim');
+		$account_type	  = input('request.account_type', '', 'trim');
+		$out_orderid	  = input('request.out_orderid', '', 'trim');
+
+		$where = array();
+        // 获取查询日期
+        if (!empty($start_time) || !empty($end_time)){
+            $where['d.create_time'] = $this->getTimeCondition($start_time,$end_time,false);
+        }
+        //渠道ID
+        if ($channel_id) {
+            $where['d.channel_id'] = $channel_id;
+        }
+        //渠道名称
+        if ($channel_name != '') {
+            $where['d.channel_name'] = $channel_name;
+        }
+        //账户类型
+        if ($account_type != '') {
+            $where['d.account_type'] =  $account_type;
+        }
+        //类型
+        if ($type != '') {
+            $where['d.type'] =  $type;
+        }
+	    //游戏ID
+        if ($out_orderid != '') {
+            $where['d.out_orderid'] = $out_orderid;
+        }
+        return $where;
+    }
+
+    /**
+     * 获取日期查询条件
+     * @param  int  $start_time  开始时间
+     * @param  int  $end_time    结束时间
+     * @param  bool  $isdefault    是否默认今天日期
+     * @return
+     */
+    private function getTimeCondition($start_time,$end_time,$isdefault = true)
+    {
+        $this->start_time = $start_time;
+        $this->end_time   = $end_time;
+        $time = [];
+        //开始时间和结束时间不为空时
+        if ($start_time != '' && $end_time != '') {
+            $time = [
+                ['>=', strtotime($start_time)],
+                ['<=', strtotime($end_time . ' 23:59:59')],
+            ];
+        } //开始时间不为空时
+        elseif ($start_time != '') {
+            $time = ['>=', strtotime($start_time)];
+        } //结束时间不为空时
+        elseif ($end_time != '') {
+            $time = ['<=', strtotime($end_time . ' 23:59:59')];
+        } else {
+
+            if($isdefault){
+                $this->start_time = $this->end_time = date('Y-m-d', time());
+
+                $time = [
+                    ['>=', strtotime($this->start_time)],
+                    ['<=', strtotime($this->end_time . ' 23:59:59')],
+                ];
+            }
+        }
+
+        return $time;
+    }
+}

+ 142 - 0
www/new_sdk/application/admin/controller/ChannelAccountDet.php

@@ -0,0 +1,142 @@
+<?php
+/**
+ * 前台注册用户管理控制器
+ */
+
+namespace app\admin\controller;
+
+use app\common\model\Members as MembersModel;
+use app\common\model\MembersTwo;
+use think\Db;
+use app\common\logic\Member as MemberService;
+use app\common\library\MakeReport;
+use app\common\library\FileUpload;
+use think\Exception;
+
+class ChannelAccountDet extends Admin
+{
+    protected $membersModel;
+    protected $where;
+    protected $start_time;
+    protected $end_time;
+
+    /**
+     * 不进行父类的登录验证,所以增加构造方法重写了父类的初始化方法
+     */
+    protected function _initialize()
+    {
+        parent::_initialize();
+
+        $this->membersModel = new MembersModel;
+        $this->gameList     = $gameList = model('Common/Game')->getAllByCondition('id,name');
+		$this->selfGameList  = $selfGameList = model('Common/Game')->getAllByCondition('id,name', [],'','self');
+        $this->where        = [];
+    }
+
+    /**
+     * 渠道账户明细列表
+     */
+    public function detList()
+    {
+        $where = $this->_getDetListCondition();
+		//var_dump($where);
+
+        $list = model("ChannelAccountDet")->alias('d')
+			->join('nw_channel channel', 'd.channel_id = channel.id','left')
+            ->field("d.id,d.channel_id,d.channel_name,d.change_amount,d.change_amount,d.account_type,d.type,d.out_orderid,d.create_time,channel.level,channel.status,channel.amount,channel.js_amount")
+            ->where($where)
+            ->order('d.create_time desc')
+            ->paginate(10, false, ['query' => input('get.')]);
+
+        $data = $list->toArray()['data'];
+
+        $this->assign('list', $data);
+        $this->assign('total', $list->total());     //总条数
+        $this->assign('page', $list->render());
+        $this->assign('start_time', $this->start_time);
+        $this->assign('end_time', $this->end_time);
+
+        return $this->fetch('det_list');
+    }
+
+    /**
+     * 账户变动明细  条件查询
+     * @return array
+     */
+    protected function _getDetListCondition()
+    {
+        $start_time       = input('request.start_time', '', 'trim');
+        $end_time         = input('request.end_time', '', 'trim');
+        $channel_name     = input('request.channel_name', '', 'trim');
+		$channel_id		  = input('request.channel_id', 0, 'intval');
+		$type			  = input('request.type', '', 'trim');
+		$account_type	  = input('request.account_type', '', 'trim');
+		$out_orderid	  = input('request.out_orderid', '', 'trim');
+
+		$where = array();
+        // 获取查询日期
+        if (!empty($start_time) || !empty($end_time)){
+            $where['d.create_time'] = $this->getTimeCondition($start_time,$end_time,false);
+        }
+        //渠道ID
+        if ($channel_id) {
+            $where['d.channel_id'] = $channel_id;
+        }
+        //渠道名称
+        if ($channel_name != '') {
+            $where['d.channel_name'] = $channel_name;
+        }
+        //账户类型
+        if ($account_type != '') {
+            $where['d.account_type'] =  $account_type;
+        }
+        //类型
+        if ($type != '') {
+            $where['d.type'] =  $type;
+        }
+	    //游戏ID
+        if ($out_orderid != '') {
+            $where['d.out_orderid'] = $out_orderid;
+        }
+        return $where;
+    }
+
+    /**
+     * 获取日期查询条件
+     * @param  int  $start_time  开始时间
+     * @param  int  $end_time    结束时间
+     * @param  bool  $isdefault    是否默认今天日期
+     * @return
+     */
+    private function getTimeCondition($start_time,$end_time,$isdefault = true)
+    {
+        $this->start_time = $start_time;
+        $this->end_time   = $end_time;
+        $time = [];
+        //开始时间和结束时间不为空时
+        if ($start_time != '' && $end_time != '') {
+            $time = [
+                ['>=', strtotime($start_time)],
+                ['<=', strtotime($end_time . ' 23:59:59')],
+            ];
+        } //开始时间不为空时
+        elseif ($start_time != '') {
+            $time = ['>=', strtotime($start_time)];
+        } //结束时间不为空时
+        elseif ($end_time != '') {
+            $time = ['<=', strtotime($end_time . ' 23:59:59')];
+        } else {
+
+            if($isdefault){
+                $this->start_time = $this->end_time = date('Y-m-d', time());
+
+                $time = [
+                    ['>=', strtotime($this->start_time)],
+                    ['<=', strtotime($this->end_time . ' 23:59:59')],
+                ];
+            }
+        }
+
+        return $time;
+    }
+}

+ 267 - 0
www/new_sdk/application/admin/controller/ChannelApply.php

@@ -0,0 +1,267 @@
+<?php
+/**
+ * 渠道注册申请控制器
+ */
+
+namespace app\admin\controller;
+
+use app\common\model\ChannelApply as ChannelApplyModel;
+use app\common\model\Channel;
+use app\common\model\CoinSetting;
+use app\common\model\Admin as AdminModel;
+use app\common\library\MakeReport;
+use app\common\logic\Coin as CoinLogic;
+use think\Db;
+
+class ChannelApply extends Admin
+{
+    private $applyModel;
+    
+    /**
+     * 不进行父类的登录验证,所以增加构造方法重写了父类的初始化方法
+     */
+    protected function _initialize()
+    {
+        parent::_initialize();
+        
+        $this->applyModel = new ChannelApplyModel;
+    }
+    
+    /**
+     * 发币申请列表
+     */
+    public function index()
+    {
+        $where = $this->getListCondition();
+        
+        $param = input('request.');		//分页带条件
+        
+        $list = $this->applyModel
+                    ->field('*')
+                    ->where($where)
+                    ->order('status asc,create_time desc')
+                    ->paginate(10,false,array('query' => $param));
+        
+        // 获取分页显示的HTML
+        $page = $list->render();
+        
+
+        //后台管理员列表
+        $adminList = model('Admin')->field('id,username as name')->where(['status'=>1,'type'=>1])->order('username asc')->select();
+
+        $this->assign('page', $page);
+        $this->assign('list',$list);
+        $this->assign('admin_list',$adminList);
+
+        return $this->fetch('list');
+    }
+    
+
+     /**
+    * 发币申请列表的查询条件
+    *
+    * @return array $where 查询条件
+    */
+    private function getListCondition()
+    {
+        $start_time = input('request.start_time',date('Y-m-d'));
+        
+        //渠道名
+        if(input('request.username')!='')
+        {
+            $where['username'] = input('request.username','','trim');
+        }
+        
+        //手机号
+        if(input('request.linkman_mobile')!='')
+        {
+            $where['linkman_mobile'] = input('request.linkman_mobile','','trim');
+        }
+
+        //审核状态
+        if(input('request.status')!='')
+        {
+            $where['status'] = input('request.status',0,'intval');
+        }
+        
+        //开始时间和结束时间不为空时
+        if ($start_time != '' && input('request.end_time') != '') {
+            $where['create_time'] = [
+                ['>=', strtotime($start_time)],
+                ['<=', strtotime(input('request.end_time').' 23:59:59')],
+            ];
+        } //开始时间不为空时
+        elseif ($start_time!= '') {
+            $where['create_time'] = ['>=', strtotime($start_time)];
+        } //结束时间不为空时
+        elseif (input('request.end_time') != '') {
+            $where['create_time'] = ['<=', strtotime(input('request.end_time').' 23:59:59')];
+        }
+        
+        return $where;
+    }
+    
+    
+    /**
+     * 审核
+     *
+     */
+    public function audit()
+    {
+        if (request()->isAjax()) {
+            $id = input('id',0,'intval');
+            $contact_admin_id = input('contact_admin_id',0,'intval');
+            $beizhu = input('beizhu','','trim');
+            $status = input('status','0','intval');
+
+            if(empty($id)){
+                $this->error('ID不能为空');
+            }
+	        if($status<>1 && $status<>2){
+                $this->error('非法审核状态');
+            }
+			if($status==1 &&  !$contact_admin_id){
+                $this->error('请选择对接人');
+			}
+			
+			$applyInfo =  model('ChannelApply')->where(['id' => $id])->find();
+			if(empty($applyInfo)){
+				$this->error('渠道注册申请不存在');
+			}
+
+			if($applyInfo['status'] <> 0){
+				$this->error('该渠道注册申请已审核过');
+			}
+
+			if($status==1){
+                $channelInfo = model('Channel')->where(['name' => $applyInfo['username']])->find();
+                if ( $channelInfo ) {
+					$this->error('该渠道信息已存在');
+                }
+                $userInfo = model('Admin')->field('id')->where(['username' => $applyInfo['username']])->find();
+                if ($userInfo) {
+                    $this->error("该用户名已存在! ");
+                }
+			}
+
+			$auditData = array();
+			$auditData['id'] = $id;
+			$auditData['status'] = $status;
+			$auditData['audit_time'] = NOW_TIMESTAMP;
+			$auditData['beizhu'] = $beizhu;
+			$auditData['contact_admin_id'] = $contact_admin_id;
+			$auditData['audit_admin_id'] = session('ADMIN_ID');
+
+            Db::startTrans();
+            try {
+				if($status==1){
+					$channelInfo = model("Channel")->where(['name' => $applyInfo['username']])->find();
+					if ( $channelInfo ) {
+						throw new Exception("该渠道信息已存在");
+					}
+					$userInfo = model("Admin")->field('id')->where(['username' => $applyInfo['username']])->find();
+					if ($userInfo) {
+						throw new Exception("用户名已存在! ");
+					}
+
+					//添加渠道信息
+					$channelModel = model('channel');
+					$channelModel->save([
+						'name'          => $applyInfo['username'],
+						'parent_id'     => config('TOP_CHANNEL_ID'),
+						'id_path'       => ','.config('TOP_CHANNEL_ID').',',
+						'create_time'   => $applyInfo['create_time'],
+						'flag'          => 3,
+						'show_full_account' => 0,
+						'remark'        => $beizhu,
+						'status'        => 1]);
+				//	echo $channelModel->id."----channelModel->id-----<br>";
+					if ( !$channelModel->id) {
+						throw new Exception("添加渠道信息失败");
+					}
+					
+					$channelData = array(); 
+					$channelData['channel_id']		= $channelModel->id;
+					$channelData['real_name']		= $applyInfo['real_name'];
+					$channelData['account_wechat']	= $applyInfo['account_wechat'];
+					$channelData['account_qq']		= $applyInfo['account_qq'];
+					$channelData['account_type']    = $applyInfo['account_type'];
+					$channelData['alipay_realname']    = $applyInfo['alipay_realname'];
+					$channelData['alipay_account']    = $applyInfo['alipay_account'];
+					$channelData['bank_account']    = $applyInfo['bank_account'];
+					$channelData['company_name']    = $applyInfo['company_name'];
+					$channelData['bank_name']    = $applyInfo['bank_name'];
+					$channelData['linkman_email']    = $applyInfo['linkman_email'];
+					$channelData['business_mode_public']    = $applyInfo['business_mode_public'];
+					$channelData['business_mode_private']    = $applyInfo['business_mode_private'];
+					$channelData['company_scale']    = $applyInfo['company_scale'];
+					$channelData['promote_games']    = $applyInfo['promote_games'];
+					$channelData['promote_platforms']    = $applyInfo['promote_platforms'];
+					$channelData['company_has_shop']    = $applyInfo['company_has_shop'];
+					$channelData['shop_platform']    = $applyInfo['shop_platform'];
+					$channelData['shop_address']    = $applyInfo['shop_address'];
+					$channelData['shop_name']    = $applyInfo['shop_name'];
+					$channelData['linkman_address']    = $applyInfo['linkman_address'];
+					$channelData['linkman_mobile']    = $applyInfo['linkman_mobile'];
+					$channelData['create_time']   = $applyInfo['create_time'];
+					$channelData['audit_time']   = NOW_TIMESTAMP;
+					$channelData['contact_admin_id']   = $contact_admin_id;
+					$channelData['audit_admin_id']   = session('ADMIN_ID');
+					$channelData['update_time'] = NOW_TIMESTAMP;
+					$channelData['admin_id'] = session('ADMIN_ID');
+					$insertChannelResult = model("ChannelInfo")->insertGetId($channelData);
+				//	echo $insertChannelResult."----insertChannelResult-----<br>";
+
+					if ($insertChannelResult === false) {
+						throw new Exception("添加渠道更多信息失败");
+					}
+
+					$adminData = array(); 
+					$adminData['username']      = $applyInfo['username'];
+					$adminData['password']      = $applyInfo['password'];
+					$adminData['pay_password']  = $applyInfo['pay_password'];
+					$adminData['type']          = 2;
+					$adminData['channel_id']    = $channelModel->id;
+					$adminData['beizhu']		= $beizhu;
+					$adminData['status']		= 1;
+					$adminData['mobile']		= $applyInfo['linkman_mobile'];
+					$adminData['create_time']   = $applyInfo['create_time'];
+					$adminData['password_update_time'] = NOW_TIMESTAMP;
+					$insertAdminResult = model("Admin")->insertGetId($adminData);
+				//	echo $insertAdminResult."----insertAdminResult-----<br>";
+
+					if ($insertAdminResult === false) {
+						throw new Exception("添加管理员账号失败");
+					}
+
+					$auditData['channel_id'] = $channelModel->id;
+					$result = $this->applyModel->where(['id'=>$id])->update($auditData);
+				//	echo $result."---result-----<Br>";
+					if ($result === false) {
+						throw new Exception("审核修改状态失败");
+					}
+					$this->insertLog($this->current_node, "渠道审核表审核通过新增子渠道:".$applyInfo['username'].",状态:正常",41);
+				}
+				else if($status==2){
+					$result = $this->applyModel->where(['id'=>$id])->update($auditData);
+					if ($result === false) {
+						throw new Exception("审核修改状态失败");
+					}
+				}
+                Db::commit();
+
+
+			//	$this->success('审核成功');
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error("添加失败: " . $e->getMessage());
+            }
+			$this->success('审核成功');
+        }
+        else{
+            $this->error('非法请求');
+        }
+    }
+    
+
+}

+ 378 - 0
www/new_sdk/application/admin/controller/ChannelCa.php

@@ -0,0 +1,378 @@
+<?php
+/**
+ * 渠道通信密钥管理
+ *
+ * @category Controller
+ * @author   SHUI
+ * @since    2018/9/26
+ */
+
+namespace app\admin\controller;
+
+use app\common\model\ChannelCa as ChannelCaModel;
+use think\Db;
+use think\Validate;
+use think\Exception;
+use app\common\model\ChannelApiRequestAuth;
+
+class ChannelCa extends Admin {
+
+    private $_channelCaModel = null;
+
+    public function _initialize() {
+        parent::_initialize();
+
+        $this->_channelCaModel = new ChannelCaModel;
+    }
+
+    /**
+     * 显示已经配置的秘钥对列表
+     */
+    public function encryptionKeyList() {
+        $where =[];
+
+        $list = $this->cyModel('channel_ca')
+            ->alias("a")
+            ->join("nw_channel b", 'a.ca_channel_id = b.id', 'left')
+            ->field("a.ca_id as id, a.ca_public_key, a.ca_encryption_key_type, a.ca_enable_status, a.ca_realname, a.ca_mobile, a.ca_mail, a.ca_create_time, a.ca_channel_id as channel_id, b.name as channel_name")
+            ->where($where)
+            ->order("a.ca_id desc")
+            ->paginate(10, false, ['query' => $where])->each(function($item, $key){
+                // 顶级渠道
+                $item['top_channel_name'] = get_top_channel($item['channel_id'])['name'];
+                return $item;
+            });
+
+        $page = $list->render();
+        $this->assign('total', $list->total());
+        $this->assign("page", $page);
+        $this->assign('list', $list);
+
+        return $this->fetch('encryptionKeyList');
+    }
+
+    /**
+     * 密钥对编辑和添加公用的页面
+     */
+    public function encryptionKeyEdit() {
+        if ( request()->isGet() ) {
+            $id = input('id', 0, 'intval');
+
+            $info = [];
+            if ( ! empty($id) ) {
+                $info = $this->_channelCaModel->where(['ca_id' => $id])->find();
+            }
+
+            $channelList= model('Channel')->field('id,name')->where(['status'=>1,'flag'=>3])->order('name asc')->select();
+
+            $this->assign('channel_list', $channelList);
+
+            $this->assign('info', $info);
+
+            return $this->fetch('encryptionKeyEdit');
+        }
+
+        if ( request()->isPost() ) {
+            $param = input('param.');
+
+            $validate = new Validate([
+                ['ca_private_key', 'isValidPrivatekey:1', '私钥信息错误'],
+                ['ca_public_key', 'isValidPublickey:1', '公钥信息错误'],
+                ['ca_realname', 'require', '请填写联系人姓名'],
+                ['ca_mobile', 'require', '请填写联系人手机号'],
+                ['ca_mail', 'email', '请正确填写联系人邮箱'],
+            ]);
+
+            $validate->extend([
+                'isValidPrivatekey' => function ($value, $rule) {
+                    if ( empty($value) ) {
+                        return false;
+                    }
+
+                    $resource_id = openssl_pkey_get_private($value);
+                    if ( empty($resource_id) ) {
+                        return false;
+                    }
+
+                    return true;
+                },
+                'isValidPublickey' => function ($value, $rule) {
+                    if ( empty($value) ) {
+                        return false;
+                    }
+
+                    $resource_id = openssl_pkey_get_public($value);
+                    if ( empty($resource_id) ) {
+                        return false;
+                    }
+
+                    return true;
+                }
+            ]);
+
+            $result = $validate->check($param);
+
+            if ( true !== $result ) {
+                $this->error($validate->getError());
+            }
+
+            $id = isset($param['id']) ? (int)$param['id'] : 0;
+            unset($param['id']);
+            if ( 0 < $id ) {
+                $param['ca_update_time'] = NOW_TIMESTAMP;
+
+                unset($param['ca_channel_id'],$param['ca_private_key'],$param['ca_public_key']);
+                $result = $this->_channelCaModel->allowField(true)->save($param, ['ca_id' => $id]);
+            } else {
+                if ( ! $this->_channelCaModel->isValidEncryptionKey($param['ca_private_key'], $param['ca_public_key']) ) {
+                    $this->error('公钥和私钥不匹配,请重新生成或填写');
+                }
+
+                if ( $this->_channelCaModel->where(['ca_channel_id' => $param['ca_channel_id']])->count() ) {
+                    $this->error('已添加该渠道,请勿重复添加');
+                }
+
+                $param['ca_create_time'] = NOW_TIMESTAMP;
+
+                $result = $this->_channelCaModel->allowField(true)->save($param);
+            }
+
+            if ( false === $result ) {
+                $this->error('操作失败');
+            } else {
+                $this->success('操作成功','ChannelCa/encryptionKeyList');
+            }
+        }
+    }
+
+    /**
+     * 秘钥对记录删除
+     */
+    public function encryptionKeyDelete() {
+        $id = input('id', 0, 'intval');
+
+        if ( empty($id) ) {
+            $this->error('参数错误');
+        }
+
+        $info = $this->_channelCaModel->where(['ca_id' => $id])->find();
+
+        if ( empty($info) ) {
+            $this->error('记录不存在');
+        }
+
+        if ( $info['ca_enable_status'] != '0' ) {
+            $this->error('请先禁用该密钥对');
+        }
+
+        $result = $this->_channelCaModel->where(['ca_id' => $id])->delete();
+
+        if ( false === $result ) {
+            $this->error('删除失败');
+        } else {
+            $content = '删除了渠道:'.model('Channel')->where(['id' => $info['ca_channel_id']])->value('name').'的密钥对';
+            $this->insertLog($this->current_node, $content, 63);
+
+            $this->success('删除成功','ChannelCa/encryptionKeyList');
+        }
+    }
+
+    /**
+     * @param $data
+     * @return array
+     */
+    private function getChange($data){
+        $limit = $this->_freezingLimit();           //限制的接口
+        $limit = explode(',',$limit);   //转化成数组
+        $limit_open = $data;     //可以访问的接口
+        $limit_open = explode(',',$limit_open);  //转化成数组
+        $diff = array_diff($limit,$limit_open);          //差集
+        $intersect = array_intersect($limit,$limit_open);     //交集
+        $limit_diff = array_flip($diff);
+        foreach($limit_diff as $k => $v){
+            $limit_diff[$k] = 0;
+        }
+        $limit_intersect = array_flip($intersect);
+        foreach($limit_intersect as $k => $v){
+            $limit_intersect[$k] = 1;
+        }
+        $data_limit = array_merge($limit_diff,$limit_intersect);
+        return $data_limit;
+    }
+
+    /*
+     * 外部渠道冻结管理
+     */
+    public function freezing()
+    {
+        $channel_name = $this->request->get('channel_name','','trim');
+        $condition = [];
+        if(!is_null($channel_name)){
+            $condition['n.name'] = ['like','%'.$channel_name.'%'];
+        }
+        $freezingModel = new ChannelApiRequestAuth;
+        $list = $freezingModel->alias('a')
+            ->join('nw_channel n','a.channel_id = n.id','LEFT')
+            ->field('a.*,n.name')
+            ->where($condition)
+            ->order('id desc')
+            ->paginate(10, false, ['query' => input('get.')]);
+        foreach ($list as &$item){
+
+            $data_limit = $this->getChange($item['allow_action']);
+
+            $item['game_list'] = $data_limit['game_list'];
+            $item['member_recharge'] = $data_limit['member_recharge'];
+            $item['query_discount'] = $data_limit['query_discount'];
+            $item['login_list'] = $data_limit['login_list'];
+            $item['register_list'] = $data_limit['register_list'];
+            $item['pay_list'] = $data_limit['pay_list'];
+            $item['sub_package'] = $data_limit['sub_package'];
+            $item['detect_channel'] = $data_limit['detect_channel'];
+            $item['detect_user'] = $data_limit['detect_user'];
+            $item['subchannel_recharge'] = $data_limit['subchannel_recharge'];
+            $item['create_channel'] = $data_limit['create_channel'];
+            $item['check_balance'] = $data_limit['check_balance'];
+            $item['channel_discount'] = $data_limit['channel_discount'];
+            $item['operate_package'] = $data_limit['operate_package'];
+            $item['game_point'] = $data_limit['game_point'];
+            $item['sub_packageurl'] = $data_limit['sub_packageurl'];
+        }
+        $this->assign('list', $list);
+        $this->assign('total', $list->total());
+        $this->assign('page', $list->render());
+        return $this->fetch('freezing');
+    }
+
+    /**
+     * 获取聚合渠道列表
+     * @return array
+     */
+    protected function _getChannelList() {
+        $channelModel   = new \app\common\model\Channel();
+        $channel_id = Db::table('cy_channel_ca')->column('ca_channel_id');
+        $where = array(
+            'id' => array('IN',$channel_id)
+        );
+        $list = $channelModel->field(['id', 'name'])->order('id desc')->where($where)->select();
+        return $list ?: [];
+    }
+
+    /*
+     *外部渠道冻结管理添加
+     */
+    public function addFreezing()
+    {
+        if($this->request->isPost()){
+            $freezingModel = new ChannelApiRequestAuth;
+            $data = [
+                'channel_id' => input('post.channel_id'),
+                'create_time' => time(),
+                'per_hour_max_request_times' => '1000',
+                'allow_action' => $this->getFreezing()
+            ];
+            $check = $freezingModel->where('channel_id',$data['channel_id'])->find();
+            if ($check){
+                $this->error('该渠道存在,请不要重复添加');
+            }
+            $result = $this->validate($data,[
+                ['channel_id','require','渠道名称不能为空'],
+            ]);
+            if (true !== $result) {
+                $this->error($result);
+            }
+            if ($freezingModel->allowField(true)->save($data)) {
+                $content = '新增渠道冻结管理:'.model('Channel')->where(['id' => $data['channel_id']])->value('name');
+                $this->insertLog($this->current_node, $content, 64);
+                $this->success('添加成功',url('freezing'));
+            }
+            $this->error($freezingModel->getError() ?: '添加失败');
+        }
+        $this->assign('channel_list', $this->_getChannelList());
+        return $this->fetch('addFreezing');
+    }
+
+    /*
+     * 外部渠道冻结管理编辑
+     */
+    public function editFreezing()
+    {
+        $id = $this->request->param('id',0,'intval');
+        $freezingModel = new ChannelApiRequestAuth;
+        $list = $freezingModel->where('id',$id)->find();
+        if ($this->request->isPost()){
+            $id = input('id');
+            $data = [
+                'allow_action' => $this->getFreezing()
+            ];
+            $freezingModel = new ChannelApiRequestAuth;
+            if ($freezingModel->allowField(true)->save($data, ['id' => $id]) !== false ) {
+                $content = '编辑渠道冻结管理:'.model('Channel')->where(['id' => $list['channel_id']])->value('name');
+                $this->insertLog($this->current_node, $content, 65);
+                $this->success('编辑成功',url('freezing'));
+            }
+            $this->error($freezingModel->getError() ?: '编辑失败');
+        }
+
+        $data_limit = $this->getChange($list['allow_action']);
+
+        $list['game_list'] = $data_limit['game_list'];
+        $list['member_recharge'] = $data_limit['member_recharge'];
+        $list['query_discount'] = $data_limit['query_discount'];
+        $list['login_list'] = $data_limit['login_list'];
+        $list['register_list'] = $data_limit['register_list'];
+        $list['pay_list'] = $data_limit['pay_list'];
+        $list['sub_package'] = $data_limit['sub_package'];
+        $list['detect_channel'] = $data_limit['detect_channel'];
+        $list['detect_user'] = $data_limit['detect_user'];
+        $list['subchannel_recharge'] = $data_limit['subchannel_recharge'];
+        $list['create_channel'] = $data_limit['create_channel'];
+        $list['check_balance'] = $data_limit['check_balance'];
+        $list['channel_discount'] = $data_limit['channel_discount'];
+        $list['operate_package'] = $data_limit['operate_package'];
+        $list['game_point'] = $data_limit['game_point'];
+        $list['sub_packageurl'] = $data_limit['sub_packageurl'];
+
+        $this->assign('list',$list);
+        return $this->fetch('editFreezing');
+    }
+
+    /*
+     *允许访问的接口拼接
+     */
+    private function getFreezing()
+    {
+        $allow = [
+            'sync_time' => 'sync_time',
+            'hello_world' => 'hello_world',
+            'game_list' => input('post.game_list'),
+            'member_recharge' => input('post.member_recharge'),
+            'query_discount' => input('post.query_discount'),
+            'login_list' => input('post.login_list'),
+            'register_list' => input('post.register_list'),
+            'pay_list' => input('post.pay_list'),
+            'sub_package' => input('post.sub_package'),
+            'detect_channel' => input('post.detect_channel'),
+            'detect_user' => input('post.detect_user'),
+            'subchannel_recharge' => input('post.subchannel_recharge'),
+            'create_channel' => input('post.create_channel'),
+            'check_balance' => input('post.check_balance'),
+            'channel_discount' => input('post.channel_discount'),
+            'operate_package' => input('post.operate_package'),
+            'game_point' => input('post.game_point'),
+            'sub_packageurl' => input('post.sub_packageurl'),
+        ];
+        $allow = array_filter($allow);    //去掉数组中的空值
+        $allow = implode(',',$allow);     //将数组转化成字符串
+        return $allow;
+    }
+
+    /*
+     * 外部渠道冻结配置表
+     */
+    protected function _freezingLimit()
+    {
+        return 'sync_time,hello_world,game_list,member_recharge,query_discount,login_list,register_list,pay_list,sub_package,detect_channel,detect_user,subchannel_recharge,create_channel,check_balance,channel_discount,operate_package,game_point,sub_packageurl';
+    }
+
+}

+ 482 - 0
www/new_sdk/application/admin/controller/ChannelCps.php

@@ -0,0 +1,482 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: edison
+ * Date: 2018/3/26
+ * Time: 上午11:50
+ */
+
+namespace app\admin\controller;
+
+use app\common\library\FileUpload;
+use app\common\model\CpsTemplate;
+use app\common\model\CpsChannel;
+use app\common\model\Agent;
+use app\common\model\CpsChannelGame;
+use think\Db;
+
+class ChannelCps extends Admin
+{
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->CPSTemplate = $this->cyModel('cps_template');
+        $this->CPSChannel = $this->cyModel('cps_channel');
+    }
+
+    /**
+     * 加载 模板列表 页面
+     */
+    public function index()
+    {
+        $where =[];
+        $list = $this->CPSTemplate
+            ->where($where)
+            ->order("id DESC")
+            ->paginate(10, false, ['query' => $where]);
+
+        // 获取分页显示
+        $page = $list->render();
+        $this->assign("page", $page);
+        $this->assign('list', $list);
+        return $this->fetch();
+    }
+
+    /**
+     * 加载模板添加页
+     */
+    public function templateAdd(){
+        if (request()->isPost()) {
+            $data = [
+                'name'      => input('post.name'),
+                'html_code' => input('post.html_code'),
+            ];
+
+            $result = $this->validate($data, [
+                ['name', 'require', '模板名不能为空'],
+                ['html_code', 'require', 'HTML代码不能为空'],
+            ]);
+
+            if (true !== $result) {
+                $this->error($result);
+            } else {
+                $where['name']        = $data['name'];
+                $where['html_code']   = $data['html_code'];
+                $where['create_time'] = time();
+
+                $file = request()->file('img');
+
+                if ($file) {
+                    $fl = new FileUpload();
+                    // 1.校验
+                    $fl->set('allowExt', 'jpg,png');
+                    // 2.上传
+                    $path = $fl->upload($file);
+
+                    if ($path) {
+                        $where['icon_url'] = $path;
+                    } else {
+                        $this->error('上传失败! ' . $fl->getError());
+                    };
+                }
+
+                $id = $this->CPSTemplate->insertGetId($where);
+
+                if (empty($id)) {
+                    $this->error('添加失败!');
+                }
+                $this->success('CPS渠道模板添加成功!', 'index');
+            }
+
+        }
+        return $this->fetch('template_add');
+    }
+
+    /**
+     * 加载模板编辑页
+     */
+    public function templateEdit(){
+
+        $id = $this->request->param('id');
+        if (empty($id)){
+            $this->error("参数错误");
+        }
+        $list = $this->CPSTemplate->where('id',$id)->find();
+        $this->assign('list', $list);
+        return $this->fetch('template_edit');
+    }
+
+    /**
+     * 更新 CPS渠道模板内容
+     */
+    public function templateEditPost(){
+        $data = [
+            'id'      => input('post.id'),
+            'name'      => input('post.name'),
+            'html_code' => input('post.html_code'),
+        ];
+
+        $result = $this->validate($data, [
+            ['name', 'require', '模板名不能为空'],
+            ['html_code', 'require', 'HTML代码不能为空'],
+        ]);
+
+        if (true !== $result) {
+            $this->error($result);
+        } else {
+            $where['name']        = $data['name'];
+            $where['html_code']   = $data['html_code'];
+            $where['update_time'] = time();
+
+            $file = request()->file('img');
+
+            if ($file) {
+                $fl = new FileUpload();
+                // 1.校验
+                $fl->set('allowExt' , 'jpg,png');
+                // 2.上传
+                $path = $fl->upload($file);
+
+                if ($path) {
+                    $where['icon_url'] = $path;
+                } else {
+                    $this->error('上传失败! ' . $fl->getError());
+                };
+            }
+
+            $this->CPSTemplate->where(['id' => $data['id']])->update($where);
+
+            $this->success('CPS渠道模板更新成功!' , 'index');
+        }
+    }
+
+    /**
+     * 加载 渠道列表 页面
+     */
+    public function channelList()
+    {
+        $where =[];
+        $list = $this->CPSChannel
+            ->alias("a")
+            ->join("nw_channel b", 'a.channel_id = b.id', 'left')
+            ->field("a.*, b.name as channel_name")
+            ->where($where)
+            ->order("a.id asc")
+            ->paginate(10, false, ['query' => $where])->each(function($item, $key){
+                // 分别统计每个渠道加了几款游戏
+                $item['hot_game'] = model('CpsChannelGame')->where(['channel_id' => $item['channel_id'], 'game_type' => '2'])->count();
+                $item['new_game'] = model('CpsChannelGame')->where(['channel_id' => $item['channel_id'], 'game_type' => '1'])->count();
+                return $item;
+            });
+
+        $page = $list->render();
+        $this->assign("page", $page);
+        $this->assign('list', $list);
+        return $this->fetch('channel_list');
+    }
+
+    /**
+     * 加载 渠道列表添加渠道 页面
+     */
+    public function channelAdd(){
+        $channel_list = model('Department')
+            ->where(['flag' => 3])
+            ->field('id,name')
+            ->select();
+        $this->assign('channel_list', $channel_list);
+        return $this->fetch('channel_add');
+    }
+
+    /**
+     * 添加 渠道
+     */
+    public function channelAddPost(){
+        $channel_id = input('post.channel_id');
+        if (empty($channel_id)){
+            $this->error('请选择渠道');
+        }
+        $user           = new CpsChannel;
+
+        $res_is = $user->where('channel_id', $channel_id)->find();
+        if(!empty($res_is)){
+            $this->error('渠道已存在,请勿重复添加');
+        }
+
+        $user->channel_id     = $channel_id;
+        $user->create_time     = time();
+        $user->save();
+        if($user->id) {
+            $this->success('添加渠道成功', 'channelList');
+        }
+    }
+
+    /**
+     * 删除CPS渠道
+     */
+    public function channelDelete(){
+        $id = $this->request->param('id');
+
+        empty($id) && $this->error('参数错误');
+
+        // 获取该条记录的渠道信息
+        $channel_id = $this->CPSChannel->where('id',$id)->column('channel_id');
+        $channel_id = $channel_id[0];
+
+        // 判断一下,该渠道是否已经添加了游戏
+        $count = model('CpsChannelGame')->where(['channel_id' => $channel_id])->count();
+
+        if($count) {
+            $this->error('该渠道已添加游戏,请先删除');
+        }
+
+        $res = $this->CPSChannel->delete($id);
+
+        if($res) {
+            $this->success('删除成功');
+        }else{
+            $this->error('删除失败');
+        }
+    }
+
+    /**
+     * 渠道游戏表
+     */
+    public function channelGameList(){
+        $channel_id = $this->request->param('channel_id');
+        empty($channel_id) && $this->error('参数错误');
+
+        $model = model('CpsChannelGame');
+
+        $map = ['a.channel_id' => $channel_id];
+        $game_type = $this->request->param('game_type');
+        !empty($game_type) && $map['a.game_type'] = $game_type;
+
+        $list   = $model
+            ->alias("a")
+            ->join("nw_channel b", 'a.channel_id = b.id', 'left')
+            ->join("cy_game c", 'a.game_id = c.id', 'left')
+            ->field("a.*, b.name as channel_name, c.name as ganme_name")
+            ->where($map)
+            ->paginate(10, false, ['query' => []]);
+        $this->assign('list', $list);
+        $this->assign('channel_id', $channel_id);
+        $this->assign('game_type', $game_type);
+        $this->assign('page', $list->render());
+        return $this->fetch('channel_game_list');
+    }
+
+    /**
+     * 渠道游戏添加
+     */
+    public function channelGameAdd(){
+        if(request()->isGet()) {
+            $game_list = model('Game')->field('id,name')->select();
+            $this->assign('game_list', $game_list);
+            $channel_id = input('channel_id');
+            $this->assign('channel_id', $channel_id);
+            return $this->fetch('channel_game_add');
+        }
+
+        if(request()->isPost()) {
+            $model = model('CpsChannelGame');
+            $runnable = true;
+            $msg      = '';
+            // 需要同步添加分包表记录
+            $data = [
+                'channel_id'    => input('post.channel_id') ,
+                'game_id'       => input('post.game_id') ,
+                'game_type'     => input('post.game_type') ,
+                'sort'          => input('post.sort') ,
+                'create_time'   => time()
+            ];
+            // channel_id 和 game_id 是联合主键,不能重复
+            $uniquename = $model->get(['channel_id'=>$data['channel_id'], 'game_id'=>$data['game_id']]);
+            if(sizeof($uniquename)){
+                $runnable  = false;
+                $this->error('该游戏已经在该CPS渠道里了,请重新选择~~~');
+            }
+
+            $model->data($data);
+            $result = $model->save();
+
+            if(empty($result)) {
+                $runnable  = false;
+                $msg       = '添加游戏失败,游戏已存在';
+            }
+
+            if($runnable) {
+                // 先判断一下cy_agent是否已经存在游戏了
+                $agent_model = model('Agent');
+                $has_record = $agent_model
+                    ->where(['gameid' => $data['game_id'], 'departmentid' => $data['channel_id']])
+                    ->count('id');
+                if(1) { // 不存在记录,创建一条
+                    $agent_data = [];
+                    $agent_data['gameid']       = $data['game_id'];
+                    $agent_data['departmentid'] = $data['channel_id'];
+                    $agent_data['create_time']  = time();
+                    $agent_data['agent']  = date('YmdHis').rand(10, 99);
+
+                    // channel_id 和 game_id 是联合主键,不能重复
+                    $uniqueKey = $agent_model->get(['departmentid'=>$data['channel_id'], 'gameid'=>$data['game_id']]);
+                    if(sizeof($uniqueKey)){
+                        $runnable  = false;
+                        $this->error('创建分包记录失败');
+                    }
+
+                    $agent_model->data($agent_data);
+                    $agent_result = $agent_model->save($agent_data);
+                    if(empty($agent_result)) {
+                        $runnable  = false;
+                        $msg       = '创建分包记录失败';
+                    }
+                }
+            }
+
+            if($runnable) {
+                $this->success('添加游戏成功', url('channelGameList', ['channel_id' => input('post.channel_id')]));
+            }else{
+                $this->error($msg);
+            }
+        }
+    }
+
+    /**
+     * 渠道游戏编辑
+     */
+    public function channelGameEdit(){
+        if(request()->isGet()) {
+            $id = input('id', 0, 'intval');
+            empty($id) && $this->error('参数错误');
+            $data = model('CpsChannelGame')->find($id);
+            $game_name = model('Game')->getName($data['game_id']);
+            $this->assign('data', $data);
+            $this->assign('game_name', $game_name);
+            /*$channel_id = input('channel_id');
+            $this->assign('channel_id', $channel_id);*/
+            return $this->fetch('channel_game_edit');
+        }
+
+        if(request()->isPost()) {
+            $model               = model('CpsChannelGame');
+            $data                = [];
+            $data['game_type']   = input('post.game_type' , 1 , 'intval');
+            $data['sort']        = input('post.sort' , 0 , 'intval');
+            $data['update_time'] = NOW_TIMESTAMP;
+            $id                  = input('post.id' , 0 , 'intval');
+
+            $res = $model->where('id', $id)->update($data);
+            if($res === false) {
+                $this->error('游戏修改失败');
+            }else{
+                $this->success('游戏修改成功', url('channelGameList', ['channel_id' => input('post.channel_id')]));
+            }
+        }
+    }
+
+    /**
+     * 渠道游戏记录删除
+     */
+    public function channelGameDelete(){
+        $id = input('id', 0, 'intval');
+        empty($id) && $this->error('参数错误');
+
+        $model = CpsChannelGame::get($id);
+        $res = $model->delete();
+
+        if($res === false) {
+            $this->error('游戏删除失败');
+        }else{
+            $this->success('游戏删除成功');
+        }
+    }
+
+    /**
+     * 加载 渠道推广列表 页面
+     */
+    public function channelListTg()
+    {
+        $where =[];
+        $list = $this->CPSChannel
+            ->alias("a")
+            ->join("nw_channel b", 'a.channel_id = b.id', 'left')
+            ->field("a.*, b.name as channel_name")
+            ->where($where)
+            ->order("a.id asc")
+            ->paginate(10, false, ['query' => $where])->each(function($item, $key){
+                // 分别统计每个渠道加了几款游戏
+                $item['hot_game'] = model('CpsChannelGame')->where(['channel_id' => $item['channel_id'], 'game_type' => '2'])->count();
+                $item['new_game'] = model('CpsChannelGame')->where(['channel_id' => $item['channel_id'], 'game_type' => '1'])->count();
+                return $item;
+            });
+
+        $page = $list->render();
+        $this->assign("page", $page);
+        $this->assign('list', $list);
+        return $this->fetch('channel_list_tg');
+    }
+
+    /**
+     * CPS模板选择页面
+     */
+    public function CPSTemplateSelect(){
+        if(request()->isGet()) {
+            $id = input('id', 0, 'intval');
+            empty($id) && $this->error('参数错误');
+
+            $info = model('CpsChannel')->find($id);
+            $template_list = model('CpsTemplate')->select();
+            $this->assign('info', $info);
+            $this->assign('template_list', $template_list);
+            return $this->fetch('CPSTemplateSelect');
+        }
+
+        if(request()->isPost()) {
+            $id           = input('post.id', 0, 'intval');
+            $template_id  = input('post.template_id', 0, 'intval');
+
+//            $res = model('CpsChannel')->save(['id' => $id, 'template_id' => $template_id]);
+            $res = model('CpsChannel')->save([
+                            'template_id'  => $template_id,
+                            'update_time' => time()
+                        ],['id' => $id]);
+            if($res === false) {
+                $this->error('模板选择失败');
+            }else{
+                $this->success('模板选择成功', url('channelListTg'));
+            }
+        }
+    }
+
+    /**
+     * CPS游戏列表页面
+     */
+    public function CPSGameList(){
+        $channel_id = input('channel_id', 0, 'intval');
+        empty($channel_id) && $this->error('参数错误');
+
+        $game_type = input('game_type', 0, 'intval');
+        $map = [];
+        $map['a.channel_id'] = $channel_id;
+        !empty($game_type) && $map['a.game_type'] = $game_type;
+
+        $model = model('CpsChannelGame');
+
+        $game_list = $model
+            ->alias("a")
+            ->join("cy_game b", "a.game_id = b.id", 'left')
+            ->field("a.*, b.name as game_name")
+            ->where($map)
+            ->order('a.sort DESC, a.id DESC')
+            ->paginate(10, false, ['query' => $map]);
+
+        $page = $game_list->render();
+        $this->assign('page', $page);
+        $this->assign('list', $game_list);
+        $this->assign('game_type', $game_type);
+        $this->assign('channel_id', $channel_id);
+        return $this->fetch('CPSGameList');
+    }
+
+
+}

+ 0 - 0
www/new_sdk/application/admin/controller/ChannelDataManager.php


Some files were not shown because too many files changed in this diff