Files
app-api/.qoder/repowiki/zh/content/核心模块/数据访问层.md
T
eafonyang c1547f9a8f feat(impedance): 新增用户耳机阻抗功能模块
- 设计并添加 user_headphone_impedance 表,包含唯一复合索引保证数据完整性
- 实现阻抗上报API接口 /audio/reportImpedance,支持设备上传阻抗数据
- 使用Redis Hash结构缓存阻抗数据,键为 "headphone_impedances"
- 新增定时持久化任务,每5分钟从Redis读取数据批量写入数据库
- 对品牌和型号数据进行trim+lower归一化处理,支持数据去重更新
- 优化查询性能,确保mac_addr与品牌型号归一化组合唯一索引生效
- 更新故障排查指南,新增Redis缓存及唯一索引相关问题检查
- 新增阻抗持久化配置开关,允许按需启用该功能
2026-07-07 19:16:02 +08:00

83 KiB
Raw Blame History

数据访问层

**本文引用的文件** - [internal/repository/brand.go](file://internal/repository/brand.go) - [internal/repository/model.go](file://internal/repository/model.go) - [internal/repository/device.go](file://internal/repository/device.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/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/cache/redis.go](file://internal/cache/redis.go) - [internal/database/mysql.go](file://internal/database/mysql.go) - [internal/config/database.go](file://internal/config/database.go) - [internal/config/redis.go](file://internal/config/redis.go) - [internal/config/config.go](file://internal/config/config.go) - [internal/config/share_code_ttl.go](file://internal/config/share_code_ttl.go) - [internal/config/share_code_ttl_test.go](file://internal/config/share_code_ttl_test.go) - [internal/model/brand.go](file://internal/model/brand.go) - [internal/model/model.go](file://internal/model/model.go) - [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) - [go.mod](file://go.mod)

更新摘要

变更内容

  • 新增耳机阻抗数据数据库访问层(HeadphoneImpedanceRepository),支持按MAC地址和标准化耳机标识符查找、插入和更新记录
  • 实现完整的耳机阻抗数据持久化流程,包括Redis异步处理和定时任务同步
  • 新增ImpedanceHandler处理API请求,提供耳机阻抗上报接口
  • 新增ImpedancePersistTask定时任务,从Redis批量同步阻抗数据到数据库
  • 在main.go中集成新的阻抗持久化任务,支持环境变量控制开关
  • OTA仓库层已移除pawVerCode、pawVerName、pawUrl、pawMd5字段,简化查询逻辑

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 缓存策略详解
  7. 依赖关系分析
  8. 性能考量
  9. 故障排查指南
  10. 结论
  11. 附录:扩展新 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 路由暴露接口。

graph TB
subgraph "应用入口"
MAIN["cmd/server/main.go<br/>启动服务与依赖初始化"]
END
subgraph "路由与控制器"
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/>分享码处理器"]
end
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"]
end
subgraph "缓存层"
BR_CACHE["internal/cache/brand_cache.go<br/>BrandCache"]
MD_CACHE["internal/cache/model_cache.go<br/>ModelCache"]
CURVE_CACHE["internal/cache/curve_cache.go<br/>CurveCache"]
SHARE_CACHE["internal/cache/share_code_cache.go<br/>ShareCodeCache"]
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 "数据库与配置"
DB_SQL["internal/database/mysql.go<br/>sql.DB 连接与池配置"]
CFG_DB["internal/config/database.go<br/>数据库配置加载"]
CFG_REDIS["internal/config/redis.go<br/>Redis 配置加载"]
CFG_SHARE_TTL["internal/config/share_code_ttl.go<br/>分享码TTL配置"]
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/>分享码日志表"]
end
subgraph "领域模型"
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
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
BR_REPO --> BR_CACHE
MD_REPO --> MD_CACHE
CURVE_REPO --> CURVE_CACHE
SHARE_REPO --> SHARE_CACHE
BR_CACHE --> REDIS
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
CFG_APP --> CFG_DB
CFG_APP --> CFG_REDIS
CFG_APP --> CFG_SHARE_TTL
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

图表来源

章节来源

核心组件

  • 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配置,生产环境要求提供密码。
  • Handler 注入Gin 控制器通过 NewXxxHandler 构造函数注入相应的 Repository 实例,再由 Handler 内部执行业务逻辑,形成清晰的依赖注入链路。

章节来源

架构总览

数据访问层采用"仓储模式"Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。新增的缓存层提供多级缓存策略,支持智能数据刷新和降级机制。新增的设备持久化仓库、阻抗持久化仓库、OTA仓库、曲线仓库和分享码仓库进一步完善了数据访问层的功能,支持设备信息上报、耳机阻抗数据采集、固件升级查询、频响曲线计算和分享码管理等核心业务场景。新增的CurveCache和ShareCodeCache提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等。通过集成 Redis 缓存层和异步持久化任务,显著提升了系统的响应性能和数据可靠性。整体流程如下:

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"
participante CurveCache as "CurveCache"
participant ShareRepo as "ShareCodeRepository"
participant ShareCache as "ShareCodeCache"
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"
Redis-->>CurveCache : "缓存数据"
alt 缓存命中
CurveCache-->>CurveHandler : "合并fr数据"
CurveHandler-->>Client : "返回曲线数据"
else 缓存未命中
CurveHandler->>CurveCache : "AcquireLock"
CurveCache->>Redis : "SetNX 分布式锁"
Redis-->>CurveHandler : "获取锁成功"
CurveHandler->>CurveCache : "双重检查缓存"
CurveCache->>Redis : "HGet brand name target"
Redis-->>CurveHandler : "缓存已更新"
CurveHandler->>CurveRepo : "查询Model和Target"
CurveRepo->>DB : "QueryRowContext"
DB-->>CurveRepo : "模型和目标数据"
CurveHandler->>CurveCache : "Set 缓存"
CurveHandler->>CurveCache : "Set __fr 独立缓存"
CurveHandler->>CurveCache : "ReleaseLock"
CurveHandler-->>Client : "返回曲线数据"
end
Router->>ShareHandler : "分享码操作"
ShareHandler->>ShareCache : "Create/Lua原子操作"
ShareCache->>Redis : "Lua脚本原子写入"
Redis-->>ShareHandler : "创建成功"
ShareHandler->>ShareCache : "EnqueueImportLog"
ShareCache->>Redis : "HSet 导入队列"
ShareHandler->>ShareRepo : "InsertLog"
ShareRepo->>DB : "ExecContext"
DB-->>ShareRepo : "插入成功"
ShareHandler-->>Client : "操作结果"

图表来源

详细组件分析

BrandRepository 设计与实现

  • 设计模式:仓储模式,面向领域模型 Brand,封装查询逻辑,现已集成缓存感知功能。
  • 关键特性
    • 支持缓存感知的全量品牌查询,优先从 Redis 获取全量品牌数据
    • 缓存未命中或 Redis 异常时自动降级到数据库查询
    • 支持按品牌名称模糊过滤,无参数时返回全部记录并按名称升序排序
    • 使用 QueryContext 传递请求上下文,便于超时与取消控制
    • 使用 defer rows.Close() 确保资源释放
    • 使用 fmt.Errorf 包裹底层错误,保留调用栈信息
    • 缓存回写机制:成功从数据库获取数据后自动回写全量缓存
  • 缓存策略
    • 缓存键:brand:all
    • TTL30分钟
    • 应用层过滤:在缓存命中时进行模糊过滤,减少数据库压力
  • 错误处理:对缓存读取、数据库查询、扫描、迭代阶段分别进行错误包装,便于定位问题。
classDiagram
class BrandRepository {
-db : "*sql.DB"
-cache : "*cache.BrandCache"
+NewBrandRepository(db, brandCache) BrandRepository
+List(ctx, brandName) []Brand,error
+ListFromDB(ctx, brandName) []Brand,error
-filterBrands(list, brandName) []Brand
}
class BrandCache {
-rdb : "*redis.Client"
+GetAll(ctx) []Brand,error
+SetAll(ctx, list) error
}
class Brand {
+int ID
+string Name
}
BrandRepository --> Brand : "返回领域对象"
BrandRepository --> BrandCache : "使用缓存"
BrandCache --> Brand : "返回缓存数据"

图表来源

章节来源

ModelRepository 设计与实现

  • 设计模式:仓储模式,面向领域模型 Model,封装查询逻辑,现已集成缓存感知功能。
  • 关键特性
    • 支持三种查询条件:按品牌精确匹配、按型号名模糊匹配、全量查询;两者皆为空时返回空切片
    • 缓存感知查询:按品牌查询优先从缓存获取,按型号模糊查询从全量缓存过滤
    • 缓存未命中或 Redis 异常时自动降级到数据库查询
    • 使用自定义 scanModel(rows) 将 sql.NullString 安全转换为 *string,避免空值导致的序列化问题
    • 使用 QueryContext 传递上下文,defer rows.Close() 保证资源释放
    • 对扫描与迭代阶段进行错误包装
    • 缓存回写机制:成功从数据库获取数据后自动回写相应缓存
  • 缓存策略
    • 按品牌缓存:键格式为 model:brand:{brandName}TTL 30分钟
    • 全量缓存:键为 model:allTTL 30分钟
    • 应用层过滤:在全量缓存命中时进行模糊过滤,减少数据库压力
  • 表结构要点model 表包含唯一索引 model_namebrand_name, name),有利于去重与高效检索。
