[开发]mobile_api 插件开发方案(2026-10-11,v2 已定稿)

👑Lv.11 元老 🌞 骨干会员
2026-10-26 00:26:45

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/mobile_api插件开发方案_2026-10-11.md。

mobile_api 插件开发方案(2026-10-11,v2 已定稿)

依据:docs/插件开发规范.md(v1.5)+ 需求指令《mobile_api 插件(对外 API 层)》。

与规范冲突处,以规范为准。 本方案只做设计与评估,不写代码(作者已明确:先不着急动手)。

§14 的 6 个待确认问题已全部拍板,结论见 §2 决策表 D13–D16 与 §15 核心改动清单。

阅读顺序:§0 结论速览 → §2 决策表 → §3 架构 → §5.3 缺口处置 → §15 核心改动。


0. 结论速览(先看这段)

目标:给 App / H5 / 第三方客户端提供统一 REST API;核心提供「骨架 + 扩展点」,插件通过 mobile_api_routes 钩子把功能接进来;认证 / 权限 / 限流 / 审计统一在 API 层完成。

三条调研结论直接决定架构:

  1. 核心已存在 /api 路由组(index.php:397 起 $router->group('/api', …)),内含一批站内内部接口:

/api/vote、/api/upload、/api/fetch-image、/api/captcha、/api/pow、/api/cover-preview、/api/favorites-count、/api/post/{id}/body。

→ 对外 API 与它们共用 /api 前缀,必须不冲突、不误伤(白名单放行,§3.5)。

  1. 核心 Router 能力受限(app/Core/Router.php):只有 get()/post()/any()(16–31 行),无 put()/delete()/patch();路径参数仅支持单段 {name}(addRoute() 第 47 行),无法注册多段通配;方法不匹配不回 405。

→ 需求中的 PUT/DELETE /api/threads/{id} 无法用核心 Router 做真 RESTful。

  1. 架构定案(D1):挂核心钩子 route_before_dispatch(index.php:415,早于路由匹配)做早期拦截 + 自建 ApiRouter 子路由——命中本插件路由则自行分发(支持 GET/POST/PUT/DELETE/PATCH 与多段 {param}),未命中原样放行。

→ 不改核心 Router、支持真 RESTful 与 OPTIONS/CORS 预检、统一中间件链、mobile_api_routes 扩展点自然落地。

唯一的「改核心」动作(已批准,见 §15):核心 Model/Helper 纯新增 3 个方法(通知单条已读、私信会话查询、私信发送)——这是需求「私信不能只收不发」的硬前提,比「功能降级」更值。


1. 目标与非目标

目标

  • 统一认证(Bearer:access 短期 + refresh 长期)、统一权限(走 Model 层,钩子自然触发)、统一限流(按 Token)、统一审计。
  • 提供需求 §四 全部核心接口(认证 / 版块 / 帖子 / 用户 / 搜索 / 通知 / 私信)。
  • 提供插件扩展点 mobile_api_routes,插件自注册路径,认证/限流由 API 层兜底。
  • 后台:总开关 / 限流 / CORS / 审计 / Token 管理;前台:设备管理(查看 + 撤销 + 重新生成)。

非目标

  • 不做客户端 App/H5(P5,另立项)。
  • 不改核心 Router;不改核心业务逻辑。核心仅纯新增 3 个 Model/Helper 方法(§15),零破坏。
  • 不与 MCP 合表(D13)。
  • 不做 OAuth2 授权码流程(第三方登录属 oidc_provider/third_party_login)。

2. 决策表(D1–D16,全部已拍板)

