java 项目开发架构图

先对整体进行分析、规划

数据库表之间的关系

1
2
3
4
5
一共有五张表分别为 books(图书表)、bookrack(书架表)、borrow(借阅记录表)、reader(用户表)、blacklist(黑名单表)

图书表与书架表关系为 一个书架可以有多本书,一本书只能在一个书架上放着,是一对多的关系。
图书表与用户表是多对多的关系,一本书可以被多个人借阅,一个人可以借阅多本书,所以是多对多的关系,它们之间的联系 "借阅" 又可以单独成一张借阅记录表。
用户表与黑名单表之间的关系为 多对一 的关系,一个用户只能在在黑名单中出现一次,一张黑名单能记录多个进入黑名单的用户
功能需求分析:

利用 UML 图先对图书管理系统的功能进行分析

界面规划

根据功能序需求,分为 8 个页面来实现

下面开始设计界面的草图

配置数据库

利用 navicat 连接本地 MySQL8,连接上去后执行以下下命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
create database library;

use library;

-- 一个书架只能有一个房间号,一个房间可以有多个书架
create table bookrack
(
bookrack_id int(11) primary key,
room_id int(11),

unique(room_id,bookrack_id)
);

create table books
(
book_id int(11) auto_increment primary key,
book_name varchar(255) unique,
publication_date datetime(0),
publisher varchar(255),
bookrack_id int(11),
status varchar(255),

foreign key(bookrack_id) references bookrack(bookrack_id)
);

create table reader
(
borrow_book_id int(11) auto_increment primary key,
name varchar(20) not null unique,
age int(11),
sex varchar(255),
address varchar(255),
password varchar(255) not null,
role int(11) DEFAULT 1
);

-- 一本书不能被多次被借阅
create table borrow
(
borrow_book_id int(11),
book_id int(11),
borrow_date datetime(0),
return_date datetime(0),

primary key(book_id),
foreign key(book_id) references books(book_id),
foreign key(borrow_book_id) references reader(borrow_book_id),
unique(borrow_book_id,book_id)
);

create table blacklist
(
borrow_book_id int(11) primary key,
name varchar(255),
reason varchar(255),

foreign key(borrow_book_id) references reader(borrow_book_id),
foreign key(name) references reader(name)
);

-- 创建三个用户分别为(普通用户、管理员、黑名单用户)
insert into reader(name,age,sex,address,password,role)
values('bbdolt',20,'男','武汉','4c79273eed3d095e55d1224f6524ae92',1),('admin',20,'男','moon','4c79273eed3d095e55d1224f6524ae92',0),('blackman',99,'男','河南','4c79273eed3d095e55d1224f6524ae92',1);

-- 创建6个书架
insert into bookrack(bookrack_id,room_id)
values(1,1),(2,1),(3,1),(4,2),(5,2),(6,2);

-- 添加图书
insert into books(book_name,publication_date,publisher,bookrack_id,status)
values('狼王梦','1970-1-1','BB社','1','可借阅'),('查理','1970-1-1','BB社','2','已借阅'),('test1','1970-1-1','BB社','1','可借阅'),('test2','1970-1-1','BB社','1','可借阅'),('test3','1970-1-1','BB社','1','可借阅'),('test4','1970-1-1','BB社','1','可借阅');

-- 添加借阅记录
insert into borrow(borrow_book_id,book_id,borrow_date,return_date)
values(1,2,'2024-11-30','2024-12-30');

-- 添加黑名单用户(黑名单用户不能借阅书)
insert into blacklist(borrow_book_id,name,reason)
values(3,'blackman','逾期未归还图书');
insert into blacklist(borrow_book_id,name,reason)
values(1,'bbdolt','逾期未归还图书');
insert into blacklist(borrow_book_id,name,reason)
values(5,'test','逾期未归还图书');

开始创建项目

选择 Spring Initializr ,用来创建 SpringBoot 工程
这种方式构建的 SpringBoot 工程其实也是 Maven 工程

选中 Web,然后勾选 Spring Web
由于我们需要开发一个 web 程序,使用到了 SpringMVC 技术,所以按照下图红框进行勾选

经过以上步骤后就创建了如下结构的模块,它会帮我们自动生成一个 Application 类,而该类一会再启动服务器时会用到

配置 application.properties 文件(配置与本地数据库进行连接并设置服务端口)

src/main/resources/application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 配置端口号为8081
server.port=808
# 配置数据库
# 配置驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 配置数据库URL,确保数据库的名称是library
spring.datasource.url=jdbc:mysql://localhost:3306/library?serverTimezone=UTC
# 数据库用户名
spring.datasource.username=root
# 数据库密码
spring.datasource.password=Admin123
# JPA
# spring.jpa.show-sql=true
# spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
# spring.jpa.hibernate.ddl-auto=update
根据数据库中的表定义实体类 Domain(一共 5 个)

src/main/java/top/cengweiye/library/domain/Bookrack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package top.cengweiye.library.domain;
import javax.persistence.*;

@Entity
@Table(name = "bookrack")
public class Bookrack {

@Id
@Column(name = "bookrack_id")
private int bookrackId;

@Column(name = "room_id")
private int roomId;

public void setBookrackId(int bookrackId) {
this.bookrackId = bookrackId;
}

public int getRoomId() {
return roomId;
}

public void setRoomId(int roomId) {
this.roomId = roomId;
}
public int getBookrackId() {
return bookrackId;
}
}

src/main/java/top/cengweiye/library/domain/Book

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package top.cengweiye.library.domain;
import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "book_id")
private int bookId;

@Column(name = "book_name", unique = true)
private String bookName;

@Column(name = "publication_date")
private Date publicationDate;

@Column(name = "publisher")
private String publisher;

@Column(name = "bookrack_id")
private int bookrackId;

@Column(name="status")
private String status;

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

// getters and setters
public int getBookId() {
return bookId;
}

public void setBookId(int bookId) {
this.bookId = bookId;
}

public String getBookName() {
return bookName;
}

public void setBookName(String bookName) {
this.bookName = bookName;
}

public Date getPublicationDate() {
return publicationDate;
}

public void setPublicationDate(Date publicationDate) {
this.publicationDate = publicationDate;
}

public String getPublisher() {
return publisher;
}

public void setPublisher(String publisher) {
this.publisher = publisher;
}

public int getBookrackId() {
return bookrackId;
}

public void setBookrackId(int bookrackId) {
this.bookrackId = bookrackId;
}
}

src/main/java/top/cengweiye/library/domain/Reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package top.cengweiye.library.domain;
import javax.persistence.*;

@Entity
@Table(name = "reader")
public class Reader {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "borrow_book_id")
private int borrowBookId;

@Column(name = "name", unique = true, nullable = false)
private String name;

@Column(name = "age")
private int age;

@Column(name = "sex")
private String sex;

@Column(name = "address")
private String address;

@Column(name = "password", nullable = false)
private String password;

public int getRole() {
return role;
}

public void setRole(int role) {
this.role = role;
}

@Column(name = "role")
private int role = 1;

// getters and setters
public int getBorrowBookId() {
return borrowBookId;
}

public void setBorrowBookId(int borrowBookId) {
this.borrowBookId = borrowBookId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

src/main/java/top/cengweiye/library/domain/Borrow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package top.cengweiye.library.domain;
import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "borrow")
public class Borrow {
@Column(name = "borrow_book_id")
private int borrowBookId;

public int getBookId() {
return bookId;
}

public void setBookId(int bookId) {
this.bookId = bookId;
}

@Id
@Column(name = "book_id")
private int bookId;

@Column(name = "borrow_date")
private LocalDateTime borrowDate;

@Column(name = "return_date")
private LocalDateTime returnDate;

public int getBorrowBookId() {
return borrowBookId;
}

public void setBorrowBookId(int borrowBookId) {
this.borrowBookId = borrowBookId;
}

public LocalDateTime getBorrowDate() {
return borrowDate;
}

public void setBorrowDate(LocalDateTime borrowDate) {
this.borrowDate = borrowDate;
}

public LocalDateTime getReturnDate() {
return returnDate;
}

public void setReturnDate(LocalDateTime returnDate) {
this.returnDate = returnDate;
}
}

src/main/java/top/cengweiye/library/domain/Blacklist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package top.cengweiye.library.domain;
import javax.persistence.*;

@Entity
@Table(name = "blacklist")
public class Blacklist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "borrow_book_id")
private int borrowBookId;

@Column(name = "name")
private String name;

@Column(name = "reason")
private String reason;

public int getBorrowBookId() {
return borrowBookId;
}

