Bootstrap

Spring Web入门练习

加法计算器

约定前后端交互接⼝

约定 "前后端交互接⼝" 是进⾏ Web 开发中的关键环节.

接⼝⼜叫 API(Application Programming Interface), 我们⼀般讲到接⼝或者 API,指的都是同⼀个东西.

是指应⽤程序对外提供的服务的描述, ⽤于交换信息和执⾏任务(与JavaSE阶段学习的[类和接⼝]中的接⼝是两回事).

简单来说, 就是允许客⼾端给服务器发送哪些 HTTP 请求, 并且每种请求预期获取什么样的 HTTP 响应.

现在"前后端分离"模式开发, 前端和后端代码通常由不同的团队负责开发. 双⽅团队在开发之前, 会提前

约定好交互的⽅式. 客⼾端发起请求, 服务器提供对应的服务. 服务器提供的服务种类有很多, 客⼾端按照双⽅约定指定选择哪⼀个服务.

接⼝, 其实也就是我们前⾯⽹络模块讲的的"应⽤层协议". 把约定的内容写在⽂档上, 就是"接⼝⽂档" ,接⼝⽂档也可以理解为是 应⽤程序的"操作说明书".

需求分析

加法计算器功能, 对两个整数进⾏相加, 需要客⼾端提供参与计算的两个数, 服务端返回这两个整数计算的结果

基于以上分析, 我们来定义接⼝

接⼝定义

请求路径:calc/sum

请求⽅式:GET/POST

接⼝描述:计算两个整数相加

响应数据:

Content-Type: text/html

响应内容: 计算机计算结果: 8

服务器代码


@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;
    }
}

调整前端⻚⾯代码

<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>

⽤⼾登录

需求: ⽤⼾输⼊账号和密码, 后端进⾏校验密码是否正确

1. 如果不正确, 前端进⾏⽤⼾告知

2. 如果正确, 跳转到⾸⻚. ⾸⻚显⽰当前登录⽤⼾

3. 后续再访问⾸⻚, 可以获取到登录⽤⼾信息

约定前后端交互接⼝

需求分析

对于后端开发⼈员⽽⾔, 不涉及前端⻚⾯的展⽰, 只需要提供两个功能

1. 登录⻚⾯: 通过账号和密码, 校验输⼊的账号密码是否正确, 并告知前端

2. ⾸⻚: 告知前端当前登录⽤⼾. 如果当前已有⽤⼾登录, 返回登录的账号, 如果没有, 返回空

接⼝定义

校验接⼝
请求路径:/user/login
请求⽅式:POST
接⼝描述:校验账号密码是否正确
请求参数:

响应数据:
Content-Type: text/html
响应内容:
true //账号密码验证成功
false//账号密码验证失败

查询登录⽤⼾接⼝

请求路径:/user/getLoginUser
请求⽅式:GET
接⼝描述:查询当前登录的⽤⼾
请求参数:
响应数据:
Content-Type: text/html
响应内容: 
zhangsan
返回当前登录的⽤⼾

实现服务器端代码

package com.example.demo;

import ch.qos.logback.core.util.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public Boolean login(String userName, String password, HttpServletRequest request) {
//        if(userName == null || "".equals(userName)) {
//            return false;
//        }
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return false;
        }
        if("admin".equals(userName) && "admin".equals(password)) {
            //set session
            HttpSession session = request.getSession(true);
            session.setAttribute("userName", userName);
            return true;
        }
        return false;
    }

    @RequestMapping(value = "getLoginUser", method= RequestMethod.GET)
    public String getLoginUser(HttpSession session) {
        if(session.getAttribute("userName") == null) {
            return "";
        }
        return (String)session.getAttribute("userName");
    }
}

调整前端⻚⾯代码

<!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="jquery-3.7.1.min.js"></script>
  <script>
    function login() {
      $.ajax({
        type:"post",
        url: "/user/login",
        data: {
          "userName":$("#userName").val(),
          "password":$("#password").val()
        },
        success: function(body) {
          if(body == true){
            //skip to index page
            location.href = "index.html"
          }else {
            //current page
            alert("wrong userName or password")
          }
        }
      });
    }

  </script>
</body>

</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="jquery-3.7.1.min.js"></script>
    <script>
        $.ajax({
            type : "get",
            url : "/user/getLoginUser",
            success: function(userName) {
                $("#loginUser").text(userName);
            }
        });
    </script>
</body>

</html>

留⾔板

需求:

界⾯如下图所⽰

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
}

实现服务器端代码

lombok介绍

在这个环节, 我们介绍⼀个新的⼯具包 lombok

Lombok是⼀个Java⼯具库,通过添加注解的⽅式,简化Java的开发.

简单来学习下它的使⽤

1. 引⼊依赖

<dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <optional>true</optional>
</dependency>
2. 使⽤

lombok通过⼀些注解的⽅式, 可以帮助我们消除⼀些冗⻓代码, 使代码看起来简洁⼀些

@Data
public class Person {
 private int id;
 private String name;
 private String password;
}
如果觉得@Data⽐较粗暴(⽣成⽅法太多), lombok也提供了⼀些更精细粒度的注解

服务器代码实现

package com.example.demo;

import lombok.Data;

@Data
public class MessageInfo {
    private String from;
    private String to;
    private String message;
}


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;

@RestController
@RequestMapping("/message")
public class MessageController {

    //save message
    private List<MessageInfo> messageInfos = new ArrayList<>();

    @RequestMapping("/getList")
    public List<MessageInfo> getList() {
        return messageInfos;
    }

    @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()))
        ) {
            messageInfos.add(messageInfo);
            return "{\"ok\":1}";
        }
        return "{\"ok\":0}";
    }

}

调整前端⻚⾯代码

<!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="jquery-3.7.1.min.js"></script>
    <script>
        getList();
        function getList() {
            $.ajax({
                type: "get",
                url: "/message/getList",
                success: function (message) {
                    for (var msg of message) {
                        //2. 构造节点
                        var divE = "<div>" + msg.from + "对" + msg.to + "说:" + msg.message + "</div>";
                        //3. 把节点添加到页面上    
                        $(".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",
                data: JSON.stringify({
                    from: from,
                    to: to,
                    message: say
                }),
                success: function (result) {
                    if (result.ok == 1) {
                        alert("add success!")
                        //2. 构造节点
                        var divE = "<div>" + from + "对" + to + "说:" + say + "</div>";
                        //3. 把节点添加到页面上    
                        $(".container").append(divE);
                        //4. 清空输入框的值
                        $('#from').val("");
                        $('#to').val("");
                        $('#say').val("");
                    } else {
                        alert("add fail!");
                    }
                }
            });





        }

    </script>
</body>

</html>

图书管理系统

需求:

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": "可借阅"
 },
 ...
]

字段说明:

服务器代码

package com.pixiao.book;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
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(value = "/getList", method = RequestMethod.GET)
    public List<BookInfo> getList() {
        List<BookInfo> bookInfos = mockData();
        for(BookInfo bookInfo: bookInfos) {
            if(bookInfo.getStatus() == 1) {
                bookInfo.setStatusCN("可借阅");
            } else {
                bookInfo.setStatusCN("不可借阅");
            }
        }
        return bookInfos;
    }

    private List<BookInfo> mockData() {
        List<BookInfo> bookInfos = new ArrayList<>();
        for (int i = 1; i <= 15; i++) {
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId((long) i);
            bookInfo.setBookName("book" + i);
            bookInfo.setAuthor("author" + i);
            bookInfo.setPublishName("press" + i);
            bookInfo.setNum("" + new Random().nextInt(100));
            bookInfo.setPrice(new BigDecimal(new Random().nextInt(70) + 10));
            bookInfo.setStatus(i % 5 == 0 ? 0: 1);
            bookInfos.add(bookInfo);
        }
        return bookInfos;
    }
}


package com.pixiao.book;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class BookInfo {
    private Long id;
    private String bookName;
    private String author;
    private String num;
    private BigDecimal price;
    private String publishName;
    private Integer status;
    private String statusCN;
}