#议题**冻结结论**理由 / 落地
D1路由实现早期拦截 + 自建 ApiRouter(挂 route_before_dispatch)核心 Router 无 put/delete、无多段通配(§3.1)
D2Token 形态Bearer:access(2h)+ refresh(30d),前缀 fh_app_需求 §三
D3Token 存储只存 sha256 hash,明文仅生成时展示一次;表 api_tokens§6.1
D4CSRF对外 API 一律不加 CSRF(纯 Token 认证,非浏览器 Session)与 /mcp 同口径
D5限流按 Token(非 IP):写 5 次/分/Token、读 60 次/分/Token表 api_rate 固定窗口(§6.5)
D6CORS默认同源;后台可加白名单(**不允许 * 与凭据并存**)同源是安全基线;全开 * 在 Token 泄露时风险大(§7)
D7权限对齐只走 Model 层 → 等级/积分/审核/版块权限随核心 + 插件钩子自然触发需求 §二-④
D8插件扩展点钩子 mobile_api_routes,传 ['router' => $apiRouter]§5.4
D9API 注册默认关闭;后台开启后强制 PoW(复用 /api/pow)§6.4
D10封禁校验每次调用校验(auth_login_check 钩子结果 + users.status)与核心登录同口径
D11审计关键操作(登录/刷新/发帖/回帖/编辑/删除/撤销)落 api_logs,大字段剔除§6.6
D12分期P0 + P1 先做;P2 扩展点(示例 = daily_checkin);P3/P4 后续§10
D13MCP 共用 Token先不合表:API 用 fh_app_/api_tokens,MCP 保持 fh_mcp_/mcp_tokens;api_tokens 预留 consumer TEXT DEFAULT 'app'MCP 刚上线,改它是回归风险;留口子以后可合(§8)
D14API 登录口径沿用核心口径(锁定计次 + PoW);折中:登录首次要 PoW,后续 refresh 不再要 PoW防爆破 + 防机器人,同时避免客户端每次都要解 PoW(§6.3)
D15通知/私信缺口走方案 B:核心纯新增 3 个方法(不降级)私信是客户端刚需,不能只收不发(§5.3 / §15)
D16P2 示例插件daily_checkin(已存在:plugins/daily_checkin/)轻量、有状态、正好验证扩展点


3. 架构设计

3.1 为什么是「拦截式」而非「核心 Router 注册式」

方案能否真 RESTful改动核心结论
A. 核心 Router 显式注册(any() + X-HTTP-Method-Override)❌ 只能 GET/POST否备选,牺牲规范
B. route_before_dispatch 早期拦截 + 自建 ApiRouter✅ GET/POST/PUT/DELETE/PATCH否采纳(D1)

route_before_dispatch 在 index.php:415 触发(Plugin::hook('route_before_dispatch', ['router' => $router])),早于 $router->dispatch()(index.php:431)。插件在此读 REQUEST_METHOD + 路径,命中即自行处理并 exit,否则 return 放行。

⚠️ 该钩子会被触发两次(index.php:415 一次、Router::dispatch() 内 Router.php:93 一次)。钩子必须幂等:非 /api 请求立即 return;已处理请求直接 exit,永远走不到第二次。

3.2 ApiRouter(自建子路由,仅本插件内)

class ApiRouter {
    get/post/put/delete/patch/any($path, $handler)   // $path 支持多段 {param}
    match($method, $path): ?array                    // 返回 [handler, params, meta]
}

  • 路径参数编译为命名捕获组,允许多段(如 /threads/{id}/replies)。
  • $handler 形式:[ControllerClass::class, 'method'](与核心一致,new $class() 无 DI)。
  • 每条路由带元数据:auth(是否需登录)、scope(read/write)、rate(限流桶名)。

3.3 请求生命周期

HTTP 请求
  └─ index.php
       ├─ route_register(本插件:注册前台 /api-tokens;后台走 admin_route_register)
       ├─ route_before_dispatch ★
       │     └─ MobileApi::intercept()
       │          1) 取 METHOD + PATH,剥离 BASE_PATH
       │          2) 路径不以 /api/ 开头 → return
       │          3) 路径命中核心内部接口白名单 → return(放行,绝不动它们)
       │          4) OPTIONS → 回 204 + CORS 头,exit
       │          5) ApiRouter::match() 未命中 → return(交核心 404)
       │          6) 中间件链:CORS → 总开关 → 认证 → 封禁 → 限流 → scope → 业务 → 审计
       │          7) 统一 JSON 输出,exit
       └─ $router->dispatch()(仅非 API 请求走到这里)

