module5
90 分钟

后端 API 开发

学习进度

当前模块0/6 课
总进度0/36 课
学习时长0 分钟

学习目标

  • 用 Codex 生成 Prisma 数据库模型和迁移脚本
  • 自动生成 RESTful API 路由和控制器
  • 实现输入验证和统一错误处理
  • 生成 Swagger API 文档

402 后端 API 开发

用 Codex 从数据库模型到 RESTful API 的全流程自动化生成

学习目标

完成本课程后,你将能够:

一、数据库模型设计

对话式模型设计

我要为任务管理平台设计数据库模型,实体包括:
- User(用户):邮箱、密码、姓名、角色
- Team(团队):名称、描述、创建者
- Project(项目):名称、描述、所属团队
- Task(任务):标题、描述、状态、优先级、指派人
- Comment(评论):内容、作者、所属任务

请生成 Prisma Schema,包含关联关系和索引

Codex 生成的 Prisma Schema

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

enum Role {
  ADMIN
  MEMBER
  VIEWER
}

enum TaskStatus {
  TODO
  IN_PROGRESS
  REVIEW
  DONE
}

enum Priority {
  LOW
  MEDIUM
  HIGH
  URGENT
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  password  String
  name      String
  avatar    String?
  role      Role     @default(MEMBER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  teams     TeamMember[]
  tasks     Task[]        @relation("Assignee")
  comments  Comment[]
}

model Team {
  id          String   @id @default(cuid())
  name        String
  description String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  members     TeamMember[]
  projects    Project[]
}

model TeamMember {
  id     String @id @default(cuid())
  role   Role   @default(MEMBER)

  userId String
  teamId String
  user   User   @relation(fields: [userId], references: [id])
  team   Team   @relation(fields: [teamId], references: [id])

  @@unique([userId, teamId])
}

model Project {
  id          String   @id @default(cuid())
  name        String
  description String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  teamId String
  team   Team   @relation(fields: [teamId], references: [id])
  tasks  Task[]
}

model Task {
  id          String     @id @default(cuid())
  title       String
  description String?
  status      TaskStatus @default(TODO)
  priority    Priority   @default(MEDIUM)
  dueDate     DateTime?
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt

  projectId  String
  project    Project    @relation(fields: [projectId], references: [id])
  assigneeId String?
  assignee   User?      @relation("Assignee", fields: [assigneeId], references: [id])
  comments   Comment[]
  tags       TaskTag[]
}

model Tag {
  id    String    @id @default(cuid())
  name  String    @unique
  color String    @default("#6366F1")
  tasks TaskTag[]
}

model TaskTag {
  taskId String
  tagId  String
  task   Task   @relation(fields: [taskId], references: [id])
  tag    Tag    @relation(fields: [tagId], references: [id])

  @@id([taskId, tagId])
}

model Comment {
  id        String   @id @default(cuid())
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  taskId String
  task   Task   @relation(fields: [taskId], references: [id])
  authorId String
  author   User   @relation(fields: [authorId], references: [id])
}

执行迁移

/init 帮我生成 Prisma 迁移脚本并执行

npx prisma migrate dev --name init

二、API 路由生成

自动生成 RESTful 路由

请为 Task 模型生成完整的 RESTful API,包括:
- GET /api/tasks — 列表(支持分页、筛选、排序)
- GET /api/tasks/:id — 详情
- POST /api/tasks — 创建
- PUT /api/tasks/:id — 更新
- DELETE /api/tasks/:id — 删除
- PATCH /api/tasks/:id/status — 状态变更

要求:
- Express + TypeScript
- 输入验证(Zod)
- 统一错误处理
- 权限检查中间件

路由示例

// backend/src/routes/task.routes.ts
import { Router } from 'express';
import { TaskController } from '../controllers/task.controller';
import { authenticate } from '../middleware/auth';
import { authorize } from '../middleware/authorize';
import { validate } from '../middleware/validate';
import {
  createTaskSchema,
  updateTaskSchema,
  taskQuerySchema,
} from '../schemas/task.schema';

const router = Router();

router.use(authenticate); // 所有任务路由需要认证

router
  .route('/')
  .get(validate(taskQuerySchema), TaskController.list)
  .post(validate(createTaskSchema), TaskController.create);

router
  .route('/:id')
  .get(TaskController.getById)
  .put(validate(updateTaskSchema), TaskController.update)
  .delete(authorize('ADMIN'), TaskController.delete);

router.patch('/:id/status', TaskController.updateStatus);

export default router;

三、输入验证与错误处理

Zod 验证 Schema

// backend/src/schemas/task.schema.ts
import { z } from 'zod';

export const createTaskSchema = z.object({
  body: z.object({
    title: z.string().min(1).max(200),
    description: z.string().optional(),
    priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional(),
    dueDate: z.string().datetime().optional(),
    projectId: z.string().min(1),
    assigneeId: z.string().optional(),
  }),
});

export const updateTaskSchema = z.object({
  body: z.object({
    title: z.string().min(1).max(200).optional(),
    description: z.string().optional(),
    priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional(),
    status: z.enum(['TODO', 'IN_PROGRESS', 'REVIEW', 'DONE']).optional(),
    dueDate: z.string().datetime().nullable().optional(),
    assigneeId: z.string().nullable().optional(),
  }),
});

export const taskQuerySchema = z.object({
  query: z.object({
    page: z.coerce.number().min(1).default(1),
    limit: z.coerce.number().min(1).max(100).default(20),
    status: z.enum(['TODO', 'IN_PROGRESS', 'REVIEW', 'DONE']).optional(),
    priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional(),
    search: z.string().optional(),
    sortBy: z.enum(['createdAt', 'priority', 'dueDate']).default('createdAt'),
    sortOrder: z.enum(['asc', 'desc']).default('desc'),
  }),
});

统一错误处理中间件

// backend/src/middleware/errorHandler.ts
export class AppError extends Error {
  constructor(
    public statusCode: number,
    public message: string,
    public isOperational = true
  ) {
    super(message);
    Object.setPrototypeOf(this, AppError.prototype);
  }
}

export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction) => {
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      success: false,
      error: err.message,
    });
  }

  if (err instanceof ZodError) {
    return res.status(400).json({
      success: false,
      error: '输入验证失败',
      details: err.errors,
    });
  }

  console.error('Unexpected error:', err);
  return res.status(500).json({
    success: false,
    error: '服务器内部错误',
  });
};

