Spring MVC练习(前后端分离开发实例)

White graces:个人主页

🙉专栏推荐:Java入门知识🙉

🐹今日诗词:二十五弦弹夜月,不胜清怨却飞来🐹


⛳️点赞 ☀️收藏⭐️关注💬卑微小博主🙏

⛳️点赞 ☀️收藏⭐️关注💬卑微小博主🙏


目录

加法计算器

前端页面

约定前后端交互接口

用户登录

约定前后端交互接口

查询用户登录接口

前端页面

同步操作和异步操作

留言板

约定前后端接口

public接口

getList接口

图书管理系统

 应用分层(重点)

美图分享


加法计算器

前端页面

这里的计算器是前后端交互的方式, 单纯的前端也可以实现, 但是就达不到练习的目的了

准备工作: 我们先搭建一个简单的页面, 代码和效果如下: 

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

约定前后端交互接口

  • 简单来说,就是允许客户端给服务器发送哪些HTTP请求,并且每种请求预期获取什么样的HTTP响应, 不能请求A, 你给我返回B, 请求响应必须正确的
  • 现在"前后端分离"模式开发,前端和后端代码通常由不同的团队负责开发.双⽅团队在开发之前,会提前 约定好交互的方式
  • 把约定的内容写在⽂档上,就是"接⼝⽂档",接 ⼝⽂档也可以理解为是应⽤程序的"操作说明书

因此后端代码这样写

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

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

效果演示

用户登录

约定前后端交互接口

login方法(登录)

首先就是参数效验, 用户名和密码肯定不能为null或者空

参数效验: 1. 可以用if进行多次效验判断(麻烦一点)

2. 使用StringUtils类的hasLength方法进行效验(推荐)

这是登录效验代码

    @RequestMapping("/login")
    public Boolean login(String username, String password, HttpSession session) {
        //参数效验(null和空)
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return false;
        }
        //效验账号密码是否正确
        //这里使用硬编码方式演示, 数据库操作效验密码不方便
        //假设账号密码是: "zhangsan" "123456"
        if ("zhangsan".equals(username) && "123456".equals(password)) {
            //设置Session, 需要创建Session
            session.setAttribute("username", username);
            return true;
        }
        return false;
    }

测试一下接口有没问题

查询用户登录接口

​
@RequestMapping("/getUserInfo")
    public String getUserInfo(HttpSession session) {
        String username = (String) session.getAttribute("username");
        return username==null?"":username;
    }

​
这样后端接口就写好了, 接下来搞定前端

前端页面

需要用到Ajax

Ajax,全称是Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)

作用: 

  • 异步:用户在等待服务器响应时可以继续操作界面,提高了用户体验。
  • 无需刷新:局部更新页面,不需要重新加载整个页面。
  • 数据交换格式:虽然名字中包含 XML,但 Ajax 可以使用多种数据格式,包括 JSON、HTML 等,不仅限于 XML

同步操作和异步操作

举例解释二者区别

比如去买饭, 我要卖蛋炒饭和奶茶, 正常情况下是买完蛋炒饭, 等老板做完然后去买奶茶

这种情况就是同步操作

异步操作就是: 我先预购蛋炒饭, 然后我去买奶茶, 等蛋炒饭做好了老板通知去拿就可以了

登录界面

通过Ajax, 登录成功进行页面跳转(重定向), 登录失败就提示错误

<!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.0.min.js"></script>
  <script>
    function login() {
      
      $.ajax({
        url: "/user/login",  //指定请求的URL地址
        type: "post",    //指定请求的类型
        data: {  //指定要发送的数据, 这里的数据是从表单中获取的用户名和密码
          "userName": $("#userName").val(), // 获取用户名输入框的值
          "password": $("#password").val() // 获取密码输入框的值
        },

        //指定请求成功时的回调函数
        success: function (result) {
          // 如果服务器返回的结果为 true,进行页面跳转
          if (result) {
            location.href = "index.html"; //重定向到index.html页面
          } else {
            alert("密码错误"); //如果结果为 false,弹出密码错误的提示
          }
        }
      });
      
    }

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

