【JavaEE11-后端部分】SpringMVC06-综合案例2-从用户登录看前后端交互:接口文档与HTTP通信详解

1. 案例背景与需求

1.1 需求描述

实现一个用户登录功能:

  • 页面提供用户名和密码输入框,点击登录按钮,通过AJAX提交数据。
  • 后端验证用户名密码(固定为 lisi/admin),验证成功则将用户名存入Session,并返回 true;否则返回 false
  • 前端根据返回结果,成功则跳转到主页,失败则弹出提示。
  • 主页通过另一个AJAX请求获取当前登录的用户名并显示。

1.2 为什么用AJAX?

为了更好的用户体验,不刷新整个页面即可完成登录。AJAX允许异步通信,页面无闪动。

1.3 涉及接口

  • 登录接口:/user/login (POST)
  • 获取登录用户接口:/user/getLoginUser (GET)

1.4 结果演示



2. 接口文档定义

2.1 登录接口

项目 内容
请求路径 /user/login
请求方法 POST
接口描述 用户登录,验证用户名密码
请求参数
参数名 类型 是否必须 说明
username String 用户名
password String 密码

请求示例(表单格式):

复制代码
username=lisi&password=admin
响应数据
  • Content-Type : application/json;charset=UTF-8
  • 响应体 : JSON布尔值 truefalse

响应示例

json 复制代码
true

2.2 获取登录用户接口

项目 内容
请求路径 /user/getLoginUser
请求方法 GET
接口描述 获取当前登录用户的用户名
请求参数

响应数据
  • Content-Type : text/plain;charset=UTF-8
  • 响应体: 用户名(字符串),如果未登录则返回空或 null

响应示例

复制代码
lisi

2.3 为什么这样定义?

  • 登录接口返回JSON布尔值,便于前端直接判断。
  • 获取用户接口返回纯文本,因为只是简单字符串。
  • 登录接口使用POST,因为提交数据;获取用户接口使用GET,因为获取资源。

3. 前端开发:基于接口文档实现

3.1 登录页面(login.html)

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>登录页面</title>
</head>
<style>
    /* 页面整体居中+浅背景 */
    body {
      font-family: "Microsoft YaHei", sans-serif;
      background-color: #f8f9fa;
      margin: 0;
      min-height: 100vh;
      display: flex;
      justify-content: center; /* 水平居中 */
      align-items: center; /* 垂直居中 */
    }
    /* 登录表单容器:白色背景+轻微阴影+圆角 */
    .login-box {
      background: white;
      padding: 30px 40px;
      border-radius: 8px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
      width: 300px;
    }
    /* 标题居中+深蓝色 */
    h1 {
      color: #2d3748;
      text-align: center;
      margin-top: 0;
      margin-bottom: 20px;
    }
    /* 输入框美化:浅边框+圆角+聚焦变色 */
    input[type="text"], input[type="password"] {
      width: 100%;
      padding: 8px;
      margin: 8px 0 12px 0;
      border: 1px solid #dee2e6;
      border-radius: 4px;
      box-sizing: border-box;
      color: #2d3748;
    }
    input[type="text"]:focus, input[type="password"]:focus {
      outline: none;
      border-color: #4299e1;
    }
    /* 登录按钮:居中+蓝色+轻微hover效果 */
    input[type="button"] {
      width: 100%;
      background-color: #4299e1;
      color: white;
      border: none;
      padding: 10px;
      border-radius: 4px;
      cursor: pointer;
      margin-top: 8px;
      font-size: 14px;
    }
    input[type="button"]:hover {
      background-color: #3182ce;
    }
  </style>
<body>
  <div class="login-box">
    <h1>用户登录</h1>
    用户名:<input name="userName" type="text" id="userName"><br>
    密码:<input name="password" type="password" id="password"><br>
    <input type="button" value="登录" onclick="login()">
  </div>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  <script>
    function login() {
      //实现登录的话,那么这里边我们使用ajax,因为他可以做到异步通信,而不需要刷新这个页面
      $.ajax({
        method: "post",
        url: "/user/login",
        data: {
          username: $("#userName").val(),
          password: $("#password").val()
        },
        success: function(result) {//注意:这里只是看HTTP请求是否成功,不涉及是否返回值
          //如果成功
          if(result) {
            //调转页面
            location.href="index.html";
          }else{
            alert("用户名或者密码错误,请确认!");
          }
        }
      });
    }

  </script>
