文章目录
- 功能实现
-
- [(四) 修改图书](#(四) 修改图书)
- [(五) 删除图书](#(五) 删除图书)
-
- 1.约定前后端交互接口
- [2. 实现客户端代码](#2. 实现客户端代码)
- [(六) 批量删除](#(六) 批量删除)
- [(七) 强制登录](#(七) 强制登录)
功能实现
(四) 修改图书
1.约定前后端交互接口
[请求]
/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"
}
根据图书ID,获取当前图书的信息
点击修改按钮,修改图书信息
[请求]
/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
[响应]
"" //失败信息,成功时返回空字符串
我们约定,浏览器给服务器发送一个 /book/updateBook 这样的 HTTP 请求, form表单的形式来提交数据
服务器返回处理结果,返回""表示添加图书成功,否则,返回失败信息.
2.实现服务端代码
- BookController
java
@RequestMapping("/queryBookById")
public BookInfo queryBookById(Integer bookId){
log.info("查询图书信息,bookId:" + bookId);
return bookService.queryBookById(bookId);
}
@RequestMapping("/updateBook")
public String updateBook(BookInfo bookInfo){
log.info("修改图书,bookInfo: {}",bookInfo);
try{
bookService.updateBook(bookInfo);
//成功
return "";
}catch (Exception e){
log.error("修改图书发生异常, e:",e);
return "修改图书发生异常";
}
}
- 业务层:
BookService
java
public void updateBook(BookInfo bookInfo){
bookMapper.updateBook(bookInfo);
}
public Integer batchDelete(List<Integer> ids){
return bookMapper.batchDelete(ids);
}
- 数据层:
java
@Select("select * from book_info where `status` != 0 and id = #{bookId}")
BookInfo queryBookById(Integer bookId);
- xml实现 更新
创建BookInfoMapper.xml文件

java
<?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.chaoxin.book.mapper.BookMapper">
<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>
<delete id="batchDelete">
update book_info set status=0 where id in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
</mapper>

3.实现客户端代码
http://127.0.0.1:8080/book_update.html?bookId=25 (25为对应的图书ID)
finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>';
点击[修改]链接时,就会自动跳转到 http://127.0.0.1:8080/book_update.html?bookId=25 (25为对应的图书ID)
进入[修改图书]页面时,需要先从后端拿到当前图书的信息,显示在页面上
java
//进⼊[修改图书]⻚⾯时需要先从后端拿到当前图书的信息,显⽰在⻚⾯上
getBookInfo();
function getBookInfo(){
$.ajax({
type: "get",
url: "/book/queryBookById" + location.search,
success: function(result){
//TODO 检验非常简陋
var bookInfo = result.data;
if(bookInfo != null){
$("#bookId").val(bookInfo.id);
$("#bookName").val(bookInfo.bookName);
$("#bookAuthor").val(bookInfo.author);
$("#bookStock").val(bookInfo.count);
$("#bookPrice").val(bookInfo.price);
$("#bookPublisher").val(bookInfo.publish);
$("#bookStatus").val(bookInfo.status);
}else{
console.log(result);
}
},
error: function(error){
if(error.status == 401){
//用户未登录
location.href = "login.html";
}
}
});
}
//修改图书
function update() {
$.ajax({
type: "post",
url: "/book/updateBook",
data: $("#updateBook").serialize(),
success: function(result){
if(result == ""){
location.href = "book_list.html"
}else{
console.log(result);
alert("修改失败:" + result);
}
},
error: function(error){
console.log(error);
}
});
// alert("更新成功");
// location.href = "book_list.html"
}
我们修改图书信息,是根据图书ID来修改的,所以需要前端传递的参数中,包含图书ID.
有两种方式:
- 获取url中参数的值(比较复杂,需要拆分url)
- 在form表单中,再增加一个隐藏输入框,存储图书ID,随
$("#updateBook").serialize()一起提交到后端
我们采用第二种方式
在form表单中,添加隐藏输入框
java
<form id="updateBook">
<input type="hidden" class="form-control" id="bookId" name="id">
<div class="form-group">
<label for="bookName">图书名称:</label>
<input type="text" class="form-control" id="bookName" name="bookName">
</div>
<div class="form-group">
<label for="bookAuthor">图书作者</label>
<input type="text" class="form-control" id="bookAuthor" name="author"/>
</div>
<div class="form-group">
<label for="bookStock">图书库存</label>
<input type="text" class="form-control" id="bookStock" name="count"/>
</div>
<div class="form-group">
<label for="bookPrice">图书定价:</label>
<input type="number" class="form-control" id="bookPrice" name="price">
</div>
<div class="form-group">
<label for="bookPublisher">出版社</label>
<input type="text" id="bookPublisher" class="form-control" name="publish"/>
</div>
<div class="form-group">
<label for="bookStatus">图书状态</label>
<select class="custom-select" id="bookStatus" name="status">
<option value="1" selected>可借阅</option>
<option value="2">不可借阅</option>
</select>
</div>
<div class="form-group" style="text-align: right">
<button type="button" class="btn btn-info btn-lg" onclick="update()">确定</button>
<button type="button" class="btn btn-secondary btn-lg" onclick="javascript:history.back()">返回</button>
</div>
</form>
hidden 类型的
<input>元素
隐藏表单,用户不可见、不可改的数据,在用户提交表单时,这些数据会一并发送出
使用场景: 正被请求或编辑的内容的 ID. 这些隐藏的 input 元素在渲染完成的页面中完全不可见,而且没有方法可以使它重新变为可见.
页面加载时,给该hidden框赋值
javascript
$("#bookId").val(book.id);

(五) 删除图书
1.约定前后端交互接口
删除分为逻辑删除和物理删除
逻辑删除
逻辑删除也称为软删除、假删除、Soft Delete,即不真正删除数据,而在某行数据上增加类型is_deleted的删除标识,一般使用UPDATE语句
物理删除物理删除也称为硬删除,从数据库表中删除某一行或某一集合数据,一般使用DELETE语句
删除图书的两种实现方式
逻辑删除
sql
update book_info set status=0 where id = 1
物理删除
sql
delete from book_info where id=25
数据是公司的重要财产,通常情况下,我们采用逻辑删除的方式,当然也可以采用[物理删除+归档]的方式
物理删除并归档
- 创建一个与原表差不多结构,记录删除时间,实现INSERT ... SELECT即可 [SQL INSERT INTO SELECT 语句 | 菜鸟教程](SQL INSERT INTO SELECT 语句 | 菜鸟教程链接)
- 插入和删除操作,放在同一个事务中执行
物理删除+归档的方式实现有些复杂,咱们采用逻辑删除的方式
逻辑删除的话,依然是更新逻辑,我们可以直接使用修改图书的接口/book/updateBook
[请求]
/book/updateBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
id=1&status=0
[响应]
"" //失败信息,成功时返回空字符串
2. 实现客户端代码
java
function deleteBook(id) {
var isDelete = confirm("确认删除?");
if (isDelete) {
$.ajax({
type: "post",
url: "/book/deleteBook?bookId=" + id,
success: function (result) {
if (result.code == "SUCCESS" && result.data == "") {
location.href = "book_list.html?currentPage=1";
} else {
alert(result.errMsg);
}
}
});
}
}

(六) 批量删除
批量删除,其实就是批量修改数据
1.约定前后端交互接口
[请求]
/book/batchDeleteBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
[响应]
"" //失败信息,成功时返回空字符串
点击[批量删除]按钮时,只需要把复选框选中的图书的ID,发送到后端即可
多个id,我们使用List的形式来传递参数
2.实现服务器代码
- 控制层:
BookController
java
@RequestMapping("/batchDelete")
public Boolean batchDelete(@RequestParam List<Integer> ids) {
log.info("批量删除图书, ids:{}", ids);
try {
bookService.batchDelete(ids);
return true;
} catch (Exception e) {
log.error("批量删除图书失败, e:", e);
return false;
}
}
- 业务层:
java
public Integer batchDelete(List<Integer> ids){
return bookMapper.batchDelete(ids);
}
- 数据层:
批量删除需要用到动态SQL,我们这里都用xml来实现
BookInfoMapper.java
接口定义
java
void batchDeleteBook(List<Integer> ids);
xml接口实现
java
<?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.chaoxin.book.mapper.BookMapper">
<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>
<delete id="batchDelete">
update book_info set status=0 where id in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
<select id="queryBookListByPage" resultType="com.chaoxin.book.model.BookInfo"></select>
</mapper>


(七) 强制登录
虽然我们做了用户登录,但是我们发现,用户不登录,依然可以操作图书.
这是有极大风险的.所以我们需要进行强制登录.
如果用户未登录就访问图书列表或者添加图书等页面,强制跳转到登录页面.
实现思路分析
用户登录时,我们已经把登录用户的信息存储在了Session中.那就可以通过Session中的信息来判断用户是否登录.
- 如果Session中可以取到登录用户的信息,说明用户已经登录了,可以进行后续操作
- 如果Session中取不到登录用户的信息,说明用户未登录,则跳转到登录页面.
直接访问接口,无需登录就可以对书籍进行删除添加
我们需要在增加一个属性告知后端的状态以及后端出错的原因:
java
@Data
public class PageResult<T> {
private int status;
private String errorMessage;
private int total;//所有记录数
private List<T> records; // 当前页数据
private PageRequest pageRequest;
public PageResult(Integer total, PageRequest pageRequest, List<T> records) {
this.total = total;
this.pageRequest = pageRequest;
this.records = records;
}
}
后端数据所有的接口都要跟着修改,这样修改代码效率低,不放对所有后端返回的数据进行一个封装
java
@Data
public class Result<T> {
private int status;
private String errorMessage;
private T data;
}
data为之前接口返回的数据
status为后端业务处理的状态码,也可以使用枚举来表示
java
public enum ResultStatus {
SUCCESS(200),
UNLOGIN(-1),
FAIL(-2);
private Integer code;
ResultStatus(int code) {
this.code = code;
}
public Integer getCode() {
return code;
}
}
修改 Result 添加常用方法
java
@Data
public class Result<T> {
private ResultCodeEnum code; //-1 未登录 200 正常 -2 出错
private String errMsg;
private T data;
public static <T> Result success(T data){
Result result = new Result();
result.setCode(ResultCodeEnum.SUCCESS);
result.setErrMsg("");
result.setData(data);
return result;
}
public static <T> Result fail(String errMsg){
Result result = new Result();
result.setCode(ResultCodeEnum.FAIL);
result.setErrMsg(errMsg);
result.setData(null);
return result;
}
public static <T> Result fail(String errMsg, T data){
Result result = new Result();
result.setCode(ResultCodeEnum.FAIL);
result.setErrMsg(errMsg);
result.setData(data);
return result;
}
public static <T> Result unlogin(){
Result result = new Result();
result.setCode(ResultCodeEnum.UNLOGIN);
result.setErrMsg("用户未登录");
return result;
}
}
实现服务端代码
修改图书列表接口,进行登录校验
java
@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest, HttpSession session) {
log.info("获取图书列表, pageRequest:{}", pageRequest);
//判断用户是否登录
if (session.getAttribute("session_user_key")==null){
return Result.unlogin();
}
UserInfo userInfo = (UserInfo) session.getAttribute("session_user_key");
if (userInfo==null || userInfo.getId()<0 ||
"".equals(userInfo.getUserName())){
return Result.unlogin();
}
//用户登录,返回图书列表
PageResult<BookInfo> pageResult =
bookService.getBookListByPage(pageRequest);
return Result.success(pageResult);
}
问题:如果修改常量session的key,就需要修改所有使用到这个key的地方,出于高内聚低耦合的思想,我们把常量集中在一个类里
创建类:Constants
java
public class Constants {
public static final String SESSION_USER_KEY = "session_user_key";
}
常量命名规则:
常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要在意名字长度.
正例: MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
反例: COUNT / TIME
实现客户端代码
由于后端接口发生变化,所以前端接口也需要进行调整
这也就是为什么前后端交互接口一旦定义好,尽量不要发生变化.
所以后端接口返回的数据类型一般不定义为基本类型,包装类型或者集合类等,而是定义为自定义对象.方便后续做扩展
修改图书列表的方法(下面代码为修改部分):
javascript
function getBookList() {
//...
success: function (result) {
//真实前端代码需要分的更细一点,此处不做判断
if (result == null || result.data == null) {
location.href = "login.html";
return;
}
//....
var data = result.data;
$("#pageContainer").jqPaginator({
totalCounts: data.total, //总记录数
pageSize: 10, //每页的个数
visiblePages: 5, //可视页数
currentPage: data.pageRequest.currentPage, //当前页码
//....
});
}
}