You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Nest.js+PostgreSQL,如何存储Google Analytics 4的文章浏览量?

解决方案:Nest.js + PostgreSQL 文章浏览量统计优化方案

核心思路:规避Google API频繁调用 + 兼容缓存拦截器

直接在请求链路中每次调用Google API会破坏缓存逻辑,同时增加不必要的API开销,建议采用异步统计+本地缓冲的方案,分以下步骤实现:

1. 仅在缓存未命中时触发统计

利用Nest.js CacheInterceptor的特性,只有当请求未命中缓存时才执行浏览量统计——缓存命中意味着用户拿到的是静态缓存内容,无需重复统计(若业务需统计缓存命中的浏览,可跳过此判断)。

实现方式:
自定义拦截器继承CacheInterceptor,在afterCache钩子中判断缓存状态,触发统计任务:

import { CacheInterceptor, ExecutionContext, Injectable } from '@nestjs/common';

@Injectable()
export class PostViewCountInterceptor extends CacheInterceptor {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: CacheManager,
    private readonly postViewService: PostViewService,
  ) {
    super();
  }

  async afterCache(context: ExecutionContext, result: any) {
    const request = context.switchToHttp().getRequest();
    const postId = request.params.id;
    // 检查缓存是否存在,不存在则触发统计
    const cachedPost = await this.cacheManager.get(`posts:${postId}`);
    if (!cachedPost) {
      // 异步执行统计,不阻塞请求响应
      this.postViewService.recordView(postId, request.ip);
    }
    return result;
  }
}

替换文章详情接口的拦截器:

@Get(':id')
@UseInterceptors(PostViewCountInterceptor)
async getPost(@Param('id') id: string) {
  return this.postService.findById(id);
}

2. 本地缓冲+批量同步到Google API

先将浏览量临时存储在PostgreSQL中间表,积累到阈值或定时批量同步,避免频繁调用Google API:

  • 创建临时统计表:
CREATE TABLE post_view_temporary (
  post_id VARCHAR(50) PRIMARY KEY,
  view_count INT DEFAULT 1,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 统计时原子累加本地计数:
async recordView(postId: string) {
  await this.prisma.$executeRaw`
    INSERT INTO post_view_temporary (post_id)
    VALUES (${postId})
    ON CONFLICT (post_id) DO UPDATE 
    SET view_count = post_view_temporary.view_count + 1,
        updated_at = CURRENT_TIMESTAMP;
  `;
}
  • 用Nest.js定时任务批量同步:
@Injectable()
export class ViewSyncService {
  constructor(private readonly prisma: PrismaService) {}

  @Cron('0 * * * *') // 每小时执行一次
  async syncToGoogleAPI() {
    // 读取临时表数据
    const tempViews = await this.prisma.postViewTemporary.findMany();
    if (tempViews.length === 0) return;

    // 批量调用Google API同步逻辑(此处替换为实际API调用代码)
    await Promise.all(
      tempViews.map(view => this.googleAPIService.sendViewCount(view.post_id, view.view_count)),
    );

    // 清空已同步的临时数据
    await this.prisma.postViewTemporary.deleteMany({
      where: { post_id: { in: tempViews.map(v => v.post_id) } },
    });
  }
}

3. 过滤无效请求(可选)

为避免爬虫、重复刷新导致的统计失真,可增加简单校验:

  • 基于IP+文章ID做短时间去重:
async isDuplicateView(postId: string, ip: string) {
  const key = `view:${postId}:${ip}`;
  const exists = await this.redisClient.exists(key);
  if (exists) return true;
  await this.redisClient.setEx(key, 600, '1'); // 10分钟内同一IP仅统计一次
  return false;
}

在recordView方法中先调用此方法,非重复请求才执行累加。

不建议同步调用Google API的原因

  • 增加接口响应耗时,影响用户体验
  • 缓存拦截器生效时,请求不会进入业务逻辑,同步调用会导致统计数据缺失
  • 频繁调用易触发Google API限流,降低服务稳定性

内容的提问来源于stack exchange,提问作者kilian.sch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 16:33:31