</body>

</html>

3.2 主页(index.html)

html 复制代码
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>用户登录首页</title>
</head>
<style>
      body {
        font-family: "Microsoft YaHei", sans-serif;
        background-color: #f8f9fa;
        margin: 0;
        min-height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 18px;
      }
      /* 标签深灰色,用户名蓝色加粗 */
      .welcome-text {
        color: #2d3748;
      }
      #loginUser {
        color: #4299e1;
        font-weight: bold;
        margin-left: 8px;
      }
    </style>
<body>
    登录人: <span id="loginUser"></span>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
        //使用ajax请求获取后台的用户登录信息
        $.ajax({
            method: "get",
            url: "/user/getLoginUser",
            success: function(result){
                $("#loginUser").text(result);

            }
        });
    </script>
</body>

</html>

3.3 关键点解释

  • 登录请求data 是一个对象,jQuery默认会将其转换为 application/x-www-form-urlencoded 格式,并设置 Content-Type 为该格式。这里我们没有手动设置 contentType ,因为后端接口期待的是表单格式(从接口文档看,参数是 usernamepassword,后端没有用 @RequestBody,所以默认从请求参数取值,正好匹配表单格式)。如果后端要求JSON,则需要手动设置。
  • 响应处理success 中的 result 是 jQuery 根据响应头自动解析后的结果。由于登录接口返回的是 application/json,jQuery会将其解析为布尔值,所以可以直接用 if(result)
  • 获取用户请求:GET请求,无参数,响应是纯文本,jQuery直接作为字符串,插入到span中。

3.4 浏览器发送的登录请求报文


4. 后端开发:根据接口文档实现

4.1 Controller代码

java 复制代码
package com.zhongge.demo;

import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName UserLoginController
 * @Description TODO 用户登录控制器
 * @Author 笨忠
 * @Date 2026-02-22 15:13
 * @Version 1.0
 */
@RequestMapping("/user")
@RestController
public class UserLoginController {


    /**
     * 用户登录
     * @param username 用户名
     * @param password 密码
     * @param session session对象,用于存储用户信息
     * @return true-登录成功   false-登录失败
     */
    @RequestMapping("/login")
    public boolean login(String username, String password, HttpSession session) {
        //参数校验:使用的是工具类StringUtils
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return false;//如果参数是null或者是为""那么就是登录失败
        }
        //用户校验
        if ("lisi".equals(username) && "admin".equals(password)) {
            //存储用户信息
            session.setAttribute("username", "lisi");
            return true;
        }
        return false;//如果程序到这里 那么就是登录失败
    }

    /**
     * 获取用户的登陆信息【用户名】
     * @param session 存储用户信息
     * @return 用户信息
     */
    @RequestMapping("/getLoginUser")
    public String getUserInfo(HttpSession session) {
        //根据sessionid找到对应的session对象,然后再根据对应的键找到对应的值
        String username = (String) session.getAttribute("username");
        return username;
    }
}

4.2 关键点解释

  • @RestController:等同于 @Controller + @ResponseBody,所有方法返回值直接写入响应体。
  • login 方法参数String username, String password 没有注解,SpringMVC从请求参数中获取(因为前端发的是表单格式)。HttpSession session 由Spring自动注入。
  • 返回值 boolean :Spring会使用 MappingJackson2HttpMessageConvertertrue/false 序列化为JSON布尔值,并设置响应头 Content-Type: application/json。因为返回的是对象(boolean是基本类型,但会被自动装箱为Boolean),不是String,所以默认使用JSON转换器。
  • getUserInfo 返回值 String :Spring使用 StringHttpMessageConverter,设置响应头 text/plain,符合接口文档。
  • Session的使用:登录成功时将用户名存入session,后续请求通过session获取。session通过cookie中的JSESSIONID维持。

4.3 响应报文:

4.4 后端处理获取用户请求的响应报文:


5. 深入理解:为什么登录接口不用 @RequestBody

