25 KiB
25 KiB
数据库设计
**本文引用的文件** - [backend/src/models/Brand.js](file://backend/src/models/Brand.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/models/ShareCodeLog.js](file://backend/src/models/ShareCodeLog.js) - [backend/src/models/index.js](file://backend/src/models/index.js) - [backend/src/config/database.js](file://backend/src/config/database.js) - [backend/src/validators/brand.js](file://backend/src/validators/brand.js) - [backend/src/validators/model.js](file://backend/src/validators/model.js) - [backend/src/validators/ota.js](file://backend/src/validators/ota.js) - [backend/src/services/eqCacheStorage.js](file://backend/src/services/eqCacheStorage.js) - [backend/src/services/otaStorage.js](file://backend/src/services/otaStorage.js) - [backend/src/routes/brands.js](file://backend/src/routes/brands.js) - [backend/src/routes/models.js](file://backend/src/routes/models.js) - [backend/src/routes/ota.js](file://backend/src/routes/ota.js)目录
简介
本文件系统性梳理后端数据库模型与相关数据访问模式,覆盖实体关系、字段定义、数据类型、主键/外键、索引与约束、数据验证与业务规则,并给出数据库模式图、示例数据、缓存策略、性能优化建议、数据生命周期与归档策略、迁移与版本管理思路,以及数据安全与访问控制要点。重点实体包括 Brand、Model、Ota、DashboardUser、ShareCodeLog。
项目结构
- 数据模型采用 Sequelize ORM 定义,位于 backend/src/models,统一通过 backend/src/models/index.js 暴露。
- 数据库连接在 backend/src/config/database.js 中配置,使用 MySQL,关闭默认时间戳与表名冻结,便于与现有表结构对齐。
- 校验层使用 Zod 在 backend/src/validators 下定义,确保入参合法性。
- 业务访问通过 Express 路由在 backend/src/routes 下实现,结合服务层完成复杂流程(如 OTA 包上传、S3 存储、Redis 缓存读取等)。
graph TB
subgraph "模型层"
M_Brand["Brand<br/>品牌"]
M_Model["Model<br/>型号"]
M_Ota["Ota<br/>OTA 版本"]
M_User["DashboardUser<br/>后台用户"]
M_Share["ShareCodeLog<br/>分享码日志"]
end
subgraph "配置与入口"
C_DB["database.js<br/>MySQL 连接"]
I_Index["models/index.js<br/>模型导出"]
end
subgraph "校验层"
V_Brand["validators/brand.js"]
V_Model["validators/model.js"]
V_Ota["validators/ota.js"]
end
subgraph "服务层"
S_Eq["eqCacheStorage.js<br/>Redis EQ 缓存"]
S_Ota["otaStorage.js<br/>OTA 存储(S3/本地)"]
end
subgraph "路由层"
R_Brand["routes/brands.js"]
R_Model["routes/models.js"]
R_Ota["routes/ota.js"]
end
C_DB --> M_Brand
C_DB --> M_Model
C_DB --> M_Ota
C_DB --> M_User
C_DB --> M_Share
I_Index --> M_Brand
I_Index --> M_Model
I_Index --> M_Ota
I_Index --> M_User
I_Index --> M_Share
V_Brand --> R_Brand
V_Model --> R_Model
V_Ota --> R_Ota
R_Model --> S_Eq
R_Ota --> S_Ota
图表来源
- backend/src/config/database.js:1-24
- backend/src/models/index.js:1-8
- backend/src/models/Brand.js:1-23
- backend/src/models/Model.js:1-53
- backend/src/models/Ota.js:1-97
- backend/src/models/DashboardUser.js:1-58
- backend/src/models/ShareCodeLog.js:1-60
- backend/src/validators/brand.js:1-12
- backend/src/validators/model.js:1-22
- backend/src/validators/ota.js:1-36
- backend/src/services/eqCacheStorage.js:1-73
- backend/src/services/otaStorage.js:1-113
- backend/src/routes/brands.js:1-147
- backend/src/routes/models.js:1-569
- backend/src/routes/ota.js:1-292
章节来源
核心组件
本节聚焦五个核心实体的字段、类型、约束与业务含义。
-
品牌 Brand
- 字段与类型:id(INTEGER, 主键, 自增)、name(STRING(100), 唯一, 非空)
- 约束:唯一索引(name)
- 业务规则:品牌名称唯一;用于型号归属
- 参考路径:Brand 模型定义:1-23
-
型号 Model
- 字段与类型:id(INTEGER, 主键, 自增)、brand_name(STRING(100), 非空)、name(STRING(100), 非空)、form(STRING(100))、rig(STRING(100))、source(STRING(100))、eq_key(STRING(255))、create_at(DATE, 默认 NOW)
- 约束:无显式外键;但业务上以 brand_name 关联品牌
- 业务规则:同一品牌下型号名称唯一;create_at 默认当前时间
- 参考路径:Model 模型定义:1-53
-
OTA 版本 Ota
- 字段与类型:id(INTEGER, 主键, 自增)、verCode(INTEGER, 非空)、verName(STRING(20), 非空)、url(STRING(255), 非空)、md5(STRING(32), 非空)、force(SMALLINT, 默认0)、desc(STRING(255))、model(STRING(100))、hw(INTEGER, 默认0)、target(SMALLINT, 默认0)、beta(SMALLINT, 默认0)、startTime/endTime(DATE)、status(SMALLINT, 默认1)、create_at(DATE, 默认 NOW)
- 约束:verCode+model 组合唯一(业务逻辑保证)
- 业务规则:verCode 必须大于当前设备版本才视为可用升级;target/beta 控制定向/灰度;status=1 表示可用
- 参考路径:Ota 模型定义:1-97
-
后台用户 DashboardUser
- 字段与类型:id(INTEGER, 主键, 自增)、username(STRING(64), 唯一, 非空)、password_hash(STRING(255), 非空)、is_super_admin(TINYINT, 默认0)、status(TINYINT, 默认1)、last_login_at(DATE)、create_at/update_at(DATE, 默认 NOW)
- 约束:username 唯一
- 业务规则:is_super_admin=1 表示超级管理员;status=1 启用
- 参考路径:DashboardUser 模型定义:1-58
-
分享码日志 ShareCodeLog
- 字段与类型:id(INTEGER, 主键, 自增)、mac_addr(STRING(17), 非空)、share_code(CHAR(5), 非空)、action(ENUM('export','import'), 非空)、ip_addr(STRING(45), 默认'')、eq_data(JSON, 非空)、expire_at(DATE)、create_at(DATE, 默认 NOW)
- 索引:idx_mac_addr、idx_share_code、idx_create_at
- 业务规则:记录导出/导入分享码的操作明细;expire_at 与导出快照关联
- 参考路径:ShareCodeLog 模型定义:1-60
章节来源
- backend/src/models/Brand.js:1-23
- backend/src/models/Model.js:1-53
- backend/src/models/Ota.js:1-97
- backend/src/models/DashboardUser.js:1-58
- backend/src/models/ShareCodeLog.js:1-60
架构总览
数据库层采用 MySQL,ORM 层为 Sequelize。路由层负责请求接入与参数校验,服务层封装外部存储(S3、本地文件系统、Redis)与业务逻辑。核心实体间的关系如下:
erDiagram
BRAND {
int id PK
varchar name UK
}
MODEL {
int id PK
varchar brand_name
varchar name
varchar form
varchar rig
varchar source
varchar eq_key
datetime create_at
}
OTA {
int id PK
int verCode
varchar verName
varchar url
char md5
smallint force
varchar desc
varchar model
int hw
smallint target
smallint beta
datetime startTime
datetime endTime
smallint status
datetime create_at
}
DASHBOARD_USER {
int id PK
varchar username UK
varchar password_hash
tinyint is_super_admin
tinyint status
datetime last_login_at
datetime create_at
datetime update_at
}
SHARE_CODE_LOG {
int id PK
varchar mac_addr
char share_code
enum action
varchar ip_addr
json eq_data
datetime expire_at
datetime create_at
}
MODEL }o--|| BRAND : "brand_name 关联"
OTA ||--o{ OTA : "同 model 的多版本"
DASHBOARD_USER ||--o{ SHARE_CODE_LOG : "操作记录"
图表来源
- backend/src/models/Brand.js:1-23
- backend/src/models/Model.js:1-53
- backend/src/models/Ota.js:1-97
- backend/src/models/DashboardUser.js:1-58
- backend/src/models/ShareCodeLog.js:1-60
详细组件分析
品牌 Brand
- 设计理念:最小化品牌实体,仅保留 id 与 name,确保品牌唯一性,降低跨表关联复杂度。
- 关系映射:被 Model 以 brand_name 文本字段引用,业务上保持一致性。
- 索引与约束:name 唯一;无外键约束(文本引用)。
- 参考路径:Brand 模型定义:1-23
章节来源
型号 Model
- 设计理念:集中存储型号元信息与 EQ 快照键,便于搜索与展示;频响文件通过外部存储(S3/本地)管理。
- 关系映射:与 Brand 通过 brand_name 文本关联;与 EQ 缓存通过 eq_key 或 Redis key 约定关联。
- 索引与约束:无外键;同一品牌内型号名称唯一(业务约束)。
- 数据访问模式:
- 列表/详情:路由层提供分页、排序、过滤与详情查询。
- EQ 缓存:通过服务层读取 Redis Hash 字段列表与具体值。
- 测量文件:仅允许特定来源与佩戴方式,且通过 S3 获取 CSV 内容。
- 参考路径:
sequenceDiagram
participant Client as "客户端"
participant Route as "models 路由"
participant Model as "Model 模型"
participant EqSvc as "eqCacheStorage 服务"
participant Redis as "Redis"
Client->>Route : "GET /api/models/ : model_id/eq-cache"
Route->>Model : "findByPk(modelId)"
Model-->>Route : "型号对象"
Route->>EqSvc : "getEqCacheKeys(brand_name, name)"
EqSvc->>Redis : "hkeys(redisKey)"
Redis-->>EqSvc : "field_keys"
EqSvc-->>Route : "{redis_key, field_keys}"
Route-->>Client : "返回字段列表"
图表来源
章节来源
- backend/src/models/Model.js:1-53
- backend/src/routes/models.js:133-569
- backend/src/services/eqCacheStorage.js:1-73
OTA 版本 Ota
- 设计理念:以 verCode 为主键语义(实际仍由数据库自增 id 保证唯一),verCode+model 组合唯一,确保按版本与设备维度的幂等管理。
- 关系映射:无外键;通过 model 字段标识目标设备型号。
- 数据访问模式:
- 上传升级包:根据设备型号选择 S3 或本地存储,计算 MD5 并生成下载地址。
- 查询最新可用版本:按 currentVerCode、model、hw 过滤,取最大 verCode。
- 参考路径:
sequenceDiagram
participant Device as "设备端"
participant Route as "ota 路由"
participant Ota as "Ota 模型"
participant Store as "otaStorage 服务"
Device->>Route : "GET /api/ota/latest/check?currentVerCode&model&hw"
Route->>Ota : "findOne({status=1, verCode>current, model, hw})"
Ota-->>Route : "最新 OTA 记录"
Route-->>Device : "返回升级信息"
图表来源
章节来源
- backend/src/models/Ota.js:1-97
- backend/src/routes/ota.js:1-292
- backend/src/services/otaStorage.js:1-113
后台用户 DashboardUser
- 设计理念:集中管理后台账号,支持超级管理员与启用/禁用状态,记录最近登录时间。
- 访问控制:所有品牌/型号/OTA 接口均需登录中间件保护。
- 参考路径:
章节来源
- backend/src/models/DashboardUser.js:1-58
- backend/src/routes/brands.js:11-147
- backend/src/routes/models.js:70-569
- backend/src/routes/ota.js:21-292
分享码日志 ShareCodeLog
- 设计理念:记录分享码导出/导入行为,包含 MAC、分享码、操作类型、IP、EQ 快照与过期时间。
- 索引策略:针对高频查询字段建立索引,提升筛选效率。
- 参考路径:
章节来源
依赖分析
- 模型依赖:各模型通过 Sequelize 连接 MySQL;models/index.js 统一导出,供路由层引用。
- 校验依赖:路由层在写入操作前使用 Zod Schema 校验请求体,减少脏数据进入数据库。
- 服务依赖:路由层调用服务层完成外部存储与缓存交互,解耦业务逻辑与基础设施。
- 外部依赖:S3 SDK、Redis 客户端、Meilisearch HTTP 客户端。
graph LR
R_B["brands.js"] --> M_B["Brand.js"]
R_M["models.js"] --> M_M["Model.js"]
R_O["ota.js"] --> M_O["Ota.js"]
R_M --> S_Eq["eqCacheStorage.js"]
R_O --> S_Ota["otaStorage.js"]
V_B["validators/brand.js"] --> R_B
V_Mod["validators/model.js"] --> R_M
V_O["validators/ota.js"] --> R_O
图表来源
- backend/src/routes/brands.js:1-147
- backend/src/routes/models.js:1-569
- backend/src/routes/ota.js:1-292
- backend/src/models/Brand.js:1-23
- backend/src/models/Model.js:1-53
- backend/src/models/Ota.js:1-97
- backend/src/validators/brand.js:1-12
- backend/src/validators/model.js:1-22
- backend/src/validators/ota.js:1-36
- backend/src/services/eqCacheStorage.js:1-73
- backend/src/services/otaStorage.js:1-113
章节来源
性能考量
- 查询性能
- Model 列表:支持按 brand_name/name 过滤与按 id/create_at 排序,建议在相应列建立索引以优化分页查询。
- ShareCodeLog:已建立 idx_mac_addr、idx_share_code、idx_create_at,满足常见筛选场景。
- 缓存策略
- EQ 缓存:使用 Redis Hash 存储型号 EQ 数据,提供字段列表与单字段读取接口,避免一次性传输大体积 JSON。
- OTA 包:X8 使用 S3,X9 使用本地目录,结合 MD5 前缀组织目录,便于快速定位与去重。
- IO 与网络
- 测量文件与 OTA 包上传/下载涉及大量 IO 与网络,建议:
- 限制上传文件大小与格式;
- 使用内存存储配合流式处理;
- 对热点数据进行本地缓存或 CDN 加速。
- 测量文件与 OTA 包上传/下载涉及大量 IO 与网络,建议:
- 时间字段
- create_at/update_at 默认使用数据库时间,注意时区与同步问题。
章节来源
- backend/src/models/ShareCodeLog.js:51-56
- backend/src/services/eqCacheStorage.js:1-73
- backend/src/services/otaStorage.js:1-113
- backend/src/routes/models.js:133-181
- backend/src/routes/ota.js:107-143
故障排查指南
- 品牌/型号重复
- 现象:创建/更新时报“已存在”。
- 排查:确认品牌名称或型号组合是否已在数据库中存在。
- 参考路径:
- OTA 版本冲突
- 现象:创建/更新时报“该版本已存在”。
- 排查:verCode+model 组合唯一,检查是否已有相同版本。
- 参考路径:
- Redis 缓存异常
- 现象:读取 EQ 缓存报错或字段不存在。
- 排查:确认 Redis 连接、key 是否存在、字段是否正确。
- 参考路径:
- S3 上传失败
- 现象:OTA 包上传到 S3 失败。
- 排查:检查 AWS 凭证、Bucket 权限、区域设置。
- 参考路径:
- 登录态缺失
- 现象:访问受保护接口返回错误。
- 排查:确认鉴权中间件是否生效、Token 是否有效。
- 参考路径:
章节来源
- backend/src/routes/brands.js:68-72
- backend/src/routes/models.js:313-317
- backend/src/routes/ota.js:175-181
- backend/src/routes/ota.js:221-228
- backend/src/services/eqCacheStorage.js:38-66
- backend/src/services/otaStorage.js:95-98
- backend/src/routes/brands.js:12-12
- backend/src/routes/ota.js:105-105
- backend/src/routes/models.js:71-71
结论
本数据库设计以简洁的实体与清晰的业务边界为核心,通过校验层与服务层实现输入约束与外部集成解耦。Model 与 Brand 通过文本字段关联,Ota 以 verCode+model 组合唯一,ShareCodeLog 提供完整审计轨迹。建议后续完善外键约束、补充索引与分区策略,并制定数据生命周期与归档规范。
附录
数据验证与业务规则摘要
- 品牌
- 名称必填且唯一
- 参考路径:品牌校验:1-12
- 型号
- 品牌名、型号名必填;同一品牌下型号名唯一
- 参考路径:型号校验:1-22
- OTA
- verCode 整数;verName、url、md5 长度限制;force/target/beta/status 0/1 限定
- 参考路径:OTA 校验:1-36
章节来源
- backend/src/validators/brand.js:1-12
- backend/src/validators/model.js:1-22
- backend/src/validators/ota.js:1-36
示例数据
- 品牌 Brand
- id: 1, name: "Luxsin"
- 型号 Model
- id: 1, brand_name: "Luxsin", name: "X8", form: "in-ear", rig: "32Ω", source: "Eafonyoung", eq_key: "luxsin_x8_eq", create_at: "2025-01-01 12:00:00"
- OTA 版本 Ota
- id: 1, verCode: 101, verName: "v1.0.1", url: "http://.../LUXSIN_X8.PKG", md5: "d41d8cd98f00b204e9800998ecf8427e", force: 1, model: "Luxsin-X8", hw: 1, target: 0, beta: 0, status: 1, create_at: "2025-01-01 12:00:00"
- 后台用户 DashboardUser
- id: 1, username: "admin", password_hash: "
2b...", is_super_admin: 1, status: 1, create_at: "2025-01-01 12:00:00", update_at: "2025-01-01 12:00:00"
- id: 1, username: "admin", password_hash: "
- 分享码日志 ShareCodeLog
- id: 1, mac_addr: "AA:BB:CC:DD:EE:FF", share_code: "ABCDE", action: "export", ip_addr: "::ffff:127.0.0.1", eq_data: "{}", create_at: "2025-01-01 12:00:00"
数据访问模式与缓存策略
- Model
- 列表/详情:分页、过滤、排序
- EQ 缓存:字段列表与单字段读取
- 参考路径:models 路由:133-569, eqCacheStorage:1-73
- OTA
- 上传包:S3 或本地存储,返回下载地址
- 最新版本查询:按 verCode 与 hw 过滤
- 参考路径:ota 路由:24-102, otaStorage:1-113
章节来源
- backend/src/routes/models.js:133-569
- backend/src/services/eqCacheStorage.js:1-73
- backend/src/routes/ota.js:24-102
- backend/src/services/otaStorage.js:1-113
数据生命周期、保留策略与归档规则
- 建议
- ShareCodeLog:按月清理历史日志,保留必要审计周期(如 90 天)。
- OTA:旧版本保留 3-6 个月或按产品策略归档;S3/本地定期清理过期包。
- Model:测量文件与 EQ 缓存按访问频率与容量阈值进行轮转。
- 实施要点
- 为 ShareCodeLog 增加 expire_at 字段与自动清理任务。
- 为 OTA 增加归档标记与版本保留策略。
- 对 S3/本地存储建立配额与过期清理机制。
[本节为通用建议,不直接分析具体文件]
数据迁移路径与版本管理
- 建议
- 使用数据库迁移工具(如 Sequelize CLI)管理结构变更。
- 新增字段采用非空默认值或分阶段上线,避免影响在线服务。
- 对于索引与约束,先添加再回填数据,最后替换为严格约束。
- 参考路径
章节来源
数据安全、隐私与访问控制
- 访问控制
- 所有受保护接口均需登录中间件;超级管理员具备更高权限。
- 参考路径:brands/ota/models 路由中间件:12-12, backend/src/routes/ota.js#L105-L105:105-105, backend/src/routes/models.js:71-71
- 数据脱敏
- 日志中避免输出敏感字段(如密码哈希);对外接口仅返回必要字段。
- 合规
- 对用户数据与操作日志遵循最小化原则,遵守隐私政策与数据保留期限。
章节来源