public void setBorrowBookId(int borrowBookId) {
this.borrowBookId = borrowBookId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getReason() {
return reason;
}

public void setReason(String reason) {
this.reason = reason;
}
}
处理跨域访问问题

src/main/java/top/cengweiye/library/config/GlobalCorsConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
跨域问题可以简单理解成如果你的前端项目的IP地址和端口号和后端的IP地址和端口号不一样,就会导致前端无法获取到数据,这是一个规定。而在前后端分离开发的项目中,前后端项目的端口号一般都是不一样的,假设我们这个项目的前端端口号是 8080,后端端口号是 8081,就会造成跨域访问的问题,跨域访问的问题可以在前端解决也可以在后端解决,后端只要加上一个配置文件就行了

在config文件下创建全局跨域配置类GlobalCorsConfig.java

注意!!! :SpringBoot2.4.0 以后下方 allowedOrigins 需要被 allowedOriginPatterns 代替!!!!
*/

package top.cengweiye.library.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class GlobalCorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //添加映射路径,“/**”表示对所有的路径实行全局跨域访问权限的设置
.allowedOriginPatterns("*") //开放哪些ip、端口、域名的访问权限 SpringBoot2.4.0以后allowedOrigins被allowedOriginPatterns代替
.allowCredentials(true) //是否允许发送Cookie信息
.allowedMethods("GET", "POST", "PUT", "DELETE") //开放哪些Http方法,允许跨域访问
.allowedHeaders("*") //允许HTTP请求中的携带哪些Header信息
.exposedHeaders("*"); //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
}
};
}
}

下面开始先实现 登录/注册/注销功能

定义数据访问对象 ReaderDao(访问 Reader 表)

src/main/java/top/cengweiye/library/repository/ReaderDao.java(接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package top.cengweiye.library.repository;
import top.cengweiye.library.domain.Reader; // 导入Reader实体类
import org.springframework.data.jpa.repository.JpaRepository; // 导入JpaRepository接口
import org.springframework.stereotype.Repository; // 导入@Repository注解

// 使用@Repository注解标记该接口为一个Spring Data JPA的仓库接口
@Repository
public interface ReaderDao extends JpaRepository<Reader, Long> {
//此方法遵循Spring Data JPA的命名约定,Spring Data JPA会根据方法名自动生成查询。
Reader findByName(String name);
//此方法遵循Spring Data JPA的命名约定,Spring Data JPA会根据方法名自动生成查询。
Reader findByNameAndPassword(String name, String password);
Reader findByBorrowBookId(int borrowBookId);
}

由于我们只实现登录注册功能,所以只要有根据账号密码查询用户和插入用户信息的方法就行了,这里我们已经实现了根据用户名密码查找用户的方法,而插入用户信息的方法save(object o)JPA已经帮我们实现了,可以直接调用,这里就不需要写了。
实现工具类 Result

工具类 Result 的作用是作为返回给前端的统一后的对象。也就是说返回给前端的都是 Result 对象,只是对象中的属性不太一样,这样方便前端固定接收格式。
src/main/java/top/cengweiye/library/Utilities/Result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package top.cengweiye.library.Utilities;

public class Result<T> {
private String code;
private String msg;
private T data;

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public Result() {
}

public Result(T data) {
this.data = data;
}

public static Result<Void> success() {
Result<Void> result = new Result<>();
result.setCode("0");
result.setMsg("成功");
return result;
}

public static <T> Result<T> success(T data) {
Result<T> result = new Result<>(data);
result.setCode("0");
result.setMsg("成功");
return result;
}

public static <T> Result<T> success(T data,String msg) {
Result<T> result = new Result<>(data);
result.setCode("0");
result.setMsg(msg);
return result;
}

public static <T> Result<T> error(String code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
配置 jwt 生成、验证 token

src/main/java/top/cengweiye/library/Utilities/TokenUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package top.cengweiye.library.Utilities;
import io.jsonwebtoken.*;
import java.util.Date;

public class TokenUtil {
// 设置Token的过期时间(例如:1小时)
private static final long EXPIRATION_TIME = 7 * 24 * 60 * 60 * 1000L;
private static final String SECRET_KEY ="MTIzNDU2"; // 用于加密的密钥

// 生成Token的方法
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username) // 设置JWT的主体,通常是用户名
.setIssuedAt(new Date()) // 设置Token的签发时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 设置Token的过期时间
.signWith(SignatureAlgorithm.HS512, SECRET_KEY) // 设置签名算法和密钥
.compact();
}

// 验证Token的方法
public static boolean verify(String token) {
if (TokenBlacklist.isTokenBlacklisted(token)) {
System.out.println("Token已注销");
return false;
}
try {
Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token); // 验证Token的签名
return true;
} catch (ExpiredJwtException e) {
System.out.println("Token已过期");
} catch (JwtException e) {
System.out.println("无效的Token:" + e.getMessage());
System.out.println(token);
}
return false;
}

// 获取Token中的用户名(作为用户的标识)
public static String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
return claims.getSubject(); // 获取JWT中的用户名
}
}
添加工具类 token 黑名单(实现注销功能)

src/main/java/top/cengweiye/library/Utilities/TokenBlacklist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package top.cengweiye.library.Utilities;
import java.util.HashSet;
import java.util.Set;

public class TokenBlacklist {
private static final Set<String> blacklistedTokens = new HashSet<>();
// 将Token加入黑名单
public static void blacklistToken(String token) {
blacklistedTokens.add(token);
}
// 检查Token是否在黑名单中
public static boolean isTokenBlacklisted(String token) {
return blacklistedTokens.contains(token);
}
}
定义业务逻辑(实现登录注册的核心代码的接口)

src/main/java/top/cengweiye/library/service/ReaderSevice.java

1
2
3
4
5
6
7
8
9
10
package top.cengweiye.library.service;
import org.springframework.data.domain.Page;
import top.cengweiye.library.domain.Reader;

public interface ReaderService {
Reader loginService(String name, String password);
//注册服务
Reader registService(Reader reader);
Page<Reader> getReaderList(int pageNum, int pageSize);
}

src/main/java/top/cengweiye/library/service/serviceImpl/ReaderServiceImpl(我们将在 ReaderServiceImpl 中实现 ReaderService 中的方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*在Java编程语言中,implements 关键字用于表示一个类实现了某个接口(interface)。接口是一种抽象类型,它定义了一组方法,但是不提供这些方法的实现。当类使用 implements 关键字后面跟着接口名称时,它意味着这个类必须实现接口中定义的所有方法。
@Override 是 Java 语言中的一个注解,用于修饰方法,表示这是一个覆盖(override)父类方法的方法。
*/
package top.cengweiye.library.service.serviceImpl;

import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.repository.ReaderDao;
import top.cengweiye.library.service.ReaderService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

public class ReaderServiceImpl implements ReaderService{
@Override
public Reader loginService(String name, String password) {
//下面是实现的具体方法
return null;
}
@Override
public Reader registService(User user) {
return null;
}
}

//因为要用到UserDao中的方法,所以先通过@Resource注解帮助我们实例化UserDao对象
//登录逻辑如下:

//什么是Bean,是一个由Spring容器创建、管理和配置的对象。一个Bean通常是一个简单的Java类,它被Spring容器实例化、组装并管理。这个类通常会有一些注解或配置信息,告诉Spring如何创建和管理这个类的实例。

//Bean可以通过XML配置文件、Java注解或Java配置类来定义。例如,使用@Component、@Service、@Repository、@Controller等注解标记的类

//@Resource 注解没有指定 name 或 type,所以容器会尝试找到一个名为 userDao 的Bean,并将其注入到 UserServiceImpl 类的 userDao 字段中。

//完整代码如下:
package top.cengweiye.library.service.serviceImpl;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.repository.ReaderDao;
import top.cengweiye.library.service.ReaderService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class ReaderServiceImpl implements ReaderService {
@Resource
private ReaderDao readerDao;
@Override
public Reader loginService(String name, String password) {
// 如果账号密码都对则返回登录的用户对象,若有一个错误则返回null
Reader reader = readerDao.findByNameAndPassword(name, password);
// 重要信息置空
if(reader != null){
reader.setPassword("");
}
return reader;
}
@Override
public Reader registService(Reader reader) {
//当新用户的用户名已存在时
if(readerDao.findByName(reader.getName())!=null){
// 无法注册
return null;
}else{
//返回创建好的用户对象(带uid)
return readerDao.save(reader);
}
}
@Override
public Page<Reader> getReaderList(int pageNum, int pageSize){
return readerDao.findAll(PageRequest.of(pageNum, pageSize));
}
}
定义控制器(注册/登录/注销部分)

src/main/java/top/cengweiye/library/Controller/ReaderController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package top.cengweiye.library.Controller;
import org.springframework.web.bind.annotation.*;
import top.cengweiye.library.Utilities.Result;
import top.cengweiye.library.Utilities.TokenBlacklist;
import top.cengweiye.library.Utilities.TokenUtil;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.service.ReaderService;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Cookie;

@RestController
@RequestMapping("/reader")
public class ReaderController {
@Resource
private ReaderService readerService;

@PostMapping("/login")
public Result<Reader> loginController(@RequestBody Reader newUser,HttpServletResponse response){
Reader user = readerService.loginService(newUser.getName(), newUser.getPassword());
if(user!=null){
String token = TokenUtil.generateToken(user.getName());
// 返回Token给前端,可以通过Cookie或响应头设置
Cookie cookie = new Cookie("Authorization","Bearer" + token);
cookie.setHttpOnly(true); // 防止客户端 JavaScript 访问,提高安全性
cookie.setSecure(true); // 仅在 HTTPS 下传输(生产环境中应启用)
cookie.setPath("/"); // 设置路径为根目录,确保整个应用都能访问
cookie.setMaxAge(60 * 60 * 24 * 7); // 设置过期时间为 7 天(根据需要调整)

// 将 Cookie 添加到响应中
response.addCookie(cookie);
return Result.success(user, "登录成功!");

}else{
return Result.error("401","账号或密码错误!");
}
}
@PostMapping("/register")
public Result<Reader> registController(@RequestBody Reader newUser){
Reader user = readerService.registService(newUser);
if(user!=null){
return Result.success(user,"注册成功!");
}else{
return Result.error("409","用户名已存在!");
}
}
@PostMapping("/logout")
public Result<String> logoutController(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("Authorization".equals(cookie.getName())) {
String token = cookie.getValue();
if (token != null) {
token = token.substring(6);
if (TokenUtil.verify(token)) {
TokenBlacklist.blacklistToken(token);
}
}
}
}
}
return Result.success("成功退出登录!");
}
}
配置拦截器(拦截规则)

