PS: 开闭原则 定义和背景 开闭原则(Open-Closed Principle, OCP),也称为开放封闭原则,是面向对象设计中的一个基本原则。该原则强调软件中的模块、类或函数应该对扩展开放,对修改封闭。这意味着一个软件实体应该在不修改现有代码的基础上,能够适应新的变化和需求。 核心思想 开闭原则的核心思想是:
针对先上线的程序而言 比如后端接口从A改成了B。(前端接口也要修改) 如果后端先上线:前端调用就会出错,找不到A接口 如果前端先上线,前端调用依然会报错,找不到B接口。因为此时后端还没上线。 正确的做法:后端对之前的接口进行兼容,如果兼容则同时存在A和B接口 实现方式 实现开闭原则的方式主要包括:
相关设计原则 开闭原则与其他设计原则密切相关,包括:
提到展示图书列表,就不得不提到分页了
分页时,数据是如何展示的呢
第1页:显示1-10 条的数据
第2页:显示11-20 条的数据
第3页:显示 21-30 条的数据
以此类推...
要想实现这个功能,从数据库中进行分页查询,我们要使用 LIMIT 关键字,格式为:limit 开始索引 每页显示的条数(开始索引从0开始)。
select * from book_info where status <> 0 limit 0,10;select * from book_info where status <> 0 limit 10,10;select * from book_info where status <> 0 limit 20,10;我们发现只有开始索引在改变。每页显示的条数是固定的。
开始索引的计算公式:开始索引 = (当前页码 - 1) * 每页显示条数。
因此:
1.前端发起查询请求时,需要向服务器端传递的参数。
currentPage 当前页码 :默认值为1
pageSize 每页显示条数 默认值为10
注:
为了项目更好的扩展性,通常不设置固定值,而是是以参数的形式来进行传递
扩展性: 软件系统具备面对未来需求变化而进行扩展的能力。 比如当前需求一页显示10条,后期需求改为一页显示20条,
后端代码不需要任何修改。
2. 后端响应时,需要响应给前端的数据。
records :所查询到的数据列表(存储到List集合中)
count :总记录数(用于告诉前端显示多少页,
显示页数为:(count + pageSize -1)/pageSize
翻页请求和响应部分, 我们通常封装在两个对象中
创建PageRequest 前端进行请求 1.会请求当前页 和 每页显示的个数。 2.由上面两个数据计算出offset,用作参数传递给SQL语句
package com.qiyangyang.springbook.demos.model;
import lombok.Data;
@Data
public class PageRequest {
private Integer currentPage = 1;//当前页
private Integer pageSize = 10;//每页显示个数
private Integer offset;
/**
* 从多少条记录开始查询
* @return
*/
public Integer getOffset() {
return (currentPage-1) * pageSize;
}
}package com.qiyangyang.springbook.demos.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 定义一个泛型类,就是,这个地方不定义具体类型
* 我们在进行对象的生成的时候,它才有具体的类型。
* @param <T>
*/
//上面两个注解用来创建构造方法
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PageResult<T> {
/**
* 返回的结果也是一个泛型
* 不定义具体类型,在对象的创建才会有具体类型
*/
private List<T> records; //当前页数据
private Integer count; //所有记录数
private PageRequest pageRequest; //小驼峰,用来返回给前端当前页数
// 这里将整个对象告诉result。用来给前端获取多少页
}返回结果中, 使用泛型来定义记录的类型 后端定义参数。 offset(起始序号)和limit(显示多少条)
MySQL语句

前端根据总记录数,来显示分了多少页。

1.创建enums文件夹
2.创建BookStatusEnums类
package com.qiyangyang.springbook.demos.enums;
/**
* 枚举类
* 可以列举出来的,是一个有限的个数,我们将他们定义成枚举类
* 方便定义类似
* 根据状态设置描述
* if(bookInfo.getStatus() == 1){
* bookInfo.setStateCN("可借阅");
* } else if (bookInfo.getStatus() == 2) {
* bookInfo.setStateCN("不可借阅");
* }else {
* bookInfo.setStateCN("无效");
* }
*/
public enum BookStatusEnums {
DELETE(0,"无效"), //删除
NORMAL(1,"可借阅"), //有效的
FORBIDDEN(2,"不可借阅"), //禁止
;
private int code;
private String desc;
BookStatusEnums(int code, String desc) {
this.code = code;
this.desc = desc;
}
/**
* 我们将这个封装成一个方法
* 根据code获取描述
*
* @return
*/
public static BookStatusEnums getDescByCode(int code){
switch (code){
case 0: return BookStatusEnums.DELETE;
case 1: return BookStatusEnums.NORMAL;
case 2: return BookStatusEnums.FORBIDDEN;
}
return BookStatusEnums.DELETE;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}稍后在业务层我们会对这个方法进行调用
接口定义: url:/book/getListByPage?currentPage=1 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 参数: 当前页数 返回结果: 当前页的数据+总记录数(决定前端显示多少页数)
我们约定,浏览器给服务器发送一个 /book/getListByPage 这样的 HTTP 请求, 通过 currentPage 参数告诉服务器,当前请求为第几页的数据, 后端根据请求参数,返回对应页的数据 第一页可以不传参数, currentPage默认值为1。
完善 BookController
@RequestMapping("getListByPage")
public PageResult<BookInfo> getListByPage(PageRequest pageRequest){
log.info("查询列表信息,pageRequest:{}",pageRequest);
if(pageRequest.getCurrentPage()< 1){
return null;
}//这里返回null。会导致前端不知道是没有数据为null。还是当前页错误返回null
//先不管,后续改进
/**
* 通过Service来去调用数据库
*/
return bookService.getListByPage(pageRequest);
}完善 BookService
public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {
/**
* 1.查询记录的总数
* 2.查询当前页的数据
*/
Integer count = bookInfoMapper.count();
//bookInfos来接收查询到的数据
List<BookInfo> bookInfos = bookInfoMapper.queryListByPage(pageRequest);
for(BookInfo bookInfo : bookInfos){
/**
* 根据book状态设置描述(stateCN)
*/
bookInfo.setStateCN(BookStatusEnums.getDescByCode(bookInfo.getStatus()).getDesc());
}
return new PageResult<>(bookInfos,count);
}完善 BookInfoMapper
/**
* 查询总数
* @return
*/
//count(1):返回满足条件的记录数(即行数)。count(1) 和 count(*) 基本等效,都是用于统计记录数。
@Select(("select count(1) from book_info where status <> 0"))
Integer count();
//希望把新添加的图书放到下面,因此order by id desc降序。
@Select("select * from book_info where status <> 0 order by id desc limit #{offset},#{pageSize}")
List<BookInfo> queryListByPage(PageRequest pageRequest);不用传参也行,因为我们默认currentPage 为1。且pageSize为5。 我们发现返回正确。 总记录数也返回正确。为46。 我们发现后端接口没有问题。

1.6.1显示图书数据的内容 将前端<tbody>标签中的内容,也就是
<tbody>
//这里的内容我们用findHtml变量拼接并传送到这个标签里了
</tbody>我们写在ajax中使用findHtml变量进行拼接。并用如下方法传送到这个标签中。
$("tbody").html(findHtml); //塞到tbody这个标签里面 success: function (result) {
var books = result.records;
console.log(books); //如果前端没有报错,那么我们打印日志。观察后端返回结果对不对
var findHtml = ""; //用这个变量来拼接HTML
for (var book of books) {
//拼接html。假如后端返回10个tr那么直接for循环拼接在这里面。findHtml
//我们用单引号拼接,因为里面有双引号
findHtml += '<tr>';
findHtml += '<td><input type="checkbox" name="selectBook" value="' +book.id +'" id="selectBook" class="book-select"></td>';
findHtml += "<td>" + book.id + "</td>";
findHtml += "<td>" + book.bookName + "</td>";
findHtml += "<td>" + book.author + "</td>";
findHtml += "<td>" + book.count + "</td>";
findHtml += "<td>" + book.price + "</td>";
findHtml += "<td>" + book.publish + "</td>";
findHtml += "<td>" + book.stateCN + "</td>";
findHtml += "<td>";
findHtml += '<div class="op">';
findHtml +=
'<a href="book_update.html?bookId=' + book.id + '">修改</a>';
findHtml +=
'<a href="javascript:void(0)" onclick="deleteBook('+book.id +')">删除</a>';
findHtml += "</div>";
findHtml += "</td>";
findHtml += "</tr>";
}
$("tbody").html(findHtml); //塞到tbody这个标签里面
我们需要在前端head标签中引入jquery 和 paginator
相当于引入插件
<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> <div class="demo">
<ul id="pageContainer" class="pagination justify-content-center"></ul>
</div> //处理翻页信息
console.log(result);
console.log(result.pageRequest);
//翻页信息
$("#pageContainer").jqPaginator({
totalCounts: result.count, //总记录数
pageSize: 10, //每页的个数
visiblePages: 5, //可视页数
currentPage: result.pageRequest.currentPage, //当前页码
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);
if(type == "change"){
location.href = "book_list.html?currentPage="+page;
}
},
});成功实现图书列表显示以及翻页功能。


