前言
在Web开发中,前后端分离已经成为主流。前端负责页面展示和用户交互,后端负责业务逻辑和数据存储。为了让前后端能够顺畅地"对话",我们需要一份接口文档,它就像一份合同,规定了双方如何交换数据。本文将通过一个最简单的加法计算器案例,带你从头到尾理解接口文档的定义、前后端的实现,以及HTTP通信背后的原理。
1. 案例背景与需求
1.1 需求描述
我们需要实现一个加法计算器页面,用户输入两个整数,点击提交后,后端计算它们的和,并将结果以醒目的HTML格式返回显示在页面上。
1.2 技术栈
- 前端:HTML + 原生表单提交(不使用AJAX)
- 后端:Spring Boot + SpringMVC
1.3 为什么用原生表单?
因为这是最简单的示例,可以让我们聚焦于HTTP通信的本质,避免AJAX带来的额外概念。
1.4 效果演示
正确演示


错误演示


2. 接口文档的定义
在动手写代码之前,前后端开发人员需要坐在一起(或通过文档)明确接口的细节。接口文档通常包含以下内容:
- 请求路径:URL
- 请求方法:GET/POST/PUT/DELETE等
- 接口描述:说明接口的作用
- 请求参数:每个参数的名称、类型、是否必须、说明
- 请求示例:一个具体的请求样子
- 响应说明:响应内容格式、状态码、示例等
对于加法计算器,我们可以定义如下接口:
加法计算器接口文档
| 项目 | 内容 |
|---|---|
| 请求路径 | /calc/sum |
| 请求方法 | POST(也可以用GET,但这里选POST) |
| 接口描述 | 计算两个整数的和,返回HTML结果 |
请求参数
| 参数名 | 类型 | 是否必须 | 说明 |
|---|---|---|---|
| num1 | Integer | 是 | 参与计算的第一个数 |
| num2 | Integer | 是 | 参与计算的第二个数 |
请求示例(表单格式):
num1=5&num2=3
响应数据
- Content-Type :
text/html;charset=UTF-8 - 响应体 : HTML片段,例如
<h1 style='color: #67c23a;'>计算出的结果是:8</h1>
响应示例:
html
<h1 style='color: #67c23a;'>计算出的结果是:8</h1>
2.1 为什么这样定义?
- 请求方法:虽然GET也能传参,但POST更适合提交数据,且参数不会暴露在URL中(不过本例中其实没有敏感数据,用GET也行,但为了示范,我们用POST)。
- 参数格式 :采用
application/x-www-form-urlencoded,这是HTML表单默认的格式,简单易用。 - 响应格式:返回HTML,因为前端就是普通的页面提交,直接显示结果即可。
3. 前端开发:基于接口文档实现页面
3.1 HTML页面
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加法计算器</title>
<style>
/* 页面整体样式:居中、浅背景、字体优化 */
body {
font-family: "Microsoft YaHei", Arial, sans-serif; /* 友好的中文字体 */
background-color: #f5f5f5; /* 浅灰色背景,避免纯白刺眼 */
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
min-height: 100vh; /* 占满屏幕高度 */
margin: 0;
}
/* 表单容器样式:白色背景、圆角、阴影、内边距 */
form {
background-color: white;
padding: 30px 40px;
border-radius: 8px; /* 圆角 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 轻微阴影,提升层次感 */
width: 300px; /* 固定宽度,适配移动端 */
}
/* 标题样式:居中、颜色、间距 */
h1 {
text-align: center;
color: #333;
margin-top: 0;
margin-bottom: 20px;
font-size: 24px;
}
/* 输入框样式:美化、统一尺寸 */
input[type="text"] {
width: 100%; /* 占满容器宽度 */
padding: 10px;
margin: 8px 0 15px 0; /* 上下间距 */
border: 1px solid #ddd; /* 浅灰色边框 */
border-radius: 4px; /* 小圆角 */
box-sizing: border-box; /* 内边距不影响总宽度 */
font-size: 16px;
}
/* 输入框聚焦样式:高亮边框 */
input[type="text"]:focus {
outline: none; /* 去掉默认外边框 */
border-color: #409eff; /* 蓝色高亮,常用的交互色 */
box-shadow: 0 0 5px rgba(64, 158, 255, 0.2); /* 轻微发光效果 */
}
/* 提交按钮样式:美化、hover效果 */
input[type="submit"] {
width: 100%; /* 占满宽度 */
padding: 12px;
background-color: #409eff; /* 主色调:蓝色 */
color: white; /* 文字白色 */
border: none; /* 去掉默认边框 */
border-radius: 4px;
font-size: 16px;
cursor: pointer; /* 鼠标悬浮变手型 */
margin-top: 10px;
}
/* 按钮悬浮效果:加深颜色,提升交互感 */
input[type="submit"]:hover {
background-color: #337ecc;
}
</style>
</head>
<body>
<form action="calc/sum" method="post">
<h1>计算器</h1>
数字1:<input name="num1" type="text"><br>
数字2:<input name="num2" type="text"><br>
<input type="submit" value=" 点击相加 ">
</form>
</body>
</html>
3.2 关键点解释
<form action="/calc/sum" method="post">:指定提交地址和方式,完全符合接口文档。input的name属性必须与接口文档中的参数名一致(num1、num2)。- 没有写任何JavaScript,浏览器会自动处理表单提交。
3.3 浏览器做了什么?
当用户点击提交按钮时,浏览器会自动:
- 收集表单中所有带有
name属性的输入框的值。 - 按照
application/x-www-form-urlencoded格式对数据进行编码(比如中文会被URL编码,数字不变)。 - 设置请求头
Content-Type: application/x-www-form-urlencoded。 - 将编码后的字符串放入请求体,发送POST请求到
/calc/sum。
实际发送的HTTP请求报文:
POST /calc/sum HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
num1=5&num2=3
4. 后端开发:根据接口文档实现接口
4.1 创建Controller
java
package com.zhongge.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @ClassName CalculatorController
* @Description TODO 实现计算器的控制类
* @Author 笨忠
* @Date 2026-02-22 13:22
* @Version 1.0
*/
@RequestMapping("/calc")
@Controller
public class CalculatorController {
/**
* 实现加法计算器
* @param num1 参数1
* @param num2 参数2
* @return 和的结果
*
* 他接收的是普通参数的形式
*/
@RequestMapping("/sum")
@ResponseBody
public String sum(Integer num1, Integer num2) {
//首先判断参数的合法性
if (num1 == null || num2 == null) {
return "<h1 style='color: #f56c6c;'>参数不合法,请重新输入</h1>";
}
int num = num1 + num2;
//程序跑到这里,说明参数是合法的
return "<h1 style='color: #67c23a;'>计算出的结果是:" + num + "</h1>";
}
}
4.2 关键点解释
@Controller:表明这是一个控制器,可以处理HTTP请求。@RequestMapping("/calc"):类级别的路径,所有方法都继承这个前缀。@RequestMapping("/sum"):方法级别的路径,完整路径为/calc/sum。- 方法参数
Integer num1, Integer num2:这里没有加任何注解。SpringMVC会默认从请求参数 中获取同名参数的值,并进行类型转换(字符串转Integer)。为什么能拿到?因为浏览器发送的是表单格式的POST请求,请求体中的num1=5&num2=3会被Servlet容器解析为请求参数,SpringMVC就能自动绑定。 @ResponseBody:告诉Spring将返回值直接写入响应体,而不是解析为视图(JSP等)。因为我们要返回HTML片段,所以直接返回字符串。- 返回值是HTML字符串,Spring会使用
StringHttpMessageConverter将其写入响应,并设置默认的Content-Type: text/html;charset=UTF-8(实际上由于返回的是HTML标签,Spring会协商为text/html)。
4.3 后端处理流程
- 请求到达Tomcat,解析为
HttpServletRequest。 - SpringMVC的
DispatcherServlet根据/calc/sum找到sum方法。 - 参数解析器看到
num1和num2没有注解,于是从请求参数Map中取值(请求参数包括URL查询参数和POST表单体参数),分别赋值。 - 方法执行,返回HTML字符串。
- 返回值处理器看到方法有
@ResponseBody,使用StringHttpMessageConverter写入响应,并设置响应头Content-Type: text/html;charset=UTF-8。 - 浏览器收到响应,根据
text/html渲染页面,用户看到带颜色的结果。
5. 接口文档的重要性
5.1 为什么需要接口文档?
- 明确约定:前后端可以并行开发,只要按照文档实现,联调时就不会出现数据对不上的问题。
- 减少沟通成本:不需要每次都口头沟通,文档就是唯一的真理。
- 便于维护:后续修改或新增功能,可以追溯原有设计。
5.2 如何编写接口文档?
- 确定业务需求:加法计算器需要两个数,返回和。
- 设计请求路径和方法 :RESTful风格建议用
/calc/sum,方法用POST(或GET)。 - 设计参数:列出每个参数的名称、类型、是否必须、说明。
- 设计响应:包括状态码、Content-Type、响应体示例。
- 提供示例:让开发者一目了然。
5.3 前后端如何遵守?
- 前端 :确保表单的
name与文档一致,method 正确。 - 后端:确保接收的参数名与文档一致,返回的内容格式正确。
6. 深入理解HTTP通信【整体的流程】
6.1 HTTP传输的是什么?
HTTP传输的是字节流 。在加法计算器中,前端发送的请求体 num1=5&num2=3 是文本,被编码成UTF-8字节;后端返回的HTML也是文本,同样编码成字节。网络传输的始终是0和1,但我们可以通过 Content-Type 知道如何解读。
1). 用户填写表单并点击提交
- 用户在两个输入框中分别输入数字,例如
5和3。 - 点击"提交"按钮,浏览器触发表单提交事件。