src/main/java/top/cengweiye/library/Config/WebConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package top.cengweiye.library.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.cengweiye.library.interceptor.VisitInterceptor;

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public VisitInterceptor visitInterceptor() {
return new VisitInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(visitInterceptor())
.addPathPatterns("/**") // 拦截页面
.excludePathPatterns(
"/reader/login",
"/reader/register",
"/login.html",
"/register.html",
"/**/*.js", // 排除所有.js结尾的静态资源
"/**/*.css", // 排除所有.css结尾的静态资源
"/**/*.png", // 排除所有.png结尾的图片资源
"/**/*.jpg", // 排除所有.jpg结尾的图片资源
"/**/*.jpeg" // 排除所有.jpeg结尾的图片资源
); // 排除登录和注册接口
}
}
拦截器(用于拦截没有登录的用户,preHandle 在到达处理器之前拦截)

src/main/java/top/cengweiye/library/interceptor/VisitInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package top.cengweiye.library.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import top.cengweiye.library.Utilities.TokenUtil;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class VisitInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求头获取Token
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("Authorization".equals(cookie.getName())) {
String token = cookie.getValue();
if (token != null) {
token = token.substring(6);
if (TokenUtil.verify(token)) {
// Token有效,可以继续访问
return true;
}
}
}
}
}
// 如果Token无效,跳转到登录页面
response.sendRedirect("/login.html");
return false;
}
}
前端页面

src/main/resource/static/login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>登录</title>
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/login.css" />
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>

<body>
<div class="container-login">
<div class="container-pic">
<img src="images/computer.png" width="350px" />
</div>
<div class="login-dialog">
<h3>登陆</h3>
<div class="row">
<span>用户名</span>
<input type="text" name="Name" id="Name" class="form-control" />
</div>
<div class="row">
<span>密码</span>
<input
type="password"
name="password"
id="password"
class="form-control"
/>
</div>
<div class="register">
没有账号?<a href="register.html">点击注册</a>
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="login()">
登录
</button>
</div>
</div>
</div>

<script src="js/jquery.min.js"></script>
<script>
function login() {
let name = document.getElementById("Name").value;
let password = document.getElementById("password").value;

fetch("http://localhost:8081/reader/login", {
// 确保地址端口正确
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name, password }),
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error("登录失败");
}
})
.then((data) => {
alert(data.msg); // 显示成功或失败信息
if (data.code === "0") {
window.location.href = "/";
}
// 在这里处理登录成功后的操作,例如页面跳转
})
.catch((error) => console.error(error));
}
</script>
</body>
</html>

src/main/resource/static/register.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/login.css" />
<script type="text/javascript" src="js/jquery.min.js"></script>
<style>
.login-dialog {
width: 400px;
height: 500px;
}
.container-login {
width: 750px;
height: 500px;
}
</style>
<title>注册</title>
</head>
<body>
<div class="container-login">
<div class="container-pic">
<img src="images/computer.png" width="350px" alt="" />
</div>
<div class="login-dialog">
<h3>注册</h3>
<div class="row">
<label for="Name">用户名</label>
<input type="text" name="Name" id="Name" class="form-control" />
</div>
<div class="row">
<label for="age">年龄</label>
<input type="text" name="age" id="age" class="form-control" />
</div>
<div class="row">
<label for="sex">性别</label>
<input type="text" name="sex" id="sex" class="form-control" />
</div>
<div class="row">
<label for="address">地址</label>
<input type="text" name="address" id="address" class="form-control" />
</div>
<div class="row">
<label for="password">密码</label>
<input
type="password"
name="password"
id="password"
class="form-control"
/>
</div>
<div class="row">
<label for="confirmPassword">确认密码:</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
required
/>
</div>
<div class="row">
<button
type="button"
class="btn btn-info btn-lg"
onclick="register()"
>
注册
</button>
</div>
</div>
</div>

<script src="js/jquery.min.js"></script>
<script>
function register() {
let password = document.getElementById("password").value;
let confirmPassword = document.getElementById("confirmPassword").value;
if (password !== confirmPassword) {
alert("两次密码不一致");
return;
}
let uname = document.getElementById("Name").value;
let age = document.getElementById("age").value;
let sex = document.getElementById("sex").value;
let address = document.getElementById("address").value;

fetch("http://localhost:8081/user/register", {
// 确保地址端口正确
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ uname, password, age, sex, address }),
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error("注册失败");
}
})
.then((data) => {
alert(data.msg); // 显示成功或失败信息
if (data.code === "0") {
window.location.href = "login.html";
}
// 在这里处理登录成功后的操作,例如页面跳转
})
.catch((error) => console.error(error));
}
</script>
</body>
</html>

实现首页查看图书功能

定义数据访问对象

src/main/java/top/cengweiye/library/repository/BookDao

1
2
3
4
5
6
7
8
9
10
package top.cengweiye.library.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import top.cengweiye.library.domain.Book;
import org.springframework.stereotype.Repository;

@Repository
public interface BookDao extends JpaRepository<Book, Integer> {
Page<Book> findByBookNameContaining(String keyWord, Pageable pageable);
}

src/main/java/top/cengweiye/library/repository/BorrowDao

1
2
3
4
5
6
7
8
package top.cengweiye.library.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import top.cengweiye.library.domain.Borrow;

public interface BorrowDao extends JpaRepository<Borrow, Integer> {

}
定义业务逻辑

定义 BookService 接口
src/main/java/top/cengweiye/library/service/BookService

1
2
3
4
5
6
7
8
9
package top.cengweiye.library.service;

import org.springframework.data.domain.Page;
import top.cengweiye.library.domain.Book;

public interface BookService {
public Page<Book> getBooks(int page, int size);
public Page<Book> getBooklikes(int page, int size, String keyWord);
}

定义 BorrowService 接口
src/main/java/top/cengweiye/library/service/BorrowService

1
2
3
4
5
package top.cengweiye.library.service;

public interface BorrowService {
public boolean borrowBook(Integer bookId, Integer borrowBookId);
}

实现接口功能
src/main/java/top/cengweiye/library/service/serviceImpl/BookServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package top.cengweiye.library.service.serviceImpl;

import org.springframework.stereotype.Service;
import top.cengweiye.library.service.BookService;
import top.cengweiye.library.repository.BookDao;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import top.cengweiye.library.domain.Book;
import javax.annotation.Resource;

