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
@@ -8,6 +8,7 @@
- [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/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)
@@ -24,20 +25,24 @@
- [internal/model/share_code.go](file://internal/model/share_code.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/model/ota.go](file://internal/model/ota.go)
- [internal/handler/brand.go](file://internal/handler/brand.go)
- [internal/handler/model.go](file://internal/handler/model.go)
- [internal/handler/device.go](file://internal/handler/device.go)
- [internal/handler/impedance.go](file://internal/handler/impedance.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/router/router.go](file://internal/router/router.go)
- [internal/task/device_persist.go](file://internal/task/device_persist.go)
- [internal/task/impedance_persist.go](file://internal/task/impedance_persist.go)
- [internal/task/share_code_persist.go](file://internal/task/share_code_persist.go)
- [cmd/server/main.go](file://cmd/server/main.go)
- [sql/model.sql](file://sql/model.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)
- [sql/ota.sql](file://sql/ota.sql)
- [sql/ota_target_device.sql](file://sql/ota_target_device.sql)
- [sql/share_code_log.sql](file://sql/share_code_log.sql)
@@ -46,10 +51,12 @@
## 更新摘要
**变更内容**
- OTA仓库层实现已移除pawVerCode、pawVerName、pawUrl、pawMd5字段,简化了查询逻辑
- OTA模型结构已精简,移除了不必要的字段,提高了数据传输效率
- OTA查询方法的SQL语句已优化,减少了字段选择和扫描操作
- OTA表结构已更新,移除了相关字段定义
- 新增耳机阻抗数据数据库访问层(HeadphoneImpedanceRepository),支持按MAC地址和标准化耳机标识符查找、插入和更新记录
- 实现完整的耳机阻抗数据持久化流程,包括Redis异步处理和定时任务同步
- 新增ImpedanceHandler处理API请求,提供耳机阻抗上报接口
- 新增ImpedancePersistTask定时任务,从Redis批量同步阻抗数据到数据库
- 在main.go中集成新的阻抗持久化任务,支持环境变量控制开关
- OTA仓库层已移除pawVerCode、pawVerName、pawUrl、pawMd5字段,简化查询逻辑
## 目录
1. [简介](#简介)
@@ -65,10 +72,10 @@
11. [附录:扩展新 Repository 指南](#附录扩展新-repository-指南)
## 简介
本文件聚焦于 Luxsin 应用 API 的数据访问层(Repository 层),系统性阐述 BrandRepository、ModelRepository、DeviceRepository、OTARepository、CurveRepository、ShareCodeRepository 的设计模式、实现原理与职责边界;解释其在整体架构中的位置与交互方式;深入分析数据库连接管理、SQL 查询优化与事务处理机制;详细介绍新增的缓存感知查询逻辑、多级缓存策略和智能数据刷新机制;给出使用示例路径、错误处理与异常管理策略;总结并发安全、连接池管理与资源清理的最佳实践,并提供扩展新 Repository 的指导原则与注意事项。文档兼顾初学者与资深开发者的需求,既提供高层架构视图,也给出可落地的实现细节。
本文件聚焦于 Luxsin 应用 API 的数据访问层(Repository 层),系统性阐述 BrandRepository、ModelRepository、DeviceRepository、OTARepository、CurveRepository、ShareCodeRepository 以及新增的 HeadphoneImpedanceRepository 的设计模式、实现原理与职责边界;解释其在整体架构中的位置与交互方式;深入分析数据库连接管理、SQL 查询优化与事务处理机制;详细介绍新增的缓存感知查询逻辑、多级缓存策略和智能数据刷新机制;给出使用示例路径、错误处理与异常管理策略;总结并发安全、连接池管理与资源清理的最佳实践,并提供扩展新 Repository 的指导原则与注意事项。文档兼顾初学者与资深开发者的需求,既提供高层架构视图,也给出可落地的实现细节。
## 项目结构
数据访问层位于 internal/repository 目录,配合 internal/cache 提供缓存层,internal/database 提供底层数据库连接,internal/model 定义领域模型,internal/handler 通过注入的 Repository 执行业务逻辑,internal/task 提供设备数据持久化任务,最终由 Gin 路由暴露接口。
数据访问层位于 internal/repository 目录,配合 internal/cache 提供缓存层,internal/database 提供底层数据库连接,internal/model 定义领域模型,internal/handler 通过注入的 Repository 执行业务逻辑,internal/task 提供设备数据和阻抗数据持久化任务,最终由 Gin 路由暴露接口。
```mermaid
graph TB
@@ -80,6 +87,7 @@ ROUTER["internal/router/router.go<br/>注册路由与中间件"]
BRAND_H["internal/handler/brand.go<br/>品牌处理器"]
MODEL_H["internal/handler/model.go<br/>型号处理器"]
DEVICE_H["internal/handler/device.go<br/>设备处理器"]
IMPEDANCE_H["internal/handler/impedance.go<br/>阻抗处理器"]
OTA_H["internal/handler/ota.go<br/>OTA处理器"]
CURVE_H["internal/handler/curve.go<br/>曲线处理器"]
SHARE_H["internal/handler/share_code.go<br/>分享码处理器"]
@@ -88,6 +96,7 @@ subgraph "数据访问层"
BR_REPO["internal/repository/brand.go<br/>BrandRepository"]
MD_REPO["internal/repository/model.go<br/>ModelRepository"]
DEV_REPO["internal/repository/device.go<br/>DeviceRepository"]
IMPEDANCE_REPO["internal/repository/headphone_impedance.go<br/>HeadphoneImpedanceRepository"]
OTA_REPO["internal/repository/ota.go<br/>OTARepository"]
CURVE_REPO["internal/repository/curve.go<br/>CurveRepository"]
SHARE_REPO["internal/repository/share_code.go<br/>ShareCodeRepository"]
@@ -101,6 +110,7 @@ REDIS["internal/cache/redis.go<br/>Redis 客户端"]
end
subgraph "任务调度"
PERSIST_TASK["internal/task/device_persist.go<br/>设备持久化任务"]
IMPEDANCE_PERSIST_TASK["internal/task/impedance_persist.go<br/>阻抗持久化任务"]
SHARE_PERSIST_TASK["internal/task/share_code_persist.go<br/>分享码持久化任务"]
end
subgraph "数据库与配置"
@@ -112,6 +122,7 @@ CFG_APP["internal/config/config.go<br/>应用配置聚合"]
MODEL_TBL["sql/model.sql<br/>表结构定义"]
USER_DEVICE_TBL["sql/user_device.sql<br/>用户设备表"]
USER_ACTIVE_TBL["sql/user_active.sql<br/>用户活跃表"]
USER_IMPEDANCE_TBL["sql/user_headphone_impedance.sql<br/>用户耳机阻抗表"]
OTA_TBL["sql/ota.sql<br/>OTA表"]
OTA_TARGET_TBL["sql/ota_target_device.sql<br/>OTA定向设备表"]
SHARE_LOG_TBL["sql/share_code_log.sql<br/>分享码日志表"]
@@ -121,6 +132,7 @@ M_BRAND["internal/model/brand.go<br/>Brand 模型"]
M_MODEL["internal/model/model.go<br/>Model 模型"]
M_USER_DEVICE["internal/model/user_device.go<br/>UserDevice 模型"]
M_USER_ACTIVE["internal/model/user_active.go<br/>UserActive 模型"]
M_USER_IMPEDANCE["internal/model/user_headphone_impedance.go<br/>UserHeadphoneImpedance 模型"]
M_OTA["internal/model/ota.go<br/>OTA 模型"]
M_SHARE["internal/model/share_code.go<br/>ShareCode 模型"]
end
@@ -128,12 +140,14 @@ MAIN --> ROUTER
ROUTER --> BRAND_H
ROUTER --> MODEL_H
ROUTER --> DEVICE_H
ROUTER --> IMPEDANCE_H
ROUTER --> OTA_H
ROUTER --> CURVE_H
ROUTER --> SHARE_H
BRAND_H --> BR_REPO
MODEL_H --> MD_REPO
DEVICE_H --> DEV_REPO
IMPEDANCE_H --> IMPEDANCE_REPO
OTA_H --> OTA_REPO
CURVE_H --> CURVE_REPO
SHARE_H --> SHARE_REPO
@@ -146,11 +160,14 @@ MD_CACHE --> REDIS
CURVE_CACHE --> REDIS
SHARE_CACHE --> REDIS
DEV_REPO --> DB_SQL
IMPEDANCE_REPO --> DB_SQL
OTA_REPO --> DB_SQL
CURVE_REPO --> DB_SQL
SHARE_REPO --> DB_SQL
PERSIST_TASK --> DEV_REPO
PERSIST_TASK --> REDIS
IMPEDANCE_PERSIST_TASK --> IMPEDANCE_REPO
IMPEDANCE_PERSIST_TASK --> REDIS
SHARE_PERSIST_TASK --> SHARE_REPO
SHARE_PERSIST_TASK --> SHARE_CACHE
DB_SQL --> CFG_DB
@@ -161,11 +178,13 @@ BR_REPO --> M_BRAND
MD_REPO --> M_MODEL
DEV_REPO --> M_USER_DEVICE
DEV_REPO --> M_USER_ACTIVE
IMPEDANCE_REPO --> M_USER_IMPEDANCE
OTA_REPO --> M_OTA
SHARE_REPO --> M_SHARE
MODEL_TBL --> DB_SQL
USER_DEVICE_TBL --> DB_SQL
USER_ACTIVE_TBL --> DB_SQL
USER_IMPEDANCE_TBL --> DB_SQL
OTA_TBL --> DB_SQL
OTA_TARGET_TBL --> DB_SQL
SHARE_LOG_TBL --> DB_SQL
@@ -177,15 +196,17 @@ SHARE_LOG_TBL --> DB_SQL
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
- [internal/handler/device.go:15-25](file://internal/handler/device.go#L15-L25)
- [internal/handler/impedance.go:23-28](file://internal/handler/impedance.go#L23-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)
- [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/device.go:11-17](file://internal/repository/device.go#L11-L17)
- [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/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-18)
- [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)
@@ -197,10 +218,12 @@ SHARE_LOG_TBL --> DB_SQL
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
- [internal/config/share_code_ttl.go:12-59](file://internal/config/share_code_ttl.go#L12-L59)
- [internal/task/device_persist.go:14-22](file://internal/task/device_persist.go#L14-L22)
- [internal/task/impedance_persist.go:22-28](file://internal/task/impedance_persist.go#L22-L28)
- [internal/task/share_code_persist.go:14-22](file://internal/task/share_code_persist.go#L14-L22)
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
- [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)
- [sql/ota.sql:23-44](file://sql/ota.sql#L23-L44)
- [sql/ota_target_device.sql:23-31](file://sql/ota_target_device.sql#L23-L31)
- [sql/share_code_log.sql:8-23](file://sql/share_code_log.sql#L8-L23)
@@ -211,12 +234,14 @@ SHARE_LOG_TBL --> DB_SQL
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
- [internal/handler/device.go:15-25](file://internal/handler/device.go#L15-L25)
- [internal/handler/impedance.go:23-28](file://internal/handler/impedance.go#L23-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)
- [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/device.go:11-17](file://internal/repository/device.go#L11-L17)
- [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)
@@ -231,10 +256,12 @@ SHARE_LOG_TBL --> DB_SQL
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
- [internal/config/share_code_ttl.go:12-59](file://internal/config/share_code_ttl.go#L12-L59)
- [internal/task/device_persist.go:14-22](file://internal/task/device_persist.go#L14-L22)
- [internal/task/impedance_persist.go:22-28](file://internal/task/impedance_persist.go#L22-L28)
- [internal/task/share_code_persist.go:14-22](file://internal/task/share_code_persist.go#L14-L22)
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
- [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)
- [sql/ota.sql:23-44](file://sql/ota.sql#L23-L44)
- [sql/ota_target_device.sql:23-31](file://sql/ota_target_device.sql#L23-L31)
- [sql/share_code_log.sql:8-23](file://sql/share_code_log.sql#L8-L23)
@@ -243,12 +270,14 @@ SHARE_LOG_TBL --> DB_SQL
- **BrandRepository**:负责品牌列表查询,支持缓存感知的全量查询和按名称模糊过滤,返回 Brand 领域对象切片。
- **ModelRepository**:负责型号列表查询,支持缓存感知的按品牌精确匹配、按型号名模糊匹配和全量查询,返回 Model 领域对象切片,并对可空字段进行 NullString 到指针字符串的安全转换。
- **DeviceRepository**:负责设备信息管理,支持按MAC地址查询设备、插入新设备、更新设备版本信息,以及用户活跃信息的查询和更新。
- **HeadphoneImpedanceRepository****新增** 负责耳机阻抗数据管理,支持按MAC地址和标准化耳机标识符查找记录、插入新记录和更新现有记录,实现阻抗数据的去重和最新值维护。
- **OTARepository**:负责固件升级信息查询,支持按型号、硬件版本、灰度标识查询最新OTA记录,黑名单检查、定向设备筛选等功能。**已精简**:移除了pawVerCode、pawVerName、pawUrl、pawMd5字段,简化了查询逻辑和数据处理流程。
- **CurveRepository**:负责频响曲线数据查询,支持按品牌+型号查询Model信息和按标签查询Target信息,为曲线缓存提供数据支撑。
- **ShareCodeRepository**:负责分享码日志管理,支持插入分享码操作日志、查询导出日志等数据库操作,提供完整的分享码数据持久化能力。
- **CurveCache**:新增的曲线缓存层,支持fr数据分离存储、分布式锁机制、缓存合并逻辑,优化曲线数据访问性能。
- **ShareCodeCache**:新增的分享码缓存层,支持Lua原子操作、MAC地址索引、导入导出队列、分布式锁等高级缓存功能,提供完整的分享码生命周期管理。
- **设备持久化任务**DevicePersistTask 从Redis批量读取设备信息,异步写入MySQL user_device 和 user_active 表,支持数据可靠性保障。
- **阻抗持久化任务****新增** ImpedancePersistTask 从Redis批量读取耳机阻抗数据,异步写入MySQL user_headphone_impedance 表,支持数据去重和最新值更新。
- **分享码持久化任务**ShareCodePersistTask 从Redis批量读取分享码数据,异步写入MySQL share_code_log 表,支持数据可靠性保障。
- **数据库连接**:通过 sql.DB 统一管理连接池,设置最大打开连接数、空闲连接数与连接生命周期,并在启动时进行 Ping 校验。
- **配置加载**:从环境变量或默认值加载数据库、Redis和分享码TTL配置,生产环境要求提供密码。
@@ -258,12 +287,14 @@ SHARE_LOG_TBL --> DB_SQL
- [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/device.go:19-87](file://internal/repository/device.go#L19-L87)
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
- [internal/repository/ota.go:19-158](file://internal/repository/ota.go#L19-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/cache/curve_cache.go:18-148](file://internal/cache/curve_cache.go#L18-L148)
- [internal/cache/share_code_cache.go:127-353](file://internal/cache/share_code_cache.go#L127-L353)
- [internal/task/device_persist.go:14-167](file://internal/task/device_persist.go#L14-L167)
- [internal/task/impedance_persist.go:30-163](file://internal/task/impedance_persist.go#L30-L163)
- [internal/task/share_code_persist.go:14-22](file://internal/task/share_code_persist.go#L14-L22)
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
- [internal/config/database.go:17-72](file://internal/config/database.go#L17-L72)
@@ -273,17 +304,21 @@ SHARE_LOG_TBL --> DB_SQL
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
- [internal/handler/device.go:15-25](file://internal/handler/device.go#L15-L25)
- [internal/handler/impedance.go:23-28](file://internal/handler/impedance.go#L23-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)
## 架构总览
数据访问层采用"仓储模式"Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。新增的缓存层提供多级缓存策略,支持智能数据刷新和降级机制。新增的设备持久化仓库和OTA仓库进一步完善了数据访问层的功能,支持设备信息上报、固件升级查询等核心业务场景。新增的曲线缓存和分享码缓存层提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等。通过集成 Redis 缓存层和异步持久化任务,显著提升了系统的响应性能和数据可靠性。整体流程如下:
数据访问层采用"仓储模式"Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。新增的缓存层提供多级缓存策略,支持智能数据刷新和降级机制。新增的设备持久化仓库、阻抗持久化仓库、OTA仓库、曲线仓库和分享码仓库进一步完善了数据访问层的功能,支持设备信息上报、耳机阻抗数据采集、固件升级查询、频响曲线计算和分享码管理等核心业务场景。新增的CurveCache和ShareCodeCache提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等。通过集成 Redis 缓存层和异步持久化任务,显著提升了系统的响应性能和数据可靠性。整体流程如下:
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Router as "Gin 路由"
participant ImpedanceHandler as "ImpedanceHandler"
participant ImpedanceRepo as "HeadphoneImpedanceRepository"
participant ImpedanceTask as "ImpedancePersistTask"
participant CurveHandler as "CurveHandler"
participant ShareHandler as "ShareCodeHandler"
participant CurveRepo as "CurveRepository"
@@ -294,6 +329,26 @@ participant DB as "sql.DB"
participant Redis as "Redis 服务器"
participant MySQL as "MySQL 服务器"
Client->>Router : "HTTP 请求"
Router->>ImpedanceHandler : "上报阻抗"
ImpedanceHandler->>Redis : "HSet headphone_impedances"
Redis-->>ImpedanceHandler : "写入成功"
ImpedanceHandler-->>Client : "操作成功"
Note over ImpedanceTask,MySQL : 定时任务每5分钟执行
ImpedanceTask->>Redis : "HGetAll headphone_impedances"
Redis-->>ImpedanceTask : "获取待持久化数据"
ImpedanceTask->>ImpedanceRepo : "FindByMacAndNorm"
ImpedanceRepo->>DB : "QueryRowContext"
DB-->>ImpedanceRepo : "查询结果"
alt 记录不存在
ImpedanceTask->>ImpedanceRepo : "Insert"
ImpedanceRepo->>DB : "ExecContext"
DB-->>ImpedanceRepo : "插入成功"
else 记录存在
ImpedanceTask->>ImpedanceRepo : "Update"
ImpedanceRepo->>DB : "ExecContext"
DB-->>ImpedanceRepo : "更新成功"
end
ImpedanceTask->>Redis : "HDel 已处理数据"
Router->>CurveHandler : "曲线查询"
CurveHandler->>CurveCache : "GetWithFR"
CurveCache->>Redis : "HGet brand name target"
@@ -329,6 +384,9 @@ ShareHandler-->>Client : "操作结果"
```
**图表来源**
- [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/curve.go:214-292](file://internal/handler/curve.go#L214-L292)
- [internal/handler/share_code.go:40-125](file://internal/handler/share_code.go#L40-L125)
- [internal/repository/curve.go:19-65](file://internal/repository/curve.go#L19-L65)
@@ -513,6 +571,57 @@ 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)
### HeadphoneImpedanceRepository 设计与实现
- **设计模式**:仓储模式,面向领域模型 UserHeadphoneImpedance,封装耳机阻抗数据管理逻辑。
- **关键特性**
- **按MAC地址和标准化标识符查找**:`FindByMacAndNorm` 方法支持按MAC地址、标准化品牌名和标准化型号名精确查询阻抗记录
- **插入新记录**`Insert` 方法支持插入新的阻抗数据,包含原始输入和标准化字段
- **更新现有记录**`Update` 方法支持更新现有记录的阻抗值、设备型号、IP地址等信息
- **标准化处理**:支持耳机品牌和型号的标准化(trim+lower),用于去重和精确匹配
- **错误处理**:针对不同操作场景提供详细的错误包装,便于问题定位
- **资源管理**:使用 QueryRowContext 和 ExecContext,确保数据库连接正确释放
- **表结构要点**
- user_headphone_impedance 表:主键ID,唯一索引(mac_addr, headphone_brand_norm, headphone_model_norm),支持按MAC和设备型号查询
- 支持原始输入字段(headphone_brand, headphone_model)和标准化字段(headphone_brand_norm, headphone_model_norm
- 自动时间戳字段(create_at, update_at
- **应用场景**:耳机阻抗数据采集、用户设备配置管理、音频参数个性化等。
```mermaid
classDiagram
class HeadphoneImpedanceRepository {
-db : "*sql.DB"
+NewHeadphoneImpedanceRepository(db) HeadphoneImpedanceRepository
+FindByMacAndNorm(ctx, macAddr, brandNorm, modelNorm) *UserHeadphoneImpedance,error
+Insert(ctx, rec) error
+Update(ctx, rec) error
}
class UserHeadphoneImpedance {
+int ID
+string MacAddr
+string DeviceModel
+int ImpedanceOhm
+string HeadphoneBrand
+string HeadphoneModel
+string HeadphoneBrandNorm
+string HeadphoneModelNorm
+string IpAddr
+time CreateAt
+time UpdateAt
}
HeadphoneImpedanceRepository --> UserHeadphoneImpedance : "阻抗数据"
```
**图表来源**
- [internal/repository/headphone_impedance.go:11-17](file://internal/repository/headphone_impedance.go#L11-L17)
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
- [internal/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
**章节来源**
- [internal/repository/headphone_impedance.go:11-17](file://internal/repository/headphone_impedance.go#L11-L17)
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
- [internal/model/user_headphone_impedance.go:5-17](file://internal/model/user_headphone_impedance.go#L5-L17)
- [sql/user_headphone_impedance.sql:20-36](file://sql/user_headphone_impedance.sql#L20-L36)
### OTARepository 设计与实现
- **设计模式**:仓储模式,面向领域模型 OTA、BlackList 和 OTATargetDevice,封装固件升级查询逻辑。
- **关键特性**
@@ -682,7 +791,7 @@ ShareCodeRepository --> ShareCodeLog : "日志记录"
```
**图表来源**
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-18)
- [internal/repository/share_code.go:20-58](file://internal/repository/share_code.go#L20-L58)
- [internal/model/share_code.go:14-24](file://internal/model/share_code.go#L14-L24)
@@ -821,6 +930,53 @@ O --> P[结束]
**章节来源**
- [internal/task/device_persist.go:14-167](file://internal/task/device_persist.go#L14-L167)
### 阻抗持久化任务设计与实现
- **设计模式**:定时任务模式,基于时间轮询的异步数据同步机制。
- **关键特性**
- 定时执行:使用 time.Ticker 按固定间隔(默认5分钟)启动持久化任务
- 批量处理:从Redis Hash `headphone_impedances` 一次性读取所有待持久化的阻抗数据
- 去重更新:根据MAC地址和标准化耳机标识符判断记录是否存在,决定插入还是更新
- 标准化处理:使用标准化后的品牌名和型号名进行精确匹配
- 成功确认:批量删除已成功持久化的数据,避免重复处理
- 错误隔离:单条数据处理失败不影响其他数据的处理
- 配置开关:通过 `ENABLE_IMPEDANCE_PERSIST_TASK` 环境变量控制任务启用
- **处理流程**
1. 从Redis读取 `headphone_impedances` Hash 中的所有阻抗数据
2. 解析JSON格式的阻抗信息
3. 调用 `FindByMacAndNorm` 检查记录是否存在
4. 如果不存在则调用 `Insert` 插入新记录
5. 如果存在则调用 `Update` 更新现有记录
6. 批量删除已成功处理的数据
- **应用场景**:高并发阻抗数据采集、数据可靠性保障、异步数据同步等。
```mermaid
flowchart TD
A[定时器触发] --> B[HGetAll headphone_impedances]
B --> C{是否有待持久化数据?}
C --> |否| D[结束]
C --> |是| E[遍历阻抗数据]
E --> F[解析JSON数据]
F --> G[FindByMacAndNorm]
G --> H{记录存在?}
H --> |否| I[Insert 新记录]
H --> |是| J[Update 现有记录]
I --> K{处理成功?}
J --> K
K --> |是| L[记录成功字段]
K --> |否| M[跳过该记录]
L --> N[批量删除成功数据]
M --> O[继续下一个]
N --> P[记录处理结果]
O --> E
P --> Q[结束]
```
**图表来源**
- [internal/task/impedance_persist.go:44-163](file://internal/task/impedance_persist.go#L44-L163)
**章节来源**
- [internal/task/impedance_persist.go:30-163](file://internal/task/impedance_persist.go#L30-L163)
### 分享码持久化任务设计与实现
- **设计模式**:定时任务模式,基于时间轮询的异步数据同步机制。
- **关键特性**
@@ -864,6 +1020,7 @@ D --> L[结束]
### Handler 与 Repository 的协作
- Handler 通过 NewXxxHandler 注入相应的 Repository 实例,内部执行具体的业务逻辑。
- DeviceHandler 处理设备信息上报,将数据写入Redis Hash,等待异步持久化任务处理。
- **ImpedanceHandler****新增** 处理耳机阻抗上报,接收参数并进行标准化处理,将数据写入Redis Hash,等待异步持久化任务处理。
- OTAHandler 处理OTA升级查询,调用OTARepository执行复杂的业务逻辑,包括黑名单检查、定向设备筛选等。
- CurveHandler 处理曲线查询,调用CurveRepository和CurveCache执行数据查询和缓存操作,支持fr数据分离和分布式锁机制。
- ShareCodeHandler 处理分享码操作,调用ShareCodeCache执行Lua原子操作和队列管理,支持导入导出流程。
@@ -874,44 +1031,61 @@ D --> L[结束]
```mermaid
sequenceDiagram
participant C as "客户端"
participant IH as "ImpedanceHandler"
participant IR as "HeadphoneImpedanceRepository"
participant IT as "ImpedancePersistTask"
participant CH as "CurveHandler"
participant CR as "CurveRepository"
participante CC as "CurveCache"
C->>CH : "GET /audio/modelCurve"
CH->>CC : "GetFR"
CC->>Redis : "HGet brand name __fr"
Redis-->>CC : "fr数据"
alt fr缓存命中
CH-->>C : "返回fr数据"
else fr缓存未命中
partcipante CC as "CurveCache"
C->>IH : "GET /audio/reportImpedance"
IH->>IH : "参数验证和标准化"
IH->>Redis : "HSet headphone_impedances"
Redis-->>IH : "写入成功"
IH-->>C : "操作成功"
Note over IT,IR : 定时任务每5分钟执行
IT->>Redis : "HGetAll headphone_impedances"
Redis-->>IT : "获取待持久化数据"
IT->>IR : "FindByMacAndNorm"
IR->>DB : "QueryRowContext"
DB-->>IR : "查询结果"
alt 记录不存在
IT->>IR : "Insert"
IR->>DB : "ExecContext"
DB-->>IR : "插入成功"
else 记录存在
IT->>IR : "Update"
IR->>DB : "ExecContext"
DB-->>IR : "更新成功"
end
IT->>Redis : "HDel 已处理数据"
C->>CH : "GET /audio/getCurve"
CH->>CC : "GetWithFR"
CC->>Redis : "HGet brand name target"
Redis-->>CC : "target数据"
alt target缓存命中
CH->>CC : "GetFR"
CC->>Redis : "HGet brand name __fr"
Redis-->>CC : "fr数据"
CH-->>C : "合并返回"
else target缓存未命中
Redis-->>CC : "缓存数据"
alt 缓存命中
CC-->>CH : "合并fr数据"
CH-->>C : "返回曲线数据"
else 缓存未命中
CH->>CC : "AcquireLock"
CC->>Redis : "SetNX 分布式锁"
Redis-->>CH : "获取锁成功"
CH->>CR : "GetModelByBrandAndName"
CH->>CC : "双重检查缓存"
CC->>Redis : "HGet brand name target"
Redis-->>CH : "缓存已更新"
CH->>CR : "查询Model和Target"
CR->>DB : "QueryRowContext"
DB-->>CR : "模型数据"
CH->>CR : "GetTargetByLabel"
CR->>DB : "QueryRowContext"
DB-->>CR : "目标数据"
DB-->>CR : "模型和目标数据"
CH->>CC : "Set 缓存"
CH->>CC : "Set __fr 缓存"
CH->>CC : "Set __fr 独立缓存"
CH->>CC : "ReleaseLock"
CH-->>C : "返回曲线数据"
end
end
```
**图表来源**
- [internal/handler/curve.go:56-127](file://internal/handler/curve.go#L56-L127)
- [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/curve.go:214-292](file://internal/handler/curve.go#L214-L292)
- [internal/repository/curve.go:19-65](file://internal/repository/curve.go#L19-L65)
- [internal/cache/curve_cache.go:59-148](file://internal/cache/curve_cache.go#L59-L148)
@@ -923,6 +1097,8 @@ end
- [internal/handler/model.go:25-49](file://internal/handler/model.go#L25-L49)
- [internal/handler/device.go:15-25](file://internal/handler/device.go#L15-L25)
- [internal/handler/device.go:27-82](file://internal/handler/device.go#L27-L82)
- [internal/handler/impedance.go:23-28](file://internal/handler/impedance.go#L23-L28)
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
- [internal/handler/ota.go:14-21](file://internal/handler/ota.go#L14-L21)
- [internal/handler/ota.go:23-132](file://internal/handler/ota.go#L23-L132)
- [internal/handler/curve.go:27-39](file://internal/handler/curve.go#L27-L39)
@@ -933,6 +1109,7 @@ end
- [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/device.go:19-87](file://internal/repository/device.go#L19-L87)
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
- [internal/repository/ota.go:19-158](file://internal/repository/ota.go#L19-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)
@@ -951,6 +1128,7 @@ end
- **型号按品牌缓存**`model:brand:{brandName}` - 存储特定品牌的所有型号
- **型号全量缓存**`model:all` - 存储所有型号信息
- **设备活跃缓存**`device:active:{date}` - 存储特定日期的活跃设备信息
- **耳机阻抗缓存****新增** `headphone_impedances` Hash - 存储待持久化的阻抗数据,字段格式为 `mac|brand_norm|model_norm`
- **曲线缓存**`brand name` Hash - 存储曲线数据,字段为target或__fr
- **分享码缓存**`share:{code}` Hash - 存储分享码主数据,字段包括mac_addr、ip_addr、model、eq_data、expire_at、persisted
- **分享码导入队列**`share:import:pending` Hash - 存储导入日志,支持幂等性
@@ -961,6 +1139,7 @@ end
- **自动回写策略**:成功从数据库获取数据后自动回写到缓存
- **TTL 策略**:所有缓存设置 30 分钟过期时间,平衡性能和数据新鲜度
- **设备数据特殊处理**:设备活跃信息采用分区表存储,缓存策略需考虑日期维度
- **耳机阻抗数据特殊处理**:**新增** 阻抗数据采用异步持久化策略,先写入Redis Hash,再由定时任务批量同步到数据库
- **曲线数据优化**:fr数据独立存储在__fr字段,避免每个target重复存储
- **分享码TTL配置**:支持多种时间单位格式,最大365天,动态配置
- **分布式锁机制**:防止缓存击穿和竞态条件,确保数据一致性
@@ -977,10 +1156,11 @@ D --> E[写入 brand:all 缓存]
E --> F[查询型号全量数据]
F --> G[写入 model:all 缓存]
G --> H[设备数据无需预热]
H --> I[OTA数据无需预热]
I --> J[曲线缓存无需预热]
J --> K[分享码缓存无需预热]
K --> L[缓存预热完成]
H --> I[阻抗数据无需预热]
I --> J[OTA数据无需预热]
J --> K[曲线缓存无需预热]
K --> L[分享码缓存无需预热]
L --> M[缓存预热完成]
```
**图表来源**
@@ -997,7 +1177,7 @@ K --> L[缓存预热完成]
- **外部依赖**Go MySQL Driver、Gin、Zap 日志、Redis 客户端、Meilisearch 客户端、AWS SDK。
- **内部依赖**Handler 依赖 RepositoryRepository 依赖 *sql.DB 和缓存实例;缓存层依赖 Redis 客户端;数据库模块负责连接池与 Ping 校验;配置模块负责环境变量解析与校验;任务模块依赖 Redis 和 Repository。
- **循环依赖**:未发现循环依赖,职责边界清晰。
- **新增依赖**CurveRepository 和 ShareCodeRepository 直接依赖 *sql.DBCurveCache 和 ShareCodeCache 依赖 Redis 客户端;分享码持久化任务依赖 ShareCodeCache 和 ShareCodeRepositoryCurveHandler 依赖 CurveRepository、CurveCache 和 S3 存储;ShareCodeHandler 依赖 ShareCodeCache。
- **新增依赖**CurveRepository 和 ShareCodeRepository 直接依赖 *sql.DBCurveCache 和 ShareCodeCache 依赖 Redis 客户端;分享码持久化任务依赖 ShareCodeCache 和 ShareCodeRepositoryCurveHandler 依赖 CurveRepository、CurveCache 和 S3 存储;ShareCodeHandler 依赖 ShareCodeCache**ImpedanceHandler 依赖 Redis 客户端;ImpedancePersistTask 依赖 HeadphoneImpedanceRepository 和 Redis 客户端**
```mermaid
graph LR
@@ -1016,12 +1196,14 @@ CFG_LOAD --> CFG_SHARE_TTL["internal/config/share_code_ttl.go::parseShareCodeTTL
ROUTER["internal/router/router.go"] --> BRAND_H["internal/handler/brand.go"]
ROUTER --> MODEL_H["internal/handler/model.go"]
ROUTER --> DEVICE_H["internal/handler/device.go"]
ROUTER --> IMPEDANCE_H["internal/handler/impedance.go"]
ROUTER --> OTA_H["internal/handler/ota.go"]
ROUTER --> CURVE_H["internal/handler/curve.go"]
ROUTER --> SHARE_H["internal/handler/share_code.go"]
BRAND_H --> BR_REPO["internal/repository/brand.go"]
MODEL_H --> MD_REPO["internal/repository/model.go"]
DEVICE_H --> DEV_REPO["internal/repository/device.go"]
IMPEDANCE_H --> IMPEDANCE_REPO["internal/repository/headphone_impedance.go"]
OTA_H --> OTA_REPO["internal/repository/ota.go"]
CURVE_H --> CURVE_REPO["internal/repository/curve.go"]
SHARE_H --> SHARE_REPO["internal/repository/share_code.go"]
@@ -1034,11 +1216,14 @@ MD_CACHE --> REDIS_CLIENT
CURVE_CACHE --> REDIS_CLIENT
SHARE_CACHE --> REDIS_CLIENT
DEV_REPO --> DB_SQL["*sql.DB"]
IMPEDANCE_REPO --> DB_SQL
OTA_REPO --> DB_SQL
CURVE_REPO --> DB_SQL
SHARE_REPO --> DB_SQL
PERSIST_TASK --> DEV_REPO
PERSIST_TASK --> REDIS_CLIENT
IMPEDANCE_PERSIST_TASK --> IMPEDANCE_REPO
IMPEDANCE_PERSIST_TASK --> REDIS_CLIENT
SHARE_PERSIST_TASK --> SHARE_REPO
SHARE_PERSIST_TASK --> SHARE_CACHE
BR_CACHE --> REDIS_CLIENT
@@ -1056,12 +1241,14 @@ MD_CACHE --> REDIS_CLIENT
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
- [internal/handler/device.go:15-25](file://internal/handler/device.go#L15-L25)
- [internal/handler/impedance.go:23-28](file://internal/handler/impedance.go#L23-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)
- [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/device.go:11-17](file://internal/repository/device.go#L11-L17)
- [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)
@@ -1081,12 +1268,14 @@ MD_CACHE --> REDIS_CLIENT
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
- [internal/handler/device.go:15-25](file://internal/handler/device.go#L15-L25)
- [internal/handler/impedance.go:23-28](file://internal/handler/impedance.go#L23-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)
- [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/device.go:11-17](file://internal/repository/device.go#L11-L17)
- [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)
@@ -1110,10 +1299,12 @@ MD_CACHE --> REDIS_CLIENT
- 分布式锁:限制同一时间只有一个请求在计算曲线数据
- 分享码缓存优化:Lua原子操作确保数据一致性,避免竞态条件
- 分布式锁:防止缓存击穿和竞态条件,确保数据一致性
- **阻抗数据优化**:**新增** 采用异步持久化策略,先写入Redis再批量同步,提升响应速度和数据可靠性
- **查询优化**
- BrandRepository:按名称模糊匹配,建议在 name 上建立合适索引;对大结果集引入分页。
- ModelRepository:按品牌名精确匹配或按型号名模糊匹配,建议为 brand_name 与 name 建立索引;对大结果集引入分页。
- DeviceRepositoryuser_device 表的 MAC 地址建立唯一索引,user_active 表建立复合索引(mac_addr, active_date)。
- **HeadphoneImpedanceRepository****新增** user_headphone_impedance 表建立唯一索引(mac_addr, headphone_brand_norm, headphone_model_norm),支持按MAC和设备型号查询。
- OTARepositoryota 表建立复合索引(model, hw, beta, status),black_list 表建立索引(ota_id, mac),ota_target_device 表建立索引(ota_id, mac_addr)。**已优化**:移除了pawVerCode、pawVerName、pawUrl、pawMd5字段,减少了查询字段数量,提升了查询性能。
- CurveRepositorymodel 表建立复合索引(brand_name, name),target 表建立索引(label)。
- ShareCodeRepositoryshare_code_log 表建立索引(mac_addr, share_code, create_at)。
@@ -1122,26 +1313,31 @@ MD_CACHE --> REDIS_CLIENT
- *sql.DB 是并发安全的,可在多个 goroutine 中共享使用;Repository 实例不持有状态,亦可并发安全使用。
- Redis 客户端是并发安全的,支持多goroutine同时访问。
- 设备持久化任务使用独立的 goroutine 执行,避免阻塞主线程。
- **阻抗持久化任务**:**新增** 使用独立的 goroutine 执行,避免阻塞主线程,支持环境变量控制开关。
- 分布式锁机制确保缓存击穿防护,防止多个请求同时计算相同数据。
- 分享码缓存使用Lua脚本确保原子性,避免竞态条件。
- **资源清理**
- 使用 defer rows.Close() 保证结果集关闭;在 main 中 defer db.Close() 保证应用退出时关闭连接池。
- Redis 连接在应用退出时自动关闭。
- 设备持久化任务使用 time.Ticker,确保正确停止。
- **阻抗持久化任务****新增** 使用 time.Ticker,确保正确停止,支持优雅关闭。
- 分布式锁在释放时自动清理,避免锁泄露。
- 分享码导入队列使用TTL自动清理,避免无限增长。
- **事务处理**
- 当前实现均为只读查询,未涉及事务;如需写操作,应在 Repository 层封装事务,使用 sql.Tx 并在错误时回滚,成功时提交。
- 设备持久化任务采用幂等设计,避免重复处理造成的数据不一致。
- **阻抗持久化任务**:**新增** 采用幂等设计,通过唯一索引确保数据一致性,避免重复插入。
- 分布式锁机制确保缓存更新的一致性。
- 分享码持久化任务使用分布式锁确保数据一致性。
- **异步处理优化**
- 设备信息上报采用异步持久化,提高响应速度
- **阻抗数据上报**:**新增** 采用异步持久化,先写入Redis Hash,再由定时任务批量同步到数据库,显著提升响应速度
- Redis Hash 存储大量设备数据,内存占用较高,需监控内存使用情况
- 定时任务间隔可根据数据量调整,平衡实时性和系统负载
- 分布式锁重试机制平衡性能和一致性
- Lua原子操作确保数据一致性,避免竞态条件
- 分享码导入队列支持幂等性,避免重复导入
- **阻抗数据去重**:**新增** 通过标准化字段和唯一索引实现智能去重,确保每条记录反映最新阻抗值
**章节来源**
- [internal/database/mysql.go:33-43](file://internal/database/mysql.go#L33-L43)
@@ -1152,6 +1348,7 @@ MD_CACHE --> REDIS_CLIENT
- [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/device.go:19-87](file://internal/repository/device.go#L19-L87)
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
- [internal/repository/ota.go:19-158](file://internal/repository/ota.go#L19-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)
@@ -1167,6 +1364,9 @@ MD_CACHE --> REDIS_CLIENT
- **设备持久化失败**
- 现象:设备信息上报成功但数据库中无数据。
- 排查:检查设备持久化任务日志,确认Redis连接正常;验证user_device和user_active表结构;检查JSON数据格式;确认定时任务正常运行。
- **阻抗持久化失败**:**新增**
- 现象:阻抗上报成功但数据库中无数据。
- 排查:检查阻抗持久化任务是否启用(ENABLE_IMPEDANCE_PERSIST_TASK=true);查看任务日志确认Redis连接正常;验证user_headphone_impedance表结构;检查JSON数据格式;确认定时任务正常运行;验证标准化字段是否正确处理。
- **OTA查询错误**
- 现象:OTA升级查询返回错误或结果不符合预期。
- 排查:检查OTA表数据完整性,确认status=1且有效时间范围内;验证黑名单和定向设备表数据;检查参数传递(model、hw、beta、mac);**注意**OTA查询已移除pawVerCode、pawVerName、pawUrl、pawMd5字段,确保查询逻辑正确。
@@ -1181,7 +1381,7 @@ MD_CACHE --> REDIS_CLIENT
- 排查:查看日志中错误上下文(cache read/write/brand/model/curve/share),定位具体环节;检查 Redis 连接状态;确认缓存数据格式正确。
- **查询错误**
- 现象:Handler 返回内部错误。
- 排查:查看日志中错误上下文(query brand/query model/device/ota/curve/share/scan brand/scan model/iterate brand/iterate model),定位具体环节;检查 SQL 参数绑定与字段映射。
- 排查:查看日志中错误上下文(query brand/query model/device/ota/curve/share),定位具体环节;检查 SQL 参数绑定与字段映射。
- **结果为空**
- 现象:Repository 在查询数据时返回空值。
- 排查:确认传入的查询参数是否正确;检查表中是否存在匹配数据;验证MAC地址格式;确认缓存键是否正确。
@@ -1200,6 +1400,9 @@ MD_CACHE --> REDIS_CLIENT
- **分享码持久化问题**
- 现象:分享码操作日志未写入数据库。
- 排查:检查分享码持久化任务日志;确认分布式锁获取成功;验证数据库连接;检查HasExportLog查询;确认MarkPersisted执行。
- **阻抗数据去重问题**:**新增**
- 现象:相同耳机的阻抗数据重复插入或更新失败。
- 排查:检查标准化字段(headphone_brand_norm, headphone_model_norm)是否正确处理;验证唯一索引约束;确认查询条件包含正确的标准化字段;检查数据格式转换。
**章节来源**
- [internal/config/database.go:17-72](file://internal/config/database.go#L17-L72)
@@ -1207,6 +1410,7 @@ MD_CACHE --> REDIS_CLIENT
- [internal/handler/brand.go:25-48](file://internal/handler/brand.go#L25-L48)
- [internal/handler/model.go:25-49](file://internal/handler/model.go#L25-L49)
- [internal/handler/device.go:27-82](file://internal/handler/device.go#L27-L82)
- [internal/handler/impedance.go:43-107](file://internal/handler/impedance.go#L43-L107)
- [internal/handler/ota.go:23-132](file://internal/handler/ota.go#L23-L132)
- [internal/handler/curve.go:56-127](file://internal/handler/curve.go#L56-L127)
- [internal/handler/curve.go:214-292](file://internal/handler/curve.go#L214-L292)
@@ -1214,14 +1418,15 @@ MD_CACHE --> REDIS_CLIENT
- [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/device.go:19-87](file://internal/repository/device.go#L19-L87)
- [internal/repository/headphone_impedance.go:19-99](file://internal/repository/headphone_impedance.go#L19-L99)
- [internal/repository/ota.go:19-158](file://internal/repository/ota.go#L19-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)
## 结论
Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知查询逻辑,实现了多级缓存策略和智能数据刷新机制。新增的设备持久化仓库、OTA仓库、曲线仓库和分享码仓库进一步完善了数据访问层的功能,支持设备信息上报、固件升级查询、频响曲线计算和分享码管理等核心业务场景。新增的CurveCache和ShareCodeCache提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等,显著提升了系统的响应性能和数据一致性。通过集成 Redis 缓存层和异步持久化任务,系统在高并发场景下表现出色。
Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知查询逻辑,实现了多级缓存策略和智能数据刷新机制。新增的设备持久化仓库、**阻抗持久化仓库**、OTA仓库、曲线仓库和分享码仓库进一步完善了数据访问层的功能,支持设备信息上报、**耳机阻抗数据采集**、固件升级查询、频响曲线计算和分享码管理等核心业务场景。新增的CurveCache和ShareCodeCache提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等,显著提升了系统的响应性能和数据一致性。通过集成 Redis 缓存层和异步持久化任务,系统在高并发场景下表现出色。
**重要更新**:OTA仓库层已成功移除了pawVerCode、pawVerName、pawUrl、pawMd5等冗余字段,简化了查询逻辑和数据处理流程,提升了系统性能和维护性。OTA模型结构已精简,移除了不必要的字段,提高了数据传输效率
**重要更新**:OTA仓库层已成功移除了pawVerCode、pawVerName、pawUrl、pawMd5等冗余字段,简化了查询逻辑和数据处理流程,提升了系统性能和维护性。**新增了完整的耳机阻抗数据管理功能**,包括HeadphoneImpedanceRepository、ImpedanceHandler、ImpedancePersistTask等组件,实现了按MAC地址和标准化耳机标识符的智能去重和数据更新机制
建议后续继续优化缓存策略,增加缓存监控指标,完善缓存失效和更新机制,并持续改进监控与日志体系。
@@ -1235,6 +1440,8 @@ Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知
- 考虑异步处理场景,设计幂等的持久化逻辑。
- 支持分布式锁机制,防止缓存击穿和竞态条件。
- 使用Lua脚本确保数据一致性,避免竞态条件。
- **对于高频写入场景**:**新增** 考虑采用异步持久化模式,先写入Redis再批量同步到数据库。
- **对于需要去重的数据**:**新增** 设计合理的标准化字段和唯一索引,确保数据一致性。
- **实现步骤**
- 定义领域模型(Model)与 Repository 接口/实现。
- 创建对应的缓存结构体,定义缓存键和TTL策略。
@@ -1244,6 +1451,8 @@ Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知
- 实现缓存预热逻辑,确保应用启动时缓存可用。
- 如涉及异步处理,实现独立的任务调度器。
- 配置分享码TTL等配置项,支持动态参数。
- **对于异步持久化场景**:**新增** 实现环境变量控制开关,支持任务的启用和禁用。
- **对于去重逻辑**:**新增** 实现标准化处理函数,确保数据一致性。
- **注意事项**
- 必须在每个查询后 defer rows.Close()。
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息。
@@ -1259,11 +1468,15 @@ Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知
- Lua脚本需确保原子性和错误处理。
- 分享码TTL配置需支持多种时间单位格式。
- 分享码长度必须为5位字符,使用字母数字组合(排除易混淆字符)。
- **对于异步持久化任务**:**新增** 确保任务能够优雅停止,避免数据丢失。
- **对于去重逻辑**:**新增** 确保标准化处理函数正确处理各种边界情况。
- **对于高频写入场景**:**新增** 监控Redis内存使用情况,避免内存溢出。
**章节来源**
- [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/device.go:11-17](file://internal/repository/device.go#L11-L17)
- [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)
@@ -1274,6 +1487,7 @@ Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
- [internal/handler/device.go:15-25](file://internal/handler/device.go#L15-L25)
- [internal/handler/impedance.go:23-28](file://internal/handler/impedance.go#L23-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)