ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

dotnet-starter-kit 租户端 Dashboard 前端工程规范详解:权限拉取、SSE/Realtime 双通道与手写表单

dotnet-starter-kit 租户端 Dashboard 前端工程规范详解:权限拉取、SSE/Realtime 双通道与手写表单 dotnet-starter-kit 租户端 Dashboard 前端工程规范详解权限拉取、SSE/Realtime 双通道与手写表单【免费下载链接】dotnet-starter-kitProduction Grade Cloud-Ready .NET 10 Starter Kit (Web API React Client) with Multitenancy Support, and Clean/Modular Architecture that saves roughly 200 Development Hours! All Batteries Included.项目地址: https://gitcode.com/GitHub_Trending/do/dotnet-starter-kit本文是 dotnet-starter-kit 仓库中.agents/rules/frontend/dashboard.md开发规范的完整解读与源码级展开。该规范面向clients/dashboard这一租户端Tenant-facing应用是开发者在该目录下做任何 React 改动前必须阅读的分歧清单——它假定你已经读懂了适用于 admin/dashboard 两个前端应用的公共规范 shared.md因此只记录 dashboard 独有的约定。读完本文你将掌握dashboard 的端口/环境变量/HTTPS 代理设计意图、权限为什么拉取而非内嵌 JWT以及如何防闪烁、SSE 两步令牌与双 Context 拆分的实现原理、跨应用模拟登录Impersonation的令牌换手机制、chroma 0 中性色主题体系以及新增页面的完整差异步骤。一、定位dashboard 是什么与 admin 的分工dashboard 是仓库clients/dashboard目录下的 React 19 Vite 7 TypeScript 应用服务于租户tenant——即实际使用产品的企业客户而clients/admin是运营方operator的后台。两者的技术栈底座相同TanStack Query v5、React Router 7、Radix UI、Tailwind v4、microsoft/signalr但设计语言与权限模型刻意不同详见下文主题与权限两节。两者差异的权威对照表见 shared.md 的Design language小节admin 使用冷色调中性色hue 240 带少量彩度与单一 chartreuse 强调色而 dashboard 使用chroma 0 无色调中性色与可切换的 rose 品牌色——中性色必须保持 chroma 0是 dashboard 专属规则修改时须按所在文件判断归属。二、端口、环境变量与 HTTPS 开发代理2.1 端口 5174 与代理目标端口5174clients/dashboard/vite.config.ts中server.port且strictPort: true与 admin 的端口区分开允许两个应用并存开发。开发代理目标https://localhost:7030HTTPS且对/api配置了ws: true——这是 SignalR Hub 的 WebSocket 升级所必需的否则/api/v1/realtime/hub的 negotiate 虽然能成功但 WS 升级会落入 Vite 自己的 dev server导致聊天状态永远停在 CONNECTING。localStorage 前缀fsh.dashboard.*admin 为fsh.admin.*这是两者能并排运行的前提。登录请求头X-FSH-App: dashboard服务端据此拒绝 root 租户在租户端登录。2.2 为什么开发代理刻意用 HTTPS规范明确指出开发代理使用 HTTPS 是有意为之——如果走 HTTP→HTTPS 的 307 重定向Authorization承载令牌会在重定向过程中被剥离。因此 dev proxy 直接changeOrigin: true, secure: false指向https://localhost:7030保证 bearer token 直达 API。2.3 运行时环境变量src/env.tsdashboard 的运行时配置只有三个核心字段{ apiBase, defaultTenant, demoMode }。阅读 env.ts 可以看到它比 shared 规范中描述的多继承了inactivityIdleMs默认 20 分钟与inactivityWarningMs默认 60 秒两个非活动超时参数并通过positiveOr()做防御性取值要求正有限数否则回退默认值。关键的架构决策是环境变量是运行时加载而非构建时注入。loadRuntimeConfig()在main.tsx挂载 React 前await一次/config.jsonenv是 getter过早读取会抛错。这意味着同一个构建产物可以跨环境直接晋升——运维只需改写config.json不需要重新构建镜像。dashboard 的vite.config.ts中还内置了一个名为fsh-dev-direct-api-config的开发插件dev 模式下直接改写/config.json的响应把apiBase指向https://localhost:7030让 REST 请求与长连接的 SSE/SignalR 流绕过 Vite 代理直连 API——否则这些长连接占用 localhost:5174 的 HTTP/1.1 每主机约 6 条连接上限会间歇性饿死懒加载的路由 chunk注释里直接写了 page wont load 的教训。生产环境的clients/dashboard/public/config.json保持apiBase: 同源默认。三、表单拒绝 RHF/zod手写受控组件dashboard 的硬性约定不依赖 react-hook-form 或 zod不要为了与 admin 对齐而引入这两个依赖。表单一律使用受控输入controlled inputs 组件本地 state 手写。这是与 admin 的明确分歧admin 允许/使用 RHF 风格的表单方案dashboard 刻意保持轻量。在新增页面时见第八节表单部分必须遵循此约定。四、权限体系从接口拉取而非内嵌 JWT这是 dashboard 与 admin 在权限模型上的核心差异也是规范着墨最多的部分。4.1 JWT 只携带角色名dashboard 的 JWT只包含角色名role names不包含权限列表。权限的权威来源是服务端按角色解析后的接口GET /api/v1/identity/permissions封装为src/api/identity.ts中的getMyPermissions()见 identity.ts 中getMyPermissions的实现注释明确写着 The JWT carries only role names; permissions are resolved server-side per role here。4.2 拉取、缓存与防闪烁auth-context.tsx见 auth-context.tsx中的AuthProvider在签名主体subject变化时——包括冷启动、登录、模拟登录切换——都会触发权限拉取调用getMyPermissions()结果写入tokenStore.setPermissions(perms)缓存于 localStorage 的fsh.dashboard.permissions键见 token-store.ts 中getPermissions/setPermissions带 JSON 解析防御置位permissionsHydrated标志。permissionsHydrated的存在意义是避免受权限门控的 UI 在请求进行中闪现。初始 state 会从缓存权限列表种子化tokenStore.getPermissions().length 0即视为已水合因此热刷新不会闪未门控的 UI。拉取失败不会登出用户——门控 UI 保持隐藏直到下次成功水合。login()时还会先清空权限缓存再发令牌防止上一个用户的权限泄漏进新会话。此外还有refreshPermissions()暴露给 AuthContext用于角色变更后主动刷新权限集。4.3 导航门控perm/anyPerm导航项在 nav-data.ts 中通过NavSpec的perm单一权限AND 语义与anyPerm任选其一即可字段门控// perm必须持有 Permissions.Chat.Channels.View 才显示 { to: /chat, label: Chat, icon: MessageCircle, perm: Permissions.Chat.Channels.View }, // anyPermTrash 有五个页签、各门控在不同资源的 restore/view-trash 权限上 // 只要用户能进任何一个页签就显示入口 { to: /system/trash, label: Trash, icon: Trash2, anyPerm: ALL_TRASH_PERMISSIONS },isNavItemVisible()实现perm AND anyPerm的判定visibleSections()/visibleItems()负责过滤并丢弃空 section。一个值得注意的细节/identity/users|roles|groups门控在*.Update而非*.View——因为 View 属于 IsBasic 权限、每个成员都持有聊天/用户选择器依赖 Users.View只有管理员级别的用户才应看到管理页面。4.4 路由守卫仍是仅认证与 admin 不同dashboard 的ProtectedRoute只做认证检查不做逐路由权限门控——规范明确要求不要在此引入 admin 那种RouteGuard风格的门控。导航门控隐藏入口 服务端 403API 层拦截构成了双层防线前端不再画蛇添足。五、路由与实时通道SignalR 专属 SSE5.1 路由与懒加载每个路由元素都用withSuspense(node)包裹逐路由骨架屏 fallback不设逐路由权限守卫。Provider 挂载顺序RealtimeProvider和SseProvider都挂在AppShell内部仅认证路由外层再套CommandPaletteProvidercmdk 命令面板。页面全部为命名导出通过lazyNamed(importer, name)适配为React.lazy见 shared 规范。5.2 SignalRsrc/realtime/realtime-context.tsx单一共享的HubConnection连接到/api/v1/realtime/hub预接线约 11 个聊天/通知事件。microsoft/signalr是动态导入的约 37KB gzip全 shell 中最重的单依赖只在已认证会话打开 hub 时才拉取——settings/files/health/auth 等无实时消费者的页面永远不会下载它见 realtime-context.tsx 中loadSignalR()的单例 Promise 设计。认证走accessTokenFactorytokenEpoch由tokenStore.subscribe递增任何登录/刷新/模拟登录切换都会强制重建连接避免旧令牌的僵尸连接。重连采用递归退避[2s, 5s, 10s, 30s]连续失败上限 60s 并带 ±15% 抖动传输方式不固定WebSockets → SSE → long-polling 自动降级因为企业代理后方的仪表盘常常需要回退。消费方式useRealtimeEvent(EventName, handler, deps)handler 存在 ref 中避免闭包过期。5.3 SSEsrc/sse/dashboard 独有SSE 是 dashboard 区别于 admin 的专属能力采用两步令牌流程POST /api/v1/sse/token换取一次性短寿命令牌见 sse-api.ts 的issueSseToken()GET /api/v1/sse/stream?tokenguid通过fetch 流式读取消费parseSseStream异步生成器手写解析event:/id:/data:字段与\n\n分隔符。为什么不用 EventSource因为 EventSource 无法发送Authorization请求头而 SSE 流需要认证。令牌只在校验握手那一刻被检查流一旦建立其生命周期由传输层网络/服务端决定断线重连时会重新调用issueSseToken()而它背后的用户 JWT 会通过 api-client 的 401 单飞刷新自动续期——长会话的令牌刷新被隐式纳入重连路径无需专用定时器。连接失败采用 1s→30s 的指数退避INITIAL_BACKOFF_MS 1000MAX_BACKOFF_MS 30_000事件列表上限 200 条。双 Context 拆分是性能关键设计见 sse-context.tsx 中注释记录的历史教训旧版单 Context 的 value 因events每次都是新数组而每次事件都变化导致整个 overview 树级联重渲染。现在拆成useSseStatus()——稳定{ status, eventCount }只适合顶部状态点、铃铛角标这类只关心连接状态的消费者useSseEvents()——每次事件都变化{ events }只在真正渲染事件列表的组件中挂载overview 实时动态、activity 页useSse()——向后兼容的组合钩子新代码应改用上面两个按需切片。六、模拟登录Impersonation跨应用单程移交6.1 令牌藏匿stash机制token-store.ts提供了三个关键方法见 token-store.tsbeginImpersonation(accessToken, impersonatedTenant)把操作者operator的原始 access/refresh/tenant 令牌藏到fsh.dashboard.impersonation.*键下然后把活动令牌换成模拟登录令牌并移除 refresh 槽——因为服务端不签发模拟登录的 refresh 令牌api-client 检测到无 refresh 令牌就会静默跳过自动刷新模拟登录会话刻意设计为短命endImpersonationWithFreshTokens(access, refresh)End 成功路径用服务端为原始操作者新铸的令牌对替换活动令牌并清空藏匿restoreStashedActor()End 失败时的兜底本地恢复藏匿令牌原 access 可能已过期此时藏匿的 refresh 会触发自动刷新。6.2 AuthProvider 暴露的接口与防御性设计AuthProvider暴露beginImpersonation/stopImpersonation并从act_sub/act_tenant/act_name声明推导ImpersonationInfo见 auth-context.tsx 的claimsToImpersonation。stopImpersonation分两种情况逻辑非常讲究无藏匿跨应用移交说明操作者是 root SuperAdmin 从 admin 端发起的移交dashboard 端没有可回归的会话且把 root 账户恢复到租户端正是login()明令禁止的——所以立即登出服务端endImpersonation只做 best-effort 调用用于吊销授权 审计30s 超时不容阻塞 UI有藏匿应用内模拟await 服务端 End 换取操作者新令牌若新令牌 tenant 仍是 root防御纵深同样登出而非恢复。另外beginImpersonation/stopImpersonation都会queryClient.clear()避免 actor 会话的用户/角色/权限缓存泄漏给被模拟者。移交方向是单向的admin 通过其dashboardUrl触发移交dashboard 不反向移交这也是env.ts注释说明 dashboard 不需要dashboardUrl配置的原因。6.3 会话恢复的边界AuthProvider还处理了两个容易踩坑的会话恢复场景冷启动时 access 过期但 refresh 存在会先做一次静默刷新isInitializing期间渲染 loader而不是闪现注定 401 的仪表盘以及跨标签页storage事件与visibilitychange监听确保 DevTools 手动清令牌或另一标签登出后本标签不会继续用已丢失的令牌发请求。七、性能与主题7.1 性能约定tanstack/react-virtual任何大集合聊天历史、大表格必须用它做虚拟滚动。cmdk驱动命令面板CommandPaletteProvider。7.2 主题chroma 0 中性色 可换强调色设计语言定义在 globals.css遵循原始值 → 语义变量 →theme inline工具类的三层 token 架构中性色全部 chroma 0--neutral-*: oklch(L 0 0)无色调。规范明确指出暖纸色warm-paper被刻意移除——旧版的暖纸底盘会让每个表面都泛黄在深色模式下表现为整体黄色滤镜且与非 rose 的强调色打架。现在中性色完全无色调让所选强调色成为房间里唯一的颜色。默认品牌色 rose600 停靠点#f91942附近的 oklch通过:root上的.accent-{rose,indigo,violet,sky,emerald,amber}类覆盖全部--brand-*oklch 停靠点实现整套品牌色一键切换。saffron 次强调色--saffron-*暖调第二通道用于渐变端点、主视觉数字、信任标记。字体 Figtree区别于 admin 的 Geist / Geist Mono。新增 token 的流程是在globals.css中按原始值 → 语义值 →theme inline补全三层然后使用工具类禁止在组件里硬编码颜色。八、新增页面在共享步骤之上的 deltashared 规范给出了四步通用流程扩展src/api/{feature}.ts手写类型与apiFetch调用 → 建src/pages/{area}/{name}.tsx命名导出页 → 在AppShell下注册lazyNamed路由 → 写tests/{area}/{name}.spec.ts测试。dashboard 在此基础上叠加五条差异手写表单不用 RHF/zod受控输入 本地 state路由元素包withSuspense(X/)不设权限守卫导航门控 服务端 403 负责权限若页面消费推送SignalR 用useRealtimeEvent(EventName, handler)新事件名须先在 realtime-context.tsx 的预接线事件列表中注册SSE 用useSseEvents()长列表用react-virtual保持中性色 chroma 0。九、测试与验证依据dashboard 的 Playwright 测试route-mocked无真实后端落在clients/dashboard/tests/{area}/{name}.spec.ts覆盖 auth、billing、catalog、chat、files、identity、impersonation、overview、settings、system、tickets 各域。测试采用 shared 规范描述的 JWT 种子化seedAuthedSession构造假 JWT 写入fsh.dashboard.*localStorage shell mockinstallShellMocks会abort SSE/SignalR策略beforeEach统一执行。对于实现细节的验证最直接的路径是权限拉取链identity.ts 的getMyPermissions→ auth-context.tsx 的水合 effect → token-store.ts 的fsh.dashboard.permissions缓存SSE 两步令牌sse-api.ts → sse-context.tsx 的连接循环代理与端口vite.config.ts。十、总结clients/dashboard的设计哲学可以概括为几条清晰的取舍权限走服务端权威JWT 只带角色、权限单独拉取并缓存、实时走双通道SignalR 管高吞吐事件、SSE 管可认证的流式推送、表单保持原生不引入重型表单库、主题保持纯净chroma 0 中性色 可换强调色。理解这些约定与其背后的历史教训307 重定向剥 header、HTTP/1.1 连接数饿死懒加载、SSE 单 Context 级联重渲染、跨应用移交的 root 账户防御是在该租户端应用上高效、合规地新增功能的前提。修改任何 dashboard 代码前请先通读 shared.md 与本文件再对照上述源码路径核实行为。【免费下载链接】dotnet-starter-kitProduction Grade Cloud-Ready .NET 10 Starter Kit (Web API React Client) with Multitenancy Support, and Clean/Modular Architecture that saves roughly 200 Development Hours! All Batteries Included.项目地址: https://gitcode.com/GitHub_Trending/do/dotnet-starter-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表