项目实训:表白墙,图书管理系统
1. 表白墙
之前我们的表白墙里面的信息是存在我们的内存里面,如果我们重启一下数据就没有了,此时,我们需要完成的就是把数据进行持久化
1> 加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2> 在yml文件配置数据库
我们先创建数据库
这里注意一下 `update_time` DATETIME DEFAULT now() ON UPDATE now(),是表示在更新语句进行的时候会自动更改创建时间,并且创建时间设置为更新时间
DROP TABLE IF EXISTS message_info;
CREATE TABLE `message_info` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`from` VARCHAR ( 127 ) NOT NULL,
`to` VARCHAR ( 127 ) NOT NULL,
`message` VARCHAR ( 256 ) NOT NULL,
`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
配置yml
spring:
application:
name=springMVC_tets
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis⽇志
map-underscore-to-camel-case: true #配置驼峰⾃动转换
3> 编写后端代码
完善实体类(model)
编写mapper
1> 定义接口![](https://i-blog.csdnimg.cn/direct/4dbca628df764956856b7907d1e9af63.png)
2> 具体实现
注意一下,单引号的加入是看这个单词是不是关键字,不是关键字可以不加
进行测试
3> 连接contorller和service
运行结果,我们发现我们的say是个null值
也就是我们实体类的属性和数据库的字段对应不上
解决方式:
1> 修改say为message
2> 修改映射关系
2.图书管理系统
数据库表设计(开发之前做的事情)
在完善我们图书管理系统之前,我们先学习一下数据库表的设计
数据库表一般分为: 实体表(刚刚的图书用户和图书,这个实体里面有多少个名词)和关系表(实体之间的关系,几对几的关系)
然后我们正式进入到图书管理系统的完善
准备工作:
1> 配置pom文件,添加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2> 在yml文件配置数据库
我们先创建实体表: 用户表和图书表
-- 创建数据库
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, '民主与建设出版社');
然后我们配置yml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis⽇志
map-underscore-to-camel-case: true #配置驼峰⾃动转换
# 设置日志文件
logging:
file:
name: spring-book.log
3> 编写后端代码
创建model
定义接口
实现接口并且测试
登录接口
后端代码
service层: 主要就是对数据进行加工处理,也就是对DAO 层或 Repository里面的数据进行处理
controller层:主要负责接收前端请求并返回响应
mapper: 也称为 DAO 层或 Repository 层,是数据持久层的组件。Mapper 层的主要作用是访问数据库,执行数据的增删改查操作。它通常包含一些基本的 SQL 语句或使用 ORM 框架提供的 API 来执行数据库操作。
运行结果:
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</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="pic/computer.png" width="350px">
</div>
<div class="login-dialog">
<h3>登陆</h3>
<div class="row">
<span>用户名</span>
<input type="text" name="userName" id="userName" class="form-control">
</div>
<div class="row">
<span>密码</span>
<input type="password" name="password" id="password" class="form-control">
</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() {
$.ajax({
url :"/user/login",
type: "post",
data:{
userName: $("#userName").val(),
password: $("#password").val()
},
success: function(rs){//成功的话就接收后端的请求
if(rs == ""){
//密码正确
location.href="book_list.html"
}else{
alert(rs);
}
}
})
}
</script>
</body>
</html>
我们直接进行测试
登录成功后出现的页面
添加图书接口
接口的定义:
[ 请求]
/book/addBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8 1 2 3 4
[参数]
bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
[响应]
"" //失败信息, 成功时返回空字符串
后端代码:
mapper:
controller:
service:
进行测试:
前端代码:
这里我们需要传递的参数太多了,因此我们使用序列化函数$().serialize()
这里主意一个点:我们前端里面,id表示前端样式(或者js使用)name表示的是提交的后端参数名称.
我们修改一下name值看看报错:
运行结果:
分页接口
接口定义:
站在后端的角度: 当前页的数据(后端可以提供什么信息,返回给前端)
站在前端的角度: 当前页的数据,总记录数(前端页面展示时,需要什么信息)
url: /book/getBookListByPage
参数: pageNum
返回结果: list<>,count
后端代码:
我们进行分页操作,先使用sql来进解释:
假设我们一页有 10条记录
第一页: select * from book_info limit 0,10;(我们从第0条数据开始往后数10条数据)
第二页: select * from book_info limit 10,10;(我们从第11条数据开始往后数10条数据)
第一个数字是起始位置offset,第二个数字是limit,表示我们取多少条数据.pageSize表示每页条数,page表示第几页
offset = (page -1)*pageSize;limit = pageSIze
为了计算offset和limit,我们需要获取到pageNum和pageSize(这俩这个需要调用方告知)
mapper:我拼接上述的sql语句需要参数: offset,limt.
里面的count(1)表示我们计算有多少行数据.SELECT COUNT(1):这里的 COUNT(1) 意味着对表中的每一行返回一个数字 1,然后 COUNT 函数会对这些 1 进行计数,实际上就是统计行数。使用 COUNT(*) 或 COUNT(1) 都可以达到相同的目的,即计算表中的总行数。
controller: 从前端获取offset
service:调用mapper.getBookListBypage这个方法获取总的记录数和多少页,然后返回一个PageRequest对象
moodel: 我们进行翻页需要传的参数我们搞成一个整体也就是PageRequest.而PageResult也就是返回结果,我们使用泛型就可以封装起来,提高代码的复用性.
其中我们把图书的状态信息设置成了一个枚举类(提高代码的复用性)
枚举的命名规范: 枚举项用大写显示,多个单词使用_进行分割.
getDescByCode有以下几种实现方案
1> 返回desc
2> 返回枚举项
3> 写一个static代码块,把枚举信息存储在map中,get方法根据map的Key取查找(性能更高)
三层架构的再次理解:
controller:进行参数的校验,结果的返回,响应结果格式的转换
service: 获取数据,调用mapper里面的方法进行业务处理
mapper: 查询数据库
前端代码:
我们先介绍一个分页插件:jqPaginator分页组件
直接引入下面代码即可使用:
$('#id').jqPaginator({
totalPages: 100,
visiblePages: 10,
currentPage: 1,
onPageChange: function (num, type) {
$('#text').html('当前第' + num + '页');
}
});
我们使用一下:
我们使用location.search来从页面上获取pageNum(多少页),我们用户点击到第几页就算第几页
图书列表显示,点击的时候依次跳转
运行结果:
修改图书接口
接口定义:
1> 获取图书的信息,显示到页面上
根据图书ID, 获取当前图书的信息
2> 图书修改
针对 一些废弃的接口,如果是新项目,可以直接删除,如果已经上线了,下线某个接口,要写文档,删除前需要通过监控等工具,确保没有调用方在使用了.如果有调用方在使用,需要告知调用方新的接口是什么,对于废弃的接口,我们可以使用@Deprecated注解标识一下
获取图书信息
代码:
controller:
mapper:
service
运行结果:
更新图书
后端代码:
service:
controller:
mapper:
此刻拼接sql我们采用xml形式来写
先配置yml文件:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis⽇志
map-underscore-to-camel-case: true #配置驼峰⾃动转换
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
mapper-locations: classpath:mapper/*Mapper.xml
# 设置日志文件
logging:
file:
name: spring-book.log
然后我们通过xml来实现接口
运行结果:
前端代码:
我们要从url中获取bookId,可以有以下方式
对前端进行赋值
运行结果: 我们发现我们成功把值赋进去了
更新新操作
运行结果:
删除图书接口
删除一般分为俩种: 逻辑删除,物理删除
逻辑删除: update book_info set status = 0 where id =1
物理删除: delete from book_info where id = 25
在企业上一般会有删除+归档的操作,sql的写法: SQL INSERT INTO SELECT 语句 | 菜鸟教程
设计接口
逻辑删除,我们只是把状态进行修改了,并没有真正的删除这个数据
后端代码
controller:
service:
mapper:
前端代码
\
批量删除接口
后端代码:
controller:
service:
mapper:
xml实现接口:
运行结果:
前端代码:
运行结果:
强制登录接口
虽然我们做了用户登录, 但是我们发现, 用户不登录, 依然可以操作图书. 这是有极⼤⻛险的. 所以我们需要进⾏强制登录.如果用户未登录就访问图书列表或者添加图书等页面, 强制跳转到登录⻚⾯.
我们先复习一下session和cookie
会话: 在一段事件内我和这个系统进行交流(通过在浏览器点击应用相关的功能)也就是客户端和服务器进行的交流.而cookie和session就是一个会话机制.http请求时一个无状态的,为了让它有记忆功能,我们就采用cookie和session(存储机制).客户端用cookie来记忆,服务器用sesson来记. 在登录完之后会把用户信息存在session里面,下一次客户端再来,服务器就晓得它是谁了(根据sessionId)
host: ip
domain: url,ip等(标识是哪个机器)
这个就是domain
这个也是domain
在我们登录图书管理系统的时候,cookie就会有信息:sessionID
这是因为我们后端,服务器里面存的session会响应前端它有个setCookie
我们登录之后,session里面存了值,我们就可以去拿这个信息,如果发现userName和password都有值,那么这个用户就是登录了的.就可以进行更新,删除等操作.如果拿到的session值是空的,就说明没有登录,让用户跳转到登录界面.此时我们需要一个业务码进行判断
http状态码和业务码的区别
具体的流程
后端代码
model: Result
PageResult
枚举类: 我们的业务码设置为枚举类
像sessionID这种会变的,我们设置为一个常量,常量的规范写法: 单词大写,单词与单词之间用下划线连接
判断session里面的sessionId获取Session信息,判断是否用户登录
我们可以进行进一步的封装:
直接调用
运行结果
拿到session
一般HttpSession session: 从Cookie中获取SessionID,根据SessionID获取Session信息,如果有Session获取session.没有session就创建session
有时候报错可能是缓存问题
前端清除缓存: ctrl+shift+delete
后端清除缓存: maven点clean
前端代码
小总结:
我们在获取图书列表上增加了强制登录,我们也要分别在增加图书,删除图书上也增加强制登录,因此这个是个重复操作,我们此时就要用到统一功能的拦截器.因为篇幅比较长,我重新开一个博客继续介绍