1.进入修改页面,需要显示当前 Id 图书的信息
[请求] /book/queryBookById?bookId=25 [参数] bookId [响应] { "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" }
根据图书ID,获取当前图书的信息 2.点击修改按钮,修改图书信息
[请求] /book/updateBook Content-Type: application/x-www-form-urlencoded; charset=UTF-8 [参数] id=1&bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1 [响应] true/false
我们约定, 浏览器给服务器发送一个 /book/updateBook 这样的HTTP请求, form表单的形式来 提交数据 服务器返回处理结果, 返回"修改成功"修改图书成功,否则,返回失败信息.
@RequestMapping("/queryBookById")
public BookInfo queryBookById(Integer bookId){
log.info("查询图书信息,bookId:"+bookId);
if(bookId == null || bookId<=0){
return new BookInfo();
}
return bookService.queryBookById(bookId);
}
@RequestMapping("/updateBook")
//先使用boolean类型返回。后续我们还会再进行完善。
public boolean upDateBook(BookInfo bookInfo){
log.info("修改图书信息, updateBook{}:",bookInfo);
if(!StringUtils.hasLength(bookInfo.getBookName())
|| !StringUtils.hasLength(bookInfo.getAuthor())
|| !StringUtils.hasLength(bookInfo.getPublish())
|| bookInfo.getCount() <=0
|| bookInfo.getPrice()==null){
return false;
}
try {
Integer result = bookService.updateBook(bookInfo);
if(result <= 0){
return false;
}
}catch (Exception e){
log.error("更新图书失败");
return false;
}
return true;
} public BookInfo queryBookById(Integer bookId) {
return bookInfoMapper.queryBookById(bookId);
}
public Integer updateBook(BookInfo bookInfo) {
return bookInfoMapper.updateBook(bookInfo);
} /**
* 根据Id查询图书信息
* @param id
* @return
*/
@Select("select * from book_info where status <> 0 and id = #{id}")
BookInfo queryBookById(Integer id);
/**
* 根据Id修改图书信息
*/
Integer updateBook(BookInfo bookInfo);根据 Id 修改图书信息 我们使用的是XML方式实现的SQL
<?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.qiyangyang.springbook.demos.mapper.BookInfoMapper">
<update id="updateBook">
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>我发现这个接口都校验成功。