3.4 中间件链(顺序固定)

  1. CORS:Origin 校验(D6:默认同源,白名单可配),回 Access-Control-*。
  2. 总开关:api_enabled 关闭 → 503。
  3. 认证:解析 Authorization: Bearer fh_app_… → sha256 → 查 api_tokens → 校验 enabled/expires_at。
  4. 封禁:Plugin::isBanned($uid)(auth_login_check 钩子 + users.status)→ 403。
  5. 限流:RateLimit::hit($tokenId,$action,$window) → 超限 429。
  6. scope:写操作要求 scope=write,否则 403。
  7. 业务:调用控制器(内部走 Model 层)。
  8. 审计:关键操作写 api_logs。

3.5 与既有 /api 组的关系(放行白名单)

本插件内置核心内部接口白名单(写死,防误伤):

/api/vote、/api/upload、/api/fetch-image、/api/captcha、/api/pow、/api/cover-preview、/api/favorites-count、/api/post/{id}/body。

命中白名单 → 立即放行。对外 API 路径与白名单无重叠。规范 §11.34b:/api 无同名物理目录,安全。


4. 插件目录结构

plugins/mobile_api/
├── plugin.json                 # hooks: init_after / route_register / admin_route_register / layout_head_end
│                               # category=integration, icon=mobile, admin_url=/admin/mobile-api
├── Plugin.php                  # 常量 / configDefaults / DDL / 配置读写 / loadUser / isBanned / class_alias
├── ApiRouter.php               # 自建子路由(多段 {param})
├── MobileApi.php               # intercept() + 中间件链 + 统一 JSON 输出
├── TokenManager.php            # fh_app_ Token 生成/校验/刷新(轮换)/撤销(access+refresh)
├── RateLimit.php               # 按 Token 固定窗口限流(api_rate)
├── Audit.php                   # api_logs 写入 + 大字段剔除
├── ApiResponse.php             # {code,message,data} 统一响应 + 错误码
├── Controllers/
│   ├── AuthController.php      # login/register/refresh/logout/me
│   ├── CategoryController.php  # categories / categories/{id}
│   ├── ThreadController.php    # threads CRUD + replies
│   ├── UserController.php      # users/{id}、users/{id}/threads、users/{id}/replies、users/me
│   ├── SearchController.php    # search
│   ├── NotificationController.php
│   └── MessageController.php
├── hook/
│   ├── init_after.php          # 惰性建表兜底(标记文件守卫)
│   ├── route_register.php      # 前台 /api-tokens(设备管理)
│   ├── admin_route_register.php# 后台 /mobile-api
│   └── layout_head_end.php     # 前台设备管理页引 style.css
├── views/{tokens.php, admin.php}
├── assets/{style.css, script.js}
├── lang/{zh.php, en.php, zh_tw.php}
└── data/                       # 独立 SQLite(api_tokens/api_logs/api_rate/api_config)— 不进发布包

命名空间:Plugin\MobileApi\…;目录名 mobile_api 含下划线 → Plugin.php 末尾必须 class_alias(Plugin::class, 'Plugin\mobile_api\Plugin', false)(规范 §399-432;否则 activate/uninstall 静默跳过)。

自动加载:Plugin\MobileApi\Controllers\AuthController → mobile_api/Controllers/AuthController.php ✓。


5. 数据模型、接口映射与扩展点

5.1 DDL(落独立库)

api_tokens / api_logs / api_rate 三表照需求 §八原样,另加:

-- 本插件新增:总配置(KV),避免写全局 settings
CREATE TABLE IF NOT EXISTS api_config (
  k TEXT PRIMARY KEY, v TEXT NOT NULL
);
-- 建议补充字段(D13 预留 + 设备管理/审计可读性)
-- api_tokens 增列 consumer TEXT NOT NULL DEFAULT 'app'     -- D13:为将来与 MCP 合表留口
-- api_tokens 增列 last_ip_hash TEXT NOT NULL DEFAULT ''    -- 设备管理展示

