目录
留言板
需求:
界⾯如下图所⽰
- 输⼊留⾔信息, 点击提交. 后端把数据存储起来.
- ⻚⾯展⽰输⼊的表⽩墙的信息
1. 准备工作
前端没有保存数据的功能,后端把数据保存下来(内存或者数据库中...,这里先存内存中)
把前端⻚⾯放在项⽬中
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>
.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="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
function submit(){
//1. 获取留言的内容
var from = $('#from').val();
var to = $('#to').val();
var say = $('#say').val();
if (from== '' || to == '' || say == '') {
return;
}
//2. 构造节点
var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";
//3. 把节点添加到页面上
$(".container").append(divE);
//4. 清空输入框的值
$('#from').val("");
$('#to').val("");
$('#say').val("");
}
</script>
</body>
</html>
2. 约定前后端交互接口
需求分析
后端需要提供两个服务
- 提交留⾔: ⽤⼾输⼊留⾔信息之后, 后端需要把留⾔信息保存起来
- 展⽰留⾔: ⻚⾯展⽰时, 需要从后端获取到所有的留⾔信息
接⼝定义
- 获取全部留⾔
全部留⾔信息, 我们⽤List 来表⽰, 可以⽤JSON来描述这个List数据.
请求:
参数:无
GET /message/getList
响应: JSON 格式
返回结果:List<MessageInfo>
json
[
{
"from": "⿊猫",
"to": "⽩猫",
"message": "喵"
},{
"from": "⿊狗",
"to": "⽩狗",
"message": "汪"
},
//...
]
浏览器给服务器 发送⼀个
GET /message/getList
这样的请求, 就能返回当前⼀共有哪些留⾔记录. 结果以 json 的格式返回过来
- 发表新留⾔
请求: body 也为 JSON 格式.
参数:MessageInfo(from,to,message)
json
POST /message/publish
{
"from": "⿊猫",
"to": "⽩猫",
"message": "喵"
}
响应: JSON 格式.
返回结果:true/false
{
ok: 1
}
我们期望浏览器给服务器 发送⼀个
POST /message/publish
这样的请求, 就能把当前的留⾔提交给服务器.
lombok
在这个环节, 我们介绍⼀个新的⼯具包 lombok
Lombok是⼀个Java⼯具库,通过添加注解的⽅式,简化Java的开发.
简单来学习下它的使⽤
- 引⼊依赖
xml
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
引入后记得刷新
- 使用
lombok通过⼀些注解的⽅式, 可以帮助我们消除⼀些冗⻓代码, 使代码看起来简洁⼀些
⽐如之前的Person
对象 就可以改为
java
@Data
public class Person {
private int id;
private String name;
private String password;
}
@Data
注解会帮助我们⾃动⼀些⽅法, 包含getter/setter, equals, toString
等
- 原理解释
可以观察加了 @Data
注解之后, Idea反编译的class⽂件
这不是真正的字节码⽂件, ⽽是 IDEA 根据字节码进⾏反编译后的⽂件
反编译是将可执⾏的程序代码转换为某种形式的⾼级编程语⾔, 使其具有更易读的格式. 反编译是⼀种逆向⼯程,它的作⽤与编译器的作⽤相反
即.java文件就是用了注解的,.class文件是把加了这个注解的功能反编译了,变成你本应该自己写的代码
可以看出来, lombok是⼀款在编译期⽣成代码的⼯具包.
Java 程序的运⾏原理:
Lombok 的作⽤如下图所⽰:
- 更多使⽤
如果觉得@Data
⽐较粗暴(⽣成⽅法太多), lombok也提供了⼀些更精细粒度的注解
注解 | 作用 |
---|---|
@Getter | ⾃动添加 getter ⽅法 |
@Setter | ⾃动添加 setter ⽅法 |
@ToString | ⾃动添加 toString ⽅法 |
@EqualsAndHashCode | ⾃动添加 equals 和 hashCode ⽅法 |
@NoArgsConstructor | ⾃动添加⽆参构造⽅法 |
@AllArgsConstructor | ⾃动添加全属性构造⽅法,顺序按照属性的定义顺序 |
@NonNull | 属性不能为 null |
@RequiredArgsConstructor | ⾃动添加必需属性的构造⽅法,final + @NonNull 的属性为必需 |
@Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor + @NoArgsConstructor
更快捷的引⼊依赖
上述引⼊lombok依赖, 需要去找lombok的坐标
接下来介绍更简单引⼊依赖的⽅式
- 安装插件EditStarter, 重启Idea
- 在pom.xml⽂件中, 单击右键, 选择Generate, 操作如下图所⽰
进⼊Edit Starters的编辑界⾯, 添加对应依赖即可.
注意:
不是所有依赖都可以在这⾥添加的, 这个界⾯和SpringBoot创建项⽬界⾯⼀样.
依赖不在这⾥的, 还需要去Maven仓库查找坐标, 添加依赖.
3. 服务器代码
定义留⾔对象 MessageInfo 类
java
@Data
public class MessageInfo {
private String from;
private String to;
private String message;
}
创建 MessageController 类
使⽤
List<MessageInfo>
来存储留⾔板信息
java
@RestController
@RequestMapping("/message")
public class MessageController {
private List<MessageInfo> messageInfos=new ArrayList<>();
@RequestMapping("/publish")
public Boolean publishMessage(MessageInfo messageInfo){
//进行参数校验
//三个任意一个为空
if(!StringUtils.hasLength(messageInfo.getFrom())
|| !StringUtils.hasLength(messageInfo.getTo())
|| !StringUtils.hasLength(messageInfo.getMessage())){
return false;
}
//添加留言
messageInfos.add(messageInfo);
return false;
}
@RequestMapping("/getMessageInfo")
public List<MessageInfo> getMessage(){
//直接返回留言
return messageInfos;
}
}
测试一下后端接口
为什么前端校验之后,后端还需要校验呢?
- 这是两个团队的事情
- 后端可能会收到攻击,收到非正常请求,比如什么爬虫之类的
4. 调整前端页面代码
修改 messagewall.html
vscode的代码格式化:
alt+shift+F
- 添加函数, ⽤于在⻚⾯加载的时候获取数据
js
//页面加载时,请求后端,获取留言列表
$.ajax({
url: "/message/getMessageInfo",
type: "get",
success: function (messages) {
for (var m of messages) {
//拼接显示的记录
//2. 拼接节点的html
var divE = "<div>" + m.from + "对" + m.to + "说:" + m.message + "</div>";
//3. 把节点添加到页面上
$(".container").append(divE);
}
}
});
- 修改原来的点击事件函数. 在点击按钮的时候给服务器发送添加留⾔请求
js
function submit() {
//1. 获取留言的内容
var from = $('#from').val();
var to = $('#to').val();
var say = $('#say').val();
if (from == '' || to == '' || say == '') {
return;
}
//提交留言
$.ajax({
url: "/message/publish",
type: "post",
data: {
"from": from,
"to": to,
"message": say
},
success: function (result) {
if (result) {
//添加成功
//2. 拼接节点的html
var divE = "<div>" + from + "对" + to + "说:" + say + "</div>";
//3. 把节点添加到页面上
$(".container").append(divE);
//4. 清空输入框的值
$('#from').val("");
$('#to').val("");
$('#say').val("");
} else {
//添加失败
alert("留言发布失败");
}
}
});
}
测试http://127.0.0.1:8080/messagewall.html
输入信息后点击刷新然后fiddler抓包
此时我们每次提交的数据都会发送给服务器. 每次打开⻚⾯的时候⻚⾯都会从服务器加载数据. 因此及时关闭⻚⾯, 数据也不会丢失.
但是数据此时是存储在服务器的内存中 (
private List<Message> messages = new ArrayList<Message>();
)⼀旦服务器重启, 数据仍然会丢失.
要想数据不丢失, 需要把数据存储在数据库中, 后⾯再讲
图书管理系统
需求:
- 登录: ⽤⼾输⼊账号,密码完成登录功能
- 列表展⽰: 展⽰图书
1. 准备工作
创建新项⽬, 引⼊对应依赖, 把前端⻚⾯放在项⽬中
系统后续其他功能也会完善, 此处建议创建新项⽬
2. 约定前后端交互接口
需求分析
图书管理系统是⼀个相对较⼤⼀点的案例, 咱们先实现其中的⼀部分功能.
1. ⽤⼾登录
2. 图书列表
根据需求可以得知, 后端需要提供两个接⼝
- 账号密码校验接⼝: 根据输⼊⽤⼾名和密码校验登录是否通过
- 图书列表: 提供图书列表信息
接⼝定义
-
登录接口
[URL]
POST /user/login[请求参数]
userName=admin&password=admin[响应]
true //账号密码验证成功
false//账号密码验证失败 -
图书列表展示
[URL]
POST /book/getBookList[请求参数]
⽆[响应]
返回图书列表
[
{
"id": 1,
"bookName": "活着",
"author": "余华",
"count": 270,
"price": 20,
"publish": "北京⽂艺出版社",
"status": 1,
"statusCN": "可借阅"
},
...
]
字段说明:
字段 | 含义 |
---|---|
id | 图书ID |
bookName | 图书名称 |
author | 作者 |
count | 数量 |
price | 定价 |
publish | 图书出版社 |
status | 图书状态 1-可借阅, 其他-不可借阅 |
statusCN | 图书状态中⽂含义 |
3. 服务器代码
创建图书类 BookInfo
java
@Data
public class BookInfo {
//图书ID
private Integer id;
//书名
private String bookName;
//作者
private String author;
//数量
private Integer conut;
//价格
private BigDecimal price;
//出版社
private String publish;
//状态
private Integer status;//1-可借阅 2-不可借阅
private String statusCN;
}
这里为什么
price
用BigDecimal
可以去查一下https://www.cnblogs.com/jayworld/p/10107335.html
还有什么这里状态
status
用Integer
而不是用字符串表示?是因为这里数据库只能存数字,如果后续改需求了,就需要大改,而我们存的是数字,可以通过数字来映射成文字
创建 UserController, 实现登录验证接⼝
java
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public Boolean login(String userName, String password, HttpSession session) {
//校验参数
if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
return false;
}
//验证账号密码是否正确
//if(userName.equals("admin")这种写法,如果userName为null就会报空指针异常
//这是开发习惯,要养成
if("admin".equals(userName)&&"admin".equals(password)){
//账号密码正确
//存session
session.setAttribute("userName",userName);
return true;
}
return false;
}
}
创建 BookController, 获取图书列表
java
@RestController
@RequestMapping("/book")
public class BookController {
@RequestMapping("/getBookList")
public List<BookInfo> getBookList(){
//1.获取图书数据
List<BookInfo> bookInfos=mockData();
//2.对图书数据进行修改处理
for(BookInfo bookInfo:bookInfos){
if(bookInfo.getStatus()==1){
bookInfo.setStatusCN("可借阅");
}else{
bookInfo.setStatusCN("不可借阅");
}
}
//3.返回数据
return bookInfos;
}
//mock - 虚拟数据,假数据
private List<BookInfo> mockData() {
//对于已知的数据量或者大概知道这个集合的数量时,创建List时建议指定初始化容量
List<BookInfo> bookInfos=new ArrayList<>(15);
for(int i=0;i<15;i++){
BookInfo bookInfo=new BookInfo();
bookInfo.setId(i);
bookInfo.setBookName("图书"+i);
bookInfo.setAuthor("作者"+i);
bookInfo.setConut(new Random().nextInt(200));
bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
bookInfo.setPublish("出版社"+i);
bookInfo.setStatus(i%5==0?2:1);
bookInfos.add(bookInfo);
}
return bookInfos;
}
}
数据采⽤mock的⽅式, 实际数据应该从数据库中获取
mock:模拟的, 假的
在开发和测试过程中,由于环境不稳定或者协同开发的同事未完成等情况下, 有些数据不容易构造或者不容易获取,就创建⼀个虚拟的对象或者数据样本,⽤来辅助开发或者测试⼯作.
简单来说, 就是假数据。
上述经过Postman测试都没有问题
4. 调整前端页面代码
登录⻚⾯:
添加登录处理逻辑
js
function login() {
$.ajax({
url:"/user/login",
type:"post",
data:{
"userName":$("#userName").val(),
"password":$("#password").val()
},
success:function(result){
//console.log(result);
if(result){
location.href = "book_list.html";
}else{
alert("用户名或密码错误");
}
}
});
}
图书列表展⽰:
删除前端伪造的代码, 从后端获取数据并渲染到⻚⾯上.
- 删除 标签中的内容
- 完善获取图书⽅法
js
getBookList();
function getBookList() {
$.ajax({
type:"get",
url:"/book/getBookList",
success:function(books){
var finalHtml="";
for(var book of books){
//根据每一条记录拼接html,也就是一个<tr>
finalHtml+='<tr>';
finalHtml+='<td><input type="checkbox" name="selectBook" value="'+book.id+'" id="selectBook" class="book-select"></td>';
finalHtml+='<td>'+book.id+'</td>';
finalHtml+='<td>'+book.bookName+'</td>';
finalHtml+='<td>'+book.author+'</td>';
finalHtml+='<td>'+book.count+'</td>';
finalHtml+='<td>'+book.price+'</td>';
finalHtml+='<td>'+book.publish+'</td>';
finalHtml+='<td>'+book.statusCN+'</td>';
finalHtml+='<td>';
finalHtml+='<div class="op">';
finalHtml+='<a href="book_update.html?bookId='+book.id+'">修改</a>';
finalHtml+='<a href="javascript:void(0)" οnclick="deleteBook('+book.id+')">删除</a>';
finalHtml+='</div>';
finalHtml+='</td>';
finalHtml+='</tr>';
}
console.log(finalHtml);
$("tBody").html(finalHtml);
}
});
}
运行测试:http://127.0.0.1:8080/login.html
,输⼊账号密码: admin admin, 登录成功, 跳转到 图书列表⻚
界⾯展⽰: