之前AI写的浏览量统计中间件使用了自增ID,每次写入都要检查是否存在,效率有些低,这次让AI重构,引入uuid5。
1. 系统架构
缓存层设计
- 数据结构:采用嵌套的内存映射结构
map[string]map[string]int64
,存储在viewsCache
变量中- 外层map键:页面类型(如"home"、"admin"、"novel"等)
- 内层map键:页面ID(如特定的小说ID、用户ID等)
- 值:对应页面的访问计数
- 并发控制:使用
sync.RWMutex
保护缓存访问,允许多个goroutine并发读取
持久化层设计
- 存储引擎:使用SQLite数据库的
page_views
表进行持久化存储 - 持久化策略:
- 定期写入:根据配置的时间间隔(如每10分钟)将内存中的访问数据批量写入数据库
- 关机保存:系统关闭时执行一次最终写入操作,确保数据不丢失
2. 关键组件
中间件组件 (PageViewMiddleware)
- 位置:
internal/middleware/pageview.go
- 功能:
- 拦截所有GET请求
- 过滤掉静态资源和API请求
- 通过
getPageIdentifier
函数识别页面类型和ID - 调用
models.RecordPageView
记录访问
页面识别器 (getPageIdentifier)
- 功能:
- 根据URL路径和参数识别不同类型的页面
- 返回规范化的页面类型和页面ID
- 支持多种页面模式匹配(用户页面、文章页面、管理页面等)
统计模型 (pageview.go)
- 核心功能:
RecordPageView
:记录页面访问GetPageViewCount
:获取页面访问计数- 管理内存缓存的更新和查询
- 处理数据的持久化操作
3. 数据流程
- 请求到达:用户访问页面时,请求通过PageViewMiddleware
- 页面识别:中间件调用
getPageIdentifier
识别页面类型和ID - 记录访问:调用
RecordPageView
增加访问计数 - 内存更新:更新内存缓存中的计数器
- 定期持久化:后台goroutine按照配置的时间间隔将缓存数据批量写入数据库
- 关机保存:系统关闭时执行一次最终保存操作
4. 性能优化措施
- 内存缓存:减少直接数据库操作,提高响应速度
- 批量更新:定期批量写入数据库,而非每次访问都写入
- 读写锁:使用
sync.RWMutex
实现高效的并发控制 - UUID v5应用:
- 生成页面访问记录的唯一标识符(view_id)
- 优势:
- 确定性:相同输入产生相同UUID,确保数据一致性
- 高效更新:可直接使用"INSERT OR UPDATE"(UPSERT)操作,避免先查询
- 无冲突:确保不同页面访问记录的唯一性
5. 实现细节
在 pageview.go
中的具体实现:
- 使用UUID v5为每条访问记录生成唯一标识
- 实现缓存与数据库的同步机制
- 提供线程安全的访问计数接口
- 处理系统关闭时的优雅终止和数据保存