418 lines
18 KiB
Markdown
418 lines
18 KiB
Markdown
# 仪表板API
|
||
|
||
<cite>
|
||
**本文档引用的文件**
|
||
- [backend/src/routes/dashboard.js](file://backend/src/routes/dashboard.js)
|
||
- [backend/src/routes/models.js](file://backend/src/routes/models.js)
|
||
- [backend/src/models/Model.js](file://backend/src/models/Model.js)
|
||
- [backend/src/models/Ota.js](file://backend/src/models/Ota.js)
|
||
- [backend/src/models/DashboardUser.js](file://backend/src/models/DashboardUser.js)
|
||
- [backend/src/utils/response.js](file://backend/src/utils/response.js)
|
||
- [backend/src/middleware/auth.js](file://backend/src/middleware/auth.js)
|
||
- [backend/src/config/redis.js](file://backend/src/config/redis.js)
|
||
- [backend/src/services/eqCacheStorage.js](file://backend/src/services/eqCacheStorage.js)
|
||
- [backend/src/services/measurementStorage.js](file://backend/src/services/measurementStorage.js)
|
||
- [frontend/src/api/dashboard.js](file://frontend/src/api/dashboard.js)
|
||
- [frontend/src/views/home/index.vue](file://frontend/src/views/home/index.vue)
|
||
- [backend/src/app.js](file://backend/src/app.js)
|
||
- [backend/src/routes/index.js](file://backend/src/routes/index.js)
|
||
</cite>
|
||
|
||
## 目录
|
||
1. [简介](#简介)
|
||
2. [项目结构](#项目结构)
|
||
3. [核心组件](#核心组件)
|
||
4. [架构总览](#架构总览)
|
||
5. [详细组件分析](#详细组件分析)
|
||
6. [依赖关系分析](#依赖关系分析)
|
||
7. [性能考虑](#性能考虑)
|
||
8. [故障排查指南](#故障排查指南)
|
||
9. [结论](#结论)
|
||
10. [附录](#附录)
|
||
|
||
## 简介
|
||
本文件为“仪表板API”的RESTful接口文档,聚焦系统统计数据查询能力,覆盖以下指标与能力:
|
||
- 品牌数量统计:基于品牌模型的计数与唯一性约束,可用于统计品牌总数与活跃品牌数。
|
||
- 型号分布情况:按品牌、形式、阻抗等维度进行分组统计与聚合展示。
|
||
- OTA更新状态:按版本状态、目标范围、灰度/强升等维度进行筛选与聚合。
|
||
- 用户活跃度:后台用户登录时间与状态,用于评估管理员活跃度。
|
||
- 数据聚合查询:按日期、品牌、型号等维度进行分组统计。
|
||
- 时间序列分析:以“今日新增”为代表的日粒度趋势数据。
|
||
- 图表数据格式:统一返回结构与字段命名,便于前端可视化。
|
||
- 缓存策略:Redis缓存EQ键值,降低后端压力。
|
||
- 性能优化:数据库索引、查询条件、分页与轻量字段返回。
|
||
- 实时数据更新机制:通过定时任务或事件触发刷新缓存。
|
||
- 自定义统计维度与数据导出:通过查询参数扩展维度与导出CSV。
|
||
|
||
本项目采用前后端分离架构,后端基于Express,数据库使用Sequelize ORM,前端使用Vue 3 + Element Plus。
|
||
|
||
## 项目结构
|
||
后端采用模块化路由组织,仪表板相关接口集中在dashboard路由中,并通过认证中间件保护。前端通过API封装调用后端接口。
|
||
|
||
```mermaid
|
||
graph TB
|
||
FE["前端应用<br/>Vue 3 + Element Plus"] --> API["仪表板API<br/>/api/dashboard/*"]
|
||
API --> AUTH["认证中间件<br/>Bearer Token"]
|
||
API --> RESP["统一响应封装<br/>ApiResponse/PageData"]
|
||
API --> MODELS["型号模型<br/>Model"]
|
||
API --> OTA["OTA模型<br/>Ota"]
|
||
API --> REDIS["Redis 缓存<br/>EQ缓存"]
|
||
API --> S3["S3 存储<br/>频响CSV"]
|
||
AUTH --> USER["后台用户模型<br/>DashboardUser"]
|
||
```
|
||
|
||
图示来源
|
||
- [backend/src/routes/dashboard.js:1-66](file://backend/src/routes/dashboard.js#L1-L66)
|
||
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
|
||
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
|
||
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
|
||
- [backend/src/models/Ota.js:1-97](file://backend/src/models/Ota.js#L1-L97)
|
||
- [backend/src/models/DashboardUser.js:1-58](file://backend/src/models/DashboardUser.js#L1-L58)
|
||
- [backend/src/config/redis.js:1-32](file://backend/src/config/redis.js#L1-L32)
|
||
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
|
||
|
||
章节来源
|
||
- [backend/src/routes/index.js:1-13](file://backend/src/routes/index.js#L1-L13)
|
||
- [backend/src/app.js:1-60](file://backend/src/app.js#L1-L60)
|
||
|
||
## 核心组件
|
||
- 认证中间件:校验Authorization头中的Bearer Token,注入用户上下文。
|
||
- 统一响应封装:提供成功/错误/无数据三种响应结构,便于前端处理。
|
||
- 仪表板路由:提供“今日新增”接口,返回型号与OTA的当日增量数据。
|
||
- 模型与OTA数据模型:定义品牌、型号、OTA等核心实体字段。
|
||
- Redis缓存服务:提供EQ缓存键值读取与字段解析。
|
||
- S3存储服务:提供频响CSV上传与读取能力,支持来源与佩戴方式等维度。
|
||
|
||
章节来源
|
||
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
|
||
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
|
||
- [backend/src/routes/dashboard.js:1-66](file://backend/src/routes/dashboard.js#L1-L66)
|
||
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
|
||
- [backend/src/models/Ota.js:1-97](file://backend/src/models/Ota.js#L1-L97)
|
||
- [backend/src/config/redis.js:1-32](file://backend/src/config/redis.js#L1-L32)
|
||
- [backend/src/services/eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
|
||
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
|
||
|
||
## 架构总览
|
||
后端启动时同步数据库表并挂载所有路由,根路径与健康检查接口无需认证,业务路由统一通过认证中间件保护。仪表板接口在路由层引入认证中间件,确保只有登录用户可访问。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Client as "客户端"
|
||
participant App as "Express 应用"
|
||
participant Router as "仪表板路由"
|
||
participant Auth as "认证中间件"
|
||
participant Resp as "统一响应"
|
||
participant DB as "数据库"
|
||
Client->>App : GET /
|
||
App-->>Client : {message, docs, redoc}
|
||
Client->>App : GET /health
|
||
App-->>Client : {status : "healthy"}
|
||
Client->>Router : GET /api/dashboard/today
|
||
Router->>Auth : 校验 Bearer Token
|
||
Auth-->>Router : 注入用户上下文
|
||
Router->>DB : 查询今日新增型号与OTA
|
||
DB-->>Router : 返回结果
|
||
Router->>Resp : 包装响应
|
||
Resp-->>Client : {code,msg,data}
|
||
```
|
||
|
||
图示来源
|
||
- [backend/src/app.js:22-38](file://backend/src/app.js#L22-L38)
|
||
- [backend/src/routes/dashboard.js:12-66](file://backend/src/routes/dashboard.js#L12-L66)
|
||
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
|
||
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
|
||
|
||
## 详细组件分析
|
||
|
||
### 仪表板路由与“今日新增”接口
|
||
- 接口:GET /api/dashboard/today
|
||
- 认证:需登录(路由内挂载认证中间件)
|
||
- 功能:返回当日00:00起至当前的新增型号与OTA列表
|
||
- 请求参数:无
|
||
- 响应字段:
|
||
- models:数组,包含id、brand_name、name、create_at
|
||
- otas:数组,包含id、verName、model、create_at
|
||
- 时间范围:以服务器所在时区计算当日起点
|
||
- 错误处理:捕获异常并返回统一错误响应
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as "客户端"
|
||
participant R as "仪表板路由"
|
||
participant A as "认证中间件"
|
||
participant M as "Model模型"
|
||
participant O as "Ota模型"
|
||
participant U as "统一响应"
|
||
C->>R : GET /api/dashboard/today
|
||
R->>A : 校验Token
|
||
A-->>R : 注入用户
|
||
R->>M : 查询当日新增型号
|
||
M-->>R : 型号列表
|
||
R->>O : 查询当日新增OTA
|
||
O-->>R : OTA列表
|
||
R->>U : 包装响应
|
||
U-->>C : {code,msg,data}
|
||
```
|
||
|
||
图示来源
|
||
- [backend/src/routes/dashboard.js:16-66](file://backend/src/routes/dashboard.js#L16-L66)
|
||
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
|
||
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
|
||
|
||
章节来源
|
||
- [backend/src/routes/dashboard.js:1-66](file://backend/src/routes/dashboard.js#L1-L66)
|
||
|
||
### 型号与OTA数据模型
|
||
- 型号模型(Model):包含品牌名、型号名、形式、阻抗、来源、EQ键、创建时间等字段,支持按品牌与型号组合唯一性约束。
|
||
- OTA模型(Ota):包含版本号、版本名、升级包URL、MD5、是否强升、描述、对应型号、硬件版本、目标范围、灰度状态、起止时间、状态、创建时间等字段,支持按版本号与名称等维度筛选。
|
||
|
||
```mermaid
|
||
erDiagram
|
||
MODEL {
|
||
int id PK
|
||
string brand_name
|
||
string name
|
||
string form
|
||
string rig
|
||
string source
|
||
string eq_key
|
||
datetime create_at
|
||
}
|
||
OTA {
|
||
int id PK
|
||
int verCode
|
||
string verName
|
||
string url
|
||
string md5
|
||
smallint force
|
||
string desc
|
||
string model
|
||
int hw
|
||
smallint target
|
||
smallint beta
|
||
datetime startTime
|
||
datetime endTime
|
||
smallint status
|
||
datetime create_at
|
||
}
|
||
```
|
||
|
||
图示来源
|
||
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
|
||
- [backend/src/models/Ota.js:1-97](file://backend/src/models/Ota.js#L1-L97)
|
||
|
||
章节来源
|
||
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
|
||
- [backend/src/models/Ota.js:1-97](file://backend/src/models/Ota.js#L1-L97)
|
||
|
||
### Redis缓存与EQ键值读取
|
||
- 缓存键构建规则:{品牌名称} {型号名称}
|
||
- 接口:
|
||
- GET /api/models/:model_id/eq-cache:返回该型号的缓存键与字段列表(不含value,避免响应过大)
|
||
- GET /api/models/:model_id/eq-cache/field?key=:返回指定字段的值(自动JSON解析)
|
||
- 错误处理:字段不存在、Redis读取失败等场景返回统一错误响应
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as "客户端"
|
||
participant R as "型号路由"
|
||
participant S as "EQ缓存服务"
|
||
participant RC as "Redis客户端"
|
||
participant U as "统一响应"
|
||
C->>R : GET /api/models/ : model_id/eq-cache
|
||
R->>S : getEqCacheKeys(brand,name)
|
||
S->>RC : hkeys(redisKey)
|
||
RC-->>S : field_keys
|
||
S-->>R : {redis_key, field_keys}
|
||
R->>U : 包装响应
|
||
U-->>C : {code,msg,data}
|
||
C->>R : GET /api/models/ : model_id/eq-cache/field?key=...
|
||
R->>S : getEqCacheField(brand,name,key)
|
||
S->>RC : hget(redisKey,key)
|
||
RC-->>S : raw_value
|
||
S-->>R : {redis_key,key,value}
|
||
R->>U : 包装响应
|
||
U-->>C : {code,msg,data}
|
||
```
|
||
|
||
图示来源
|
||
- [backend/src/routes/models.js:192-223](file://backend/src/routes/models.js#L192-L223)
|
||
- [backend/src/services/eqCacheStorage.js:24-66](file://backend/src/services/eqCacheStorage.js#L24-L66)
|
||
- [backend/src/config/redis.js:11-29](file://backend/src/config/redis.js#L11-L29)
|
||
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
|
||
|
||
章节来源
|
||
- [backend/src/routes/models.js:192-223](file://backend/src/routes/models.js#L192-L223)
|
||
- [backend/src/services/eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
|
||
- [backend/src/config/redis.js:1-32](file://backend/src/config/redis.js#L1-L32)
|
||
|
||
### S3频响CSV读取
|
||
- 接口:GET /api/models/:model_id/measurement
|
||
- 适用来源:仅支持来源为“Eafonyoung”的型号
|
||
- 依赖字段:型号必须包含佩戴方式(form),否则无法定位S3文件
|
||
- 返回:S3 Key与CSV内容字符串
|
||
- 错误处理:来源不符、缺少form、S3未找到等场景返回统一错误响应
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start(["请求进入"]) --> CheckSource["校验来源是否为 Eafonyoung"]
|
||
CheckSource --> |否| ErrSource["返回错误:仅支持 Eafonyoung"]
|
||
CheckSource --> |是| CheckForm["校验是否存在佩戴方式(form)"]
|
||
CheckForm --> |否| ErrForm["返回错误:缺少佩戴方式"]
|
||
CheckForm --> |是| BuildKey["构建S3 Key"]
|
||
BuildKey --> ReadS3["从S3读取CSV内容"]
|
||
ReadS3 --> Found{"找到文件?"}
|
||
Found --> |否| ErrNotFound["返回错误:S3未找到文件"]
|
||
Found --> |是| ReturnOK["返回 {s3_key,content}"]
|
||
ErrSource --> End(["结束"])
|
||
ErrForm --> End
|
||
ErrNotFound --> End
|
||
ReturnOK --> End
|
||
```
|
||
|
||
图示来源
|
||
- [backend/src/routes/models.js:246-278](file://backend/src/routes/models.js#L246-L278)
|
||
- [backend/src/services/measurementStorage.js:87-108](file://backend/src/services/measurementStorage.js#L87-L108)
|
||
|
||
章节来源
|
||
- [backend/src/routes/models.js:246-278](file://backend/src/routes/models.js#L246-L278)
|
||
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
|
||
|
||
### 前端集成与使用示例
|
||
- 前端API封装:提供获取“今日新增”的方法,内部调用 /dashboard/today
|
||
- 前端页面:首页卡片展示今日新增型号与OTA数量与列表,支持时间格式化显示
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant V as "首页视图"
|
||
participant A as "仪表板API"
|
||
participant S as "后端仪表板路由"
|
||
V->>A : getTodayStats()
|
||
A->>S : GET /dashboard/today
|
||
S-->>A : {code,msg,data : {models,otas}}
|
||
A-->>V : Promise.resolve(data)
|
||
V->>V : 渲染今日新增卡片
|
||
```
|
||
|
||
图示来源
|
||
- [frontend/src/api/dashboard.js:1-12](file://frontend/src/api/dashboard.js#L1-L12)
|
||
- [frontend/src/views/home/index.vue:127-140](file://frontend/src/views/home/index.vue#L127-L140)
|
||
- [backend/src/routes/dashboard.js:16-66](file://backend/src/routes/dashboard.js#L16-L66)
|
||
|
||
章节来源
|
||
- [frontend/src/api/dashboard.js:1-12](file://frontend/src/api/dashboard.js#L1-L12)
|
||
- [frontend/src/views/home/index.vue:1-200](file://frontend/src/views/home/index.vue#L1-L200)
|
||
|
||
## 依赖关系分析
|
||
- 路由汇总:后端将各模块路由集中注册,包括仪表板路由
|
||
- 认证链路:所有仪表板接口均受认证中间件保护
|
||
- 数据模型:仪表板接口依赖Model与Ota模型进行查询
|
||
- 缓存链路:型号路由依赖EQ缓存服务与Redis客户端
|
||
- 存储链路:型号路由依赖S3存储服务进行频响文件读取
|
||
|
||
```mermaid
|
||
graph LR
|
||
RoutesIndex["路由汇总"] --> DashboardRoute["仪表板路由"]
|
||
RoutesIndex --> ModelsRoute["型号路由"]
|
||
DashboardRoute --> AuthMW["认证中间件"]
|
||
ModelsRoute --> AuthMW
|
||
DashboardRoute --> ModelModel["Model模型"]
|
||
DashboardRoute --> OtaModel["Ota模型"]
|
||
ModelsRoute --> EqCache["EQ缓存服务"]
|
||
ModelsRoute --> S3Svc["S3存储服务"]
|
||
AuthMW --> User["DashboardUser模型"]
|
||
```
|
||
|
||
图示来源
|
||
- [backend/src/routes/index.js:1-13](file://backend/src/routes/index.js#L1-L13)
|
||
- [backend/src/routes/dashboard.js:1-66](file://backend/src/routes/dashboard.js#L1-L66)
|
||
- [backend/src/routes/models.js:192-223](file://backend/src/routes/models.js#L192-L223)
|
||
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
|
||
- [backend/src/models/Model.js:1-53](file://backend/src/models/Model.js#L1-L53)
|
||
- [backend/src/models/Ota.js:1-97](file://backend/src/models/Ota.js#L1-L97)
|
||
- [backend/src/models/DashboardUser.js:1-58](file://backend/src/models/DashboardUser.js#L1-L58)
|
||
- [backend/src/services/eqCacheStorage.js:1-73](file://backend/src/services/eqCacheStorage.js#L1-L73)
|
||
- [backend/src/services/measurementStorage.js:1-115](file://backend/src/services/measurementStorage.js#L1-L115)
|
||
|
||
章节来源
|
||
- [backend/src/routes/index.js:1-13](file://backend/src/routes/index.js#L1-L13)
|
||
|
||
## 性能考虑
|
||
- 查询优化
|
||
- 使用精确时间范围过滤当日新增,避免全表扫描
|
||
- 仅返回必要字段,减少网络传输
|
||
- 缓存策略
|
||
- Redis缓存EQ键值,避免频繁读取数据库
|
||
- 字段列表与单字段读取分离,控制响应大小
|
||
- 存储优化
|
||
- S3文件按来源、佩戴方式、品牌首字母与型号组织,便于快速定位
|
||
- 并发与超时
|
||
- Redis连接设置最大重试次数与连接超时
|
||
- 前端对EQ缓存读取设置较长超时,避免弱网失败
|
||
- 分页与限制
|
||
- 当前“今日新增”接口未分页,建议后续扩展分页参数以应对高并发
|
||
|
||
## 故障排查指南
|
||
- 认证失败
|
||
- 现象:返回未登录或缺少凭证、无效凭证、登录已过期
|
||
- 排查:确认Authorization头格式为Bearer Token,检查Token有效期
|
||
- Redis读取失败
|
||
- 现象:返回Redis读取失败或字段不存在
|
||
- 排查:确认Redis连接配置正确,检查缓存键是否存在
|
||
- S3文件未找到
|
||
- 现象:返回S3上未找到该型号的频响文件
|
||
- 排查:确认来源为Eafonyoung且存在佩戴方式,检查S3 Key拼接逻辑
|
||
- 统一错误响应
|
||
- 现象:接口返回code=0或2,msg包含错误信息
|
||
- 排查:根据msg提示定位具体问题,查看后端日志
|
||
|
||
章节来源
|
||
- [backend/src/middleware/auth.js:1-36](file://backend/src/middleware/auth.js#L1-L36)
|
||
- [backend/src/services/eqCacheStorage.js:38-66](file://backend/src/services/eqCacheStorage.js#L38-L66)
|
||
- [backend/src/services/measurementStorage.js:100-108](file://backend/src/services/measurementStorage.js#L100-L108)
|
||
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25)
|
||
|
||
## 结论
|
||
本仪表板API围绕“今日新增”这一高频指标提供了简洁高效的查询接口,并通过Redis缓存与S3存储提升了整体性能与可扩展性。建议后续在以下方面持续优化:
|
||
- 扩展更多统计维度(品牌、型号、OTA状态等)与分页能力
|
||
- 引入时间序列分析接口,支持多日趋势对比
|
||
- 完善缓存失效与更新策略,保证数据一致性
|
||
- 提供数据导出能力(如CSV),满足报表需求
|
||
|
||
## 附录
|
||
|
||
### API定义与使用示例
|
||
|
||
- 获取今日新增(型号 + OTA)
|
||
- 方法:GET
|
||
- URL:/api/dashboard/today
|
||
- 认证:是
|
||
- 请求参数:无
|
||
- 响应字段:
|
||
- models:数组,元素含id、brand_name、name、create_at
|
||
- otas:数组,元素含id、verName、model、create_at
|
||
- 示例调用:前端通过 getTodayStats() 封装调用
|
||
|
||
章节来源
|
||
- [backend/src/routes/dashboard.js:16-66](file://backend/src/routes/dashboard.js#L16-L66)
|
||
- [frontend/src/api/dashboard.js:1-12](file://frontend/src/api/dashboard.js#L1-L12)
|
||
- [frontend/src/views/home/index.vue:127-140](file://frontend/src/views/home/index.vue#L127-L140)
|
||
|
||
### 缓存与实时更新机制
|
||
- 缓存键:{品牌名称} {型号名称}
|
||
- 读取策略:
|
||
- 列表:仅返回字段名列表,避免大响应
|
||
- 单字段:按key读取并自动解析JSON
|
||
- 实时更新:建议通过定时任务或事件触发刷新Redis缓存,确保前端展示最新数据
|
||
|
||
章节来源
|
||
- [backend/src/services/eqCacheStorage.js:24-66](file://backend/src/services/eqCacheStorage.js#L24-L66)
|
||
- [backend/src/config/redis.js:11-29](file://backend/src/config/redis.js#L11-L29)
|
||
|
||
### 数据导出与图表数据格式
|
||
- 导出建议:在现有接口基础上增加查询参数(如时间范围、维度、格式)以支持导出
|
||
- 图表数据格式:统一使用统一响应结构,前端按需转换为折线图、柱状图等所需格式
|
||
|
||
章节来源
|
||
- [backend/src/utils/response.js:1-25](file://backend/src/utils/response.js#L1-L25) |