这是一个基于 Next.js + tRPC + Prisma + Tailwind CSS 构建的现代化 Web 应用模板,提供完整的用户认证、文件管理和管理后台功能。
- Next.js 14
- React 18
- TypeScript
- TailwindCSS
- Ant Design
- tRPC Client
- React Query
- Recoil (状态管理)
- tRPC Server
- Prisma (ORM)
- MySQL (数据库)
- Redis (缓存)
- MinIO (对象存储)
- JWT (认证)
- 高性能 - 使用 Next.js 和 React 构建,确保快速加载和响应式体验
- 安全可靠 - 内置完善的认证系统和权限控制,保护数据安全
- 可扩展架构 - 基于 tRPC 和 Prisma 的架构,方便扩展和维护
- 美观界面 - 结合 Ant Design 和 Tailwind CSS 打造现代化用户界面
- 完整的登录体系 - 包含登录、注册和权限管理
- 功能完备 - 完整的用户认证系统、API 类型安全、实时数据更新、后台管理系统
├── app/ # Next.js 应用目录
├── server/ # tRPC 服务器代码
├── prisma/ # Prisma 配置和迁移
├── public/ # 静态资源
├── utils/ # 工具函数
└── docs/ # 文档
系统采用了分层的路由结构:
- / - Landing Page,产品介绍页面,未登录用户可访问,已登录用户也可查看并有引导进入系统的按钮
- /login - 用户登录页面
- /register - 用户注册页面
- /webapp - 应用主页,需要登录才能访问,包含系统主要功能
- /files - 文件管理页面,需要登录才能访问
- /admin - 管理后台,需要管理员权限
- 未登录用户 - 可以访问产品介绍页面、登录和注册页面
- 已登录用户 - 可以在产品介绍页面看到自己的登录状态,并通过"进入系统"按钮直接进入应用
- 系统内部 - 登录后可以使用所有功能,并通过顶部导航在各功能间切换
- Node.js 18+
- pnpm (推荐) 或 npm/yarn
- MySQL 8.0+
- MinIO (可选)
- 克隆项目
git clone https://github.com/wengxiaoxiong/trpc-template.git
cd trpc-template
- 安装依赖
pnpm install
# 或者
npm install
# 或者
yarn install
- 配置环境变量
cp .env.example .env
编辑 .env
文件,填入必要的环境变量。
DATABASE_URL="mysql://root:12345678@localhost:3306/mydb"
NEXTAUTH_URL="http://localhost:3002"
NEXTAUTH_SECRET="your-secret-key"
- 初始化数据库
pnpm prisma generate
pnpm prisma db push
- 创建初始管理员账号
INSERT INTO `User` (`id`, `createdAt`, `updatedAt`, `username`, `password`, `avatar`, `isAdmin`) VALUES
(1, '2025-03-23 02:14:04.236', '2025-03-23 02:14:04.236', 'admin', '$2b$10$aaO94E2iiaDYKksaDZbPp./bKXU7n.1A2iT3LZrs1y2PPDSS15lHq', NULL, 1);
启动开发服务器:
pnpm dev
# 或者
npm run dev
# 或者
yarn dev
访问地址:
后台管理初始账号:
- 用户名:admin
- 密码:adminadmin
构建生产版本:
pnpm build
# 或者
npm run build
# 或者
yarn build
启动生产服务器:
pnpm start
# 或者
npm run start
# 或者
yarn start
系统提供邀请码注册功能,可以限制用户注册,只有拥有有效邀请码的用户才能注册。
- 管理员可以在后台创建邀请码
- 每个邀请码可以设置有效期和使用次数
- 系统会记录邀请码的使用记录
- 登录管理后台 (/admin)
- 进入"邀请码管理"页面
- 点击"生成邀请码"按钮
- 设置邀请码有效期和可使用次数
- 点击"确认"生成邀请码
系统支持动态配置网站的多种属性,管理员可以在后台进行配置管理,无需修改代码即可更新网站信息。
- 网站基本信息配置:包括网站标题、描述、Logo等
- 页脚信息配置:包括版权信息、页脚标语等
- 自定义配置项:支持添加自定义配置项
系统当前支持以下预定义配置字段:
配置键 | 说明 | 示例值 |
---|---|---|
site.title | 网站标题 | tRPC全栈应用 |
site.description | 网站描述 | 基于Next.js和tRPC的高性能全栈应用 |
site.footer.copyright | 页脚版权信息 | © 2024 Example Inc. |
site.footer.slogan | 页脚标语 | 为开发者提供更好的全栈体验 |
site.logo.url | Logo图片路径 | /images/logo.png |
site.year | 版权年份 | 2024 |
- 使用管理员账号登录系统
- 进入管理后台 (/admin)
- 在侧边栏找到"网站配置"选项
- 在配置页面可以编辑现有配置或添加新的配置项
- 修改后点击"保存"按钮使配置生效
- 在网站配置页面点击"添加配置"按钮
- 输入配置键(推荐使用点分隔的命名方式,如
site.new.config
) - 输入配置值
- 添加配置描述(可选)
- 点击"保存"按钮
配置项可以在前端代码中通过自定义Hook获取:
// 导入自定义Hook
import { useSiteConfig } from '@/hooks/useSiteConfig'
// 在组件中使用
const MyComponent = () => {
// 获取配置工具函数
const { getConfigValue } = useSiteConfig()
// 获取特定配置,第二个参数为默认值
const siteTitle = getConfigValue('site.title', '模版项目')
const siteDesc = getConfigValue('site.description', '一个现代化的Web应用')
const footerCopyright = getConfigValue('site.footer.copyright', '© 2024')
return (
<div>
<h1>{siteTitle}</h1>
<p>{siteDesc}</p>
<footer>{footerCopyright}</footer>
</div>
)
}
该Hook提供了类型安全且便捷的方式来读取网站配置,无需重复编写API调用代码。
项目可以部署到任何支持 Next.js 的平台:
npm run build
npm run start
本项目支持使用 Docker 进行容器化部署,简化环境配置和应用部署过程。
docker build -t trpc-app .
docker run -d \
--name trpc-app \
-p 3002:3002 \
-e DATABASE_URL="mysql://user:password@host:3306/dbname" \
-e MINIO_ACCESS_KEY="minio_access_key" \
-e MINIO_SECRET_KEY="minio_secret_key" \
-e MINIO_ENDPOINT="minio_endpoint" \
-e MINIO_USE_SSL=false \
-e MINIO_BUCKET="bucket_name" \
-e JWT_SECRET="your_jwt_secret" \
-e PORT=3002 \
trpc-app
环境变量 | 说明 | 示例 |
---|---|---|
DATABASE_URL | 数据库连接字符串 | mysql://user:password@host:3306/dbname |
MINIO_ACCESS_KEY | MinIO 访问密钥 | minio_access_key |
MINIO_SECRET_KEY | MinIO 秘密密钥 | minio_secret_key |
MINIO_ENDPOINT | MinIO 服务端点 | minio.example.com |
MINIO_USE_SSL | 是否使用 SSL 连接 MinIO | true/false |
MINIO_BUCKET | MinIO 存储桶名称 | mybucket |
JWT_SECRET | JWT 签名密钥 | your_secure_secret_key |
PORT | 应用服务端口 | 3002 |
创建 docker-compose.yml
文件:
version: '3'
services:
app:
build: .
ports:
- "3002:3002"
environment:
- DATABASE_URL=mysql://user:password@db:3306/trpc_db
- MINIO_ACCESS_KEY=minio_access_key
- MINIO_SECRET_KEY=minio_secret_key
- MINIO_ENDPOINT=minio:9000
- MINIO_USE_SSL=false
- MINIO_BUCKET=trpc-bucket
- JWT_SECRET=your_jwt_secret
- PORT=3002
depends_on:
- db
- minio
db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=root_password
- MYSQL_DATABASE=trpc_db
- MYSQL_USER=user
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
- MINIO_ROOT_USER=minio_access_key
- MINIO_ROOT_PASSWORD=minio_secret_key
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
volumes:
mysql_data:
minio_data:
运行 Docker Compose:
docker-compose up -d
import { prisma } from '@/utils/prisma' // 数据库操作
import { router, publicProcedure, protectedProcedure } from '@/utils/trpc' // tRPC工具
import { z } from 'zod' // 参数校验
在 server/routers/
目录下创建新的路由文件,基本结构如下:
export const newRouter = router({
// 公开接口
publicEndpoint: publicProcedure
.input(z.object({
// 使用zod定义入参类型
param1: z.string(),
param2: z.number()
}))
.query(async ({ctx, input}) => {
// 使用prisma进行数据库操作
const result = await prisma.someModel.findMany({
where: {
field: input.param1
}
})
return result
}),
// 需要认证的接口
protectedEndpoint: protectedProcedure
.input(z.object({
// 入参定义
}))
.mutation(async ({ctx, input}) => {
// ctx.user 可以获取当前登录用户信息
const result = await prisma.someModel.create({
data: {
...input,
userId: ctx.user.id
}
})
return result
})
})
在 server/index.ts
中注册新的路由:
export const appRouter = router({
existingRouter: existingRouter,
newRouter: newRouter // 添加新的路由
})
直接从工具包引入 trpc 客户端:
import { trpc } from '@/utils/trpc/client'
用于获取数据的查询接口:
// 基础查询
const { data, isLoading } = trpc.workflow.list.useQuery();
// 带参数的查询
const { data: task } = trpc.task.getById.useQuery(taskId);
// 带配置的查询
const { data, refetch } = trpc.auth.getCurrentUser.useQuery(undefined, {
retry: false, // 失败是否重试
refetchOnWindowFocus: false, // 窗口聚焦是否重新请求
refetchOnMount: true // 组件挂载时是否重新请求
});
用于修改数据的变更接口:
// 基础用法
const { mutateAsync: createTask } = trpc.task.create.useMutation();
// 带回调的用法
const { mutateAsync: login } = trpc.auth.login.useMutation({
onSuccess: (data) => {
message.success('操作成功');
// 其他成功处理
},
onError: (error) => {
message.error(error.message);
// 其他错误处理
}
});
// 调用方式
try {
await mutateAsync({
param1: 'value1',
param2: 'value2'
});
} catch (error) {
console.error('操作失败:', error);
}
// 1. 使用 refetch 刷新单个查询
const { data, refetch } = trpc.someApi.useQuery();
await refetch();
// 2. 使用 utils 刷新指定查询
const utils = trpc.useUtils();
utils.someApi.someQuery.invalidate();
// 3. 在mutation成功后刷新
const mutation = trpc.someApi.create.useMutation({
onSuccess: () => {
// 刷新相关查询
utils.someApi.list.invalidate();
}
});
// 后端路由定义 (server/routers/task.ts)
export const taskRouter = router({
create: protectedProcedure
.input(z.object({
name: z.string(),
workflowId: z.number()
}))
.mutation(async ({ctx, input}) => {
return prisma.task.create({
data: {
name: input.name,
workflowId: input.workflowId,
ownerId: ctx.user.id,
status: 'PENDING'
}
})
})
});
// 前端使用 (app/components/CreateTask.tsx)
const CreateTask = () => {
const utils = trpc.useUtils();
const createTask = trpc.task.create.useMutation({
onSuccess: () => {
message.success('任务创建成功');
utils.task.list.invalidate(); // 刷新任务列表
}
});
const handleCreate = async () => {
try {
await createTask.mutateAsync({
name: 'New Task',
workflowId: 1
});
} catch (error) {
message.error('创建失败');
}
};
};
// 后端路由定义 (server/routers/task.ts)
export const taskRouter = router({
list: protectedProcedure
.query(async ({ctx}) => {
return prisma.task.findMany({
where: {
ownerId: ctx.user.id
},
orderBy: {
createdAt: 'desc'
}
})
})
});
// 前端使用 (app/components/TaskList.tsx)
const TaskList = () => {
const { data: tasks, isLoading } = trpc.task.list.useQuery();
if (isLoading) return <div>加载中...</div>;
return (
<div>
{tasks?.map(task => (
<div key={task.id}>{task.name}</div>
))}
</div>
);
};
项目内置了完整的国际化支持,基于 i18next 和 react-i18next 实现,支持中文、英文和日语三种语言。
系统的国际化主要分为以下几个部分:
- 前端国际化:通过
i18n-provider
实现,提供 React Context API - 后端国际化:通过 tRPC 中间件实现,支持服务端渲染和响应中的多语言
- 翻译资源:存放于
public/locales/{语言代码}/common.json
中
支持的语言:
- 简体中文 (zh)
- 英文 (en)
- 日语 (ja)
翻译文件采用嵌套的 JSON 结构,方便组织和管理:
{
"title": "网站标题",
"header": {
"home": "首页",
"login": "登录"
},
"nested": {
"key": {
"deep": "深层嵌套的文本"
}
}
}
- 基础用法:
import { useI18n } from '@/app/i18n-provider';
const MyComponent = () => {
// 获取翻译函数和当前语言
const { t, locale } = useI18n();
return (
<div>
<h1>{t('title')}</h1>
<p>{t('header.home')}</p>
{/* 带默认值的用法 */}
<p>{t('some.missing.key', '默认值')}</p>
{/* 当前语言代码 */}
<p>当前语言: {locale}</p>
</div>
);
};
- 切换语言:
import { useI18n } from '@/app/i18n-provider';
const LanguageSelector = () => {
const { locale, setLocale } = useI18n();
return (
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
<option value="zh">简体中文</option>
<option value="en">English</option>
<option value="ja">日本語</option>
</select>
);
};
系统已内置语言切换组件 LanguageSwitcher
,可直接使用:
import LanguageSwitcher from '@/app/components/LanguageSwitcher';
const Header = () => (
<div className="header">
<LanguageSwitcher />
</div>
);
tRPC API 中可以通过 ctx
访问国际化功能:
export const exampleRouter = router({
greeting: publicProcedure
.query(({ ctx }) => {
// 获取当前请求的语言
const locale = ctx.locale; // 'zh', 'en', 'ja'
// 使用翻译函数
const message = ctx.t('greeting.welcome', '欢迎');
return {
message,
locale
};
})
});
- 在
public/locales/{语言}/common.json
中添加新的翻译键值对 - 确保所有支持的语言都添加了对应的翻译
示例:
// public/locales/zh/common.json
{
"new_feature": {
"title": "新功能",
"description": "这是一个新的功能描述"
}
}
// public/locales/en/common.json
{
"new_feature": {
"title": "New Feature",
"description": "This is a description of the new feature"
}
}
// public/locales/ja/common.json
{
"new_feature": {
"title": "新機能",
"description": "これは新機能の説明です"
}
}
如需添加新的语言支持,需要修改以下文件:
- 添加新的翻译文件:
public/locales/{新语言代码}/common.json
- 更新
app/i18n-provider.tsx
中的支持语言列表 - 更新
server/i18n.ts
中的支持语言列表 - 更新
LanguageSwitcher.tsx
中的语言显示名称
示例添加韩语支持:
// app/i18n-provider.tsx
export const supportedLocales = ['zh', 'en', 'ja', 'ko']; // 添加 'ko'
// server/i18n.ts
export const supportedLocales = ['zh', 'en', 'ja', 'ko']; // 添加 'ko'
// app/components/LanguageSwitcher.tsx
const localeNames: Record<string, string> = {
zh: '简体中文',
en: 'English',
ja: '日本語',
ko: '한국어', // 添加韩语显示名称
};
- 使用嵌套结构:按功能模块组织翻译键,避免扁平结构导致的命名冲突
- 保持一致性:为所有支持的语言提供完整的翻译
- 提供默认值:使用
t('key', '默认值')
形式,确保在翻译缺失时有适当的回退 - 模块化管理:将大型应用的翻译拆分为多个命名空间,提高管理效率
欢迎提交 Issue 和 Pull Request!
MIT