2). 浏览器自动构造 HTTP 请求

- 请求方法 :
POST(因为<form method="post">)。 - 请求 URL :
/calc/sum(由action属性指定)。 - 请求头 :
Content-Type:浏览器自动设置为application/x-www-form-urlencoded,这是表单的默认编码格式。Accept:浏览器自动添加,通常为text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,表示浏览器期望接收 HTML 页面(优先级最高),但也接受其他格式。- 其他请求头如
Host、Connection等自动添加。
- 请求体 :浏览器将表单数据编码为
key=value格式,并用&连接,然后进行 URL 编码(但这里数字不需要编码),最终请求体为num1=5&num2=3。
3). 后端服务器接收请求
- Tomcat 接收到字节流,解析出 HTTP 报文,将请求头、请求体等信息封装成
HttpServletRequest对象。 - 对于
application/x-www-form-urlencoded格式的 POST 请求,Tomcat 会自动解析请求体,将键值对放入请求参数 Map 中(可以通过request.getParameterMap()获取)。
4). SpringMVC 处理请求
DispatcherServlet根据 URL/calc/sum找到对应的控制器方法sum(Integer num1, Integer num2)。- 由于方法参数没有注解,SpringMVC 使用
ServletRequestMethodArgumentResolver(或类似解析器)来解析参数。它会从请求参数 Map 中查找名为num1和num2的参数值,并进行类型转换(将字符串"5"转为Integer 5)。如果找不到,则赋值为null。 - 方法执行:计算两数之和,并返回一个包含 HTML 标签的字符串。
5). 后端返回响应