api_tokens.token_hash / refresh_hash 均存 sha256(明文),绝不存明文。

5.2 接口 → 核心 Model / Helper 映射

接口走的核心能力状态
POST /api/auth/loginUser::findByUsername + password_verify + auth_login_check + users.status✅(§6.3)
POST /api/auth/registerUser::create() + PoW✅(D9:默认关)
POST /api/auth/refresh / logout本插件 TokenManager✅
GET /api/auth/meToken→uid 载入(等效 Auth::getCurrentUser())✅
GET /api/categoriesCategory::allOrdered() + getCategoryStats()✅
GET /api/categories/{id}Category::allOrdered() 过滤(无 getById)✅(插件内筛选)
GET /api/threadsThread::getIndexCursor() / getIndexPaginated()✅
GET /api/threads/{id}Thread::getById() + fillExternContent()✅
POST /api/threadsThread::insert() → 触发 thread_create_after 等✅
PUT /api/threads/{id}Thread::update() + 归属校验✅
DELETE /api/threads/{id}Thread::softDelete()✅
GET /api/threads/{id}/repliesPost::getByThread() + countByThread()✅
POST /api/threads/{id}/repliesPost::insert() → 触发 post_create_after✅
GET /api/users/{id}User::getProfileById()✅
GET /api/users/{id}/threadsUser::getThreads($uid,$limit)✅
GET /api/users/{id}/replies核心新增 User::getReplies($uid,$limit,$offset)✅(§15-①)
PUT /api/users/meUser::updateProfile($id,$email,$signature)✅
GET /api/searchSearch::search($q,$type,$page,$perPage,$allowedCatIds,$timeRange)✅
GET /api/notificationsNotification::getList() + getTotal() + getUnreadCount()✅
POST /api/notifications/{id}/read核心新增 Notification::markRead($uid,$ids)✅(§15-②)
GET /api/messagesMessage::getInbox() + countInbox()(+ getSent())✅
GET /api/messages/{user_id}核心新增 Message::getConversation($uid,$peerId,$limit,$offset)✅(§15-③)
POST /api/messages核心新增 Message::send($from,$to,$content)✅(§15-③)

5.3 缺口处置(D15 = 方案 B:核心纯新增 3 个方法)

需求 §四 有 3 处接口在核心 Model 层无对应能力;规范禁止插件直写核心表,故按 D15 由核心纯新增 3 个方法(签名与文件见 §15)。核心改动仅新增、不修改既有方法、不影响任何现有调用方。

缺口结论
通知单条已读核心加 Notification::markRead(int $uid, array $ids): int(复用现有 UPDATE 模式,调用方限定 user_id)
私信会话查询核心加 Message::getConversation(int $uid, int $peerId, int $limit, int $offset): array(双向 sender/receiver 组合,按时间排序)
私信发送核心加 Message::send(int $from, int $to, string $content): int(写入 messages 表;与核心 MessageController 同口径)

5.4 插件扩展点 mobile_api_routes

核心(本插件)触发:

$apiRouter = new ApiRouter();
$apiRouter->get('/categories', [CategoryController::class, 'index']);   // …核心接口
\app\Helpers\Plugin::hook('mobile_api_routes', ['router' => $apiRouter]);
$apiRouter->dispatch();   // 统一分发

插件订阅(示例:daily_checkin,D16):

// plugins/daily_checkin/hook/mobile_api_routes.php
if (!isset($router)) return;
$router->get('/checkin/status', [\Plugin\DailyCheckin\ApiController::class, 'status']);
$router->post('/checkin',        [\Plugin\DailyCheckin\ApiController::class, 'checkin']);

  • 插件在 plugin.json 加一行:"hooks": { "mobile_api_routes": "hook/mobile_api_routes.php" }。
  • 插件只注册「路径 + 控制器方法」;认证 / 权限 / 限流 / CORS / 审计全部由 API 层统一处理。
  • 插件路径自动挂 /api/ 之下(/checkin → /api/checkin),避免与核心接口冲突。
  • 钩子参数名必须是 $params 且可读 $router(Plugin::hook() 用 extract($params, EXTR_SKIP|EXTR_REFS),Plugin.php:715-722)。
  • P2 需给 daily_checkin 增加:1 个 hook/mobile_api_routes.php + 1 个 ApiController.php(复用其现有业务方法),纯增量、不改既有逻辑。

