doge пре 2 дана
родитељ
комит
26f28536c7
40 измењених фајлова са 1977 додато и 276 уклоњено
  1. 0 59
      app/Http/Controllers/api/Promotion.php
  2. 4 1
      app/Services/PromotionFacts.php
  3. 1 1
      app/Services/PromotionService.php
  4. 34 0
      docs/前端交付/README.md
  5. 20 0
      docs/前端交付/bot-28/后台/README.md
  6. 157 0
      docs/前端交付/bot-28/后台/信息管理-后台接口.md
  7. 141 0
      docs/前端交付/bot-28/后台/公告系统-后台接口.md
  8. 182 0
      docs/前端交付/bot-28/后台/整站与游戏维护-后台接口.md
  9. 11 0
      docs/前端交付/bot-28/后台/独立活动-后台接口.md
  10. 75 0
      docs/前端交付/bot-28/后台/独立活动/净亏邀请-后台接口.md
  11. 102 0
      docs/前端交付/bot-28/后台/独立活动/转盘活动-后台接口.md
  12. 73 0
      docs/前端交付/bot-28/后台/独立活动/首充奖励-后台接口.md
  13. 205 0
      docs/前端交付/bot-28/后台/龙虎榜资金流动与报表-接口.md
  14. 7 0
      docs/前端交付/melbet_sport-api/README.md
  15. 5 0
      docs/前端交付/melbet_sport-api/代理端/README.md
  16. 15 0
      docs/前端交付/melbet_sport-api/前台/README.md
  17. 102 0
      docs/前端交付/melbet_sport-api/前台/信息中心-前台接口.md
  18. 98 0
      docs/前端交付/melbet_sport-api/前台/公告系统-前台接入文档.md
  19. 165 0
      docs/前端交付/melbet_sport-api/前台/整站与游戏维护-前台接口.md
  20. 11 0
      docs/前端交付/melbet_sport-api/前台/独立活动/会员接入.md
  21. 56 0
      docs/前端交付/melbet_sport-api/前台/独立活动/净亏邀请-会员接口.md
  22. 0 0
      docs/前端交付/melbet_sport-api/前台/独立活动/转盘播报示例.json
  23. 93 0
      docs/前端交付/melbet_sport-api/前台/独立活动/转盘活动-会员接口.md
  24. 47 0
      docs/前端交付/melbet_sport-api/前台/独立活动/首充奖励-会员接口.md
  25. 56 0
      docs/前端交付/melbet_sport-api/前台/统计装置与活动采集-前台接口.md
  26. 5 0
      docs/前端交付/melbet_sport-api/后台/README.md
  27. 240 0
      docs/前端交付/交付清单.json
  28. 39 0
      docs/前端交付/交付清单.md
  29. 4 8
      docs/前端接口/独立活动/会员接入.md
  30. 4 51
      docs/前端接口/独立活动/净亏邀请-会员接口.md
  31. 4 88
      docs/前端接口/独立活动/转盘活动-会员接口.md
  32. 4 42
      docs/前端接口/独立活动/首充奖励-会员接口.md
  33. 3 1
      docs/后端/报表统计口径与数据补录.md
  34. 3 3
      docs/总后台/独立活动-后台接口.md
  35. 1 1
      docs/总后台/独立活动/净亏邀请-后台接口.md
  36. 2 2
      docs/总后台/独立活动/转盘活动-后台接口.md
  37. 1 1
      docs/总后台/独立活动/首充奖励-后台接口.md
  38. 4 2
      docs/总后台/龙虎榜资金流动与报表-接口.md
  39. 0 12
      routes/api.php
  40. 3 4
      tests/Integration/PromotionActivitiesTest.php

+ 0 - 59
app/Http/Controllers/api/Promotion.php

@@ -1,59 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\api;
-
-use App\Constants\HttpStatus;
-use App\Http\Controllers\Controller;
-use App\Services\PromotionService;
-use Illuminate\Support\Facades\Log;
-use Illuminate\Validation\ValidationException;
-
-class Promotion extends Controller
-{
-    private function execute(string $action, PromotionService $service)
-    {
-        try {
-            $rules = ['activity_id' => ['required', 'integer', 'min:1'], 'platform' => ['required', 'in:PC,H5,APP']];
-            if ($action === 'spin') $rules['request_key'] = ['required', 'string', 'regex:/^[a-zA-Z0-9_-]{16,64}$/'];
-            if ($action === 'claim') $rules['round_no'] = ['required', 'integer', 'min:1'];
-            if (in_array($action, ['records', 'rewards'], true)) $rules += ['page' => ['sometimes', 'integer', 'min:1'], 'limit' => ['sometimes', 'integer', 'between:1,100']];
-            $input = request()->validate($rules); $id = (int)$input['activity_id']; $userId = (int)request()->user->id;
-            $data = match ($action) {
-                'detail' => $service->detail($id, $userId, $input['platform']),
-                'progress' => $service->progress($id, $userId, $input['platform']),
-                'apply' => $service->apply($id, $userId, $input['platform']),
-                'spin' => $service->spin($id, $userId, $input['request_key'], $input['platform']),
-                'claim' => $service->claim($id, $userId, (int)$input['round_no'], $input['platform']),
-                'records' => $service->records($id, $userId, (int)($input['page'] ?? 1), (int)($input['limit'] ?? 20)),
-                'rewards' => $service->rewards($id, $userId, (int)($input['page'] ?? 1), (int)($input['limit'] ?? 20)),
-                'broadcasts' => $service->broadcasts($id),
-            };
-            return $this->success($data);
-        } catch (ValidationException $e) { return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first()); }
-        catch (\Throwable $e) { Log::error('promotion_failed', ['action' => $action, 'exception' => $e]); return $this->error(HttpStatus::CUSTOM_ERROR, '活动服务暂不可用,请稍后重试'); }
-    }
-    public function detail(PromotionService $s) { return $this->execute('detail', $s); }
-    public function heartbeat(PromotionService $s)
-    {
-        try {
-            $p = request()->validate(['platform' => ['required', 'in:PC,H5,APP']]);
-            return $this->success($s->heartbeat((int)request()->user->id, (string)request()->header('Authorization'), $p['platform']));
-        } catch (ValidationException $e) { return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first()); }
-        catch (\Throwable $e) { Log::error('promotion_heartbeat_failed', ['exception' => $e]); return $this->error(HttpStatus::CUSTOM_ERROR, '活动记录暂不可用'); }
-    }
-    public function index(PromotionService $s)
-    {
-        try {
-            $p = request()->validate(['platform' => ['required', 'in:PC,H5,APP'], 'lang' => ['sometimes', 'string', 'max:10'], 'type' => ['sometimes', 'integer', 'in:7,8,9']]);
-            return $this->success($s->listing((int)request()->user->id, $p['platform'], $p['lang'] ?? 'zh', isset($p['type']) ? (int)$p['type'] : null));
-        } catch (ValidationException $e) { return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first()); }
-        catch (\Throwable $e) { Log::error('promotion_list_failed', ['exception' => $e]); return $this->error(HttpStatus::CUSTOM_ERROR, '活动服务暂不可用'); }
-    }
-    public function progress(PromotionService $s) { return $this->execute('progress', $s); }
-    public function apply(PromotionService $s) { return $this->execute('apply', $s); }
-    public function spin(PromotionService $s) { return $this->execute('spin', $s); }
-    public function claim(PromotionService $s) { return $this->execute('claim', $s); }
-    public function records(PromotionService $s) { return $this->execute('records', $s); }
-    public function rewards(PromotionService $s) { return $this->execute('rewards', $s); }
-    public function broadcasts(PromotionService $s) { return $this->execute('broadcasts', $s); }
-}

+ 4 - 1
app/Services/PromotionFacts.php

@@ -117,7 +117,10 @@ class PromotionFacts
         foreach (DB::table('user_login')->where('user_id', $memberId)->where('status', 1)->where('created_at', '>=', $from)
             ->where('created_at', '<=', self::now())->cursor() as $row) $days[substr($row->created_at, 0, 10)] = true;
         $userId = DB::table('users')->where('member_id', $memberId)->value('id');
-        foreach (DB::table('promotion_presence')->where('user_id', $userId)->where('observed_at', '>=', $from)->where('observed_at', '<=', self::now())->cursor() as $row) $days[substr($row->observed_at, 0, 10)] = true;
+        foreach (['promotion_presence', 'reporting_activity'] as $table) {
+            if (!Schema::hasTable($table)) continue;
+            foreach (DB::table($table)->where('user_id', $userId)->where('observed_at', '>=', $from)->where('observed_at', '<=', self::now())->cursor() as $row) $days[substr($row->observed_at, 0, 10)] = true;
+        }
         return $days;
     }
 

+ 1 - 1
app/Services/PromotionService.php

@@ -11,7 +11,7 @@ use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Schema;
 use Illuminate\Validation\ValidationException;
 