- 方法上有
@ResponseBody(或类上有@RestController),Spring 知道要将返回值直接写入响应体。 - 内容协商 :Spring 会考虑客户端的
Accept头(浏览器期望text/html)以及返回值类型(String),来决定使用哪个HttpMessageConverter以及设置什么Content-Type。StringHttpMessageConverter支持多种媒体类型,包括text/plain和text/html。它内部会根据请求的Accept头以及返回值内容来选择合适的媒体类型。通常,如果返回值包含 HTML 标签,它会倾向于选择text/html。- 最终,Spring 设置响应头
Content-Type: text/html;charset=UTF-8,并将字符串写入响应体。
6). 浏览器接收响应并渲染

- 浏览器收到响应,看到
Content-Type: text/html,就知道这是一个 HTML 页面(片段),于是将其作为 HTML 解析并渲染。用户看到带颜色的计算结果。
7)补充:后端是如何接受参数的?
请看下述简图:

-
请求到达后,Tomcat解析请求体
对于
application/x-www-form-urlencoded格式的POST请求,Tomcat会自动将请求体中的字符串(如num1=5&num2=3)按&分割成键值对,再按=分割出参数名和参数值。最终,Tomcat会在
HttpServletRequest内部维护一个参数Map ,这个Map的类型实际上是Map<String, String[]>。为什么是数组?因为同一个参数名可能对应多个值(例如复选框<input type="checkbox" name="hobby" value="读书">可以选多个)。对于简单情况,数组里只有一个元素。 -
SpringMVC的参数解析器登场
当执行到你的
sum(Integer num1, Integer num2)方法时,SpringMVC需要给这两个参数赋值。由于参数没有加任何注解,Spring会使用ServletRequestMethodArgumentResolver(或者更具体的RequestParamMethodArgumentResolver)来解析。 -
从参数Map中查找
解析器会调用
request.getParameterValues("num1"),得到String[]数组,取第一个元素(即"5")。然后,Spring利用内置的类型转换器 (如StringToIntegerConverter)将字符串"5"转换为Integer类型。如果转换失败(例如传入了"abc"),会抛出TypeMismatchException。 -
赋值给形参
转换后的
Integer对象被赋值给方法参数num1。同理处理num2。如果请求中缺少某个参数,则赋值为null(对于基本类型int会报错,所以用包装类型Integer更安全)。
为什么能直接从参数Map中取到值?
关键在于Tomcat的预处理 。Tomcat在解析HTTP请求时,对于 application/x-www-form-urlencoded 格式的POST请求,它会主动读取请求体,解析出参数并填充到请求参数Map中。这意味着,无论是URL查询参数(如 ?num1=5)还是POST表单体中的参数,最终都统一存放在这个Map里。所以SpringMVC可以透明地获取,而不关心参数来自哪里。
重要补充:参数名匹配的细节
- 严格匹配 :默认情况下,参数名必须与方法形参名完全一致。如果前端传的是
number1而你写的形参是num1,就会找不到,导致num1为null。这时可以用@RequestParam("number1") Integer num1来指定映射。 - 大小写敏感 :参数名是大小写敏感的,
num1和NUM1不同。
一句话总结就是:
前端以键值对形式传递数据 → 后端容器解析成参数Map → SpringMVC根据形参名从Map中取值并转换 → 赋值给形参。这个机制是SpringMVC的基石之一,掌握它就能轻松应对各种表单提交场景。
7. 常见问题解答
Q1:如果我用GET请求会怎么样?
如果 <form method="get">,浏览器会把参数拼接到URL后面,例如 /calc/sum?num1=5&num2=3,然后发送GET请求。后端同样能通过 Integer num1 接收到,因为参数在URL中也是请求参数的一部分。但GET请求有长度限制,且参数暴露在地址栏,不适合提交大量或敏感数据。
Q2:后端返回的HTML为什么能直接显示?
因为响应头 Content-Type: text/html 告诉浏览器这是HTML,浏览器就会解析并渲染。如果返回纯文本(text/plain),浏览器会把标签当作文本显示出来。
Q3:如果参数名和方法参数名不一致怎么办?
可以使用 @RequestParam("前端参数名") 来指定映射关系。
Q4:为什么没有设置 produces,响应头却是 text/html?
SpringMVC的内容协商机制:当返回 String 且内容看起来像HTML时,默认协商为 text/html;如果返回纯文本,可能协商为 text/plain。这是由 StringHttpMessageConverter 的行为决定的。也可以手动指定 produces = "text/html" 强制。
Q5:后端框架如何知道你返回字符串中有HTML标签?然后你的响应类型就是HTML呢?
你看,如图,我们返回的是字符串,但是字符串中有html标签。

