feat(ota): 增强设备管理与OTA功能模块

- 新增OTA固件升级处理器,支持固件升级请求及黑名单过滤
- 引入设备持久化仓储,支持设备信息上报与活动记录
- 扩展数据模型,新增UserDevice与UserActive,支持设备版本跟踪
- 实现Redis到MySQL的异步数据同步任务
- 更新路由配置,集成新的API端点以支持OTA功能
- 优化架构图,反映新增的数据流与处理流程
This commit is contained in:
yangy
2026-05-31 09:52:11 +08:00
parent f3e3e82f52
commit 665397ede7
7 changed files with 1764 additions and 293 deletions
@@ -4,6 +4,8 @@
**本文引用的文件**
- [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/cache/brand_cache.go](file://internal/cache/brand_cache.go)
- [internal/cache/model_cache.go](file://internal/cache/model_cache.go)
- [internal/cache/redis.go](file://internal/cache/redis.go)
@@ -13,21 +15,31 @@
- [internal/config/config.go](file://internal/config/config.go)
- [internal/model/brand.go](file://internal/model/brand.go)
- [internal/model/model.go](file://internal/model/model.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/router/router.go](file://internal/router/router.go)
- [internal/task/device_persist.go](file://internal/task/device_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)
- [go.mod](file://go.mod)
</cite>
## 更新摘要
**变更内容**
- 新增缓存感知查询逻辑,增强品牌和型号仓库的缓存策略
- 引入多级缓存架构,支持全量缓存和按品牌缓存
- 实现智能数据刷新和预热机制
- 添加缓存降级和错误处理策略
- 更新架构图以反映新的缓存层集成
- 新增设备持久化仓库(DeviceRepository)和OTA仓库(OTARepository),支持设备信息上报和固件升级查询
- 引入设备活跃信息持久化任务,实现Redis到MySQL的异步数据同步
- 新增用户设备和用户活跃两个核心数据模型,支持设备版本跟踪和用户活跃统计
- 扩展OTA查询逻辑,支持黑名单检查、定向设备筛选和灰度发布控制
- 更新架构图以反映新增的设备数据流和OTA查询流程
## 目录
1. [简介](#简介)
@@ -43,10 +55,10 @@
11. [附录:扩展新 Repository 指南](#附录扩展新-repository-指南)
## 简介
本文件聚焦于 Luxsin 应用 API 的数据访问层(Repository 层),系统性阐述 BrandRepositoryModelRepository 的设计模式、实现原理与职责边界;解释其在整体架构中的位置与交互方式;深入分析数据库连接管理、SQL 查询优化与事务处理机制;详细介绍新增的缓存感知查询逻辑、多级缓存策略和智能数据刷新机制;给出使用示例路径、错误处理与异常管理策略;总结并发安全、连接池管理与资源清理的最佳实践,并提供扩展新 Repository 的指导原则与注意事项。文档兼顾初学者与资深开发者的需求,既提供高层架构视图,也给出可落地的实现细节。
本文件聚焦于 Luxsin 应用 API 的数据访问层(Repository 层),系统性阐述 BrandRepositoryModelRepository、DeviceRepository 和 OTARepository 的设计模式、实现原理与职责边界;解释其在整体架构中的位置与交互方式;深入分析数据库连接管理、SQL 查询优化与事务处理机制;详细介绍新增的缓存感知查询逻辑、多级缓存策略和智能数据刷新机制;给出使用示例路径、错误处理与异常管理策略;总结并发安全、连接池管理与资源清理的最佳实践,并提供扩展新 Repository 的指导原则与注意事项。文档兼顾初学者与资深开发者的需求,既提供高层架构视图,也给出可落地的实现细节。
## 项目结构
数据访问层位于 internal/repository 目录,配合 internal/cache 提供缓存层,internal/database 提供底层数据库连接,internal/model 定义领域模型,internal/handler 通过注入的 Repository 执行业务逻辑,最终由 Gin 路由暴露接口。
数据访问层位于 internal/repository 目录,配合 internal/cache 提供缓存层,internal/database 提供底层数据库连接,internal/model 定义领域模型,internal/handler 通过注入的 Repository 执行业务逻辑,internal/task 提供设备数据持久化任务,最终由 Gin 路由暴露接口。
```mermaid
graph TB
@@ -57,44 +69,71 @@ 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处理器"]
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"]
end
subgraph "缓存层"
BR_CACHE["internal/cache/brand_cache.go<br/>BrandCache"]
MD_CACHE["internal/cache/model_cache.go<br/>ModelCache"]
REDIS["internal/cache/redis.go<br/>Redis 客户端"]
end
subgraph "任务调度"
PERSIST_TASK["internal/task/device_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_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定向设备表"]
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 模型"]
end
MAIN --> ROUTER
ROUTER --> BRAND_H
ROUTER --> MODEL_H
ROUTER --> DEVICE_H
ROUTER --> OTA_H
BRAND_H --> BR_REPO
MODEL_H --> MD_REPO
DEVICE_H --> DEV_REPO
OTA_H --> OTA_REPO
BR_REPO --> BR_CACHE
MD_REPO --> MD_CACHE
BR_CACHE --> REDIS
MD_CACHE --> REDIS
BR_REPO --> DB_SQL
MD_REPO --> DB_SQL
DEV_REPO --> DB_SQL
OTA_REPO --> DB_SQL
PERSIST_TASK --> DEV_REPO
PERSIST_TASK --> REDIS
DB_SQL --> CFG_DB
CFG_APP --> CFG_DB
CFG_APP --> CFG_REDIS
BR_REPO --> M_BRAND
MD_REPO --> M_MODEL
DEV_REPO --> M_USER_DEVICE
DEV_REPO --> M_USER_ACTIVE
OTA_REPO --> M_OTA
MODEL_TBL --> DB_SQL
USER_DEVICE_TBL --> DB_SQL
USER_ACTIVE_TBL --> DB_SQL
OTA_TBL --> DB_SQL
OTA_TARGET_TBL --> DB_SQL
```
**图表来源**
@@ -102,8 +141,12 @@ MODEL_TBL --> DB_SQL
- [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/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/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/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
@@ -111,15 +154,24 @@ MODEL_TBL --> DB_SQL
- [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/task/device_persist.go:14-22](file://internal/task/device_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)
**章节来源**
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)
- [internal/router/router.go:14-41](file://internal/router/router.go#L14-L41)
- [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/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/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/redis.go:10-16](file://internal/cache/redis.go#L10-L16)
@@ -127,72 +179,99 @@ MODEL_TBL --> DB_SQL
- [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/task/device_persist.go:14-22](file://internal/task/device_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)
## 核心组件
- **BrandRepository**:负责品牌列表查询,支持缓存感知的全量查询和按名称模糊过滤,返回 Brand 领域对象切片。
- **ModelRepository**:负责型号列表查询,支持缓存感知的按品牌精确匹配、按型号名模糊匹配和全量查询,返回 Model 领域对象切片,并对可空字段进行 NullString 到指针字符串的安全转换。
- **DeviceRepository**:负责设备信息管理,支持按MAC地址查询设备、插入新设备、更新设备版本信息,以及用户活跃信息的查询和更新。
- **OTARepository**:负责固件升级信息查询,支持按型号、硬件版本、灰度标识查询最新OTA记录,黑名单检查、定向设备筛选等功能。
- **缓存层**BrandCache 和 ModelCache 提供多级缓存策略,支持全量缓存和按品牌缓存,具有智能刷新和降级机制。
- **设备持久化任务**DevicePersistTask 从Redis批量读取设备信息,异步写入MySQL user_device 和 user_active 表,支持数据可靠性保障。
- **数据库连接**:通过 sql.DB 统一管理连接池,设置最大打开连接数、空闲连接数与连接生命周期,并在启动时进行 Ping 校验。
- **配置加载**:从环境变量或默认值加载数据库和 Redis 配置,生产环境要求提供密码。
- **Handler 注入**Gin 控制器通过 NewXxxHandler 构造函数注入 *sql.DB 和缓存实例,再由 Handler 内部构造对应的 Repository 实例,形成清晰的依赖注入链路。
- **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:11-88](file://internal/repository/device.go#L11-L88)
- [internal/repository/ota.go:11-159](file://internal/repository/ota.go#L11-L159)
- [internal/cache/brand_cache.go:26-44](file://internal/cache/brand_cache.go#L26-L44)
- [internal/cache/model_cache.go:31-69](file://internal/cache/model_cache.go#L31-L69)
- [internal/task/device_persist.go:14-167](file://internal/task/device_persist.go#L14-L167)
- [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/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)
## 架构总览
数据访问层采用"仓储模式"Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。新增的缓存层提供多级缓存策略,支持智能数据刷新和降级机制。Handler 仅依赖 Repository 接口,Repository 依赖 *sql.DB 和缓存实例,配置与数据库模块负责基础设施初始化。整体流程如下:
数据访问层采用"仓储模式"Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。新增的缓存层提供多级缓存策略,支持智能数据刷新和降级机制。新增的设备持久化任务实现Redis到MySQL的异步数据同步。Handler 仅依赖 Repository 接口,Repository 依赖 *sql.DB 和缓存实例,配置与数据库模块负责基础设施初始化。整体流程如下:
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Router as "Gin 路由"
participant Handler as "BrandHandler/ModelHandler"
participant Repo as "BrandRepository/ModelRepository"
participant Cache as "BrandCache/ModelCache"
participant DeviceHandler as "DeviceHandler"
participant OTAHandler as "OTAHandler"
participant DeviceRepo as "DeviceRepository"
participant OTARepo as "OTARepository"
participant DB as "sql.DB"
participant Redis as "Redis 服务器"
participant MySQL as "MySQL 服务器"
Client->>Router : "HTTP 请求"
Router->>Handler : "分发到对应处理器"
Handler->>Repo : "调用 List(...)"
alt 缓存命中
Repo->>Cache : "GetAll/GetByBrand"
Cache->>Redis : "执行 GET"
Redis-->>Cache : "返回缓存数据"
Cache-->>Repo : "返回缓存数据"
else 缓存未命中或异常
Repo->>Cache : "GetAll/GetByBrand"
Cache->>Redis : "执行 GET"
Redis-->>Cache : "返回错误或空值"
Repo->>DB : "QueryContext(ctx, query, args...)"
DB->>MySQL : "执行 SQL"
MySQL-->>DB : "返回结果集"
DB-->>Repo : "Rows"
Repo->>Cache : "SetAll/SetByBrand"
Cache->>Redis : "执行 SET"
Redis-->>Cache : "确认存储"
Router->>DeviceHandler : "设备信息上报"
DeviceHandler->>Redis : "HSet devices"
Redis-->>DeviceHandler : "存储成功"
Note over DeviceHandler : "异步持久化任务"
DevicePersistTask->>Redis : "HGetAll devices"
Redis-->>DevicePersistTask : "批量数据"
DevicePersistTask->>DeviceRepo : "FindDeviceByMac"
DeviceRepo->>DB : "QueryContext"
DB-->>DeviceRepo : "设备信息"
alt 设备不存在
DevicePersistTask->>DeviceRepo : "InsertDevice"
DeviceRepo->>DB : "ExecContext"
DB-->>DeviceRepo : "插入成功"
else 设备存在
DevicePersistTask->>DeviceRepo : "UpdateDeviceVer"
DeviceRepo->>DB : "ExecContext"
DB-->>DeviceRepo : "更新成功"
end
Repo-->>Handler : "领域对象切片"
Handler-->>Client : "JSON 或 Base64 响应"
Router->>OTAHandler : "OTA升级查询"
OTAHandler->>OTARepo : "GetLatestOTA"
OTARepo->>DB : "QueryRowContext"
DB-->>OTARepo : "最新OTA记录"
OTAHandler->>OTARepo : "IsInBlackList"
OTARepo->>DB : "QueryRowContext"
DB-->>OTARepo : "黑名单检查结果"
alt 在黑名单中
OTAHandler->>OTARepo : "GetLatestOTANotInBlackList"
OTARepo->>DB : "QueryRowContext"
DB-->>OTARepo : "非黑名单最新记录"
else 不在黑名单
OTAHandler->>OTARepo : "FindTargetDevice"
OTARepo->>DB : "QueryRowContext"
DB-->>OTARepo : "定向设备检查结果"
end
OTAHandler-->>Client : "OTA升级信息"
```
**图表来源**
- [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/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/cache/brand_cache.go:26-44](file://internal/cache/brand_cache.go#L26-L44)
- [internal/cache/model_cache.go:31-69](file://internal/cache/model_cache.go#L31-L69)
- [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/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/task/device_persist.go:37-91](file://internal/task/device_persist.go#L37-L91)
- [internal/database/mysql.go:28-43](file://internal/database/mysql.go#L28-L43)
## 详细组件分析
@@ -312,36 +391,225 @@ ModelCache --> Model : "返回缓存数据"
- [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 安全处理数据库可空字段
- 复杂查询:支持子查询、条件筛选、时间范围检查等复杂业务逻辑
- **业务逻辑**
- 支持灰度发布(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
+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](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)
### 设备持久化任务设计与实现
- **设计模式**:定时任务模式,基于时间轮询的异步数据同步机制。
- **关键特性**
- 定时执行:使用 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)
### Handler 与 Repository 的协作
- Handler 通过 NewBrandHandler/NewModelHandler 注入 *sql.DB 和缓存实例,内部构造对应 Repository 实例。
- Handler 在 GetBrand/GetModel 中读取查询参数,调用 Repository.List(...),并将结果以 JSON 或 Base64 编码返回
- 错误处理:若 Repository 返回错误,Handler 记录日志并返回统一的内部错误响应
- 缓存感知:Handler 不需要关心缓存逻辑,只需调用 Repository 的标准方法即可享受缓存优势
- Handler 通过 NewXxxHandler 注入相应的 Repository 实例,内部执行具体的业务逻辑
- DeviceHandler 处理设备信息上报,将数据写入Redis Hash,等待异步持久化任务处理
- OTAHandler 处理OTA升级查询,调用OTARepository执行复杂的业务逻辑,包括黑名单检查、定向设备筛选等
- BrandHandler 和 ModelHandler 继续使用原有的缓存感知查询逻辑
- 错误处理:各 Handler 对 Repository 返回的错误进行统一处理和日志记录。
- 缓存感知:DeviceHandler 和 OTAHandler 不需要关心缓存逻辑,只需调用相应的 Repository 方法。
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "BrandHandler"
participant R as "BrandRepository"
participant RC as "Redis 客户端"
C->>H : "GET /audio/getBrand?brandName=..."
H->>R : "List(ctx, brandName)"
R->>RC : "GetAll(ctx)"
RC-->>R : "缓存命中/未命中"
alt 缓存命中
R-->>H : "[]Brand"
else 缓存未命中
R->>DB : "QueryContext(ctx, ...)"
DB-->>R : "rows"
R->>RC : "SetAll(ctx, allBrands)"
R-->>H : "[]Brand"
participant DH as "DeviceHandler"
participant DR as "DeviceRepository"
participant RT as "Redis 服务器"
C->>DH : "POST /audio/reportDevInfo"
DH->>RT : "HSet devices"
RT-->>DH : "存储成功"
DH-->>C : "操作成功"
participant OH as "OTAHandler"
participant OR as "OTARepository"
OH->>OR : "GetLatestOTA"
OR-->>OH : "最新OTA记录"
OH->>OR : "IsInBlackList"
OR-->>OH : "黑名单检查结果"
alt 在黑名单中
OH->>OR : "GetLatestOTANotInBlackList"
OR-->>OH : "非黑名单最新记录"
else 不在黑名单
OH->>OR : "FindTargetDevice"
OR-->>OH : "定向设备检查结果"
end
H-->>C : "JSON 或 Base64"
OH-->>C : "OTA升级信息"
```
**图表来源**
- [internal/handler/brand.go:25-48](file://internal/handler/brand.go#L25-L48)
- [internal/repository/brand.go:23-52](file://internal/repository/brand.go#L23-L52)
- [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/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/database/mysql.go:28-43](file://internal/database/mysql.go#L28-L43)
**章节来源**
@@ -349,8 +617,14 @@ H-->>C : "JSON 或 Base64"
- [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/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)
## 缓存策略详解
@@ -365,12 +639,14 @@ H-->>C : "JSON 或 Base64"
- **品牌全量缓存**`brand:all` - 存储所有品牌信息
- **型号按品牌缓存**`model:brand:{brandName}` - 存储特定品牌的所有型号
- **型号全量缓存**`model:all` - 存储所有型号信息
- **设备活跃缓存**`device:active:{date}` - 存储特定日期的活跃设备信息
### 缓存策略实现
- **缓存优先策略**:优先从缓存获取数据,缓存未命中时才查询数据库
- **智能降级策略**:Redis 异常时自动降级到数据库查询,保证系统可用性
- **自动回写策略**:成功从数据库获取数据后自动回写到缓存
- **TTL 策略**:所有缓存设置 30 分钟过期时间,平衡性能和数据新鲜度
- **设备数据特殊处理**:设备活跃信息采用分区表存储,缓存策略需考虑日期维度
### 预热机制
应用启动时会自动预热缓存,从数据库加载全量数据到 Redis:
@@ -383,7 +659,9 @@ C --> D[查询品牌全量数据]
D --> E[写入 brand:all 缓存]
E --> F[查询型号全量数据]
F --> G[写入 model:all 缓存]
G --> H[缓存预热完成]
G --> H[设备数据无需预热]
H --> I[OTA数据无需预热]
I --> J[缓存预热完成]
```
**图表来源**
@@ -396,9 +674,9 @@ G --> H[缓存预热完成]
## 依赖关系分析
- **外部依赖**Go MySQL Driver、Gin、Zap 日志、Redis 客户端、Meilisearch 客户端。
- **内部依赖**Handler 依赖 RepositoryRepository 依赖 *sql.DB 和缓存实例;缓存层依赖 Redis 客户端;数据库模块负责连接池与 Ping 校验;配置模块负责环境变量解析与校验。
- **内部依赖**Handler 依赖 RepositoryRepository 依赖 *sql.DB 和缓存实例;缓存层依赖 Redis 客户端;数据库模块负责连接池与 Ping 校验;配置模块负责环境变量解析与校验;任务模块依赖 Redis 和 Repository
- **循环依赖**:未发现循环依赖,职责边界清晰。
- **新增依赖**缓存层增加了对 Redis 客户端的依赖,以及对缓存配置的依赖
- **新增依赖**DeviceRepository 和 OTARepository 直接依赖 *sql.DB;设备持久化任务依赖 Redis 和 RepositoryOTA 查询逻辑依赖复杂的表关联
```mermaid
graph LR
@@ -414,14 +692,20 @@ CFG_LOAD --> CFG_DB["internal/config/database.go::loadDatabase"]
CFG_LOAD --> CFG_REDIS["internal/config/redis.go::loadRedis"]
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"]
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"]
BR_REPO --> BR_CACHE["internal/cache/brand_cache.go"]
MD_REPO --> MD_CACHE["internal/cache/model_cache.go"]
BR_CACHE --> REDIS_CLIENT["internal/cache/redis.go::NewClient"]
MD_CACHE --> REDIS_CLIENT
BR_REPO --> DB_SQL["*sql.DB"]
MD_REPO --> DB_SQL
DEV_REPO --> DB_SQL["*sql.DB"]
OTA_REPO --> DB_SQL
PERSIST_TASK --> DEV_REPO
PERSIST_TASK --> REDIS_CLIENT
BR_CACHE --> REDIS_CLIENT
MD_CACHE --> REDIS_CLIENT
```
@@ -435,8 +719,12 @@ MD_CACHE --> REDIS_CLIENT
- [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/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/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)
@@ -449,8 +737,12 @@ MD_CACHE --> REDIS_CLIENT
- [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/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/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)
@@ -468,15 +760,24 @@ MD_CACHE --> REDIS_CLIENT
- **查询优化**
- BrandRepository:按名称模糊匹配,建议在 name 上建立合适索引;对大结果集引入分页。
- ModelRepository:按品牌名精确匹配或按型号名模糊匹配,建议为 brand_name 与 name 建立索引;对大结果集引入分页。
- DeviceRepositoryuser_device 表的 MAC 地址建立唯一索引,user_active 表建立复合索引(mac_addr, active_date)。
- OTARepositoryota 表建立复合索引(model, hw, beta, status),black_list 表建立索引(ota_id, mac),ota_target_device 表建立索引(ota_id, mac_addr)。
- 应用层过滤:在缓存命中时进行过滤,减少数据库压力。
- **并发安全**
- *sql.DB 是并发安全的,可在多个 goroutine 中共享使用;Repository 实例不持有状态,亦可并发安全使用。
- Redis 客户端是并发安全的,支持多goroutine同时访问。
- 设备持久化任务使用独立的 goroutine 执行,避免阻塞主线程。
- **资源清理**
- 使用 defer rows.Close() 保证结果集关闭;在 main 中 defer db.Close() 保证应用退出时关闭连接池。
- Redis 连接在应用退出时自动关闭。
- 设备持久化任务使用 time.Ticker,确保正确停止。
- **事务处理**
- 当前实现均为只读查询,未涉及事务;如需写操作,应在 Repository 层封装事务,使用 sql.Tx 并在错误时回滚,成功时提交。
- 设备持久化任务采用幂等设计,避免重复处理造成的数据不一致。
- **异步处理优化**
- 设备信息上报采用异步持久化,提高响应速度
- Redis Hash 存储大量设备数据,内存占用较高,需监控内存使用情况
- 定时任务间隔可根据数据量调整,平衡实时性和系统负载
**章节来源**
- [internal/database/mysql.go:33-43](file://internal/database/mysql.go#L33-L43)
@@ -484,6 +785,8 @@ MD_CACHE --> REDIS_CLIENT
- [internal/cache/model_cache.go:13-16](file://internal/cache/model_cache.go#L13-L16)
- [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)
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)
## 故障排查指南
@@ -493,40 +796,54 @@ MD_CACHE --> REDIS_CLIENT
- **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)。
- **缓存查询错误**
- 现象:Handler 返回内部错误或缓存数据格式异常。
- 排查:查看日志中错误上下文(cache read/write/brand/model),定位具体环节;检查 Redis 连接状态;确认缓存数据格式正确。
- **查询错误**
- 现象:Handler 返回内部错误。
- 排查:查看日志中错误上下文(query brand/query model/scan brand/scan model/iterate brand/iterate model),定位具体环节;检查 SQL 参数绑定与字段映射。
- 排查:查看日志中错误上下文(query brand/query model/device/ota/scan brand/scan model/iterate brand/iterate model),定位具体环节;检查 SQL 参数绑定与字段映射。
- **结果为空**
- 现象:ModelRepository 在两种条件都为空时返回空切片
- 排查:确认传入的查询参数是否正确;检查表中是否存在匹配数据。
- 现象:DeviceRepository 在查询设备或活跃信息时返回空
- 排查:确认传入的查询参数是否正确;检查表中是否存在匹配数据;验证MAC地址格式
- **资源泄漏**
- 现象:长时间运行后连接数异常。
- 排查:确认是否遗漏 rows.Close();检查连接池配置是否合理;观察连接生命周期与空闲连接上限。
- **缓存未命中**
- 现象:频繁出现缓存未命中,数据库压力过大。
- 排查:检查缓存键是否正确;确认 TTL 设置是否合理;验证缓存预热是否成功;检查 Redis 内存使用情况。
- **性能问题**
- 现象:OTA查询响应慢或设备持久化延迟。
- 排查:检查相关表的索引是否完整;监控Redis内存使用;评估定时任务执行频率;分析数据库查询计划。
**章节来源**
- [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/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)
## 结论
Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知查询逻辑,实现了多级缓存策略和智能数据刷新机制。通过集成 Redis 缓存层,显著提升了查询性能和系统可用性。缓存感知查询逻辑、智能降级策略、预热机制自动回写功能共同构成了一个健壮数据访问层。建议后续继续优化缓存策略,增加缓存监控指标,完善缓存失效和更新机制,并持续改进监控与日志体系。
Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知查询逻辑,实现了多级缓存策略和智能数据刷新机制。新增的设备持久化仓库和OTA仓库进一步完善了数据访问层的功能,支持设备信息上报、固件升级查询等核心业务场景。通过集成 Redis 缓存层和异步持久化任务,显著提升了系统的响应性能和数据可靠性。缓存感知查询逻辑、智能降级策略、预热机制自动回写功能和异步数据同步共同构成了一个健壮、高效的现代化数据访问层。建议后续继续优化缓存策略,增加缓存监控指标,完善缓存失效和更新机制,并持续改进监控与日志体系。
## 附录:扩展新 Repository 指南
- **设计原则**
- 保持 Repository 无状态,仅依赖 *sql.DB 和缓存实例。
- 查询方法接收 context.Context,便于超时与取消控制。
- 对外返回领域模型(Model),避免直接暴露数据库结构。
- 对可空字段使用 sql.NullString 到指针字符串的安全转换。
- 对可空字段使用 sql.NullString、sql.NullInt64、sql.NullTime 等安全转换。
- 集成缓存感知查询逻辑,提供智能降级和自动回写功能。
- 考虑异步处理场景,设计幂等的持久化逻辑。
- **实现步骤**
- 定义领域模型(Model)与 Repository 接口/实现。
- 创建对应的缓存结构体,定义缓存键和TTL策略。
@@ -534,6 +851,7 @@ Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知
- 在路由中注册对应处理器。
- 在 main 中确保 *sql.DB 和缓存实例注入到 Handler。
- 实现缓存预热逻辑,确保应用启动时缓存可用。
- 如涉及异步处理,实现独立的任务调度器。
- **注意事项**
- 必须在每个查询后 defer rows.Close()。
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息。
@@ -543,13 +861,19 @@ Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知
- 实现缓存降级机制,确保Redis异常时系统仍可正常工作。
- 设计合理的缓存键和TTL策略,平衡性能和数据新鲜度。
- 实现缓存预热和自动回写机制,提升用户体验。
- 异步任务需考虑幂等性,避免重复处理造成数据不一致。
- 监控异步任务的执行状态和错误日志。
**章节来源**
- [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/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/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/router/router.go:22-25](file://internal/router/router.go#L22-L25)
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)