架构
数据流设计
🎯 概述
本文档详细介绍数据在 NextJS Base 中的流转过程,从用户操作到数据库存储的完整链路。
🔄 请求流程
完整请求链路
┌─────────────────────────────────────────────────────────────────────┐
│ 用户操作 │
│ (点击按钮、提交表单) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ SmartCrudPage │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 收集用户输入 │ │
│ │ 2. 根据 fieldsConfig 转换搜索参数 │ │
│ │ 3. 调用对应的 Server Action │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ 调用 actions.getList(params)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ wrapAction │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 解析 Action 名称,确定权限级别 │ │
│ │ - pub* → PUBLIC (无需登录) │ │
│ │ - auth* → AUTH (需要登录) │ │
│ │ - sys* → SYSTEM (需要后台权限) │ │
│ │ │ │
│ │ 2. 执行认证检查 │ │
│ │ - 获取当前会话 │ │
│ │ - 验证用户身份 │ │
│ │ │ │
│ │ 3. 执行权限检查 (sys* 前缀) │ │
│ │ - 检查后台访问权限 │ │
│ │ - 检查 RBAC 权限 │ │
│ │ │ │
│ │ 4. 执行业务逻辑 │ │
│ │ │ │
│ │ 5. 记录操作日志 │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ 执行 handler(params, context)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ BaseDAO │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 构建查询条件 │ │
│ │ - 解析 whereJson (搜索条件) │ │
│ │ - 添加软删除过滤 │ │
│ │ - 构建排序条件 │ │
│ │ │ │
│ │ 2. 执行数据验证 (create/update) │ │
│ │ - Schema 验证 │ │
│ │ - 自定义验证器 │ │
│ │ - 唯一性检查 │ │
│ │ │ │
│ │ 3. 执行 Hooks │ │
│ │ - beforeCreate / beforeUpdate / beforeDelete │ │
│ │ │ │
│ │ 4. 字段过滤 │ │
│ │ - 只保留 creatable/updatable 字段 │ │
│ │ │ │
│ │ 5. 数据转换 (transforms) │ │
│ │ - input 转换 (写入前) │ │
│ │ - output 转换 (读取后) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ 调用 Prisma 方法
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Prisma Client │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 构建 SQL 查询 │ │
│ │ 2. 执行数据库操作 │ │
│ │ 3. 返回结果 │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ PostgreSQL │
└─────────────────────────────────────────────────────────────────────┘🔐 权限检查
权限级别
| 前缀 | 级别 | 认证 | RBAC | 日志 |
|---|---|---|---|---|
pub | PUBLIC | ❌ | ❌ | ✅ |
auth | AUTH | ✅ | ❌ | ✅ |
sys | SYSTEM | ✅ | ✅ | ✅ |
_ | PRIVATE | - | - | - |
权限检查流程
┌─────────────────────────────────────────────────────────────────────┐
│ wrapAction 权限检查 │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────┐
│ 解析 Action 名称 │
│ 确定权限级别 │
└───────────┬───────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ pub* 前缀 │ │ auth* 前缀 │ │ sys* 前缀 │
│ PUBLIC 级别 │ │ AUTH 级别 │ │ SYSTEM 级别 │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 可选获取用户 │ │ 必须登录 │ │ 必须登录 │
│ 信息 │ │ │ │ │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
│ │ ▼
│ │ ┌───────────────┐
│ │ │ 检查后台权限 │
│ │ │ hasBackendAccess │
│ │ └───────┬───────┘
│ │ │
│ │ ▼
│ │ ┌───────────────┐
│ │ │ RBAC 权限检查 │
│ │ │ 检查用户角色 │
│ │ │ 是否有此 Action │
│ │ └───────┬───────┘
│ │ │
└───────────────────┴───────────────────┘
│
▼
┌───────────────────────┐
│ 执行业务逻辑 │
└───────────────────────┘RBAC 权限检查详情
// 1. 获取用户角色
const user = await getUser(userId)
const roleIds = user.roles // ['role_id_1', 'role_id_2']
// 2. 获取角色的权限
const roles = await prisma.role.findMany({
where: { id: { in: roleIds }, enable: true }
})
// 3. 汇总所有权限 ID
const permissionIds = roles.flatMap(role => role.permission)
// 4. 获取权限详情
const permissions = await prisma.permission.findMany({
where: { id: { in: permissionIds } }
})
// 5. 检查 Action 是否在权限列表中
const allActions = permissions.flatMap(p => p.actions)
const hasPermission = allActions.includes(actionName)🔄 数据转换
搜索参数转换
SmartCrudPage 会根据 fieldsConfig 中的搜索配置,自动转换搜索参数为 Prisma 查询条件:
// 用户输入
{
name: '张三',
status: 'active',
createdAt: ['2024-01-01', '2024-12-31']
}
// 转换后的 whereJson
{
name: { contains: '张三' }, // like 模式
status: 'active', // exact 模式
createdAt: { // range 模式
gte: '2024-01-01T00:00:00.000Z',
lte: '2024-12-31T23:59:59.999Z'
}
}搜索模式对照
| 模式 | 用户输入 | Prisma 查询 |
|---|---|---|
like | "张三" | { contains: "张三" } |
exact | "active" | "active" |
in | ["a", "b"] | { in: ["a", "b"] } |
range | ["2024-01", "2024-12"] | { gte: ..., lte: ... } |
数据输出转换
BaseDAO 会自动处理 Prisma 返回的特殊类型:
// Prisma 返回
{
id: 'cuid123',
price: Decimal(99.99), // Prisma Decimal 类型
count: BigInt(1000000), // BigInt 类型
createdAt: Date(...) // Date 类型
}
// 转换后
{
id: 'cuid123',
price: 99.99, // 转为 number
count: 1000000, // 转为 number
createdAt: Date(...) // 保持 Date(Next.js 会序列化)
}📊 列表查询流程
完整的 getList 流程
┌─────────────────────────────────────────────────────────────────────┐
│ SmartCrudPage request │
└─────────────────────────────────────────────────────────────────────┘
│
│ params: { current, pageSize, sort, filter }
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 搜索参数转换 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ generateSearchTransform(fieldsConfig) │ │
│ │ │ │
│ │ 输入: { name: '张三', status: 'active' } │ │
│ │ 输出: { whereJson: { name: { contains: '张三' }, ... } } │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ actions.getList(params) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ params: { │ │
│ │ page: 1, │ │
│ │ pageSize: 20, │ │
│ │ whereJson: { ... }, │ │
│ │ sort: { createdAt: 'desc' } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ BaseDAO.getList() │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 构建 WHERE 条件 │ │
│ │ - 合并 whereJson │ │
│ │ - 添加软删除过滤 (deletedAt: null) │ │
│ │ │ │
│ │ 2. 构建排序条件 │ │
│ │ - 解析 sort 参数 │ │
│ │ - 应用默认排序 │ │
│ │ │ │
│ │ 3. 执行查询 │ │
│ │ - 查询数据 (findMany) │ │
│ │ - 查询总数 (count) │ │
│ │ │ │
│ │ 4. 数据转换 │ │
│ │ - serializeRecord (处理 Decimal/BigInt) │ │
│ │ - output transform (自定义转换) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 返回结果 │
│ { │
│ success: true, │
│ data: { │
│ list: [...], │
│ total: 100, │
│ page: 1, │
│ pageSize: 20 │
│ } │
│ } │
└─────────────────────────────────────────────────────────────────────┘✏️ 创建/更新流程
完整的 create/update 流程
┌─────────────────────────────────────────────────────────────────────┐
│ SmartModalForm 提交 │
│ formData: { name: '测试', enable: true, remark: '备注' } │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ actions.create(formData) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ wrapAction 处理 │
│ - 认证检查 ✓ │
│ - 权限检查 ✓ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ BaseDAO.create() │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 1: 字段过滤 │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ 只保留 creatable 字段 │ │
│ │ { name: '测试', enable: true, remark: '备注' } │ │
│ │ ↓ │ │
│ │ { name: '测试', enable: true, remark: '备注' } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 2: 数据验证 │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ - Schema 验证 (required, maxLength, enum...) │ │
│ │ - 自定义验证器 │ │
│ │ - 唯一性检查 │ │
│ │ │ │
│ │ 验证失败 → 抛出 ValidationError │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 3: 执行 beforeCreate Hook │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ beforeCreate: async (data) => { │ │
│ │ // 可以修改数据 │ │
│ │ data.code = data.code.toUpperCase() │ │
│ │ return data │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 4: 执行 input 转换 │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ transforms: { │ │
│ │ input: (data) => { │ │
│ │ // 写入前的数据转换 │ │
│ │ return { ...data, updatedBy: userId } │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 5: 执行 Prisma create │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ prisma.model.create({ data: filteredData }) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 6: 执行 afterCreate Hook │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ afterCreate: async (record) => { │ │
│ │ // 创建后的操作,如发送通知 │ │
│ │ await sendNotification(record) │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Step 7: 执行 output 转换 │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ transforms: { │ │
│ │ output: (record) => { │ │
│ │ // 返回前的数据转换 │ │
│ │ return { ...record, displayName: `#${record.id}` } │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ wrapAction 记录日志 │
│ { │
│ action: 'sysCreateRole', │
│ resource: 'role', │
│ params: { name: '测试' }, │
│ result: { id: 'xxx' }, │
│ success: true, │
│ duration: 123 │
│ } │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 返回结果 │
│ { success: true, data: { id: 'xxx', name: '测试', ... } } │
└─────────────────────────────────────────────────────────────────────┘