基于Spring MVC的前后端交互案例及应用分层的实现

目录

分析程序报错的步骤

案例

一.加法计算器

二.实现用户登录

1.登录接口

2.获取用户的登录信息

三.留言板

1.接口定义

2.完成后端代码

3.测试后端代码

四.图书管理系统

1.定义接口

2.后端代码

3.测试后端代码

4.前端交互代码

应用分层

1.三层架构


分析程序报错的步骤

1.定位是前端还是后端:通过日志

1)前端:F12、看控制台

2)后端:接口、控制台

2.判断请求是否到达后端

1)后端代码在相应方法的第一行,通过打印一些标记的字符串来判断。运行后如果没有打印内容则请求没有到达后端

2)抓包查看访问的接口是否正确

3.当感觉前端代码没有错,后端却接收不到请求

测试接口:http地址中加上后端参数访问,测试后端代码是否可以实现。

4.查看缓存问题

常见的是当前端有代码覆盖后,后面新的代码运行成功却在页面显示的是覆盖前的页面,这时在Maven面板点击clean清除缓存。

案例

一.加法计算器

前端代码:calc.html

html 复制代码
<!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>

后端实现相加:

java 复制代码
package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/calc")
@RestController
public class calcController {

    @RequestMapping("/sum")
    public String sum(Integer num1,Integer num2){
        Integer sum=num1+num2;
        return "计算结果为:"+sum;
    }
}

二.实现用户登录

1.登录接口

/user/login

userName = ? & password = ?

接口返回:校验成功/失败

true 密码正确

false 密码错误

2.获取用户的登录信息

/user/getUserInfo

接口返回:当前登录用户的名称

前端代码

login.html

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() {
      // 测试日志 检测前端请求是否能发出(onlick)的时候
      console.log("登录...")

      $.ajax({
        url:"/user/login",
        type:"post",
        data:{
          "userName":$("#userName").val(),
          "password":$("#password").val()
        },
        success:function(result){
          if(result){
            location.href ="/index2.html";
          }else{
            alert("密码错误");
          }
        }
      });
    }

  </script>
</body>

</html>

index.html

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({
      url:"/user/getUserInfo",
      type:"post",
      success:function(userName){
        $("#loginUser").text(userName);
      }
    })
</script>
</body>

</html>

后端代码:

java 复制代码
package com.example.demo.controller;

import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping("/login")
    public Boolean login(String userName, String password, HttpSession session){
        //校验参数的合法性
//        if(userName==null ||userName.length()==0 || password==null || password.length()==0){
//            return false;
//        }
        if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){
            return false;
        }
        //进行用户名和密码的校验
        if("admin".equals(userName)&&"admin".equals(password)){
            //设置session
            session.setAttribute("username","admin");
            return true;
        }
        return false;
    }

    @RequestMapping("/getUserInfo")
//    public String getUserInfo(HttpSession session){
//        //从session中获取登录用户
//        String username=(String) session.getAttribute("username");
//        return username;

    public String getUserInfo(HttpServletRequest request){
        //从session中获取用户
        HttpSession session= request.getSession(false);
        String username=null;
        if(session!=null){
            username=(String)session.getAttribute("username");
        }
        return username;
    }
}

三.留言板

前端页面

现在后端需要完成的是:把数据保存下来(内存、数据库....)

1.接口定义

(1)提交留言

/message/publish

参数:MessgaeInfo(from,to,message)

返回结果:true/false

(2)查看所有留言

/message/getMessageList

参数:无

返回结果:List<MessageInfo>

后端代码

定义一个信息类,这时我们可以使用maven自带的工具lombok自动定义set和get方法。

针对所有属性加上set和get方法,@Data放在类的外面

2.完成后端代码

java 复制代码
package com.example.demo;

import org.springframework.util.StringUtils;
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 MessageController {
    private List<MessageInfo> messageInfos=new ArrayList<>();
    //发布留言
    @RequestMapping("/publish")
    public Boolean publishMassage(MessageInfo messageInfo){
       //进行参数校验
        if(!StringUtils.hasLength(messageInfo.getFrom()) ||
                !StringUtils.hasLength(messageInfo.getTo()) ||
                !StringUtils.hasLength(messageInfo.getMessage())){
            return false;
        }
        //添加留言
        messageInfos.add(messageInfo);
       return true;
    }

    //查看留言
    @RequestMapping("/getMessageInfo")
    public List<MessageInfo> getMessageInfo(){
        return messageInfos;
    }
}

3.测试后端代码

在postman上进行测试

publish测试

getMessageInfo测试

由此可见后端代码正确。

前端交互主要代码:

