SpringBoot + Shiro安全框架 + vue(保姆级从零开始)

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
        }
    }
})

在这里插入图片描述

②:配置彩色日志

详细地址:https://blog.csdn.net/cygqtt/article/details/134280707

③:整合knife4j生成Api接口文档

详细地址:https://blog.csdn.net/cygqtt/article/details/134544894

④:数据库 数据准备

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. 必看思路

  1. 用户登录(用过滤器拦截JwtFilter) ==>
  2. 执行认证逻辑(MyRealm) ==>
  3. 用户输入的密码与数据库中的密码匹配(MyCredentialsMatcher) ==>
  4. 使用自定义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;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
这个框架整合可以分为以下几个步骤: 1. 集成 Spring Boot:在 pom.xml 文件中添加 Spring Boot 依赖,创建 Spring Boot 启动类。 2. 集成 MyBatis Plus:在 pom.xml 文件中添加 MyBatis Plus 依赖,配置数据源和 MyBatis Plus 相关配置。 3. 集成 Shiro:在 pom.xml 文件中添加 Shiro 依赖,创建 Shiro 配置类,配置 Shiro 的 Realm 和 SecurityManager。 4. 集成 JWT:在 pom.xml 文件中添加 jjwt 依赖,创建 JWT 工具类,用于生成和解析 JWT。 下面是一个简单的示例代码,你可以根据你的实际需求进行调整: 1. pom.xml 文件中添加依赖: ```xml <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- Shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <!-- jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>${jjwt.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> ``` 2. 创建 Spring Boot 启动类: ```java @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 3. 配置 MyBatis Plus: ```java @Configuration @MapperScan("com.example.mapper") public class MyBatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } } ``` 4. 配置 Shiro: ```java @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); shiroFilter.setUnauthorizedUrl("/401"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/401", "anon"); filterChainDefinitionMap.put("/**", "jwt"); shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap); Map<String, Filter> filters = new LinkedHashMap<>(); filters.put("jwt", new JwtFilter()); shiroFilter.setFilters(filters); return shiroFilter; } @Bean public DefaultWebSecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public Realm realm() { return new UserRealm(); } } ``` 5. 创建 JWT 工具类: ```java public class JwtUtil { private static final String SECRET_KEY = "your_secret_key"; private static final long EXPIRATION_TIME = 3600_000; // 1 hour public static String generateToken(String username) { Date now = new Date(); Date expiration = new Date(now.getTime() + EXPIRATION_TIME); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (JwtException e) { return false; } } } ``` 6. 创建 JwtFilter: ```java public class JwtFilter extends AuthenticatingFilter { @Override protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) { HttpServletRequest request = (HttpServletRequest) servletRequest; String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { return null; } return new JwtToken(token); } @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { HttpServletResponse response = (HttpServletResponse) servletResponse; response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期,请重新登录\"}"); return false; } @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { HttpServletRequest request = (HttpServletRequest) servletRequest; String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { return false; } return JwtUtil.validateToken(token); } } ``` 7. 创建 JwtToken: ```java public class JwtToken implements AuthenticationToken { private final String token; public JwtToken(String token) { this.token = token; } @Override public Object getPrincipal() { return JwtUtil.getUsernameFromToken(token); } @Override public Object getCredentials() { return token; } } ``` 8. 创建 UserRealm: ```java public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String username = (String) authenticationToken.getPrincipal(); User user = userService.getByUsername(username); if (user == null) { throw new UnknownAccountException("账号不存在"); } return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } } ``` 这样,简单的 Spring Boot + Shiro + JWT + MyBatis Plus 整合就完成了。你可以根据具体的需求,对代码进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七@归七

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值