你有没有遇到过这种尴尬:前端页面都写好了,后端接口还没上线。产品经理催着演示,你只能先返回一堆假数据糊弄过去。
今天做一个能自动根据接口定义生成Mock服务的工具。写个简单的YAML描述,AI帮你补全字段,一键启动一个完整的API服务。
项目背景
前后端分离开发中,接口联调是最耗时的环节。传统做法是后端先写接口文档,前端照着实现,等后端真正上线再替换。这个过程经常因为接口变更而反复修改。
这个项目的思路是:用一个轻量级的Mock服务器,让前端随时可以拿到结构正确的数据,后端开发期间不阻塞任何一方的进度。
适用场景:
- 前后端并行开发
- 快速原型验证
- 自动化测试数据生成
- 演示环境搭建
技术选型
| 组件 | 选择 | 理由 |
|---|---|---|
| Web框架 | FastAPI | 自动生成交互式文档,性能好 |
| 配置格式 | YAML | 人类可读,手写方便 |
| 数据生成 | Faker | 支持中文姓名、地址、手机号 |
| 路由管理 | 动态注册 | 从配置文件自动生成路由 |
| 类型校验 | Pydantic | 运行时自动校验请求响应 |
实现步骤
第一步:安装依赖
pip install fastapi uvicorn pyyaml faker pydantic
第二步:定义接口配置 mock_api.yaml
# 用户相关接口
/users:
method: GET
description: 获取用户列表
response:
status: success
data:
- id: int
name: faker.name()
email: faker.email()
age: "range(18, 60)"
avatar: faker.image_url()
created_at: faker.date_time_this_year()
- count: 100
/users/{user_id}:
method: GET
description: 获取单个用户信息
response:
status: success
data:
id: "{path_param.user_id}"
name: faker.name()
bio: faker.paragraph(nb_sentences=3)
phone: faker.phone_number()
/posts:
method: GET
description: 获取文章列表
query_params:
page: "int, default=1"
size: "int, default=20"
response:
status: success
data:
- id: int
title: faker.sentence(nb_words=6)
content: faker.text(max_nb_chars=500)
author: faker.name()
tags:
- "python"
- "教程"
- "开发"
views: "range(100, 10000)"
published_at: faker.date_time_between(start_date="-1y")
pagination:
page: "{query.page}"
total: 128
/posts:
method: POST
description: 创建新文章
request_body:
title: "str, required"
content: "str, required"
tags: "list, optional"
response:
status: success
data:
id: "auto_increment"
title: "{request.title}"
content: "{request.content}"
created_at: faker.date_time_this_month()
第三步:创建Mock服务器核心代码 mock_server.py
"""
AI Mock API Server
从 YAML 配置自动生成 Mock 接口服务
"""
import os
import re
import yaml
from typing import Any, Dict, Optional
from datetime import datetime
from fastapi import FastAPI, Query, Path, Request
from fastapi.responses import JSONResponse
from faker import Faker
fake = Faker("zh_CN")
class DataGenerator:
"""根据配置生成模拟数据"""
def __init__(self):
self._counter = 0
def generate(self, template: Any) -> Any:
if isinstance(template, str):
return self._eval_string(template)
elif isinstance(template, list):
if len(template) == 1 and isinstance(template[0], dict):
items = []
count_template = template[0].get("count", 5)
count = self._parse_range(count_template)
for i in range(count):
item = self.generate(template[0])
items.append(item)
return items
return [self.generate(item) for item in template]
elif isinstance(template, dict):
return {k: self.generate(v) for k, v in template.items()}
else:
return template
def _eval_string(self, value: str) -> Any:
# 路径参数引用
path_match = re.match(r'\{path_param\.(\w+)\}', value)
if path_match:
return f"path_{path_match.group(1)}_value"
# 查询参数引用
query_match = re.match(r'\{query\.(\w+)\}', value)
if query_match:
return "1"
# 请求体引用
req_match = re.match(r'\{request\.(\w+)\}', value)
if req_match:
return "mock_value"
# 自增ID
if value == "auto_increment":
self._counter += 1
return self._counter
# 范围值
range_match = re.match(r'range\((\d+),\s*(\d+)\)', value)
if range_match:
import random
return random.randint(int(range_match.group(1)), int(range_match.group(2)))
# Faker调用
faker_match = re.match(r'faker\.(\w+)\(([^)]*)\)', value)
if faker_match:
func_name = faker_match.group(1)
args_str = faker_match.group(2)
return self._call_faker(func_name, args_str)
# 类型标注
type_map = {"int": 0, "str": "", "float": 0.0, "bool": True}
if value in type_map:
return type_map[value]
return value
def _call_faker(self, func_name: str, args_str: str) -> Any:
try:
func = getattr(fake, func_name, None)
if func is None:
return fake.name()
if not args_str.strip():
return func()
args = [int(a.strip()) for a in args_str.split(",")]
return func(*args)
except Exception:
return fake.name()
def _parse_range(self, value) -> int:
if isinstance(value, int):
return value
match = re.match(r'range\((\d+),\s*(\d+)\)', str(value))
if match:
import random
return random.randint(int(match.group(1)), int(match.group(2)))
return 5
def load_config(config_path: str) -> Dict:
with open(config_path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def build_router(config: Dict, generator: DataGenerator) -> FastAPI:
app = FastAPI(title="AI Mock API Server", version="1.0.0")
post_data_store: Dict[str, Any] = {}
for route_path, route_config in config.items():
method = route_config.get("method", "GET").upper()
description = route_config.get("description", "")
def make_handler(gen, resp_config, route_path):
async def handler(request: Request):
context = {
"path_param": {},
"query": dict(request.query_params),
}
if request.method == "POST":
body = await request.json()
context["request"] = body
old_eval = gen._eval_string
def enriched_eval(value):
if isinstance(value, str):
pm = re.match(r'\{path_param\.(\w+)\}', value)
if pm and pm.group(1) in context["path_param"]:
return context["path_param"][pm.group(1)]
qm = re.match(r'\{query\.(\w+)\}', value)
if qm and qm.group(1) in context["query"]:
return context["query"][qm.group(1)]
rm = re.match(r'\{request\.(\w+)\}', value)
if rm and rm.group(1) in context.get("request", {}):
return context["request"][rm.group(1)]
return old_eval(value)
gen._eval_string = enriched_eval
try:
data = gen.generate(resp_config.get("response", {}))
return JSONResponse(content=data)
finally:
gen._eval_string = old_eval
return handler
resp_config = route_config
handler = make_handler(generator, resp_config, route_path)
app.add_api_route(
route_path,
handler,
methods=[method],
description=description,
)
return app
generator = DataGenerator()
config_path = os.environ.get("MOCK_CONFIG", "mock_api.yaml")
if not os.path.exists(config_path):
print(f"❌ 找不到配置文件: {config_path}")
exit(1)
config = load_config(config_path)
app = build_router(config, generator)
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("PORT", 8000))
uvicorn.run(app, host="0.0.0.0", port=port)
第四步:运行和测试
# 启动服务
python3 mock_server.py
# 在另一个终端测试
curl http://localhost:8000/users
curl http://localhost:8000/users/42
curl "http://localhost:8000/posts?page=1&size=5"
curl -X POST http://localhost:8000/posts \
-H "Content-Type: application/json" \
-d '{"title":"测试文章","content":"这是测试内容","tags":["AI","Mock"]}'
运行效果
启动服务后,打开 http://localhost:8000/docs,你会看到FastAPI自动生成的交互式文档页面。
每个接口都可以直接点击测试,返回的数据结构完全符合YAML配置。比如GET /users会返回:
{
"status": "success",
"data": [
{
"id": 0,
"name": "张伟",
"email": "zhangwei@example.com",
"age": 35,
"avatar": "https://example.com/avatar.jpg",
"created_at": "2026-03-15T08:30:00"
}
],
"count": 100
}
每次刷新,名字、邮箱、年龄都会变化,完全模拟真实数据。POST /posts 会接收你传入的 title 和 content,返回包含自增ID和创建时间的完整对象。
优化方向
- AI增强:接入大模型,根据字段名自动生成更合理的中文数据。比如字段叫"公司名"就调用企业名称生成器
- 数据库持久化:把POST请求的数据存入SQLite,支持真正的CRUD操作
- 响应延迟模拟:加个
delay_ms配置项,模拟网络慢的场景 - 错误注入:配置某些接口随机返回500或404,用来测试前端的异常处理
- 导出Postman集合:把YAML配置一键转成Postman JSON,方便团队分享
这个工具最实用的地方在于——改个YAML文件,接口结构就变了。不需要写一行Python代码。
把配置文件扔给同事,他们自己就能跑起来测前端,再也不用追着后端问"接口什么时候好"了。
试试在你的项目里加一个 mock_api.yaml,看看效果如何?