GitHub:https://github.com/kzj666/bookstore
1.新建Spring Initializr項目
Spring Boot 版本2.2.5
勾選相關架構支持
Lombok
Spring Web
Thymeleaf
MySQL Driver
先新建一個TestController測試啓動
@RestController
public class TestController {
@RequestMapping("/test")
public String test(){
return "success";
}
}
springboot約定大於配置,默認是集成的thymleaf。
thymleaf約定
1.默認靜態文件(js,css,jpg等)放在resources下面的static文件夾下面
2頁面文件放在templates文件夾下面
默認情況下:
spring-boot項目靜態文件目錄:/src/java/resources/static (比如:js、css、img等靜態資源)
spring-boot項目模板文件目錄:/src/java/resources/templates
新建一個application.yml配置文件,用於配置
將官網下載的整個layui包導入static文件夾下
此時項目目錄如下(這裏的html頁面,經過thymeleaf改寫後放入templates,然後編寫相應的controller訪問)
配置thymeleaf
spring:
application:
name: myspringboot
output:
ansi:
enabled: always #高亮輸入日誌
profiles:
active: dev
thymeleaf:
encoding: UTF-8
prefix: classpath:/templates/ 默認到classpath:templates下檢索頁面
cache: false #關閉thymeleaf的緩存,不然在開發過程中修改頁面不會立刻生效需要重啓,生產可配置爲true
改寫的頁面都放在resource下的templates下
書寫login頁面,利用th重新改寫css和js的引入
<!DOCTYPE html>
<html class="x-admin-sm" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>KK書屋後臺登錄</title>
<meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi"/>
<meta http-equiv="Cache-Control" content="no-siteapp"/>
<link rel="stylesheet" th:href="@{/css/font.css}">
<link rel="stylesheet" th:href="@{/css/login.css}">
<link rel="stylesheet" th:href="@{/css/xadmin.css}">
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script th:src="@{lib/layui/layui.js}" charset="utf-8"></script>
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body class="login-bg">
<div class="login layui-anim layui-anim-up">
<div class="message">KK書屋管理系統</div>
<div id="darkbannerwrap"></div>
<div align="center"><p th:text="${msg}" style="color: red; font-size: 17px; font-weight: bold"></p></div>
<hr class="hr15">
<form method="post" class="layui-form" th:action="tologin">
<input name="username" placeholder="用戶名" type="text" lay-verify="required" class="layui-input">
<hr class="hr15">
<input name="password" lay-verify="required" placeholder="密碼" type="password" class="layui-input">
<hr class="hr15">
<input value="登錄" lay-submit lay-filter="login" style="width:100%;" type="submit">
<hr class="hr20">
</form>
</div>
<div align="center" style="margin-top: 300px;">
<p>© 2020 <a href="http://www.layui.com/" target="_blank">湖人總冠軍</a></p>
<br>
<span><a href="https://blog.csdn.net/K_kzj_K" target="_blank">CSDN</a></span>
<span style="padding-left:30px;"><a href="https://github.com/kzj666" target="_blank">GitHub</a></span>
<span style="padding-left:30px;"><a href="https://blog.csdn.net/K_kzj_K/article/details/104878242" target="_blank">開發文檔</a></span>
</div>
</body>
</html>
然後在controller包下創建一個LoginController控制類,編寫login請求映射,return”login”,即跳轉到login頁面
Controller
public class LoginController {
@GetMapping({"/login","/"})
public String login(){
return "login";
}
}
請求localhost:8080/或者localhost:8080/login即可訪問
再寫一個index後臺頁面,同理也要利用th重新改寫css和js的引入
<!doctype html>
<html class="x-admin-sm" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>KKBOOKSTORE後臺</title>
<meta name="renderer" content="webkit|ie-comp|ie-stand">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi"/>
<meta http-equiv="Cache-Control" content="no-siteapp"/>
<link rel="stylesheet" th:href="@{/css/font.css}">
<link rel="stylesheet" th:href="@{/css/xadmin.css}">
<!-- <link rel="stylesheet" href="./css/theme5.css"> -->
<script th:src="@{/lib/layui/layui.js}" charset="utf-8"></script>
<script type="text/javascript" th:src="@{/js/xadmin.js}"></script>
<!-- 讓IE8/9支持媒體查詢,從而兼容柵格 -->
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<script>
// 是否開啓刷新記憶tab功能
// var is_remember = false;
</script>
</head>
<body class="index">
<!-- 頂部開始 -->
<div class="container">
<div class="logo">
<a th:href="@{/index}">KK書屋後臺</a></div>
<div class="left_open">
<a><i title="展開左側欄" class="iconfont"></i></a>
</div>
<ul class="layui-nav left fast-add" lay-filter="">
<li class="layui-nav-item">
<a href="javascript:;">+新增</a>
<dl class="layui-nav-child">
<!-- 二級菜單 -->
<dd>
<a onclick="xadmin.open('最大化','http://www.baidu.com','','',true)">
<i class="iconfont"></i>彈出最大化</a></dd>
<dd>
<a onclick="xadmin.open('彈出自動寬高','http://www.baidu.com')">
<i class="iconfont"></i>彈出自動寬高</a></dd>
<dd>
<a onclick="xadmin.open('彈出指定寬高','http://www.baidu.com',500,300)">
<i class="iconfont"></i>彈出指定寬高</a></dd>
<dd>
<a onclick="xadmin.add_tab('在tab打開','member-list.html')">
<i class="iconfont"></i>在tab打開</a></dd>
<dd>
<a onclick="xadmin.add_tab('在tab打開刷新','member-del.html',true)">
<i class="iconfont"></i>在tab打開刷新</a></dd>
</dl>
</li>
</ul>
<ul class="layui-nav right" lay-filter="">
<li class="layui-nav-item">
<a href="javascript:;" th:text="${session.user.username}"></a>
<dl class="layui-nav-child">
<!-- 二級菜單 -->
<dd>
<a th:value="${session.user.id}" onclick="xadmin.open2('個人信息','/user/to-admin-edit/','','',false,this)">個人信息</a></dd>
<dd>
<a onclick="xadmin.open('切換帳號','http://www.baidu.com')">切換帳號</a></dd>
<dd>
<a th:href="@{/logout}">退出</a></dd>
</dl>
</li>
<li class="layui-nav-item to-index">
<a href="/">前臺首頁</a></li>
</ul>
</div>
<!-- 頂部結束 -->
<!-- 中部開始 -->
<!-- 左側菜單開始 -->
<div class="left-nav">
<div id="side-nav">
<ul id="nav">
<li shiro:hasPermission="U">
<a href="javascript:;">
<i class="iconfont left-nav-li" lay-tips="管理員管理"></i>
<cite>管理員管理</cite>
<i class="iconfont nav_right"></i></a>
<ul class="sub-menu">
<li>
<a onclick="xadmin.add_tab('管理員列表','user/selectList')">
<i class="iconfont"></i>
<cite>管理員列表</cite></a>
</li>
</ul>
</li>
<li>
<a href="javascript:;">
<i class="iconfont left-nav-li" lay-tips="書架"></i>
<cite>書架</cite>
<i class="iconfont nav_right"></i></a>
<ul class="sub-menu">
<li>
<a onclick="xadmin.add_tab('所有書籍','book/selectList')">
<i class="iconfont"></i>
<cite>所有書籍</cite></a>
</li>
</ul>
</li>
</ul>
</div>
</div>
<!-- <div class="x-slide_left"></div> -->
<!-- 左側菜單結束 -->
<!-- 右側主體開始 -->
<div class="page-content">
<div class="layui-tab tab" lay-filter="xbs_tab" lay-allowclose="false">
<ul class="layui-tab-title">
<li class="home">
<i class="layui-icon"></i>我的桌面
</li>
</ul>
<div class="layui-unselect layui-form-select layui-form-selected" id="tab_right">
<dl>
<dd data-type="this">關閉當前</dd>
<dd data-type="other">關閉其它</dd>
<dd data-type="all">關閉全部</dd>
</dl>
</div>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
<iframe th:src="@{./welcome.html}" frameborder="0" scrolling="yes" class="x-iframe"></iframe>
</div>
</div>
<div id="tab_show"></div>
</div>
</div>
<div class="page-content-bg"></div>
<style id="theme_style"></style>
<!-- 右側主體結束 -->
<!-- 中部結束 -->
</body>
</html>
在LoginController寫一個請求處理方法
@PostMapping("/index")
public String tologin(){
return "index";
}
訪問localhost:8080/tologin,跳轉到index頁面
2.設計表結構
腳本內容
/*
Navicat MySQL Data Transfer
Source Server : localhost_3306
Source Server Version : 80018
Source Host : localhost:3306
Source Database : bookstore
Target Server Type : MYSQL
Target Server Version : 80018
File Encoding : 65001
Date: 2020-03-24 10:21:25
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for book
-- ----------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bookname` varchar(30) NOT NULL,
`pub` varchar(30) NOT NULL,
`price` decimal(10,0) NOT NULL,
`date` datetime NOT NULL,
`count` int(11) NOT NULL,
`kind` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book` VALUES ('1', '霸道總裁愛上我', '總裁出版社', '58', '2020-03-23 20:01:27', '20', '言情類');
INSERT INTO `book` VALUES ('2', '霸道總裁迷上我', '總裁出版社', '58', '2020-03-23 06:01:27', '10', '言情類');
INSERT INTO `book` VALUES ('5', '西遊記', '西遊出版社', '66', '2020-02-02 06:00:00', '40', '文學類');
INSERT INTO `book` VALUES ('6', '三國演義', '三國出版社', '68', '2019-12-31 16:00:00', '45', '文學類');
INSERT INTO `book` VALUES ('7', '紅樓夢', '紅樓出版社', '58', '2020-01-01 06:00:00', '23', '言情類');
INSERT INTO `book` VALUES ('8', '水滸傳', '水壺出版社', '76', '2024-08-03 06:00:00', '50', '文學類');
INSERT INTO `book` VALUES ('9', '人生', '神祕出版社', '66', '2020-02-22 16:00:00', '50', '文學類');
INSERT INTO `book` VALUES ('10', '貓生', '神祕出版社', '38', '2020-02-01 16:00:00', '10', '文學類');
INSERT INTO `book` VALUES ('11', '狗生', '神祕出版社', '33', '2020-02-01 16:00:00', '20', '文學類');
INSERT INTO `book` VALUES ('12', '蛋生', '神祕出版社', '45', '2020-02-01 16:00:00', '32', '文學類');
INSERT INTO `book` VALUES ('13', '鳥生', '神祕出版社', '45', '2020-02-01 16:00:00', '32', '文學類');
INSERT INTO `book` VALUES ('14', '龍生', '神祕出版社', '45', '2020-02-02 06:00:00', '32', '文學類');
INSERT INTO `book` VALUES ('15', '雞生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('16', '鴨生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('17', '花生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('18', '強生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('19', '醫生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('20', '鵝生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('21', '豬生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('22', '餘生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('23', '獅生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('24', '豹生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('25', '狒生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('26', '鹿生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('27', '象生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('28', '馬生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('29', '牛生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('30', '羊生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
INSERT INTO `book` VALUES ('31', '學生', '神祕出版社', '50', '2020-02-01 16:00:00', '12', '文學類');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(20) DEFAULT NULL,
`phone` varchar(11) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`sex` char(5) DEFAULT NULL,
`perm` varchar(20) DEFAULT NULL,
`role` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'kk', '123123', '13531174771', '[email protected]', 'm', 'A-C-U-R-D', 'super-administrator');
INSERT INTO `user` VALUES ('2', 'zz', '123456', '13531174771', '[email protected]', 'f', 'C-R-U', 'adminstrator');
INSERT INTO `user` VALUES ('3', 'jj', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
INSERT INTO `user` VALUES ('4', 'aa', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
INSERT INTO `user` VALUES ('5', 'bb', '123456', '13531174771', '[email protected]', 'f', 'R', 'guest');
INSERT INTO `user` VALUES ('7', 'dd', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
INSERT INTO `user` VALUES ('8', 'ee', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
INSERT INTO `user` VALUES ('9', 'ff', '123456', '13531174771', '[email protected]', 'f', 'R', 'guest');
INSERT INTO `user` VALUES ('10', 'gg', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
INSERT INTO `user` VALUES ('12', 'ii', '123456', '13531174771', '[email protected]', 'f', 'R', 'guest');
INSERT INTO `user` VALUES ('14', 'mm', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
INSERT INTO `user` VALUES ('15', 'nn', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
INSERT INTO `user` VALUES ('16', 'oo', '123456', '13531174771', '[email protected]', 'f', 'R', 'guest');
INSERT INTO `user` VALUES ('17', 'pp', '123456', '13531174771', '[email protected]', 'f', 'R', 'guest');
INSERT INTO `user` VALUES ('26', 'rr', '123456', '13531174771', '[email protected]', 'm', 'R', 'guest');
3.生成各層代碼
利用idea的easycode插件 ,生成相應代碼
在dao接口上加上@Mapper註解,在主啓動類上加上@MapperScan(“com.kk.dao”)
爲UserDao接口新增一個根據用戶名檢索的查詢(用於shiro認證)
/**
* 通過username查詢單挑數據
* @param username 用戶名
* @return 實例對象
*/
@Select("select * from user where username = #{username}")
User queryByName(@Param("username") String username);
同樣在UserService接口額UserServiceImpl下實現
4.集成mybatis和mysql
選擇一個數據源,這裏選擇druid數據源
後面還要實現分頁功能,所以導入pagehelper的依賴,分頁的依賴jar包依賴於mybatis,會自動導入mybats依賴,所以只需導入如下兩個依賴
<!--druid數據源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- 分頁插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
追加mybatis和數據源的配置
server:
tomcat:
uri-encoding: UTF-8
max-connections: 500
min-spare-threads: 25
max-threads: 300
accept-count: 200
port: 9090
mybatis:
type-aliases-package: com.kk.entity
mapper-locations: classpath:mapper/*.xml
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
logging:
level:
com.kk.entity: debug
---
#開發配置
#autoReconnect mysql超過8小時沒有操作,會自動斷開連接,該屬性可配置是否重新連接?
#failOverReadOnly 自動重連成功後,連接是否設置爲只讀?
#useSSL=false MySQL在高版本需要指明是否進行SSL連接?
spring:
profiles: dev
datasource:
url: jdbc:mysql://localhost:3306/kk_book?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&autoReconnect=true&failOverReadOnly=false&useSSL=false
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
filters: stat
#最大連接個數
maxActive: 20
#初始化連接個數
initialSize: 1
#獲取連接時最大等待時間,單位毫秒。
maxWait: 60000
#最小空閒連接個數
minIdle: 1
#配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
#配置一個連接在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 300000
#用來檢測連接是否有效的sql,要求是一個查詢語句。
validationQuery: select 'x'
#建議配置爲true,不影響性能,並且保證安全性。如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
testWhileIdle: true
#申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能
testOnBorrow: false
#歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能
testOnReturn: false
#是否緩存preparedStatement,也就是PSCache。PSCache對支持遊標的數據庫性能提升巨大,比如說oracle。在mysql下建議關閉。
poolPreparedStatements: true
#最大緩存數
maxOpenPreparedStatements: 20
5.實現index頁面的書籍列表
EasyCode在UserDao接口中有自動生成queryAll(User)的方法是根據User字段查詢記錄,我們加入無參數的查詢所有記錄的方法queryListl(),並且在UserService和UserServiceImpl中實現一下,然後再在UserController中對請求進行處理
/**
* 查詢出所有記錄
* @return 實例對象列表
*/
@Select("select * from user")
List<User> queryList();
在userController中添加對請求的處理
@GetMapping("selectList")
public String selectList(Model model, @RequestParam(value = "pn", defaultValue = "1") int pn, @RequestParam(value = "size", defaultValue = "5") int size) {
PageHelper.startPage(pn, size);
List<User> users = userService.queryList();
PageInfo<User> page = new PageInfo<>(users);
model.addAttribute("users", users);
model.addAttribute("page", page);
return "user/admin-list";
}
跳轉到自己改寫的admin-list頁面,頁面中的各種增刪改查操作,都是通過改寫js事件實現觸發,發出指定請求。(頁面的改寫就不在這裏碼出來了,主要是一些按鈕的觸發事件改寫,如點擊【添加】和【編輯】按鈕,彈出添加和編輯的頁面,點擊【刪除】,發送刪除請求。這些請求都要在controller中進行映射處理)
<!DOCTYPE html>
<html class="x-admin-sm" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>歡迎頁面-X-admin2.2</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport"
content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi"/>
<link rel="stylesheet" th:href="@{/css/font.css}">
<link rel="stylesheet" th:href="@{/css/xadmin.css}">
<script th:src="@{/lib/layui/layui.js}" charset="utf-8"></script>
<script type="text/javascript" th:src="@{/js/xadmin.js}"></script>
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="x-nav">
<span class="layui-breadcrumb">
<a href="">首頁</a>
<a href="">演示</a>
<a>
<cite>導航元素</cite></a>
</span>
<a class="layui-btn layui-btn-small" style="line-height:1.6em;margin-top:3px;float:right"
onclick="location.reload()" title="刷新">
<i class="layui-icon layui-icon-refresh" style="line-height:30px"></i></a>
</div>
<div class="layui-fluid">
<div class="layui-row layui-col-space15">
<div class="layui-col-md12">
<div class="layui-card">
<div class="layui-card-body ">
<form class="layui-form layui-col-space5">
<div class="layui-inline layui-show-xs-block">
<input class="layui-input" autocomplete="off" placeholder="開始日" name="start" id="start">
</div>
<div class="layui-inline layui-show-xs-block">
<input class="layui-input" autocomplete="off" placeholder="截止日" name="end" id="end">
</div>
<div class="layui-inline layui-show-xs-block">
<input type="text" name="username" placeholder="請輸入用戶名" autocomplete="off"
class="layui-input">
</div>
<div class="layui-inline layui-show-xs-block">
<button class="layui-btn" lay-submit="" lay-filter="sreach"><i
class="layui-icon"></i></button>
</div>
</form>
</div>
<div class="layui-card-header">
<button shiro:hasPermission="D" class="layui-btn layui-btn-danger" onclick="delAll()"><i class="layui-icon"></i>批量刪除
</button>
<!--這裏的url有點奇怪,本來應該是請求user/to-admin-add從而跳到admin-add頁面的,
但是報404,發現是user/user/to-admin-add,去掉user,反而能正確加載頁面,不明所以,尚未深究-->
<button shiro:hasPermission="C" class="layui-btn" onclick="xadmin.open('添加用戶','to-admin-add',600,540)"><i
class="layui-icon"></i>添加
</button>
</div>
<div class="layui-card-body ">
<table class="layui-table layui-form">
<thead>
<tr >
<th>
<input type="checkbox" name="" lay-skin="primary">
</th>
<th style="text-align: center" width="16%">ID</th>
<th width="16%">登錄名</th>
<th width="16%">手機</th>
<th width="16%">郵箱</th>
<th width="16%">性別</th>
<th width="16%">角色</th>
<!--<th>加入時間</th>-->
<!--<th>狀態</th>-->
<th>操作</th>
</thead>
<tbody>
<tr th:each="user:${page.list}">
<td>
<input type="checkbox" name="" lay-skin="primary">
</td>
<td th:text="${user.id}"></td>
<td th:text="${user.username}"></td>
<td th:text="${user.phone}"></td>
<td th:text="${user.email}"></td>
<td th:text="${user.sex}"></td>
<td th:text="${user.role}"></td>
<td class="td-manage">
<!--<a onclick="member_stop(this,'10001')" href="javascript:;" title="啓用">-->
<!--<i class="layui-icon"></i></a>-->
<!--改寫xadmin.open()方法,傳this過去,就能根據id查詢然後回顯數據,注意不要重名-->
<a shiro:hasPermission="A" title="編輯" th:value="${user.id}" onclick="xadmin.open2('編輯','/user/to-admin-edit/','','',false,this)" href="javascript:;">
<i class="layui-icon"></i>
</a>
<span> </span>
<!--有Delete權限的用戶才顯示刪除按鈕-->
<!--onclick,將本標籤元素this傳入-->
<a shiro:hasPermission="D" title="刪除" th:value="${user.id}" onclick=member_del(this)>
<i class="layui-icon"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="layui-card-body ">
<div class="page">
<!--當前頁數小於等於1時,上一頁還是顯示第一頁的信息-->
<div th:if="${page.pageNum} le 1">
<a class="prev" th:href="@{/user/selectList(pn=1)}"><<</a>
<a class="num" th:href="@{/user/selectList(pn=${page.pageNum})}">上一頁</a>
<span class="current" th:href="@{/user/selectList(pn=${page.pageNum})}" th:text="${page.pageNum}"></span>
<a class="num" th:if="${page.pageNum} lt ${page.pages}" th:href="@{/user/selectList(pn=${page.pageNum+1})}">下一頁</a>
<a class="next" th:href="@{/user/selectList(pn=${page.pages})}">>></a>
</div>
<!--當前頁數大於等於最後一頁時,點擊下一頁還是顯示最尾頁頁的信息-->
<div th:if="${page.pageNum} != 1 and ${page.pageNum} ge ${page.pages}">
<a class="prev" th:href="@{/user/selectList(pn=1)}"><<</a>
<a class="num" th:href="@{/user/selectList(pn=${page.pageNum-1})}">上一頁</a>
<span class="current" th:href="@{/user/selectList(pn=${page.pageNum})}" th:text="${page.pageNum}"></span>
<a class="num" th:href="@{/user/selectList(pn=${page.pageNum})}">下一頁</a>
<a class="next" th:href="@{/user/selectList(pn=${page.pages})}">>></a>
</div>
<!--非首尾頁-->
<div th:if="${page.pageNum} gt 1 and ${page.pageNum} lt ${page.pages}">
<a class="prev" th:href="@{/user/selectList(pn=1)}"><<</a>
<a class="num" th:href="@{/user/selectList(pn=${page.pageNum-1})}">上一頁</a>
<span class="current" th:href="@{/user/selectList(pn=${page.pageNum})}" th:text="${page.pageNum}"></span>
<a class="num" th:href="@{/user/selectList(pn=${page.pageNum+1})}">下一頁</a>
<a class="next" th:href="@{/user/selectList(pn=${page.pages})}">>></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
layui.use(['laydate', 'form'], function () {
var laydate = layui.laydate;
var form = layui.form;
//執行一個laydate實例
laydate.render({
elem: '#start' //指定元素
});
//執行一個laydate實例
laydate.render({
elem: '#end' //指定元素
});
});
/*用戶-停用*/
function member_stop(obj, id) {
layer.confirm('確認要停用嗎?', function (index) {
if ($(obj).attr('title') == '啓用') {
//發異步把用戶狀態進行更改
$(obj).attr('title', '停用')
$(obj).find('i').html('');
$(obj).parents("tr").find(".td-status").find('span').addClass('layui-btn-disabled').html('已停用');
layer.msg('已停用!', {icon: 5, time: 1000});
} else {
$(obj).attr('title', '啓用')
$(obj).find('i').html('');
$(obj).parents("tr").find(".td-status").find('span').removeClass('layui-btn-disabled').html('已啓用');
layer.msg('已啓用!', {icon: 5, time: 1000});
}
});
}
/*用戶-刪除*/
function member_del(obj) {
//拿到元素的value(即id),傳給異步刪除的請求
var id = obj.getAttribute('value');
// console.log("///////////////////////////////////////////////"+id);
layer.confirm('確認要刪除嗎?', function (index) {
//發異步刪除數據
$(obj).parents("tr").remove();
//在確認刪除後,發送刪除請求
$.ajax({
type: 'GET',
url: "/user/deleteById",
data: {'id':id},
});
layer.msg('已刪除!', {icon: 1, time: 1000});
});
}
function delAll(argument) {
var data = tableCheck.getData();
layer.confirm('確認要刪除嗎?' + data, function (index) {
//捉到所有被選中的,發異步進行刪除
layer.msg('刪除成功', {icon: 1});
$(".layui-form-checked").not('.header').parents('tr').remove();
});
}
</script>
</html>
同理實現書籍列表的增刪改查頁面
在書籍列表中有一個根據類別查詢,改變本來的form提交方式(因爲form好像會被自動監聽,發送一個我不期望的請求,所以乾脆不用form標籤包裹),自定義一個function()監聽搜索按鈕的點擊,然後提交輸入的內容,拼接成url,發送搜索請求
【傳參數進行查詢時,後臺接收參數後,利用StringUtils.deleteWhitespace(kind)進行去空格】
這個工具類需要導入commons-lang依賴
<!--commons-lang依賴-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
6.shiro實現認證、授權
導入相關依賴
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.2</version>
</dependency>
<!--thymeleaf-extras-shiro依賴-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
在con.kk.config包下新建一個名爲ShiroConfig的java文件
用@Configuration聲明此類爲一個配置類,配置shiro相關配置
此配置類旨在注入ShiroFilterFactoryBean對象,所以寫一個返回值爲ShiroFilterFactoryBean的方法並注入容器(@Bean),該方法中需要注入安全管理器DefaultWebSecurityManager,所以先就new一個管理器放入容器,然而manager需要注入自定義的Realm,所以需要自定義Realm。邏輯如下
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
return bean;
}
@Bean
public DefaultWebSecurityManager securityManager(MyRealm myRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm);
return manager;
}
@Bean
public MyRealm myRealm(){
return new MyRealm();
}
}
那就在同包下新建一個繼承AuthorizingRealm的MyRealm類,實現doGetAuthenticationInfo認證方法和DoGetAuthorizationInfo授權方法
package com.kk.config;
/*
@author kzj
@date 2020/3/18 - 17:43
*/
import com.kk.entity.User;
import com.kk.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
import java.util.HashSet;
public class MyRealm extends AuthorizingRealm {
private static final Logger logger = LoggerFactory.getLogger(MyRealm.class);
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("------------------------------->執行了授權");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//獲取當前登錄的對象
User user = (User)principals.getPrimaryPrincipal();
//拿到登錄對象的權限,用-分離出每一個權限,放入數組
String[] perm = user.getPerm().split("-");
//將整個perm數組轉成集合放入HashSet中
HashSet<String> set = new HashSet<>(Arrays.asList(perm));
info.setStringPermissions(set);
//添加權限
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("------------------------------->執行了認證");
//userToken:是一個簡單的用戶名/密碼身份驗證令牌,以支持使用最廣泛的身份驗證機制。
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
String username = userToken.getUsername();
//驗證用戶名是否在數據庫中存在
User user = userService.queryByName(username);
if (user == null) {
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
}
寫完doGetAuthenticationInfo邏輯後,回到LoginController實現登錄認證
@Controller
public class LoginController {
@GetMapping({"login", "/"})
public String login() {
return "login";
}
@PostMapping("tologin")
public String tologin(User user, Model model) {
String username = user.getUsername();
String password = user.getPassword();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
//login認證通過後,便可拿到shiro保存的用戶對象
User user1 = (User) subject.getPrincipal();
subject.getSession().setAttribute("user", user1);
return "redirect:/index";
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用戶名錯誤");
return "login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密碼錯誤");
return "login";
} catch (ExcessiveAttemptsException e) {
model.addAttribute("msg", "登錄失敗次數過多");
return "login";
} catch (LockedAccountException e) {
model.addAttribute("msg", "帳號已被鎖定. The account for username " + token.getPrincipal() + " was locked.");
return "login";
} catch (DisabledAccountException e) {
model.addAttribute("msg", "帳號已被禁用. The account for username " + token.getPrincipal() + " was disabled.");
return "login";
} catch (ExpiredCredentialsException e) {
model.addAttribute("msg", "帳號已過期. the account for username " + token.getPrincipal() + " was expired.");
return "login";
} catch (UnauthorizedException e) {
model.addAttribute("msg", "您沒有得到相應的授權!" + e.getMessage());
return "login";
}
}
接着到shiroconfig配置類下的ShiroFilterFactoryBean下配置攔截鏈
@Configuration
public class ShiroConfig {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
/*
anon: 無需認證就可以訪問
authc: 必須認證了才能訪問
user: 必須擁有 記住我 功能才能使用
perms: 擁有某個資源的權限才能訪問
role: 擁有某個角色權限才能訪問
*/
//定義攔截鏈
LinkedHashMap<String, String> filtermap = new LinkedHashMap<>();
// 配置不會被攔截的鏈接 順序判斷
filtermap.put("/*/*.js", "anon");
filtermap.put("/*/*.css", "anon");
filtermap.put("*.html", "anon");
filtermap.put("/tologin", "anon");
filtermap.put("/login", "anon");
filtermap.put("/index", "authc");
// 配置退出過濾器,其中的具體的退出代碼Shiro已經替我們實現了
filtermap.put("/logout", "logout");
//攔截所有請求,一般放最後面
filtermap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filtermap);
//設置登錄要跳轉的鏈接
//如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
bean.setLoginUrl("/login");
//設置成功後要跳轉的鏈接
bean.setSuccessUrl("/index");
//設置未授權頁面
bean.setUnauthorizedUrl("/noauth");
return bean;
}
最後在開啓shiro的註解功能,最終的ShirConfig文件如下
package com.kk.config;
/*
@author kzj
@date 2020/3/18 - 17:38
*/
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
/*
anon: 無需認證就可以訪問
authc: 必須認證了才能訪問
user: 必須擁有 記住我 功能才能使用
perms: 擁有某個資源的權限才能訪問
role: 擁有某個角色權限才能訪問
*/
//定義攔截鏈
LinkedHashMap<String, String> filtermap = new LinkedHashMap<>();
// 配置不會被攔截的鏈接 順序判斷
filtermap.put("/*/*.js", "anon");
filtermap.put("/*/*.css", "anon");
filtermap.put("*.html", "anon");
filtermap.put("/tologin", "anon");
filtermap.put("/login", "anon");
filtermap.put("/index", "authc");
// 配置退出過濾器,其中的具體的退出代碼Shiro已經替我們實現了
filtermap.put("/logout", "logout");
//攔截所有請求,一般放最後面
filtermap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filtermap);
//設置登錄要跳轉的鏈接
//如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
bean.setLoginUrl("/login");
//設置成功後要跳轉的鏈接
bean.setSuccessUrl("/index");
//設置未授權頁面
bean.setUnauthorizedUrl("/noauth");
return bean;
}
@Bean
public DefaultWebSecurityManager securityManager(MyRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
@Bean
public MyRealm myRealm() {
return new MyRealm();
}
/**
* 整合shiroDialect:用來整合shiro和thymeleaf
*
* @return
*/
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
/**
* 開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證
* 配置以下兩個bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可實現此功能
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 開啓aop註解支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
便可實現登錄認證,對於授權:在相應需要權限的位置利用shiro命名空間(如只有super-administrator才能delete,則在delete按鈕標籤添加shiro命名空間,shiro:hasPermission="D"),實現不同權限不同操作。
這裏再說明shiro的執行過程
進行了上述的shiro配置後,在LoginController中,
//拿到前臺傳來的user對象,拿到用戶名和密碼,放入令牌
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
//SecurityUtils拿到當前的登錄對象
Subject subject = SecurityUtils.getSubject();
//登錄
subject.login(token);
執行subject.login(token);會跳轉到Realm中的doGetAuthenticationInfo()方法中,進行認證,如果認證不通過,會拋出異常;如果認證通過,方法中return new SimpleAuthenticationInfo(user, user.getPassword(), getName());返回了認證通過的用戶信息,則LoginController中可以通過(User) subject.getPrincipal();拿到通過認證的用戶,然後可放入session等等其他操作。
Shiro詳解
最後在template下創建error目錄,以各種對應錯誤的響應碼命名自定義的錯誤頁面(如:404.html),當發生相應錯誤時,會自動顯示自定義的錯誤頁面(約定大於配置)
請求列表
GitHub:https://github.com/kzj666/bookstore