之前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. 数据流程

  1. 请求到达:用户访问页面时,请求通过PageViewMiddleware
  2. 页面识别:中间件调用 getPageIdentifier 识别页面类型和ID
  3. 记录访问:调用 RecordPageView 增加访问计数
  4. 内存更新:更新内存缓存中的计数器
  5. 定期持久化:后台goroutine按照配置的时间间隔将缓存数据批量写入数据库
  6. 关机保存:系统关闭时执行一次最终保存操作

4. 性能优化措施

  1. 内存缓存:减少直接数据库操作,提高响应速度
  2. 批量更新:定期批量写入数据库,而非每次访问都写入
  3. 读写锁:使用 sync.RWMutex 实现高效的并发控制
  4. UUID v5应用
    • 生成页面访问记录的唯一标识符(view_id)
    • 优势:
      • 确定性:相同输入产生相同UUID,确保数据一致性
      • 高效更新:可直接使用"INSERT OR UPDATE"(UPSERT)操作,避免先查询
      • 无冲突:确保不同页面访问记录的唯一性

5. 实现细节

pageview.go 中的具体实现:

  • 使用UUID v5为每条访问记录生成唯一标识
  • 实现缓存与数据库的同步机制
  • 提供线程安全的访问计数接口
  • 处理系统关闭时的优雅终止和数据保存