四、控制器与服务层

控制器模式

Codex 生成标准的三层架构:

Controller(控制器)
  → 处理 HTTP 请求/响应
  → 调用 Service

Service(服务层)
  → 业务逻辑
  → 调用 Prisma Client

Prisma Client(数据层)
  → 数据库操作

任务控制器示例

// backend/src/controllers/task.controller.ts
export class TaskController {
  static async list(req: Request, res: Response, next: NextFunction) {
    try {
      const { page, limit, status, priority, search, sortBy, sortOrder } = req.query;
      const result = await TaskService.findMany({
        page, limit, status, priority, search, sortBy, sortOrder,
        userId: req.user!.id,
      });
      res.json({ success: true, ...result });
    } catch (error) {
      next(error);
    }
  }

  static async create(req: Request, res: Response, next: NextFunction) {
    try {
      const task = await TaskService.create({
        ...req.body,
        creatorId: req.user!.id,
      });
      res.status(201).json({ success: true, data: task });
    } catch (error) {
      next(error);
    }
  }
}

五、Swagger 文档自动生成

用 Codex 生成 API 文档注解

/docs 为所有 Task API 添加 Swagger JSDoc 注解,生成 OpenAPI 3.0 文档

注解示例

/**
 * @swagger
 * /api/tasks:
 *   get:
 *     summary: 获取任务列表
 *     tags: [Tasks]
 *     security:
 *       - bearerAuth: []
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *           default: 1
 *       - in: query
 *         name: status
 *         schema:
 *           type: string
 *           enum: [TODO, IN_PROGRESS, REVIEW, DONE]
 *     responses:
 *       200:
 *         description: 任务列表
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 success:
 *                   type: boolean
 *                 data:
 *                   type: array
 *                   items:
 *                     $ref: '#/components/schemas/Task'
 */

配置 Swagger UI

// backend/src/swagger.ts
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';

const options: swaggerJsdoc.Options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'TaskFlow API',
      version: '1.0.0',
      description: '团队任务管理平台 API 文档',
    },
    servers: [{ url: '/api' }],
  },
  apis: ['./src/routes/*.ts', './src/controllers/*.ts'],
};

const specs = swaggerJsdoc(options);

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

访问 `http://localhost:3000/api-docs` 即可查看交互式 API 文档。

六、实战练习

练习 1:生成用户认证 API

请为 User 模型生成完整的认证 API:
- POST /api/auth/register
- POST /api/auth/login
- POST /api/auth/refresh-token
- GET /api/auth/me
- 使用 JWT 认证方案

练习 2:编写集成测试

/test 为 Task API 编写集成测试用例,覆盖:
- 正常创建/更新/删除
- 参数验证失败
- 权限不足
- 不存在的资源

练习 3:性能优化

/review 审查 TaskService 的查询,优化 N+1 问题

七、本课总结

**核心要点:**

  • Prisma Schema 由对话生成,验证关联关系后执行迁移
  • RESTful API 采用 Controller-Service-Prisma 三层架构
  • Zod 提供类型安全的输入验证和 TypeScript 类型推导
  • 统一错误处理中间件捕获所有异常,避免未处理崩溃
  • Swagger JSDoc 注解与代码共存,始终与实现保持同步

**下一步:** 模块 403 — 前端页面构建,搭建任务看板和交互式 UI

下一模块