6. 认证体系

6.1 Token 形态

  • 明文:fh_app_ + 32 字节随机(base64url),生成时返回一次,之后只留 sha256。
  • access:expires_at = now + 2h;refresh:refresh_expires_at = now + 30d。
  • 刷新即轮换:旧 refresh 换新 access+refresh,旧 refresh 立即失效(防重放,参照 OIDC 插件轮换+复用检测思路)。

6.2 设备管理

  • 前台 /api-tokens:列出本用户全部 Token(设备名 / scope / 最近使用 / 过期),可撤销单个、重新生成。
  • 明文仅在建/刷新响应出现一次,列表页永不回显。

6.3 登录(D14:沿用核心口径 + 首次 PoW)

① 首次登录(POST /api/auth/login):
   客户端先 GET /api/pow 取题 → 提交带 PoW 答案
   → 核心口径校验:失败锁定计次(防爆破,复用核心锁定逻辑)
   → User::findByUsername → password_verify(用户不存在也用陪跑 hash,防时序侧信道)
   → Plugin::hook('auth_login_check', ['user'=>$user]) → $GLOBALS['mod_system_login_blocked']
   → $user['status'] !== 'active' → 拒绝
   → 邮箱未验证(开关开启时)→ 拒绝
   → 通过:签发 access(2h) + refresh(30d)

② 后续续期(POST /api/auth/refresh):
   只校验 refresh_token(**不再要求 PoW**)→ 轮换签发新 access+refresh
   → refresh 失效/过期 → 401,客户端回到 ①

折中收益:机器人批量爆破被 ① 的 PoW + 锁定计次挡住;正常客户端只在首次登录解一次 PoW,续期无感。

6.4 注册(D9)

  • 默认关闭(api_allow_register=0)。
  • 开启后:GET /api/pow 取题 → 带 PoW 提交 → User::create() → 欢迎通知(Notification::notifyWelcome)。邮箱验证继承核心开关。

6.5 限流(D5)

  • 按 Token(非 IP)固定窗口:写 5 次/分/Token、读 60 次/分/Token(表 api_rate,UNIQUE(token_id, action, window_at) UPSERT 原子计数)。
  • 后台可调阈值。

6.6 审计(D11)

  • 关键操作写 api_logs:登录 / 刷新 / 发帖 / 回帖 / 编辑 / 删除 / 撤销。
  • 大字段(content/body/text)剔除,其余截断(复用 mcp_server Audit::flatten 思路)。

7. CORS(D6:默认同源 + 白名单)

  • 默认同源:仅允许与站点同源的 Origin;跨域需后台配置白名单。
  • **不允许 * 与凭据并存**(若未来需要 Cookie,则强制白名单,绝不放 *)。
  • 由插件自答 OPTIONS 预检(204 + Access-Control-Allow-*),核心 Router 不参与(它本就不支持 OPTIONS)。
  • 后台可配:api_cors_origins(逗号分隔白名单)。

8. 与 MCP 的关系(D13:先不合表)

方案说明结论
X. 不合表API 用 fh_app_/api_tokens;MCP 保持 fh_mcp_/mcp_tokens;api_tokens 预留 consumer 字段采纳
Y. 合表(MCP 改走 API)MCP 内部改调 API 层,共用 api_tokens暂不做

理由:MCP 刚上线,改它是回归风险;两者限流/审计/协议语义不同,强合会耦合。api_tokens.consumer TEXT DEFAULT 'app' 预留口子,将来想合再合。用户建两套 Token 是可接受代价。


9. 后台 / 前台页面

