feat(cache): 完善缓存系统实现及集成数据访问层
- 新增BrandCache与ModelCache,实现多级缓存和TTL管理 - 引入缓存预热机制,启动时自动加载品牌与型号数据 - 实现缓存穿透防护和数据库降级策略,提升服务稳定性 - 优化缓存键命名和数据序列化策略,增强管理便捷性 - 数据访问层新增缓存感知查询,品牌与型号仓库支持缓存优先 - 调整整体架构,增强组件解耦及依赖注入链路清晰度 - 提供详细的性能优化建议和故障排查指南 - 补充监控指标说明,便于后续运维与监控扩展
This commit is contained in:
@@ -20,48 +20,59 @@
|
||||
- [internal/middleware/request_id.go](file://internal/middleware/request_id.go)
|
||||
- [internal/model/brand.go](file://internal/model/brand.go)
|
||||
- [internal/model/model.go](file://internal/model/model.go)
|
||||
- [internal/cache/brand_cache.go](file://internal/cache/brand_cache.go)
|
||||
- [internal/cache/model_cache.go](file://internal/cache/model_cache.go)
|
||||
</cite>
|
||||
|
||||
## 更新摘要
|
||||
**变更内容**
|
||||
- 更新依赖注入模式:所有处理器现在通过构造函数接收依赖项实例
|
||||
- 新增仓储层依赖:BrandHandler 和 ModelHandler 现在依赖仓储实例而非直接数据库连接
|
||||
- 更新路由装配:路由在启动时集中装配依赖并传递给处理器
|
||||
- 简化处理器实现:移除了对全局数据库连接的直接依赖
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [项目结构](#项目结构)
|
||||
3. [核心组件](#核心组件)
|
||||
4. [架构总览](#架构总览)
|
||||
5. [详细组件分析](#详细组件分析)
|
||||
6. [依赖分析](#依赖分析)
|
||||
7. [性能考虑](#性能考虑)
|
||||
8. [故障排查指南](#故障排查指南)
|
||||
9. [结论](#结论)
|
||||
10. [附录](#附录)
|
||||
6. [依赖注入模式详解](#依赖注入模式详解)
|
||||
7. [依赖分析](#依赖分析)
|
||||
8. [性能考虑](#性能考虑)
|
||||
9. [故障排查指南](#故障排查指南)
|
||||
10. [结论](#结论)
|
||||
11. [附录](#附录)
|
||||
|
||||
## 简介
|
||||
本文件面向 Luxsin 应用 API 的业务处理器模块,系统性梳理各处理器的职责、接口设计与实现细节,覆盖 BrandHandler、ModelHandler、ModelListHandler、DeviceHandler 与 HealthHandler。文档同时阐述依赖注入模式、错误处理机制、响应格式化策略,以及与 Gin 框架的集成方式;并通过流程图与类图展示处理器间的协作关系与数据流转过程,帮助初学者快速上手,同时为高级开发者提供深入的技术参考。
|
||||
本文件面向 Luxsin 应用 API 的业务处理器模块,系统性梳理各处理器的职责、接口设计与实现细节,覆盖 BrandHandler、ModelHandler、ModelListHandler、DeviceHandler 与 HealthHandler。文档重点阐述新的依赖注入模式、错误处理机制、响应格式化策略,以及与 Gin 框架的集成方式;并通过流程图与类图展示处理器间的协作关系与数据流转过程,帮助初学者快速上手,同时为高级开发者提供深入的技术参考。
|
||||
|
||||
## 项目结构
|
||||
业务处理器位于 internal/handler 目录,围绕“控制器-仓储-搜索-缓存-响应”的分层组织,配合中间件与路由装配,形成清晰的控制流与依赖注入入口。
|
||||
业务处理器位于 internal/handler 目录,采用依赖注入模式,围绕"控制器-仓储-搜索-缓存-响应"的分层组织,配合中间件与路由装配,形成清晰的控制流与依赖注入入口。
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "应用入口"
|
||||
MAIN["cmd/server/main.go<br/>启动与配置加载"]
|
||||
end
|
||||
END
|
||||
subgraph "路由与中间件"
|
||||
ROUTER["internal/router/router.go<br/>路由注册与中间件装配"]
|
||||
ROUTER["internal/router/router.go<br/>路由注册与依赖注入"]
|
||||
CORS["internal/middleware/cors.go"]
|
||||
LOGMW["internal/middleware/logger.go"]
|
||||
REQID["internal/middleware/request_id.go"]
|
||||
end
|
||||
subgraph "业务处理器"
|
||||
HEALTH["internal/handler/health.go"]
|
||||
BRAND["internal/handler/brand.go"]
|
||||
MODEL["internal/handler/model.go"]
|
||||
MODELLIST["internal/handler/model_list.go"]
|
||||
DEVICE["internal/handler/device.go"]
|
||||
HEALTH["internal/handler/health.go<br/>NewHealthHandler()"]
|
||||
BRAND["internal/handler/brand.go<br/>NewBrandHandler(brandRepo, log)"]
|
||||
MODEL["internal/handler/model.go<br/>NewModelHandler(modelRepo, log)"]
|
||||
MODELLIST["internal/handler/model_list.go<br/>NewModelListHandler(searchClient, log)"]
|
||||
DEVICE["internal/handler/device.go<br/>NewDeviceHandler(redis, log)"]
|
||||
end
|
||||
subgraph "基础设施"
|
||||
RESP["internal/response/response.go<br/>统一响应体"]
|
||||
ENCODE["pkg/encode/base64.go<br/>自定义Base64编码"]
|
||||
SEARCH["internal/search/meilisearch.go<br/>Meilisearch客户端"]
|
||||
CACHE["internal/cache/*<br/>Redis缓存"]
|
||||
end
|
||||
MAIN --> ROUTER
|
||||
ROUTER --> CORS
|
||||
@@ -77,43 +88,42 @@ MODEL --> RESP
|
||||
MODELLIST --> RESP
|
||||
DEVICE --> RESP
|
||||
MODELLIST --> SEARCH
|
||||
BRAND --> CACHE
|
||||
MODEL --> CACHE
|
||||
BRAND --> ENCODE
|
||||
MODEL --> ENCODE
|
||||
MODELLIST --> ENCODE
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [cmd/server/main.go:22-96](file://cmd/server/main.go#L22-L96)
|
||||
- [internal/router/router.go:14-42](file://internal/router/router.go#L14-L42)
|
||||
- [internal/handler/health.go:8-19](file://internal/handler/health.go#L8-L19)
|
||||
- [internal/handler/brand.go:14-50](file://internal/handler/brand.go#L14-L50)
|
||||
- [internal/handler/model.go:14-51](file://internal/handler/model.go#L14-L51)
|
||||
- [internal/handler/model_list.go:14-57](file://internal/handler/model_list.go#L14-L57)
|
||||
- [internal/handler/device.go:14-85](file://internal/handler/device.go#L14-L85)
|
||||
- [internal/response/response.go:9-37](file://internal/response/response.go#L9-L37)
|
||||
- [pkg/encode/base64.go:8-52](file://pkg/encode/base64.go#L8-L52)
|
||||
- [internal/search/meilisearch.go:13-46](file://internal/search/meilisearch.go#L13-L46)
|
||||
**图表来源**
|
||||
- [cmd/server/main.go:25-101](file://cmd/server/main.go#L25-L101)
|
||||
- [internal/router/router.go:16-52](file://internal/router/router.go#L16-L52)
|
||||
- [internal/handler/health.go:10](file://internal/handler/health.go#L10)
|
||||
- [internal/handler/brand.go:18](file://internal/handler/brand.go#L18)
|
||||
- [internal/handler/model.go:18](file://internal/handler/model.go#L18)
|
||||
- [internal/handler/model_list.go:19](file://internal/handler/model_list.go#L19)
|
||||
- [internal/handler/device.go:19](file://internal/handler/device.go#L19)
|
||||
|
||||
章节来源
|
||||
- [internal/router/router.go:14-42](file://internal/router/router.go#L14-L42)
|
||||
- [cmd/server/main.go:22-96](file://cmd/server/main.go#L22-L96)
|
||||
**章节来源**
|
||||
- [internal/router/router.go:16-52](file://internal/router/router.go#L16-L52)
|
||||
- [cmd/server/main.go:25-101](file://cmd/server/main.go#L25-L101)
|
||||
|
||||
## 核心组件
|
||||
- 健康检查处理器:提供轻量级健康状态返回,便于外部探活与编排。
|
||||
- 品牌查询处理器:按品牌名模糊查询品牌列表,支持可选的自定义 Base64 响应。
|
||||
- 型号查询处理器:按品牌或型号关键字查询型号列表,支持可选的自定义 Base64 响应。
|
||||
- 型号检索处理器:基于 Meilisearch 执行全文检索,支持可选的自定义 Base64 响应。
|
||||
- 设备上报处理器:接收设备信息(MAC、型号、版本、来源 IP),写入 Redis Hash。
|
||||
- **健康检查处理器**:提供轻量级健康状态返回,便于外部探活与编排。
|
||||
- **品牌查询处理器**:按品牌名模糊查询品牌列表,现在依赖 BrandRepository 实例。
|
||||
- **型号查询处理器**:按品牌或型号关键字查询型号列表,现在依赖 ModelRepository 实例。
|
||||
- **型号检索处理器**:基于 Meilisearch 执行全文检索,依赖搜索客户端实例。
|
||||
- **设备上报处理器**:接收设备信息(MAC、型号、版本、来源 IP),依赖 Redis 客户端。
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/handler/health.go:8-19](file://internal/handler/health.go#L8-L19)
|
||||
- [internal/handler/brand.go:14-50](file://internal/handler/brand.go#L14-L50)
|
||||
- [internal/handler/model.go:14-51](file://internal/handler/model.go#L14-L51)
|
||||
- [internal/handler/model_list.go:14-57](file://internal/handler/model_list.go#L14-L57)
|
||||
- [internal/handler/device.go:14-85](file://internal/handler/device.go#L14-L85)
|
||||
- [internal/handler/brand.go:13-23](file://internal/handler/brand.go#L13-L23)
|
||||
- [internal/handler/model.go:13-23](file://internal/handler/model.go#L13-L23)
|
||||
- [internal/handler/model_list.go:14-24](file://internal/handler/model_list.go#L14-L24)
|
||||
- [internal/handler/device.go:14-24](file://internal/handler/device.go#L14-L24)
|
||||
|
||||
## 架构总览
|
||||
下图展示从请求进入至响应返回的关键路径,以及处理器与仓储、搜索、缓存、日志等组件的交互。
|
||||
下图展示从请求进入至响应返回的关键路径,以及处理器与仓储、搜索、缓存、日志等组件的交互,体现了完整的依赖注入架构。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -130,149 +140,162 @@ alt "品牌/型号查询"
|
||||
H->>REPO : "List(ctx, filters)"
|
||||
REPO-->>H : "结果集"
|
||||
else "型号检索"
|
||||
H->>S : "SearchWithContext(key, opts)"
|
||||
H->>S : "ModelList(ctx, key, count)"
|
||||
S-->>H : "命中结果"
|
||||
else "设备上报"
|
||||
H->>RC : "HSet(mac, json)"
|
||||
H->>RC : "HSet(ctx, key, mac, json)"
|
||||
RC-->>H : "OK 或错误"
|
||||
end
|
||||
H->>L : "记录日志/错误"
|
||||
H-->>C : "JSON 或自定义Base64响应"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/router/router.go:27-38](file://internal/router/router.go#L27-L38)
|
||||
- [internal/handler/brand.go:26-49](file://internal/handler/brand.go#L26-L49)
|
||||
- [internal/handler/model.go:26-50](file://internal/handler/model.go#L26-L50)
|
||||
- [internal/handler/model_list.go:26-56](file://internal/handler/model_list.go#L26-L56)
|
||||
- [internal/handler/device.go:26-84](file://internal/handler/device.go#L26-L84)
|
||||
- [internal/repository/brand.go:20-50](file://internal/repository/brand.go#L20-L50)
|
||||
- [internal/repository/model.go:20-61](file://internal/repository/model.go#L20-L61)
|
||||
**图表来源**
|
||||
- [internal/router/router.go:32-36](file://internal/router/router.go#L32-L36)
|
||||
- [internal/handler/brand.go:29](file://internal/handler/brand.go#L29)
|
||||
- [internal/handler/model.go:30](file://internal/handler/model.go#L30)
|
||||
- [internal/handler/model_list.go:37](file://internal/handler/model_list.go#L37)
|
||||
- [internal/handler/device.go:71](file://internal/handler/device.go#L71)
|
||||
- [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/search/meilisearch.go:22-45](file://internal/search/meilisearch.go#L22-L45)
|
||||
|
||||
## 详细组件分析
|
||||
|
||||
### 健康检查处理器(HealthHandler)
|
||||
- 职责:对外暴露健康检查端点,返回统一响应体中的状态字段。
|
||||
- 接口设计:无状态对象,构造函数仅初始化空实例。
|
||||
- 实现要点:
|
||||
- **职责**:对外暴露健康检查端点,返回统一响应体中的状态字段。
|
||||
- **接口设计**:无状态对象,构造函数仅初始化空实例。
|
||||
- **实现要点**:
|
||||
- 使用统一响应体封装返回值。
|
||||
- 适合被反向代理或编排系统定期探测。
|
||||
- 典型调用路径:/api/v1/health
|
||||
- **典型调用路径**:/api/v1/health
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/handler/health.go:8-19](file://internal/handler/health.go#L8-L19)
|
||||
- [internal/response/response.go:9-37](file://internal/response/response.go#L9-L37)
|
||||
- [internal/router/router.go:29](file://internal/router/router.go#L29)
|
||||
- [internal/response/response.go:15-21](file://internal/response/response.go#L15-L21)
|
||||
- [internal/router/router.go:40](file://internal/router/router.go#L40)
|
||||
|
||||
### 品牌处理器(BrandHandler)
|
||||
- 职责:根据品牌名称模糊查询品牌列表。
|
||||
- 输入参数:
|
||||
- **职责**:根据品牌名称模糊查询品牌列表。
|
||||
- **输入参数**:
|
||||
- 查询字符串:brandName(可选)
|
||||
- 查询字符串:base64Resp(可选,默认开启)
|
||||
- 处理流程:
|
||||
- **处理流程**:
|
||||
- 读取查询参数并解析 base64Resp。
|
||||
- 调用仓储层执行数据库查询。
|
||||
- 若开启 base64Resp,则对结果进行 JSON 编码后返回字符串;否则直接返回 JSON。
|
||||
- 错误处理:
|
||||
- **错误处理**:
|
||||
- 仓储查询失败时记录错误并返回统一内部错误。
|
||||
- 编码失败时同样返回统一内部错误。
|
||||
- 数据模型:Brand
|
||||
- **数据模型**:Brand
|
||||
- **依赖注入**:通过 NewBrandHandler 构造函数注入 BrandRepository 和 zap.Logger
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BrandHandler {
|
||||
-repo : "BrandRepository"
|
||||
-log : "zap.Logger"
|
||||
+GetBrand(c)
|
||||
- repo : "BrandRepository"
|
||||
- log : "zap.Logger"
|
||||
+ NewBrandHandler(repo, log) : "BrandHandler"
|
||||
+ GetBrand(c)
|
||||
}
|
||||
class BrandRepository {
|
||||
-db : "sql.DB"
|
||||
+List(ctx, brandName) : "[]Brand,error"
|
||||
- db : "sql.DB"
|
||||
- cache : "BrandCache"
|
||||
+ NewBrandRepository(db, cache) : "BrandRepository"
|
||||
+ List(ctx, brandName) : "[]Brand,error"
|
||||
}
|
||||
class Brand {
|
||||
+int id
|
||||
+string name
|
||||
+ ID : "int"
|
||||
+ Name : "string"
|
||||
}
|
||||
BrandHandler --> BrandRepository : "依赖"
|
||||
BrandHandler --> BrandRepository : "依赖注入"
|
||||
BrandRepository --> Brand : "返回"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/brand.go:14-50](file://internal/handler/brand.go#L14-L50)
|
||||
- [internal/repository/brand.go:12-50](file://internal/repository/brand.go#L12-L50)
|
||||
**图表来源**
|
||||
- [internal/handler/brand.go:13-23](file://internal/handler/brand.go#L13-L23)
|
||||
- [internal/handler/brand.go:25-48](file://internal/handler/brand.go#L25-L48)
|
||||
- [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/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
|
||||
|
||||
章节来源
|
||||
- [internal/handler/brand.go:14-50](file://internal/handler/brand.go#L14-L50)
|
||||
- [internal/repository/brand.go:20-50](file://internal/repository/brand.go#L20-L50)
|
||||
**章节来源**
|
||||
- [internal/handler/brand.go:13-48](file://internal/handler/brand.go#L13-L48)
|
||||
- [internal/repository/brand.go:23-52](file://internal/repository/brand.go#L23-L52)
|
||||
- [internal/model/brand.go:3-7](file://internal/model/brand.go#L3-L7)
|
||||
|
||||
### 型号处理器(ModelHandler)
|
||||
- 职责:根据品牌或型号关键字查询型号列表。
|
||||
- 输入参数:
|
||||
- **职责**:根据品牌或型号关键字查询型号列表。
|
||||
- **输入参数**:
|
||||
- 查询字符串:brandName(可选)
|
||||
- 查询字符串:modelName(可选)
|
||||
- 查询字符串:base64Resp(可选,默认开启)
|
||||
- 处理流程:
|
||||
- **处理流程**:
|
||||
- 读取查询参数并解析 base64Resp。
|
||||
- 调用仓储层执行数据库查询。
|
||||
- 结果处理与品牌处理器一致。
|
||||
- 错误处理:
|
||||
- **错误处理**:
|
||||
- 仓储查询失败时记录错误并返回统一内部错误。
|
||||
- 编码失败时同样返回统一内部错误。
|
||||
- 数据模型:Model
|
||||
- **数据模型**:Model
|
||||
- **依赖注入**:通过 NewModelHandler 构造函数注入 ModelRepository 和 zap.Logger
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class ModelHandler {
|
||||
-repo : "ModelRepository"
|
||||
-log : "zap.Logger"
|
||||
+GetModel(c)
|
||||
- repo : "ModelRepository"
|
||||
- log : "zap.Logger"
|
||||
+ NewModelHandler(repo, log) : "ModelHandler"
|
||||
+ GetModel(c)
|
||||
}
|
||||
class ModelRepository {
|
||||
-db : "sql.DB"
|
||||
+List(ctx, brandName, modelName) : "[]Model,error"
|
||||
- db : "sql.DB"
|
||||
- cache : "ModelCache"
|
||||
+ NewModelRepository(db, cache) : "ModelRepository"
|
||||
+ List(ctx, brandName, modelName) : "[]Model,error"
|
||||
}
|
||||
class Model {
|
||||
+int id
|
||||
+string brandName
|
||||
+string name
|
||||
+*string form
|
||||
+*string rig
|
||||
+*string source
|
||||
+*string eqKey
|
||||
+time createAt
|
||||
+ ID : "int"
|
||||
+ BrandName : "string"
|
||||
+ Name : "string"
|
||||
+ Form : "*string"
|
||||
+ Rig : "*string"
|
||||
+ Source : "*string"
|
||||
+ EqKey : "*string"
|
||||
+ CreateAt : "time.Time"
|
||||
}
|
||||
ModelHandler --> ModelRepository : "依赖"
|
||||
ModelHandler --> ModelRepository : "依赖注入"
|
||||
ModelRepository --> Model : "返回"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/model.go:14-51](file://internal/handler/model.go#L14-L51)
|
||||
- [internal/repository/model.go:12-61](file://internal/repository/model.go#L12-L61)
|
||||
**图表来源**
|
||||
- [internal/handler/model.go:13-23](file://internal/handler/model.go#L13-L23)
|
||||
- [internal/handler/model.go:25-49](file://internal/handler/model.go#L25-L49)
|
||||
- [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/model/model.go:5-15](file://internal/model/model.go#L5-L15)
|
||||
|
||||
章节来源
|
||||
- [internal/handler/model.go:14-51](file://internal/handler/model.go#L14-L51)
|
||||
- [internal/repository/model.go:20-61](file://internal/repository/model.go#L20-L61)
|
||||
**章节来源**
|
||||
- [internal/handler/model.go:13-49](file://internal/handler/model.go#L13-L49)
|
||||
- [internal/repository/model.go:23-79](file://internal/repository/model.go#L23-L79)
|
||||
- [internal/model/model.go:5-15](file://internal/model/model.go#L5-L15)
|
||||
|
||||
### 型号检索处理器(ModelListHandler)
|
||||
- 职责:基于 Meilisearch 执行全文检索,返回匹配的型号元数据。
|
||||
- 输入参数:
|
||||
- **职责**:基于 Meilisearch 执行全文检索,返回匹配的型号元数据。
|
||||
- **输入参数**:
|
||||
- 查询字符串:key(必需)
|
||||
- 查询字符串:count(可选,默认 100)
|
||||
- 查询字符串:base64Resp(可选,默认开启)
|
||||
- 处理流程:
|
||||
- **处理流程**:
|
||||
- 读取查询参数并解析 base64Resp。
|
||||
- 限制 count 的最大值以避免过大的返回量。
|
||||
- 调用搜索客户端执行检索。
|
||||
- 结果处理与前两个处理器一致。
|
||||
- 错误处理:
|
||||
- **错误处理**:
|
||||
- 检索失败时记录错误并返回统一内部错误。
|
||||
- 编码失败时同样返回统一内部错误。
|
||||
- 数据模型:map[string]any(由搜索结果解码而来)
|
||||
- **数据模型**:map[string]any(由搜索结果解码而来)
|
||||
- **依赖注入**:通过 NewModelListHandler 构造函数注入 search.Client 和 zap.Logger
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -283,41 +306,42 @@ participant E as "自定义Base64"
|
||||
participant R as "统一响应"
|
||||
C->>ML : "GET /audio/modelList?key=...&count=..."
|
||||
ML->>ML : "解析参数与count限制"
|
||||
ML->>S : "SearchWithContext(key, limit, attributes)"
|
||||
ML->>S : "ModelList(ctx, key, count)"
|
||||
S-->>ML : "hits"
|
||||
ML->>E : "可选:EncodeJSON(hits)"
|
||||
ML-->>C : "JSON 或 Base64(JSON)"
|
||||
```
|
||||
|
||||
图表来源
|
||||
**图表来源**
|
||||
- [internal/handler/model_list.go:26-56](file://internal/handler/model_list.go#L26-L56)
|
||||
- [internal/search/meilisearch.go:22-45](file://internal/search/meilisearch.go#L22-L45)
|
||||
- [pkg/encode/base64.go:35-51](file://pkg/encode/base64.go#L35-L51)
|
||||
- [internal/response/response.go:15-36](file://internal/response/response.go#L15-L36)
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/handler/model_list.go:14-57](file://internal/handler/model_list.go#L14-L57)
|
||||
- [internal/search/meilisearch.go:22-45](file://internal/search/meilisearch.go#L22-L45)
|
||||
- [pkg/encode/base64.go:35-51](file://pkg/encode/base64.go#L35-L51)
|
||||
|
||||
### 设备上报处理器(DeviceHandler)
|
||||
- 职责:接收设备上报信息(MAC、型号、版本、来源 IP),写入 Redis Hash。
|
||||
- 输入参数:
|
||||
- **职责**:接收设备上报信息(MAC、型号、版本、来源 IP),写入 Redis Hash。
|
||||
- **输入参数**:
|
||||
- 查询字符串:mac(必填)
|
||||
- 查询字符串:model(必填)
|
||||
- 查询字符串:ver(可选)
|
||||
- 处理流程:
|
||||
- **处理流程**:
|
||||
- 校验必填参数,若缺失则返回统一错误响应。
|
||||
- 组装设备信息结构体(含活跃日期、来源 IP、版本)。
|
||||
- 将结构体序列化为 JSON 并写入 Redis Hash。
|
||||
- 返回统一成功响应。
|
||||
- 错误处理:
|
||||
- **错误处理**:
|
||||
- 参数校验失败返回统一错误。
|
||||
- JSON 序列化失败返回统一错误。
|
||||
- Redis 写入失败返回统一错误。
|
||||
- 并发与安全:
|
||||
- **并发与安全**:
|
||||
- Redis HSet 是原子操作,适合高并发场景。
|
||||
- 建议对 MAC 去除空白字符,避免重复键。
|
||||
- **依赖注入**:通过 NewDeviceHandler 构造函数注入 redis.Client 和 zap.Logger
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@@ -327,7 +351,7 @@ Validate -- 否 --> RespFail["返回统一错误响应"]
|
||||
Validate -- 是 --> BuildInfo["组装设备信息结构体"]
|
||||
BuildInfo --> Marshal{"JSON序列化成功?"}
|
||||
Marshal -- 否 --> RespFail
|
||||
Marshal -- 是 --> HSet["Redis HSet(mac, json)"]
|
||||
Marshal -- 是 --> HSet["Redis HSet(ctx, key, mac, json)"]
|
||||
HSet --> SetOK{"写入成功?"}
|
||||
SetOK -- 否 --> RespFail
|
||||
SetOK -- 是 --> RespOK["返回统一成功响应"]
|
||||
@@ -335,20 +359,102 @@ RespFail --> End(["结束"])
|
||||
RespOK --> End
|
||||
```
|
||||
|
||||
图表来源
|
||||
**图表来源**
|
||||
- [internal/handler/device.go:26-84](file://internal/handler/device.go#L26-L84)
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/handler/device.go:14-85](file://internal/handler/device.go#L14-L85)
|
||||
|
||||
## 依赖注入模式详解
|
||||
|
||||
### 依赖注入架构
|
||||
所有处理器现在都采用构造函数依赖注入模式,通过 New 函数接收所需的依赖项实例:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
MAIN["main.go<br/>应用启动"] --> ROUTER["router.New()<br/>集中装配依赖"]
|
||||
ROUTER --> DB["sql.DB<br/>数据库连接"]
|
||||
ROUTER --> RDB["redis.Client<br/>Redis客户端"]
|
||||
ROUTER --> SEARCH["search.Client<br/>Meilisearch客户端"]
|
||||
ROUTER --> LOG["zap.Logger<br/>日志器"]
|
||||
ROUTER --> BRANCACHE["cache.BrandCache<br/>品牌缓存"]
|
||||
ROUTER --> MODELCACHE["cache.ModelCache<br/>型号缓存"]
|
||||
ROUTER --> BRANDREPO["repository.NewBrandRepository()<br/>品牌仓储"]
|
||||
ROUTER --> MODELREPO["repository.NewModelRepository()<br/>型号仓储"]
|
||||
ROUTER --> HEALTH["handler.NewHealthHandler()<br/>健康检查处理器"]
|
||||
ROUTER --> BRAND["handler.NewBrandHandler()<br/>品牌处理器"]
|
||||
ROUTER --> MODEL["handler.NewModelHandler()<br/>型号处理器"]
|
||||
ROUTER --> MODELLIST["handler.NewModelListHandler()<br/>型号检索处理器"]
|
||||
ROUTER --> DEVICE["handler.NewDeviceHandler()<br/>设备上报处理器"]
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
- [cmd/server/main.go:53-69](file://cmd/server/main.go#L53-L69)
|
||||
- [internal/router/router.go:16-36](file://internal/router/router.go#L16-L36)
|
||||
|
||||
### 依赖注入优势
|
||||
- **测试友好**:可以轻松注入模拟对象进行单元测试
|
||||
- **解耦**:处理器不再直接依赖具体实现,只依赖抽象接口
|
||||
- **可配置**:运行时可以注入不同的实现
|
||||
- **生命周期管理**:依赖项的创建和销毁由路由集中管理
|
||||
|
||||
### 仓储层依赖
|
||||
BrandRepository 和 ModelRepository 现在接收 Redis 缓存实例作为依赖:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BrandRepository {
|
||||
- db : "sql.DB"
|
||||
- cache : "BrandCache"
|
||||
+ NewBrandRepository(db, cache) : "BrandRepository"
|
||||
+ List(ctx, brandName) : "[]Brand,error"
|
||||
+ ListFromDB(ctx, brandName) : "[]Brand,error"
|
||||
}
|
||||
class ModelRepository {
|
||||
- db : "sql.DB"
|
||||
- cache : "ModelCache"
|
||||
+ NewModelRepository(db, cache) : "ModelRepository"
|
||||
+ List(ctx, brandName, modelName) : "[]Model,error"
|
||||
+ ListAllFromDB(ctx) : "[]Model,error"
|
||||
+ ListByBrandFromDB(ctx, brandName) : "[]Model,error"
|
||||
+ ListByModelFromDB(ctx, modelName) : "[]Model,error"
|
||||
}
|
||||
class BrandCache {
|
||||
- rdb : "redis.Client"
|
||||
+ NewBrandCache(rdb) : "BrandCache"
|
||||
+ GetAll(ctx) : "[]Brand,error"
|
||||
+ SetAll(ctx, list) : "error"
|
||||
}
|
||||
class ModelCache {
|
||||
- rdb : "redis.Client"
|
||||
+ NewModelCache(rdb) : "ModelCache"
|
||||
+ GetByBrand(ctx, brandName) : "[]Model,error"
|
||||
+ SetByBrand(ctx, brandName, list) : "error"
|
||||
+ GetAll(ctx) : "[]Model,error"
|
||||
+ SetAll(ctx, list) : "error"
|
||||
}
|
||||
BrandRepository --> BrandCache : "依赖注入"
|
||||
ModelRepository --> ModelCache : "依赖注入"
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
- [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/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/router/router.go:16-36](file://internal/router/router.go#L16-L36)
|
||||
- [cmd/server/main.go:53-69](file://cmd/server/main.go#L53-L69)
|
||||
|
||||
## 依赖分析
|
||||
- 依赖注入模式:
|
||||
- 控制器通过构造函数注入仓储、搜索客户端、Redis 客户端与日志器。
|
||||
- **依赖注入模式**:
|
||||
- 所有处理器通过构造函数注入依赖项,包括仓储、搜索客户端、Redis 客户端与日志器。
|
||||
- 路由在应用启动时集中装配,保证依赖一次性构建与共享。
|
||||
- 组件耦合:
|
||||
- **组件耦合**:
|
||||
- 处理器与仓储之间为单向依赖,职责清晰。
|
||||
- 搜索与缓存作为外部服务,通过客户端封装接入。
|
||||
- 可能的循环依赖:
|
||||
- **可能的循环依赖**:
|
||||
- 当前结构未见循环导入,符合 Go 包管理最佳实践。
|
||||
|
||||
```mermaid
|
||||
@@ -363,6 +469,10 @@ BRAND --> BREPO["repository/brand.go"]
|
||||
MODEL --> MREPO["repository/model.go"]
|
||||
MODELLIST --> SEARCH["search/meilisearch.go"]
|
||||
DEVICE --> REDIS["Redis 客户端"]
|
||||
BRAND --> BREPO
|
||||
BREPO --> BCACHE["cache/brand_cache.go"]
|
||||
MODEL --> MREPO
|
||||
MREPO --> MCACHE["cache/model_cache.go"]
|
||||
BRAND --> ENCODE["pkg/encode/base64.go"]
|
||||
MODEL --> ENCODE
|
||||
MODELLIST --> ENCODE
|
||||
@@ -370,62 +480,70 @@ ALL["各处理器"] --> RESP["internal/response/response.go"]
|
||||
ALL --> LOG["zap.Logger"]
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [cmd/server/main.go:64](file://cmd/server/main.go#L64)
|
||||
- [internal/router/router.go:21-25](file://internal/router/router.go#L21-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/model_list.go:19-24](file://internal/handler/model_list.go#L19-L24)
|
||||
- [internal/handler/device.go:19-24](file://internal/handler/device.go#L19-L24)
|
||||
- [internal/repository/brand.go:16](file://internal/repository/brand.go#L16)
|
||||
- [internal/repository/model.go:16](file://internal/repository/model.go#L16)
|
||||
**图表来源**
|
||||
- [cmd/server/main.go:67-70](file://cmd/server/main.go#L67-L70)
|
||||
- [internal/router/router.go:27-36](file://internal/router/router.go#L27-L36)
|
||||
- [internal/handler/brand.go:18](file://internal/handler/brand.go#L18)
|
||||
- [internal/handler/model.go:18](file://internal/handler/model.go#L18)
|
||||
- [internal/handler/model_list.go:19](file://internal/handler/model_list.go#L19)
|
||||
- [internal/handler/device.go:19](file://internal/handler/device.go#L19)
|
||||
- [internal/repository/brand.go:19](file://internal/repository/brand.go#L19)
|
||||
- [internal/repository/model.go:19](file://internal/repository/model.go#L19)
|
||||
- [internal/search/meilisearch.go:17](file://internal/search/meilisearch.go#L17)
|
||||
- [pkg/encode/base64.go:8](file://pkg/encode/base64.go#L8)
|
||||
- [internal/response/response.go:9](file://internal/response/response.go#L9)
|
||||
|
||||
章节来源
|
||||
- [internal/router/router.go:14-42](file://internal/router/router.go#L14-L42)
|
||||
- [cmd/server/main.go:22-96](file://cmd/server/main.go#L22-L96)
|
||||
**章节来源**
|
||||
- [internal/router/router.go:16-52](file://internal/router/router.go#L16-L52)
|
||||
- [cmd/server/main.go:25-101](file://cmd/server/main.go#L25-L101)
|
||||
|
||||
## 性能考虑
|
||||
- 响应体积优化
|
||||
- **响应体积优化**
|
||||
- 对于大结果集,优先启用自定义 Base64 响应,减少传输体积与字符转义开销。
|
||||
- 检索处理器对 count 进行上限控制,避免超大数据量返回。
|
||||
- 数据库查询
|
||||
- **数据库查询**
|
||||
- 品牌与型号查询均使用 ORDER BY 与 LIKE,建议在数据库侧建立合适索引以提升模糊查询性能。
|
||||
- 搜索与缓存
|
||||
- 仓储层现在支持缓存降级,当缓存不可用时自动回退到数据库查询。
|
||||
- **搜索与缓存**
|
||||
- Meilisearch 适合全文检索,建议合理设置属性检索范围与分页大小。
|
||||
- Redis 写入为单键 HSet,具备良好吞吐能力;建议评估内存占用与持久化策略。
|
||||
- 中间件与日志
|
||||
- 缓存层现在支持全量缓存和按品牌缓存两种策略,提升查询性能。
|
||||
- **中间件与日志**
|
||||
- 开启 Recovery、CORS、Logger、RequestID 中间件,有助于可观测性与稳定性;注意日志级别与输出频率对性能的影响。
|
||||
- 并发安全
|
||||
- **并发安全**
|
||||
- Redis HSet 为线程安全操作;处理器方法本身无共享可变状态,天然并发安全。
|
||||
- 最佳实践
|
||||
- 依赖注入模式减少了全局状态,提升了并发安全性。
|
||||
- **最佳实践**
|
||||
- 在生产环境启用 Release 模式,降低框架开销。
|
||||
- 对外部依赖(数据库、搜索、缓存)增加超时与重试策略,提升鲁棒性。
|
||||
- 利用缓存预热机制,在应用启动时加载常用数据到缓存。
|
||||
|
||||
## 故障排查指南
|
||||
- 健康检查失败
|
||||
- **健康检查失败**
|
||||
- 确认路由已正确注册到 /api/v1/health。
|
||||
- 查看统一响应体是否返回状态字段。
|
||||
- 品牌/型号查询异常
|
||||
- **品牌/型号查询异常**
|
||||
- 检查数据库连接与 SQL 查询逻辑。
|
||||
- 关注日志中“query brand”、“query model”的错误堆栈。
|
||||
- 型号检索异常
|
||||
- 关注日志中"get brand list failed"、"get model list failed"的错误堆栈。
|
||||
- 验证缓存是否正常工作,检查 Redis 连接状态。
|
||||
- **型号检索异常**
|
||||
- 检查 Meilisearch 配置与索引可用性。
|
||||
- 关注“meilisearch search”与“decode meilisearch hit”的错误。
|
||||
- 设备上报异常
|
||||
- 关注"model list search failed"与"decode meilisearch hit"的错误。
|
||||
- **设备上报异常**
|
||||
- 校验必填参数 mac 与 model 是否传入。
|
||||
- 关注 JSON 序列化与 Redis HSet 的错误日志。
|
||||
- 统一响应与错误码
|
||||
- 关注"marshal device info failed"与"redis hset failed"的错误日志。
|
||||
- **依赖注入相关问题**
|
||||
- 确认所有处理器都通过构造函数正确注入了依赖项。
|
||||
- 检查路由装配顺序,确保依赖项在处理器之前创建。
|
||||
- **统一响应与错误码**
|
||||
- 使用 internal/response/response.go 提供的 OK/Fail/BadRequest/InternalError 方法,确保错误码与消息格式一致。
|
||||
- 日志与追踪
|
||||
- **日志与追踪**
|
||||
- 通过 RequestID 中间件串联一次请求的全链路日志,结合 Logger 中间件定位问题。
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/handler/health.go:14-18](file://internal/handler/health.go#L14-L18)
|
||||
- [internal/handler/brand.go:31-35](file://internal/handler/brand.go#L31-L35)
|
||||
- [internal/handler/model.go:32-36](file://internal/handler/model.go#L32-L36)
|
||||
- [internal/handler/brand.go:30-34](file://internal/handler/brand.go#L30-L34)
|
||||
- [internal/handler/model.go:31-35](file://internal/handler/model.go#L31-L35)
|
||||
- [internal/handler/model_list.go:38-42](file://internal/handler/model_list.go#L38-L42)
|
||||
- [internal/handler/device.go:61-78](file://internal/handler/device.go#L61-L78)
|
||||
- [internal/response/response.go:15-36](file://internal/response/response.go#L15-L36)
|
||||
@@ -433,25 +551,34 @@ ALL --> LOG["zap.Logger"]
|
||||
- [internal/middleware/request_id.go:20-31](file://internal/middleware/request_id.go#L20-L31)
|
||||
|
||||
## 结论
|
||||
本处理器模块遵循清晰的分层与依赖注入原则,通过统一响应体与自定义 Base64 编码实现灵活的输出策略,结合 Gin 中间件与外部服务(数据库、Meilisearch、Redis)形成稳定高效的业务处理链路。建议在生产环境中进一步完善超时与重试、索引优化与缓存策略,持续提升性能与可靠性。
|
||||
本处理器模块采用全新的依赖注入模式,通过构造函数注入依赖项,实现了更好的解耦和可测试性。通过统一响应体与自定义 Base64 编码实现灵活的输出策略,结合 Gin 中间件与外部服务(数据库、Meilisearch、Redis)形成稳定高效的业务处理链路。新的架构模式简化了处理器实现,提升了代码质量,建议在生产环境中进一步完善超时与重试、索引优化与缓存策略,持续提升性能与可靠性。
|
||||
|
||||
## 附录
|
||||
- 路由与处理器映射
|
||||
- **路由与处理器映射**
|
||||
- /api/v1/health -> HealthHandler.Check
|
||||
- /audio/getBrand -> BrandHandler.GetBrand
|
||||
- /audio/getModel -> ModelHandler.GetModel
|
||||
- /audio/modelList -> ModelListHandler.ModelList
|
||||
- /audio/reportDevInfo -> DeviceHandler.ReportDevInfo
|
||||
- 常用调用示例(路径引用)
|
||||
- 品牌查询:[internal/handler/brand.go:26](file://internal/handler/brand.go#L26)
|
||||
- 型号查询:[internal/handler/model.go:26](file://internal/handler/model.go#L26)
|
||||
- **依赖注入示例**
|
||||
- 品牌处理器:BrandHandler.NewBrandHandler(brandRepo, log)
|
||||
- 型号处理器:ModelHandler.NewModelHandler(modelRepo, log)
|
||||
- 型号检索处理器:ModelListHandler.NewModelListHandler(searchClient, log)
|
||||
- 设备上报处理器:DeviceHandler.NewDeviceHandler(redis, log)
|
||||
- **常用调用示例(路径引用)**
|
||||
- 品牌查询:[internal/handler/brand.go:25](file://internal/handler/brand.go#L25)
|
||||
- 型号查询:[internal/handler/model.go:25](file://internal/handler/model.go#L25)
|
||||
- 型号检索:[internal/handler/model_list.go:26](file://internal/handler/model_list.go#L26)
|
||||
- 设备上报:[internal/handler/device.go:26](file://internal/handler/device.go#L26)
|
||||
- 健康检查:[internal/handler/health.go:14](file://internal/handler/health.go#L14)
|
||||
- 配置与启动
|
||||
- **配置与启动**
|
||||
- 配置加载与环境变量:[internal/config/config.go:18-64](file://internal/config/config.go#L18-L64)
|
||||
- 服务器启动与优雅关闭:[cmd/server/main.go:22-96](file://cmd/server/main.go#L22-L96)
|
||||
- 中间件
|
||||
- 服务器启动与优雅关闭:[cmd/server/main.go:25-101](file://cmd/server/main.go#L25-L101)
|
||||
- **中间件**
|
||||
- CORS:[internal/middleware/cors.go:7-21](file://internal/middleware/cors.go#L7-L21)
|
||||
- Logger:[internal/middleware/logger.go:10-45](file://internal/middleware/logger.go#L10-L45)
|
||||
- RequestID:[internal/middleware/request_id.go:20-31](file://internal/middleware/request_id.go#L20-L31)
|
||||
- RequestID:[internal/middleware/request_id.go:20-31](file://internal/middleware/request_id.go#L20-L31)
|
||||
- **缓存策略**
|
||||
- 品牌缓存:brand:all 键,30分钟TTL
|
||||
- 型号缓存:model:brand:{brand} 和 model:all 键,30分钟TTL
|
||||
- 启动时预热缓存,提升首次查询性能
|
||||
@@ -4,8 +4,12 @@
|
||||
**本文引用的文件**
|
||||
- [internal/repository/brand.go](file://internal/repository/brand.go)
|
||||
- [internal/repository/model.go](file://internal/repository/model.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)
|
||||
- [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/model/brand.go](file://internal/model/brand.go)
|
||||
- [internal/model/model.go](file://internal/model/model.go)
|
||||
@@ -17,29 +21,38 @@
|
||||
- [go.mod](file://go.mod)
|
||||
</cite>
|
||||
|
||||
## 更新摘要
|
||||
**变更内容**
|
||||
- 新增缓存感知查询逻辑,增强品牌和型号仓库的缓存策略
|
||||
- 引入多级缓存架构,支持全量缓存和按品牌缓存
|
||||
- 实现智能数据刷新和预热机制
|
||||
- 添加缓存降级和错误处理策略
|
||||
- 更新架构图以反映新的缓存层集成
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [项目结构](#项目结构)
|
||||
3. [核心组件](#核心组件)
|
||||
4. [架构总览](#架构总览)
|
||||
5. [详细组件分析](#详细组件分析)
|
||||
6. [依赖关系分析](#依赖关系分析)
|
||||
7. [性能考量](#性能考量)
|
||||
8. [故障排查指南](#故障排查指南)
|
||||
9. [结论](#结论)
|
||||
10. [附录:扩展新 Repository 指南](#附录扩展新-repository-指南)
|
||||
6. [缓存策略详解](#缓存策略详解)
|
||||
7. [依赖关系分析](#依赖关系分析)
|
||||
8. [性能考量](#性能考量)
|
||||
9. [故障排查指南](#故障排查指南)
|
||||
10. [结论](#结论)
|
||||
11. [附录:扩展新 Repository 指南](#附录扩展新-repository-指南)
|
||||
|
||||
## 简介
|
||||
本文件聚焦于 Luxsin 应用 API 的数据访问层(Repository 层),系统性阐述 BrandRepository 与 ModelRepository 的设计模式、实现原理与职责边界;解释其在整体架构中的位置与交互方式;深入分析数据库连接管理、SQL 查询优化与事务处理机制;给出使用示例路径、错误处理与异常管理策略;总结并发安全、连接池管理与资源清理的最佳实践,并提供扩展新 Repository 的指导原则与注意事项。文档兼顾初学者与资深开发者的需求,既提供高层架构视图,也给出可落地的实现细节。
|
||||
本文件聚焦于 Luxsin 应用 API 的数据访问层(Repository 层),系统性阐述 BrandRepository 与 ModelRepository 的设计模式、实现原理与职责边界;解释其在整体架构中的位置与交互方式;深入分析数据库连接管理、SQL 查询优化与事务处理机制;详细介绍新增的缓存感知查询逻辑、多级缓存策略和智能数据刷新机制;给出使用示例路径、错误处理与异常管理策略;总结并发安全、连接池管理与资源清理的最佳实践,并提供扩展新 Repository 的指导原则与注意事项。文档兼顾初学者与资深开发者的需求,既提供高层架构视图,也给出可落地的实现细节。
|
||||
|
||||
## 项目结构
|
||||
数据访问层位于 internal/repository 目录,配合 internal/database 提供底层数据库连接,internal/model 定义领域模型,internal/handler 通过注入的 Repository 执行业务逻辑,最终由 Gin 路由暴露接口。
|
||||
数据访问层位于 internal/repository 目录,配合 internal/cache 提供缓存层,internal/database 提供底层数据库连接,internal/model 定义领域模型,internal/handler 通过注入的 Repository 执行业务逻辑,最终由 Gin 路由暴露接口。
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "应用入口"
|
||||
MAIN["cmd/server/main.go<br/>启动服务与依赖初始化"]
|
||||
end
|
||||
END
|
||||
subgraph "路由与控制器"
|
||||
ROUTER["internal/router/router.go<br/>注册路由与中间件"]
|
||||
BRAND_H["internal/handler/brand.go<br/>品牌处理器"]
|
||||
@@ -49,9 +62,15 @@ subgraph "数据访问层"
|
||||
BR_REPO["internal/repository/brand.go<br/>BrandRepository"]
|
||||
MD_REPO["internal/repository/model.go<br/>ModelRepository"]
|
||||
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 "数据库与配置"
|
||||
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/>表结构定义"]
|
||||
end
|
||||
@@ -64,57 +83,74 @@ ROUTER --> BRAND_H
|
||||
ROUTER --> MODEL_H
|
||||
BRAND_H --> BR_REPO
|
||||
MODEL_H --> MD_REPO
|
||||
BR_REPO --> BR_CACHE
|
||||
MD_REPO --> MD_CACHE
|
||||
BR_CACHE --> REDIS
|
||||
MD_CACHE --> REDIS
|
||||
BR_REPO --> DB_SQL
|
||||
MD_REPO --> DB_SQL
|
||||
DB_SQL --> CFG_DB
|
||||
CFG_APP --> CFG_DB
|
||||
CFG_APP --> CFG_REDIS
|
||||
BR_REPO --> M_BRAND
|
||||
MD_REPO --> M_MODEL
|
||||
MODEL_TBL --> DB_SQL
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [cmd/server/main.go:38-48](file://cmd/server/main.go#L38-L48)
|
||||
**图表来源**
|
||||
- [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/repository/brand.go:12-18](file://internal/repository/brand.go#L12-L18)
|
||||
- [internal/repository/model.go:12-18](file://internal/repository/model.go#L12-L18)
|
||||
- [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/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)
|
||||
- [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)
|
||||
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
|
||||
|
||||
章节来源
|
||||
- [cmd/server/main.go:38-48](file://cmd/server/main.go#L38-L48)
|
||||
**章节来源**
|
||||
- [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/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/repository/brand.go:12-18](file://internal/repository/brand.go#L12-L18)
|
||||
- [internal/repository/model.go:12-18](file://internal/repository/model.go#L12-L18)
|
||||
- [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/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)
|
||||
- [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)
|
||||
- [sql/model.sql:24-35](file://sql/model.sql#L24-L35)
|
||||
|
||||
## 核心组件
|
||||
- BrandRepository:负责品牌列表查询,支持按名称模糊过滤,返回 Brand 领域对象切片。
|
||||
- ModelRepository:负责型号列表查询,支持按品牌名精确匹配或按型号名模糊匹配,返回 Model 领域对象切片,并对可空字段进行 NullString 到指针字符串的安全转换。
|
||||
- 数据库连接:通过 sql.DB 统一管理连接池,设置最大打开连接数、空闲连接数与连接生命周期,并在启动时进行 Ping 校验。
|
||||
- 配置加载:从环境变量或默认值加载数据库配置,生产环境要求提供密码。
|
||||
- Handler 注入:Gin 控制器通过 NewXxxHandler 构造函数注入 *sql.DB,再由 Handler 内部构造对应的 Repository 实例,形成清晰的依赖注入链路。
|
||||
- **BrandRepository**:负责品牌列表查询,支持缓存感知的全量查询和按名称模糊过滤,返回 Brand 领域对象切片。
|
||||
- **ModelRepository**:负责型号列表查询,支持缓存感知的按品牌精确匹配、按型号名模糊匹配和全量查询,返回 Model 领域对象切片,并对可空字段进行 NullString 到指针字符串的安全转换。
|
||||
- **缓存层**:BrandCache 和 ModelCache 提供多级缓存策略,支持全量缓存和按品牌缓存,具有智能刷新和降级机制。
|
||||
- **数据库连接**:通过 sql.DB 统一管理连接池,设置最大打开连接数、空闲连接数与连接生命周期,并在启动时进行 Ping 校验。
|
||||
- **配置加载**:从环境变量或默认值加载数据库和 Redis 配置,生产环境要求提供密码。
|
||||
- **Handler 注入**:Gin 控制器通过 NewXxxHandler 构造函数注入 *sql.DB 和缓存实例,再由 Handler 内部构造对应的 Repository 实例,形成清晰的依赖注入链路。
|
||||
|
||||
章节来源
|
||||
- [internal/repository/brand.go:20-50](file://internal/repository/brand.go#L20-L50)
|
||||
- [internal/repository/model.go:20-95](file://internal/repository/model.go#L20-L95)
|
||||
**章节来源**
|
||||
- [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/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)
|
||||
|
||||
## 架构总览
|
||||
数据访问层采用“仓储模式”(Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。Handler 仅依赖 Repository 接口,Repository 依赖 *sql.DB,配置与数据库模块负责基础设施初始化。整体流程如下:
|
||||
数据访问层采用"仓储模式"(Repository Pattern)封装数据库访问,将查询逻辑与业务逻辑解耦。新增的缓存层提供多级缓存策略,支持智能数据刷新和降级机制。Handler 仅依赖 Repository 接口,Repository 依赖 *sql.DB 和缓存实例,配置与数据库模块负责基础设施初始化。整体流程如下:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -122,83 +158,131 @@ participant Client as "客户端"
|
||||
participant Router as "Gin 路由"
|
||||
participant Handler as "BrandHandler/ModelHandler"
|
||||
participant Repo as "BrandRepository/ModelRepository"
|
||||
participant Cache as "BrandCache/ModelCache"
|
||||
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 : "确认存储"
|
||||
end
|
||||
Repo-->>Handler : "领域对象切片"
|
||||
Handler-->>Client : "JSON 或 Base64 响应"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/brand.go:30-35](file://internal/handler/brand.go#L30-L35)
|
||||
- [internal/handler/model.go:31-36](file://internal/handler/model.go#L31-L36)
|
||||
- [internal/repository/brand.go:31-35](file://internal/repository/brand.go#L31-L35)
|
||||
- [internal/repository/model.go:42-46](file://internal/repository/model.go#L42-L46)
|
||||
**图表来源**
|
||||
- [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/database/mysql.go:28-43](file://internal/database/mysql.go#L28-L43)
|
||||
|
||||
## 详细组件分析
|
||||
|
||||
### BrandRepository 设计与实现
|
||||
- 设计模式:仓储模式,面向领域模型 Brand,封装查询逻辑。
|
||||
- 关键点:
|
||||
- 支持按品牌名称模糊过滤,无参数时返回全部记录并按名称升序排序。
|
||||
- 使用 QueryContext 传递请求上下文,便于超时与取消控制。
|
||||
- 使用 defer rows.Close() 确保资源释放。
|
||||
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息。
|
||||
- 错误处理:对查询、扫描、迭代阶段分别进行错误包装,便于定位问题。
|
||||
- 性能优化建议:
|
||||
- 若品牌名称查询频繁,可在 name 字段建立索引(当前表结构未见显式索引,但可考虑)。
|
||||
- 对于大结果集,建议引入分页参数(limit/offset)避免一次性返回过多数据。
|
||||
- **设计模式**:仓储模式,面向领域模型 Brand,封装查询逻辑,现已集成缓存感知功能。
|
||||
- **关键特性**:
|
||||
- 支持缓存感知的全量品牌查询,优先从 Redis 获取全量品牌数据
|
||||
- 缓存未命中或 Redis 异常时自动降级到数据库查询
|
||||
- 支持按品牌名称模糊过滤,无参数时返回全部记录并按名称升序排序
|
||||
- 使用 QueryContext 传递请求上下文,便于超时与取消控制
|
||||
- 使用 defer rows.Close() 确保资源释放
|
||||
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息
|
||||
- 缓存回写机制:成功从数据库获取数据后自动回写全量缓存
|
||||
- **缓存策略**:
|
||||
- 缓存键:`brand:all`
|
||||
- TTL:30分钟
|
||||
- 应用层过滤:在缓存命中时进行模糊过滤,减少数据库压力
|
||||
- **错误处理**:对缓存读取、数据库查询、扫描、迭代阶段分别进行错误包装,便于定位问题。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class BrandRepository {
|
||||
-db : "*sql.DB"
|
||||
+NewBrandRepository(db) BrandRepository
|
||||
-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:12-18](file://internal/repository/brand.go#L12-L18)
|
||||
- [internal/repository/brand.go:20-50](file://internal/repository/brand.go#L20-L50)
|
||||
**图表来源**
|
||||
- [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:12-18](file://internal/repository/brand.go#L12-L18)
|
||||
- [internal/repository/brand.go:20-50](file://internal/repository/brand.go#L20-L50)
|
||||
**章节来源**
|
||||
- [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,封装查询逻辑。
|
||||
- 关键点:
|
||||
- 支持两种查询条件:按品牌名精确匹配或按型号名模糊匹配;两者皆为空时返回空切片。
|
||||
- 使用自定义 scanModel(rows) 将 sql.NullString 安全转换为 *string,避免空值导致的序列化问题。
|
||||
- 使用 QueryContext 传递上下文,defer rows.Close() 保证资源释放。
|
||||
- 对扫描与迭代阶段进行错误包装。
|
||||
- 表结构要点:model 表包含唯一索引 model_name(brand_name, name),有利于去重与高效检索。
|
||||
- 性能优化建议:
|
||||
- 为 brand_name 建立索引以提升按品牌筛选性能。
|
||||
- 为 name 建立前缀匹配索引(如使用 LIKE '%pattern%' 的场景)。
|
||||
- 引入分页参数,限制单次查询返回数量。
|
||||
- **设计模式**:仓储模式,面向领域模型 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"
|
||||
+NewModelRepository(db) ModelRepository
|
||||
-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
|
||||
@@ -211,55 +295,110 @@ class Model {
|
||||
+time CreateAt
|
||||
}
|
||||
ModelRepository --> Model : "返回领域对象"
|
||||
ModelRepository --> ModelCache : "使用缓存"
|
||||
ModelCache --> Model : "返回缓存数据"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/repository/model.go:12-18](file://internal/repository/model.go#L12-L18)
|
||||
- [internal/repository/model.go:20-95](file://internal/repository/model.go#L20-L95)
|
||||
**图表来源**
|
||||
- [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:12-18](file://internal/repository/model.go#L12-L18)
|
||||
- [internal/repository/model.go:20-95](file://internal/repository/model.go#L20-L95)
|
||||
**章节来源**
|
||||
- [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)
|
||||
|
||||
### Handler 与 Repository 的协作
|
||||
- Handler 通过 NewBrandHandler/NewModelHandler 注入 *sql.DB,内部构造对应 Repository 实例。
|
||||
- Handler 通过 NewBrandHandler/NewModelHandler 注入 *sql.DB 和缓存实例,内部构造对应 Repository 实例。
|
||||
- Handler 在 GetBrand/GetModel 中读取查询参数,调用 Repository.List(...),并将结果以 JSON 或 Base64 编码返回。
|
||||
- 错误处理:若 Repository 返回错误,Handler 记录日志并返回统一的内部错误响应。
|
||||
- 缓存感知:Handler 不需要关心缓存逻辑,只需调用 Repository 的标准方法即可享受缓存优势。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as "客户端"
|
||||
participant H as "BrandHandler"
|
||||
participant R as "BrandRepository"
|
||||
participant DB as "sql.DB"
|
||||
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"
|
||||
end
|
||||
H-->>C : "JSON 或 Base64"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/brand.go:26-49](file://internal/handler/brand.go#L26-L49)
|
||||
- [internal/repository/brand.go:20-50](file://internal/repository/brand.go#L20-L50)
|
||||
**图表来源**
|
||||
- [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/database/mysql.go:28-43](file://internal/database/mysql.go#L28-L43)
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/handler/brand.go:19-24](file://internal/handler/brand.go#L19-L24)
|
||||
- [internal/handler/brand.go:26-49](file://internal/handler/brand.go#L26-L49)
|
||||
- [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:26-50](file://internal/handler/model.go#L26-L50)
|
||||
- [internal/repository/brand.go:20-50](file://internal/repository/brand.go#L20-L50)
|
||||
- [internal/repository/model.go:20-95](file://internal/repository/model.go#L20-L95)
|
||||
- [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)
|
||||
|
||||
## 缓存策略详解
|
||||
|
||||
### 多级缓存架构
|
||||
数据访问层采用了多级缓存策略,分为以下层级:
|
||||
|
||||
1. **应用层缓存**:Redis 缓存,提供高性能的数据访问
|
||||
2. **内存缓存**:应用进程内的缓存,提供最快的访问速度
|
||||
3. **数据库缓存**:MySQL 查询缓存,利用数据库自身的缓存机制
|
||||
|
||||
### 缓存键设计
|
||||
- **品牌全量缓存**:`brand:all` - 存储所有品牌信息
|
||||
- **型号按品牌缓存**:`model:brand:{brandName}` - 存储特定品牌的所有型号
|
||||
- **型号全量缓存**:`model:all` - 存储所有型号信息
|
||||
|
||||
### 缓存策略实现
|
||||
- **缓存优先策略**:优先从缓存获取数据,缓存未命中时才查询数据库
|
||||
- **智能降级策略**:Redis 异常时自动降级到数据库查询,保证系统可用性
|
||||
- **自动回写策略**:成功从数据库获取数据后自动回写到缓存
|
||||
- **TTL 策略**:所有缓存设置 30 分钟过期时间,平衡性能和数据新鲜度
|
||||
|
||||
### 预热机制
|
||||
应用启动时会自动预热缓存,从数据库加载全量数据到 Redis:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[应用启动] --> B[创建缓存实例]
|
||||
B --> C[创建仓库实例]
|
||||
C --> D[查询品牌全量数据]
|
||||
D --> E[写入 brand:all 缓存]
|
||||
E --> F[查询型号全量数据]
|
||||
F --> G[写入 model:all 缓存]
|
||||
G --> H[缓存预热完成]
|
||||
```
|
||||
|
||||
**图表来源**
|
||||
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)
|
||||
|
||||
**章节来源**
|
||||
- [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)
|
||||
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)
|
||||
|
||||
## 依赖关系分析
|
||||
- 外部依赖:Go MySQL Driver、Gin、Zap 日志、Redis 客户端、Meilisearch 客户端。
|
||||
- 内部依赖:Handler 依赖 Repository;Repository 依赖 *sql.DB;数据库模块负责连接池与 Ping 校验;配置模块负责环境变量解析与校验。
|
||||
- 循环依赖:未发现循环依赖,职责边界清晰。
|
||||
- **外部依赖**:Go MySQL Driver、Gin、Zap 日志、Redis 客户端、Meilisearch 客户端。
|
||||
- **内部依赖**:Handler 依赖 Repository;Repository 依赖 *sql.DB 和缓存实例;缓存层依赖 Redis 客户端;数据库模块负责连接池与 Ping 校验;配置模块负责环境变量解析与校验。
|
||||
- **循环依赖**:未发现循环依赖,职责边界清晰。
|
||||
- **新增依赖**:缓存层增加了对 Redis 客户端的依赖,以及对缓存配置的依赖。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
@@ -270,105 +409,147 @@ GO_MOD --> REDIS["github.com/redis/go-redis/v9"]
|
||||
GO_MOD --> MEILI["github.com/meilisearch/meilisearch-go"]
|
||||
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"]
|
||||
ROUTER["internal/router/router.go"] --> BRAND_H["internal/handler/brand.go"]
|
||||
ROUTER --> MODEL_H["internal/handler/model.go"]
|
||||
BRAND_H --> BR_REPO["internal/repository/brand.go"]
|
||||
MODEL_H --> MD_REPO["internal/repository/model.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
|
||||
BR_CACHE --> REDIS_CLIENT
|
||||
MD_CACHE --> REDIS_CLIENT
|
||||
```
|
||||
|
||||
图表来源
|
||||
**图表来源**
|
||||
- [go.mod:5-11](file://go.mod#L5-L11)
|
||||
- [cmd/server/main.go:38-48](file://cmd/server/main.go#L38-L48)
|
||||
- [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/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/repository/brand.go:12-18](file://internal/repository/brand.go#L12-L18)
|
||||
- [internal/repository/model.go:12-18](file://internal/repository/model.go#L12-L18)
|
||||
- [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/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)
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [go.mod:5-11](file://go.mod#L5-L11)
|
||||
- [cmd/server/main.go:38-48](file://cmd/server/main.go#L38-L48)
|
||||
- [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/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/repository/brand.go:12-18](file://internal/repository/brand.go#L12-L18)
|
||||
- [internal/repository/model.go:12-18](file://internal/repository/model.go#L12-L18)
|
||||
- [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/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)
|
||||
|
||||
## 性能考量
|
||||
- 连接池配置
|
||||
- **连接池配置**
|
||||
- 最大打开连接数:25
|
||||
- 最大空闲连接数:5
|
||||
- 连接最大生命周期:5 分钟
|
||||
- 启动时使用 PingContext 进行健康检查,失败则关闭连接并返回错误
|
||||
- 查询优化
|
||||
- **缓存性能优化**
|
||||
- 缓存命中率:通过多级缓存策略最大化缓存命中率
|
||||
- TTL 设置:30分钟的TTL平衡了性能和数据新鲜度
|
||||
- 预热机制:启动时预热缓存,避免冷启动时的缓存穿透
|
||||
- 降级机制:Redis异常时自动降级到数据库,保证系统可用性
|
||||
- **查询优化**
|
||||
- BrandRepository:按名称模糊匹配,建议在 name 上建立合适索引;对大结果集引入分页。
|
||||
- ModelRepository:按品牌名精确匹配或按型号名模糊匹配,建议为 brand_name 与 name 建立索引;对大结果集引入分页。
|
||||
- 并发安全
|
||||
- 应用层过滤:在缓存命中时进行过滤,减少数据库压力。
|
||||
- **并发安全**
|
||||
- *sql.DB 是并发安全的,可在多个 goroutine 中共享使用;Repository 实例不持有状态,亦可并发安全使用。
|
||||
- 资源清理
|
||||
- Redis 客户端是并发安全的,支持多goroutine同时访问。
|
||||
- **资源清理**
|
||||
- 使用 defer rows.Close() 保证结果集关闭;在 main 中 defer db.Close() 保证应用退出时关闭连接池。
|
||||
- 事务处理
|
||||
- Redis 连接在应用退出时自动关闭。
|
||||
- **事务处理**
|
||||
- 当前实现均为只读查询,未涉及事务;如需写操作,应在 Repository 层封装事务,使用 sql.Tx 并在错误时回滚,成功时提交。
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/database/mysql.go:33-43](file://internal/database/mysql.go#L33-L43)
|
||||
- [internal/repository/brand.go:31-35](file://internal/repository/brand.go#L31-L35)
|
||||
- [internal/repository/model.go:42-46](file://internal/repository/model.go#L42-L46)
|
||||
- [cmd/server/main.go:42](file://cmd/server/main.go#L42)
|
||||
- [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/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)
|
||||
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)
|
||||
|
||||
## 故障排查指南
|
||||
- 连接失败
|
||||
- **连接失败**
|
||||
- 现象:启动时 Ping 失败或无法连接数据库。
|
||||
- 排查:检查 DATABASE_HOST/DATABASE_PORT/DATABASE_NAME/DATABASE_USER/DATABASE_PASSWORD 等环境变量;确认网络连通性;核对生产环境必须提供 DATABASE_PASSWORD。
|
||||
- 查询错误
|
||||
- **Redis 连接失败**
|
||||
- 现象:应用启动时无法连接 Redis 或缓存预热失败。
|
||||
- 排查:检查 REDIS_HOST/REDIS_PORT/REDIS_PASSWORD/REDIS_DATABASE 等环境变量;确认 Redis 服务正常运行;验证认证信息正确。
|
||||
- **缓存查询错误**
|
||||
- 现象:Handler 返回内部错误或缓存数据格式异常。
|
||||
- 排查:查看日志中错误上下文(cache read/write/brand/model),定位具体环节;检查 Redis 连接状态;确认缓存数据格式正确。
|
||||
- **查询错误**
|
||||
- 现象:Handler 返回内部错误。
|
||||
- 排查:查看日志中错误上下文(query brand/query model/scan brand/scan model/iterate brand/iterate model),定位具体环节;检查 SQL 参数绑定与字段映射。
|
||||
- 结果为空
|
||||
- **结果为空**
|
||||
- 现象:ModelRepository 在两种条件都为空时返回空切片。
|
||||
- 排查:确认传入的查询参数是否正确;检查表中是否存在匹配数据。
|
||||
- 资源泄漏
|
||||
- **资源泄漏**
|
||||
- 现象:长时间运行后连接数异常。
|
||||
- 排查:确认是否遗漏 rows.Close();检查连接池配置是否合理;观察连接生命周期与空闲连接上限。
|
||||
- **缓存未命中**
|
||||
- 现象:频繁出现缓存未命中,数据库压力过大。
|
||||
- 排查:检查缓存键是否正确;确认 TTL 设置是否合理;验证缓存预热是否成功;检查 Redis 内存使用情况。
|
||||
|
||||
章节来源
|
||||
**章节来源**
|
||||
- [internal/config/database.go:17-72](file://internal/config/database.go#L17-L72)
|
||||
- [internal/handler/brand.go:30-35](file://internal/handler/brand.go#L30-L35)
|
||||
- [internal/handler/model.go:31-36](file://internal/handler/model.go#L31-L36)
|
||||
- [internal/repository/brand.go:32-47](file://internal/repository/brand.go#L32-L47)
|
||||
- [internal/repository/model.go:43-58](file://internal/repository/model.go#L43-L58)
|
||||
- [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/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)
|
||||
|
||||
## 结论
|
||||
Luxsin 的数据访问层采用清晰的仓储模式,将数据库访问与业务逻辑解耦,具备良好的可维护性与扩展性。通过合理的连接池配置、上下文传播与错误包装,实现了稳定可靠的查询能力。建议后续在查询性能上引入索引与分页,在写操作上引入事务封装,并持续完善监控与日志体系。
|
||||
Luxsin 的数据访问层经过重构后,采用了更加完善的缓存感知查询逻辑,实现了多级缓存策略和智能数据刷新机制。通过集成 Redis 缓存层,显著提升了查询性能和系统可用性。缓存感知查询逻辑、智能降级策略、预热机制和自动回写功能共同构成了一个健壮的数据访问层。建议后续继续优化缓存策略,增加缓存监控指标,完善缓存失效和更新机制,并持续改进监控与日志体系。
|
||||
|
||||
## 附录:扩展新 Repository 指南
|
||||
- 设计原则
|
||||
- 保持 Repository 无状态,仅依赖 *sql.DB。
|
||||
- **设计原则**
|
||||
- 保持 Repository 无状态,仅依赖 *sql.DB 和缓存实例。
|
||||
- 查询方法接收 context.Context,便于超时与取消控制。
|
||||
- 对外返回领域模型(Model),避免直接暴露数据库结构。
|
||||
- 对可空字段使用 sql.NullString 到指针字符串的安全转换。
|
||||
- 实现步骤
|
||||
- 集成缓存感知查询逻辑,提供智能降级和自动回写功能。
|
||||
- **实现步骤**
|
||||
- 定义领域模型(Model)与 Repository 接口/实现。
|
||||
- 在 Handler 中注入 *sql.DB,构造 Repository 实例。
|
||||
- 创建对应的缓存结构体,定义缓存键和TTL策略。
|
||||
- 在 Handler 中注入 *sql.DB 和缓存实例,构造 Repository 实例。
|
||||
- 在路由中注册对应处理器。
|
||||
- 在 main 中确保 *sql.DB 注入到 Handler。
|
||||
- 注意事项
|
||||
- 在 main 中确保 *sql.DB 和缓存实例注入到 Handler。
|
||||
- 实现缓存预热逻辑,确保应用启动时缓存可用。
|
||||
- **注意事项**
|
||||
- 必须在每个查询后 defer rows.Close()。
|
||||
- 使用 fmt.Errorf 包裹底层错误,保留调用栈信息。
|
||||
- 生产环境务必提供数据库密码等敏感配置。
|
||||
- 如需写操作,封装事务并在错误时回滚,成功时提交。
|
||||
- 对高频查询建立合适的索引,必要时引入分页参数。
|
||||
- 实现缓存降级机制,确保Redis异常时系统仍可正常工作。
|
||||
- 设计合理的缓存键和TTL策略,平衡性能和数据新鲜度。
|
||||
- 实现缓存预热和自动回写机制,提升用户体验。
|
||||
|
||||
章节来源
|
||||
- [internal/repository/brand.go:12-18](file://internal/repository/brand.go#L12-L18)
|
||||
- [internal/repository/model.go:12-18](file://internal/repository/model.go#L12-L18)
|
||||
**章节来源**
|
||||
- [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/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/router/router.go:22-25](file://internal/router/router.go#L22-L25)
|
||||
- [cmd/server/main.go:64](file://cmd/server/main.go#L64)
|
||||
- [cmd/server/main.go:103-130](file://cmd/server/main.go#L103-L130)
|
||||
Reference in New Issue
Block a user