Next.js 全栈实战:数据清洗、ORM 设计与 AI Prompt 工程最佳实践

16 阅读8分钟

本文基于一个真实的单词管理系统项目,深入讲解数据清洗、数据库设计、ORM 集成、AI Prompt 工程等核心知识点,附带面试高频考点。

一、项目背景

1.1 项目简介

构建一个单词后台管理系统,核心功能:

  • 单词书管理(CRUD)
  • 管理员权限系统
  • 数据清洗与导入
  • H5 单词学习应用

1.2 技术栈

技术用途面试亮点
Next.js 16全栈框架App Router、Server Components
SupabaseBaaS 平台云端 PostgreSQL、RLS
Drizzle ORM数据库 ORM类型安全、Schema 驱动
shadcn/uiUI 组件库按需加载、AI 友好
TypeScript类型系统全链路类型推导

二、数据清洗:后端核心能力

2.1 什么是数据清洗?

数据清洗是将原始数据转换为可用格式的过程,常见场景:

原始数据(GitHub JSON)→ 清洗 → 标准格式(CSV/数据库)

2.2 为什么需要数据清洗?

问题示例
格式不统一JSON 嵌套层级不同
字段缺失某些单词没有音标
数据冗余包含不需要的字段
编码问题中文编码不一致

2.3 传统方案 vs AI 方案

传统方案:手写脚本

// scripts/json2csv.mjs
import { readFileSync, writeFileSync } from "fs";

const data = readFileSync("input.json", "utf-8");
const parsed = JSON.parse(data);

// 清洗逻辑
const cleaned = parsed.map(item => ({
  word: item.headWord,
  rank: item.wordRank,
  content: JSON.stringify(item.content),
}));

// 输出 CSV
writeFileSync("output.csv", cleaned.join("\n"));

优点:确定性强、可调试 缺点:需要手写代码

AI 方案:生成脚本

给 AI 的 Prompt:

写一个 Node.js 脚本,把 JSON 转成 CSV。
输入格式:[{ "wordRank": 1, "headWord": "ruler", "content": {...} }]
输出列:wordRank, headWord, content, bookId
要求:content 列保留原始 JSON 字符串

优点:快速生成、1000 token 搞定 缺点:需要验证、可能有边界情况

2.4 面试考点

Q:数据清洗的流程是什么? A:数据采集 → 格式分析 → 清洗规则制定 → 脚本编写 → 验证输出

Q:如何处理大数据量的清洗? A:流式处理(Stream)、分批处理、并行处理


三、Supabase RLS(行级安全)

3.1 什么是 RLS?

RLS(Row Level Security) 是 PostgreSQL 的安全特性,控制用户只能访问特定行的数据。

-- 开启 RLS
ALTER TABLE words ENABLE ROW LEVEL SECURITY;

-- 创建策略:用户只能看到自己的数据
CREATE POLICY "Users can view own words" ON words
  FOR SELECT USING (auth.uid() = user_id);

3.2 什么时候需要 RLS?

场景是否需要 RLS
单词表(公共数据)❌ 不需要
用户背单词记录✅ 需要
管理员操作日志✅ 需要
单词书(公共)❌ 不需要

3.3 为什么单词表不需要 RLS?

单词数据是公共资源,所有用户共享
├── 单词表(words)→ 公开读取,无需 RLS
└── 用户记录(user_words)→ 私有数据,需要 RLS

3.4 面试考点

Q:RLS 和应用层权限控制的区别? A:RLS 在数据库层过滤,更安全;应用层在代码过滤,更灵活。推荐数据库层 + 应用层双重保障。

Q:如何设计 RLS 策略? A:最小权限原则,只授予必要的访问权限。


四、Prompt 工程:让 AI 高效工作

4.1 核心原则

原则一:给充足的上下文

❌ 错误:帮我写个接口
✅ 正确:用 Next.js App Router 写一个 GET 接口,查询 books 表,返回 JSON 格式

实践:把技术架构放在 AGENTS.md

# AGENTS.md
## 技术栈
- Next.js 16 App Router
- Drizzle ORM + Supabase PostgreSQL
- shadcn/ui + Tailwind CSS

