留言板
需求界面如下图所示 :
- 输入留言信息,点击提交。后端把数据存储起来。
- 页面展示输入的留言墙的信息
1. 准备工作
把前端页面放在项目中(课件中提供)
码云地址:JavaEE进阶课程资料包
2. 约定前后端交互接口
2.1 需求分析
后端需要提供两个服务 :
- 提交留言:用户输入留言信息之后,后端需要把留言信息保存起来
- 展示留言:页面展示时,需要从后端获取到所有的留言信息
2.2 接口定义
(1) 获取全部留言
全部留言信息,我们用List来表示,可以用 JSON
来描述这个 List
数据。
请求:
响应:JSON 格式
浏览器
给服务器
发送一个GET /message/getList
这样的请求,就能返回当前一共有哪些留言记录。结果以
json
的格式返回过来。
(2) 发表新留言
请求:body 也为 JSON 格式。
响应:JSON 格式
{ ok : 0 }
复制
我们期望
浏览器
给服务器
发送一个POST /message/publish
这样的请求,就能把当前的留言提交给服务器。
3. 服务器代码
(1) 获取全部留言接口代码写法
定义留言对象 MessageInfo
类
创建 MessageController
类 ;
当客户端发送 JSON 格式的请求体(Content-Type为application/json
)时,必须使用@RequestBody
注解。
对拿到的对象属性进行校验
注意:
虽然上面响应的 json 数据中 , ok 是没有双引号的,但是正规的 json 写法,属性名需要带双引号;
我们需要对对象中的字符串属性的双引号转义
,如 \" 属性名 \"
(JSON数据不能有空格
,煮啵写到这里的时候还不知道,后面通过 deepseek 纠错才知道)
使用 produces
:
produces
声明了该方法的响应格式
为 JSON,它不影响Spring如何解析请求体,只设置响应头的Content-Type
;
注意,如果这个方法的
返回值
是一个对象
,就不需要使用produces
声明返回格式
因为我们还没有学到连接数据库的操作,所以我们先把数据存储在内存中;
使用List 来存储留言板信息
我们定义一个 List
,如果校验的请求对象数据校验成功,把请求的数据存储在 List
中:
我们再定义一个方法,用于拿到留言信息:
(2) 测试接口
重新启动程序,我们先调用 getList() 方法;
刚开始存储留言信息的 List 什么都没有,所以返回一个空的集合:
接下来,我们测试 publish
接口,下面是构造 JSON 数据为请求体
的方法:
我们再来测试 getList
的接收功能:
(3) 通过调试讲解不使用 @RequestBody 的后果
如果在 publish()
接口中去掉 @RequestBody
会怎么样:
重新运行程序,发送请求:
@RequestBody
表示的是请求参数为 JSON
,通过 Postman 响应可以看出,后端校验,发现数据错误,返回 ok:0:
在调试过程中,程序的运行会卡在断点处,我们打开 Postman 查看发送请求,返回响应的过程:
produces
声明了该方法的响应格式
为 JSON,与请求的数据解析方式无关。它不影响Spring如何解析请求体,只设置响应头的Content-Type
。
@RequestBody
的必要性:
- 当客户端发送 JSON 格式的请求体(Content-Type为
application/json
)时,必须使用@RequestBody
注解。@RequestBody
告诉 Spring 使用 JSON解析器(如Jackson)将请求体反序列化为Java对象。- 如果没有该注解,Spring默认从
请求参数
(URL参数
或表单数据
)中绑定数据,而非请求体
。
4. 调整前端点击事件函数代码
(1) 使用 @ajax 进行前后端数据交互
修改 messagewall.html
我们使用 ajax
来让前后端对象属性交互:
JavaScript 中
var
,let
,const
用于声明参数类型;
下图第五点
的对应关系应该是 submit()
中的前三行参数,这三个参数通过 id 选择器去上面的样式中拿值:
data 属性用于传递数据,后端是使用 JSON
接收数据(@RequestBody
注解会以 JSON
格式解析数据),前端也需要使用 JSON
来发送数据;
(2) 将前端发送的数据由对象转成 JSON 字符串
在前端如何将发送的数据构造成JSON
呢?
在 Data
中,我们先赋值为一个对象({}
表示一个对象
),对象中的属性为后端的请求参数,值为 submit() 前面定义的几个参数;
此时 Data 默认传的是一个对象
,不是一个JSON
数据,我们需要把这个对象转成 JSON
方法一 |
方法二(推荐) |
我们将对象单独抽离,将新定义的对象参数传入 stringify()
,这样更美观且不容易写错:
(3) 前端声明传递的数据类型为 JSON
(4) 处理后端返回的 JSON 数据
后端返回的是 JSON 数据:
如果后端发送的值为 JSON,那么前端是可以直接把 JSON 数据当成对象使用的
使用 success
关键字,只要后端返回数据,就会执行 success 对应的 function
;
(5) 测试接口
重新运行程序,在浏览器中打开页面:http://127.0.0.1:8080/messagewall.html
提交数据
查看抓包结果:
数据内容格式转换的过程(前后端格式设置相同的过程):
后端没有指定响应返回数据的类型为 JSON |
上面的 if …else … 部分修改:
假设我们后端没有约定返回格式为 JSON(消去 produces)的内容):
此时返回前端的数据是字符串,我们在前端打日志;
重新运行程序,刷新浏览器,在浏览器控制台观察输出结果:
查看抓包结果:
根本原因:
- 在前端,
字符串无法像 JSON 一样解析成对象
; - 此时
result.ok
就是字符串.属性
,而不是对象.属性
,就会在页面进行提交数据时,就会出现失败提示的弹窗;
直接原因:
- 这是后端返回数据,没有指定响应类型的结果;
如果要保持此时的后端不变,我们就需要在前端调用刚刚学到的 parse()
方法:
所以前后端是需要相互配合的,所以接口文档中要有清晰的边界划分,明确指定好数据的类型;
5. 在页面加载的时候获取数据
发布留言后,页面刷新,我们会发现数据丢失:
在学到这里的时候,煮啵运行代码,发现还是依旧出现失败弹窗,这时候我们就需要万能的
deepseek
:
所以我们刷新的时候,需要把之前的信息存储在留言列表中:
我们只需要在前端调用 getList
接口,在加载页面的时候,获取留言列表信息;
也就是一进入页面,就发送 ajax
请求,来直接调用 getList
接口,获取留言列表并且显示;
重新启动程序,刷新浏览器页面:
我们发送了两条数据,刷新页面 (不是重新启动程序)
此时我们每次提交的数据都会发送给服务器。每次打开页面的时候,页面都会从服务器加载数据。因此,即使关闭页面,数据也不会丢失。
但是数据此时是存储在服务器的内存中(
private List<Message> messages = new ArrayList<Message>();
),一旦服务器重启,数据仍然会丢失。要想数据不丢失,需要把数据存储在数据库中,后面再讲。
6. 完整代码
前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>留言板</title> <style> .container { width: 350px; height: 300px; margin: 0 auto; /* border: 1px black solid; */ text-align: center; } .grey { color: grey; } .container .row { width: 350px; height: 40px; display: flex; justify-content: space-between; align-items: center; } .container .row input { width: 260px; height: 30px; } #submit { width: 350px; height: 40px; background-color: orange; color: white; border: none; margin: 10px; border-radius: 5px; font-size: 20px; } </style> </head> <body> <div class="container"> <h1>留言板</h1> <p class="grey">输入后点击提交, 会将信息显示下方空白处</p> <div class="row"> <span>谁:</span> <input type="text" name="" id="from"> </div> <div class="row"> <span>对谁:</span> <input type="text" name="" id="to"> </div> <div class="row"> <span>说什么:</span> <input type="text" name="" id="say"> </div> <input type="button" value="提交" id="submit" onclick="submit()"> <!-- <div>A 对 B 说: hello</div> --> </div> <script src="js/jquery-3.7.1.min.js"></script> <script> function load(){ $.ajax({ type: "get", url: "/message/getList", success: function (messages){ // 成功拿到响应数据,就遍历列表并且打印 if(messages!=null && messages.length > 0){ var finalHTML = ""; // 注意 js 的 for 循环写法 for(var message of messages){ // 每次遍历到一个元素,都把单个信息拼接在 item 中 var item = "<div>"+message.from +"对" + message.to + "说:" + message.message+"</div>"; // 在把单个信息,拼接到最终显示的 finalHTML 中 finalHTML += item; } // 把留言列表显示在页面下端 $(".container").append(finalHTML); } } }); } // 调用封装方法 load(); function submit(){ //1. 获取留言的内容 var from = $('#from').val(); var to = $('#to').val(); var say = $('#say').val(); if (from== '' || to == '' || say == '') { return; } var data = { from: from, to: to, message: say } $.ajax({ type: "post", url: "/message/publish", contentType : "application/json", data: JSON.stringify(data), success : function (result){ if(result.ok == 1){ //2. 构造节点 var divE = "<div>"+from +"对" + to + "说:" + say+"</div>"; //3. 把节点添加到页面上 $(".container").append(divE); //4. 清空输入框的值 $('#from').val(""); $('#to').val(""); $('#say').val(""); }else { // 失败 alert("留言发布失败"); } } }); } </script> </body> </html>
复制
后端代码:
MessageInfo:
package com.example.springmvc_demo; import lombok.Data; @Data public class MessageInfo { private String from; // 谁 private String to; // 对谁 private String message; // 说什么 }
复制
MessageController:
package com.example.springmvc_demo; import com.example.springmvc_demo.MessageInfo; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @RequestMapping("/message") @RestController // 操作数据, 不操作视图 public class MessageController { private List<MessageInfo> messageInfoList = new ArrayList<>(); @RequestMapping(value = "/publish" , produces = "application/json") public String publish(@RequestBody MessageInfo messageInfo){ // 对拿到的对象属性进行校验 if(!StringUtils.hasLength(messageInfo.getFrom()) || !StringUtils.hasLength((messageInfo.getTo())) || !StringUtils.hasLength(messageInfo.getMessage())) { // 失败,返回0 return "{\"ok\":0}"; } // 校验成功, 把传递的请求存储到 List 中 messageInfoList.add(messageInfo); return "{\"ok\":1}"; } @RequestMapping("/getList") public List<MessageInfo> getList(){ return messageInfoList; } }
复制