本节目标:理解当应用从"能跑"走向"能用"时,接口设计会遇到哪些真实问题,以及该怎么跟 AI 描述这些需求。
假设前端需要电影信息和导演信息。后端可以用两种方式返回:
把关联数据"嵌"在主对象里,一次返回所有信息:
嵌套响应长什么样?展开看看{
"id": 1,
"title": "千与千寻",
"year": 2001,
"director": {
"id": 5,
"name": "宫崎骏",
"nationality": "日本"
},
"tags": ["动画", "奇幻", "冒险"],
"rating": {
"average": 9.4,
"count": 2156
}
}
前端直接用 movie.director.name 就能拿到导演名字,用 movie.rating.average 就能拿到评分,不用再发第二个、第三个请求。所有数据一次到位。
只返回关联数据的 ID,前端需要的话自己再查:
扁平响应长什么样?展开看看{
"id": 1,
"title": "千与千寻",
"year": 2001,
"directorId": 5,
"tagIds": [1, 3, 7],
"averageRating": 9.4
}
前端拿到 directorId: 5 后,如果要显示导演名字,还得再调 GET /api/directors/5。如果要显示标签名字,还得拿着 tagIds 去查标签表。
这不是非此即彼的选择,而是看场景:
| 场景 | 推荐结构 | 理由 |
|---|---|---|
| 电影详情页 | 嵌套 | 页面需要展示完整信息,一次查完省得前端多跑几趟 |
| 电影列表页 | 扁平(或轻度嵌套) | 列表只需要标题、年份、海报,带上完整导演信息和所有标签是浪费带宽 |
| 管理后台的表格 | 扁平 | 表格每行只显示关键字段,点击某行再加载详情 |
| 搜索结果 | 扁平 + 少量嵌套 | 搜索结果需要显示标题和评分,但不需要完整的导演履历 |
一个实用的判断标准:前端拿到数据后,还需不需要再发请求才能渲染页面? 如果需要,说明你的接口返回的数据不够,应该嵌套更多关联数据。如果前端拿到数据就能直接渲染,说明刚刚好。
小明把电影详情接口改成了嵌套结构,一个请求返回所有信息。页面从"零件拼装"变成了"一次成型",加载速度快了,体验也好了。
跟 AI 说的时候,直接描述你的需求就行:
"电影详情接口需要同时返回导演信息、标签列表和平均评分,用嵌套结构,一个请求返回所有数据。电影列表接口只返回标题、年份、海报 URL 和平均评分。"
分页搞定了,小明又有了新需求。
他的朋友来试用,说:"你这个电影列表能不能只看动画片?我不想在 500 部电影里一页一页翻着找。"另一个朋友说:"能不能按评分从高到低排?我想看你评分最高的电影。"
小明纠结了:是给每种筛选条件都建一个新接口?GET /api/movies/animation 返回动画片,GET /api/movies/top-rated 返回高分电影?那如果又要按标签筛选又要按评分排序呢?再建一个 GET /api/movies/animation/top-rated?排列组合下来,接口数量会爆炸。
老师傅说:"不用建新接口。一个列表接口,用查询参数组合就行。"
GET /api/movies?tag=动画&sort=rating&order=desc&page=1&limit=20
这一个请求就表达了:"给我标签是'动画'的电影,按评分从高到低排,第 1 页,每页 20 条。"
查询参数的好处是可以自由组合,就像乐高积木:
GET /api/movies?sort=rating&order=descGET /api/movies?tag=动画GET /api/movies?tag=动画&tag=日本GET /api/movies 返回默认排序的全部数据(带分页)接口只有一个,前端根据用户的操作拼不同的参数就行。用户在筛选栏选了"动画",前端加上 tag=动画;用户点了"按评分排序",前端加上 sort=rating&order=desc。接口代码不用改,所有组合都自动支持。
| 参数 | 用途 | 示例 |
|---|---|---|
page / limit |
分页 | ?page=2&limit=20 |
sort / order |
排序 | ?sort=rating&order=desc |
tag / genre |
按分类筛选 | ?tag=科幻 |
year |
按年份筛选 | ?year=2024 或 ?yearFrom=2020&yearTo=2024 |
q / search |
关键词搜索 | ?q=千与千寻 |
minRating |
最低评分 | ?minRating=8 |
这些参数都应该是可选的。不传就用默认值——默认不筛选、默认按创建时间倒序、默认第 1 页每页 20 条。这样既灵活又不会破坏已有的调用方式。
筛选(Filter) 是精确匹配——"标签等于动画"、"年份等于 2024"。搜索(Search) 是模糊匹配——"标题里包含'千与千寻'"。两者可以组合使用,但实现方式不同。筛选用数据库的 WHERE 条件就行,搜索可能需要全文索引。
对于小明的电影库,简单的 LIKE '%关键词%' 搜索就够了。如果数据量大到几十万条,可能需要 PostgreSQL 的全文搜索功能——但那是后面的事,先跑起来再优化。
跟 AI 说:
"电影列表接口支持以下可选查询参数:tag(按标签过滤)、year(按年份过滤)、q(按标题搜索)、sort(排序字段,支持 rating/year/createdAt)、order(asc 或 desc)。所有参数都是可选的,不传就返回默认排序的全部数据。"
把上面学到的概念组合起来,你可以用一段话描述一个完整的接口需求:
从零设计接口时:
"帮我设计电影列表和详情两个接口。列表接口支持分页(offset/limit,默认每页 20 条)、按标签过滤、按评分或年份排序,响应里带上总数和总页数。详情接口返回电影信息,嵌套导演信息、标签列表和平均评分。统一用
{ success, data, error }格式返回。"
给已有接口加功能时:
"现有的
GET /api/movies只返回全部数据,帮我加上分页和过滤功能。分页用 query 参数 page 和 limit,过滤支持按 tag 和 year 筛选,排序支持 sort 和 order 参数。所有新参数都是可选的——不传这些参数时行为跟之前一样,保持向后兼容。"
让 AI 自查时:
"检查一下现有的电影列表接口,有没有以下问题:1)是否支持分页?2)查询参数是否都做了类型校验(比如 page 必须是正整数)?3)排序字段是否限制了允许的值(防止用户传入任意列名)?"
接口能查能筛了,但上线后会遇到新问题——有人提交空数据、重复点击、服务器突然 500。去 当接口出了问题 看看怎么应对。