图书管理系统(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)图书列表接口

相关推荐
小飞Coding2 天前
Spring Boot 中关于 Bean 加载、实例化、初始化全生命周期的扩展点
spring boot
小飞Coding2 天前
彻底搞懂 Spring 容器导入配置类:@EnableXXX 与 spring.factories 核心原理
spring boot
悟空码字3 天前
Spring Boot 整合 MongoDB 最佳实践:CRUD、分页、事务、索引全覆盖
java·spring boot·后端
皮皮林5514 天前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
用户908324602737 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840828 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解8 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解8 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记8 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者9 天前
Kafka 基础介绍
spring boot·kafka·消息队列