【实战】动态 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,  //当前页码
            //....
        });
    }
}
相关推荐
Codebee10 分钟前
Ooder A2UI框架开源首发:构建企业级应用的全新选择
java·人工智能·全栈
倔强的石头1061 小时前
【金仓数据库】ksql 指南(五) —— 创建与管理索引和视图(KingbaseES 查询优化核心)
数据库·oracle·kingbase
数据知道1 小时前
PostgreSQL 的开源扩展:高效存储和检索向量数据的 PGVector 的详细使用
数据库·postgresql·开源
程序员三明治1 小时前
【重学计网】TCP如何保证可靠传输?怎么保证可靠性?可靠传输的原理?
java·网络·后端·网络协议·tcp/ip·tcp·可靠传输
前进的李工7 小时前
SQL聚合函数与分组查询详解
数据库·sql·mysql
Nonoas8 小时前
动态代理:发布订阅的高级玩法
java·ide·intellij-idea
程序员-周李斌9 小时前
Java 死锁
java·开发语言·后端
2301_800050999 小时前
mysql
数据库·笔记·mysql
数据皮皮侠9 小时前
2m气温数据集(1940-2024)
大数据·数据库·人工智能·制造·微信开放平台
皮皮林5519 小时前
Prometheus+Grafana,打造强大的监控与可视化平台
java