【JavaEE进阶】Spring Web MVC 综合性练习

结合上节内容, 我们可以做⼀些⼩案例

主要掌握知识点:

  • 理解前后端交互过程
  • 接⼝传参, 数据返回, 以及⻚⾯展⽰

一.加法计算器

需求: 输⼊两个整数, 点击"点击相加"按钮, 显⽰计算结果

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响应状态码.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值