☰
DooTask 插件/微应用菜单角标(Badge)机制详解:数据模型、API 接口与实时推送实现
2026/10/16 2:30:29 网站建设 项目流程
  • 后端
  • 前端
  • 协同办公
  • 即时通讯
  • 企业应用

【免费下载链接】dootask

DooTask是一款开源在线项目任务管理工具,提供各类文档协作工具、在线思维导图、在线流程图、项目管理、任务分发、即时IM,文件管理等工具;同时消息功能使用非对称加密技术让你的沟通更安全。

项目地址:https://gitcode.com/gh_mirrors/do/dootask
点击查看免费下载

导读

DooTask 的插件/微应用可以在「应用中心卡片」「主导航平铺入口」以及父级「应用」入口上展示角标——一个提醒数字或一个小红点,用于提示用户有待处理事项。本文以 插件/微应用菜单角标概念文档 为骨架,结合仓库中app_badges表、Badge 业务模块、AppsController 接口 与前端appBadgesstore 的源码实现,完整讲清角标的定义、展示规则、菜单配置属性、三个后端接口的调用方式、自动清零与多端实时同步机制,以及卸载/离职时的生命周期清理。读完本文,你将能独立为自有插件接入 DooTask 角标能力,或从源码层面理解角标的完整数据流。


一、角标是什么:真值归插件,主程序只做存储与分发

角标(Badge)是插件/微应用菜单上显示的一个数字或小红点,用来提醒用户「这里有待处理事项」。它的核心设计原则是:

角标真值归插件。DooTask 主程序不自己产生角标数字,只负责存储、下发与实时推送。插件没有接入角标接口,就不会有角标出现。

这意味着角标不是消息未读数,其具体含义由各插件自行定义——可以代表「待审批数量」「待办提醒」或任意业务计数;自定义微应用菜单(纯外链)默认也不会有角标,除非其后端接入了角标接口。

二、展示位置与聚合规则

角标出现在三个层级:

展示位置说明
应用中心卡片对应插件/微应用卡片右上角显示数字或红点
主导航平铺入口被平铺到左侧主菜单的单个插件入口显示其角标
父「应用」入口(聚合)左侧「应用」一级入口与移动端底部「应用」标签显示聚合角标

父级「应用」入口的聚合规则:

  • 聚合数字= 工作报告未读数 + 所有插件角标数字之和
  • 总数 > 0 时显示数字;超过 999 显示999+
  • 总数为 0 时,若任一插件存在红点则显示红点

该聚合逻辑在前端 appBadges store 中有直接实现:

// 所有应用角标数字之和 totalCount: (state) => { /* 遍历 map 累加 count */ }, // 是否存在任一红点 anyDot: (state) => { /* 遍历 map 判断任意 dot 为 true */ }, // 父『应用』入口聚合数字:工作报告未读 + 所有插件角标数字之和 applicationCount: (state, getters, rootState) => (Number(rootState.reportUnreadNumber) || 0) + getters.totalCount, // 父『应用』入口聚合红点:总数为 0 时,任一插件存在红点则显示 applicationDot: (state, getters) => getters.anyDot,

从源码可见,聚合数字是「工作报告未读数 + 全部插件角标数字之和」,聚合红点则是在总数为 0 时由任意插件的红点决定,与概念文档完全一致。

三、菜单配置关键属性:key 与 badge_clear_on_open

角标的挂载目标是「菜单」,因此两个配置属性定义在菜单配置menu_items中(自定义微应用在管理员配置页填写,插件应用在插件包config.yml的menu_items中声明):

3.1key:菜单稳定标识

  • 用于定位某个菜单的角标,即角标绑定到哪个菜单;
  • 留空时表示该应用的第一个菜单。

后端在解析时遵循同样的规则,见 Badge.php 的 resolveMenu:menu_key为空时直接取menus[0](若列表为空则退化为['key' => '', 'visible' => ['all'], 'badge_clear_on_open' => false]);非空时必须在已声明的菜单 key 中命中,否则判定「菜单不存在」。

