引言
本文件聚焦后台「更新角标」的异步刷新系统,说明其如何在不阻塞页面渲染的前提下,从云端拉取系统、模块、主题、插件等可更新数量,并以结构化数据注入到后台模板中,最终由前端脚本将数字徽标同步到侧栏对应位置。该方案通过控制器端点、服务层节流、配置落库、视图中间件注入以及前端异步刷新五个层次协作完成。
项目结构
围绕徽章异步刷新,主要涉及以下代码位置:
- 控制器端点:后台首页控制器提供 JSON 接口,供前端在 DOM ready 或安装完成后静默调用。
- 云服务层:负责调用云端 /connect、节流判断、计数归一化并写入数据库配置表。
- 构建器:将原始计数标准化为模板可用的 $unum 结构,并计算汇总值。
- 中间件与初始化:在后台请求生命周期内把 $unum 注入到视图引擎。
- 前端样式与脚本:定义徽标样式,并在页面加载后异步刷新徽标。
graph TB
A["浏览器"] --> B["后台首页控制器<br/>更新角标端点"]
B --> C["云服务层<br/>refreshUpdateNumber"]
C --> D["云端 /connect"]
C --> E["数据库 config.update_number"]
B --> F["更新角标构建器<br/>fromRaw/build"]
F --> G["视图引擎变量 unum"]
H["后台工作台中间件"] --> G
I["后台初始化 Init"] --> G
J["前端脚本 update.badge.js"] --> K["DOM 徽标元素"]
A --> J
图示来源
- IndexController.php:84-115
- CloudService.php:392-437
- UpdateBadgeBuilder.php:38-94
- AdminWorkspaceMiddleware.php:58-84
- Init.php:296-347
- update.badge.js:54-76
章节来源
- IndexController.php:84-115
- CloudService.php:392-437
- UpdateBadgeBuilder.php:38-94
- AdminWorkspaceMiddleware.php:58-84
- Init.php:296-347
- update.badge.js:54-76
核心组件
- 更新角标构建器:负责读取配置、反序列化、补齐默认键、计算 system 汇总值,并提供 fromRaw 以支持同一请求内即时返回最新计数。
- 后台首页控制器:暴露 updateNumber 接口,处理关闭检测、强制刷新参数、调用云服务层、组装响应。
- 云服务层:实现云端连接、节流标记、计数归一化、落库持久化。
- 后台工作台中间件:在已登录且权限通过后,向视图注入 global_admin、workspace、unum。
- 后台初始化:注册容器单例、装配视图基础变量,并在合适阶段注入 unum。
- 前端徽标脚本:按 unum 映射更新侧栏徽标,避免阻塞首屏。
章节来源
- UpdateBadgeBuilder.php:24-94
- IndexController.php:84-115
- CloudService.php:392-477
- AdminWorkspaceMiddleware.php:26-84
- Init.php:296-347
- update.badge.js:19-76
架构总览
徽章异步刷新采用“后端异步 + 前端静默”的模式:
- 首次进入后台时,中间件和初始化逻辑从配置读取上次落库的 update_number,构造 $unum 并注入视图。
- 当需要立即刷新(例如模块升级完成)时,前端调用控制器 updateNumber 接口;若开启强制刷新,则跳过节流直接访问云端,并将结果回写配置。
- 前端脚本根据返回的 unum 更新侧栏徽标,不阻塞主流程。
sequenceDiagram
participant Browser as "浏览器"
participant Controller as "后台首页控制器"
participant CloudSvc as "云服务层"
participant Cloud as "云端 /connect"
participant DB as "数据库 config"
participant Builder as "更新角标构建器"
participant View as "视图引擎"
participant Script as "前端徽标脚本"
Browser->>Controller : GET /admin/index/update_number?force=0|1
Controller->>CloudSvc : refreshUpdateNumber(localsite, localsystem, force)
alt 非强制且节流命中
CloudSvc-->>Controller : null
else 强制或节流未命中
CloudSvc->>Cloud : 请求 /connect
Cloud-->>CloudSvc : {update, patch, module, plugin, theme}
CloudSvc->>DB : 写入 update_number
CloudSvc-->>Controller : 原始计数数组
end
Controller->>Builder : fromRaw(原始计数) 或 build()
Builder-->>Controller : 标准化 unum
Controller-->>Browser : {closed, unum}
Browser->>Script : apply(unum)
Script->>View : 更新侧栏徽标 DOM
图示来源
- IndexController.php:84-115
- CloudService.php:392-437
- UpdateBadgeBuilder.php:38-94
- update.badge.js:54-76
详细组件分析
更新角标构建器 UpdateBadgeBuilder
职责与行为:
- build:读取 site.close_update 开关;若关闭则返回 null;否则读取 site.update_number,反序列化为数组,补齐默认键并计算 system 汇总值。
- fromRaw:用于同一请求内使用刚获取的云端原始计数,避免 Config 尚未重载导致的数据不一致。
- normalize:合并默认键 update/patch/module/plugin/theme/miniprogram,并计算 system = update + patch + module + theme。
复杂度与健壮性:
- 时间复杂度 O(1),空间复杂度 O(1)。
- 对空值、非法字符串、缺失键均有防御性处理。
classDiagram
class UpdateBadgeBuilder {
+build() array|null
+fromRaw(number) array
-normalize(number) array
}
图示来源
- UpdateBadgeBuilder.php:38-94
章节来源
- UpdateBadgeBuilder.php:24-94
后台首页控制器 IndexController::updateNumber
职责与行为:
- 若 site.close_update 为真,直接返回 closed=true 且 unum=null。
- 解析 force 参数决定是否强制刷新。
- 调用 CloudService.refreshUpdateNumber 获取最新计数;若返回数组则用 fromRaw 构造 unum,否则回退到 build。
- 统一返回 ApiResponse.success({closed, unum})。
flowchart TD
Start(["进入 updateNumber"]) --> CheckClose{"site.close_update 是否关闭?"}
CheckClose --> |是| ReturnClosed["返回 {closed:true, unum:null}"]
CheckClose --> |否| ParseForce["解析 force 参数"]
ParseForce --> CallCloud["调用 CloudService.refreshUpdateNumber"]
CallCloud --> FreshIsArray{"fresh 是否为数组?"}
FreshIsArray --> |是| FromRaw["UpdateBadgeBuilder.fromRaw(fresh)"]
FreshIsArray --> |否| Build["UpdateBadgeBuilder.build()"]
FromRaw --> Respond["返回 {closed:false, unum}"]
Build --> Respond
ReturnClosed --> End(["结束"])
Respond --> End
图示来源
- IndexController.php:84-115
- CloudService.php:392-437
- UpdateBadgeBuilder.php:38-94
章节来源
- IndexController.php:84-115
云服务层 CloudService::refreshUpdateNumber
职责与行为:
- 节流控制:若非强制且距上次尝试不足 10 分钟,直接返回 null。
- 云端调用:请求 /connect,无论成功与否都记录本次尝试时间戳,防止频繁重试。
- 数据归一化:提取 update/patch/module/plugin/theme 字段,转为整型,写入 config.update_number。
- 返回值:成功时返回原始计数数组,失败或节流命中返回 null。
flowchart TD
Enter(["进入 refreshUpdateNumber"]) --> ForceCheck{"force 为真?"}
ForceCheck --> |否| FreshCheck{"10 分钟内已刷新?"}
FreshCheck --> |是| ReturnNull1["返回 null"]
FreshCheck --> |否| CallConnect["调用云端 /connect"]
ForceCheck --> |是| CallConnect
CallConnect --> MarkTime["记录本次尝试时间戳"]
MarkTime --> DataValid{"返回数据是否为数组?"}
DataValid --> |否| ReturnNull2["返回 null"]
DataValid --> |是| Normalize["归一化计数"]
Normalize --> Persist["写入 config.update_number"]
Persist --> ReturnData["返回计数数组"]
图示来源
- CloudService.php:392-477
章节来源
- CloudService.php:392-477
后台工作台中间件 AdminWorkspaceMiddleware
职责与行为:
- 在 AuthMiddleware 与 PermissionMiddleware 之后执行,确保登录态与权限已通过。
- 若当前管理员上下文为空或视图引擎不可用,静默跳过。
- 注入 global_admin、workspace,并根据 UpdateBadgeBuilder.build() 的结果注入 unum。
flowchart TD
MWEnter(["中间件 handle"]) --> GetAdmin["获取 admin 上下文"]
GetAdmin --> AdminEmpty{"admin 是否为空?"}
AdminEmpty --> |是| Next1["直接 next()"]
AdminEmpty --> |否| GetEngine["获取 DouView 引擎"]
GetEngine --> EngineNull{"引擎是否存在?"}
EngineNull --> |否| Next2["直接 next()"]
EngineNull --> |是| AssignVars["注入 global_admin / workspace"]
AssignVars --> BadgeBuild["UpdateBadgeBuilder.build()"]
BadgeBuild --> BadgeNull{"unum 是否为 null?"}
BadgeNull --> |是| Next3["直接 next()"]
BadgeNull --> |否| AssignUnum["注入 unum"]
AssignUnum --> Next4["继续 next()"]
图示来源
- AdminWorkspaceMiddleware.php:58-84
- UpdateBadgeBuilder.php:38-94
章节来源
- AdminWorkspaceMiddleware.php:26-84
后台初始化 Init
职责与行为:
- 在 loadModules 完成后注册 WorkspaceBuilder 与 UpdateBadgeBuilder 单例。
- 在合适的时机调用 UpdateBadgeBuilder.build() 并将 unum 注入视图引擎。
- 同时注入 workspace、setting、features、js_route_config_json 等通用变量。
flowchart TD
Boot(["Init.loadModules"]) --> Register["注册 WorkspaceBuilder / UpdateBadgeBuilder"]
Register --> AssignSetting["注入 setting / features / js_routes"]
AssignSetting --> BuildBadge["UpdateBadgeBuilder.build()"]
BuildBadge --> AssignUnum["注入 unum"]
图示来源
- Init.php:296-347
- UpdateBadgeBuilder.php:38-94
章节来源
- Init.php:296-347
前端徽标脚本 update.badge.js
职责与行为:
- 维护 BADGE_MAP,将 unum 键映射到侧栏 li[data-id]。
- apply(unum) 方法根据 unum 对象更新对应徽标文本。
- refresh() 方法调用控制器 updateNumber 接口,成功后应用 unum。
flowchart TD
Ready(["DOM ready / finalize"]) --> Refresh["调用 refresh()"]
Refresh --> Ajax["GET /admin/index/update_number"]
Ajax --> Resp{"响应是否包含 unum?"}
Resp --> |是| Apply["apply(unum)"]
Resp --> |否| Skip["跳过更新"]
Apply --> Done(["完成"])
Skip --> Done
图示来源
- update.badge.js:19-76
- IndexController.php:84-115
章节来源
- update.badge.js:19-76
徽标样式 common.css
职责与行为:
- 定义 .badge 基础样式,包括尺寸、颜色、圆角等。
- 提供状态切换药丸样式,便于列表行内状态展示。
graph LR
CSS[".badge 样式"] --> UI["后台徽标显示"]
图示来源
- common.css:1648-1682
章节来源
- common.css:1648-1682
依赖关系分析
- 控制器依赖云服务层与构建器,并通过容器获取构建器实例。
- 云服务层依赖云端 API 与数据库配置表。
- 中间件与初始化均依赖构建器,负责将 unum 注入视图。
- 前端脚本依赖控制器接口与 DOM 结构。
graph TB
Ctrl["IndexController"] --> Svc["CloudService"]
Ctrl --> Builder["UpdateBadgeBuilder"]
MW["AdminWorkspaceMiddleware"] --> Builder
Init["Init"] --> Builder
Svc --> DB["config.update_number"]
JS["update.badge.js"] --> Ctrl
图示来源
- IndexController.php:84-115
- CloudService.php:392-437
- UpdateBadgeBuilder.php:38-94
- AdminWorkspaceMiddleware.php:58-84
- Init.php:296-347
- update.badge.js:54-76
章节来源
- IndexController.php:84-115
- CloudService.php:392-437
- UpdateBadgeBuilder.php:38-94
- AdminWorkspaceMiddleware.php:58-84
- Init.php:296-347
- update.badge.js:54-76
性能与可靠性
- 节流策略:CloudService 使用文件时间戳实现 10 分钟节流,避免每次后台请求都等待云端。
- 降级策略:云端异常或返回非数组时,保持上次落库值,保证后台可用。
- 关闭开关:site.close_update 为真时,整个链路直接返回 closed,不再发起云端请求。
- 前端非阻塞:update.badge.js 在 DOM ready 或安装完成后静默刷新,不影响首屏渲染。
- 数据一致性:fromRaw 在同一请求内直接使用云端原始计数,避免 Config 重载延迟导致的显示不一致。
故障排查指南
常见问题与定位建议:
- 徽标始终为 0:检查 site.close_update 是否被设置为真;确认 config.update_number 是否有有效值;查看 CloudService 是否因节流或云端异常返回 null。
- 强制刷新无效:确认 force 参数是否正确传递;检查 storage/cache/cloud/update_number_refresh.txt 是否可写;验证云端 /connect 是否返回预期字段。
- 徽标未更新:检查前端脚本是否成功调用 updateNumber 接口;确认响应中包含 unum;核对 BADGE_MAP 与侧栏 li[data-id] 映射是否一致。
- 视图未注入 unum:检查 AdminWorkspaceMiddleware 是否执行;确认 UpdateBadgeBuilder.build() 返回非 null;查看 Init 是否在合适阶段注入 unum。
章节来源
- CloudService.php:392-477
- IndexController.php:84-115
- AdminWorkspaceMiddleware.php:58-84
- Init.php:296-347
- update.badge.js:54-76
结论
徽章异步刷新系统通过控制器端点、云服务层节流、配置落库、视图中间件注入与前端脚本刷新形成完整闭环。系统在云端不可用时具备良好降级能力,并通过节流与关闭开关保障后台性能与可控性。未来可在多进程环境下将节流标记迁移至分布式缓存,进一步提升并发场景下的可靠性与一致性。