架构

数据流设计

理解 NextJS Base 的请求处理和数据流转

请求流程 · 权限检查 · 数据转换


🎯 概述

本文档详细介绍数据在 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日志
pubPUBLIC
authAUTH
sysSYSTEM
_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: '测试', ... } }          │
└─────────────────────────────────────────────────────────────────────┘

📚 相关文档