Spring MVC 综合案例
目录
一. 加法计算器
1. 准备工作
2. 约定前后端交互接口
需求分析
接口定义
3. 服务器端代码
4. 运行测试
二. 用户登录
1. 准备工作
2. 约定前后端交互接口
需求分析
接口定义
(1) 登录界面接口
(2) 首页接口
3. 服务器端代码
4. 运行测试
三. 留言板
1. 准备工作
2. 约定前后端交互接口
需求分析
接口定义
3. 服务器端代码
四. 图书管理系统
1. 准备工作
2. 约定前后端交互接口
需求分析
接口定义
3. 服务器端代码
登录页面
图书列表
创建图书:
返回图书列表:
五. lombook 介绍
六. 应用分层
一. 加法计算器
1. 准备工作
创建SpringBoot项目, 并引入SpringBoot依赖. 把前端页面的代码放到项目中.
前端代码:
<!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>
2. 约定前后端交互接口
由于现在大多是以 "前后端分离模式" 开发, 前后端代码通常由不同团队进行开发. 所以双方团队在开发之前, 会提前约定好前后端交互方式. 常用 "接口文档" 来描述它. (接口文档 也可以理解为"应用程序的说明书").
-
需求分析
加法计算器功的能是对两个整数进行相加. 需要客户端提供参与计算的两个数, 服务端返回这两个整数相加的结果.
-
接口定义
请求路径: clac / sum
请求方式: GET / POST
接口描述: 计算两个整数相加
请求参数:
响应数据:
Content-Type: text / html
响应内容: 计算机计算结果: x
3. 服务器端代码
@RestController
@RequestMapping("/calc")
public class CalcController {
@RequestMapping("/sum")
public String sum(Integer num1, Integer num2) {
int sum = num1 + num2;
return "<h1>计算机计算结果: "+sum+"</h1>";
}
}
4. 运行测试
运行main方法, 启动服务.
访问服务地址: http://127.0.0.1:8080/calc.html
二. 用户登录
1. 准备工作
创建SpringBoot项目, 并引入SpringBoot依赖. 把前端页面的代码放到项目中.
- 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进行前后端交互
$.ajax({
//小括号中是一个对象,对象用大括号括起来
type:"post",
url:"/user/login",
data:{
"username":$("#userName").val(),
"password":$("#password").val()//通过Id获取值,给后端传递参数
},
success: function (result) {//参数名任意,用于接收后端返回的参数
if (result){
location.href = "/index.html"//跳转页面
}else {
alert("账号或密码有误");//弹窗
}
}
});
}
</script>
</body>
</html>
- login.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>
<body>
登录人: <span id="loginUser"></span>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
$.ajax({
type : "get",
url : "/user/getLoginUser",
success:function (result) {
$("#loginUser").text(result);//给loginUser参数赋值为后端返回的result值
}
})
</script>
</body>
</html>
2. 约定前后端交互接口
-
需求分析
(1) 登录页面: 通过账号和密码, 校验输入的账号密码是否正确, 并告知前端.
(2) 首页: 告知前端当前登录用户. 如果当前已有用户登录, 返回登录的账号; 如果没有, 返回空.
-
接口定义
(1) 登录界面接口
接口定义:
请求路径: /user/login
请求方式: POST
接口描述: 校验账号密码是否正确.
请求参数:
响应数据:
Content-Type : text/html
响应内容:
账号密码正确:true
账号密码错误:false
(2) 首页接口
接口定义:
请求路径: /user/getLoginuser
请求方式: GET
接口描述: 显示当前登录用户的主页,主页上显示用户名.
请求参数: 无
响应数据:
Content-Type:text/html
响应内容: 登录的用户名.
3. 服务器端代码
(1) 登录界面接口
@RestController
@RequestMapping("/user")
public class LoginController {
@RequestMapping("/login")
public boolean login(String userName, String password, HttpSession session) {
// 账号或密码为空
if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
return false;
}
// 校验账号密码是否正确 (这里先写死)
if (!"zhangsan".equals(userName) || !"123456".equals(password)) {
return false;
}
//密码验证成功后, 把用户名存储在Session中.
session.setAttribute("userName",userName);
return true;
}
//StringUtils.hasLength()是Spring提供的一个方法, 用于判断字符串是否有值.
//字符串为 null 或 "" 时, 返回false, 其他情况返回true.
}
(2) 首页接口
@RestController
@RequestMapping("/getLoginUser")
public class getLoginUser {
@RequestMapping("/")
public String getLoginUser(HttpSession session) {
//从Session中获取用户登录信息
String userName = (String) session.getAttribute("userName");
//如果用户已经登录, 则直接返回用户名
if (StringUtils.hasLength(userName)) {
return userName;
}
//否则什么都不返回
return "";
}
}
4. 运行测试
验证不成功:
验证成功:
三. 留言板
1. 准备工作
创建SpringBoot项目, 并引入SpringBoot依赖. 把前端页面的代码放到项目中.
<!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>
load();//每次在重新加载页面之后,都要从后端的List中调动数据,保证上次添加的数据不会丢失
function load(){
$.ajax({
type: "get",
url:"message/getList",
success:function (result){
for (var message of result){
var divE = "<div>"+message.from +"对" + message.to + "说:" + message.say+"</div>";
$(".container").append(divE);
}
}
});
}
function submit(){
//1. 获取留言的内容
var from = $('#from').val();
var to = $('#to').val();
var say = $('#say').val();
if (from== '' || to == '' || say == '') {
return;
}
$.ajax({
type : "post",
url : "message/publish",
contentType: "application/json",
//传递的值是json类型,data就是在向后端传递数据
data:JSON.stringify({
from : from,
to : to,
say : say//从前端参数的ID中获取对应的值传递给后端
}),
//后端返回结果
success:function (result) {
if (result){
//2. 构造节点
var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";
//3. 把节点添加到页面上
$(".container").append(divE);
//4. 清空输入框的值
$('#from').val("");
$('#to').val("");
$('#say').val("");
}else{
alert("提交留言失败")
}
}
});
}
</script>
</body>
</html>
2. 约定前后端交互接口
-
需求分析
后端需要提供两个服务;
(1) 提交留言: 客户输入留言信息之后, 后端要把留言信息保存起来.
(2) 展示留言: 页面展示时, 需要从后端获取到所有的留言信息.
-
接口定义
1. 获取全部留言
2. 发表新留言
3. 服务器端代码
package com.example.demo;
import org.springframework.util.StringUtils;
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 MessageWall {
public List<MessageInfo> messageInfoList = new ArrayList<>();
@RequestMapping("/publish")
public Boolean messageController(@RequestBody MessageInfo messageInfo){
System.out.println(messageInfo); //打印日志
if (StringUtils.hasLength(messageInfo.from) &&
StringUtils.hasLength(messageInfo.to) &&
StringUtils.hasLength(messageInfo.say)){
messageInfoList.add(messageInfo);
return true; //都有长度,添加成功,返回true
}
// 否则 添加失败,返回false
return false;
}
@RequestMapping("/getList")
public List<MessageInfo> getList(){
return messageInfoList;
}
}
package com.example.demo;
import lombok.Data;
@Data
public class MessageInfo {
public String from;
public String to;
public String say;
}
四. 图书管理系统
1. 准备工作
创建SpringBoot项目, 并引入SpringBoot依赖. 把前端页面的代码放到项目中.
- 登录页面:
<!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:{
name:$("#userName").val(),
password:$("#password").val()
},
success:function (result) {
if (result){
location.href = "book_list.html";
}else{
alert("账号或密码错误")
}
}
});
}
</script>
</body>
</html>
- 图书列表:
<!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>
</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 (result) {
console.log(result);
if (result != null) {
var finalHtml = "";//构造字符串
for (var book of result) {
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><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></td>';
finalHtml += "</tr>";
}
$("tbody").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>
2. 约定前后端交互接口
-
需求分析
登录: 用户输入账号和密码完成登录功能.
列表: 展示图书
-
接口定义
登录接口:
[URL]
POST /user/login
[请求参数]
name=admin&password=admin
[响应]
true //账号密码验证成功
false//账号密码验证失败
列表:
[URL]
POST /book/getList
[请求参数]
⽆
[响应]
返回图书列表
[
{
"id": 1,
"bookName": "活着",
"author": "余华",
"count": 270,
"price": 20,
"publish": "北京⽂艺出版社",
"status": 1,
"statusCN": "可借阅"
},
...
属性说明:
3. 服务器端代码
-
登录页面
package com.jrj.library;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class Login {
@RequestMapping("/login")
public Boolean login(String name, String password, HttpSession session){
if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)){
return false;
}
if ("zhangsan".equals(name) && "123456".equals(password)){
session.setAttribute("userName",name);
return true;
}
return false;
}
}
-
图书列表
创建图书:
package com.jrj.library;
import lombok.Data;
@Data
public class BookInfo {//构造一本书所有的属性
public Integer id;
public String bookName;
public String author;
public Integer count;
public Integer price;
public String publish;
public Integer status;//1-可借阅,2-不可借阅
public String statusCN;
}
返回图书列表:
package com.jrj.library;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RequestMapping("/book")
@RestController
public class BookController {
@RequestMapping("/getList")
public List<BookInfo> getList(){
List<BookInfo> list = mockData();
for (BookInfo bookInfo:list){
if (bookInfo.status == 1){
bookInfo.setStatusCN("可借阅");
}else{
bookInfo.setStatusCN("不可借阅");
}
}
return list;
}
//模拟数据
private List<BookInfo> mockData(){
List<BookInfo> list2 = new ArrayList<>();
for (int i = 0; i < 5; i++) {
BookInfo bookInfo = new BookInfo();
bookInfo.setId(i);
bookInfo.setBookName("Java编程思想"+i);
bookInfo.setCount(1);
bookInfo.setPublish("机械工业出版社");
bookInfo.setPrice(new Random().nextInt(100));
bookInfo.setAuthor("高斯林");
bookInfo.setStatus(1);
list2.add(bookInfo);
}
return list2;
}
}
五. lombook 介绍
lombook是一个Java工具库, 通过添加注解的方式, 来简化Java开发.
使用方法:
也可直接使用 @Data注解, 只不过 @Data注解 比较粗暴:
@Data =
@Getter+@Setter+@ToString+@NoArgsConstructor +@RequiredArgsConstructor
六. 应用分层
为了不使我们的代码看起来比较杂乱, 我们使用应用分层来对代码进行分层管理.
Spring项目常见的三层应用分层:
① Controller (接口层) : 负责与外部做交互.
② Service (逻辑层) : 负责做逻辑处理.
③ Mapper / Dao (持久层) : 负责从数据库拿数据 (相当于数据库的客户端).
还有最底层的数据库 (DB) : 使用MySQL或Oracle (不在三层分层中).
MVC和三层架构的关系:
软件设计原则: 高内聚+低耦合