基于springsecurity的权限管理练习项目(一)

一、项目描述

​ 一个简单的学生信息管理系统,主要用来练习前后端分离下的权限管理

​ 学生可以查看自己所在班级的学生信息,修改自己的信息

​ 老师可以查看自己管理班级的学生信息,能增加、修改学生信息

​ 管理员可以查看所有班级的老师和学生信息,并可以增删改

二、所用技术

​ 前端:vue(cli)、elementui、axios

​ 后端:springboot、mybatisplus、springsecurity、jwt、easyexcel

​ 数据库:mysql、redis

三、前后端协同点

​ 对axios进行请求拦截器封装,每次请求携带token,后端根据token进行鉴权

​ 对axios进行响应拦截器封装,后端进行全局异常处理时,前端展示相应提示

​ 前端负责页面跳转管理,使用路由管理页面访问路径,无权限不能进入对应页面;后端只负责api鉴权,不涉及页面管理

四、数据库设计

​ 一共四张表:学生表,班级表,教师表,教师_班级表,表之间使用逻辑外键、不建立物理外键关联。

​ 管理员默认admin,密码123456,不再建表。学生和班级表初始信息使用excle导入,教师信息由管理员手动添加

​ 表之间关系:

​ 班级 —— 学生  一对多(在学生表中加入班级id作为外键)

​ 班级 —— 教师  多对多(使用中间表,即教师_班级表)

​ 创建数据库sims,建表sql语句如下

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for clazz
-- ----------------------------
DROP TABLE IF EXISTS `clazz`;
CREATE TABLE `clazz`  (
  `id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '班级id',
  `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '班级名称',
  `gmt_create` datetime(0) NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime(0) NOT NULL COMMENT '更新时间',
  `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除,默认为0表示未删,1为已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '学生id',
  `sno` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '学号,11位',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '学生姓名',
  `password` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '学生密码',
  `address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '家庭住址',
  `email` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
  `clazz_id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '所属班级id',
  `gmt_create` datetime(0) NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime(0) NOT NULL COMMENT '更新时间',
  `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除,默认为0表示未删,1为已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher`  (
  `id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '教师id',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '教师姓名',
  `tno` int(0) NOT NULL COMMENT '教师工号',
  `gmt_create` datetime(0) NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime(0) NOT NULL COMMENT '更新时间',
  `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除,默认为0表示未删,1为已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for teacher_clazz
-- ----------------------------
DROP TABLE IF EXISTS `teacher_clazz`;
CREATE TABLE `teacher_clazz`  (
  `id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '教师班级表id',
  `teacher_id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '教师id',
  `clazz_id` char(19) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '班级id',
  `gmt_create` datetime(0) NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime(0) NOT NULL COMMENT '更新时间',
  `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除,默认为0表示未删,1为已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

五、创建前后端项目、搭建基础框架

​ 这一步只是把项目简单搭建起来,并做好基础配置,如前端axios封装,后端生成代码、swagger配置、全局异常处理等

(一)、创建前端项目

1、创建项目

//创建项目 创建的时候选择使用vue-router,之后不再安装
vue init webpack sims

//安装依赖
cnpm i element-ui qs axios -S

2、引入依赖并调整项目结构

在main.js中引入elementui。axios和qs之后我们会在封装的时候用到,封装后只在api中使用用,就不在这里引入了

import Vue from 'vue'
import App from './App'
import router from './router'

//引入并声明elementui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

整个项目的目录结构如下,我们需要新建的有api、utils、views文件夹

基于springsecurity的权限管理练习项目(一)

现在项目默认的是components文件夹里的HelloWorld.vue为主页,先把它改为我们自定义的

  • 删除HelloWorld.vue文件,并在views下新建index.vue
  • 修改router下的index.js文件,引入index.vue并设置路由
import Vue from 'vue'
import Router from 'vue-router'
import index from '@/views/index'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'index',
      component: index
    }
  ]
})
  • 修改App.vue,去除logo和样式
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>


执行命令npm run dev启动项目,效果如下

基于springsecurity的权限管理练习项目(一)

接下来在utils文件夹下新建request.js对axios进行封装,代码如下

import axios from 'axios';
import qs from 'qs';

//请求超时时间为10s
axios.defaults.timeout = 10000;

//允许跨域携带资源凭证
axios.defaults.withCredentials = true;

//设置请求头,如果后端要接收json格式,则改为application/json
axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded';

//使用qs格式化数据,若为json则格式化为json
axios.defaults.transformRequest = data => qs.stringify(data);

//设置请求拦截器,每次请求在请求头里加上token
axios.interceptors.request.use(config => {
    const token = localStorage.getItem('token');
    token && (config.Authorization = token);
    return config;
},error => {
    return Promise.reject(error);
});