classDiagram
class ModelRepository {
-db : "*sql.DB"
-cache : "*cache.ModelCache"
+NewModelRepository(db, modelCache) ModelRepository
+List(ctx, brandName, modelName) []Model,error
+ListAllFromDB(ctx) []Model,error
+ListByBrandFromDB(ctx, brandName) []Model,error
+ListByModelFromDB(ctx, modelName) []Model,error
-filterModels(list, modelName) []Model
}
class ModelCache {
-rdb : "*redis.Client"
+GetByBrand(ctx, brandName) []Model,error
+SetByBrand(ctx, brandName, list) error
+GetAll(ctx) []Model,error
+SetAll(ctx, list) error
}
class Model {
+int ID
+string BrandName
+string Name
+*string Form
+*string Rig
+*string Source
+*string EqKey
+time CreateAt
}
ModelRepository --> Model : "返回领域对象"
ModelRepository --> ModelCache : "使用缓存"
ModelCache --> Model : "返回缓存数据"

图表来源

章节来源

DeviceRepository 设计与实现

  • 设计模式:仓储模式,面向领域模型 UserDevice 和 UserActive,封装设备信息管理逻辑。
  • 关键特性
    • 设备查询:按MAC地址精确查询用户设备信息,支持版本号的可空字段处理
    • 设备管理:支持插入新设备、更新设备版本信息
    • 用户活跃:按MAC地址和日期查询用户活跃信息,支持IP地址更新
    • 可空字段处理:使用 sql.NullString 和 sql.NullTime 安全转换数据库可空字段
    • 错误处理:针对不同查询场景提供详细的错误包装,便于问题定位
    • 资源管理:使用 QueryRowContext 和 ExecContext,确保数据库连接正确释放
  • 表结构要点
    • user_device 表:主键ID,唯一MAC地址索引,支持设备版本跟踪
    • user_active 表:复合主键(ID, active_date),唯一MAC+日期索引,支持分区存储
  • 应用场景:设备信息上报、用户活跃统计、设备版本管理等业务场景。
