c1547f9a8f
- 设计并添加 user_headphone_impedance 表,包含唯一复合索引保证数据完整性 - 实现阻抗上报API接口 /audio/reportImpedance,支持设备上传阻抗数据 - 使用Redis Hash结构缓存阻抗数据,键为 "headphone_impedances" - 新增定时持久化任务,每5分钟从Redis读取数据批量写入数据库 - 对品牌和型号数据进行trim+lower归一化处理,支持数据去重更新 - 优化查询性能,确保mac_addr与品牌型号归一化组合唯一索引生效 - 更新故障排查指南,新增Redis缓存及唯一索引相关问题检查 - 新增阻抗持久化配置开关,允许按需启用该功能
1173 lines
58 KiB
Markdown
1173 lines
58 KiB
Markdown
# 业务处理器
|
||
|
||
<cite>
|
||
**本文引用的文件**
|
||
- [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/device.go](file://internal/handler/device.go)
|
||
- [internal/handler/health.go](file://internal/handler/health.go)
|
||
- [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)
|
||
- [internal/router/router.go](file://internal/router/router.go)
|
||
- [cmd/server/main.go](file://cmd/server/main.go)
|
||
- [internal/config/config.go](file://internal/config/config.go)
|
||
- [internal/config/equalize.go](file://internal/config/equalize.go)
|
||
- [internal/config/share_code_ttl.go](file://internal/config/share_code_ttl.go)
|
||
- [internal/middleware/logger.go](file://internal/middleware/logger.go)
|
||
- [internal/middleware/cors.go](file://internal/middleware/cors.go)
|
||
- [internal/middleware/request_id.go](file://internal/middleware/request_id.go)
|
||
- [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/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>
|
||
|
||
## 更新摘要
|
||
**变更内容**
|
||
- 新增耳机阻抗数据处理业务逻辑,包含 ImpedanceHandler 处理器
|
||
- 实现了完整的输入验证、Redis存储、错误处理和异步持久化功能
|
||
- 新增 HeadphoneImpedanceRepository 仓储层和用户阻抗数据模型
|
||
- 添加了 ImpedancePersistTask 定时任务,实现Redis到数据库的数据同步
|
||
- 在路由中注册了 /audio/reportImpedance 端点
|
||
- 支持设备MAC地址、品牌型号归一化处理和数据去重更新
|
||
|
||
## 目录
|
||
1. [简介](#简介)
|
||
2. [项目结构](#项目结构)
|
||
3. [核心组件](#核心组件)
|
||
4. [架构总览](#架构总览)
|
||
5. [详细组件分析](#详细组件分析)
|
||
6. [依赖注入模式详解](#依赖注入模式详解)
|
||
7. [依赖分析](#依赖分析)
|
||
8. [性能考虑](#性能考虑)
|
||
9. [故障排查指南](#故障排查指南)
|
||
10. [结论](#结论)
|
||
11. [附录](#附录)
|
||
|
||
## 简介
|
||
本文件面向 Luxsin 应用 API 的业务处理器模块,系统性梳理各处理器的职责、接口设计与实现细节,覆盖 BrandHandler、ModelHandler、ModelListHandler、DeviceHandler、HealthHandler、OTAHandler、CurveHandler、ShareCodeHandler 和新增的 ImpedanceHandler。文档重点阐述新的依赖注入模式、错误处理机制、响应格式化策略,以及与 Gin 框架的集成方式;并通过流程图与类图展示处理器间的协作关系与数据流转过程,帮助初学者快速上手,同时为高级开发者提供深入的技术参考。
|
||
|
||
## 项目结构
|
||
业务处理器位于 internal/handler 目录,采用依赖注入模式,围绕"控制器-仓储-搜索-缓存-响应"的分层组织,配合中间件与路由装配,形成清晰的控制流与依赖注入入口。新增的分享码、等化曲线和耳机阻抗数据处理功能通过独立的处理器、仓储层和数据模型实现完整的业务能力。
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "应用入口"
|
||
MAIN["cmd/server/main.go<br/>启动与配置加载"]
|
||
END
|
||
subgraph "路由与中间件"
|
||
ROUTER["internal/router/router.go<br/>路由注册与依赖注入"]
|
||
CORS["internal/middleware/cors.go"]
|
||
LOGMW["internal/middleware/logger.go"]
|
||
REQID["internal/middleware/request_id.go"]
|
||
end
|
||
subgraph "业务处理器"
|
||
HEALTH["internal/handler/health.go<br/>NewHealthHandler()"]
|
||
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)"]
|
||
end
|
||
subgraph "仓储层"
|
||
BRANDREPO["internal/repository/brand.go<br/>BrandRepository"]
|
||
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/>统一响应体"]
|
||
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/>多表结构"]
|
||
TASK["internal/task/*<br/>异步持久化任务"]
|
||
end
|
||
MAIN --> ROUTER
|
||
ROUTER --> CORS
|
||
ROUTER --> LOGMW
|
||
ROUTER --> REQID
|
||
ROUTER --> HEALTH
|
||
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
|
||
CURVE --> S3
|
||
SHARECODE --> CACHE
|
||
CURVE --> CACHE
|
||
BRAND --> RESP
|
||
MODEL --> RESP
|
||
MODELLIST --> RESP
|
||
DEVICE --> RESP
|
||
IMPEDANCE --> RESP
|
||
OTA --> RESP
|
||
CURVE --> RESP
|
||
SHARECODE --> RESP
|
||
MODELLIST --> SEARCH
|
||
BRAND --> CACHE
|
||
MODEL --> CACHE
|
||
BRAND --> ENCODE
|
||
MODEL --> ENCODE
|
||
MODELLIST --> ENCODE
|
||
TASK --> DB
|
||
TASK --> CACHE
|
||
```
|
||
|
||
**图表来源**
|
||
- [cmd/server/main.go:27-108](file://cmd/server/main.go#L27-L108)
|
||
- [internal/router/router.go:16-82](file://internal/router/router.go#L16-L82)
|
||
- [internal/handler/health.go:10](file://internal/handler/health.go#L10)
|
||
- [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/router/router.go:16-82](file://internal/router/router.go#L16-L82)
|
||
- [cmd/server/main.go:27-108](file://cmd/server/main.go#L27-L108)
|
||
|
||
## 核心组件
|
||
- **健康检查处理器**:提供轻量级健康状态返回,便于外部探活与编排。
|
||
- **品牌查询处理器**:按品牌名模糊查询品牌列表,现在依赖 BrandRepository 实例。
|
||
- **型号查询处理器**:按品牌或型号关键字查询型号列表,现在依赖 ModelRepository 实例。
|
||
- **型号检索处理器**:基于 Meilisearch 执行全文检索,依赖搜索客户端实例。
|
||
- **设备上报处理器**:接收设备信息(MAC、型号、版本、来源 IP),依赖 Redis 客户端。
|
||
- **耳机阻抗处理器**:接收耳机阻抗数据上报,包含输入验证、Redis存储、错误处理和异步持久化。**新增功能**。
|
||
- **OTA固件升级处理器**:处理固件升级请求,支持黑名单过滤、目标设备分布和版本选择等OTA固件升级能力。**已简化内部逻辑,移除了paw相关字段处理**。
|
||
- **等化曲线处理器**:处理频响曲线计算和参数化EQ生成,支持多种目标曲线和设备源。
|
||
- **分享代码处理器**:处理EQ数据分享功能,支持创建、查询、导入、删除分享码。
|
||
|
||
**章节来源**
|
||
- [internal/handler/health.go:8-19](file://internal/handler/health.go#L8-L19)
|
||
- [internal/handler/brand.go:13-23](file://internal/handler/brand.go#L13-L23)
|
||
- [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
|
||
participant C as "客户端"
|
||
participant R as "Gin 路由"
|
||
participant H as "业务处理器"
|
||
participant REPO as "仓储层"
|
||
participant DB as "MySQL数据库"
|
||
participant L as "Zap 日志"
|
||
C->>R : "HTTP 请求"
|
||
R->>H : "匹配到处理器并调用"
|
||
alt "品牌/型号查询"
|
||
H->>REPO : "List(ctx, filters)"
|
||
REPO-->>H : "结果集"
|
||
else "型号检索"
|
||
H->>DB : "Meilisearch查询"
|
||
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记录"
|
||
DB-->>REPO : "OTA记录"
|
||
REPO-->>H : "OTA记录"
|
||
H->>REPO : "IsInBlackList(ctx, otaID, mac)"
|
||
REPO->>DB : "检查黑名单"
|
||
DB-->>REPO : "命中/未命中"
|
||
H->>REPO : "FindTargetDevice(ctx, otaID, mac)"
|
||
REPO->>DB : "检查定向设备"
|
||
DB-->>REPO : "匹配/不匹配"
|
||
else "等化曲线"
|
||
H->>REPO : "GetModelByBrandAndName(ctx, brand, name)"
|
||
REPO->>DB : "查询型号信息"
|
||
DB-->>REPO : "型号记录"
|
||
H->>REPO : "GetTargetByLabel(ctx, target)"
|
||
REPO->>DB : "查询目标曲线"
|
||
DB-->>REPO : "目标记录"
|
||
H->>DB : "调用外部EQ接口"
|
||
DB-->>H : "参数化EQ数据"
|
||
else "分享代码"
|
||
H->>DB : "Redis 操作:创建/查询/删除"
|
||
DB-->>H : "分享码数据"
|
||
H->>REPO : "插入分享码日志"
|
||
REPO->>DB : "写入数据库"
|
||
DB-->>REPO : "OK"
|
||
end
|
||
H->>L : "记录日志/错误"
|
||
H-->>C : "JSON 响应"
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/router/router.go:63-78](file://internal/router/router.go#L63-L78)
|
||
- [internal/handler/brand.go:29](file://internal/handler/brand.go#L29)
|
||
- [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)
|
||
- [internal/search/meilisearch.go:22-45](file://internal/search/meilisearch.go#L22-L45)
|
||
|
||
## 详细组件分析
|
||
|
||
### 健康检查处理器(HealthHandler)
|
||
- **职责**:对外暴露健康检查端点,返回统一响应体中的状态字段。
|
||
- **接口设计**:无状态对象,构造函数仅初始化空实例。
|
||
- **实现要点**:
|
||
- 使用统一响应体封装返回值。
|
||
- 适合被反向代理或编排系统定期探测。
|
||
- **典型调用路径**:/api/v1/health
|
||
|
||
**章节来源**
|
||
- [internal/handler/health.go:8-19](file://internal/handler/health.go#L8-L19)
|
||
- [internal/response/response.go:15-21](file://internal/response/response.go#L15-L21)
|
||
- [internal/router/router.go:55](file://internal/router/router.go#L55)
|
||
|
||
### 品牌处理器(BrandHandler)
|
||
- **职责**:根据品牌名称模糊查询品牌列表。
|
||
- **输入参数**:
|
||
- 查询字符串:brandName(可选)
|
||
- 查询字符串:base64Resp(可选,默认开启)
|
||
- **处理流程**:
|
||
- 读取查询参数并解析 base64Resp。
|
||
- 调用仓储层执行数据库查询。
|
||
- 若开启 base64Resp,则对结果进行 JSON 编码后返回字符串;否则直接返回 JSON。
|
||
- **错误处理**:
|
||
- 仓储查询失败时记录错误并返回统一内部错误。
|
||
- 编码失败时同样返回统一内部错误。
|
||
- **数据模型**:Brand
|
||
- **依赖注入**:通过 NewBrandHandler 构造函数注入 BrandRepository 和 zap.Logger
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class BrandHandler {
|
||
- repo : "BrandRepository"
|
||
- log : "zap.Logger"
|
||
+ NewBrandHandler(repo, log) : "BrandHandler"
|
||
+ GetBrand(c)
|
||
}
|
||
class BrandRepository {
|
||
- db : "sql.DB"
|
||
- cache : "BrandCache"
|
||
+ NewBrandRepository(db, cache) : "BrandRepository"
|
||
+ List(ctx, brandName) : "[]Brand,error"
|
||
}
|
||
class Brand {
|
||
+ ID : "int"
|
||
+ Name : "string"
|
||
}
|
||
BrandHandler --> BrandRepository : "依赖注入"
|
||
BrandRepository --> Brand : "返回"
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/handler/brand.go:13-23](file://internal/handler/brand.go#L13-L23)
|
||
- [internal/handler/brand.go:25-48](file://internal/handler/brand.go#L25-L48)
|
||
- [internal/repository/brand.go:14-21](file://internal/repository/brand.go#L14-L21)
|
||
- [internal/repository/brand.go:23-52](file://internal/repository/brand.go#L23-L52)
|
||
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
|
||
|
||
**章节来源**
|
||
- [internal/handler/brand.go:13-48](file://internal/handler/brand.go#L13-L48)
|
||
- [internal/repository/brand.go:23-52](file://internal/repository/brand.go#L23-L52)
|
||
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
|
||
|
||
### 型号处理器(ModelHandler)
|
||
- **职责**:根据品牌或型号关键字查询型号列表。
|
||
- **输入参数**:
|
||
- 查询字符串:brandName(可选)
|
||
- 查询字符串:modelName(可选)
|
||
- 查询字符串:base64Resp(可选,默认开启)
|
||
- **处理流程**:
|
||
- 读取查询参数并解析 base64Resp。
|
||
- 调用仓储层执行数据库查询。
|
||
- 结果处理与品牌处理器一致。
|
||
- **错误处理**:
|
||
- 仓储查询失败时记录错误并返回统一内部错误。
|
||
- 编码失败时同样返回统一内部错误。
|
||
- **数据模型**:Model
|
||
- **依赖注入**:通过 NewModelHandler 构造函数注入 ModelRepository 和 zap.Logger
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class ModelHandler {
|
||
- repo : "ModelRepository"
|
||
- log : "zap.Logger"
|
||
+ NewModelHandler(repo, log) : "ModelHandler"
|
||
+ GetModel(c)
|
||
}
|
||
class ModelRepository {
|
||
- db : "sql.DB"
|
||
- cache : "ModelCache"
|
||
+ NewModelRepository(db, cache) : "ModelRepository"
|
||
+ List(ctx, brandName, modelName) : "[]Model,error"
|
||
}
|
||
class Model {
|
||
+ ID : "int"
|
||
+ BrandName : "string"
|
||
+ Name : "string"
|
||
+ Form : "*string"
|
||
+ Rig : "*string"
|
||
+ Source : "*string"
|
||
+ EqKey : "*string"
|
||
+ CreateAt : "time.Time"
|
||
}
|
||
ModelHandler --> ModelRepository : "依赖注入"
|
||
ModelRepository --> Model : "返回"
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/handler/model.go:13-23](file://internal/handler/model.go#L13-L23)
|
||
- [internal/handler/model.go:25-49](file://internal/handler/model.go#L25-L49)
|
||
- [internal/repository/model.go:14-21](file://internal/repository/model.go#L14-L21)
|
||
- [internal/repository/model.go:23-79](file://internal/repository/model.go#L23-L79)
|
||
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
|
||
|
||
**章节来源**
|
||
- [internal/handler/model.go:13-49](file://internal/handler/model.go#L13-L49)
|
||
- [internal/repository/model.go:23-79](file://internal/repository/model.go#L23-L79)
|
||
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
|
||
|
||
### 型号检索处理器(ModelListHandler)
|
||
- **职责**:基于 Meilisearch 执行全文检索,返回匹配的型号元数据。
|
||
- **输入参数**:
|
||
- 查询字符串:key(必需)
|
||
- 查询字符串:count(可选,默认 100)
|
||
- 查询字符串:base64Resp(可选,默认开启)
|
||
- **处理流程**:
|
||
- 读取查询参数并解析 base64Resp。
|
||
- 限制 count 的最大值以避免过大的返回量。
|
||
- 调用搜索客户端执行检索。
|
||
- 结果处理与前两个处理器一致。
|
||
- **错误处理**:
|
||
- 检索失败时记录错误并返回统一内部错误。
|
||
- 编码失败时同样返回统一内部错误。
|
||
- **数据模型**:map[string]any(由搜索结果解码而来)
|
||
- **依赖注入**:通过 NewModelListHandler 构造函数注入 search.Client 和 zap.Logger
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as "客户端"
|
||
participant ML as "ModelListHandler"
|
||
participant S as "Meilisearch 客户端"
|
||
participant E as "自定义Base64"
|
||
participant R as "统一响应"
|
||
C->>ML : "GET /audio/modelList?key=...&count=..."
|
||
ML->>ML : "解析参数与count限制"
|
||
ML->>S : "ModelList(ctx, key, count)"
|
||
S-->>ML : "hits"
|
||
ML->>E : "可选:EncodeJSON(hits)"
|
||
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-36)
|
||
|
||
**章节来源**
|
||
- [internal/handler/model_list.go:14-57](file://internal/handler/model_list.go#L14-L57)
|
||
- [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)
|
||
|
||
### 设备上报处理器(DeviceHandler)
|
||
- **职责**:接收设备上报信息(MAC、型号、版本、来源 IP),写入 Redis Hash。
|
||
- **输入参数**:
|
||
- 查询字符串:mac(必填)
|
||
- 查询字符串:model(必填)
|
||
- 查询字符串:ver(可选)
|
||
- **处理流程**:
|
||
- 校验必填参数,若缺失则返回统一错误响应。
|
||
- 组装设备信息结构体(含活跃日期、来源 IP、版本)。
|
||
- 将结构体序列化为 JSON 并写入 Redis Hash。
|
||
- 返回统一成功响应。
|
||
- **错误处理**:
|
||
- 参数校验失败返回统一错误。
|
||
- JSON 序列化失败返回统一错误。
|
||
- Redis 写入失败返回统一错误。
|
||
- **并发与安全**:
|
||
- Redis HSet 是原子操作,适合高并发场景。
|
||
- 建议对 MAC 去除空白字符,避免重复键。
|
||
- **依赖注入**:通过 NewDeviceHandler 构造函数注入 redis.Client 和 zap.Logger
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start(["进入 ReportDevInfo"]) --> ReadParams["读取参数 mac/model/ver/X-Forwarded-For"]
|
||
ReadParams --> Validate{"参数校验通过?"}
|
||
Validate -- 否 --> RespFail["返回统一错误响应"]
|
||
Validate -- 是 --> BuildInfo["组装设备信息结构体"]
|
||
BuildInfo --> Marshal{"JSON序列化成功?"}
|
||
Marshal -- 否 --> RespFail
|
||
Marshal -- 是 --> HSet["Redis HSet(ctx, key, mac, json)"]
|
||
HSet --> SetOK{"写入成功?"}
|
||
SetOK -- 否 --> RespFail
|
||
SetOK -- 是 --> RespOK["返回统一成功响应"]
|
||
RespFail --> End(["结束"])
|
||
RespOK --> End
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/handler/device.go:26-84](file://internal/handler/device.go#L26-L84)
|
||
|
||
**章节来源**
|
||
- [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升级流程。
|
||
|
||
- **职责**:处理固件升级请求,支持黑名单过滤、目标设备分布和版本选择等OTA固件升级能力。
|
||
- **输入参数**:
|
||
- 查询字符串:model(必填)
|
||
- 查询字符串:hw(必填)
|
||
- 查询字符串:mac(可选)
|
||
- 查询字符串:beta(可选,默认0)
|
||
- **处理流程**:
|
||
1. 参数校验:验证model和hw参数,解析beta参数
|
||
2. 查询最新OTA记录:按model+hw+beta+status=1查询最新一条OTA记录
|
||
3. 黑名单检查:检查mac是否在指定ota_id的黑名单中
|
||
4. 定向升级检查:如果是定向升级,检查mac是否在定向设备列表中
|
||
5. 版本选择:根据检查结果返回合适的OTA版本
|
||
- **错误处理**:
|
||
- 参数校验失败返回统一错误
|
||
- 数据库查询失败记录错误并返回统一内部错误
|
||
- 黑名单或定向设备检查失败返回统一内部错误
|
||
- **数据模型**:OTA、BlackList、OTATargetDevice
|
||
- **依赖注入**:通过 NewOTAHandler 构造函数注入 OTARepository 和 zap.Logger
|
||
- **API端点**:/audio/ota(保持不变)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start(["进入 GetOTA"]) --> ParseParams["解析参数 model/hw/mac/beta"]
|
||
ParseParams --> Validate{"参数校验通过?"}
|
||
Validate -- 否 --> RespBadReq["返回BadRequest错误"]
|
||
Validate -- 是 --> QueryLatest["查询最新OTA记录"]
|
||
QueryLatest --> QueryOK{"查询成功?"}
|
||
QueryOK -- 否 --> RespInternal["返回InternalError"]
|
||
QueryOK -- 是 --> CheckBlackList["检查黑名单"]
|
||
CheckBlackList --> InBlackList{"命中黑名单?"}
|
||
InBlackList -- 是 --> QueryNotInBlack["查询不在黑名单中的最新OTA"]
|
||
QueryNotInBlack --> BlackListOK{"查询成功?"}
|
||
BlackListOK -- 否 --> RespInternal
|
||
BlackListOK -- 是 --> ReturnOTA["返回OTA记录"]
|
||
InBlackList -- 否 --> CheckTarget{"是否定向升级?"}
|
||
CheckTarget -- 否 --> ReturnOTA
|
||
CheckTarget -- 是 --> CheckTargetDevice["检查定向设备"]
|
||
CheckTargetDevice --> TargetHit{"定向命中?"}
|
||
TargetHit -- 是 --> ReturnOTA
|
||
TargetHit -- 否 --> QueryNotTarget["查询非定向最新OTA"]
|
||
QueryNotTarget --> NotTargetOK{"查询成功?"}
|
||
NotTargetOK -- 否 --> RespInternal
|
||
NotTargetOK -- 是 --> ReturnOTA
|
||
ReturnOTA --> End(["结束"])
|
||
RespBadReq --> End
|
||
RespInternal --> End
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/handler/ota.go:25-132](file://internal/handler/ota.go#L25-L132)
|
||
|
||
**章节来源**
|
||
- [internal/handler/ota.go:14-132](file://internal/handler/ota.go#L14-L132)
|
||
- [internal/repository/ota.go:20-158](file://internal/repository/ota.go#L20-L158)
|
||
- [internal/model/ota.go:6-42](file://internal/model/ota.go#L6-L42)
|
||
|
||
### 等化曲线处理器(CurveHandler)
|
||
- **职责**:处理频响曲线计算和参数化EQ生成,支持多种目标曲线和设备源。
|
||
- **输入参数**:
|
||
- 查询字符串:brand(必填)
|
||
- 查询字符串:name(必填)
|
||
- 查询字符串:target(可选,modelCurve接口固定为Harman over-ear 2018)
|
||
- 查询字符串:base64Resp(可选,默认关闭)
|
||
- **处理流程**:
|
||
1. 参数校验:验证brand、name和target参数
|
||
2. 缓存查询:优先查询Redis缓存,包含独立存储的fr数据优化
|
||
3. 分布式锁:缓存未命中时获取分布式锁避免并发重复请求
|
||
4. 外部API调用:调用EQ计算接口获取参数化EQ数据
|
||
5. 缓存写入:将结果写入Redis缓存,分离存储fr数据
|
||
6. 响应返回:根据base64Resp参数决定响应格式
|
||
- **错误处理**:
|
||
- 参数校验失败返回统一错误
|
||
- 缓存操作失败记录警告并继续处理
|
||
- 外部API调用失败返回统一内部错误
|
||
- 分布式锁获取失败返回统一内部错误
|
||
- **数据模型**:Model、Target
|
||
- **依赖注入**:通过 NewCurveHandler 构造函数注入 CurveRepository、CurveCache、EqualizeConfig、S3Storage 和 zap.Logger
|
||
- **S3集成**:支持从S3读取CSV文件作为测量数据
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start(["进入 CurveHandler"]) --> ParseParams["解析参数 brand/name/target/base64Resp"]
|
||
ParseParams --> Validate{"参数校验通过?"}
|
||
Validate -- 否 --> BadReq["返回BadRequest错误"]
|
||
Validate -- 是 --> CacheQuery["查询Redis缓存"]
|
||
CacheQuery --> CacheHit{"缓存命中?"}
|
||
CacheHit -- 是 --> ReturnCached["返回缓存数据"]
|
||
CacheHit -- 否 --> AcquireLock["获取分布式锁"]
|
||
AcquireLock --> LockOK{"获取成功?"}
|
||
LockOK -- 否 --> WaitRetry["等待后重试"]
|
||
WaitRetry --> CacheQuery
|
||
LockOK -- 是 --> DoubleCheck["双重检查缓存"]
|
||
DoubleCheck --> CacheHit2{"缓存已存在?"}
|
||
CacheHit2 -- 是 --> ReleaseLock["释放锁"]
|
||
ReleaseLock --> ReturnCached
|
||
CacheHit2 -- 否 --> CallEQ["调用外部EQ接口"]
|
||
CallEQ --> ProcessResult["处理返回结果"]
|
||
ProcessResult --> SplitFR["分离fr数据"]
|
||
SplitFR --> WriteCache["写入Redis缓存"]
|
||
WriteCache --> ReleaseLock2["释放锁"]
|
||
ReleaseLock2 --> ReturnResult["返回结果"]
|
||
BadReq --> End(["结束"])
|
||
ReturnCached --> End
|
||
ReturnResult --> End
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/handler/curve.go:214-292](file://internal/handler/curve.go#L214-L292)
|
||
- [internal/handler/curve.go:301-368](file://internal/handler/curve.go#L301-L368)
|
||
|
||
**章节来源**
|
||
- [internal/handler/curve.go:27-582](file://internal/handler/curve.go#L27-L582)
|
||
- [internal/repository/curve.go:19-65](file://internal/repository/curve.go#L19-L65)
|
||
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
|
||
- [internal/model/target.go:5-14](file://internal/model/target.go#L5-L14)
|
||
|
||
### 分享代码处理器(ShareCodeHandler)
|
||
- **职责**:处理EQ数据分享功能,支持创建、查询、导入、删除分享码,包含Redis缓存、分布式锁和异步持久化。
|
||
- **输入参数**:
|
||
- POST /audio/shareCreate:mac(必填)、model(必填)、eq_data(必填)
|
||
- GET /audio/shareList:mac(必填)
|
||
- GET /audio/shareQuery:shareCode(必填)
|
||
- GET /audio/shareAccept:mac(必填)、model(必填)、shareCode(必填)
|
||
- GET /audio/shareDelete:mac(必填)、shareCode(必填)
|
||
- **处理流程**:
|
||
1. 参数校验:验证所有必需参数,检查设备型号有效性
|
||
2. 分布式锁:创建分享码时获取分布式锁避免重复
|
||
3. Redis操作:使用Lua脚本原子性写入多个键值对
|
||
4. 缓存查询:查询MAC地址对应的未过期分享码数量
|
||
5. 异步持久化:将操作记录放入队列,由后台任务刷入数据库
|
||
6. 响应返回:根据base64Resp参数决定响应格式
|
||
- **错误处理**:
|
||
- 参数校验失败返回统一错误
|
||
- Redis操作失败记录错误并返回统一内部错误
|
||
- 分布式锁获取失败返回统一内部错误
|
||
- 设备型号无效返回统一错误
|
||
- **数据模型**:ShareCodeData、ShareCodeLog
|
||
- **依赖注入**:通过 NewShareCodeHandler 构造函数注入 ShareCodeCache、zap.Logger 和 maxPerMac
|
||
- **Redis结构**:share:{code}、share:mac:{mac}、share:pending、share:import:pending等
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start(["进入 ShareCodeHandler"]) --> ParseAction["解析具体操作"]
|
||
ParseAction --> CreateShare["创建分享码"]
|
||
CreateShare --> ValidateParams["参数校验"]
|
||
ValidateParams --> CheckCount["检查MAC分享码数量"]
|
||
CheckCount --> AcquireLock["获取分布式锁"]
|
||
AcquireLock --> AtomicWrite["Lua原子写入Redis"]
|
||
AtomicWrite --> EnqueuePending["加入待持久化队列"]
|
||
EnqueuePending --> ReturnResult["返回分享码"]
|
||
Start --> QueryShare["查询分享码"]
|
||
QueryShare --> ValidateQuery["参数校验"]
|
||
ValidateQuery --> ListByMAC["按MAC查询未过期分享码"]
|
||
ListByMAC --> ReturnList["返回分享码列表"]
|
||
Start --> AcceptShare["接受分享码"]
|
||
AcceptShare --> ValidateAccept["参数校验"]
|
||
ValidateAccept --> LookupCode["查找分享码"]
|
||
LookupCode --> EnqueueImport["加入导入持久化队列"]
|
||
EnqueueImport --> ReturnData["返回EQ数据"]
|
||
Start --> DeleteShare["删除分享码"]
|
||
DeleteShare --> ValidateDelete["参数校验"]
|
||
ValidateDelete --> CheckOwnership["校验所有权"]
|
||
CheckOwnership --> DeleteRedis["删除Redis数据"]
|
||
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-L331)
|
||
|
||
**章节来源**
|
||
- [internal/handler/share_code.go:15-383](file://internal/handler/share_code.go#L15-L383)
|
||
- [internal/cache/share_code_cache.go:33-353](file://internal/cache/share_code_cache.go#L33-L353)
|
||
- [internal/repository/share_code.go:12-59](file://internal/repository/share_code.go#L12-L59)
|
||
- [internal/model/share_code.go:5-25](file://internal/model/share_code.go#L5-L25)
|
||
|
||
## 依赖注入模式详解
|
||
|
||
### 依赖注入架构
|
||
所有处理器现在都采用构造函数依赖注入模式,通过 New 函数接收所需的依赖项实例:
|
||
|
||
```mermaid
|
||
graph LR
|
||
MAIN["main.go<br/>应用启动"] --> ROUTER["router.New()<br/>集中装配依赖"]
|
||
ROUTER --> DB["sql.DB<br/>数据库连接"]
|
||
ROUTER --> RDB["redis.Client<br/>Redis客户端"]
|
||
ROUTER --> SEARCH["search.Client<br/>Meilisearch客户端"]
|
||
ROUTER --> S3["storage.S3Storage<br/>S3存储"]
|
||
ROUTER --> LOG["zap.Logger<br/>日志器"]
|
||
ROUTER --> EQCFG["config.EqualizeConfig<br/>等化配置"]
|
||
ROUTER --> SHAREMAX["int<br/>分享码数量限制"]
|
||
ROUTER --> SHARETTL["time.Duration<br/>分享码TTL"]
|
||
ROUTER --> BRANCACHE["cache.BrandCache<br/>品牌缓存"]
|
||
ROUTER --> MODELCACHE["cache.ModelCache<br/>型号缓存"]
|
||
ROUTER --> CURVECACHE["cache.CurveCache<br/>曲线缓存"]
|
||
ROUTER --> SHARECODECACHE["cache.ShareCodeCache<br/>分享码缓存"]
|
||
ROUTER --> BRANDREPO["repository.NewBrandRepository()<br/>品牌仓储"]
|
||
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/>分享代码处理器"]
|
||
```
|
||
|
||
**图表来源**
|
||
- [cmd/server/main.go:77](file://cmd/server/main.go#L77)
|
||
- [internal/router/router.go:22-82](file://internal/router/router.go#L22-82)
|
||
|
||
### 依赖注入优势
|
||
- **测试友好**:可以轻松注入模拟对象进行单元测试
|
||
- **解耦**:处理器不再直接依赖具体实现,只依赖抽象接口
|
||
- **可配置**:运行时可以注入不同的实现
|
||
- **生命周期管理**:依赖项的创建和销毁由路由集中管理
|
||
|
||
### 仓储层依赖
|
||
BrandRepository、ModelRepository、OTARepository、CurveRepository、ShareCodeRepository 和新增的 HeadphoneImpedanceRepository 现在接收相应的依赖:
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class BrandRepository {
|
||
- db : "sql.DB"
|
||
- cache : "BrandCache"
|
||
+ NewBrandRepository(db, cache) : "BrandRepository"
|
||
+ List(ctx, brandName) : "[]Brand,error"
|
||
+ ListFromDB(ctx, brandName) : "[]Brand,error"
|
||
}
|
||
class ModelRepository {
|
||
- db : "sql.DB"
|
||
- cache : "ModelCache"
|
||
+ NewModelRepository(db, cache) : "ModelRepository"
|
||
+ List(ctx, brandName, modelName) : "[]Model,error"
|
||
+ ListAllFromDB(ctx) : "[]Model,error"
|
||
+ 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"
|
||
+ GetLatestOTA(ctx, modelVal, hw, beta) : "OTA,error"
|
||
+ GetLatestOTANotInBlackList(ctx, modelVal, hw, beta, mac) : "OTA,error"
|
||
+ GetLatestOTANotTarget(ctx, modelVal, hw, beta) : "OTA,error"
|
||
+ IsInBlackList(ctx, otaID, mac) : "bool,error"
|
||
+ FindTargetDevice(ctx, otaID, mac) : "OTATargetDevice,error"
|
||
}
|
||
class CurveRepository {
|
||
- db : "sql.DB"
|
||
+ NewCurveRepository(db) : "CurveRepository"
|
||
+ GetModelByBrandAndName(ctx, brandName, name) : "Model,error"
|
||
+ GetTargetByLabel(ctx, label) : "Target,error"
|
||
}
|
||
class ShareCodeRepository {
|
||
- db : "sql.DB"
|
||
+ NewShareCodeRepository(db) : "ShareCodeRepository"
|
||
+ InsertLog(ctx, log) : "error"
|
||
+ HasExportLog(ctx, shareCode) : "bool,error"
|
||
}
|
||
class BrandCache {
|
||
- rdb : "redis.Client"
|
||
+ NewBrandCache(rdb) : "BrandCache"
|
||
+ GetAll(ctx) : "[]Brand,error"
|
||
+ SetAll(ctx, list) : "error"
|
||
}
|
||
class ModelCache {
|
||
- rdb : "redis.Client"
|
||
+ NewModelCache(rdb) : "ModelCache"
|
||
+ GetByBrand(ctx, brandName) : "[]Model,error"
|
||
+ SetByBrand(ctx, brandName, list) : "error"
|
||
+ GetAll(ctx) : "[]Model,error"
|
||
+ SetAll(ctx, list) : "error"
|
||
}
|
||
class CurveCache {
|
||
- rdb : "redis.Client"
|
||
+ NewCurveCache(rdb) : "CurveCache"
|
||
+ Get(ctx, brand, name, target) : "string,error"
|
||
+ Set(ctx, brand, name, target, data) : "error"
|
||
+ GetFR(ctx, brand, name) : "string,error"
|
||
+ GetWithFR(ctx, brand, name, target) : "string,error"
|
||
+ AcquireLock(ctx, brand, name, target) : "bool,error"
|
||
+ ReleaseLock(ctx, brand, name, target) : "error"
|
||
}
|
||
class ShareCodeCache {
|
||
- rdb : "redis.Client"
|
||
- codeTTL : "time.Duration"
|
||
+ NewShareCodeCache(rdb, codeTTL) : "ShareCodeCache"
|
||
+ Create(ctx, macAddr, ipAddr, model, eqData) : "ShareCodeData,error"
|
||
+ Get(ctx, shareCode) : "ShareCodeData,error"
|
||
+ ListByMac(ctx, macAddr) : "[]ShareCodeData,error"
|
||
+ Delete(ctx, macAddr, shareCode) : "ShareDeleteResult,error"
|
||
+ EnqueueImportLog(ctx, macAddr, shareCode, model, ipAddr, eqData, expireAt) : "error"
|
||
+ AcquireFlushLock(ctx, shareCode) : "bool,error"
|
||
+ ReleaseFlushLock(ctx, shareCode) : "error"
|
||
}
|
||
BrandRepository --> BrandCache : "依赖注入"
|
||
ModelRepository --> ModelCache : "依赖注入"
|
||
HeadphoneImpedanceRepository --> DB : "依赖注入"
|
||
OTARepository --> DB : "依赖注入"
|
||
CurveRepository --> DB : "依赖注入"
|
||
ShareCodeRepository --> DB : "依赖注入"
|
||
CurveCache --> RDB : "依赖注入"
|
||
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)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [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-82)
|
||
- [cmd/server/main.go:77](file://cmd/server/main.go#L77)
|
||
|
||
## 依赖分析
|
||
- **依赖注入模式**:
|
||
- 所有处理器通过构造函数注入依赖项,包括仓储、搜索客户端、Redis 客户端、S3存储与日志器。
|
||
- 路由在应用启动时集中装配,保证依赖一次性构建与共享。
|
||
- **组件耦合**:
|
||
- 处理器与仓储之间为单向依赖,职责清晰。
|
||
- 搜索与缓存作为外部服务,通过客户端封装接入。
|
||
- OTA处理器与数据库直接交互,提供完整的OTA数据访问能力。
|
||
- CurveHandler与外部EQ接口和S3存储集成,支持复杂的音频处理。
|
||
- ShareCodeHandler与Redis缓存深度集成,实现高性能的分享码管理。
|
||
- **新增**:ImpedanceHandler与Redis集成,实现高效的阻抗数据存储。
|
||
- **新增**:HeadphoneImpedanceRepository提供完整的数据库CRUD操作。
|
||
- **可能的循环依赖**:
|
||
- 当前结构未见循环导入,符合 Go 包管理最佳实践。
|
||
|
||
```mermaid
|
||
graph LR
|
||
MAIN["main.go"] --> ROUTER["router.go"]
|
||
ROUTER --> HEALTH["health.go"]
|
||
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"]
|
||
BRAND --> BREPO
|
||
BREPO --> BCACHE["cache/brand_cache.go"]
|
||
MODEL --> MREPO
|
||
MREPO --> MCACHE["cache/model_cache.go"]
|
||
IMPEDANCEREPO --> DB["MySQL数据库"]
|
||
CURVE --> CURVEREPO
|
||
CURVEREPO --> DB
|
||
SHARECODE --> SHARECODEREPO
|
||
SHARECODEREPO --> DB
|
||
CURVE --> S3["S3存储"]
|
||
CURVE --> ENCODE["pkg/encode/base64.go"]
|
||
SHARECODE --> ENCODE
|
||
ALL["各处理器"] --> RESP["internal/response/response.go"]
|
||
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-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)
|
||
- [internal/search/meilisearch.go:17](file://internal/search/meilisearch.go#L17)
|
||
- [pkg/encode/base64.go:8](file://pkg/encode/base64.go#L8)
|
||
- [internal/response/response.go:9](file://internal/response/response.go#L9)
|
||
|
||
**章节来源**
|
||
- [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)
|
||
|
||
## 性能考虑
|
||
- **响应体积优化**
|
||
- 对于大结果集,优先启用自定义 Base64 响应,减少传输体积与字符转义开销。
|
||
- 检索处理器对 count 进行上限控制,避免超大数据量返回。
|
||
- **数据库查询**
|
||
- 品牌与型号查询均使用 ORDER BY 与 LIKE,建议在数据库侧建立合适索引以提升模糊查询性能。
|
||
- OTA查询涉及多表关联,建议在ota表的model、hw、beta、status字段建立复合索引。
|
||
- 仓储层现在支持缓存降级,当缓存不可用时自动回退到数据库查询。
|
||
- 分享码持久化查询优化,使用索引加速查询。
|
||
- **新增**:耳机阻抗数据使用唯一索引确保数据去重,提升查询性能。
|
||
- **搜索与缓存**
|
||
- Meilisearch 适合全文检索,建议合理设置属性检索范围与分页大小。
|
||
- Redis 写入为单键 HSet,具备良好吞吐能力;建议评估内存占用与持久化策略。
|
||
- 缓存层现在支持全量缓存和按品牌缓存两种策略,提升查询性能。
|
||
- **新增**:耳机阻抗数据存储在Redis Hash中,使用组合键实现高效去重。
|
||
- **新增**:CurveHandler的fr数据独立存储优化,避免每个target重复存储fr数据。
|
||
- **新增**:ShareCodeCache使用Lua脚本原子性操作,减少网络往返。
|
||
- **外部服务集成**
|
||
- EQ接口调用设置10秒超时,避免阻塞请求。
|
||
- S3读取CSV文件时进行错误处理和缓存优化。
|
||
- 分布式锁避免并发重复请求EQ接口。
|
||
- **分享码性能优化**
|
||
- Redis ZSET存储MAC到分享码的映射,支持快速查询未过期分享码。
|
||
- 分布式锁防止重复创建分享码。
|
||
- 异步持久化任务批量刷入数据库,减少实时写入压力。
|
||
- 分享码TTL配置支持灵活的时间单位(分钟、小时、天)。
|
||
- **耳机阻抗性能优化**
|
||
- Redis HSet原子操作确保数据一致性。
|
||
- 数据归一化处理减少存储空间和查询复杂度。
|
||
- 异步持久化任务每5分钟执行一次,平衡实时性和性能。
|
||
- 数据库唯一索引确保数据去重,避免重复插入。
|
||
- **中间件与日志**
|
||
- 开启 Recovery、CORS、Logger、RequestID 中间件,有助于可观测性与稳定性;注意日志级别与输出频率对性能的影响。
|
||
- **并发安全**
|
||
- Redis HSet 为线程安全操作;处理器方法本身无共享可变状态,天然并发安全。
|
||
- 分布式锁确保同一资源的串行访问。
|
||
- 依赖注入模式减少了全局状态,提升了并发安全性。
|
||
- **最佳实践**
|
||
- 在生产环境启用 Release 模式,降低框架开销。
|
||
- 对外部依赖(数据库、搜索、缓存、S3)增加超时与重试策略,提升鲁棒性。
|
||
- 利用缓存预热机制,在应用启动时加载常用数据到缓存。
|
||
- OTA查询流程复杂,建议添加适当的超时控制和错误重试机制。
|
||
- **新增**:耳机阻抗持久化任务建议设置合理的间隔时间,平衡实时性和性能。
|
||
- **新增**:Redis存储的阻抗数据应设置合理的过期时间,避免无限增长。
|
||
|
||
## 故障排查指南
|
||
- **健康检查失败**
|
||
- 确认路由已正确注册到 /api/v1/health。
|
||
- 查看统一响应体是否返回状态字段。
|
||
- **品牌/型号查询异常**
|
||
- 检查数据库连接与 SQL 查询逻辑。
|
||
- 关注日志中"get brand list failed"、"get model list failed"的错误堆栈。
|
||
- 验证缓存是否正常工作,检查 Redis 连接状态。
|
||
- **型号检索异常**
|
||
- 检查 Meilisearch 配置与索引可用性。
|
||
- 关注"model list search failed"与"decode meilisearch hit"的错误。
|
||
- **设备上报异常**
|
||
- 校验必填参数 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"等错误日志。
|
||
- 检查数据库连接,确认ota、black_list、ota_target_device表存在且结构正确。
|
||
- 验证OTA状态、硬件版本号、灰度标识等字段是否符合预期。
|
||
- **更新**:OTA处理器内部逻辑已简化,不再处理paw相关字段,如遇相关错误请检查其他字段。
|
||
- **等化曲线异常**
|
||
- 校验必填参数 brand、name 是否传入且格式正确。
|
||
- 关注"get curve point failed"、"get model curve failed"等错误日志。
|
||
- 检查外部EQ接口连通性和响应格式。
|
||
- 验证S3存储权限和CSV文件是否存在。
|
||
- 关注分布式锁获取失败的情况。
|
||
- **分享代码异常**
|
||
- 校验必填参数是否完整,特别是POST请求的JSON格式。
|
||
- 关注"参数校验失败"、"系统错误"等错误信息。
|
||
- 检查Redis连接状态和Lua脚本执行情况。
|
||
- 验证分享码TTL配置是否正确。
|
||
- 关注分布式锁获取失败和权限验证失败的情况。
|
||
- 检查异步持久化任务是否正常运行。
|
||
- **依赖注入相关问题**
|
||
- 确认所有处理器都通过构造函数正确注入了依赖项。
|
||
- 检查路由装配顺序,确保依赖项在处理器之前创建。
|
||
- **统一响应与错误码**
|
||
- 使用 internal/response/response.go 提供的 OK/Fail/BadRequest/InternalError 方法,确保错误码与消息格式一致。
|
||
- **日志与追踪**
|
||
- 通过 RequestID 中间件串联一次请求的全链路日志,结合 Logger 中间件定位问题。
|
||
|
||
**章节来源**
|
||
- [internal/handler/health.go:14-18](file://internal/handler/health.go#L14-L18)
|
||
- [internal/handler/brand.go:30-34](file://internal/handler/brand.go#L30-L34)
|
||
- [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)
|
||
- [internal/response/response.go:15-36](file://internal/response/response.go#L15-L36)
|
||
- [internal/middleware/logger.go:10-45](file://internal/middleware/logger.go#L10-L45)
|
||
- [internal/middleware/request_id.go:20-31](file://internal/middleware/request_id.go#L20-L31)
|
||
|
||
## 结论
|
||
本处理器模块采用全新的依赖注入模式,通过构造函数注入依赖项,实现了更好的解耦和可测试性。通过统一响应体与自定义 Base64 编码实现灵活的输出策略,结合 Gin 中间件与外部服务(数据库、Meilisearch、Redis、S3)形成稳定高效的业务处理链路。新增的分享码、等化曲线和耳机阻抗数据处理功能提供了完整的业务能力,包括高性能的Redis缓存、分布式锁、异步持久化和复杂的音频处理流程。新的架构模式简化了处理器实现,提升了代码质量,建议在生产环境中进一步完善超时与重试、索引优化与缓存策略,持续提升性能与可靠性。
|
||
|
||
## 附录
|
||
- **路由与处理器映射**
|
||
- /api/v1/health -> HealthHandler.Check
|
||
- /audio/getBrand -> BrandHandler.GetBrand
|
||
- /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
|
||
- /audio/shareAccept -> ShareCodeHandler.ImportShareCode
|
||
- /audio/shareDelete -> ShareCodeHandler.DeleteShareCode
|
||
- **依赖注入示例**
|
||
- 品牌处理器:BrandHandler.NewBrandHandler(brandRepo, log)
|
||
- 型号处理器: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)
|
||
- **等化曲线查询参数说明**
|
||
- brand:设备品牌(必填)
|
||
- name:设备型号(必填)
|
||
- target:目标曲线名称(可选,默认modelCurve固定为Harman over-ear 2018)
|
||
- base64Resp:是否返回base64编码响应(可选)
|
||
- **等化曲线数据模型字段**
|
||
- Model:id、brand_name、name、form、rig、source、eq_key、create_at
|
||
- Target:id、label、read_csv、file、bassBoost、addTime
|
||
- **分享代码查询参数说明**
|
||
- POST /audio/shareCreate:mac(必填)、model(必填)、eq_data(必填)
|
||
- GET /audio/shareList:mac(必填)
|
||
- GET /audio/shareQuery:shareCode(必填)
|
||
- GET /audio/shareAccept:mac(必填)、model(必填)、shareCode(必填)
|
||
- GET /audio/shareDelete:mac(必填)、shareCode(必填)
|
||
- **分享代码数据模型字段**
|
||
- ShareCodeData:share_code、mac_addr、ip_addr、model、eq_data、expire_at、persisted
|
||
- ShareCodeLog:id、mac_addr、share_code、action、model、ip_addr、eq_data、expire_at、create_at
|
||
- **耳机阻抗查询参数说明**
|
||
- GET /audio/reportImpedance:mac(必填)、name(必填)、brand(必填)、model(必填)、value(必填)
|
||
- **耳机阻抗数据模型字段**
|
||
- UserHeadphoneImpedance:id、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)
|
||
- 健康检查:[internal/handler/health.go:14](file://internal/handler/health.go#L14)
|
||
- **配置与启动**
|
||
- 配置加载与环境变量:[internal/config/config.go:18-64](file://internal/config/config.go#L18-L64)
|
||
- 服务器启动与优雅关闭:[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)
|
||
- RequestID:[internal/middleware/request_id.go:20-31](file://internal/middleware/request_id.go#L20-L31)
|
||
- **缓存策略**
|
||
- 品牌缓存:brand:all 键,30分钟TTL
|
||
- 型号缓存: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
|
||
- 启动时预热缓存,提升首次查询性能
|
||
- **异步持久化任务**
|
||
- 分享码持久化任务:每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自增主键 |