//设置响应拦截器
axios.interceptors.response.use(response => {
    //这里也可以拿到后台传过来data里面自定义的code码,以确定是否成功及后续操作
    return response.data;
},error => {
    //根据http状态码进行相应的错误处理
    if(error.response){
        switch(error.response.status){
            case 401:
                break;
            case 403:
                break;
            case 404:
                break;
        }
    }else{
        //若用户网络断开,执行的操作
        if(!window.navigator.onLine){
            return;
        }
        return Promise.reject(error);
    }
});

export default request;


至此,简单的前端基础项目搭建完成,页面逻辑我们之后再写

(二)、创建后端项目

1、创建项目

新建springboot项目,名称为sims,包名为com.sims。项目结构如下

基于springsecurity的权限管理练习项目(一)

2、添加依赖

pom文件如下,项目比较简单,不再划分子模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>sims</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sims</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--代码生成器依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!-- velocity 模板引擎,用于代码生成 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--lombok,idea需安装插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--knife4j中已经引入了swagger,不用再单独引入-->
        <!--<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>-->

        <!--knife4j增强swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3、修改配置文件

springboot默认生成的是properties文件,这里改成了yml文件。

# 服务端口
server:
  port: 8081
spring:
  # 环境设置:dev、test、prod
  profiles:
    active: dev
  # mysql数据库连接
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/sims?serverTimezone=GMT%2B8
    username: root
    password: 123456
#mybatis-plus逻辑删除,实际开发中根据需求选择是否使用
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

4、使用代码生成器生成代码

@Test
public void run() {

    // 1、创建代码生成器
    AutoGenerator mpg = new AutoGenerator();

    // 2、全局配置
    GlobalConfig gc = new GlobalConfig();
    String projectPath = System.getProperty("user.dir");
    gc.setOutputDir("E:\\ideaProject\\sims" + "/src/main/java");

    gc.setAuthor("leaf");
    gc.setOpen(false); //生成后是否打开资源管理器
    gc.setFileOverride(false); //重新生成时文件是否覆盖

    //UserServie
    gc.setServiceName("%sService");	//去掉Service接口的首字母I

    gc.setIdType(IdType.ID_WORKER_STR); //主键策略
    gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
    gc.setSwagger2(true);//开启Swagger2模式

    mpg.setGlobalConfig(gc);

    // 3、数据源配置
    DataSourceConfig dsc = new DataSourceConfig();
    dsc.setUrl("jdbc:mysql://localhost:3306/sims?serverTimezone=GMT%2B8");
    dsc.setDriverName("com.mysql.cj.jdbc.Driver");
    dsc.setUsername("root");
    dsc.setPassword("123456");
    dsc.setDbType(DbType.MYSQL);
    mpg.setDataSource(dsc);

    // 4、包配置
    PackageConfig pc = new PackageConfig();
    pc.setModuleName(""); //模块名
    pc.setParent("com.sims");
    pc.setController("controller");
    pc.setEntity("entity");
    pc.setService("service");
    pc.setMapper("mapper");
    mpg.setPackageInfo(pc);

    // 5、策略配置
    StrategyConfig strategy = new StrategyConfig();

    strategy.setInclude("student","teacher","clazz","teacher_clazz");

    strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
    strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

    strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
    strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

    strategy.setRestControllerStyle(true); //restful api风格控制器
    strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

    mpg.setStrategy(strategy);


    // 6、执行
    mpg.execute();
}

将以上代码复制到springboot项目自带的test类中运行即可,需要注意修改gc.setOutputDir里的路径为项目的磁盘路径,若数据库连接、用户名、密码不一致也需要修改,其他设置根据需求自行修改。

基于springsecurity的权限管理练习项目(一)

生成完毕后项目目录如下

基于springsecurity的权限管理练习项目(一)

5、完善项目配置

(1)、mybatis-plus配置

在com.sims下新建包config,在config包下新建类MybatisPlusConfig ,代码如下

@Configuration
@MapperScan("com.sims.mapper")
public class MybatisPlusConfig  {
    //分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

由于每张表里都有gmt_create和gmt_modified字段,每次添加或修改都要进行操作,因此使用处理器实现自动填充

新建包handler,在handler下新建类MyMetaObjectHandler并实现MetaObjectHandler接口

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时,创建时间和更新时间一致
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("gmtCreate",new Date(),metaObject);
        this.setFieldValByName("gmtModified",new Date(),metaObject);
    }
	//修改时时只修改更新时间
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified",new Date(),metaObject);
    }
}

除此之外还需要在每个实体类中的的gmtCreate和gmtModified字段上添加@TableField注解使handler生效

@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

(2)、swagger配置

在pom文件中我们已经引入了增强swagger的knife4j,接下来只需要进行简单的配置就可以使用了

在config包下新建类SwaggerConfig,代码如下

package com.sims.config;
@Configuration
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sims.controller"))
                .paths(PathSelectors.any())
                .build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("学生信息管理系统")
                .version("1.0")
                .build();
    }
}

