大连华南网站建设/竞价推广网络推广运营
目录
🚩项目所需要的技术栈
🚩项目准备工作
🎈环境准备
🎈数据库准备
🚩前后端交互分析
🎈登录
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈添加图书
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈图书列表
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈修改图书
📝前后端交互
📝实现服务器代码
📝测试前后端代码是否正确
🎈删除图书
📝约定前后端交互接⼝
📝实现服务器代码
📝测试前后端代码是否正确
🎈批量删除图书
📝约定前后端交互接⼝
📝实现服务器代码
📝测试前后端代码是否正确
🚩项目所需要的技术栈
该项目是一个针对于SpringBoot+Mybatis+SpringMVC的基础运用项目适合初学者来检验水平测试能力,该项目所需技术栈如下:
>* SpringBoot:作为项目的框架,使用Maven托管代码
>* Mybatis:使用Mybatis框架操纵数据库,其中使用了xml和注解两种方式去操作数据库
>* 前端ajax:前后端的交互使用的是ajax作为前端为后端发送数据以及接收数据
>* 项目分层:项目分为前端页面+control(与前端建立连接的控制层)+Service(服务层供control层进行调用)+Mapper(操纵数据库实现数据与后端代码的 交互)+model(需要实现的主类)。
🚩项目准备工作
🎈环境准备
项目的创建需要选好项目名,项目路径,语言为java,type是基于maven构建,jdk可以选择17以上的(切记最好不要用jdk8),packing是打成jar包。
此时项目创建成功。MySQL Driver和MyBatis Framework引⼊MyBatis 和 MySQL驱动依赖
也可以手动引入依赖,上面只是更简单。
这是围绕整个项目的配置文件,没有该配置文件,是无法运行成功的,没有它们你就完成不了一个项目。我们依赖该pom.xml文件,让我们能完成该项目。
SpringBookt配置文件,统一使用yml格式 application.yml
很多项⽬或者框架的配置信息也放在配置⽂件中, ⽐如:
- • 项⽬的启动端⼝
- • 数据库的连接信息(包含⽤⼾名和密码的设置)
- • ⽤于发现和定位问题的普通⽇志和异常⽇志等
server:port: 8080
#配置数据库
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/book_system?characterEncoding=utf8&useSSL=falseusername: rootpassword: driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:#配置驼峰自动转换map-underscore-to-camel-case: true#sql日志(打印出来让我们可以清楚自己的sql语句是否正确)log-impl: org.apache.ibatis.logging.stdout.StdOutImpl#Spring Boot可以正确找到并加载位于 classpath:mapper/目录下的 xxxxMapper XML 文件,#从而在应用程序中使用这些 Mapper 进行数据库操作。(因为有时候sql语句需要动态)mapper-locations: classpath:mapper/BookMapper.xml
#将日志记录到一个文件可以通过在配置文件中指定日志文件的位置和名称来实现。
logging:file:name: spring-book.log
🎈数据库准备
- 数据库表的设计是应用程序开发的一个重要的环节。设计数据库表是根据业务需求相关的。
- 数据库表通常分成两种:实体表和关系表,就如图书管理系统来说,图书馆里系统相对是简单的,只有两个实体:用户和图书,并且用户和图书之间没有关联关系。
- 表的具体字段设计,也与需求相关。
⽤⼾表:有⽤⼾名和密码即可(复杂的业务可能还涉及昵称, 年龄等资料)
图书表:有哪些字段, 也是参考需求⻚⾯(通常不是⼀个⻚⾯决定的, ⽽是要对整个 系统进⾏全⾯分析观察后定的)
创建数据库book_system 用户表user_info 图书表 book_info
DROP DATABASE IF EXISTS book_system;
CREATE DATABASE book_system DEFAULT CHARACTER SET utf8mb4;
use book_system;-- ⽤⼾表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (`id` INT NOT NULL AUTO_INCREMENT,`user_name` VARCHAR ( 128 ) NOT NULL,`password` VARCHAR ( 128 ) NOT NULL,`delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` ),
UNIQUE INDEX `user_name_UNIQUE` ( `user_name` ASC )) ENGINE = INNODB DEFAULT
CHARACTER
SET = utf8mb4 COMMENT = '⽤⼾表';-- 图书表
DROP TABLE IF EXISTS book_info;
CREATE TABLE `book_info` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`book_name` VARCHAR ( 127 ) NOT NULL,`author` VARCHAR ( 127 ) NOT NULL,`count` INT ( 11 ) NOT NULL,`price` DECIMAL (7,2 ) NOT NULL,`publish` VARCHAR ( 256 ) NOT NULL,`status` TINYINT ( 4 ) DEFAULT 1 COMMENT '0-⽆效, 1-正常, 2-不允许借阅',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;--初始化数据
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "admin", "admin" );
INSERT INTO user_info ( user_name, PASSWORD ) VALUES ( "zhangsan", "123456" );INSERT INTO book_info (book_name, author, count, price, publish, status)
VALUES
('深入理解计算机系统', 'Randal E. Bryant', 18, 98.00, '机械工业出版社', 1),
('现代操作系统', 'Andrew S. Tanenbaum', 10, 89.00, '清华大学出版社', 1),
('计算机网络:自顶向下方法', 'James Kurose', 14, 85.00, '电子工业出版社', 1),
('数据库系统概念', 'Abraham Silberschatz', 7, 110.00, '机械工业出版社', 1),
('算法导论', 'Thomas H. Cormen', 5, 130.00, '机械工业出版社', 1),
('机器学习', '周志华', 20, 120.00, '清华大学出版社', 1),
('Python编程:从入门到实践', 'Eric Matthes', 25, 65.00, '电子工业出版社', 1),
('深度学习', 'Ian Goodfellow', 12, 180.00, '人民邮电出版社', 1),
('计算机图形学', 'John F. Hughes', 9, 95.00, '机械工业出版社', 1),
('操作系统真相还原', '史蒂文·穆查', 15, 80.00, '电子工业出版社', 1);
package com.example.cl.model;import lombok.Data;import java.math.BigDecimal;
import java.util.Date;@Data
public class BookInfo {//图书IDprivate Integer id;//书名private String bookName;//作者private String author;//数量private Integer count;//定价private BigDecimal price;//出版社private String publish;//状态 0-⽆效 1-允许借阅 2-不允许借阅private Integer status;private String statusCN;//创建时间private Date createTime;//更新时间private Date updateTime;
}package com.example.cl.model;import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {private Integer id;private String userName;private String password;private Integer deleteFlag;private Date createTime;private Date updateTime;
}
🚩前后端交互分析
🎈登录
<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>
前端登录有两个表单,输入用户名和密码,表单中name属性是用于表单提交时用于标识表单数据的属性,服务器端通过‘name’属性来获取提交的数据。此时前端的id属性来获取客户端输入的用户名和密码给后端。
📝前后端交互
function login() {// var userName= $("#userName").val();// var password = $("#password").val()$.ajax({url: "/user/login",type: "post",data:{userName: $("#userName").val(),password: $("#password").val()}}
前端发出请求:
url:/user/login
type:post
参数:
data:userName=$("#userName").val()【admin】
password=$("#password").val()【admin】
响应:
true (密码或者用户名正确,返回true)
function login() {// var userName= $("#userName").val();// var password = $("#password").val()$.ajax({url: "/user/login",type: "post",data:{userName: $("#userName").val(),password: $("#password").val()},success:function(result){if(result.code=="SUCCESS" && result.data ==""){//密码正确location.href = "book_list.html?pageNum=1";}else{alert(result.errMsg);}}});}
因为登录页面基本上错误出现在用户名和密码上,如果用户名和密码出现问题,其实并没有必要返回error信息,如果服务器返回的结果码是“SUCCESS”和返回的结果数据是“”,那么就表明密码正确,否则就弹窗 结果的errMsg错误信息。
📝实现服务器代码
我们要知道三层架构,mapper——service——controller
control(与前端建立连接的控制层)
Service(服务层供control层进行调用)
Mapper(操纵数据库实现数据与后端代码的 交互)
model(需要实现的主类)。
我们该如何实现服务器代码呢?首先,我们需要获取到delet_flag=0对应的userName因为,如果已经删除了,那么就也得返回null,如果没有找到服务器收到的userName那么返回null,如果找到了并且并没有删除该用户,那么就成功。
🎓数据层
package com.example.cl.mapper;import com.example.cl.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface UserInfoMapper {@Select("select * from user_info where delete_flag=0 and user_name=#{name}")UserInfo queryUserByName(String name);
}
🎓业务层
package com.example.cl.service;import com.example.cl.mapper.UserInfoMapper;
import com.example.cl.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;public UserInfo queryUserByName(String userName){return userInfoMapper.queryUserByName(userName);}
}
🎓控制层


package com.example.cl.model;import com.example.cl.enums.ResultStatus;
import lombok.Data;@Data
public class Result<T> {private ResultStatus code; //业务码 不是Http状态码 200-成功 -2 失败 -1 未登录private String errMsg; //错误信息, 如果业务成功, errMsg为空private T data;public static <T> Result success(T data){Result result=new Result<>();result.setCode(ResultStatus.SUCCESS);result.setData(data);return result;}public static Result fail(String msg){Result result=new Result<>();result.setCode(ResultStatus.Fail);result.setErrMsg(msg);return result;}
}
package com.example.cl;public enum StatusResult {SUCCESS(200),//成功返回200FAIL(-1),//错误返回-1NOLOGIN(-2)//未登录返回-2;private int code;StatusResult(int code) {this.code=code;}public int getCode() {return code;}public void setCode(int code) {this.code = code;}
}
package com.example.cl.controller;import com.example.cl.constant.constants;
import com.example.cl.model.Result;
import com.example.cl.model.UserInfo;
import com.example.cl.service.UserInfoService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@RequestMapping(value = "/login",produces = "application/json")public Result login(String userName, String password, HttpSession session){//1.效验参数if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){//如果长度其中一个为空,return Result.fail("用户名或者密码为空");}//账号和密码是否正确//获取查找当前userName的用户所有信息UserInfo userInfo=userInfoService.queryUserByName(userName);if(userInfo==null){return Result.fail("用户不存在");}if(!password.equals(userInfo.getPassword())){return Result.fail("密码错误");}//3.正确情况session.setAttribute(constants.USER_SESSION_KEY,userInfo);return Result.success("");//成功返回空串}
}
📝测试前后端代码是否正确
后端通过postman进行检查,最后返回的结果正确,说明后端代码是没有问题的, 如果后端代码有问题会返回505错误码。
我们可以看到如果成功,errMsg为空,data返回给前端也是空,code是success


🎈添加图书
📝前后端交互
前端发出请求:
url:/book/addBook
type:post
参数:
date:传给服务器的数据 ,用到的$("#addBook").serialize() 表示form表单中所有的数据
响应:
"" //失败信息,成功返回空串
function add() {$.ajax({url: "/book/addBook",type: "post",data: $("#addBook").serialize(),success: function (result) {if (result.code == "SUCCESS" && result.data == "") {//添加成功location.href = "book_list.html";} else {alert(result.data);}}, error: function (error) {//用户未登录if (error.code=="NOLOGIN"&&error.data==null) {location.href = "login.html";}}});}
如果没有返回错误,那么就判断result的data是否为空,如果为空,我们就跳转到博客列表页,如果不等于“”,我们就弹框
如果返回错误,说明是用户未登录,那么就要判断error中的数据是否是未登录页码
📝实现服务器代码
package com.example.cl.mapper;import com.example.cl.model.BookInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface BookInfoMapper {/*** 插入图书*/@Insert("insert into book_info (book_name, author, `count`, price, publish, `status`) "+ "values (#{bookName}, #{author}, #{count}, #{price}, #{publish}, #{status})")Integer insertBook(BookInfo bookInfo);
}
🎓业务层
package com.example.cl.service;import com.example.cl.mapper.BookInfoMapper;
import com.example.cl.mapper.UserInfoMapper;
import com.example.cl.model.BookInfo;
import com.example.cl.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class BookInfoService {@Autowiredprivate BookInfoMapper bookInfoMapper;public Integer insertBook(BookInfo bookInfo){return bookInfoMapper.insertBook(bookInfo);}
}
🎓控制层

添加@Slf4j可以让我们再运行的时候,控制台会进行报出日志消息。
public static<T> Result nologin(String errMsg){Result result=new Result();result.setErrMsg(errMsg);result.setCode(StatusResult.NOLOGIN);return result;}
/*** 增加图书*/@RequestMapping(value = "/addBook",produces = "application/json")public Result<String> addBook(BookInfo bookInfo, HttpSession session){Result<String> ans=new Result<>();//1.判断用户是否登录//如果用户信息为空, 说明用户未登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){return Result.nologin("用户未登录");}//1.效验参数log.info("添加图书,接收到的参数bookInfo:{}",bookInfo);if (!StringUtils.hasLength(bookInfo.getBookName())|| !StringUtils.hasLength(bookInfo.getAuthor())|| bookInfo.getCount()==null|| bookInfo.getPrice()==null|| !StringUtils.hasLength(bookInfo.getPublish())|| bookInfo.getStatus()==null){ans.setData("输入错误");ans.setCode(StatusResult.FAIL);return ans;}//添加图书try {Integer result=bookInfoService.insertBook(bookInfo);if(result>0){ans.setData("");ans.setCode(StatusResult.SUCCESS);return ans;}}catch (Exception e){log.error("添加图书失败");}ans.setCode(StatusResult.FAIL);ans.setData("添加失败");return ans;}
📝测试前后端代码是否正确


我们再来看用户未登录状态:返回的错误信息“errMsg”,code为LOGIN
检查前端代码
此时查看数据库中的数据,此时增加了一条。
🎈图书列表
需求分析我们之前做的表⽩墙查询功能,是将数据库中所有的数据查询出来并展⽰到⻚⾯上,试想如果数据库中的数据有很多(假设有⼗⼏万条)的时候,将数据全部展⽰出来肯定不现实,那如何解决这个问题呢?
分⻚时, 数据是如何展⽰的呢第1⻚: 显⽰1-10 条的数据第2⻚: 显⽰11-20 条的数据第3⻚: 显⽰21-30 条的数据以此类推...要想实现这个功能, 从数据库中进⾏分⻚查询,我们要使⽤ LIMIT 关键字,格式为:limit 开始索引每⻚显⽰的条数(开始索引从0开始)
我们先增加一些数据,让每一页都更加的明显显示出来。
第一页的开始索引是0 (1-0)*10
第二页的开始索引是10,(2-1)*10
前端发出请求,需要向服务器传递参数
- currentPage 当前⻚码 //默认值为1
- ◦ pageSize 每⻚显⽰条数 //默认值为10
为了项⽬更好的扩展性, 通常不设置固定值, ⽽是以参数的形式来进⾏传递扩展性: 软件系统具备⾯对未来需求变化⽽进⾏扩展的能⼒ ,⽐如当前需求⼀⻚显⽰10条, 后期需求改为⼀⻚显⽰20条, 后端代码不需要任何修改
后端响应时, 需要响应给前端的数据
- records 所查询到的数据列表(存储到List 集合中)
- ◦ total 总记录数 (⽤于告诉前端显⽰多少⻚, 显⽰⻚数为: (total + pageSize -1)/pageSize
package com.example.cl.model;import lombok.Data;@Data
public class PageRequest {private Integer pageNum =1;//当前页private Integer pageSize = 10;//每页中的记录数private Integer offset;//起始索引public Integer getOffset() {return (pageNum-1) * pageSize;}
}
通过当前页和每页的记录数,记录当前的起始索引。
package com.example.cl.model;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.List;@AllArgsConstructor //生成构造方法
@NoArgsConstructor //生成无构造方法
@Data
public class PageResult<T>{private List<T> records;//当前页的数据private Integer count;//所有的记录数private PageRequest pageRequest;//从哪个下标开始,显示多少本书
}
📝前后端交互
前端发出请求
url:
/book/getListByPage?currentPage=1&pageSize=10Content-Type: application/x-www-form-urlencoded; charset=UTF-8type: get
参数:
响应:{"errMsg":"","data":"","code":""}
📝实现服务器代码
🎓数据层
/*** 获取当前页数据*/@Select("select * from book_info where status !=0 order by id asc limit #{offset}, #{pageSize}")//升序List<BookInfo> queryBookListByPage(PageRequest pageRequest);//查询当前页所有未借阅的书
//offset索引起始 pageSize一页展示多少数据@Select("select count(1) from book_info where status<>0")Integer count();//统计未借阅的书的本数
🎓业务层
package com.example.cl.enums;public enum BookStatus {DELETE(0,"删除"),NORMAL(1,"可借阅"),FORBIDDEN(2,"不可借阅");BookStatus(Integer code, String desc) {this.code = code;this.desc = desc;}private Integer code;//数字private String desc;//内容//根据code,返回描述信息public static BookStatus getDescByCode(Integer code){switch (code){case 0:return DELETE;case 1:return NORMAL;case 2:default:return FORBIDDEN;}}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}
业务层
public PageResult<BookInfo> queryBookListByPage(PageRequest pageRequest){//获取所有未借阅的书的总数Integer count=bookInfoMapper.count();//获取当前页的记录List<BookInfo>bookInfos=bookInfoMapper.queryBookListByPage(pageRequest.getOffset(), pageRequest.getPageSize());//3.处理状态(0表示删除,1表示可借阅,2表示未借阅,之前用数字代替,现在需要转成string形式for (BookInfo bookInfo:bookInfos) {bookInfo.setStatus(BookStatus.getDescByCode(bookInfo.getStatus()).getCode());}return new PageResult(bookInfos,count,pageRequest);}
🎓控制层
/*** 获取当前页数据*/@RequestMapping("/getBookListByPage")public Result<PageResult<BookInfo>> getBookListByPage(PageRequest pageRequest,HttpSession session){Result<PageResult<BookInfo>> ans=new Result<>();//1.判断用户是否登录//如果用户信息为空, 说明用户未登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){return Result.nologin("用户未登录");}//PageResult<BookInfo> bookList=bookInfoService.queryBookListByPage(pageRequest);return Result.success(bookList);}
public static <T> Result success(T data){Result result=new Result();result.setCode(StatusResult.SUCCESS);result.setData(data);return result;}
此时结果返回bookList类型的。
📝测试前后端代码是否正确




🎈修改图书
📝前后端交互
[请求]/book/queryBookById?bookId=25[参数]⽆[响应]{"id": 25,"bookName": " 图书 21","author": " 作者 2","count": 999,"price": 222.00,"publish": " 出版社 1","status": 2,"statusCN": null,"createTime": "2023-09-04T04:01:27.000+00:00","updateTime": "2023-09-05T03:37:03.000+00:00"}
[ 请求 ]/book/updateBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8[ 参数 ]id=1&bookName= 图书 1&author= 作者 1&count=23&price=36&publish= 出版社 1&status=1[ 响应 ]"" // 失败信息 , 成功时返回空字符串
📝实现服务器代码



/*** 修改图书*///通过id查询图书信息@Select("select * from book_info where id=#{bookid} and status!=0")BookInfo queryBookById(Integer bookid);Integer updateBookById(BookInfo bookInfo);
通过点击修改按钮,之后跳转到更新页面的时候,就相当于通过id获取到图书信息显示再表单中,然后我们就可以再文本框中输入值,进行更改数据。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.cl.mapper.BookMapper"><update id="updateBookById">update book_info<set><if test="bookName != null">book_name = #{bookName},</if><if test="author != null">author =#{author},</if><if test="count != null">count = #{count},</if><if test="price !=null">price = #{price},</if><if test="publish != null">publish =#{publish},</if><if test="status != null">status =#{status}</if></set>where id=#{id}</update>
<mapper>
因为有些数据我们可能是不想改的,所以我们再更新图书的时候进行了动态sql,用了xml文件进行实现动态sql。

🎓业务层
/*更改图书*/public Integer updateBookById(BookInfo bookInfo){return bookInfoMapper.updateBookById(bookInfo);}public BookInfo queryBookById(Integer bookid){return bookInfoMapper.queryBookById(bookid);}
🎓控制层
/*** 查询图书信息*/@RequestMapping("/queryBookById")public Result<BookInfo> queryBookById(Integer bookId,HttpSession session) {Result<BookInfo>ans=new Result<>();//校验用户信息是否为空,说明是未登录状态UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setData(null);ans.setCode(StatusResult.NOLOGIN);return ans;}log.info("根据ID查询图书信息, id:"+bookId);long start=System.currentTimeMillis();BookInfo bookInfo=bookInfoService.queryBookById(bookId);ans.setCode(StatusResult.SUCCESS);ans.setData(bookInfo);log.info("queryBookById 耗时: "+ (System.currentTimeMillis()-start) + "ms");return ans;}/*** 更新图书*/@RequestMapping(value = "/updateBook", produces = "application/json")public Result<String> updateBook(BookInfo bookInfo,HttpSession session) {Result<String>ans=new Result<>();//效验参数是否登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setCode(StatusResult.NOLOGIN);ans.setData(null);return ans;}log.info("更新图书, bookInfo: {}", bookInfo);try {Integer result=bookInfoService.updateBookById(bookInfo);if(result>0){ans.setData("");ans.setCode(StatusResult.SUCCESS);return ans;}}catch (Exception e){log.error("更新图书失败");}ans.setData(null);ans.setCode(StatusResult.FAIL);return ans;}
首先通过id查询图书信息,返回的数据是BookInfo类型的,因为我们需要把书返回给客户端。
然后通过id进行修改该书。如果更新的结果行数大于0,说明更新成功,如果没有,那么就更新失败。返回null数据。
📝测试前后端代码是否正确
先测后端代码,首先是未登录状态
登录状态:
通过id获取图书信息
通过id更改图书信息
检查前端代码:


🎈删除图书
📝约定前后端交互接⼝
逻辑删除逻辑删除也称为软删除、假删除、Soft Delete,即不真正删除数据,⽽在某⾏数据上增加类型 is_deleted的删除标识,⼀般使⽤UPDATE语句物理删除物理删除也称为硬删除,从数据库表中删除某⼀⾏或某⼀集合数据,⼀般使⽤DELETE语句
删除图书的两种实现⽅式
- 逻辑删除 update book_info set status=0 where id = 1
- 物理删除 delete from book_info where id=25
/book/deleteBook[ 请求 ]/book/deleteBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8[ 参数 ]id=1&status=0[ 响应 ]"" // 失败信息 , 成功时返回空字符串

当后端接收到bookid的时候,数据层通过id删除该图书,由于删除的sql语句,返回的是int类型,如果删除成功,执行的长度>0,并且返回""数据,并且设置字节码为successs,然后跳转到列表页,如果执行失败,长度为0,返回的是null数据,并且设置字节码fail。失败的情况是未登录状态,如果返回的数据是空并且字节码是nologin,就返回的到登录页面
📝实现服务器代码
🎓数据层
删除我们是按照id进行删除的,并且我们是按照逻辑删除的,所以就相当于通过id进行更新。
🎓服务层
/*** 删除图书*/public Integer deleteBook(Integer bookId){BookInfo bookInfo=new BookInfo();bookInfo.setId(bookId);bookInfo.setStatus(0);return bookInfoMapper.updateBookById(bookInfo);}
🎓控制层
/*** 删除图书*/@RequestMapping("/deleteBook")public Result<Boolean> deleteBook(Integer bookId,HttpSession session){Result<Boolean>ans=new Result<>();//效验参数是否登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setCode(StatusResult.NOLOGIN);ans.setData(null);return ans;}log.info("删除图书,bookId:{}",bookId);Integer result=bookInfoService.deleteBook(bookId);if(result>0){ans.setData(true);ans.setCode(StatusResult.SUCCESS);return ans;}ans.setData(false);ans.setCode(StatusResult.FAIL);return ans;}
控制层返回的数据是返回给客户端代码中。
📝测试前后端代码是否正确
先测后端代码,首先是未登录状态



检查前端代码:
此时删除成功,返回的列表页。
🎈批量删除图书
📝约定前后端交互接⼝
/book/batchDeleteBook[ 请求 ]/book/batchDeleteBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8[ 参数 ]id=1&status=0[ 响应 ]"" // 失败信息 , 成功时返回空字符串
function batchDelete() {var isDelete = confirm("确认批量删除?");if (isDelete) {//获取复选框的idvar ids = [];//已经选中的元素$("input:checkbox[name='selectBook']:checked").each(function () {ids.push($(this).val());});console.log(ids);$.ajax({url: "/book/batchDeleteBook?ids=" + ids,type: "post",success: function (result) {if (result.code == "SUCCESS" && result.data == "") {//删除成功location.href = "book_list.html";} else {alert(result.data);}},error: function (error) {//用户未登录if (error.data == null && error.data=="NOLOGIN") {location.href = "login.html";}}});// alert("批量删除成功");}}
前端将url和type发送给服务器。
📝实现服务器代码
Integer batchDeleteBookByIds(List<Integer> ids);
批量删除,我们需要将选中的id对应的图书都删除掉。用到动态sql语句
<delete id="batchDeleteBookByIds">update book_info set status=0where id in<foreach collection="ids" open="(" close=")" item="id" separator=",">#{id}</foreach></delete>
🎓业务层
/*** 批量删除*/public Integer batchDeleteBookByIds(List<Integer> ids){return bookInfoMapper.batchDeleteBookByIds(ids);}
🎓控制层
/*** 批量删除 ids*/@RequestMapping(value = "/batchDeleteBook", produces = "application/json")public Result<String> batchDelete(@RequestParam List<Integer> ids, HttpSession session) {Result<String>ans=new Result<>();//效验参数是否登录UserInfo loginUserInfo = (UserInfo) session.getAttribute(constants.USER_SESSION_KEY);if (loginUserInfo==null || loginUserInfo.getId()<=0){ans.setCode(StatusResult.NOLOGIN);ans.setData(null);return ans;}Integer result= bookInfoService.batchDeleteBookByIds(ids);if(result>0){ans.setData("");ans.setCode(StatusResult.SUCCESS);return ans;}ans.setData("批量删除失败");ans.setCode(StatusResult.FAIL);return ans;}
📝测试前后端代码是否正确
先测后端代码,首先是未登录状态

检查前端代码:
此时批量删除成功!
永远有优秀的代码,永远有提升的空间,比如,我们次实现都要调用一个接口,是不是很复杂,如果有更多的功能接口,那么就会更复杂。我们想想会有什么方法来优化呢?后续会有统一功能等等,让这个项目做进一步的优化。
披星戴月走过的路必将繁华似锦!
相关文章:

【JavaEE进阶】——利用框架完成功能全面的图书管理系统
目录 🚩项目所需要的技术栈 🚩项目准备工作 🎈环境准备 🎈数据库准备 🚩前后端交互分析 🎈登录 📝前后端交互 📝实现服务器代码 📝测试前后端代码是否正确 &am…...

WDF驱动开发-内存缓冲区
驱动程序通常使用内存缓冲区向/从框架和其他驱动程序传递数据,或在本地存储信息。 WDF常见的内存缓冲区包括框架内存对象(WDFMEMORY)、 lookaside、 MDL 和 本地缓冲区。 使用框架内存对象 框架使用 内存对象 来描述驱动程序从中接收并传递给框架的内存缓冲区。 每…...

c语言连接两个字符串
在C语言中,连接两个字符串可以使用 strcat 函数。这个函数将一个字符串复制到另一个字符串的末尾。使用 strcat 函数之前,需要确保目标字符串有足够的空间来容纳源字符串,否则可能会导致缓冲区溢出。 下面是一个使用 strcat 函数连接两个字符…...

基于springboot的大学计算机基础网络教学系统
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的大学计算机基础网络教学…...

UOS常用命令
shutdown 关机 reboot 重启 reboot -f 强制重启 history 查看使用的历史命令 history -c 清空命令行常见目录结构 /bin 存储常用用户指令 /boot 存放用于系统引导时使用的各种文件 /dev 存放设备文件 /etc 存放系统,服务的配置…...

vue3 如何给表单添加表单效验+正则表达式
校验要求 我们的表单中有密码、电话号码 ,两项。 我们设置用密码为3到20位的非空字符 电话号码就用目前用的电话号码正则表达式,要求手机号码以 1 开头,第二位为 3 到 9 之间的数字,后面跟着任意 9 个数字,总共是 11…...

JavaScript算法实现dfs查找省市区路径
需求 存在如下数组,实现一个算法通过输入区名,返回省->市->区格式的路径,例如输入西湖区,返回浙江省->杭州市->西湖区。 // 定义省市区的嵌套数组 const data [{name: "浙江省",children: [{name: "…...

map文件分析
以下是一个具体的map文件示例,并附上详细的描述,帮助你更好地理解如何读取和分析map文件: 示例map文件 Memory ConfigurationName Origin Length Attributes FLASH 0x08000000 0x0…...

MySQL-创建表~数据类型
070-创建表 create table t_user(no int,name varchar(20),gender char(1) default 男);071-插入数据 语法格式: insert into 表名(字段名1, 字段名2, 字段名3,......) values (值1,值2,值3,......);insert into t_user(no, name, gender) values(1, Cupid, 男);字…...

【鸿蒙 HarmonyOS】Swiper组件
一、背景 项目中通常会遇到图片轮播,内容轮播的场景;如:在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。 二、源码地址 ✍Gitee开源项目地址👉:https://gitee.com/cheinlu/harmony-os-next-swi…...

玩具机器人脚本适合场景
玩具机器人脚本作为一个模拟的玩具机器人脚本,适合以下场合: 1.教育和学习:对于初学者和编程爱好者来说,这个脚本是一个很好的学习工具,可以帮助他们理解如何编写和执行简单的控制逻辑。 2.在计算机科学、机器人技术或…...

人工智能模型组合学习的理论和实验实践
组合学习,即掌握将基本概念结合起来构建更复杂概念的能力,对人类认知至关重要,特别是在人类语言理解和视觉感知方面。这一概念与在未观察到的情况下推广的能力紧密相关。尽管它在智能中扮演着核心角色,但缺乏系统化的理论及实验研…...

MySQL备份与恢复:确保数据的安全与可靠性
引言: 数据的安全性和可靠性的重要性 在现代企业和组织中,数据已经成为了最重要的资产之一。数据的安全性和可靠性对于企业的运营至关重要。首先,数据的安全性保证了敏感信息不会落入错误的手中,防止了潜在的经济损失和法律风险。其次,数据的可靠性则确保了企业能够准确…...

Noisee AI – AI音乐影片MV在线生成工具,专门为Suno的好搭子来了~
导读 现在很多各大平台,抖音、快手、微视,还不能直接发布音频文件,如果有一个好听的音乐想做成MV,怎么办呢? 这时候就是Noisee AI的主场,上传一段音乐加上简单的描述就可以在3-5分钟内生成一个可以发布到…...

实战计算机网络02——物理层
实战计算机网络02——物理层 1、物理层实现的功能2、数据与信号2.1 数据通信模型2.2 通信领域常用术语2.3 模拟信号和数字信号 3、信道和调制3.1 信道3.2 单工通信、半双工通信、全双工通信3.3 调制3.4 奈式准则3.5 香农定律 4、传输媒体4.1 导向传输媒体4.2 非导向传输媒体 5、…...

Doris:冷热分层
目录 一、冷热分层介绍 二、存储策略(Storage policy) 2.1 创建存储资源 2.2 创建存储策略 2.3 使用存储策略 三、使用限制 一、冷热分层介绍 冷热分层支持所有 Doris 功能,只是把部分数据放到对象存储上,以节省成本&am…...

28.启动与暂停程序
上一个内容:27.设计注入功能界面 以它 27.设计注入功能界面 的代码为基础进行修改 点击添加游戏按钮之后就把游戏启动了 CWndINJ.cpp文件中修改: void CWndINJ::OnBnClickedButton1() {// TODO: 在此添加控件通知处理程序代码/*ExeLst.InsertItem(0, L…...

404 页面代码
<template> <div class"container"><h1>404</h1> <div ><p class"text-center">当前页面无法访问,可能没有权限或已删除</p><p class"text-center"> 去别处看看吧</p> </div> <…...

java设计模式和面向对象编程思想
Java设计模式和面向对象编程思想是软件开发中的核心概念,对于构建可维护、可扩展的软件系统至关重要。下面是对这两个主题的知识点总结: 面向对象编程(OOP)思想 封装:将数据(属性)和操作这些数据…...

超万卡训练集群网络互联技术解读
超万卡训练集群互联关键技术 大模型迈向万亿参数的多模态升级,万卡集群计算能力亟需飞跃。关键在于增强单芯片性能、提升超节点算力、融合DPU多计算能力,并追求算力能效比极致。这一系列提升将强有力支撑更大规模模型训练和推理,快速响应业务…...

AtomicInteger类介绍
文章目录 一、AtomicInteger的定义二、AtomicInteger的使用场景和作用1.使用场景2.作用 三、AtomicInteger的常用方法四、AtomicInteger的底层原理五、AtomicInteger和Integer的区别1.数据类型与线程安全性2.默认值与初始化3.常用方法与操作:4.内存模型与可见性5.使…...

Es 索引查询排序分析
文章目录 概要一、Es数据存储1.1、_source1.2、stored fields 二、Doc values2.1、FieldCache2.2、DocValues 三、Fielddata四、Index sorting五、小结六、参考 概要 倒排索引 优势在于快速的查找到包含特定关键词的所有文档,但是排序,过滤、聚合等操作…...

【C语言】解决C语言报错:Format String Vulnerability
文章目录 简介什么是Format String VulnerabilityFormat String Vulnerability的常见原因如何检测和调试Format String Vulnerability解决Format String Vulnerability的最佳实践详细实例解析示例1:直接使用不受信任的输入作为格式化字符串示例2:未验证格…...

Python深度学习:Bi-LSTM和LSTM在网络上有什么区别,对比来看
文章目录 LSTM代码解释类定义和构造函数前向传播方法 (`forward`)总结Bi-LSTMLSTM 代码 class BaseLSTMModel(nn.Module):def __init__(self, input_dim, hidden_dim, layer_dim, class_num):super().__init__...

Keepalived LVS群集
一、Keepalived案例分析 企业应用中,单台服务器承担应用存在单点故障的危险 单点故障一旦发生,企业服务将发生中断,造成极大的危害 二、Keepalived工具介绍 专为LVS和HA设计的一款健康检查工具 支持故障自动切换(Failover&#…...

harbor问题总结
1. http协议的仓库docker login不上,更改/etc/docker/daemon.json,加一个镜像仓库地址 http: server gave HTTP response to HTTPS client 分析一下这个问题如何解决中文告诉我详细的解决方案-CSDN博客 2. Error response from daemon: login attempt t…...

windows系统,家庭自用NAS。本地局域网 Docker安装nextcloud
windows系统,家庭自用NAS。本地局域网 Docker安装nextcloud 1、docker安装 太简单了,直接去搜一搜。 docker-compose 相关命令 docker-compose down docker compose up -d2、还是使用老的 在你需要挂载的目录下,新建一个文件,…...

迅狐跨境商城系统|全平台兼容|前端采用uni-app跨端框架,后端采用ThinkPHP5框架
高效实现全平台兼容的迅狐跨境商城系统 迅狐跨境商城系统是一款专为跨境电商企业设计的全平台兼容系统。其前端采用uni-app跨端框架,后端采用ThinkPHP5框架,旨在实现高效的开发和运营管理。 1. 全平台兼容的前端设计 迅狐跨境商城系统的前端采用uni-a…...

Elixir学习笔记——进程(Processes)
在 Elixir 中,所有代码都在进程内运行。进程彼此隔离,彼此并发运行并通过消息传递进行通信。进程不仅是 Elixir 中并发的基础,而且还提供了构建分布式和容错程序的方法。 Elixir 的进程不应与操作系统进程混淆。Elixir 中的进程在内存和 CPU…...

困惑度作为nlp指标的理解示例
为了更清晰地说明困惑度的计算过程以及如何通过困惑度判断模型的优劣,我们可以通过一个简单的例子来演示。假设我们有一个非常简单的文本语料库和两个基础的语言模型进行比较。 示例文本 假设我们的文本数据包括以下两个句子: “cat sits on the mat”…...