-/** One wallet writer for both clients. Wallet -> activity -> account is the lock order. */
+/** Backend jobs and deposit awards. Member HTTP APIs are owned by melbet_sport-api. */
 class PromotionService
 {
     private PromotionFacts $facts;

+ 34 - 0
docs/前端交付/README.md

@@ -0,0 +1,34 @@
+# 前端接口增量交付
+
+本目录是2026-10-26的接口文档交付副本,按**项目 → 前台/后台/代理端**组织。
+
+## 本次范围
+
+- **bot-28**:从 `c1b4269bc9c45e2610b9078ddb39a1215b0d81aa` 到当前工作区,包含已提交及全部未提交;当前HEAD也是该提交,本次实际交付其未提交增量。
+- **melbet_sport-api**:只包含当前未提交的暂存、未暂存与未跟踪文档,HEAD为 `65a54eacc06374cda464bef4688d600e93c56f58`。
+- 共 **14份接口正文、2份来源文档目录、1份转盘示例数据**,来源文件合计17个。其中补充3份已在bot-28基准提交内的后台文档:公告、信息管理、整站与游戏维护;其余按指定Git增量范围收录。
+
+## 阅读入口
+
+| 项目 | 前台 | 后台 | 代理端 |
+|---|---|---|---|
+| bot-28 | 本次无独立前台增量,会员接口见下一行 | [后台接口目录](bot-28/后台/README.md) | 本次无增量 |
+| melbet_sport-api | [前台接口目录](melbet_sport-api/前台/README.md) | [本次范围说明](melbet_sport-api/后台/README.md) | [本次范围说明](melbet_sport-api/代理端/README.md) |
+
+## 优先对接
+
+- 三个活动:[后台配置](bot-28/后台/独立活动-后台接口.md) · [会员端申请/抽奖/领奖](melbet_sport-api/前台/独立活动/会员接入.md)
+- 统计:[后台龙虎榜/资金流/报表](bot-28/后台/龙虎榜资金流动与报表-接口.md) · [前台装置及请求活跃采集](melbet_sport-api/前台/统计装置与活动采集-前台接口.md)
+- 后台管理:[信息管理](bot-28/后台/信息管理-后台接口.md) · [公告](bot-28/后台/公告系统-后台接口.md) · [整站与游戏维护](bot-28/后台/整站与游戏维护-后台接口.md)
+- 会员前台:[信息中心](melbet_sport-api/前台/信息中心-前台接口.md) · [公告](melbet_sport-api/前台/公告系统-前台接入文档.md) · [整站与游戏维护](melbet_sport-api/前台/整站与游戏维护-前台接口.md)
+
+## 服务与返回值
+
+- `bot-28/后台` 调用bot-28后台服务,使用后台认证;成功 `code=0`,具体错误见各文档。
+- `melbet_sport-api/前台` 调用会员服务,沿用会员认证及加解密;通常成功 `code=1`、业务失败 `code=0`,`code=401` 为登录失效。公共维护查询等例外见对应文档。
+- 三个活动的会员 `/api/promotion/*` 入口归melbet_sport-api,后台 `/admin/promotion/*` 归bot-28。
+- 正常认证API请求会自动记录活跃,额外定时心跳不是必需;统计时长为请求间隔估算。
+
+[完整文件及排除清单](交付清单.md) · [机器可读来源清单](交付清单.json)
+
+源文件保留在各自项目;本目录为交付快照。后续接口变更应同步更新对应源文档并重新生成交付副本。

+ 20 - 0
docs/前端交付/bot-28/后台/README.md

@@ -0,0 +1,20 @@
+# bot-28 后台接口交付目录
+
+以下接口调用bot-28后台服务,使用管理员认证;成功 `code=0`。字段、权限和错误说明以各文档为准。
+
+| 功能 | 文档 |
+|---|---|
+| 公告:多语言、展示平台、发布时段、启停和日志 | [公告系统-后台接口](公告系统-后台接口.md) |
+| 信息:发送对象、多语言、定时发送、撤回和日志 | [信息管理-后台接口](信息管理-后台接口.md) |
+| 维护:整站与游戏分类、平台、时区、起止时间、开关和日志 | [整站与游戏维护-后台接口](整站与游戏维护-后台接口.md) |
+| 三个独立活动的公共约定和目录 | [独立活动-后台接口](独立活动-后台接口.md) |
+| 净亏邀请配置与记录 | [净亏邀请-后台接口](独立活动/净亏邀请-后台接口.md) |
+| 首充奖励配置与记录 | [首充奖励-后台接口](独立活动/首充奖励-后台接口.md) |
+| 转盘任务、概率配置与记录 | [转盘活动-后台接口](独立活动/转盘活动-后台接口.md) |
+| 龙虎榜、资金流动与统计报表 | [龙虎榜资金流动与报表-接口](龙虎榜资金流动与报表-接口.md) |
+
+公告的活动选择器复用 `GET /admin/ActivityReward?page=1&limit=20`,其中 `page`、`limit` 必填;读取 `data.data[]` 中的 `id`、`title`,需要 `admin/ActivityReward` 权限。该选择器对应既有优惠活动列表。
+
+公告、信息管理、维护三份文档在基准提交中已存在,本次按用户要求补充交付。其余文件属于指定范围的未提交增量。本目录共8个来源文件。
+
+[会员前台接口目录](../../melbet_sport-api/前台/README.md) · [交付总目录](../../README.md) · [来源与排除清单](../../交付清单.md)

+ 157 - 0
docs/前端交付/bot-28/后台/信息管理-后台接口.md

@@ -0,0 +1,157 @@
+# 信息管理:后台接入
+
+接口项目 `bot-28`,请求使用后台 API 域名和 `Authorization: Bearer <admin_token>`;POST 为 JSON。成功 `code=0`,参数/业务错误 `code=-3`,权限不足 `code=-1`,登录失效沿用后台拦截器。
+
+本模块是持久化站内信息,不发送短信、邮件或 Telegram,不等同于弹窗公告。
+
+## 接口清单
+
+| 方法 | 地址 | 作用 |
+| --- | --- | --- |
+| GET | `/admin/message` | 信息管理列表 |
+| GET | `/admin/message/options` | 对象、分类、语言枚举及权限信息 |
+| GET | `/admin/message/recipients/users` | 指定用户搜索按钮 |
+| GET | `/admin/message/recipients/agents` | 指定代理搜索按钮 |
+| GET | `/admin/message/detail?id=123` | 完整翻译、投递统计、发送对象 |
+| POST | `/admin/message/send` | 新增发送或安排定时发送 |
+| POST | `/admin/message/delete` | 撤回,保留后台记录 |
+| GET | `/admin/message/logs` | 信息日记 |
+
+沿用后台登录和按钮权限。`options/detail` 复用 `admin/message`,收件人搜索复用 `admin/message/send`;发送、撤回和日志各用对应接口按钮权限。代理搜索及代理类信息的发送、查看、撤回需要现有代理管理授权(`AGENT_ADMIN_IDS`)。无代理授权的管理员,列表和日志也只返回用户类信息。
+
+## 选项
+
+`GET /admin/message/options` 返回 `data.categories/recipient_types/languages`,结构均为 `[{value,label}]`,另含 `can_manage_agents` 和 `timezone=Asia/Shanghai`。
+
+| 字段 | 值与标签 |
+| --- | --- |
+| `recipient_type` | `all_users` 所有用户;`specified_users` 指定用户;`all_agents` 所有代理;`specified_agents` 指定代理 |
+| `category` | `discount` 优惠活动;`site_notice` 网站公告;`internal` 站内信;`promotion` 活动宣传 |
+| `language` | `zh` 中文;`en` 英文;`vi` 越南语 |
+
+用户范围固定为 `users.from=1` 且未删除,只包含前台注册会员。代理是独立代理账号本人,不包括旗下会员或下级代理。停用账号仍可作为收件人,能否登录继续由现有登录规则控制。
+
+## 指定收件人的搜索按钮
+
+请使用以下轻量接口,不使用附带钱包、三方余额的完整会员/代理列表接口:
+
+```http
+GET /admin/message/recipients/users?q=alice&page=1&limit=20
+GET /admin/message/recipients/agents?q=agent&page=1&limit=20
+```
+
+共同参数:`q` 可空,最长100字符;`page` 默认1;`limit` 默认20,最大200。
+
+| 搜索对象 | 匹配字段 | 结果字段 | 发送时保存的 ID |
+| --- | --- | --- | --- |
+| 用户 | 账号、用户名、昵称模糊匹配;数字关键字还精确匹配编号 | `id,user_id,member_id,account,username,first_name,language` | **`users.id`** |
+| 代理 | `username/real_name` 模糊匹配;数字关键字精确匹配代理 ID | `id,username,real_name,language,status` | **`agents.id`** |
+
+两个接口统一返回 `data.total` 和 `data.data[]`。不要把业务 `user_id`、`member_id` 或会员的 `agent_id` 当作选中 ID。建议搜索弹窗显示账号、昵称和语言,支持跨页多选,前端按 `id` 去重,单条最多指定500个账号。用户和代理 ID 属于不同命名空间,不能混选。
+
+## 多语言发送表单
+
+`POST /admin/message/send`
+
+```json
+{
+  "request_id": "message-20261026-001",
+  "recipient_type": "specified_users",
+  "recipient_ids": [12, 23],
+  "category": "internal",
+  "send_at": null,
+  "translations": {
+    "zh": {"title":"安全提醒","subtitle":"请查看","content":"请妥善保管您的账号。"},
+    "en": {"title":"Security notice","subtitle":"","content":"Keep your account secure."},
+    "vi": {"title":"","subtitle":"","content":""}
+  }
+}
+```
+
+| 字段 | 规则 |
+| --- | --- |
+| `request_id` | 必填,每次新发送生成新标识,最多64字符;首位字母/数字,后续允许字母数字及 `._:-`。超时重试必须沿用同一值 |
+| `recipient_type` | 必填,四个对象之一 |
+| `recipient_ids` | 指定对象时必填非空正整数数组,最多500个;服务端去重。发送全部时省略或传 `[]` |
+| `category` | 四种分类之一,省略默认 `internal`;分类只作标签,不自动创建活动或弹窗公告 |
+| `send_at` | 可空;空/省略立即发送,未来时间定时发送;格式 `YYYY-MM-DD HH:mm:ss`,北京时间 |
+| `translations` | 必填,以 `zh/en/vi` 为键的对象;内容独立存入翻译表 |
+| 每语言 `title` | 正文非空时必填,最多500字 |
+| 每语言 `subtitle` | 可空,最多500字 |
+| 每语言 `content` | 纯文本,最多5000字;空或纯空白时该语言不投递 |
+
+前端为每种语言分别提供标题、副标题和正文输入框。至少一个语言需要完整标题和正文;三个正文都空时拒绝发送。未填写的语言可省略,也可提交空对象/空字符串字段。已选用户语言没有对应正文时跳过,不使用其他语言替代。未知或空账号语言同样不兜底。
+
+发送者由登录管理员生成,返回 `sender_name` 和 `sender_display=批核者:账号`,不可由前端指定。没有独立审批流程,也不提供草稿或发送后编辑。
+
+语言依据会员 `users.language`、代理 `agents.language`,按实际发送时的值匹配。不要用本次后台请求头语言决定收件人。
+
+### 返回与重复提交
+
+成功时 `data` 返回完整信息详情,包括:
+
+- `id/request_id/category/category_name/recipient_type/recipient_label/recipient_ids`。
+- `send_at` 计划发件时间、`sent_at` 实际投递时间、`retracted_at` 撤回时间。
+- `status/status_name`:`scheduled` 待发送、`sent` 已发送、`retracted` 已撤回。
+- `sender_id/sender_name/sender_display/created_at/updated_at`。
+- `languages`、`titles`(语言→标题)、完整 `translations`。
+- `recipient_count` 实际投递数、`skipped_count` 被跳过数;`delivered_by_language` 按语言统计实际投递数。
+
+同一管理员以相同 `request_id` 和相同内容重试,返回原信息,不重复投递;相同标识换内容会报错。用户选择顺序变化、重复 ID 不会造成重复投递。
+
+`sent` 表示本次投递处理已完成,不保证人数大于0。全部对象无对应翻译或定时等待期间账号已删除时,可能完成为0人,请展示实际人数。
+
+## 管理列表和详情
+
+`GET /admin/message` 参数:
+
+| 参数 | 含义 |
+| --- | --- |
+| `page/limit` | 默认1/20,limit最多200 |
+| `category` | 分类,不传不限 |
+| `language` | `zh/en/vi`;只查有该语言非空版本的信息 |
+| `title` | 最多500字,模糊查询;有language时只匹配该语言标题,否则匹配任意已填写语言 |
+| `start_date/end_date` | 成对 `YYYY-MM-DD`,按 `send_at` 筛选,包含首尾两天 |
+
+返回 `data.total/data.data[]`,按 `send_at`、ID 倒序。一条多语言信息只占一行,总数不会因语言或收件人数重复。列表只返回 `languages/titles`,不批量返回正文;详情接口返回完整 `translations`。标题列可在未选语言时分语言展示 `titles`,选语言后读取 `titles[language]`。
+
+后台保留已撤回记录,显示 `status_name=已撤回` 并禁用重复“删除”按钮。
+
+## 撤回与收件人快照
+
+```http
+POST /admin/message/delete
+```
+
+```json
+{"id":123}
+```
+
+撤回只移除所有收件人的前台可见性,不物理删除后台记录、翻译和审计。已读、未读均隐藏;未读数同步扣除。定时消息撤回后不会发送。重复撤回幂等,返回完整详情。
+
+“所有用户/代理”以**实际投递时**已有账号为准。定时等待时新注册的账号可被纳入;投递完成后新注册账号不补收。收件记录保存当时的语言,之后改语言不替换旧正文,也不补收历史未投递信息。
+
+## 信息日记
+
+`GET /admin/message/logs`,支持 `page/limit`、`start_date/end_date`(按操作时间)、`operator_name` 模糊匹配、`language`、可选 `resource_id`(信息ID)。
+
+返回 `data.total/data.data[]`,每项含 `id/resource_id/created_at/operator_name/action/languages/titles/recipient_type/recipient_label/old_values/new_values`。
+
+`action=schedule/send/retract` 为安排定时、实际发送、撤回;自动投递操作员为“系统”,发送者始终保留原管理员。前后值为变化字段对象,包括完整翻译快照。语言筛选匹配该操作前后包含的语言,不与当前翻译或当前账号语言关联,历史记录不会被后来变化改写。
+
+## 上线与调度
+
+语言内容表分别为 `site_message_translations`、`announcement_translations`;收件记录在 `site_message_recipients`。不要复用旧的 Telegram `messages` 表。
+
+在项目现有代理表和审计表已建立的前提下执行:
+
+```bash
+php artisan migrate --path=database/migrations/2026_10_26_100000_create_announcements.php --force
+php artisan migrate --path=database/migrations/2026_10_26_110000_create_site_messages.php --force
+```
+
+公告尚未上线,本次直接调整其原建表迁移,无旧数据转换。信息迁移同时新增 `agents.language`,现有代理默认中文。会员既有 `users.language` 不重写。
+
+确保服务器已有每分钟 `php artisan schedule:run` 调度;不要重复配置。新增 `messages:dispatch` 每分钟检查到期信息,失败回滚并写 `site_message_delivery_failed`,后续继续重试。命令也可手动执行,仅处理已到时间且未撤回的信息。
+
+会员项目及代理前端应按各自独立文档接收和标记已读。字段或语言无效、对象越界、相同请求标识换内容均返回业务错误;内部异常统一“信息服务暂不可用,请稍后重试”。

+ 141 - 0
docs/前端交付/bot-28/后台/公告系统-后台接口.md

@@ -0,0 +1,141 @@
+# 公告系统:后台接口(多语言)
+
+后台项目 `bot-28`。请求头 `Authorization: Bearer <admin_token>`;POST 使用JSON。成功 `code=0`,校验失败 `code=-3`,权限和登录失效沿用后台拦截器。
+
+## 接口
+
+| 方法 | 地址 | 作用 |
+|---|---|---|
+| GET | `/admin/announcement` | 分页管理列表 |
+| GET | `/admin/announcement/options` | 分类、语言、平台、频率、点击类型、状态及默认值 |
+| GET | `/admin/announcement/detail?id=123` | 多语言详情及编辑回显 |
+| POST | `/admin/announcement/submit` | 新增、编辑 |
+| POST | `/admin/announcement/status` | 开启/关闭,JSON `{"id":123,"status":0}` |
+| GET | `/admin/announcement/logs` | 公告日记 |
+
+沿用管理员登录和按钮权限,`options/detail` 复用 `admin/announcement`,启停复用 `admin/announcement/submit`,日志使用 `admin/announcement/logs`。本次无删除、独立审批和展示回执接口。
+
+## 分类与语言表单
+
+选项接口返回 `categories/languages/platforms/display_frequencies/click_types/statuses`,均为 `[{value,label}]`;另返回 `timezone=Asia/Shanghai` 和 `defaults`。
+
+| 字段 | 值 |
+|---|---|
+| category | `content` 弹出内容公告;`image` 弹出图片公告;`update` 弹出更新公告 |
+| language | `zh` 中文;`en` 英文;`vi` 越南语 |
+| publish_platforms | 非空、去重数组,元素为 `PC/H5/APP`;三项全选为全部 |
+| display_frequency | `daily_first_login` 每日首次登录;`every_login` 每次登录 |
+| click_type | `none` 无链接;`external` 外站;`internal` 站内路径;`activity` 优惠活动 |
+| status | 整数 `0` 关闭、`1` 开启 |
+
+创建默认:内容公告、全部平台、每日首次登录、立即发布、不结束、开启、无链接。**标题和正文改用translations,不再提交顶层title/content。** 每个语言独立填写标题和正文;图片公告每个语言分别上传一张图,仍用content保存图片地址。
+
+```json
+{
+  "category":"content",
+  "translations":{
+    "zh":{"title":"网站维护","content":"今晚维护,请提前安排。"},
+    "en":{"title":"Maintenance","content":"Scheduled maintenance tonight."},
+    "vi":{"title":"","content":""}
+  },
+  "publish_platforms":["PC","H5","APP"],
+  "display_frequency":"daily_first_login",
+  "publish_immediately":false,
+  "publish_at":"2026-10-27 09:00:00",
+  "no_end":false,
+  "end_at":"2026-10-28 09:00:00",
+  "status":1,
+  "click_type":"none",
+  "link":null,
+  "activity_id":null
+}
+```
+
+- `translations` 是以zh/en/vi为键的对象,标题与正文存入独立 `announcement_translations` 表,公共配置存在 `announcements`。
+- 正文空或纯空白时该语言不保存、不展示,不自动使用其他语言代替。至少一个语言有完整标题和正文;正文非空时标题必填。
+- 标题最多200字;两种文本公告正文最多20000字,按纯文本渲染。图片公告content最长2048字符,使用jpg/jpeg/png/webp图片地址。
+- 编辑传id,省略translations时保留旧内容;提交translations时**完整替换**所有语言,空正文或未包含的语言版本会被移除。切换分类必须重新提交translations。
+- sender_id/sender_name/sender_display不可提交指定,发送者由创建时登录管理员生成。后续修改不改变原发送者,实际操作员另记日记。
+
+## 图片与点击行为
+
+图片上传复用 `POST /admin/upload/uploadFile`,multipart字段image,最大10MB,支持jpg/jpeg/png/webp,返回data.path。该地址放入对应语言 `translations.zh.content` 等;图片类型没有额外文字正文,另外两类没有版本升级功能。
+
+所有语言共用click_type、link、activity_id:
+
+- none清空两个目标字段。
+- external需要完整HTTP/HTTPS link,禁止其他协议和URL账号密码。
+- internal需要单个 `/` 开头的站内路径,禁止 `//外站` 和反斜杠。
+- activity需要已存在的activity_id,link清空;活动选择器复用 `GET /admin/ActivityReward`,取ID和标题。
+
+活动未开始、失效、被删、语言不符或对当前会员不可见时,公告照常展示,会员接口只禁用跳转。活动可见但会员暂不能参加时仍允许打开详情。
+
+## 时间与状态
+
+时间统一北京时间Asia/Shanghai,格式 `YYYY-MM-DD HH:mm:ss`。新增未提供时间设置默认立即、不结束。
+
+`publish_immediately=false` 的定时新增必须填publish_at;true会把未来计划改为当前时间,已发布公告编辑时保留原发布时间。编辑省略时间字段保留原值。详情publish_immediately是“发布时间是否已经到达”的回显值。
+
+`no_end=true` 清空end_at;false必须有结束时间,且严格晚于发布时间。到期不会改写手动status,也无需专用定时任务。
+
+| publication_state | 中文 | 条件 |
+|---|---|---|
+| disabled | 关闭 | status=0 |
+| scheduled | 未开始 | 开启但尚未到publish_at |
+| active | 展示中 | 开启、开始时间已到、未结束 |
+| expired | 已结束 | 开启且结束时间已到 |
+
+返回effective_status为当前时间条件是否有效的0/1。该值不代表每个语言和平台都有正文;会员实际可见还要匹配账号语言及平台。状态相同的重复保存不重复写日记。
+
+## 管理列表、详情与语言筛选
+
+`GET /admin/announcement?page=1&limit=20&language=en&title=Maintenance`
+
+| 参数 | 说明 |
+|---|---|
+| page/limit | 默认1/20,limit最大200 |
+| category | 可选三种分类 |
+| platform | PC/H5/APP,匹配发布平台数组 |
+| status | 手动开关0/1,0不能被前端过滤 |
+| language | zh/en/vi;不传不限语言 |
+| title | 标题模糊查询;选language时只匹配该语言,否则匹配任意已有语言 |
+| start_date/end_date | 成对YYYY-MM-DD,含首尾两天,筛选publish_at |
+
+一条多语言公告只占一行,语言和标题过滤在统计分页前完成。按publish_at、ID倒序,返回 `data.total/data.data`。列表提供 `languages:["zh","en"]`、`titles:{"zh":"网站维护","en":"Maintenance"}`,不批量返回正文;选择语言后标题列读取titles[language],不选择时可按语言标签分行展示。
+
+公共返回字段为id、category/category_name、publish_platforms/platform_label、display_frequency/display_frequency_name、publish_at、end_at、status、click_type、link、activity_id、sender_id/sender_name/sender_display、created_at/updated_at、publication_state/publication_state_name、effective_status、publish_immediately、no_end。
+
+“发送时间”列对应publish_at,“发送者”使用sender_display,例如 `批核者:admin`,账号不可写死。“不结束”对应end_at=null。
+
+详情和保存成功data在上述字段上增加完整translations对象。客户端应先按options.languages初始化三个空表单,再合并已有内容,不把titles当正文提交。
+
+## 公告日记
+
+`GET /admin/announcement/logs` 支持page/limit、start_date/end_date(操作日期)、operator_name(模糊)、language、resource_id。兼容announcement_id作为资源ID参数。
+
+返回 `data.total/data.data`,按操作时间和ID倒序,每项包含:
+
+```json
+{
+  "id":8,"resource_id":123,"created_at":"2026-10-26 10:00:00",
+  "operator_name":"admin","action":"update",
+  "languages":["zh","en"],"titles":{"zh":"网站维护","en":"Maintenance"},
+  "recipient_type":"all_users","recipient_label":"所有人",
+  "old_values":{"translations":{"en":{"title":"Maintenance","content":"Old"}}},
+  "new_values":{"translations":null}
+}
+```
+
+action为create/update/status。old_values/new_values只含变化字段。语言筛选匹配**该次操作前后**的语言快照,所以后来移除英文版本,英文历史操作仍能查询。选择语言后,翻译前后值仅呈现该语言,其他公共配置变化照常返回。原管理员、历史标题和内容不会被后续编辑改写。
+
+公告与审计同事务,审计失败全部回滚;自然到期不生成管理员操作日志。
+
+## 上线
+
+原公告尚未上线,本次直接调整其建表迁移,新增独立翻译表,不做旧数据转换:
+
+```bash
+php artisan migrate --path=database/migrations/2026_10_26_100000_create_announcements.php --force
+```
+
+依赖现有operation_audits表,后台及melbet_sport-api共享业务库并同步部署。校验/不存在错误code=-3,内部异常统一“公告服务暂不可用,请稍后重试”。

+ 182 - 0
docs/前端交付/bot-28/后台/整站与游戏维护-后台接口.md

@@ -0,0 +1,182 @@
+# 整站与游戏维护:后台接入
+
+后台项目 `bot-28`,前台项目 `melbet_sport-api`。本文件为新增独立文档。
+
+## 权限与返回
+
+请求头 `Authorization: Bearer <admin_token>`,POST 使用 JSON。后台成功 `code=0`,校验失败 `code=-3`;沿用管理员登录与按钮权限,并要求登录管理员属于 `config/operations.php` 配置的运营管理员(默认 ID=1)。
+
+| 方法 | 地址 | 作用 / 参数 |
+|---|---|---|
+| GET | `/admin/maintenance` | 管理列表 |
+| GET | `/admin/maintenance/options` | 范围、分类、平台、语言、状态、时区、默认值与业务 code |
+| GET | `/admin/maintenance/detail?id=1` | 设置详情,含三个语言的已填写文案 |
+| POST | `/admin/maintenance/submit` | 编辑固定设置,必须传 `id` |
+| POST | `/admin/maintenance/status` | 开关,`{"id":1,"enabled":0}` |
+| GET | `/admin/maintenance/logs` | 操作日志 |
+
+`options/detail` 复用按钮 URI `admin/maintenance`;`status` 复用 `admin/maintenance/submit`;日志 URI 为 `admin/maintenance/logs`。菜单和按钮由后台前端按这些 URI 接入。
+
+## 固定范围
+
+初始化有 10 条设置,**全部默认关闭**。前端从列表或 options 获取范围,不写死数据库 ID。仅编辑和启停,无新增/删除设置操作。
+
+| scope_code | 名称 |
+|---|---|
+| `site` | 整站 |
+| `third:1` | 真人 |
+| `third:2` | 电子 |
+| `third:3` | 彩票 |
+| `third:4` | 体育 |
+| `third:5` | 电竞 |
+| `third:6` | 捕鱼 |
+| `third:7` | 棋牌 |
+| `self:sport` | F1体育 |
+| `self:lottery` | F1彩票 |
+
+九个游戏分类与返佣分类相同;第三方体育与 F1 体育分别配置。
+
+## 表单与保存
+
+options 返回 `scopes/categories/platforms/languages/states`,格式均为 `[{"value":"...","label":"..."}]`;`timezones` 是标准 IANA 时区字符串数组;`defaults` 含默认关闭、全部平台、Asia/Shanghai 时区和无结束时间;`business_codes` 为 `{"site":11001,"game":11002}`。
+
+```json
+{
+  "id": 1,
+  "enabled": 1,
+  "platforms": ["PC", "H5", "APP"],
+  "timezone": "Asia/Shanghai",
+  "start_now": false,
+  "starts_at": "2026-10-27 02:00:00",
+  "ends_at": "2026-10-27 04:00:00",
+  "translations": {
+    "zh": {"content": "系统维护中,预计北京时间04:00恢复。"},
+    "en": {"content": "Scheduled maintenance. Expected to resume at 04:00 (UTC+8)."},
+    "vi": {"content": "Hệ thống đang bảo trì. Vui lòng thử lại sau."}
+  }
+}
+```
+
+| 字段 | 说明 |
+|---|---|
+| id | 必填,列表返回的设置 ID |
+| enabled | 整数 `0/1`,开关 |
+| platforms | 非空去重数组,仅 `PC/H5/APP`,可多选 |
+| timezone | 标准时区,例 `Asia/Shanghai`、`America/New_York`、`UTC`,使用 options 候选 |
+| start_now | 布尔值;true 将开始时间设为服务器当前时间,优先于 starts_at |
+| starts_at | 所选时区的本地时间,格式 `YYYY-MM-DD HH:mm:ss` |
+| ends_at | 同一时区的本地时间,必须晚于开始;传 null 清空,之后手动关闭 |
+| translations | 以 `zh/en/vi` 为键;每个语言仅 content,最多 5000 字符,按纯文本展示 |
+
+- 编辑省略字段时保留原值。`scope_code` 固定,不可提交修改。
+- `start_now=true` 每次明确提交都会重新设置开始时间,编辑普通文案时应省略此字段。若原结束时间已经过去,立即开启时同时传新的 ends_at 或 `ends_at:null`。
+- 提交 translations 时完整替换所有语言;未包含、正文空或纯空白的语言不保存。允许全部留空,该语言的用户将看到系统默认维护提示;**空文案不会跳过维护**。
+- 用户填写的文案不会被其他语言替换。系统默认提示分别有中文、英文、越南语版本。
+- 仅修改 timezone 且省略起止时间时,保留已经保存的实际时间点,按新时区回显。若要更改时间点,一并提交对应本地时间。
+- 拒绝该时区中不存在或有夏令时重复歧义的本地时间;选择其他明确时间后重试。
+- 关闭保留时间、平台和文案。再次开启不会自动重排时间;已过期的设置仍为“已结束”,需要修改结束时间或清空。
+
+保存与详情返回相同的完整记录:
+
+```json
+{
+  "code": 0,
+  "msg": "OK",
+  "data": {
+    "id": 1,
+    "scope_code": "site",
+    "scope_name": "整站",
+    "scope": "site",
+    "category": null,
+    "enabled": 1,
+    "state": "scheduled",
+    "platforms": ["PC", "H5", "APP"],
+    "timezone": "Asia/Shanghai",
+    "starts_at": "2026-10-27 02:00:00",
+    "ends_at": "2026-10-27 04:00:00",
+    "starts_at_utc": "2026-10-26T18:00:00Z",
+    "ends_at_utc": "2026-10-26T20:00:00Z",
+    "translations": {
+      "zh": {"content": "系统维护中,预计北京时间04:00恢复。"},
+      "en": {"content": "Scheduled maintenance. Expected to resume at 04:00 (UTC+8)."},
+      "vi": {"content": "Hệ thống đang bảo trì. Vui lòng thử lại sau."}
+    }
+  }
+}
+```
+
+成功包装同时有 timestamp,按项目通用拦截器处理。无语言文案时 translations 返回 `{}`。
+
+## 状态与优先级
+
+| state | 含义 |
+|---|---|
+| disabled | 手动关闭 |
+| scheduled | 已开启,但未到开始时间 |
+| active | 已开启且在维护区间内 |
+| expired | 已开启,但已到或超过结束时间 |
+
+有效区间是 `starts_at <= 当前时间 < ends_at`;结束为空则持续到手动关闭。按 UTC 的实际时间判断,无需每分钟任务改写 enabled;界面应同时展示开关和计算状态。
+
+整站维护只有在**当前客户端平台及当前时间都命中**时,才优先于游戏分类。整站未开启、待开始、已结束或仅维护其他平台时,仍判断当前游戏分类。整站和分类规则互不覆盖保存值,整站恢复后原有分类规则继续独立生效。
+
+每个新请求读取当前配置,无跨请求配置缓存。已经通过检查的进行中请求不强制中断。
+
+## 列表筛选
+
+`GET /admin/maintenance?page=1&limit=20&platform=H5&enabled=1&state=active&language=en`
+
+| 参数 | 说明 |
+|---|---|
+| page / limit | 默认 1 / 20,limit 最大 100,page 最大 1000000 |
+| scope_code | 固定范围代码,精确匹配 |
+| platform | PC/H5/APP,包含该平台 |
+| enabled | 0/1,手动开关 |
+| state | disabled/scheduled/active/expired,当前计算状态 |
+| language | zh/en/vi,仅筛选该语言有填写文案的设置,不影响实际维护拦截 |
+
+返回 `data:{"total":10,"data":[详情结构...]}`。所有筛选先执行,再计数和分页。
+
+## 操作日志
+
+`GET /admin/maintenance/logs?resource_id=1&operator_name=admin&language=en&start_date=2026-10-26&end_date=2026-10-27&page=1&limit=20`
+
+支持 resource_id、操作员账号包含搜索、语言、日期和分页。日期是操作日志的北京时间,起止日期一起传;limit 最大 200。返回 `data:{total,data:[]}`,条目含 id、resource_id、created_at、operator_name、action、languages、titles、old_values、new_values。维护日志 action 为 update,old/new_values 是修改前后有差异的字段。
+
+日志的 translations 中 title 是系统生成的“整站/体育/F1体育”等范围名称,content 是当时文案快照;前端维护表单只提交 content。语言筛选同时查询修改前和修改后,删除某语言后仍可查其历史。重复保存相同配置不新增日志。设置、多语言内容和日志在同一事务保存,日志失败则整体回滚。
+
+## 实际拦截范围
+
+- `melbet_sport-api`:整站维护拦截前台请求,包括免登录读取和登录/注册;响应业务 code=11001。
+- 分类维护:第三方 gameUrl/demoUrl/play 返回 code=11002,在玩家创建、资金转入和调用供应商入口前检查;gameCode 对应分类由服务端目录解析。
+- F1 体育:Order 的 submit/submit_delete、SportGameOrder 的 submit,以及进入赛事详情/足球游戏配置的请求。
+- F1 彩票:Game.bet、LhcOrder.submit、JisuGameOrder.submit,以及 PC28、六合彩、极速游戏的进入/状态请求。
+- 保留维护状态查询、User.logout、Egame.recycle、Order.refund、LhcOrder.refund、Wallet.withdrawCancel 的原有认证和业务处理。
+- `bot-28` 现有前台 `/api/wallet/*`、`/api/ActivityReward/*`、`/api/newPc/*`、`/api/issue/*`、`/api/pcissue/*` 同步检查整站规则,沿用其 JSON 包装和相同维护业务 code。
+- 支付回调、系统结算、Telegram webhook/机器人、管理后台、代理独立后台和后台任务继续处理。
+- 本功能按 H5/PC/APP 限制会员网页及 App 入口,不改变机器人渠道已有的独立开关、原 PC28 日常维护及供应商/游戏上架开关。
+
+客户端统一传 `Platform: PC|H5|APP`(请求头优先,兼容请求参数 platform)。未传或非法值按平台未知处理,保守命中当前有效的维护规则,不能通过省略平台绕过维护。平台是客户端声明值,应由各客户端统一固定,不能作为可信身份凭据。
+
+第三方已经打开的游戏会话无法仅靠本站入口检查强制停止;需要供应商停服/踢出能力。维护不撤销旧订单,结算、回调与资金回收继续。
+
+## 数据与上线
+
+新增迁移:`database/migrations/2026_10_26_120000_create_maintenance_settings.php`。使用项目既有数据库前缀,默认实体表为 `bot_maintenance_settings`、`bot_maintenance_translations`。开始、结束及设置记录的时间按 UTC 保存;操作日志按现有北京时间约定记录。
+
+部署顺序:
+
+1. 在 `bot-28` 数据库执行新迁移,再发布两个项目的维护拦截代码;避免拦截代码先访问尚不存在的表。确认既有 `2026_08_30_120000_create_operation_configuration_tables.php` 已提供 operation_audits。
+2. 发布 `melbet_sport-api` 的服务、控制器、中间件、route 配置、游戏入口和跨域请求头变更;两个项目连接同一业务数据库。
+3. 刷新部署环境的路由/代码缓存及常驻进程,接入两个前端的 Platform 请求头和业务 code。
+4. 初始十条规则全部关闭,核对正常请求后,再通过后台开启需要的范围。
+
+本功能不新增定时命令。回滚代码前先关闭所有维护,停止引用新表后再考虑回滚迁移;down 会删除维护配置与文案。
+
+仅执行本次新增迁移的命令(在 bot-28 项目目录,按部署流程使用生产确认参数):
+
+```bash
+php artisan migrate --path=database/migrations/2026_10_26_120000_create_maintenance_settings.php
+```
+
+验收:平台隔离;整站优先且只在自身生效时优先;精确开始/结束边界;无结束时间;三语言及空文案默认提示;后台语言筛选和日志;真实/试玩入口与自营下注受控;被拦截请求不创建玩家、不转入资金、不创建订单;回调、结算、退款及资金回收继续。

+ 11 - 0
docs/前端交付/bot-28/后台/独立活动-后台接口.md

@@ -0,0 +1,11 @@
+# 独立活动后台文档目录
+
+三个活动各自维护完整接入文档,接口和数据独立于原活动列表。
+
+| 活动 | 独立后台文档 | 对应会员文档 |
+|---|---|---|
+| 净亏邀请 | [净亏邀请-后台接口](独立活动/净亏邀请-后台接口.md) | [净亏邀请-会员接口](../../melbet_sport-api/前台/独立活动/净亏邀请-会员接口.md) |
+| 首充奖励 | [首充奖励-后台接口](独立活动/首充奖励-后台接口.md) | [首充奖励-会员接口](../../melbet_sport-api/前台/独立活动/首充奖励-会员接口.md) |
+| 转盘活动 | [转盘活动-后台接口](独立活动/转盘活动-后台接口.md) | [转盘活动-会员接口](../../melbet_sport-api/前台/独立活动/转盘活动-会员接口.md) |
+
+本页仅保留导航;每个活动的参数、示例、上下架、资格和发奖说明均在各自文档内。

+ 75 - 0
docs/前端交付/bot-28/后台/独立活动/净亏邀请-后台接口.md

@@ -0,0 +1,75 @@
+# 净亏邀请-后台接口
+
+业务类型固定为 `7`。本页为该活动的完整后台接入说明。
+
+本活动使用独立的 `promotion_activities` 配置和 `/admin/promotion` 接口,不加入原活动列表或原活动类型目录。以下接口使用现有后台登录和按钮权限,成功 `code=0`,参数或业务错误 `code=-3`,展示实际 `msg`。
+
+## 配置接口
+
+| 方法 | 路径 | 参数与用途 |
+|---|---|---|
+| GET | `/admin/promotion?type=7` | 仅查看本活动类型;可选 `page/limit/status/title` |
+| GET | `/admin/promotion/options` | 读取 `defaults["7"]` 默认规则 |
+| GET | `/admin/promotion/detail?id=1` | 配置详情,返回 `params` 对象及 `first_published_at` |
+| POST | `/admin/promotion/submit` | 新增或编辑本活动,固定 `type=7` |
+| POST | `/admin/promotion/status` | 传 `id/status` 上下架 |
+| GET | `/admin/promotion/records` | 查看参与或发奖记录 |
+
+列表默认 `page=1/limit=20`,`limit` 最大100。`status=0` 为下架、`1` 为上架,不能丢弃0值筛选。新增省略 `id`,编辑带 `id`;创建后不能改变业务类型。
+
+保存必填根字段:`title/type/status/start_time/end_time`。时间按UTC+8填写,结束必须晚于开始。可选 `sub_title/lang/sort/content/image/publish_platforms`;平台为 `PC/H5/APP`,空数组表示全部。`params` 传对象,未知规则键报错;新增省略的项使用默认值,编辑局部规则保留其余原值。
+
+可选通用资格:`register_time_limited/register_start_time/register_end_time/register_platforms`、`deposit_limited/deposit_min/deposit_max`、`show_ineligible`。新增默认不限制,编辑省略字段保留原值。
+
+可选 `reward_quota` 限制不同获奖会员数,`budget` 限制累计奖励金额;`null` 为不限制,0为无剩余额度。`rewarded_count/budget_used/first_published_at` 为服务端字段,不能手动改写。
+
+## 保存示例
+
+```json
+{
+  "title": "净亏邀请奖励",
+  "type": 7,
+  "status": 0,
+  "start_time": "2026-10-26 00:00:00",
+  "end_time": "2027-10-26 23:59:59",
+  "publish_platforms": [
+    "PC",
+    "H5",
+    "APP"
+  ],
+  "params": {
+    "loss_amount": "500.00",
+    "invite_count": 3,
+    "reward_amount": "500.00",
+    "turnover_ratio": "0.00"
+  }
+}
+```
+
+## 业务规则与字段
+
+| params字段 | 默认值 | 含义 |
+|---|---|---|
+| loss_amount | 500.00 | 申请所需累计净亏 |
+| invite_count | 3 | 申请后需新增的有效直属会员数 |
+| reward_amount | 500.00 | 达标后领取奖金 |
+| turnover_ratio | 0.00 | 奖金所需有效投注倍数 |
+
+1. 从本活动第一次上架后下单的真实注单累计净亏:`max(0,-已结算且未撤销注单净输赢合计)`,盈亏相抵。充值、提现、赠送和转账不算游戏损益。
+2. 达到净亏门槛后会员点击申请;申请成功固定当时的奖励规则、净亏快照及申请时间。无效申请不消耗机会,重复有效申请返回原进度。
+3. 只计申请后新注册且建立直属关系的下级。必须同时达到系统累计真实充值、累计有效投注双门槛;使用当日已生效政策,系统修改门槛按既有规则次日生效,不写死截图200。
+4. 有效投注逐笔按 `min(本金, |净输赢|)` 计算,只计已结算未撤销的真实注单。人工上分、活动赠送不计真实充值。
+5. 新增有效直属会员达到人数后,会员自行领取。每名会员在本活动只能领取一次,下架再上架不重置机会或首次上架时间。
+6. 奖金按申请时保存的规则发放。`turnover_ratio=0` 直接进入可用余额,大于0进入冻结余额,发放后新增有效投注达到“奖金×倍数”后自动解冻,各笔奖金独立计算。
+
+## 参与与发奖记录
+
+`GET /admin/promotion/records?activity_id=1&kind=accounts&page=1&limit=20` 查看申请记录,`kind=rewards` 查看奖金,可追加会员数据库ID `user_id`。返回 `total/data`。
+
+申请记录包含 `applied_at/application_snapshot`;奖金有 `reserved/locked/paid` 状态和资金流水关联。没有有效申请前不允许领奖,重复领取不会重复入账。停止活动使用下架,不删除参与历史。
+
+## 权限与上线
+
+独立菜单为 `promotion`;配置、保存与上下架、记录权限分别为 `admin/promotion`、`admin/promotion/submit`、`admin/promotion/records`。上线执行新增迁移 `2026_10_26_160000_create_promotion_activities.php`,计划任务每分钟运行 `promotions:sync`;有效投注沿用已有分享注单同步。
+
+对应会员端文档:[《净亏邀请-会员接口》](../../../melbet_sport-api/前台/独立活动/净亏邀请-会员接口.md)。

+ 102 - 0
docs/前端交付/bot-28/后台/独立活动/转盘活动-后台接口.md

@@ -0,0 +1,102 @@
+# 转盘活动-后台接口
+
+业务类型固定为 `9`。本页为该活动的完整后台接入说明。
+
+本活动使用独立的 `promotion_activities` 配置和 `/admin/promotion` 接口,不加入原活动列表或原活动类型目录。以下接口使用现有后台登录和按钮权限,成功 `code=0`,参数或业务错误 `code=-3`,展示实际 `msg`。
+
+## 配置接口
+
+| 方法 | 路径 | 参数与用途 |
+|---|---|---|
+| GET | `/admin/promotion?type=9` | 仅查看本活动类型;可选 `page/limit/status/title` |
+| GET | `/admin/promotion/options` | 读取 `defaults["9"]` 默认规则 |
+| GET | `/admin/promotion/detail?id=1` | 配置详情,返回 `params` 对象及 `first_published_at` |
+| POST | `/admin/promotion/submit` | 新增或编辑本活动,固定 `type=9` |
+| POST | `/admin/promotion/status` | 传 `id/status` 上下架 |
+| GET | `/admin/promotion/records` | 查看参与或发奖记录 |
+
+列表默认 `page=1/limit=20`,`limit` 最大100。`status=0` 为下架、`1` 为上架,不能丢弃0值筛选。新增省略 `id`,编辑带 `id`;创建后不能改变业务类型。
+
+保存必填根字段:`title/type/status/start_time/end_time`。时间按UTC+8填写,结束必须晚于开始。可选 `sub_title/lang/sort/content/image/publish_platforms`;平台为 `PC/H5/APP`,空数组表示全部。`params` 传对象,未知规则键报错;新增省略的项使用默认值,编辑局部规则保留其余原值。
+
+可选通用资格:`register_time_limited/register_start_time/register_end_time/register_platforms`、`deposit_limited/deposit_min/deposit_max`、`show_ineligible`。新增默认不限制,编辑省略字段保留原值。
+
+可选 `reward_quota` 限制不同获奖会员数,`budget` 限制累计奖励金额;`null` 为不限制,0为无剩余额度。`rewarded_count/budget_used/first_published_at` 为服务端字段,不能手动改写。
+
+## 保存示例
+
+```json
+{
+  "title": "幸运转盘",
+  "type": 9,
+  "status": 0,
+  "start_time": "2026-10-26 00:00:00",
+  "end_time": "2027-10-26 23:59:59",
+  "publish_platforms": [
+    "PC",
+    "H5",
+    "APP"
+  ],
+  "params": {
+    "completion_probability": "20.00",
+    "online_seconds": 3600,
+    "deposit_amount": "3000.00",
+    "team_deposit_amount": "500.00",
+    "bet_amount": "5000.00",
+    "team_bet_amount": "3000.00",
+    "turnover_ratio": "0.00"
+  }
+}
+```
+
+## 概率、任务与奖金配置
+
+| params字段 | 默认值 | 含义 |
+|---|---|---|
+| completion_probability | 20.00 | 全部七项完成后,每抽一次达到666的概率百分比 |
+| online_seconds | 3600 | 每日在线秒数 |
+| deposit_amount | 3000.00 | 本人当日充值门槛 |
+| team_deposit_amount | 500.00 | 直属下级当日充值合计门槛 |
+| bet_amount | 5000.00 | 本人当日有效投注门槛 |
+| team_bet_amount | 3000.00 | 直属下级当日有效投注合计门槛 |
+| turnover_ratio | 0.00 | 领取奖金所需有效投注倍数 |
+
+`completion_probability` 允许0~100,最多两位小数,默认20。保存0必须仍为0,不回退20。修改只影响后续抽奖,不修改历史结果、概率记录或已预留奖金。未完成全部七项条件时,达到666概率固定为0。
+
+## 七项任务与每日口径
+
+| 任务key | 规则 | 获得次数 |
+|---|---|---:|
+| online | 每日累计在线达到配置秒数 | 每天+1 |
+| login | 每日登录,认证进入活动也计入 | 每天+1 |
+| invite | 每名未计奖的有效直属下级 | 每人+1 |
+| deposit | 本人当日累计真实充值达到门槛 | 每轮每天+1 |
+| team_deposit | 直属下级当日真实充值合计达到门槛 | 每轮每天+1 |
+| bet | 本人当日有效投注达到门槛 | 每轮每天+1 |
+| team_bet | 直属下级当日有效投注合计达到门槛 | 每轮每天+1 |
+
+七项各完成一次即可,可以分别在不同日期完成,完成标记可跨天保留;“当日累计”的金额必须在同一个UTC+8自然日达到,不能跨天拼接。例如昨天充值2000、今天1000,不算达到3000。
+
+有效推广读取当日已生效的系统累计充值、有效投注双门槛,不写死截图200。有效投注逐笔按 `min(本金, |净输赢|)` 计算,未结算和撤销注单不计;人工上分、活动赠送不计充值。团队金额只计直属关系建立后的贡献。
+
+## 奖池与循环领取
+
+奖项顺序固定:`0–666、100、50、200、350、500、150、366`,浮动奖项支持两位小数。金币是活动进度,只有累计达到666并领取才发放666奖金。
+
+全部条件完成后,每次抽奖先按配置概率判定;命中时浮动奖项补齐剩余差额。未命中或条件未完成时进入普通奖池:只保留累计后小于666的固定奖项及浮动奖项;可用奖项等概率,浮动金额在0至剩余差额减0.01之间按分等概率抽取。固定500不会被截成较小金额,页面须展示奖池规则。
+
+达到666时立即预留奖金,停止本轮抽奖;领取不会再次消耗预算,已达到后活动下架仍可领取。领取成功后:金币清零、进入下一轮、未用次数保留、登录和在线完成标记保留,其余五项清零重做。当日登录/在线不因换轮重复发次;旧充值、旧投注及已经计奖的有效下级不能重复贡献。
+
+同一会员多轮只占一个获奖人数名额,每轮666均消耗金额预算。`turnover_ratio=0` 直接进入可用余额,大于0进入冻结余额,各笔奖金独立累计发放后有效投注,达到“666×倍数”自动解冻。
+
+## 记录与播报
+
+`GET /admin/promotion/records?activity_id=1&kind=spins&page=1&limit=20`,`kind` 支持 `accounts/rounds/tasks/spins/rewards`,可加 `user_id`,返回 `total/data`。抽奖记录保存实际概率、是否全部达标、奖项和累计金币;领奖记录有关联资金流水。达到666后的状态依次为 `reserved` 待领取、`locked` 冻结待流水或 `paid` 已可用。
+
+会员“我的记录”使用真实抽奖记录。普通播报及达到666播报各最多50条真实脱敏记录;联调随机样例每组50条,见[转盘播报示例](../../../melbet_sport-api/前台/独立活动/转盘播报示例.json),均带 `is_demo=true`,不作真实中奖记录。
+
+## 权限与上线
+
+独立菜单为 `promotion`;配置、保存与上下架、记录权限分别为 `admin/promotion`、`admin/promotion/submit`、`admin/promotion/records`。上线执行新增迁移 `2026_10_26_160000_create_promotion_activities.php`,计划任务每分钟运行 `promotions:sync`;有效投注沿用已有分享注单同步。
+
+对应会员端文档:[《转盘活动-会员接口》](../../../melbet_sport-api/前台/独立活动/转盘活动-会员接口.md)。

+ 73 - 0
docs/前端交付/bot-28/后台/独立活动/首充奖励-后台接口.md

@@ -0,0 +1,73 @@
+# 首充奖励-后台接口
+
+业务类型固定为 `8`。本页为该活动的完整后台接入说明。
+
+本活动使用独立的 `promotion_activities` 配置和 `/admin/promotion` 接口,不加入原活动列表或原活动类型目录。以下接口使用现有后台登录和按钮权限,成功 `code=0`,参数或业务错误 `code=-3`,展示实际 `msg`。
+
+## 配置接口
+
+| 方法 | 路径 | 参数与用途 |
+|---|---|---|
+| GET | `/admin/promotion?type=8` | 仅查看本活动类型;可选 `page/limit/status/title` |
+| GET | `/admin/promotion/options` | 读取 `defaults["8"]` 默认规则 |
+| GET | `/admin/promotion/detail?id=1` | 配置详情,返回 `params` 对象及 `first_published_at` |
+| POST | `/admin/promotion/submit` | 新增或编辑本活动,固定 `type=8` |
+| POST | `/admin/promotion/status` | 传 `id/status` 上下架 |
+| GET | `/admin/promotion/records` | 查看参与或发奖记录 |
+
+列表默认 `page=1/limit=20`,`limit` 最大100。`status=0` 为下架、`1` 为上架,不能丢弃0值筛选。新增省略 `id`,编辑带 `id`;创建后不能改变业务类型。
+
+保存必填根字段:`title/type/status/start_time/end_time`。时间按UTC+8填写,结束必须晚于开始。可选 `sub_title/lang/sort/content/image/publish_platforms`;平台为 `PC/H5/APP`,空数组表示全部。`params` 传对象,未知规则键报错;新增省略的项使用默认值,编辑局部规则保留其余原值。
+
+可选通用资格:`register_time_limited/register_start_time/register_end_time/register_platforms`、`deposit_limited/deposit_min/deposit_max`、`show_ineligible`。新增默认不限制,编辑省略字段保留原值。
+
+可选 `reward_quota` 限制不同获奖会员数,`budget` 限制累计奖励金额;`null` 为不限制,0为无剩余额度。`rewarded_count/budget_used/first_published_at` 为服务端字段,不能手动改写。
+
+## 保存示例
+
+```json
+{
+  "title": "首充奖励",
+  "type": 8,
+  "status": 0,
+  "start_time": "2026-10-26 00:00:00",
+  "end_time": "2027-10-26 23:59:59",
+  "publish_platforms": [
+    "PC",
+    "H5",
+    "APP"
+  ],
+  "params": {
+    "min_deposit": "100.00",
+    "reward_amount": "25.00",
+    "turnover_ratio": "0.00"
+  }
+}
+```
+
+## 业务规则与字段
+
+| params字段 | 示例值 | 含义 |
+|---|---|---|
+| min_deposit | 100.00 | 历史首笔成功充值的最低实际到账金额 |
+| reward_amount | 25.00 | 固定赠送金额,可自行设置 |
+| turnover_ratio | 0.00 | 奖金所需有效投注倍数 |
+
+1. 判断会员全历史第一笔正向成功入账的“充值/三方充值”,不是活动期间第一笔、当天第一笔或活动开启后的第一次充值。
+2. 以该笔实际到账换算金额判断门槛。失败订单、人工上分、活动赠送不计首充;前台提交凭证并审核成功的真实充值计入。
+3. 第一笔不足门槛或发生在活动下架时,后续第二笔不能补领;已有充值历史的会员不会因新活动上架重新获得资格。
+4. 充值成功自动赠送,不提供会员申请或领取按钮。重复支付回调、补单、重复审核不能重复发奖,下架再上架不重置首充资格。
+5. 多个首充配置同时符合时,按同语言的排序值、ID倒序选择一项;与原有即充即送等充值奖励分别判定,可叠加。
+6. `turnover_ratio=0` 直接进入可用余额,大于0进入冻结余额,发放后新的有效投注达到“奖金×倍数”后自动解冻,各笔奖金独立计算。有效投注逐笔取 `min(本金, |净输赢|)`,未结算和撤销注单不计。
+
+## 发奖记录
+
+`GET /admin/promotion/records?activity_id=1&kind=rewards&page=1&limit=20` 查看奖金,可加会员数据库ID `user_id`,返回 `total/data`;`kind=accounts` 查看参与账号。
+
+奖金状态 `locked` 表示已进入冻结余额待完成流水,`paid` 表示已进入可用余额。可通过 `credited_log_id/unlocked_log_id` 核对资金流水,`rule_snapshot` 保存使用的规则和首充流水来源。停止活动使用下架,历史记录保留。
+
+## 权限与上线
+
+独立菜单为 `promotion`;配置、保存与上下架、记录权限分别为 `admin/promotion`、`admin/promotion/submit`、`admin/promotion/records`。上线执行新增迁移 `2026_10_26_160000_create_promotion_activities.php`,计划任务每分钟运行 `promotions:sync`;有效投注沿用已有分享注单同步。
+
+对应会员端文档:[《首充奖励-会员接口》](../../../melbet_sport-api/前台/独立活动/首充奖励-会员接口.md)。

+ 205 - 0
docs/前端交付/bot-28/后台/龙虎榜资金流动与报表-接口.md

@@ -0,0 +1,205 @@
+# 龙虎榜、资金流动、营业/推广/分享报表:后台前端接入
+
+项目 `bot-28`,本文件为新增独立接口文档。后台只提供 API;页面按参考图接入。
+
+## 通用约定与接口
+
+请求头 `Authorization: Bearer <admin_token>`。POST 使用 JSON,后台成功 code=0、参数错误 code=-3,登录与权限错误沿用现有处理。普通接口需管理员登录和按钮权限;设置写入、费用核对、订单时间核对额外要求运营管理员。
+
+| 方法 | 完整路径 | 用途 |
+|---|---|---|
+| GET | `/admin/reporting/options` | 下拉选项和统计规则 |
+| GET | `/admin/reporting/leaderboard` | 龙虎榜 |
+| GET | `/admin/reporting/fund-flow` | 总资金流与每日列表 |
+| GET | `/admin/reporting/summary` | 三类报表汇总、九类游戏卡片 |
+| GET | `/admin/reporting/details` | 会员/直属代理/直接邀请人明细 |
+| GET | `/admin/reporting/orders` | 明细的原始游戏交易追溯 |
+| GET | `/admin/reporting/events` | 存提款、优惠、佣金等资金记录追溯 |
+| GET / POST | `/admin/reporting/settings` | 读取/设置有效会员最低存款 |
+| GET / POST | `/admin/reporting/payment-reconciliation` | 收付款待核对列表/保存核对结果(运营管理员) |
+| POST | `/admin/reporting/order-reconciliation` | 补录已结算/已撤销订单的真实结算时间(运营管理员) |
+| GET | `/admin/reporting/data-quality` | 统计导入异常、来源状态,支持page/limit(运营管理员) |
+
+options/details/orders/events/settings 的读取权限复用 `admin/reporting/summary`。三个菜单分别使用 `admin/reporting/leaderboard`、`admin/reporting/fund-flow`、`admin/reporting/summary`;写入配置和核对分别配置对应按钮。后台操作不会触发打款、扣款或重新结算。
+
+## 公共筛选
+
+| 参数 | 允许值/默认值 |
+|---|---|
+| start_date / end_date | `YYYY-MM-DD`;默认当天,北京时间,含结束日,最多366天,不允许未来日期 |
+| report_type | `business` 营业、`promotion` 代理推广、`sharing` 新分享;默认 business |
+| platform | `all/H5/PC/APP/unknown`;默认 all。这里是业务发生时的客户端装置 |
+| category | `all` 或九类游戏代码,默认 all;明细和追溯可按卡片范围查询 |
+| settlement_status | `all/pending/settled`,默认 all,只筛游戏订单,不筛资金或佣金的发放状态 |
+| member_account | 会员账号包含搜索;账号优先 username,再 account,再会员编号 |
+| agent_account | 代理账号包含搜索 |
+| inviter_account | 直接邀请人账号包含搜索 |
+| group_id | 从明细返回,继续追溯该组交易;作为字符串传递 |
+| page / limit | 默认1/20,limit最大100,page最大1000000 |
+
+资金流固定为全系统、所有游戏、所有结算状态。金额为钱包本位币,响应 currency 标明币种,通常 CNY。所有金额是十进制字符串,前端格式化为两位小数,不能先转浮点再进行金额汇总。
+
+options 返回 report_types、categories(键值标签对象)、platforms、settlement_statuses、ranking_metrics、timezone、currency、max_days、rules。
+
+九类代码:third:1真人、third:2电子、third:3彩票、third:4体育、third:5电竞、third:6捕鱼、third:7棋牌、self:sport F1体育、self:lottery F1彩票。
+
+## 龙虎榜
+
+`GET /admin/reporting/leaderboard?start_date=2026-10-26&end_date=2026-10-26&metric=deposit_amount&page=1&limit=10`
+
+项目下拉由 metric 控制排名,可选 deposit_amount、bet_amount、payout_amount、withdraw_amount、activity_minutes,默认存款降序,同值按稳定组 ID 排序,rank 跨页连续。
+
+返回 data 包含 total、data 数组,以及下文的通用统计元数据。行字段:rank、group_id、member_account、nickname、deposit_amount、bet_amount、payout_amount、withdraw_amount、activity_minutes。
+
+消费总额展示 bet_amount(投注总额),销售总额展示 payout_amount(派奖总额),建议页面直接使用“投注总额/派奖总额”。在线时间展示 activity_minutes,单位分钟;历史缺失时是 null,不能显示成0。选择活动时长排名时,缺失时长的行不参与排名。
+
+## 三类报表
+
+```http
+GET /admin/reporting/summary?report_type=promotion&platform=H5&settlement_status=settled&start_date=2026-10-26&end_date=2026-10-26
+```
+
+返回 data.summary 为“所有订单”,data.categories 为九类卡片数组。每个卡片以 group_id 表示 all 或分类代码,label 为展示名称;所有卡片结构一致。
+
+完整的“所有订单”指标示例(省略其他指标):
+
+```json
+{
+  "group_id": "all",
+  "label": "所有订单",
+  "deposit_amount": "1000.0000000000",
+  "withdraw_amount": "686.0000000000",
+  "member_withdraw_amount": "686.0000000000",
+  "agent_withdraw_amount": "0.0000000000",
+  "deposit_fee": "2.0000000000",
+  "withdraw_fee": "1.0000000000",
+  "customer_fee_income": "14.0000000000",
+  "bet_amount": "100.0000000000",
+  "settled_bet_amount": "100.0000000000",
+  "payout_amount": "60.0000000000",
+  "refund_amount": "0.0000000000",
+  "game_gross_profit": "40.0000000000",
+  "bonus_amount": "5.0000000000",
+  "agent_commission": "7.0000000000",
+  "share_commission": "3.0000000000",
+  "company_profit": "22.0000000000",
+  "net_cash_flow": "311.0000000000",
+  "valid_member_count": 1,
+  "member_count": 4,
+  "active_member_count": 1,
+  "active_rate": "0.250000",
+  "activity_minutes": 2,
+  "order_count": 1,
+  "complete": true,
+  "quality": {
+    "missing_time": 0,
+    "missing_amount": 0,
+    "unverified_fees": 0,
+    "foreign_currency": 0,
+    "unverified_attribution": 0,
+    "sync_errors": 0,
+    "not_synchronized": false
+  }
+}
+```
+
+| 字段 | 含义 |
+|---|---|
+| deposit_amount | 期间成功收款本金,采用钱包本位币 |
+| withdraw_amount | 实际对外提款净额,含会员和代理成功出款;不是预扣申请额 |
+| deposit_fee / withdraw_fee | 已核对的平台实际外部手续费,未知费用不能当0 |
+| customer_fee_income | 向会员/代理内扣的手续费收入,单列,不当平台成本重复扣 |
+| configured_fee | 原订单按配置算出的费率金额,仅参考,不冒充实际费用 |
+| bet_amount | 期间下单投注本金;取消/退款金额另外展示 |
+| settled_bet_amount | 期间实际结算的投注净额,扣除退款本金 |
+| payout_amount / refund_amount | 期间实际结算的派奖/退款,部分退款与派奖分开 |
+| game_gross_profit | 已结算投注净额减派奖,平台盈利为正 |
+| bonus_amount | 实发优惠,含笔笔返、回水、可用奖励、利息及冻结奖励实际解冻;不重复计原冻结金额 |
+| agent_commission / share_commission | 实际计入代理余额/审核通过计入会员钱包的佣金 |
+| agent_accrual / share_accrual | 期间佣金业务日的应计金额,分享包含修正差额;不再次作为实发成本扣减 |
+| manual_credit / manual_debit | 人工上分/扣分,独立展示,不直接冒充现金存提款 |
+| company_profit | 游戏毛利减实发优惠、代理佣金、分享佣金、实际平台费用;未结算筛选下为null |
+| net_cash_flow | 成功收款减实际对外打款减实际平台费用;优惠和内部佣金不直接当外部现金再减 |
+| valid_member_count | 期间存款严格超过门槛且至少一笔非撤销投注的去重人数 |
+| active_rate | 区间活跃会员占比,例如0.25展示25%;采集不覆盖或归属分母无法核实时为null,不是查询时实时在线率 |
+| activity_population_complete | 活动快照中的活跃会员是否全部仍在当前人口筛选范围内;改绑造成范围不一致时为false |
+
+分类卡片只展示可可靠归属的游戏指标和分类佣金。存提款、公共优惠、通用手续费、净入金及公司净收益无法直接分配到九类游戏,返回 null;用“—”展示,不复制总金额、不按投注比例摊分。分类毛利可用 game_gross_profit 展示。各分类人数可能重复,不能相加当总人数。
+
+营业包含系统真实用户和 Telegram 真实业务;排除试玩和虚拟投注。推广与分享不是互斥集合,同一会员可能同时属于两者,**营业总计不能通过推广加分享得出**。
+
+## 点击卡片后的明细
+
+```http
+GET /admin/reporting/details?report_type=promotion&category=self:lottery&agent_account=agent&page=1&limit=20
+```
+
+返回 total、data、unallocated。data 行有 group_id、label 和相同统计指标:
+
+- business:按会员分组,member_account、nickname、agent_account、inviter_account 用于回显。
+- promotion:按直属代理分组,agent_account;父代理不重复包含下级代理会员。
+- sharing:按直接邀请人分组,label/member_account 为邀请人账号;实际佣金按接收人单列。
+- unallocated 是无法分给会员的公共资金/成本,例如独立代理账户成本,不算一名会员。界面可在明细底部展示;为空时没有此类数据。
+
+分页后的行不能代替完整总计。完整总计使用相同筛选的 summary;人员、费用归属和日期基础不同,不自行将子报表拼成营业总计。
+
+追溯游戏交易:`GET /admin/reporting/orders?report_type=promotion&group_id=10&category=self:lottery`。返回 total、data,行含 source、source_id、parent_source、parent_id、user_id、category、client_platform、currency、bet_at、settled_at、bet_amount、payout_amount、refund_amount、settled、voided、time_quality、attribution_quality。
+
+追溯资金:`GET /admin/reporting/events?report_type=business&group_id=2&kind=deposit`。kind 可用 deposit/withdraw/agent_withdraw/bonus/agent_commission/share_commission/agent_accrual/share_accrual/manual_credit/manual_debit。返回原始 amount、occurred_at、candidate_at,及核对后的 verified_amount、verified_at、external_fee、reconciliation_id;有核对记录时以 verified_* 为准。未确认时间的记录列为待核对,不用创建时间冒充成功时间。
+
+## 资金流动
+
+`GET /admin/reporting/fund-flow?start_date=2026-10-01&end_date=2026-10-26&page=1&limit=10`
+
+返回 summary(全日期总计)、total(天数)、data(每日列表)。日期倒序、没有业务的日期也有记录;每日 date 为 YYYY-MM-DD。金额使用上表字段;图中的“总盈利”使用 game_gross_profit,并另外展示 company_profit,图中资金流公式改为已确认的实际净入金公式。
+
+日期无法确认的收付款会使相应汇总处于待核对状态;每日列表也提示缺失时间的影响,不强行将其放到申请日。
+
+## 完整性、更新与在线数据
+
+统计接口同时返回 filters、timezone、currency、data_updated_at、server_time、activity_started_at、valid_deposit_min、commission_balances。
+
+- complete=false 时 company_profit/net_cash_flow 为 null。known_company_profit/known_net_cash_flow 是**已知小计**,必须带“未完成核对”的说明,不能改名为最终结果。
+- quality 分别提示缺失时间/金额、未核对费用、非本位币、未核实的历史代理归属、导入错误、尚未完成首次同步。0金额与缺失null不是同一个含义。
+- attribution_quality=event_snapshot 是事件快照;historical_current_agent 是没有历史快照时参考当前代理关系。推广报表会将这种历史归属标为待核对,不冒充精确历史业绩。
+- 装置没有可靠证据时为 unknown,包括第三方回单;供应商 platform 不能当H5/PC/APP。
+- 活动时长从新采集上线后记录,依据已认证API请求的服务端观测及TG用户操作,保留可选心跳兼容;间隔超过5分钟拆段、多端重叠去重。active_rate分母是范围内期末真实会员,不按注册端分组。
+- 历史活动的时长和活跃人数按观测保存的agent_id/inviter_id筛选、分组,会员改绑不会将历史活动转给新代理或邀请人。多端重叠先合并覆盖区间,再按时间线上最新观测的归属划分,每秒只计算一次;快照为null时不回填当前关系。
+- member_count仍为现有人口范围的参考人数。若历史活动中的会员已因改绑离开该范围,activity_population_complete=false,active_rate返回null,前端显示“—”;activity_minutes、known_activity_minutes及active_member_count仍返回快照范围内的真实值,不用当前人数反推历史比例。
+- 日期早于 activity_started_at 时活动分钟和活跃率为null,同时给 known_activity_minutes。资金流日列表不提供每日在线指标。
+- commission_balances 的 date_basis=current_account_balance,为查询时账户快照,不是期间新增成本;包含 agent_unpaid、share_unclaimed(含预留)、share_reserved、share_debt。不能分配到装置或分类时为null。
+
+## 有效会员配置
+
+`POST /admin/reporting/settings`,例 `{"valid_deposit_min":"20.00"}`。门槛默认20,可为0,最多14位整数及2位小数;“超过”为严格大于。保存后查询按当前门槛重算,接口回显使用值;不会修改分享返佣的门槛或政策。
+
+## 核对界面(仅运营管理员)
+
+列表:`GET /admin/reporting/payment-reconciliation?status=pending&kind=withdraw&page=1&limit=20`。status=all/pending/verified,kind=deposit/withdraw/agent_withdraw;返回 total、data。
+
+保存:
+
+```json
+{
+  "event_id": 123,
+  "actual_amount": "686.00",
+  "external_fee": "1.00",
+  "occurred_at": "2026-10-26 10:00:00",
+  "currency": "CNY",
+  "evidence": "支付账单或转账记录编号"
+}
+```
+
+金额按钱包本位币填写;external_fee 是平台真实承担的外部费用,确认为没有费用时明确填0。occurred_at 是真实成功发生的北京时间,不得晚于当前时间。重复保存同一 event_id 更新该笔核对,有审计,不重复创建资金记录。原配置费率金额仅作参考。
+
+订单时间核对:`POST /admin/reporting/order-reconciliation`,例 `{"id":456,"settled_at":"2026-10-26 10:00:00","evidence":"结算日志编号"}`。id 为 orders 返回的报表记录ID;只补录已结算/撤销订单的时间,不改变原游戏状态,同一父单的已结算明细一起更新。
+
+历史归属核对:两个核对POST还可按凭据提交agent_id(直属代理ID,可null表示直营)、inviter_id(直接邀请人的users.id,可null)、client_platform(H5/PC/APP/unknown)。不能改写会员身份或独立代理账户的实际收付款接收人。order-reconciliation可只提交id、归属字段、evidence,不填settled_at,此时不修改结算时间,待结算订单也可核对归属。明确核对agent_id后,historical_current_agent变为verified;只补装置不会顺便假定代理归属也已核实。保存只改变报表快照,原会员绑定关系不改。
+
+data-quality返回total、data异常列表(source、source_id、candidate_at、reason)及sources同步状态。用于定位无法解析的来源记录,解决后重跑同步;不要仅修改图表显示来隐藏异常。
+
+来源状态与已核实的非零收付款冲突时,统计暂停给出最终净值;根据凭据核对原状态及实际金额,真实未打款可以更正核对金额为0,发生的外部费用仍单列。不得为了消除提示随意填写0。
+
+前台会员端在正常API请求中携带[统计装置与活动采集-前台接口.md](../../melbet_sport-api/前台/统计装置与活动采集-前台接口.md)规定的Platform即可自动记录活跃,无需额外定时心跳;未传装置仍归unknown。
+
+活动时长是API请求间隔估算,已认证GET、POST和业务参数失败均记录访问;游客、无效Token及维护前置拦截不记。无请求的静态停留不继续增长,后台轮询可能计入,不能视为精确在线时长。

+ 7 - 0
docs/前端交付/melbet_sport-api/README.md

@@ -0,0 +1,7 @@
+# melbet_sport-api 接口交付
+
+- [前台](前台/README.md):9 个交付文件
+- [后台](后台/README.md):0 个交付文件
+- [代理端](代理端/README.md):0 个交付文件
+
+[交付总目录](../README.md)

+ 5 - 0
docs/前端交付/melbet_sport-api/代理端/README.md

@@ -0,0 +1,5 @@
+# melbet_sport-api · 代理端接口交付
+
+本次指定Git范围内,没有需要交付的该端新增或修改接口文档。
+
+[项目目录](../README.md) · [交付总目录](../../README.md)

+ 15 - 0
docs/前端交付/melbet_sport-api/前台/README.md

@@ -0,0 +1,15 @@
+# melbet_sport-api · 前台接口交付
+
+| 文档 | 类型 |
+|---|---|
+| [信息中心-前台接口](信息中心-前台接口.md) | 正文 |
+| [公告系统-前台接入文档](公告系统-前台接入文档.md) | 正文 |
+| [整站与游戏维护-前台接口](整站与游戏维护-前台接口.md) | 正文 |
+| [统计装置与活动采集-前台接口](统计装置与活动采集-前台接口.md) | 正文 |
+| [会员接入](独立活动/会员接入.md) | 目录 |
+| [净亏邀请-会员接口](独立活动/净亏邀请-会员接口.md) | 正文 |
+| [首充奖励-会员接口](独立活动/首充奖励-会员接口.md) | 正文 |
+| [转盘活动-会员接口](独立活动/转盘活动-会员接口.md) | 正文 |
+| [转盘播报示例](独立活动/转盘播报示例.json) | 示例数据 |
+
+[项目目录](../README.md) · [交付总目录](../../README.md)

+ 102 - 0
docs/前端交付/melbet_sport-api/前台/信息中心-前台接口.md

@@ -0,0 +1,102 @@
+# 会员信息中心:前台接入
+
+接口项目 `melbet_sport-api`,使用会员 API 域名和原有加密请求工具。
+
+```http
+Authorization: <用户登录返回的原始token>
+Lang: zh
+Content-Type: application/json
+```
+
+原始token不加 `Bearer `。成功 `code=1`,业务失败 `code=0`,登录失效 `code=401`,均按前台原有拦截器处理。身份从token映射到会员主键,不能通过请求中的 `user_id/member_id/recipient_id` 切换收件箱。
+
+## 接口
+
+| 方法 | 地址 | 用途 |
+| --- | --- | --- |
+| GET | `/api/message/lists` | 我的信息列表 |
+| GET | `/api/message/detail?id=123` | 信息详情 |
+| GET | `/api/message/unreadCount` | 我的未读总数 |
+| POST | `/api/message/read` | 标记已读,JSON `{"id":123}` |
+
+列表参数:`page`默认1、`limit`默认20且最多100;可选 `category`、`is_read=0/1`。分类为 `discount` 优惠活动、`site_notice` 网站公告、`internal` 站内信、`promotion` 活动宣传。没有 `platform` 参数,各端共享同一账号收件箱。
+
+```json
+{
+  "code": 1,
+  "show": 0,
+  "msg": "",
+  "data": {
+    "count": 1,
+    "list": [{
+      "id": 123,
+      "category": "internal",
+      "category_name": "站内信",
+      "sent_at": "2026-10-26 11:00:00",
+      "title": "安全提醒",
+      "subtitle": "请查看",
+      "language": "zh",
+      "read_at": null,
+      "is_read": false
+    }]
+  }
+}
+```
+
+按 `sent_at`、ID倒序。`count` 是筛选后的全部数量,分页之前统计;空列表返回 `count=0/list=[]`,页码超过末页时保留实际count。列表不带正文,详情在上述字段基础上增加 `content`。
+
+`title/subtitle/content` 均为纯文本,保留换行,不使用HTML方式渲染。详情不返回其他语言版本、管理员身份或其他收件人的资料。
+
+## 已读、未读与撤回
+
+`GET /api/message/unreadCount` 返回 `data:{"count":3}`,可用于导航角标。计数仅包含实际投递给当前账号、未读且未撤回的信息,不限制发送日期。
+
+拉取列表、预加载或读取详情不会标记已读。真正打开详情后调用:
+
+```http
+POST /api/message/read
+```
+
+```json
+{"id":123}
+```
+
+成功返回 `data:{"id":123,"is_read":true,"read_at":"2026-10-26 11:05:00"}`。重复调用幂等,不更新首次阅读时间;已读成功后刷新未读总数和列表状态。
+
+后台撤回后,已读和未读信息都从前台列表隐藏,未读总数同步减少,旧ID的详情和标记已读统一提示“信息不存在”。不能读取代理独立后台或其他会员的收件记录。没有会员个人删除接口。
+
+## 语言与投递
+
+- 实际发送时按 `bot_users.language` 匹配 `zh/en/vi` 翻译,后台没填写该语言正文时不投递,不用其他语言兜底。
+- 本次请求头 `Lang` 只影响响应提示文案,不能切换已收到信息的正文。
+- 每条收件记录保存当时的投递语言,详情中的 `language` 是该快照。账号后来改语言,旧信息仍显示原来收到的内容。
+- 后续注册账号不补收之前群发的信息。定时等待期间已注册的账号可在实际发送时被纳入。
+- 待发送及已撤回的信息不可见。重复轮询不会生成新收件记录。
+
+当前账号语言可从现有 `GET /api/user/info` 的 `language` 字段读取。显式切换并保存使用原有资料更新接口:
+
+```http
+POST /api/user/update
+```
+
+```json
+{"language":"vi"}
+```
+
+仅允许 `zh/en/vi`,不接受空值或其他代码。服务端不会从每次请求头自动覆盖账号语言,会员旧语言值不会被迁移重写。前台已有英文语言包,本次也补入允许语言配置。
+
+## 调用示例与错误
+
+```javascript
+const result = await request.get('/api/message/lists', {
+  params: { page: 1, limit: 20, is_read: 0 }
+});
+if (result.code === 1) {
+  const rows = result.data.list;
+  const total = result.data.count;
+}
+```
+
+`is_read=0` 必须保留,不能被“过滤空参数”逻辑丢弃。错误页码、超限limit、非法分类/已读状态、错误请求方式等返回 `code=0`。未投递给本人、待发送、已撤回及不存在的信息都提示“信息不存在”,前端移除旧详情并刷新列表即可。
+
+内部错误统一提示“信息服务暂不可用,请稍后重试”。接口沿用项目原有请求/响应加密,不额外使用 WebSocket 或外部推送。后台须先执行消息表迁移和运行每分钟投递调度,会员项目只读取共享业务表。

+ 98 - 0
docs/前端交付/melbet_sport-api/前台/公告系统-前台接入文档.md

@@ -0,0 +1,98 @@
+# 公告系统:会员端接入(多语言)
+
+接口项目melbet_sport-api。使用会员API域名和已有加密请求工具。
+
+```http
+GET /api/announcement/lists?platform=PC
+Authorization: <原始用户token>
+Lang: zh
+```
+
+需要登录,token不加Bearer;platform必填PC/H5/APP,由当前客户端明确传递,不用注册平台或User-Agent代替。`/api/announcement?platform=PC` 是兼容入口。
+
+## 语言来源
+
+公告正文以当前登录会员 `users.language` 为准,只支持zh/en/vi。**Lang请求头不能覆盖账号语言,也不会自动写回账号。** 对应语言没有填写正文时不返回该公告;账号语言为空或未知时返回空列表,不跨语言兜底。
+
+账号当前语言可从GET /api/user/info读取。前端明确保存切换语言使用:
+
+```http
+POST /api/user/update
+Content-Type: application/json
+```
+
+```json
+{"language":"vi"}
+```
+
+只允许zh/en/vi。该修改影响以后请求公告和以后的信息投递,不改写已收到的站内信历史。既有会员语言不会被迁移重写,前端应接入显式保存,而不是只更换请求头。
+
+## 响应
+
+```json
+{
+  "code":1,"show":0,"msg":"",
+  "data":{
+    "count":1,"language":"zh","server_date":"2026-10-26","timezone":"Asia/Shanghai",
+    "list":[{
+      "id":123,"title":"网站维护","category":"content","category_name":"弹出内容公告",
+      "content":"今晚维护,请提前安排。","display_frequency":"daily_first_login",
+      "publish_at":"2026-10-26 10:00:00","end_at":null,
+      "click_type":"none","link":null,"activity_id":null,"can_jump":false,"jump_unavailable_reason":null
+    }]
+  }
+}
+```
+
+返回全部有效公告,不分页,按发布时间和ID倒序。开启、开始时间已到、尚未结束,且平台和账号语言匹配才返回。开始边界包含,结束边界不含;所有日期时间为北京时间。
+
+count是服务端筛选后的数量,list为空时count=0;server_date/timezone一直返回。账号语言不可识别时data.language=null,否则为实际所用语言。接口只返回当前账号对应版本,不返回完整translations、管理员或日志资料。
+
+## 内容渲染
+
+| category | 含义 | content |
+|---|---|---|
+| content | 弹出内容公告 | 纯文本 |
+| image | 弹出图片公告 | 当前语言单张图片地址 |
+| update | 弹出更新公告 | 纯文本,不附带App升级规则 |
+
+标题与正文均直接使用返回版本。纯文本用textContent/v-text等展示,用white-space:pre-wrap保留换行,不用v-html。图片公告只把content作为图片地址,不显示为正文字符串,也不拼接会员域名。
+
+## 展示频率与队列
+
+后端不记录展示回执。不同设备独立控制,读取接口不消耗公告。
+
+- daily_first_login:按“会员标识+公告ID+server_date”每天最多展示一次。
+- every_login:按“会员标识+公告ID+本次登录标识”去重。只在实际登录成功时更换登录标识;刷新、路由切换、轮询和token续期不算新登录。
+- 按接口顺序逐条弹出,前一条关闭后显示下一条。只有真正展示成功后才写标记,请求完成、预加载或图片失败时不能提前标记。
+- 不把完整token放入本地去重键。切换账号使用独立范围。
+- 普通编辑、补语言版本都保留公告ID,不重置每日或当前登录的展示记录。
+
+```javascript
+const result = await request.get('/api/announcement/lists', {params:{platform:'H5'}});
+if (result.code === 1) {
+  for (const item of result.data.list) {
+    const interval = item.display_frequency === 'daily_first_login'
+      ? result.data.server_date : loginSessionId;
+    const key = `announcement:${memberId}:${item.id}:${item.display_frequency}:${interval}`;
+    if (localStorage.getItem(key)) continue;
+    await showAnnouncement(item, {onShown:()=>localStorage.setItem(key,'1')});
+  }
+}
+```
+
+示例showAnnouncement在真正可见时调用onShown,关闭时完成Promise。memberId、loginSessionId来自登录上下文,不向公告接口传身份参数。count不扣除本地已展示记录。
+
+## 点击行为
+
+先检查can_jump,再按click_type处理:none不跳;external打开HTTP/HTTPS link;internal用站内路由打开link;activity用activity_id打开原活动详情。
+
+活动的可见性也按本次账号语言判断。调用已有 `/api/index/activityInfo?id=…&platform=…` 时,请带 `Lang: result.data.language`,保持活动详情和公告所采用语言一致。
+
+活动未开始、到期、下架、删除、语言或平台不符,或对当前会员不可见时,公告仍展示,返回can_jump=false、activity_id/link=null、jump_unavailable_reason=activity_unavailable。不能从旧缓存恢复目标。活动可见但暂不能参加时,仍可打开详情,参与资格由原活动接口判断。
+
+## 错误与部署
+
+成功code=1,未登录/失效code=401,错误平台、请求方式或服务异常code=0。内部错误统一“公告服务暂不可用,请稍后重试”。HTTP通常仍为200,按业务code处理。
+
+后台需先执行新版公告迁移创建announcements和announcement_translations,并保存语言正文;会员端同步更新后读取同一业务库。无需公告专用定时任务,不提供实时推送或展示回执。会员英文语言包同步补入允许语言配置。

+ 165 - 0
docs/前端交付/melbet_sport-api/前台/整站与游戏维护-前台接口.md

@@ -0,0 +1,165 @@
+# 整站与游戏维护:前台接入
+
+项目 `melbet_sport-api`,适用于 H5、PC、APP。本文件为新增独立文档,覆盖整站维护页面、游戏维护提示和自动恢复。
+
+## 所有请求统一携带平台
+
+```http
+Platform: H5
+Lang: zh
+Authorization: <原始会员token,可选>
+```
+
+Platform 仅使用 `H5/PC/APP`,由客户端固定;请求头优先,兼容请求参数 platform。已补充跨域允许头。
+
+已登录用户按账号保存的 users.language 返回维护文案(zh/en/vi),游客按 Lang 请求头,兼容 lang 请求参数;未知语言默认 vi。维护检查不会自动修改账号语言。游客请求语言缺省时也使用 vi。
+
+没有传平台或平台非法时,服务端按“平台未知”保守匹配当前有效维护,返回 platform=null、platform_unknown=true。请在公共请求拦截器统一补齐 Platform;不要根据是否有维护临时增删此头。
+
+## 业务 code
+
+| code | 含义 | 界面处理 |
+|---|---|---|
+| 11001 | 当前平台整站维护 | 统一切换到维护页面,显示 msg 和维护时间 |
+| 11002 | 当前游戏分类维护 | 停止跳转/下注,展示 msg,留在可返回的页面 |
+
+HTTP 状态仍为 200;普通成功 code=1,普通错误沿用既有约定。不要把维护误判为登录过期或清空 token。
+
+整站维护示例(解密后的业务内容):
+
+```json
+{
+  "code": 11001,
+  "show": 1,
+  "msg": "系统维护中,预计北京时间04:00恢复。",
+  "data": {
+    "scope": "site",
+    "category": null,
+    "platforms": ["H5", "PC", "APP"],
+    "timezone": "Asia/Shanghai",
+    "starts_at": "2026-10-27 02:00:00",
+    "ends_at": "2026-10-27 04:00:00",
+    "starts_at_utc": "2026-10-26T18:00:00Z",
+    "ends_at_utc": "2026-10-26T20:00:00Z",
+    "language": "zh",
+    "content": "系统维护中,预计北京时间04:00恢复。",
+    "platform": "H5",
+    "platform_unknown": false,
+    "server_time": "2026-10-26T18:30:00Z"
+  }
+}
+```
+
+分类维护结构相同,code=11002、scope=game、category 例如 `third:4` 或 `self:sport`。msg 与 data.content 相同。文案按纯文本展示,保留换行,不作为 HTML 执行。
+
+starts_at/ends_at 是 timezone 指定时区的本地时间;对应 *_utc 是 UTC ISO8601 时间。倒计时应使用 *_utc 和 server_time,避免设备时区造成偏差。ends_at=null 表示未设置预计结束时间,展示“恢复时间待定”,通过状态接口等待恢复。
+
+## 维护状态查询
+
+`GET /api/maintenance/status`,免登录;带 token 时识别已保存的语言。此接口始终可用,查询不会续期登录活动。
+
+查询成功始终返回 code=1,**通过 data.is_maintaining 判断整站维护**,不通过该接口的顶层 code 判断是否维护。
+
+```json
+{
+  "code": 1,
+  "show": 0,
+  "msg": "",
+  "data": {
+    "platform": "H5",
+    "platform_unknown": false,
+    "language": "zh",
+    "server_time": "2026-10-26T18:30:00Z",
+    "is_maintaining": false,
+    "site": null,
+    "games": [
+      {"category": "third:1", "is_maintaining": false, "maintenance": null},
+      {"category": "third:2", "is_maintaining": false, "maintenance": null},
+      {"category": "third:3", "is_maintaining": false, "maintenance": null},
+      {
+        "category": "third:4",
+        "is_maintaining": true,
+        "maintenance": {
+          "scope": "game",
+          "category": "third:4",
+          "platforms": ["H5"],
+          "timezone": "Asia/Shanghai",
+          "starts_at": "2026-10-27 02:00:00",
+          "ends_at": "2026-10-27 04:00:00",
+          "starts_at_utc": "2026-10-26T18:00:00Z",
+          "ends_at_utc": "2026-10-26T20:00:00Z",
+          "language": "zh",
+          "content": "体育维护中,请稍后再试。"
+        }
+      },
+      {"category": "third:5", "is_maintaining": false, "maintenance": null},
+      {"category": "third:6", "is_maintaining": false, "maintenance": null},
+      {"category": "third:7", "is_maintaining": false, "maintenance": null},
+      {"category": "self:sport", "is_maintaining": false, "maintenance": null},
+      {"category": "self:lottery", "is_maintaining": false, "maintenance": null}
+    ]
+  }
+}
+```
+
+整站维护时 data.site 为维护对象、data.is_maintaining=true;games 九个分类均显示维护,maintenance 指向当前有效的整站维护对象(scope=site、category=null)。整站维护恢复后,某些游戏分类仍可能维护,继续按 games 单独判断。
+
+没有生效的维护时 site=null,九个分类 is_maintaining=false、maintenance=null。未来和已结束的计划不会作为当前维护返回。未登录请求与已登录请求都可查询,不返回管理员身份或其他语言文案。
+
+## 分类对应
+
+| category | 游戏分类 |
+|---|---|
+| third:1 | 真人 |
+| third:2 | 电子 |
+| third:3 | 彩票 |
+| third:4 | 体育 |
+| third:5 | 电竞 |
+| third:6 | 捕鱼 |
+| third:7 | 棋牌 |
+| self:sport | F1体育 |
+| self:lottery | F1彩票 |
+
+不使用客户端传入的 category 参数决定实际拦截。第三方通过服务端游戏目录核对 gameCode 和分类;F1 按实际进入/下注接口确定分类。
+
+## 前端接入流程
+
+1. 公共请求层固定携带 Platform,按现有协议加解密;解密后统一处理 11001/11002。
+2. 应用启动、恢复到前台及进入游戏前查询状态。整站维护时进入维护页;分类维护时给相应入口显示维护标记,阻止跳转并显示当前文案。
+3. 维护页可每 30 秒查询一次状态,页面隐藏时暂停,恢复可见时立即查询;网络失败继续保留当前维护页并提供重试,不当作维护已经结束。
+4. 当整站恢复时重新请求页面必要数据,再继续按分类维护状态显示入口。以服务端状态为准,不能仅靠本地倒计时恢复。
+5. 多个并发请求同时返回维护时,仅显示一个整站页面/提示,避免连续弹框。gameUrl 返回维护时不要继续转入游戏;下注返回维护时不要自动反复重试。
+
+前端状态查询用于展示,实际业务接口仍会重新检查。已开始并通过检查的进行中请求不强制中断。已填文案按当前语言展示,未填对应语言时返回该语言系统默认提示,仍然维护,不回退到其他语言。
+
+## 被拦截与保留的接口
+
+整站维护拦截普通前台请求,包括免登录列表、登录/注册、上传及钱包新操作。
+
+分类维护覆盖第三方 `/api/egame/gameUrl`、`/api/egame/demoUrl`、`/api/egame/play`。传 gameCode 时即使额外传 gameType,也按服务端目录的真实分类判断。F1 自营覆盖新下注及其进入数据接口:
+
+| 分类 | 入口 / 新下注 |
+|---|---|
+| self:sport | `/api/index/sportInfo`、`/api/index/sportGame`、`/api/order/submit`、`/api/order/submit_delete`、`/api/sportGameOrder/submit` |
+| self:lottery | `/api/game/index`、`/api/game/lotteryStatus`、`/api/game/bet`、`/api/lhcGame/index`、`/api/lhc/info`、`/api/jisuGame/index`、`/api/jisuGame/lotteryStatus`、`/api/lhcOrder/submit`、`/api/jisuGameOrder/submit` |
+
+维护期间以下路径继续进入原有认证及业务处理:
+
+- `GET /api/maintenance/status`。
+- `/api/user/logout` 退出。
+- `/api/egame/recycle` 第三方余额回收。
+- `/api/order/refund`、`/api/lhcOrder/refund`、`/api/wallet/withdrawCancel` 旧订单撤销/退款。
+
+支付回调和已有订单结算在后台继续处理。第三方已打开的游戏会话不会因本站停止发放链接而强制退出,供应商可能需要额外停服措施。历史数据等原有接口仍有其各自的登录、状态和上架规则。
+
+## 原有 Laravel 前台接口兼容
+
+`bot-28` 仍在使用的 `/api/wallet/*`、`/api/ActivityReward/*`、`/api/newPc/*`、`/api/issue/*`、`/api/pcissue/*` 同样返回整站维护 code=11001。其响应沿用 `code/timestamp/msg/data` 包装,没有 show;上述两项目的请求拦截器都应识别 11001。维护状态统一查询 `melbet_sport-api` 的 `/api/maintenance/status`。
+
+## 加密与验收
+
+状态查询和维护错误保持原有 API 加密约定:启用加密的环境继续传 `X-Api-Encrypted: 1` 和加密 payload,返回加密信封,解密后才是本文件 JSON。原本排除加密的 egame/play 仍按原协议处理。Platform 请求头可直接传,lang 参数若放 payload 则参与现有加密。
+
+服务端设置 Cache-Control=no-store,前端也不要持久缓存维护结果。
+
+验收:三端隔离;整站优先;整站无效时分类仍生效;三个语言及空文案;未登录维护页;起止边界和无结束时间;试玩/真实游戏/F1 下注;资金回收和退出;到期恢复后分类独立维护;普通 JSON 与加密信封两种环境。

+ 11 - 0
docs/前端交付/melbet_sport-api/前台/独立活动/会员接入.md

@@ -0,0 +1,11 @@
+# 独立活动会员文档目录
+
+三个活动分别接入,各自文档包含认证、接口、状态、字段和业务规则:
+
+- [净亏邀请-会员接口](净亏邀请-会员接口.md)
+- [首充奖励-会员接口](首充奖励-会员接口.md)
+- [转盘活动-会员接口](转盘活动-会员接口.md)
+
+[后台文档目录](../../../bot-28/后台/独立活动-后台接口.md) · [转盘播报联调示例,每组50条](转盘播报示例.json)
+
+上述活动使用melbet_sport-api主机的 `/api/promotion/*` 独立入口,不加入原活动列表。

+ 56 - 0
docs/前端交付/melbet_sport-api/前台/独立活动/净亏邀请-会员接口.md

@@ -0,0 +1,56 @@
+# 净亏邀请-会员接口
+
+业务类型为 `7`。
+
+本活动独立于原活动列表,通过 **melbet_sport-api主机** 的 `/api/promotion/*` 接口接入,与现有钱包接口使用相同Token。原 `/api/index/activity` 不返回本活动。
+
+请求头为原始 `Authorization: <token>`,无需Bearer前缀。每次传 `platform=PC/H5/APP`;身份由Token识别,不提交会员ID。成功 `code=1`,业务错误 `code=0`,Token过期返回 `code=401`,展示实际 `msg`。所有金额按服务端字符串展示。
+
+独立入口调用 `GET /api/promotion/list?type=7&platform=H5`,可加 `lang=zh`,返回 `total/data`。各条包含 `activity_id/type/type_name/title/image/content/rules/is_eligible/ineligible_reason`。不要在原活动类型选项中追加此类型。
+
+`GET /api/promotion/detail?activity_id=1&platform=H5` 只读返回本人状态;不要把旧活动ID当成独立活动ID。提交操作始终以服务端校验为准。
+
+## 页面与接口
+
+| 方法 | 路径 | 参数及用途 |
+|---|---|---|
+| GET | `/api/promotion/list` | `type=7`,独立活动入口 |
+| GET | `/api/promotion/detail` | `activity_id`,净亏、申请及邀请进度 |
+| POST | `/api/promotion/apply` | `activity_id`,达到净亏门槛后申请 |
+| POST | `/api/promotion/progress` | `activity_id`,返回最新参与状态 |
+| POST | `/api/promotion/claim` | `activity_id/round_no=1`,邀请达标后领奖 |
+| GET | `/api/promotion/rewards` | `activity_id/page/limit`,本人奖金记录 |
+
+分页默认1/20,`limit` 最大100。详情和progress返回:
+
+```json
+{
+  "activity_id": 1, "type": 7, "round_no": 1,
+  "rules": {"loss_amount":"500.00","invite_count":3,"reward_amount":"500.00","turnover_ratio":"0.00"},
+  "loss": "500.00", "applied_at": "2026-10-26 15:00:00",
+  "qualified_invitees": 2, "status": "collecting",
+  "valid_member_policy": {"id":1,"effective_date":"2026-10-26","min_deposit":"200","min_effective_bet":"300"},
+  "rewards": []
+}
+```
+
+门槛数值仅为示例,有效推广的充值、投注条件使用 `valid_member_policy`,不写死200。
+
+## 状态与领奖流程
+
+| status | 含义 | 页面操作 |
+|---|---|---|
+| not_applied | 尚未成功申请 | 净亏达到 `rules.loss_amount` 后可申请 |
+| collecting | 已申请,继续邀请 | 展示 `qualified_invitees/rules.invite_count` |
+| ready | 有效邀请人数达标 | 传 `round_no=1` 领取 |
+| claimed | 已领取 | 展示奖金记录,关闭重复领取入口 |
+
+净亏从本活动第一次上架后下单的已结算、未撤销真实注单累计,盈利与亏损相抵,不是充值减提现。申请后新注册并建立直属关系的下级,累计真实充值和有效投注同时达标,才增加有效人数。有效投注逐笔取 `min(本金, |净输赢|)`;人工上分、赠送不计充值。
+
+每人同一活动只有一次机会。重复申请返回原进度,无效申请不消耗机会;下架再上架不重置。申请成功时固定奖励规则,页面显示服务端返回规则;奖励配置后来修改不影响该申请快照。领取重试始终使用 `round_no=1`,不会重复入账。
+
+`rewards.status=paid` 表示已可用,`locked` 表示已发入冻结余额待完成流水。倍数0直接可用,大于0时独立累计奖金发放后有效投注,达到“奖金×倍数”自动解冻。活动下架后仍可查询本人rewards,历史记录保留。
+
+后台配置见[《净亏邀请-后台接口》](../../../bot-28/后台/独立活动/净亏邀请-后台接口.md)。
+
+正常API请求自动采集的是活跃时长估算:无请求的静态停留不会继续增加时长,后台自动轮询可能计入;不要将其展示为精确在线时长。

+ 0 - 0
docs/前端接口/独立活动/转盘播报示例.json → docs/前端交付/melbet_sport-api/前台/独立活动/转盘播报示例.json


+ 93 - 0
docs/前端交付/melbet_sport-api/前台/独立活动/转盘活动-会员接口.md

@@ -0,0 +1,93 @@
+# 转盘活动-会员接口
+
+业务类型为 `9`。
+
+本活动独立于原活动列表,通过 **melbet_sport-api主机** 的 `/api/promotion/*` 接口接入,与现有钱包接口使用相同Token。原 `/api/index/activity` 不返回本活动。
+
+请求头为原始 `Authorization: <token>`,无需Bearer前缀。每次传 `platform=PC/H5/APP`;身份由Token识别,不提交会员ID。成功 `code=1`,业务错误 `code=0`,Token过期返回 `code=401`,展示实际 `msg`。所有金额按服务端字符串展示。
+
+独立入口调用 `GET /api/promotion/list?type=9&platform=H5`,可加 `lang=zh`,返回 `total/data`。各条包含 `activity_id/type/type_name/title/image/content/rules/is_eligible/ineligible_reason`。不要在原活动类型选项中追加此类型。
+
+`GET /api/promotion/detail?activity_id=1&platform=H5` 只读返回本人状态;不要把旧活动ID当成独立活动ID。提交操作始终以服务端校验为准。
+
+## 接口与页面流程
+
+| 方法 | 路径 | 参数及用途 |
+|---|---|---|
+| GET | `/api/promotion/list` | 独立入口数据,固定 `type=9`、`lang=zh` |
+| GET | `/api/promotion/detail` | `activity_id`,规则与本人状态;只读 |
+| POST | `/api/promotion/heartbeat` | 兼容可选接口;正常已认证API请求会自动记录活跃,无需额外定时调用 |
+| POST | `/api/promotion/progress` | `activity_id`,初始化参与状态并结算已完成任务次数,返回最新状态 |
+| POST | `/api/promotion/spin` | `activity_id/request_key`,转盘抽奖 |
+| POST | `/api/promotion/claim` | `activity_id/round_no`,领取转盘奖金 |
+| GET | `/api/promotion/records` | `activity_id/page/limit`,本人真实转盘记录 |
+| GET | `/api/promotion/rewards` | `activity_id/page/limit`,本人奖金记录,活动下架后仍可查询 |
+| GET | `/api/promotion/broadcasts` | `activity_id`,普通及达到666播报 |
+
+分页默认1/20,limit最大100。以上各接口均须传platform。
+
+正常已认证API请求会自动采集活跃时间,进入转盘后调用progress即可,不要求额外定时heartbeat。heartbeat保留兼容,页面可见时可选调用,隐藏/离开后停止。已登录用户当天首次认证进入活动也计“每日登录”;日登录次数只发一次。活跃时长使用服务端API请求时间估算,相邻观测超过5分钟不连算,多标签页、多端重叠去重;不能用前端倒计时直接申报已完成,也不能用登录到现在的时差代替在线时长。已有报表采集的真实观测可合并使用。
+
+## 转盘状态
+
+```json
+{
+  "activity_id": 1, "type": 9, "round_no": 1,
+  "spins": 7, "coins": "600.25", "remaining": "65.75", "status": "active",
+  "rules": {"completion_probability": "20.00", "online_seconds": 3600},
+  "conditions_complete": true,
+  "completed_tasks": {"online":true,"login":true,"invite":true,"deposit":true,"team_deposit":true,"bet":true,"team_bet":true},
+  "today_granted_tasks": ["login", "online", "deposit"],
+  "tasks": [{"key":"online","round_completed":true,"value":3600,"target":3600,"today_rewarded":true,"reward_spins":1,"remaining_seconds":0}],
+  "valid_member_policy": {"id":1,"effective_date":"2026-10-26","min_deposit":"200","min_effective_bet":"300"},
+  "prizes": ["0-666","100","50","200","350","500","150","366"]
+}
+```
+
+示例省略部分规则和任务。实际 `tasks` 返回七项:
+
+| key | 标题 | 默认门槛 |
+|---|---|---|
+| online | 每日在线 | 3600秒 |
+| login | 每日登录 | 1 |
+| invite | 每有效推广一人 | 同时达到生效的系统充值与有效投注门槛 |
+| deposit | 本人当日累计充值 | 3000 |
+| team_deposit | 直属下级当日累计充值合计 | 500 |
+| bet | 本人当日累计有效投注 | 5000 |
+| team_bet | 直属下级当日累计有效投注合计 | 3000 |
+
+`round_completed` 是累计任务资格,`value` 对四项金额任务表示本轮今天的金额;`invite.value` 为本轮已贡献的有效人数,`online.value` 为今天在线秒数。不要因为某天已完成任务,就把今天的金额显示为昨天的金额。`today_rewarded` 表示今天是否已发该每日任务次数;推广是每个独立下级事件加次,使用实际 `spins` 更新中心按钮。
+
+当日金额不跨天拼接:昨天充值2000、今天1000,不算达标3000。某一天达到3000后,本轮 `deposit` 完成标记可以保留。七项各完成一次即可,不必同一天完成。
+
+有效投注逐笔计算 `min(本金, |净输赢|)`,只计已结算且未撤销的真实注单;例如本金100、净亏50,有效为50。团队金额只计直属下级建立关系后的贡献,试玩不计,充值不含人工上分或赠送。
+
+### 抽奖、概率、领取
+
+抽奖前生成16~64位的字母数字/下划线/短横线 `request_key`;重试同一次操作必须保留原值,新抽奖才换值。推荐使用UUID。同一键只扣一次次数、返回同一结果。服务端成功后再按 `prize_index` 播放动画,禁止客户端先随机再上传结果。
+
+抽奖返回 `spin` 和剩余 `spins`,例如:
+
+```json
+{"spin":{"id":12,"round_no":1,"prize_index":0,"prize":"0-666","amount":"65.75","coins":"666.00","reached":true,"conditions_complete":true,"probability":"20.00","created_at":"2026-10-26 15:00:00"},"spins":6,"replayed":false}
+```
+
+未完成七项时概率固定0;全部完成后每次按后台配置概率判定(默认20%)。普通奖池不会让累计达到666:只抽累计后小于666的固定奖项及浮动奖项;可用奖项等概率,浮动奖项按分等概率取值。完成条件并命中时由浮动奖项补齐差额。界面展示服务端返回的 `ordinary_pool_rule`,固定面额不会被暗中截减。
+
+状态 `not_started/active/reached` 分别表示尚未初始化、正在累计、达到666待领取。达到后停抽,领取传本轮的 `round_no`。领取重试必须保留旧轮号,不用新轮号替换重试请求。奖金已预留,达到后下架也可领取。
+
+领取后金币清零,未用次数保留,进入下一轮。登录、在线完成标记保留且当天不重复加次;其他五项归零,只认领取后新充值、新投注及尚未计奖的新有效推广。当天领取前的金额不能重新用于下一轮。下级整个活动只贡献一次有效推广次数,但其之后的新充值、新投注仍可用于团队金额任务。
+
+`rewards.status`:`reserved` 已达到且待领取,`locked` 已发入冻结余额等待流水,`paid` 已可用。流水倍数0直接可用,大于0时按每笔奖金发放后的新增有效投注达到所需流水后自动解冻;不把解冻当作再次中奖。
+
+## 我的记录及播报
+
+“我的记录”使用records,是本人真实抽奖历史,含轮次、实际金币、累计金币、时间,空数组显示暂无内容。奖金历史使用rewards单独展示。
+
+播报返回 `ordinary`、`reached_666` 两组真实记录,各最多50条,账号脱敏;数据不足时返回实际条数。联调数据见同目录 `转盘播报示例.json`,每组各50条随机样例,带 `is_demo=true`,仅作示例展示,不作为真实用户中奖播报。
+
+关闭活动暂停新增参与、任务结算和抽奖;历史记录及已达到666的领取权益保留。重新上架延续原记录和进度。
+
+后台配置见[《转盘活动-后台接口》](../../../bot-28/后台/独立活动/转盘活动-后台接口.md)。
+
+正常API请求自动采集的是活跃时长估算:无请求的静态停留不会继续增加时长,后台自动轮询可能计入;不要将其展示为精确在线时长。

+ 47 - 0
docs/前端交付/melbet_sport-api/前台/独立活动/首充奖励-会员接口.md

@@ -0,0 +1,47 @@
+# 首充奖励-会员接口
+
+业务类型为 `8`。
+
+本活动独立于原活动列表,通过 **melbet_sport-api主机** 的 `/api/promotion/*` 接口接入,与现有钱包接口使用相同Token。原 `/api/index/activity` 不返回本活动。
+
+请求头为原始 `Authorization: <token>`,无需Bearer前缀。每次传 `platform=PC/H5/APP`;身份由Token识别,不提交会员ID。成功 `code=1`,业务错误 `code=0`,Token过期返回 `code=401`,展示实际 `msg`。所有金额按服务端字符串展示。
+
+独立入口调用 `GET /api/promotion/list?type=8&platform=H5`,可加 `lang=zh`,返回 `total/data`。各条包含 `activity_id/type/type_name/title/image/content/rules/is_eligible/ineligible_reason`。不要在原活动类型选项中追加此类型。
+
+`GET /api/promotion/detail?activity_id=1&platform=H5` 只读返回本人状态;不要把旧活动ID当成独立活动ID。提交操作始终以服务端校验为准。
+
+## 页面与接口
+
+| 方法 | 路径 | 参数及用途 |
+|---|---|---|
+| GET | `/api/promotion/list` | `type=8`,首充独立入口 |
+| GET | `/api/promotion/detail` | `activity_id`,查看首充规则及是否已有充值 |
+| GET | `/api/promotion/rewards` | `activity_id/page/limit`,本人奖金记录 |
+
+分页默认1/20,`limit` 最大100。详情示例:
+
+```json
+{
+  "activity_id": 2, "type": 8,
+  "rules": {"min_deposit":"100.00","reward_amount":"25.00","turnover_ratio":"0.00"},
+  "has_deposited": false, "rewards": []
+}
+```
+
+页面展示“首次成功充值达到多少、固定送多少”,直接读取 `rules.min_deposit/reward_amount`。无需申请、转盘或手动领奖按钮,使用原充值入口,充值成功后刷新详情和钱包。
+
+## 首次与到账规则
+
+- 首次指会员全历史第一笔正向成功入账的“充值/三方充值”,不是活动期间第一笔或每日第一笔。
+- 失败订单、人工上分、活动赠送不计;前台真实充值凭证审核成功计入。
+- 首笔不足门槛或发生在活动下架时,第二笔不能补领;已有充值历史的会员不因活动上架恢复首充资格。
+- 成功后自动赠送,重复支付回调、补单或重复审核不会重复发奖。与既有充值活动分别判断资格,可叠加。
+- `has_deposited=true` 表示已有成功充值,不代表一定获得首充奖金,是否获奖以 `rewards` 为准。
+
+奖金记录 `status=paid` 表示已可用,`locked` 表示已发入冻结余额待流水。`turnover_ratio=0` 直接可用;大于0时,各笔奖金独立累计发放后的新有效投注,达到“奖金×倍数”后自动解冻。有效投注逐笔按 `min(本金, |净输赢|)`,未结算和撤销注单不计。
+
+活动下架后rewards仍可查,空数组显示暂无奖金。重复刷新不能改变首充资格。
+
+后台配置见[《首充奖励-后台接口》](../../../bot-28/后台/独立活动/首充奖励-后台接口.md)。
+
+正常API请求自动采集的是活跃时长估算:无请求的静态停留不会继续增加时长,后台自动轮询可能计入;不要将其展示为精确在线时长。

+ 56 - 0
docs/前端交付/melbet_sport-api/前台/统计装置与活动采集-前台接口.md

@@ -0,0 +1,56 @@
+# 报表统计:装置与活动采集
+
+项目melbet_sport-api。本文件是新增接入说明,供H5、PC、APP共用请求层使用;总后台统计接口见bot-28的[龙虎榜资金流动与报表-接口.md](../../bot-28/后台/龙虎榜资金流动与报表-接口.md)。
+
+## 每次业务请求携带装置
+
+```http
+Platform: H5
+Authorization: <原始会员token>
+```
+
+Platform固定传H5、PC或APP,请求头优先,兼容platform请求参数。不要使用供应商platType或游戏类型代替装置。缺失/非法值记unknown,不根据用户最近登录端推断历史。
+
+新增投注、充值、提款等源记录保存事件当时的装置、直属代理和分享邀请人快照;后续换端或订单更新不会覆盖最初装置。第三方回单缺少可靠装置关联,暂时仍可能归unknown。
+
+此统计采集不改变业务接口已有的登录、加密、金额或返回值;维护中的请求继续按维护接口约定处理。
+
+## 使用正常API请求时间采集
+
+前端无需新增定时心跳。服务端在登录校验通过后、业务处理前,按已认证会员身份和当前服务端时间记录本次访问;GET、POST、余额等自动查询以及业务校验失败的请求均可记录。不接受客户端自报时间、用户ID、在线分钟或累计秒数。
+
+- 同一用户、会话、装置和分钟去重,多端重叠的活动时长在汇总时也会去重。
+- 游客、无效/过期token不会记录;维护状态查询和在认证前被维护中间件拦截的请求不会记录。
+- 采集失败只记录服务端日志,不阻塞业务,也不改变业务接口返回值。收到401或11001时继续按原登录、维护逻辑处理。
+
+## 兼容原有心跳接口
+
+`POST /api/activity/heartbeat`继续保留,必须登录,空JSON请求体即可。已接入的客户端可以继续使用,新接入无需为统计额外定时调用;与正常API请求使用相同的分钟去重规则。
+
+```json
+{}
+```
+
+```json
+{
+  "code": 1,
+  "show": 0,
+  "msg": "",
+  "data": {
+    "recorded": true,
+    "server_time": "2026-10-26 15:00:00",
+    "client_platform": "H5",
+    "interval_seconds": 60
+  }
+}
+```
+
+成功code=1;未登录/过期code=401;只允许POST;普通失败code=0。沿用API现有加解密协议,启用加密时照常发送加密payload。interval_seconds保留兼容,表示分钟采集粒度,不要求客户端按此间隔发心跳。
+
+## 报表展示含义
+
+在线率实际采用区间活跃人数占比;activity_minutes是按API请求时间估算的活动分钟,不是精确在线时长。仅同一会话相邻请求观测间隔不超过5分钟时,才累计两次观测之间的时间;超过5分钟拆段,多端重叠去重。只有一次观测会记为活跃会员,但没有可累计的时长,最后一次请求后也不会持续累加。
+
+静态阅读、停留页面或第三方游戏期间没有请求本站API,无法计入该段停留;后台自动轮询可能被计入。历史没有可靠记录时返回null,不能补成0或按整段登录时间计算。
+
+验收:登录校验;三端各自装置;GET/POST和业务失败均记录访问;同分钟请求去重;游客/无效token/维护拦截不记;采集故障不影响业务响应;无客户端伪造时间;请求间隔拆段与多端去重;跨端不覆盖原订单装置;旧订单和第三方无法识别时仍显示unknown。

+ 5 - 0
docs/前端交付/melbet_sport-api/后台/README.md

@@ -0,0 +1,5 @@
+# melbet_sport-api · 后台接口交付
+
+本次指定Git范围内,没有需要交付的该端新增或修改接口文档。
+
+[项目目录](../README.md) · [交付总目录](../../README.md)

+ 240 - 0
docs/前端交付/交付清单.json

@@ -0,0 +1,240 @@
+{
+  "snapshot_date": "2026-10-26",
+  "timezone": "Asia/Taipei",
+  "scope": {
+    "bot-28": {
+      "base_commit": "c1b4269bc9c45e2610b9078ddb39a1215b0d81aa",
+      "head_commit": "c1b4269bc9c45e2610b9078ddb39a1215b0d81aa",
+      "include": "base到当前工作区,含已提交及暂存/未暂存/未跟踪",
+      "supplemental_sources": [
+        "docs/总后台/公告系统-后台接口.md",
+        "docs/总后台/信息管理-后台接口.md",
+        "docs/总后台/整站与游戏维护-后台接口.md"
+      ],
+      "supplemental_reason": "用户要求补齐后台公告、信息管理和维护接口;这三份文档已在基准提交内,作为明确补充项交付"
+    },
+    "melbet_sport-api": {
+      "head_commit": "65a54eacc06374cda464bef4688d600e93c56f58",
+      "include": "仅未提交的暂存/未暂存/未跟踪"
+    }
+  },
+  "files": [
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/独立活动-后台接口.md",
+      "delivery_path": "bot-28/后台/独立活动-后台接口.md",
+      "role": "后台",
+      "kind": "目录",
+      "git_status": "M",
+      "source_sha256": "0dec78a9626dabae959ea293b9e00699ea21e297f4c3c176b2a2161e856ea2d2",
+      "delivery_sha256": "72f9ff3773c930234253195125d8225e6a96f696d68ed3da63878a9882e29ca9"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/独立活动/净亏邀请-后台接口.md",
+      "delivery_path": "bot-28/后台/独立活动/净亏邀请-后台接口.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "M",
+      "source_sha256": "5198b3e020e976c01c46cae3cd1e72782b510f303243315a5996c8b06fde490d",
+      "delivery_sha256": "c82363e086bf052a9cb3133000e39640cd2d73af476f7626ec29f1380be56a56"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/独立活动/首充奖励-后台接口.md",
+      "delivery_path": "bot-28/后台/独立活动/首充奖励-后台接口.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "M",
+      "source_sha256": "fd114933a803e079709c6f331edd14e60b726b2f522d5ef2af02ef4ea5f95ca4",
+      "delivery_sha256": "a502cf579b91165ba4b10a55df8c6cc8419bfb336d7a5328c25b3310df5c22d5"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/独立活动/转盘活动-后台接口.md",
+      "delivery_path": "bot-28/后台/独立活动/转盘活动-后台接口.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "M",
+      "source_sha256": "a239af80f1a70ef8b50ef4abaea2ea0abea21bed4eb36fc096b16e46ada32646",
+      "delivery_sha256": "a34fad93a22ae8df3a7fb7c3c242d6e39e4ecbb331734d41ad7d999d63a3b80c"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/龙虎榜资金流动与报表-接口.md",
+      "delivery_path": "bot-28/后台/龙虎榜资金流动与报表-接口.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "M",
+      "source_sha256": "0d2259ff63359e5d6f4e8cf41aa45a5c4426e499e0ffdbe976a3b79ff40e9a83",
+      "delivery_sha256": "2e25138ba245007dfd317d60d43f50309a853fcf8179fd5762b7064a05f76fd2"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/公告系统-后台接口.md",
+      "delivery_path": "bot-28/后台/公告系统-后台接口.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "unchanged",
+      "scope_reason": "基准提交已包含;按用户补充要求交付后台公告、信息管理和维护接口",
+      "source_sha256": "14d8e83193e71724a3cb1b04c83b222859ffb2f16a8aa1352d63fa5584933dc3",
+      "delivery_sha256": "14d8e83193e71724a3cb1b04c83b222859ffb2f16a8aa1352d63fa5584933dc3"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/信息管理-后台接口.md",
+      "delivery_path": "bot-28/后台/信息管理-后台接口.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "unchanged",
+      "scope_reason": "基准提交已包含;按用户补充要求交付后台公告、信息管理和维护接口",
+      "source_sha256": "c0e2f19fc9c58d4fd86ecfb400af0f93f2a6d45dcf013ca2562e2c118d06adcd",
+      "delivery_sha256": "c0e2f19fc9c58d4fd86ecfb400af0f93f2a6d45dcf013ca2562e2c118d06adcd"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/总后台/整站与游戏维护-后台接口.md",
+      "delivery_path": "bot-28/后台/整站与游戏维护-后台接口.md",
+      "role": "后台",
+      "kind": "正文",
+      "git_status": "unchanged",
+      "scope_reason": "基准提交已包含;按用户补充要求交付后台公告、信息管理和维护接口",
+      "source_sha256": "1090a20075e8e014e3fa5bf93002567d7f8acb24b7a2aa516a034ec58c62b4fc",
+      "delivery_sha256": "1090a20075e8e014e3fa5bf93002567d7f8acb24b7a2aa516a034ec58c62b4fc"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/信息中心-前台接口.md",
+      "delivery_path": "melbet_sport-api/前台/信息中心-前台接口.md",
+      "role": "前台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "bb6be4ff17dc616b67e6ab2fc9fc6471a067bb7f07b5791ba8fcbb083d1f423f",
+      "delivery_sha256": "bb6be4ff17dc616b67e6ab2fc9fc6471a067bb7f07b5791ba8fcbb083d1f423f"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/公告系统-前台接入文档.md",
+      "delivery_path": "melbet_sport-api/前台/公告系统-前台接入文档.md",
+      "role": "前台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "ee672e97ddc79fae88807f69a3822a0060cb40666ccadc5e95c9dfa8d8bc0b01",
+      "delivery_sha256": "ee672e97ddc79fae88807f69a3822a0060cb40666ccadc5e95c9dfa8d8bc0b01"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/整站与游戏维护-前台接口.md",
+      "delivery_path": "melbet_sport-api/前台/整站与游戏维护-前台接口.md",
+      "role": "前台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "1d8ef632d853a041941e771f57962aa6042288f8629ed741d90b44cbf3e2b788",
+      "delivery_sha256": "1d8ef632d853a041941e771f57962aa6042288f8629ed741d90b44cbf3e2b788"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/统计装置与活动采集-前台接口.md",
+      "delivery_path": "melbet_sport-api/前台/统计装置与活动采集-前台接口.md",
+      "role": "前台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "249e9ac762f36db0c9acbf901b771d7f30708ee7ea96a25bedfd128b521b3f74",
+      "delivery_sha256": "0137ef168c56aa33a41e09f4cdf588a4cb4c5da2111f09c8cf2a4ade875ef683"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/独立活动/会员接入.md",
+      "delivery_path": "melbet_sport-api/前台/独立活动/会员接入.md",
+      "role": "前台",
+      "kind": "目录",
+      "git_status": "??",
+      "source_sha256": "be9e0fc412e1e825f5d26f0802ed98ef2733a3ed6a4e28886e74e21fa031f3e5",
+      "delivery_sha256": "10d508510ba8676afc7b0bbba2ded573be23e6e4e57ac6cedaef8da1096419ca"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/独立活动/净亏邀请-会员接口.md",
+      "delivery_path": "melbet_sport-api/前台/独立活动/净亏邀请-会员接口.md",
+      "role": "前台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "65b495054d2f776b32c6a0c5f30218990034e8ca4f65cfe67c4250143eb80dea",
+      "delivery_sha256": "55d9d4852e146c238f1258fa4de13a55c40ccf4f03fde7b5c2a6a6c5a5ae8c89"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/独立活动/首充奖励-会员接口.md",
+      "delivery_path": "melbet_sport-api/前台/独立活动/首充奖励-会员接口.md",
+      "role": "前台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "5f671510ee3106e4d515f60423be6010fb23fcbb6f9c98a71cb8f4a6cb07be3b",
+      "delivery_sha256": "59128fc52f4eed8dd59fd5db49b1604ca4e4083ac1d1d22dcb40ba0d55d4c65c"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/独立活动/转盘活动-会员接口.md",
+      "delivery_path": "melbet_sport-api/前台/独立活动/转盘活动-会员接口.md",
+      "role": "前台",
+      "kind": "正文",
+      "git_status": "??",
+      "source_sha256": "4455bff0356a1b6701f80ec36b70a997d8c7b278fc86bb01247a9e65833cc3bc",
+      "delivery_sha256": "97c2e36468e5d4c17b5799c799c52a36d0457d5399eb6f480a04872ef0d9fd24"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/独立活动/转盘播报示例.json",
+      "delivery_path": "melbet_sport-api/前台/独立活动/转盘播报示例.json",
+      "role": "前台",
+      "kind": "示例数据",
+      "git_status": "??",
+      "source_sha256": "82b9661fc19659d42e068c1de64b1396d22b6b62e8745da79f110101d1273f32",
+      "delivery_sha256": "82b9661fc19659d42e068c1de64b1396d22b6b62e8745da79f110101d1273f32"
+    }
+  ],
+  "excluded": [
+    {
+      "project": "bot-28",
+      "source_path": "docs/前端接口/独立活动/会员接入.md",
+      "git_status": "M",
+      "reason": "旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/前端接口/独立活动/净亏邀请-会员接口.md",
+      "git_status": "M",
+      "reason": "旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/前端接口/独立活动/转盘播报示例.json",
+      "git_status": "D",
+      "reason": "旧位置已删除,示例从melbet_sport-api当前文件交付"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/前端接口/独立活动/转盘活动-会员接口.md",
+      "git_status": "M",
+      "reason": "旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/前端接口/独立活动/首充奖励-会员接口.md",
+      "git_status": "M",
+      "reason": "旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例"
+    },
+    {
+      "project": "bot-28",
+      "source_path": "docs/后端/报表统计口径与数据补录.md",
+      "git_status": "M",
+      "reason": "后端实现与部署说明,不是前端接口接入文档"
+    },
+    {
+      "project": "melbet_sport-api",
+      "source_path": "docs/返佣列表-前端接入文档.md",
+      "git_status": "M",
+      "reason": "仅空白格式变化,接口正文/合约没有增量"
+    }
+  ]
+}