3.2badge_clear_on_open:打开时是否自动清零

  • true:打开对应应用菜单时角标自动清除;
  • false(默认):永不自动清零,角标的增减完全由插件控制。

在 Setting.php 的菜单规范化逻辑 中,自定义微应用菜单保存时会强制规范化这两个字段:

// 角标:菜单稳定标识 与 打开时是否自动清零 $payload['key'] = Base::newTrim($menu['key'] ?? ''); $payload['badge_clear_on_open'] = (bool)($menu['badge_clear_on_open'] ?? false);

插件应用的菜单声明同样会经过 Apps.php 的 mapMenuItem 归一为['key', 'visible', 'badge_clear_on_open']三元组,供角标模块解析。

四、数据存储:app_badges 表

角标持久化在独立的app_badges表中,由迁移 2026_06_28_000001_create_app_badges_table.php 创建,对应的模型为 AppBadge:

字段类型说明
idbigint主键
app_idstring(100)应用 ID(appstore 插件 appid 或自定义微应用 id)
menu_keystring(100)菜单稳定标识;空串表示该应用的第一个菜单
useridbigint用户 ID
countint角标数字,默认 0
dotboolean是否显示红点,默认 false
updated_attimestamp更新时间

表的关键约束:

  • 唯一键(app_id, menu_key, userid):同一用户在同一应用的同一菜单上只有一行角标记录,天然支持「绝对设置(幂等覆盖)」;
  • 索引userid:支撑「用户离职清空」「按用户拉取快照」这类按用户维度的高频查询。

值得注意的存储语义:app_badges仅存非清除态。当count=0 且 dot=false时该行会被直接删除,而不是写入一条「零值记录」,见 Badge.php 的 applySet:

// 清除态:一条 whereIn 删除 if ($count === 0 && !$dot) { AppBadge::whereAppId($appId)->whereMenuKey($menuKey)->whereIn('userid', $userids)->delete(); return; } // 非清除态:依赖唯一键 (app_id,menu_key,userid) 批量 upsert AppBadge::upsert($rows, ['app_id', 'menu_key', 'userid'], ['count', 'dot', 'updated_at']);

五、三个后端接口:set / clear / list

角标能力对外暴露为三个接口,全部定义在 AppsController 中,通过动态路由api/apps/{method}映射(见 routes/web.php):

5.1POST api/apps/badge/set:设置角标(应用密钥鉴权)

由插件服务端使用应用密钥调用,对(appid, 菜单, 每个 userid)绝对设置角标(幂等覆盖)。

参数类型说明
appidString应用 ID
secretString应用密钥(APP_SECRET)
useridNumber | Number[]目标用户 ID(单个或数组)
menu_keyString(可选)菜单稳定标识;留空表示该应用第一个菜单
countNumber(可选,默认 0)角标数字
dotBoolean(可选,默认 false)是否显示红点(count=0 且 dot=false 即清除)

该接口的完整执行链在 Badge::set:

  1. 参数校验:appid、secret非空,userid归一化为去重去零的整型数组;
  2. 密钥校验:Apps::appSecret($appId)取出该应用独立密钥,用hash_equals常量时间比较,避免时序攻击(见 Apps::appSecret)。APP_SECRET 与全局 APP_KEY 不同——每个已安装应用独立、唯一,持久化在docker/appstore/config/{appid}/config.yml的app_secret字段,首次需要时惰性生成;
  3. 菜单解析:resolveAppMenu校验应用已安装、菜单存在(见 Badge::resolveAppMenu),同时兼容两类来源——插件应用读取docker/appstore/apps/{appid}/{version}/config.yml的menu_items,自定义微应用读取microapp_menu系统设置(见 Apps::appMenuConfig);
  4. 可见性过滤:只保留对该应用菜单有可见权限的用户(见下文第六节);
  5. 落库 + 推送:applySet幂等写库,push通过 WebSocket 实时下发。

响应示例:

{ "ret": 1, "msg": "success", "data": { "appid": "approve", "menu_key": "todo", "count": 3, "dot": false, "affected": 25 } }

affected为实际被写入并推送的用户数——当过滤后目标用户为空时,接口直接返回affected: 0,不会产生任何写入。

