本文引用的文件
本文聚焦桌面客户端前端中“授权单元”和“页面水印”机制。重点说明:
App.vue 如何在应用启动后通过 authorizationInfoApi 获取服务端授权信息;license.ts 中的 extractAuthorizationCache 如何解析 authorizationLabel、overDateTime、authorizationUnit,并写入用户页缓存;useWatermark.ts 如何根据 authorizationUnit 动态设置或清除页面水印;checkAuthApi、authorizationInfoApi 的关系;授权与水印相关代码集中在 Electron 桌面端的前端工程内:
ai-electron/frontend/src/App.vueai-electron/frontend/src/core/utils/license.tsai-electron/frontend/src/core/api/sys/login.tsai-electron/frontend/src/core/hooks/web/useWatermark.tsai-electron/frontend/src/core/layouts/default/setting/components/AuthorizationInfoModal.vue授权管理页面:ai-electron/frontend/src/plat/views/authorization/index.vue
graph TB
App["App.vue<br/>应用根组件"] --> LoginApi["login.ts<br/>authorizationInfoApi / checkAuthApi"]
App --> License["license.ts<br/>extractAuthorizationCache"]
App --> Watermark["useWatermark.ts<br/>setWatermark / clear"]
AuthorizationPage["authorization/index.vue<br/>授权管理页"] --> LoginApi
AuthorizationPage --> License
AuthorizationModal["AuthorizationInfoModal.vue<br/>授权信息弹窗"] --> LoginApi
AuthorizationModal --> License
图表来源
章节来源
| 组件 | 职责 | 关键输出/副作用 |
|---|---|---|
App.vue |
应用初始化时拉取授权信息,监听授权单元变化,驱动水印显示 | 调用 authorizationInfoApi、写入 userStore 的 authorizationUnit、调用 setWatermark 或 clear |
license.ts |
从任意结构的授权响应中提取授权标签、过期时间、授权单元,并提供过期判断和消息生成 | extractAuthorizationCache、isAuthorizationExpired、getAuthorizationExpiredMessage、getAuthorizationLabel、isFormalAuthorization |
login.ts |
定义与后端 /sys/info、/sys/checkAuth 等接口的请求封装 |
authorizationInfoApi、checkAuthApi、offlineAuthorizationApi 等 |
useWatermark.ts |
在页面 DOM 上绘制可防篡改、自适应尺寸的水印层 | setWatermark(str)、clear()、clearAll() |
AuthorizationInfoModal.vue |
展示当前授权信息,计算是否正式授权、是否过期 | 使用 extractAuthorizationCache、isAuthorizationExpired |
authorization/index.vue |
授权管理主流程:加载授权信息、校验授权、更新授权、提示过期 | 同时使用 checkAuthApi、authorizationInfoApi、isAuthorizationExpired、getAuthorizationExpiredMessage |
章节来源
整体数据流分为两条主线:
App.vue → authorizationInfoApi → 后端 /sys/info → extractAuthorizationCache → userStore 页面缓存。水印渲染线:authorizationUnit 变化 → useWatermark.setWatermark 或 clear → Canvas 生成背景图 → 注入全局水印节点。
sequenceDiagram
participant App as "App.vue"
participant Api as "login.ts authorizationInfoApi"
participant Server as "后端 /sys/info"
participant License as "license.ts extractAuthorizationCache"
participant Store as "userStore 页面缓存"
participant WM as "useWatermark.ts"
App->>Api: "GET /sys/info"
Api-->>App: "授权原始响应"
App->>License: "extractAuthorizationCache(响应)"
License-->>App: "{ authorizationLabel, overDateTime, authorizationUnit }"
App->>Store: "写入 authorizationUnit"
Store-->>App: "authorizationUnit 变化"
App->>WM: "setWatermark(authorizationUnit) 或 clear()"
WM-->>WM: "Canvas 生成水印背景并插入 DOM"
图表来源
App.vue:授权信息加载与水印联动App.vue 是授权单元与水标的协调者:
userStore 读取 authorizationUnit 作为计算属性;authorizationInfoApi;extractAuthorizationCache(res).authorizationUnit 写回 userStore.setPageCache('authorizationUnit', unit);authorizationUnit 变化:非空时调用 setWatermark(unit),为空时调用 clear();请求失败时直接清理水印,避免残留旧水印。
flowchart TD
Start["onMounted"] --> CheckUnit["检查 authorizationUnit 是否为空"]
CheckUnit -->|不为空| WatchUnit["监听授权单元变化"]
CheckUnit -->|为空| CheckServer["检查服务器地址是否可用"]
CheckServer -->|不可用| Wait["等待网络就绪"]
CheckServer -->|可用| CallApi["调用 authorizationInfoApi"]
CallApi --> Parse["extractAuthorizationCache 解析"]
Parse --> WriteStore["写入 userStore.authorizationUnit"]
WriteStore --> WatchUnit
CallApi --> Error["请求失败"]
Error --> ClearWm["clear() 清除水印"]
WatchUnit --> UnitEmpty{"authorizationUnit 是否为空"}
UnitEmpty -->|是| ClearWm
UnitEmpty -->|否| SetWm["setWatermark(authorizationUnit)"]
图表来源
章节来源
license.ts:授权缓存提取、过期判断与消息生成license.ts 的核心目标是把后端返回的结构化或非结构化授权响应,统一成前端可用的三个字段:
authorizationLabel:授权标签,如“试用”或正式授权时的空字符串;overDateTime:授权过期时间;authorizationUnit:授权单元,用于水印展示。extractAuthorizationCache 的处理顺序为:
source.data 优先取值;data 中没有目标字段,则回退到顶层 rawData;overDateTime 从 overDateTime 或 overdatetime 两个键中匹配;authorizationUnit 从 unit 键取值;authorizationLabel 优先从以下位置取值:
rawData 本身是字符串时,直接作为标签;data.authorizationLabel、data.authorizationStatus、data.authStatus、data.message;overDateTime 计算的 getAuthorizationLabel。getAuthorizationExpirationTime 支持带时区的时间字符串、日期格式、以及包含时分秒的完整时间;isAuthorizationExpired 比较过期时间与当前时间;isFormalAuthorization 通过固定正式授权日期常量判断正式授权;getAuthorizationExpiredMessage 根据是否存在规范化后的 overDateTime 生成中文提示。
flowchart TD
Input["输入 source"] --> Resolve["resolveAuthorizationSource<br/>分离 raw/rawData/data"]
Resolve --> OverDateTime["读取 overDateTime<br/>overDateTime / overdatetime"]
Resolve --> Unit["读取 authorizationUnit<br/>unit"]
Resolve --> LabelFallback["读取 authorizationLabel<br/>authorizationLabel / authorizationStatus / authStatus / message"]
LabelFallback --> StringRaw{"rawData 是否为字符串"}
StringRaw -->|是| UseString["直接使用 rawData 作为 label"]
StringRaw -->|否| ComputeLabel["按 overDateTime 计算 label"]
OverDateTime --> Expire["isAuthorizationExpired / getAuthorizationExpirationTime"]
Unit --> Output["输出 { authorizationLabel, overDateTime, authorizationUnit }"]
ComputeLabel --> Output
UseString --> Output
图表来源
章节来源
login.ts:授权相关接口login.ts 提供两类授权相关接口:
authorizationInfoApi:获取 /sys/info,用于展示授权信息和驱动水印;checkAuthApi:获取 /sys/checkAuth,用于授权拦截判断。两者都配置了 errorMessageMode: 'none',表示错误不自动弹出默认错误提示,由调用方自行处理。authorizationInfoApi 还关闭响应转换,以便 App.vue 和授权模块能拿到原始结构,再由 extractAuthorizationCache 解析。
graph LR
Client["前端业务组件"] --> AuthorizationInfo["authorizationInfoApi<br/>/sys/info"]
Client --> CheckAuth["checkAuthApi<br/>/sys/checkAuth"]
AuthorizationInfo --> RawResponse["原始 Result 响应"]
CheckAuth --> AuthCheckResult["授权校验结果"]
图表来源
章节来源
useWatermark.ts:水印绘制与防篡改useWatermark 是一个 Vue Hook,负责:
document.body)上插入一个绝对定位的水印层;MutationObserver 监听 DOM 变更,防止控制台删除或修改水印;setWatermark(str)、clear()、clearAll() 三个对外方法。关键行为:
setWatermark 会创建水印节点并注册 ResizeListener;clear 会移除水印节点、断开观察者、移除监听;clearAll 会清理所有已注册水印实例;MutationObserver 会尝试恢复水印节点;当水印层样式被修改时,会重新执行 setWatermark,实现防篡改。
classDiagram
class UseWatermark {
+setWatermark(str)
+clear()
+clearAll()
-createBase64(str, options)
-resetWatermarkStyle(element, text, options)
-updateWatermark(options)
-createWatermark(str)
}
class MutationObserver {
+observe(node, config)
+disconnect()
}
class Canvas {
+toDataURL(type)
+fillText(text, x, y)
}
UseWatermark --> MutationObserver : "监听 DOM 变更"
UseWatermark --> Canvas : "生成水印图片"
图表来源
章节来源
AuthorizationInfoModal.vue 与 authorization/index.vue:授权界面联动这两个组件展示了授权能力在真实页面中的用法:
AuthorizationInfoModal.vue 调用 authorizationInfoApi,再用 extractAuthorizationCache 解析,并通过 isAuthorizationExpired 判断是否过期;authorization/index.vue 同时使用 checkAuthApi 做授权拦截判断,并使用 getAuthorizationExpiredMessage 生成过期提示。
sequenceDiagram
participant Modal as "AuthorizationInfoModal.vue"
participant Page as "authorization/index.vue"
participant Api as "login.ts"
participant License as "license.ts"
Modal->>Api: "authorizationInfoApi()"
Api-->>Modal: "授权响应"
Modal->>License: "extractAuthorizationCache / isAuthorizationExpired"
Page->>Api: "checkAuthApi()"
Page->>License: "isAuthorizationExpired / getAuthorizationExpiredMessage"
图表来源
章节来源
授权与水印的依赖关系可以概括为:
App.vue 依赖 login.ts 的 authorizationInfoApi;App.vue 依赖 license.ts 的 extractAuthorizationCache;App.vue 依赖 useWatermark.ts 的 setWatermark 和 clear;authorization/index.vue 和 AuthorizationInfoModal.vue 同时依赖 login.ts 和 license.ts;license.ts 是纯工具模块,不依赖 UI 组件。
graph TB
App["App.vue"] --> LoginApi["login.ts"]
App --> License["license.ts"]
App --> Watermark["useWatermark.ts"]
AuthPage["authorization/index.vue"] --> LoginApi
AuthPage --> License
AuthModal["AuthorizationInfoModal.vue"] --> LoginApi
AuthModal --> License
图表来源
章节来源
假设后端在 /sys/info 返回的新字段名为 enterpriseCode,新增步骤如下:
license.ts 的 extractAuthorizationCache 中增加对 enterpriseCode 的读取;data 或 rawData,复用现有 getAuthorizationValue 的容错逻辑;isAuthorizationExpired 或新的状态函数中扩展判断。参考路径:
当前水印文字来自 authorizationUnit。如需同时显示企业编码或授权类型:
App.vue 中组合新的水印文本,例如拼接 authorizationUnit 与新增字段;setWatermark(newText);useWatermark 的选项类型扩展配置;参考路径:
当前过期逻辑主要由 license.ts 提供:
isAuthorizationExpired 用于判断是否过期;getAuthorizationExpiredMessage 用于生成中文提示;authorization/index.vue 在授权管理页中使用这两个函数;AuthorizationInfoModal.vue 也使用 isAuthorizationExpired 展示过期状态。建议扩展点:
getAuthorizationExpirationTime 中增加自定义规则;checkAuthApi 并结合 isAuthorizationExpired 做跳转或提示;参考路径:
useWatermark 使用 Canvas 生成 Base64 背景图,适合短文本水印;长文本会增加绘图和内存开销;MutationObserver 监听 DOM 变更,存在一定监听成本,但用于防篡改是必要代价;App.vue 仅在 authorizationUnit 为空时才请求授权信息,减少重复请求;authorizationInfoApi 超时时间为 10 秒,避免长时间阻塞应用启动;[本节为通用行为说明,未直接分析具体代码片段]
现象:页面启动后没有水印,userStore 中也没有 authorizationUnit。
可能原因:
isServerSetupMissing() 或 getApiOrigin() 返回无效值;/sys/info 接口返回空对象或结构不符合预期;catch 分支调用了 clear() 清理水印;unit、overDateTime、authorizationLabel 字段名不一致。排查步骤:
/sys/info;authorizationInfoApi 返回的原始响应;data.unit、data.overDateTime、data.authorizationLabel 或其兼容键;App.vue 中 ensureAuthorizationUnit 是否进入 try 分支;license.ts 的 extractAuthorizationCache 是否能正确解析返回值。参考路径:
现象:授权单元已有值,但页面没有水印。
可能原因:
authorizationUnit 为空字符串或空白字符;setWatermark 没有被调用;useWatermark 的 MutationObserver 被异常中断。排查步骤
userStore.getPageCacheByKey('authorizationUnit') 的值;watch(authorizationUnit) 中添加日志,确认是否触发 setWatermark 或 clear;参考路径:
现象:overDateTime 已过期,但用户仍可继续使用。
重要区分:
checkAuthApi 与 isAuthorizationExpired;authorization/index.vue 明确说明其判定方式与合并前一致,即 checkAuthApi() + isAuthorizationExpired()。排查步骤
isAuthorizationExpired;authorizationLabel 做拦截;checkAuthApi 是否返回允许继续使用的结果;getAuthorizationExpiredMessage 并提示用户;参考路径:
现象:本地缓存中的 authorizationUnit 与实际后端返回不一致。
可能原因:
localStorage;userStore.setPageCache 写入后,其他组件未重新读取。处理建议
App.vue 启动时拉取的授权信息为准;userStore.setPageCache('authorizationUnit', unit);localStorage 中相关键,观察应用是否重新拉取;参考路径:
现象:前端授权信息显示正常,但网关拒绝请求或提示无权限。
可能原因:
/sys/info 展示授权信息,而网关可能基于 token 或会话状态做权限校验;处理建议
authorization/index.vue 中授权更新流程是否同步了必要的认证上下文;/sys/checkAuth、网关鉴权和授权库一致性。参考路径:
授权单元与水印机制在前端工程中形成了清晰的职责分工:
login.ts 负责与后端交互;license.ts 负责解析授权数据、判断过期和生成提示;App.vue 负责启动时拉取授权信息、写入缓存并驱动水印;useWatermark.ts 负责在 DOM 层绘制稳定、可恢复的水印;实际权限控制不应依赖水印。水印是可视化手段,真正的授权判断应结合 checkAuthApi、后端接口和用户会话。新增授权字段时,应优先扩展 license.ts 的解析逻辑,再在需要的 UI 层消费新字段,并确保授权过期路径在所有相关页面保持一致。