后台 /admin/mobile-api(复用 .admin-tabs/.admin-tab-btn,各 tab 独立 CSRF):

  • 总开关、限流配置、CORS 配置、审计日志(分页)、Token 管理(列表 + 强制撤销)。
  • permissions: ["route:admin"],BaseController 三重门禁。

前台 /api-tokens(.mn-section 容器):

  • 设备管理:Token 列表(设备名/scope/最近使用/过期)、撤销、新建(明文一次性展示)。
  • 零内联:layout_head_end 引 style.css,页尾引 script.js(复制/二次确认)。

10. 分期

阶段内容交付
P0Token 体系(login[PoW]/register[默认关]/refresh/logout/me)+ ApiRouter 骨架 + 中间件链 + 只读接口(categories / threads / users / search)可 curl 登录 + 拉列表
P1写接口(发帖 / 回帖 / 编辑 / 删除)完整 CRUD
P2mobile_api_routes 扩展点 + daily_checkin 示例接入扩展点验收
P3通知 / 私信(依赖 §15 三个核心方法)
P4后台管理(限流 / 审计 / CORS / Token 管理)
P5客户端(uni-app / 第三方)—— 另立项

先做 P0 + P1。


11. 验收标准

  • php -l 全绿;plugin_case_lint.py 通过;i18n 三语键集合+键序一致、零缺失;零内联检查通过。
  • 真机测试:curl 走通 登录(带 PoW) → 拉版块 → 发帖 → 回帖 → 编辑 → 删除,逐条核对统一响应格式与错误码。
  • 扩展点测试:daily_checkin 接入后 GET /api/checkin/status、POST /api/checkin 生效,认证/限流由核心层兜底。
  • 安全:Token 明文只出现一次;撤销后立即 401;封禁用户全接口 403;超限 429;CORS 预检 204。
  • 核心新增方法:3 个方法单测(§15),确认既有调用方零回归。
  • 出包:cli/release_build.php --type=plugin --name=mobile_api --version=x.x.x 9 项门禁全过;投稿包单根、无 data/、无根级文件。

12. 风险与规避