修改成功!
注意:前端传递数据的时候记得加上id。
<script type="text/javascript" src="js/jquery.min.js"></script>
<script>
//查询当前ID图书
$.ajax({
type: "get",
url: "book/queryBookById"+location.search,
success:function(book){
if(book!=null){
$("#bookId").val(book.id);
$("#bookName").val(book.bookName);
$("#bookAuthor").val(book.author);
$("#bookStock").val(book.count);
$("#bookPrice").val(book.price);
$("#bookPublisher").val(book.publish);
$("#bookStatus").val(book.status);
}
}
});
//更新当前Id图书
function update() {
$.ajax({
type: "get",
url: "/book/updateBook",
data:$("#updateBook").serialize(),//提交整个表单
success:function(result){
if(result == true){
alert("更新成功");
location.href = "book_list.html"
}else{
alert("更新失败");
}
}
});
}
</script>比如我们要将图书ID为135的图书 作者 修改为 洋洋 数量 修改为 888 价格 修改为 666 出版社 修改为 人民出版社 可借阅 修改为 不可借阅



修改成功!!!!!

删除图书分为 逻辑删除(update): 从逻辑上进行删除,数据并没有真实删除 物理删除(delete语句): 数据真实删除。 但数据并没有真实清空,只是数据库上看不到了。 硬件存储上还是存在的 删除并归档(操作交为复杂):insert into... select....语句 1.删除(delete or update) 2.归档(把已经删除的数据存储下来)
逻辑删除的话, 依然是更新逻辑,我们可以直接使用修改图书的接口
[请求] /book/updateBook Content-Type: application/x-www-form-urlencoded; charset=UTF-8 [参数] id=1&status=0 [响应] true / false
由于我们在upadate接口中只需要传递 id 和 status 两个参数。 因此我们需要修改控制层中的校验参数的步骤
@RequestMapping("/updateBook")
//先使用boolean类型返回。后续我们还会再进行完善。
public boolean upDateBook(BookInfo bookInfo){
log.info("修改图书信息, updateBook{}:",bookInfo);
if(!StringUtils.hasLength(bookInfo.getBookName())
|| !StringUtils.hasLength(bookInfo.getAuthor())
|| !StringUtils.hasLength(bookInfo.getPublish())
|| bookInfo.getCount() <=0
|| bookInfo.getPrice()==null){
return false;
}
try {
Integer result = bookService.updateBook(bookInfo);
if(result <= 0){
return false;
}
}catch (Exception e){
log.error("更新图书失败");
return false;
}
return true;
}修改为
@RequestMapping("/updateBook")
//先使用boolean类型返回。后续我们还会再进行完善。
public boolean updateBook(BookInfo bookInfo){
log.info("修改图书信息, updateBook{}:",bookInfo);
if(bookInfo.getId()<0){
return false;
}
try {
Integer result = bookService.updateBook(bookInfo);
if(result <= 0){
return false;
}
}catch (Exception e){
log.error("更新图书失败");
return false;
}
return true;
}同之前的updateBook一样
public Integer updateBook(BookInfo bookInfo) {
return bookInfoMapper.updateBook(bookInfo);
}同之前的update一样
/**
* 根据Id修改图书信息
*/
Integer updateBook(BookInfo bookInfo); <update id="updateBook">
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>
134号图书 status 成功修改为0

