feat(impedance): 新增用户耳机阻抗功能模块

- 设计并添加 user_headphone_impedance 表,包含唯一复合索引保证数据完整性
- 实现阻抗上报API接口 /audio/reportImpedance,支持设备上传阻抗数据
- 使用Redis Hash结构缓存阻抗数据,键为 "headphone_impedances"
- 新增定时持久化任务,每5分钟从Redis读取数据批量写入数据库
- 对品牌和型号数据进行trim+lower归一化处理,支持数据去重更新
- 优化查询性能,确保mac_addr与品牌型号归一化组合唯一索引生效
- 更新故障排查指南,新增Redis缓存及唯一索引相关问题检查
- 新增阻抗持久化配置开关,允许按需启用该功能
This commit is contained in:
eafonyang
2026-07-07 19:16:02 +08:00
parent 5d2910e89e
commit c1547f9a8f
8 changed files with 1567 additions and 398 deletions
@@ -10,11 +10,13 @@
- [internal/handler/ota.go](file://internal/handler/ota.go)
- [internal/handler/curve.go](file://internal/handler/curve.go)
- [internal/handler/share_code.go](file://internal/handler/share_code.go)
- [internal/handler/impedance.go](file://internal/handler/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/curve.go](file://internal/repository/curve.go)
- [internal/repository/share_code.go](file://internal/repository/share_code.go)
- [internal/repository/headphone_impedance.go](file://internal/repository/headphone_impedance.go)
- [internal/response/response.go](file://internal/response/response.go)
- [pkg/encode/base64.go](file://pkg/encode/base64.go)
- [internal/search/meilisearch.go](file://internal/search/meilisearch.go)
@@ -31,23 +33,28 @@
- [internal/model/ota.go](file://internal/model/ota.go)
- [internal/model/share_code.go](file://internal/model/share_code.go)
- [internal/model/target.go](file://internal/model/target.go)
- [internal/model/user_headphone_impedance.go](file://internal/model/user_headphone_impedance.go)
- [internal/cache/brand_cache.go](file://internal/cache/brand_cache.go)
- [internal/cache/model_cache.go](file://internal/cache/model_cache.go)
- [internal/cache/curve_cache.go](file://internal/cache/curve_cache.go)
- [internal/cache/share_code_cache.go](file://internal/cache/share_code_cache.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)
- [sql/ota.sql](file://sql/ota.sql)
- [sql/black_list.sql](file://sql/black_list.sql)
- [sql/ota_target_device.sql](file://sql/ota_target_device.sql)
- [sql/share_code_log.sql](file://sql/share_code_log.sql)
- [sql/user_headphone_impedance.sql](file://sql/user_headphone_impedance.sql)
</cite>
## 更新摘要
**变更内容**
- OTA处理器内部逻辑已简化:移除了paw相关字段处理,现在专注于标准OTA升级流程
- 处理器接口保持不变,继续提供/api/v1/audio/ota端点
- 查询逻辑已更新以适配简化的OTA结构,但仍保持原有的黑名单过滤和定向升级功能
- 数据库结构保持不变,仍包含ota、black_list、ota_target_device表
- 新增耳机阻抗数据处理业务逻辑,包含 ImpedanceHandler 处理器
- 实现了完整的输入验证、Redis存储、错误处理和异步持久化功能
- 新增 HeadphoneImpedanceRepository 仓储层和用户阻抗数据模型
- 添加了 ImpedancePersistTask 定时任务,实现Redis到数据库的数据同步
- 在路由中注册了 /audio/reportImpedance 端点
- 支持设备MAC地址、品牌型号归一化处理和数据去重更新
## 目录
1. [简介](#简介)
@@ -63,10 +70,10 @@
11. [附录](#附录)
## 简介
本文件面向 Luxsin 应用 API 的业务处理器模块,系统性梳理各处理器的职责、接口设计与实现细节,覆盖 BrandHandler、ModelHandler、ModelListHandler、DeviceHandler、HealthHandler、OTAHandler、CurveHandler 和新增的 ShareCodeHandler。文档重点阐述新的依赖注入模式、错误处理机制、响应格式化策略,以及与 Gin 框架的集成方式;并通过流程图与类图展示处理器间的协作关系与数据流转过程,帮助初学者快速上手,同时为高级开发者提供深入的技术参考。
本文件面向 Luxsin 应用 API 的业务处理器模块,系统性梳理各处理器的职责、接口设计与实现细节,覆盖 BrandHandler、ModelHandler、ModelListHandler、DeviceHandler、HealthHandler、OTAHandler、CurveHandler、ShareCodeHandler 和新增的 ImpedanceHandler。文档重点阐述新的依赖注入模式、错误处理机制、响应格式化策略,以及与 Gin 框架的集成方式;并通过流程图与类图展示处理器间的协作关系与数据流转过程,帮助初学者快速上手,同时为高级开发者提供深入的技术参考。
## 项目结构
业务处理器位于 internal/handler 目录,采用依赖注入模式,围绕"控制器-仓储-搜索-缓存-响应"的分层组织,配合中间件与路由装配,形成清晰的控制流与依赖注入入口。新增的分享码等化曲线功能通过独立的处理器、仓储层和数据模型实现完整的业务能力。
业务处理器位于 internal/handler 目录,采用依赖注入模式,围绕"控制器-仓储-搜索-缓存-响应"的分层组织,配合中间件与路由装配,形成清晰的控制流与依赖注入入口。新增的分享码等化曲线和耳机阻抗数据处理功能通过独立的处理器、仓储层和数据模型实现完整的业务能力。
```mermaid
graph TB
@@ -85,6 +92,7 @@ BRAND["internal/handler/brand.go<br/>NewBrandHandler(brandRepo, log)"]
MODEL["internal/handler/model.go<br/>NewModelHandler(modelRepo, log)"]
MODELLIST["internal/handler/model_list.go<br/>NewModelListHandler(searchClient, log)"]
DEVICE["internal/handler/device.go<br/>NewDeviceHandler(redis, log)"]
IMPEDANCE["internal/handler/impedance.go<br/>NewImpedanceHandler(redis, log)"]
OTA["internal/handler/ota.go<br/>NewOTAHandler(otaRepo, log)"]
CURVE["internal/handler/curve.go<br/>NewCurveHandler(curveRepo, curveCache, eqCfg, s3, log)"]
SHARECODE["internal/handler/share_code.go<br/>NewShareCodeHandler(shareCache, log, maxPerMac)"]
@@ -95,6 +103,7 @@ MODELREPO["internal/repository/model.go<br/>ModelRepository"]
OTAREPO["internal/repository/ota.go<br/>OTARepository"]
CURVEREPO["internal/repository/curve.go<br/>CurveRepository"]
SHARECODEREPO["internal/repository/share_code.go<br/>ShareCodeRepository"]
IMPEDANCEREPO["internal/repository/headphone_impedance.go<br/>HeadphoneImpedanceRepository"]
end
subgraph "基础设施"
RESP["internal/response/response.go<br/>统一响应体"]
@@ -102,8 +111,8 @@ ENCODE["pkg/encode/base64.go<br/>自定义Base64编码"]
SEARCH["internal/search/meilisearch.go<br/>Meilisearch客户端"]
CACHE["internal/cache/*<br/>Redis缓存"]
S3["S3存储<br/>CSV文件"]
DB["MySQL 数据库<br/>ota, black_list, ota_target_device, share_code_log 表"]
TASK["internal/task/share_code_persist.go<br/>分享码持久化任务"]
DB["MySQL 数据库<br/>多表结构"]
TASK["internal/task/*<br/>异步持久化任务"]
end
MAIN --> ROUTER
ROUTER --> CORS
@@ -114,16 +123,19 @@ ROUTER --> BRAND
ROUTER --> MODEL
ROUTER --> MODELLIST
ROUTER --> DEVICE
ROUTER --> IMPEDANCE
ROUTER --> OTA
ROUTER --> CURVE
ROUTER --> SHARECODE
BRAND --> BRANDREPO
MODEL --> MODELREPO
IMPEDANCE --> IMPEDANCEREPO
OTA --> OTAREPO
CURVE --> CURVEREPO
SHARECODE --> SHARECODEREPO
BRANDREPO --> DB
MODELREPO --> DB
IMPEDANCEREPO --> DB
OTAREPO --> DB
CURVEREPO --> DB
SHARECODEREPO --> DB
@@ -134,6 +146,7 @@ BRAND --> RESP
MODEL --> RESP
MODELLIST --> RESP
DEVICE --> RESP
IMPEDANCE --> RESP
OTA --> RESP
CURVE --> RESP
SHARECODE --> RESP
@@ -143,6 +156,8 @@ MODEL --> CACHE
BRAND --> ENCODE
MODEL --> ENCODE
MODELLIST --> ENCODE
TASK --> DB
TASK --> CACHE
```
**图表来源**
@@ -153,6 +168,7 @@ MODELLIST --> ENCODE
- [internal/handler/model.go:18](file://internal/handler/model.go#L18)
- [internal/handler/model_list.go:19](file://internal/handler/model_list.go#L19)
- [internal/handler/device.go:19](file://internal/handler/device.go#L19)
- [internal/handler/impedance.go:23](file://internal/handler/impedance.go#L23)
- [internal/handler/ota.go:19](file://internal/handler/ota.go#L19)
- [internal/handler/curve.go:37](file://internal/handler/curve.go#L37)
- [internal/handler/share_code.go:21](file://internal/handler/share_code.go#L21)
@@ -167,6 +183,7 @@ MODELLIST --> ENCODE
- **型号查询处理器**:按品牌或型号关键字查询型号列表,现在依赖 ModelRepository 实例。
- **型号检索处理器**:基于 Meilisearch 执行全文检索,依赖搜索客户端实例。
- **设备上报处理器**:接收设备信息(MAC、型号、版本、来源 IP),依赖 Redis 客户端。
- **耳机阻抗处理器**:接收耳机阻抗数据上报,包含输入验证、Redis存储、错误处理和异步持久化。**新增功能**。
- **OTA固件升级处理器**:处理固件升级请求,支持黑名单过滤、目标设备分布和版本选择等OTA固件升级能力。**已简化内部逻辑,移除了paw相关字段处理**。
- **等化曲线处理器**:处理频响曲线计算和参数化EQ生成,支持多种目标曲线和设备源。
- **分享代码处理器**:处理EQ数据分享功能,支持创建、查询、导入、删除分享码。
@@ -177,12 +194,13 @@ MODELLIST --> ENCODE
- [internal/handler/model.go:13-23](file://internal/handler/model.go#L13-L23)
- [internal/handler/model_list.go:14-24](file://internal/handler/model_list.go#L14-L24)
- [internal/handler/device.go:14-24](file://internal/handler/device.go#L14-L24)
- [internal/handler/impedance.go:17-28](file://internal/handler/impedance.go#L17-L28)
- [internal/handler/ota.go:14-21](file://internal/handler/ota.go#L14-L21)
- [internal/handler/curve.go:27-39](file://internal/handler/curve.go#L27-L39)
- [internal/handler/share_code.go:15-23](file://internal/handler/share_code.go#L15-L23)
## 架构总览
下图展示从请求进入至响应返回的关键路径,以及处理器与仓储、搜索、缓存、日志等组件的交互,体现了完整的依赖注入架构。新增的分享码等化曲线功能通过独立的处理流程支持复杂的业务场景。
下图展示从请求进入至响应返回的关键路径,以及处理器与仓储、搜索、缓存、日志等组件的交互,体现了完整的依赖注入架构。新增的分享码等化曲线和耳机阻抗数据处理功能通过独立的处理流程支持复杂的业务场景。
```mermaid
sequenceDiagram
@@ -203,6 +221,12 @@ DB-->>H : "命中结果"
else "设备上报"
H->>DB : "Redis HSet(ctx, key, mac, json)"
DB-->>H : "OK 或错误"
else "耳机阻抗上报"
H->>H : "参数校验与数据归一化"
H->>DB : "Redis HSet(headphone_impedances, field, json)"
DB-->>H : "OK 或错误"
H->>L : "记录日志/错误"
H-->>C : "JSON 响应"
else "OTA固件升级"
H->>REPO : "GetLatestOTA(ctx, model, hw, beta)"
REPO->>DB : "查询最新OTA记录"
@@ -240,11 +264,13 @@ H-->>C : "JSON 响应"
- [internal/handler/model.go:30](file://internal/handler/model.go#L30)
- [internal/handler/model_list.go:37](file://internal/handler/model_list.go#L37)
- [internal/handler/device.go:71](file://internal/handler/device.go#L71)
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
- [internal/handler/ota.go:57-131](file://internal/handler/ota.go#L57-L131)
- [internal/handler/curve.go:302-489](file://internal/handler/curve.go#L302-L489)
- [internal/handler/share_code.go:40-382](file://internal/handler/share_code.go#L40-382)
- [internal/repository/brand.go:23-52](file://internal/repository/brand.go#L23-L52)
- [internal/repository/model.go:23-79](file://internal/repository/model.go#L23-L79)
- [internal/repository/headphone_impedance.go:19-52](file://internal/repository/headphone_impedance.go#L19-L52)
- [internal/repository/ota.go:20-158](file://internal/repository/ota.go#L20-L158)
- [internal/repository/curve.go:19-65](file://internal/repository/curve.go#L19-L65)
- [internal/repository/share_code.go:20-58](file://internal/repository/share_code.go#L20-L58)
@@ -406,7 +432,7 @@ ML-->>C : "JSON 或 Base64(JSON)"
- [internal/handler/model_list.go:26-56](file://internal/handler/model_list.go#L26-L56)
- [internal/search/meilisearch.go:22-45](file://internal/search/meilisearch.go#L22-L45)
- [pkg/encode/base64.go:35-51](file://pkg/encode/base64.go#L35-L51)
- [internal/response/response.go:15-36](file://internal/response/response.go#L15-L36)
- [internal/response/response.go:15-36](file://internal/response/response.go#L15-36)
**章节来源**
- [internal/handler/model_list.go:14-57](file://internal/handler/model_list.go#L14-L57)
@@ -455,6 +481,69 @@ RespOK --> End
**章节来源**
- [internal/handler/device.go:14-85](file://internal/handler/device.go#L14-L85)
### 耳机阻抗处理器(ImpedanceHandler
**新增功能** 耳机阻抗数据处理处理器,负责接收用户上报的耳机阻抗数据,包含完整的输入验证、数据归一化、Redis存储和错误处理机制。
- **职责**:接收耳机阻抗数据上报,进行参数校验、数据归一化处理,并将数据存储到Redis中供后续异步持久化。
- **输入参数**
- 查询字符串:mac(必填)- 设备MAC地址
- 查询字符串:name(必填)- 设备型号(Luxsin-X8/Luxsin-X9
- 查询字符串:brand(必填)- 耳机品牌
- 查询字符串:model(必填)- 耳机型号
- 查询字符串:value(必填)- 阻抗值(整数,单位Ω)
- **处理流程**
1. 参数提取与清洗:获取所有查询参数并去除首尾空白字符
2. 参数校验:验证所有必填参数是否存在且不为空
3. 数据类型转换:将阻抗值字符串转换为整数类型
4. 数据归一化:对品牌和型号进行trim+lower处理,用于去重比较
5. 数据结构组装:构建包含完整信息的阻抗数据对象
6. JSON序列化:将数据对象序列化为JSON格式
7. Redis存储:使用HSet命令将数据存入headphone_impedances哈希表
8. 响应返回:返回统一的成功响应
- **错误处理**
- 参数校验失败:返回400错误码和"参数校验失败"消息
- 数据类型转换失败:返回400错误码和"参数校验失败"消息
- JSON序列化失败:记录错误日志并返回500错误码和"系统错误"消息
- Redis写入失败:记录错误日志并返回500错误码和"系统错误"消息
- **数据模型**UserHeadphoneImpedance
- **依赖注入**:通过 NewImpedanceHandler 构造函数注入 redis.Client 和 zap.Logger
- **API端点**/audio/reportImpedance
- **Redis存储结构**
- Key: headphone_impedances
- Field: mac|brand_norm|model_norm(唯一标识)
- Value: JSON格式的阻抗数据
```mermaid
flowchart TD
Start(["进入 ReportImpedance"]) --> ExtractParams["提取参数 mac/name/brand/model/value"]
ExtractParams --> TrimParams["去除空白字符"]
TrimParams --> Validate{"参数校验通过?"}
Validate -- 否 --> BadReq["返回400错误:参数校验失败"]
Validate -- 是 --> ParseValue["解析阻抗值为整数"]
ParseValue --> ParseOK{"解析成功?"}
ParseOK -- 否 --> BadReq
ParseOK -- 是 --> NormalizeData["归一化品牌和型号"]
NormalizeData --> BuildInfo["构建阻抗数据对象"]
BuildInfo --> Marshal{"JSON序列化成功?"}
Marshal -- 否 --> InternalErr["返回500错误:系统错误"]
Marshal -- 是 --> RedisField["生成Redis字段名"]
RedisField --> HSet["Redis HSet存储数据"]
HSet --> SetOK{"写入成功?"}
SetOK -- 否 --> InternalErr
SetOK -- 是 --> Success["返回200成功响应"]
BadReq --> End(["结束"])
InternalErr --> End
Success --> End
```
**图表来源**
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
**章节来源**
- [internal/handler/impedance.go:17-116](file://internal/handler/impedance.go#L17-L116)
- [internal/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
- [internal/router/router.go:70](file://internal/router/router.go#L70)
### OTA固件升级处理器(OTAHandler
**更新** OTA处理器内部逻辑已简化,移除了paw相关字段处理,现在专注于标准OTA升级流程。
@@ -626,7 +715,7 @@ DeleteRedis --> ReturnDelete["返回删除结果"]
**图表来源**
- [internal/handler/share_code.go:40-382](file://internal/handler/share_code.go#L40-382)
- [internal/cache/share_code_cache.go:142-331](file://internal/cache/share_code_cache.go#L142-331)
- [internal/cache/share_code_cache.go:142-331](file://internal/cache/share_code_cache.go#L142-L331)
**章节来源**
- [internal/handler/share_code.go:15-383](file://internal/handler/share_code.go#L15-L383)
@@ -659,11 +748,13 @@ ROUTER --> MODELREPO["repository.NewModelRepository()<br/>型号仓储"]
ROUTER --> OTAREPO["repository.NewOTARepository()<br/>OTA仓储"]
ROUTER --> CURVEREPO["repository.NewCurveRepository()<br/>曲线仓储"]
ROUTER --> SHARECODEREPO["repository.NewShareCodeRepository()<br/>分享码仓储"]
ROUTER --> IMPEDANCEREPO["repository.NewHeadphoneImpedanceRepository()<br/>耳机阻抗仓储"]
ROUTER --> HEALTH["handler.NewHealthHandler()<br/>健康检查处理器"]
ROUTER --> BRAND["handler.NewBrandHandler()<br/>品牌处理器"]
ROUTER --> MODEL["handler.NewModelHandler()<br/>型号处理器"]
ROUTER --> MODELLIST["handler.NewModelListHandler()<br/>型号检索处理器"]
ROUTER --> DEVICE["handler.NewDeviceHandler()<br/>设备上报处理器"]
ROUTER --> IMPEDANCE["handler.NewImpedanceHandler()<br/>耳机阻抗处理器"]
ROUTER --> OTA["handler.NewOTAHandler()<br/>OTA固件升级处理器"]
ROUTER --> CURVE["handler.NewCurveHandler()<br/>等化曲线处理器"]
ROUTER --> SHARECODE["handler.NewShareCodeHandler()<br/>分享代码处理器"]
@@ -671,7 +762,7 @@ ROUTER --> SHARECODE["handler.NewShareCodeHandler()<br/>分享代码处理器"]
**图表来源**
- [cmd/server/main.go:77](file://cmd/server/main.go#L77)
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-L82)
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-82)
### 依赖注入优势
- **测试友好**:可以轻松注入模拟对象进行单元测试
@@ -680,7 +771,7 @@ ROUTER --> SHARECODE["handler.NewShareCodeHandler()<br/>分享代码处理器"]
- **生命周期管理**:依赖项的创建和销毁由路由集中管理
### 仓储层依赖
BrandRepository、ModelRepository、OTARepository、CurveRepositoryShareCodeRepository 现在接收相应的依赖:
BrandRepository、ModelRepository、OTARepository、CurveRepositoryShareCodeRepository 和新增的 HeadphoneImpedanceRepository 现在接收相应的依赖:
```mermaid
classDiagram
@@ -700,6 +791,13 @@ class ModelRepository {
+ ListByBrandFromDB(ctx, brandName) : "[]Model,error"
+ ListByModelFromDB(ctx, modelName) : "[]Model,error"
}
class HeadphoneImpedanceRepository {
- db : "sql.DB"
+ NewHeadphoneImpedanceRepository(db) : "HeadphoneImpedanceRepository"
+ FindByMacAndNorm(ctx, macAddr, brandNorm, modelNorm) : "UserHeadphoneImpedance,error"
+ Insert(ctx, rec) : "error"
+ Update(ctx, rec) : "error"
}
class OTARepository {
- db : "sql.DB"
+ NewOTARepository(db) : "OTARepository"
@@ -759,6 +857,7 @@ class ShareCodeCache {
}
BrandRepository --> BrandCache : "依赖注入"
ModelRepository --> ModelCache : "依赖注入"
HeadphoneImpedanceRepository --> DB : "依赖注入"
OTARepository --> DB : "依赖注入"
CurveRepository --> DB : "依赖注入"
ShareCodeRepository --> DB : "依赖注入"
@@ -769,6 +868,7 @@ ShareCodeCache --> RDB : "依赖注入"
**图表来源**
- [internal/repository/brand.go:14-21](file://internal/repository/brand.go#L14-L21)
- [internal/repository/model.go:14-21](file://internal/repository/model.go#L14-L21)
- [internal/repository/headphone_impedance.go:15-17](file://internal/repository/headphone_impedance.go#L15-L17)
- [internal/repository/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
- [internal/repository/curve.go:11-17](file://internal/repository/curve.go#L11-L17)
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
@@ -778,7 +878,7 @@ ShareCodeCache --> RDB : "依赖注入"
- [internal/cache/share_code_cache.go:128-135](file://internal/cache/share_code_cache.go#L128-L135)
**章节来源**
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-L82)
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-82)
- [cmd/server/main.go:77](file://cmd/server/main.go#L77)
## 依赖分析
@@ -791,6 +891,8 @@ ShareCodeCache --> RDB : "依赖注入"
- OTA处理器与数据库直接交互,提供完整的OTA数据访问能力。
- CurveHandler与外部EQ接口和S3存储集成,支持复杂的音频处理。
- ShareCodeHandler与Redis缓存深度集成,实现高性能的分享码管理。
- **新增**ImpedanceHandler与Redis集成,实现高效的阻抗数据存储。
- **新增**HeadphoneImpedanceRepository提供完整的数据库CRUD操作。
- **可能的循环依赖**
- 当前结构未见循环导入,符合 Go 包管理最佳实践。
@@ -802,11 +904,13 @@ ROUTER --> BRAND["brand.go"]
ROUTER --> MODEL["model.go"]
ROUTER --> MODELLIST["model_list.go"]
ROUTER --> DEVICE["device.go"]
ROUTER --> IMPEDANCE["impedance.go"]
ROUTER --> OTA["ota.go"]
ROUTER --> CURVE["curve.go"]
ROUTER --> SHARECODE["share_code.go"]
BRAND --> BREPO["repository/brand.go"]
MODEL --> MREPO["repository/model.go"]
IMPEDANCE --> IMPEDANCEREPO["repository/headphone_impedance.go"]
OTA --> OTAREPO["repository/ota.go"]
CURVE --> CURVEREPO["repository/curve.go"]
SHARECODE --> SHARECODEREPO["repository/share_code.go"]
@@ -814,8 +918,9 @@ BRAND --> BREPO
BREPO --> BCACHE["cache/brand_cache.go"]
MODEL --> MREPO
MREPO --> MCACHE["cache/model_cache.go"]
IMPEDANCEREPO --> DB["MySQL数据库"]
CURVE --> CURVEREPO
CURVEREPO --> DB["MySQL数据库"]
CURVEREPO --> DB
SHARECODE --> SHARECODEREPO
SHARECODEREPO --> DB
CURVE --> S3["S3存储"]
@@ -827,16 +932,18 @@ ALL --> LOG["zap.Logger"]
**图表来源**
- [cmd/server/main.go:77](file://cmd/server/main.go#L77)
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-L82)
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-82)
- [internal/handler/brand.go:18](file://internal/handler/brand.go#L18)
- [internal/handler/model.go:18](file://internal/handler/model.go#L18)
- [internal/handler/model_list.go:19](file://internal/handler/model_list.go#L19)
- [internal/handler/device.go:19](file://internal/handler/device.go#L19)
- [internal/handler/impedance.go:23](file://internal/handler/impedance.go#L23)
- [internal/handler/ota.go:19](file://internal/handler/ota.go#L19)
- [internal/handler/curve.go:37](file://internal/handler/curve.go#L37)
- [internal/handler/share_code.go:21](file://internal/handler/share_code.go#L21)
- [internal/repository/brand.go:19](file://internal/repository/brand.go#L19)
- [internal/repository/model.go:19](file://internal/repository/model.go#L19)
- [internal/repository/headphone_impedance.go:15](file://internal/repository/headphone_impedance.go#L15)
- [internal/repository/ota.go:15](file://internal/repository/ota.go#L15)
- [internal/repository/curve.go:15](file://internal/repository/curve.go#L15)
- [internal/repository/share_code.go:16](file://internal/repository/share_code.go#L16)
@@ -845,8 +952,8 @@ ALL --> LOG["zap.Logger"]
- [internal/response/response.go:9](file://internal/response/response.go#L9)
**章节来源**
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-L82)
- [cmd/server/main.go:27-108](file://cmd/server/main.go#L27-L108)
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-82)
- [cmd/server/main.go:27-108](file://cmd/server/main.go#L27-108)
## 性能考虑
- **响应体积优化**
@@ -857,10 +964,12 @@ ALL --> LOG["zap.Logger"]
- OTA查询涉及多表关联,建议在ota表的model、hw、beta、status字段建立复合索引。
- 仓储层现在支持缓存降级,当缓存不可用时自动回退到数据库查询。
- 分享码持久化查询优化,使用索引加速查询。
- **新增**:耳机阻抗数据使用唯一索引确保数据去重,提升查询性能。
- **搜索与缓存**
- Meilisearch 适合全文检索,建议合理设置属性检索范围与分页大小。
- Redis 写入为单键 HSet,具备良好吞吐能力;建议评估内存占用与持久化策略。
- 缓存层现在支持全量缓存和按品牌缓存两种策略,提升查询性能。
- **新增**:耳机阻抗数据存储在Redis Hash中,使用组合键实现高效去重。
- **新增**CurveHandler的fr数据独立存储优化,避免每个target重复存储fr数据。
- **新增**ShareCodeCache使用Lua脚本原子性操作,减少网络往返。
- **外部服务集成**
@@ -872,6 +981,11 @@ ALL --> LOG["zap.Logger"]
- 分布式锁防止重复创建分享码。
- 异步持久化任务批量刷入数据库,减少实时写入压力。
- 分享码TTL配置支持灵活的时间单位(分钟、小时、天)。
- **耳机阻抗性能优化**
- Redis HSet原子操作确保数据一致性。
- 数据归一化处理减少存储空间和查询复杂度。
- 异步持久化任务每5分钟执行一次,平衡实时性和性能。
- 数据库唯一索引确保数据去重,避免重复插入。
- **中间件与日志**
- 开启 Recovery、CORS、Logger、RequestID 中间件,有助于可观测性与稳定性;注意日志级别与输出频率对性能的影响。
- **并发安全**
@@ -883,7 +997,8 @@ ALL --> LOG["zap.Logger"]
- 对外部依赖(数据库、搜索、缓存、S3)增加超时与重试策略,提升鲁棒性。
- 利用缓存预热机制,在应用启动时加载常用数据到缓存。
- OTA查询流程复杂,建议添加适当的超时控制和错误重试机制。
- **新增**分享码持久化任务建议设置合理的间隔时间,平衡实时性和性能。
- **新增**耳机阻抗持久化任务建议设置合理的间隔时间,平衡实时性和性能。
- **新增**:Redis存储的阻抗数据应设置合理的过期时间,避免无限增长。
## 故障排查指南
- **健康检查失败**
@@ -899,6 +1014,12 @@ ALL --> LOG["zap.Logger"]
- **设备上报异常**
- 校验必填参数 mac 与 model 是否传入。
- 关注"marshal device info failed"与"redis hset failed"的错误日志。
- **耳机阻抗上报异常**
- 校验必填参数 mac、name、brand、model、value 是否传入且格式正确。
- 关注"参数校验失败"、"marshal impedance info failed"、"redis hset failed"等错误日志。
- 检查Redis连接状态和headphone_impedances键是否存在。
- 验证阻抗值是否为有效的整数类型。
- 关注数据归一化处理是否正确执行。
- **OTA固件升级异常**
- 校验必填参数 model、hw 是否传入且格式正确。
- 关注"query ota failed"、"check black list failed"、"check target device failed"等错误日志。
@@ -932,6 +1053,7 @@ ALL --> LOG["zap.Logger"]
- [internal/handler/model.go:31-35](file://internal/handler/model.go#L31-L35)
- [internal/handler/model_list.go:38-42](file://internal/handler/model_list.go#L38-L42)
- [internal/handler/device.go:61-78](file://internal/handler/device.go#L61-L78)
- [internal/handler/impedance.go:51-101](file://internal/handler/impedance.go#L51-L101)
- [internal/handler/ota.go:58-94](file://internal/handler/ota.go#L58-L94)
- [internal/handler/curve.go:93-95](file://internal/handler/curve.go#L93-L95)
- [internal/handler/share_code.go:87-111](file://internal/handler/share_code.go#L87-L111)
@@ -940,7 +1062,7 @@ ALL --> LOG["zap.Logger"]
- [internal/middleware/request_id.go:20-31](file://internal/middleware/request_id.go#L20-L31)
## 结论
本处理器模块采用全新的依赖注入模式,通过构造函数注入依赖项,实现了更好的解耦和可测试性。通过统一响应体与自定义 Base64 编码实现灵活的输出策略,结合 Gin 中间件与外部服务(数据库、Meilisearch、Redis、S3)形成稳定高效的业务处理链路。新增的分享码等化曲线功能提供了完整的业务能力,包括高性能的Redis缓存、分布式锁、异步持久化和复杂的音频处理流程。新的架构模式简化了处理器实现,提升了代码质量,建议在生产环境中进一步完善超时与重试、索引优化与缓存策略,持续提升性能与可靠性。
本处理器模块采用全新的依赖注入模式,通过构造函数注入依赖项,实现了更好的解耦和可测试性。通过统一响应体与自定义 Base64 编码实现灵活的输出策略,结合 Gin 中间件与外部服务(数据库、Meilisearch、Redis、S3)形成稳定高效的业务处理链路。新增的分享码等化曲线和耳机阻抗数据处理功能提供了完整的业务能力,包括高性能的Redis缓存、分布式锁、异步持久化和复杂的音频处理流程。新的架构模式简化了处理器实现,提升了代码质量,建议在生产环境中进一步完善超时与重试、索引优化与缓存策略,持续提升性能与可靠性。
## 附录
- **路由与处理器映射**
@@ -949,9 +1071,11 @@ ALL --> LOG["zap.Logger"]
- /audio/getModel -> ModelHandler.GetModel
- /audio/modelList -> ModelListHandler.ModelList
- /audio/reportDevInfo -> DeviceHandler.ReportDevInfo
- /audio/reportImpedance -> ImpedanceHandler.ReportImpedance**新增**
- /audio/ota -> OTAHandler.GetOTA**已简化内部逻辑**
- /audio/getCurve -> CurveHandler.GetCurve
- /audio/modelCurve -> CurveHandler.ModelCurve
- /audio/getModelCSV -> ModelCSVHandler.GetModelCSV
- /audio/shareCreate -> ShareCodeHandler.ExportShareCode
- /audio/shareList -> ShareCodeHandler.ListShareCodesByMac
- /audio/shareQuery -> ShareCodeHandler.QueryShareCode
@@ -962,6 +1086,7 @@ ALL --> LOG["zap.Logger"]
- 型号处理器:ModelHandler.NewModelHandler(modelRepo, log)
- 型号检索处理器:ModelListHandler.NewModelListHandler(searchClient, log)
- 设备上报处理器:DeviceHandler.NewDeviceHandler(redis, log)
- 耳机阻抗处理器:ImpedanceHandler.NewImpedanceHandler(redis, log)**新增**
- OTA固件升级处理器:OTAHandler.NewOTAHandler(otaRepo, log)
- 等化曲线处理器:CurveHandler.NewCurveHandler(curveRepo, curveCache, eqCfg, s3, log)
- 分享代码处理器:ShareCodeHandler.NewShareCodeHandler(shareCache, log, maxPerMac)
@@ -982,21 +1107,33 @@ ALL --> LOG["zap.Logger"]
- **分享代码数据模型字段**
- ShareCodeDatashare_code、mac_addr、ip_addr、model、eq_data、expire_at、persisted
- ShareCodeLogid、mac_addr、share_code、action、model、ip_addr、eq_data、expire_at、create_at
- **耳机阻抗查询参数说明**
- GET /audio/reportImpedancemac(必填)、name(必填)、brand(必填)、model(必填)、value(必填)
- **耳机阻抗数据模型字段**
- UserHeadphoneImpedanceid、mac_addr、device_model、impedance_ohm、headphone_brand、headphone_model、headphone_brand_norm、headphone_model_norm、ip_addr、create_at、update_at
- **数据库表结构**
- ota表:存储OTA固件升级记录
- black_list表:存储OTA黑名单
- ota_target_device表:存储OTA定向设备
- share_code_log表:存储分享码操作日志
- user_headphone_impedance表:存储用户耳机阻抗数据(**新增**)
- **OTA处理器内部逻辑更新**
- 移除了paw相关字段处理,专注于标准OTA升级流程
- 保持原有API端点:/audio/ota
- 查询逻辑已更新以适配简化的OTA结构
- 仍支持黑名单过滤和定向升级功能
- **耳机阻抗处理器特性**
- 完整的输入验证和类型转换
- 数据归一化处理(trim+lower
- Redis原子存储操作
- 统一的错误处理和日志记录
- 异步持久化任务支持
- **常用调用示例(路径引用)**
- 品牌查询:[internal/handler/brand.go:25](file://internal/handler/brand.go#L25)
- 型号查询:[internal/handler/model.go:25](file://internal/handler/model.go#L25)
- 型号检索:[internal/handler/model_list.go:26](file://internal/handler/model_list.go#L26)
- 设备上报:[internal/handler/device.go:26](file://internal/handler/device.go#L26)
- 耳机阻抗上报:[internal/handler/impedance.go:43](file://internal/handler/impedance.go#L43)**新增**
- OTA固件升级:[internal/handler/ota.go:25](file://internal/handler/ota.go#L25)
- 等化曲线:[internal/handler/curve.go:56](file://internal/handler/curve.go#L56)
- 分享代码创建:[internal/handler/share_code.go:40](file://internal/handler/share_code.go#L40)
@@ -1006,6 +1143,7 @@ ALL --> LOG["zap.Logger"]
- 服务器启动与优雅关闭:[cmd/server/main.go:27-108](file://cmd/server/main.go#L27-L108)
- 等化配置:[internal/config/equalize.go:12-33](file://internal/config/equalize.go#L12-L33)
- 分享码TTL配置:[internal/config/share_code_ttl.go:15-59](file://internal/config/share_code_ttl.go#L15-L59)
- 耳机阻抗持久化任务配置:ENABLE_IMPEDANCE_PERSIST_TASK环境变量
- **中间件**
- CORS[internal/middleware/cors.go:7-21](file://internal/middleware/cors.go#L7-L21)
- Logger[internal/middleware/logger.go:10-45](file://internal/middleware/logger.go#L10-L45)
@@ -1015,16 +1153,21 @@ ALL --> LOG["zap.Logger"]
- 型号缓存:model:brand:{brand} 和 model:all 键,30分钟TTL
- 等化曲线缓存:Redis Hash存储,__fr键独立存储fr数据
- 分享码缓存:share:{code}、share:mac:{mac}、share:pending等多键结构
- 耳机阻抗缓存:headphone_impedances哈希表,field格式为mac|brand_norm|model_norm
- 启动时预热缓存,提升首次查询性能
- **等化曲线查询流程**
- 缓存查询:优先查询Redis缓存,包含独立存储的fr数据优化
- 分布式锁:缓存未命中时获取分布式锁避免并发重复请求
- 外部API调用:调用EQ计算接口获取参数化EQ数据
- 缓存写入:将结果写入Redis缓存,分离存储fr数据
- 版本选择:根据检查结果返回合适的OTA版本
- **分享码查询流程**
- 创建分享码:Lua原子性写入多个Redis键,获取分布式锁
- 查询分享码:按MAC查询未过期分享码,主动清理过期成员
- 导入分享码:验证分享码有效性,加入导入持久化队列
- 删除分享码:校验所有权后删除Redis数据
- 持久化任务:定时将分享码操作记录刷入MySQL数据库
- **异步持久化任务**
- 分享码持久化任务:每5分钟执行一次,处理分享码操作日志
- 耳机阻抗持久化任务:每5分钟执行一次,将Redis中的阻抗数据刷入数据库
- 任务启动条件:通过环境变量ENABLE_PERSIST_TASK和ENABLE_IMPEDANCE_PERSIST_TASK控制
- **耳机阻抗数据流程**
- 数据采集:客户端通过/audio/reportImpedance接口上报阻抗数据
- 参数验证:服务端验证必填参数和数值类型
- 数据归一化:对品牌和型号进行trim+lower处理
- Redis存储:使用HSet命令存储到headphone_impedances哈希表
- 异步持久化:定时任务从Redis读取数据并写入数据库
- 数据去重:基于唯一索引(mac_addr + headphone_brand_norm + headphone_model_norm)实现
- 更新策略:相同设备相同耳机的数据会更新现有记录而非创建新记录
- **数据库索引优化**
- 唯一索引:uniq_mac_brand_model(mac_addr, headphone_brand_norm, headphone_model_norm)
- 辅助索引:idx_mac(mac_addr)、idx_device_model(device_model)
- 主键索引:id自增主键