此时我们观察到响应中自动将Content-Type设置为text/html

这个是为什么呢? 具体请看下述解释:
我们的前端页面虽然没有手动设置 Content-Type,但浏览器在发送请求时,会自动添加一系列默认的请求头 ,其中就包括 Accept 头。这个 Accept 头告诉服务器:"我(浏览器)能够处理哪些类型的响应内容"。对于普通的表单提交或页面导航,浏览器的默认 Accept 头通常包含 text/html,因为它期望服务器返回一个 HTML 页面来展示。
一、浏览器默认的 Accept 头是什么样的?
当你直接在浏览器地址栏输入网址访问,或者通过 <form> 表单提交(非 AJAX),浏览器会自动在请求头中加入类似这样的内容:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
这个头的意思是:
- 我最希望收到
text/html(HTML 页面)。 - 如果不行,
application/xhtml+xml(XHTML)也可以。 - 再不行,
application/xml(XML)也能接受,但权重稍低(q=0.9)。 - 图片格式(
image/avif,image/webp等)也能处理。 - 最后,
*/*表示任何类型都行,但权重最低(q=0.8)。
所以,浏览器天生就是为浏览网页设计的,它默认就期望服务器返回 HTML。即使你没有写任何 AJAX 代码,浏览器也会在请求头里带上这些信息。
二、后端 SpringMVC 如何利用 Accept 头?
在我们的后端代码中,你返回的是一个包含 HTML 标签的字符串:
java
return "<h1 style='color: #67c23a;'>计算出的结果是:" + num + "</h1>";
SpringMVC 在处理方法返回值时,会进行 内容协商(Content Negotiation),步骤大致如下:
- 查看客户端期望的响应格式 :从请求头
Accept中获取。 - 查看可用的消息转换器 :每个转换器能写的媒体类型不同。
StringHttpMessageConverter可以写text/plain和text/html(以及*/*)。MappingJackson2HttpMessageConverter可以写application/json。
- 匹配最合适的媒体类型 :因为客户端
Accept中有text/html,且StringHttpMessageConverter支持text/html,所以协商结果为text/html。 - 设置响应头 :
Content-Type: text/html;charset=UTF-8,然后用该转换器将字符串写入响应体。
浏览器收到响应后,看到 Content-Type: text/html,就知道这是 HTML 内容,于是把它当作网页渲染,你就能看到带样式的标题了。
三、如果浏览器期望的是 JSON 怎么办?
如果前端是用 JavaScript 发起的 AJAX 请求(比如 fetch 或 axios),通常浏览器不会自动添加 text/html 到 Accept 头,而是默认接受 */*,或者你可以手动设置 Accept: application/json。此时,后端内容协商的结果就会不同:
- 如果
Accept是application/json,Spring 会优先选择能写 JSON 的转换器(如MappingJackson2HttpMessageConverter),即使你返回的是字符串,也可能会尝试将其转换为 JSON(如果是字符串,可能会用StringHttpMessageConverter协商出text/plain,但最终取决于配置)。 - 但更常见的是,后端返回一个 Java 对象,Spring 自动序列化成 JSON,响应头就是
application/json。
四、总结:你无需手动设置,浏览器已经帮你做了
- 前端没有手动设置
Accept,但浏览器默认加了,因为它认为你在进行网页浏览。 - 后端没有手动指定
produces,但 SpringMVC 自动根据Accept协商出了text/html。 - 这一切都是 Web 标准和框架的默认行为,让开发者可以专注于业务逻辑。
所以,我们的例子完美展示了 浏览器与服务器之间基于 HTTP 头部的自动协商机制。如果我们希望后端强制返回 JSON,即使浏览器期望 HTML,我们也可以:
- 在
@RequestMapping中添加produces = "application/json"。 - 或者返回
ResponseEntity并手动设置Content-Type。 - 或者前端修改请求,让
Accept头只包含application/json(例如用 AJAX 请求)。
8. 总结
好了,老铁们通过这个最简单的加法计算器案例,我们看到了:
- 接口文档是如何定义前后端契约的。
- 前端如何按照文档实现表单提交。
- 后端如何按照文档接收参数并返回结果。
- HTTP通信的底层细节:字节流、Content-Type、参数绑定等。
加法计算器虽然简单,但揭示了Web开发的核心思想:约定优先,文档先行。后续的案例将引入AJAX和JSON,进一步展示更复杂的交互。老铁们别忘了点赞👍、关注、加收藏,咱们下期见👋~
写在最后
最后或许我们网络通信大家还有一点迷糊,于是呢我去向AI查了一些相关的资料:彻底搞懂 HTTP 通信:从字节流到对象,前后端数据交换全解析