图书管理系统(1)项目准备,用户登录接口,添加图书接口

综合案例1:图书管理系统(1)项目准备,用户登录接口,添加图书接口

文章目录

观前提醒:

这个图书管理系统,非常的简陋,仅作为练习使用。不建议大家使用我介绍的 图书管理系统 ,去作为 课程设计。

无Mybatis版本获取:

这个 图书管理系统 的实现,需要你从我的 gitee上,将 无Mybatis版本 的图书管理系统源代码下载下来。

gitee链接:https://gitee.com/mrbgvhbhjv/java-ee-course/tree/master/后端代码/springboot_bookManage_System

基于 Mybatis版本 的获取:

如果你想直接获取 基于 Mybatis 完全实现了增删查改功能的图书管理系统,就从我的 gitee 上面获取,下载源代码。

gitee链接:https://gitee.com/mrbgvhbhjv/java-ee-course/tree/master/后端代码/Book_System_20251107

个人建议:

这篇博客的代码,建议自己敲一下。

跟着我的步骤,使用 无Mybatis版本 的图书管理系统源代码 ,一步一步的将代码实现出来。

这里使用的数据库是 MySQL。

图形化工具:Navicat

虽然,截止到 2026年,cursor,TRAE等 AI工具,能够一键编写代码,甚至一个系统。

但是,如果你不会基础知识,只会让 ai 生成代码,不会看代码,不会修改代码,代码运行报错,你不会解决问题等等。其实,你都不算是一个 Java开发者。

如果我们将 AI工具,对我们的工作效率提升,有巨大的帮助,它一定是一个乘法结算的结果。

你的基础开发能力 * AI工具效率 == 工作效率。

如果你的基础开发能力不行,约等于 0,那么,无论 AI工具效率多高,99倍也好,0 * 99 == 0

所以,只有将我们自己的基础开发能力提升了,使用 AI工具提升效率,才是事半功倍的效果。

1. 准备工作:

一个项目的开发,可以大致分为这么几个流程:

开发阶段的设计:首先是架构设计,然后是功能设计,再是数据库设计,最后是接口设计。

接口设计,我们每一个功能模块,会单独说。

1.1 数据库设计:

数据库的表,分为两类表:

  1. 实体表
  2. 关系表

实体表:对应的是 Java对象,学生类对应学生表,还有班级表,课程表等等。

关系表:实体和实体之间的关系,例如:一个班级对应多个学生,一个课程对应多个学生。

实体间关系类型

  1. 一对一
  2. 一对多(多对一)
  3. 多对多

什么时候需要创建关系表?

答:当两个实体表的的关系为 多对多 的时候,就必须设计关系表

1.2 建立表,存储信息

创建数据库,创建图书表和用户表:

sql 复制代码
-- 创建数据库
DROP DATABASE IF EXISTS book_test;
CREATE DATABASE book_test DEFAULT CHARACTER SET utf8mb4;

