【实战】动态 SQL + 统一 Result + 登录校验:图书管理系统(下)

文章目录

功能实现

(四) 修改图书

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.

有两种方式:

  1. 获取url中参数的值(比较复杂,需要拆分url)
  2. 在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

数据是公司的重要财产,通常情况下,我们采用逻辑删除的方式,当然也可以采用[物理删除+归档]的方式

物理删除并归档

  1. 创建一个与原表差不多结构,记录删除时间,实现INSERT ... SELECT即可 [SQL INSERT INTO SELECT 语句 | 菜鸟教程](SQL INSERT INTO SELECT 语句 | 菜鸟教程链接)
  2. 插入和删除操作,放在同一个事务中执行

物理删除+归档的方式实现有些复杂,咱们采用逻辑删除的方式

逻辑删除的话,依然是更新逻辑,我们可以直接使用修改图书的接口/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中的信息来判断用户是否登录.

  1. 如果Session中可以取到登录用户的信息,说明用户已经登录了,可以进行后续操作
  2. 如果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,  //当前页码
            //....
        });
    }
}
相关推荐
枫叶落雨2221 天前
ShardingSphere 介绍
java
素玥1 天前
实训5 python连接mysql数据库
数据库·python·mysql
花花鱼1 天前
Spring Security 与 Spring MVC
java·spring·mvc
jnrjian1 天前
text index 查看index column index定义 index 刷新频率 index视图
数据库·oracle
瀚高PG实验室1 天前
审计策略修改
网络·数据库·瀚高数据库
言慢行善1 天前
sqlserver模糊查询问题
java·数据库·sqlserver
韶博雅1 天前
emcc24ai
开发语言·数据库·python
专吃海绵宝宝菠萝屋的派大星1 天前
使用Dify对接自己开发的mcp
java·服务器·前端
有想法的py工程师1 天前
PostgreSQL 分区表排序优化:Append Sort 优化为 Merge Append
大数据·数据库·postgresql
大数据新鸟1 天前
操作系统之虚拟内存
java·服务器·网络