+ 39 - 0
docs/前端交付/交付清单.md

@@ -0,0 +1,39 @@
+# 增量交付清单
+
+按用户指定Git范围收录接口资料,并按后续要求补充bot-28的后台公告、信息管理、整站与游戏维护三份文档。这三份已包含在基准提交中,原先不属于增量;现作为明确补充项交付。正文采用当前工作区版本,跨项目链接已改为包内链接。原始文档未修改。
+
+共17个来源文件:bot-28后台8个,melbet_sport-api前台9个。
+
+| 项目 | 来源路径 | 状态 | 交付文件 | 类型 |
+|---|---|---|---|---|
+| bot-28 | `docs/总后台/独立活动-后台接口.md` | M | [独立活动-后台接口.md](bot-28/后台/独立活动-后台接口.md) | 目录 |
+| bot-28 | `docs/总后台/独立活动/净亏邀请-后台接口.md` | M | [净亏邀请-后台接口.md](bot-28/后台/独立活动/净亏邀请-后台接口.md) | 正文 |
+| bot-28 | `docs/总后台/独立活动/首充奖励-后台接口.md` | M | [首充奖励-后台接口.md](bot-28/后台/独立活动/首充奖励-后台接口.md) | 正文 |
+| bot-28 | `docs/总后台/独立活动/转盘活动-后台接口.md` | M | [转盘活动-后台接口.md](bot-28/后台/独立活动/转盘活动-后台接口.md) | 正文 |
+| bot-28 | `docs/总后台/龙虎榜资金流动与报表-接口.md` | M | [龙虎榜资金流动与报表-接口.md](bot-28/后台/龙虎榜资金流动与报表-接口.md) | 正文 |
+| bot-28 | `docs/总后台/公告系统-后台接口.md` | 基准已有(补充) | [公告系统-后台接口.md](bot-28/后台/公告系统-后台接口.md) | 正文 |
+| bot-28 | `docs/总后台/信息管理-后台接口.md` | 基准已有(补充) | [信息管理-后台接口.md](bot-28/后台/信息管理-后台接口.md) | 正文 |
+| bot-28 | `docs/总后台/整站与游戏维护-后台接口.md` | 基准已有(补充) | [整站与游戏维护-后台接口.md](bot-28/后台/整站与游戏维护-后台接口.md) | 正文 |
+| melbet_sport-api | `docs/信息中心-前台接口.md` | ?? | [信息中心-前台接口.md](melbet_sport-api/前台/信息中心-前台接口.md) | 正文 |
+| melbet_sport-api | `docs/公告系统-前台接入文档.md` | ?? | [公告系统-前台接入文档.md](melbet_sport-api/前台/公告系统-前台接入文档.md) | 正文 |
+| melbet_sport-api | `docs/整站与游戏维护-前台接口.md` | ?? | [整站与游戏维护-前台接口.md](melbet_sport-api/前台/整站与游戏维护-前台接口.md) | 正文 |
+| melbet_sport-api | `docs/统计装置与活动采集-前台接口.md` | ?? | [统计装置与活动采集-前台接口.md](melbet_sport-api/前台/统计装置与活动采集-前台接口.md) | 正文 |
+| melbet_sport-api | `docs/独立活动/会员接入.md` | ?? | [会员接入.md](melbet_sport-api/前台/独立活动/会员接入.md) | 目录 |
+| melbet_sport-api | `docs/独立活动/净亏邀请-会员接口.md` | ?? | [净亏邀请-会员接口.md](melbet_sport-api/前台/独立活动/净亏邀请-会员接口.md) | 正文 |
+| melbet_sport-api | `docs/独立活动/首充奖励-会员接口.md` | ?? | [首充奖励-会员接口.md](melbet_sport-api/前台/独立活动/首充奖励-会员接口.md) | 正文 |
+| melbet_sport-api | `docs/独立活动/转盘活动-会员接口.md` | ?? | [转盘活动-会员接口.md](melbet_sport-api/前台/独立活动/转盘活动-会员接口.md) | 正文 |
+| melbet_sport-api | `docs/独立活动/转盘播报示例.json` | ?? | [转盘播报示例.json](melbet_sport-api/前台/独立活动/转盘播报示例.json) | 示例数据 |
+
+## 未交付项
+
+| 项目 | 来源路径 | 原因 |
+|---|---|---|
+| bot-28 | `docs/前端接口/独立活动/会员接入.md` | 旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例 |
+| bot-28 | `docs/前端接口/独立活动/净亏邀请-会员接口.md` | 旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例 |
+| bot-28 | `docs/前端接口/独立活动/转盘播报示例.json` | 旧位置已删除,示例从melbet_sport-api当前文件交付 |
+| bot-28 | `docs/前端接口/独立活动/转盘活动-会员接口.md` | 旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例 |
+| bot-28 | `docs/前端接口/独立活动/首充奖励-会员接口.md` | 旧位置已迁移,交付包使用melbet_sport-api的当前会员文档/示例 |
+| bot-28 | `docs/后端/报表统计口径与数据补录.md` | 后端实现与部署说明,不是前端接口接入文档 |
+| melbet_sport-api | `docs/返佣列表-前端接入文档.md` | 仅空白格式变化,接口正文/合约没有增量 |
+
+`M` 为修改,`??` 为未跟踪,`D` 为删除;“基准已有(补充)”对应JSON中的 `git_status=unchanged`,表示按用户要求额外交付的已有文档。`交付清单.json` 记录完整基准与来源/副本SHA-256,便于核对。

