本文基于一个真实的单词管理系统项目,深入讲解数据清洗、数据库设计、ORM 集成、AI Prompt 工程等核心知识点,附带面试高频考点。
一、项目背景
1.1 项目简介
构建一个单词后台管理系统,核心功能:
- 单词书管理(CRUD)
- 管理员权限系统
- 数据清洗与导入
- H5 单词学习应用
1.2 技术栈
| 技术 | 用途 | 面试亮点 |
|---|---|---|
| Next.js 16 | 全栈框架 | App Router、Server Components |
| Supabase | BaaS 平台 | 云端 PostgreSQL、RLS |
| Drizzle ORM | 数据库 ORM | 类型安全、Schema 驱动 |
| shadcn/ui | UI 组件库 | 按需加载、AI 友好 |
| TypeScript | 类型系统 | 全链路类型推导 |
二、数据清洗:后端核心能力
2.1 什么是数据清洗?
数据清洗是将原始数据 转换为可用格式的过程,常见场景:
javascript
原始数据(GitHub JSON)→ 清洗 → 标准格式(CSV/数据库)
2.2 为什么需要数据清洗?
| 问题 | 示例 |
|---|---|
| 格式不统一 | JSON 嵌套层级不同 |
| 字段缺失 | 某些单词没有音标 |
| 数据冗余 | 包含不需要的字段 |
| 编码问题 | 中文编码不一致 |
2.3 传统方案 vs AI 方案
传统方案:手写脚本
javascript
// 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:
css
写一个 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 的安全特性,控制用户只能访问特定行的数据。
sql
-- 开启 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 核心原则
原则一:给充足的上下文
vbnet
❌ 错误:帮我写个接口
✅ 正确:用 Next.js App Router 写一个 GET 接口,查询 books 表,返回 JSON 格式
实践:把技术架构放在 AGENTS.md
markdown
# 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
解决方案:只给格式,不给数据
bash
# 太大,不要读全文
# head -c 2000 input.json → 只看前 2KB,了解结构
Prompt 示例:
javascript
写一个 JSON 转 CSV 脚本。
输入格式(每行一个 JSON):
{"wordRank":1,"headWord":"ruler","content":{...},"bookId":"PEP_1"}
输出列:wordRank, headWord, content, bookId
效果:
| 方式 | Token 消耗 |
|---|---|
| 读取完整文件 | ~44,000 tokens |
| 只看格式样本 | ~500 tokens |
| 节省 | 99% |
4.2 Prompt 颗粒度
规则/规范类 Prompt
diff
项目规范:
- 使用 Conventional Commits 格式
- API 返回统一用 NextResponse.json()
- 错误处理用 try-catch,返回 500
表单字段类 Prompt
diff
创建单词书表单字段:
- title: 文本,必填
- wordCount: 数字,默认 0
- coverUrl: 文本,可选
- bookId: 文本,必填,唯一
- tags: 文本,逗号分隔
业务场景类 Prompt
markdown
场景:管理员不能修改自己的角色
逻辑:
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 设计
typescript
// 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)
typescript
bookId: text("book_id")
.notNull()
.references(() => books.bookId, { onDelete: "cascade" }),
效果:删除单词书时,自动删除关联的单词
面试考点:
Q:级联删除的优缺点? A:优点是简化代码,自动清理关联数据;缺点是可能误删大量数据,需要谨慎使用。
5.3 CRUD 操作
typescript
// 创建
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 角色权限控制
typescript
// 前端:根据角色显示菜单
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 数据库表创建
bash
# 1. 定义 Schema
lib/db/schema.ts
# 2. 推送到数据库
pnpm db:push
# 3. 验证表结构
pnpm db:studio
7.2 API 路由设计
bash
/api/auth/*
├── check-first-run → 检查首次运行
├── signup → 注册系统管理员
├── signin → 登录
├── signout → 退出
└── me → 获取当前用户
/api/books/*
├── GET / → 单词书列表
├── POST / → 创建单词书
├── PUT /[id] → 编辑单词书
└── DELETE /[id] → 删除单词书
7.3 前端页面
bash
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 核心知识点
- 数据清洗:后端常见任务,AI 生成脚本效率高
- RLS:数据库层安全,公共数据不需要开启
- Prompt 工程:上下文要准确,格式要清晰,隐藏数据开销
- ORM 设计:Schema 驱动,级联删除,类型安全
- 权限系统:Session 认证 + 角色控制 + 前后端双重校验
9.2 面试亮点
- ✅ 全栈能力:前端 + 后端 + 数据库
- ✅ 安全意识:密码加密、权限控制、RLS
- ✅ AI 工程:Prompt 优化、Token 管理
- ✅ 工程实践:ORM 设计、API 设计、错误处理
项目地址 :GitHub - danci1
技术交流:欢迎在评论区留言讨论!
📝 作者 :h206421 📅 发布时间 :2026 年 8 月 🏷️ 标签:Next.js, Supabase, Drizzle ORM, Prompt 工程, 数据清洗