classDiagram
class DeviceRepository {
-db : "*sql.DB"
+NewDeviceRepository(db) DeviceRepository
+FindDeviceByMac(ctx, macAddr) *UserDevice,error
+InsertDevice(ctx, d) error
+UpdateDeviceVer(ctx, id, ver) error
+FindActiveByMacAndDate(ctx, macAddr, activeDate) *UserActive,error
+InsertActive(ctx, a) error
+UpdateActiveIp(ctx, id, ipAddr) error
}
class UserDevice {
+int ID
+string MacAddr
+string Model
+time AddTime
+*string Ver
}
class UserActive {
+int ID
+string MacAddr
+string Model
+string ActiveDate
+string IpAddr
+time CreateAt
}
DeviceRepository --> UserDevice : "设备信息"
DeviceRepository --> UserActive : "活跃信息"

图表来源

章节来源

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
  • 应用场景:耳机阻抗数据采集、用户设备配置管理、音频参数个性化等。
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 : "阻抗数据"

图表来源

章节来源

OTARepository 设计与实现

  • 设计模式:仓储模式,面向领域模型 OTA、BlackList 和 OTATargetDevice,封装固件升级查询逻辑。
  • 关键特性
    • 最新OTA查询:按型号、硬件版本、灰度标识查询最新一条可用OTA记录
    • 黑名单检查:检查设备是否在指定OTA的黑名单中
    • 非黑名单查询:查询不在黑名单中的最新OTA记录
    • 非定向查询:查询非定向(target=0)的最新OTA记录
    • 定向设备检查:检查设备是否在指定OTA的定向设备列表中
    • 可空字段处理:使用 sql.NullString 和 sql.NullTime 安全处理数据库可空字段
    • 复杂查询:支持子查询、条件筛选、时间范围检查等复杂业务逻辑
    • 已精简:移除了pawVerCode、pawVerName、pawUrl、pawMd5字段,简化了查询逻辑和数据处理流程
  • 业务逻辑
    • 支持灰度发布(beta=1)和正式发布(beta=0)两种模式
    • 支持强制升级(force=1)和普通升级(force=0
    • 支持定向升级和非定向升级两种分发策略
    • 支持时间窗口限制(startTime/endTime
  • 应用场景:固件升级推送、灰度发布控制、定向设备管理等。
classDiagram
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 OTA {
+int ID
+int VerCode
+string VerName
+string URL
+string MD5
+BoolInt Force
+*string Desc
+*string Model
+int HW
+BoolInt Target
+BoolInt Beta
+*time StartTime
+*time EndTime
+int Status
}
class BlackList {
+int ID
+int OTAID
+string Mac
+time CreateAt
}
class OTATargetDevice {
+int ID
+int OTAID
+string MacAddr
+int Type
+time CreateAt
}
OTARepository --> OTA : "OTA记录"
OTARepository --> BlackList : "黑名单"
OTARepository --> OTATargetDevice : "定向设备"

图表来源

章节来源

CurveRepository 设计与实现

  • 设计模式:仓储模式,面向领域模型 Model 和 Target,封装频响曲线数据查询逻辑。
  • 关键特性
    • Model查询:按品牌+型号精确查询设备模型信息,支持可空字段处理
    • Target查询:按标签查询目标曲线信息,支持CSV读取和BassBoost配置
    • 可空字段处理:使用 sql.NullString 和 sql.NullTime 安全转换数据库可空字段
    • 错误处理:针对不同查询场景提供详细的错误包装,便于问题定位
    • 资源管理:使用 QueryRowContext 确保数据库连接正确释放
  • 表结构要点
    • model 表:包含品牌、型号、源信息、EQ键等字段,支持不同源的差异化处理
    • target 表:包含标签、CSV文件路径、BassBoost配置等字段
  • 应用场景:频响曲线计算、参数化EQ生成、设备匹配等音频处理场景。
classDiagram
class CurveRepository {
-db : "*sql.DB"
+NewCurveRepository(db) CurveRepository
+GetModelByBrandAndName(ctx, brandName, name) *Model,error
+GetTargetByLabel(ctx, label) *Target,error
}
class Model {
+int ID
+string BrandName
+string Name
+*string Form
+*string Rig
+*string Source
+*string EqKey
+time CreateAt
}
class Target {
+int ID
+string Label
+bool ReadCSV
+*string File
+*string BassBoost
+time AddTime
}
CurveRepository --> Model : "模型信息"
CurveRepository --> Target : "目标曲线"

图表来源

章节来源

ShareCodeRepository 设计与实现

  • 设计模式:仓储模式,面向领域模型 ShareCodeLog,封装分享码操作日志管理逻辑。
  • 关键特性
    • 日志插入:支持插入分享码导出/导入操作日志,包含MAC地址、设备型号、EQ数据等信息
    • 导出日志查询:支持查询指定分享码的导出操作记录
    • 可空字段处理:使用 *time.Time 处理过期时间等可空字段
    • 错误处理:针对不同查询场景提供详细的错误包装,便于问题定位
    • 资源管理:使用 ExecContext 和 QueryRowContext 确保数据库连接正确释放
  • 表结构要点
    • share_code_log 表:包含主键ID、MAC地址、分享码、操作类型、设备型号、EQ数据、过期时间等字段
    • 支持按MAC地址、分享码、创建时间等字段建立索引,优化查询性能
  • 应用场景:分享码操作审计、数据溯源、合规性检查等业务场景。
classDiagram
class ShareCodeRepository {
-db : "*sql.DB"
+NewShareCodeRepository(db) ShareCodeRepository
+InsertLog(ctx, log) error
+HasExportLog(ctx, shareCode) bool,error
+ParseShareExpireAt(expireAt) *time.Time
}
class ShareCodeLog {
+int ID
+string MacAddr
+string ShareCode
+string Action
+string Model
+string IpAddr
+[]byte EqData
+*time ExpireAt
+time CreateAt
}
ShareCodeRepository --> ShareCodeLog : "日志记录"

图表来源

章节来源

CurveCache 设计与实现

  • 设计模式:缓存模式,基于Redis Hash的曲线数据缓存,支持fr数据分离存储和分布式锁机制。
  • 关键特性
    • Redis Hash存储:使用品牌+型号作为键,target作为字段存储曲线数据
    • fr数据分离:__fr字段独立存储频响数据,避免每个target重复存储
    • 缓存合并:GetWithFR方法自动合并target数据和__fr数据,兼容新旧缓存格式
    • 分布式锁:AcquireLock/ReleaseLock提供并发控制,防止缓存击穿
    • 双重检查:GetWithLock方法实现双重检查机制,提高缓存命中率
    • 锁重试:支持最大重试次数和延迟机制,平衡性能和一致性
  • 缓存键设计
    • 主键:brand name(品牌+型号)
    • 字段:target(目标曲线)或__fr(独立的频响数据)
    • 锁键:brand name:target:lock(分布式锁)
  • 应用场景:频响曲线计算加速、参数化EQ生成优化、高并发场景下的缓存保护。
classDiagram
class CurveCache {
-rdb : "*redis.Client"
+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
+GetWithLock(ctx, brand, name, target) (string,bool,error)
}
CurveCache --> redis.Client : "使用Redis"

图表来源

章节来源

ShareCodeCache 设计与实现

  • 设计模式:缓存模式,基于Redis的高级分享码缓存,支持Lua原子操作、MAC地址索引、导入导出队列等。
  • 关键特性
    • Lua原子操作:Create方法使用Lua脚本实现原子性的哈希写入、集合添加、ZSET索引更新
    • MAC地址索引:share:mac:{mac} ZSET存储未过期的分享码,支持按MAC查询
    • 导入导出队列:share:pending SET存储待持久化的分享码,share:import:pending Hash存储导入日志
    • 分布式锁:share:flush:lock:{code}和share:import:flush🔒{field}提供持久化过程的锁控制
    • 幂等性:同MAC+code组合在导入队列中只保留一条记录
    • TTL管理:支持动态TTL配置,最大365天,支持多种时间单位格式
  • 缓存键设计
    • 主数据:share:{code} HashTTL = SHARE_CODE_TTL_MIN
    • MAC索引:share:mac:{mac} ZSETTTL = SHARE_CODE_TTL_MIN
    • 待持久化:share:pending SET(无TTL,过期后自动清理)
    • 导入队列:share:import:pending HashTTL 12小时)
    • 分布式锁:share:flush:lock:{code}share:import:flush:lock:{field}
  • 应用场景:分享码创建、查询、删除、导入导出等完整的分享码生命周期管理。
classDiagram
class ShareCodeCache {
-rdb : "*redis.Client"
-codeTTL : time.Duration
+ShareCodeTTL() time.Duration
+Create(ctx, macAddr, ipAddr, model, eqData) *ShareCodeData,error
+Get(ctx, shareCode) *ShareCodeData,error
+ListByMac(ctx, macAddr) []*ShareCodeData,error
+ListPending(ctx) []string,error
+EnqueueImportLog(ctx, macAddr, shareCode, model, ipAddr, eqData, expireAt) error
+ListPendingImports(ctx) map[string]string,error
+RemovePendingImport(ctx, field) error
+MarkPersisted(ctx, shareCode) error
+AcquireFlushLock(ctx, shareCode) bool,error
+ReleaseFlushLock(ctx, shareCode) error
+AcquireImportFlushLock(ctx, field) bool,error
+ReleaseImportFlushLock(ctx, field) error
+Delete(ctx, macAddr, shareCode) ShareDeleteResult,error
}
ShareCodeCache --> redis.Client : "使用Redis"

图表来源

章节来源

设备持久化任务设计与实现

  • 设计模式:定时任务模式,基于时间轮询的异步数据同步机制。
  • 关键特性
    • 定时执行:使用 time.Ticker 按固定间隔启动持久化任务
    • 批量处理:从Redis Hash一次性读取所有待持久化的设备数据
    • 数据修复:自动修复历史数据中的无效日期格式
    • 双表写入:同时处理 user_device 和 user_active 两张表
    • 成功确认:批量删除已成功持久化的数据,避免重复处理
    • 错误隔离:单条数据处理失败不影响其他数据的处理
  • 处理流程
    1. 从Redis读取 devices Hash 中的所有设备数据
    2. 解析JSON格式的设备信息
    3. 修复无效的日期格式
    4. 分别处理设备信息和活跃信息
    5. 批量删除已成功处理的数据
  • 应用场景:高并发设备信息上报、数据可靠性保障、异步数据同步等。
flowchart TD
A[定时器触发] --> B[HGetAll devices]
B --> C{是否有数据?}
C --> |否| D[结束]
C --> |是| E[遍历设备数据]
E --> F[解析JSON数据]
F --> G[修复日期格式]
G --> H[处理设备信息]
H --> I[处理活跃信息]
I --> J{处理成功?}
J --> |是| K[记录成功MAC]
J --> |否| L[跳过该设备]
K --> M[批量删除成功数据]
L --> N[继续下一个]
M --> O[记录处理结果]
N --> E
O --> P[结束]

图表来源

章节来源

阻抗持久化任务设计与实现

  • 设计模式:定时任务模式,基于时间轮询的异步数据同步机制。
  • 关键特性
    • 定时执行:使用 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. 批量删除已成功处理的数据
  • 应用场景:高并发阻抗数据采集、数据可靠性保障、异步数据同步等。
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[结束]

图表来源

章节来源

分享码持久化任务设计与实现

  • 设计模式:定时任务模式,基于时间轮询的异步数据同步机制。
  • 关键特性
    • 定时执行:使用 time.Ticker 按固定间隔启动持久化任务
    • 批量处理:从Redis的share:pending集合读取待持久化的分享码数据
    • 原子写入:使用Lua脚本实现原子性的数据写入和状态更新
    • 幂等性:通过ShareCodeData结构体的Persisted字段确保重复处理的安全性
    • 错误处理:单个分享码持久化失败不影响其他数据的处理
    • TTL维护:在HSET操作前后正确维护TTL,避免数据丢失
  • 处理流程
    1. 从Redis读取share:pending集合中的所有分享码
    2. 逐个查询对应的Hash数据和导入队列日志
    3. 插入MySQL share_code_log表
    4. 标记已持久化并移除待持久化标记
    5. 清理导入队列中的对应记录
  • 应用场景:分享码操作审计、数据可靠性保障、异步数据同步等。
flowchart TD
A[定时器触发] --> B[SMembers share:pending]
B --> C{是否有待持久化数据?}
C --> |否| D[结束]
C --> |是| E[遍历分享码]
E --> F[HGetAll share:{code}]
F --> G[HGetAll share:import:pending]
G --> H[Insert share_code_log]
H --> I[HSet mark persisted]
I --> J[SRem from pending]
K[继续下一个]
E --> K
J --> K
D --> L[结束]

图表来源

章节来源

Handler 与 Repository 的协作

  • Handler 通过 NewXxxHandler 注入相应的 Repository 实例,内部执行具体的业务逻辑。
  • DeviceHandler 处理设备信息上报,将数据写入Redis Hash,等待异步持久化任务处理。
  • ImpedanceHandler新增 处理耳机阻抗上报,接收参数并进行标准化处理,将数据写入Redis Hash,等待异步持久化任务处理。
  • OTAHandler 处理OTA升级查询,调用OTARepository执行复杂的业务逻辑,包括黑名单检查、定向设备筛选等。
  • CurveHandler 处理曲线查询,调用CurveRepository和CurveCache执行数据查询和缓存操作,支持fr数据分离和分布式锁机制。
  • ShareCodeHandler 处理分享码操作,调用ShareCodeCache执行Lua原子操作和队列管理,支持导入导出流程。
  • BrandHandler 和 ModelHandler 继续使用原有的缓存感知查询逻辑。
  • 错误处理:各 Handler 对 Repository 返回的错误进行统一处理和日志记录。
  • 缓存感知:CurveHandler 和 ShareCodeHandler 需要处理复杂的缓存逻辑,包括分布式锁、缓存合并等。
sequenceDiagram
participant C as "客户端"
participant IH as "ImpedanceHandler"
participant IR as "HeadphoneImpedanceRepository"
participant IT as "ImpedancePersistTask"
participant CH as "CurveHandler"
participant CR as "CurveRepository"
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 : "缓存数据"
alt 缓存命中
CC-->>CH : "合并fr数据"
CH-->>C : "返回曲线数据"
else 缓存未命中
CH->>CC : "AcquireLock"
CC->>Redis : "SetNX 分布式锁"
Redis-->>CH : "获取锁成功"
CH->>CC : "双重检查缓存"
CC->>Redis : "HGet brand name target"
Redis-->>CH : "缓存已更新"
CH->>CR : "查询Model和Target"
CR->>DB : "QueryRowContext"
DB-->>CR : "模型和目标数据"
CH->>CC : "Set 缓存"
CH->>CC : "Set __fr 独立缓存"
CH->>CC : "ReleaseLock"
CH-->>C : "返回曲线数据"
end

图表来源

章节来源

缓存策略详解

多级缓存架构

数据访问层采用了多级缓存策略,分为以下层级:

  1. 应用层缓存:Redis 缓存,提供高性能的数据访问
  2. 内存缓存:应用进程内的缓存,提供最快的访问速度
  3. 数据库缓存:MySQL 查询缓存,利用数据库自身的缓存机制

缓存键设计

  • 品牌全量缓存brand:all - 存储所有品牌信息
  • 型号按品牌缓存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 - 存储导入日志,支持幂等性

缓存策略实现

  • 缓存优先策略:优先从缓存获取数据,缓存未命中时才查询数据库
  • 智能降级策略:Redis 异常时自动降级到数据库查询,保证系统可用性
  • 自动回写策略:成功从数据库获取数据后自动回写到缓存
  • TTL 策略:所有缓存设置 30 分钟过期时间,平衡性能和数据新鲜度
  • 设备数据特殊处理:设备活跃信息采用分区表存储,缓存策略需考虑日期维度
  • 耳机阻抗数据特殊处理新增 阻抗数据采用异步持久化策略,先写入Redis Hash,再由定时任务批量同步到数据库
  • 曲线数据优化:fr数据独立存储在__fr字段,避免每个target重复存储
  • 分享码TTL配置:支持多种时间单位格式,最大365天,动态配置
  • 分布式锁机制:防止缓存击穿和竞态条件,确保数据一致性

预热机制

应用启动时会自动预热缓存,从数据库加载全量数据到 Redis:

flowchart TD
A[应用启动] --> B[创建缓存实例]
B --> C[创建仓库实例]
C --> D[查询品牌全量数据]
D --> E[写入 brand:all 缓存]
E --> F[查询型号全量数据]
F --> G[写入 model:all 缓存]
G --> H[设备数据无需预热]
H --> I[阻抗数据无需预热]
I --> J[OTA数据无需预热]
J --> K[曲线缓存无需预热]
K --> L[分享码缓存无需预热]
L --> M[缓存预热完成]

图表来源

章节来源

依赖关系分析

  • 外部依赖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 依赖 ShareCodeCacheImpedanceHandler 依赖 Redis 客户端;ImpedancePersistTask 依赖 HeadphoneImpedanceRepository 和 Redis 客户端
graph LR
GO_MOD["go.mod 依赖声明"] --> MYSQL["github.com/go-sql-driver/mysql"]
GO_MOD --> GIN["github.com/gin-gonic/gin"]
GO_MOD --> ZAP["go.uber.org/zap"]
GO_MOD --> REDIS["github.com/redis/go-redis/v9"]
GO_MOD --> MEILI["github.com/meilisearch/meilisearch-go"]
GO_MOD --> AWS["github.com/aws/aws-sdk-go-v2"]
MAIN["cmd/server/main.go"] --> DB_OPEN["internal/database/mysql.go::Open"]
MAIN --> CFG_LOAD["internal/config/config.go::Load"]
MAIN --> WARM_UP["warmUpCache 预热缓存"]
CFG_LOAD --> CFG_DB["internal/config/database.go::loadDatabase"]
CFG_LOAD --> CFG_REDIS["internal/config/redis.go::loadRedis"]
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"]
BR_REPO --> BR_CACHE["internal/cache/brand_cache.go"]
MD_REPO --> MD_CACHE["internal/cache/model_cache.go"]
CURVE_REPO --> CURVE_CACHE["internal/cache/curve_cache.go"]
SHARE_REPO --> SHARE_CACHE["internal/cache/share_code_cache.go"]
BR_CACHE --> REDIS_CLIENT["internal/cache/redis.go::NewClient"]
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
MD_CACHE --> REDIS_CLIENT

图表来源

章节来源

性能考量

  • 连接池配置
    • 最大打开连接数:25
    • 最大空闲连接数:5
    • 连接最大生命周期:5 分钟
    • 启动时使用 PingContext 进行健康检查,失败则关闭连接并返回错误
  • 缓存性能优化
    • 缓存命中率:通过多级缓存策略最大化缓存命中率
    • TTL 设置:30分钟的TTL平衡了性能和数据新鲜度
    • 预热机制:启动时预热缓存,避免冷启动时的缓存穿透
    • 降级机制:Redis异常时自动降级到数据库,保证系统可用性
    • 曲线缓存优化:fr数据分离存储,减少缓存体积和网络传输
    • 分布式锁:限制同一时间只有一个请求在计算曲线数据
    • 分享码缓存优化: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)。
    • 应用层过滤:在缓存命中时进行过滤,减少数据库压力。
  • 并发安全
    • *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原子操作确保数据一致性,避免竞态条件
    • 分享码导入队列支持幂等性,避免重复导入
    • 阻抗数据去重新增 通过标准化字段和唯一索引实现智能去重,确保每条记录反映最新阻抗值

