Skip to Content
开发指南资源定义最佳实践

资源定义最佳实践

本文档详细介绍如何设计和定义 MTPC 资源,包括设计原则、Schema 定义、特性配置、元数据设计和关系定义。

资源设计原则

单一职责

每个资源只负责一个业务实体,避免将多个实体混在一个资源中。

反例:

// ❌ 错误:一个资源包含多个实体 const badResource = defineResource({ name: 'user_and_order', // 违反单一职责原则 schema: z.object({ userId: z.string(), orderId: z.string(), userName: z.string(), orderAmount: z.number(), }) })

正例:

// ✅ 正确:分离为两个资源 const userResource = defineResource({ name: 'user', schema: z.object({ id: z.string(), name: z.string(), email: z.string().email(), }), }) const orderResource = defineResource({ name: 'order', schema: z.object({ id: z.string(), userId: z.string(), amount: z.number(), status: z.enum(['pending', 'completed', 'cancelled']), }), relations: [{ name: 'user', type: 'belongsTo', target: 'user', foreignKey: 'userId', }], })

合理的粒度

资源粒度应该根据业务需求和访问模式来决定。

粒度描述适用场景
细粒度每个字段都是一个独立的资源字段级别的权限控制
中粒度每个业务实体都是一个资源实体级别的权限控制
粗粒度每个业务模块都是一个资源模块级别的权限控制

示例:

// 粗粒度:每个字段都是独立资源 const userProfileField = defineResource({ name: 'user_profile_field', schema: z.object({ userId: z.string(), fieldName: z.string(), canEdit: z.boolean(), }), }) // 中粒度:每个业务实体都是一个资源 const userResource = defineResource({ name: 'user', schema: z.object({ id: z.string(), name: z.string(), email: z.string().email(), }), }) // 粗粒度:每个业务模块都是一个资源 const contentModule = defineResource({ name: 'content', schema: z.object({ id: z.string(), title: z.string(), body: z.string(), status: z.enum(['draft', 'published', 'archived']), }), })

关系设计

明确定义资源之间的关系,使用正确的关系类型。

关系类型描述示例
hasOne一对一关系User ↔ UserProfile
hasMany一对多关系User ↔ Order
belongsTo多对一关系Order ↔ User
belongsToMany多对多关系User ↔ Role

示例:

const userResource = defineResource({ name: 'user', schema: z.object({ id: z.string(), name: z.string(), email: z.string().email(), }), relations: [{ name: 'profile', type: 'hasOne', target: 'user_profile', foreignKey: 'userId', description: '用户资料', }], }) const orderResource = defineResource({ name: 'order', schema: z.object({ id: z.string(), userId: z.string(), amount: z.number(), status: z.enum(['pending', 'completed', 'cancelled']), }), relations: [{ name: 'user', type: 'belongsTo', target: 'user', foreignKey: 'userId', description: '订单所属用户', }], })

Schema 定义

Zod Schema 基础

MTPC 使用 Zod 进行 Schema 定义,提供完整的类型安全和验证。

import { z } from 'zod' const userSchema = z.object({ id: z.string().uuid(), name: z.string().min(1).max(100), email: z.string().email(), role: z.enum(['admin', 'user', 'guest']), createdAt: z.date().default(() => new Date()), updatedAt: z.date().default(() => new Date()), })

字段类型选择

Zod 类型描述示例
z.string()字符串z.string().min(1).max(100)
z.number()数字z.number().min(0)
z.boolean()布尔值z.boolean().default(false)
z.date()日期z.date().default(() => new Date())
z.enum([...])枚举z.enum(['admin', 'user', 'guest'])
z.array(...)数组z.array(z.string())
z.object({...})对象z.object({ name: z.string(), age: z.number() })
z.union([...])联合z.union([z.string(), z.number()])

验证规则

使用 Zod 的验证规则确保数据完整性。

const userSchema = z.object({ id: z.string().uuid('Invalid user ID format'), name: z.string().min(1).max(100, 'Name is required'), email: z.string().email('Invalid email format'), age: z.number().min(0).max(150, 'Age must be between 0 and 150'), role: z.enum(['admin', 'user', 'guest'], 'Invalid role'), })

自定义验证器

import { z } from 'zod' // 自定义验证器 const customValidator = z.custom( (value) => { // 自定义验证逻辑 if (typeof value !== 'string' || value.length < 3 || value.length > 20) { return false } return true }, { message: 'Invalid format' } ) const userSchema = z.object({ username: customValidator, })

特性配置

CRUD 能力选择

根据业务需求选择合适的 CRUD 能力。

特性描述使用场景
creatable允许创建资源用户、订单、文章
readable允许读取资源所有资源
updatable允许更新资源用户资料、订单状态
deletable允许删除资源订单、文章
listable允许列表资源所有资源