+ 4 - 8
docs/前端接口/独立活动/会员接入.md

@@ -1,11 +1,7 @@
-# 独立活动会员文档目录
+# 会员接口文档已迁移
 
-三个活动分别接入,各自文档包含认证、接口、状态、字段和业务规则:
+会员活动接口由 `melbet_sport-api` 提供,完整接入说明已迁至对应项目:
 
-- [净亏邀请-会员接口](净亏邀请-会员接口.md)
-- [首充奖励-会员接口](首充奖励-会员接口.md)
-- [转盘活动-会员接口](转盘活动-会员接口.md)
+[打开会员接口文档](../../../../melbet_sport-api/docs/独立活动/会员接入.md)
 
-[后台文档目录](../../总后台/独立活动-后台接口.md) · [转盘播报联调示例,每组50条](转盘播报示例.json)
-
-上述活动使用bot-28主机的 `/api/promotion/*` 独立入口,不加入原活动列表。
+`bot-28` 仅提供后台配置、充值成功后的自动赠送及后台任务,不再注册 `/api/promotion/*` 会员入口。

+ 4 - 51
docs/前端接口/独立活动/净亏邀请-会员接口.md

@@ -1,54 +1,7 @@
-# 净亏邀请-会员接口
+# 会员接口文档已迁移
 
