💜 想真正擅长编程? 从一门课程开始,100% 免费

2-7、前后端联调测试

前端和后端都跑起来了,但它们之间可能连不上。跨域问题、接口路径对不上、数据格式不匹配……这一节讲前后端联调的完整流程。


一、联调前的检查清单

在开始联调前,确认以下条件都满足:

检查项 确认方式
后端启动 访问后端接口能返回数据
前端启动 浏览器能打开前端页面
接口地址 前端配置的API地址指向后端
端口不冲突 前端和后端端口不同
数据库有数据 至少有测试数据
登录功能 如果有登录,先测试登录接口

二、配置前端请求后端地址

1. 找到前端API配置

Vite项目 — 看.env.development:

env
VITE_API_BASE_URL=http://localhost:8080/api

Vue CLI项目 — 看vue.config.js里的proxy:

JavaScript
module.exports = {
  devServer: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        pathRewrite: { '^/api': '' }
      }
    }
  }
}

2. 理解Proxy的作用

前端跑在3000端口,后端跑在8080端口,不同端口就是跨域。

Proxy的原理:前端请求发到前端服务器(3000端口),前端服务器把请求转发到后端(8080端口)。因为转发发生在服务器端,没有浏览器的跨域限制。

TEXT
浏览器 → http://localhost:3000/api/user → 代理转发 → http://localhost:8080/api/user

3. 没有Proxy的情况

如果前端没配Proxy,直接用完整地址请求:

JavaScript
// src/utils/request.js
const baseURL = 'http://localhost:8080/api'

const instance = axios.create({
  baseURL: baseURL,
  timeout: 10000
})

这种情况后端必须配跨域(下面讲)。


三、跨域问题处理

什么是跨域

浏览器有个安全策略叫同源策略:A网站不能直接请求B网站的接口。

同源 = 协议相同 + 域名相同 + 端口相同。

TEXT
前端:http://localhost:3000
后端:http://localhost:8080

端口不同 → 跨域

跨域报错长什么样

浏览器F12 → Console面板:

TEXT
Access to XMLHttpRequest at 'http://localhost:8080/api/user' from origin
'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

F12 → Network面板,请求状态显示红色(CORS错误)。

解决方案1:前端Proxy(推荐)

最简单的方式,前端配代理,浏览器不报跨域。

JavaScript
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
}

前端代码里请求时用相对路径:

JavaScript
axios.get('/api/user/list')

解决方案2:后端配置CORS

如果前端不方便配Proxy,后端加跨域配置。

方式1:全局配置(推荐)

Java
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

方式2:注解方式

在Controller类上加:

Java
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/user")
public class UserController {
    // ...
}

方式3:Filter方式

Java
@Component
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse) response;
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        res.setHeader("Access-Control-Allow-Headers", "*");
        res.setHeader("Access-Control-Max-Age", "3600");
        chain.doFilter(request, response);
    }
}

解决方案3:Nginx反向代理(进阶)

生产环境一般用Nginx统一入口:

nginx
server {
    listen 80;
    server_name localhost;

    # 前端
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }

    # 后端API
    location /api/ {
        proxy_pass http://localhost:8080/;
    }
}

四、联调流程

1. 先测后端接口

用Postman或IDEA HTTP Client单独测试后端接口,确认接口能正常返回数据。

TEXT
GET http://localhost:8080/api/user/list

返回:

JSON
{
  "code": 200,
  "message": "success",
  "data": [
    {"id": 1, "username": "admin", "email": "admin@test.com"},
    {"id": 2, "username": "zhangsan", "email": "zs@test.com"}
  ]
}

2. 再测前端页面

