数据统计
控制台「数据统计」是一组 Tab 页面(系统概览、用户与组织、登录与安全、消息通知、文件存储、接口监控、开放平台)。本篇讲统计数据的口径设计——重点回答两个问题:统计如何按组织体系限定范围、如何按组织性质拆分呈现,以便您扩展新的统计指标。
1. 核心机制:resolveScope 数据域隔离
统计不走通用 @DataScope 引擎,而是服务层手工解析当前登录人的数据域(DashboardUserServiceImpl.resolveScope),再注入 SQL 参数:
static OrgScope resolveScope() {
Integer nature = ContextUtil.getCurrentTopCompanyNature(); // 当前登录人顶级公司的组织性质
if (nature == null || OrgNatureEnum.OPERATIONS.getCode().equals(nature)) {
return new OrgScope(true, nature, null, null); // 运营(或缺失)→ 全平台全量
}
Long topCompanyId = ContextUtil.getCurrentTopCompanyId();
String prefix = topCompanyId == null ? "/-1/%" : "/" + topCompanyId + "/%";
return new OrgScope(false, nature, topCompanyId, prefix); // 其他性质 → 本公司子树
}规则一句话:运营性质看全平台,其他性质只看自己顶级公司子树。两个兜底:nature 缺失按全量;topCompanyId 缺失按"查不到任何数据"(/-1/%)。
2. 按组织体系统计:两种 SQL 口径
① 用户口径(EXISTS 子查询):以 countByDayRange 为典型(UserMapper.java),统计方法普遍带 treePathPrefix 参数:
SELECT DATE_FORMAT(u.created_at, '%Y-%m-%d') AS date, COUNT(*) AS value
FROM mdc_user u
WHERE u.deleted_at = 0
AND EXISTS (SELECT 1 FROM mdc_user_org_rel r
JOIN mdc_org o ON o.id = r.org_id AND o.deleted_at = 0
WHERE r.user_id = u.id
AND o.tree_path LIKE #{treePathPrefix}) -- 组织树前缀过滤
GROUP BY date ORDER BY date ASC同样模式的还有 countUsersInScope(范围内总用户数)、countByState、countBySex、countNewUsersInMonth。
② 组织口径(contains 匹配):组织表自身过滤用 like "/{topCompanyId}/"(contains 语义,非前缀 %)——因为顶级公司自身及所有子孙的 treePath 都包含 /{id}/ 片段(baseOrgWrapper)。部门用户排行(OrgMapper.rankByUserCount)同样用此口径:
SELECT o.id, o.name, COUNT(our.user_id) AS value
FROM mdc_org o LEFT JOIN mdc_user_org_rel our ON our.org_id = o.id
WHERE o.deleted_at = 0 AND o.state = 1 AND o.tree_path LIKE #{treePathPrefix}
GROUP BY o.id, o.name ORDER BY value DESC, o.weight DESC LIMIT #{limit}3. 按组织性质统计
countByNature(UserMapper.java):mdc_user JOIN mdc_user_org_rel JOIN mdc_org,按 o.nature 分组,COUNT(DISTINCT u.id)——同一性质内用户去重,跨性质分别计入(一个用户同时挂在总公司和开发者平台时两边各计一次);无组织关系的用户不计入。
countByDayRangeGroupByNature:同口径加日期维度(GROUP BY date, o.nature),供运营视角的趋势图按性质拆曲线(固定顺序:总公司 → 开发者 → 运营)。
4. 「用户与组织」页面
后端前缀 /dashboard/user,接口与卡片对应:
| 卡片 | 接口 | 口径说明 |
|---|---|---|
| 用户总数/单位数/部门数/角色数 | GET /overview | 按数据域过滤;返回含当前登录人 nature,前端据此控制展示 |
| 用户增长趋势 | GET /trend?startDate&endDate | 默认近 30 天;运营 = 4 条曲线(总用户 + 3 个性质),其他性质 = 1 条"新增用户" |
| 部门用户排行 | GET /orgRank?limit | 默认 10,上限 100 |
| 角色用户排行 | GET /roleRank?limit | 非运营按 nature 过滤角色 |
| 用户状态分布 | GET /statusDistribution | 区间内新增用户的正常/禁用占比;日期清空=全部时间 |
| 用户性别分布 | GET /genderDistribution | 男/女/未知 |
| 用户类型分布 | GET /typeDistribution | 组织性质占比,仅运营性质可见(其他性质后端直接返回空) |
前端按 overview.nature === 99(isOperations)决定是否渲染「用户类型分布」卡片及趋势图布局——数据域由后端按当前登录人返回,前端无手动切换器。
5. 「系统概览」页面
纯指标卡页,前端并发调三个接口:
| 接口 | 内容 |
|---|---|
GET /console/dashboard/overview/console | 用户总数/组织总数/本月新增(同样走 resolveScope 数据域隔离)、文件总数/总容量/临时文件占用率、今日待办/预警/公告未读、消息成功率、接口成功率 |
GET /open/dashboard/open/overview | 应用总数、开放平台回调/API调用/事件推送成功率 |
GET /workbench/dashboard/overview/workbench | 今日登录等(异常时前端兜底为 0) |
6. 其余统计页速查
| 页面 | 后端前缀 | 主要指标 |
|---|---|---|
| 登录与安全 | /workbench/dashboard/login/* | 概览、地域分布(中国地图)、IP/活跃用户排行、浏览器/OS/登录方式/渠道分布、每日登录、时段分布 |
| 消息通知 | /console/dashboard/message/* | 概览、类型/分类分布、发送趋势、模板排行 |
| 文件存储 | /console/dashboard/file/* | 概览、类型/业务类型/存储平台/大小分布、增长趋势 |
| 接口监控 | /console/dashboard/monitor/*、/console/dashboard/requestLog/* | 成功率仪表盘、调用/失败排行、日志类型/地域/耗时分布、IP/接口排行 |
| 开放平台 | /open/dashboard/* | API 调用趋势、应用/API 排行、OAuth 授权分布、事件推送统计 |
7. 扩展新统计指标的要点
- 数据域隔离:复用
resolveScope()(或同模式),运营全量、其他性质传treePathPrefix;用户口径用 EXISTS 子查询、组织口径用like "/id/"contains 匹配,二选一按统计主体决定; - 按性质拆分:参考
countByNature的COUNT(DISTINCT 用户id)去重口径,并明确"跨性质是否重复计入"的语义; - 敏感指标的性质可见性:参考
typeDistribution的做法——非运营性质后端直接返回空(呈现层的 fail fast),不要只靠前端隐藏; - 手写 SQL 自行处理逻辑删除:统计 SQL 必须显式带
deleted_at = 0(注解 SQL 不经过 MyBatis-Flex 逻辑删除处理器); - 统计实时查库无缓存:现有统计接口均实时查库,若新增重量级统计请自行评估是否引入缓存,并注意与权限缓存(resource_api_all 等)的失效边界不同。