为什么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可以一次性搞定。
注意事项
- API设计一定要人审:MonkeyCode生成的规范通常80%正确,但字段命名、业务逻辑需要人工把关
- Mock数据要贴近真实:让MonkeyCode生成有业务含义的mock数据,不要用
test1、test2 - 版本管理从v1开始:不要省略API版本号,即使你觉得只有一版
- 错误码要统一:在需求里明确错误响应格式,MonkeyCode会全局统一
MonkeyCode做API设计最大的优势不是”写代码快”,而是”从需求到接口规范的链路短”。传统流程需要产品写PRD→后端定接口→前端确认→文档维护,现在一条prompt就能输出结构化的API规范。
文章摘自:https://www.cnblogs.com/jaryn/p/20218845
