68 KiB
数据访问层
**本文引用的文件** - [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/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/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/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/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/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)更新摘要
变更内容
- 新增分享代码仓储层(ShareCodeRepository),支持分享码日志管理与数据库操作
- 增强曲线仓储层(CurveRepository),支持频响曲线数据查询与缓存合并
- 新增分享码缓存层(ShareCodeCache),提供高级Redis缓存功能与Lua原子操作
- 新增分享码持久化任务(ShareCodePersistTask),实现异步数据同步
- 新增分享码处理器(ShareCodeHandler),支持完整的分享码生命周期管理
- 扩展Handler层以支持分享码创建、查询、导入、导出和删除操作
- 增强缓存策略,支持fr数据分离存储和分布式锁机制
目录
简介
本文件聚焦于 Luxsin 应用 API 的数据访问层(Repository 层),系统性阐述 BrandRepository、ModelRepository、DeviceRepository、OTARepository、CurveRepository、ShareCodeRepository 的设计模式、实现原理与职责边界;解释其在整体架构中的位置与交互方式;深入分析数据库连接管理、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/>设备处理器"]
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"]
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/>设备持久化任务"]
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/>用户活跃表"]
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_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 --> OTA_H
ROUTER --> CURVE_H
ROUTER --> SHARE_H
BRAND_H --> BR_REPO
MODEL_H --> MD_REPO
DEVICE_H --> DEV_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
OTA_REPO --> DB_SQL
CURVE_REPO --> DB_SQL
SHARE_REPO --> DB_SQL
PERSIST_TASK --> DEV_REPO
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
OTA_REPO --> M_OTA
SHARE_REPO --> M_SHARE
MODEL_TBL --> DB_SQL
USER_DEVICE_TBL --> DB_SQL
USER_ACTIVE_TBL --> DB_SQL
OTA_TBL --> DB_SQL
OTA_TARGET_TBL --> DB_SQL
SHARE_LOG_TBL --> DB_SQL
图表来源
- cmd/server/main.go:103-130
- internal/router/router.go:22-25
- internal/handler/brand.go:19-24
- internal/handler/model.go:19-24
- internal/handler/device.go:15-25
- internal/handler/ota.go:14-21
- internal/handler/curve.go:27-39
- internal/handler/share_code.go:15-23
- internal/repository/brand.go:14-21
- internal/repository/model.go:14-21
- internal/repository/device.go:11-17
- internal/repository/ota.go:11-17
- internal/repository/curve.go:11-17
- internal/repository/share_code.go:12-18
- internal/cache/brand_cache.go:18-24
- internal/cache/model_cache.go:19-25
- internal/cache/curve_cache.go:18-25
- internal/cache/share_code_cache.go:127-135
- internal/cache/redis.go:10-16
- internal/database/mysql.go:14-46
- internal/config/database.go:17-40
- internal/config/redis.go:9-57
- internal/config/config.go:18-52
- internal/config/share_code_ttl.go:12-59
- internal/task/device_persist.go:14-22
- internal/task/share_code_persist.go:14-22
- sql/model.sql:24-35
- sql/user_device.sql:23-32
- sql/user_active.sql:23-32
- sql/ota.sql:23-44
- sql/ota_target_device.sql:23-31
- sql/share_code_log.sql:8-23
章节来源
- cmd/server/main.go:103-130
- internal/router/router.go:14-56
- internal/handler/brand.go:19-24
- internal/handler/model.go:19-24
- internal/handler/device.go:15-25
- internal/handler/ota.go:14-21
- internal/handler/curve.go:27-39
- internal/handler/share_code.go:15-23
- internal/repository/brand.go:14-21
- internal/repository/model.go:14-21
- internal/repository/device.go:11-17
- internal/repository/ota.go:11-17
- internal/repository/curve.go:11-17
- internal/repository/share_code.go:12-18
- internal/cache/brand_cache.go:18-24
- internal/cache/model_cache.go:19-25
- internal/cache/curve_cache.go:18-25
- internal/cache/share_code_cache.go:127-135
- internal/cache/redis.go:10-16
- internal/database/mysql.go:14-46
- internal/config/database.go:17-40
- internal/config/redis.go:9-57
- internal/config/config.go:18-52
- internal/config/share_code_ttl.go:12-59
- internal/task/device_persist.go:14-22
- internal/task/share_code_persist.go:14-22
- sql/model.sql:24-35
- sql/user_device.sql:23-32
- sql/user_active.sql:23-32
- sql/ota.sql:23-44
- sql/ota_target_device.sql:23-31
- sql/share_code_log.sql:8-23
核心组件
- BrandRepository:负责品牌列表查询,支持缓存感知的全量查询和按名称模糊过滤,返回 Brand 领域对象切片。
- ModelRepository:负责型号列表查询,支持缓存感知的按品牌精确匹配、按型号名模糊匹配和全量查询,返回 Model 领域对象切片,并对可空字段进行 NullString 到指针字符串的安全转换。
- DeviceRepository:负责设备信息管理,支持按MAC地址查询设备、插入新设备、更新设备版本信息,以及用户活跃信息的查询和更新。
- OTARepository:负责固件升级信息查询,支持按型号、硬件版本、灰度标识查询最新OTA记录,黑名单检查、定向设备筛选等功能。
- CurveRepository:负责频响曲线数据查询,支持按品牌+型号查询Model信息和按标签查询Target信息,为曲线缓存提供数据支撑。
- ShareCodeRepository:负责分享码日志管理,支持插入分享码操作日志、查询导出日志等数据库操作,提供完整的分享码数据持久化能力。
- CurveCache:新增的曲线缓存层,支持fr数据分离存储、分布式锁机制、缓存合并逻辑,优化曲线数据访问性能。
- ShareCodeCache:新增的分享码缓存层,支持Lua原子操作、MAC地址索引、导入导出队列、分布式锁等高级缓存功能,提供完整的分享码生命周期管理。
- 设备持久化任务:DevicePersistTask 从Redis批量读取设备信息,异步写入MySQL user_device 和 user_active 表,支持数据可靠性保障。
- 分享码持久化任务:ShareCodePersistTask 从Redis批量读取分享码数据,异步写入MySQL share_code_log 表,支持数据可靠性保障。
- 数据库连接:通过 sql.DB 统一管理连接池,设置最大打开连接数、空闲连接数与连接生命周期,并在启动时进行 Ping 校验。
- 配置加载:从环境变量或默认值加载数据库、Redis和分享码TTL配置,生产环境要求提供密码。
- Handler 注入:Gin 控制器通过 NewXxxHandler 构造函数注入相应的 Repository 实例,再由 Handler 内部执行业务逻辑,形成清晰的依赖注入链路。
章节来源
- internal/repository/brand.go:23-52
- internal/repository/model.go:23-79
- internal/repository/device.go:19-87
- internal/repository/ota.go:19-158
- internal/repository/curve.go:19-65
- internal/repository/share_code.go:20-58
- internal/cache/curve_cache.go:18-148
- internal/cache/share_code_cache.go:127-353
- internal/task/device_persist.go:14-167
- internal/task/share_code_persist.go:14-22
- internal/database/mysql.go:14-46
- internal/config/database.go:17-72
- internal/config/redis.go:16-57
- internal/config/share_code_ttl.go:12-59
- internal/config/config.go:18-52
- internal/handler/brand.go:19-24
- internal/handler/model.go:19-24
- internal/handler/device.go:15-25
- internal/handler/ota.go:14-21
- internal/handler/curve.go:27-39
- internal/handler/share_code.go:15-23
架构总览
数据访问层采用"仓储模式"(Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。新增的缓存层提供多级缓存策略,支持智能数据刷新和降级机制。新增的设备持久化仓库和OTA仓库进一步完善了数据访问层的功能,支持设备信息上报、固件升级查询等核心业务场景。新增的曲线缓存和分享码缓存层提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等。通过集成 Redis 缓存层和异步持久化任务,显著提升了系统的响应性能和数据可靠性。整体流程如下:
sequenceDiagram
participant Client as "客户端"
participant Router as "Gin 路由"
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->>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 : "操作结果"
图表来源
- internal/handler/curve.go:214-292
- internal/handler/share_code.go:40-125
- internal/repository/curve.go:19-65
- internal/cache/curve_cache.go:59-148
- internal/repository/share_code.go:20-58
- internal/cache/share_code_cache.go:142-353
- internal/task/share_code_persist.go:14-22
详细组件分析
BrandRepository 设计与实现
- 设计模式:仓储模式,面向领域模型 Brand,封装查询逻辑,现已集成缓存感知功能。
- 关键特性:
- 支持缓存感知的全量品牌查询,优先从 Redis 获取全量品牌数据
- 缓存未命中或 Redis 异常时自动降级到数据库查询
- 支持按品牌名称模糊过滤,无参数时返回全部记录并按名称升序排序
- 使用 QueryContext 传递请求上下文,便于超时与取消控制
- 使用 defer rows.Close() 确保资源释放
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息
- 缓存回写机制:成功从数据库获取数据后自动回写全量缓存
- 缓存策略:
- 缓存键:
brand:all - TTL:30分钟
- 应用层过滤:在缓存命中时进行模糊过滤,减少数据库压力
- 缓存键:
- 错误处理:对缓存读取、数据库查询、扫描、迭代阶段分别进行错误包装,便于定位问题。
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 : "返回缓存数据"
图表来源
- internal/repository/brand.go:14-21
- internal/repository/brand.go:23-52
- internal/cache/brand_cache.go:18-24
- internal/model/brand.go:3-6
章节来源
- internal/repository/brand.go:14-21
- internal/repository/brand.go:23-52
- internal/cache/brand_cache.go:18-24
- internal/model/brand.go:3-6
ModelRepository 设计与实现
- 设计模式:仓储模式,面向领域模型 Model,封装查询逻辑,现已集成缓存感知功能。
- 关键特性:
- 支持三种查询条件:按品牌精确匹配、按型号名模糊匹配、全量查询;两者皆为空时返回空切片
- 缓存感知查询:按品牌查询优先从缓存获取,按型号模糊查询从全量缓存过滤
- 缓存未命中或 Redis 异常时自动降级到数据库查询
- 使用自定义 scanModel(rows) 将 sql.NullString 安全转换为 *string,避免空值导致的序列化问题
- 使用 QueryContext 传递上下文,defer rows.Close() 保证资源释放
- 对扫描与迭代阶段进行错误包装
- 缓存回写机制:成功从数据库获取数据后自动回写相应缓存
- 缓存策略:
- 按品牌缓存:键格式为
model:brand:{brandName},TTL 30分钟 - 全量缓存:键为
model:all,TTL 30分钟 - 应用层过滤:在全量缓存命中时进行模糊过滤,减少数据库压力
- 按品牌缓存:键格式为
- 表结构要点:model 表包含唯一索引 model_name(brand_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 : "返回缓存数据"
图表来源
- internal/repository/model.go:14-21
- internal/repository/model.go:23-79
- internal/cache/model_cache.go:19-25
- internal/model/model.go:5-14
章节来源
- internal/repository/model.go:14-21
- internal/repository/model.go:23-79
- internal/cache/model_cache.go:19-25
- internal/model/model.go:5-14
- sql/model.sql:34
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 : "活跃信息"
图表来源
- internal/repository/device.go:11-17
- internal/repository/device.go:19-87
- internal/model/user_device.go:5-11
- internal/model/user_active.go:5-12
章节来源
- internal/repository/device.go:11-17
- internal/repository/device.go:19-87
- internal/model/user_device.go:5-11
- internal/model/user_active.go:5-12
- sql/user_device.sql:23-32
- sql/user_active.sql:23-32
OTARepository 设计与实现
- 设计模式:仓储模式,面向领域模型 OTA、BlackList 和 OTATargetDevice,封装固件升级查询逻辑。
- 关键特性:
- 最新OTA查询:按型号、硬件版本、灰度标识查询最新一条可用OTA记录
- 黑名单检查:检查设备是否在指定OTA的黑名单中
- 非黑名单查询:查询不在黑名单中的最新OTA记录
- 非定向查询:查询非定向(target=0)的最新OTA记录
- 定向设备检查:检查设备是否在指定OTA的定向设备列表中
- 可空字段处理:使用 sql.NullString 和 sql.NullTime 安全处理数据库可空字段
- 复杂查询:支持子查询、条件筛选、时间范围检查等复杂业务逻辑
- 业务逻辑:
- 支持灰度发布(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
+int Force
+*string Desc
+*string Model
+int HW
+int Target
+int Beta
+int PawVerCode
+string PawVerName
+string PawURL
+string PawMD5
+*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 : "定向设备"
图表来源
章节来源
- internal/repository/ota.go:11-17
- internal/repository/ota.go:19-158
- internal/model/ota.go:6-42
- sql/ota.sql:23-44
- sql/ota_target_device.sql:23-31
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 : "目标曲线"
图表来源
- internal/repository/curve.go:11-17
- internal/repository/curve.go:19-65
- internal/model/model.go:5-14
- internal/model/target.go:5-12
章节来源
- internal/repository/curve.go:11-17
- internal/repository/curve.go:19-65
- internal/model/model.go:5-14
- internal/model/target.go:5-12
- sql/model.sql:24-35
- sql/target.sql:23-31
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 : "日志记录"
图表来源
- internal/repository/share_code.go:12-18
- internal/repository/share_code.go:20-58
- internal/model/share_code.go:14-24
章节来源
- internal/repository/share_code.go:12-18
- internal/repository/share_code.go:20-58
- internal/model/share_code.go:14-24
- sql/share_code_log.sql:8-23
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}Hash(TTL = SHARE_CODE_TTL_MIN) - MAC索引:
share:mac:{mac}ZSET(TTL = SHARE_CODE_TTL_MIN) - 待持久化:
share:pendingSET(无TTL,过期后自动清理) - 导入队列:
share:import:pendingHash(TTL 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 两张表
- 成功确认:批量删除已成功持久化的数据,避免重复处理
- 错误隔离:单条数据处理失败不影响其他数据的处理
- 处理流程:
- 从Redis读取 devices Hash 中的所有设备数据
- 解析JSON格式的设备信息
- 修复无效的日期格式
- 分别处理设备信息和活跃信息
- 批量删除已成功处理的数据
- 应用场景:高并发设备信息上报、数据可靠性保障、异步数据同步等。
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 按固定间隔启动持久化任务
- 批量处理:从Redis的share:pending集合读取待持久化的分享码数据
- 原子写入:使用Lua脚本实现原子性的数据写入和状态更新
- 幂等性:通过ShareCodeData结构体的Persisted字段确保重复处理的安全性
- 错误处理:单个分享码持久化失败不影响其他数据的处理
- TTL维护:在HSET操作前后正确维护TTL,避免数据丢失
- 处理流程:
- 从Redis读取share:pending集合中的所有分享码
- 逐个查询对应的Hash数据和导入队列日志
- 插入MySQL share_code_log表
- 标记已持久化并移除待持久化标记
- 清理导入队列中的对应记录
- 应用场景:分享码操作审计、数据可靠性保障、异步数据同步等。
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,等待异步持久化任务处理。
- OTAHandler 处理OTA升级查询,调用OTARepository执行复杂的业务逻辑,包括黑名单检查、定向设备筛选等。
- CurveHandler 处理曲线查询,调用CurveRepository和CurveCache执行数据查询和缓存操作,支持fr数据分离和分布式锁机制。
- ShareCodeHandler 处理分享码操作,调用ShareCodeCache执行Lua原子操作和队列管理,支持导入导出流程。
- BrandHandler 和 ModelHandler 继续使用原有的缓存感知查询逻辑。
- 错误处理:各 Handler 对 Repository 返回的错误进行统一处理和日志记录。
- 缓存感知:CurveHandler 和 ShareCodeHandler 需要处理复杂的缓存逻辑,包括分布式锁、缓存合并等。
sequenceDiagram
participant C as "客户端"
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缓存未命中
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缓存未命中
CH->>CC : "AcquireLock"
CC->>Redis : "SetNX 分布式锁"
Redis-->>CH : "获取锁成功"
CH->>CR : "GetModelByBrandAndName"
CR->>DB : "QueryRowContext"
DB-->>CR : "模型数据"
CH->>CR : "GetTargetByLabel"
CR->>DB : "QueryRowContext"
DB-->>CR : "目标数据"
CH->>CC : "Set 缓存"
CH->>CC : "Set __fr 缓存"
CH->>CC : "ReleaseLock"
CH-->>C : "返回曲线数据"
end
end
图表来源
- internal/handler/curve.go:56-127
- internal/handler/curve.go:214-292
- internal/repository/curve.go:19-65
- internal/cache/curve_cache.go:59-148
章节来源
- internal/handler/brand.go:19-24
- internal/handler/brand.go:25-48
- internal/handler/model.go:19-24
- internal/handler/model.go:25-49
- internal/handler/device.go:15-25
- internal/handler/device.go:27-82
- internal/handler/ota.go:14-21
- internal/handler/ota.go:23-132
- internal/handler/curve.go:27-39
- internal/handler/curve.go:56-127
- internal/handler/curve.go:214-292
- internal/handler/share_code.go:15-23
- internal/handler/share_code.go:40-125
- internal/repository/brand.go:23-52
- internal/repository/model.go:23-79
- internal/repository/device.go:19-87
- internal/repository/ota.go:19-158
- internal/repository/curve.go:19-65
- internal/repository/share_code.go:20-58
缓存策略详解
多级缓存架构
数据访问层采用了多级缓存策略,分为以下层级:
- 应用层缓存:Redis 缓存,提供高性能的数据访问
- 内存缓存:应用进程内的缓存,提供最快的访问速度
- 数据库缓存:MySQL 查询缓存,利用数据库自身的缓存机制
缓存键设计
- 品牌全量缓存:
brand:all- 存储所有品牌信息 - 型号按品牌缓存:
model:brand:{brandName}- 存储特定品牌的所有型号 - 型号全量缓存:
model:all- 存储所有型号信息 - 设备活跃缓存:
device:active:{date}- 存储特定日期的活跃设备信息 - 曲线缓存:
brand nameHash - 存储曲线数据,字段为target或__fr - 分享码缓存:
share:{code}Hash - 存储分享码主数据,字段包括mac_addr、ip_addr、model、eq_data、expire_at、persisted - 分享码导入队列:
share:import:pendingHash - 存储导入日志,支持幂等性
缓存策略实现
- 缓存优先策略:优先从缓存获取数据,缓存未命中时才查询数据库
- 智能降级策略:Redis 异常时自动降级到数据库查询,保证系统可用性
- 自动回写策略:成功从数据库获取数据后自动回写到缓存
- TTL 策略:所有缓存设置 30 分钟过期时间,平衡性能和数据新鲜度
- 设备数据特殊处理:设备活跃信息采用分区表存储,缓存策略需考虑日期维度
- 曲线数据优化: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[OTA数据无需预热]
I --> J[曲线缓存无需预热]
J --> K[分享码缓存无需预热]
K --> L[缓存预热完成]
图表来源
章节来源
- internal/cache/brand_cache.go:13-16
- internal/cache/model_cache.go:13-16
- internal/cache/curve_cache.go:12-16
- internal/cache/share_code_cache.go:5-25
- cmd/server/main.go:142-191
依赖关系分析
- 外部依赖:Go MySQL Driver、Gin、Zap 日志、Redis 客户端、Meilisearch 客户端、AWS SDK。
- 内部依赖:Handler 依赖 Repository;Repository 依赖 *sql.DB 和缓存实例;缓存层依赖 Redis 客户端;数据库模块负责连接池与 Ping 校验;配置模块负责环境变量解析与校验;任务模块依赖 Redis 和 Repository。
- 循环依赖:未发现循环依赖,职责边界清晰。
- 新增依赖:CurveRepository 和 ShareCodeRepository 直接依赖 *sql.DB;CurveCache 和 ShareCodeCache 依赖 Redis 客户端;分享码持久化任务依赖 ShareCodeCache 和 ShareCodeRepository;CurveHandler 依赖 CurveRepository、CurveCache 和 S3 存储;ShareCodeHandler 依赖 ShareCodeCache。
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 --> 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"]
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"]
OTA_REPO --> DB_SQL
CURVE_REPO --> DB_SQL
SHARE_REPO --> DB_SQL
PERSIST_TASK --> DEV_REPO
PERSIST_TASK --> REDIS_CLIENT
SHARE_PERSIST_TASK --> SHARE_REPO
SHARE_PERSIST_TASK --> SHARE_CACHE
BR_CACHE --> REDIS_CLIENT
MD_CACHE --> REDIS_CLIENT
图表来源
- go.mod:5-11
- cmd/server/main.go:25-69
- internal/config/config.go:18-52
- internal/config/database.go:17-40
- internal/config/redis.go:16-57
- internal/config/share_code_ttl.go:12-59
- internal/router/router.go:22-25
- internal/handler/brand.go:19-24
- internal/handler/model.go:19-24
- internal/handler/device.go:15-25
- internal/handler/ota.go:14-21
- internal/handler/curve.go:27-39
- internal/handler/share_code.go:15-23
- internal/repository/brand.go:14-21
- internal/repository/model.go:14-21
- internal/repository/device.go:11-17
- internal/repository/ota.go:11-17
- internal/repository/curve.go:11-17
- internal/repository/share_code.go:12-18
- internal/cache/brand_cache.go:18-24
- internal/cache/model_cache.go:19-25
- internal/cache/curve_cache.go:18-25
- internal/cache/share_code_cache.go:127-135
章节来源
- go.mod:5-11
- cmd/server/main.go:25-69
- internal/config/config.go:18-52
- internal/config/database.go:17-40
- internal/config/redis.go:16-57
- internal/config/share_code_ttl.go:12-59
- internal/router/router.go:22-25
- internal/handler/brand.go:19-24
- internal/handler/model.go:19-24
- internal/handler/device.go:15-25
- internal/handler/ota.go:14-21
- internal/handler/curve.go:27-39
- internal/handler/share_code.go:15-23
- internal/repository/brand.go:14-21
- internal/repository/model.go:14-21
- internal/repository/device.go:11-17
- internal/repository/ota.go:11-17
- internal/repository/curve.go:11-17
- internal/repository/share_code.go:12-18
- internal/cache/brand_cache.go:18-24
- internal/cache/model_cache.go:19-25
- internal/cache/curve_cache.go:18-25
- internal/cache/share_code_cache.go:127-135
性能考量
- 连接池配置
- 最大打开连接数:25
- 最大空闲连接数:5
- 连接最大生命周期:5 分钟
- 启动时使用 PingContext 进行健康检查,失败则关闭连接并返回错误
- 缓存性能优化
- 缓存命中率:通过多级缓存策略最大化缓存命中率
- TTL 设置:30分钟的TTL平衡了性能和数据新鲜度
- 预热机制:启动时预热缓存,避免冷启动时的缓存穿透
- 降级机制:Redis异常时自动降级到数据库,保证系统可用性
- 曲线缓存优化:fr数据分离存储,减少缓存体积和网络传输
- 分布式锁:限制同一时间只有一个请求在计算曲线数据
- 分享码缓存优化:Lua原子操作确保数据一致性,避免竞态条件
- 分布式锁:防止缓存击穿和竞态条件,确保数据一致性
- 查询优化
- BrandRepository:按名称模糊匹配,建议在 name 上建立合适索引;对大结果集引入分页。
- ModelRepository:按品牌名精确匹配或按型号名模糊匹配,建议为 brand_name 与 name 建立索引;对大结果集引入分页。
- DeviceRepository:user_device 表的 MAC 地址建立唯一索引,user_active 表建立复合索引(mac_addr, active_date)。
- OTARepository:ota 表建立复合索引(model, hw, beta, status),black_list 表建立索引(ota_id, mac),ota_target_device 表建立索引(ota_id, mac_addr)。
- CurveRepository:model 表建立复合索引(brand_name, name),target 表建立索引(label)。
- ShareCodeRepository:share_code_log 表建立索引(mac_addr, share_code, create_at)。
- 应用层过滤:在缓存命中时进行过滤,减少数据库压力。
- 并发安全
- *sql.DB 是并发安全的,可在多个 goroutine 中共享使用;Repository 实例不持有状态,亦可并发安全使用。
- Redis 客户端是并发安全的,支持多goroutine同时访问。
- 设备持久化任务使用独立的 goroutine 执行,避免阻塞主线程。
- 分布式锁机制确保缓存击穿防护,防止多个请求同时计算相同数据。
- 分享码缓存使用Lua脚本确保原子性,避免竞态条件。
- 资源清理
- 使用 defer rows.Close() 保证结果集关闭;在 main 中 defer db.Close() 保证应用退出时关闭连接池。
- Redis 连接在应用退出时自动关闭。
- 设备持久化任务使用 time.Ticker,确保正确停止。
- 分布式锁在释放时自动清理,避免锁泄露。
- 分享码导入队列使用TTL自动清理,避免无限增长。
- 事务处理
- 当前实现均为只读查询,未涉及事务;如需写操作,应在 Repository 层封装事务,使用 sql.Tx 并在错误时回滚,成功时提交。
- 设备持久化任务采用幂等设计,避免重复处理造成的数据不一致。
- 分布式锁机制确保缓存更新的一致性。
- 分享码持久化任务使用分布式锁确保数据一致性。
- 异步处理优化
- 设备信息上报采用异步持久化,提高响应速度
- Redis Hash 存储大量设备数据,内存占用较高,需监控内存使用情况
- 定时任务间隔可根据数据量调整,平衡实时性和系统负载
- 分布式锁重试机制平衡性能和一致性
- Lua原子操作确保数据一致性,避免竞态条件
- 分享码导入队列支持幂等性,避免重复导入
章节来源
- internal/database/mysql.go:33-43
- internal/cache/brand_cache.go:13-16
- internal/cache/model_cache.go:13-16
- internal/cache/curve_cache.go:12-16
- internal/cache/share_code_cache.go:5-25
- internal/repository/brand.go:23-52
- internal/repository/model.go:23-79
- internal/repository/device.go:19-87
- internal/repository/ota.go:19-158
- internal/repository/curve.go:19-65
- internal/repository/share_code.go:20-58
- cmd/server/main.go:142-191
故障排查指南
- 连接失败
- 现象:启动时 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数据格式;确认定时任务正常运行。
- OTA查询错误
- 现象:OTA升级查询返回错误或结果不符合预期。
- 排查:检查OTA表数据完整性,确认status=1且有效时间范围内;验证黑名单和定向设备表数据;检查参数传递(model、hw、beta、mac)。
- 曲线查询错误
- 现象:曲线查询返回错误或结果不符合预期。
- 排查:检查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/scan brand/scan model/iterate brand/iterate model),定位具体环节;检查 SQL 参数绑定与字段映射。
- 结果为空
- 现象:Repository 在查询数据时返回空值。
- 排查:确认传入的查询参数是否正确;检查表中是否存在匹配数据;验证MAC地址格式;确认缓存键是否正确。
- 资源泄漏
- 现象:长时间运行后连接数异常。
- 排查:确认是否遗漏 rows.Close();检查连接池配置是否合理;观察连接生命周期与空闲连接上限。
- 缓存未命中
- 现象:频繁出现缓存未命中,数据库压力过大。
- 排查:检查缓存键是否正确;确认 TTL 设置是否合理;验证缓存预热是否成功;检查 Redis 内存使用情况。
- 性能问题
- 现象:曲线查询响应慢或设备持久化延迟。
- 排查:检查相关表的索引是否完整;监控Redis内存使用;评估定时任务执行频率;分析数据库查询计划;检查分布式锁等待时间。
- 分布式锁问题
- 现象:缓存更新时出现锁竞争或死锁。
- 排查:检查锁TTL设置;确认锁释放逻辑;验证重试机制;检查锁键格式;确认Redis集群状态。
- 分享码持久化问题
- 现象:分享码操作日志未写入数据库。
- 排查:检查分享码持久化任务日志;确认分布式锁获取成功;验证数据库连接;检查HasExportLog查询;确认MarkPersisted执行。
章节来源
- internal/config/database.go:17-72
- internal/config/redis.go:16-57
- internal/handler/brand.go:25-48
- internal/handler/model.go:25-49
- internal/handler/device.go:27-82
- internal/handler/ota.go:23-132
- internal/handler/curve.go:56-127
- internal/handler/curve.go:214-292
- internal/handler/share_code.go:40-125
- internal/repository/brand.go:23-52
- internal/repository/model.go:23-79
- internal/repository/device.go:19-87
- internal/repository/ota.go:19-158
- internal/repository/curve.go:19-65
- internal/repository/share_code.go:20-58
结论
Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知查询逻辑,实现了多级缓存策略和智能数据刷新机制。新增的设备持久化仓库、OTA仓库、曲线仓库和分享码仓库进一步完善了数据访问层的功能,支持设备信息上报、固件升级查询、频响曲线计算和分享码管理等核心业务场景。新增的CurveCache和ShareCodeCache提供了更高级的缓存策略,包括fr数据分离存储、分布式锁机制、Lua原子操作等,显著提升了系统的响应性能和数据一致性。通过集成 Redis 缓存层和异步持久化任务,系统在高并发场景下表现出色。建议后续继续优化缓存策略,增加缓存监控指标,完善缓存失效和更新机制,并持续改进监控与日志体系。
附录:扩展新 Repository 指南
- 设计原则
- 保持 Repository 无状态,仅依赖 *sql.DB 和缓存实例。
- 查询方法接收 context.Context,便于超时与取消控制。
- 对外返回领域模型(Model),避免直接暴露数据库结构。
- 对可空字段使用 sql.NullString、sql.NullInt64、sql.NullTime 等安全转换。
- 集成缓存感知查询逻辑,提供智能降级和自动回写功能。
- 考虑异步处理场景,设计幂等的持久化逻辑。
- 支持分布式锁机制,防止缓存击穿和竞态条件。
- 使用Lua脚本确保数据一致性,避免竞态条件。
- 实现步骤
- 定义领域模型(Model)与 Repository 接口/实现。
- 创建对应的缓存结构体,定义缓存键和TTL策略。
- 在 Handler 中注入 *sql.DB 和缓存实例,构造 Repository 实例。
- 在路由中注册对应处理器。
- 在 main 中确保 *sql.DB 和缓存实例注入到 Handler。
- 实现缓存预热逻辑,确保应用启动时缓存可用。
- 如涉及异步处理,实现独立的任务调度器。
- 配置分享码TTL等配置项,支持动态参数。
- 注意事项
- 必须在每个查询后 defer rows.Close()。
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息。
- 生产环境务必提供数据库密码等敏感配置。
- 如需写操作,封装事务并在错误时回滚,成功时提交。
- 对高频查询建立合适的索引,必要时引入分页参数。
- 实现缓存降级机制,确保Redis异常时系统仍可正常工作。
- 设计合理的缓存键和TTL策略,平衡性能和数据新鲜度。
- 实现缓存预热和自动回写机制,提升用户体验。
- 异步任务需考虑幂等性,避免重复处理造成数据不一致。
- 监控异步任务的执行状态和错误日志。
- 分布式锁需设置合理的TTL和重试机制。
- Lua脚本需确保原子性和错误处理。
- 分享码TTL配置需支持多种时间单位格式。
- 分享码长度必须为5位字符,使用字母数字组合(排除易混淆字符)。
章节来源
- internal/repository/brand.go:14-21
- internal/repository/model.go:14-21
- internal/repository/device.go:11-17
- internal/repository/ota.go:11-17
- internal/repository/curve.go:11-17
- internal/repository/share_code.go:12-18
- internal/cache/brand_cache.go:18-24
- internal/cache/model_cache.go:19-25
- internal/cache/curve_cache.go:18-25
- internal/cache/share_code_cache.go:127-135
- internal/handler/brand.go:19-24
- internal/handler/model.go:19-24
- internal/handler/device.go:15-25
- internal/handler/ota.go:14-21
- internal/handler/curve.go:27-39
- internal/handler/share_code.go:15-23
- internal/router/router.go:22-25
- cmd/server/main.go:142-191