html 复制代码
<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>
        //页面加载时,请求后端,获取留言列表
        $.ajax({
            url: "/message/getMessageInfo",
            type: "get",
            success: function (messages) {
                for (var m of messages) {
                    //2. 构造节点
                    var divE = "<div>" + m.from + "对" + m.to + "说:" + m.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({
                url: "/message/publish",
                type: "post",
                data: {
                    "from": from,
                    "to": to,
                    "message": say
                },
                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>

四.图书管理系统

1.定义接口

(1)登录

url: /user/login

参数:userName=?&password=?

响应:True/False

(2)图书列表展示

url: /book/getBookList

参数:无

响应:List<BookInfo>

2.后端代码

UserController

java 复制代码
package com.lele.book;

import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

@RequestMapping("/user")
@RestController
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")){}这种写法会报空指针异常
        //校验账号密码是否正确
        if("admin".equals(userName) && "admin".equals(password)){
            //账号密码正确
            //存session
            session.setAttribute("userName",userName);
            return true;
        }
        return false;
    }
}

BookController

java 复制代码
package com.lele.book;

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;

@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/getBookList")
    public List<BookInfo> getBookList(){
        //1.获取图书数据
        //2.对图书数据进行修改
        //3.返回数据

        //mock表示虚拟的假数据
        List<BookInfo> bookInfos=mockData();
        for (BookInfo bookInfo:bookInfos){
            if(bookInfo.getStatus()==1){
                bookInfo.setStatusCN("可借阅");
            }else {
                bookInfo.setStatusCN("不可借阅");
            }
        }
        return bookInfos;
    }

    private List<BookInfo> mockData() {
        //对已知的数据量,创建list时建议指定初始化的值
        List<BookInfo> bookInfos=new ArrayList<>(15);
        //随机生成15条图书数据
        for (int i = 0; i < 15; i++) {
            BookInfo bookInfo=new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("图书"+i);
            bookInfo.setAuthor("作者"+i);
            bookInfo.setCount(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;
    }
}

BookInfo

java 复制代码
package com.lele.book;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class BookInfo {
    private Integer id;
    private String BookName;
    private String author;
    private Integer count;
    private BigDecimal price;
    private String publish;
    private Integer status;//1-可借阅 2-不可借阅
    private String statusCN;

}

3.测试后端代码

测试接口是否可以正常使用

4.前端交互代码

前端登录页跳转到图书列表页面主要代码:

html 复制代码
 function login() {
            $.ajax({
                url:"user/login",
                type:"post",
                data:{
                    "userName":$("#userName").val(),
                    "password":$("#password").val()
                },
                success:function(result){
                    if(result){
                        location.href="book_list.html";
                    }else{
                        alert("用户名或密码错误!");
                    }
                }
            })
            location.href = "book_list.html";
        }

前端图书列表页获取到后端自动生成的图书:

html 复制代码
 function getBookList() {
                $.ajax({
                    url:"/book/getBookList",
                    type:"get",
                    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> <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></tr>';
                        }
                        console.log(finalHtml);
                        $("tbody").html(finalHtml);
                    }
                })
            }

应用分层

以上代码已经是非常简单的业务需求,但前后端代码的交互已经略显混乱。

MVC分层方式已经不足以满足现在的业务需求,取而代之的是三层架构

1.三层架构

(1)表现层:接收请求,返回结果

(2)业务逻辑层:业务逻辑处理

(3)数据层:处理数据,包含数据的存储、获取(增删改查)

2.在Spring中的体现

类比上面的图书管理系统:

对图书管理系统进行三层架构:

目录分层:

调整上述代码:

MVC强调数据和视图分离,将数据和数据处理分开,使用控制器对两者进行组合。

三层架构强调不同维度的数据处理:将交互界面、业务处理和数据库的逻辑分开。

两者可以互相转换。两者的目的都是:解耦、分层、代码复用。

相关推荐
云烟成雨TD7 小时前
Spring AI 1.x 系列【51】可观测性技术选型
java·人工智能·spring
unicrom_深圳市由你创科技7 小时前
基于Spring AI框架的RAG应用
人工智能·spring·机器学习
七老板的blog9 小时前
当 Spring StateMachine 遇见大模型:构建工业级 AI 写作流水线
java·人工智能·spring
云烟成雨TD9 小时前
Spring AI 1.x 系列【46】MCP Security 模块
java·人工智能·spring
小旭952710 小时前
Spring AI Alibaba 从入门到实战:一站式掌握企业级 AI 应用开发
java·人工智能·spring
云烟成雨TD11 小时前
Spring AI 1.x 系列【50】可观测性:接入 Prometheus + Grafana
人工智能·spring·prometheus
phltxy13 小时前
MCP 从协议到 Spring AI 实战
人工智能·spring·oracle
Volunteer Technology15 小时前
SpringSecurity请求流转的本质
java·spring
云烟成雨TD16 小时前
Spring AI 1.x 系列【42】MCP 服务端 Spring Boot 启动器
java·人工智能·spring
云烟成雨TD16 小时前
Spring AI 1.x 系列【38】模型上下文协议(MCP)
java·人工智能·spring