MonkeyCode实战:用AI设计RESTful API,从接口定义到Mock服务一步到位

为什么API设计是项目的第一道坎

做过全栈开发的人都知道,前后端联调的瓶颈往往不在代码,而在接口。前端等后端定义接口,后端等前端确认字段,来回拉扯浪费时间。

MonkeyCode的SDD流程天然适合解决这个问题——先定义接口规范,再生成代码。这篇文章,我用一个完整案例演示如何用MonkeyCode从零设计一套RESTful API。

实战项目:待办事项API

需求很简单:一个Todo应用的REST API,包含CRUD、分页、筛选、认证。

Step 1:写清楚API需求

在MonkeyCode里,不要直接说”帮我写个Todo API”。要这样描述:

设计一个Todo应用的RESTful API,要求:
1. 用户注册/登录(JWT认证)
2. Todo的增删改查
3. 支持分页和按状态筛选
4. 支持标签分类
5. 接口版本管理(v1)
6. 统一错误响应格式
7. 速率限制

越具体,生成的API设计越靠谱。

Step 2:MonkeyCode生成的API设计文档

MonkeyCode在SDD的产品设计阶段会输出一份结构化的API规范:

# API Design Specification
base_url: /api/v1
auth: Bearer JWT

endpoints:
  # Auth
  POST /auth/register:
    request:
      username: string (3-20 chars)
      email: string (valid email)
      password: string (8+ chars, must contain upper/lower/digit)
    response:
      user: { id, username, email }
      token: string

  POST /auth/login:
    request:
      email: string
      password: string
    response:
      user: { id, username, email }
      token: string

  # Todos
  GET /todos:
    auth: required
    params:
      page: number (default: 1)
      per_page: number (default: 20, max: 100)
      status: enum[pending, completed, archived]
      tag: string
    response:
      data: [{ id, title, description, status, tags, created_at, updated_at }]
      meta: { page, per_page, total, total_pages }

  POST /todos:
    auth: required
    request:
      title: string (1-200 chars)
      description: string (optional)
      tags: string[] (optional)
    response:
      data: { id, title, description, status, tags, created_at }

  PUT /todos/:id:
    auth: required
    request:
      title: string (optional)
      description: string (optional)
      status: enum[pending, completed, archived]
      tags: string[] (optional)

  DELETE /todos/:id:
    auth: required
    response:
      message: string

# Error Response Format
error:
  code: number
  message: string
  details: object (optional)

Step 3:从API设计到Mock服务

这是MonkeyCode最让我惊喜的功能——确认API设计后,它可以直接生成一个Mock服务器:

// MonkeyCode生成的Mock服务器
const express = require('express');
const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');

const app = express();
app.use(express.json());

// 模拟数据库
const users = new Map();
const todos = new Map();
let todoIdCounter = 1;

// JWT中间件
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({
    code: 401, message: 'Authentication required'
  });
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET || 'dev-secret');
    next();
  } catch {
    res.status(401).json({ code: 401, message: 'Invalid token' });
  }
};

// 速率限制(简单实现)
const rateLimiter = {
  counts: new Map(),
  check(userId) {
    const now = Date.now();
    const window = 60 * 1000; // 1分钟
    const key = `${userId}:${Math.floor(now / window)}`;
    const count = (this.counts.get(key) || 0) + 1;
    this.counts.set(key, count);
    if (count > 60) return false; // 每分钟60次
    return true;
  }
};

// POST /auth/register
app.post('/api/v1/auth/register', (req, res) => {
  const { username, email, password } = req.body;

  // 验证
  if (!username || username.length < 3 || username.length > 20) {
    return res.status(400).json({
      code: 400,
      message: 'Username must be 3-20 characters',
      details: { field: 'username' }
    });
  }

  if (users.has(email)) {
    return res.status(409).json({
      code: 409,
      message: 'Email already registered'
    });
  }

  const id = uuidv4();
  const user = { id, username, email };
  users.set(email, { ...user, password }); // 实际应hash

  const token = jwt.sign({ id, email }, process.env.JWT_SECRET || 'dev-secret', {
    expiresIn: '7d'
  });

  res.status(201).json({ user, token });
});

// GET /todos
app.get('/api/v1/todos', authMiddleware, (req, res) => {
  if (!rateLimiter.check(req.user.id)) {
    return res.status(429).json({ code: 429, message: 'Rate limit exceeded' });
  }

  const page = parseInt(req.query.page) || 1;
  const perPage = Math.min(parseInt(req.query.per_page) || 20, 100);
  const status = req.query.status;
  const tag = req.query.tag;

  let filtered = [...todos.values()].filter(t => t.user_id === req.user.id);
  if (status) filtered = filtered.filter(t => t.status === status);
  if (tag) filtered = filtered.filter(t => t.tags?.includes(tag));

  const total = filtered.length;
  const totalPages = Math.ceil(total / perPage);
  const data = filtered.slice((page - 1) * perPage, page * perPage);

  res.json({
    data,
    meta: { page, per_page: perPage, total, total_pages: totalPages }
  });
});

app.listen(3000, () => console.log('Mock API running on :3000'));

前端同学拿到这个Mock服务,立刻就能开始联调,不用等后端写完。

4个让API设计更专业的MonkeyCode技巧