@Service
public class BookServiceImpl implements BookService {
@Resource
private BookDao BookDao;

public Page<Book> getBooks(int page, int size) {
return BookDao.findAll(PageRequest.of(page, size));
}
@Override
public Page<Book> getBooklikes(int page, int size, String keyWord){
if (keyWord != null && !keyWord.isEmpty()) {
return BookDao.findByBookNameContaining(keyWord, PageRequest.of(page, size));
} else {
// 如果没有提供搜索条件,则返回所有书籍
return BookDao.findAll(PageRequest.of(page, size));
}
}
}

src/main/java/top/cengweiye/library/service/serviceImpl/BorrowServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package top.cengweiye.library.service.serviceImpl;

import org.springframework.stereotype.Service;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.domain.Borrow;
import top.cengweiye.library.repository.BookDao;
import top.cengweiye.library.repository.BorrowDao;
import top.cengweiye.library.service.BorrowService;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Optional;

@Service
public class BorrowServiceImpl implements BorrowService {
@Resource
private BookDao BookDao;

@Resource
private BorrowDao BorrowDao;

@Override
public boolean borrowBook(Integer bookId,Integer borrowBookId) {
Optional<Book> optionalBook = BookDao.findById(bookId);
if (optionalBook.isPresent()) {
Book book = optionalBook.get();
if ("可借阅".equals(book.getStatus())) {
// 更新书籍状态为 "已借阅"
book.setStatus("已借阅");
BookDao.save(book);

// 向 borrow 表中插入记录
Borrow borrow = new Borrow();
borrow.setBorrowBookId(borrowBookId);
borrow.setBookId(bookId);
borrow.setBorrowDate(LocalDateTime.now());
borrow.setReturnDate(LocalDateTime.now().plusDays(30)); // 假设借阅期为30天
BorrowDao.save(borrow);

return true;
}
}
return false;
}
}
定义控制器

图书查询功能
src/main/java/top/cengweiye/library/Controller/BookController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package top.cengweiye.library.Controller;

import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.service.BookService;

import javax.annotation.Resource;

@Controller
public class BookController {

@Resource
private BookService bookService;

@GetMapping("/")
public String getBooks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size,
@RequestParam(required = false) String bookName, // 接收搜索参数
Model model) {

Page<Book> bookPage;
// 如果有搜索关键字,就执行模糊查询,否则显示所有数据
if (bookName != null && !bookName.isEmpty()) {
bookPage = bookService.getBooklikes(page, size, bookName); // 执行模糊查询
} else {
bookPage = bookService.getBooks(page, size); // 不传 bookName 参数,显示所有数据
}
model.addAttribute("bookPage", bookPage);
model.addAttribute("bookName", bookName); // 将搜索的书名添加到模型中,方便分页时保留搜索条件
return "index";
}

}

借阅功能
src/main/java/top/cengweiye/library/Controller/BorrowController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package top.cengweiye.library.Controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.cengweiye.library.Utilities.TokenUtil;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.repository.ReaderDao;
import top.cengweiye.library.service.BorrowService;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/borrow")
public class BorrowController {

@Resource
private BorrowService BorrowService;

@Resource
private ReaderDao ReaderDao;

@PostMapping
public ResponseEntity<Map<String, Object>> borrowBook(@RequestBody Map<String, Object> requestBody, HttpServletRequest request) {
Integer bookId = (Integer) requestBody.get("bookId");
String name = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("Authorization".equals(cookie.getName())) {
String token = cookie.getValue();
if (token != null) {
token = token.substring(6);
if (TokenUtil.verify(token)) {
name = TokenUtil.getUsernameFromToken(token);
}
}
}
}
}
Reader reader = ReaderDao.findByName(name);
int borrowBookId = reader.getBorrowBookId();
Map<String, Object> response = new HashMap<>();
try {
boolean success = BorrowService.borrowBook(bookId, borrowBookId);
if (success) {
response.put("success", true);
} else {
response.put("success", false);
response.put("message", "该书已经被借阅!");
}
} catch (Exception e) {
response.put("success", false);
response.put("message", "发生错误:" + e.getMessage());
}

return ResponseEntity.ok(response);
}
}
前端页面(Thymeleaf 显示)

src/main/resource/templates/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/list.css" />
<link rel="stylesheet" href="../css/bootstrap.min.css" />
<title>图书管理系统</title>
</head>
<body>
<header>
<h1><a href="/">图书管理系统</a></h1>
<img
src="../images/logo.png"
alt="Logo"
style="width: 80px; height: 80px; margin-left: 10px;"
/>
<div class="col-lg-2 col-md-3 col-sm-3 col-xs-4 pull-right">
<form method="get" action="/" class="input-group">
<label for="bookName"></label>
<input
class="form-control"
type="text"
id="bookName"
name="bookName"
placeholder="搜索书名..."
th:value="${bookName}"
/>
<button
type="submit"
class="input-group-addon"
style="color: #FFFFFF;background-color: #2EA7E0;"
>
搜索
</button>
</form>
</div>
<ul>
<li><a href="/person">个人中心</a></li>
</ul>
</header>
<main>
<div class="bookContainer">
<h2>图书列表</h2>
<table>
<thead>
<tr>
<th>编号</th>
<th>书名</th>
<th>出版日期</th>
<th>出版社</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${bookPage.content}">
<td th:text="${book.bookId}"></td>
<td th:text="${book.bookName}"></td>
<td th:text="${book.publicationDate}"></td>
<td th:text="${book.publisher}"></td>
<td th:text="${book.status}"></td>
<td>
<div class="op">
<a
th:attr="data-id=${book.bookId}, data-status=${book.status}"
onclick="borrowBook(this)"
>借阅</a
>
</div>
</td>
</tr>
</tbody>
</table>
<div>
<button
th:if="${bookPage.hasPrevious()}"
th:onclick="'window.location.href=\'?page=' + (${bookPage.number} - 1) + '&size=' + ${bookPage.size} + '\''"
>
上一页
</button>
<button
th:if="${bookPage.hasNext()}"
th:onclick="'window.location.href=\'?page=' + (${bookPage.number} + 1) + '&size=' + ${bookPage.size} + '\''"
>
下一页
</button>
</div>
</div>
</main>
</body>
<script>
function borrowBook(element) {
const bookId = parseInt(element.getAttribute("data-id"), 10);
const status = element.getAttribute("data-status");

if (status === "可借阅") {
fetch("/borrow", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ bookId: bookId }),
})
.then((response) => response.json())
.then((data) => {
if (data.success) {
alert("借阅成功!");
location.reload(); // 刷新页面更新状态
} else {
alert("借阅失败:" + data.message);
}
})
.catch((error) => {
console.error("Error:", error);
alert("借阅失败,请稍后再试!");
});
} else {
alert("该书不可借阅!");
}
}
</script>
</html>

个人中心

定义一个 ReturnRequest 实体类(用于接收返回来的图书 ID)

src/main/java/top/cengweiye/library/domain/ReturnRequest

1
2
3
4
5
6
7
8
9
10
package top.cengweiye.library.domain;

public class ReturnRequest {
private Integer bookId;
return bookId;
}
public void setBookId(Integer bookId) {
this.bookId = bookId;
}
}
定义数据访问控制层

src/main/java/top/cengweiye/library/repostory/BorrowDao

1
2
3
4
5
6
7
8
9
10
11
12
package top.cengweiye.library.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import top.cengweiye.library.domain.Borrow;

public interface BorrowDao extends JpaRepository<Borrow, Integer> {
Page<Borrow> findByBorrowBookId(int borrowBookId, Pageable pageable); //查看借阅记录

void deleteByBookId(Integer bookId); // 还书
}
定义业务逻辑

获取借阅记录
src/main/java/top/cengweiye/library/service/BoorrowService

1
2
3
4
5
6
7
8
9
10
package top.cengweiye.library.service;

import org.springframework.data.domain.Page;
import top.cengweiye.library.domain.Borrow;

public interface BorrowService {
public boolean borrowBook(Integer bookId, Integer borrowBookId);

public Page<Borrow> getBorrow(int borrowbookid, int page, int size);
}

还书
src/main/java/top/cengweiye/library/service/BookService

1
2
3
4
5
6
7
8
9
10
11
12
13
package top.cengweiye.library.service;

import org.springframework.data.domain.Page;
import org.springframework.transaction.annotation.Transactional;
import top.cengweiye.library.domain.Book;

public interface BookService {
public Page<Book> getBooks(int page, int size);

public Page<Book> getBooklikes(int page, int size, String keyWord);
@Transactional
public void returnBook(Integer bookId);
}