因为前端发送的是表单格式,后端可以直接从请求参数中获取。如果前端发送的是JSON,则必须用 @RequestBody。接口文档定义了参数格式为表单,所以前后端都遵守了这个约定。

6. 接口文档的作用再强调

  • 明确参数格式 :本例中参数是 application/x-www-form-urlencoded,所以后端不加注解就能接收。
  • 明确响应格式 :登录接口返回JSON,前端才能用 result 直接判断。
  • 避免歧义:如果前端发JSON而后端用表单接收,就会出错。

7. 常见问题

Q1:为什么登录接口返回的是布尔值,而获取用户接口返回的是字符串?

根据需求,登录只需要知道成功与否,布尔值足够;获取用户需要显示用户名,所以返回字符串。接口设计要符合业务语义。

Q2:如果登录失败,后端返回 false,前端 if(result) 会怎样?

false 被转为JSON false,前端 if(false) 不成立,进入else,弹出错误。

Q3:为什么主页的AJAX请求能拿到用户名?

因为浏览器在登录成功后收到了 Set-Cookie,后续请求会自动携带该cookie,后端就能识别会话。


8.详细讲解整个过程中HTTP通信的流程

我们一步步拆解,把每个细节都讲透。


(一)、HTTP 请求中,参数到底可以放在哪里?

首先,HTTP 请求中传递参数的位置主要有两个:

  1. URL 的查询字符串(Query String) :位于 URL 的 ? 之后,例如 GET /user/login?username=lisi&password=admin
  2. 请求体(Request Body):位于 HTTP 报文的数据部分,例如 POST 请求中可以携带表单数据或 JSON 字符串。

为什么要有两个位置?

因为 HTTP 协议定义了不同的请求方法(GET、POST、PUT 等),它们对参数的位置有不同的约定和限制:

  • GET 请求 :通常用于获取资源,参数放在 URL 的查询字符串中,因为 GET 没有请求体(或者说不应该用请求体,虽然理论上可以,但大多数服务器忽略 GET 的请求体)。

  • POST 请求 :通常用于提交数据,参数可以放在请求体中,也可以放在 URL 中(但一般放在请求体中),因为请求体容量更大,可以传输更多数据,且不会暴露在地址栏。

但请注意:参数放在哪里,并不完全由 HTTP 方法决定,还取决于前端发送时的编码方式Content-Type


(二)、前端是如何决定参数位置的?

以我们的登录案例为例,前端使用 jQuery 的 $.ajax 发送 POST 请求:

javascript 复制代码
$.ajax({
  method: "post",
  url: "/user/login",
  data: {
    username: $("#userName").val(),
    password: $("#password").val()
  },
  ...
});

这里 data 是一个 JavaScript 对象,jQuery 默认会做以下事情:

  1. 判断请求方法 :因为是 post,所以 jQuery 会将数据放在 请求体 中。
  2. 确定 Content-Type :由于没有显式指定 contentType,jQuery 默认使用 application/x-www-form-urlencoded; charset=UTF-8
  3. 编码数据 :将对象转换为 key1=value1&key2=value2 格式的字符串(并对特殊字符进行 URL 编码),然后将这个字符串作为请求体发送。
  4. 设置请求头 :自动添加 Content-Type: application/x-www-form-urlencoded; charset=UTF-8

最终发出的 HTTP 请求:

所以,这里的参数是放在请求体中的,而不是 URL 的查询字符串中。查询字符串是空的。


(三)、为什么有时候参数会在 URL 中?

如果你将 method 改为 "get",那么 jQuery 会默认将数据编码后附加到 URL 的查询字符串中,而不使用请求体。例如:

javascript 复制代码
$.ajax({
  method: "get",
  url: "/user/login",
  data: { username: "lisi", password: "admin" }
});

此时,jQuery 会构造 URL:/user/login?username=lisi&password=admin,然后发送 GET 请求,请求体为空。响应头中不会有 Content-Type(因为 GET 通常没有请求体)。

为什么 GET 要用查询字符串?

因为 HTTP 规范中,GET 请求的语义是"获取资源",不应该带有请求体(虽然技术上可以,但很多服务器不支持或忽略)。所以浏览器和库都约定将参数放在 URL 中。