-- 用户表
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) VALUES ('活
着', '余华', 29, 22.00, '北京⽂艺出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('平凡的
世界', '路遥', 5, 98.56, '北京⼗⽉⽂艺出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('三
体', '刘慈欣', 9, 102.67, '重庆出版社');
INSERT INTO `book_info` (book_name,author,count, price, publish) VALUES ('⾦字塔
原理', '⻨肯锡', 16, 178.00, '⺠主与建设出版社');

重新编写 UserInfo类:

java 复制代码
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;
}

重新编写 BookInfo类:

java 复制代码
import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;


@Data
public class BookInfo {
    //图书ID
    private 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;
}

由于 BookInfo类 的信息,发生变化,前端代码,也要进行修改:

修改前端代码:

html 复制代码
function getBookList ()
{
  $.ajax({
    type: "get",
    url: "/book/getList",
    success: function (books)
    {
      var wholeHtml = "";
      for (var book of books) {
        // 拼接列表
        wholeHtml += '<tr>';
        wholeHtml += '<td><input type="checkbox" name="selectBook" value="' + book.bookID + '" id="selectBook" class="book-select"></td>';
        wholeHtml += '<td>' + book.id + '</td>';
        wholeHtml += '<td>' + book.bookName + '</td>';
        wholeHtml += '<td>' + book.author + '</td>';
        wholeHtml += '<td>' + book.count + '</td>';
        wholeHtml += '<td>' + book.price + '</td>';
        wholeHtml += '<td>' + book.publish + '</td>';
        wholeHtml += '<td>' + book.statusCN + '</td>';
        wholeHtml += '<td><div class="op">';
        wholeHtml += '<a href="book_update.html bookId=' + book.bookID + '">修改</a>';
        wholeHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.bookID + ')">删除</a>';
        wholeHtml += '</div></td></tr>';
      }
      $("tbody").html(wholeHtml);
    }
  });
}

1.3 引入依赖

引入 Mybatis框架的依赖,Mysql的驱动依赖:

java 复制代码
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>3.0.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

1.4 编写配置文件

此处,我们使用的是 yml配置文件:

yaml 复制代码
spring:
  application:
    name: book_system

  # 数据库连接配置
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
    username: 数据库用户名
    password: 数据库用户对应的密码
    driver-class-name: com.mysql.cj.jdbc.Driver

# 配置打印 MyBatis⽇志
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #配置驼峰⾃动转换
    map-underscore-to-camel-case: true
  # XML文件映射
  mapper-locations: classpath:mapper/**Mapper.xml

2. 后端代码编写

2.1 用户登陆接口:

接口文档:

mapper层(数据持久层):

java 复制代码
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.book_system_20251107.book.modul.UserInfo;

@Mapper
public interface UserInfoMapper {

    @Select("select * from user_info where user_name = #{userName} and delete_flag = 0")
    UserInfo selectUserByUserName(String userName);

}

Service层(业务层):

java 复制代码
import org.example.book_system_20251107.book.mapper.UserInfoMapper;
import org.example.book_system_20251107.book.modul.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserInfoService {

    @Autowired
    private UserInfoMapper userInfoMapper;

    public UserInfo selectUserInfoByUserName(String userName) {
        return userInfoMapper.selectUserByUserName(userName);
    }
}

Controller层:

java 复制代码
import jakarta.servlet.http.HttpSession;
import org.example.book_system_20251107.book.modul.UserInfo;
import org.example.book_system_20251107.book.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserInfoService userInfoService;


    @RequestMapping("/login")
    public Boolean login(String name, String password, HttpSession session) {
		//1. 验证输入参数是否为空
        if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)) {
            return false;
        }

//        根据用户名,获取用户信息
        UserInfo userInfo = userInfoService.selectUserInfoByUserName(name);

        if (userInfo == null) {
//          userInfo == null 表示: 查询不到用户,就返回 false
            return false;
        }
        
        //2.匹配用户名和密码
        if (password.equals(userInfo.getPassword())){
                //3. 存储 Session
//            防止密码泄露,此处的 userInfo对象,密码设置为 null,但是不影响数据库中的数据
            userInfo.setPassword("");
//            密码为 "",其他人无法通过session,获取到用户密码
            session.setAttribute("user", userInfo);
            return true;
        }



//        没有学数据库相关的操作,写死用户名和密码
//        if ("admin".equals(name) && "admin".equals(password)) {
//            session.setAttribute("username", name);
//            return true;
//        }

        //4. 返回结果
        return false;
    }

}

接口测试:

Postman:
网页:

可以登录成功:

2.2 添加图书接口:

添加图书,并不需要输入图书id,id是自增的。

接口文档描述:

请求

/book/addBookContent-Type: application/x-www-form-urlencoded; charset=UTF-8

参数

bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1

响应

"" //失败信息, 成功时返回空字符串

mapper层(数据持久层):

java 复制代码
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.example.book_system_20251107.book.modul.BookInfo;


@Mapper
public interface BookInfoMapper {

    @Insert("insert into book_info(book_name,author,count,price,publish) " +
            "values (#{bookName},#{author},#{count},#{price},#{publish})")
    Integer insertBookInfo(BookInfo bookInfo);

}

Service层(业务层):

java 复制代码
    /**
     * 添加图书
     * @param bookInfo
     * @return 返回值表示添加的行数
     */
    public Integer addBook(BookInfo bookInfo){
        return bookInfoMapper.insertBookInfo(bookInfo);
    }

Controller层:

有这么几个需要编写的环节:

  1. 参数校验

  2. 存储数据

  3. 返回结果

参数校验问题:

参数校验流程:先执行参数校验,校验通过后再执行图书添加操作。

在实际开发中,无论前端是否已做参数校验,后端都必须强制进行参数校验。

原因:后端接口可能遭受黑客攻击,攻击者可绕过前端直接请求接口。若后端不做校验,极易产生脏数据,影响系统数据安全与稳定性。

例如:爬虫(获取数据),攻击(伤害服务器)

java 复制代码
    @RequestMapping("/addBook")
    public String addBook(BookInfo bookInfo){
//        步骤:
//        1.参数验证
//        2.存储数据
//        3.返回结果

//        此处的参数校验,十分粗糙,只判断了参数是否为空,
//        并没有考虑 Integer参数,最大值是否溢出,String参数,是否超过最大字符串限制等等
// 			hasLength:数据为空,返回 false,数据不为空,返回 true
        if (!StringUtils.hasLength(bookInfo.getBookName())){
            return "添加图书,书名为空。";
        }

        if (!StringUtils.hasLength(bookInfo.getAuthor())){
            return "添加图书,作者名为空。";
        }

        if (bookInfo.getCount() == null){
            return "添加图书,图书数量为空。";
        }

        if (bookInfo.getPrice() == null){
            return "添加图书,图书价格为空。";
        }

        if (!StringUtils.hasLength(bookInfo.getPublish())){
            return "添加图书,图书出版社为空。";
        }

//        数据存储:
            try {
                bookService.addBook(bookInfo);
                return "";
            }catch (Exception e){
                return ("添加图书发生异常,e:" + e.getMessage());
            }
    }

接口测试:

Postman:
问题:程序报错,XXX为空

我们可以进行调试,打断点。

F8 是下一步,F9 是退出调试。

我们还可以通过鼠标右键,选择某一块代码,去查看这块代码的结果:

参数不合法的 原因:没有传入 publish

解决:传入 publish
网页(须修改前端代码):

后端开发,不关注前端是如何设计的,所以,前端代码,不进行讲解,需要掌握的是:学会如何使用 ajax发送请求。

book_add.html 前端代码:
html 复制代码
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script>
        function add() {
            //前端应该进行参数校验,此处省略
            //提交请求到后端
            $.ajax({
                type: "post",
                url: "/book/addBook",
                data: $("#addBook").serialize(),
                success: function(result) {
                    if (result == "") {
                        //添加图书成功
                        location.href = "book_list.html";
                    } else {
                        //失败
                        alert(result);
                    }
                }
            });
        }
    </script>

测试:

3. 打印日志 & 调试:

3.1 打印日志

我们可以在上述程序中,添加日志,记录程序的运行信息:

使用 @Slf4j注解

打印日志的方式:

留意 log.info() 的两种方式。

一般来说,输出的参数,都是方法的参数,异常信息除外。

3.2 调试:

代码出现问题,要学会调试:

调试,首先要打断点,然后选择调试程序。

F8 是下一步,F9 是退出调试。

我们还可以通过鼠标右键,选择某一块代码,去查看这块代码的结果:

参数不合法的 原因:没有传入 publish

4. 遗留问题:

第一个:为什么是通过用户名获取数据

代码:

java 复制代码
//Mapper层
@Select("select * from user_info where user_name = #{userName} and delete_flag = 0")
UserInfo selectUserByUserName(String userName);

//Controller层:
      @RequestMapping("/login")
    public Boolean login(String name, String password, HttpSession session) {
//        1. 验证是否为空
        //2.匹配用户名和密码
        //3. 存储 Session
        //4. 返回结果
        if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)) {
            return false;
        }

//        根据用户名,获取用户信息
        UserInfo userInfo = userInfoService.selectUserInfoByUserName(name);

        if (userInfo == null) {
//          userInfo == null 表示: 查询不到用户,就返回 false
            return false;
        }

        if (password.equals(userInfo.getPassword())){
//            防止密码泄露,此处的 userInfo对象,密码设置为 null,但是不影响数据库中的数据
            userInfo.setPassword("");
//            密码为 "",其他人无法通过session,获取到用户密码
            session.setAttribute("user", userInfo);
            return true;
        }

//        没有学数据库相关的操作,写死用户名和密码
//        if ("admin".equals(name) && "admin".equals(password)) {
//            session.setAttribute("username", name);
//            return true;
//        }

        return false;
    }  

为什么是通过用户名获取数据,可以直接按照用户名密码来查找,这样返回值不为空不就是 true,就不用在 Controller代码中,验证密码了?

答:当前代码,可以这样写。

但是,企业开发中,数据库关于用户的密码,存储的方式,一般是密文存储 ,通过密码是查询不出来数据的。

如果是加密的数据库,我们建议使用 用户名 来查询数据,并对其他字段(如 密码),进行解密,然后单独验证 密码 等敏感信息。

数据库是可以加密的,这也是为了防止网络的攻击。

第二个问题:登录成功之后,为什么密码要设置为 空

代码:

java 复制代码
        if (password.equals(userInfo.getPassword())){
//            防止密码泄露,此处的 userInfo对象,密码设置为 null,但是不影响数据库中的数据
            userInfo.setPassword("");
//            密码为 "",其他人无法通过session,获取到用户密码
            session.setAttribute("user", userInfo);
            return true;
        }

可以不设置,因为 session 是存储在服务端的内存中的。

但是,密码要怎么设置,怎么保护这个敏感信息,要看后端如何实现。

session,可以通过技术手段,放到 redis 中。

redis 是一个 缓存中间件,通常也不放敏感数据,如果要放,也是要加密的。

5. 总结:

这篇博客,主要是对 无Mybatis版本 的图书管理系统,进行了部分代码的重新编写。

最后,如果这篇博客能帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!

下一篇博客:综合案例1:图书管理系统(2)图书列表接口

相关推荐
kong79069282 小时前
SpringBoot原理
java·spring boot·后端
那我掉的头发算什么2 小时前
【图书管理系统】基于Spring全家桶的图书管理系统(下)
java·数据库·spring boot·后端·spring·mybatis
前路不黑暗@13 小时前
Java项目:Java脚手架项目的文件服务(八)
java·开发语言·spring boot·学习·spring cloud·docker·maven
计算机毕设vx_bysj686921 小时前
计算机毕业设计必看必学~基于SpringBoot校园招聘系统的设计与实现,原创定制程序、单片机、java、PHP、Python、小程序、文案全套、毕设成品等!
java·spring boot·mysql·课程设计
Moshow郑锴1 天前
Java SpringBoot 疑难 Bug 排查思路解析:从“语法正确”到“行为相符”
java·spring boot·bug
J2虾虾1 天前
使用Springboot Integration做无人机飞控系统
spring boot·后端·无人机
中国胖子风清扬1 天前
GPUI 在 macOS 上编译问题排查指南
spring boot·后端·macos·小程序·rust·uni-app·web app
kong79069281 天前
SpringBoot Rest风格 API
java·spring boot·后端