从零构建 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 - Studio:
npx 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