</html>

留言板

约定前后端接口

设计思路: 

点击提交, 要把数据拼接到留言板下方,

同时需要把数据提交到后端, 交给后端存储, 我们可以通过一个链表存储

并且第一使用留言板, 还要把以前的留言显示在下面(从后端拿到数据并显示在页面上)

因此我们要设计两个接口

  1. message/public接口: 收集前端提交的数据
  2. message/getList接口: 返回数据到前端

因此我们可以这样约定前后端接口

public接口

    @RequestMapping(value = "/public", method = RequestMethod.POST)
    public Boolean publish(@RequestBody MessageInfo messageInfo) {
        list.add(messageInfo);
        System.out.println("日志: 后端存储数据成功");
        return true;
    }

getList接口

我们来测试一下接口是否正确

这样后端的代码就没问题了, 接下来我们来写, 前端页面如何通过接口来获取后端数据的代码

前端获取后端数据的代码

分成两步: 

  1.  首先将以前的留言记录显示到下面, 不能每次打开都要重新留言

    由于html代码从上向下执行, 所以这串逻辑代码必须写在script的开头

  2. 创建新的留言, 将新的留言数据交给后端保存, 同时将留言显示到下方 

显示留言记录

保存并显示新的留言

我们来验证一下效果吧

所有代码

留言板所有代码

<!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.0.min.js"></script>
    <script>
        //
        $.ajax(
            {
                url: "message/getList",
                type: "get",
                success: function(messages) {
                    if(messages != null) {
                        for(var message of messages) {
                            var tmp = "<div>" + message.from +"对" + message.to + "说:" + message.say +"</div>";
                            $(".container").append(tmp);
                        }
                    }
                }
            }
        );
        
        //获取留言数据
        function submit(){
           let from = $("#from").val();
           let to = $("#to").val();
           let say = $("#say").val();

           //不为空
           if(from == "" || to == "" || say == ""){
                return;
           }
           
           //保存并显示新的留言
           $.ajax({
            url: "message/public",
            type: "post",
            contentType: "application/json",
            data: JSON.stringify ( {
                "from": from,
                "to": to,
                "say": say
            } ),
            success: function(result) {
                if(result) {
                    //保存成功
                    //将留言信息显示到页面上
                    let message = "<div>" + from +"对" + to + "说:" + say +"</div>";
                    $(".container").append(message);
                    //清空输入信息
                    $("#from").val("");
                    $("#to").val("");
                    $("#say").val("");
                } else {
                    //保存失败
                    alert("留言失败");
                }
            }
           });
        }
        
    </script>
</body>

</html>

后端代码

Message对象代码

import lombok.Data;

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

后端接口代码

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/message")
public class MessageController {
    private List<MessageInfo> list = new ArrayList<MessageInfo>();

    @RequestMapping(value = "/public", method = RequestMethod.POST)
    public Boolean publish(@RequestBody MessageInfo messageInfo) {
        list.add(messageInfo);
        System.out.println("日志: 后端存储数据成功");
        return true;
    }

    @RequestMapping(value = "/getList", method = RequestMethod.GET)
    public List<MessageInfo> getList() {
        System.out.println("日志: 前端获取数据成功");
        return list;
    }
}

图书管理系统

界面


约定前后端接口

我们先来写登录接口: 判断账号密码是否正确, 首先要有账号密码的属性

import lombok.Data;
@Data
public class UserInfo {
    private String username;
    private String password;
}

hasLength()方法: 参数为null或者长度为空, 返回false

equal()方法: 判断二个参数是否相等

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String login(String username, String password) {
        //判断账号或密码是否为空或者长度为0, 为空或0,方法返回false
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return "账号或密码不能为空";
        }
        //硬编码方式
        if (!"zhangsan".equals(username) || !"123456".equals(password)) {
            return "账号或密码错误";
        }
        return "";
    }
}

 图书列表: getList接口

我们通过链表存储图书, 先创建图书的属性

状态码, 因为图书状态只有可借阅和不可借阅两种情况, 所以可以用数字代替中文, 企业开发用状态码, 显示成借阅和不可借阅是前端的事情