-业务类型为 `7`。
+会员活动接口由 `melbet_sport-api` 提供,完整接入说明已迁至对应项目:
 
-本活动独立于原活动列表,通过 **bot-28主机** 的 `/api/promotion/*` 接口接入,与现有钱包接口使用相同Token。原 `/api/index/activity` 不返回本活动。
+[打开会员接口文档](../../../../melbet_sport-api/docs/独立活动/净亏邀请-会员接口.md)
 
-请求头为原始 `Authorization: <token>`,无需Bearer前缀。每次传 `platform=PC/H5/APP`;身份由Token识别,不提交会员ID。成功 `code=0`,业务错误 `code=-3`,Token过期为HTTP401,展示实际 `msg`。所有金额按服务端字符串展示。
-
-独立入口调用 `GET /api/promotion/list?type=7&platform=H5`,可加 `lang=zh`,返回 `total/data`。各条包含 `activity_id/type/type_name/title/image/content/rules/is_eligible/ineligible_reason`。不要在原活动类型选项中追加此类型。
-
-`GET /api/promotion/detail?activity_id=1&platform=H5` 只读返回本人状态;不要把旧活动ID当成独立活动ID。提交操作始终以服务端校验为准。
-
-## 页面与接口
-
-| 方法 | 路径 | 参数及用途 |
-|---|---|---|
-| GET | `/api/promotion/list` | `type=7`,独立活动入口 |
-| GET | `/api/promotion/detail` | `activity_id`,净亏、申请及邀请进度 |
-| POST | `/api/promotion/apply` | `activity_id`,达到净亏门槛后申请 |
-| POST | `/api/promotion/progress` | `activity_id`,返回最新参与状态 |
-| POST | `/api/promotion/claim` | `activity_id/round_no=1`,邀请达标后领奖 |
-| GET | `/api/promotion/rewards` | `activity_id/page/limit`,本人奖金记录 |
-
-分页默认1/20,`limit` 最大100。详情和progress返回:
-
-```json
-{
-  "activity_id": 1, "type": 7, "round_no": 1,
-  "rules": {"loss_amount":"500.00","invite_count":3,"reward_amount":"500.00","turnover_ratio":"0.00"},
-  "loss": "500.00", "applied_at": "2026-10-26 15:00:00",
-  "qualified_invitees": 2, "status": "collecting",
-  "valid_member_policy": {"id":1,"effective_date":"2026-10-26","min_deposit":"200","min_effective_bet":"300"},
-  "rewards": []
-}
-```
-
-门槛数值仅为示例,有效推广的充值、投注条件使用 `valid_member_policy`,不写死200。
-
-## 状态与领奖流程
-
-| status | 含义 | 页面操作 |
-|---|---|---|
-| not_applied | 尚未成功申请 | 净亏达到 `rules.loss_amount` 后可申请 |
-| collecting | 已申请,继续邀请 | 展示 `qualified_invitees/rules.invite_count` |
-| ready | 有效邀请人数达标 | 传 `round_no=1` 领取 |
-| claimed | 已领取 | 展示奖金记录,关闭重复领取入口 |
-
-净亏从本活动第一次上架后下单的已结算、未撤销真实注单累计,盈利与亏损相抵,不是充值减提现。申请后新注册并建立直属关系的下级,累计真实充值和有效投注同时达标,才增加有效人数。有效投注逐笔取 `min(本金, |净输赢|)`;人工上分、赠送不计充值。
-
-每人同一活动只有一次机会。重复申请返回原进度,无效申请不消耗机会;下架再上架不重置。申请成功时固定奖励规则,页面显示服务端返回规则;奖励配置后来修改不影响该申请快照。领取重试始终使用 `round_no=1`,不会重复入账。
-
-`rewards.status=paid` 表示已可用,`locked` 表示已发入冻结余额待完成流水。倍数0直接可用,大于0时独立累计奖金发放后有效投注,达到“奖金×倍数”自动解冻。活动下架后仍可查询本人rewards,历史记录保留。
-
-后台配置见[《净亏邀请-后台接口》](../../总后台/独立活动/净亏邀请-后台接口.md)。
+`bot-28` 仅提供后台配置、充值成功后的自动赠送及后台任务,不再注册 `/api/promotion/*` 会员入口。