章节来源

故障排查指南

  • 连接失败
    • 现象:启动时 Ping 失败或无法连接数据库。
    • 排查:检查 DATABASE_HOST/DATABASE_PORT/DATABASE_NAME/DATABASE_USER/DATABASE_PASSWORD 等环境变量;确认网络连通性;核对生产环境必须提供 DATABASE_PASSWORD。
  • Redis 连接失败
    • 现象:应用启动时无法连接 Redis 或缓存预热失败。
    • 排查:检查 REDIS_HOST/REDIS_PORT/REDIS_PASSWORD/REDIS_DATABASE 等环境变量;确认 Redis 服务正常运行;验证认证信息正确。
  • 设备持久化失败
    • 现象:设备信息上报成功但数据库中无数据。
    • 排查:检查设备持久化任务日志,确认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字段,确保查询逻辑正确。
  • 曲线查询错误
    • 现象:曲线查询返回错误或结果不符合预期。
    • 排查:检查CurveRepository查询的Model和Target数据;确认EQ API可访问;验证Redis缓存键格式;检查分布式锁状态;确认fr数据分离存储正确。
  • 分享码操作错误
    • 现象:分享码创建、查询、删除等操作失败。
    • 排查:检查ShareCodeCache的Lua脚本执行;确认Redis键空间正确;验证TTL配置;检查分布式锁获取;确认导入导出队列状态;验证分享码长度为5位。
  • 缓存查询错误
    • 现象:Handler 返回内部错误或缓存数据格式异常。
    • 排查:查看日志中错误上下文(cache read/write/brand/model/curve/share),定位具体环节;检查 Redis 连接状态;确认缓存数据格式正确。
  • 查询错误
    • 现象:Handler 返回内部错误。
    • 排查:查看日志中错误上下文(query brand/query model/device/ota/curve/share),定位具体环节;检查 SQL 参数绑定与字段映射。
  • 结果为空
    • 现象:Repository 在查询数据时返回空值。
    • 排查:确认传入的查询参数是否正确;检查表中是否存在匹配数据;验证MAC地址格式;确认缓存键是否正确。
  • 资源泄漏
    • 现象:长时间运行后连接数异常。
    • 排查:确认是否遗漏 rows.Close();检查连接池配置是否合理;观察连接生命周期与空闲连接上限。
  • 缓存未命中
    • 现象:频繁出现缓存未命中,数据库压力过大。
    • 排查:检查缓存键是否正确;确认 TTL 设置是否合理;验证缓存预热是否成功;检查 Redis 内存使用情况。
  • 性能问题
    • 现象:曲线查询响应慢或设备持久化延迟。
    • 排查:检查相关表的索引是否完整;监控Redis内存使用;评估定时任务执行频率;分析数据库查询计划;检查分布式锁等待时间。
  • 分布式锁问题
    • 现象:缓存更新时出现锁竞争或死锁。
    • 排查:检查锁TTL设置;确认锁释放逻辑;验证重试机制;检查锁键格式;确认Redis集群状态。
  • 分享码持久化问题
    • 现象:分享码操作日志未写入数据库。
    • 排查:检查分享码持久化任务日志;确认分布式锁获取成功;验证数据库连接;检查HasExportLog查询;确认MarkPersisted执行。
  • 阻抗数据去重问题新增
    • 现象:相同耳机的阻抗数据重复插入或更新失败。
    • 排查:检查标准化字段(headphone_brand_norm, headphone_model_norm)是否正确处理;验证唯一索引约束;确认查询条件包含正确的标准化字段;检查数据格式转换。