@Data
public class BookInfo {
    private Integer bookId;  // 图书id
    private String bookName; // 书名
    private String author;   // 作者
    private Integer number;  // 数量
    private BigDecimal price;// 定价
    private  String publishName;// 出版社
    private Integer status;  // 状态码, 因为图书状态只有可借阅和不可借阅两种情况, 企业开发用状态码, 变成中文是前端的事情
    private String statusCN; // (可有可无)状态码对应的中文, 现在我们写项目前后端都是自己, 需要显示一下
}

getList接口, 以及接口测试

package com.white.demo;


import org.springframework.util.StringUtils;
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 {

    private List<BookInfo> list = new ArrayList<BookInfo>();
    @RequestMapping("/getList")
    public List<BookInfo> getList() {
        list = mockData(); //mock测试(模拟测试)
        return list;
    }

    private List<BookInfo> mockData() {  //mock测试(从数据库调用数据进行测试),
        for (int i = 0; i < 15; i++) {   //由于咱们没数据库, 所以使用mockData方法模拟数据库
            BookInfo bookInfo = new BookInfo();
            bookInfo.setBookId(i);
            bookInfo.setBookName("书名: " + i);
            bookInfo.setAuthor("作者: " + i);
            bookInfo.setNumber(3*i+1);
            bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
            bookInfo.setPublishName("出版社: " + i);
            bookInfo.setStatus(i%2==0?1:2);
            bookInfo.setStatusCN(i%2==0?"可借阅":"不可借阅");
            list.add(bookInfo);
        }
        return list;
    }


}

使用postman测试

前端页面

我们要实现从后端拿到数据放到前端, 通过ajax拿到数据后, 如何展示页面呢?

使用HTML的话, 只能一个一个的拼接

拼接方法: 

1. 先只写一对 单引号'' 

例如: 

var tmp = '<tr>123<tr>';

2. 然后把123替换成一对 单引号, 变成这样

tmp = '<tr>''<tr>';     (中间两个单引号)

3.在中间两个单引号之间加上两个+号, 变成这样

tmp = '<tr>'++'<tr>';

4. 再两个 + 号中间填上属性变量即可

tmp = '<tr>'+要替换的变量+'<tr>';

比如这种形式: tmp = '<tr>'+bookId+'<tr>';

拼接结果就是前面两个单引号是一对

根据上述方法, 可以得到以下界面

运行结果

 应用分层(重点)

应用分层是一种设计架构, 将代码分为多个独立的层次, 可以提高代码的可阅读性, 可维护性、可扩展性和灵活性, 并且代码出现问题非常容易找到出错的部分

应用三层一般分为三层: 表现层(Controller), 业务逻辑层(Service), 数据层(Dao), 有时候还会加入实体类层(model, pojo, DTO, VO.....)

表现层(Controller)

作用: 比如用户登录例子, 表现层获取用户的输入(如用户名和密码),将这些数据传递给业务逻辑层

业务逻辑层(Service)

作用: 对用户登录的账户密码进行效验的代码属于业务逻辑层, 效验成功, 调用数据层的数据, 将页面展示出来

数据层(Dao)

作用: 接收业务逻辑层, 存储数据, 或者返回数据给业务逻辑层, 数据层一般不会调用表现层, 业务逻辑层, 

三者调用关系

表现层:创建一个业务逻辑层对象,逻辑层去执行获取的操作
业务逻辑层:创建一个数据层对象,通过数据层对象调用数据库中的数据
数据层:存储数据,提供一个接口方法,让业务逻辑层调用

那我们现在来对代码进行分层吧

首先创建4个包, 用于待会存放分层的代码

我们来对这串代码进行应用分层

表现层:创建一个业务逻辑层对象,逻辑层去执行获取的操作

业务逻辑层:创建一个数据层对象,通过数据层对象调用数据库中的数据

数据层:存储数据,提供一个接口方法,让业务逻辑层调用

美图分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/924189.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

重构项目架构

