Uncategorized
18 min read

by LZG

从零构建 AI 资讯平台:一个 Monorepo 全栈项目的实践总结

基于 Next.js + NestJS + Prisma + BullMQ 构建的渠道聚合型 AI 资讯平台,分享从架构设计到落地的完整实践

前言

最近我完成了一个基于 Monorepo 的全栈项目 —— news-platform(AI 资讯平台)。这个项目通过多源数据采集(RSS / API / 爬虫)结合大模型智能处理(分类 / 翻译 / 摘要),提供高质量的资讯服务,并支持飞书自动化推送。

本文将分享我在项目架构设计、技术选型、以及开发实践中的经验和思考。


项目概览

核心功能

类别功能描述
数据采集RSS 订阅源管理、公开 API 对接、合规爬虫(含 robots.txt 检查)
智能处理大模型调用(分类/翻译/摘要)、内容去重、质量过滤
应用展示响应式 PC/移动端资讯页(Next.js)、飞书表格 + 群聊推送
运维部署Docker Compose 一键部署、GitHub Actions CI/CD

技术栈一览

┌─────────────────────────────────────────────────────────────────┐
│                        Frontend Layer                           │
│  Next.js 14 (App Router) + Tailwind CSS + shadcn/ui             │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                        Backend Layer                            │
│  NestJS 10 + Prisma ORM + JWT 认证                              │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                        Async Layer                              │
│  BullMQ + Redis (任务队列) + Crawler + AI Worker                │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                        Infrastructure                           │
│  PostgreSQL + Redis + Docker Compose + GitHub Actions           │
└─────────────────────────────────────────────────────────────────┘

一、为什么选择 Monorepo?

在项目初期,我面临一个选择:是采用传统的 Multi-Repo 还是 Monorepo?

经过权衡,我选择了 pnpm + Turborepo 的 Monorepo 方案,主要原因如下:

1.1 代码复用效率

Monorepo 让我能够将公共逻辑抽离到 packages 目录下,被多个应用共享:

packages/
├── types/          # 全局 TS 类型,自动同步到所有应用
├── db/             # Prisma 封装 + migrations
├── queue/          # BullMQ 任务队列封装
└── shared-utils/   # 合规检查/文本处理/重试机制

实际收益:修改 packages/types 中的类型定义,Web、API、Crawler、AI Worker 四个服务都能即时获得类型提示,彻底告别接口文档不同步的痛点。

1.2 统一的依赖管理

使用 pnpm workspace,依赖安装和升级更加高效:

{
  "packageManager": "pnpm@8.15.9",
  "scripts": {
    "dev": "pnpm run dev:all",
    "dev:all": "turbo run dev --parallel",
    "build": "turbo run build"
  }
}

Turborepo 的缓存机制让二次构建速度提升 70%+(Cache Hit 场景)。


二、前端架构设计

2.1 选择 Next.js 14 App Router

选择 Next.js 14 作为前端框架主要考虑:

特性收益
App Router更灵活的布局与嵌套,支持 Server Components
SSG/ISR资讯类页面 SEO 友好,静态生成提升首屏速度
File-based Routing路由即文件结构,降低心智负担
API Routes前后端同仓库,BFF 层可灵活编写

2.2 UI 方案:Tailwind CSS + shadcn/ui

// 响应式卡片组件示例
import { Card } from "@/components/ui/card"

export function NewsCard({ news }: { news: NewsItem }) {
  return (
    <Card className="p-4 hover:shadow-lg transition-shadow">
      <h3 className="text-lg font-semibold">{news.title}</h3>
      <p className="text-muted-foreground mt-2">{news.summary}</p>
      <div className="flex gap-2 mt-4">
        <Badge variant="secondary">{news.category}</Badge>
        <span className="text-sm text-muted-foreground">
          {news.source}
        </span>
      </div>
    </Card>
  )
}

shadcn/ui 的特点:

  • 可复制粘贴:组件直接拷贝到项目,完全可控
  • 基于 Radix UI:无障碍访问性开箱即用
  • Tailwind 原生:样式修改无需跳出组件文件

三、后端架构设计

3.1 为什么选择 NestJS?

作为前端工程师,选择 NestJS 有几个重要原因:

1. 装饰器风格,上手成本低

// NestJS Controller 写法与 Angular 高度相似
@Controller('news')
export class NewsController {
  @Get()
  @UseGuards(JwtAuthGuard) // JWT 认证
  async getNews(@Query('page') page: number) {
    return this.newsService.findAll(page)
  }

  @Get(':id')
  async findOne(@Param('id') id: string) {
    return this.newsService.findOne(id)
  }
}

2. 模块化清晰

@Module({
  imports: [PrismaModule],
  providers: [NewsService, CrawlerService],
  controllers: [NewsController],
  exports: [NewsService],
})
export class NewsModule {}

3. 依赖注入,测试友好

// 可轻松 mock 依赖进行单元测试
const mockNewsService = { findAll: jest.fn() }
const module = await Test.createTestingModule({
  providers: [NewsController, { provide: NewsService, useValue: mockNewsService }]
}).compile()