示例:

const userResource = defineResource({ name: 'user', schema: userSchema, features: { creatable: true, readable: true, updatable: true, deletable: false, // 用户不能直接删除,只能禁用 listable: true, }, }) const orderResource = defineResource({ name: 'order', schema: orderSchema, features: { creatable: true, readable: true, updatable: true, deletable: true, listable: true, }, }) const auditLogResource = defineResource({ name: 'audit_log', schema: auditLogSchema, features: { creatable: false, // 审计日志只能通过系统创建 readable: true, updatable: false, deletable: false, listable: true, }, })

权限定义

根据业务需求定义合适的权限。

const orderResource = defineResource({ name: 'order', schema: orderSchema, features: { creatable: true, readable: true, updatable: true, deletable: true, listable: true, }, permissions: [ { action: 'create', description: '创建订单', scope: 'tenant' }, { action: 'read', description: '查看订单', scope: 'tenant' }, { action: 'update', description: '更新订单', scope: 'own' }, { action: 'delete', description: '删除订单', scope: 'own' }, { action: 'list', description: '列出订单', scope: 'tenant' }, { action: 'export', description: '导出订单', scope: 'tenant' }, ], })

钩子使用

资源生命周期钩子

钩子用于在资源生命周期关键节点插入自定义逻辑。

可用的钩子

钩子触发时机用途
beforeCreate创建数据前数据验证、默认值设置
afterCreate创建数据后发送通知、关联数据创建
beforeUpdate更新数据前权限检查、数据验证
afterUpdate更新数据后审计日志、缓存失效
beforeDelete删除数据前权限检查、关联数据检查
afterDelete删除数据后审计日志、关联数据清理
filterQuery查询过滤行级权限控制

钩子执行顺序

请求 → beforeCreate → 数据验证 → 创建数据 → afterCreate → 响应 请求 → beforeUpdate → 权限检查 → 更新数据 → afterUpdate → 响应 请求 → beforeDelete → 权限检查 → 删除数据 → afterDelete → 响应 查询 → filterQuery → 查询过滤 → 返回数据

beforeCreate 示例

const userResource = defineResource({ name: 'user', schema: userSchema, hooks: { beforeCreate: async (ctx, data) => { // 验证数据 if (!data.email.endsWith('@company.com')) { throw new Error('仅允许公司邮箱') } // 设置默认值 if (!data.status) { data.status = 'active' } // 返回修改后的数据 return data }, afterCreate: async (ctx, result) => { // 发送欢迎邮件 await sendWelcomeEmail(result.email) // 记录审计日志 await auditLog(ctx, { action: 'user.create', resource: 'user', resourceId: result.id, subjectId: ctx.subject.id, }) }, }, })

filterQuery 示例

const orderResource = defineResource({ name: 'order', schema: orderSchema, metadata: { dataScope: { enabled: true, defaultScope: 'department', ownerField: 'createdBy', }, }, hooks: { filterQuery: async (ctx, query) => { const subject = ctx.subject const dataScope = ctx.resource.metadata.dataScope // 如果是管理员,不过滤 if (subject.roles.includes('admin')) { return query } // 根据数据范围过滤 switch (dataScope.defaultScope) { case 'department': if (dataScope.departmentField) { return query.where(eq(orders.departmentId, subject.departmentId)) } break case 'team': if (dataScope.teamField) { return query.where(eq(orders.teamId, subject.teamId)) } break case 'self': if (dataScope.ownerField) { return query.where(eq(orders.createdBy, subject.id)) } break default: return query } }, }, })

元数据设计

显示名称

const userResource = defineResource({ name: 'user', schema: userSchema, metadata: { displayName: '用户', pluralName: '用户列表', description: '系统用户管理', icon: 'user-icon', }, })

分组和排序

const userResource = defineResource({ name: 'user', schema: userSchema, metadata: { group: 'user-management', sortOrder: 1, }, }) const orderResource = defineResource({ name: 'order', schema: orderSchema, metadata: { group: 'order-management', sortOrder: 2, }, }) const productResource = defineResource({ name: 'product', schema: productSchema, metadata: { group: 'product-management', sortOrder: 3, }, })

标签使用

const userResource = defineResource({ name: 'user', schema: userSchema, metadata: { tags: ['core', 'auth', 'user-management'], }, }) const adminResource = defineResource({ name: 'admin', schema: adminSchema, metadata: { tags: ['admin', 'system', 'configuration'], }, })

数据范围控制配置

const orderResource = defineResource({ name: 'order', schema: orderSchema, metadata: { dataScope: { enabled: true, defaultScope: 'department', ownerField: 'createdBy', adminBypass: true, }, }, })

示例:设计用户资源

import { defineResource } from '@mtpc/core' import { z } from 'zod' const userSchema = z.object({ id: z.string().uuid(), name: z.string().min(1).max(100, 'Name is required'), email: z.string().email('Invalid email format'), role: z.enum(['admin', 'user', 'guest'], 'Invalid role'), status: z.enum(['active', 'inactive', 'suspended'], 'Invalid status'), avatar: z.string().url().optional(), bio: z.string().max(500).optional(), createdAt: z.date().default(() => new Date()), updatedAt: z.date().default(() => new Date()), }) export const userResource = defineResource({ name: 'user', schema: userSchema, features: { creatable: true, readable: true, updatable: true, deletable: false, listable: true, }, permissions: [ { action: 'create', description: '创建用户', scope: 'tenant' }, { action: 'read', description: '查看用户', scope: 'tenant' }, { action: 'update', description: '更新用户', scope: 'own' }, { action: 'list', description: '列出用户', scope: 'tenant' }, ], metadata: { displayName: '用户', pluralName: '用户列表', description: '系统用户管理', icon: 'user-icon', group: 'user-management', sortOrder: 1, tags: ['core', 'auth', 'user-management'], dataScope: { enabled: true, defaultScope: 'self', ownerField: 'id', adminBypass: true, }, }, hooks: { beforeCreate: async (ctx, data) => { if (!data.email.endsWith('@company.com')) { throw new Error('仅允许公司邮箱') } if (!data.status) { data.status = 'active' } return data }, afterCreate: async (ctx, result) => { await sendWelcomeEmail(result.email) }, }, })

示例:设计订单资源

import { defineResource } from '@mtpc/core' import { z } from 'zod' const orderSchema = z.object({ id: z.string().uuid(), userId: z.string().uuid(), totalAmount: z.number().min(0, 'Amount must be positive'), status: z.enum(['pending', 'completed', 'cancelled', 'refunded']), items: z.array(z.object({ productId: z.string().uuid(), quantity: z.number().min(1, 'Quantity must be positive'), price: z.number().min(0, 'Price must be positive'), })), createdAt: z.date().default(() => new Date()), updatedAt: z.date().default(() => new Date()), }) export const orderResource = defineResource({ name: 'order', schema: orderSchema, features: { creatable: true, readable: true, updatable: true, deletable: true, listable: true, }, permissions: [ { action: 'create', description: '创建订单', scope: 'tenant' }, { action: 'read', description: '查看订单', scope: 'tenant' }, { action: 'update', description: '更新订单', scope: 'own' }, { action: 'delete', description: '删除订单', scope: 'own' }, { action: 'list', description: '列出订单', scope: 'tenant' }, { action: 'export', description: '导出订单', scope: 'tenant' }, ], metadata: { displayName: '订单', pluralName: '订单列表', description: '订单生命周期管理', icon: 'shopping-cart', group: 'order-management', sortOrder: 2, tags: ['commerce', 'finance'], dataScope: { enabled: true, defaultScope: 'department', ownerField: 'createdBy', adminBypass: true, }, }, relations: [{ name: 'user', type: 'belongsTo', target: 'user', foreignKey: 'userId', description: '订单所属用户', }], hooks: { filterQuery: async (ctx, query) => { const subject = ctx.subject const dataScope = ctx.resource.metadata.dataScope if (subject.roles.includes('admin')) { return query } switch (dataScope.defaultScope) { case 'department': if (dataScope.departmentField) { return query.where(eq(orders.departmentId, subject.departmentId)) } break case 'self': if (dataScope.ownerField) { return query.where(eq(orders.createdBy, subject.id)) } break default: return query } }, }, })

常见问题和解决方案

Q: 如何处理复杂的验证逻辑?

A: 使用 Zod 的 refine()transform() 进行复杂验证。

const passwordSchema = z.object({ password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[a-z])(?=.*\d)(?=.*[@$!%*#]).*$/, 'Password must contain at least one letter, one digit and one special character', ) })

Q: 如何实现软删除?

A: 使用 @mtpc/soft-delete 扩展。

import { defineResource } from '@mtpc/core' import { createSoftDeletePlugin } from '@mtpc/soft-delete' const plugin = createSoftDeletePlugin() const userResource = defineResource({ name: 'user', schema: userSchema, metadata: { softDelete: { enabled: true, deletedAtColumn: 'deleted_at', deletedByColumn: 'deleted_by', }, }, }) const mtpc = createMTPC() mtpc.use(plugin) await mtpc.init()

Q: 如何实现多语言支持?

A: 在 Schema 中使用 z.object()describe() 方法。

const userSchema = z.object({ name: z.string().describe('用户姓名'), email: z.string().email().describe('用户邮箱'), role: z.enum(['admin', 'user', 'guest']).describe('用户角色'), })

继续学习: 权限设计

Last updated on