前言 我们上篇文章对整个项目进行一个整体的规划&#xff0c;其中对于APP类规划了类&#xff0c;本篇文章我们就来实现这个规划&#xff1b; class App {//加载页面constructor() {}//获取位置_getPosition() {}//接受位置_loadMap() {}//在地图上点击展现表单_showForm() {}/…

哈希C++

文章目录 一.哈希的概念1.直接定址法2.负载因子 二.哈希函数1.除法散列法 / 除留余数法2.乘法散列法3.全域散列法&#xff08;了解&#xff09; 三.处理哈希冲突哈希冲突&#xff1a;1.开放定址法&#xff08;1&#xff09;线性探测&#xff1a;&#xff08;2&#xff09;二次探…

转录组数据挖掘(生物技能树)(第11节)下游分析

转录组数据挖掘&#xff08;生物技能树&#xff09;&#xff08;第11节&#xff09; 文章目录 R语言复习转录组数据差异分析差异分析的输入数据操作过程示例一&#xff1a;示例二&#xff1a;示例三&#xff1a;此代码只适用于人的样本 R语言复习 #### 读取 ####dat read.deli…

Diving into the STM32 HAL-----Cyclic Redundancy Check笔记

在数字系统中&#xff0c;数据完全有可能被损坏&#xff0c;特别是当它流经通信介质时。在数字电子学中&#xff0c;消息是等于 0 或 1 的比特流&#xff0c;当这些比特中的一个或多个在传输过程中意外更改时&#xff0c;它就会损坏。因此&#xff0c;消息中始终有一些额外的数…

Swift——类与结构体

一.结构体 在swift的标准库中&#xff0c;大部分的类型都是结构体&#xff0c;比如&#xff1a;Int&#xff0c;Double&#xff0c;String&#xff0c;Array&#xff0c;Dictionary等等&#xff0c;它们都是结构体。 结构体定义如下&#xff1a; struct Person {var name:St…

反射泛型

反射 class 包含哪些内容&#xff1f; 当使用new 对象时需要构造函数是public 的&#xff0c;而当变成私有时再new则会报错 反射通过私有构造方法创建对象&#xff0c;破环单例模式 Clazz.getDeclared(构造函数&#xff0c;方法属性等)和直接get构造函数&#xff0c;方法属性等…

RHCE——SELinux

SELinux 什么是SELinux呢&#xff1f;其实它是【Security-Enhanced Linux】的英文缩写&#xff0c;字母上的意思就是安全强化Linux的意思。 SELinux是由美国国家安全局(NSA)开发的&#xff0c;当初开发的原因是很多企业发现&#xff0c;系统出现问题的原因大部分都在于【内部…

etcd、kube-apiserver、kube-controller-manager和kube-scheduler有什么区别

在我们部署K8S集群的时候 初始化master节点之后&#xff08;在master上面执行这条初始化命令&#xff09; kubeadm init --apiserver-advertise-address10.0.1.176 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.16.0 --service…

uniapp定义new plus.nativeObj.View实现APP端全局弹窗

为什么要用new plus.nativeObj.View在APP端实现弹窗&#xff1f;因为uni.showModal在APP端太难看了。 AppPopupView弹窗函数参数定义 参数一:弹窗信息(所有属性可不填&#xff0c;会有默认值) 1.title:"", //标题 2.content:"", //内容 3.confirmBoxCo…

一文学习开源框架OkHttp

OkHttp 是一个开源项目。它由 Square 开发并维护&#xff0c;是一个现代化、功能强大的网络请求库&#xff0c;主要用于与 RESTful API 交互或执行网络通信操作。它是 Android 和 Java 开发中非常流行的 HTTP 客户端&#xff0c;具有高效、可靠、可扩展的特点。 核心特点 高效…

DRM(数字权限管理技术)防截屏录屏----视频转hls流加密、web解密播放

提示&#xff1a;视频转hls流加密、web解密播放 需求&#xff1a;研究视频截屏时&#xff0c;播放器变黑&#xff0c;所以先研究的视频转hls流加密 文章目录 [TOC](文章目录) 前言一、工具ffmpeg、openssl二、后端nodeexpress三、web播放四、文档总结 前言 ‌HLS流媒体协议‌&a…