src/main/java/top/cengweiye/library/service/serviceImpl/BorrowServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package top.cengweiye.library.service.serviceImpl;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.domain.Borrow;
import top.cengweiye.library.repository.BookDao;
import top.cengweiye.library.repository.BorrowDao;
import top.cengweiye.library.service.BorrowService;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Optional;

@Service
public class BorrowServiceImpl implements BorrowService {
@Resource
private BookDao BookDao;

@Resource
private BorrowDao BorrowDao;

@Override
public boolean borrowBook(Integer bookId,Integer borrowBookId) {
Optional<Book> optionalBook = BookDao.findById(bookId);
if (optionalBook.isPresent()) {
Book book = optionalBook.get();
if ("可借阅".equals(book.getStatus())) {
// 更新书籍状态为 "已借阅"
book.setStatus("已借阅");
BookDao.save(book);

// 向 borrow 表中插入记录
Borrow borrow = new Borrow();
borrow.setBorrowBookId(borrowBookId);
borrow.setBookId(bookId);
borrow.setBorrowDate(LocalDateTime.now());
borrow.setReturnDate(LocalDateTime.now().plusDays(30)); // 假设借阅期为30天
BorrowDao.save(borrow);

return true;
}
}
return false;
}

@Override
public Page<Borrow> getBorrow(int borrowbookid, int page, int size) { return BorrowDao.findByBorrowBookId(borrowbookid,PageRequest.of(page, size));}
}

src/main/java/top/cengweiye/library/service/serviceImpl/BookServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package top.cengweiye.library.service.serviceImpl;

import org.springframework.stereotype.Service;
import top.cengweiye.library.service.BookService;
import top.cengweiye.library.repository.BookDao;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import top.cengweiye.library.domain.Book;
import javax.annotation.Resource;
import java.util.Optional;

@Service
public class BookServiceImpl implements BookService {
@Resource
private BookDao BookDao;
@Override
public Page<Book> getBooks(int page, int size) {
return BookDao.findAll(PageRequest.of(page, size));
}

@Override
public Page<Book> getBooklikes(int page, int size, String keyWord){
if (keyWord != null && !keyWord.isEmpty()) {
return BookDao.findByBookNameContaining(keyWord, PageRequest.of(page, size));
} else {
// 如果没有提供搜索条件,则返回所有书籍
return BookDao.findAll(PageRequest.of(page, size));
}
}

@Override
public void returnBook(Integer bookId){
Optional<Book> optionalBook = BookDao.findById(bookId);
if (optionalBook.isPresent()) {
Book book = optionalBook.get();
if ("已借阅".equals(book.getStatus())) {
book.setStatus("可借阅");
BookDao.save(book);
}
}
}
}
定义控制器

实现查看借阅记录
src/main/java/top/cengweiye/library/Controller/PersonController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package top.cengweiye.library.Controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import top.cengweiye.library.Utilities.TokenUtil;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.domain.Borrow;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.repository.ReaderDao;
import top.cengweiye.library.service.BorrowService;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Controller
public class PersonController {

@Resource
private BorrowService borrowService;
@Resource
private ReaderDao readerDao;

@GetMapping("/person")
public String getperson(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size,
Model model, HttpServletRequest request) {
String name = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("Authorization".equals(cookie.getName())) {
String token = cookie.getValue();
if (token != null) {
token = token.substring(6);
if (TokenUtil.verify(token)) {
name = TokenUtil.getUsernameFromToken(token);
}
}
}
}
}
if (name != null) {
// 获取用户信息
Reader reader = readerDao.findByName(name);
if (reader != null) {
model.addAttribute("reader", reader);
int borrowBookId = reader.getBorrowBookId();

// 获取用户的借阅记录
Page<Borrow> borrowPage = borrowService.getBorrow(borrowBookId, page, size);
model.addAttribute("borrowPage", borrowPage);
}
}

return "person";
}
}

实现还书功能
src/main/java/top/cengweiye/library/Controller/ReturnController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package top.cengweiye.library.Controller;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.cengweiye.library.Utilities.Result;
import top.cengweiye.library.domain.ReturnRequest;
import top.cengweiye.library.repository.BorrowDao;
import top.cengweiye.library.service.BookService;

import javax.annotation.Resource;

@RestController
@RequestMapping("/return")
public class ReturnController {
@Resource
private BookService bookService;
@Resource
private BorrowDao borrowDao;

@PostMapping
@Transactional // 进行事务管理
public Result<String> returnBook(@RequestBody ReturnRequest returnRequest){

borrowDao.deleteByBookId(returnRequest.getBookId());
bookService.returnBook(returnRequest.getBookId());
return Result.success("还书成功");
}
}
前端页面(将注销功能写在这里)

src/main/resource/templates/person.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>个人中心</title>
<link rel="stylesheet" href="../css/list.css" />
<link rel="stylesheet" href="../css/style.css" />
</head>
<body>
<header>
<h1><a href="/">图书管理系统</a></h1>
<img
src="../images/logo.png"
alt="Logo"
style="width: 80px; height: 80px; margin-left: 10px;"
/>
<ul>
<li><a href="/person">个人中心</a></li>
</ul>
</header>
<main>
<div class="container">
<div class="bookContainer">
<h2>图书列表</h2>
<table>
<thead>
<tr>
<th>图书编号</th>
<th>借阅日期</th>
<th>归还日期</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="borrow : ${borrowPage}">
<td th:text="${borrow.bookId}">图书编号</td>
<td th:text="${borrow.borrowDate}">借阅日期</td>
<td th:text="${borrow.returnDate}">归还日期</td>
<td>
<div class="op">
<a
th:attr="book-id=${borrow.bookId}"
onclick="borrowBook(this)"
>还书</a
>
</div>
</td>
</tr>
</tbody>
</table>
<div>
<button
th:if="${borrowPage.hasPrevious()}"
th:onclick="'window.location.href=\'/person?page=' + (${borrowPage.number} - 1) + '&size=' + ${borrowPage.size} + '\''"
>
上一页
</button>
<button
th:if="${borrowPage.hasNext()}"
th:onclick="'window.location.href=\'/person?page=' + (${borrowPage.number} + 1) + '&size=' + ${borrowPage.size} + '\''"
>
下一页
</button>
</div>
</div>

<div class="readerInfo">
<h2>读者信息</h2>
<p><strong>姓名:</strong><span th:text="${reader.name}"></span></p>
<p><strong>年龄:</strong><span th:text="${reader.age}"></span></p>
<p><strong>性别:</strong><span th:text="${reader.sex}"></span></p>
<p>
<strong>地址:</strong><span th:text="${reader.address}"></span>
</p>
<p>
<strong>角色:</strong
><span th:text="${reader.role == 1 ? '普通用户' : '管理员'}"></span>
</p>
<div>
<a href="/updateperson.html" class="user-button">修改个人信息</a>
</div>
<div th:if="${reader.role != 1}">
<a href="/bookmanage" class="admin-button">进入管理员页面</a>
</div>
<div>
<a class="logout-button" onclick="logout()">退出登录</a>
</div>
</div>
</div>
</main>
</body>
<script>
function borrowBook(element) {
const bookId = parseInt(element.getAttribute("book-id"), 10);
fetch("/return", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ bookId: bookId }),
})
.then((response) => response.json())
.then((data) => {
if (data.code === "0") {
alert(data.data);
location.reload();
}
})
.catch((error) => console.error("Error:", error));
}

function logout() {
fetch("/reader/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data) => {
if (data.code === "0") {
window.location.href = "/login.html";
}
})
.catch((error) => console.error("Error:", error));
}
</script>
</html>

添加修改个人信息功能

添加工具类 GetToken(用于将获取 token 的代码封装)

src/main/java/top/cengweiye/library/Utilities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package top.cengweiye.library.Utilities;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

public class GetToken {
public static String getToken(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("Authorization".equals(cookie.getName())) {
return cookie.getValue().substring(6);
}
}
}
return null;
}
}
定义业务逻辑

src/main/java/top/cengweiye/library/service/UpdatePersonService

1
2
3
4
5
6
7
package top.cengweiye.library.service;
import top.cengweiye.library.domain.Reader;
import javax.servlet.http.HttpServletRequest;

public interface UpdatePersonService {
public String updatePerson(Reader reader, String token);
}

src/main/java/top/cengweiye/library/service/serviceImpl/UpdatePersonServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package top.cengweiye.library.service.serviceImpl;

