1281 lines
68 KiB
Markdown
1281 lines
68 KiB
Markdown
# 数据访问层
|
||
|
||
<cite>
|
||
**本文引用的文件**
|
||
- [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)
|
||
</cite>
|
||
|
||
## 更新摘要
|
||
**变更内容**
|
||
- OTA仓库层实现已移除pawVerCode、pawVerName、pawUrl、pawMd5字段,简化了查询逻辑
|
||
- OTA模型结构已精简,移除了不必要的字段,提高了数据传输效率
|
||
- OTA查询方法的SQL语句已优化,减少了字段选择和扫描操作
|
||
- OTA表结构已更新,移除了相关字段定义
|
||
|
||
## 目录
|
||
1. [简介](#简介)
|
||
2. [项目结构](#项目结构)
|
||
3. [核心组件](#核心组件)
|
||
4. [架构总览](#架构总览)
|
||
5. [详细组件分析](#详细组件分析)
|
||
6. [缓存策略详解](#缓存策略详解)
|
||
7. [依赖关系分析](#依赖关系分析)
|
||
8. [性能考量](#性能考量)
|
||
9. [故障排查指南](#故障排查指南)
|
||
10. [结论](#结论)
|
||
11. [附录:扩展新 Repository 指南](#附录扩展新-repository-指南)
|
||
|
||
## 简介
|
||
本文件聚焦于 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 路由暴露接口。
|
||
|
||
```mermaid
|
||
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](file://cmd/server/main.go#L103-L130)
|
||
- [internal/router/router.go:22-25](file://internal/router/router.go#L22-L25)
|
||
- [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/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/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
|
||
- [internal/repository/curve.go:11-17](file://internal/repository/curve.go#L11-L17)
|
||
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [internal/cache/share_code_cache.go:127-135](file://internal/cache/share_code_cache.go#L127-L135)
|
||
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
||
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
|
||
- [internal/config/database.go:17-40](file://internal/config/database.go#L17-L40)
|
||
- [internal/config/redis.go:9-57](file://internal/config/redis.go#L9-L57)
|
||
- [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/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/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)
|
||
|
||
**章节来源**
|
||
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)
|
||
- [internal/router/router.go:14-56](file://internal/router/router.go#L14-L56)
|
||
- [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/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/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
|
||
- [internal/repository/curve.go:11-17](file://internal/repository/curve.go#L11-L17)
|
||
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [internal/cache/share_code_cache.go:127-135](file://internal/cache/share_code_cache.go#L127-L135)
|
||
- [internal/cache/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
|
||
- [internal/database/mysql.go:14-46](file://internal/database/mysql.go#L14-L46)
|
||
- [internal/config/database.go:17-40](file://internal/config/database.go#L17-L40)
|
||
- [internal/config/redis.go:9-57](file://internal/config/redis.go#L9-L57)
|
||
- [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/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/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)
|
||
|
||
## 核心组件
|
||
- **BrandRepository**:负责品牌列表查询,支持缓存感知的全量查询和按名称模糊过滤,返回 Brand 领域对象切片。
|
||
- **ModelRepository**:负责型号列表查询,支持缓存感知的按品牌精确匹配、按型号名模糊匹配和全量查询,返回 Model 领域对象切片,并对可空字段进行 NullString 到指针字符串的安全转换。
|
||
- **DeviceRepository**:负责设备信息管理,支持按MAC地址查询设备、插入新设备、更新设备版本信息,以及用户活跃信息的查询和更新。
|
||
- **OTARepository**:负责固件升级信息查询,支持按型号、硬件版本、灰度标识查询最新OTA记录,黑名单检查、定向设备筛选等功能。**已精简**:移除了pawVerCode、pawVerName、pawUrl、pawMd5字段,简化了查询逻辑和数据处理流程。
|
||
- **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](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/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/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)
|
||
- [internal/config/redis.go:16-57](file://internal/config/redis.go#L16-L57)
|
||
- [internal/config/share_code_ttl.go:12-59](file://internal/config/share_code_ttl.go#L12-L59)
|
||
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
||
- [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/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 缓存层和异步持久化任务,显著提升了系统的响应性能和数据可靠性。整体流程如下:
|
||
|
||
```mermaid
|
||
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](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)
|
||
- [internal/cache/curve_cache.go:59-148](file://internal/cache/curve_cache.go#L59-L148)
|
||
- [internal/repository/share_code.go:20-58](file://internal/repository/share_code.go#L20-L58)
|
||
- [internal/cache/share_code_cache.go:142-353](file://internal/cache/share_code_cache.go#L142-L353)
|
||
- [internal/task/share_code_persist.go:14-22](file://internal/task/share_code_persist.go#L14-L22)
|
||
|
||
## 详细组件分析
|
||
|
||
### BrandRepository 设计与实现
|
||
- **设计模式**:仓储模式,面向领域模型 Brand,封装查询逻辑,现已集成缓存感知功能。
|
||
- **关键特性**:
|
||
- 支持缓存感知的全量品牌查询,优先从 Redis 获取全量品牌数据
|
||
- 缓存未命中或 Redis 异常时自动降级到数据库查询
|
||
- 支持按品牌名称模糊过滤,无参数时返回全部记录并按名称升序排序
|
||
- 使用 QueryContext 传递请求上下文,便于超时与取消控制
|
||
- 使用 defer rows.Close() 确保资源释放
|
||
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息
|
||
- 缓存回写机制:成功从数据库获取数据后自动回写全量缓存
|
||
- **缓存策略**:
|
||
- 缓存键:`brand:all`
|
||
- TTL:30分钟
|
||
- 应用层过滤:在缓存命中时进行模糊过滤,减少数据库压力
|
||
- **错误处理**:对缓存读取、数据库查询、扫描、迭代阶段分别进行错误包装,便于定位问题。
|
||
|
||
```mermaid
|
||
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](file://internal/repository/brand.go#L14-L21)
|
||
- [internal/repository/brand.go:23-52](file://internal/repository/brand.go#L23-L52)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/model/brand.go:3-6](file://internal/model/brand.go#L3-L6)
|
||
|
||
**章节来源**
|
||
- [internal/repository/brand.go:14-21](file://internal/repository/brand.go#L14-L21)
|
||
- [internal/repository/brand.go:23-52](file://internal/repository/brand.go#L23-L52)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/model/brand.go:3-6](file://internal/model/brand.go#L3-L6)
|
||
|
||
### 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),有利于去重与高效检索。
|
||
|
||
```mermaid
|
||
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](file://internal/repository/model.go#L14-L21)
|
||
- [internal/repository/model.go:23-79](file://internal/repository/model.go#L23-L79)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/model/model.go:5-14](file://internal/model/model.go#L5-L14)
|
||
|
||
**章节来源**
|
||
- [internal/repository/model.go:14-21](file://internal/repository/model.go#L14-L21)
|
||
- [internal/repository/model.go:23-79](file://internal/repository/model.go#L23-L79)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/model/model.go:5-14](file://internal/model/model.go#L5-L14)
|
||
- [sql/model.sql:34](file://sql/model.sql#L34)
|
||
|
||
### DeviceRepository 设计与实现
|
||
- **设计模式**:仓储模式,面向领域模型 UserDevice 和 UserActive,封装设备信息管理逻辑。
|
||
- **关键特性**:
|
||
- 设备查询:按MAC地址精确查询用户设备信息,支持版本号的可空字段处理
|
||
- 设备管理:支持插入新设备、更新设备版本信息
|
||
- 用户活跃:按MAC地址和日期查询用户活跃信息,支持IP地址更新
|
||
- 可空字段处理:使用 sql.NullString 和 sql.NullTime 安全转换数据库可空字段
|
||
- 错误处理:针对不同查询场景提供详细的错误包装,便于问题定位
|
||
- 资源管理:使用 QueryRowContext 和 ExecContext,确保数据库连接正确释放
|
||
- **表结构要点**:
|
||
- user_device 表:主键ID,唯一MAC地址索引,支持设备版本跟踪
|
||
- user_active 表:复合主键(ID, active_date),唯一MAC+日期索引,支持分区存储
|
||
- **应用场景**:设备信息上报、用户活跃统计、设备版本管理等业务场景。
|
||
|
||
```mermaid
|
||
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](file://internal/repository/device.go#L11-L17)
|
||
- [internal/repository/device.go:19-87](file://internal/repository/device.go#L19-L87)
|
||
- [internal/model/user_device.go:5-11](file://internal/model/user_device.go#L5-L11)
|
||
- [internal/model/user_active.go:5-12](file://internal/model/user_active.go#L5-L12)
|
||
|
||
**章节来源**
|
||
- [internal/repository/device.go:11-17](file://internal/repository/device.go#L11-L17)
|
||
- [internal/repository/device.go:19-87](file://internal/repository/device.go#L19-L87)
|
||
- [internal/model/user_device.go:5-11](file://internal/model/user_device.go#L5-L11)
|
||
- [internal/model/user_active.go:5-12](file://internal/model/user_active.go#L5-L12)
|
||
- [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)
|
||
|
||
### 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)
|
||
- **应用场景**:固件升级推送、灰度发布控制、定向设备管理等。
|
||
|
||
```mermaid
|
||
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 : "定向设备"
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/repository/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
|
||
- [internal/repository/ota.go:19-158](file://internal/repository/ota.go#L19-L158)
|
||
- [internal/model/ota.go:6-42](file://internal/model/ota.go#L6-L42)
|
||
|
||
**章节来源**
|
||
- [internal/repository/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
|
||
- [internal/repository/ota.go:19-158](file://internal/repository/ota.go#L19-L158)
|
||
- [internal/model/ota.go:6-42](file://internal/model/ota.go#L6-L42)
|
||
- [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)
|
||
|
||
### CurveRepository 设计与实现
|
||
- **设计模式**:仓储模式,面向领域模型 Model 和 Target,封装频响曲线数据查询逻辑。
|
||
- **关键特性**:
|
||
- Model查询:按品牌+型号精确查询设备模型信息,支持可空字段处理
|
||
- Target查询:按标签查询目标曲线信息,支持CSV读取和BassBoost配置
|
||
- 可空字段处理:使用 sql.NullString 和 sql.NullTime 安全转换数据库可空字段
|
||
- 错误处理:针对不同查询场景提供详细的错误包装,便于问题定位
|
||
- 资源管理:使用 QueryRowContext 确保数据库连接正确释放
|
||
- **表结构要点**:
|
||
- model 表:包含品牌、型号、源信息、EQ键等字段,支持不同源的差异化处理
|
||
- target 表:包含标签、CSV文件路径、BassBoost配置等字段
|
||
- **应用场景**:频响曲线计算、参数化EQ生成、设备匹配等音频处理场景。
|
||
|
||
```mermaid
|
||
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](file://internal/repository/curve.go#L11-L17)
|
||
- [internal/repository/curve.go:19-65](file://internal/repository/curve.go#L19-L65)
|
||
- [internal/model/model.go:5-14](file://internal/model/model.go#L5-L14)
|
||
- [internal/model/target.go:5-12](file://internal/model/target.go#L5-L12)
|
||
|
||
**章节来源**
|
||
- [internal/repository/curve.go:11-17](file://internal/repository/curve.go#L11-L17)
|
||
- [internal/repository/curve.go:19-65](file://internal/repository/curve.go#L19-L65)
|
||
- [internal/model/model.go:5-14](file://internal/model/model.go#L5-L14)
|
||
- [internal/model/target.go:5-12](file://internal/model/target.go#L5-L12)
|
||
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
|
||
- [sql/target.sql:23-31](file://sql/target.sql#L23-L31)
|
||
|
||
### ShareCodeRepository 设计与实现
|
||
- **设计模式**:仓储模式,面向领域模型 ShareCodeLog,封装分享码操作日志管理逻辑。
|
||
- **关键特性**:
|
||
- 日志插入:支持插入分享码导出/导入操作日志,包含MAC地址、设备型号、EQ数据等信息
|
||
- 导出日志查询:支持查询指定分享码的导出操作记录
|
||
- 可空字段处理:使用 *time.Time 处理过期时间等可空字段
|
||
- 错误处理:针对不同查询场景提供详细的错误包装,便于问题定位
|
||
- 资源管理:使用 ExecContext 和 QueryRowContext 确保数据库连接正确释放
|
||
- **表结构要点**:
|
||
- share_code_log 表:包含主键ID、MAC地址、分享码、操作类型、设备型号、EQ数据、过期时间等字段
|
||
- 支持按MAC地址、分享码、创建时间等字段建立索引,优化查询性能
|
||
- **应用场景**:分享码操作审计、数据溯源、合规性检查等业务场景。
|
||
|
||
```mermaid
|
||
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](file://internal/repository/share_code.go#L12-L18)
|
||
- [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)
|
||
|
||
**章节来源**
|
||
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
|
||
- [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)
|
||
- [sql/share_code_log.sql:8-23](file://sql/share_code_log.sql#L8-L23)
|
||
|
||
### 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生成优化、高并发场景下的缓存保护。
|
||
|
||
```mermaid
|
||
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"
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [internal/cache/curve_cache.go:27-148](file://internal/cache/curve_cache.go#L27-L148)
|
||
|
||
**章节来源**
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [internal/cache/curve_cache.go:27-148](file://internal/cache/curve_cache.go#L27-L148)
|
||
|
||
### 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:lock:{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:pending` SET(无TTL,过期后自动清理)
|
||
- 导入队列:`share:import:pending` Hash(TTL 12小时)
|
||
- 分布式锁:`share:flush:lock:{code}`、`share:import:flush:lock:{field}`
|
||
- **应用场景**:分享码创建、查询、删除、导入导出等完整的分享码生命周期管理。
|
||
|
||
```mermaid
|
||
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"
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/cache/share_code_cache.go:127-135](file://internal/cache/share_code_cache.go#L127-L135)
|
||
- [internal/cache/share_code_cache.go:142-353](file://internal/cache/share_code_cache.go#L142-L353)
|
||
|
||
**章节来源**
|
||
- [internal/cache/share_code_cache.go:127-135](file://internal/cache/share_code_cache.go#L127-L135)
|
||
- [internal/cache/share_code_cache.go:142-353](file://internal/cache/share_code_cache.go#L142-L353)
|
||
|
||
### 设备持久化任务设计与实现
|
||
- **设计模式**:定时任务模式,基于时间轮询的异步数据同步机制。
|
||
- **关键特性**:
|
||
- 定时执行:使用 time.Ticker 按固定间隔启动持久化任务
|
||
- 批量处理:从Redis Hash一次性读取所有待持久化的设备数据
|
||
- 数据修复:自动修复历史数据中的无效日期格式
|
||
- 双表写入:同时处理 user_device 和 user_active 两张表
|
||
- 成功确认:批量删除已成功持久化的数据,避免重复处理
|
||
- 错误隔离:单条数据处理失败不影响其他数据的处理
|
||
- **处理流程**:
|
||
1. 从Redis读取 devices Hash 中的所有设备数据
|
||
2. 解析JSON格式的设备信息
|
||
3. 修复无效的日期格式
|
||
4. 分别处理设备信息和活跃信息
|
||
5. 批量删除已成功处理的数据
|
||
- **应用场景**:高并发设备信息上报、数据可靠性保障、异步数据同步等。
|
||
|
||
```mermaid
|
||
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[结束]
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/task/device_persist.go:24-91](file://internal/task/device_persist.go#L24-L91)
|
||
|
||
**章节来源**
|
||
- [internal/task/device_persist.go:14-167](file://internal/task/device_persist.go#L14-L167)
|
||
|
||
### 分享码持久化任务设计与实现
|
||
- **设计模式**:定时任务模式,基于时间轮询的异步数据同步机制。
|
||
- **关键特性**:
|
||
- 定时执行:使用 time.Ticker 按固定间隔启动持久化任务
|
||
- 批量处理:从Redis的share:pending集合读取待持久化的分享码数据
|
||
- 原子写入:使用Lua脚本实现原子性的数据写入和状态更新
|
||
- 幂等性:通过ShareCodeData结构体的Persisted字段确保重复处理的安全性
|
||
- 错误处理:单个分享码持久化失败不影响其他数据的处理
|
||
- TTL维护:在HSET操作前后正确维护TTL,避免数据丢失
|
||
- **处理流程**:
|
||
1. 从Redis读取share:pending集合中的所有分享码
|
||
2. 逐个查询对应的Hash数据和导入队列日志
|
||
3. 插入MySQL share_code_log表
|
||
4. 标记已持久化并移除待持久化标记
|
||
5. 清理导入队列中的对应记录
|
||
- **应用场景**:分享码操作审计、数据可靠性保障、异步数据同步等。
|
||
|
||
```mermaid
|
||
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[结束]
|
||
```
|
||
|
||
**图表来源**
|
||
- [internal/task/share_code_persist.go:14-22](file://internal/task/share_code_persist.go#L14-L22)
|
||
|
||
**章节来源**
|
||
- [internal/task/share_code_persist.go:14-22](file://internal/task/share_code_persist.go#L14-L22)
|
||
|
||
### Handler 与 Repository 的协作
|
||
- Handler 通过 NewXxxHandler 注入相应的 Repository 实例,内部执行具体的业务逻辑。
|
||
- DeviceHandler 处理设备信息上报,将数据写入Redis Hash,等待异步持久化任务处理。
|
||
- OTAHandler 处理OTA升级查询,调用OTARepository执行复杂的业务逻辑,包括黑名单检查、定向设备筛选等。
|
||
- CurveHandler 处理曲线查询,调用CurveRepository和CurveCache执行数据查询和缓存操作,支持fr数据分离和分布式锁机制。
|
||
- ShareCodeHandler 处理分享码操作,调用ShareCodeCache执行Lua原子操作和队列管理,支持导入导出流程。
|
||
- BrandHandler 和 ModelHandler 继续使用原有的缓存感知查询逻辑。
|
||
- 错误处理:各 Handler 对 Repository 返回的错误进行统一处理和日志记录。
|
||
- 缓存感知:CurveHandler 和 ShareCodeHandler 需要处理复杂的缓存逻辑,包括分布式锁、缓存合并等。
|
||
|
||
```mermaid
|
||
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](file://internal/handler/curve.go#L56-L127)
|
||
- [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)
|
||
|
||
**章节来源**
|
||
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
|
||
- [internal/handler/brand.go:25-48](file://internal/handler/brand.go#L25-L48)
|
||
- [internal/handler/model.go:19-24](file://internal/handler/model.go#L19-L24)
|
||
- [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/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)
|
||
- [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)
|
||
- [internal/handler/share_code.go:15-23](file://internal/handler/share_code.go#L15-L23)
|
||
- [internal/handler/share_code.go:40-125](file://internal/handler/share_code.go#L40-L125)
|
||
- [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/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)
|
||
|
||
## 缓存策略详解
|
||
|
||
### 多级缓存架构
|
||
数据访问层采用了多级缓存策略,分为以下层级:
|
||
|
||
1. **应用层缓存**:Redis 缓存,提供高性能的数据访问
|
||
2. **内存缓存**:应用进程内的缓存,提供最快的访问速度
|
||
3. **数据库缓存**:MySQL 查询缓存,利用数据库自身的缓存机制
|
||
|
||
### 缓存键设计
|
||
- **品牌全量缓存**:`brand:all` - 存储所有品牌信息
|
||
- **型号按品牌缓存**:`model:brand:{brandName}` - 存储特定品牌的所有型号
|
||
- **型号全量缓存**:`model:all` - 存储所有型号信息
|
||
- **设备活跃缓存**:`device:active:{date}` - 存储特定日期的活跃设备信息
|
||
- **曲线缓存**:`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 分钟过期时间,平衡性能和数据新鲜度
|
||
- **设备数据特殊处理**:设备活跃信息采用分区表存储,缓存策略需考虑日期维度
|
||
- **曲线数据优化**:fr数据独立存储在__fr字段,避免每个target重复存储
|
||
- **分享码TTL配置**:支持多种时间单位格式,最大365天,动态配置
|
||
- **分布式锁机制**:防止缓存击穿和竞态条件,确保数据一致性
|
||
|
||
### 预热机制
|
||
应用启动时会自动预热缓存,从数据库加载全量数据到 Redis:
|
||
|
||
```mermaid
|
||
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[缓存预热完成]
|
||
```
|
||
|
||
**图表来源**
|
||
- [cmd/server/main.go:142-191](file://cmd/server/main.go#L142-L191)
|
||
|
||
**章节来源**
|
||
- [internal/cache/brand_cache.go:13-16](file://internal/cache/brand_cache.go#L13-L16)
|
||
- [internal/cache/model_cache.go:13-16](file://internal/cache/model_cache.go#L13-L16)
|
||
- [internal/cache/curve_cache.go:12-16](file://internal/cache/curve_cache.go#L12-L16)
|
||
- [internal/cache/share_code_cache.go:5-25](file://internal/cache/share_code_cache.go#L5-L25)
|
||
- [cmd/server/main.go:142-191](file://cmd/server/main.go#L142-L191)
|
||
|
||
## 依赖关系分析
|
||
- **外部依赖**: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。
|
||
|
||
```mermaid
|
||
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](file://go.mod#L5-L11)
|
||
- [cmd/server/main.go:25-69](file://cmd/server/main.go#L25-L69)
|
||
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
||
- [internal/config/database.go:17-40](file://internal/config/database.go#L17-L40)
|
||
- [internal/config/redis.go:16-57](file://internal/config/redis.go#L16-L57)
|
||
- [internal/config/share_code_ttl.go:12-59](file://internal/config/share_code_ttl.go#L12-L59)
|
||
- [internal/router/router.go:22-25](file://internal/router/router.go#L22-L25)
|
||
- [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/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/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
|
||
- [internal/repository/curve.go:11-17](file://internal/repository/curve.go#L11-L17)
|
||
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [internal/cache/share_code_cache.go:127-135](file://internal/cache/share_code_cache.go#L127-L135)
|
||
|
||
**章节来源**
|
||
- [go.mod:5-11](file://go.mod#L5-L11)
|
||
- [cmd/server/main.go:25-69](file://cmd/server/main.go#L25-L69)
|
||
- [internal/config/config.go:18-52](file://internal/config/config.go#L18-L52)
|
||
- [internal/config/database.go:17-40](file://internal/config/database.go#L17-L40)
|
||
- [internal/config/redis.go:16-57](file://internal/config/redis.go#L16-L57)
|
||
- [internal/config/share_code_ttl.go:12-59](file://internal/config/share_code_ttl.go#L12-L59)
|
||
- [internal/router/router.go:22-25](file://internal/router/router.go#L22-L25)
|
||
- [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/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/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
|
||
- [internal/repository/curve.go:11-17](file://internal/repository/curve.go#L11-L17)
|
||
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [internal/cache/share_code_cache.go:127-135](file://internal/cache/share_code_cache.go#L127-L135)
|
||
|
||
## 性能考量
|
||
- **连接池配置**
|
||
- 最大打开连接数: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)。**已优化**:移除了pawVerCode、pawVerName、pawUrl、pawMd5字段,减少了查询字段数量,提升了查询性能。
|
||
- 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](file://internal/database/mysql.go#L33-L43)
|
||
- [internal/cache/brand_cache.go:13-16](file://internal/cache/brand_cache.go#L13-L16)
|
||
- [internal/cache/model_cache.go:13-16](file://internal/cache/model_cache.go#L13-L16)
|
||
- [internal/cache/curve_cache.go:12-16](file://internal/cache/curve_cache.go#L12-L16)
|
||
- [internal/cache/share_code_cache.go:5-25](file://internal/cache/share_code_cache.go#L5-L25)
|
||
- [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/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)
|
||
- [cmd/server/main.go:142-191](file://cmd/server/main.go#L142-L191)
|
||
|
||
## 故障排查指南
|
||
- **连接失败**
|
||
- 现象:启动时 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);**注意**: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/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](file://internal/config/database.go#L17-L72)
|
||
- [internal/config/redis.go:16-57](file://internal/config/redis.go#L16-L57)
|
||
- [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/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)
|
||
- [internal/handler/share_code.go:40-125](file://internal/handler/share_code.go#L40-L125)
|
||
- [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/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 缓存层和异步持久化任务,系统在高并发场景下表现出色。
|
||
|
||
**重要更新**:OTA仓库层已成功移除了pawVerCode、pawVerName、pawUrl、pawMd5等冗余字段,简化了查询逻辑和数据处理流程,提升了系统性能和维护性。OTA模型结构已精简,移除了不必要的字段,提高了数据传输效率。
|
||
|
||
建议后续继续优化缓存策略,增加缓存监控指标,完善缓存失效和更新机制,并持续改进监控与日志体系。
|
||
|
||
## 附录:扩展新 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](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/ota.go:11-17](file://internal/repository/ota.go#L11-L17)
|
||
- [internal/repository/curve.go:11-17](file://internal/repository/curve.go#L11-L17)
|
||
- [internal/repository/share_code.go:12-18](file://internal/repository/share_code.go#L12-L18)
|
||
- [internal/cache/brand_cache.go:18-24](file://internal/cache/brand_cache.go#L18-L24)
|
||
- [internal/cache/model_cache.go:19-25](file://internal/cache/model_cache.go#L19-L25)
|
||
- [internal/cache/curve_cache.go:18-25](file://internal/cache/curve_cache.go#L18-L25)
|
||
- [internal/cache/share_code_cache.go:127-135](file://internal/cache/share_code_cache.go#L127-L135)
|
||
- [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/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/router/router.go:22-25](file://internal/router/router.go#L22-L25)
|
||
- [cmd/server/main.go:142-191](file://cmd/server/main.go#L142-L191) |