经常有刚转行做前端的同学跑来找我,第一句话就是:后端哥们给了我一个接口,但是我不知道该怎么调,甚至不知道从哪入手。web前端调后端接口这件事,说穿了就是让浏览器往后端发一次 HTTP 请求,再把后端返回的数据渲染到页面上。但你真去对接的时候,会碰到接口文档看不懂、参数总对不上、跨域报错、请求发出去但返回 500 之类的一堆问题。
这篇文章我就用实际项目经验,把前端调用后端接口这件事从原理到实操讲透。你会知道接口到底是什么、有哪些主流调用方式,也会看到 Java Web、JSP 老项目、FastAPI 这些后端场景下,前端具体该怎么对接,还有开发中最常见的跨域、请求头、参数格式、联调排查这些坑到底怎么解决。无论你是刚入门的前端新人,还是做全栈想补一补前端这一侧知识,这文章都值得收藏。
1. 先搞清楚:后端接口到底是啥
1.1 接口就是“前后端提前约定好的暗号”
很多新人一听“接口”两个字就觉得很高大上,其实你用个生活里的例子就秒懂。你去餐厅吃饭,你要和服务员说“来一份宫保鸡丁”,服务员才能把需求传给厨师,然后把菜端给你。这里“点菜”这个过程,就是一次接口调用。
前端调后端接口,本质就是:前端发一个请求,告诉后端“我要什么数据”或“我要做什么操作”,后端收到请求后把结果返回给前端。这个请求和响应的格式,是前后端提前约定好的。后端不能随便返回一堆嵌套特别深、字段名还是拼音缩写的数据,前端也不能想传什么就传什么,必须按约定来。
在真实开发里,“后端接口”通常指一个可以被 HTTP 访问的 URL。你在浏览器里输入 https://api.example.com/user/1001,如果后端写好了对应处理逻辑,就会返回一段 JSON 数据。这段 JSON 就是给前端用的,不是给人看的 HTML 页面。
1.2 一个接口由哪些部分组成
我建议每个前端至少在脑子里建立这样一个接口模型:一个接口等于“请求地址 + 请求方法 + 请求参数 + 响应数据”。
举例说明。后端如果给了一个接口文档:
- 接口地址:
/api/user/{id} - 请求方法:GET
- 参数说明:
id是用户 ID - 响应格式:
json复制{
"code": 0,
"msg": "ok",
"data": {
"id": 1001,
"name": "张三",
"avatar": "https://xxx.com/a.png"
}
}
那么前端要拿到 id 为 1001 的用户信息,就直接发一个 GET 请求到 /api/user/1001,然后从返回结果里读取 data.name 和 data.avatar,渲染到页面上。
这里还有一个很多新手理解错的地方:code 用来表示业务成功还是失败,不等于 HTTP 状态码。接口响应里虽然也带着 HTTP 状态码,但业务上经常是“HTTP 返回 200,但 code 是 5001,表示这个用户没有权限”。前端业务逻辑判断要用 code,而不是光看请求成没成功。想明白这一点,后面联调会少踩很多坑。
1.3 前后端联调的本质:对暗号
在 JSP 时代,页面数据经常在后端用模板引擎拼好再渲染,用户在页面上基本感觉不到“接口”存在。现在前后端分离开发,后端只提供 API 数据接口,前端用 JavaScript 动态请求数据并渲染,这也是这套知识越来越重要的原因。
所以一个前端开发者在接到需求时,第一步不是急着敲代码,而是“看接口文档”。没有文档就去找后端确认:返回字段有哪些、失败时 code 怎么定义、分页参数叫什么、时间字段是时间戳还是字符串。这些确认得越细,后面联调就越省事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 前端调用接口的方式与选型
2.1 浏览器环境下怎么发起请求
浏览器里有一个核心对象 XMLHttpRequest,它是 API 请求的“老前辈”。后来出现的 jQuery Ajax、axios,本质都是对它的封装。再往后浏览器又提供了原生 fetch API,基于 Promise 实现,写法更简洁,但和 XHR 并不是完全等价的。
由于 js 运行在单线程,发请求不会让页面卡死,真正做事的是浏览器网络线程。请求发出后,异步回调再回过来处理结果。这也是为什么你会看到 then、async/await 这些写法。理解了这个基础,你就明白“页面不刷新也能拿到数据”是怎么回事了。
2.2 原生 XHR 怎么发 GET/POST 请求
看一个最原始的写法,能帮你理解封装后的工具做了什么:
javascript复制const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/user/1001', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
const data = JSON.parse(xhr.responseText);
console.log(data);
} else {
console.error('请求失败', xhr.status);
}
}
};
xhr.send();
readyState 有 0 到 4 几个状态,其中 4 表示请求已完成,响应已就绪。xhr.status 是 HTTP 状态码,200 到 299 都算成功。这里要注意,原生 XHR 默认情况下拿到的 responseText 是字符串,需要手动 JSON.parse 才能变成对象。
POST 请求则要多一步,把数据放进 send() 里:
javascript复制const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/approval/flow', true);
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.send(JSON.stringify({ flowName: '请假审批', level: 2 }));
用 XHR 最大的问题是写起来啰嗦,还要手动处理状态判断、序列化、超时等逻辑。所以实际项目里很少直接这么写,但一旦出了玄学问题,打开浏览器 Network 面板看的依然是这些底层信息。
2.3 fetch 与现代 async/await 写法
fetch 是浏览器原生提供的现代请求 API,基于 Promise,写起来比 XHR 清爽很多:
javascript复制const response = await fetch('/api/user/1001', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
有一个非常经典的坑:fetch 在 HTTP 状态码 404、500 的时候,默认不会进入 catch,它照常返回一个 response,只是 response.ok 是 false。所以想用 try/catch 统一捕获异常,绝不意味着请求失败一定走到了 catch,必须手动判断 response.ok。这一点和 axios 的行为不一样,初学 fetch 的人几乎都栽在这里过。
POST JSON 数据的写法是:
javascript复制const data = await fetch('/api/approval/flow', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ flowName: '请假审批' })
}).then((res) => res.json());
2.4 axios 为什么成了工程化项目的首选
如果你用过 Vue 或 React 做项目,大概率项目里已经装了 axios。它本质上还是对 XHR 的封装,但提供了一个更好用的 Promise 接口。
一个最常用的 POST:
javascript复制import axios from 'axios';
const res = await axios.post('/api/approval/flow', {
flowName: '请假审批'
});
console.log(res.data); // 注意数据在 res.data 里
axios 的响应对象 res 是经过它自己包装的,里面包含 data、status、statusText、headers 等字段,而我们业务上最关心的后端 JSON 结果在 res.data。很多人第一次用 axios 时直接把 res 当成后端返回的数据,发现拿不到 code,就是因为少了这一层理解。
axios 真正强的地方在于拦截器。可以在请求发出前统一加上 token、loading 状态,在响应回来后统一处理业务错误和登录失效逻辑:
javascript复制axios.interceptors.request.use((config) => {
config.headers.Authorization = localStorage.getItem('token') || '';
return config;
});
axios.interceptors.response.use(
(response) => {
const res = response.data;
if (res.code !== 0) {
alert(res.msg);
return Promise.reject(res);
}
return res;
},
(error) => {
if (error.response && error.response.status === 401) {
location.href = '/login';
}
return Promise.reject(error);
}
);
封装之后,每个业务请求里不用再重复处理错误提示和 401 跳转,代码会干净很多。这也是 axios 在团队项目里流行的原因。
2.5 JSP 老项目里 jQuery Ajax 怎么用
热词里提到了 Java Web + JSP 项目,这类老项目在前端页面里经常用 jQuery 的 $.ajax 来调接口。JSP 渲染出页面后,页面内部通过 jQuery 发异步请求,再局部刷新 DOM,其实已经是前后端交互的常见套路。
比如一个审批流设置页面,用户在前端勾了几个审批节点,点保存,你要把这些配置 POST 给后端:
javascript复制$.ajax({
url: '/api/flow/set',
type: 'POST',
contentType: 'application/json;charset=utf-8',
data: JSON.stringify({
businessType: 'leave',
leaveDays: 3,
nodes: [
{ role: 'manager', order: 1 },
{ role: 'hr', order: 2 }
]
}),
dataType: 'json',
success: function (res) {
if (res.code === 0) {
alert('保存成功');
} else {
alert(res.msg);
}
},
error: function (xhr) {
console.error(xhr.status);
}
});
这里最大的坑是 contentType。假如你不写 contentType,jQuery 默认会把它当成 application/x-www-form-urlencoded,也就是表单格式;而如果你又在 data 里传了一个 JS 对象,jQuery 会想办法把它转成 key=value&key2=value2 这种查询串。但是如果后端接口明确用 @RequestBody 接收 JSON,那必须设置 contentType: 'application/json' 并且自己用 JSON.stringify 把对象转成字符串,否则后端的 @RequestBody 接不到数据。
老项目里还有一个禁忌:不要为了等结果把 async 设为 false,那会让浏览器在请求期间完全卡死,用户体验极差,主流浏览器还会直接报“Synchronous XMLHttpRequest on the main thread is deprecated”的警告。
2.6 几种方式的选型对照
新手容易陷入“哪个更好”的焦虑,其实没有绝对好坏,只有场景适合不适合。
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| JSP 老项目、传统页面 | jQuery $.ajax | 不需要额外打包工具,直接在 script 里用 |
| Vue/React 工程化项目 | axios | 支持拦截器、取消请求、统一封装,团队维护方便 |
| 无任何依赖的原生页面 | fetch | 浏览器原生支持,写法简洁,不需要引库 |
| 必须兼容老 IE | XHR 或 polyfill | IE 不支持 fetch,老项目经常有这种限制 |
| 新工具链项目但后端返回 XML | XHR 或 jQuery | 处理 XML 时自由度更高 |
我见过一些项目真的很古老,连 Vue 都不用,页面是后端模板 + jQuery 拼出来的,硬上 axios 反而麻烦,这种情况用 jQuery Ajax 就很顺手。工程项目里则老老实实上 axios,毕竟团队成员都会用,生态也成熟。
3. 完整实操:Java 后端、JSP 审批流和 FastAPI 接口的对接
3.1 后端从源代码层面怎样“给前端一个接口”
想理解前端怎么调到后端,至少要能看明白后端的一段接口代码。以 Java Spring Boot 为例,一个典型的接口控制器是这样的:
java复制@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.getById(id);
return Result.success(user);
}
}
@GetMapping("/{id}") 表示这个方法处理 GET 请求,路径是 /api/user/{id}。前端调 /api/user/1001,Spring 会把 URL 里的 1001 绑定到 id 变量上,然后去数据库查询,最后把 User 对象包在 Result 里返回给前端。
Result 基础封装长这样:
java复制public class Result<T> {
private int code;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.code = 0;
r.msg = "ok";
r.data = data;
return r;
}
// getter/setter 省略
}
为什么要包一层 Result?因为前端需要统一判断请求是否成功。没有这层包装,后端报业务错误时就只能靠 HTTP 状态码,而 HTTP 状态码语义有限,业务错误五花八门根本表达不完。封装一个 { code, msg, data } 结构,所有接口都按这个规则走,前端统一解析,整个项目就稳了。
3.2 Java 后端怎么写“审批流设置”接口给你调
回到热词里的“java web + jsp 项目中前端使用 js + jquery 实现设置审批流”这个真实场景。假设你目前项目里的需求是:提交一种请假审批流程,要求节点配置包括审批角色、审批顺序、最大请假天数。
后端可以提供一个这样的接口:
java复制@PostMapping("/api/flow/set")
public Result<Void> setApprovalFlow(@RequestBody ApprovalFlowDto dto) {
approvalFlowService.saveFlow(dto);
return Result.success(null);
}
ApprovalFlowDto 大概长这样:
java复制public class ApprovalFlowDto {
private String businessType; // 业务类型:leave 请假、overtime 加班
private Integer leaveDays; // 最大请假天数
private List<FlowNodeDTO> nodes; // 审批节点
public static class FlowNodeDTO {
private String role; // 角色标识
private Integer order; // 审批顺序
// getter/setter 省略
}
}
它要求前端 POST 一段 JSON 给 /api/flow/set,内容结构如下:
json复制{
"businessType": "leave",
"leaveDays": 3,
"nodes": [
{ "role": "manager", "order": 1 },
{ "role": "hr", "order": 2 }
]
}
对应到前端 JSP 页面上的代码就是我在 2.5 节写的那样,$.ajax 提交一个 JSON 字符串。你要特别注意,前端的属性名必须和后端 DTO 的字段名一致。后端写的是 leaveDays,前端如果传成 leave_days,Spring 默认情况下根本绑定不上。
3.3 FastAPI 后端的接口长什么样
Python 后端起一个 API 服务也很快,FastAPI 是目前很主流的选择。它比 Spring Boot 更轻,很多内部工具、算法服务都喜欢用它暴露接口。和前端对应的关系也很直观:
python复制from fastapi import FastAPI, Body
from pydantic import BaseModel
from typing import List
app = FastAPI()
class FlowNode(BaseModel):
role: str
order: int
class FlowItem(BaseModel):
businessType: str
leaveDays: int
nodes: List[FlowNode]
@app.post("/api/flow/set")
def set_flow(item: FlowItem):
# 这里把 item 保存到数据库
return {"code": 0, "msg": "ok", "data": None}
FastAPI 能自动生成 Swagger 文档,启动服务后直接访问 http://127.0.0.1:8000/docs,就能看到页面上有所有接口的调试界面,这对前端来说非常友好。前端调这个 Python 接口和调 Java 接口没区别,因为它最终暴露出来的也是 HTTP JSON 接口,调用方根本不关心后端是什么语言写的。
启动时有两点要注意:一是本地联调时要么让后端配好 CORS,要么在前端开发服务器里配代理,不然浏览器会跨域报错;二是如果用 uvicorn main:app --reload 启动,默认地址是 127.0.0.1:8000,前端不能把请求地址写错成 3000 或其他端口。
3.4 用 axios 调 FastAPI 接口的例子
在 Vue 或者 React 项目里,调 FastAPI 的接口没有任何特殊之处:
javascript复制const res = await axios.post('/api/flow/set', {
businessType: 'leave',
leaveDays: 3,
nodes: [
{ role: 'manager', order: 1 },
{ role: 'hr', order: 2 }
]
});
console.log(res.data.code); // 0
我见过有些团队是把 Python 服务单独部署在一个端口,前端开发时直接跨域调 http://localhost:8000/api/flow/set,结果每次都因为 CORS 报错,折腾半天。更有保障的做法是在前端工程里配置开发代理,让浏览器以为请求发给了自己,再由开发服务器转发到 8000 端口。这个细节放到后面跨域章节细说。
4. 参数传递的几种姿势和踩坑记录
4.1 GET 请求的 Query 参数
GET 请求的参数通常拼在 URL 的 ? 后面,这叫 query string。后端用 Spring 接收时可以这样声明:
java复制@GetMapping("/api/user/list")
public Result<List<User>> getUserList(
@RequestParam Integer page,
@RequestParam Integer pageSize,
@RequestParam(required = false) String keyword) {
// ...
}
前端对应可以这样调:
javascript复制// 原生方式
const url = `/api/user/list?page=1&pageSize=10&keyword=张`;
// axios 方式更推荐
const res = await axios.get('/api/user/list', {
params: {
page: 1,
pageSize: 10,
keyword: '张'
}
});
axios 的 params 会自动帮你把对象转成 query string。这里有两个容易忽略的问题:数组参数序列化,例如 ids=[1,2,3],axios 默认转成 ids[]=1&ids[]=2&ids[]=3,后端如果期望的是 ids=1&ids=2&ids=3,就可能会出问题。这种场景最好直接和后端确认参数风格,不行就用 paramsSerializer 自定义转换。
4.2 POST 请求的表单与 JSON,哪一种对口
POST 请求传参通常有两种主流格式:表单格式和 JSON 格式。
表单格式就是把内容编码成 name=张三&age=20,请求头的 Content-Type 是 application/x-www-form-urlencoded。后端如果用 Spring 接收,常写成这样:
java复制@PostMapping("/api/user/form")
public Result<Void> addUser(@RequestParam String name,
@RequestParam Integer age) {
// ...
}
前端 jQuery 写法:
javascript复制$.ajax({
url: '/api/user/form',
type: 'POST',
data: { name: '张三', age: 20 }, // jQuery 默认会转成表单格式
success: function () {}
});
JSON 格式则是把对象转成 JSON 字符串塞进 body 里,请求头 Content-Type 必须是 application/json。后端用 Spring 接收时用 @RequestBody:
java复制@PostMapping("/api/user/json")
public Result<Void> addUser(@RequestBody UserDTO dto) {
// ...
}
axios 传 JS 对象时会默认序列化成 JSON,所以用 axios.post(url, data) 已经自动是 JSON 请求。真正容易出问题的是 jQuery 或者原生 XHR,别人给了你一个 JSON 接口,你却忘了设 contentType 与 JSON.stringify。这是“为什么前端调通了 Postman 但调不通页面”的经典原因。
4.3 请求头里的 Token 和其他信息
接口鉴权通常用 Header 里的 Authorization 字段。后端要求前端这样带 token:
text复制Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxxxx
前端 axios 带 header 的姿势:
javascript复制axios.get('/api/user/info', {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`
}
});
如果在项目里用了 axios 拦截器,就不需要每次手动加。比较稳妥的做法是在请求拦截器里统一读取 token 并挂到 header 上,统一处理 401 跳转登录页。JWT token 有有效期,用户停留时间长了 token 会过期,通常还需要在响应拦截器捕获 401 后去调刷新 token 的接口,这个属于稍微进阶一点的方案,但知道这个流程会让面试官对你刮目相看。
4.4 参数类型与精度问题
我在多个项目里都踩过同一个坑:后端返回一个雪花 ID,长度为 19 位的数字,比如 1648715696427343872,JavaScript 的 Number 类型最大安全整数是 9007199254740991,超出这个范围后精度就会丢失。你在控制台打印出来可能看到的是 1648715696427343900,页面跳转再用这个 ID 去调详情时,后端会回你“数据不存在”。
这种问题的解法是让后端在返回这种超长 ID 时转成字符串,或者前端把 id 字段当作字符串处理,不要做数字运算。所以调接口时如果发现大数字 ID 对不上,先怀疑精度问题,别让数据链路来回排查半天。这是我做了多年前后端联调最印象深刻的教训之一。
5. 跨域、代理配置和联调排查
5.1 浏览器为什么会跨域报错
你在本地开发页面地址是 http://localhost:5173,后端接口在 http://localhost:8080,此时页面发请求到另一个“源”,浏览器就会根据同源策略拦截响应。同源指的是协议、域名、端口完全一致。5173 和 8080 端口不同,所以不同源。
关键是你要理解:这个请求其实发出去了,后端可能也处理成功了,但浏览器出于安全机制不让前端 JS 读取返回结果。这就是为什么你用 Postman 调接口能通,放在浏览器页面里却报跨域错误。这不是后端没写好,而是浏览器策略拦住了。
5.2 后端开启 CORS 的正确姿势
如果后端能改代码,在响应头里加 CORS 配置是最直接的。Java Spring Boot 里可以加一个全局配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*");
}
}
FastAPI 里则内置了 CORSMiddleware:
python复制from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
注意一点:如果后面的接口需要携带 Cookie 进行会话认证,allow_origins 不能配成 "*",必须写明确的前端地址,同时 allow_credentials 设为 true。开发环境图省事全开可以,生产环境还是按白名单来。
5.3 前端开发代理怎么配
开发阶段最推荐的方式其实是代理。前端工程里配置好代理后,页面请求还是发给本地开发服务器,由开发服务器转发到真实后端,这样浏览器看不到跨域,因为请求的同源目标就是本地服务器。
以 Vite 项目为例:
javascript复制// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
});
配置完成后,前端代码里请求写 /api/user/list,Vite 开发服务器会把请求转发到 http://localhost:8080/api/user/list。后端不用专门配 CORS,前端也不用写完整的后端地址。这个方案在本地联调时最省心,我几乎每个前端项目都会用。
如果是 React 的 webpack 构建,devServer 配置里同样有 proxy,基本思路一样。生产环境则通过 Nginx 反向代理解决:
nginx复制location /api/ {
proxy_pass http://192.168.1.10:8080;
}
5.4 请求失败的快速定位套路
很多朋友在联调卡住时会很慌,到处乱改代码。我的建议是先把问题拆成四步走:
第一步,打开浏览器开发者工具的 Network 面板,看请求到底发出去没有。如果 Network 里根本没有这个请求,说明前端代码可能没执行到,要么是事件没绑定,要么是代码报错停了。
第二步,请求发出去了,但 HTTP 状态码是 404,那大概率是 URL 路径不匹配。对照后端的 @RequestMapping 注解或 FastAPI 路由看看,是不是少了一层 /api,是不是单词拼错了。
第三步,状态码是 500 或 502,说明问题大概率在后端。这时别反复折腾前端代码了,赶紧去看后端日志,问后端要异常堆栈。500 不是前端能改好的。
第四步,状态码 200,但页面上没展示数据。这种情况最隐蔽,要么是后端返回的 JSON 结构和你预期不一样,要么是某个字段名对不上。在 success 回调里先 console.log 整个返回对象,和后端提供的文档逐字核对。下面给一张速查表:
| 现象 | 可能原因 | 优先排查方向 |
|---|---|---|
| Network 里没有请求 | 前端代码没有执行到 | 看 Console 报错、断点检查 |
| 请求显示 404 | 路径不对 | 核对 URL 与后端路由 |
| 请求显示 500 | 后端异常 | 看后端日志 |
| 状态 200 但数据为空 | 字段或结构不一致 | 打印返回对象逐字比对 |
| 报错 CORS | 跨域未配置 | 后端 CORS 或前端代理 |
| 请求发出去但参数到不了后端 | Content-Type 不对 | 检查是否 JSON.stringify 并设置对应 contentType |
6. 从拿到接口到上线的完整流程与面试题应对
6.1 别急着写代码,先在工具里把接口调通
我见过不少新人拿到接口文档,第一反应就是直接在业务代码里开写,结果漏了参数、记错了请求方法,然后回头反复改。更稳妥的流程是先用 Postman 或 Apifox 这类工具把接口调通,确认能拿到数据,再去写页面代码。
打开 Postman,选择请求方法,填 URL,设置好 Headers 和 body,发送请求。如果返回结果里的字段和你预期一样,再把同样的格式搬到前端代码里。这一步能帮你把“接口本身的问题”和“前端代码问题”分开,定位效率高很多。
后端如果有 Swagger,更简单,直接在 Swagger 页面点“Try it out”,就能看到请求参数结构和响应示例。前端拿到 Swagger 后能少问后端很多问题。
6.2 给接口单独建一个 api 层
在工程化前端项目里,我不建议每个页面直接散落一堆 axios 请求。更好的做法是给所有请求统一建一层模块。例如 src/api/flow.js:
javascript复制import request from '@/utils/request';
export function setApprovalFlow(data) {
return request({
url: '/api/flow/set',
method: 'post',
data
});
}
export function getApprovalFlow(businessType) {
return request({
url: '/api/flow/detail',
method: 'get',
params: { businessType }
});
}
页面里调用时只看这个模块的方法名,如果以后接口地址变了,只有 api/flow.js 需要改,页面不用动。请求模块 request 里统一处理 baseURL、超时、token、错误提示。这会让项目结构很清晰,也方便其他同事复用。
6.3 Web 前端面试常见相关问题怎么答
“web前端怎么调用后端接口”这个问题,在面试里基本会以这些形式出现:axios 封装怎么设计、如何解决跨域、POST 和 GET 区别、@RequestBody 和 @RequestParam 对应的请求格式差异、接口返回特大数据要注意什么。
回答这类问题不需要背八股,抓住核心逻辑就能答好:前端调用后端接口就是基于 HTTP 协议发请求,常用工具有 XHR、fetch、axios;axios 在工程里流行是因为拦截器等封装能力;跨域解决方式有 CORS、开发代理、Nginx 代理;报文格式由 Content-Type 决定;业务成败看后端约定好的 code 字段,不能只看 HTTP 状态码。
如果把上面这些讲清楚,面试官基本就能确认你是一个真正联调过项目的人,而不是只会照抄代码的人。
6.4 一个前端在真实项目里的工作时间分布
你可能以为写页面占比最大,其实真正常规页面开发里,“调接口接口联调”往往是耗费时间最多的环节。理解接口、确认字段、传参、调试渲染、处理边界情况,每一步都可能有坑。所以日常项目里别只关注 UI 层面,多花点精力理解接口设计、HTTP 协议和调试工具,长期看提升非常明显。
我自己养成的一个习惯是:每次开始新的前后端联调前,先把接口文档或 Swagger 页面完整过一遍,把请求参数和返回字段整理成一张本地表格,甚至能生成一个简单的 TypeScript 类型定义。这能很大程度避免项目后期因为字段类型、命名不统一造成的返工。
最后聊聊我踩过几次坑之后的心得
做这个方向久了,我最大的体会是:前端调后端接口,最耗时间的往往不是“不会发请求”,而是参数格式和命名对不上。前后端各说各话,后端写的是 userName,前端传的是 username,刚联调时来回报错,谁也不认为是自己的问题。后来我们都坚持用接口文档或 Swagger 作为唯一标准,后端出文档、前端按文档自测,问题率立刻降了很多。
另外一个建议是,前端老项目如果遇到“后端接口调用不顺畅”的情况,可以先不用急着改工程代码,到浏览器 Network 里看请求的 Headers、Payload、Response 三个页签,确认请求发出去了、参数对了、响应是什么,再决定从哪一侧动手。90% 的联调问题都能靠这个基本功解决。
个人经验里还有一个特别值得强调的是:参数校验永远不要只靠前端页面做了就行,真正底线的校验一定在后端,但前端也必须在调用前对必填项、格式、范围做一次拦截,免得把用户填错的数据浪费一次网络请求。这不只是体验问题,也是后端同学对前端信任的来源。
接口这块内容看着多,只要从头到尾完整走一遍“看文档、调通 Postman、封装请求、页面渲染、处理错误”这个流程,你很快会形成肌肉记忆。以后再遇到陌生的后端接口,心里就有底了。