+ 4 - 88
docs/前端接口/独立活动/转盘活动-会员接口.md

@@ -1,91 +1,7 @@
-# 转盘活动-会员接口
+# 会员接口文档已迁移
 
-业务类型为 `9`。
+会员活动接口由 `melbet_sport-api` 提供,完整接入说明已迁至对应项目:
 
-本活动独立于原活动列表,通过 **bot-28主机** 的 `/api/promotion/*` 接口接入,与现有钱包接口使用相同Token。原 `/api/index/activity` 不返回本活动。
+[打开会员接口文档](../../../../melbet_sport-api/docs/独立活动/转盘活动-会员接口.md)
 
-请求头为原始 `Authorization: <token>`,无需Bearer前缀。每次传 `platform=PC/H5/APP`;身份由Token识别,不提交会员ID。成功 `code=0`,业务错误 `code=-3`,Token过期为HTTP401,展示实际 `msg`。所有金额按服务端字符串展示。
-
-独立入口调用 `GET /api/promotion/list?type=9&platform=H5`,可加 `lang=zh`,返回 `total/data`。各条包含 `activity_id/type/type_name/title/image/content/rules/is_eligible/ineligible_reason`。不要在原活动类型选项中追加此类型。
-
-`GET /api/promotion/detail?activity_id=1&platform=H5` 只读返回本人状态;不要把旧活动ID当成独立活动ID。提交操作始终以服务端校验为准。
-
-## 接口与页面流程
-
-| 方法 | 路径 | 参数及用途 |
-|---|---|---|
-| GET | `/api/promotion/list` | 独立入口数据,固定 `type=9`、`lang=zh` |
-| GET | `/api/promotion/detail` | `activity_id`,规则与本人状态;只读 |
-| POST | `/api/promotion/heartbeat` | 认证进入可见页面时调用,随后每60秒调用;记录每日登录及实际在线观测 |
-| POST | `/api/promotion/progress` | `activity_id`,初始化参与状态并结算已完成任务次数,返回最新状态 |
-| POST | `/api/promotion/spin` | `activity_id/request_key`,转盘抽奖 |
-| POST | `/api/promotion/claim` | `activity_id/round_no`,领取转盘奖金 |
-| GET | `/api/promotion/records` | `activity_id/page/limit`,本人真实转盘记录 |
-| GET | `/api/promotion/rewards` | `activity_id/page/limit`,本人奖金记录,活动下架后仍可查询 |
-| GET | `/api/promotion/broadcasts` | `activity_id`,普通及达到666播报 |
-
-分页默认1/20,limit最大100。以上各接口均须传platform。
-
-进入转盘页面后发送heartbeat,再调用progress;页面可见时每60秒发送heartbeat并刷新progress,隐藏/离开后停止。已登录用户当天首次认证进入活动也计“每日登录”;日登录次数只发一次。在线以服务端时间采集,相邻观测超过5分钟不连算,多标签页、多端重叠去重;不能用前端倒计时直接申报已完成,也不能用登录到现在的时差代替在线时长。已有报表采集的真实观测可合并使用。
-
-## 转盘状态
-
-```json
-{
-  "activity_id": 1, "type": 9, "round_no": 1,
-  "spins": 7, "coins": "600.25", "remaining": "65.75", "status": "active",
-  "rules": {"completion_probability": "20.00", "online_seconds": 3600},
-  "conditions_complete": true,
-  "completed_tasks": {"online":true,"login":true,"invite":true,"deposit":true,"team_deposit":true,"bet":true,"team_bet":true},
-  "today_granted_tasks": ["login", "online", "deposit"],
-  "tasks": [{"key":"online","round_completed":true,"value":3600,"target":3600,"today_rewarded":true,"reward_spins":1,"remaining_seconds":0}],
-  "valid_member_policy": {"id":1,"effective_date":"2026-10-26","min_deposit":"200","min_effective_bet":"300"},
-  "prizes": ["0-666","100","50","200","350","500","150","366"]
-}
-```
-
-示例省略部分规则和任务。实际 `tasks` 返回七项:
-
-| key | 标题 | 默认门槛 |
-|---|---|---|
-| online | 每日在线 | 3600秒 |
-| login | 每日登录 | 1 |
-| invite | 每有效推广一人 | 同时达到生效的系统充值与有效投注门槛 |
-| deposit | 本人当日累计充值 | 3000 |
-| team_deposit | 直属下级当日累计充值合计 | 500 |
-| bet | 本人当日累计有效投注 | 5000 |
-| team_bet | 直属下级当日累计有效投注合计 | 3000 |
-
-`round_completed` 是累计任务资格,`value` 对四项金额任务表示本轮今天的金额;`invite.value` 为本轮已贡献的有效人数,`online.value` 为今天在线秒数。不要因为某天已完成任务,就把今天的金额显示为昨天的金额。`today_rewarded` 表示今天是否已发该每日任务次数;推广是每个独立下级事件加次,使用实际 `spins` 更新中心按钮。
-
-当日金额不跨天拼接:昨天充值2000、今天1000,不算达标3000。某一天达到3000后,本轮 `deposit` 完成标记可以保留。七项各完成一次即可,不必同一天完成。
-
-有效投注逐笔计算 `min(本金, |净输赢|)`,只计已结算且未撤销的真实注单;例如本金100、净亏50,有效为50。团队金额只计直属下级建立关系后的贡献,试玩不计,充值不含人工上分或赠送。
-
-### 抽奖、概率、领取
-
-抽奖前生成16~64位的字母数字/下划线/短横线 `request_key`;重试同一次操作必须保留原值,新抽奖才换值。推荐使用UUID。同一键只扣一次次数、返回同一结果。服务端成功后再按 `prize_index` 播放动画,禁止客户端先随机再上传结果。
-
-抽奖返回 `spin` 和剩余 `spins`,例如:
-
-```json
-{"spin":{"id":12,"round_no":1,"prize_index":0,"prize":"0-666","amount":"65.75","coins":"666.00","reached":true,"conditions_complete":true,"probability":"20.00","created_at":"2026-10-26 15:00:00"},"spins":6,"replayed":false}
-```
-
-未完成七项时概率固定0;全部完成后每次按后台配置概率判定(默认20%)。普通奖池不会让累计达到666:只抽累计后小于666的固定奖项及浮动奖项;可用奖项等概率,浮动奖项按分等概率取值。完成条件并命中时由浮动奖项补齐差额。界面展示服务端返回的 `ordinary_pool_rule`,固定面额不会被暗中截减。
-
-状态 `not_started/active/reached` 分别表示尚未初始化、正在累计、达到666待领取。达到后停抽,领取传本轮的 `round_no`。领取重试必须保留旧轮号,不用新轮号替换重试请求。奖金已预留,达到后下架也可领取。
-
-领取后金币清零,未用次数保留,进入下一轮。登录、在线完成标记保留且当天不重复加次;其他五项归零,只认领取后新充值、新投注及尚未计奖的新有效推广。当天领取前的金额不能重新用于下一轮。下级整个活动只贡献一次有效推广次数,但其之后的新充值、新投注仍可用于团队金额任务。
-
-`rewards.status`:`reserved` 已达到且待领取,`locked` 已发入冻结余额等待流水,`paid` 已可用。流水倍数0直接可用,大于0时按每笔奖金发放后的新增有效投注达到所需流水后自动解冻;不把解冻当作再次中奖。
-
-## 我的记录及播报
-
-“我的记录”使用records,是本人真实抽奖历史,含轮次、实际金币、累计金币、时间,空数组显示暂无内容。奖金历史使用rewards单独展示。
-
-播报返回 `ordinary`、`reached_666` 两组真实记录,各最多50条,账号脱敏;数据不足时返回实际条数。联调数据见同目录 `转盘播报示例.json`,每组各50条随机样例,带 `is_demo=true`,仅作示例展示,不作为真实用户中奖播报。
-
-关闭活动暂停新增参与、任务结算和抽奖;历史记录及已达到666的领取权益保留。重新上架延续原记录和进度。
-
-后台配置见[《转盘活动-后台接口》](../../总后台/独立活动/转盘活动-后台接口.md)。
+`bot-28` 仅提供后台配置、充值成功后的自动赠送及后台任务,不再注册 `/api/promotion/*` 会员入口。