function deleteBook(id) {
//删除图书
var isDelete = confirm("确认删除?");
if (isDelete) {
$.ajax({
type: "post",
url: "/book/updateBook",
data:{
id: id,
status: 0
},
success:function(result){
if(result == true){
alert("删除成功");
location.href = "book_list.html";
}
}
});
}
}删除132号图书三体


成功删除!!!!!!!!!!!
请求: /book/batchDeleteBook 参数: 响应: true/false
注意加上:注解@RequestParam
@RequestMapping("/batchDelete")
public boolean batchDelete(@RequestParam List<Integer> ids){
log.info("批量删除数据,ids:{}",ids);
try {
Integer result = bookService.batchDelete(ids);
if(result <= 0){
return false;
}
}catch (Exception e){
log.info("批量删除失败,id:{},e:{}", ids, e);
return false;
}
return true;
} public Integer batchDelete(List<Integer> ids) {
return bookInfoMapper.batchDelete(ids);
}注意加上注解: @Param("ids")
Integer batchDelete(@Param("ids") List<Integer> ids); <update id="batchDelete">
update book_info
set status = 0
where id in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</update>后端操作成功


function batchDelete() {
var isDelete = confirm("确认批量删除?");
if (isDelete) {
//获取复选框的id
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
$.ajax({
type: "post",
url: "/book/batchDelete?ids="+ids,
success:function(result){
if(result == true){
alert("批量删除成功");
location.href = "book_list.html";
}else{
alert("删除失败,请联系管理员!");
}
}
});
}
}测试成功!!!!



这个功能的实现我们下一篇文章再讲哦!!!! 到这里其实这个图书管理系统的功能就基本实现完成了。 不过对于这个图书管理系统。 我们没有进行登录也可以进行操作。 因此我们下一篇文章会详细讲解强制登录功能。 并且后续会讲到统一功能!!!!!!!!!!!!!!!