本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/flinthub-client客户端改造开发方案_2026-10-11.md。
flinthub-client 客户端改造开发方案(2026-10-11)
依据:
flinthub-client/(站长提供的 bbs1 客户端源码,uni-app Vue3+Vite)+ 已发布的mobile_api插件 (v1.0.0,min_core 1.1.21)。本方案只做设计与评估,不写代码(用户明确「先不动手做」)。
阅读顺序:§0 结论速览 → §2 源码核查 → §3 差异矩阵 → §4 方案选型 → §5 插件侧改造 → §6 客户端侧改造 → §10 待拍板问题。
0. 结论速览(先看这段)
一句话结论:传输层信封不兼容是主要工作量,其余是「按我们的语义补齐缺口」。改造可行,且客户端传输层只需重写约 90 行(utils/api.js)。
三条核查结论直接决定改造路线:
- 客户端是 op 风格协议,与插件 REST 完全不兼容(详见 §3.1)。客户端所有请求都走
utils/api.js的call(op)→base + '&op=' + op,响应判定payload.ok。插件的 REST 信封是{code,message,data},没有ok字段,客户端若不改会把一切响应都当成server_error。好消息:全部网络访问都收敛在utils/api.js一个文件里(约 90 行),改造面极小。
- PoW 与无状态 Bearer 冲突,是登录集成的硬阻塞(详见 §3.2 / §8.1)。核心 PoW 的 challenge 存在 PHP Session(
$_SESSION['pow_challenge']),而插件签发的 Bearer Token 是无状态的、不碰 Session。客户端要过 PoW 就必须带PHPSESSID——App 端手工维护 Cookie 非常脆弱,H5 端还会被 CORS(插件不下发Allow-Credentials)拒绝。推荐方案:在插件内实现「无状态 PoW」(自签 challenge = 时间戳 + HMAC,客户端算 nonce,插件本地校验),不碰核心 PoW、不改核心。
- 部署层路由阻塞:当前 nginx 配置下
/api/...根本到不了 PHP(详见 §3.3)。nginx vhost的location /没有try_files ... /index.php回退,/api/threads这类真实路径在 nginx 层即 404;而/index.php/api/threads又因config.php写死define('BASE_PATH',''),插件拿到的requestPath()是/index.php/api/threads,前缀匹配不上/api/。这是环境问题、不是代码问题,上线前必须给 vhost 加一行try_files(或明确改用 Apache)。
工作量分布(粗估,非时间承诺):传输层重写(小)+ 字段/语义补齐(中,插件侧新增若干端点与字段)+ PoW 无状态化(中,插件侧新增 1 个自签校验器)+ 品牌替换与配置(小)+ 真机验收(中)。
1. 目标与非目标
目标
- 以
flinthub-client为基底,产出「我们自己的」App / H5 客户端,不重写一套。 - 客户端适配
mobile_api插件的 REST 契约(信封、端点、字段、分页、Token)。 - 补齐客户端需要、而插件当前缺失的功能(站点信息 / 楼层 / 通知全读 / 私信会话聚合 / 改密与资料完善等)。
- 解决 PoW 集成与部署路由两个硬阻塞。
- 品牌、站点名、图标、启动图替换为本站。
非目标
- 不做「服务端 op 兼容垫片」(否决,理由见 §4.2)。
- 不改核心
Router;不改核心既有业务逻辑(缺口优先在插件侧新增端点实现,必要时才在核心纯新增方法)。 - 不做 iOS 包、不做应用商店上架、不做服务端部署(沿用原客户端
部署教程.md的边界)。 - 本期不做多语言(客户端目前全硬编码中文,无 i18n;是否引入见 §10 待拍板)。
2. 源码核查结论
2.1 工程形态与技术栈
| 项 | 值 |
|---|---|
| 工程名 | bbs1-client v0.1.0,描述「6735 社区 客户端(uni-app,H5 + Android)」 |
| 技术栈 | uni-app + Vue 3.4.21 + Vite 5.2.8;@dcloudio/* 全家桶 3.0.0-alpha-5030120260930001 |
| 产物 | H5 网页版(命令行可出)+ Android APK(必须 HBuilderX 云打包,命令行出不了 .apk) |
| 页面数 | 12 页:home / forums / forum / topic / search / user / mine / notice / dm-thread / login / post / settings |
| 导航 | 全局 navigationStyle:"custom",自绘导航栏(app-nav.vue、utils/system.js 的 NAV_HEIGHT=44 + 状态栏高度) |
| 配置入口 | src/utils/site.config.js(SITE_API_BASE/SITE_APP_KEY)+ .env 的 VITE_API_BASE/VITE_APP_KEY(env 优先) |
| 无原生能力 | 无推送 / 分享 / 相机;权限仅 INTERNET;上线前不要顺手加权限(§9) |
关键文件定位(改造核心)
src/utils/api.js—— 网络层唯一入口(call/data/decode/headers),改造重点。src/utils/config.js——apiBase()/appKey()/siteOrigin()/connectionSummary()。src/utils/store.js—— 会话存储bbs_session(token/expires_at(秒级)/user)。src/utils/site.js—— 站点信息与限值表(FALLBACK)。src/utils/format.js——absolutizeHtml()、topicUrl()(指向站内/index.php?a=topic&id=)。src/utils/menu.js/system.js/swipe-back.js—— 编译期菜单常量、导航布局、自绘左滑返回。src/components/*——app-avatar(纯本地首字母头像,不请求网络图)、topic-item、app-tabbar、app-nav、app-sheet、state-block、app-icon。
2.2 客户端原协议(op 风格)
- URL =
apiBase + '&op=' + op;apiBase形如https://域名/index.php?a=api(LightSNS 入口写法)。 - GET:参数拼 query;POST:
application/x-www-form-urlencoded,body 含op。 - 请求头:
X-Requested-With;登录后Authorization: Bearer <token>;X-App-Key: <应用密钥>。 - 响应信封:
{ok:true,data}/{ok:false,error:{code,msg}}。 - 客户端错误码字符串:
unauthorized/token_expired/network/bad_response/server_error(多处按e.code==='network'?'offline':'error'决定空态样式)。 - 会话:
uni.setStorageSync('bbs_session',{token,expires_at,user});expires_at按秒级时间戳判过期(Number(expires_at)*1000 > Date.now())。
2.3 客户端逐页「要什么」(端点 + 字段)
以下为核查结论,是 §3.4 差异矩阵的原始依据。所有字段名均为客户端实际读取的键名。
| 页面 | 调用的 op | 读取的字段 |
|---|---|---|
home | topics{fid,sort,p} / site / forums | rows/page/has_more/page_size;行 pinned/title/title_color/title_large/username/user_id/time_text/reply_count/forum_name/matched_excerpt;site_name/user_count/topic_count/reply_count;排序枚举 comment(按回复)/post(按发帖) |
topic | topic{id,p}、reply_create{topic_id,body}、reply_update{id,body}、topic_delete{id}、reply_delete{id} | payload 即 topic(author{id,username}/forum_name/forum_id/time_text/pinned/can_edit/can_reply/title_color/title_large/view_count/reply_count/body(裸文)/body_html);payload.replies={rows:[{id,author,floor,time_text,edited,body,body_html}],page,total,has_more,page_size};reply_create 回 {topic:<完整detail>, reply_id} |
login | login{username,password}(POST) / register{...}(POST) | 无 PoW/验证码;site.info.allow_register!==false;成功 setSession(result.data) |
mine | me、topics{uid}、site | user.group_name/points/unread/allow_manage/id/username;topics.total;site_name/topic_count/forum_count;退出仅本地 clearSession()(未调 logout) |
settings | me、profile_update{...} | bio/avatar_seed/avatar_style/created_at/last_post_at/is_muted/group_name/points;保存后若回 token 则换会话(改密换 token);头像 17 样式 dylan…toon-head(样式/种子仅作用于网页端图案头像) |
post | forums、topic{id}(编辑)、topic_create{forum_id,title,body}、topic_update{...,reply_order:'0'} | forums.rows[].can_post;title/body/forum_id/forum_name/can_edit;成功后 topic_create.data.id;提示「图片与附件上传暂未接入」 |
search | search{q,field,sort,p}、site | rows/page/has_more;field∈`title |
user | topics{uid,p}、me | rows/page/has_more/total;points/group_name/bio/created_at/created_at_text/username |
notice | dm_threads、notifications{box:'notice',p}、notifications_read{id}或{all:'1',box}、dm_user{username} | 私信会话 rows:[{partner{id,username}, last{content,mine,time_text}, unread}], unread_total, unread;通知 rows:[{id,kind,read,content,time_text,topic_id,sender_id}], page, has_more, unread;kind∈`mention |
dm-thread | dm_thread{partner_id}、...{partner_id,before_created_at,before_id}、dm_read{partner_id}、dm_send{partner_id,content} | rows:[{id,mine,content,time_text,created_at}], has_older, partner{username};游标分页(非 page);发送回 data.message;maxlength:5000 |
forums / forum | forums / forums+topics{fid,sort,p} | rows[].id/name(版块列表);详情复用列表逻辑 |
2.4 服务端(插件)现有契约要点
- 前缀
/api;信封{code,message,data}(0成功 /4xxxx客户端错 /5xxxx服务端错)。 - Token:Bearer,前缀
fh_app_;access 2h / refresh 30d(轮换);Token 级限流「写 5/分、读 60/分」。 - 中间件链:总开关 → 认证+封禁 → scope(
read/write)→ 限流 → 业务 → 审计。 - 已实现端点:
/api/auth/{login,register,refresh,logout,me}、/api/categories(/{id})、/api/threads(GET/POST、/{id}的 GET/PUT/DELETE、/{id}/repliesGET/POST)、/api/users/{id}(、/{id}/threads、/{id}/replies)、PUT /api/users/me、/api/search、/api/notifications(、/{id}/read)、/api/messages(、/{user_id}、POST)。 - 前端设备管理页:
/api-tokens*(hookroute_register)。
3. 差异矩阵
3.1 传输层(不兼容,必须重写客户端网络层)
| 维度 | 客户端(op) | 插件(REST) | 处置 |
|---|---|---|---|
| URL | base + '&op=' + op,base=…?a=api | base + '/api/...' 真实路径 | 客户端 config.apiBase() 改存站点根 URL;call 改拼 REST 路径 |
| 方法 | 仅 GET/POST | GET/POST/PUT/DELETE | 客户端调用点传入 REST 方法 |
| 请求体 | form-urlencoded,含 op | JSON 或表单 | 统一 JSON |
| 信封 | {ok, data} / {ok:false,error:{code,msg}} | {code,message,data} | decode 改判 code===0 |
| 鉴权 | Authorization: Bearer + X-App-Key | Authorization: Bearer fh_app_…(无 App-Key 概念) | 客户端去掉 App-Key |
| 刷新 | 无 refresh 概念 | access 2h + refresh 30d 轮换 | 客户端新增 401 自动 refresh |
结论:唯一改动点是 src/utils/api.js(重写)+ config.js(去 appKey)+ store.js(增 refresh_token 字段与过期语义)。调用点几乎不动(仍走 call/data),只需改 op 字符串为 REST 路径 + 方法。
3.2 认证/PoW(硬阻塞,需方案拍板)
| 维度 | 客户端 | 插件/核心 | 冲突 |
|---|---|---|---|
| 登录 | 直接提交 username/password | 要求 pow_nonce(PoW 通过才放行) | 客户端无 PoW |
| PoW challenge | 无 | 存 PHP Session(5 分钟、一次性) | Bearer 无状态,客户端不带 PHPSESSID 必 400 err_pow_or_captcha |
| CORS 凭据 | — | applyCors 不下发 Allow-Credentials | H5 跨域带 Cookie 会被浏览器拒 |
→ 见 §4.3 三个候选方案,推荐方案 ②(插件内无状态 PoW)。
3.3 部署/路由(环境阻塞)
| 事实 | 影响 |
|---|---|
vhost location / 无 try_files ... /index.php | /api/threads 真实路径在 nginx 层即 404,到不了 PHP |
config.php 写死 define('BASE_PATH','') | /index.php/api/threads 的 requestPath() 含 /index.php 前缀,不匹配 /api/,插件不拦截 |
.htaccess 有 RewriteRule ^(.*)$ index.php/$1 | Apache 下可用;当前 nginx 不读 .htaccess |
→ 处置见 §7.3。
3.4 端点与字段差异(客户端要 → 插件给)
A. 完全缺失的端点
| 客户端 op | 插件现状 | 处置 |
|---|---|---|
site 站点信息 | 无 | 插件新增 GET /api/site |
reply_update | 无 | 新增 PUT /api/replies/{id} |
reply_delete | 无 | 新增 DELETE /api/replies/{id} |
notifications_read 全读 | 仅单条已读 | 新增 POST /api/notifications/read-all |
dm_threads 会话聚合列表 | 无(只有消息列表) | 新增 GET /api/messages/conversations |
dm_user 用户名解析 | 无 | 新增 GET /api/users/lookup?username= |
dm_read 会话已读 | 无 | 新增 POST /api/messages/{user_id}/read |
| 无对应(客户端退出未调) | POST /api/auth/logout 已存在 | 客户端退出补调 logout |
B. 已存在但语义/字段不匹配的端点
| 端点 | 差异 | 处置 |
|---|---|---|
GET /api/categories | 返回 {list:[...]},客户端读 data.rows | 兼容:外层加 rows(保留 list) |
GET /api/categories | 行缺 can_post | 行内补 can_post |
GET /api/threads | 行缺 time_text/pinned/title_color/title_large/matched_excerpt/forum_name | 补齐字段(is_pinned→pinned 等) |
GET /api/threads/{id} | 详情缺 author{} 对象、body(裸文)、内联 replies | 详情补 author{}、body、内联 replies(或客户端二次拉取,见 §4.4) |
GET /api/threads/{id}/replies | 行缺 floor/author{}/body/body_html/edited/time_text | 补齐楼层号等 |
GET /api/users/{id} | 缺 group_name/bio/created_at_text | 补齐 |
GET /api/auth/me | 缺 group_name/unread/allow_manage/last_post_at/is_muted/bio/avatar_style/avatar_seed | 补齐 |
PUT /api/users/me | 只支持 email/signature | 扩为支持 bio/avatar_style/avatar_seed + 改密 password/password2(改密需换 token) |
GET /api/search | 参数 type/time_range,客户端要 `field(title\ | body\ |
GET /api/notifications | 无 box 分箱;行字段 type/title/summary/...;无 kind/topic_id/sender_id/read | 行补 kind/topic_id/sender_id/read(布尔);支持 box |
GET /api/messages | 是消息流水,非会话聚合 | 见上「新增会话聚合」 |
GET /api/messages/{user_id} | 用路径 user_id,回 peer{}、无 has_older、无游标 | 客户端适配:partner_id→路径 user_id;peer→partner;补游标 before_created_at/before_id 与 has_older |
POST /api/messages | 回 {id,receiver_id} | 补回 {message:{...}} |
topic_create 返回 | POST /api/threads 回 {id} | 客户端读 data.id ✓(已兼容) |
3.5 功能缺口清单(汇总,按优先级)
P0(不做则核心流程走不通)
- 传输层重写(§3.1)
- PoW 集成(§3.2)
- 部署路由修复(§3.3)
site站点信息端点(首页/搜索/登录均依赖)categories外层rows键(首页/版块/发帖均依赖)
P1(主要页面功能完整)
- 楼中楼:
reply_update/reply_delete/floor - 通知:
read-all+ 行字段补齐 - 私信:会话聚合列表 +
dm_read+dm_user+ 游标分页 +partner/message命名 me字段补齐 +profile_update支持 bio/头像样式/改密- 帖子行/详情字段补齐(
time_text/pinned/author{}/body/matched_excerpt/forum_name)
P2(体验完整)
search的field/sort语义topics排序枚举comment/post对齐- 客户端退出补调
logout connectionSummary自检项调整(去 appKey、改显示站点根)
4. 方案选型与推荐
4.1 总路线(推荐)
改客户端适配插件 + 插件按 REST 语义补齐缺口。
- 客户端:重写
utils/api.js(REST + 新信封 + refresh),调整config.js/store.js/site.js/format.js,按 §3.4 逐页微调字段与端点调用。 - 插件:现状已是 REST,只需新增若干端点 + 补齐若干字段,发 v1.1.0。
- 核心:优先不改;若必须,只做「纯新增方法」(沿用 mobile_api 方案 D15 的口径)。
4.2 为什么不选「服务端 op 兼容垫片」
否决理由(三条,均为长期成本):
- 双协议/双信封永久负债:
a=api&op=与/api/...两套入口、{ok}与{code}两套信封要长期并存,任何字段改动都要维护两份。 - 与插件既有设计冲突:垫片会绕过
scope/限流/审计中间件链,或需要在链前做一次协议翻译,破坏「认证/限流/审计统一在 API 层」的核心架构(mobile_api 方案 §0)。 - 不划算:客户端传输层全部收敛在
utils/api.js约 90 行,重写成本极低;而垫片要在服务端模拟出客户端期望的全部字段(含time_text这种展示字段),成本远高于改客户端。
4.3 PoW 方案(需拍板)
| 方案 | 做法 | 评价 |
|---|---|---|
| ① 客户端维护 Cookie | App 端手工读写 Set-Cookie/PHPSESSID,H5 端启用 withCredentials | ❌ 脆弱;且插件 CORS 未下发 Allow-Credentials,H5 直接被拒 |
| ② 插件内无状态 PoW(推荐) | 插件新增 GET /api/auth/pow 签发 challenge=base64(时间戳.HMAC(时间戳))+difficulty;客户端本地算 nonce;插件本地校验 HMAC+时效+难度,不碰核心 Session | ✅ 不依赖 Session、不碰核心、天然无状态;与 Bearer 架构一致 |
| ③ 首登走图片验证码 | — | ❌ 验证码同样依赖 Session,未解决根本问题 |
推荐 ②。落地要点:challenge 有效期 ≤ 5 分钟、一次性(用 api_rate 或独立小表记录已用 challenge 防重放)、difficulty 复用核心 Settings.pow_difficulty;HMAC 密钥取插件配置项(后台生成/存储)。客户端复刻 assets/js/pow.js 的纯 JS SHA-256 逻辑即可。
4.4 详情内联 replies 的处置(需拍板)
客户端 topic 页期望「详情接口一次返回 replies」。两种做法:
- A(推荐,改客户端):详情只回正文,客户端用已有的
GET /api/threads/{id}/replies二次拉取并填进payload.replies。改动小、职责清晰、利于分页。 - B(改插件):详情内联首页若干条 replies。少一次请求,但插件详情接口变重、与分页耦合。
4.5 私信会话聚合放哪(需拍板)
- A(推荐,插件新增端点):插件新增
GET /api/messages/conversations,服务端一次算好partner/last/unread。客户端只渲染,性能好、离线态简单。 - B(客户端拼装):客户端拉消息流水自行聚合。请求多、逻辑重、易错,不推荐。
5. 插件侧改造清单(→ v1.1.0)
原则:全部走现有
ApiRouter+ 中间件链,复用 Model 层权限/积分/审核,不新开数据库、不直连核心业务表。
5.1 新增端点
| 方法 | 路径 | scope | 限流 | 入参 | 出参要点 |
|---|---|---|---|---|---|
| GET | /api/site | read | read | — | site_name/user_count/topic_count/reply_count/forum_count/search_min_chars/allow_register/info{...} |
| GET | /api/auth/pow | 免认证 | 独立 | — | {challenge,difficulty,mode:'normal'} |
| PUT | /api/replies/{id} | write | write | body | {reply} 或 {id} |
| DELETE | /api/replies/{id} | write | write | — | {ok} |
| POST | /api/notifications/read-all | write | write | box | {unread,unread_total} |
| GET | /api/messages/conversations | read | read | p | {rows:[{partner{id,username},last{content,mine,time_text},unread}],unread_total,unread,has_more} |
| GET | /api/users/lookup | read | read | username | {partner{id,username}} |
| POST | /api/messages/{user_id}/read | write | write | — | {unread} |
5.2 字段/语义增强(现有端点)
GET /api/categories→ 外层新增rows(= 现有list);行补can_post。GET /api/threads行补:pinned(=is_pinned)、time_text、title_color、title_large、matched_excerpt、forum_name;sort支持comment|post。GET /api/threads/{id}补:author{id,username}、body(裸文,供编辑)、can_edit、time_text;replies按 §4.4 结论处理。GET /api/threads/{id}/replies行补:floor、author{id,username}、body(裸文)、body_html、edited、time_text。GET /api/users/{id}补:group_name、bio、created_at_text。GET /api/auth/me补:group_name、unread、allow_manage、last_post_at、is_muted、bio、avatar_style、avatar_seed。PUT /api/users/me扩:bio、avatar_style、avatar_seed;新增改密password/password2(成功换发 token,回{token,user})。GET /api/search参数适配:field(title|body|reply)、sort(post|comment)(内部映射到现有type)。GET /api/notifications:支持box;行补kind、topic_id、sender_id、read(布尔);保留unread。GET /api/messages/{user_id}:补游标before_created_at/before_id与has_older;peer兼容为partner。POST /api/messages:返回补message:{...}。
5.3 兼容性策略
- 所有「补字段」为新增键,不删不改现有键 → 对已存在调用方零破坏。
categories的rows与list并存;peer与partner并存。- 插件版本
1.0.0 → 1.1.0;min_core维持1.1.21(若无新核心方法依赖则不升)。
5.4 门禁(沿用发布流程手册)
php -l 全量、i18n 三语一致、plugin_case_lint、零内联、BOM、发布包结构(无 data/、无根级文件、activated=false)、签名与清单。
6. 客户端侧改造清单
6.1 传输层(核心改动)
src/utils/api.js(重写,约 90 行)base改为站点根 URL;call(path, {method, params, body, auth})拼 REST 路径。- 方法支持 GET/POST/PUT/DELETE。
decode:判payload.code === 0→ 成功回payload.data;否则构造带code/message/status的错误;错误码映射为客户端既有字符串(40101→unauthorized、42901→rate_limited、network)。- 401 处理:先尝试
POST /api/auth/refresh(用 refresh_token)换新 access → 重放原请求一次;失败则clearSession()。 - 保持
call返回统一信封、data抛错(含code/status)的既有契约,调用点零改动。 src/utils/config.js:apiBase()语义改为「站点根 URL」;删除appKey()与X-App-Key;connectionSummary()改显示站点根 + 登录态。src/utils/store.js:会话增加refresh_token;expires_at统一为秒级(对齐插件expires_in);新增setTokens()。
6.2 逐页适配
utils/site.js:data('site')→GET /api/site;limit()读新端点字段(search_min_chars等)。utils/format.js:topicUrl()由siteOrigin()+'/index.php?a=topic&id='改为站内真实链接(我们的路由,如/thread/{id}或/index.php?route=...,按实际前台路由确定)。pages/home、forum:data('topics',{fid,sort,p})→GET /api/threads?category_id=&sort=&page=;行字段已由插件补齐 → 渲染侧基本不动。pages/topic:详情/回复/编辑/删除 → REST;replies处理按 §4.4。pages/post:forums→/api/categories(rows[].can_post);topic_create→POST /api/threads。pages/login:接入 PoW(按 §4.3 方案);register走/api/auth/register;错误码适配。pages/mine:me→/api/auth/me(字段补齐);退出补调POST /api/auth/logout后clearSession()。pages/settings:profile_update→PUT /api/users/me;改密换 token 后更新会话。pages/search:field/sort语义对齐。pages/notice、pages/dm-thread:私信会话聚合、dm_read、dm_user、游标分页、partner/message命名对齐。- **
components/***:app-avatar保持纯本地首字母头像(无需改);topic-item字段来自插件已补齐项,基本不动。
6.3 配置与品牌
src/utils/site.config.js:填本站SITE_API_BASE(站点根 URL)。- 品牌替换 4 处:
package.json描述、index.html<title>、src/manifest.json的name/description、SITE_NAME_FALLBACK(utils/site.config.js)。 - 启动图:
启动图/三张(720x1280、1080x1920、1080x2340)替换为本站。 manifest.json:appid云打包前必须重取;versionName/versionCode按发版递增。
7. 安全
7.1 PoW 无状态化(同 §4.3)
挑战自签 + HMAC 密钥由插件后台管理;有效期 ≤ 5 分钟;防重放;难度复用 pow_difficulty。
7.2 CORS
- 默认同源;后台白名单;dev 环境需加
http://localhost:5173(Vite 默认端口,见vite.config.js)。 - 不使用
*;若未来 H5 需带凭据,才考虑Allow-Credentials(当前 REST+Bearer 无需 Cookie,故不下发)。
7.3 部署路由(必做)
- 给 nginx vhost 的
location /增加:try_files $uri $uri/ /index.php?$query_string; - 或明确改用 Apache(
.htaccess已具备RewriteRule ^(.*)$ index.php/$1 [QSA,L])。 - 注:
config.php写死BASE_PATH=''使「settings 覆盖部署路径」失效(app/init.php有!defined守卫);因此不能靠/index.php/api/...绕过,必须修 nginx。
7.4 Token 与存储
- access/refresh 仅存客户端本地存储(
bbs_session),明文只在生成时出现一次(服务端只存 sha256)。 - 客户端 401 自动 refresh 后必须用新 refresh 覆盖旧的(插件为轮换制,复用旧 refresh 会整族撤销)。
7.5 源码目录泄露风险(必须处理)
flinthub-client/当前位于站点根内(d:\phpstudy_pro\WWW\flinthub-client)。其node_modules/dist/源码可被 HTTP 直接访问(配置泄露、源码泄露)。- 处置建议(三选一):① 移到站点根之外;② 保留在根内但加 nginx
location ^~ /flinthub-client/ { deny all; };③ 至少deny源码与node_modules、仅允许dist(不推荐,易漏)。 - 另:原客户端
部署教程.md提及站点需 HTTPS、APK 不放开明文流量——沿用(usesCleartextTraffic不开启)。
8. 里程碑与验收
分步推进、每步验证(沿用既有「分步修复、每批测试」的工作流)。
- M1 环境打通:修 nginx
try_files;GET /api/site用 curl 返回{code:0,...}。验收:可直达 PHP。 - M2 认证闭环:插件无状态 PoW + 客户端登录/注册/refresh;H5 dev 联调。验收:登录成功、token 过期自动续期。
- M3 读路径:
site/categories/threads/thread/user/search全通;首页/版块/帖子/搜索/用户页可浏览。验收:H5 端五页无空白、无 404。 - M4 写路径:发帖/回帖/编辑/删除/改资料/改密。验收:操作成功且积分/通知/审计正常触发。
- M5 通知与私信:通知全读、私信会话聚合/已读/发送/游标翻页。验收:红点同步、会话列表正确。
- M6 品牌与打包:品牌 4 处 + 启动图 + manifest;H5 构建 + HBuilderX 云打包 APK。验收:真机安装可用。
- M7 发布:插件 v1.1.0 门禁 + 签名发布;核心(若有纯新增)按需打包。
真机验收项:冷启动到首页、登录/注册、发帖/回帖、楼中楼编辑删除、搜索、通知红点、私信收发与翻页、改资料/改密后免重登、弱网/断网空态样式、左滑返回。
9. 风险与缓解
| 风险 | 影响 | 缓解 |
|---|---|---|
| PoW 方案选定后实现偏差 | 登录不能过 | M2 先做,H5 dev 端到端验证后再推后续 |
@dcloudio/uni-app-plus 版本不一致 | 静默按 H5 编译(假成功) | 锁定与其余 @dcloudio/* 同版本号;构建后校验产物含 app-service.js |
dist/ 未清即构建 | 打入死页面 | 构建前必删 dist/ |
| nginx 漏修 | /api/* 全 404 | M1 首项做并 curl 验证 |
| 源码目录在站点根内 | 源码/配置泄露 | §7.5 三选一处置 |
| refresh 轮换未覆盖旧值 | 会话整族被撤销、强制重登 | §7.4 客户端 refresh 逻辑必须覆盖 |
| 字段补齐破坏既有调用方 | 回归 | 仅新增键、不改现有键(§5.3) |
10. 待拍板问题(4 项)
- PoW 方案:是否采用 §4.3 方案 ②「插件内无状态 PoW」?(推荐)
- nginx 路由修复归属:由谁改 vhost 加
try_files?还是改用 Apache?(推荐改 nginx) - 客户端多语言:本期是否引入 i18n?(客户端现全硬编码中文;推荐本期不做,后续按需)
- 详情 replies 与私信聚合的实现位置:详情内联 replies 采用 §4.4 A(改客户端)还是 B(改插件)?私信会话聚合采用 §4.5 A(插件新增端点)还是 B(客户端拼装)?(均推荐 A)
附:改造文件清单(速查)
客户端:src/utils/api.js(重写)、src/utils/config.js、src/utils/store.js、src/utils/site.js、src/utils/format.js、各 pages//index.vue(按 §6.2)、src/utils/site.config.js、package.json、index.html、src/manifest.json、启动图/。
插件(mobile_api → v1.1.0):Plugin.php、MobileApi.php(注册新路由)、Controllers/(新增/增强)、ApiResponse.php(如需新错误码)、lang/、plugin.json(版本)。
部署:nginx vhosts/*.conf(加 try_files)。