(3)、统一结果返回

新建包utils,在utils下新建Result类,用于向前端返回统一数据,这里只是简单分类,实际开发中可以根据业务将状态码详细划分

@Data
public class Result<T> {
    public static Integer SUCCESS = 1; //成功

    public static Integer ERROR = 0; //失败

    @ApiModelProperty(value = "状态码")
    private Integer code;

    @ApiModelProperty(value = "提示信息")
    private String message;

    @ApiModelProperty(value = "数据")
    private T data;

    private Result() {}
    public static Result success() {
        Result result = new Result();
        result.setCode(SUCCESS);
        result.setMessage("操作成功");
        return result;
    }
    public static Result error() {
        Result result = new Result();
        result.setCode(ERROR);
        result.setMessage("操作失败");
        return result;
    }

    public Result code(Integer code) {
        this.setCode(code);
        return this;
    }

    public Result message(String message) {
        this.setMessage(message);
        return this;
    }

    public Result data(T data) {
        this.setData(data);
        return this;
    }
}

(4)、日志处理

在resources目录下新建logback-spring.xml文件

其中需要修改的有

<property name="log.path" value="E:/sims/log"/>,将其中的value值修改为想输出的文件路径
<logger name="com.sims" level="INFO" />,将其中的name值改为当前项目的包路径

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="E:/sims/log" />

    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
        <logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              如果未设置此属性,那么当前logger将会继承上级的级别。
    -->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
     -->
    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
        <logger name="com.sims" level="INFO" />

        <!--
            root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
            可以包含零个或多个appender元素。
        -->
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>


    <!--生产环境:输出到文件-->
    <springProfile name="pro">

        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>

</configuration>

在utils包下新建类ExceptionUtil,用于打印完整日志信息

public class ExceptionUtil {
    public static String getMessage(Exception e) {
        StringWriter sw = null;
        PrintWriter pw = null;
        try {
            sw = new StringWriter();
            pw = new PrintWriter(sw);
            // 将出错的栈信息输出到printWriter中
            e.printStackTrace(pw);
            pw.flush();
            sw.flush();
        } finally {
            if (sw != null) {
                try {
                    sw.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (pw != null) {
                pw.close();
            }
        }
        return sw.toString();
    }
}

(5)、全局异常处理

在handler包下新建全局异常处理类GlobalExceptionHandler和MyException自定义异常类

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    //所有异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result error(Exception e){
        e.printStackTrace();
        log.error(ExceptionUtil.getMessage(e));
        return Result.error().message("服务器错误");
    }
    //自定义异常
    @ExceptionHandler(MyException.class)
    @ResponseBody
    public Result error(MyException myException){
        myException.printStackTrace();
        log.error(ExceptionUtil.getMessage(myException));
        return Result.error().code(myException.getCode()).message(myException.getMsg());
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyException extends RuntimeException{
    private Integer code;
    private String msg;
}

之后手动抛出自定义异常并传入异常信息即可

6、测试

至此,项目的基础配置已经完成,其他的一些配置需要结合逻辑代码实现,我们之后进行。

现在,先测试一下配置是否正确,能否跑通

  • 在StudentController下添加增删改查四个方法(只做测试用,之后还会进行优化修改)
@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    private StudentService studentService;

    @PostMapping
    public Result insert(Student student){
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("sno", student.getSno());
        Student s = studentService.getOne(queryWrapper);
        if(s == null){
            studentService.save(student);
        }else {
            throw new MyException(1, "学号已存在,无法添加");
        }
        return Result.success().message("添加成功");
    }

    @GetMapping
    public Result getList(){
        return Result.success().data(studentService.list());
    }

    @DeleteMapping("{id}")
    public Result delete(@PathVariable String id){
        studentService.removeById(id);
        return Result.success();
    }

    @PutMapping
    public Result update(Student student){
        studentService.update(student,null);
        return Result.success();
    }

}
  • 启动项目,访问http://localhost:8081/doc.html,成功进入swagger页面则swagger配置无问题

基于springsecurity的权限管理练习项目(一)

这里先删除掉图中的四个选项,正常情况下前端是不会传的,由后端处理,为空会报错。

点击发送,响应信息为成功且为同一返回结果格式,则数据库配置正常,统一返回处理类生效

基于springsecurity的权限管理练习项目(一)

再次点击发送,响应信息为自定义异常信息,自定义异常处理器生效

基于springsecurity的权限管理练习项目(一)

找到之前在日志处理配置xml中配置的路径,打开log_error.log日志文件,里面有错误信息,日志处理正常

基于springsecurity的权限管理练习项目(一)

上一篇:Mysql数据库drop表不用跑路,表空间传输助你恢复数据


下一篇:2021-2-18:请你说说MySQL的字符集与排序规则对开发有哪些影响?