结合上节内容, 我们可以做⼀些⼩案例
主要掌握知识点:
- 理解前后端交互过程
- 接⼝传参, 数据返回, 以及⻚⾯展⽰
一.加法计算器
需求: 输⼊两个整数, 点击"点击相加"按钮, 显⽰计算结果
1.准备⼯作
创建SpringBoot项⽬: 引⼊Spring Web依赖, 把前端⻚⾯放在项⽬中
2.约定前后端交互接⼝
概念介绍
约定 "前后端交互接⼝" 是进⾏ Web 开发中的关键环节.
接⼝⼜叫 API(Application Programming Interface), 我们⼀般讲到接⼝或者 API,指的都是同⼀个东西.是指应⽤程序对外提供的服务的描述, ⽤于交换信息和执⾏任务
简单来说, 就是允许客⼾端给服务器发送哪些 HTTP 请求, 并且每种请求预期获取什么样的 HTTP 响应.
现在"前后端分离"模式开发, 前端和后端代码通常由不同的团队负责开发. 双⽅团队在开发之前, 会提前约定好交互的⽅式. 客⼾端发起请求, 服务器提供对应的服务. 服务器提供的服务种类有很多, 客⼾端按照双⽅约定指定选择哪⼀个服务接⼝, 其实也就是我们前⾯⽹络模块讲的的"应⽤层协议". 把约定的内容写在⽂档上, 就是"接⼝⽂档" ,接⼝⽂档也可以理解为是 应⽤程序的"操作说明书".
⽐如⼉童电话玩具
按1: 响应⼉歌
按2: 响应钢琴乐曲
按3: 安抚睡眠
按4: 交通⼯具⾳效
....
等等
但是这些操作说明, 如果没有⼀个⽂档来说明, ⽤⼾就不太清楚哪个按键对应哪个, 所以商品⼀般会带⼀个说明书。这些按键, 就是接⼝.
这个"说明书", 就是应⽤程序的"接⼝⽂档"
在项⽬开发前, 根据需求先约定好前后端交互接⼝, 双⽅按照接⼝⽂档进⾏开发.
接⼝⽂档通常由服务提供⽅来写, 交由服务使⽤⽅确认,也就是客⼾端.
接⼝⽂档⼀旦写好, 尽量不要轻易改变.
如若需要改变, 必须要通知另⼀⽅知晓.
需求分析
加法计算器功能, 对两个整数进⾏相加, 需要客⼾端提供参与计算的两个数, 服务端返回这两个整数计算的结果
基于以上分析, 我们来定义接⼝
接⼝定义
请求路径:calc/sum
请求⽅式:GET/POST
接⼝描述:计算两个整数相加
请求参数:
参数名 | 类型 | 是否必须 | 备注 |
num1 | Integer | 是 | 参与计算的第⼀个数 |
num2 | Integer | 是 | 参与计算的第⼆个数 |
⽰例: num1=5&num2=3
响应数据:
Content-Type: text/html
响应内容: 计算机计算结果: 8
服务器给浏览器返回计算的结果
3.服务器代码
package com.springboot.demo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
//@RestController表示返回的数据,@Controller表示返回视图
@RestController
@RequestMapping("/calc")
public class calcController {
@RequestMapping("/sum")
public String sum(@RequestParam("num1") Integer num1,
@RequestParam("num2") Integer num2){
Integer sum=num1+num2;
return "计算机计算结果为:"+sum;
}
@RequestMapping("/sum2")
public String sum2(Integer num1, Integer num2){
if(num1==null||num2==null){
return "参数不合法,请检查后提交";
}
Integer sum=num1+num2;
return "计算机计算结果为:"+sum;
}
}
4.调整前端⻚⾯代码
添加访问url和请求⽅式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</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>
5.运⾏测试
启动服务, 运⾏并测试
二.⽤⼾登录
需求: ⽤⼾输⼊账号和密码, 后端进⾏校验密码是否正确
1. 如果不正确, 前端进⾏⽤⼾告知
2. 如果正确, 跳转到⾸⻚. ⾸⻚显⽰当前登录⽤⼾
3. 后续再访问⾸⻚, 可以获取到登录⽤⼾信息
1 准备⼯作
把前端⻚⾯放在项⽬中
2 约定前后端交互接⼝
需求分析
对于后端开发⼈员⽽⾔, 不涉及前端⻚⾯的展⽰, 只需要提供两个功能
1. 登录⻚⾯: 通过账号和密码, 校验输⼊的账号密码是否正确, 并告知前端
2. ⾸⻚: 告知前端当前登录⽤⼾. 如果当前已有⽤⼾登录, 返回登录的账号, 如果没有, 返回空
接⼝定义
1. 校验接⼝
请求路径:/user/login
请求⽅式:POST
接⼝描述:校验账号密码是否正确
请求参数
参数名 | 类型 | 是否必须 | 备注 |
userName | String | 是 | 校验的账号 |
passWord | String | 是 | 校验的密码 |
Content-Type: text/html
响应内容:
true //账号密码验证成功
false//账号密码验证失败
3. 查询登录⽤⼾接⼝
请求路径:/user/getLoginUser
请求⽅式:GET
接⼝描述:查询当前登录的⽤⼾
请求参数:
⽆
响应数据:
Content-Type: text/html
响应内容:
zhangsan
返回当前登录的⽤⼾
4. 实现服务器端代码
1. 校验接⼝
package com.springboot.demo;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/user")
@RestController
public class LoginController {
@RequestMapping(value = "/login",method = RequestMethod.POST)
public Boolean login(@RequestParam("username") String username,
@RequestParam("password")String password,
HttpSession session){
// if(username!=null||username!=""||password!=null||password!=""){
// return false;
// }
//参数校验
if(!StringUtils.hasLength(username)||!StringUtils.hasLength(password)){
return false;
}
if("admin".equals(username)&&"admin".equals(password)){
session.setAttribute("username",username);
return true;
}
//.equals会报空指针异常,左边为空时会报,因此通常把常量放在前面
return false;
}
@RequestMapping(value = "/login2",method = RequestMethod.POST)
public Boolean login2( String username,
String password,
HttpSession session){
//参数校验
if(username==null||"".equals(username)||password==null||"".equals(password)){
return false;//常量写在前面
}
//不为空,那么校验账号和密码是否正确
if("admin".equals(username)&&"admin".equals(password)){
session.setAttribute("username",username);
return true;
}
//.equals会报空指针异常,左边为空时会报,因此通常把常量放在前面
return false;
}
@RequestMapping(value = "/login3",method = RequestMethod.POST)
public Boolean login3( String username,
String password,
HttpSession session){
//参数校验
if(!StringUtils.hasLength(username)||!StringUtils.hasLength(password)){
return false;
}
//不为空,那么校验账号和密码是否正确
if("admin".equals(username)&&"admin".equals(password)){
session.setAttribute("username",username);//设置session
return true;
}
//.equals会报空指针异常,左边为空时会报,因此通常把常量放在前面
return false;
}
}
2. 查询登录⽤⼾接⼝
@RequestMapping("/getLoginInfo")
public String getLoginInfo(HttpSession session){
String username = (String) session.getAttribute("username");
if(username!=null){
return "用户名为:"+username;
}
return "";
}
5.调整前端⻚⾯代码
1. 调整登录⻚⾯login.html
对于前端⽽⾔, 当点击登录按钮时, 需要把⽤⼾输⼊的信息传递到后端进⾏校验, 后端校验成功, 则跳转到⾸⻚:index.html, 后端校验失败, 则直接弹窗
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>用户登录</h1>
用户名:<input name="userName" type="text" id="userName"><br>
密码:<input name="password" type="password" id="password"><br>
<input type="button" value="登录" onclick="login()">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
function login() {
$.ajax({
type: "post",
url:"/user/login",
data:{
userName:$("#userName").val(),
password:$("#password").val()
} ,
success:function(result){
if(result){
//密码正确
location.href="index.html";
}else{
//密码错误
alert("密码错误,请重新输入");
}
}
});
}
</script>
</body>
</html>
⻚⾯跳转的三种⽅式:
1. window.location.href = "book_list.html";
2. window.location.assign("book_list.html");
3. window.location.replace("book_list.html");
以上写法, 通常把"window." 省略, ⽐如 window.location.href = "book_list.html";
写成 location.href = "book_list.html";
2. 调整⾸⻚代码
⾸⻚代码⽐较简单, 只显⽰当前登录⽤⼾即可.
当前登录⽤⼾需要从后端获取, 并显⽰到前端
<!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>
<body>
登录人: <span id="loginUser"></span>
<script src="jquery-3.7.1.min.js"></script>
<script>
$.ajax({
type:"get",
url:"/user/getLoginInfo",
success:function (result) {
$("#loginUser").text(result);
}
})
</script>
</body>
</html>
6 运⾏测试
多次刷新 http://127.0.0.1:8080/index.html 发现依然可以获取到登录⽤⼾
如果重启服务器, 则登录⼈显⽰: 空
Session 存在内存中, 如果不做任何处理, 默认服务器重启, Session数据就丢失了
三、留⾔板
需求:
界⾯如下图所⽰
1. 输⼊留⾔信息, 点击提交. 后端把数据存储起来.
2. ⻚⾯展⽰输⼊的表⽩墙的信息
1 准备⼯作
把前端⻚⾯放在项⽬中
2 约定前后端交互接⼝
需求分析
后端需要提供两个服务
1. 提交留⾔: ⽤⼾输⼊留⾔信息之后, 后端需要把留⾔信息保存起来
2. 展⽰留⾔: ⻚⾯展⽰时, 需要从后端获取到所有的留⾔信息
接⼝定义
1. 获取全部留⾔
全部留⾔信息, 我们⽤List来表⽰, 可以⽤JSON来描述这个List数据.
请求:
GET /message/getList
响应: JSON 格式
[
{
"from": "⿊猫",
"to": "⽩猫",
"message": "喵"
},{
"from": "⿊狗",
"to": "⽩狗",
"message": "汪"
},
//...
]
浏览器给服务器发送⼀个 GET /message/getList 这样的请求, 就能返回当前⼀共有哪些留⾔记录. 结果以 json 的格式返回过来.
2. 发表新留⾔
请求: body 也为 JSON 格式.
POST /message/publish
{
"from": "⿊猫",
"to": "⽩猫",
"message": "喵"
}
响应: JSON 格式.
{ok: 1}
我们期望浏览器给服务器发送⼀个 POST /message/publish 这样的请求, 就能把当前的留⾔提交给服务器.
3 实现服务器端代码
(1) lombok介绍
在这个环节, 我们介绍⼀个新的⼯具包 lombok
Lombok是⼀个Java⼯具库,通过添加注解的⽅式,简化Java的开发.
简单来学习下它的使⽤
a. 引⼊依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>true</scope>
</dependency>
b. 使⽤
lombok通过⼀些注解的⽅式, 可以帮助我们消除⼀些冗⻓代码, 使代码看起来简洁⼀些
⽐如之前的Person对象 就可以改为
@Data
public class MessageInfo {
private String from;
private String to;
private String message;
}
@Data 注解会帮助我们⾃动⼀些⽅法, 包含getter/setter, equals, toString等
c. 原理解释
可以观察加了 @Data 注解之后, Idea反编译的class⽂件
这不是真正的字节码⽂件, ⽽是Idea根据字节码进⾏反编译后的⽂件
反编译是将可执⾏的程序代码转换为某种形式的⾼级编程语⾔, 使其具有更易读的格式. 反编译是⼀
种逆向⼯程,它的作⽤与编译器的作⽤相反.
可以看出来, lombok是⼀款在编译期⽣成代码的⼯具包.
Java 程序的运⾏原理:
Lombok 的作⽤如下图所⽰:
d. 更多使⽤
如果觉得@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
(2)更快捷的引⼊依赖
上述引⼊lombok依赖, 需要去找lombok的坐标
接下来介绍更简单引⼊依赖的⽅式
1. 安装插件EditStarter, 重启Idea
2. 在pom.xml⽂件中, 单击右键, 选择Generate, 操作如下图所⽰
进⼊Edit Starters的编辑界⾯, 添加对应依赖即可.
注意:
不是所有依赖都可以在这⾥添加的, 这个界⾯和SpringBoot创建项⽬界⾯⼀样.
依赖不在这⾥的, 还需要去Maven仓库查找坐标, 添加依赖.
(3) 服务器代码实现
定义留⾔对象 MessageInfo 类
package com.springboot.demo;
import lombok.*;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class MessageInfo {
private String from;
private String to;
private String message;
}
创建 MessageWallController 类
List<MessageInfo> 来存储留⾔板信息
package com.springboot.demo;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RequestMapping("/message")
@RestController
public class MessageWallController {
private List<MessageInfo> messageInfoList=new ArrayList<>();
//@PostMapping(value = "/publish",produces = "application/json")
@RequestMapping("/publish")//@RequestBody表示请求方法为Json
public String publish(@RequestBody MessageInfo messageInfo){
if(!StringUtils.hasLength(messageInfo.getFrom())
||!StringUtils.hasLength(messageInfo.getTo())
||!StringUtils.hasLength(messageInfo.getMessage())){
messageInfoList.add(messageInfo);
return "{\"ok\":0}";
}
messageInfoList.add(messageInfo);
return "{\"ok\":1}";
}
@RequestMapping("/getList")
private List<MessageInfo> getList(){
return messageInfoList;
}
}
(4) 调整前端⻚⾯代码
修改 messagewall.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>
<script src="js/jquery-3.7.1.min.js"></script>
<script>
$.ajax({
type:"get",
url:"/message/getList",
success:function(message){
if(message!=null&&message.length){
var finalHtml="";
for(var msg of message){
let finalHtml="<div>"+msg.from+"对"+msg.to+"说"+msg.message+"/<div>";
$(".container").append(finalHtml);
}
}
}
})
function submit(){
let from = $("#from").val();
let to = $("#to").val();
let say = $("#say").val();
if(from==="" || to === "" || say === ""){
return;
}
$.ajax({
type: "post",
url: "/message/publish",
contentType: "application/json",
data: JSON.stringify({
from: from,
to: to,
message: say
}),
success: function (result) {
let o=JSON.parse(result)
if (o.ok == 1) {
//添加成功
let html = "<div>" + from + " 对 " + to + " 说: " + say + "</div>";
$(".container").append(html);
$(":text").val("");
} else {
alert("留言发布失败");
}
}
})
};
</script>
</body>
</html>
(5 )运⾏测试
此时在浏览器通过 URL http://127.0.0.1:8080/messagewall.html 访问服务器, 即可看到
此时我们每次提交的数据都会发送给服务器. 每次打开⻚⾯的时候⻚⾯都会从服务器加载数据. 因此及时关闭⻚⾯, 数据也不会丢失.
但是数据此时是存储在服务器的内存中 ( private List<Message> messages = new ArrayList<Message>(); ), ⼀旦服务器重启, 数据仍然会丢失.
要想数据不丢失, 需要把数据存储在数据库中
四、 图书管理系统
需求:
1. 登录: ⽤⼾输⼊账号,密码完成登录功能
2. 列表展⽰: 展⽰图书
1 准备⼯作
创建新项⽬, 引⼊对应依赖, 把前端⻚⾯放在项⽬中
系统后续其他功能也会完善, 此处建议创建新项⽬
2 约定前后端交互接⼝
需求分析
图书管理系统是⼀个相对较⼤⼀点的案例, 咱们先实现其中的⼀部分功能.
⽤⼾登录
图书列表
根据需求可以得知, 后端需要提供两个接⼝
1. 账号密码校验接⼝: 根据输⼊⽤⼾名和密码校验登录是否通过
2. 图书列表: 提供图书列表信息
接⼝定义
1. 登录接⼝
[URL]
POST /user/login
[请求参数]
name=admin&password=admin
[响应]
true //账号密码验证成功
false//账号密码验证失败
2. 图书列表展⽰
[URL]
POST /book/getList
[请求参数]
⽆
[响应]
返回图书列表
[
{
"id": 1,
"bookName": "活着",
"author": "余华",
"count": 270,
"price": 20,
"publish": "北京⽂艺出版社",
"status": 1,
"statusCN": "可借阅"
},
...
]
字段说明:
id | 图书ID |
bookName | 图书名称 |
author | 作者 |
count | 数量 |
price | 定价 |
publish | 图书出版社 |
status | 图书状态 1-可借阅, 其他-不可借阅 |
statusCN | 图书状态中⽂含义 |
3 服务器代码
创建图书类 BookInfo
package com.example.demo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class BookInfo {
private Integer id;
private String bookName;
private String author;
private Integer number;
private BigDecimal price;
private String publisher;
private Integer status;
private String statusCN;
private Date createTime;
private Date updateTime; //更新时间
}
创建 UserController, 实现登录验证接⼝
package com.example.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;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public Boolean login(String username, String password, HttpSession session) {
//1.校验参数
//3.如果正确存储session,返回true,否则返回false
if(!StringUtils.hasLength(username) ||!StringUtils.hasLength(password)) {
return false;
}
//2.检验账号和密码是否正确
if("admin".equals(username) && "123456".equals(password)) {
//账号正确
session.setAttribute("user", username);
return true;
}
return false;
}
}
创建 BookController, 获取图书列表
package com.example.demo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping("/book")
public class bookController {
@RequestMapping("/getList")
public List<BookInfo> getList(){
//从数据库查询,mock数据一下
List<BookInfo> list = mockData();
for (BookInfo bookInfo : list) {
if(bookInfo.getStatus()==1){
bookInfo.setStatusCN("可借阅");
}else {
bookInfo.setStatusCN("不可借阅");
}
}
return list;
}
private List<BookInfo> mockData() {
List<BookInfo> list = new ArrayList<>();
for (int i = 0; i <= 15; i++) {
BookInfo bookInfo = new BookInfo();
bookInfo.setId(i);
bookInfo.setBookName("图书"+i);
bookInfo.setAuthor("作者"+i);
bookInfo.setPublisher("出版社"+i);
bookInfo.setNumber(new Random().nextInt(100));
bookInfo.setPrice(new BigDecimal(new Random().nextInt(70)+10));
bookInfo.setStatus(i%5==0?0:1);
list.add(bookInfo);
}
return list;
}
}
数据采⽤mock的⽅式, 实际数据应该从数据库中获取
mock:模拟的, 假的
在开发和测试过程中,由于环境不稳定或者协同开发的同事未完成等情况下, 有些数据不容易构造或者不容易获取,就创建⼀个虚拟的对象或者数据样本,⽤来辅助开发或者测试⼯作.
简单来说, 就是假数据.
4 调整前端⻚⾯代码
登录⻚⾯:
添加登录处理逻辑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/login.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
<div class="container-login">
<div class="container-pic">
<img src="pic/computer.png" width="350px">
</div>
<div class="login-dialog">
<h3>登陆</h3>
<div class="row">
<span>用户名</span>
<input type="text" name="userName" id="userName" class="form-control">
</div>
<div class="row">
<span>密码</span>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script>
function login() {
$.ajax({
type:"post",
url:"/user/login",
data:{
username:$("#userName").val(),
password:$("#password").val()
},
success:function(result){
if(result==true){
location.href = "book_list.html";
}else{
alter("账号有误");
}
}
})
}
</script>
</body>
</html>
图书列表展⽰:
删除前端伪造的代码, 从后端获取数据并渲染到⻚⾯上.
1. 删除 <tbody></tbody>标签中的内容
2. 完善获取图书⽅法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书列表展示</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/list.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script src="js/jq-paginator.js"></script>
</head>
<body>
<div class="bookContainer">
<h2>图书列表展示</h2>
<div class="navbar-justify-between">
<div>
<button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
<button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
</div>
</div>
<table>
<thead>
<tr>
<td>选择</td>
<td class="width100">图书ID</td>
<td>书名</td>
<td>作者</td>
<td>数量</td>
<td>定价</td>
<td>出版社</td>
<td>状态</td>
<td class="width200">操作</td>
</tr>
</thead>
<tbody id="bookList">
</tbody>
</table>
<div class="demo">
<ul id="pageContainer" class="pagination justify-content-center"></ul>
</div>
<script>
getBookList();
function getBookList() {
$.ajax({
type:"get",
url:"/book/getList",
success:function(books){
var finalHTML="";
for(var book of books){
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.number+'</td>';
finalHTML+=' <td>'+book.price+'</td>';
finalHTML+=' <td>'+book.publisher+'</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)" onclick="deleteBook('+book.id+')">删除</a> ';
finalHTML+=' </div> ';
finalHTML+=' </td>';
finalHTML+=' </tr> ';
}
$("#bookList").html(finalHTML);
}
})
}
//翻页信息
$("#pageContainer").jqPaginator({
totalCounts: 100, //总记录数
pageSize: 10, //每页的个数
visiblePages: 5, //可视页数
currentPage: 1, //当前页码
first: '<li class="page-item"><a class="page-link">首页</a></li>',
prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',
next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',
last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',
page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
//页面初始化和页码点击时都会执行
onPageChange: function (page, type) {
console.log("第"+page+"页, 类型:"+type);
}
});
function deleteBook(id) {
var isDelete = confirm("确认删除?");
if (isDelete) {
//删除图书
alert("删除成功");
}
}
function batchDelete() {
var isDelete = confirm("确认批量删除?");
if (isDelete) {
//获取复选框的id
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
alert("批量删除成功");
}
}
</script>
</div>
</body>
</html>
5 运⾏测试
启动项⽬:
访问: http://127.0.0.1:8080/login.html ,输⼊账号密码: admin admin, 登录成功, 跳转到 图书列表⻚
界⾯展⽰:
⻚⾯其他功能先不完善, 重点是理解前后端的交互过程, 以及Spring MVC项⽬的开发流程.
五. 应⽤分层
通过上⾯的练习, 我们学习了Spring MVC简单功能的开发, 但是我们也发现了⼀些问题
⽬前我们程序的代码有点"杂乱", 然⽽当前只是"⼀点点功能"的开发. 如果我们把整个项⽬功能完成呢?
代码会更加的"杂乱⽆章"(⽂件乱, 代码内容乱)也基于此, 咱们接下来学习应⽤分层.
类似公司的组织架构
公司初创阶段, ⼀个⼈⾝兼数职, 既做财务, ⼜做⼈事, 还有⾏政.
随着公司的逐渐壮⼤, 会把岗位进⾏细分, 划分为财务部⻔, ⼈事部⻔, ⾏政部⻔等. 各个部⻔内部还会再进⾏细分.
项⽬开发也是类似, 最开始功能简单时, 我们前后端放在⼀起开发, 随着项⽬功能的复杂, 我们分为前端和后端不同的团队, 甚⾄更细粒度的团队. 后端开发也会根据功能再进⾏细分. MVC就是其中的⼀种拆分⽅式.
但是随着后端⼈员不再涉及前端, 后端开发⼜有了新的分层⽅式.
1 介绍
阿⾥开发⼿册中, 关于⼯程结构部分, 定义了常⻅⼯程的应⽤分层结构:
那么什么是应⽤分层呢?
应⽤分层 是⼀种软件开发设计思想, 它将应⽤程序分成N个层次, 这N个层次分别负责各⾃的职责, 多个层次之间协同提供完整的功能. 根据项⽬的复杂度, 把项⽬分成三层, 四层或者更多层.
常⻅的MVC设计模式, 就是应⽤分层的⼀种具体体现.
为什么需要应⽤分层?
在最开始的时候,为了让项⽬快速上线,我们通常是不考虑分层的. 但是随着业务越来越复杂,⼤量的代码混在⼀起,会出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动⼀处就牵⼀发⽽动全⾝等问题. 所以学习对项⽬进⾏分层就是我们程序员的必修课了.
如何分层(三层架构)
"MVC", 就是把整体的系统分成了 Model(模型), View(视图)和Controller(控制器)三个层次,也就是将⽤⼾视图和业务处理隔离开,并且通过控制器连接起来,很好地实现了表现和逻辑的解耦,是⼀种标准的软件分层架构。
⽬前现在更主流的开发⽅式是 "前后端分离" 的⽅式, 后端开发⼯程师不再需要关注前端的实现, 所以对于Java后端开发者, ⼜有了⼀种新的分层架构: 把整体架构分为表现层、业务逻辑层和数据层. 这种分层⽅式也称之为"三层架构".
1. 表现层: 就是展⽰数据结果和接受⽤⼾指令的,是最靠近⽤⼾的⼀层;
2. 业务逻辑层: 负责处理业务逻辑, ⾥⾯有复杂业务的具体实现;
3. 数据层: 负责存储和管理与应⽤程序相关的数据
可以看到, 咱们前⾯的代码, 并不符合这种设计思想, ⽽是所有的代码堆砌在⼀起
按照上⾯的层次划分, Spring MVC 站在后端开发⼈员的⻆度上, 也进⾏了⽀持, 把上⾯的代码划分为三个部分:
- 请求处理、响应数据:负责,接收⻚⾯的请求,给⻚⾯响应数据.
- 逻辑处理:负责业务逻辑处理的代码.
- 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作.
这三个部分, 在Spring的实现中, 均有体现:
- Controller:控制层。接收前端发送的请求,对请求进⾏处理,并响应数据。
- Service:业务逻辑层。处理具体的业务逻辑。
- Dao:数据访问层,也称为持久层。负责数据访问操作,包括数据的增、删、改、查.
MVC 和三层架构的区别和联系
关于⼆者的关系, ⼀直存在不同的观点. 有⼈认为三层架构是MVC模式的⼀种实现, 也有⼈认为MVC是三层架构的替代⽅案, 等等各种说法都有. 根本原因是⼤家站在不同的⻆度来看待这个问题的, ⼤家根据⾃⼰的理解, 能够⾃圆其说, 说出⾃⼰的观点即可, 也不建议去背书.
从概念上来讲, ⼆者都是软件⼯程领域中的架构模式.
- MVC架构模式由三部分组成, 分别是: 模型(Model), 视图(View)和控制器(Controller).
- 三层架构将业务应⽤划分为:表现层, 业务逻辑层, 数据访问层.
MVC中, 视图和控制器合起来对应三层架构中的表现层. 模型对应三层架构中的业务逻辑层, 数据层,以及实体类。
⼆者其实是从不同⻆度对软件⼯程进⾏了抽象.
- MVC模式强调数据和视图分离, 将数据展⽰和数据处理分开, 通过控制器对两者进⾏组合.
- 三层架构强调不同维度数据处理的⾼内聚和低耦合, 将交互界⾯, 业务处理和数据库操作的逻辑分开.
⻆度不同也就谈不上互相替代了,在⽇常的开发中可以经常看到两种共存的情况,⽐如我们设计模型层的时候往往也会拆分出业务逻辑层(Service层)和数据访问层(Dao层)。
但是⼆者的⽬的是相同的, 都是"解耦,分层,代码复⽤"
软件设计原则:⾼内聚低耦合.
⾼内聚指的是:⼀个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越⾼,则内聚性越⾼,即 "⾼内聚"。
低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。修改⼀处代码, 其他模块的代码改动越少越好.
⾼内聚低耦合⽭盾吗?
不⽭盾, ⾼内聚指的是⼀个模块中各个元素之间的联系的紧密程度, 低耦合指的是各个模块之间的紧
密程度
这就好⽐⼀个企业, 包含很多部⻔, 各个部⻔之间的关联关系要尽可能的⼩, ⼀个部⻔发⽣问题, 要尽可能对降低对其他部⻔的影响, 就是耦合. 但是部⻔内部员⼯关系要尽量紧密, 遇到问题⼀起解决,克服. 这叫做内聚.
⽐如邻⾥邻居, 楼上漏⽔, 楼下遭殃, 就是耦合. 家庭⼀个成员⽣病, 其他成员帮忙照顾, 就叫内聚.
⼀个家庭内部的关系越紧密越好, ⼀个家庭尽可能少的影响另⼀个家庭, 就是低耦合.
2 应⽤分层的好处
- 降低层与层之间的依赖, 结构更加的明确, 利于各层逻辑的复⽤
- 开发⼈员可以只关注整个结构中的其中某⼀层, 极⼤地降低了维护成本和维护时间
- 可以很容易的⽤新的实现来替换原有层次的实现
- 有利于标准化
五、企业规范
以下企业规范, 适⽤于多数企业, 均不做强制要求. 具体以所在企业为准.
1. 类名使⽤⼤驼峰⻛格,但以下情形例外:DO/BO/DTO/VO/AO
2. ⽅法名、参数名、成员变量、局部变量统⼀使⽤⼩驼峰⻛格
3. 包名统⼀使⽤⼩写,点分隔符之间有且仅有⼀个⾃然语义的英语单词.
4.常⻅命名命名⻛格介绍
- ⼤驼峰: 所有单词⾸字⺟都需要⼤写, ⼜叫帕斯卡命名法, ⽐如: UserController
- 类名
- ⼩驼峰: 除了第⼀个单词,其他单词⾸字⺟⼤写,⽐如: userController
- ⽅法名、参数名、成员变量、局部变量
- 蛇形: ⽤下划线(_)作⽤单词间的分隔符, ⼀般⼩写, ⼜叫下划线命名法, ⽐如: user_controller
- 常用于数据库字段命名
- 串形: ⽤短横线(-)作⽤单词间的分隔符, ⼜叫脊柱命名法, ⽐如: user-controller
- 常用于css命名
六 总结
1. 学习Spring MVC, 其实就是学习各种Web开发需要⽤的到注解
a. @RequestMapping: 路由映射
b. @RequestParam: 后端参数重命名
c. @RequestBody: 接收JSON类型的参数
d. @PathVariable: 接收路径参数
e. @RequestPart: 上传⽂件
f. @ResponseBody: 返回数据
g. @CookieValue: 从Cookie中获取值
h. @SessionAttribute: 从Session中获取值
i. @RequestHeader: 从Header中获取值
j. @Controller: 定义⼀个控制器, Spring 框架启动时加载, 把这个对象交给Spring管理. 默认返回
视图.
k. @RestController: @ResponseBody + @Controller 返回数据
2. Cookie 和Session都是会话机制, Cookie是客⼾端机制, Session是服务端机制. ⼆者通过SessionId来关联.
Spring MVC内置HttpServletRequest, HttpServletResponse两个对象. 需要使⽤时, 直接在⽅法中添加对应参数即可, Cookie和Session可以从HttpServletRequest中来获取, 也可以直接使⽤HttpServletResponse设置Http响应状态码.