SpringBoot + Shiro安全框架 + vue
先看效果
1.用户名错误
2密码错误
3.正常登录
4.登出,跳转到登录页面
5.不携带token测试 查看选课列表
6.登录后
(没有查看课程权限的用户登录)
携带Token测试
7.登录后
(有查看课程权限的用户登录)
携带Token测试
一、准备工作
①:创建项目
01.创建java项目
事先创建好一些包
02.配置文件和依赖
- 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 生成配置元数据-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--引入hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- json转换工具-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<!-- 字符串处理工具-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- mybatis 分页工具-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.jeffreyning</groupId>
<artifactId>mybatisplus-plus</artifactId>
<version>1.3.1-RELEASE</version>
</dependency>
</dependencies>
- yml文件
server:
port: 8086
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://1.17.91.114:3306/shirodb?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: www.com
redis:
host: 1.17.91.114
password: www.com
port: 6379
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
type-aliases-package: com.it.App.eneity
configuration:
map-underscore-to-camel-case: true
use-generated-keys: true
pagehelper:
helperDialect: mysql
reasonable: true
supportMethodsArguments: true
params: count=countSql
mpp: # mpp支持多个字段联合主键(复合主键)增删改查,mapper需要继承MppBaseMapper
entityBasePath: com.it.App.eneit
coke:
jwt:
header: Authorization
expire: 604800 #7天,秒单位
secret: ji8n3439n439n43ld9ne9343fdfer49h
03.创建Vue项目
1 安装node.js
官网(https://nodejs.org/zh-cn/)
2.安装完成之后检查下版本信息:
创建vue项目
1.接下来,我们安装vue的环境
# 安装淘宝npm
npm install -g cnpm --registry=https://registry.npm.taobao.org
# vue-cli 安装依赖包
cnpm install --g vue-cli
# 打开vue的可视化管理工具界面
vue ui
2.创建
spring_security_vue
项目 运行vue ui
3. 会为我们打开一个http://localhost:8001/dashboard的页面:
4.我们将在这个页面完成我们的前端Vue项目的新建。然后切换到【创建】,注意创建的目录最好是和你运行vue ui同一级。这样方便管理和切换
5.然后点击按钮【在此创建新项目】下一步中,项目文件夹中输入项目名称“sping_security_vue”
6.点击下一步,选择【手动】,再点击下一步,如图点击按钮,勾选上路由Router、状态管理Vuex,去掉js的校验。
7.下一步中,也选上【Use history mode for router】,点击创建项目,然后弹窗中选择按钮【创建项目,不保存预设】,就进入项目创建啦
稍等片刻之后,项目就初始化完成了。上面的步骤中,我们创建了一个vue项目,并且安装了Router、Vuex。这样我们后面就可以直接使用。
Router: WebApp的链接路径管理系统,简单就是建立起url和页面之间的映射关系
Vuex: 一个专为 Vue.js 应用程序开发的状态管理模式,简单来说就是为了方便数据的操作而建立的一个临时” 前端数据库“,用于各个组件间共享和检测数据变化。
ok,我们使用IDEA导入项目,看看创建好的项目长啥样子:
启动项目
1.然后我们在IDEA窗口的底部打开Terminal命令行窗口,输入
yarn run serve
运行vue项目,我们就可以通过http://localhost:8080/打开我们的项目了。
2.效果如下,Hello Vue!
04.前端配置
1.安装axios
接下来,我们来安装axios(http://www.axios-js.com/),axios是一个基于 promise 的 HTTP 库,这样我们进行前后端对接的时候,使用这个工具可以提高我们的开发效率。
1.安装命令
yarn add axios --save
2.在main.js中全局引入axios
import axios from 'axios'
Vue.prototype.$axios = axios
2.安装Ant Design of Vue
1.安装命令
yarn add ant-design-vue@1.7.8 -S
2.在main.js中全局引入Ant
import Antd, {Modal,message} from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
Vue.use(Antd);
Vue.prototype.$message = message;
Vue.prototype.$warning = Modal.warning;
3.测试是否引入成功
1.首先可以删除自动生成的其他页面 留一个HelloWorld.vue页面做测试即可
2.引入几个按钮看效果
<a-space>
<a-button type="primary">
Primary
</a-button>
<a-button>Default</a-button>
<a-button type="dashed">
Dashed
</a-button>
<a-button type="danger">
Danger
</a-button>
</a-space>
- 引入成功
4. 安装vue-loader
1.安装命令
yarn add vue-loader@15.7.1
5. 安装worker-loader
1.安装命令
yarn add worker-loader@3.0.8
6. 安装less
1.安装命令
yarn add less@3.10.3 -D
yarn add less-loader@5.0.0 -D
7.创建axios.js 配置基础请求url和响应拦截
1.在src目录下创建axios.js
// 引入所需的库和模块
import axios from "axios";
import router from "@/router"; // 假设这是指向路由模块的路径
// 设置所有 Axios 请求的基础 URL
axios.defaults.baseURL = "http://127.0.0.1:8086";
// 创建一个具有自定义设置的 Axios 实例
let request = axios.create({
timeout: 30000, // 设置请求的超时时间为5000毫秒
headers: {
'Content-Type': 'application/json;charset=utf-8' // 设置请求数据的内容类型为 JSON
}
});
// 在处理响应之前拦截响应
request.interceptors.response.use(response => {
// 从响应中提取数据
let res = response.data;
// 检查响应代码是否为200(成功)
if (res.success) {
return response; // 如果成功,则返回响应
} else {
// 如果响应代码不是200,显示错误消息
this.$message.error(res.msg ? res.msg : '系统异常');
return Promise.reject(res.msg); // 使用错误消息拒绝 Promise
}
}, error => {
console.log('error', error);
// 处理特定的错误情况
if (error.code === 401) {
router.push('/login'); // 如果错误代码是401(未经授权),则重定向到登录页面
}
console.log(error.message);
// 显示错误消息,持续时间为3000毫秒
this.$message.error(error.message);
return Promise.reject(error.message); // 使用错误消息拒绝 Promise
});
// 将配置好的 Axios 实例导出,以在应用程序的其他部分中使用
export default request;
2.在src目录下创建api文件夹
- 通过js定义请求接口api
- 写一个测试接口等下用于测试
import axios from "@/axios";
// 测试前后端接口交互
export function getUserList(data) {
return axios({
url: '/user/test',
method: 'get',
params: data
})
}
08.弹出红色报错遮罩层
- 弹出红色报错遮罩层
module.exports = defineConfig({
transpileDependencies: true,
productionSourceMap: false,
devServer: {
// 解决页面弹出红色报错遮罩层
client: {
//将overlay设置为false即可
overlay: false
}
}
})
②:配置彩色日志
③:整合knife4j生成Api接口文档
④:数据库 数据准备
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` int NOT NULL AUTO_INCREMENT,
`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`password` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
`nickname` varchar(255) NOT NULL,
`school_id` int NOT NULL,
`sex` varchar(255) NOT NULL,
`tel` varchar(100) NOT NULL,
`student_class` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`) USING BTREE,
KEY `fk_sch` (`school_id`),
CONSTRAINT `fk_sch` FOREIGN KEY (`school_id`) REFERENCES `school` (`school_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '20201001', '4d3d7008836ee74feebc32a43ed24181', 'wcwad12', '张三', '1', '男', '12135', '计算机2018-01班');
INSERT INTO `user` VALUES ('2', '20201002', '4d3d7008836ee74feebc32a43ed24181', 'wcwad12', '李四', '1', '女', '215454', null);
INSERT INTO `user` VALUES ('3', '20201003', '4d3d7008836ee74feebc32a43ed24181', 'wcwad12', '王五', '1', '男', '1548', null);
INSERT INTO `user` VALUES ('4', '20201004', '4d3d7008836ee74feebc32a43ed24181', 'wcwad12', '管理员', '1', '男', '1558', null);
INSERT INTO `user` VALUES ('5', '20201005', '4d3d7008836ee74feebc32a43ed24181', 'wcwad12', '丽丽', '1', '女', '2166', '计算机2018-02班');
INSERT INTO `user` VALUES ('6', '20201006', '4d3d7008836ee74feebc32a43ed24181', 'wcwad12', '呜呜', '1', '男', '1158', null);
-- ----------------------------
-- Table structure for choose_course
-- ----------------------------
DROP TABLE IF EXISTS `choose_course`;
CREATE TABLE `choose_course` (
`user_id` int NOT NULL,
`course_id` int NOT NULL,
`usual_grade` double(4,1) DEFAULT NULL,
`end_grade` double(4,1) DEFAULT NULL,
`total_grade` double(4,1) DEFAULT NULL,
`choose_status` int NOT NULL DEFAULT '0',
`is_pass` varchar(10) DEFAULT NULL,
PRIMARY KEY (`user_id`,`course_id`),
KEY `fk_course` (`course_id`) USING BTREE,
CONSTRAINT `fk_course` FOREIGN KEY (`course_id`) REFERENCES `course` (`course_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_student` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of choose_course
-- ----------------------------
INSERT INTO `choose_course` VALUES ('1', '3', '80.0', '96.0', '89.6', '1', '是');
INSERT INTO `choose_course` VALUES ('1', '4', '85.0', '92.0', '89.2', '1', '是');
INSERT INTO `choose_course` VALUES ('1', '5', null, null, null, '0', null);
-- ----------------------------
-- Table structure for course
-- ----------------------------
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
`course_id` int NOT NULL AUTO_INCREMENT,
`course_code` varchar(255) NOT NULL,
`course_name` varchar(255) NOT NULL,
`course_type` varchar(255) NOT NULL,
`school_id` int NOT NULL,
`teacher_id` int NOT NULL,
`course_status` int NOT NULL DEFAULT '0',
`course_term_id` int NOT NULL,
`course_term` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`course_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`course_place` varchar(255) NOT NULL,
`course_class` int NOT NULL,
`course_credit` int NOT NULL,
`usual_weight` double NOT NULL DEFAULT '0.4',
`end_weight` double NOT NULL DEFAULT '0.6',
PRIMARY KEY (`course_id`),
KEY `fk_school` (`school_id`),
KEY `fk_teacher` (`teacher_id`),
KEY `fk_item` (`course_term_id`),
CONSTRAINT `fk_item` FOREIGN KEY (`course_term_id`) REFERENCES `term` (`term_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_school` FOREIGN KEY (`school_id`) REFERENCES `school` (`school_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `user` (`user_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of course
-- ----------------------------
INSERT INTO `course` VALUES ('3', '10003', '数据结构', '必修', '1', '2', '1', '10', '2020-2021学年第二学期', '1-17周 周一6-7节', 'X1203', '1', '2', '0.4', '0.6');
INSERT INTO `course` VALUES ('4', '1004', 'c语言', '必修', '1', '6', '1', '10', '2020-2021学年第二学期', '1-17周 周四8-9节', 'X9306', '1', '2', '0.4', '0.6');
INSERT INTO `course` VALUES ('5', '1005', '英语', '限选', '1', '6', '1', '10', '2020-2021学年第二学期', '1-17周 周四8-9节', 'X1206', '1', '1', '0.4', '0.6');
INSERT INTO `course` VALUES ('17', '1234', '线性代数', '必修', '1', '2', '1', '10', '2020-2021学年第二学期', '1-17周 周四1-2节', 'X9605', '1', '2', '0.3', '0.7');
INSERT INTO `course` VALUES ('18', '26565', '概率论', '必修', '1', '2', '1', '10', '2020-2021学年第二学期', '1-17周 周午3-5节', 'X2132', '1', '3', '0.4', '0.6');
INSERT INTO `course` VALUES ('19', '2312', '离散数学', '必修', '1', '2', '0', '8', '2019-2020学年第二学期', '1-17周 周三1-2节', 'X3620', '1', '2', '0.3', '0.7');
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`permission_id` int NOT NULL AUTO_INCREMENT,
`permission_code` varchar(20) NOT NULL,
`permission_name` varchar(20) NOT NULL,
`father_id` int NOT NULL,
`path` varchar(255) NOT NULL,
`is_menu` int NOT NULL,
PRIMARY KEY (`permission_id`),
UNIQUE KEY `permission_code` (`permission_code`),
UNIQUE KEY `permission_name` (`permission_name`)
) ENGINE=InnoDB AUTO_INCREMENT=135 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES ('1', 'course', '选课相关', '0', '/course', '1');
INSERT INTO `permission` VALUES ('2', 'course:choose', '选课', '1', '/course/choose', '1');
INSERT INTO `permission` VALUES ('3', 'course:drop', '退课', '1', '/course/drop', '0');
INSERT INTO `permission` VALUES ('4', 'course:result', '选课结果', '1', '/course/result', '1');
INSERT INTO `permission` VALUES ('5', 'course:history', '历史课程', '1', '/course/history', '1');
INSERT INTO `permission` VALUES ('6', 'grade', '成绩相关', '0', '/grade', '1');
INSERT INTO `permission` VALUES ('7', 'grade:this', '本学期成绩', '6', '/grade/this', '1');
INSERT INTO `permission` VALUES ('8', 'grade:history', '历史成绩', '6', '/grade/history', '1');
INSERT INTO `permission` VALUES ('9', 'grade_manage', '成绩管理', '0', '/grade_manage', '0');
INSERT INTO `permission` VALUES ('10', 'grade_manage:in', '录入成绩', '9', '/grade_manage/in', '0');
INSERT INTO `permission` VALUES ('11', 'course_start', '查看开课', '0', '/course_start', '1');
INSERT INTO `permission` VALUES ('12', 'course_start:history', '历史开课', '11', '/course_start/history', '1');
INSERT INTO `permission` VALUES ('13', 'course_start:this', '本学期开课', '11', '/course_start/this', '1');
INSERT INTO `permission` VALUES ('14', 'showCourseStudent', '查看课程学生名单', '11', '/showCourseStudent', '0');
INSERT INTO `permission` VALUES ('15', 'course_manage', '课程管理', '0', '/course_manage', '1');
INSERT INTO `permission` VALUES ('16', 'course_manage:add', '增加课程', '15', '/course_manage/add', '1');
INSERT INTO `permission` VALUES ('17', 'course_manage:look', '查看课程', '15', '/course_manage/look', '1');
INSERT INTO `permission` VALUES ('18', 'course_manage:delete', '删除课程', '15', '/course_manage/delete', '0');
INSERT INTO `permission` VALUES ('19', 'course_manage:update', '修改课程', '15', '/course_manage/update', '0');
INSERT INTO `permission` VALUES ('20', 'term', '学期管理', '0', '/term', '1');
INSERT INTO `permission` VALUES ('21', 'term:set', '设置当前学期', '20', '/term/set', '1');
INSERT INTO `permission` VALUES ('22', 'choose_manage', '选课管理', '0', '/choose_manage', '1');
INSERT INTO `permission` VALUES ('23', 'choose_manage:open', '开放选课', '22', '/choose_manage/open', '1');
INSERT INTO `permission` VALUES ('24', 'choose_manage:close', '关闭选课', '22', '/choose_manage/close', '0');
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`role_id` int NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) NOT NULL,
PRIMARY KEY (`role_id`),
UNIQUE KEY `role_name` (`role_name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', '学生');
INSERT INTO `role` VALUES ('3', '教务');
INSERT INTO `role` VALUES ('2', '教师');
INSERT INTO `role` VALUES ('4', '系统管理员');
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`role_id` int NOT NULL,
`permission_id` int NOT NULL,
PRIMARY KEY (`role_id`,`permission_id`),
KEY `fk_permission` (`permission_id`),
CONSTRAINT `fk_permission` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`permission_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_role_p` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES ('1', '1');
INSERT INTO `role_permission` VALUES ('1', '2');
INSERT INTO `role_permission` VALUES ('1', '3');
INSERT INTO `role_permission` VALUES ('1', '4');
INSERT INTO `role_permission` VALUES ('1', '5');
INSERT INTO `role_permission` VALUES ('1', '6');
INSERT INTO `role_permission` VALUES ('1', '7');
INSERT INTO `role_permission` VALUES ('1', '8');
INSERT INTO `role_permission` VALUES ('2', '9');
INSERT INTO `role_permission` VALUES ('2', '10');
INSERT INTO `role_permission` VALUES ('2', '11');
INSERT INTO `role_permission` VALUES ('2', '12');
INSERT INTO `role_permission` VALUES ('2', '13');
INSERT INTO `role_permission` VALUES ('2', '14');
INSERT INTO `role_permission` VALUES ('3', '14');
INSERT INTO `role_permission` VALUES ('3', '15');
INSERT INTO `role_permission` VALUES ('3', '16');
INSERT INTO `role_permission` VALUES ('3', '17');
INSERT INTO `role_permission` VALUES ('3', '18');
INSERT INTO `role_permission` VALUES ('3', '19');
INSERT INTO `role_permission` VALUES ('4', '20');
INSERT INTO `role_permission` VALUES ('4', '21');
INSERT INTO `role_permission` VALUES ('4', '22');
INSERT INTO `role_permission` VALUES ('4', '23');
INSERT INTO `role_permission` VALUES ('4', '24');
-- ----------------------------
-- Table structure for school
-- ----------------------------
DROP TABLE IF EXISTS `school`;
CREATE TABLE `school` (
`school_id` int NOT NULL AUTO_INCREMENT,
`school_name` varchar(255) NOT NULL,
PRIMARY KEY (`school_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of school
-- ----------------------------
INSERT INTO `school` VALUES ('1', '计算机学院');
-- ----------------------------
-- Table structure for term
-- ----------------------------
DROP TABLE IF EXISTS `term`;
CREATE TABLE `term` (
`term_id` int NOT NULL AUTO_INCREMENT,
`term_name` varchar(255) NOT NULL,
`term_status` int NOT NULL DEFAULT '0',
PRIMARY KEY (`term_id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of term
-- ----------------------------
INSERT INTO `term` VALUES ('1', '2016-2017学年第一学期', '0');
INSERT INTO `term` VALUES ('2', '2016-2017学年第二学期', '0');
INSERT INTO `term` VALUES ('3', '2017-2018学年第一学期', '0');
INSERT INTO `term` VALUES ('4', '2017-2018学年第二学期', '0');
INSERT INTO `term` VALUES ('5', '2018-2019学年第一学期', '0');
INSERT INTO `term` VALUES ('6', '2018-2019学年第二学期', '0');
INSERT INTO `term` VALUES ('7', '2019-2020学年第一学期', '0');
INSERT INTO `term` VALUES ('8', '2019-2020学年第二学期', '0');
INSERT INTO `term` VALUES ('9', '2020-2021学年第一学期', '0');
INSERT INTO `term` VALUES ('10', '2020-2021学年第二学期', '1');
INSERT INTO `term` VALUES ('11', '2021-2022学年第一学期', '0');
INSERT INTO `term` VALUES ('12', '2021-2022学年第二学期', '0');
INSERT INTO `term` VALUES ('13', '2022-2023学年第一学期', '0');
INSERT INTO `term` VALUES ('14', '2022-2023学年第二学期', '0');
INSERT INTO `term` VALUES ('15', '2023-2024学年第一学期', '0');
INSERT INTO `term` VALUES ('16', '2023-2024学年第二学期', '0');
INSERT INTO `term` VALUES ('17', '2024-2025学年第一学期', '0');
INSERT INTO `term` VALUES ('18', '2024-2025学年第二学期', '0');
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_id` int NOT NULL,
`role_id` int NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
KEY `fk_role` (`role_id`),
CONSTRAINT `fk_role` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1');
INSERT INTO `user_role` VALUES ('5', '1');
INSERT INTO `user_role` VALUES ('2', '2');
INSERT INTO `user_role` VALUES ('6', '2');
INSERT INTO `user_role` VALUES ('3', '3');
INSERT INTO `user_role` VALUES ('4', '4');
DROP TRIGGER IF EXISTS `tri_choose_course_update`;
DELIMITER ;;
CREATE TRIGGER `tri_choose_course_update` BEFORE UPDATE ON `choose_course` FOR EACH ROW BEGIN
DECLARE endWe DOUBLE;
DECLARE usualWe DOUBLE;
SET endWe = (SELECT end_weight FROM course WHERE course_id = new.course_id);
SET usualWe = (SELECT usual_weight FROM course WHERE course_id = new.course_id);
SET new.total_grade = new.usual_grade*usualWe + new.end_grade*endWe;
IF (new.total_grade>=60) THEN
SET new.is_pass = '是' ;
END IF;
IF (new.total_grade<60) THEN
SET new.is_pass = '否' ;
END IF;
END
;;
DELIMITER ;
DROP TRIGGER IF EXISTS `tri_course_insert`;
DELIMITER ;;
CREATE TRIGGER `tri_course_insert` BEFORE INSERT ON `course` FOR EACH ROW BEGIN
declare c int;
declare b VARCHAR(255);
SET c = NEW.course_term_id;
SET b = (SELECT term_name FROM term WHERE term_id = c);
SET new.course_term = b;
IF((SELECT term_status FROM term WHERE term_id = c)=1) THEN
SET NEW.course_status = 1;
END IF;
END
;;
DELIMITER ;
⑤:创建表对应的实体类
1.ChooseCourse
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Accessors(chain = true)
@Component
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("学生选课实体")
public class ChooseCourse implements Serializable {
private static final long serialVersionUID = 1L;
@MppMultiId
@ApiModelProperty("学生id")
private Integer userId;
@MppMultiId
@ApiModelProperty("课程id")
private Integer courseId;
@ApiModelProperty("平时成绩")
private Double usualGrade;
@ApiModelProperty("期末考试成绩")
private Double endGrade;
@ApiModelProperty("总成绩")
private Double totalGrade;
@ApiModelProperty("是否出成绩")
private int chooseStatus;
@ApiModelProperty("是否通过")
private String isPass;
}
2.Course
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Accessors(chain = true)
@Component
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("学生选课实体")
public class ChooseCourse implements Serializable {
private static final long serialVersionUID = 1L;
@MppMultiId
@ApiModelProperty("学生id")
private Integer userId;
@MppMultiId
@ApiModelProperty("课程id")
private Integer courseId;
@ApiModelProperty("平时成绩")
private Double usualGrade;
@ApiModelProperty("期末考试成绩")
private Double endGrade;
@ApiModelProperty("总成绩")
private Double totalGrade;
@ApiModelProperty("是否出成绩")
private int chooseStatus;
@ApiModelProperty("是否通过")
private String isPass;
}
3.Permission
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Component
@AllArgsConstructor
@NoArgsConstructor
public class Permission implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "permission_id", type = IdType.AUTO)
private Integer permissionId;
private String permissionCode;
private String permissionName;
private String fatherId;
private String path;
private int isMenu;
}
4.Role
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Component
@AllArgsConstructor
@NoArgsConstructor
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "role_id", type = IdType.AUTO)
private Integer roleId;
private String roleName;
}
5.RolePermission
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Component
@AllArgsConstructor
@NoArgsConstructor
public class RolePermission implements Serializable {
private static final long serialVersionUID = 1L;
private Integer roleId;
private Integer permissionId;
}
6.School
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Accessors(chain = true)
@Component
@AllArgsConstructor
@NoArgsConstructor
public class School implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "school_id", type = IdType.AUTO)
private Integer schoolId;
private String schoolName;
}
7.Term
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Component
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Term implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "term_id", type = IdType.AUTO)
private Integer termId;
private String termName;
private Integer termStatus;
}
8.User
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Component
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("用户实体")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "user_id", type = IdType.AUTO)
@ApiModelProperty("用户id")
private Integer userId;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("加密的盐")
private String salt;
@ApiModelProperty("姓名")
private String nickname;
@ApiModelProperty("学院id")
private int schoolId;
@ApiModelProperty("性别")
private String sex;
@ApiModelProperty("电话号码")
private String tel;
@ApiModelProperty("班级")
private String studentClass;
}
9.UserRole
@Data
@EqualsAndHashCode(callSuper = false) // 实现equals方法和hashcode方法
@Component
@AllArgsConstructor
@NoArgsConstructor
public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
private Integer userId;
private Integer roleId;
}
⑥: 创建mapper 接口 和 实现类
1.创建CourseMapper
@Mapper
public interface CourseMapper extends BaseMapper<Course> {
}
2.创建CourseMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.App.mapper.CourseMapper">
</mapper>
3.创建CourseService
public interface CourseService extends IService<Course> {
}
4.创建CourseServiceImpl
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {
}
5.创建CourseController
@RestController
@RequestMapping("/course")
@Api(tags = "课程相关接口")
public class CourseController {
@Autowired
private CourseService courseService;
}
- 这里省略 创建所有的接口 实现类 和 mapper
⑦:创建统一返回结果类
1.com.it.App.vo.Response
@Data
public class Response<T> {
/**
* 结果
*
* @mock true
*/
private boolean success;
/**
* 状态码
*
* @mock 200
*/
private int code;
/**
* 消息提示
*
* @mock 操作成功
*/
private String msg;
/**
* 结果体
*
* @mock null
*/
private T data;
public Response () {
}
public Response (int code, Object status) {
super();
this.code = code;
this.msg = status.toString();
if (code == 1) {
this.success = true;
} else {
this.success = false;
}
}
public Response (int code, String status, T result) {
super();
this.code = code;
this.msg = status;
this.data = result;
if (code == 1) {
this.success = true;
} else {
this.success = false;
}
}
public static Response<?> ok() {
return new Response<>(1, "success");
}
public static <T> Response<T> ok(T t) {
return new Response<T>(1, "success", t);
}
public static Response<?> error(String status) {
return new Response<>(500, status);
}
public static Response<?> error(int code, String status) {
return new Response<>(code, status);
}
}
⑧:创建工具类
1.JwtUtil
@Slf4j
@Component
public class JwtUtil {
private static String header;
private static String secret;
private static long expire;
@Value("${coke.jwt.header}")
public void setHeader(String header) {
JwtUtil.header = header;
}
@Value("${coke.jwt.secret}")
public void setSecret(String secret) {
JwtUtil.secret = secret;
}
@Value("${coke.jwt.expire}")
public void setExpire(long expire) {
JwtUtil.expire = expire;
}
/**
* 创建JWT Token
*
* @param username 用户名
* @return JWT Token字符串
*/
public static String createJWT(String username) {
log.info(secret);
// 获取当前时间
Date nowDate = new Date();
// 计算过期时间,当前时间 + 过期时长
Date expireDate = new Date(nowDate.getTime() + expire);
// 使用 JWT Builder 构建 JWT
return Jwts.builder()
.setHeaderParam("typ", header) // 设置头部信息,通常为JWT
.setId(username)
.setSubject(username) // 设置主题,通常为用户名
.setIssuedAt(nowDate) // 设置签发时间,即当前时间
.setExpiration(expireDate) // 设置过期时间
.signWith(SignatureAlgorithm.HS512, secret) // 使用HS512签名算法和密钥进行签名
.compact();
}
/**
* 解析JWT Token
*
* @param jwt JWT Token字符串
* @return JWT声明
*/
public static Claims parseJWT(String jwt) {
// 这一行将抛出异常,如果它不是已签名的JWS(预期的情况)
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
} catch (ExpiredJwtException e) {
// Token已过期
return null;
}
}
/**
* 检查Token是否已过期
*
* @return 是否已过期
*/
public static boolean isTokenExpired(Claims claims) {
// 检查过期时间是否在当前时间之前
return claims.getExpiration().before(new Date());
}
public static void main(String[] args) {
// 示例:加密
String jwt = createJWT("zhangsan");
log.info(jwt);
// 示例:解密
Claims claims = parseJWT(jwt);
log.info(claims.toString());
log.info(claims.getId());
}
}
2.RedisUtil
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//================有序集合 sort set===================
/**
* 有序set添加元素
*
* @param key
* @param value
* @param score
* @return
*/
public boolean zSet(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
public long batchZSet(String key, Set<ZSetOperations.TypedTuple> typles) {
return redisTemplate.opsForZSet().add(key, typles);
}
public void zIncrementScore(String key, Object value, long delta) {
redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
public void zUnionAndStore(String key, Collection otherKeys, String destKey) {
redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 获取zset数量
* @param key
* @param value
* @return
*/
public long getZsetScore(String key, Object value) {
Double score = redisTemplate.opsForZSet().score(key, value);
if(score==null){
return 0;
}else{
return score.longValue();
}
}
/**
* 获取有序集 key 中成员 member 的排名 。
* 其中有序集成员按 score 值递减 (从大到小) 排序。
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple> getZSetRank(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}
}
3.JsonUtil
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.it.App.vo.Response;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class JsonUtil {
public static <E, T> T getJavaBeanInMapData(String json, E key, Class<T> tClass){
Map<E, T> data = getData(json, Map.class);
return JSON.parseObject(JSON.toJSONString(data.get(key)), tClass);
}
public static <E, T> List<T> getJavaBeanInMapListData(String json, E key, Class<T> tClass){
String jsonString = JSON.toJSONString(getData(json, Map.class).get(key));
return JSONObject.parseObject(jsonString, buildType(List.class, tClass));
}
public static int getCode(String json) {
JSONObject jsonObject = JSON.parseObject(json);
return (int) jsonObject.get("code");
}
public static String getMessage(String json) {
JSONObject jsonObject = JSON.parseObject(json);
return (String) jsonObject.get("message");
}
public static <T> T getData(String json, Class<T> clazz) {
return JSONObject.parseObject(json, new TypeReference<Response<T>>(clazz) {}).getData();
}
public static <T> List<T> getListData(String json, Class<T> clazz) {
return parseListResponse(json, clazz).getData();
}
public static <T> Response<T> parseResponse(String json, Class<T> clazz) {
return JSONObject.parseObject(json, new TypeReference<Response<T>>(clazz) {
});
}
public static <T> Response<List<T>> parseListResponse(String json, Class<T> clazz) {
return JSONObject.parseObject(json, buildType(Response.class, List.class, clazz));
}
public static Type buildType(Type... types) {
ParameterizedTypeImpl beforeType = null;
if (types != null && types.length > 0) {
for (int i = types.length - 1; i > 0; i--) {
beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);
}
}
return beforeType;
}
}
4.StringUtil
public class StringUtil {
/**
* 生成随机字符串
*/
public static String generateUUID(){
return UUID.randomUUID().toString().replaceAll("-","");
}
/**
* MD5加密
*/
public static String md5(String key){
if (StringUtils.isBlank(key)){
return null;
}
return DigestUtils.md5DigestAsHex(key.getBytes());
}
public static Integer changeString(String string){
if (string != null && !StringUtils.isBlank(string)) {
return Integer.valueOf(string);
}else {
return null;
}
}
}
⑨:前后端环境交互测试
1.后端UserController
@RestController
@RequestMapping("/user")
@CrossOrigin
@Api(tags = "用户相关接口")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/test")
@ApiOperation("测试前后端接口交互")
public Response<?> test() {
List<User> list = userService.list();
return Response.ok(list);
}
}
2.前端HelloWorld.vue
<template>
<div style="width: 40rem; margin: 2rem">
<a-card title="前后端接口交互测试" size="small" type="inner">
<div slot="extra" >
<a-button type="primary" @click="getUserList">发生请求</a-button>
</div>
{{ description }}
</a-card>
</div>
</template>
<script>
import {getUserList} from "@/api/login";
export default {
name: 'HelloWorld',
data() {
return {
description: null
}
},
methods: {
getUserList() {
getUserList().then((res) =>{
this.description = res.data.data[0]
})
}
}
}
</script>
3.测试 成功
二、与SpringBoot整合 Shiro
①:自定义Realm
1.com.it.App.realm.MyRealm
@Component
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
②:创建 ShrioConfig
1.com.it.App.config.ShrioConfig
@Configuration
public class ShrioConfig {
@Autowired
private MyRealm myRealm;
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm);
return manager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(manager);
Map<String, String> map = new LinkedHashMap<>();
map.put("/user/test","anon"); // 放行不需要登录
map.put("/user/test2","authc"); // 需要登录
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
shiroFilterFactoryBean.setLoginUrl("/user/login"); // 调转到登录页面
return shiroFilterFactoryBean;
}
}
③:测试
1.在UserController中添加 测试接口2 和 登录接口
@GetMapping("/test2")
@ApiOperation("测试前后端接口交互2")
public Response<?> test2() {
List<User> list = userService.list();
return Response.ok(list);
}
@GetMapping("/login")
@ApiOperation("登录页面")
public Response<?> login() {
List<User> list = userService.list();
return Response.ok("需要登录");
}
2.测试接口
/user/test
(不需要登录)
3.测试接口
/user/test2
(需要登录)
三、Shiro配置
①:Shiro配置(一)认证
01. 必看思路
- 用户登录(用过滤器拦截JwtFilter)
==>
- 执行认证逻辑(MyRealm)
==>
- 用户输入的密码与数据库中的密码匹配(MyCredentialsMatcher)
==>
- 使用自定义token进行密码匹配(JwtToken)
02.JwtFilter过滤器
/**
* @DateTime: 2023/11/28/15:19
* @注释: 创建Token、访问被拒绝时的处理、登录失败时的处理以及预处理(处理跨域请求)
**/
@Slf4j
public class JwtFilter extends AuthenticatingFilter {
/**
* 创建Token对象
*/
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String jwt = request.getHeader("Authorization");
if (StringUtils.isEmpty(jwt)) {
return null;
}
return new JwtToken(jwt);
}
/**
* 访问被拒绝时的处理
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String jwt = request.getHeader("Authorization");
if (StringUtils.isEmpty(jwt)) {
return true;
} else {
// 校验jwt
Claims claim = JwtUtil.parseJWT(jwt);
if (claim == null || JwtUtil.isTokenExpired(claim)) {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(Response.error(401, "身份已过期,请重新登录")));
return false;
}
// 执行登录
return executeLogin(servletRequest, servletResponse);
}
}
/**
* 登录失败时的处理
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String json = JSON.toJSONString(Response.error(e.getMessage()));
try {
httpServletResponse.getWriter().print(json);
} catch (IOException ioException) {
log.error("JwtFilter onLoginFailure IOException: {}", ioException.getMessage());
}
return false;
}
/**
* 预处理,处理跨域请求
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
// 设置跨域信息
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个OPTIONS请求,这里给OPTIONS请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
03. 根据用户名查询用户信息
1.UserServiceImpl
@Autowired
private UserMapper userMapper;
/**
* 根据用户名查询用户信息
*
* @return 用户信息
* @DateTime: 2023/11/28 14:01
*/
public User selectUserByName(String username){
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getUsername, username);
return userMapper.selectOne(lambdaQueryWrapper);
}
04. 定义自己的JwtToken
1.com.it.App.token.JwtToken
/**
* @DateTime: 2023/11/28/14:04
* @注释: 封装我们自己的JWT令牌相关的信息
**/
@Data
@Component
public class JwtToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
// JWT令牌
private String token;
// 用户密码(字符数组形式)
private char[] password;
// 是否记住我
private boolean rememberMe;
// 主机信息
private String host;
// 构造函数,通过令牌初始化JwtToken对象
public JwtToken(String token) {
this.token = token;
}
// 获取认证主体(令牌)
@Override
public Object getPrincipal() {
return token;
}
// 获取凭证(密码)
@Override
public Object getCredentials() {
return this.password;
}
// 无参构造函数,初始化时记住我标志为false
public JwtToken() {
this.rememberMe = false;
}
// 构造函数,通过令牌和密码初始化JwtToken对象
public JwtToken(String token, char[] password) {
this(token, (char[])password, false, null);
}
// 构造函数,通过令牌和密码初始化JwtToken对象(字符串密码)
public JwtToken(String token, String password) {
this(token, (char[])(password != null ? password.toCharArray() : null), false, null);
}
// 构造函数,通过令牌、密码和主机信息初始化JwtToken对象
public JwtToken(String token, char[] password, String host) {
this(token, password, false, host);
}
// 构造函数,通过令牌、密码、主机信息和记住我标志初始化JwtToken对象
public JwtToken(String token, String password, String host) {
this(token, password != null ? password.toCharArray() : null, false, host);
}
// 构造函数,通过令牌、密码和记住我标志初始化JwtToken对象
public JwtToken(String token, char[] password, boolean rememberMe) {
this(token, (char[])password, rememberMe, null);
}
// 构造函数,通过令牌、密码、记住我标志和主机信息初始化JwtToken对象
public JwtToken(String token, String password, boolean rememberMe) {
this(token, (char[])(password != null ? password.toCharArray() : null), rememberMe, null);
}
// 构造函数,通过令牌、密码、记住我标志和主机信息初始化JwtToken对象
public JwtToken(String token, char[] password, boolean rememberMe, String host) {
this.rememberMe = false;
this.token = token;
this.password = password;
this.rememberMe = rememberMe;
this.host = host;
}
// 构造函数,通过用户名、密码、记住我标志和主机信息初始化JwtToken对象
public JwtToken(String username, String password, boolean rememberMe, String host) {
this(username, password != null ? password.toCharArray() : null, rememberMe, host);
}
// toString方法,返回JwtToken对象的字符串表示
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName());
sb.append(" - ");
sb.append(JwtUtil.parseJWT(this.token).getId());
sb.append(", rememberMe=").append(this.rememberMe);
if (this.host != null) {
sb.append(" (").append(this.host).append(")");
}
return sb.toString();
}
}
05.定义自己的凭证匹配器
1.com.it.App.shiro.MyCredentialsMatcher
/**
* @DateTime: 2023/11/28/14:19
* @注释: 用户登录时验证用户输入的密码是否与数据库中存储的密码匹配
**/
@Component
public class MyCredentialsMatcher extends SimpleCredentialsMatcher {
@Autowired
private UserServiceImpl userService;
// 使用自定义token进行密码匹配
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 将AuthenticationToken强制转换为JwtToken,因为我们在该项目中使用了自定义的JwtToken
JwtToken jwtToken = (JwtToken) token;
// 如果密码为空,表示无需进行密码匹配,直接返回true
if (jwtToken.getPassword() == null) {
return true;
}
// 获得用户输入的密码
String inPassword = new String(jwtToken.getPassword());
// 获得数据库中的用户名
String username = String.valueOf(info.getPrincipals());
// 获取数据库中保存的用户信息,包括密码和盐
User user = userService.selectUserByName(username);
// 获取数据库中的密码
String dbPassword = user.getPassword();
// 获取盐
String salt = user.getSalt();
// 进行密码的比对,使用MD5加盐方式
return this.equals(StringUtil.md5(inPassword + salt), dbPassword);
}
}
06.MyRealm执行授权和认证逻辑
/**
* @DateTime: 2023/11/28/12:50
* @注释: 执行授权和认证逻辑,验证用户身份信息
**/
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserServiceImpl userService;
/**
* 判断是否支持自定义的Token类型
*
* @param token 认证信息的Token
* @return 是否支持该Token类型
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 执行授权逻辑
*
* @param principalCollection 当前用户的身份信息集合
* @return 授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 执行认证逻辑,验证用户身份信息
*
* @param authenticationToken 认证信息的Token
* @return 认证信息
* @throws AuthenticationException 认证异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 转换为自定义的JwtToken
JwtToken jwtToken = (JwtToken) authenticationToken;
// 从Token中获取用户信息
String token = (String) jwtToken.getPrincipal();
Claims claim = JwtUtil.parseJWT(token);
String username = claim.getId();
// 根据用户名查询用户信息
User user = userService.selectUserByName(username);
// 如果用户不存在,返回null,表示认证失败
if (ObjectUtil.isNull(user)) {
return null;
}
// 返回认证信息,包括用户名和数据库中的密码
return new SimpleAuthenticationInfo(username, user.getPassword(), getName());
}
}
07.ShrioConfig中配置凭证匹配器和认证逻辑
/**
* @DateTime: 2023/11/28/12:53
* @注释: 配置Shiro的两个关键组件:Web安全管理器 (DefaultWebSecurityManager) 和 Shiro过滤器工厂 (ShiroFilterFactoryBean)
**/
@Configuration
public class ShrioConfig {
@Autowired
private MyRealm myRealm;
@Autowired
private MyCredentialsMatcher myCredentialsMatcher;
/**
* 配置默认的Web安全管理器
*
* @return 默认的Web安全管理器
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 设置自定义的凭证匹配器
myRealm.setCredentialsMatcher(myCredentialsMatcher);
// 注入自定义的Realm
manager.setRealm(myRealm);
return manager;
}
/**
* 配置Shiro过滤器工厂
*
* @param manager Web安全管理器
* @param jwtFilter JWT过滤器
* @return Shiro过滤器工厂
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager manager, JwtFilter jwtFilter) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置Web安全管理器
shiroFilterFactoryBean.setSecurityManager(manager);
// 设置JWT过滤器
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("jwt", jwtFilter);
shiroFilterFactoryBean.setFilters(filterMap);
// 设置URL权限配置
Map<String, String> map = new LinkedHashMap<>();
// 放行Swagger相关的URL
map.put("/swagger-ui.html**", "anon");
map.put("/v2/api-docs", "anon");
map.put("/swagger-resources/**", "anon");
map.put("/webjars/**", "anon");
// 设置其他URL需要经过JWT过滤器
map.put("/**", "jwt");
// 设置登录URL和未授权URL
shiroFilterFactoryBean.setLoginUrl("/user/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/user/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public JwtFilter getJwtFilter() {
return new JwtFilter();
}
}
08.登录接口 成功后返回token
1.UserController
@PostMapping("/login")
@ApiOperation("登录")
public Response<?> login(@RequestBody User user) {
return userService.login(user);
}
2.UserServiceImpl
@Override
public Response<?> login(User user) {
// 检查用户名和密码是否为空
if (ObjectUtil.isNull(user.getUsername()) || ObjectUtil.isNull(user.getPassword())) {
return Response.error("账号密码不能为空");
}
// 获取Shiro的主体对象
Subject subject = SecurityUtils.getSubject();
// 生成JWT令牌
String token = JwtUtil.createJWT(user.getUsername());
// 创建JWT令牌对象
JwtToken jwtToken = new JwtToken(token, user.getPassword());
try {
// 使用JWT令牌进行登录
subject.login(jwtToken);
} catch (UnknownAccountException e) {
// 账号不存在异常
return Response.error(401, "账号不存在");
} catch (IncorrectCredentialsException e) {
// 密码错误异常
return Response.error(402, "密码错误");
}
// 登录成功,查询用户信息
User backUser = selectUserByName(user.getUsername());
// 构建响应结果,包含用户信息和令牌
Map<String, Object> map = new HashMap<>();
map.put("user", backUser);
map.put("token", token);
return Response.ok(map);
}
②:测试认证
1.用户名错误
2密码错误
3.正常登录
③:Shiro配置(二)鉴权
01.开启注解代理和设置代理对象
1.ShrioConfig中添加以下代码
@Bean
public JwtFilter getJwtFilter() {
return new JwtFilter();
}
/**
* 创建AuthorizationAttributeSourceAdvisor Bean,用于支持Shiro注解 (开启注解代理)
*
* @param securityManager 安全管理器
* @return AuthorizationAttributeSourceAdvisor Bean
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
// 设置安全管理器
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 创建DefaultAdvisorAutoProxyCreator Bean,用于自动创建代理对象
*
* @return DefaultAdvisorAutoProxyCreator Bean
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
// 设置是否使用CGLIB代理,默认为false,即使用JDK动态代理
creator.setProxyTargetClass(true);
return creator;
}
02. 根据用户名获取角色信息
1.RoleService
/**
* 通过用户名获取所有角色名
*
* @param username: 用户名
* @return Set<String> 角色
* @DateTime: 2023/11/29 12:09
*/
Set<String> getAllRoleNamesByUsername(String username);
2.RoleServiceImpl
@Autowired
private RoleMapper roleMapper;
@Override
public Set<String> getAllRoleNamesByUsername(String username) {
return roleMapper.selectAllRoleNamesByUsername(username);
}
3.RoleMapper
@Select("select r.role_name from user u left join user_role ur on ur.user_id = u.user_id " +
"left join role r on r.role_id = ur.role_id where u.username = #{username}")
Set<String> selectAllRoleNamesByUsername(String username);
03.根据用户名获取权限信息
1.PermissionService
/**
* 通过用户名获取所有权限码
*
* @param username: 用户名
* @return Set<String> 用户权限
* @DateTime: 2023/11/29 12:10
*/
Set<String> getAllPermissionByUsername(String username);
2.PermissionServiceImpl
@Autowired
private PermissionMapper permissionMapper;
@Override
public Set<String> getAllPermissionByUsername(String username) {
return permissionMapper.selectAllPermissionCodeByUsername(username);
}
3.PermissionMapper
@Select("select p.permission_code from user u left join user_role ur on ur.user_id = u.user_id\n" +
"left join role r on r.role_id = ur.role_id\n" +
"left join role_permission rp on rp.role_id = r.role_id\n" +
"left join permission p on p.permission_id = rp.permission_id where u.username = #{username} and p.father_id != 0")
Set<String> selectAllPermissionCodeByUsername(String username);
④:添加全局异常处理
1.com.it.App.exception.GlobalException
/**
* 全局异常处理
* @ClassName:GlobalException
*/
@Slf4j
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(value = UnauthorizedException.class)
public Response<?> handler(UnauthorizedException e) {
log.error("运行时异常:----------------{}", e.getMessage());
return Response.error(402,"无权限操作");
}
@ExceptionHandler(value = ExpiredCredentialsException.class)
public Response<?> handler(ExpiredCredentialsException e) {
log.error("运行时异常:----------------{}", e.getMessage());
return Response.error(401,"登录已过期,请重新登录");
}
@ExceptionHandler(value = UnauthenticatedException.class)
public Response<?> handler(UnauthenticatedException e) {
log.error("运行时异常:----------------{}", e);
return Response.error(401,"未登录");
}
@ExceptionHandler(value = UnknownAccountException.class)
public Response<?> handler(UnknownAccountException e) {
log.error("运行时异常:----------------{}", e);
return Response.error(402,"未登录123");
}
}
⑤:测试鉴权
1.添加一个测试接口 接口上加上
@RequiresPermissions("course")
指定需要的权限
@GetMapping("/course")
@ApiOperation("查看课程")
@RequiresPermissions("course:choose")
public Response<?> course() {
return Response.ok("您有课程的权限,可以查看!");
}
2.不携带token测试
3.登录后
(没有查看课程权限的用户登录)
携带Token测试
4.登录后
(有查看课程权限的用户登录)
携带Token测试
四、登录功能
①:后端接口
- 登录接口上面已经写过了
这里写一下登出接口
1.UserController
@ApiOperation("退出登录")
@PostMapping("/logout")
public Response<?> logout() {
SecurityUtils.getSubject().logout();
return Response.ok("成功退出");
}
②:前端路由配置
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from "@/views/Login";
import Index from "@/views/Index";
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'login',
component: Login
},
{
path: '/index',
name: 'index',
component: Index
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
③:全局共享变量的添加和删除
1.src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token: null,
},
getters: {
},
mutations: {
SET_TOKEN(state, token) {
state.token = token;
localStorage.setItem('token', token)
},
RESET_STATE(state, token) {
state.token = null;
localStorage.clear();
sessionStorage.clear();
},
},
actions: {
},
modules: {
}
})
④:前端登录页面开发
1.src/views/Login.vue
<template>
<div class="row-bg">
<div class="container">
<div class="input-container">
<div class="input-content">
<div class="input-dist">
<div class="input-type">
<input class="input-is" type="text" placeholder="用户名" v-model="form.username"/>
<input class="input-is" type="password" placeholder="密码" v-model="form.password"/>
</div>
</div>
</div>
</div>
<button class="submit-button" @click="submitForm">登录</button>
</div>
</div>
</template>
<script>
import {getCaptchaImg, toLogin} from "@/api/login";
import qs from 'qs'
export default {
name: "Login",
data() {
return {
form: {
username: null, // 用户名
password: null, // 密码
},
}
},
methods: {
// 登录
toLogin() {
toLogin(this.form).then((res) => {
if (res.data.success) {
const jwt = res.data.data.token
this.$store.commit('SET_TOKEN', jwt)
this.$router.push('/index')
} else {
this.$message.error(res.data.msg)
}
})
},
submitForm() {
if (!this.form.username || !this.form.password) {
this.$message.warning('用户名和密码不能为空!')
return
}
if (this.form.password.length < 4) {
this.$message.warning('密码长度至少 4 个字符!')
return;
}
this.toLogin();
},
}
}
</script>
<style scoped>
.row-bg {
background-image: url("/public/img/login_bk2.jpg");
background-size: cover;
background-repeat: no-repeat;
/*background-color: #fafafa;*/
height: 100vh;
opacity: 0.9;
filter: none;
}
.container {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-style: italic;
font-weight: bold;
display: flex;
margin: auto;
aspect-ratio: 16/9;
align-items: center;
justify-items: center;
justify-content: center;
flex-wrap: nowrap;
flex-direction: column;
gap: 1em;
position: absolute;
top: 49%;
left: 52%;
transform: translate(-50%, -50%);
}
.input-container {
filter: drop-shadow(46px 36px 24px #4090b5) drop-shadow(-55px -40px 25px #9e30a9);
animation: blinkShadowsFilter 8s ease-in infinite;
}
.input-content {
display: grid;
align-content: center;
justify-items: center;
align-items: center;
text-align: center;
padding-inline: 1em;
}
.input-content::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
filter: blur(40px);
-webkit-clip-path: polygon(
26% 0,
66% 0,
92% 0,
100% 8%,
100% 89%,
91% 100%,
7% 100%,
0 92%,
0 0
);
clip-path: polygon(
26% 0,
66% 0,
92% 0,
100% 8%,
100% 89%,
91% 100%,
7% 100%,
0 92%,
0 0
);
background: rgba(122, 251, 255, 0.5568627451);
transition: all 1s ease-in-out;
}
.input-content::after {
content: "";
position: absolute;
width: 98%;
height: 98%;
box-shadow: inset 0px 0px 20px 20px #212121;
background: repeating-linear-gradient(
to bottom,
transparent 0%,
rgba(64, 144, 181, 0.6) 1px,
rgb(0, 0, 0) 3px,
hsl(295, 60%, 12%) 5px,
#153544 4px,
transparent 0.5%
),
repeating-linear-gradient(
to left,
hsl(295, 60%, 12%) 100%,
hsla(295, 60%, 12%, 0.99) 100%
);
-webkit-clip-path: polygon(
26% 0,
31% 5%,
61% 5%,
66% 0,
92% 0,
100% 8%,
100% 89%,
91% 100%,
7% 100%,
0 92%,
0 0
);
clip-path: polygon(
26% 0,
31% 5%,
61% 5%,
66% 0,
92% 0,
100% 8%,
100% 89%,
91% 100%,
7% 100%,
0 92%,
0 0
);
animation: backglitch 50ms linear infinite;
}
.input-dist {
z-index: 80;
display: grid;
align-items: center;
text-align: center;
width: 100%;
padding-inline: 1em;
padding-block: 1.2em;
grid-template-columns: 1fr;
}
.input-type {
display: flex;
flex-wrap: wrap;
flex-direction: column;
gap: 1em;
font-size: 1.1rem;
background-color: transparent;
width: 100%;
border: none;
}
.input-is {
color: #fff;
font-size: 0.9rem;
background-color: transparent;
width: 100%;
box-sizing: border-box;
padding-inline: 0.5em;
padding-block: 0.7em;
border: none;
transition: all 1s ease-in-out;
border-bottom: 1px solid hsl(221, 26%, 43%);
}
.input-is:hover {
transition: all 1s ease-in-out;
background: linear-gradient(
90deg,
transparent 0%,
rgba(102, 224, 255, 0.2) 27%,
rgba(102, 224, 255, 0.2) 63%,
transparent 100%
);
}
.input-content:focus-within::before {
transition: all 1s ease-in-out;
background: hsla(0, 0%, 100%, 0.814);
}
.input-is:focus {
outline: none;
border-bottom: 1px solid hsl(192, 100%, 100%);
color: hsl(192, 100%, 88%);
background: linear-gradient(
90deg,
transparent 0%,
rgba(102, 224, 255, 0.2) 27%,
rgba(102, 224, 255, 0.2) 63%,
transparent 100%
);
}
.input-is::-moz-placeholder {
color: hsla(192, 100%, 88%, 0.806);
}
.input-is::placeholder {
color: hsla(192, 100%, 88%, 0.806);
}
.submit-button {
width: 200px;
border: none;
color: hsla(192, 100%, 88%, 0.806);
background: linear-gradient(
90deg,
transparent 0%,
rgba(102, 224, 255, 0.2) 27%,
rgba(102, 224, 255, 0.2) 63%,
transparent 100%
);
clip-path: polygon(0 0, 85% 0%, 100% 0, 100% 15%, 100% 90%, 91% 100%, 0 100%);
padding: 0.5em;
animation: blinkShadowsFilter 0.5s ease-in infinite;
transition: all 500ms;
}
.submit-button:hover {
color: hsl(0, 0%, 100%);
cursor: pointer;
font-size: medium;
font-weight: bold;
}
@keyframes backglitch {
0% {
box-shadow: inset 0px 20px 20px 30px #212121;
}
50% {
box-shadow: inset 0px -20px 20px 30px hsl(297, 42%, 10%);
}
to {
box-shadow: inset 0px 20px 20px 30px #212121;
}
}
@keyframes rotate {
0% {
transform: rotate(0deg) translate(-50%, 20%);
}
50% {
transform: rotate(180deg) translate(40%, 10%);
}
to {
transform: rotate(360deg) translate(-50%, 20%);
}
}
@keyframes blinkShadowsFilter {
0% {
filter: drop-shadow(46px 36px 28px rgba(64, 144, 181, 0.3411764706)) drop-shadow(-55px -40px 28px #9e30a9);
}
25% {
filter: drop-shadow(46px -36px 24px rgba(64, 144, 181, 0.8980392157)) drop-shadow(-55px 40px 24px #9e30a9);
}
50% {
filter: drop-shadow(46px 36px 30px rgba(64, 144, 181, 0.8980392157)) drop-shadow(-55px 40px 30px rgba(159, 48, 169, 0.2941176471));
}
75% {
filter: drop-shadow(20px -18px 25px rgba(64, 144, 181, 0.8980392157)) drop-shadow(-20px 20px 25px rgba(159, 48, 169, 0.2941176471));
}
to {
filter: drop-shadow(46px 36px 28px rgba(64, 144, 181, 0.3411764706)) drop-shadow(-55px -40px 28px #9e30a9);
}
}
</style>
2. src/api/login.js
import axios from "@/axios";
// 测试前后端接口交互
export function getUserList(data) {
return axios({
url: '/user/test',
method: 'get',
params: data
})
}
/**
* 登录
* @param data 用户名和密码
* @returns {*}
*/
export function toLogin(data) {
return axios({
url: '/user/login',
method: 'post',
data: data,
})
}
/**
* 登出
* @param data
* @returns {*}
*/
export function logout(data) {
return axios({
url: '/user/logout',
method: 'post',
data: data
})
}
⑤:前端首页开发
1.src/views/Index.vue
<template>
<a-result
status="success"
title="这是首页正在开发中..."
sub-title="恭喜您登录成功,现在您可以尝试登出。"
>
<template #extra>
<a-button key="buy" @click="logout">
登出
</a-button>
</template>
</a-result>
</template>
<script>
import {logout} from "@/api/login";
export default {
name: "Index",
data() {
return {
}
},
methods:{
logout(){
logout().then((res) => {
if (res.data.success){
this.$store.commit('RESET_STATE')
this.$router.push('/')
}else {
this.$message.error(res.data.msg)
}
})
}
}
}
</script>
<style lang="less" scoped>
</style>
⑥:前端请求和响应拦截器
1.src/axios.js
// 引入所需的库和模块
import axios from "axios";
import router from "@/router";
import {message} from "ant-design-vue";
// 设置所有 Axios 请求的基础 URL
axios.defaults.baseURL = "http://127.0.0.1:8086";
// 创建一个具有自定义设置的 Axios 实例
let request = axios.create({
timeout: 30000, // 设置请求的超时时间为5000毫秒
headers: {
'Content-Type': 'application/json;charset=utf-8' // 设置请求数据的内容类型为 JSON
}
});
// 在发送请求之前拦截请求
request.interceptors.request.use(config => {
// 使用本地存储中的令牌设置请求的 'Authorization' 头部
config.headers['token'] = localStorage.getItem('token');
return config;
});
// 在处理响应之前拦截响应
request.interceptors.response.use(response => {
// 从响应中提取数据
let res = response.data;
console.log(response.data)
// 检查响应代码是否为200(成功)
if (res.success) {
return response; // 如果成功,则返回响应
} else {
// 如果响应代码不是200,显示错误消息
message.error(res.msg ? res.msg : '系统异常');
return Promise.reject(res.msg); // 使用错误消息拒绝 Promise
}
}, error => {
console.log('error', error);
// 处理特定的错误情况
if (error.code === 401) {
message.error(error.msg);
router.push('/login'); // 如果错误代码是401(未经授权),则重定向到登录页面
}
console.log(error.msg);
// 显示错误消息,持续时间为3000毫秒
message.error(error.msg);
return Promise.reject(error.msg); // 使用错误消息拒绝 Promise
});
// 将配置好的 Axios 实例导出,以在应用程序的其他部分中使用
export default request;