## 目录结构
- app/api/ - API 路由
- lib/db/ - 数据库配置
- components/ui/ - UI 组件

原则二:隐藏上下文开销

问题:178KB 的 JSON 文件,直接给 AI 读取会消耗 44,000+ tokens

解决方案:只给格式,不给数据

# 太大,不要读全文
# head -c 2000 input.json  → 只看前 2KB,了解结构

Prompt 示例

写一个 JSONCSV 脚本。
输入格式(每行一个 JSON):
{"wordRank":1,"headWord":"ruler","content":{...},"bookId":"PEP_1"}
输出列:wordRank, headWord, content, bookId

效果

方式Token 消耗
读取完整文件~44,000 tokens
只看格式样本~500 tokens
节省99%

4.2 Prompt 颗粒度

规则/规范类 Prompt

项目规范:
- 使用 Conventional Commits 格式
- API 返回统一用 NextResponse.json()
- 错误处理用 try-catch,返回 500

表单字段类 Prompt

创建单词书表单字段:
- title: 文本,必填
- wordCount: 数字,默认 0
- coverUrl: 文本,可选
- bookId: 文本,必填,唯一
- tags: 文本,逗号分隔

业务场景类 Prompt

场景:管理员不能修改自己的角色
逻辑:
1. 编辑时判断 currentUser.id === editingUser.id
2. 如果是自己,禁用角色选择器
3. 后端也做校验,忽略传入的 role

4.3 让 AI 自己跑

❌ 错误:帮我看看这段代码有没有 bug
✅ 正确:运行这个脚本,如果有错误就修复,直到成功

AI 擅长的事情

  • 生成代码
  • 运行脚本
  • 调试错误
  • 迭代优化

4.4 面试考点

Q:如何优化 AI 的 Token 消耗? A:1. 只给格式样本,不给完整数据;2. 使用 AGENTS.md 存储项目上下文;3. 明确 Prompt,减少猜测

Q:Prompt 工程的核心是什么? A:准确、清晰、完整。规则要明确,字段要完整,场景要具体。


五、Drizzle ORM:类型安全的数据库操作

5.1 Schema 设计

// lib/db/schema.ts
import { pgTable, serial, text, timestamp, integer, uuid, jsonb } from "drizzle-orm/pg-core";

// 单词书表
export const books = pgTable("book", {
  id: uuid("id").defaultRandom().primaryKey(),
  title: text("title").notNull(),
  wordCount: integer("word_count").notNull().default(0),
  coverUrl: text("cover_url"),
  bookId: text("book_id").notNull().unique(),
  tags: text("tags"),
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
});

// 单词表(外键关联)
export const words = pgTable("words", {
  id: serial("id").primaryKey(),
  wordRank: integer("word_rank").notNull(),
  headWord: text("head_word").notNull(),
  content: jsonb("content").notNull(),
  bookId: text("book_id")
    .notNull()
    .references(() => books.bookId, { onDelete: "cascade" }),
});

5.2 级联删除(Cascade)

bookId: text("book_id")
  .notNull()
  .references(() => books.bookId, { onDelete: "cascade" }),

效果:删除单词书时,自动删除关联的单词

面试考点

Q:级联删除的优缺点? A:优点是简化代码,自动清理关联数据;缺点是可能误删大量数据,需要谨慎使用。

5.3 CRUD 操作

// 创建
const [book] = await db.insert(books).values({
  title: "CET-4 核心词汇",
  bookId: "cet4",
}).returning();

// 查询
const bookList = await db.select().from(books).orderBy(books.createdAt);

// 更新
await db.update(books)
  .set({ title: "新标题", updatedAt: new Date() })
  .where(eq(books.id, id));

// 删除(级联删除关联单词)
await db.delete(books).where(eq(books.id, id));

5.4 面试考点

Q:Drizzle ORM 和 Prisma 的区别? A:Drizzle 更轻量,Schema 就是代码,类型推导更直接;Prisma 功能更全,但需要生成客户端。

