Files
crmeb-mp/.trae/documents/mp_to_h5_token_design.md

78 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 方案:小程序跳转到 H5 页面携带 token
### Summary
- 目标:在微信小程序内通过 web-view 打开指定 H5 地址时,让 H5 自动获得与小程序一致的登录态(token),并在 H5 内完成 Vuex/缓存写入与用户信息同步。
- 约束(来自现状与确认):打开方式为 web-view;当前不方便改后端接口;倾向直接携带 Bearer token。
- 推荐策略:优先把 token 放到 URL 的 fragment(`#` 之后)以避免出现在 HTTP 请求与 Referer/服务端日志里;H5 启动时从 fragment 解析 token,写入 `LOGIN_STATUS` 缓存并拉取用户信息,随后清理 URL 中的 token。
### Current State Analysis
- 小程序打开 H5 的通用入口:`utils/util.js: JumpPath`,当 url 包含 `http` 时在 MP/APP-PLUS 走 web-view 页面:
- [util.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/utils/util.js#L162-L201)
- web-view 页面接收 `option.url` 并 `decodeURIComponent` 后赋给 `<web-view :src="url">`:
- [pages/annex/web_view/index.vue](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/pages/annex/web_view/index.vue#L1-L28)
- H5 端已有“从 URL query 自动登录”的机制,但目前只处理 `remote_token`:
- H5 `App.vue`:`if (!isLogin && option.query.hasOwnProperty('remote_token')) this.remoteRegister(...)`
- [App.vue](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/App.vue#L88-L92)
- `remoteRegister` 调用 `GET /api/remote_register`(noAuth)换取 token 或跳转 URL:
- [api/public.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/api/public.js#L231-L239)
- token 使用方式:请求层统一在 `utils/request.js` 注入 header `Authori-zation: Bearer <token>`:
- [request.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/utils/request.js#L53-L63)
- 登录态落地:`store/modules/app.js: LOGIN` 将 token 写入 `Cache.set(LOGIN_STATUS, token, time)`;并可配合 `getUserInfo()` 写入 uid/userInfo:
- [app.js](file:///c:/Users/Jone/Documents/HBuilderProjects/crmeb/store/modules/app.js#L89-L117)
### Proposed Changes
#### 1) 小程序侧:构造带 token 的 H5 URL(尽量放在 fragment)
- 修改文件:`utils/util.js`
- 修改点:在 `JumpPath` 的 `url.indexOf("http") != -1` 分支(MP/APP-PLUS)中,在跳转到 `/pages/annex/web_view/index` 之前对目标 url 做“携带 token”的包装。
- 行为设计:
- 若用户未登录(`store.getters.isLogin` 为 false 或 `store.state.app.token` 为空),直接走现有登录流程(可复用 `libs/login.js: toLogin`),不打开 H5。
- 若已登录:
- 优先把 token 附加在 hash 内:`https://h5.xxx.com/#/some/path?token=<encoded>`
- 若目标 url 不含 `#`,则退化为 query:`https://h5.xxx.com/some/path?token=<encoded>`(存在服务端日志/Referer 风险,属于“不得不”的兼容路径)。
- 在把 url 作为参数传给 web_view 页面时,对整段 url 做 `encodeURIComponent`,避免 token 中的特殊字符以及 `&` 被小程序路由二次解析。
- 预计新增/调整函数(放在 util.js 内部):
- `appendTokenToUrl(rawUrl, token): string`(只做字符串处理,不引入新依赖)
- `encodeForWebView(url): string`(统一 `encodeURIComponent`)
- 影响面:
- 任何通过 `this.$util.JumpPath('https://...')` 打开的 H5 链接,都将自动带上 token(仅在 MP/APP-PLUS 生效)。
#### 2) web-view 容器页:强制 decode 前提下保证传参完整
- 修改文件:`pages/annex/web_view/index.vue`
- 修改点:保持现有 `decodeURIComponent(option.url)`,但要求所有调用方都传入 encode 后的 url。
- 如果项目内存在“未 encode 直接拼接 url”的调用点(当前 `utils/util.js` 即如此),统一改为 encode 后再传。
#### 3) H5 侧:解析 token → 写入登录态 → 拉取用户信息 → 清理 URL
- 修改文件:`App.vue`
- 修改点:H5 `onLaunch` 内(`// #ifdef H5` 分支)补充 token 自动登录逻辑,优先级建议:
1. 若当前已登录(`this.$store.getters.isLogin` 为 true),不处理外部 token(避免覆盖用户主动登录态)。
2. 仍保留现有 `remote_token` 分支逻辑(兼容已有能力)。
3. 新增 `token` 分支逻辑:
- 从 `option.query.token` 读取(query 方案)
- 若取不到,再从 `location.hash` 里解析 `token`(fragment 方案)
- 拿到 token 后:
- `this.$store.commit("LOGIN", { token, time: 0 })`(time=0 表示缓存不主动过期,后续由服务端 410xxx 兜底踢下线)
- 调用 `getUserInfo()`,成功后 `SETUID/UPDATE_USERINFO`
- 最后用 `history.replaceState` 把 URL 中的 token 移除(避免用户复制链接时携带 token)
- 需要补一个轻量解析方法(不引入库):
- `parseTokenFromHash(hash): string | ''`:兼容 `#/path?token=...` 和 `#/?token=...` 两种。
### Assumptions & Decisions
- H5 地址可能是 uni-app 默认 hash 路由(`/#/`);因此默认把 token 放在 fragment 内最安全。
- 不改后端时,“直接 token”不可避免有泄露风险;通过 fragment + 清理 URL 将风险降到相对可接受:
- fragment 不会出现在 HTTP 请求行与一般服务端 access log
- Referer 通常不包含 fragment
- token 仍可能在以下场景泄露:用户截屏、设备调试、前端错误上报采集 `location.href` 等;因此上线前需确认前端埋点/监控不会上报完整 URL。
### Verification
- 本地/测试环境:
- 小程序端登录后,通过任意入口打开一个 H5 链接(走 `JumpPath`):
- web-view 打开的 H5 页面无需再次登录
- H5 内 `store.getters.isLogin` 为 true,且请求头带 `Authori-zation: Bearer ...`
- `getUserInfo()` 可正常返回并写入 uid/userInfo
- 打开后检查 H5 当前地址:token 参数已被移除(history.replaceState 生效)
- 回归:
- 未登录时打开 H5 链接:应先跳登录,不应打开带 token 的 H5
- H5 原有 `remote_token` 登录不受影响