Files
app-api/.qoder/repowiki/zh/content/核心模块/数据模型.md
T
yangy 665397ede7 feat(ota): 增强设备管理与OTA功能模块
- 新增OTA固件升级处理器,支持固件升级请求及黑名单过滤
- 引入设备持久化仓储,支持设备信息上报与活动记录
- 扩展数据模型,新增UserDevice与UserActive,支持设备版本跟踪
- 实现Redis到MySQL的异步数据同步任务
- 更新路由配置,集成新的API端点以支持OTA功能
- 优化架构图,反映新增的数据流与处理流程
2026-05-31 09:52:11 +08:00

896 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据模型
<cite>
**本文引用的文件**
- [internal/model/brand.go](file://internal/model/brand.go)
- [internal/model/model.go](file://internal/model/model.go)
- [internal/model/ota.go](file://internal/model/ota.go)
- [internal/model/user_device.go](file://internal/model/user_device.go)
- [internal/model/user_active.go](file://internal/model/user_active.go)
- [internal/repository/brand.go](file://internal/repository/brand.go)
- [internal/repository/model.go](file://internal/repository/model.go)
- [internal/repository/ota.go](file://internal/repository/ota.go)
- [internal/repository/device.go](file://internal/repository/device.go)
- [internal/handler/brand.go](file://internal/handler/brand.go)
- [internal/handler/model.go](file://internal/handler/model.go)
- [internal/handler/model_list.go](file://internal/handler/model_list.go)
- [internal/handler/ota.go](file://internal/handler/ota.go)
- [internal/handler/device.go](file://internal/handler/device.go)
- [internal/router/router.go](file://internal/router/router.go)
- [internal/config/database.go](file://internal/config/database.go)
- [internal/response/response.go](file://internal/response/response.go)
- [internal/task/device_persist.go](file://internal/task/device_persist.go)
- [pkg/encode/base64.go](file://pkg/encode/base64.go)
- [sql/model.sql](file://sql/model.sql)
- [sql/ota.sql](file://sql/ota.sql)
- [sql/user_device.sql](file://sql/user_device.sql)
- [sql/user_active.sql](file://sql/user_active.sql)
- [sql/ota_target_device.sql](file://sql/ota_target_device.sql)
- [cmd/server/main.go](file://cmd/server/main.go)
</cite>
## 更新摘要
**变更内容**
- 新增OTA固件升级相关模型和功能模块
- 新增用户设备跟踪模型和活动记录模型
- 新增设备报告接口和异步持久化任务
- 扩展路由配置以支持新的API端点
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向 Luxsin 应用 API 的数据模型,系统性梳理 Brand、Model、OTA、UserDevice 和 UserActive 五类数据模型的设计理念、字段定义、约束条件、业务含义以及与数据库表结构的映射关系。文档还覆盖 ORM 映射配置(基于原生 sql.DB 的扫描映射)、模型验证与序列化机制、模型在各层之间的传递方式与性能考量,并给出扩展、版本管理与向后兼容的最佳实践建议。内容兼顾初学者与高级开发者,既提供高层概览,也包含代码级的可视化图示与来源标注。
## 项目结构
该项目采用分层架构:路由层负责请求入口与中间件;处理器层处理业务逻辑与参数解析;仓库层封装数据库访问;模型层承载数据结构;响应层统一返回格式;编码层提供可选的响应体压缩编码;配置层加载数据库等外部服务配置。新增的OTA固件升级、用户设备跟踪和设备活动记录功能进一步完善了系统的数据模型体系。
```mermaid
graph TB
subgraph "应用入口"
MAIN["cmd/server/main.go"]
end
subgraph "路由层"
ROUTER["internal/router/router.go"]
end
subgraph "处理器层"
BRAND_H["internal/handler/brand.go"]
MODEL_H["internal/handler/model.go"]
MODEL_LIST_H["internal/handler/model_list.go"]
OTA_H["internal/handler/ota.go"]
DEVICE_H["internal/handler/device.go"]
end
subgraph "仓库层"
BRAND_R["internal/repository/brand.go"]
MODEL_R["internal/repository/model.go"]
OTA_R["internal/repository/ota.go"]
DEVICE_R["internal/repository/device.go"]
end
subgraph "模型层"
MODEL_M["internal/model/brand.go"]
MODEL_M2["internal/model/model.go"]
MODEL_M3["internal/model/ota.go"]
MODEL_M4["internal/model/user_device.go"]
MODEL_M5["internal/model/user_active.go"]
end
subgraph "响应与编码"
RESP["internal/response/response.go"]
ENCODE["pkg/encode/base64.go"]
end
subgraph "配置与数据库"
DB_CFG["internal/config/database.go"]
DB_SQL["sql/*.sql"]
end
subgraph "任务处理"
TASK["internal/task/device_persist.go"]
end
MAIN --> ROUTER
ROUTER --> BRAND_H
ROUTER --> MODEL_H
ROUTER --> MODEL_LIST_H
ROUTER --> OTA_H
ROUTER --> DEVICE_H
BRAND_H --> BRAND_R
MODEL_H --> MODEL_R
MODEL_LIST_H --> MODEL_R
OTA_H --> OTA_R
DEVICE_H --> DEVICE_R
BRAND_R --> MODEL_M
MODEL_R --> MODEL_M2
OTA_R --> MODEL_M3
DEVICE_R --> MODEL_M4
DEVICE_R --> MODEL_M5
BRAND_H --> RESP
MODEL_H --> RESP
MODEL_LIST_H --> RESP
OTA_H --> RESP
DEVICE_H --> RESP
DEVICE_H --> ENCODE
TASK --> DEVICE_R
DB_CFG --> DB_SQL
```
**图表来源**
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
- [internal/router/router.go:14-56](file://internal/router/router.go#L14-L56)
- [internal/handler/brand.go:19-50](file://internal/handler/brand.go#L19-L50)
- [internal/handler/model.go:19-51](file://internal/handler/model.go#L19-L51)
- [internal/handler/model_list.go:19-57](file://internal/handler/model_list.go#L19-L57)
- [internal/handler/ota.go:14-133](file://internal/handler/ota.go#L14-L133)
- [internal/handler/device.go:15-83](file://internal/handler/device.go#L15-L83)
- [internal/repository/brand.go:16-51](file://internal/repository/brand.go#L16-L51)
- [internal/repository/model.go:16-95](file://internal/repository/model.go#L16-L95)
- [internal/repository/ota.go:11-159](file://internal/repository/ota.go#L11-L159)
- [internal/repository/device.go:11-88](file://internal/repository/device.go#L11-L88)
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
- [internal/model/ota.go:5-43](file://internal/model/ota.go#L5-L43)
- [internal/model/user_device.go:5-12](file://internal/model/user_device.go#L5-L12)
- [internal/model/user_active.go:5-13](file://internal/model/user_active.go#L5-L13)
- [internal/response/response.go:9-37](file://internal/response/response.go#L9-L37)
- [pkg/encode/base64.go:21-52](file://pkg/encode/base64.go#L21-L52)
- [internal/config/database.go:17-72](file://internal/config/database.go#L17-L72)
- [sql/model.sql:20-38](file://sql/model.sql#L20-L38)
- [sql/ota.sql:23-44](file://sql/ota.sql#L23-L44)
- [sql/user_device.sql:23-32](file://sql/user_device.sql#L23-L32)
- [sql/user_active.sql:23-32](file://sql/user_active.sql#L23-L32)
- [sql/ota_target_device.sql:23-31](file://sql/ota_target_device.sql#L23-L31)
- [internal/task/device_persist.go:14-167](file://internal/task/device_persist.go#L14-L167)
**章节来源**
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
- [internal/router/router.go:14-56](file://internal/router/router.go#L14-L56)
## 核心组件
- **传统模型层(Model**
- Brand:承载品牌标识与名称,用于品牌列表查询。
- Model:承载型号信息,包含品牌名、型号名、形态、设备类型、来源、等价键、创建时间等。
- **OTA固件升级模型层**
- OTA:固件升级记录,包含版本信息、下载地址、MD5校验、强制升级标志、灰度发布状态、定向升级配置等。
- BlackList:OTA黑名单,记录被禁止升级的设备MAC地址。
- OTATargetDeviceOTA定向设备,记录允许特定设备升级的白名单或黑名单。
- **用户设备跟踪模型层**
- UserDevice:用户设备信息,包含设备MAC地址、型号、添加时间、当前版本等。
- UserActive:用户活动记录,包含设备活跃信息、IP地址、活跃日期等。
- **仓库层(Repository**
- BrandRepository:提供按品牌名模糊查询的品牌列表。
- ModelRepository:提供按品牌名或型号名模糊查询的型号列表,并处理可空字段的扫描。
- OTARepository:提供OTA升级信息查询、黑名单检查、定向设备查询等功能。
- DeviceRepository:提供设备信息查询、插入、更新以及活动记录管理。
- **处理器层(Handler**
- BrandHandler:接收查询参数,调用仓库层,支持可选的响应体 Base64 编码。
- ModelHandler:接收品牌与型号查询参数,调用仓库层,支持可选的响应体 Base64 编码。
- ModelListHandler:通过搜索客户端返回模型列表,支持可选的响应体 Base64 编码。
- OTAHandler:获取OTA升级信息,支持黑名单检查、定向升级检查等功能。
- DeviceHandler:处理设备信息上报,将设备活动信息写入Redis缓存。
- **任务处理层**
- DevicePersistTask:定时从Redis缓存批量持久化设备信息到数据库。
**章节来源**
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
- [internal/model/ota.go:5-43](file://internal/model/ota.go#L5-L43)
- [internal/model/user_device.go:5-12](file://internal/model/user_device.go#L5-L12)
- [internal/model/user_active.go:5-13](file://internal/model/user_active.go#L5-L13)
- [internal/repository/brand.go:20-51](file://internal/repository/brand.go#L20-L51)
- [internal/repository/model.go:20-95](file://internal/repository/model.go#L20-L95)
- [internal/repository/ota.go:19-159](file://internal/repository/ota.go#L19-L159)
- [internal/repository/device.go:19-88](file://internal/repository/device.go#L19-L88)
- [internal/handler/brand.go:26-50](file://internal/handler/brand.go#L26-L50)
- [internal/handler/model.go:26-51](file://internal/handler/model.go#L26-L51)
- [internal/handler/model_list.go:26-57](file://internal/handler/model_list.go#L26-L57)
- [internal/handler/ota.go:23-133](file://internal/handler/ota.go#L23-L133)
- [internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
- [internal/task/device_persist.go:24-167](file://internal/task/device_persist.go#L24-L167)
## 架构总览
下图展示了从请求到响应的关键路径,包括处理器、仓库、数据库与可选编码流程,以及新增的OTA固件升级和设备跟踪功能。
```mermaid
sequenceDiagram
participant C as "客户端"
participant R as "路由层"
participant H as "处理器层"
participant Repo as "仓库层"
participant DB as "数据库"
participant Enc as "编码层"
C->>R : "GET /audio/ota?model=xxx&hw=1&mac=xx : xx : xx&beta=0"
R->>H : "OTAHandler.GetOTA"
H->>Repo : "GetLatestOTA(model, hw, beta)"
Repo->>DB : "查询最新OTA记录"
DB-->>Repo : "OTA记录"
Repo-->>H : "OTA对象"
H->>Repo : "IsInBlackList(otaID, mac)"
Repo->>DB : "检查黑名单"
DB-->>Repo : "黑名单状态"
Repo-->>H : "黑名单检查结果"
alt "在黑名单中"
H->>Repo : "GetLatestOTANotInBlackList"
Repo->>DB : "查询非黑名单OTA"
DB-->>Repo : "OTA记录"
Repo-->>H : "OTA对象"
else "非定向升级"
H->>Repo : "FindTargetDevice(otaID, mac)"
Repo->>DB : "检查定向设备"
DB-->>Repo : "设备信息"
Repo-->>H : "设备检查结果"
else "定向升级"
H->>Repo : "GetLatestOTANotTarget"
Repo->>DB : "查询非定向OTA"
DB-->>Repo : "OTA记录"
Repo-->>H : "OTA对象"
end
alt "base64Resp=true"
H->>Enc : "EncodeJSON(OTA)"
Enc-->>H : "Base64 字符串"
H-->>C : "200 OK + Base64"
else "默认"
H-->>C : "200 OK + JSON"
end
```
**图表来源**
- [internal/router/router.go:51](file://internal/router/router.go#L51)
- [internal/handler/ota.go:25-133](file://internal/handler/ota.go#L25-L133)
- [internal/repository/ota.go:19-159](file://internal/repository/ota.go#L19-L159)
- [pkg/encode/base64.go:35-52](file://pkg/encode/base64.go#L35-L52)
## 详细组件分析
### Brand 数据模型
- **设计理念**
- 轻量级品牌实体,仅包含标识与名称,用于品牌筛选与列表展示。
- **字段定义与约束**
- id:整数,主键,自增。
- name:字符串,非空,用于品牌名匹配与排序。
- **业务含义**
- 作为 Model 的上游维度,配合 Model 的 brandName 字段形成关联。
- **数据库映射**
- 表名:brand(未在本文直接列出,但与 Model 的 brand_name 关联一致)。
- 字段:id、name。
- **ORM 映射配置**
- 使用原生 sql.DB 扫描,通过结构体标签与 Scan 对齐。
- **验证与序列化**
- 无显式校验逻辑,依赖数据库约束与上层参数清洗。
- JSON 标签用于序列化输出。
- **查询流程**
- 支持按品牌名模糊查询,使用 LIKE 匹配并按名称升序排列。
- 返回 []Brand 列表。
```mermaid
classDiagram
class Brand {
+int id
+string name
}
class BrandRepository {
-db *sql.DB
+List(ctx, brandName) []Brand
}
BrandRepository --> Brand : "返回"
```
**图表来源**
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
- [internal/repository/brand.go:12-51](file://internal/repository/brand.go#L12-L51)
**章节来源**
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
- [internal/repository/brand.go:20-51](file://internal/repository/brand.go#L20-L51)
### Model 数据模型
- **设计理念**
- 型号实体承载品牌名、型号名及可选属性(形态、设备类型、来源、等价键),并记录创建时间。
- **字段定义与约束**
- id:整数,主键,自增。
- brandName:字符串,非空,与品牌维度关联。
- name:字符串,非空,型号名。
- form、rig、source、eqKey:字符串(指针),可空,表示形态、设备类型、来源、等价键。
- createAt:时间戳,非空,记录创建时间。
- **业务含义**
- 作为核心产品维度,支持按品牌或型号名检索,可为空的扩展属性满足多样化设备描述。
- **数据库映射**
- 表名:model。
- 字段:id、brand_name、name、form、rig、source、eq_key、create_at。
- 约束:唯一索引 (brand_name, name),保证同品牌下型号名唯一。
- **ORM 映射配置**
- 使用 sql.NullString 扫描可空列,再转换为指针字符串,避免零值歧义。
- JSON 标签用于序列化输出,可空字段支持 omitempty。
- **验证与序列化**
- 无显式校验逻辑,依赖数据库约束与上层参数清洗。
- JSON 标签用于序列化输出。
- **查询流程**
- 支持按品牌名精确匹配或按型号名模糊匹配,返回 []Model 列表。
- 默认返回空切片而非 nil,便于前端处理。
```mermaid
classDiagram
class Model {
+int id
+string brandName
+string name
+*string form
+*string rig
+*string source
+*string eqKey
+time createAt
}
class ModelRepository {
-db *sql.DB
+List(ctx, brandName, modelName) []Model
}
ModelRepository --> Model : "返回"
```
**图表来源**
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
- [internal/repository/model.go:12-95](file://internal/repository/model.go#L12-L95)
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
**章节来源**
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
- [internal/repository/model.go:20-95](file://internal/repository/model.go#L20-L95)
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
### OTA 固件升级模型
- **设计理念**
- OTA模型用于管理固件升级信息,支持强制升级、灰度发布、定向升级等功能。
- **字段定义与约束**
- OTA模型:
- id:整数,主键,自增。
- verCode:整数,版本号,用于排序。
- verName:字符串,版本名称。
- url:字符串,固件下载地址。
- md5:字符串,固件MD5校验值。
- force:整数,是否强制升级(0-否,1-是)。
- desc:字符串指针,升级描述。
- model:字符串指针,对应设备型号。
- hw:整数,硬件版本号。
- target:整数,是否定向升级(0-否,1-是)。
- beta:整数,是否灰度发布(0-否,1-是)。
- pawVerCode:整数,配套软件版本号。
- pawVerName:字符串,配套软件版本名称。
- pawUrl:字符串,配套软件下载地址。
- pawMD5:字符串,配套软件MD5校验值。
- startTime:时间戳指针,升级开始时间。
- endTime:时间戳指针,升级结束时间。
- status:整数,状态(1-可用,0-不可用)。
- BlackList模型:
- id:整数,主键,自增。
- ota_id:整数,关联OTA记录ID。
- mac:字符串,设备MAC地址。
- create_at:时间戳,创建时间。
- OTATargetDevice模型:
- id:整数,主键,自增。
- ota_id:整数,关联OTA记录ID。
- mac_addr:字符串,设备MAC地址。
- type:整数,类型(1-白名单,2-黑名单)。
- create_at:时间戳,创建时间。
- **业务含义**
- 支持复杂的固件升级策略,包括强制升级、灰度发布、定向升级和黑名单管理。
- **数据库映射**
- 表名:ota、black_list、ota_target_device。
- 字段:完整映射关系详见数据库表结构。
- **ORM 映射配置**
- 使用 sql.NullString 和 sql.NullTime 扫描可空列。
- JSON 标签用于序列化输出,可空字段支持 omitempty。
- **查询流程**
- 支持多种查询策略:最新OTA记录、非黑名单OTA记录、非定向OTA记录等。
- 支持黑名单检查和定向设备检查。
```mermaid
classDiagram
class OTA {
+int id
+int verCode
+string verName
+string url
+string md5
+int force
+*string desc
+*string model
+int hw
+int target
+int beta
+int pawVerCode
+string pawVerName
+string pawUrl
+string pawMD5
+*time startTime
+*time endTime
+int status
}
class BlackList {
+int id
+int ota_id
+string mac
+time create_at
}
class OTATargetDevice {
+int id
+int ota_id
+string mac_addr
+int type
+time create_at
}
class OTARepository {
-db *sql.DB
+GetLatestOTA(ctx, model, hw, beta) *OTA
+GetLatestOTANotInBlackList(ctx, model, hw, beta, mac) *OTA
+GetLatestOTANotTarget(ctx, model, hw, beta) *OTA
+IsInBlackList(ctx, otaID, mac) bool
+FindTargetDevice(ctx, otaID, mac) *OTATargetDevice
}
OTARepository --> OTA : "返回"
OTARepository --> BlackList : "返回"
OTARepository --> OTATargetDevice : "返回"
```
**图表来源**
- [internal/model/ota.go:6-42](file://internal/model/ota.go#L6-L42)
- [internal/repository/ota.go:19-159](file://internal/repository/ota.go#L19-L159)
- [sql/ota.sql:24-44](file://sql/ota.sql#L24-L44)
- [sql/ota_target_device.sql:24-31](file://sql/ota_target_device.sql#L24-L31)
**章节来源**
- [internal/model/ota.go:5-43](file://internal/model/ota.go#L5-L43)
- [internal/repository/ota.go:19-159](file://internal/repository/ota.go#L19-L159)
- [sql/ota.sql:23-44](file://sql/ota.sql#L23-L44)
- [sql/ota_target_device.sql:23-31](file://sql/ota_target_device.sql#L23-L31)
### 用户设备跟踪模型
- **设计理念**
- UserDevice模型用于跟踪用户设备信息,支持设备注册、版本更新等功能。
- UserActive模型用于记录用户设备的活跃信息,支持按日期分区存储。
- **字段定义与约束**
- UserDevice模型:
- id:整数,主键,自增。
- mac_addr:字符串,设备MAC地址,唯一索引。
- model:字符串,设备型号。
- add_time:时间戳,默认当前时间。
- ver:字符串指针,设备当前版本。
- UserActive模型:
- id:整数,主键。
- mac_addr:字符串,设备MAC地址。
- model:字符串,设备型号。
- active_date:日期,活跃日期,联合主键。
- ip_addr:字符串,设备IP地址。
- create_at:时间戳,默认当前时间。
- **业务含义**
- 支持设备信息的实时上报和历史活动记录追踪。
- **数据库映射**
- 表名:user_device、user_active。
- 字段:完整映射关系详见数据库表结构。
- 索引:user_device的mac_addr唯一索引,user_active的(mac_addr, active_date)联合索引。
- **ORM 映射配置**
- 使用 sql.NullString 扫描可空列。
- JSON 标签用于序列化输出。
- **查询流程**
- 支持按MAC地址查询设备信息。
- 支持按MAC地址和日期查询活跃记录。
- 支持设备信息的插入、更新和活跃记录的插入、更新。
```mermaid
classDiagram
class UserDevice {
+int id
+string mac_addr
+string model
+time add_time
+*string ver
}
class UserActive {
+int id
+string mac_addr
+string model
+date active_date
+string ip_addr
+time create_at
}
class DeviceRepository {
-db *sql.DB
+FindDeviceByMac(ctx, macAddr) *UserDevice
+InsertDevice(ctx, device) error
+UpdateDeviceVer(ctx, id, ver) error
+FindActiveByMacAndDate(ctx, macAddr, activeDate) *UserActive
+InsertActive(ctx, active) error
+UpdateActiveIp(ctx, id, ipAddr) error
}
DeviceRepository --> UserDevice : "返回"
DeviceRepository --> UserActive : "返回"
```
**图表来源**
- [internal/model/user_device.go:5-12](file://internal/model/user_device.go#L5-L12)
- [internal/model/user_active.go:5-13](file://internal/model/user_active.go#L5-L13)
- [internal/repository/device.go:19-88](file://internal/repository/device.go#L19-L88)
- [sql/user_device.sql:24-32](file://sql/user_device.sql#L24-L32)
- [sql/user_active.sql:24-32](file://sql/user_active.sql#L24-L32)
**章节来源**
- [internal/model/user_device.go:5-12](file://internal/model/user_device.go#L5-L12)
- [internal/model/user_active.go:5-13](file://internal/model/user_active.go#L5-L13)
- [internal/repository/device.go:19-88](file://internal/repository/device.go#L19-L88)
- [sql/user_device.sql:23-32](file://sql/user_device.sql#L23-L32)
- [sql/user_active.sql:23-32](file://sql/user_active.sql#L23-L32)
### 处理器与序列化机制
- **统一响应体**
- 统一响应体包含 code、message、data 字段,便于前端统一处理。
- **可选 Base64 编码**
- 支持通过 base64Resp 参数控制是否对 JSON 响应进行自定义 Base64 编码。
- 自定义映射表将标准 Base64 字符集映射为更紧凑的字符集,减少体积。
- **OTA处理器特殊逻辑**
- 支持参数校验、黑名单检查、定向设备检查等复杂业务逻辑。
- 根据不同情况返回不同的OTA升级信息。
- **设备处理器特殊逻辑**
- 将设备信息写入Redis缓存,支持异步持久化。
- 提供设备信息上报接口。
- **错误处理**
- 处理器捕获仓库层错误,记录日志并通过统一响应体返回内部错误。
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "OTAHandler"
participant Repo as "OTARepository"
participant DB as "数据库"
participant Enc as "编码层"
C->>H : "GET /audio/ota?model=...&hw=...&mac=...&beta=..."
H->>Repo : "GetLatestOTA(model, hw, beta)"
Repo->>DB : "查询最新OTA记录"
DB-->>Repo : "OTA记录"
Repo-->>H : "OTA对象"
H->>Repo : "IsInBlackList(otaID, mac)"
Repo->>DB : "检查黑名单"
DB-->>Repo : "黑名单状态"
Repo-->>H : "检查结果"
alt "在黑名单中"
H->>Repo : "GetLatestOTANotInBlackList"
Repo->>DB : "查询非黑名单OTA"
DB-->>Repo : "OTA记录"
Repo-->>H : "OTA对象"
else "非定向升级"
H->>Repo : "FindTargetDevice(otaID, mac)"
Repo->>DB : "检查定向设备"
DB-->>Repo : "设备信息"
Repo-->>H : "检查结果"
else "定向升级"
H->>Repo : "GetLatestOTANotTarget"
Repo->>DB : "查询非定向OTA"
DB-->>Repo : "OTA记录"
Repo-->>H : "OTA对象"
end
alt "base64Resp=true"
H->>Enc : "EncodeJSON(OTA)"
Enc-->>H : "Base64 字符串"
H-->>C : "200 OK + Base64"
else "默认"
H-->>C : "200 OK + JSON"
end
```
**图表来源**
- [internal/handler/ota.go:25-133](file://internal/handler/ota.go#L25-L133)
- [internal/repository/ota.go:19-159](file://internal/repository/ota.go#L19-L159)
- [pkg/encode/base64.go:21-52](file://pkg/encode/base64.go#L21-L52)
- [internal/response/response.go:9-37](file://internal/response/response.go#L9-L37)
**章节来源**
- [internal/handler/brand.go:26-50](file://internal/handler/brand.go#L26-L50)
- [internal/handler/model.go:26-51](file://internal/handler/model.go#L26-L51)
- [internal/handler/model_list.go:26-57](file://internal/handler/model_list.go#L26-L57)
- [internal/handler/ota.go:23-133](file://internal/handler/ota.go#L23-L133)
- [internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
- [pkg/encode/base64.go:21-52](file://pkg/encode/base64.go#L21-L52)
- [internal/response/response.go:9-37](file://internal/response/response.go#L9-L37)
### 查询与过滤逻辑
- **Brand 查询**
- 支持按品牌名模糊匹配,使用 LIKE 并按名称升序排列。
- **Model 查询**
- 优先按品牌名精确匹配;若为空则按型号名模糊匹配;否则返回空切片。
- 扫描时将 sql.NullString 转换为指针字符串,避免零值歧义。
- **OTA 查询**
- 支持按model+hw+beta+status=1查询最新OTA记录。
- 支持黑名单检查和定向设备检查。
- 提供多种查询策略以适应不同的升级场景。
- **设备查询**
- 支持按MAC地址查询设备信息。
- 支持按MAC地址和日期查询活跃记录。
- 扫描时将 sql.NullString 转换为指针字符串,避免零值歧义。
```mermaid
flowchart TD
Start(["进入 OTA 查询"]) --> Params["参数校验"]
Params --> CheckBrand{"model和hw是否非空?"}
CheckBrand --> |是| QueryOTA["GetLatestOTA 查询"]
CheckBrand --> |否| Error["返回错误"]
QueryOTA --> CheckBlackList["IsInBlackList 检查"]
CheckBlackList --> |在黑名单| QueryNotInBlackList["GetLatestOTANotInBlackList"]
CheckBlackList --> |不在黑名单| CheckTarget{"ota.target 是否为0?"}
QueryNotInBlackList --> ReturnOTA["返回OTA"]
CheckTarget --> |是| ReturnOTA
CheckTarget --> |否| CheckTargetDevice["FindTargetDevice 检查"]
CheckTargetDevice --> |在定向设备| ReturnOTA
CheckTargetDevice --> |不在定向设备| QueryNotTarget["GetLatestOTANotTarget"]
QueryNotTarget --> ReturnOTA
ReturnOTA --> End(["返回结果"])
Error --> End
```
**图表来源**
- [internal/handler/ota.go:25-133](file://internal/handler/ota.go#L25-L133)
- [internal/repository/ota.go:19-159](file://internal/repository/ota.go#L19-L159)
**章节来源**
- [internal/repository/model.go:20-61](file://internal/repository/model.go#L20-L61)
- [internal/repository/brand.go:20-51](file://internal/repository/brand.go#L20-L51)
- [internal/repository/ota.go:19-159](file://internal/repository/ota.go#L19-L159)
- [internal/repository/device.go:19-88](file://internal/repository/device.go#L19-L88)
## 依赖分析
- **层间耦合**
- Handler 依赖 RepositoryRepository 依赖 sql.DBModel 为纯数据结构。
- 统一响应体与编码层被 Handler 调用,降低重复逻辑。
- 新增的Redis依赖用于设备信息缓存。
- **外部依赖**
- 数据库:MySQL,通过 sql.DB 访问。
- 日志:zap。
- Web 框架:Gin。
- 搜索:MeilisearchModelListHandler)。
- 缓存:RedisDevice 相关处理器)。
- 任务调度:定时任务用于设备信息持久化。
- **潜在循环依赖**
- 当前结构清晰,无循环导入迹象。
- **新增依赖关系**
- OTA功能依赖多个表的关联查询。
- 设备跟踪功能依赖Redis缓存和数据库的双重存储。
```mermaid
graph LR
H_Brand["BrandHandler"] --> Repo_Brand["BrandRepository"]
H_Model["ModelHandler"] --> Repo_Model["ModelRepository"]
H_ModelList["ModelListHandler"] --> Search["Meilisearch 客户端"]
H_OTA["OTAHandler"] --> Repo_OTA["OTARepository"]
H_Device["DeviceHandler"] --> Repo_Device["DeviceRepository"]
Repo_Brand --> DB["sql.DB"]
Repo_Model --> DB
Repo_OTA --> DB
Repo_Device --> DB
H_Device --> Redis["Redis Client"]
Repo_Device --> Redis
H_Brand --> Resp["统一响应体"]
H_Model --> Resp
H_ModelList --> Resp
H_OTA --> Resp
H_Device --> Resp
H_Brand --> Encode["Base64 编码"]
H_Model --> Encode
H_ModelList --> Encode
Task["DevicePersistTask"] --> Repo_Device
Task --> Redis
Task --> DB
```
**图表来源**
- [internal/handler/brand.go:14-50](file://internal/handler/brand.go#L14-L50)
- [internal/handler/model.go:14-51](file://internal/handler/model.go#L14-L51)
- [internal/handler/model_list.go:14-57](file://internal/handler/model_list.go#L14-L57)
- [internal/handler/ota.go:14-133](file://internal/handler/ota.go#L14-L133)
- [internal/handler/device.go:15-83](file://internal/handler/device.go#L15-L83)
- [internal/repository/brand.go:12-51](file://internal/repository/brand.go#L12-L51)
- [internal/repository/model.go:12-95](file://internal/repository/model.go#L12-L95)
- [internal/repository/ota.go:11-159](file://internal/repository/ota.go#L11-L159)
- [internal/repository/device.go:11-88](file://internal/repository/device.go#L11-L88)
- [internal/response/response.go:9-37](file://internal/response/response.go#L9-L37)
- [pkg/encode/base64.go:21-52](file://pkg/encode/base64.go#L21-L52)
- [internal/task/device_persist.go:14-167](file://internal/task/device_persist.go#L14-L167)
**章节来源**
- [internal/router/router.go:14-56](file://internal/router/router.go#L14-L56)
- [internal/config/database.go:17-72](file://internal/config/database.go#L17-L72)
## 性能考虑
- **查询优化**
- Model 表对 (brand_name, name) 建有唯一索引,有利于去重与快速定位。
- OTA表对(model, hw, beta, status)建立索引,支持快速查询最新OTA记录。
- UserDevice表对(mac_addr)建立唯一索引,支持快速设备查询。
- UserActive表对(mac_addr, active_date)建立联合索引,支持按日期分区查询。
- 查询时优先按品牌名匹配,减少 LIKE 的范围。
- **扫描与内存**
- 使用 sql.NullString 扫描可空列,避免零值歧义;转换为指针字符串减少冗余存储。
- OTA模型使用sql.NullTime处理可空时间字段。
- **缓存策略**
- 设备信息先写入Redis缓存,通过定时任务批量持久化到数据库,减少数据库压力。
- 品牌和型号信息使用Redis缓存提升查询性能。
- **编码策略**
- 可选的自定义 Base64 编码可降低响应体积,适合大列表传输场景。
- **并发与超时**
- 服务器设置读写超时与优雅关闭,保障稳定性。
- 设备持久化任务使用定时器定期执行,避免阻塞主线程。
**章节来源**
- [sql/model.sql:34-35](file://sql/model.sql#L34-L35)
- [sql/ota.sql:24-44](file://sql/ota.sql#L24-L44)
- [sql/user_device.sql:24-32](file://sql/user_device.sql#L24-L32)
- [sql/user_active.sql:24-32](file://sql/user_active.sql#L24-L32)
- [internal/repository/model.go:63-95](file://internal/repository/model.go#L63-L95)
- [internal/repository/ota.go:20-159](file://internal/repository/ota.go#L20-L159)
- [internal/repository/device.go:19-88](file://internal/repository/device.go#L19-L88)
- [pkg/encode/base64.go:21-52](file://pkg/encode/base64.go#L21-L52)
- [cmd/server/main.go:66-95](file://cmd/server/main.go#L66-L95)
- [internal/task/device_persist.go:24-91](file://internal/task/device_persist.go#L24-L91)
## 故障排查指南
- **常见问题**
- 数据库连接失败:检查环境变量与配置加载逻辑。
- 查询无结果:确认查询参数是否为空或大小写敏感;Model 查询默认返回空切片而非 nil。
- 编码异常:确认 base64Resp 参数与 JSON 序列化是否成功。
- OTA查询失败:检查model、hw参数是否正确,确认OTA记录状态是否为1。
- 设备信息未持久化:检查Redis连接和数据库连接,确认定时任务是否正常运行。
- **排查步骤**
- 查看日志:处理器记录错误日志,统一响应体返回错误码。
- 核对数据库:确认表结构与索引是否存在。
- 验证参数:确认查询参数是否符合预期。
- 检查Redis:确认设备信息是否正确写入Redis缓存。
- 监控任务:确认设备持久化任务是否按预期执行。
**章节来源**
- [internal/config/database.go:57-72](file://internal/config/database.go#L57-L72)
- [internal/handler/brand.go:31-35](file://internal/handler/brand.go#L31-L35)
- [internal/handler/model.go:33-36](file://internal/handler/model.go#L33-L36)
- [internal/handler/model_list.go:39-42](file://internal/handler/model_list.go#L39-L42)
- [internal/handler/ota.go:31-48](file://internal/handler/ota.go#L31-L48)
- [internal/handler/device.go:33-39](file://internal/handler/device.go#L33-L39)
- [internal/response/response.go:30-37](file://internal/response/response.go#L30-L37)
## 结论
本项目的数据模型设计简洁明确:Brand 与 Model 分别承担品牌与型号的维度,通过仓库层的原生 SQL 访问实现高效查询;处理器层统一响应与可选编码,提升传输效率与前端体验。新增的OTA固件升级模型、用户设备跟踪模型和用户活动模型进一步完善了系统的数据模型体系,支持复杂的固件升级策略、设备信息跟踪和用户行为分析。数据库层面通过唯一索引与合理字段设计保障一致性与性能。建议在后续迭代中引入显式的校验与转换层,增强健壮性与可维护性。
## 附录
### 数据库表结构与模型映射对照
- **表:model**
- 字段:id(主键)、brand_name、name、form、rig、source、eq_key、create_at。
- 约束:唯一索引 (brand_name, name)。
- **表:ota**
- 字段:id(主键)、verCode、verName、url、md5、force、desc、model、hw、target、beta、pawVerCode、pawVerName、pawUrl、pawMd5、startTime、endTime、status。
- 约束:无显式约束。
- **表:black_list**
- 字段:id(主键)、ota_id、mac、create_at。
- 约束:无显式约束。
- **表:ota_target_device**
- 字段:id(主键)、ota_id、mac_addr、type、create_at。
- 约束:无显式约束。
- **表:user_device**
- 字段:id(主键)、mac_addr、model、add_time、ver。
- 约束:唯一索引 (mac_addr)。
- **表:user_active**
- 字段:id、mac_addr、model、active_date、ip_addr、create_at。
- 约束:联合主键 (id, active_date),联合索引 (mac_addr, active_date)。
- **映射关系**
- Model.id ↔ model.id
- Model.brandName ↔ model.brand_name
- Model.name ↔ model.name
- OTA.id ↔ ota.id
- OTA.verCode ↔ ota.verCode
- OTA.verName ↔ ota.verName
- OTA.url ↔ ota.url
- OTA.md5 ↔ ota.md5
- OTA.force ↔ ota.force
- OTA.desc ↔ ota.desc
- OTA.model ↔ ota.model
- OTA.hw ↔ ota.hw
- OTA.target ↔ ota.target
- OTA.beta ↔ ota.beta
- OTA.pawVerCode ↔ ota.pawVerCode
- OTA.pawVerName ↔ ota.pawVerName
- OTA.pawUrl ↔ ota.pawUrl
- OTA.pawMD5 ↔ ota.pawMD5
- OTA.startTime ↔ ota.startTime
- OTA.endTime ↔ ota.endTime
- OTA.status ↔ ota.status
- BlackList.id ↔ black_list.id
- BlackList.ota_id ↔ black_list.ota_id
- BlackList.mac ↔ black_list.mac
- BlackList.create_at ↔ black_list.create_at
- OTATargetDevice.id ↔ ota_target_device.id
- OTATargetDevice.ota_id ↔ ota_target_device.ota_id
- OTATargetDevice.mac_addr ↔ ota_target_device.mac_addr
- OTATargetDevice.type ↔ ota_target_device.type
- OTATargetDevice.create_at ↔ ota_target_device.create_at
- UserDevice.id ↔ user_device.id
- UserDevice.mac_addr ↔ user_device.mac_addr
- UserDevice.model ↔ user_device.model
- UserDevice.add_time ↔ user_device.add_time
- UserDevice.ver ↔ user_device.ver
- UserActive.id ↔ user_active.id
- UserActive.mac_addr ↔ user_active.mac_addr
- UserActive.model ↔ user_active.model
- UserActive.active_date ↔ user_active.active_date
- UserActive.ip_addr ↔ user_active.ip_addr
- UserActive.create_at ↔ user_active.create_at
**章节来源**
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
- [sql/ota.sql:24-44](file://sql/ota.sql#L24-L44)
- [sql/ota_target_device.sql:24-31](file://sql/ota_target_device.sql#L24-L31)
- [sql/user_device.sql:24-32](file://sql/user_device.sql#L24-L32)
- [sql/user_active.sql:24-32](file://sql/user_active.sql#L24-L32)
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
- [internal/model/ota.go:6-42](file://internal/model/ota.go#L6-L42)
- [internal/model/user_device.go:5-12](file://internal/model/user_device.go#L5-L12)
- [internal/model/user_active.go:5-13](file://internal/model/user_active.go#L5-L13)
### 请求与响应示例(路径参考)
- **获取品牌列表**
- 路由:/audio/getBrand
- 方法:GET
- 参数:brandName(可选),base64Resp(可选)
- 返回:[]Brand 或 Base64 编码后的 JSON
- 参考路径:[internal/handler/brand.go:26-50](file://internal/handler/brand.go#L26-L50)
- **获取型号列表**
- 路由:/audio/getModel
- 方法:GET
- 参数:brandName(可选)、modelName(可选)、base64Resp(可选)
- 返回:[]Model 或 Base64 编码后的 JSON
- 参考路径:[internal/handler/model.go:26-51](file://internal/handler/model.go#L26-L51)
- **搜索模型列表**
- 路由:/audio/modelList
- 方法:GET
- 参数:key(必需)、count(可选)、base64Resp(可选)
- 返回:[]Model 或 Base64 编码后的 JSON
- 参考路径:[internal/handler/model_list.go:26-57](file://internal/handler/model_list.go#L26-L57)
- **获取OTA升级信息**
- 路由:/audio/ota
- 方法:GET
- 参数:model(必需)、hw(必需)、mac(可选)、beta(可选)、base64Resp(可选)
- 返回:OTA对象或 Base64 编码后的 JSON
- 参考路径:[internal/handler/ota.go:23-133](file://internal/handler/ota.go#L23-L133)
- **设备信息上报**
- 路由:/audio/reportDevInfo
- 方法:GET
- 参数:mac(必需)、model(必需)、ver(可选)
- 返回:操作结果
- 参考路径:[internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
**章节来源**
- [internal/router/router.go:45-52](file://internal/router/router.go#L45-L52)
- [internal/handler/brand.go:26-50](file://internal/handler/brand.go#L26-L50)
- [internal/handler/model.go:26-51](file://internal/handler/model.go#L26-L51)
- [internal/handler/model_list.go:26-57](file://internal/handler/model_list.go#L26-L57)
- [internal/handler/ota.go:23-133](file://internal/handler/ota.go#L23-L133)
- [internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
### 最佳实践建议
- **模型扩展**
- 引入显式的校验与转换层,如参数清洗、长度限制、正则校验等。
- 对可空字段提供默认值策略,避免前端空值判断复杂化。
- OTA模型支持复杂的升级策略,建议在业务层添加更多的参数验证。
- **版本管理与向后兼容**
- 通过 API 版本号(如 /api/v1)隔离变更;新增字段采用可选策略,保持旧字段必填。
- 对于破坏性变更,提供迁移脚本与双写策略。
- OTA升级策略的变更需要谨慎处理,确保向后兼容性。
- **性能优化**
- 为高频查询字段建立索引;避免 SELECT *,仅选择必要字段。
- 对大列表启用可选 Base64 编码;结合分页与缓存策略。
- 设备信息使用Redis缓存,定期批量持久化到数据库。
- OTA查询使用复合索引,支持快速定位最新OTA记录。
- **错误处理与可观测性**
- 统一错误码与消息格式;记录关键链路日志;对数据库与外部服务增加超时与重试。
- OTA查询失败需要详细的日志记录,包括参数、查询结果等。
- 设备持久化任务需要监控执行状态和错误日志。
- **安全考虑**
- OTA下载链接需要安全性验证,防止恶意下载。
- 设备信息上报需要参数验证,防止注入攻击。
- 黑名单和白名单管理需要权限控制和审计日志。