+ 4 - 42
docs/前端接口/独立活动/首充奖励-会员接口.md

@@ -1,45 +1,7 @@
-# 首充奖励-会员接口
+# 会员接口文档已迁移
 
-业务类型为 `8`。
+会员活动接口由 `melbet_sport-api` 提供,完整接入说明已迁至对应项目:
 
-本活动独立于原活动列表,通过 **bot-28主机** 的 `/api/promotion/*` 接口接入,与现有钱包接口使用相同Token。原 `/api/index/activity` 不返回本活动。
+[打开会员接口文档](../../../../melbet_sport-api/docs/独立活动/首充奖励-会员接口.md)
 
-请求头为原始 `Authorization: <token>`,无需Bearer前缀。每次传 `platform=PC/H5/APP`;身份由Token识别,不提交会员ID。成功 `code=0`,业务错误 `code=-3`,Token过期为HTTP401,展示实际 `msg`。所有金额按服务端字符串展示。
-
-独立入口调用 `GET /api/promotion/list?type=8&platform=H5`,可加 `lang=zh`,返回 `total/data`。各条包含 `activity_id/type/type_name/title/image/content/rules/is_eligible/ineligible_reason`。不要在原活动类型选项中追加此类型。
-
-`GET /api/promotion/detail?activity_id=1&platform=H5` 只读返回本人状态;不要把旧活动ID当成独立活动ID。提交操作始终以服务端校验为准。
-
-## 页面与接口
-
-| 方法 | 路径 | 参数及用途 |
-|---|---|---|
-| GET | `/api/promotion/list` | `type=8`,首充独立入口 |
-| GET | `/api/promotion/detail` | `activity_id`,查看首充规则及是否已有充值 |
-| GET | `/api/promotion/rewards` | `activity_id/page/limit`,本人奖金记录 |
-
-分页默认1/20,`limit` 最大100。详情示例:
-
-```json
-{
-  "activity_id": 2, "type": 8,
-  "rules": {"min_deposit":"100.00","reward_amount":"25.00","turnover_ratio":"0.00"},
-  "has_deposited": false, "rewards": []
-}
-```
-
-页面展示“首次成功充值达到多少、固定送多少”,直接读取 `rules.min_deposit/reward_amount`。无需申请、转盘或手动领奖按钮,使用原充值入口,充值成功后刷新详情和钱包。
-
-## 首次与到账规则
-
-- 首次指会员全历史第一笔正向成功入账的“充值/三方充值”,不是活动期间第一笔或每日第一笔。
-- 失败订单、人工上分、活动赠送不计;前台真实充值凭证审核成功计入。
-- 首笔不足门槛或发生在活动下架时,第二笔不能补领;已有充值历史的会员不因活动上架恢复首充资格。
-- 成功后自动赠送,重复支付回调、补单或重复审核不会重复发奖。与既有充值活动分别判断资格,可叠加。
-- `has_deposited=true` 表示已有成功充值,不代表一定获得首充奖金,是否获奖以 `rewards` 为准。
-
-奖金记录 `status=paid` 表示已可用,`locked` 表示已发入冻结余额待流水。`turnover_ratio=0` 直接可用;大于0时,各笔奖金独立累计发放后的新有效投注,达到“奖金×倍数”后自动解冻。有效投注逐笔按 `min(本金, |净输赢|)`,未结算和撤销注单不计。
-
-活动下架后rewards仍可查,空数组显示暂无奖金。重复刷新不能改变首充资格。
-
-后台配置见[《首充奖励-后台接口》](../../总后台/独立活动/首充奖励-后台接口.md)。
+`bot-28` 仅提供后台配置、充值成功后的自动赠送及后台任务,不再注册 `/api/promotion/*` 会员入口。