浏览器打开前端页面(如http://localhost:3000),看页面能否正常显示。

3. 打开浏览器开发者工具

按F12打开开发者工具,关注以下面板:

Console面板:

  • 看有没有红色报错
  • 看有没有CORS跨域错误

Network面板:

  • 看请求有没有发出去
  • 看请求URL对不对
  • 看响应状态码
  • 看响应数据对不对

Application面板:

  • 看Cookie和Token存了没有
  • 看Local Storage里的数据

4. 测试完整流程

以用户管理为例:

步骤1:登录

  1. 在登录页输入账号密码
  2. 点登录
  3. F12 → Network,找到登录请求
  4. 确认请求参数正确
  5. 确认返回了Token
  6. 确认Token被保存到Local Storage或Cookie

步骤2:访问需要认证的页面

  1. 跳转到用户列表页
  2. F12 → Network,找到列表请求
  3. 确认请求头带了Token
  4. 确认返回了用户列表数据
  5. 确认页面上显示了数据

步骤3:增删改测试

  1. 新增用户 → 确认列表多了一条
  2. 编辑用户 → 确认数据更新了
  3. 删除用户 → 确认列表少了一条

五、常见联调问题

问题1:前端请求404

原因: 前端请求的URL和后端接口路径对不上。

排查:

  1. F12 → Network → 找到404的请求
  2. 看Request URL:http://localhost:8080/api/user/list
  3. 对比后端Controller的路径:
Java
@RestController
@RequestMapping("/api/user")  // 后端路径
public class UserController {
    @GetMapping("/list")     // 完整路径:/api/user/list
    public Result list() { ... }
}
  1. 确认前端请求路径和后端路径一致

常见原因:

  • 前端多加了或漏掉了/api前缀
  • context-path没配对
  • 路径大小写不一致

问题2:前端请求500

原因: 后端代码报错了。

排查:

  1. F12 → Network → 找到500的请求
  2. 看Response,可能有错误信息
  3. 看后端控制台日志,找到报错的堆栈
  4. 根据报错信息定位问题

常见原因:

  • 空指针异常(最常见)
  • 参数没传或格式不对
  • 数据库查询出错

问题3:参数没传到后端

前端传了参数但后端收不到。

排查:

  1. F12 → Network → 看请求的Payload(POST)或Query Params(GET)
  2. 看参数名和后端接收的参数名是否一致

GET请求参数:

JavaScript
// 前端
axios.get('/api/user/list', {
  params: { page: 1, size: 10 }
})
// URL: /api/user/list?page=1&size=10
Java
// 后端
@GetMapping("/list")
public Result list(@RequestParam Integer page, @RequestParam Integer size) {
    // 参数名page、size必须和前端传的一致
}

POST请求JSON:

JavaScript
// 前端
axios.post('/api/user/add', {
  username: 'zhangsan',
  password: '123456'
})
Java
// 后端
@PostMapping("/add")
public Result add(@RequestBody UserDTO dto) {
    // UserDTO的属性名要和前端JSON的key一致
    // UserDTO需要有无参构造和getter/setter
}

问题4:Token没带上

登录成功了,但后续请求返回401(未授权)。

排查:

  1. F12 → Application → Local Storage,看Token存了没有
  2. F12 → Network → 看后续请求的Headers里有没有Authorization

如果没有Token:

检查前端拦截器是否正确设置:

JavaScript
// src/utils/request.js
import axios from 'axios'

const instance = axios.create({
  baseURL: '/api',
  timeout: 10000
})

// 请求拦截器:自动添加Token
instance.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

// 响应拦截器:处理401
instance.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response.status === 401) {
      // Token过期,跳转登录
      localStorage.removeItem('token')
      window.location.href = '/login'
    }
    return Promise.reject(error)
  }
)

export default instance

问题5:数据格式不匹配

前端期望的数据格式和后端返回的不一致。

后端返回:

JSON
{
  "code": 200,
  "data": {
    "list": [...],
    "total": 100
  }
}

前端期望:

JSON
{
  "list": [...],
  "total": 100
}

解决: 统一前后端数据格式,一般后端包一层:

Java
// 后端统一返回格式
@Data
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setMessage("success");
        r.setData(data);
        return r;
    }
}

前端在响应拦截器里统一处理:

JavaScript
instance.interceptors.response.use(response => {
  const res = response.data
  if (res.code === 200) {
    return res.data  // 只返回data部分
  } else {
    alert(res.message)
    return Promise.reject(new Error(res.message))
  }
})

问题6:日期格式不一致

后端返回日期格式和前端期望的不一致。

后端返回: 2026-01-01T08:00:00.000+08:00 前端期望: 2026-01-01 08:00:00

解决: 后端统一日期格式:

Java
// 全局配置
@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customizer() {
        return builder -> {
            builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
            builder.timeZone("Asia/Shanghai");
        };
    }
}

// 或在字段上注解
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createTime;

问题7:OPTIONS预检请求被拦截

跨域请求时浏览器会先发OPTIONS请求(预检),后端没处理就直接返回401。

解决: 后端放开OPTIONS请求:

Java
// 在安全配置里放开OPTIONS
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and()
        .authorizeRequests()
        .antMatchers(HttpMethod.OPTIONS).permitAll()  // 放开OPTIONS
        .antMatchers("/login").permitAll()
        .anyRequest().authenticated();
}

六、调试技巧

1. 后端打断点

在Controller方法里打断点,前端发请求时后端会暂停:

  1. 在方法左边栏点一下,出现红点
  2. 前端触发请求
  3. 后端停到断点处
  4. F8单步执行,F9继续
  5. 查看变量值,确认参数是否正确

2. 前端打断点

F12 → Sources → 找到对应的JS文件 → 断点 → 触发请求 → 逐步调试。

或者在代码里加debugger:

JavaScript
async function getUserList() {
  debugger  // 代码执行到这里会暂停
  const res = await request.get('/api/user/list')
  console.log(res)
}

3. Console.log大法

前端最直接的调试方式:

JavaScript
console.log('请求参数:', params)
const res = await request.post('/api/user/add', params)
console.log('返回结果:', res)

4. 后端打印SQL

确认后端执行的SQL是否正确:

YAML
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

控制台会输出每条SQL语句和参数。

5. Charles/Fiddler抓包

如果前端请求不知道发了什么,用抓包工具看完整的请求和响应。


七、联调完成检查清单

功能 测试点 结果
登录 输入正确账号密码,能登录 ✅
登录 输入错误密码,提示错误 ✅
列表 分页、排序、搜索正常 ✅
新增 数据能保存到数据库 ✅
编辑 修改后数据更新 ✅
删除 删除后列表刷新 ✅
权限 未登录访问跳转登录页 ✅
Token过期 过期后跳转登录 ✅
跨域 无CORS报错 ✅

小结

问题 解决方案
跨域 前端Proxy或后端CORS配置
404 检查前后端路径是否一致
500 看后端日志找报错
参数丢失 检查参数名和格式
401 检查Token是否带上
数据格式不匹配 统一前后端返回格式

前后端联调通了,整个项目就能本地完整运行了。