import org.springframework.stereotype.Service;
import top.cengweiye.library.Utilities.GetToken;
import top.cengweiye.library.Utilities.TokenBlacklist;
import top.cengweiye.library.Utilities.TokenUtil;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.repository.ReaderDao;
import top.cengweiye.library.service.UpdatePersonService;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@Service
public class UpdatePersonServiceImpl implements UpdatePersonService {
@Resource
private ReaderDao readerdao;

@Override
public String updatePerson(Reader reader, String token) {
String name = TokenUtil.getUsernameFromToken(token);
TokenBlacklist.blacklistToken(token);
String update_name = reader.getName();
int update_age = reader.getAge();
String update_sex = reader.getSex();
String update_address = reader.getAddress();
Reader old = readerdao.findByName(name);
old.setAddress(update_address);
old.setAge(update_age);
old.setSex(update_sex);
old.setName(update_name);
readerdao.save(old);
return old.getName();
}
}
定义控制器

src/main/java/top/cengweiye/library/Controller/UpdateController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package top.cengweiye.library.Controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.cengweiye.library.Utilities.GetToken;
import top.cengweiye.library.Utilities.Result;
import top.cengweiye.library.Utilities.TokenUtil;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.service.UpdatePersonService;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/update")
public class UpdateController {

@Resource
private UpdatePersonService updatePersonService;

@PostMapping
public Result<String> update(@RequestBody Reader newreader, HttpServletRequest request, HttpServletResponse response){
String token = GetToken.getToken(request);
String name = updatePersonService.updatePerson(newreader, token);
String token_twice = TokenUtil.generateToken(name);
Cookie cookie = new Cookie("Authorization","Bearer" + token_twice);
cookie.setHttpOnly(true); // 防止客户端 JavaScript 访问,提高安全性
cookie.setSecure(true); // 仅在 HTTPS 下传输(生产环境中应启用)
cookie.setPath("/"); // 设置路径为根目录,确保整个应用都能访问
cookie.setMaxAge(60 * 60 * 24 * 7); // 设置过期时间为 7 天(根据需要调整)
response.addCookie(cookie);
return Result.success("更新成功");
}
}
前端页面

src/main/resource/static/updateperson.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/login.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
<style>
.login-dialog {
width: 400px;
height: 500px;
}
.container-login{
width: 750px;
height: 500px;
}
</style>
<title>修改个人信息</title>
</head>
<body>

<div class="container-login">
<div class="container-pic">
<img src="images/computer.png" width="350px" alt="">
</div>
<div class="login-dialog">
<h3>修改个人信息</h3>
<div class="row">
<label for="Name">用户名</label>
<input type="text" name="Name" id="Name" class="form-control">
</div>
<div class="row">
<label for="age">年龄</label>
<input type="text" name="age" id="age" class="form-control">
</div>
<div class="row">
<label for="sex">性别</label>
<input type="text" name="sex" id="sex" class="form-control">
</div>
<div class="row">
<label for="address">地址</label>
<input type="text" name="address" id="address" class="form-control">
</div>
<div class="row">
<label for="password">密码</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="row">
<label for="confirmPassword">确认密码:</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="update()">修改个人信息</button>
</div>
</div>
</div>

<script src="js/jquery.min.js"></script>
<script>
function update() {
let password = document.getElementById('password').value;
let confirmPassword = document.getElementById('confirmPassword').value;
if (password !== confirmPassword) {
alert('两次密码不一致');
return;
}
let name = document.getElementById('Name').value;
let age = document.getElementById('age').value;
let sex = document.getElementById('sex').value;
let address = document.getElementById('address').value;

fetch('/update', { // 确保地址端口正确
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, password, age, sex, address }),
})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('更新失败');
}
})
.then(data => {
alert(data.data); // 显示成功或失败信息
if (data.code === "0") {
window.location.href = '/person';
}
// 在这里处理登录成功后的操作,例如页面跳转
})
.catch(error => console.error(error));
}
</script>
</body>
</html>

开始设计管理员页面(分为 图书管理/用户管理 两个页面)

图书管理

下面我们根据这个界面来完成我们的代码

配置访问控制对象

src/main/java/top/cengweiye/library/repository/BookDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package top.cengweiye.library.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import top.cengweiye.library.domain.Book;
import org.springframework.stereotype.Repository;

@Repository
public interface BookDao extends JpaRepository<Book, Integer> {

public Book findByBookId(Integer bookId);
void deleteByBookId(Integer bookId);
Page<Book> findByBookNameContaining(String keyWord, Pageable pageable);
}
定义业务逻辑

src/main/java/top/cengweiye/library/service/EditBookService

1
2
3
4
5
6
7
8
package top.cengweiye.library.service;

import top.cengweiye.library.domain.Book;

public interface EditBookService {
public String addBook(Book book);
public String editBook(Book book);
}

src/main/java/top/cengweiye/library/service/serviceImpl/EditBookServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package top.cengweiye.library.service.serviceImpl;

import org.springframework.stereotype.Service;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.repository.BookDao;
import top.cengweiye.library.service.EditBookService;

import javax.annotation.Resource;

@Service
public class EditBookServiceImpl implements EditBookService {
@Resource
private BookDao bookDao;

@Override
public String addBook(Book book){
if(bookDao.findByBookId(book.getBookId()) != null){
return "该书已存在";
}
book.setStatus("可借阅");
bookDao.save(book);
return "添加成功";
}

@Override
public String editBook(Book book){
book.setStatus("可借阅");
bookDao.save(book);
return "修改成功";
}
}
定义控制器

返回页面控制器
src/main/java/top/cengweiye/library/Controller/BookManageController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package top.cengweiye.library.Controller;

import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.service.BookService;

import javax.annotation.Resource;

@Controller
@RequestMapping("/bookmanage")
public class BookManageController {

@Resource
private BookService bookService;

@GetMapping
public String getBooks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size,
Model model) {
Page<Book> bookPage = bookService.getBooks(page, size);
model.addAttribute("bookPage", bookPage);
return "bookmanage";
}
}

删除图书控制器
src/main/java/top/cengweiye/library/Controller/DeleteBookController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package top.cengweiye.library.Controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.cengweiye.library.Utilities.Result;
import top.cengweiye.library.domain.ReturnRequest;
import top.cengweiye.library.repository.BookDao;
import top.cengweiye.library.repository.BorrowDao;

import javax.annotation.Resource;

@RestController
@RequestMapping("/deletebook")
public class DeleteBookController {
@Resource
private BookDao bookDao;

@Resource
private BorrowDao borrowDao;

@Transactional
@PostMapping
public Result<String> deleteBook(@RequestBody ReturnRequest returnRequest) {
int bookId = returnRequest.getBookId();
borrowDao.deleteByBookId(bookId);
bookDao.deleteByBookId(bookId);
return Result.success("删除成功");
}
}

返回编辑图书的页面控制器
src/main/java/top/cengweiye/library/Controller/VimBookController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package top.cengweiye.library.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/vimbook")
public class VimBookController {
@GetMapping
public String vimbook(@RequestParam("bookId") int bookId, Model model) {
model.addAttribute("bookId", bookId);

return "bookinfo"; // 返回视图名称
}
}

提交修改功能控制器
src/main/java/top/cengweiye/library/Controller/EditBookController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package top.cengweiye.library.Controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.cengweiye.library.Utilities.Result;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.service.EditBookService;

import javax.annotation.Resource;

@RestController
@RequestMapping("/editbook")
public class EditBookController {
@Resource
private EditBookService editBookService;

@PostMapping
public Result<String> editBook(@RequestBody Book book){
String data = editBookService.editBook(book);
return Result.success(data);
}
}

添加图书功能控制器
src/main/java/top/cengweiye/library/Controller/EditBookController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package top.cengweiye.library.Controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.cengweiye.library.Utilities.Result;
import top.cengweiye.library.domain.Book;
import top.cengweiye.library.service.EditBookService;

import javax.annotation.Resource;

@RestController
@RequestMapping("/editbook")
public class EditBookController {
@Resource
private EditBookService editBookService;

@PostMapping
public Result<String> editBook(@RequestBody Book book){
String data = editBookService.editBook(book);
return Result.success(data);
}

@PostMapping("/add")
public Result<String> addBook(@RequestBody Book book){
String data = editBookService.addBook(book);
return Result.success(data);
}
}
前端页面

图书管理界面
src/main/resources/templates/bookmanage.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/list.css" />

