第41期 | 项目1:AI知识库产品

🎯 今天你将学会

  • 从产品视角设计一个 AI 知识库产品(不只是技术实现)
  • 产品级开发的项目规划方法(需求→设计→实现→测试→部署)
  • 实现完整的 AI 知识库:文档管理 + RAG问答 + 对话历史 + 用户认证
  • 理解产品级代码跟教程代码的区别——可维护、可扩展、可部署

📖 核心知识

从「教程代码」到「产品代码」

模块一到四你写了大量代码,但那些是教程代码——为了让你理解概念,代码尽可能简单。

产品代码不同:

维度 教程代码 产品代码
错误处理 try-catch + console.log 统一错误边界 + 用户友好提示 + 自动恢复
状态管理 简单 useState Zustand + persist + optimistic updates
数据验证 假设数据格式正确 Zod schema 验证 + 边界情况处理
性能 不考虑 虚拟列表 + 懒加载 + 缓存 + debounce
安全 不考虑 API Key 保护 + 输入验证 + 内容过滤
部署 localhost CI/CD + 环境变量 + 监控 + 日志
可维护性 写完就扔 代码规范 + 测试覆盖 + 文档同步

本期目标:把教程代码升级为产品代码。

产品定义:KnowBase AI

产品名: KnowBase — AI 驱动的知识库

核心功能:

功能 描述 优先级
文档管理 上传、查看、删除文档 P0
RAG 问答 基于文档的智能问答 + 引用来源 P0
对话历史 多轮对话 + 历史记录 P0
用户认证 注册/登录 + 个人知识库 P1
文档分享 生成分享链接,让其他人也能用你的知识库 P2
API 接口 提供 REST API,让其他应用也能调用知识库 P2

技术栈:

层级 技术 原因
前端 Next.js 14 (App Router) + React 18 + TS SSR + API Routes 一体
UI shadcn/ui + Tailwind CSS 轻量可定制
状态管理 Zustand + React Query Zustand 管 UI 状态,RQ 管 API 状态
后端 Next.js API Routes 前后端一体,减少运维
LLM OpenAI GPT-4o-mini + DALL-E 成本可控
向量存储 Supabase pgvector 免费 + SQL兼容
数据库 Supabase PostgreSQL 用户数据 + 文档元数据
认证 Supabase Auth 免费 + 简单
部署 Vercel 免费 + 自动 CI/CD

项目规划:5 天开发计划

Day 1:项目搭建 + 认证

上午:
- 创建 Next.js 项目 + 安装依赖
- 配置 Tailwind + shadcn/ui
- 配置 Supabase(数据库 + Auth)
- 实现注册/登录页面

下午:
- 实现认证中间件(保护 API 路由)
- 实现布局组件(Sidebar + Header + AuthGuard)
- 基础 CRUD API(文档管理接口)

Day 2:文档管理

上午:
- DocumentUpload 组件(拖拽上传 + 进度显示)
- 文档上传 API(接收文件 → 分片 → embedding → 存入 pgvector)
- DocumentList 组件(文档列表 + 搜索 + 删除)

下午:
- ChunkPreview 组件(分片预览 + 编辑)
- 文档详情页面
- 知识库统计面板(文档数、分片数、搜索次数)

Day 3:RAG 问答核心

上午:
- ChatInterface 组件(结合第33期的聊天界面)
- RAG Chat API(向量搜索 + LLM + 流式返回)
- SourceReference 组件(引用来源展示)

下午:
- 对话历史管理(多轮对话 + 持久化)
- 对话标题自动生成
- 模式切换(纯对话 / RAG问答)

Day 4:产品级打磨

上午:
- 错误边界组件(统一错误处理)
- 数据验证(Zod schema)
- 性能优化(虚拟列表 + 缓存)
- Token 使用量追踪 + 成本估算面板

下午:
- 暗黑模式完善
- 响应式布局优化(移动端适配)
- 加载状态优化(Skeleton + Suspense)
- 空状态设计

Day 5:部署 + 文档

上午:
- Vercel 部署配置
- 环境变量管理
- CI/CD 配置(自动测试 + 自动部署)
- 性能监控(Vercel Analytics)

下午:
- README 完善
- API 文档生成
- 用户指南编写
- 项目复盘 + 记录经验

核心代码实现

1. 项目结构(产品级)