+ 3 - 1
docs/后端/报表统计口径与数据补录.md

@@ -49,7 +49,7 @@ payment_orders.fee是配置费率快照,withdraws.service_charge是向客户
 
 ## 活动和装置
 
-melbet_sport-api新增认证POST /api/activity/heartbeat;前台可见时每60秒发送,隐藏/退出后停止。成功的前台POST业务操作也记活动,普通GET轮询不记,整站维护状态轮询不记。TG真实用户操作独立记录为unknown装置。
+melbet_sport-api在登录认证后、业务处理前自动记录正常API请求的服务端时间,GET、POST及业务参数失败均计为访问;游客、无效Token和认证前的维护拦截不记。POST /api/activity/heartbeat保留兼容,不再要求前台额外定时调用。TG真实用户操作独立记录为unknown装置。
 
 服务端时间按分钟去重,session_hash不存原token。活动区间只累计连续观测之间最多5分钟的间隔,超过5分钟拆段;同一会员的多端重叠合并。起始采集时间在reporting_settings,之前历史不伪造,已知小计与null区分。
 
@@ -87,3 +87,5 @@ php artisan reporting:sync
 统计异常日志关键字reporting_context_failed、reporting_activity_failed、reporting_telegram_activity_failed、reporting_request_failed。生产MySQL、实际支付商账单和完整历史数据仍需部署后核对;隔离SQLite回归不替代这些核对。
 
 回滚先停reporting:sync及新采集,回滚两个项目代码,再处理新表。down删除统计投影、核对及活动数据;核对证据和审计应先备份,原钱包/业务订单不会被down删除。
+
+时长按认证API请求间隔估算:同会话相邻不超过5分钟才连接,最后一次请求之后不继续累计;无请求的静态停留可能少计,后台自动轮询可能多计。user_session.update_time仅表示最后请求时间,不能替代历史活动观测。

+ 3 - 3
docs/总后台/独立活动-后台接口.md

@@ -4,8 +4,8 @@
 
 | 活动 | 独立后台文档 | 对应会员文档 |
 |---|---|---|
-| 净亏邀请 | [净亏邀请-后台接口](独立活动/净亏邀请-后台接口.md) | [净亏邀请-会员接口](../前端接口/独立活动/净亏邀请-会员接口.md) |
-| 首充奖励 | [首充奖励-后台接口](独立活动/首充奖励-后台接口.md) | [首充奖励-会员接口](../前端接口/独立活动/首充奖励-会员接口.md) |
-| 转盘活动 | [转盘活动-后台接口](独立活动/转盘活动-后台接口.md) | [转盘活动-会员接口](../前端接口/独立活动/转盘活动-会员接口.md) |
+| 净亏邀请 | [净亏邀请-后台接口](独立活动/净亏邀请-后台接口.md) | [净亏邀请-会员接口](../../../melbet_sport-api/docs/独立活动/净亏邀请-会员接口.md) |
+| 首充奖励 | [首充奖励-后台接口](独立活动/首充奖励-后台接口.md) | [首充奖励-会员接口](../../../melbet_sport-api/docs/独立活动/首充奖励-会员接口.md) |
+| 转盘活动 | [转盘活动-后台接口](独立活动/转盘活动-后台接口.md) | [转盘活动-会员接口](../../../melbet_sport-api/docs/独立活动/转盘活动-会员接口.md) |
 
 本页仅保留导航;每个活动的参数、示例、上下架、资格和发奖说明均在各自文档内。

+ 1 - 1
docs/总后台/独立活动/净亏邀请-后台接口.md

@@ -72,4 +72,4 @@
 
 独立菜单为 `promotion`;配置、保存与上下架、记录权限分别为 `admin/promotion`、`admin/promotion/submit`、`admin/promotion/records`。上线执行新增迁移 `2026_10_26_160000_create_promotion_activities.php`,计划任务每分钟运行 `promotions:sync`;有效投注沿用已有分享注单同步。
 
-对应会员端文档:[《净亏邀请-会员接口》](../../前端接口/独立活动/净亏邀请-会员接口.md)。
+对应会员端文档:[《净亏邀请-会员接口》](../../../../melbet_sport-api/docs/独立活动/净亏邀请-会员接口.md)。

+ 2 - 2
docs/总后台/独立活动/转盘活动-后台接口.md

@@ -93,10 +93,10 @@
 
 `GET /admin/promotion/records?activity_id=1&kind=spins&page=1&limit=20`,`kind` 支持 `accounts/rounds/tasks/spins/rewards`,可加 `user_id`,返回 `total/data`。抽奖记录保存实际概率、是否全部达标、奖项和累计金币;领奖记录有关联资金流水。达到666后的状态依次为 `reserved` 待领取、`locked` 冻结待流水或 `paid` 已可用。
 
-会员“我的记录”使用真实抽奖记录。普通播报及达到666播报各最多50条真实脱敏记录;联调随机样例每组50条,见[转盘播报示例](../../前端接口/独立活动/转盘播报示例.json),均带 `is_demo=true`,不作真实中奖记录。
+会员“我的记录”使用真实抽奖记录。普通播报及达到666播报各最多50条真实脱敏记录;联调随机样例每组50条,见[转盘播报示例](../../../../melbet_sport-api/docs/独立活动/转盘播报示例.json),均带 `is_demo=true`,不作真实中奖记录。
 
 ## 权限与上线
 
 独立菜单为 `promotion`;配置、保存与上下架、记录权限分别为 `admin/promotion`、`admin/promotion/submit`、`admin/promotion/records`。上线执行新增迁移 `2026_10_26_160000_create_promotion_activities.php`,计划任务每分钟运行 `promotions:sync`;有效投注沿用已有分享注单同步。
 
-对应会员端文档:[《转盘活动-会员接口》](../../前端接口/独立活动/转盘活动-会员接口.md)。
+对应会员端文档:[《转盘活动-会员接口》](../../../../melbet_sport-api/docs/独立活动/转盘活动-会员接口.md)。

+ 1 - 1
docs/总后台/独立活动/首充奖励-后台接口.md

@@ -70,4 +70,4 @@
 
 独立菜单为 `promotion`;配置、保存与上下架、记录权限分别为 `admin/promotion`、`admin/promotion/submit`、`admin/promotion/records`。上线执行新增迁移 `2026_10_26_160000_create_promotion_activities.php`,计划任务每分钟运行 `promotions:sync`;有效投注沿用已有分享注单同步。
 
-对应会员端文档:[《首充奖励-会员接口》](../../前端接口/独立活动/首充奖励-会员接口.md)。
+对应会员端文档:[《首充奖励-会员接口》](../../../../melbet_sport-api/docs/独立活动/首充奖励-会员接口.md)。

+ 4 - 2
docs/总后台/龙虎榜资金流动与报表-接口.md

@@ -163,7 +163,7 @@ GET /admin/reporting/details?report_type=promotion&category=self:lottery&agent_a
 - quality 分别提示缺失时间/金额、未核对费用、非本位币、未核实的历史代理归属、导入错误、尚未完成首次同步。0金额与缺失null不是同一个含义。
 - attribution_quality=event_snapshot 是事件快照;historical_current_agent 是没有历史快照时参考当前代理关系。推广报表会将这种历史归属标为待核对,不冒充精确历史业绩。
 - 装置没有可靠证据时为 unknown,包括第三方回单;供应商 platform 不能当H5/PC/APP。
-- 活动时长从新采集上线后记录,依据有效前台操作、每60秒可见页面心跳及TG用户操作;间隔超过5分钟拆段、多端重叠去重。active_rate分母是范围内期末真实会员,不按注册端分组。
+- 活动时长从新采集上线后记录,依据已认证API请求的服务端观测及TG用户操作,保留可选心跳兼容;间隔超过5分钟拆段、多端重叠去重。active_rate分母是范围内期末真实会员,不按注册端分组。
 - 历史活动的时长和活跃人数按观测保存的agent_id/inviter_id筛选、分组,会员改绑不会将历史活动转给新代理或邀请人。多端重叠先合并覆盖区间,再按时间线上最新观测的归属划分,每秒只计算一次;快照为null时不回填当前关系。
 - member_count仍为现有人口范围的参考人数。若历史活动中的会员已因改绑离开该范围,activity_population_complete=false,active_rate返回null,前端显示“—”;activity_minutes、known_activity_minutes及active_member_count仍返回快照范围内的真实值,不用当前人数反推历史比例。
 - 日期早于 activity_started_at 时活动分钟和活跃率为null,同时给 known_activity_minutes。资金流日列表不提供每日在线指标。
@@ -200,4 +200,6 @@ data-quality返回total、data异常列表(source、source_id、candidate_at
 
 来源状态与已核实的非零收付款冲突时,统计暂停给出最终净值;根据凭据核对原状态及实际金额,真实未打款可以更正核对金额为0,发生的外部费用仍单列。不得为了消除提示随意填写0。
 
-前台会员端还需接入新增文档《统计装置与活动采集-前台接口.md》中的 Platform 和可见页面心跳,否则新增业务可能仍归未知端。
+前台会员端在正常API请求中携带《统计装置与活动采集-前台接口.md》规定的Platform即可自动记录活跃,无需额外定时心跳;未传装置仍归unknown。
+
+活动时长是API请求间隔估算,已认证GET、POST和业务参数失败均记录访问;游客、无效Token及维护前置拦截不记。无请求的静态停留不继续增长,后台轮询可能计入,不能视为精确在线时长。

+ 0 - 12
routes/api.php

@@ -80,18 +80,6 @@ Route::fallback(function () {
 
 // 足球app充值提现的接口需要校验 token 的接口
 Route::middleware([\App\Http\Middleware\MemberMaintenance::class, 'check.token'])->group(function () {
-    Route::prefix('/promotion')->group(function () {
-        Route::post('/heartbeat', [\App\Http\Controllers\api\Promotion::class, 'heartbeat']);
-        Route::get('/list', [\App\Http\Controllers\api\Promotion::class, 'index']);
-        Route::get('/detail', [\App\Http\Controllers\api\Promotion::class, 'detail']);
-        Route::post('/progress', [\App\Http\Controllers\api\Promotion::class, 'progress']);
-        Route::post('/apply', [\App\Http\Controllers\api\Promotion::class, 'apply']);
-        Route::post('/spin', [\App\Http\Controllers\api\Promotion::class, 'spin']);
-        Route::post('/claim', [\App\Http\Controllers\api\Promotion::class, 'claim']);
-        Route::get('/records', [\App\Http\Controllers\api\Promotion::class, 'records']);
-        Route::get('/rewards', [\App\Http\Controllers\api\Promotion::class, 'rewards']);
-        Route::get('/broadcasts', [\App\Http\Controllers\api\Promotion::class, 'broadcasts']);
-    });
     Route::prefix('/wallet')->group(function () {
         Route::get("/scan", [Wallet::class, 'scan']);
         Route::post("/recharge", [Wallet::class, 'recharge']);

+ 3 - 4
tests/Integration/PromotionActivitiesTest.php

@@ -235,7 +235,7 @@ class PromotionActivitiesTest extends TestCase
         $this->assertSame('reserved', DB::table('promotion_rewards')->value('status')); $this->assertSame(1, (int)DB::table('promotion_activities')->where('id', $id)->value('rewarded_count'));
     }
 
-    public function test_independent_campaigns_do_not_touch_old_catalog_or_activity_list_and_routes_require_auth(): void
+    public function test_independent_campaigns_do_not_touch_old_catalog_and_member_routes_live_in_member_project(): void
     {
         $id = $this->activity(9); $this->activity(7); $this->activity(8);
         $this->assertSame(0, DB::table('activity_rewards')->count());
@@ -245,9 +245,8 @@ class PromotionActivitiesTest extends TestCase
         $this->assertSame(3, DB::table('menus')->where('type', 2)->where('uri', 'like', 'admin/promotion%')->count());
         $router = new \Illuminate\Routing\Router(new \Illuminate\Events\Dispatcher($this->app), $this->app); $this->app->instance('router', $router);
         $router->group(['prefix' => 'api'], function () { require dirname(__DIR__, 2) . '/routes/api.php'; });
-        $route = $router->getRoutes()->match(Request::create('/api/promotion/spin', 'POST'));
-        $this->assertContains('check.token', $route->middleware());
-        $this->assertContains(\App\Http\Middleware\MemberMaintenance::class, $route->middleware());
+        $memberRoutes=array_filter($router->getRoutes()->getRoutes(),fn ($route)=>str_starts_with($route->uri(),'api/promotion'));
+        $this->assertSame([],$memberRoutes,'Member promotion routes must be owned by melbet_sport-api');
         $router->group(['prefix' => 'admin'], function () { require dirname(__DIR__, 2) . '/routes/admin.php'; });
         $route = $router->getRoutes()->match(Request::create('/admin/promotion/submit', 'POST'));
         $this->assertContains('admin.jwt', $route->middleware()); $this->assertContains('check.button.uri', $route->middleware());