package com.pixiao.book;

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

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/login" , method = RequestMethod.POST)
    public Boolean login(String name, String password, HttpSession session) {
        //1.校验参数
        if(!StringUtils.hasLength(name) || !StringUtils.hasLength(password)) {
            return false;
        }
        //2.校验账号密码
        if("admin".equals(name) && "admin".equals(password)) {
            //3.如果正确,存储session,返回true
            session.setAttribute("userName", name);
            return true;
        }
        return false;
    }

}

调整前端⻚⾯代码

<!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("error");
                    }
                }
            }); 
        }
    </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 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.num+"</td>";
                            finalHtml += "<td>"+book.price+"</td>";
                            finalHtml += "<td>"+book.publishName+"</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>

应⽤分层

⽬前现在更主流的开发⽅式是 "前后端分离" 的⽅式, 后端开发⼯程师不再需要关注前端的实现, 所以对于Java后端开发者, ⼜有了⼀种新的分层架构: 把整体架构分为表现层、业务逻辑层和数据层. 这种分层⽅式也称之为"三层架构".

1. 表现层: 就是展⽰数据结果和接受⽤⼾指令的,是最靠近⽤⼾的⼀层;

2. 业务逻辑层: 负责处理业务逻辑, ⾥⾯有复杂业务的具体实现;

3. 数据层: 负责存储和管理与应⽤程序相关的数据

按照上⾯的层次划分, Spring MVC 站在后端开发⼈员的⻆度上, 也进⾏了⽀持, 把上⾯的代码划分为三个部分:

请求处理、响应数据:负责,接收⻚⾯的请求,给⻚⾯响应数据.

逻辑处理:负责业务逻辑处理的代码.

数据访问:负责业务数据的维护操作,包括增、删、改、查等操作.

这三个部分, 在Spring的实现中, 均有体现:

Controller:控制层。接收前端发送的请求,对请求进⾏处理,并响应数据。
Service:业务逻辑层。处理具体的业务逻辑。
Dao:数据访问层,也称为持久层。负责数据访问操作,包括数据的增、删、改、查

MVC 和三层架构的区别和联系

从概念上来讲, ⼆者都是软件⼯程领域中的架构模式.

MVC架构模式由三部分组成, 分别是: 模型(Model), 视图(View)和控制器(Controller).

三层架构将业务应⽤划分为:表现层, 业务逻辑层, 数据访问层

MVC中, 视图和控制器合起来对应三层架构中的表现层. 模型对应三层架构中的业务逻辑层, 数据层, 以及实体类

⼆者其实是从不同⻆度对软件⼯程进⾏了抽象.

MVC模式强调数据和视图分离, 将数据展⽰和数据处理分开, 通过控制器对两者进⾏组合.

三层架构强调不同维度数据处理的⾼内聚和低耦合, 将交互界⾯, 业务处理和数据库操作的逻辑分开.

⻆度不同也就谈不上互相替代了,在⽇常的开发中可以经常看到两种共存的情况,⽐如我们设计模型层的时候往往也会拆分出业务逻辑层(Service层)和数据访问层(Dao层)。

但是⼆者的⽬的是相同的, 都是"解耦,分层,代码复⽤"

软件设计原则:⾼内聚低耦合.

⾼内聚指的是:⼀个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越⾼,则内聚性越⾼,即 "⾼内聚"。

低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。修改⼀处代码, 其他模块的代码改动越少越好.

⾼内聚低耦合⽭盾吗?

不⽭盾, ⾼内聚指的是⼀个模块中各个元素之间的联系的紧密程度, 低耦合指的是各个模块之间的紧

密程度         

应⽤分层的好处

降低层与层之间的依赖, 结构更加的明确, 利于各层逻辑的复⽤

开发⼈员可以只关注整个结构中的其中某⼀层, 极⼤地降低了维护成本和维护时间

可以很容易的⽤新的实现来替换原有层次的实现

有利于标准化

;