3.2 Prisma ORM:类型安全的数据库操作

// packages/db/src/index.ts
export const prisma = new PrismaClient()

// 查询示例
const news = await prisma.news.findMany({
  where: { category: 'tech' },
  include: { source: true },
  orderBy: { publishedAt: 'desc' },
  take: 20,
})

Prisma 的优势:

  • 类型安全prisma.news.findMany 返回的类型自动推断
  • 迁移管理npx prisma migrate dev 一键生成 SQL
  • Studionpx prisma studio 可视化数据库管理

四、异步任务处理架构

资讯平台的核心痛点是:采集与 AI 处理耗时较长,不能阻塞主流程

解决方案:BullMQ + Redis 任务队列

4.1 架构图

flowchart LR
  CRON[定时调度] --> Q[(BullMQ)]
  Q --> CRAWLER[采集服务]
  Q --> AI[AI Worker]
  Q --> PUSH[推送 Worker]
  CRAWLER --> EXT[外部数据源]
  AI --> LLM[大模型 API]

4.2 核心代码示例

// packages/queue/src/index.ts
import { Queue, Worker } from 'bullmq'

export const newsQueue = new Queue('news-processing', {
  connection: { host: 'localhost', port: 6379 }
})

// Worker 处理
const worker = new Worker('news-processing', async (job) => {
  if (job.name === 'crawl') {
    return await crawlSource(job.data.sourceUrl)
  }
  if (job.name === 'ai-process') {
    return await processWithAI(job.data.newsId)
  }
})

BullMQ 优势

  • 内置重试机制(指数退避)
  • 优先级队列
  • 延迟任务支持
  • Redis 持久化,服务重启不丢失任务

五、爬虫合规设计

爬虫开发中最容易被忽视的是法律合规。项目中设计了专门的合规模块:

5.1 合规检查

// packages/shared-utils/src/crawler/compliance.ts
export class CrawlerCompliance {
  // 检查 robots.txt
  async checkRobots(url: string): Promise<boolean> {
    const robots = robotsParser('https://example.com/robots.txt', robotsTxt)
    return robots.isAllowed(url, 'AiNewsBot')
  }

  // 频率控制
  private requestLog = new Map<string, number[]>()
  rateLimit(domain: string, requestsPerHour = 60): boolean {
    const now = Date.now()
    const oneHourAgo = now - 3600000
    const requests = this.requestLog.get(domain) || []
    const recent = requests.filter(t => t > oneHourAgo)
    if (recent.length >= requestsPerHour) return false
    recent.push(now)
    this.requestLog.set(domain, recent)
    return true
  }

  // 规范 User-Agent
  getHeaders(): Record<string, string> {
    return {
      "User-Agent": "AiNewsBot/1.0 (+https://yourdomain.com/bot-info)",
      From: "contact@yourdomain.com",
    }
  }
}

六、部署方案

6.1 Docker Compose 一键部署

# infra/docker-compose.yml
version: "3.8"
services:
  web:
    build: ./apps/web
    ports: ["3000:3000"]
    depends_on: [api]

  api:
    build: ./apps/api
    ports: ["3001:3001"]
    environment:
      - DATABASE_URL=postgresql://postgres:password@postgres:5432/news_platform
    depends_on: [postgres, redis]

  postgres:
    image: postgres:15-alpine
    volumes: ["pgdata:/var/lib/postgresql/data"]

  redis:
    image: redis:7-alpine

6.2 GitHub Actions CI/CD

name: Deploy to Production
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Install dependencies
        run: pnpm install
      - name: Build
        run: pnpm build
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          script: |
            cd /opt/news-platform
            docker compose pull
            docker compose up -d

七、项目亮点总结

亮点说明
类型安全闭环packages/types 全局共享,前后端类型完全同步
前端工程师友好NestJS 装饰器 + Prisma 让 TS 开发者无缝衔接后端
合规模块前置爬虫内置 robots.txt 检查、频率控制、UA 规范
渐进式演进Phase 1 MVP → Phase 2 AI 增强 → Phase 3 用户系统
运维极简化Docker Compose + GitHub Actions 一键部署

八、未来规划

  • 用户系统(JWT 认证已完成)
  • 推荐算法(基于阅读历史)
  • 小程序/App 接口扩展
  • Prometheus + Grafana 监控体系

结语

这个项目让我深入实践了 Monorepo 全栈开发模式。从前端的 Next.js 到后端的 NestJS,再到异步任务队列和部署方案,每一个环节都有值得深挖的技术点。

如果你对项目感兴趣,欢迎交流讨论!


技术栈速查

领域技术选型
包管理pnpm + Turborepo
前端Next.js 14 + Tailwind CSS + shadcn/ui
后端NestJS 10 + Prisma ORM
队列BullMQ + Redis
采集axios + cheerio + rss-parser
AI@anthropic-ai/sdk / @google/generative-ai
部署Docker Compose + GitHub Actions

项目地址:[GitHub Repository]

作者:[Your Name]

发布时间:2026-02-03