knowbase/
├── app/
│   ├── (auth)/               — 认证相关页面(登录/注册)
│   │   ├── login/page.tsx
│   │   └── register/page.tsx
│   ├── (main)/               — 主功能页面(需要认证)
│   │   ├── layout.tsx        — AuthGuard + Sidebar + Header
│   │   ├── page.tsx          — 首页(对话界面)
│   │   ├── knowledge/
│   │   │   ├── page.tsx      — 知识库管理
│   │   │   └── [id]/page.tsx — 文档详情
│   │   └── settings/page.tsx — 设置(模型切换/Token追踪)
│   └── api/
│   │   ├── auth/             — 认证接口
│   │   ├── ai/
│   │   │   ├── chat/route.ts — 基础对话
│   │   │   └── rag/route.ts  — RAG问答
│   │   ├── documents/
│   │   │   ├── upload/route.ts
│   │   │   ├── list/route.ts
│   │   │   └── delete/[id]/route.ts
│   │   └── search/route.ts   — 向量搜索
│   └── layout.tsx            — 全局布局
├── features/
│   ├── auth/                 — 认证模块
│   ├── chat/                 — 聊天模块
│   ├── knowledge/            — 知识库模块
│   └── settings/             — 设置模块
├── lib/
│   ├── ai-client.ts          — AI客户端
│   ├── supabase.ts           — Supabase客户端
│   ├── zod-schemas.ts        — Zod验证schema
│   └── error-handler.ts      — 统一错误处理
├── components/
│   ├── ui/                   — shadcn/ui
│   └── layout/               — 布局组件
├── types/
└── middleware.ts              — 认证中间件

2. 认证中间件(Next.js App Router)

// middleware.ts
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs';
import { NextRequest, NextResponse } from 'next/server';

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient({ req, res });

  const { data: { session } } = await supabase.auth.getSession();

  // 未登录 → 重定向到登录页
  if (!session && req.nextUrl.pathname.startsWith('/(main)')) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  // 已登录 → 不允许访问登录页
  if (session && req.nextUrl.pathname.startsWith('/(auth)')) {
    return NextResponse.redirect(new URL('/', req.url));
  }

  return res;
}

export const config = {
  matcher: ['/(main):path*', '/(auth):path*'],
};

3. Zod 数据验证

// lib/zod-schemas.ts
import { z } from 'zod';

// 文档上传验证
export const documentUploadSchema = z.object({
  file: z.instanceof(File)
    .refine(f => f.size <= 20 * 1024 * 1024, '文件大小不能超过 20MB')
    .refine(f => ['application/pdf', 'text/markdown', 'text/plain'].includes(f.type) || f.name.endsWith('.md'),
      '请上传 PDF、Markdown 或纯文本文件'),
  title: z.string().min(1).max(100),
});

// 聊天消息验证
export const chatMessageSchema = z.object({
  message: z.string().min(1, '消息不能为空').max(4000, '消息太长'),
  conversationId: z.string().uuid(),
  mode: z.enum(['chat', 'rag']),
});

// API 响应验证
export const apiResponseSchema = z.object({
  content: z.string(),
  sources: z.array(z.object({
    id: z.string(),
    title: z.string(),
    content: z.string(),
    source: z.string(),
    relevance: z.number().min(0).max(1),
  })).optional(),
  usage: z.object({
    prompt_tokens: z.number(),
    completion_tokens: z.number(),
    total_tokens: z.number(),
  }).optional(),
});

4. 统一错误处理

// components/ErrorBoundary.tsx
import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="p-8 text-center">
          <h2 className="text-xl font-semibold mb-2">出了点问题</h2>
          <p className="text-gray-500 mb-4">{this.state.error?.message}</p>
          <button
            onClick={() => this.setState({ hasError: false, error: null })}
            className="px-4 py-2 rounded-lg bg-blue-500 text-white"
          >
            重试
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

5. Supabase 数据库 Schema

