Skip to main content

Black Pearl 技术文档

目录


项目概述

Black Pearl(黑珍珠)是一个基于 Next.js 16 的 SaaS 模板,用于搭建 GitHub 工具发现站点。支持中英双语、用户认证、收藏、评论,以及完整的后台管理。

  • 域名: qiyuruixin.com
  • 品牌: 奇宇瑞信

技术栈

层级技术
框架Next.js 16.2.9 (App Router, RSC)
UIReact 19.2.4, Tailwind CSS 4, shadcn/ui (Radix 基础)
图标Lucide React
后端/数据库/认证Supabase (PostgreSQL + Auth + RLS + Storage)
语言TypeScript 5
校验Zod 4
包管理pnpm
部署Vercel (海外) + Docker standalone (国内)
LintESLint 9 (flat config)

项目结构

black_pearl_template/
├── src/
│ ├── app/
│ │ ├── layout.tsx # 根布局(html/body/字体)
│ │ ├── globals.css # Tailwind + CSS 变量
│ │ ├── global-error.tsx # 全局错误边界
│ │ ├── [locale]/ # 所有页面(locale=en|zh)
│ │ │ ├── (public)/ # 公开页面
│ │ │ │ ├── page.tsx # 首页
│ │ │ │ ├── tools/ # 工具列表 + 详情
│ │ │ │ ├── topics/ # 圈子 + 工具过滤
│ │ │ │ └── collections/ # 精选集
│ │ │ ├── (protected)/ # 需登录
│ │ │ │ └── dashboard/ # 仪表盘/收藏/设置
│ │ │ ├── (admin)/ # 后台管理
│ │ │ │ └── superadmin/ # 工具/圈子/精选集管理
│ │ │ └── auth/ # 登录/注册/回调
│ │ └── api/
│ │ └── github/fetch/ # GitHub 数据 API
│ ├── components/
│ │ ├── ui/ # shadcn/ui 基础组件
│ │ ├── layout/ # Header/Footer/Nav/LanguageSwitcher
│ │ ├── home/ # Hero/FeaturedTools/TopicGrid
│ │ ├── tool/ # ToolCard/StarBadge
│ │ ├── topic/ # TopicCard
│ │ ├── collection/ # CollectionCard
│ │ ├── auth/ # 登录/注册/忘记密码表单
│ │ ├── dashboard/ # DashboardNav/FavoriteList/SettingsForm
│ │ ├── admin/ # ToolForm/GitHubFetcher
│ │ └── shared/ # SearchBar/Pagination
│ ├── actions/ # Server Actions(数据变更)
│ ├── data/ # 数据查询层(Supabase 调用)
│ ├── lib/
│ │ ├── supabase/ # Supabase 客户端
│ │ │ ├── server.ts # 服务端 Client (Server Components/Actions)
│ │ │ ├── client.ts # 浏览器 Client (Client Components)
│ │ │ └── middleware.ts # 中间件 Client
│ │ ├── utils.ts # cn() 工具
│ │ ├── constants.ts # PAGE_SIZE 等常量
│ │ └── schemas.ts # Zod 校验 schemas
│ ├── i18n/
│ │ ├── en.json # 英文字典
│ │ ├── zh.json # 中文字典
│ │ ├── index.ts # getDictionary()
│ │ └── utils.ts # getLocaleFromParam/getLocalePrefix
│ ├── config/
│ │ ├── site.ts # 站点名/URL/描述
│ │ └── navigation.ts # 导航配置
│ ├── types/
│ │ └── database.ts # Supabase 数据库类型
│ └── middleware.ts # locale + 鉴权 middleware
├── supabase/
│ └── migrations/ # DDL + 种子数据
├── scripts/
│ └── import-github.ts # GitHub 批量导入脚本
├── public/ # 静态资源
├── emails/ # 邮件模板(未来)
├── Dockerfile # Docker 构建
├── next.config.ts # Next.js 配置
├── tsconfig.json
├── components.json # shadcn/ui 配置
├── package.json
└── .env.local # 环境变量(不提交 Git)

快速开始

前置条件

  • Node.js 22+
  • pnpm 9+
  • 一个 Supabase 项目(免费计划即可)

1. 克隆 & 安装

git clone <repo-url>
cd black_pearl_template
pnpm install

2. 配置环境变量

复制 .env.local 并填入你的 Supabase 密钥:

# 站点 URL(本地开发)
NEXT_PUBLIC_SITE_URL=http://localhost:3000

# Supabase 项目 URL(Supabase Dashboard → Settings → API → Project URL)
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxx.supabase.co

# Supabase 匿名密钥(同上页面 → anon public key)
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxx

# Supabase Service Role 密钥(同上 → service_role secret — 有数据库全权限)
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=sb_secret_xxxx

# GitHub Personal Access Token(可选,提高 API 限速。Classic token + repo 权限)
NEXT_PUBLIC_GITHUB_TOKEN=ghp_xxxx

3. 创建数据库表