(四)、后端如何获取参数?为什么不加注解也能拿到?

在我们的后端代码中:

java 复制代码
@RequestMapping("/login")
public boolean login(String username, String password, HttpSession session) { ... }

参数 usernamepassword 没有加任何注解,SpringMVC 却能正确获取到前端传来的值。这是为什么?

4.1 SpringMVC 的参数解析机制

当请求到达时,SpringMVC 会遍历所有注册的 参数解析器(HandlerMethodArgumentResolver) ,寻找能处理当前方法参数的解析器。对于没有注解的简单类型参数(如 String、Integer 等),Spring 会使用 ServletRequestMethodArgumentResolver (或者更精确地说,RequestParamMethodArgumentResolver 在处理没有注解时也会起作用,但逻辑是类似的)。它的工作逻辑是:

  1. 查看请求参数来源 :SpringMVC 会将 所有可用的请求参数 合并到一个 Map 中。这些请求参数来自两个地方:
    • URL 查询字符串 (如 ?username=xxx
    • POST 请求的表单数据 (即 application/x-www-form-urlencoded 格式的请求体)
  2. 参数名匹配 :遍历方法参数名(如 username),在请求参数 Map 中查找同名的键,如果找到,就将对应的值赋值给该参数。

关键点:SpringMVC 并不关心参数是来自 URL 还是请求体,它统一当作"请求参数"处理。只要请求中包含了这些键值对,无论位置如何,都能被绑定。

4.2 为什么 JSON 格式的参数不行?

如果前端发送的是 JSON 格式(Content-Type: application/json),请求体内容是 {"username":"lisi","password":"admin"},那么 SpringMVC 默认的请求参数 Map 中不会包含这些键值对 ,因为 JSON 不是 application/x-www-form-urlencoded 格式,容器不会自动解析 JSON 为参数。此时,必须使用 @RequestBody 注解,触发消息转换器来解析 JSON。

总结

  • 表单格式application/x-www-form-urlencoded)的请求体会被 Servlet 容器解析成参数,放入请求参数 Map,所以不加注解也能获取。
  • JSON 格式 的请求体不会被自动解析成参数,必须用 @RequestBody

(五)、HttpSession 到底做了什么?

5.1 会话的创建与识别

当你第一次调用 session.setAttribute() 时(前提是方法参数中声明了 HttpSession),SpringMVC 会从当前请求中获取会话对象。获取过程如下:

  • 容器(Tomcat)检查请求中是否包含名为 JSESSIONID 的 Cookie。
  • 如果有,则根据这个 ID 查找对应的 HttpSession 对象(存储在服务器内存中)。
  • 如果没有,则创建一个新的 HttpSession 对象,生成一个新的唯一 ID(如 123456),并在响应中添加 Set-Cookie: JSESSIONID=123456; Path=/; HttpOnly 头。浏览器收到后会保存这个 Cookie,后续请求会自动带上。
5.2 数据的存储

session.setAttribute("username", "lisi") 将键值对保存在该会话对象中。这个会话对象与特定的 JSESSIONID 绑定,后续请求只要携带相同的 Cookie,就能通过 session.getAttribute("username") 取出数据。

5.3 在我们的案例中
  • 登录成功时,调用 session.setAttribute("username", "lisi"),此时如果没有 JSESSIONID,则创建新会话,响应中返回 Set-Cookie。
  • 浏览器跳转到 index.html 时,会携带这个 Cookie(因为同域名下所有请求都会自动携带)。
  • 主页的 AJAX 请求 /user/getLoginUser 中,后端根据 Cookie 找到之前的 session,通过 session.getAttribute("username") 获取用户名并返回。

(六)、完整的通信过程(含报文细节)

具体的简图如下:


该图的解析如下:


6.1 登录请求

