谷粒学院day02——讲师管理模块的后端实现

2022-10-26 17:19:40 浏览数 (2)

目录
  • 1.前后端分离概念
  • 2.数据库建表
  • 3.搭建项目工程
  • 4.创建子模块service
  • 5.讲师管理模块的模块配置
  • 6.mp中的代码生成器
  • 7.讲师列表
  • 8.swagger整合
  • 9.统一返回结果
  • 10.分页功能
  • 11.多条件组合查询
  • 12.教师添加功能
  • 13.教师查询与更新
  • 14.统一异常处理
  • 14.1 全局异常处理
  • 14.2 特定异常处理
  • 14.3 自定义异常处理
  • 15.统一日志处理

1.前后端分离概念
2.数据库建表

新建数据库。

代码语言:javascript复制
CREATE DATABASE gulischool;

建表

代码语言:javascript复制
CREATE TABLE IF NOT EXISTS 'edu_teacher' (
  'id' varchar(19) NOT NULL COMMENT '讲师ID',
  'name' varchar(20) NOT NULL COMMENT '讲师姓名',
  'intro' varchar(500) NOT NULL DEFAULT '' COMMENT '讲师简介',
  'career' varchar(500) DEFAULT NULL COMMENT '讲师资历,一句话说明讲师',
  'level' int(10) unsigned NOT NULL COMMENT '头衔 1高级讲师 2首席讲师',
  'avatar' varchar(255) DEFAULT NULL COMMENT '讲师头像',
  'sort' int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
  'is_deleted' tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  'gmt_create' datetime NOT NULL COMMENT '创建时间',
  'gmt_modified' datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY ('id'),
  UNIQUE KEY 'uk_name' ('name')
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='讲师';

报错

代码语言:javascript复制
 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near...

原因是语法错误,改为

代码语言:javascript复制
CREATE TABLE IF NOT EXISTS edu_teacher (
  id char(19) NOT NULL COMMENT '讲师ID',
  name varchar(20) NOT NULL COMMENT '讲师姓名',
  intro varchar(500) NOT NULL DEFAULT '' COMMENT '讲师简介',
  career varchar(500) DEFAULT NULL COMMENT '讲师资历,一句话说明讲师',
  level int(10) unsigned NOT NULL COMMENT '头衔 1高级讲师 2首席讲师',
  avatar varchar(255) DEFAULT NULL COMMENT '讲师头像',
  sort int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
  is_deleted tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  gmt_create datetime NOT NULL COMMENT '创建时间',
  gmt_modified datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (id),
  UNIQUE KEY uk_name (name)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='讲师'

注:表设计规范参考《阿里巴巴java设计规范》如下。

1.表中必备三个字段:id,gmt_create,gmt_modified(gmt为格林时间),id必为主键,类型为bigint unsigned,单表时自增,步长为1.

2.库名与应用名称尽量一致

3.表名、字段名使用小写字母或数字,禁止以数字开头

4.表名不使用复数名词

5.表的命名最好加上"业务名称_表的作用",如"edu_teacher"

6.非负值必须使用unsigned

7.表达与否使用is_xxx

8.单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 说明:如果预计三年后的数 据量根本达不到这个级别,请不要在创建表时就分库分表

9.小数类型为 decimal,禁止使用 float 和 double。 说明:float 和 double 在存储的时候,存在精度损 失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议 将数据拆成整数和小数分开存储

10.唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。

3.搭建项目工程

项目的工程结构如图。

具体建立过程如下。

1)建立父工程,菜单栏选择file->new->new project->springboot initial

注意:笔者下面的project SDK选错了,应该选择1.8版本,java版本是8!!!

在pom.xml中更改springboot版本为2.2.1.RELEASE

代码语言:javascript复制
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
</parent>

添加如下字段,表示父工程是一个pom类型,用于管理依赖版本和公共依赖。

代码语言:javascript复制
 <packaging>pom</packaging>

删除pom文件中的dependencies。

代码语言:javascript复制
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

替换pom文件中的properties,进行版本的控制。

代码语言:javascript复制
    <properties>
        <java.version>1.8</java.version>
        <guli.version>0.0.1-SNAPSHOT</guli.version>
        <mybatis-plus.version>3.0.5</mybatis-plus.version>
        <velocity.version>2.0</velocity.version>
        <swagger.version>2.7.0</swagger.version>
        <aliyun.oss.version>2.8.3</aliyun.oss.version>
        <jodatime.version>2.10.1</jodatime.version>
        <poi.version>3.17</poi.version>
        <commons-fileupload.version>1.3.1</commons-fileupload.version>
        <commons-io.version>2.6</commons-io.version>
        <httpclient.version>4.5.1</httpclient.version>
        <jwt.version>0.7.0</jwt.version>
        <aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version>
        <aliyun-sdk-oss.version>3.1.0</aliyun-sdk-oss.version>
        <aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version>
        <aliyun-java-vod-upload.version>1.4.11</aliyun-java-vod-upload.version>
        <aliyun-sdk-vod-upload.version>1.4.11</aliyun-sdk-vod-upload.version>
        <fastjson.version>1.2.28</fastjson.version>
        <gson.version>2.8.2</gson.version>
        <json.version>20170516</json.version>
        <commons-dbutils.version>1.7</commons-dbutils.version>
        <canal.client.version>1.1.0</canal.client.version>
        <docker.image.prefix>zx</docker.image.prefix>
        <cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
    </properties>

配置dependencyManagement,锁定依赖的版本,这里采用${xxx}直接引用变量来自于上面properties的版本控制,

代码语言:javascript复制
    <!--依赖管理-->
    <dependencyManagement>
        <dependencies>
            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--mybatis-plus 持久层-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity.version}</version>
            </dependency>

            <!--swagger-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--swagger ui-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>${swagger.version}</version>
            </dependency>
            <!--aliyunOSS-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun.oss.version}</version>
            </dependency>
            <!--日期时间工具-->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>${jodatime.version}</version>
            </dependency>
            <!--xls-->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>${poi.version}</version>
            </dependency>
            <!--xlsx-->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.version}</version>
            </dependency>
            <!--文件上传-->
            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>${commons-fileupload.version}</version>
            </dependency>
            <!--commons-io-->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons-io.version}</version>
            </dependency>
            <!--httpclient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>${httpclient.version}</version>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.version}</version>
            </dependency>
            <!-- JWT -->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <!--aliyun-->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>${aliyun-java-sdk-core.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun-sdk-oss.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-vod</artifactId>
                <version>${aliyun-java-sdk-vod.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-vod-upload</artifactId>
                <version>${aliyun-java-vod-upload.version}</version>
            </dependency>
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-sdk-vod-upload</artifactId>
                <version>${aliyun-sdk-vod-upload.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>${json.version}</version>
            </dependency>
            <dependency>
                <groupId>commons-dbutils</groupId>
                <artifactId>commons-dbutils</artifactId>
                <version>${commons-dbutils.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba.otter</groupId>
                <artifactId>canal.client</artifactId>
                <version>${canal.client.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

删除src目录,因为父工程不需要写代码,具体代码会在子模块中实现。

4.创建子模块service

在项目中创建maven子模块service。

如第3步,artifactId节点后添加以下字段(因为service下还会建立子模块)

代码语言:javascript复制
<packaging>pom</packaging>

添加依赖。

代码语言:javascript复制
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <!--hystrix依赖,主要是用 @HystrixCommand -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--服务注册-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--服务调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <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>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--xls-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
        </dependency>
        <!--httpclient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <!--commons-io-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>
        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

将上述至服务调用的依赖(spring cloud相关,暂且用不到,会影响后面的项目启动)暂时注释。

将service下的src删除。在service下新建子子模块service_edu,注意location一定选择在service下。

5.讲师管理模块的模块配置

service_edu模块下src/main/resources目录新建application.properties。

进行如下配置。

代码语言:javascript复制
# 服务端口
server.port=8001
# 服务名
spring.application.name=service-edu
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT+8
spring.datasource.username=root
spring.datasource.password=00000
# mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

上面服务端口配置为8001,如不进行配置则默认为8080。mysql数据中password与username需要替换为个人数据库的对应账户与密码。

6.mp中的代码生成器

在service下的pom文件导入了velocity依赖,用于Mybatis Plus 代码生成器。

代码语言:javascript复制
<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
</dependency>

在testjava下新建包com.wangzhou.eduservice,在包下新建CodeGenerator.java,拷贝以下代码。注:之所以建在test目录下是因为代码生成器不属于需要项目部署的内容,仅仅是进行辅助开发的类。

代码语言:javascript复制
package com.wangzhou.eduservice;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

    /**
     * @王舟
     */
    public class CodeGenerator {

        @Test
        public void run() {

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

            // 2、全局配置
            GlobalConfig gc = new GlobalConfig();
            gc.setOutputDir("E:\ideaworkspace\guli_parent\service\service_edu\"   "/src/main/java"); //输出目录

            gc.setAuthor("wangzhou"); //作者名
            gc.setOpen(false); //生成后是否打开资源管理器,即自动把生成的代码目录结构展开
            gc.setFileOverride(false); //重新生成时文件是否覆盖

            gc.setServiceName("%sService");	//去掉Service接口的首字母I
            gc.setIdType(IdType.ID_WORKER_STR); //主键策略,ID_WORKER_STR(主键为字符串)&&ID_WORKER(主键为数)
            gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
            gc.setSwagger2(true);//开启Swagger2模式

            mpg.setGlobalConfig(gc);

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

            // 4、包配置
            PackageConfig pc = new PackageConfig();

            //生成包:com.achang.eduservice
            pc.setModuleName("eduservice"); //模块名
            pc.setParent("com.wangzhou");

            //生成包:com.achang.controller
            pc.setController("controller");
            pc.setEntity("entity");
            pc.setService("service");
            pc.setMapper("mapper");
            mpg.setPackageInfo(pc);

            // 5、策略配置
            StrategyConfig strategy = new StrategyConfig();
            strategy.setInclude("edu_teacher");//根据数据库哪张表生成,有多张表就加逗号继续填写

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

这里代码会爆红,将edu_service下的pom文件中artifactId改为service即可。

注意上面主键策略根据代码注释进行选择。第3项中,mp的代码生成器数据库的配置需要单独配置,需要根据自己的数据库进行配置,而不是直接使用项目中配置文件的配置。执行run()方法则可以生成代码了。

7.讲师列表

进入controller包下EduTeacherController,爆红,信息为@RequestMapping报Cannot resolve symbol 'RestController’错误,解决办法: 在错误处按alt enter,选择add spring-boot-start-web to classpath。

EduTeacherController中有一个注解@RestController,其具体实现中有两个底层的注解@Controller和@ResponseBody,@Controller表示该类交给springboot管理,是控制层,@ResponseBody说明该类会返回一个json的数据。

在controller层自动注入service。

代码语言:javascript复制
  // 注入service
  @Autowired
  private EduTeacherService eduTeacherService;

service无需再注入mapper,因为mp在底层已经帮助我们进行了mapper的调用。

查询表中所有数据。

代码语言:javascript复制
 // 查询表中所有表数据
    // Rest风格
    @GetMapping("findAll")
    public List<EduTeacher> findAllTeacher() {
        List<EduTeacher> list = eduTeacherService.list(null);
        return list;
    }

在eduservice模块下新建模块启动类。

代码语言:javascript复制
@SpringBootApplication
public class EduApplication {
    public static void main(String[] args) {
        SpringApplication.run(EduApplication.class, args);
    }
}

由于mapper是接口,需要配置mapper的自动扫描。在eduservice下新建config包,包下新建EduConfig类,配置Mapper的自动扫描。

代码语言:javascript复制
@Configuration
@MapperScan("com.wangzhou.eduservice.mapper")
public class EduConfig {

}

启动EduApplication。报错

代码语言:javascript复制
java: java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @0x590)

这是因pom文件中没有指定lombok版本或者版本太低。在maven仓库搜索Lombok最新版本,将其替换eduservice下的pom文件的对应依赖。

代码语言:javascript复制
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <scope>provided</scope>
</dependency>

重新编译重新编译可以顺利通过,通过时将打印如下日志信息。注意如果此时报Unsupported class file major version XX 是由于编译时的jdk版本与运行时的jdk版本不一致。笔者尝试解决未果,从头搭建项目,注意第3节中的提醒,保持编译、运行时jdk版本统一为jdk1.8。

代码语言:javascript复制
2021-10-06 09:40:06.014  INFO 11352 --- [           main] com.wangzhou.eduservice.EduApplication   : Started EduApplication in 4.808 seconds (JVM running for 6.533)

先在数据库中随意添加几条数据,访问http://localhost/8001/eduservice/edu-teacher/findAll。显示的数据是服务器端返回的json数据(@RestController会将contoller交给springboot管理,并返回json数据)。显示数据如下。

代码语言:javascript复制
[{"id":"1","name":"wz","intro":"coolBoy","career":"developer","level":100,"avatar":null,"sort":0,"isDeleted":0,"gmtCreate":"2021-12-01T01:56:30.000 0000","gmtModified":"2021-10-06T01:56:44.000 0000"},{"id":"2","name":"cc","intro":"beautifulgirl","career":"writter","level":18,"avatar":null,"sort":0,"isDeleted":0,"gmtCreate":"2021-10-06T01:57:49.000 0000","gmtModified":"2021-10-06T01:58:02.000 0000"}]

注意上面的时间信息的显示,似乎不太正确。正常的应该是2021-12-01 09:56:30的格式。这是因为这个时间是带时区的显示,显示的是格林尼的标准时间。在application.properties中可以配置时区和时间格式。

代码语言:javascript复制
#配置时间格式及时区
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT 8

重启项目,访问url地址。撒花!

代码语言:javascript复制
[{"id":"1","name":"wz","intro":"coolBoy","career":"developer","level":100,"avatar":null,"sort":0,"isDeleted":0,"gmtCreate":"2021-12-01 09:56:30","gmtModified":"2021-10-06 09:56:44"},{"id":"2","name":"cc","intro":"beautifulgirl","career":"writter","level":18,"avatar":null,"sort":0,"isDeleted":0,"gmtCreate":"2021-10-06 09:57:49","gmtModified":"2021-10-06 09:58:02"}]

下面我们开始crud的传统艺能,先搞个逻辑删除。

步骤如下:

(1)配置一个逻辑删除插件。

代码语言:javascript复制
 @Bean
 public ISqlInjector sqlInjector() {
     return new LogicSqlInjector();
 }

(2)在实体类中对逻辑删除的标识属性isdeleted添加注解@TableLogic

(3)在controller中编写逻辑删除方法。

代码语言:javascript复制
@DeleteMapping("/deleteTeacherById/{id}")
public boolean removeTeacher(@PathVariable String id) {
    eduTeacherService.removeById(id);
    return false;
}

对以上代码简要解释如下。

8.swagger整合

由于使用浏览器只能够测试get类型的提交,我们对于delete方法的提交则需要借助一些工具来测试。如swagger、postman。

使用swagger的作用是:

1.可以进行接口测试。

2.生成一个接口测试的文档,可以从接口文档中读到接口测试的参数,测试的具体功能等。

下面在项目中整合swagger。为了使所有模块都能够使用swagger来进行接口测试,我们新建立一个模块common来进行swagger的整合。如下图,在guli_parent下新建maven模块common。

导入相关依赖。

代码语言:javascript复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>provided </scope>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <scope>provided </scope>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided </scope>
</dependency>
<!--swagger-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <scope>provided </scope>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <scope>provided </scope>
</dependency>
<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X集成redis所需common-pool2
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
</dependency>-->

由于该模块下还会有子模块。删除src包。在pom文件中artifactId后配置:

代码语言:javascript复制
 <packaging>pom</packaging>

common下新建maven子模块service_base。按照如下目录结构创建SwaggerConfig配置类。如果爆红,alt enter,按照提示信息添加依赖并导包即可。

复制下面代码,配置swagger插件,使用Predicates过滤url中admin/.*/error.*的路径,包含这些串的url不进行显示。

代码语言:javascript复制
package com.wangzhou.servicebase;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();
    }


    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("网站-课程中心API文档")
                .description("本文档描述了课程中心微服务接口定义")
                .version("1.0")
                .contact(new Contact("wangzhou", "http://wangzhou.com",
                        "wangzhou@qq.com"))
                .build();
    }
}

要如何在service_edu中来使用这个service_base呢?

(1)引入依赖文件

在service_edu的pom文件中引入service_base作为依赖(敲service_base即有提示,也可以从service_base的pom文件复制groupId等信息)

代码语言:javascript复制
<dependency>
       <groupId>com.wangzhou</groupId>
       <artifactId>service_base</artifactId>
       <version>0.0.1-SNAPSHOT</version>
</dependency>

(2)更改文件扫描规则

在service_base中SwaggerConfig是配置类。service_edu在启动时会扫描该模块的文件,然而配置类不在项目service_edu中。我们可以在service_edu的启动类中增加注解@ComponentScan(basePackages = {"com.wangzhou"}),这样所有com.wangzhou包下的文件都可以被扫描到。

现在来测试下。

启动项目,访问http://localhost:8001/swagger-ui.html。出现了一个绿油油的网页。说明swagger已经整合成功了。不过,好像还有一点点小瑕疵。提示"Uable to infer base url".

在启动类添加注解@EnableSwagger2,重启项目,再次访问。漂漂亮亮,干干净净了。

小手点一点。点edu-teacher-controller -> findAll ->try it out.

测试结果出来了。

测试下逻辑删除功能。

查看数据库。成功了。

在controller中removeTeacher()添加注解,可以使生成的文档信息包含注释,方便调试,读者可自行测试。

代码语言:javascript复制
@ApiOperation("删除讲师")
@DeleteMapping("/deleteTeacherById/{id}")
public boolean removeTeacher(@ApiParam(name = "id", value = "讲师id", required = true) @PathVariable String id) {
     boolean flag= eduTeacherService.removeById(id);
     return flag;
}
9.统一返回结果

由于项目中不同的模块、前后端一般都不是同一个人编写的,不同的接口返回的数据类型不一致,导致编程很不方便,我们采用json来作为统一返回数据格式。json类型的数据格式一般是两种:对象、数组,在实际中一般是两种格式混合使用。一般json数据的格式没有固定格式,只要能够描述清楚数据的具体信息与状态,但一般包含状态码、返回消息、数据等,我们将本项目的返回数据格式统一如下。

代码语言:javascript复制
{
"success": 布尔, //响应是否成功
"code": 数字, //响应码
"message": 字符串, //返回消息
"data": HashMap //返回数据,放在键值对中
}

下面具体来实现统一返回数据。

(1)在common下新建maven子模块common_utils.

(2)如下图所示目录结构新建接口Resultcode。

在接口中存放状态码信息。

代码语言:javascript复制
package com.wangzhou.commonutils;

public interface ResultCode {
    //状态码:成功
    public static Integer SUCCESS = 20000;
    //状态码:失败
    public static Integer ERROR = 20001;
}

在同一路径下,创建统一返回结果的类R。

代码语言:javascript复制
// lombok的注解,自动生成getter,setter等
@Data
public class R {
    // swagger的注解
    @ApiModelProperty("是否成功")
    private boolean success;

    @ApiModelProperty("响应码")
    private Integer code;

    @ApiModelProperty("返回信息")
    private String message;

    @ApiModelProperty("返回数据")
    private Map<String, Object> data = new HashMap<String, Object>();

    //无参构造方法私有,其他类不可以创建该类的实例,只能使用其镜头方法
    private R() {
    }

    public static R ok() {
        R returnData = new R();
        returnData.setSuccess(true);
        returnData.setCode(ResultCode.SUCCESS);
        returnData.setMessage("成功");
        return returnData;
    }

    public static R error() {
        R returnData = new R();
        returnData.setSuccess(false);
        returnData.setCode(ResultCode.ERROR);
        returnData.setMessage("失败");
        return returnData;
    }

    // 方便链式编程,即编程时可以:R.ok().success()
    public R success(Boolean success) {
        this.success = success;
        return this;
    }

    public R code(Integer code) {
        this.code = code;
        return this;
    }

    public R message(String message) {
        this.message = message;
        return this;
    }

    public R data(String key, Object value) {
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> data) {
        this.setData(data);
        return this;
    }
}

下面在模块edu_service中使用统一的返回结果。

(1) 在service的pom文件引入依赖

代码语言:javascript复制
<dependency>
        <groupId>com.wangzhou</groupId>
        <artifactId>common_utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
</dependency>

(2)将controller中的返回结果替换为R,注意导包R时不要导错,要导入自己项目的R,而不是baomidou的。

代码语言:javascript复制
  @ApiOperation("讲师列表")
    @GetMapping("findAll")
    public R findAllTeacher() {
        List<EduTeacher> list = eduTeacherService.list(null);
        return R.ok().data("items", list);
    }

    @ApiOperation("删除讲师")
    @DeleteMapping("/deleteTeacherById/{id}")
    public R removeTeacher(@ApiParam(name = "id", value = "讲师id", required = true) @PathVariable String id) {
        boolean flag= eduTeacherService.removeById(id);
        if(flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }

启动项目,访问http://localhost:8001/swagger-ui.html。

findAll的reponse body如下。成功了,removeTeacher请读者自测。

在测试removeTeacher时发现一个奇怪的现象:删除已经删除过的数据,并不存在的数据,甚至id格式错误的数据都会返回success。

截取日志信息如下。

代码语言:javascript复制
Execute SQL:
    UPDATE
        edu_teacher 
    SET
        is_deleted=1 
    WHERE
        id='p' 
        AND is_deleted=0

根据源码,发现原来是mybatis plus的bug,此bug在3.3.0已经修复。参考博客https://blog.csdn.net/Evian_Tian/article/details/103919089

10.分页功能

下面实现分页功能。

(1)在EduConfig中配置分页插件

代码语言:javascript复制
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}

(2)在EduTeacherController中编写分页功能

代码语言:javascript复制
    @ApiOperation("分页查询")
    @GetMapping("/pageList/{page}/{limit}")
    public R pageList(@ApiParam(name = "page", value = "当前页码", required = true) @PathVariable Long page,
                      @ApiParam(name = "limit", value = "每页记录数", required = true) @PathVariable Long limit) {
        Page<EduTeacher> teacherPage = new Page<>(page, limit);
        // mp会把结果封装到eduTeacherService中
        eduTeacherService.page(teacherPage, null);
        Long total = teacherPage.getTotal();
        List<EduTeacher> records = teacherPage.getRecords();
        Map map = new HashMap<>();
        map.put("total", total);
        map.put("records", records);
        return R.ok().data(map);
    }

多加几个数据,请读者自行启动项目进行测试。

11.多条件组合查询

实现如下图功能。

实现步骤如下。

(1)将查询条件传入接口。

一般把条件值封装成为一个对象,然后将封装对象(vo对象)传递到接口中。我们先在entity下建包vo,vo目录下创建TeacherQuery.java用于封装需要传递的数据。

代码语言:javascript复制
@ApiModel(value = "Teacher查询对象", description = "讲师查询对象封装")
@Data
public class TeacherQuery implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "教师名称,模糊查询")
    private String name;

    @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
    private Integer level;

    @ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
    private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换

    @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
    private String end;

}

(2)编写多条件查询功能

代码语言:javascript复制
    @PostMapping("pageTeacherCondition/{page}/{limit}")
    public R pageTeacherCondition(
    @ApiParam(name = "page", value = "当前页码", required = true) @PathVariable Long page,
    @ApiParam(name = "limit", value = "每页记录数", required = true) @PathVariable Long limit,
    @RequestBody(required = fals) TeacherQuery teacherQuery) {
        Page<EduTeacher> teacherPage = new Page<>();
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
        String name = teacherQuery.getName();
        Integer level = teacherQuery.getLevel();
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();
        if(!StringUtils.isEmpty(name)) {
            wrapper.like("name", name);
        }
        //判断是否传入教师头衔
        if (level != null){
            //构造条件
            wrapper.eq("level",level);
        }
        if (!StringUtils.isEmpty(begin)){
            //构造条件,注意这里的参数名称与数据库对应,而不是属性
            wrapper.ge("gmt_create",begin);//ge:大于等于
        }
        if (!StringUtils.isEmpty(begin)){
            //构造条件
            wrapper.le("gmt_modified",end);//le:小于等于
        }
        eduTeacherService.page(teacherPage, wrapper);
        Long total = teacherPage.getTotal();
        List<EduTeacher> records = teacherPage.getRecords();
        Map map = new HashMap<>();
        map.put("total", total);
        map.put("records", records);
        return R.ok().data(map);
    }

在上面传递参数时使用了@RequestBody,该注解表示用json传参,将json数据封装到对象中,在实际开发中经常使用这个格式来传参,不过使用该注解需要配合@PostMapping使用才能得到查询结果,另外需要显示表示参数值可以为空,否则必须传参。另外@ResponseBody用于返回json数据。

访问http://localhost:8001/swagger-ui.html。

返回的Response Body如下:

代码语言:javascript复制
{
  "success": true,
  "code": 20000,
  "message": "成功",
  "data": {
    "total": 2,
    "records": [
      {
        "id": "1",
        "name": "wz",
        "intro": "coolBoy",
        "career": "developer",
        "level": 2,
        "avatar": null,
        "sort": 0,
        "isDeleted": 0,
        "gmtCreate": "2021-12-01 09:56:30",
        "gmtModified": "2021-10-06 09:56:44"
      },
      {
        "id": "3",
        "name": "ww",
        "intro": "wonderfulman",
        "career": "singer",
        "level": 2,
        "avatar": null,
        "sort": 0,
        "isDeleted": 0,
        "gmtCreate": "2021-10-06 20:38:34",
        "gmtModified": "2021-10-06 20:38:43"
      }
    ]
  }
}

查询结果与预期一致。

12.教师添加功能

教师添加功能很简单.

(1)实现自动填充

可以参考官网文档自动填充功能 | MyBatis-Plus (baomidou.com),下面快速做下。

给实体类需要自动填充的数据增加注解。

代码语言:javascript复制
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;

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

在common模块下的servicebase子模块中新建包handler,handler中新建MyMetaObjectHandler.java,这个类用于实现自动填充的规则。

代码语言:javascript复制
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
    	// 参数1对应的是属性值,而不是数据库中的数据项名称,我们是设置的属性修改的规则
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}

(2)Controller实现添加操作的接口

代码语言:javascript复制
@ApiOperation("添加教师")
@PostMapping("/addTeacher")
public R addTeacher(@RequestBody EduTeacher eduTeacher) {
    boolean succuss = eduTeacherService.save(eduTeacher);
    if(succuss) {
       return R.ok();
    } else {
       return R.error();
    }
}

读者可参考使用下列json数据测试。

代码语言:javascript复制
{
  "avatar": "string",
  "career": "string",
  "intro": "string",
  "isDeleted": 0,
  "level": 0,
  "name": "test001",
  "sort": 0
}
13.教师查询与更新

(1)查找教师

代码语言:javascript复制
    @ApiOperation("查找教师")
    @GetMapping("/findTeacher/{id}")
    public R findTeacher(@PathVariable String id) {
       EduTeacher eduTeacher = eduTeacherService.getById(id);
        return R.ok().data("item", eduTeacher);
    }

(2)更新教师

代码语言:javascript复制
 	@ApiOperation("更新教师")
    @PostMapping("/updateTeacher")
    public R updateTeacher(@RequestBody  EduTeacher eduTeacher) {
        boolean success = eduTeacherService.updateById(eduTeacher);
        if(success) {
            return R.ok();
        } else {
            return R.error();
        }
    }
14.统一异常处理
14.1 全局异常处理

到目前为止,我们都没有对异常情况进行统一处理,如果发生异常,接口暴露的只是简略的信息。

比如,我们可以制造一个异常场景。

代码语言:javascript复制
  @ApiOperation("查找教师")
    @GetMapping("/findTeacher/{id}")
    public R findTeacher(@PathVariable String id) {
        EduTeacher eduTeacher = eduTeacherService.getById(id);
        int i = 5/0;
        return R.ok().data("item", eduTeacher);
    }

自行findTeacher的请求,responseBody信息如下。

下面我们统一对异常信息进行处理。在common的servicebase包下新建包exceptionhandler,包下新建类。

代码语言:javascript复制
@ControllerAdvice
public class GlobalExceptionHandler {
    // 指定全部Expection类型异常执行handler
    @ExceptionHandler(Exception.class)
    @ResponseBody //返回数据
    public R error(Exception e){
        e.printStackTrace();
        return R.error().message("执行全局统一异常处理...");
    }
}

上述代码要使用common_utils模块中的R。因此先在servicebase中引入依赖再导包。

代码语言:javascript复制
<dependency>
    <groupId>com.wangzhou</groupId>
    <artifactId>common_utils</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

由于依赖传递,如下图,故可以移除原来service-edu中对于common_utils的依赖,避免重复引入。

测试结果如下。

14.2 特定异常处理

针对不同的异常类型,我们希望有不同的处理,下面实现特定异常的处理。

代码语言:javascript复制
// 特定异常处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody //返回数据
public R error(ArithmeticException e){
    e.printStackTrace();
    return R.error().message("执行ArithmeticException异常处理...");
}

测试结果如下。

发现response body中的message只包括特定的异常,不包括全局异常。这是因为:异常处理机制是,先查找对应异常的特定处理,如有则进行特定异常处理,否则进行全局异常处理。

14.3 自定义异常处理

(1)创建自定义异常类

在exceptionhandler包下新建GuliException类。

代码语言:javascript复制
@Data // lombok注解:生成getter、setter
@NoArgsConstructor // lombok注解:生成无参构造器
@AllArgsConstructor // lombok注解:生成带参构造器
public class GuliException extends RuntimeException{
    int code;
    String msg;
}

(2)自定义异常处理

代码语言:javascript复制
// 自定义异常处理
@ExceptionHandler(GuliException.class)
@ResponseBody //返回数据
public R error(GuliException e){
    e.printStackTrace();
    return R.error().code(e.getCode()).message(e.getMsg());
}

(3)抛出异常

代码语言:javascript复制
@ApiOperation("查找教师")
@GetMapping("/findTeacher/{id}")
public R findTeacher(@PathVariable String id) {
    try {
        int i = 1 / 0;
    } catch (Exception e) {
        throw new GuliException(1234, "自定义异常");
    }
    EduTeacher eduTeacher = eduTeacherService.getById(id);
    return R.ok().data("item", eduTeacher);
}
15.统一日志处理

(1)设置日志级别

日志级别从高到低是OFF,FATAL,ERROR,WARN,INFO,DEBUG,ALL,默认的日志级别是INFO,即INFO级别以上的信息都会被打印出来。可以在application.properties中进行设置。

代码语言:javascript复制
#设置日志级别
logging.level.root = WARN

(2)配置logback日志

要想把日志信息显示到文件中,就需要借助于框架。这里使用logback作为日志框架。

先将之前在application.properties中与日志相关的配置注释,避免冲突。

代码语言:javascript复制
# mybatis日志
# mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 设置日志级别
# logging.level.root = INFO

在edu_service包下的resource中新建springboot-logback.xml.

代码语言:javascript复制
<?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="D:\gulixueyuan\logback"/>

    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(�te{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.guli" 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>

配置完后输出日志效果如下。

并且日志信息可以在配置文件指定的文件中保存、查看了。

如果想把错误的异常信息输出到log_error.log文件中,可以在GlabalExceptionHandler上添加注解@Slf4j,然后将在异常处理方法中输出异常信息。

代码语言:javascript复制
// 自定义异常处理
@ExceptionHandler(GuliException.class)
@ResponseBody //返回数据
public R error(GuliException e){
    log.error(e.getMessage());
    e.printStackTrace();
    return R.error().code(e.getCode()).message(e.getMsg());
}

0 人点赞