<title>图书管理系统</title>
</head>
<body>
<header>
<h1><a href="/">图书管理系统</a></h1>
<img
src="../images/logo.png"
alt="Logo"
style="width: 80px; height: 80px; margin-left: 10px;"
/>
<ul>
<li><a href="/usermanage">用户管理</a></li>
<li><a href="/person">个人中心</a></li>
</ul>
</header>
<main>
<div class="bookContainer">
<h2>管理图书</h2>
<a href="/addbook.html" class="addbook">添加图书</a>
<table>
<thead>
<tr>
<th>图书编号</th>
<th>图书名称</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${bookPage.content}">
<td th:text="${book.bookId}">1</td>
<td th:text="${book.bookName}">test</td>
<td th:text="${book.status}">可借阅</td>
<td>
<div class="op">
<a th:attr="data-id=${book.bookId}" onclick="vim(this)"
>编辑</a
>
<a
th:attr="data-id=${book.bookId}"
class="delete"
onclick="del(this)"
>删除</a
>
</div>
</td>
</tr>
</tbody>
</table>
<div>
<button
th:if="${bookPage.hasPrevious()}"
th:onclick="'window.location.href=\'?page=' + (${bookPage.number} - 1) + '&size=' + ${bookPage.size} + '\''"
>
上一页
</button>
<button
th:if="${bookPage.hasNext()}"
th:onclick="'window.location.href=\'?page=' + (${bookPage.number} + 1) + '&size=' + ${bookPage.size} + '\''"
>
下一页
</button>
</div>
</div>
</main>
</body>
<script>
function vim(element) {
const bookId = parseInt(element.getAttribute("data-id"), 10);
window.location.href = `/vimbook?bookId=${bookId}`;
}

function del(element) {
const bookId = parseInt(element.getAttribute("data-id"), 10);
fetch("/deletebook", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ bookId: bookId }),
})
.then((response) => response.json())
.then((data) => {
if (data.code === "0") {
alert(data.data);
location.reload();
}
})
.catch((error) => {
console.error("Error:", error);
});
}
</script>
</html>

返回编辑页面
src/main/resources/templates/bookinfo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/list.css" />
<link rel="stylesheet" href="../css/bootstrap.min.css" />
<link rel="stylesheet" href="../css/login.css" />
<style>
.login-dialog {
width: 350px;
height: 350px;
}
</style>
<title>编辑图书</title>
</head>
<body>
<header>
<h1><a href="/">图书管理系统</a></h1>
<img
src="../images/logo.png"
alt="Logo"
style="width: 80px; height: 80px; margin-left: 10px;"
/>
<ul>
<li><a href="/usermanage">用户管理</a></li>
<li><a href="/person">个人中心</a></li>
</ul>
</header>
<main>
<div class="container-login">
<div class="container-pic">
<img src="../static/images/computer.png" width="350px" alt="" />
</div>
<div class="login-dialog">
<h3 th:attr="book-id=${bookId}">编辑图书</h3>
<div class="row">
<span>图书名称</span>
<input type="text" name="Name" id="bookName" class="form-control" />
</div>
<div class="row">
<span>出版日期</span>
<input
type="text"
name="Name"
id="publicationDate"
class="form-control"
/>
</div>
<div class="row">
<span>出版社</span>
<input
type="text"
name="Name"
id="publisher"
class="form-control"
/>
</div>
<div class="row">
<span>书架</span>
<input
type="text"
name="Name"
id="bookrackId"
class="form-control"
/>
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="send()">
发送
</button>
</div>
</div>
</div>
</main>
</body>
<script>
function send() {
fetch("/editbook", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
bookId: document.querySelector("h3").getAttribute("book-id"),
bookName: document.querySelector("#bookName").value,
publicationDate: document.querySelector("#publicationDate").value,
publisher: document.querySelector("#publisher").value,
bookrackId: document.querySelector("#bookrackId").value,
}),
})
.then((response) => response.json())
.then((data) => {
if (data.code === "0") {
alert(data.data);
window.location.href = "/bookmanage";
}
})
.catch((error) => console.error("Error:", error));
}
</script>
</html>

添加图书页面
src/main/resources/static/addbook.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/list.css" />
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/login.css" />
<style>
.login-dialog {
width: 350px;
height: 350px;
}
</style>
<title>添加图书</title>
</head>
<body>
<header>
<h1><a href="/">图书管理系统</a></h1>
<img
src="../images/logo.png"
alt="Logo"
style="width: 80px; height: 80px; margin-left: 10px;"
/>
<ul>
<li><a href="/bookmanage">图书管理</a></li>
<li><a href="/usermanage">用户管理</a></li>
<li><a href="/person">个人中心</a></li>
</ul>
</header>
<main>
<div class="container-login">
<div class="container-pic">
<img src="../static/images/computer.png" width="350px" alt="" />
</div>
<div class="login-dialog">
<h3>添加图书</h3>
<div class="row">
<span>图书名称</span>
<input type="text" name="Name" id="bookName" class="form-control" />
</div>
<div class="row">
<span>出版日期</span>
<input
type="date"
name="publicationDate"
id="publicationDate"
class="form-control"
/>
</div>
<div class="row">
<span>出版社</span>
<input
type="text"
name="Name"
id="publisher"
class="form-control"
/>
</div>
<div class="row">
<span>书架</span>
<input
type="text"
name="Name"
id="bookrackId"
class="form-control"
/>
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="send()">
发送
</button>
</div>
</div>
</div>
</main>
</body>
<script>
function send() {
fetch("/editbook/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
bookName: document.querySelector("#bookName").value,
publicationDate: document.querySelector("#publicationDate").value, // 发送日期
publisher: document.querySelector("#publisher").value,
bookrackId: document.querySelector("#bookrackId").value,
}),
})
.then((response) => response.json())
.then((data) => {
if (data.code === "0") {
alert(data.data);
window.location.href = "/bookmanage";
} else {
alert(data.data);
}
})
.catch((error) => console.error("Error:", error));
}
</script>
</html>

用户管理功能

添加实体类

Blacklist
src/main/java/top/cengweiye/library/domain/Blacklist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package top.cengweiye.library.domain;

import javax.persistence.*;

@Entity
@Table(name = "blacklist")
public class Blacklist {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "borrow_book_id")
private int borrowBookId;

@Column(name = "name")
private String name;

public int getBorrowBookId() {
return borrowBookId;
}

public void setBorrowBookId(int borrowBookId) {
this.borrowBookId = borrowBookId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getReason() {
return reason;
}

public void setReason(String reason) {
this.reason = reason;
}

@Column(name = "reason")
private String reason;
}
定义数据访问对象

BlacklistDao
src/main/java/top/cengweiye/library/repository/BlacklistDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top.cengweiye.library.repository;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import top.cengweiye.library.domain.Blacklist;
import java.util.Optional;

@Repository
public interface BlacklistDao extends JpaRepository<Blacklist, Integer> {
Optional<Blacklist> findById(Integer borrowBookId);
Optional<Blacklist> findByName(String name);
void deleteByborrowBookId(int borrowBookId);
}

BorrowDao
src/main/java/top/cengweiye/library/repository/BorrowDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package top.cengweiye.library.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import top.cengweiye.library.domain.Borrow;

import java.time.LocalDateTime;
import java.util.List;

public interface BorrowDao extends JpaRepository<Borrow, Integer> {
Page<Borrow> findByBorrowBookId(int borrowBookId, Pageable pageable);
void deleteByBookId(Integer bookId);

@Query("SELECT b FROM Borrow b WHERE b.returnDate < :currentDate AND b.bookId IS NOT NULL")
List<Borrow> findAllOverdueBorrows(@Param("currentDate") LocalDateTime currentDate);
}
添加工具类

CheckBlackList(定时任务每天将逾期未归还的人加入黑名单)
src/main/java/top/cengweiye/library/Utilities/CheckBlackList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package top.cengweiye.library.Utilities;

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import top.cengweiye.library.domain.Blacklist;
import top.cengweiye.library.domain.Borrow;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.repository.BlacklistDao;
import top.cengweiye.library.repository.BorrowDao;
import top.cengweiye.library.repository.ReaderDao;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@EnableScheduling
public class CheckBlackList {
@Resource
private BorrowDao borrowDao;

@Resource
private BlacklistDao blacklistDao;

@Resource
private ReaderDao readerDao;

@Transactional
@Scheduled(cron = "0 0 0 * * ?") // 每天午夜 12 点执行一次
public void checkOverdueAndBlacklist() {
LocalDateTime currentDate = LocalDateTime.now();
// 获取所有未归还的借阅记录
List<Borrow> overdueBorrows = borrowDao.findAllOverdueBorrows(currentDate);

for (Borrow borrow : overdueBorrows) {
// 检查该借阅者是否已在黑名单中
Optional<Blacklist> existingBlacklist = blacklistDao.findById(borrow.getBorrowBookId());
if (!existingBlacklist.isPresent()) {
// 添加借阅者到黑名单
Blacklist blacklist = new Blacklist();
blacklist.setBorrowBookId(borrow.getBorrowBookId());
Reader reader = readerDao.findByBorrowBookId(borrow.getBorrowBookId());
blacklist.setName(reader.getName()); // 获取借阅者的名字
blacklist.setReason("借阅逾期未归还");
blacklistDao.save(blacklist);

// 你也可以选择在这里发送提醒或者邮件通知借阅者
}
}
}
}
定义控制器

