基于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
相关产品推荐
相关产品推荐

