ow
This commit is contained in:
1 parent
6a8258ac4b
commit
a7b25d49c4
29 files changed
+1960
-649
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
适用范围:`src/pages.json`、`src/manifest.json`、`src/router/routes.ts`、`src/router/h5-clean-url.ts`、`src/main.ts`。
|
||||
本文记录"为什么这么设计"和"新增页面要动哪几处",代码里的注释只解释局部行为,跨文件的约束以本文为准。
|
||||
后端地址的 `.env` 分层见 [environment.md](./environment.md),跳转失败与未捕获错误的兜底口径见 [error-handling.md](./error-handling.md),权限判定见 [permission.md](./permission.md)。
|
||||
|
||||
---
|
||||
|
||||
@@ -74,6 +75,16 @@ src/pages/dashboard/dashboard.vue pages.json: pages/dashboard/dashboard URL
|
||||
|
||||
`history` 开关的读取路径:编译层特征开关 `__UNI_FEATURE_ROUTER_MODE__`,取自 `manifest.web || manifest.h5` 的 `router.mode`。
|
||||
|
||||
### 3.3 权限评估层(第三轮,见 permission.md)
|
||||
|
||||
| 文件 | 改动 |
|
||||
| --- | --- |
|
||||
| `permission/permission-kernel.ts` | 新增。纯函数判定内核 + 预建索引(`buildPermissionIndex` / `can` / `canAny` / `canOperate` / `actionsOf` / `grantedPermissions`) |
|
||||
| `permission/permission-context.ts` | 新增。接 pinia 档案:`PermissionContext` / `usePermission` / `usePagePermission` / `canVisitPage` |
|
||||
| `permission/permission-kernel.spec.ts` | 新增。59 条断言的可执行校验(esbuild + node 跑,未引测试框架) |
|
||||
| `router/routes.ts` | `PAGE_PERMISSIONS` 值类型放宽为 `PermissionNames`(`string \| string[]`),注释指向 permission.md |
|
||||
| `router/nav-guard.ts` | `evaluate()` 补第三步 `canVisitPage(name)` → `error403` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 短地址机制:追加克隆记录,而不是挂 alias
|
||||
@@ -165,6 +176,7 @@ export const PAGE_PATHS = {
|
||||
2. src/pages.json 追加 { "path": "pages/album/album" }
|
||||
3. src/router/routes.ts 的 PAGE_PATHS 追加 album: "pages/album/album"
|
||||
4. src/pages/album/album.vue 受登录保护的页面调一次 useAuthGuard()
|
||||
5. src/router/routes.ts 的 PAGE_PERMISSIONS 受功能权限控制的页面才登记权限名,见 permission.md §6
|
||||
```
|
||||
|
||||
第 2 步是**绝对必须**:`pages.json` 是编译期唯一注册入口,不写就没有这个页面。
|
||||
@@ -199,7 +211,7 @@ export const PAGE_PATHS = {
|
||||
|
||||
## 8. 当前没有做的事(已知缺口)
|
||||
|
||||
- **权限守卫只做了一半**:登录守卫与 401/404 已落地(§11),但 `PAGE_PERMISSIONS` 还是空表——旧 WebVue 的 `setupPermissionGuard` 依赖 `PermissionContext.canView`(菜单/功能权限清单),那套体系尚未移植,所以页面级 403 目前只由请求层的 HTTP 403 触发。
|
||||
- **权限守卫已落地但登记表是空的**:`PAGE_PERMISSIONS` 尚无条目,路由级 403 因此还没被任何页面真正触发(现在能跳 403 的只有请求层的 HTTP 403)。判定层本身已完成并可用,语义、登记流程与已知缺口见 [permission.md](./permission.md)。
|
||||
- **history 模式的服务端兜底**:`/login`、`/dashboard` 这类深链**直接刷新依赖服务端把请求 rewrite 回 `index.html`**。dev server(Vite)已实测返回 SPA 壳,但生产环境(如 `uat.youjiala.com` 的 Nginx)需要加 `try_files $uri $uri/ /index.html;` 一条,否则刷新会 404。这一条尚未在生产环境验证。
|
||||
|
||||
---
|
||||
@@ -269,6 +281,7 @@ queue: res === false 时返回 { then(){}, catch(){} } 这样的空 thenable
|
||||
- `AUTH_FREE_PAGES` = `login` + 三个错误页。**错误页必须免登录**:401 页就是给"没登录/登录失效"的人看的,拦掉它只会让用户看到一个登录框而不知道刚才为什么被踢。
|
||||
- 登录态只认请求层的 `getJwtInfo()?.AccessToken`(令牌唯一来源,与 `stores/loginProfile` 口径一致)。
|
||||
- 刻意**不**把已登录用户从 `/login` 弹回首页:403 页的"重新登录"按钮需要先能进入登录页。
|
||||
- 第三步(403)走 **fail-open**:本地档案缺失或权限清单为空时放行,只有"档案里有清单但目标页的权限名不在其中"才拦。理由与风险边界见 permission.md §5——真正的安全边界在后端每个接口的 `[PermissionOperate]`。
|
||||
- 跳错误页用 `redirectTo`(保住返回栈,"返回上一页"才有内容),失败再退化成 `reLaunch`。
|
||||
|
||||
### 11.3 错误页与外壳组件
|
||||
@@ -290,12 +303,32 @@ queue: res === false 时返回 { then(){}, catch(){} } 这样的空 thenable
|
||||
### 11.5 403 的两个触发点
|
||||
|
||||
- 请求层:`common/http.interceptor.ts` 的非 2xx 分支里 `statusCode === 403` → `goForbidden()` 跳 403 页并带 `?from=`;已经在 403 页就不再跳,避免"错误页自己发的请求又 403"来回刷。
|
||||
- 导航层:等 `PAGE_PERMISSIONS` 与 `PermissionContext` 移植后接在 `evaluate()` 的第三步。
|
||||
- 导航层:`evaluate()` 的第三步 `if (!canVisitPage(name)) return "error403"`,数据源是 `PAGE_PERMISSIONS` + `PermissionContext`(判定语义、双失败策略、登记流程全部见 [permission.md](./permission.md))。该步排在 401 之后:未登录时档案通常也是空的,先给 401 才谈得上"去登录"。
|
||||
|
||||
`goForbidden()` 在请求层自己实现而没有复用 `nav-guard` 的 `jumpToError`:`nav-guard` 依赖请求层读登录态,反向 import 会形成循环依赖。
|
||||
`goForbidden()` 在请求层自己实现而没有复用 `nav-guard` 的 `jumpToError`:`nav-guard` 依赖请求层读登录态,反向 import 会形成循环依赖。`canVisitPage()` 由 `nav-guard` import `permission/permission-context` 单向调用,不构成环——`permission-context` 只 import `routes.ts` 的常量与 `resolvePageName`,不 import `nav-guard`。
|
||||
|
||||
### 11.6 词条与验证
|
||||
|
||||
- 文案全部走语言包:`locale/lang/{zh-CN,en-US}.json` 的 `ErrorPage` 组 30 个键;组件内固定的"可能的原因""出错的地址:"也在里面(`reasonsTitle` / `fromLabel`)。
|
||||
- 实测(H5 dev + 自动化浏览器):未登录深链 `/dashboard` → 401 页且 `from` 显示正确;401 的"去登录"→ `/login?redirect=/dashboard`;`001/123` 登录后回跳 `/dashboard`;`/no/such/page` → 404 页;404 的"返回首页"放行;清态后直访 `/error/page403` 不被再拦、无死循环;`window.__uniRoutes` 共 10 条(5 真 + 5 短地址克隆),全程零 console error/warn。
|
||||
- 三端构建 `EXIT=0`;`location.pathname` 仅出现在 H5 产物(`setupUnknownPathFallback` 的 `#ifdef H5` 生效),`page401` 三端产物均有。
|
||||
|
||||
### 11.7 `resolvePageName("")` 曾把登录成功后置地址清成空串(已修)
|
||||
|
||||
第三轮接权限层做端到端时暴露的既有缺陷,与权限无关,但正是"空地址到底算不算一个页面"这条边界没定死:
|
||||
|
||||
```text
|
||||
无 ?redirect= 的普通登录
|
||||
-> login.vue onLoad 里 decodeQueryPath(undefined) -> ""
|
||||
-> resolvePageName("") -> normalizePath("") -> "/" -> 命中入口页分支 -> "login"(真值)
|
||||
-> if (name) 成立 -> redirectUrl.value = ""(把默认的 /dashboard 覆盖成空串)
|
||||
-> uni.reLaunch({ url: "" }) -> fail: Missing required args "url"
|
||||
-> 用户点了登录、接口 200、令牌已写入,却仍然卡在登录页
|
||||
```
|
||||
|
||||
`"/"` 作为 H5 入口页的路由 path 必须能被 `resolvePageName` 认出来(冷启动落在入口页时靠它反查页面名),所以不能动那一条;问题在于 `normalizePath` 把空串也归一成 `"/"`,于是"我压根没打算跳"被误读成"我要跳入口页"。修复分两层:
|
||||
|
||||
- `routes.ts` `resolvePageName`:开头对 `!String(url || "").trim()` 直接返回 `null`(源头掐断,空地址永远不算页面)。
|
||||
- `login.vue` `onLoad`:判定条件从 `if (name)` 收紧为 `if (name && !isAuthFree(name))`——手敲 `?redirect=/` 或 `?redirect=/error/page401` 时不再把落地页设成登录页/错误页自己。
|
||||
|
||||
教训:**"`""` 和 `"/"` 等价"这种归一化只该出现在真正需要它的地方**。凡是把字符串地址解析成结构化结果的函数,都要先明确"空输入是错误还是默认值",否则默认值会顺着真值判断悄悄覆盖掉上游的初始化。
|
||||
Reference in new issue
Block a user