feat(impedance): 新增用户耳机阻抗功能模块
- 设计并添加 user_headphone_impedance 表,包含唯一复合索引保证数据完整性 - 实现阻抗上报API接口 /audio/reportImpedance,支持设备上传阻抗数据 - 使用Redis Hash结构缓存阻抗数据,键为 "headphone_impedances" - 新增定时持久化任务,每5分钟从Redis读取数据批量写入数据库 - 对品牌和型号数据进行trim+lower归一化处理,支持数据去重更新 - 优化查询性能,确保mac_addr与品牌型号归一化组合唯一索引生效 - 更新故障排查指南,新增Redis缓存及唯一索引相关问题检查 - 新增阻抗持久化配置开关,允许按需启用该功能
This commit is contained in:
@@ -9,12 +9,14 @@
|
||||
- [internal/model/target.go](file://internal/model/target.go)
|
||||
- [internal/model/user_device.go](file://internal/model/user_device.go)
|
||||
- [internal/model/user_active.go](file://internal/model/user_active.go)
|
||||
- [internal/model/user_headphone_impedance.go](file://internal/model/user_headphone_impedance.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/share_code.go](file://internal/repository/share_code.go)
|
||||
- [internal/repository/curve.go](file://internal/repository/curve.go)
|
||||
- [internal/repository/device.go](file://internal/repository/device.go)
|
||||
- [internal/repository/headphone_impedance.go](file://internal/repository/headphone_impedance.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)
|
||||
@@ -22,12 +24,15 @@
|
||||
- [internal/handler/share_code.go](file://internal/handler/share_code.go)
|
||||
- [internal/handler/device.go](file://internal/handler/device.go)
|
||||
- [internal/handler/curve.go](file://internal/handler/curve.go)
|
||||
- [internal/handler/impedance.go](file://internal/handler/impedance.go)
|
||||
- [internal/router/router.go](file://internal/router/router.go)
|
||||
- [internal/config/database.go](file://internal/config/database.go)
|
||||
- [internal/config/share_code_ttl.go](file://internal/config/share_code_ttl.go)
|
||||
- [internal/config/config.go](file://internal/config/config.go)
|
||||
- [internal/response/response.go](file://internal/response/response.go)
|
||||
- [internal/task/device_persist.go](file://internal/task/device_persist.go)
|
||||
- [internal/task/share_code_persist.go](file://internal/task/share_code_persist.go)
|
||||
- [internal/task/impedance_persist.go](file://internal/task/impedance_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)
|
||||
@@ -36,6 +41,7 @@
|
||||
- [sql/share_code_log.sql](file://sql/share_code_log.sql)
|
||||
- [sql/user_device.sql](file://sql/user_device.sql)
|
||||
- [sql/user_active.sql](file://sql/user_active.sql)
|
||||
- [sql/user_headphone_impedance.sql](file://sql/user_headphone_impedance.sql)
|
||||
- [cmd/server/main.go](file://cmd/server/main.go)
|
||||
</cite>
|
||||
|
||||
@@ -45,6 +51,9 @@
|
||||
- OTA灰度升级设备模型重构:从包含Type字段的定向设备改为不包含类型字段的灰度升级设备,简化了设备管理逻辑
|
||||
- 数据库表结构同步更新:ota_target_device表移除了type字段,OTATargetDevice结构体不再包含Type字段
|
||||
- 业务逻辑调整:OTA升级策略从"定向设备"概念转变为"灰度升级设备"概念,简化了升级控制逻辑
|
||||
- **新增**:UserHeadphoneImpedance数据模型,用于存储用户上报的耳机阻抗信息,包含MAC地址、设备型号、阻抗值、耳机信息(原始和标准化版本)、IP地址和时间戳等字段
|
||||
- **新增**:耳机阻抗持久化任务,定时从Redis缓存批量持久化阻抗数据到数据库
|
||||
- **新增**:耳机阻抗上报API接口,支持设备上报耳机阻抗信息
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
@@ -59,10 +68,10 @@
|
||||
10. [附录](#附录)
|
||||
|
||||
## 简介
|
||||
本文件面向 Luxsin 应用 API 的数据模型,系统性梳理 Brand、Model、OTA、ShareCode、Target、UserDevice 和 UserActive 七类数据模型的设计理念、字段定义、约束条件、业务含义以及与数据库表结构的映射关系。文档还覆盖 ORM 映射配置(基于原生 sql.DB 的扫描映射)、模型验证与序列化机制、模型在各层之间的传递方式与性能考量,并给出扩展、版本管理与向后兼容的最佳实践建议。内容兼顾初学者与高级开发者,既提供高层概览,也包含代码级的可视化图示与来源标注。
|
||||
本文件面向 Luxsin 应用 API 的数据模型,系统性梳理 Brand、Model、OTA、ShareCode、Target、UserDevice、UserActive 和 UserHeadphoneImpedance 八类数据模型的设计理念、字段定义、约束条件、业务含义以及与数据库表结构的映射关系。文档还覆盖 ORM 映射配置(基于原生 sql.DB 的扫描映射)、模型验证与序列化机制、模型在各层之间的传递方式与性能考量,并给出扩展、版本管理与向后兼容的最佳实践建议。内容兼顾初学者与高级开发者,既提供高层概览,也包含代码级的可视化图示与来源标注。
|
||||
|
||||
## 项目结构
|
||||
该项目采用分层架构:路由层负责请求入口与中间件;处理器层处理业务逻辑与参数解析;仓库层封装数据库访问;模型层承载数据结构;响应层统一返回格式;编码层提供可选的响应体压缩编码;配置层加载数据库、Redis等外部服务配置。新增的分享码功能、目标曲线模型和增强的OTA模型进一步完善了系统的数据模型体系。
|
||||
该项目采用分层架构:路由层负责请求入口与中间件;处理器层处理业务逻辑与参数解析;仓库层封装数据库访问;模型层承载数据结构;响应层统一返回格式;编码层提供可选的响应体压缩编码;配置层加载数据库、Redis等外部服务配置。新增的分享码功能、目标曲线模型、增强的OTA模型和用户耳机阻抗跟踪功能进一步完善了系统的数据模型体系。
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
@@ -80,6 +89,7 @@ OTA_H["internal/handler/ota.go"]
|
||||
DEVICE_H["internal/handler/device.go"]
|
||||
SHARE_CODE_H["internal/handler/share_code.go"]
|
||||
CURVE_H["internal/handler/curve.go"]
|
||||
IMPEDANCE_H["internal/handler/impedance.go"]
|
||||
end
|
||||
subgraph "仓库层"
|
||||
BRAND_R["internal/repository/brand.go"]
|
||||
@@ -88,6 +98,7 @@ OTA_R["internal/repository/ota.go"]
|
||||
DEVICE_R["internal/repository/device.go"]
|
||||
SHARE_CODE_R["internal/repository/share_code.go"]
|
||||
CURVE_R["internal/repository/curve.go"]
|
||||
IMPEDANCE_R["internal/repository/headphone_impedance.go"]
|
||||
end
|
||||
subgraph "模型层"
|
||||
MODEL_M["internal/model/brand.go"]
|
||||
@@ -97,6 +108,7 @@ MODEL_M4["internal/model/user_device.go"]
|
||||
MODEL_M5["internal/model/user_active.go"]
|
||||
MODEL_M6["internal/model/share_code.go"]
|
||||
MODEL_M7["internal/model/target.go"]
|
||||
MODEL_M8["internal/model/user_headphone_impedance.go"]
|
||||
end
|
||||
subgraph "缓存层"
|
||||
CACHE["internal/cache/*"]
|
||||
@@ -108,11 +120,13 @@ end
|
||||
subgraph "配置与数据库"
|
||||
DB_CFG["internal/config/database.go"]
|
||||
SHARE_TTL["internal/config/share_code_ttl.go"]
|
||||
CONFIG["internal/config/config.go"]
|
||||
DB_SQL["sql/*.sql"]
|
||||
end
|
||||
subgraph "任务处理"
|
||||
DEVICE_TASK["internal/task/device_persist.go"]
|
||||
SHARE_TASK["internal/task/share_code_persist.go"]
|
||||
IMPEDANCE_TASK["internal/task/impedance_persist.go"]
|
||||
end
|
||||
MAIN --> ROUTER
|
||||
ROUTER --> BRAND_H
|
||||
@@ -122,6 +136,7 @@ ROUTER --> OTA_H
|
||||
ROUTER --> DEVICE_H
|
||||
ROUTER --> SHARE_CODE_H
|
||||
ROUTER --> CURVE_H
|
||||
ROUTER --> IMPEDANCE_H
|
||||
BRAND_H --> BRAND_R
|
||||
MODEL_H --> MODEL_R
|
||||
MODEL_LIST_H --> MODEL_R
|
||||
@@ -130,6 +145,7 @@ DEVICE_H --> DEVICE_R
|
||||
SHARE_CODE_H --> CACHE
|
||||
SHARE_CODE_H --> SHARE_CODE_R
|
||||
CURVE_H --> CURVE_R
|
||||
IMPEDANCE_H --> IMPEDANCE_R
|
||||
BRAND_R --> MODEL_M
|
||||
MODEL_R --> MODEL_M2
|
||||
OTA_R --> MODEL_M3
|
||||
@@ -137,6 +153,7 @@ DEVICE_R --> MODEL_M4
|
||||
DEVICE_R --> MODEL_M5
|
||||
SHARE_CODE_R --> MODEL_M6
|
||||
CURVE_R --> MODEL_M7
|
||||
IMPEDANCE_R --> MODEL_M8
|
||||
BRAND_H --> RESP
|
||||
MODEL_H --> RESP
|
||||
MODEL_LIST_H --> RESP
|
||||
@@ -144,11 +161,15 @@ OTA_H --> RESP
|
||||
DEVICE_H --> RESP
|
||||
SHARE_CODE_H --> RESP
|
||||
CURVE_H --> RESP
|
||||
IMPEDANCE_H --> RESP
|
||||
DEVICE_H --> ENCODE
|
||||
SHARE_CODE_H --> ENCODE
|
||||
SHARE_TASK --> SHARE_CODE_R
|
||||
IMPEDANCE_TASK --> IMPEDANCE_R
|
||||
IMPEDANCE_TASK --> CACHE
|
||||
DEVICE_TASK --> DEVICE_R
|
||||
SHARE_TASK --> SHARE_CODE_R
|
||||
DB_CFG --> DB_SQL
|
||||
CONFIG --> DB_SQL
|
||||
CACHE --> SHARE_TTL
|
||||
```
|
||||
|
||||
@@ -162,12 +183,14 @@ CACHE --> SHARE_TTL
|
||||
- [internal/handler/device.go:15-83](file://internal/handler/device.go#L15-L83)
|
||||
- [internal/handler/share_code.go:1-383](file://internal/handler/share_code.go#L1-L383)
|
||||
- [internal/handler/curve.go:1-582](file://internal/handler/curve.go#L1-L582)
|
||||
- [internal/handler/impedance.go:1-115](file://internal/handler/impedance.go#L1-L115)
|
||||
- [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/repository/share_code.go:1-59](file://internal/repository/share_code.go#L1-L59)
|
||||
- [internal/repository/curve.go:1-66](file://internal/repository/curve.go#L1-L66)
|
||||
- [internal/repository/headphone_impedance.go:1-99](file://internal/repository/headphone_impedance.go#L1-L99)
|
||||
- [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-63](file://internal/model/ota.go#L5-L63)
|
||||
@@ -175,10 +198,12 @@ CACHE --> SHARE_TTL
|
||||
- [internal/model/user_active.go:5-13](file://internal/model/user_active.go#L5-L13)
|
||||
- [internal/model/share_code.go:14-25](file://internal/model/share_code.go#L14-L25)
|
||||
- [internal/model/target.go:5-14](file://internal/model/target.go#L5-L14)
|
||||
- [internal/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
|
||||
- [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)
|
||||
- [pkg/encode/base64.go:21-52](file://pkg/encode/base64.go#L21-52)
|
||||
- [internal/config/database.go:17-72](file://internal/config/database.go#L17-L72)
|
||||
- [internal/config/share_code_ttl.go:12-60](file://internal/config/share_code_ttl.go#L12-L60)
|
||||
- [internal/config/config.go:10-23](file://internal/config/config.go#L10-L23)
|
||||
- [sql/model.sql:20-38](file://sql/model.sql#L20-L38)
|
||||
- [sql/ota.sql:23-40](file://sql/ota.sql#L23-L40)
|
||||
- [sql/ota_target_device.sql:23-31](file://sql/ota_target_device.sql#L23-L31)
|
||||
@@ -186,8 +211,10 @@ CACHE --> SHARE_TTL
|
||||
- [sql/share_code_log.sql:8-23](file://sql/share_code_log.sql#L8-L23)
|
||||
- [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/user_headphone_impedance.sql:20-36](file://sql/user_headphone_impedance.sql#L20-L36)
|
||||
- [internal/task/device_persist.go:14-167](file://internal/task/device_persist.go#L14-L167)
|
||||
- [internal/task/share_code_persist.go:1-232](file://internal/task/share_code_persist.go#L1-L232)
|
||||
- [internal/task/impedance_persist.go:1-163](file://internal/task/impedance_persist.go#L1-L163)
|
||||
|
||||
**章节来源**
|
||||
- [cmd/server/main.go:22-64](file://cmd/server/main.go#L22-L64)
|
||||
@@ -209,6 +236,8 @@ CACHE --> SHARE_TTL
|
||||
- **用户设备跟踪模型层**
|
||||
- UserDevice:用户设备信息,包含设备MAC地址、型号、添加时间、当前版本等。
|
||||
- UserActive:用户活动记录,包含设备活跃信息、IP地址、活跃日期等。
|
||||
- **用户耳机阻抗模型层**
|
||||
- UserHeadphoneImpedance:用户耳机阻抗信息,包含MAC地址、设备型号、阻抗值、耳机信息(原始和标准化版本)、IP地址和时间戳等。
|
||||
- **仓库层(Repository)**
|
||||
- BrandRepository:提供按品牌名模糊查询的品牌列表。
|
||||
- ModelRepository:提供按品牌名或型号名模糊查询的型号列表,并处理可空字段的扫描。
|
||||
@@ -216,6 +245,7 @@ CACHE --> SHARE_TTL
|
||||
- DeviceRepository:提供设备信息查询、插入、更新以及活动记录管理。
|
||||
- ShareCodeRepository:提供分享码日志插入、导出记录检查等功能。
|
||||
- CurveRepository:提供型号和目标曲线查询功能。
|
||||
- HeadphoneImpedanceRepository:提供用户耳机阻抗信息的查询、插入和更新功能。
|
||||
- **处理器层(Handler)**
|
||||
- BrandHandler:接收查询参数,调用仓库层,支持可选的响应体 Base64 编码。
|
||||
- ModelHandler:接收品牌与型号查询参数,调用仓库层,支持可选的响应体 Base64 编码。
|
||||
@@ -224,9 +254,11 @@ CACHE --> SHARE_TTL
|
||||
- DeviceHandler:处理设备信息上报,将设备活动信息写入Redis缓存。
|
||||
- ShareCodeHandler:处理分享码创建、查询、导入、删除等操作,支持Redis缓存和数据库持久化。
|
||||
- CurveHandler:处理曲线数据获取,支持目标曲线配置和EQ参数化。
|
||||
- ImpedanceHandler:处理耳机阻抗上报,将阻抗信息写入Redis缓存。
|
||||
- **任务处理层**
|
||||
- DevicePersistTask:定时从Redis缓存批量持久化设备信息到数据库。
|
||||
- ShareCodePersistTask:定时从Redis缓存批量持久化分享码日志到数据库。
|
||||
- ImpedancePersistTask:定时从Redis缓存批量持久化耳机阻抗信息到数据库。
|
||||
|
||||
**章节来源**
|
||||
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
|
||||
@@ -236,12 +268,14 @@ CACHE --> SHARE_TTL
|
||||
- [internal/model/target.go:5-14](file://internal/model/target.go#L5-L14)
|
||||
- [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/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
|
||||
- [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/repository/share_code.go:12-59](file://internal/repository/share_code.go#L12-L59)
|
||||
- [internal/repository/curve.go:11-66](file://internal/repository/curve.go#L11-L66)
|
||||
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
|
||||
- [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)
|
||||
@@ -249,11 +283,13 @@ CACHE --> SHARE_TTL
|
||||
- [internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
|
||||
- [internal/handler/share_code.go:15-383](file://internal/handler/share_code.go#L15-L383)
|
||||
- [internal/handler/curve.go:27-582](file://internal/handler/curve.go#L27-L582)
|
||||
- [internal/handler/impedance.go:30-107](file://internal/handler/impedance.go#L30-L107)
|
||||
- [internal/task/device_persist.go:24-167](file://internal/task/device_persist.go#L24-L167)
|
||||
- [internal/task/share_code_persist.go:14-232](file://internal/task/share_code_persist.go#L14-L232)
|
||||
- [internal/task/impedance_persist.go:30-163](file://internal/task/impedance_persist.go#L30-L163)
|
||||
|
||||
## 架构总览
|
||||
下图展示了从请求到响应的关键路径,包括处理器、仓库、数据库与可选编码流程,以及新增的分享码功能、目标曲线配置和增强的OTA固件升级功能。
|
||||
下图展示了从请求到响应的关键路径,包括处理器、仓库、数据库与可选编码流程,以及新增的分享码功能、目标曲线配置、增强的OTA固件升级功能和用户耳机阻抗跟踪功能。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -264,6 +300,12 @@ participant Repo as "仓库层"
|
||||
participant Cache as "缓存层"
|
||||
participant DB as "数据库"
|
||||
participant Enc as "编码层"
|
||||
C->>R : "GET /audio/reportImpedance?mac=xx&name=Luxsin-X9&brand=Sony&model=WH-1000XM4&value=32"
|
||||
R->>H : "ImpedanceHandler.ReportImpedance"
|
||||
H->>H : "参数校验 + 文本归一化"
|
||||
H->>Cache : "HSet(headphone_impedances, mac|brand_norm|model_norm, JSON)"
|
||||
Cache-->>H : "确认写入"
|
||||
H-->>C : "200 OK + 操作成功"
|
||||
C->>R : "POST /audio/shareCreate"
|
||||
R->>H : "ShareCodeHandler.ExportShareCode"
|
||||
H->>Cache : "Create(分享码, EQ数据)"
|
||||
@@ -273,16 +315,6 @@ Repo->>DB : "插入分享码日志"
|
||||
DB-->>Repo : "确认插入"
|
||||
Repo-->>H : "确认"
|
||||
H-->>C : "200 OK + 分享码信息"
|
||||
C->>R : "GET /audio/shareQuery?shareCode=ABCDE"
|
||||
R->>H : "ShareCodeHandler.QueryShareCode"
|
||||
H->>Cache : "Get(分享码)"
|
||||
Cache-->>H : "ShareCodeData"
|
||||
H-->>C : "200 OK + EQ数据"
|
||||
C->>R : "GET /audio/shareAccept?mac=xx&model=Luxsin-X9&shareCode=ABCDE"
|
||||
R->>H : "ShareCodeHandler.ImportShareCode"
|
||||
H->>Cache : "EnqueueImportLog(导入日志)"
|
||||
Cache-->>H : "排队成功"
|
||||
H-->>C : "200 OK + EQ数据"
|
||||
C->>R : "GET /audio/getCurve?brand=xxx&name=xxx&target=xxx"
|
||||
R->>H : "CurveHandler.GetCurve"
|
||||
H->>Repo : "GetTargetByLabel(目标曲线)"
|
||||
@@ -295,10 +327,9 @@ H-->>C : "200 OK + parametric_eq"
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
- [internal/router/router.go:73-77](file://internal/router/router.go#L73-L77)
|
||||
- [internal/router/router.go:64-80](file://internal/router/router.go#L64-L80)
|
||||
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
|
||||
- [internal/handler/share_code.go:40-125](file://internal/handler/share_code.go#L40-L125)
|
||||
- [internal/handler/share_code.go:192-211](file://internal/handler/share_code.go#L192-L211)
|
||||
- [internal/handler/share_code.go:228-279](file://internal/handler/share_code.go#L228-L279)
|
||||
- [internal/handler/curve.go:145-198](file://internal/handler/curve.go#L145-L198)
|
||||
- [internal/repository/curve.go:44-66](file://internal/repository/curve.go#L44-L66)
|
||||
- [internal/cache/curve_cache.go:27-102](file://internal/cache/curve_cache.go#L27-L102)
|
||||
@@ -702,6 +733,76 @@ DeviceRepository --> UserActive : "返回"
|
||||
- [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)
|
||||
|
||||
### 用户耳机阻抗数据模型
|
||||
- **设计理念**
|
||||
- UserHeadphoneImpedance模型用于存储用户上报的耳机阻抗信息,支持去重更新机制。
|
||||
- 每个用户(MAC地址)可以拥有多个耳机,以"耳机品牌 + 耳机型号"进行区分。
|
||||
- 采用去重更新策略,同一耳机的阻抗信息始终反映最新值。
|
||||
- **字段定义与约束**
|
||||
- ID:整数,主键,自增。
|
||||
- MacAddr:字符串,设备MAC地址,用户标识。
|
||||
- DeviceModel:字符串,设备型号(Luxsin-X8/Luxsin-X9)。
|
||||
- ImpedanceOhm:整数,阻抗值(单位:欧姆Ω)。
|
||||
- HeadphoneBrand:字符串,耳机品牌(原始输入)。
|
||||
- HeadphoneModel:字符串,耳机型号(原始输入)。
|
||||
- HeadphoneBrandNorm:字符串,耳机品牌(trim+lower,用于去重)。
|
||||
- HeadphoneModelNorm:字符串,耳机型号(trim+lower,用于去重)。
|
||||
- IpAddr:字符串,服务端获取的设备IP地址。
|
||||
- CreateAt:时间戳,记录创建时间。
|
||||
- UpdateAt:时间戳,记录更新时间。
|
||||
- **业务含义**
|
||||
- 支持用户上报耳机阻抗信息,为后续音频调优提供数据支持。
|
||||
- 通过规范化处理确保数据一致性,避免重复记录。
|
||||
- 保持最新阻抗值,不保留历史版本。
|
||||
- **数据库映射**
|
||||
- 表名:user_headphone_impedance。
|
||||
- 字段:完整映射关系详见数据库表结构。
|
||||
- 索引:唯一索引 (mac_addr, headphone_brand_norm, headphone_model_norm),辅助索引 (mac_addr)、(device_model)。
|
||||
- **ORM 映射配置**
|
||||
- 使用原生 sql.DB 扫描,直接映射到结构体字段。
|
||||
- 时间字段使用 time.Time 类型。
|
||||
- **查询流程**
|
||||
- 支持按MAC地址和规范化耳机信息进行查询。
|
||||
- 支持插入新记录和更新现有记录。
|
||||
- **去重规则**
|
||||
- 唯一判定条件:mac_addr + headphone_brand_norm + headphone_model_norm。
|
||||
- 命中相同记录时,更新阻抗值、设备型号、IP地址和更新时间。
|
||||
- 不命中相同记录时,插入新记录。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class UserHeadphoneImpedance {
|
||||
+int ID
|
||||
+string MacAddr
|
||||
+string DeviceModel
|
||||
+int ImpedanceOhm
|
||||
+string HeadphoneBrand
|
||||
+string HeadphoneModel
|
||||
+string HeadphoneBrandNorm
|
||||
+string HeadphoneModelNorm
|
||||
+string IpAddr
|
||||
+time CreateAt
|
||||
+time UpdateAt
|
||||
}
|
||||
class HeadphoneImpedanceRepository {
|
||||
-db *sql.DB
|
||||
+FindByMacAndNorm(ctx, macAddr, brandNorm, modelNorm) *UserHeadphoneImpedance
|
||||
+Insert(ctx, rec) error
|
||||
+Update(ctx, rec) error
|
||||
}
|
||||
HeadphoneImpedanceRepository --> UserHeadphoneImpedance : "返回"
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
- [internal/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
|
||||
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
|
||||
- [sql/user_headphone_impedance.sql:20-36](file://sql/user_headphone_impedance.sql#L20-L36)
|
||||
|
||||
**章节来源**
|
||||
- [internal/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
|
||||
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
|
||||
- [sql/user_headphone_impedance.sql:20-36](file://sql/user_headphone_impedance.sql#L20-L36)
|
||||
|
||||
### 处理器与序列化机制
|
||||
- **统一响应体**
|
||||
- 统一响应体包含 code、message、data 字段,便于前端统一处理。
|
||||
@@ -723,35 +824,46 @@ DeviceRepository --> UserActive : "返回"
|
||||
- **曲线处理器特殊逻辑**
|
||||
- 支持目标曲线配置查询和EQ参数化计算。
|
||||
- 使用Redis缓存优化曲线数据访问性能。
|
||||
- **耳机阻抗处理器特殊逻辑**
|
||||
- 支持耳机阻抗信息上报,进行参数校验和文本归一化处理。
|
||||
- 将阻抗信息写入Redis缓存,支持异步持久化。
|
||||
- 提供标准化的耳机品牌和型号处理,确保数据一致性。
|
||||
- **错误处理**
|
||||
- 处理器捕获仓库层错误,记录日志并通过统一响应体返回内部错误。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as "客户端"
|
||||
participant H as "ShareCodeHandler"
|
||||
participant Cache as "ShareCodeCache"
|
||||
participant Repo as "ShareCodeRepository"
|
||||
participant H as "ImpedanceHandler"
|
||||
participant Cache as "Redis缓存"
|
||||
participant Task as "持久化任务"
|
||||
participant Repo as "HeadphoneImpedanceRepository"
|
||||
participant DB as "数据库"
|
||||
participant Enc as "编码层"
|
||||
C->>H : "POST /audio/shareCreate"
|
||||
H->>Cache : "Create(mac, model, eqData)"
|
||||
Cache-->>H : "ShareCodeData"
|
||||
H->>Repo : "InsertLog(export)"
|
||||
Repo->>DB : "插入日志"
|
||||
DB-->>Repo : "确认"
|
||||
Repo-->>H : "确认"
|
||||
H-->>C : "200 OK + 分享码信息"
|
||||
C->>H : "GET /audio/shareAccept?shareCode=ABCDE"
|
||||
H->>Cache : "EnqueueImportLog(mac, model, eqData)"
|
||||
Cache-->>H : "排队成功"
|
||||
H-->>C : "200 OK + EQ数据"
|
||||
C->>H : "GET /audio/reportImpedance?mac=xx&name=Luxsin-X9&brand=Sony&model=WH-1000XM4&value=32"
|
||||
H->>H : "参数校验 + 文本归一化"
|
||||
H->>Cache : "HSet(headphone_impedances, mac|brand_norm|model_norm, JSON)"
|
||||
Cache-->>H : "确认写入"
|
||||
H-->>C : "200 OK + 操作成功"
|
||||
Note over Task,DB : "定时任务每5分钟执行一次"
|
||||
Task->>Cache : "HGetAll(headphone_impedances)"
|
||||
Cache-->>Task : "阻抗数据列表"
|
||||
Task->>Repo : "FindByMacAndNorm(查询是否存在)"
|
||||
Repo->>DB : "查询数据库"
|
||||
DB-->>Repo : "返回结果"
|
||||
alt 记录不存在
|
||||
Repo->>DB : "INSERT 新记录"
|
||||
else 记录存在
|
||||
Repo->>DB : "UPDATE 更新记录"
|
||||
end
|
||||
Task->>Cache : "HDel 删除已持久化数据"
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
|
||||
- [internal/task/impedance_persist.go:44-163](file://internal/task/impedance_persist.go#L44-L163)
|
||||
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
|
||||
- [internal/handler/share_code.go:40-125](file://internal/handler/share_code.go#L40-L125)
|
||||
- [internal/handler/share_code.go:228-279](file://internal/handler/share_code.go#L228-L279)
|
||||
- [internal/repository/share_code.go:20-59](file://internal/repository/share_code.go#L20-L59)
|
||||
- [internal/handler/curve.go:145-198](file://internal/handler/curve.go#L145-L198)
|
||||
- [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)
|
||||
|
||||
@@ -763,6 +875,7 @@ H-->>C : "200 OK + EQ数据"
|
||||
- [internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
|
||||
- [internal/handler/share_code.go:15-383](file://internal/handler/share_code.go#L15-L383)
|
||||
- [internal/handler/curve.go:27-582](file://internal/handler/curve.go#L27-L582)
|
||||
- [internal/handler/impedance.go:30-107](file://internal/handler/impedance.go#L30-L107)
|
||||
- [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)
|
||||
|
||||
@@ -789,25 +902,28 @@ H-->>C : "200 OK + EQ数据"
|
||||
- **目标曲线查询**
|
||||
- 支持按标签查询目标曲线配置。
|
||||
- 支持CSV文件路径和贝斯增强配置的动态加载。
|
||||
- **耳机阻抗查询**
|
||||
- 支持按MAC地址和规范化耳机信息进行查询。
|
||||
- 支持插入新记录和更新现有记录的去重逻辑。
|
||||
- 使用复合唯一索引确保数据一致性。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["进入分享码查询"]) --> Params["参数校验"]
|
||||
Params --> CheckMac{"mac参数是否非空?"}
|
||||
CheckMac --> |是| ListByMac["ListByMac 查询"]
|
||||
CheckMac --> |否| CheckCode{"shareCode参数是否5位?"}
|
||||
CheckCode --> |是| GetByCode["Get 查询"]
|
||||
CheckCode --> |否| Error["返回错误"]
|
||||
ListByMac --> CleanExpired["清理过期条目"]
|
||||
CleanExpired --> GetDetails["逐个获取详情"]
|
||||
GetDetails --> ReturnList["返回分享码列表"]
|
||||
GetByCode --> ReturnDetail["返回分享码详情"]
|
||||
ReturnList --> End(["返回结果"])
|
||||
ReturnDetail --> End
|
||||
Error --> End
|
||||
Start(["进入耳机阻抗上报"]) --> Params["参数校验"]
|
||||
Params --> CheckEmpty{"所有参数是否为空?"}
|
||||
CheckEmpty --> |是| Error["返回参数校验失败"]
|
||||
CheckEmpty --> |否| ParseValue["解析阻抗值"]
|
||||
ParseValue --> CheckValue{"阻抗值是否为有效整数?"}
|
||||
CheckValue --> |否| Error
|
||||
CheckValue --> |是| Normalize["文本归一化处理"]
|
||||
Normalize --> RedisWrite["写入Redis缓存"]
|
||||
RedisWrite --> Success["返回操作成功"]
|
||||
Error --> End(["结束"])
|
||||
Success --> End
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
|
||||
- [internal/handler/share_code.go:138-177](file://internal/handler/share_code.go#L138-L177)
|
||||
- [internal/handler/share_code.go:192-211](file://internal/handler/share_code.go#L192-L211)
|
||||
- [internal/cache/share_code_cache.go:281-319](file://internal/cache/share_code_cache.go#L281-L319)
|
||||
@@ -819,29 +935,33 @@ Error --> End
|
||||
- [internal/repository/device.go:19-88](file://internal/repository/device.go#L19-L88)
|
||||
- [internal/repository/share_code.go:20-59](file://internal/repository/share_code.go#L20-L59)
|
||||
- [internal/repository/curve.go:44-66](file://internal/repository/curve.go#L44-L66)
|
||||
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
|
||||
- [internal/handler/share_code.go:138-177](file://internal/handler/share_code.go#L138-L177)
|
||||
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
|
||||
|
||||
## 依赖分析
|
||||
- **层间耦合**
|
||||
- Handler 依赖 Repository;Repository 依赖 sql.DB;Model 为纯数据结构。
|
||||
- 统一响应体与编码层被 Handler 调用,降低重复逻辑。
|
||||
- 新增的Redis依赖用于分享码缓存和设备信息缓存。
|
||||
- 新增的Redis依赖用于分享码缓存、设备信息缓存和耳机阻抗缓存。
|
||||
- 分享码功能依赖Redis缓存和数据库的双重存储。
|
||||
- 目标曲线功能依赖CurveRepository和CurveCache。
|
||||
- 耳机阻抗功能依赖Redis缓存和数据库的双重存储。
|
||||
- **外部依赖**
|
||||
- 数据库:MySQL,通过 sql.DB 访问。
|
||||
- 日志:zap。
|
||||
- Web 框架:Gin。
|
||||
- 搜索:Meilisearch(ModelListHandler)。
|
||||
- 缓存:Redis(Device、ShareCode、Curve相关处理器)。
|
||||
- 缓存:Redis(Device、ShareCode、Curve、Impedance相关处理器)。
|
||||
- 对象存储:S3(曲线数据CSV文件)。
|
||||
- 任务调度:定时任务用于设备信息和分享码持久化。
|
||||
- 任务调度:定时任务用于设备信息、分享码和耳机阻抗持久化。
|
||||
- **潜在循环依赖**
|
||||
- 当前结构清晰,无循环导入迹象。
|
||||
- **新增依赖关系**
|
||||
- 分享码功能依赖Redis缓存和数据库的双重存储。
|
||||
- 目标曲线功能依赖CurveRepository和CurveCache。
|
||||
- OTA功能依赖多个表的关联查询。
|
||||
- 耳机阻抗功能依赖Redis缓存和数据库的双重存储,以及定时持久化任务。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
@@ -853,15 +973,18 @@ H_Device["DeviceHandler"] --> Repo_Device["DeviceRepository"]
|
||||
H_ShareCode["ShareCodeHandler"] --> Cache_Share["ShareCodeCache"]
|
||||
H_ShareCode --> Repo_Share["ShareCodeRepository"]
|
||||
H_Curve["CurveHandler"] --> Repo_Curve["CurveRepository"]
|
||||
H_Impedance["ImpedanceHandler"] --> Repo_Impedance["HeadphoneImpedanceRepository"]
|
||||
Repo_Brand --> DB["sql.DB"]
|
||||
Repo_Model --> DB
|
||||
Repo_OTA --> DB
|
||||
Repo_Device --> DB
|
||||
Repo_Share --> DB
|
||||
Repo_Curve --> DB
|
||||
Repo_Impedance --> DB
|
||||
H_Device --> Redis["Redis Client"]
|
||||
H_ShareCode --> Redis
|
||||
H_Curve --> Redis
|
||||
H_Impedance --> Redis
|
||||
H_Curve --> S3["S3 Storage"]
|
||||
H_Brand --> Resp["统一响应体"]
|
||||
H_Model --> Resp
|
||||
@@ -870,6 +993,7 @@ H_OTA --> Resp
|
||||
H_Device --> Resp
|
||||
H_ShareCode --> Resp
|
||||
H_Curve --> Resp
|
||||
H_Impedance --> Resp
|
||||
H_Brand --> Encode["Base64 编码"]
|
||||
H_Model --> Encode
|
||||
H_ModelList --> Encode
|
||||
@@ -877,6 +1001,8 @@ Task_Device["DevicePersistTask"] --> Repo_Device
|
||||
Task_Device --> Redis
|
||||
Task_Share["ShareCodePersistTask"] --> Repo_Share
|
||||
Task_Share --> Redis
|
||||
Task_Impedance["ImpedancePersistTask"] --> Repo_Impedance
|
||||
Task_Impedance --> Redis
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
@@ -887,16 +1013,19 @@ Task_Share --> Redis
|
||||
- [internal/handler/device.go:15-83](file://internal/handler/device.go#L15-L83)
|
||||
- [internal/handler/share_code.go:15-383](file://internal/handler/share_code.go#L15-L383)
|
||||
- [internal/handler/curve.go:27-582](file://internal/handler/curve.go#L27-L582)
|
||||
- [internal/handler/impedance.go:18-107](file://internal/handler/impedance.go#L18-L107)
|
||||
- [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/repository/share_code.go:12-59](file://internal/repository/share_code.go#L12-L59)
|
||||
- [internal/repository/curve.go:11-66](file://internal/repository/curve.go#L11-L66)
|
||||
- [internal/repository/headphone_impedance.go:11-99](file://internal/repository/headphone_impedance.go#L11-L99)
|
||||
- [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/task/share_code_persist.go:14-232](file://internal/task/share_code_persist.go#L14-L232)
|
||||
- [internal/task/impedance_persist.go:16-163](file://internal/task/impedance_persist.go#L16-L163)
|
||||
|
||||
**章节来源**
|
||||
- [internal/router/router.go:14-82](file://internal/router/router.go#L14-L82)
|
||||
@@ -909,6 +1038,7 @@ Task_Share --> Redis
|
||||
- UserDevice表对(mac_addr)建立唯一索引,支持快速设备查询。
|
||||
- UserActive表对(mac_addr, active_date)建立联合索引,支持按日期分区查询。
|
||||
- share_code_log表对(mac_addr, share_code, create_at)建立索引,支持分享码查询。
|
||||
- user_headphone_impedance表对(mac_addr, headphone_brand_norm, headphone_model_norm)建立唯一索引,支持快速阻抗查询和去重。
|
||||
- 查询时优先按品牌名匹配,减少 LIKE 的范围。
|
||||
- **更新**:OTA查询现在使用简化的字段列表,减少了不必要的字段扫描。
|
||||
- **更新**:OTA查询现在使用灰度升级设备概念,简化了设备匹配逻辑,提高了查询性能。
|
||||
@@ -916,17 +1046,20 @@ Task_Share --> Redis
|
||||
- 使用 sql.NullString 扫描可空列,避免零值歧义;转换为指针字符串减少冗余存储。
|
||||
- OTA模型使用BoolInt类型处理布尔值,支持数据库整型与JSON布尔值的双向转换。
|
||||
- ShareCodeLog模型使用[]byte存储JSON数据,减少字符串处理开销。
|
||||
- 耳机阻抗模型使用原生类型,避免额外的类型转换开销。
|
||||
- **缓存策略**
|
||||
- 设备信息先写入Redis缓存,通过定时任务批量持久化到数据库,减少数据库压力。
|
||||
- 品牌和型号信息使用Redis缓存提升查询性能。
|
||||
- 分享码数据使用Redis缓存,支持TTL自动过期和分布式锁保证一致性。
|
||||
- 目标曲线数据使用Redis缓存,支持fr数据独立存储优化缓存空间。
|
||||
- 耳机阻抗数据使用Redis缓存,支持批量持久化和去重更新。
|
||||
- **编码策略**
|
||||
- 可选的自定义 Base64 编码可降低响应体积,适合大列表传输场景。
|
||||
- **并发与超时**
|
||||
- 服务器设置读写超时与优雅关闭,保障稳定性。
|
||||
- 设备持久化任务和分享码持久化任务使用定时器定期执行,避免阻塞主线程。
|
||||
- 设备持久化任务、分享码持久化任务和耳机阻抗持久化任务使用定时器定期执行,避免阻塞主线程。
|
||||
- 分布式锁机制保证Redis操作的原子性和数据一致性。
|
||||
- 耳机阻抗持久化任务支持部分失败重试,提高系统健壮性。
|
||||
|
||||
**章节来源**
|
||||
- [sql/model.sql:34-35](file://sql/model.sql#L34-L35)
|
||||
@@ -934,14 +1067,17 @@ Task_Share --> Redis
|
||||
- [sql/share_code_log.sql:20-23](file://sql/share_code_log.sql#L20-L23)
|
||||
- [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)
|
||||
- [sql/user_headphone_impedance.sql:32-35](file://sql/user_headphone_impedance.sql#L32-L35)
|
||||
- [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)
|
||||
- [internal/repository/share_code.go:20-59](file://internal/repository/share_code.go#L20-L59)
|
||||
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
|
||||
- [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)
|
||||
- [internal/task/share_code_persist.go:24-113](file://internal/task/share_code_persist.go#L24-L113)
|
||||
- [internal/task/impedance_persist.go:30-163](file://internal/task/impedance_persist.go#L30-L163)
|
||||
|
||||
## 故障排查指南
|
||||
- **常见问题**
|
||||
@@ -954,15 +1090,18 @@ Task_Share --> Redis
|
||||
- 分享码导入失败:检查导入日志队列、数据库连接、分布式锁状态。
|
||||
- 目标曲线查询失败:检查目标曲线配置、CSV文件路径、S3访问权限。
|
||||
- **更新**:OTA灰度升级设备查询失败:检查ota_target_device表结构,确认type字段已被移除。
|
||||
- 耳机阻抗上报失败:检查参数完整性、Redis连接状态、文本归一化处理。
|
||||
- 耳机阻抗持久化失败:检查Redis队列状态、数据库连接、唯一索引冲突。
|
||||
- **排查步骤**
|
||||
- 查看日志:处理器记录错误日志,统一响应体返回错误码。
|
||||
- 核对数据库:确认表结构与索引是否存在。
|
||||
- 验证参数:确认查询参数是否符合预期。
|
||||
- 检查Redis:确认设备信息、分享码数据是否正确写入Redis缓存。
|
||||
- 监控任务:确认设备持久化任务和分享码持久化任务是否按预期执行。
|
||||
- 检查Redis:确认设备信息、分享码数据、耳机阻抗数据是否正确写入Redis缓存。
|
||||
- 监控任务:确认设备持久化任务、分享码持久化任务和耳机阻抗持久化任务是否按预期执行。
|
||||
- 检查分布式锁:确认分享码操作的分布式锁状态和超时处理。
|
||||
- **更新**:检查OTA表结构,确认paw*字段不存在,验证简化的字段结构。
|
||||
- **更新**:检查ota_target_device表结构,确认type字段已被移除,验证灰度升级设备查询逻辑。
|
||||
- 检查耳机阻抗数据:确认规范化字段是否正确生成,唯一索引是否正常工作。
|
||||
|
||||
**章节来源**
|
||||
- [internal/config/database.go:57-72](file://internal/config/database.go#L57-L72)
|
||||
@@ -973,13 +1112,16 @@ Task_Share --> Redis
|
||||
- [internal/handler/device.go:33-39](file://internal/handler/device.go#L33-L39)
|
||||
- [internal/handler/share_code.go:46-81](file://internal/handler/share_code.go#L46-L81)
|
||||
- [internal/handler/curve.go:302-368](file://internal/handler/curve.go#L302-L368)
|
||||
- [internal/handler/impedance.go:51-66](file://internal/handler/impedance.go#L51-L66)
|
||||
- [internal/response/response.go:30-37](file://internal/response/response.go#L30-L37)
|
||||
|
||||
## 结论
|
||||
本项目的数据模型设计简洁明确:Brand 与 Model 分别承担品牌与型号的维度,通过仓库层的原生 SQL 访问实现高效查询;处理器层统一响应与可选编码,提升传输效率与前端体验。新增的分享码功能、目标曲线模型和增强的OTA模型进一步完善了系统的数据模型体系,支持复杂的固件升级策略、设备信息跟踪、用户行为分析和EQ数据分享功能。数据库层面通过唯一索引与合理字段设计保障一致性与性能。Redis缓存层提供了高性能的临时数据存储和分布式锁机制。
|
||||
本项目的数据模型设计简洁明确:Brand 与 Model 分别承担品牌与型号的维度,通过仓库层的原生 SQL 访问实现高效查询;处理器层统一响应与可选编码,提升传输效率与前端体验。新增的分享码功能、目标曲线模型、增强的OTA模型和用户耳机阻抗跟踪功能进一步完善了系统的数据模型体系,支持复杂的固件升级策略、设备信息跟踪、用户行为分析、EQ数据分享和耳机阻抗采集功能。数据库层面通过唯一索引与合理字段设计保障一致性与性能。Redis缓存层提供了高性能的临时数据存储和分布式锁机制。
|
||||
|
||||
**更新**:OTA模型经过简化重构,移除了pawVerCode、pawVerName、pawUrl、pawMd5四个冗余字段,现在只包含核心升级信息,提高了数据模型的清晰度和维护性。同时,OTATargetDevice模型从"定向设备"概念重构为"灰度升级设备"概念,移除了Type字段,简化了设备管理逻辑,提高了系统的易用性和维护性。新的字段结构更加简洁,减少了不必要的数据冗余,同时保持了完整的升级策略支持能力。
|
||||
|
||||
**新增**:UserHeadphoneImpedance数据模型为用户耳机阻抗信息采集提供了完善的数据支撑,通过规范化处理和去重更新机制确保了数据的一致性和准确性。Redis缓存与定时持久化任务的结合,在保证性能的同时实现了数据的可靠存储。
|
||||
|
||||
建议在后续迭代中引入显式的校验与转换层,增强健壮性与可维护性,同时考虑添加更多的监控指标和告警机制。
|
||||
|
||||
## 附录
|
||||
@@ -1011,6 +1153,9 @@ Task_Share --> Redis
|
||||
- **表:target**
|
||||
- 字段:id(主键)、label、read_csv、file、bassBoost、addtime。
|
||||
- 约束:无显式约束。
|
||||
- **表:user_headphone_impedance**
|
||||
- 字段:id(主键)、mac_addr、device_model、impedance_ohm、headphone_brand、headphone_model、headphone_brand_norm、headphone_model_norm、ip_addr、create_at、update_at。
|
||||
- 约束:唯一索引 uniq_mac_brand_model(mac_addr, headphone_brand_norm, headphone_model_norm),辅助索引 idx_mac(mac_addr)、idx_device_model(device_model)。
|
||||
- **映射关系**
|
||||
- Model.id ↔ model.id
|
||||
- Model.brandName ↔ model.brand_name
|
||||
@@ -1063,6 +1208,17 @@ Task_Share --> Redis
|
||||
- Target.File ↔ target.file
|
||||
- Target.BassBoost ↔ target.bassBoost
|
||||
- Target.AddTime ↔ target.addtime
|
||||
- UserHeadphoneImpedance.ID ↔ user_headphone_impedance.id
|
||||
- UserHeadphoneImpedance.MacAddr ↔ user_headphone_impedance.mac_addr
|
||||
- UserHeadphoneImpedance.DeviceModel ↔ user_headphone_impedance.device_model
|
||||
- UserHeadphoneImpedance.ImpedanceOhm ↔ user_headphone_impedance.impedance_ohm
|
||||
- UserHeadphoneImpedance.HeadphoneBrand ↔ user_headphone_impedance.headphone_brand
|
||||
- UserHeadphoneImpedance.HeadphoneModel ↔ user_headphone_impedance.headphone_model
|
||||
- UserHeadphoneImpedance.HeadphoneBrandNorm ↔ user_headphone_impedance.headphone_brand_norm
|
||||
- UserHeadphoneImpedance.HeadphoneModelNorm ↔ user_headphone_impedance.headphone_model_norm
|
||||
- UserHeadphoneImpedance.IpAddr ↔ user_headphone_impedance.ip_addr
|
||||
- UserHeadphoneImpedance.CreateAt ↔ user_headphone_impedance.create_at
|
||||
- UserHeadphoneImpedance.UpdateAt ↔ user_headphone_impedance.update_at
|
||||
|
||||
**章节来源**
|
||||
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
|
||||
@@ -1073,12 +1229,14 @@ Task_Share --> Redis
|
||||
- [sql/user_active.sql:24-32](file://sql/user_active.sql#L24-L32)
|
||||
- [sql/share_code_log.sql:9-23](file://sql/share_code_log.sql#L9-L23)
|
||||
- [sql/target.sql:24-31](file://sql/target.sql#L24-L31)
|
||||
- [sql/user_headphone_impedance.sql:20-36](file://sql/user_headphone_impedance.sql#L20-L36)
|
||||
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
|
||||
- [internal/model/ota.go:29-63](file://internal/model/ota.go#L29-L63)
|
||||
- [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/model/share_code.go:14-25](file://internal/model/share_code.go#L14-L25)
|
||||
- [internal/model/target.go:5-14](file://internal/model/target.go#L5-L14)
|
||||
- [internal/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
|
||||
|
||||
### 请求与响应示例(路径参考)
|
||||
- **获取品牌列表**
|
||||
@@ -1113,6 +1271,13 @@ Task_Share --> Redis
|
||||
- 参数:mac(必需)、model(必需)、ver(可选)
|
||||
- 返回:操作结果
|
||||
- 参考路径:[internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
|
||||
- **耳机阻抗上报**
|
||||
- 路由:/audio/reportImpedance
|
||||
- 方法:GET
|
||||
- 参数:mac(必需)、name(必需,设备型号)、brand(必需,耳机品牌)、model(必需,耳机型号)、value(必需,阻抗值整数)
|
||||
- 返回:操作结果
|
||||
- **新增**:支持用户上报耳机阻抗信息,数据进行规范化处理后写入Redis缓存
|
||||
- 参考路径:[internal/handler/impedance.go:30-107](file://internal/handler/impedance.go#L30-L107)
|
||||
- **创建分享码**
|
||||
- 路由:/audio/shareCreate
|
||||
- 方法:POST
|
||||
@@ -1145,6 +1310,7 @@ Task_Share --> Redis
|
||||
- [internal/handler/model_list.go:26-57](file://internal/handler/model_list.go#L26-L57)
|
||||
- [internal/handler/ota.go:23-145](file://internal/handler/ota.go#L23-L145)
|
||||
- [internal/handler/device.go:27-83](file://internal/handler/device.go#L27-L83)
|
||||
- [internal/handler/impedance.go:30-107](file://internal/handler/impedance.go#L30-L107)
|
||||
- [internal/handler/share_code.go:40-125](file://internal/handler/share_code.go#L40-L125)
|
||||
- [internal/handler/share_code.go:192-211](file://internal/handler/share_code.go#L192-L211)
|
||||
- [internal/handler/share_code.go:228-279](file://internal/handler/share_code.go#L228-L279)
|
||||
@@ -1158,6 +1324,7 @@ Task_Share --> Redis
|
||||
- 分享码模型支持多种设备型号,建议添加设备型号验证和限制。
|
||||
- **更新**:OTA模型简化后,减少了字段验证的复杂度,但仍需确保核心字段的完整性。
|
||||
- **更新**:OTATargetDevice模型简化后,灰度升级设备管理更加直观,减少了类型判断的复杂性。
|
||||
- **新增**:耳机阻抗模型支持用户上报阻抗信息,建议添加阻抗值范围验证和设备型号白名单验证。
|
||||
- **版本管理与向后兼容**
|
||||
- 通过 API 版本号(如 /api/v1)隔离变更;新增字段采用可选策略,保持旧字段必填。
|
||||
- 对于破坏性变更,提供迁移脚本与双写策略。
|
||||
@@ -1165,6 +1332,7 @@ Task_Share --> Redis
|
||||
- 分享码功能的新增不影响现有API,保持向前兼容。
|
||||
- **更新**:OTA模型的简化属于向后兼容的重构,不会影响现有API调用。
|
||||
- **更新**:OTATargetDevice模型的重构属于概念性重构,从"定向设备"到"灰度升级设备",保持了API的向后兼容性。
|
||||
- **新增**:耳机阻抗上报API为新功能,不影响现有API,保持向前兼容。
|
||||
- **性能优化**
|
||||
- 为高频查询字段建立索引;避免 SELECT *,仅选择必要字段。
|
||||
- 对大列表启用可选 Base64 编码;结合分页与缓存策略。
|
||||
@@ -1174,6 +1342,7 @@ Task_Share --> Redis
|
||||
- 目标曲线使用Redis缓存,优化fr数据的独立存储。
|
||||
- **更新**:OTA查询现在使用简化的字段列表,减少了查询开销。
|
||||
- **更新**:OTA查询现在使用灰度升级设备概念,简化了设备匹配逻辑,提高了查询性能。
|
||||
- **新增**:耳机阻抗使用Redis缓存,支持批量持久化和去重更新,提高写入性能。
|
||||
- **错误处理与可观测性**
|
||||
- 统一错误码与消息格式;记录关键链路日志;对数据库与外部服务增加超时与重试。
|
||||
- OTA查询失败需要详细的日志记录,包括参数、查询结果等。
|
||||
@@ -1181,6 +1350,7 @@ Task_Share --> Redis
|
||||
- 分享码持久化任务需要监控Redis队列状态和数据库插入结果。
|
||||
- 目标曲线查询需要监控S3访问权限和CSV文件读取。
|
||||
- **更新**:OTA灰度升级设备查询失败需要详细的日志记录,包括设备MAC地址和OTA ID。
|
||||
- **新增**:耳机阻抗持久化任务需要监控Redis队列状态、数据库插入结果和唯一索引冲突。
|
||||
- **安全考虑**
|
||||
- OTA下载链接需要安全性验证,防止恶意下载。
|
||||
- 设备信息上报需要参数验证,防止注入攻击。
|
||||
@@ -1189,6 +1359,7 @@ Task_Share --> Redis
|
||||
- 分享码删除操作需要MAC地址验证,防止越权操作。
|
||||
- EQ数据存储需要JSON格式验证,防止恶意数据注入。
|
||||
- **更新**:灰度升级设备管理需要MAC地址验证,防止越权访问。
|
||||
- **新增**:耳机阻抗上报需要参数验证,防止恶意数据注入和数值溢出。
|
||||
- **监控与运维**
|
||||
- 添加Redis连接池监控,确保缓存层稳定运行。
|
||||
- 添加数据库连接池监控,避免连接泄漏。
|
||||
@@ -1196,4 +1367,13 @@ Task_Share --> Redis
|
||||
- 添加分布式锁监控,防止死锁和资源竞争。
|
||||
- 添加S3访问监控,确保CSV文件读取正常。
|
||||
- **更新**:监控OTA表结构变化,确保简化的字段结构得到正确应用。
|
||||
- **更新**:监控ota_target_device表结构变化,确保type字段已被移除,验证灰度升级设备查询逻辑。
|
||||
- **更新**:监控ota_target_device表结构变化,确保type字段已被移除,验证灰度升级设备查询逻辑。
|
||||
- **新增**:监控耳机阻抗表结构变化,确保唯一索引正常工作,验证去重更新逻辑。
|
||||
- **新增**:监控耳机阻抗持久化任务的执行状态和错误率。
|
||||
|
||||
**章节来源**
|
||||
- [internal/config/config.go:10-23](file://internal/config/config.go#L10-L23)
|
||||
- [internal/config/config.go:68-69](file://internal/config/config.go#L68-L69)
|
||||
- [internal/handler/impedance.go:51-66](file://internal/handler/impedance.go#L51-L66)
|
||||
- [internal/task/impedance_persist.go:102-107](file://internal/task/impedance_persist.go#L102-L107)
|
||||
- [sql/user_headphone_impedance.sql:32-35](file://sql/user_headphone_impedance.sql#L32-L35)
|
||||
Reference in New Issue
Block a user