-- 文档表
CREATE TABLE documents (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users NOT NULL,
  title TEXT NOT NULL,
  file_type TEXT NOT NULL,
  file_size INTEGER NOT NULL,
  chunk_count INTEGER DEFAULT 0,
  status TEXT DEFAULT 'processing' CHECK (status IN ('processing', 'completed', 'error')),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 文档分片表(带向量)
CREATE TABLE document_chunks (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  document_id UUID REFERENCES documents ON DELETE CASCADE NOT NULL,
  content TEXT NOT NULL,
  chunk_index INTEGER NOT NULL,
  embedding VECTOR(1536),  -- OpenAI embedding 维度
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 向量搜索函数
CREATE FUNCTION match_documents(
  query_embedding VECTOR(1536),
  match_count INT DEFAULT 5,
  match_threshold FLOAT DEFAULT 0.5
) RETURNS TABLE (
  id UUID,
  document_id UUID,
  content TEXT,
  similarity FLOAT
) AS $
  SELECT
    id,
    document_id,
    content,
    1 - (embedding <=> query_embedding) AS similarity
  FROM document_chunks
  WHERE 1 - (embedding <=> query_embedding) > match_threshold
  ORDER BY embedding <=> query_embedding
  LIMIT match_count;
$ LANGUAGE SQL;

产品级 vs 教程级的关键差异

环节 教程做法 产品做法 为什么
认证 不做 Supabase Auth + middleware 每个用户有自己的知识库
数据验证 假设格式正确 Zod schema 验证 防止无效数据进入系统
错误处理 console.log ErrorBoundary + 用户友好提示 用户不看 console
向量存储 内存 mock Supabase pgvector 生产环境需要持久化和高性能
文档分片 简单切分 段落级分片 + 元数据 搜索精度取决于分片质量
部署 localhost Vercel + CI/CD 用户需要在线访问

常见误区

误区1:产品级 = 更多功能
产品级不是功能更多,而是每个功能更可靠。错误处理、数据验证、性能优化——这些看不见的东西才是产品级的核心。

误区2:先写完所有功能再部署
应该 Day 3 结束时就做第一次部署——尽早暴露生产环境的问题(CORS、SSL、环境变量等)。

误区3:认证不重要
没有认证 = 所有用户共享一个知识库 = 数据混乱。认证是产品级应用的基础设施。

🤖 AI协作实战

实战场景:5 天用 AI 协作开发 KnowBase

我给 Cursor Composer 的任务拆分:

Day 1 任务清单:
1. 创建 Next.js 项目 + 安装所有依赖(openai, @supabase/supabase-js, zod, zustand, react-markdown, lucide-react)
2. 配置 shadcn/ui + Tailwind CSS
3. 配置 Supabase 客户端(lib/supabase.ts)
4. 实现注册/登录页面(用 Supabase Auth)
5. 实现认证中间件(middleware.ts)
6. 实现主布局(Sidebar + Header + AuthGuard)

Day 2 任务清单:
1. DocumentUpload 组件(拖拽上传 + 进度)
2. 文档上传 API(/api/documents/upload)
3. DocumentList 组件
4. ChunkPreview 组件

Day 3 任务清单:
1. ChatInterface 组件
2. RAG Chat API(/api/ai/rag)
3. SourceReference 组件
4. 对话历史管理

Day 4 任务清单:
1. ErrorBoundary + zod-schemas
2. 性能优化(虚拟列表 + 缓存)
3. 暗黑模式 + 响应式
4. Token 追踪面板

Day 5 任务清单:
1. Vercel 部署
2. README + API 文档

AI 协作效率统计:

天数 我做决策 AI 写代码 审查时间 总耗时
Day 1 架构 + 技术选型 项目搭建 + 认证代码 30min 3h
Day 2 文档管理逻辑 组件代码 + API 20min 3h
Day 3 RAG 流程设计 聊天 + RAG 代码 25min 4h
Day 4 优化策略 优化 + 打磨代码 30min 4h
Day 5 部署 + 文档策略 配置 + 文档 15min 3h
总计 策略决策 95%代码量 2h 17h

对比纯手写预估:50-60 小时。效率提升 3.5x。

学到了什么: 5 天开发一个产品级项目是可行的——前提是 AI 写 95% 的代码,你做 100% 的决策和审查。你的角色不是「写代码的人」,而是「做产品决策的人」。

💻 动手练习

练习1(简单):搭建项目骨架 + 认证

创建 Next.js 项目 + 安装依赖 + 配置 Supabase Auth + 实现登录/注册页面 + 认证中间件。确保骨架能运行,登录流程正常。

练习2(中等):实现文档管理 + RAG 问答

在项目骨架上添加:

  • 文档上传 + 列表 + 删除
  • RAG 问答界面 + 引用来源展示
  • 对话历史管理

练习3(挑战):完整 5 天开发计划

按本期的 5 天计划完整开发 KnowBase AI:

  • Day 1-5 严格执行
  • 每天记录 AI 参与度和效率
  • 最终部署到 Vercel
  • 写一份项目复盘报告

📌 本期要点

  1. 产品级 vs 教程级: 不是功能更多,而是每个功能更可靠(错误处理/数据验证/性能优化/安全防护)
  2. 5 天开发计划: Day1(认证) → Day2(文档管理) → Day3(RAG核心) → Day4(打磨) → Day5(部署)
  3. 认证是基础设施: Supabase Auth + middleware,每个用户有自己的知识库
  4. Zod 数据验证是必需的: 防止无效数据进入系统——教程代码假设格式正确,产品代码不假设
  5. 你的角色变了: 从「写代码的人」变成「做产品决策的人」——AI 写 95% 代码,你做 100% 决策

🔗 下期预告

下一期是项目1的续篇——部署与优化。你将学会把 KnowBase 部署到 Vercel、做性能优化、SEO 配置,以及产品级监控。
如果你没有苹果电脑,需要上传ios到APPStore可以访问以下网站
iPA上传工具 - IPA解析与AppStore提交

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