Rk3588 onnx转rknn,出现 No module named ‘rknn‘

一、操作步骤&#xff1a; rk3588 需要将yolo11 的模型onnx转rknn。 https://github.com/airockchip/rknn_model_zoo/tree/main/examples/yolo11 这个是用yolo11训练的模型&#xff0c;有80种类型。 完整下载下来后&#xff0c;在按文档描述下载模型下来&#xff1a; 然后进…

IDEA 解决Python项目import导入报错、引用不到的问题

使用Idea 23.1 专业版编写Python项目时&#xff0c;import 导入爆红&#xff0c;无法引入其他package的代码&#xff0c;现象如&#xff1a; 解决方案&#xff1a;Idea表头打开 File -> Project Settring 解决效果&#xff1a;

unity 使用UI上的数字按钮,给text添加数字,并且显示光标,删除光标前数字,

今天有个需求&#xff0c;输入身份证&#xff0c;但是不用键盘&#xff0c;要点击按钮输入数字&#xff0c;并且可以控制光标&#xff0c; 1、数字按钮&#xff1a;点击后text添加数字内容 2、删除按钮&#xff1a;删除光标前的一个字符 3、左箭头&#xff1a;移动光标向左移动…

火山引擎VeDI在AI+BI领域的演进与实践

随着数字化时代的到来&#xff0c;企业对于数据分析与智能决策的需求日益增强。作为新一代企业级数据智能平台&#xff0c;火山引擎数智平台VeDI基于字节跳动多年的“数据驱动”实践经验&#xff0c;也正逐步在AI&#xff08;人工智能&#xff09;与BI&#xff08;商业智能&…

【逐行注释】自适应观测协方差R的AUKF(自适应无迹卡尔曼滤波,MATLAB语言编写),附下载链接

文章目录 自适应R的UKF逐行注释的说明运行结果部分代码各模块解释 自适应R的UKF 自适应无迹卡尔曼滤波&#xff08;Adaptive Unscented Kalman Filter&#xff0c;AUKF&#xff09;是一种用于状态估计的滤波算法。它是基于无迹卡尔曼滤波&#xff08;Unscented Kalman Filter&…

LLM应用-prompt提示:RAG query重写、相似query生成 加强检索准确率

参考&#xff1a; https://zhuanlan.zhihu.com/p/719510286 1、query重写 你是一名AI助手&#xff0c;负责在RAG&#xff08;知识库&#xff09;系统中通过重构用户查询来提高检索效果。根据原始查询&#xff0c;将其重写得更具体、详细&#xff0c;以便更有可能检索到相关信…

Spring Boot 与 Spring Cloud Alibaba 版本兼容对照

版本选择要点 Spring Boot 3.x 与 Spring Cloud Alibaba 2022.0.x Spring Boot 3.x 基于 Jakarta EE&#xff0c;javax.* 更换为 jakarta.*。 需要使用 Spring Cloud 2022.0.x 和 Spring Cloud Alibaba 2022.0.x。 Alibaba 2022.0.x 对 Spring Boot 3.x 的支持在其发行说明中…

如何通过PHP爬虫模拟表单提交,抓取隐藏数据

引言 在网络爬虫技术中&#xff0c;模拟表单提交是一项常见的任务&#xff0c;特别是对于需要动态请求才能获取的隐藏数据。在电商双十一、双十二等促销活动期间&#xff0c;商品信息的实时获取尤为重要&#xff0c;特别是针对不断变化的价格和库存动态。为了满足这种需求&…

嵌入式Qt使用ffmpeg视频开发记录

在此记录一下Qt下视频应用开发的自学历程&#xff0c;可供初学者参考和避雷。 了解常用音频格式yuv420p、h264等了解QML&#xff0c;了解QVideoOutput类的使用&#xff0c;实现播放yuv420p流参考ffmpeg官方例程&#xff0c;调用解码器实现h264解码播放 不需要手动分帧。ffmpeg…