技巧1:在需求里指定状态码

设计用户API,需要处理以下HTTP状态码:
- 200: 成功
- 201: 创建成功
- 400: 参数错误(带字段级错误信息)
- 401: 未认证
- 403: 无权限
- 404: 资源不存在
- 409: 冲突(如重复注册)
- 422: 业务逻辑错误
- 429: 速率限制

这样生成的错误处理会非常完整,不会遗漏边界情况。

技巧2:要求生成分页的HATEOAS链接

分页响应包含HATEOAS链接:
- self: 当前页
- next: 下一页(如果有)
- prev: 上一页(如果有)
- first: 第一页
- last: 最后一页

生成的响应会包含:

{
  "data": [...],
  "meta": { "page": 2, "per_page": 20, "total": 85 },
  "links": {
    "self": "/api/v1/todos?page=2",
    "next": "/api/v1/todos?page=3",
    "prev": "/api/v1/todos?page=1",
    "first": "/api/v1/todos?page=1",
    "last": "/api/v1/todos?page=5"
  }
}

技巧3:用MonkeyCode生成OpenAPI文档

在技术设计阶段,让MonkeyCode输出OpenAPI 3.0格式的规范文件:

# openapi.yaml - MonkeyCode生成
openapi: 3.0.3
info:
  title: Todo API
  version: 1.0.0
  description: Todo应用RESTful API

paths:
  /api/v1/todos:
    get:
      summary: 获取Todo列表
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: status
          in: query
          schema:
            type: string
            enum: [pending, completed, archived]
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TodoListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    Todo:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
          enum: [pending, completed, archived]
        tags:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time

    TodoListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Todo'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

把这个文件扔进Swagger UI,API文档就有了。

技巧4:让MonkeyCode生成API测试用例

为以上API生成测试用例,覆盖:
1. 正常流程(happy path)
2. 参数边界(空值、超长、非法格式)
3. 权限测试(未登录、越权访问)
4. 并发测试(重复创建、竞态条件)
5. 速率限制测试

MonkeyCode会生成完整的测试代码:

const request = require('supertest');
const app = require('./app');

describe('Todo API', () => {
  let token;
  let todoId;

  beforeAll(async () => {
    // 注册并登录获取token
    const res = await request(app)
      .post('/api/v1/auth/register')
      .send({ username: 'testuser', email: 'test@example.com', password: 'Pass1234' });
    token = res.body.token;
  });

  // Happy path
  test('创建Todo', async () => {
    const res = await request(app)
      .post('/api/v1/todos')
      .set('Authorization', `Bearer ${token}`)
      .send({ title: '测试任务', tags: ['work'] });
    expect(res.status).toBe(201);
    expect(res.body.data).toHaveProperty('id');
    todoId = res.body.data.id;
  });

  // 边界测试
  test('标题为空应返回400', async () => {
    const res = await request(app)
      .post('/api/v1/todos')
      .set('Authorization', `Bearer ${token}`)
      .send({ title: '' });
    expect(res.status).toBe(400);
  });

  test('标题超200字符应返回400', async () => {
    const res = await request(app)
      .post('/api/v1/todos')
      .set('Authorization', `Bearer ${token}`)
      .send({ title: 'x'.repeat(201) });
    expect(res.status).toBe(400);
  });

  // 权限测试
  test('未认证应返回401', async () => {
    const res = await request(app)
      .get('/api/v1/todos');
    expect(res.status).toBe(401);
  });

  test('删除别人的Todo应返回403', async () => {
    // 用另一个用户尝试删除
    const otherUser = await request(app)
      .post('/api/v1/auth/register')
      .send({ username: 'other', email: 'other@example.com', password: 'Pass1234' });
    
    const res = await request(app)
      .delete(`/api/v1/todos/${todoId}`)
      .set('Authorization', `Bearer ${otherUser.body.token}`);
    expect(res.status).toBe(403);
  });
});

从API设计到生产代码的完整路径

需求描述 → MonkeyCode产品设计 → API规范文档
                                    ↓
                    ┌───────────────┼───────────────┐
                    ↓               ↓               ↓
              OpenAPI文档      Mock服务器      测试用例
                    ↓               ↓               ↓
              Swagger UI      前端联调        CI/CD集成
                                    ↓
                              MonkeyCode技术设计
                                    ↓
                              生产代码生成

这条路径的关键是:API设计一次确认,三个产物同时生成。传统开发流程里,API文档、Mock、测试是三件分开做的事,用MonkeyCode可以一次性搞定。

注意事项

  1. API设计一定要人审:MonkeyCode生成的规范通常80%正确,但字段命名、业务逻辑需要人工把关
  2. Mock数据要贴近真实:让MonkeyCode生成有业务含义的mock数据,不要用test1test2
  3. 版本管理从v1开始:不要省略API版本号,即使你觉得只有一版
  4. 错误码要统一:在需求里明确错误响应格式,MonkeyCode会全局统一

MonkeyCode做API设计最大的优势不是”写代码快”,而是”从需求到接口规范的链路短”。传统流程需要产品写PRD→后端定接口→前端确认→文档维护,现在一条prompt就能输出结构化的API规范。

文章摘自:https://www.cnblogs.com/jaryn/p/20218845