Q:什么是 Schema 驱动? A:数据库结构由代码定义,通过迁移同步到数据库,而不是手动建表。


六、权限系统设计

6.1 Session 认证流程

登录 → 创建 Session → 写入 Cookie(7天有效期)
  ↓
后续请求 → 读取 Cookie → 验证 Session → 获取用户信息

6.2 角色权限控制

// 前端:根据角色显示菜单
const navItems = [
  { title: "单词书", href: "/books", icon: BookOpen },
  ...(user?.role === "system_admin"
    ? [{ title: "管理员", href: "/admin-users", icon: Users }]
    : []),
];

// 后端:API 权限校验
if (currentUser.role !== "system_admin") {
  return NextResponse.json({ error: "权限不足" }, { status: 403 });
}

6.3 安全规则

规则实现
不能删除自己if (currentUser.id === userId) return error
不能修改自己的角色后端忽略传入的 role
普通管理员不能访问管理接口角色校验返回 403

6.4 面试考点

Q:Session 和 JWT 的区别? A:Session 存服务端,JWT 存客户端;Session 可主动失效,JWT 需等过期;JWT 无状态,适合分布式。


七、完整实现流程

7.1 数据库表创建

# 1. 定义 Schema
lib/db/schema.ts

# 2. 推送到数据库
pnpm db:push

# 3. 验证表结构
pnpm db:studio

7.2 API 路由设计

/api/auth/*
├── check-first-run  → 检查首次运行
├── signup           → 注册系统管理员
├── signin           → 登录
├── signout          → 退出
└── me               → 获取当前用户

/api/books/*
├── GET /            → 单词书列表
├── POST /           → 创建单词书
├── PUT /[id]        → 编辑单词书
└── DELETE /[id]     → 删除单词书

7.3 前端页面

app/
├── page.tsx              → 首页(跳转逻辑)
├── (auth)/
│   ├── signin/page.tsx   → 登录页
│   └── signup/page.tsx   → 注册页
└── (dashboard)/
    ├── layout.tsx        → 布局(认证检查)
    ├── books/page.tsx    → 单词书管理
    └── admin-users/      → 管理员管理

八、面试高频问题

8.1 技术选型

问题答案要点
为什么选 Supabase?免费、PostgreSQL、BaaS、RLS
为什么选 Drizzle?类型安全、Schema 驱动、轻量
为什么选 shadcn/ui?按需加载、可定制、AI 友好

8.2 数据库设计

问题答案要点
外键怎么设计?references + onDelete cascade
什么时候用 JSONB?结构不固定、查询少的字段
索引怎么加?常用查询字段、唯一约束

8.3 安全相关

问题答案要点
密码怎么存储?bcrypt 加密,10 轮 salt
Session 怎么管理?UUID + 数据库 + Cookie(7天)
权限怎么控制?数据库层 RLS + 应用层角色校验

8.4 AI 工程

问题答案要点
怎么优化 Token?只给格式、AGENTS.md、明确 Prompt
Prompt 怎么写?准确、清晰、完整、不让 LLM 猜
AI 适合做什么?生成代码、运行脚本、调试错误

九、总结

9.1 核心知识点

  1. 数据清洗:后端常见任务,AI 生成脚本效率高
  2. RLS:数据库层安全,公共数据不需要开启
  3. Prompt 工程:上下文要准确,格式要清晰,隐藏数据开销
  4. ORM 设计:Schema 驱动,级联删除,类型安全
  5. 权限系统:Session 认证 + 角色控制 + 前后端双重校验

9.2 面试亮点

  • ✅ 全栈能力:前端 + 后端 + 数据库
  • ✅ 安全意识:密码加密、权限控制、RLS
  • ✅ AI 工程:Prompt 优化、Token 管理
  • ✅ 工程实践:ORM 设计、API 设计、错误处理

项目地址GitHub - danci1

技术交流:欢迎在评论区留言讨论!


📝 作者:h206421 📅 发布时间:2026 年 8 月 🏷️ 标签:Next.js, Supabase, Drizzle ORM, Prompt 工程, 数据清洗