5.2POST api/apps/badge/clear:清除角标(当前用户 token 鉴权)

供前端在badge_clear_on_open=true的菜单打开时调用,清除当前用户在该应用该菜单的角标:

参数类型说明
appidString应用 ID
menu_keyString(可选)菜单稳定标识;留空表示该应用第一个菜单

对应实现 Badge::clearForUser:按(app_id, menu_key, userid)删除该行,然后以count=0, dot=false的 payload 推送给该用户的所有在线端,保证多端一致。

5.3GET api/apps/badge/list:拉取自己全部角标(初始同步)

供前端初始同步:返回当前用户全部应用(插件 + 自定义微应用)的角标快照,数据结构为app_id => menu_key => {count, dot},与前端 store 的 map 结构一一对应(见 AppsController::badge__list)。

对应实现 Badge::userBadges 有一个值得一提的细节:返回快照时会过滤掉应用已不存在(卸载 / 自定义微应用被删除)的行,避免父级聚合统计残留脏数据;同时通过按app_id缓存存在性判定结果、一次性收集自定义微应用 id 集合,避免逐行重复读 yaml / 遍历设置项。

六、可见性:角标只发给对该应用菜单可见的用户

角标与 微应用权限 联动:角标只发给对该应用菜单可见的用户。过滤逻辑在 Badge::filterVisibleUserids:

  • 菜单visible包含all时,目标用户全部保留;
  • 否则按用户身份逐一调用Setting::isCustomMicroVisibleTo($visible, $user->isAdmin(), $userid)判定(见 Setting.php):admin仅管理员可见,具体 userid 则按白名单匹配。

可见性来源同样是菜单配置:插件应用取插件包config.yml中的应用级visible_to(默认all),自定义微应用取应用级visible_to(默认admin),菜单自身也可覆盖,见 Apps::readPluginMenus 与 Apps::mapMenuItem。

七、自动清零:本地先清,服务端持久化

当菜单设置了badge_clear_on_open: true,用户打开该菜单时:

  1. 前端立即本地清零:先更新本地 store,避免网络延迟造成角标残留闪烁;
  2. 调用接口持久化清除:同步请求apps/badge/clear;
  3. 多端实时推送保持一致:后端清除成功后推送给该用户的所有在线端。

前端实现见 MicroApps/index.vue 的 onOpen:

// 角标:打开 badge_clear_on_open=true 的菜单时自动清零(本地 + 服务端持久化) if (config.badge_clear_on_open === true && config.id) { const menuKey = typeof config.key === 'string' ? config.key : ''; this.$store.commit('appBadges/clearMenu', {appid: config.id, menu_key: menuKey}); this.$store.dispatch('call', { url: 'apps/badge/clear', method: 'post', data: {appid: config.id, menu_key: menuKey}, }).catch(() => {}); }

若badge_clear_on_open为false(默认),则角标的增减完全由插件通过badge/set接口控制,主程序永不自动清零。

八、实时性:WebSocket 增量推送 + 初始快照同步

角标的数据流分两条路径:

8.1 在线用户:WebSocket 实时推送

插件调用badge/set或用户调用badge/clear后,后端通过 Badge::push 向目标用户推送type: appBadge的消息:

PushTask::push([ 'userid' => $userids, 'msg' => [ 'type' => 'appBadge', 'data' => [ 'appid' => $appId, 'menu_key' => $menuKey, 'count' => $count, 'dot' => $dot, ], ], ], false);

PushTask::push依赖 Swoole 运行时绑定(见 PushTask)——非 Swoole 运行时(artisan/测试)无法推送,会直接跳过,与观察者模式的守卫逻辑一致。前端收到appBadge消息后,在 store/actions.js 中增量更新:

case "appBadge": commit("appBadges/set", msgDetail.data || {});

8.2 离线用户:进入时随初始数据同步

离线用户不会收到推送(PushTask::push的$retryOffline参数在角标场景传false,即仅投递、不补发离线)。离线用户下次进入时,由应用菜单接口在拉取微应用列表后顺带调用apps/badge/list完成角标初始同步,无需额外请求,见 store/actions.js 的 updateMicroAppsStatus:

// 角标初始同步:一次拉取当前用户全部应用(插件 + 自定义微应用)的角标快照 const {data: badgeMap} = await dispatch('call', {url: 'apps/badge/list'}); commit("appBadges/hydrateMap", badgeMap || {})

前端appBadgesstore 的完整结构(见 appBadges.js):

  • map[app_id][menu_key] = { count, dot },menu_key为空串表示该应用第一个菜单;
  • 无角标时返回冻结的{count: 0, dot: false}共享只读对象,避免 getter 每次调用新建对象;
  • hydrateMap快照整体初始化,只保留count > 0 || dot的非清除态;
  • set增量更新、clearMenu清除单个菜单、clearApp清除应用全部角标(卸载/更新时)。

九、生命周期:卸载清空与离职清空

角标的生命周期与「应用」和「用户」强绑定:

场景行为对应实现
应用被卸载该应用的全部角标清除Badge::clearByApp:AppBadge::whereAppId($appId)->delete()
用户离职该用户的全部角标清除Badge::clearByUser:AppBadge::whereUserid($userid)->delete()

同时,前端userBadges快照与hydrateMap都会过滤掉应用已不存在的行,保证卸载后父级「应用」入口的聚合统计不会残留脏数据。

十、边界与常见误区

结合概念文档的negative声明与源码实现,以下边界值得插件开发者注意:

  1. 角标真值归插件,主程序不自己产生数字——插件未接入则不会有角标;
  2. 角标不是消息未读数,具体含义由各插件自行定义;
  3. 自定义微应用菜单(纯外链)默认不会有角标,除非其后端接入了角标接口——即需要调用apps/badge/set,且自定义微应用也可在microapp_menu配置中为菜单声明key与badge_clear_on_open;
  4. 角标受菜单可见性过滤,只发给对该应用菜单可见的用户,即使接口传入了无权用户也会被过滤掉(affected会相应减少);
  5. 清除态不落库:count=0 且 dot=false即删除行,app_badges仅存非清除态;
  6. 离线不补发:实时推送仅投递给在线用户,离线用户在下次进入时通过apps/badge/list初始同步兜底。

十一、给插件开发者的接入清单

要将角标能力接入自己的插件,最小步骤为:

  1. 在插件包config.yml的menu_items中为需要角标的菜单声明key(可留空表示第一个菜单)与badge_clear_on_open;
  2. 插件服务端在业务产生未读/待办时,调用POST api/apps/badge/set,携带appid、secret(即 APP_SECRET,与主程序写入docker/appstore/config/{appid}/config.yml的app_secret一致)、userid(单个或数组)、menu_key、count/dot;
  3. 需要「打开即清零」时,在菜单配置中置badge_clear_on_open: true,前端会自动完成本地清零与apps/badge/clear调用;
  4. 无需额外处理多端一致性与离线同步——主程序通过 WebSocketappBadge消息实时推送,离线用户由apps/badge/list快照兜底。

相关阅读

  • 微应用整体概念:微应用是什么、与系统应用的区别
  • 自定义微应用菜单:microapp_menu配置、菜单属性与配置入口
  • 微应用权限:可见性过滤与登录态传递
  • 后端实现:Badge 业务模块、角标 API 控制器、AppBadge 模型、app_badges 表迁移
  • 前端实现:appBadges store、微应用组件 onOpen、角标消息处理与初始同步
  • 后端
  • 前端
  • 协同办公
  • 即时通讯
  • 企业应用

【免费下载链接】dootask

DooTask是一款开源在线项目任务管理工具,提供各类文档协作工具、在线思维导图、在线流程图、项目管理、任务分发、即时IM,文件管理等工具;同时消息功能使用非对称加密技术让你的沟通更安全。

项目地址:https://gitcode.com/gh_mirrors/do/dootask
点击查看免费下载

相关推荐

上一篇:如何用Mac Mouse Fix让普通鼠标变身Mac生产力利器
下一篇:nmrpflash完全指南:3分钟搞定Netgear路由器救砖的终极工具

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询