章节来源

结论

Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知查询逻辑,实现了多级缓存策略和智能数据刷新机制。新增的设备持久化仓库、阻抗持久化仓库、OTA仓库、曲线仓库和分享码仓库进一步完善了数据访问层的功能,支持设备信息上报、耳机阻抗数据采集、固件升级查询、频响曲线计算和分享码管理等核心业务场景。新增的CurveCache和ShareCodeCache提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等,显著提升了系统的响应性能和数据一致性。通过集成 Redis 缓存层和异步持久化任务,系统在高并发场景下表现出色。

重要更新:OTA仓库层已成功移除了pawVerCode、pawVerName、pawUrl、pawMd5等冗余字段,简化了查询逻辑和数据处理流程,提升了系统性能和维护性。新增了完整的耳机阻抗数据管理功能,包括HeadphoneImpedanceRepository、ImpedanceHandler、ImpedancePersistTask等组件,实现了按MAC地址和标准化耳机标识符的智能去重和数据更新机制。

建议后续继续优化缓存策略,增加缓存监控指标,完善缓存失效和更新机制,并持续改进监控与日志体系。

附录:扩展新 Repository 指南

  • 设计原则
    • 保持 Repository 无状态,仅依赖 *sql.DB 和缓存实例。
    • 查询方法接收 context.Context,便于超时与取消控制。
    • 对外返回领域模型(Model),避免直接暴露数据库结构。
    • 对可空字段使用 sql.NullString、sql.NullInt64、sql.NullTime 等安全转换。
    • 集成缓存感知查询逻辑,提供智能降级和自动回写功能。
    • 考虑异步处理场景,设计幂等的持久化逻辑。
    • 支持分布式锁机制,防止缓存击穿和竞态条件。
    • 使用Lua脚本确保数据一致性,避免竞态条件。
    • 对于高频写入场景新增 考虑采用异步持久化模式,先写入Redis再批量同步到数据库。
    • 对于需要去重的数据新增 设计合理的标准化字段和唯一索引,确保数据一致性。
  • 实现步骤
    • 定义领域模型(Model)与 Repository 接口/实现。
    • 创建对应的缓存结构体,定义缓存键和TTL策略。
    • 在 Handler 中注入 *sql.DB 和缓存实例,构造 Repository 实例。
    • 在路由中注册对应处理器。
    • 在 main 中确保 *sql.DB 和缓存实例注入到 Handler。
    • 实现缓存预热逻辑,确保应用启动时缓存可用。
    • 如涉及异步处理,实现独立的任务调度器。
    • 配置分享码TTL等配置项,支持动态参数。
    • 对于异步持久化场景新增 实现环境变量控制开关,支持任务的启用和禁用。
    • 对于去重逻辑新增 实现标准化处理函数,确保数据一致性。
  • 注意事项
    • 必须在每个查询后 defer rows.Close()。
    • 使用 fmt.Errorf 包裹底层错误,保留调用栈信息。
    • 生产环境务必提供数据库密码等敏感配置。
    • 如需写操作,封装事务并在错误时回滚,成功时提交。
    • 对高频查询建立合适的索引,必要时引入分页参数。
    • 实现缓存降级机制,确保Redis异常时系统仍可正常工作。
    • 设计合理的缓存键和TTL策略,平衡性能和数据新鲜度。
    • 实现缓存预热和自动回写机制,提升用户体验。
    • 异步任务需考虑幂等性,避免重复处理造成数据不一致。
    • 监控异步任务的执行状态和错误日志。
    • 分布式锁需设置合理的TTL和重试机制。
    • Lua脚本需确保原子性和错误处理。
    • 分享码TTL配置需支持多种时间单位格式。
    • 分享码长度必须为5位字符,使用字母数字组合(排除易混淆字符)。
    • 对于异步持久化任务新增 确保任务能够优雅停止,避免数据丢失。
    • 对于去重逻辑新增 确保标准化处理函数正确处理各种边界情况。
    • 对于高频写入场景新增 监控Redis内存使用情况,避免内存溢出。

章节来源