Loading...
Loading...
Compare original and translation side by side
Config → Access Check → Hook Chain → Database → Response Hooks配置 → 访问检查 → 钩子链 → 数据库 → 响应钩子| Task | Solution | Details |
|---|---|---|
| Auto-generate slugs | | [references/fields.md#slug-field] |
| Restrict by user | Access control with query constraint | [references/access-control.md] |
| Local API with auth | | [references/queries.md#local-api] |
| Draft/publish | | [references/collections.md#drafts] |
| Computed fields | | [references/fields.md#virtual] |
| Conditional fields | | [references/fields.md#conditional] |
| Filter relationships | | [references/fields.md#relationship] |
| Prevent hook loops | | [references/hooks.md#context] |
| Transactions | Pass | [references/hooks.md#transactions] |
| Background jobs | Jobs queue with tasks | [references/advanced.md#jobs] |
| 任务 | 解决方案 | 详情 |
|---|---|---|
| 自动生成别名 | | [references/fields.md#slug-field] |
| 按用户限制访问 | 带查询约束的访问控制 | [references/access-control.md] |
| 带认证的本地 API | | [references/queries.md#local-api] |
| 草稿/发布功能 | | [references/collections.md#drafts] |
| 计算字段 | | [references/fields.md#virtual] |
| 条件字段 | | [references/fields.md#conditional] |
| 过滤关联数据 | 字段上的 | [references/fields.md#relationship] |
| 避免钩子循环 | | [references/hooks.md#context] |
| 事务处理 | 为所有操作传递 | [references/hooks.md#transactions] |
| 后台任务 | 带任务的作业队列 | [references/advanced.md#jobs] |
npx create-payload-app@latest my-app
cd my-app
pnpm devnpx create-payload-app@latest my-app
cd my-app
pnpm devimport { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
export default buildConfig({
admin: { user: 'users' },
collections: [Users, Media, Posts],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET,
typescript: { outputFile: 'payload-types.ts' },
db: mongooseAdapter({ url: process.env.DATABASE_URL }),
})import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
export default buildConfig({
admin: { user: 'users' },
collections: [Users, Media, Posts],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET,
typescript: { outputFile: 'payload-types.ts' },
db: mongooseAdapter({ url: process.env.DATABASE_URL }),
})import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'author', 'status', 'createdAt'],
},
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'slug', type: 'text', unique: true, index: true },
{ name: 'content', type: 'richText' },
{ name: 'author', type: 'relationship', relationTo: 'users' },
{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
],
timestamps: true,
}import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'author', 'status', 'createdAt'],
},
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'slug', type: 'text', unique: true, index: true },
{ name: 'content', type: 'richText' },
{ name: 'author', type: 'relationship', relationTo: 'users' },
{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
],
timestamps: true,
}export const Posts: CollectionConfig = {
slug: 'posts',
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === 'create' && data.title) {
data.slug = data.title.toLowerCase().replace(/\s+/g, '-')
}
return data
},
],
},
fields: [{ name: 'title', type: 'text', required: true }],
}export const Posts: CollectionConfig = {
slug: 'posts',
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === 'create' && data.title) {
data.slug = data.title.toLowerCase().replace(/\s+/g, '-')
}
return data
},
],
},
fields: [{ name: 'title', type: 'text', required: true }],
}import type { Access } from 'payload'
// Type-safe: admin-only access
export const adminOnly: Access = ({ req }) => {
return req.user?.roles?.includes('admin') ?? false
}
// Row-level: users see only their own posts
export const ownPostsOnly: Access = ({ req }) => {
if (!req.user) return false
if (req.user.roles?.includes('admin')) return true
return { author: { equals: req.user.id } }
}import type { Access } from 'payload'
// 类型安全:仅管理员可访问
export const adminOnly: Access = ({ req }) => {
return req.user?.roles?.includes('admin') ?? false
}
// 行级权限:用户仅能查看自己的文章
export const ownPostsOnly: Access = ({ req }) => {
if (!req.user) return false
if (req.user.roles?.includes('admin')) return true
return { author: { equals: req.user.id } }
}// Local API with access control
const posts = await payload.find({
collection: 'posts',
where: {
status: { equals: 'published' },
'author.name': { contains: 'john' },
},
depth: 2,
limit: 10,
sort: '-createdAt',
user: req.user,
overrideAccess: false, // CRITICAL: enforce permissions
})// 带访问控制的本地 API
const posts = await payload.find({
collection: 'posts',
where: {
status: { equals: 'published' },
'author.name': { contains: 'john' },
},
depth: 2,
limit: 10,
sort: '-createdAt',
user: req.user,
overrideAccess: false, // 关键:强制执行权限
})// ❌ SECURITY BUG: Access control bypassed even with user
await payload.find({ collection: 'posts', user: someUser })
// ✅ SECURE: Explicitly enforce permissions
await payload.find({
collection: 'posts',
user: someUser,
overrideAccess: false, // REQUIRED
})overrideAccess: false// ❌ 安全漏洞:即使传入 user 也会绕过访问控制
await payload.find({ collection: 'posts', user: someUser })
// ✅ 安全:显式强制执行权限
await payload.find({
collection: 'posts',
user: someUser,
overrideAccess: false, // 必须设置
})overrideAccess: falsereq// ❌ DATA CORRUPTION: Separate transaction
hooks: {
afterChange: [async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { docId: doc.id },
// Missing req - breaks atomicity!
})
}]
}
// ✅ ATOMIC: Same transaction
hooks: {
afterChange: [async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { docId: doc.id },
req, // Maintains transaction
})
}]
}reqreq// ❌ 数据损坏:独立事务
hooks: {
afterChange: [async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { docId: doc.id },
// 缺少 req - 破坏原子性!
})
}]
}
// ✅ 原子操作:同一事务
hooks: {
afterChange: [async ({ doc, req }) => {
await req.payload.create({
collection: 'audit-log',
data: { docId: doc.id },
req, // 维持事务
})
}]
}req// ❌ INFINITE LOOP
hooks: {
afterChange: [async ({ doc, req }) => {
await req.payload.update({
collection: 'posts',
id: doc.id,
data: { views: doc.views + 1 },
req,
}) // Triggers afterChange again!
}]
}
// ✅ SAFE: Context flag breaks the loop
hooks: {
afterChange: [async ({ doc, req, context }) => {
if (context.skipViewUpdate) return
await req.payload.update({
collection: 'posts',
id: doc.id,
data: { views: doc.views + 1 },
req,
context: { skipViewUpdate: true },
})
}]
}// ❌ 无限循环
hooks: {
afterChange: [async ({ doc, req }) => {
await req.payload.update({
collection: 'posts',
id: doc.id,
data: { views: doc.views + 1 },
req,
}) // 会再次触发 afterChange!
}]
}
// ✅ 安全:上下文标记打破循环
hooks: {
afterChange: [async ({ doc, req, context }) => {
if (context.skipViewUpdate) return
await req.payload.update({
collection: 'posts',
id: doc.id,
data: { views: doc.views + 1 },
req,
context: { skipViewUpdate: true },
})
}]
}src/
├── app/
│ ├── (frontend)/page.tsx
│ └── (payload)/admin/[[...segments]]/page.tsx
├── collections/
│ ├── Posts.ts
│ ├── Media.ts
│ └── Users.ts
├── globals/Header.ts
├── hooks/slugify.ts
└── payload.config.tssrc/
├── app/
│ ├── (frontend)/page.tsx
│ └── (payload)/admin/[[...segments]]/page.tsx
├── collections/
│ ├── Posts.ts
│ ├── Media.ts
│ └── Users.ts
├── globals/Header.ts
├── hooks/slugify.ts
└── payload.config.ts// payload.config.ts
export default buildConfig({
typescript: { outputFile: 'payload-types.ts' },
})
// Usage
import type { Post, User } from '@/payload-types'// payload.config.ts
export default buildConfig({
typescript: { outputFile: 'payload-types.ts' },
})
// 使用示例
import type { Post, User } from '@/payload-types'// In API routes
import { getPayload } from 'payload'
import config from '@payload-config'
export async function GET() {
const payload = await getPayload({ config })
const posts = await payload.find({ collection: 'posts' })
return Response.json(posts)
}
// In Server Components
export default async function Page() {
const payload = await getPayload({ config })
const { docs } = await payload.find({ collection: 'posts' })
return <div>{docs.map(p => <h1 key={p.id}>{p.title}</h1>)}</div>
}// 在 API 路由中
import { getPayload } from 'payload'
import config from '@payload-config'
export async function GET() {
const payload = await getPayload({ config })
const posts = await payload.find({ collection: 'posts' })
return Response.json(posts)
}
// 在服务端组件中
export default async function Page() {
const payload = await getPayload({ config })
const { docs } = await payload.find({ collection: 'posts' })
return <div>{docs.map(p => <h1 key={p.id}>{p.title}</h1>)}</div>
}// Text
{ name: 'title', type: 'text', required: true }
// Relationship
{ name: 'author', type: 'relationship', relationTo: 'users' }
// Rich text
{ name: 'content', type: 'richText' }
// Select
{ name: 'status', type: 'select', options: ['draft', 'published'] }
// Upload
{ name: 'image', type: 'upload', relationTo: 'media' }
// Array
{
name: 'tags',
type: 'array',
fields: [{ name: 'tag', type: 'text' }],
}
// Blocks (polymorphic content)
{
name: 'layout',
type: 'blocks',
blocks: [HeroBlock, ContentBlock, CTABlock],
}// 文本类型
{ name: 'title', type: 'text', required: true }
// 关联类型
{ name: 'author', type: 'relationship', relationTo: 'users' }
// 富文本类型
{ name: 'content', type: 'richText' }
// 选择类型
{ name: 'status', type: 'select', options: ['draft', 'published'] }
// 上传类型
{ name: 'image', type: 'upload', relationTo: 'media' }
// 数组类型
{
name: 'tags',
type: 'array',
fields: [{ name: 'tag', type: 'text' }],
}
// 区块类型(多态内容)
{
name: 'layout',
type: 'blocks',
blocks: [HeroBlock, ContentBlock, CTABlock],
}| Scenario | Approach |
|---|---|
| Data transformation before save | |
| Data transformation after read | |
| Enforce business rules | Access control function |
| Complex validation | |
| Computed display value | Virtual field with |
| Related docs list | |
| Side effects (email, webhook) | |
| Database-level constraint | Field with |
| 场景 | 实现方式 |
|---|---|
| 保存前的数据转换 | |
| 读取后的数据转换 | |
| 执行业务规则 | 访问控制函数 |
| 复杂验证 | 字段上的 |
| 计算显示值 | 搭配 |
| 关联文档列表 | |
| 副作用操作(邮件、Webhook) | 带上下文守卫的 |
| 数据库级约束 | 设置 |
overrideAccess: falsereqcontextpayload-types.tsAccessadmin.useAsTitleoverrideAccess: falsereqcontextpayload-types.tsAccessadmin.useAsTitle