前后端联调:代码都没错,怎么就跑不起来?

前后端联调:代码都没错,怎么就跑不起来?

技术不玄学 · 前端开发手记 02

前端开发中,有一个相当微妙的阶段。

页面写完了,按钮能点,弹窗能开,表格也排列得整整齐齐。

你对自己的工作很满意。

直到后端发来一句话:

“接口好了,可以联调了。”

从这一刻开始,项目进入了一个新的阶段。

你发现登录按钮点击没反应,列表数据加载不出来,提交表单提示参数错误。

而后端告诉你:“我这边测试是正常的。”

两边都正常,合在一起却不正常。

这就是前后端联调。

一、什么是前后端联调?

前端页面完成

简单来说,前后端联调就是让前端页面和后端接口真正配合起来,验证整个业务流程能不能正常运行。

联调之前,前端可能使用模拟数据:

const user = { id: 1, name: '小明', avatar: '/default.png' };

这些数据很听话。你想让它叫什么,它就叫什么;你想让它有头像,它就有头像。

真实接口可能返回:

{
  "code": 0,
  "data": {
    "id": 1,
    "nickname": "小明",
    "avatarUrl": null
  }
}

你原本读取 user.name,后端返回的却是 nickname;你以为头像一定存在,结果得到 null。

模拟数据是你想象中的世界,联调才是项目真正生活的地方。

二、联调之前,先把接口文档看明白

阅读接口文档

假设我们需要实现用户登录:

POST /api/user/login

请求体(JSON):

{
  "username": "admin",
  "password": "123456"
}

响应示例:

{
  "code": 0,
  "message": "success",
  "data": { "token": "example-token" }
}

至少要确认:

  • 请求方法是 GET 还是 POST?
  • 参数放在 URL、请求体,还是请求头?
  • 请求体使用 JSON 还是 FormData?
  • 业务成功状态码是 0 还是 200?
  • Token 从哪里获取,后续如何携带?

计算机不会替你理解“差不多”。userName 和 username 可能就是两个不同的字段。

三、Network:联调现场最可靠的证人

打开浏览器开发者工具(通常按 F12),进入 Network → Fetch/XHR,重新触发请求。

重点检查四件事。

1. 请求发出去了吗?

如果 Network 中没有请求,先检查按钮事件、条件判断和调用链:

<template>
  <button @click="handleLogin">登录</button>
</template>

<script setup>
function handleLogin() {
  console.log('点击了登录按钮');
}
</script>

连日志都没出现,就先别急着找后端。请求还没出门呢。

2. 请求地址对吗?

你期望请求:

https://test.example.com/api/user

实际却请求:

http://localhost:5173/api/user

如果没有正确配置开发代理,请求可能根本没有到后端。

3. 参数对吗?

后端要求:

{ "pageNum": 1, "pageSize": 10 }

前端却发送:

{ "page": 1, "size": 10 }

双方的命名都挺合理,只是没有达成共识。

4. 响应说了什么?

HTTP 200 OK 不等于业务成功:

{
  "code": 40101,
  "message": "登录已过期",
  "data": null
}

服务器成功地告诉你:登录失败了。这两件事并不矛盾。

四、同样是加载失败,原因可能完全不同

Network 现象 优先排查方向
404 Not Found 请求路径、开发代理、后端路由
200 OK,业务提示 Token 过期 鉴权请求头、Token 有效期
200 OK,records: [] 查询条件、数据环境、空状态展示
请求显示 CORS 错误 预检请求、跨域响应头、代理配置
没有任何请求 点击事件、代码执行路径、请求是否被提前拦截

先拿到证据,再判断问题属于哪一层。

五、跨域:浏览器为什么不让请求过去?

前端运行在 http://localhost:5173,后端运行在 http://localhost:8080。端口不同,属于不同源。

浏览器会实施同源策略相关限制。如果后端没有正确配置 CORS,前端 JavaScript 就可能无法读取响应。

跨域不等于请求一定没到服务器。 有些请求已经到达后端,但响应缺少必要的 CORS 头,浏览器不允许前端代码读取。对于需要预检的请求,OPTIONS 预检失败后,实际业务请求可能根本不会发出。

Vite开发代理

本地开发可以使用 Vite 代理:

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
});

前端请求:

fetch('/api/user');

开发服务器会转发到后端。注意:开发代理不等于生产环境自动解决跨域,上线后仍需配置反向代理或后端 CORS。

不要把 mode: 'no-cors' 当作通用解决方案,它通常只会让你拿到无法读取内容的 opaque response。

就像有人告诉你快递已经送到了,却不允许你打开包装。

六、接口数据结构:小问题,大麻烦

字段名不一致

后端返回:

{
  "code": 0,
  "data": {
    "records": [{ "id": 1, "name": "小明" }],
    "total": 1
  }
}

如果使用 Axios,且请求封装直接返回原始 Axios 响应:

const response = await axios.get('/api/users');
const records = response.data.data.records;

第一个 data 是 Axios 响应体,第二个 data 是业务响应字段。

但如果项目里的请求拦截器已经返回 response.data,取值方式就要相应调整。

先确认拿到的是哪一层数据,再写取值代码。

七、Vue 页面不更新,不一定是接口的错

例如:

const list = ref([]);

async function loadData() {
  const response = await getUserList();
  list.value = response.data.records;
}

这段代码能否正常工作,取决于 getUserList() 返回的数据结构。

如果它返回业务响应体,这样写可能正确;如果它直接返回 records 数组,就不能再访问 response.data.records。

接口文档描述的是约定,运行时拿到的是事实。两者不一致时,先找出差异,再决定修改哪一边。

八、怎么跟后端高效沟通?

“接口有问题,你看看。”

这句话的信息量很有限。

不如这样说:

用户列表接口 /api/users,测试环境,GET 请求。

请求参数:pageNum=1&pageSize=10。

HTTP 状态码为 200,业务 code 为 0。

但 data.records 是 null,接口文档约定的是数组。

麻烦确认一下,空数据场景是否应该返回 []?

必要时附上脱敏后的请求与响应,不要在公共群里暴露真实 Token、Cookie、密码或用户隐私信息。

好的联调沟通,不是急着证明谁写错了,而是让问题尽快变得清楚。

九、前后端联调排查清单

写在最后

联调成功

前后端联调,表面上是在检查接口,实际上是在验证一份双方共同遵守的约定。

一个字段名,一种数据类型,一条错误信息,甚至一个不起眼的请求头,都可能决定功能能否顺利运行。

下次遇到问题,不妨先打开 Network,看看请求究竟经历了什么。

比起猜测,证据通常更可靠。

而比起争论是谁的问题,尽快解决问题显然更重要。

毕竟,项目上线之后,用户不会关心这是前端的问题,还是后端的问题。

联调后的傍晚

他们只会说:

“你们这个网站怎么打不开?”


《技术不玄学》· 松弛派技术手记

posted @ 2026-10-27 14:11  xyk00712  阅读(5)  评论(0)    收藏  举报