修改控制器,添加在黑名单里的人不能借书的代码段
BorrowController
src/main/java/top/cengweiye/library/Controller/BorrowController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package top.cengweiye.library.Controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.cengweiye.library.Utilities.TokenUtil;
import top.cengweiye.library.domain.Blacklist;
import top.cengweiye.library.domain.Reader;
import top.cengweiye.library.repository.BlacklistDao;
import top.cengweiye.library.repository.ReaderDao;
import top.cengweiye.library.service.BorrowService;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/borrow")
public class BorrowController {
@Resource
private BorrowService BorrowService;
@Resource
private ReaderDao ReaderDao;

@Resource
private BlacklistDao blacklistDao;

@PostMapping
public ResponseEntity<Map<String, Object>> borrowBook(@RequestBody Map<String, Object> requestBody, HttpServletRequest request) {
Integer bookId = (Integer) requestBody.get("bookId");
String name = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("Authorization".equals(cookie.getName())) {
String token = cookie.getValue();
if (token != null) {
token = token.substring(6);
if (TokenUtil.verify(token)) {
name = TokenUtil.getUsernameFromToken(token);
}
}
}
}
}
Map<String, Object> response = new HashMap<>();
Reader reader = ReaderDao.findByName(name);
Optional<Blacklist> blacklist = blacklistDao.findById(reader.getBorrowBookId());
if (blacklist.isPresent()){
response.put("success", false);
response.put("message", "您已被封禁,请联系管理员解封!");
return ResponseEntity.ok(response);
}
int borrowBookId = reader.getBorrowBookId();

try {
boolean success = BorrowService.borrowBook(bookId, borrowBookId);
if (success) {
response.put("success", true);
} else {
response.put("success", false);
response.put("message", "该书已经被借阅!");
}
} catch (Exception e) {
response.put("success", false);
response.put("message", "发生错误:" + e.getMessage());
}

return ResponseEntity.ok(response);
}
}

UsermanageController
src/main/java/top/cengweiye/library/Controller/UsermanageController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package top.cengweiye.library.Controller;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import top.cengweiye.library.Utilities.Result;
import top.cengweiye.library.domain.Blacklist;
import top.cengweiye.library.repository.BlacklistDao;
import javax.annotation.Resource;
import java.util.Map;

@Controller
@RequestMapping("/usermanage")
public class UsermanageController {

@Resource
private BlacklistDao blacklistDao;

@GetMapping("")
public String getBooks(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size,
Model model) {
Page<Blacklist> blacklistsPage = blacklistDao.findAll(PageRequest.of(page, size));
model.addAttribute("blacklistsPage", blacklistsPage);
return "usermanage";
}

@Transactional
@PostMapping("/clear")
@ResponseBody // 添加这个注解,表示返回的是一个JSON响应,而不是一个视图
public Result<String> clear(@RequestBody Map<String, Object> requestData) {
Integer id = (Integer) requestData.get("id"); // 获取传递的 id
blacklistDao.deleteByborrowBookId(id); // 删除黑名单条目
return Result.success("解封成功");
}

}
前端页面

src/main/resources/templates/usermanage.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.w3.org/1999/xhtml" xmlns="">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../css/style.css" />
<link rel="stylesheet" href="../css/list.css" />
<title>图书管理系统</title>
</head>
<body>
<header>
<h1><a href="/">图书管理系统</a></h1>
<img
src="../images/logo.png"
alt="Logo"
style="width: 80px; height: 80px; margin-left: 10px;"
/>
<ul>
<li><a href="/bookmanage">图书管理</a></li>
<li><a href="/person">个人中心</a></li>
</ul>
</header>
<main>
<div class="bookContainer">
<h2>黑名单管理</h2>
<table>
<thead>
<tr>
<th>用户ID</th>
<th>用户名</th>
<th>封禁原因</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- 检查blacklistsPage.content是否为空 -->
<tr th:each="black : ${blacklistsPage.content}">
<td th:text="${black.borrowBookId}"></td>
<td th:text="${black.name}"></td>
<td th:text="${black.reason}"></td>
<td>
<div class="op">
<a th:attr="data-id=${black.borrowBookId}" onclick="cl(this)"
>解封</a
>
</div>
</td>
</tr>
<!-- 如果blacklistsPage.content为空,显示无数据提示 -->
<tr th:if="${#lists.isEmpty(blacklistsPage.content)}">
<td colspan="4">暂无黑名单数据</td>
</tr>
</tbody>
</table>
<div>
<button
th:if="${blacklistsPage.hasPrevious()}"
th:onclick="'window.location.href=\'/usermanage?page=' + (${blacklistsPage.number} - 1) + '&size=' + ${blacklistsPage.size} + '\''"
>
上一页
</button>
<button
th:if="${blacklistsPage.hasNext()}"
th:onclick="'window.location.href=\'/usermanage?page=' + (${blacklistsPage.number} + 1) + '&size=' + ${blacklistsPage.size} + '\''"
>
下一页
</button>
</div>
</div>
</main>
</body>
<script>
function cl(element) {
const id = parseInt(element.getAttribute("data-id"), 10);
fetch("/usermanage/clear", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ id: id }),
})
.then((response) => response.json())
.then((data) => {
if (data.code === "0") {
alert(data.data);
location.reload(); // 解封后刷新页面
}
})
.catch((error) => console.error("Error:", error));
}
</script>
</html>

打包

生成 jar 包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
在library目录下执行 mvn clean package
会在target目录下生成 jar包 java -jar *.jar 即可运行

如果出现报错(jar中没有主清单属性)
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>top.cengweiye.library.LibraryApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
就看看pom.xml中的这个字段,将 <skip>true</skip> 删除如下
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>top.cengweiye.library.LibraryApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>

生成 exe 文件(无需 jdk)

需要 exe4j、jre、jar 包(将 jre 文件夹和 jar 包放一个目录下)

1
jre文件夹在你的java文件夹下,也可以用我的


配置证书,不然会有水印 Name 和 Company 随便填
License key:A-XVK258563F-1p4lv7mg7sav

这里选择 JAR 打包 EXE

填写打包的 exe 名称,还有输出文件的位置

配置 exe 文件名与图标(图标可以不配置)

生成 64 位的文件

选择 jar 包位置

选择项目启动方法

jre 版本

一直 Next 即可

完善系统安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
系统安全
1、用户表示和鉴别
(1)口令认证:密码通过md5加密存放
(2)会话管理:通过JWT技术实现身份验证
(3)注册管理:必须使用复杂密码注册
2、存取控制
(1)普通用户:仅能 查询图书/借阅书籍/修改个人信息
(2)管理员: 查询图书/借阅书籍/修改图书信息/删除图书/添加图书/解封用户/修改个人信息
3、安全防护
(1)通过实现拦截器,确保用户只能访问授权的资源,防止访问未经授权的内容
(2)通过JPA接口化访问数据库,利用参数化查询有效防止SQL注入风险
(3)设置httponly,防止xss盗取cookie
4、安全审计
(1)记录系统日志
通过 Logback 框架进行日志记录
记录信息为:"控制台日志" "文件日志" "Hibernate SQL 查询日志" "Hibernate 参数绑定日志" "Spring JDBC 日志"
(2)记录用户操作日志
操作时间
登录/登出/注册/非法访问
IP地址
设备信息

修改:
(1)register.html 添加强密码验证
(2)application.properties、logback-spring.xml 实现日志记录功能
(3)BookManageController.java 实现身份验证
(4)ReaderServiceImpl.java 添加md5加密
(5)Log.java/ LogDao.java/ GetInfo.java/ ReaderController.java/ BookManageController
如何实现记录用户操作日志:
(1)创建一个表 logs
(2)定义实体类 Log
(3)定义 数据访问对象 LogDao
(4)创建工具类 GetInfo 获取ip信息

创建 日志表

1
2
3
4
5
6
7
8
create table logs(
log_id int(11) auto_increment primary key,
name varchar(255),
op_time datetime(0),
op varchar(255),
ip varchar(255),
dev_info varchar(255)
);