在 Supabase Dashboard → SQL Editor 中依次执行:

  1. supabase/migrations/001_initial_schema.sql — 创建 11 张表 + RLS 策略
  2. supabase/migrations/002_seed_data.sql — 插入示例数据

4. 把自己设为管理员

在 Supabase Dashboard → SQL Editor 中执行:

UPDATE auth.users
SET raw_app_meta_data = jsonb_set(raw_app_meta_data, '{role}', '"admin"')
WHERE email = '你的邮箱';

5. 启动

pnpm dev

访问 http://localhost:3000 → 自动跳转 /en


Supabase 详解

什么是 Supabase?

Supabase 是 Firebase 的开源替代品,底层是标准的 PostgreSQL 数据库。它提供了:

功能说明
Database标准 PostgreSQL,可以用 SQL 操作,也有 RESTful API 和 JS SDK
Auth用户注册/登录(邮箱+密码、GitHub OAuth、Google 等)
Row Level Security (RLS)在数据库层面做权限控制,不需要写应用层中间件
Storage文件存储(图片、视频等)
Realtime数据库变更实时推送到前端(WebSocket)
Edge Functions服务端逻辑(Deno 运行时)

为什么选 Supabase 而不是其他方案?

Supabase传统 Express + Prisma + NextAuth
一套服务解决 Auth + DB + 权限需要 3 个独立的库/服务
RLS 在数据库层做权限,零代码需要手动写中间件 + 每处查询做权限判断
自带管理后台 (Dashboard)需要自己写后台
PostgreSQL 标准,随时可迁移同样基于 PG,但配置复杂
免费额度够个人/小项目需要自己托管数据库服务器
可以本地开发 (npx supabase start)通常连接远程 DB 开发

本项目中 Supabase 的使用方式

1. 数据表

11 张表,结构概览:

groups ──< topics ──< tool_topics >── tools >── tool_tags ──< tags
│ │
collections ──< collection_tools

auth.users ──< profiles ──< favorites ──> tools

└──< comments ──> tools

2. 三种 Supabase 客户端

项目中有三种不同的 Supabase Client,用于不同场景:

Server Client (src/lib/supabase/server.ts)

// 给 Server Components 和 Server Actions 使用
const supabase = await createClient();
// 有数据库完全权限(服务端 key)

Browser Client (src/lib/supabase/client.ts)

// 给 Client Components 使用
const supabase = createClient();
// 权限受 RLS 限制

Middleware Client (src/lib/supabase/middleware.ts)

// 给 middleware 使用 — 处理 cookie 同步
const supabase = createServerClient(url, key, { cookies: { getAll, setAll } });

3. 数据访问层 (src/data/)

所有 Supabase 查询都通过 src/data/ 目录下的函数进行,不在组件中直接调用 Supabase

src/data/
├── tools.ts # getTools, getToolBySlug, getFeaturedTools, getAllTools, getToolById
├── topics.ts # getTopics, getTopicBySlug, getAllTopics, getTopicById, getAllGroups
├── collections.ts # getCollections, getCollectionBySlug, getAllCollections, getCollectionById
├── favorites.ts # getUserFavorites, isFavorited
└── comments.ts # getCommentsByTool

查询示例:

// Server Component 中
const { tools, total } = await getTools({ page: 1, topic: "frontend", sort: "stars" });
const tool = await getToolBySlug("shadcn-ui");

4. Server Actions (src/actions/)

所有数据变更(创建/更新/删除)通过 Server Actions:

src/actions/
├── auth.ts # login, signup, logout, resetPassword, loginWithGithub
├── tools.ts # createTool, updateTool, deleteTool
├── topics.ts # createTopic, updateTopic, deleteTopic
├── collections.ts # createCollection, updateCollection, deleteCollection, addToolToCollection...
├── favorites.ts # toggleFavorite
├── comments.ts # addComment, deleteComment
└── profile.ts # updateProfile

5. RLS (Row Level Security)

RLS 在数据库层面控制谁能读/写哪些数据:

-- 示例:只有工具所有者才能删除自己的收藏
CREATE POLICY "Users can delete own favorites"
ON favorites FOR DELETE
USING (auth.uid() = user_id);

优势:

  • 即使有人直接调用 Supabase API,权限也生效
  • 不需要在应用代码里写 if (user.id !== resource.ownerId) 判断

本项目 RLS 策略:

策略
profiles所有人可读,用户可改自己的
favorites用户只能操作自己的收藏
comments所有人可读,认证用户可发/改/删自己的
tools所有人可读已发布工具,管理员可 CRUD

6. 本地开发

如果不想用 Supabase 云服务,可以本地运行:

# 安装 Supabase CLI
npm install -g supabase

# 启动本地 Supabase(需要 Docker)
npx supabase init
npx supabase start

本地 Supabase 提供和云版本一样的 API,但数据在你自己的 Docker 里。

Supabase 学习资源


国际化 (i18n)

使用 Next.js 标准的 [locale] 路由参数方案:

/en/tools → 英文工具列表
/zh/tools → 中文工具列表
/ → 自动跳转 /en

架构:

middleware.ts → 检查 URL 是否有 locale 前缀
→ 无前缀 → 301 重定向到 /en/xxx
→ 有前缀 → 继续 + 鉴权检查

[locale]/page.tsx → 从 params 获取 locale
→ 传给 getDictionary(locale)
→ 渲染对应语言的 UI

字典文件: src/i18n/en.json / zh.json
语言切换: LanguageSwitcher 组件,router.push 软切换,无需刷新


认证系统

基于 Supabase Auth 实现:

支持的登录方式

方式说明
邮箱 + 密码注册 → 邮箱验证邮件(配置 Resend 后可用)→ 登录
GitHub OAuth跳转 GitHub 授权 → 回调 → 自动创建/登录

认证流程

1. 用户访问 /en/auth/login
2. 填写表单 → form action 调用 login server action
3. Server action 调用 supabase.auth.signInWithPassword()
4. Supabase 返回 session → cookie 自动设置
5. redirect 到 /en/dashboard
6. middleware 检查 cookie 中的 session token 是否有效

路由保护

路由模式访问条件
/en/en/tools/en/topics任何人
/en/dashboard/*已登录
/en/superadmin/*app_metadata.role === "admin"
/en/auth/*未登录(已登录则跳 dashboard)

数据访问层

查询数据(Server Components)

// src/app/[locale]/(public)/tools/page.tsx
export default async function ToolsPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const { tools, total } = await getTools({ page: 1, sort: "stars" });
// 渲染...
}

修改数据(Server Actions + 表单)

// 表单组件
<form action={createTool}>
<input type="hidden" name="locale" value={locale} />
<input name="title" />
<button type="submit">Create</button>
</form>
// Server Action
export async function createTool(formData: FormData) {
const locale = getLocale(formData); // 从隐藏字段获取
const supabase = await createClient();
// 鉴权 + 校验 + 写入
await supabase.from("tools").insert({ ... });
redirect(`/${locale}/superadmin/tools`);
}

客户端交互(API Routes + fetch)

对于不需要表单提交的交互(搜索建议、GitHub 数据抓取等),使用 API Routes:

// Client Component
const res = await fetch("/api/github/fetch", {
method: "POST",
body: JSON.stringify({ url: githubUrl })
});
const data = await res.json();

后台管理

后台路径:/en/superadmin

页面功能
/superadmin仪表盘(工具/用户统计)
/superadmin/tools工具列表(分页表格)
/superadmin/tools/new新增工具(GitHub Fetch 自动填充)
/superadmin/tools/[id]/edit编辑工具(含 featured/hidden 选项)
/superadmin/topics圈子管理
/superadmin/topics/new新建圈子
/superadmin/collections精选集管理
/superadmin/collections/[id]/edit编辑精选集 + 管理其中工具

GitHub Fetch

在新增工具页面,粘贴 GitHub URL → 点 Fetch → 自动拉取仓库名称、描述、star 数、语言、logo、官网并填充表单。


部署

海外部署 (Vercel)

# 推送到 GitHub,Vercel 自动部署
git push origin master

Vercel 环境变量设置同上 .env.local

国内部署 (Docker)

# 构建
docker build -t black-pearl .

# 运行
docker run -d -p 3000:3000 \
--env-file .env.local \
black-pearl

两套部署共享同一 Supabase 数据源

海外用户 ─→ Vercel ──┐
├──→ Supabase(同一数据库)
国内用户 ─→ Docker ──┘

GitHub 导入工具

批量导入 GitHub 仓库到数据库:

# 单个导入
pnpm github:import --url https://github.com/shadcn-ui/ui --topic-slug frontend

# 批量导入
pnpm github:import --bulk https://github.com/owner/repo1 https://github.com/owner/repo2

# 带 featured 标记
pnpm github:import --url https://github.com/owner/repo --featured

# 预览模式(只打印,不写入数据库)
pnpm github:import --url https://github.com/owner/repo --dry-run

自动处理:

  • ✅ 从 GitHub API 拉取 star/fork/language/描述/logo
  • ✅ 自动创建唯一 slug(重名加 -2, -3...)
  • ✅ GitHub topics → 自动创建为 tag 并关联
  • ✅ 关联到指定圈子

环境变量完整列表

变量必填说明
NEXT_PUBLIC_SITE_URL站点完整 URL
NEXT_PUBLIC_SUPABASE_URLSupabase 项目 URL
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYSupabase anon/publishable key
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY仅脚本Supabase service_role key(数据库全权限)
NEXT_PUBLIC_GITHUB_TOKENGitHub PAT,提高 API 限速

常用命令

pnpm dev # 启动开发服务器
pnpm build # 生产构建
pnpm start # 启动生产服务器
pnpm lint # ESLint 检查
pnpm github:import # 运行 GitHub 导入脚本
npx supabase start # 本地启动 Supabase(需 Docker)