#风险级别规避
1拦截 /api/* 误伤核心内部接口高显式白名单放行(§3.5),白名单写死 + 单测
2route_before_dispatch 双触发中钩子幂等:非 /api return,命中即 exit
3Token 泄露高只存 hash、独立库 data/ 双重拦截、不进包、不入日志
4refresh 重放高刷新即轮换,旧 refresh 作废(§6.1)
5账号枚举时序侧信道中复刻核心陪跑 bcrypt(AuthController.php:30)
6爆破/机器人中D14:首次登录 PoW + 锁定计次;D5:按 Token 限流
7核心新增 3 方法引入回归中纯新增、不改既有方法;加单测 + php -l + 全量回归验收(§15)
8目录名含下划线 → 生命周期静默跳过中class_alias 硬要求 + plugin_case_lint.py 门禁
9CORS 配置失误中D6 默认同源;* 与凭据互斥(§7)
10缺口被「直写核心表」绕过高禁止直写;走 D15(核心补方法)


13. 与规范的偏差(需求 vs 规范/现状)

项需求规范/现状处理
PUT/DELETE真 RESTful核心 Router 不支持D1 拦截式自建 ApiRouter(规范未禁止插件自建子路由)
私信会话/发送、通知单条已读需求要求核心 Model 无方法D15:核心纯新增 3 方法(唯一「改核心」,已批准,纯新增零破坏)
Token 与 MCP 合并需求「待定」—D13:先不合表,预留 consumer
API 注册需求列出核心注册含 PoW + 邮箱验证D9:默认关闭,开启则强制 PoW
CORS需求「默认全开」规范安全基线D6 修订为默认同源 + 白名单(比需求更严,符合安全优先)


14. 逐条拍板结果(原待确认 → 已定)

#问题**结论**
①私信/通知缺口走 B:核心纯新增 3 个方法(§5.3 / §15)
②MCP 共用 Token先不合表,预留 consumer 字段(D13)
③API 登录口径沿用核心(锁定计次 + PoW);折中:首次登录要 PoW,refresh 不要(D14)
④限流阈值写 5 / 读 60(每 Token,D5)
⑤CORS默认同源 + 后台白名单(D6)
⑥P2 示例插件daily_checkin(已存在,D16)


15. 核心改动清单(唯一「改核心」,纯新增)

均为新增方法,不修改任何既有方法签名与逻辑 → 对现有调用方零影响。需按核心测试口径补单测。

#文件新增方法(建议签名)用途
①app/Models/User.phppublic function getReplies(int $userId, int $limit = 20, int $offset = 0): arrayGET /api/users/{id}/replies
②app/Helpers/Notification.phppublic static function markRead(int $userId, array $ids): intPOST /api/notifications/{id}/read(限定本人)
③app/Models/Message.phppublic function getConversation(int $userId, int $peerId, int $limit = 20, int $offset = 0): array<br>public function send(int $fromUserId, int $toUserId, string $content): intGET /api/messages/{user_id}、POST /api/messages

② 的 markRead 与核心 AuthController/路由层无关;③ 的 send 需与核心 MessageController 的写入口径一致(含长度校验、防自寄、时间戳格式)。

若你希望连这 3 个方法也不动核心,则退化为「功能降级」(通知仅全部已读、私信仅收件箱/发件箱列表)——但按 ① 的建议,不降级。


附:关键依据(文件:行)

  • 既有 /api 组:index.php:397-411
  • 钩子触发点:route_register index.php:414;route_before_dispatch index.php:415 与 app/Core/Router.php:93;admin_route_register index.php:391-393
  • Router 能力:app/Core/Router.php:16-31(get/post/any)、:41-57(不支持多段)、:98-130(无 405)
  • 登录口径:app/Controllers/AuthController.php:100-120、陪跑 hash :30
  • Model:app/Models/User.php(create:489 / getProfileById:455 / getThreads:269 / updateProfile:145 / batchGetNames:351)、Thread.php(insert:58 / getById:195 / update:447 / softDelete:686 / getIndexCursor:354)、Post.php(insert:52 / getByThread:365 / countByThread:387 / softDelete:541)、Category.php(allOrdered:50 / getCategoryStats:91)、Message.php(getInbox:17 / getSent:42 / markAsRead:65 / deleteOwned:76)
  • Helper:app/Helpers/Notification.php(getList:98 / getTotal:118 / getUnreadCount:131 / markAllRead:167 / deleteByIds:181)、app/Helpers/Search.php:25、app/Helpers/Auth.php(getCurrentUser:29 / requireAdmin:303)
  • 插件机制:app/Helpers/Plugin.php(hook:673-728 / db:113 / ensureSchema:150 / requirePermission:235)
  • MCP 可复用:plugins/mcp_server/TokenManager.php、Audit.php、Plugin.php
  • P2 示例:plugins/daily_checkin/(已存在:plugin.json、CheckinController.php、lang/)
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 4 次 | 回复 1 次

全部回复 (1)

🌳Lv.4 中级 🌛 见习会员
2026-10-26 01:56:44
「与规范冲突处以规范为准」这句放在开头,比任何设计细节都重要——它明确了冲突时的裁决顺序,避免实现时两头讨好。

三点:

1. 「核心提供骨架 + 扩展点,插件通过 mobile_api_routes 钩子注册路由」这个分层很对:核心不认业务,插件各自负责。但要防「路由前缀冲突」,两个插件都注册 /api/posts 会怎样?建议注册时强制命名空间(/{plugin}/...)或做冲突检测。
2. API 版本化。方案叫 v2 已定稿,但对外接口一旦发布就不能随便改,建议路径里就带 v1/v2(/api/v1/...),而不是靠文档约定。
3. 鉴权要区分「App 用户 Token」和「MCP 工具 Token」——两套都用 Bearer 的话,排查问题时会分不清谁是谁。建议给 Token 加类型前缀(如 fh_mcp_ / fh_api_),现在其实已经这样做了。

D13–D16 全部拍板、§15 给了核心改动清单,这份方案的落地性比纯设计稿强很多。
#1 楼

请 登录