前端构造请求

  • 方法:POST

  • URL:/user/login

  • 请求头(由浏览器和 jQuery 自动添加):

    复制代码
    Host: localhost:8080
    Connection: keep-alive
    Content-Length: 27
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    Accept: */*
    Origin: http://localhost:8080
    Referer: http://localhost:8080/login.html
    Cookie: 如果有之前的 Cookie(但首次登录可能没有)
  • 请求体:username=lisi&password=admin

后端处理

  1. Tomcat 接收请求,解析请求头,将请求体按 application/x-www-form-urlencoded 解析成参数 Map,放入 HttpServletRequest 的属性中。

  2. SpringMVC 的 DispatcherServlet 根据 URL 找到 login 方法。

  3. 参数解析器遍历,对于 String username,从请求参数 Map 中取键为 username 的值 "lisi" 并赋值;同样给 password 赋值 "admin"

  4. 方法执行,验证通过,session.setAttribute("username", "lisi")

    • 此时,容器检查到没有 JSESSIONID Cookie,于是创建新会话,生成 ID 28194A95DE378C03D2CFB9A9FFDF7E4C,在后续响应中会自动添加 Set-Cookie 头(注意:SpringMVC 不直接操作响应,但容器会在响应时自动添加)。
  5. 方法返回 true

  6. SpringMVC 的返回值处理器(因为类上有 @RestController)选择合适的消息转换器。由于项目有 Jackson,将 true 转为 JSON 字符串 "true",并设置响应头 Content-Type: application/json;charset=UTF-8

  7. Tomcat 组装响应报文:

前端接收

  • 浏览器收到响应,jQuery 根据 Content-Type: application/json"true" 解析为布尔值 true
  • success 回调执行,if(result) 为真,执行 location.href="index.html",浏览器跳转到主页。
6.2 主页请求用户信息

浏览器加载 index.html,其中包含 AJAX 请求:

javascript 复制代码
$.ajax({
  method: "get",
  url: "/user/getLoginUser",
  success: function(result) { ... }
});

构造请求

  • 方法:GET

  • URL:/user/getLoginUser

  • 请求头(浏览器自动添加):

后端处理

  1. Tomcat 接收请求,根据 Cookie 中的 JSESSIONID=28194A95DE378C03D2CFB9A9FFDF7E4C 找到对应的 HttpSession 对象。

  2. SpringMVC 找到 getUserInfo 方法,参数 HttpSession session 被自动注入(就是刚才找到的那个会话)。

  3. 方法内 session.getAttribute("username") 返回 "lisi"

  4. 返回字符串 "lisi"

  5. SpringMVC 的返回值处理器选择 StringHttpMessageConverter,将字符串直接写入响应体,设置 Content-Type: text/plain;charset=UTF-8

  6. 响应报文:

前端接收

  • jQuery 根据 Content-Type: text/plain 将响应体作为字符串,传给 result
  • $("#loginUser").text(result)"lisi" 显示在页面上。

9. 总结

本案例展示了表单格式的AJAX交互,以及session的使用。那么下一期我们将介绍如果我们交互的格式是JSON,那么我们该怎么做?

老铁们别忘了点赞👍、关注、加收藏,咱们下期见👋~

写在最后

最后或许我们网络通信大家还有一点迷糊,于是呢我去向AI查了一些相关的资料:彻底搞懂 HTTP 通信:从字节流到对象,前后端数据交换全解析

相关推荐
童话的守望者1 小时前
dc9靶场通关
java·开发语言
大黄说说1 小时前
解锁 .NET 性能极限:深入解析 Span 与零拷贝内存艺术
java·数据结构·算法
知识即是力量ol1 小时前
深入理解 Snowflake 雪花算法:原理、本质、趋势递增问题与分布式顺序困境全解析
java·分布式·算法·雪花算法·snowflake·全局唯一id·分布式id生成器
君爱学习1 小时前
G1垃圾回收器启动时 CPU 飙升的原因分析
java
xing-xing1 小时前
Spring Data Elasticsearch
后端·spring·elasticsearch
若光6721 小时前
springboot防抖 限流 幂等实现 AOP注解实现
java·spring boot·后端
今天你TLE了吗1 小时前
JVM学习笔记:第五章——堆内存
java·jvm·笔记·后端·学习
未来之窗软件服务1 小时前
AI人工智能(十五)C# AI的智障行为http服务—东方仙盟练气期
开发语言·http·c#
竟未曾年少轻狂1 小时前
JavaScript 对象与数组
java·前端·javascript·数组·对象