谷粒学院16万字笔记+1600张配图(三)——后台讲师管理模块

项目源码与所需资料
链接:https://pan.baidu.com/s/1azwRyyFwXz5elhQL0BhkCA?pwd=8z59
提取码:8z59

文章目录

demo03-后台讲师管理模块

1.前后端分离概念

前端:html、css、js、jq

  • 主要作用:数据显示

后端:controller、service、mapper

  • 主要作用:返回数据或者操作数据
  • 后端主要负责开发接口(这个接口并不是interface)这里的开发接口是我们的术语,说的是开发controller、service、mapper的过程
  • 然后前端项目用ajax调用我们在后端开发的接口,后端返回数据(基本都是json格式数据)给前端

2.搭建项目环境

2.1数据库设计

1.创建数据库guli(字符集选择"utf8mb4")

在这里插入图片描述

2.在资料中找到"guli_edu.sql"这个脚本文件并双击打开

在这里插入图片描述

3.找到下图所示的这段语句

在这里插入图片描述

CREATE TABLE `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='讲师';
  • 为什么这里设置id为19位呢?因为我们前面说过,mp自动帮我们生成的id就是19位的

4.将上一步说的那段语句复制到数据库中并执行该sql语句就可以创建出edu_teacher数据表了

在这里插入图片描述

5.《阿里巴巴Java开发手册》中部分数据库设计规约(混下眼熟即可,重点看第五条)

1、库名与应用名称尽量一致

2、表名、字段名必须使用小写字母或数字,禁止出现数字开头,

3、表名不使用复数名词

4、表的命名最好是加上“业务名称_表的作用”。如,edu_teacher

5、表必备三字段:id, gmt_create, gmt_modified

说明:

其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。

(如果使用分库分表集群部署,则id类型为verchar,非自增,业务中使用分布式id生成器)

gmt_create, gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被 动更新。

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

7、表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)。

说明:任何字段如果为非负数,必须是 unsigned。

注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的 命名方式是为了明确其取值含义与取值范围。

正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

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

9、如果存储的字符串长度几乎相等,使用 char 定长字符串类型。

10、varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。

11、唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。

说明:uk_ 即 unique key;idx_ 即 index 的简称

12、不得使用外键与级联,一切外键概念必须在应用层解决。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

2.2创建工程

2.2.1总体思路

  • 先创建一个父工程
    • 父工程的类型是pom类型,父工程负责管理依赖版本和放公共依赖
  • 然后在父工程中创建子模块(有时我们会在子模块中再创建子模块,也就是创建父工程的子子模块)
  • 我们创建的父工程是Spring Boot工程,子模块需要用到父工程中的依赖,所以我们的子模块/子子模块也可以建成Spring Boot工程,但是为了方便我们把子模块/子子模块建成Maven工程,因为Spring Boot工程本身也是Maven工程,只是在Maven工程的基础上又加了一些相关依赖

模块说明:

guli-parent:在线教学根目录(父工程),管理四个子模块:

  • canal-client:canal数据库表同步模块(统计同步数据)

  • common:公共模块父节点

    • common-util:工具类模块,所有模块都可以依赖于它
    • service-base:service服务的base包,包含service服务的公共配置类,所有service模块依赖于它
    • spring-security:认证与授权模块,需要认证授权的service服务依赖于它
  • infrastructure:基础服务模块父节点

    • api-gateway:api网关服务
  • service:api接口服务父节点

    • service-acl:用户权限管理api接口服务(用户管理、角色管理和权限管理等)
    • service-cms:cms api接口服务
    • service-edu:教学相关api接口服务
    • service-msm:短信api接口服务
    • service-order:订单相关api接口服务
    • service-oss:阿里云oss api接口服务
    • service-statistics:统计报表api接口服务
    • service-ucenter:会员api接口服务
    • service-vod:视频点播api接口服务

2.2.2创建父工程

1.点击File–>New–>Project…

在这里插入图片描述

2.选中Spring Initializr,按照下图进行填写、选择:

在这里插入图片描述

3.Spring Boot版本先随便选一个,后面会在pom.xml进行修改

在这里插入图片描述

4.在pom.xml中将Spring Boot的版本改为2.2.1.RELEASE

在这里插入图片描述

5.我们前面说过了父工程应该是pom类型的,所以我们应该在<artifactId>标签后添加<packaging>pom</packaging>,这样父工程就是pom类型了

在这里插入图片描述

6.删除下图所示的代码

在这里插入图片描述

7.添加 <properties>确定依赖的版本

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

在这里插入图片描述

8.配置<dependencyManagement>锁定依赖的版本(放在<properties>标签的后面)

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

在这里插入图片描述



遇到的问题:

我这里的这个依赖爆红了(你们如果用的是我的maven仓库,应该是不会爆红的,可以选择不看接下来的解决步骤):

在这里插入图片描述

因为aliyun-java-vod-upload-1.4.11暂时还没开源,所以我们需要手动将这个依赖安装到本地仓库中:

①找到资料中的阿里云视频点播服务--->VODUploadDemo-java-1.4.11--->lib,点击进入lib文件夹

在这里插入图片描述

②在地址栏中输入cmd后按回车进入命令行窗口

在这里插入图片描述

③输入如下命令下载依赖到我们的本地仓库:

执行该命令时如果提示mvn"'mvn’不是内部或外部命令,也不是可运行的程序
或批处理文件。",那就需要先去配置maven的环境变量

mvn install:install-file  -DgroupId=com.aliyun  -DartifactId=aliyun-java-vod-upload -Dversion=1.4.11 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.11.jar

在这里插入图片描述

④至此我们就成功将aliyun-java-vod-upload-1.4.11的依赖下载到本地仓库了,回到idea发现不再爆红了

⑤也正是因为我已经将aliyun-java-vod-upload-1.4.11的依赖下载到本地仓库了,所以如果使用的是我的maven仓库,就不会遇见这个问题了

9.删除src文件夹

我们在父工程中不写具体的mapper,service,controller,所以src这个文件夹我们不会使用,直接删掉即可

在这里插入图片描述

2.2.3创建子模块

1.右键点击"guli_parent",选择New–>Module…

在这里插入图片描述

2.创建一个Maven工程

在这里插入图片描述

在这里插入图片描述

3.因为子模块下面还有子模块,所以要将这个子工程改为pom类型

在这里插入图片描述

4.我们在父工程的pom.xml中添加的依赖都在<dependencyManagement>标签内,所以只是对依赖及版本做了定义,并没有用这些依赖,我们统一在子模块中用这些依赖,我们现在在子模块中引用这些依赖:

<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-boot-starter-web:因为我们最终建立的子子模块是一个web工程(因为子子工程要建mapper,service,mapper),那么访问项目时肯定要通过浏览器进行访问,那么就需要ip和端口号,所以就需要这个web依赖
  • 因为我们在父工程中已经设置过依赖版本了,所以给该子工程引用依赖时不再需要管版本,我们直接把这些依赖拿来用就可以了
  • 如果添加依赖后爆红,点击"Load Maven Changes"刷新一下就可以了:

在这里插入图片描述

5.上一步在子工程添加的依赖中有一部分我们暂时用不到,先将其注释掉(如果不注释掉:①功能写完了不会报错②功能没写完就会报错)

在这里插入图片描述

6.因为该子工程我们也不需要写代码,所以也将src目录删掉

在这里插入图片描述

2.2.4创建子子模块

1.右键点击"service",选择New–>Module…

在这里插入图片描述

2.创建一个Maven工程

在这里插入图片描述

在这里插入图片描述

3.在子子模块"service_edu"的resources目录下创建配置文件application.properties并编写配置

# 服务端口
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?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

在这里插入图片描述

  • server.port=8001:表示项目启动的端口号
  • spring.application.name=service-edu:后面做到Spring Cloud时使用的,暂时用不到,现在不用管
  • spring.profiles.active=dev:将项目环境设置为开发环境

2.3代码生成器

2.3.1mp创建代码生成器

mp提供代码生成器,来生成相关代码

1.需要在service模块的pom.xml中引入velocity-engine-core依赖(我们前面已经做过了)

在这里插入图片描述

2.代码生成器就是一个工具,只是供我们自己开发时使用,并不需要部署给别人用,所以我们将代码生成器放到测试目录test中:在test目录下的java目录创建包com.atguigu.demo

在这里插入图片描述

3.复制资料中的"代码生成器"目录下的java类到上一步创建的包下

在这里插入图片描述

在这里插入图片描述

这个代码生成器中的代码不要求我们会写,但必须要能看懂,以便以后修改代码

2.3.2解释&修改代码生成器的部分代码

gc.setOutputDir(projectPath + "/src/main/java");表示生成的代码输出到哪个地方,其中projectPath是得到我们当前文件夹的路径,但有时得到的路径可能有问题,所以我们这里将其改为子子模块的绝对路径

在这里插入图片描述

在这里插入图片描述

gc.setAuthor("testjava");代码生成后会有一个注释标明作者

gc.setOpen(false);如果参数为true,那么生成代码后会自动打开生成的所有代码,如果参数为false,那么就不会自动打开生成的代码

gc.setFileOverride(false);我们第一次生成代码后,我们肯定会在开发过程中将生成的代码进行修改,当我们再用代码生成器执行一次后,如果该参数为true那么就会把我们以前写的代码覆盖掉,如果参数是false就不会覆盖,我们肯定不想我们辛辛苦苦编写修改后代码被覆盖,所以这个参数一定要用false

gc.setServiceName("%sService");有这行代码,会去掉Service接口的首字母I,比如:没有这行代码是IUserService,有这行代码是UserService

gc.setIdType(IdType.ID_WORKER);:主键策略,因为我们数据表edu_teacher的id是char类型,所以这里要将参数的IdType.ID_WORKER改为IdType.ID_WORKER_STR

在这里插入图片描述

gc.setDateType(DateType.ONLY_DATE);定义生成的实体类中日期类型

因为我们的数据表edu_teacher中gmt_create和gmt_modified这两个字段的类型是datetime类型,有了这行代码就可以规定生成的实体类中的日期类型为Date类型

在这里插入图片描述

⑧mp的数据库和我们项目用的数据库不是一个配置,代码生成器无法使用我们在项目中配置的数据库,代码生成器只能单独配置

修改一下代码生成器中的数据库配置:

  • 将dsc.setUrl(“jdbc:mysql://localhost:3306/guli”);改为dsc.setUrl(“jdbc:mysql://localhost:3306/guli?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8”);
  • 将dsc.setDriverName(“com.mysql.jdbc.Driver”);改为dsc.setDriverName(“com.mysql.cj.jdbc.Driver”);

在这里插入图片描述

⑨对包配置进行修改,修改后如下:

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

pc.setParent("com.atguigu");
pc.setModuleName("eduservice"); //模块名

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

在这里插入图片描述

pc.setParent(“com.atguigu”);和pc.setModuleName(“eduservice”);这两行代码共同作用,使我们生成的代码的包是com.atguigu.eduservice

⑩策略配置

以后每次生成代码时策略配置只需要修改一个地方:

strategy.setInclude("edu_teacher");的参数改为我们准备逆向生成代码的数据表的名称,比如这里要逆向生成edu_teacher表的代码,所以参数就应该是"edu_teacher"

在这里插入图片描述

修改完成后的代码生成器完整代码如下:

public class CodeGenerator {
    
    

    @Test
    public void run() {
    
    

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

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir("D:\\study\\java\\code\\project\\guli_parent\\service\\service_edu" + "/src/main/java");
        gc.setAuthor("testjava");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖
        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/guli?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        dsc.setDbType(DbType.MYSQL); //数据库类型
        mpg.setDataSource(dsc);

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

        pc.setParent("com.atguigu");
        pc.setModuleName("eduservice"); //模块名

        pc.setController("controller"); //生成controller包
        pc.setEntity("entity"); //生成entity包
        pc.setService("service"); //生成service包
        pc.setMapper("mapper"); //生成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();
    }
}

2.3.3生成代码

1.右键点击测试方法run选择"Run ‘run()’"来执行代码生成器

在这里插入图片描述

2.可以看到我们成功生成了代码

在这里插入图片描述

在这里插入图片描述

3.查询讲师表所有数据

3.1准备工作

1.先执行下面sql语句,向数据库中插入五条数据

INSERT INTO `edu_teacher` VALUES ('1', 'lucy', '高级讲师简介', '高级讲师资历', '2', 'http://edu-longyang.oss-cn-beijing.aliyuncs.com/2020/08/05/25f411209c8b44b9b003482b6265c3c9file.png', '2', '0', '2019-10-30 11:55:19', '2019-11-12 13:37:52');
INSERT INTO `edu_teacher` VALUES ('1189390295668469762', '李刚upupup', '高级讲师简介111', '高级讲师111', '2', 'http://edu-longyang.oss-cn-beijing.aliyuncs.com/2020/08/05/25f411209c8b44b9b003482b6265c3c9file.png', '2', '0', '2019-10-30 11:55:19', '2019-11-12 13:37:52');
INSERT INTO `edu_teacher` VALUES ('1189426437876985857', '王二', '高级讲师简介', '高级讲师', '1', 'http://edu-longyang.oss-cn-beijing.aliyuncs.com/2020/08/05/25f411209c8b44b9b003482b6265c3c9file.png', '0', '0', '2019-10-30 14:18:56', '2019-11-12 13:37:35');
INSERT INTO `edu_teacher` VALUES ('1189426464967995393', '王五', '高级讲师简介', '高级讲师', '2', 'http://edu-longyang.oss-cn-beijing.aliyuncs.com/2020/08/05/25f411209c8b44b9b003482b6265c3c9file.png', '0', '0', '2019-10-30 14:19:02', '2019-11-12 13:37:18');
INSERT INTO `edu_teacher` VALUES ('1192249914833055746', '李四', '高级讲师简介', '高级讲师', '1', 'http://edu-longyang.oss-cn-beijing.aliyuncs.com/2020/08/05/25f411209c8b44b9b003482b6265c3c9file.png', '0', '0', '2019-11-07 09:18:25', '2019-11-12 13:37:01');

2.然后我们解释一下控制器EduTeacherController上的注解@RestController:

在这里插入图片描述

一边按着Ctrl一边鼠标单击"@RestController"点进去,发现接口RestController上面有两个注解@Controller@ResponseBody

在这里插入图片描述

  • 前者表示将这个类交给Spring管理

  • 后者有两个作用:①如果此时执行的控制器中的这个方法返回值是字符串(比如"success"字符串),那么直接将字符串写到客户端(如果没有这个注解就会跳转到"success.html"页面);②如果是一个java对象,会将对象转化为json串,然后写到客户端

    作用①很好理解。我来解释下作用②:假如我们在此时执行的控制器中的这个方法中创建了一个user对象,他的id值是1,username值是张三,age没有设值,此时如果我们没有这个注解,返回给前端的就是一个java对象,如果有这个注解,返回给前端的就是json串:{“id”:0,“username”:“张三”,“age”:“null”}

3.service层有一个impl包,这个包下有一个实现类"EduTeacherServiceImpl"继承了mp为我们写的类"ServiceImpl"

在这里插入图片描述

一边按着Ctrl一边鼠标单击"ServiceImpl"点进去看这个类

在这里插入图片描述

可以发现这个类实现了"IService"接口并且在这个类中注入了baseMapper,也就是mp为我们对service层进行了封装:service层自动去调mapper层的方法,然后类"ServiceImpl"中会提供我们常用的方法,所以简单操作数据库时我们不再需要关心service层和mapper层(mp也对mapper层进行了封装,在"demo02-MybatisPlus"–>"2.6编写代码"的第3步说过),只需要关心controller层就可以了,当然,复杂的操作数据库的话当然需要在mapper层和service层编写代码

3.2在控制器中编写代码

@RestController
@RequestMapping("/eduservice/edu-teacher")
public class EduTeacherController {
    
    
    //把service注入
    @Autowired
    private EduTeacherService teacherService;

    
    //1.查询讲师表所有数据
    //rest风格
    @GetMapping("findAll")
    public List<EduTeacher> findAllTeacher() {
    
    
        List<EduTeacher> list = teacherService.list(null);
        return list;
    }
}

在这里插入图片描述

  • 什么是rest风格:每种操作使用到不同的请求方式或者说是提交方式,比如查询用get提交方式,添加用put提交方式,修改用post提交方式,删除用delete提交方式,这就是rest风格。当然,你要说你不想用rest风格,你就想查询用post,修改用get也是可以的

  • list方法的参数是查询条件Wrapper,我们这里不设置条件,所以让参数为null

  • @GetMapping(“findAll”)参数可以是"/findAll",也可以是"findAll";但@RequestMapping(“/eduservice/edu-teacher”)的参数只能是"/eduservice/edu-teacher",不能是"eduservice/edu-teacher"

3.3创建启动类

虽然我们创建的子子模块service_edu是maven工程,但本质上还要让它成为一个Spring Boot工程,那么就需要配置文件"application.properties"和启动类,配置文件我们已经在前面创建并编写过了,所以我们只需要再创建一个启动类就可以了

1.右键点击com.atguigu.eduservice选择New–>Java Class,创建一个启动类EduApplication

在这里插入图片描述

2.在启动类中编写代码:

@SpringBootApplication
public class EduApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(EduApplication.class, args);
    }
}

在这里插入图片描述

3.4创建配置类

前面学习mp时说过,因为我们的mapper层只有接口没有接口实现类,所以需要使用注解@MapperScan[因为service层和controller层都有接口实现类,所以不需要这个注解,只有mapper层需要]

在com.atguigu.eduservice下创建包config,在config包下创建配置类EduConfig,然后在配置类中编写代码:

@Configuration
@MapperScan("com.atguigu.eduservice.mapper")
public class EduConfig {
    
    
}

在这里插入图片描述

3.5启动项目

1.在启动类的main方法上右键选择Run 'EduApplication’启动项目进行测试

在这里插入图片描述

2.在地址栏输入http://localhost:8001/eduservice/edu-teacher/findAll进行测试

在这里插入图片描述
在这里插入图片描述

3.但我们发现网页展示的数据中显示的时间是2019-10-30T03:55:19.000+0000,而我们数据库中的时间是2019-10-30 11:55:19,这是因为默认情况下json时间格式带有时区,并且是世界标准时间,和我们的时间差了八个小时,解决这个问题需要我们在application.properties中进行配置:

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

在这里插入图片描述

4.讲师逻辑删除

4.1编写配置&代码

1.在配置类中配置逻辑删除插件

/**
* 逻辑删除插件
*/
@Bean
public ISqlInjector sqlInjector() {
    
    
    return new LogicSqlInjector();
}

在这里插入图片描述

2.给实体类EduTeacher的isDeleted属性添加@TableLogic注解

在这里插入图片描述

3.在控制器中编写方法

//2.逻辑删除讲师的方法
@DeleteMapping("{id}")
public boolean removeTeacher(@PathVariable String id) {
    
    
    boolean flag = teacherService.removeById(id);
    return flag;
}

在这里插入图片描述

  • @DeleteMapping(“{id}”)表示id值需要通过地址栏的路径进行传递,比如说:http://localhost:8001/eduservice/edu-teacher/1
  • 好,我们现在已经知道了id是怎么传递的了,那么后端怎么得到这个id呢?
    • 这就需要使用@PathVariable注解对removeTeacher方法的形参id进行修饰,这样的话路径中的值就会注入给removeTeacher方法的形参id
    • 既然这样说,那么@DeleteMapping(“{}”)的"{}“中的字母必须和@PathVariable修饰的形参一样才可以配对。也就是说如果是@DeleteMapping(”{id22}"),那么@PathVariable修饰的形参就必须是id22,这样才能配对完成注入

4.2测试

接下来我们进行测试,但是在测试时会遇到一个问题:我们用浏览器是不能直接测delete提交的,浏览器只能测get请求,其余的post、put、delete请求都不能用浏览器直接测,我们需要借助一些工具进行测试,这些工具有swagger测试(重点)、postman(了解)

接下来我们来整合Swagger并进行接口测试

4.2.1swagger2介绍

  • 可以生成一个在线接口文档,通过这个文档我们可以看到我们的接口是做什么功能的、传的参数是什么、返回什么数据
  • 方便接口测试

为了测试该子子模块service_edu的逻辑删除功能,我们需要将swagger整合到service_edu中,但因为我们后面还会有很多模块,每个模块都需要借助swagger进行测试,所以我们不将swagger专门整合到service_edu中,而是在父工程下创建一个公共模块来整合swagger,这样的话父工程下的子模块、子子模块都可以进行使用了

4.2.2创建common模块

1.右键点击guli_parent选择File–>New Module…

在这里插入图片描述

2.子模块是maven工程

在这里插入图片描述

3.进行如下填写

在这里插入图片描述

4.因为common模块下还会再创建模块,所以需要将它改为pom类型

在这里插入图片描述

5.在common模块的pom.xml中给该模块添加依赖

<dependencies>
    <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>-->
</dependencies>

在这里插入图片描述

注意最后的commons-pool2依赖用不到,先注释掉

6.删掉common模块的src目录

在这里插入图片描述

4.2.3创建common的子模块service_base

1.右键点击common选择File–>New Module…

在这里插入图片描述

2.这个子子模块是maven工程

在这里插入图片描述

3.进行如下填写(注意Parent一栏一定要选择common而不能选择guli_parent)

在这里插入图片描述

4.2.4在service_base中整合swagger

1.在java目录下创建包com.atguigu.servicebase

在这里插入图片描述

2.在servicebase包下创建配置类SwaggerConfig

在这里插入图片描述

3.对配置类进行编写

@Configuration //配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {
    
    
    /**
     * swagger插件
     */
    @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("Helen", "http://atguigu.com", "[email protected]"))
                .build();
    }
}

在这里插入图片描述

配置类中的代码不需要会写,只要使用时会修改就可以了

webApiConfig方法就是一个swagger插件

  • .groupName("webApi")是设置组的名字,这个参数可以随便写
  • .apiInfo(webApiInfo())是调用webApiInfo方法,webApiInfo用于设置在线文档的信息
  • .paths(Predicates.not(PathSelectors.regex("/admin/.*"))).paths(Predicates.not(PathSelectors.regex("/error.*")))表示如果你的接口路径中包含admin、error,就不会对admin、error进行显示。这两行代码可有可无

4.2.5在service模块中引入service-base

前面我们已经在service_base模块中整合好swagger了,但是swagger是在service_base中的,并没有在service_edu中,所以想要在service_edu中使用swagger就需要在service模块中引入service-base

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>service_base</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

在这里插入图片描述

4.2.6添加注解@ComponentScan

1.一边按Ctrl一边点启动类上的注解@SpringBootApplication进去看下这个注解,我们发现接口SpringBootApplication上面本身就有注解@ComponentScan

在这里插入图片描述

看不懂这里的@ComponentScan注解无所谓,暂时只需要知道这个注解结合其参数最终的作用:扫描启动类同级及其子级包下的所有文件,所以说如果我们没有给启动类添加注解@ComponentScan那么默认就是扫描启动类同级及其子级包下的所有文件

借用在网上看到的三张图片来解释一下何为同级何为子级何为上级:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.我们在"demo2-demo02-MybatisPlus"中配置的乐观锁插件、分页插件、逻辑删除插件、SQL性能分析插件都是在配置类MpConfig中,而配置类MpConfig又是启动类Mpdemo1010Application的子级,所以即使我们使用默认的@ComponentScan注解(也就是不给启动类Mpdemo1010Application添加注解@ComponentScan)我们配置的这几个插件也都可以被Spring扫描到并装⼊bean容器

但是我们在"4.2.4在service_base中整合swagger"配置的swagger插件是在模块service_base中的,根本就没有在service_edu模块中,更不用谈那句"扫描启动类同级及其子级包下的所有文件",想要使swagger插件可以被Spring扫描到并装⼊bean容器需要两步:

①在当前模块(service_edu模块)的pom.xml或当前模块的父工程(service模块)的pom.xml引入需要扫描包的坐标,我们在前面的"4.2.5在service模块中引入service-base"已经在service模块的pom.xml引入需要扫描包的坐标,所以这一步不再需要做

在这里插入图片描述

②给service_edu模块的启动类EduApplication添加注解@ComponentScan(basePackages = {“com.atguigu”})

在这里插入图片描述

  • 这里的com.atguigu是因为service_base中的配置类SwaggerConfig在servicebase包下,而servicebase包又在com.atguigu包下,这样Spring扫描service_base模块的com.atguigu包时就可以扫描到servicebase包,进而扫描到配置类SwaggerConfig,进而扫描到swagger插件,最后就将swagger插件装⼊bean容器了

在这里插入图片描述

4.2.7进行测试

1.在启动类EduApplication的main方法上右键选择Run 'EduApplication’启动项目进行测试

在这里插入图片描述

2.访问swagger:在地址栏输入http://localhost:8001/swagger-ui.html

在这里插入图片描述

3.文档中会列出当前可以测试的controller,可以看到我们当前只有一个可以测试的controller——edu-teacher-controller

在这里插入图片描述

4.点击edu-teacher-controller就会看到当前controller中可以测试的方法

在这里插入图片描述

5.测试findAll方法:

①点击Try it out!就可以测试该方法了

在这里插入图片描述

②测试结果如下:

在这里插入图片描述

6.测试removeTeacher方法:

①在id的输入框中输入1,然后点击Try it out!对该方法进行测试

在这里插入图片描述

②从测试结果可以看到返回的是true,说明测试成功(因为被测试的方法removeTeacher返回值是true或者false,返回前者代表成功,返回后者代表失败)

在这里插入图片描述

③去数据库中查看数据,可以看到edu_teacher表中id为1的数据的is_deleted字段确实被从0改为了1

我们以后测试都用swagger进行测试

4.3自定义设置

我们前面已经成功使用swagger进行测试,下面来给swagger添加一些自定义设置

1.给service_edu模块的EduTeacherController类添加注解@Api(description = “讲师管理”)

在这里插入图片描述

2.给EduTeacherController类中的findAllTeacher方法添加注解@ApiOperation(value = “所有讲师列表”)

在这里插入图片描述

3.给EduTeacherController类中的removeTeacher方法添加注解@ApiOperation(value = “逻辑删除讲师”)

在这里插入图片描述

4.给EduTeacherController类中的removeTeacher方法的参数添加注解@ApiParam(name = “id”, value = “讲师ID”, required = true)

在这里插入图片描述

5.重启项目,再次在地址栏输入http://localhost:8001/swagger-ui.html看一下有什么变化

在这里插入图片描述

5.统一结果返回

5.1统一返回数据格式

1.因为在实际开发中并不是一个人开发项目,每个人做的接口返回的数据格式不一样,可能你返回的是true,而别人返回的是1,这就会造成接口数据不一样,最终的后果是前端解析从后端返回的数据时特别不方便,所以我们需要让我们所有的接口返回相同的数据格式

项目中我们会将响应封装成json返回。一般情况下统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以,但是一般会包含状态码、返回消息、数据这几部分内容

2.json数据格式有两种:对象和数组,对象是大括号,数组是中括号,一般我们会混合使用这两种数据格式

我们看一组比较全的返回数据

{
    
    
    "success": true,
    "code": 20000,
    "message": "成功",
    "data": {
    
    
        "total": 17,
        "rows": [
            {
    
    
                "id": "1",
                "name": "刘德华",
                "intro": "毕业于师范大学数学系,热爱教育事业,执教数学思维6年有余"
            }
        ]
    }
}

我们可以根据这组数据来统一返回数据格式:

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

5.2创建统一结果返回类

5.2.1在common模块下创建子模块common_utils

前面整合swagger时我们说过,因为我们在很多模块都要用到swagger,所以我们将swagger放到了公共模块common中。同样的,"统一返回结果"所有模块都要用,所以,我们把统一返回结果也放到公共模块中

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

此时不需要引入依赖,因为common_utils模块需要的依赖我们在common模块已经引入过了

5.2.2创建接口定义数据返回状态码

我们这里约定状态码为20000是成功,状态码为20001是失败,我们现在对状态码进行抽取。我知道的抽取有三种方式:创建一个接口里面写固定值、写一个常量类、枚举。这里我们用第一种方式来定义状态码:

在common_utils模块下创建包com.atguigu.commonutils,在该包下创建接口 ResultCode,然后在该接口中定义两个状态码

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

在这里插入图片描述

5.2.3定义返回数据格式

在commonutils包下创建类R并对该类进行编写:

@Data
public class R {
    
    
    @ApiModelProperty(value = "是否成功")
    private Boolean success;
    @ApiModelProperty(value = "返回码")
    private Integer code;
    @ApiModelProperty(value = "返回消息")
    private String message;
    @ApiModelProperty(value = "返回数据")
    private Map<String, Object> data = new HashMap<String, Object>();

    //构造方法私有
    private R(){
    
    }

    //成功静态方法
    public static R ok(){
    
    
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }

    //失败静态方法
    public static R error(){
    
    
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    public R success(Boolean success){
    
    
        this.setSuccess(success);
        return this;
    }
    public R message(String message){
    
    
        this.setMessage(message);
        return this;
    }
    public R code(Integer code){
    
    
        this.setCode(code);
        return this;
    }
    public R data(String key, Object value){
    
    
        this.data.put(key, value);
        return this;
    }
    public R data(Map<String, Object> map){
    
    
        this.setData(map);
        return this;
    }
}

在这里插入图片描述

  • 在类中定义四个属性:Boolean类型的success、Integer类型的code、String类型的message、Map<String, Object>类型的data

    • 其中:注意是private Map<String, Object> data = new HashMap<String, Object>();而不是private Map<String, Object> data;(这是因为Map是一个接口)
  • 类R上的注解@Data是Lombok插件的,用来生成getter、setter方法

  • ok()和error()是对应成功/失败的方法,是静态方法且返回值是R实例对象

  • private R(){}:ok()方法和error()方法都用static修饰,这样的话,使用private R(){}将这个类的构造方法私有,于是别人就不能创建类的实例对象,只能使用类名调用ok()方法和error()方法,当然,这两个方法都返回R实例对象,所以以后就可以调用R类的其他方法了

  • ok()和error方法内部的R r = new R():别人不能创建R实例对象,但是我们在自己类中是可以创建R实例对象的

  • ok和error方法内部有setSuccess、setCode、setMessage,并没有setData,data属性的赋值交给后面的data(String key, Object value)、data(Map<String, Object> map)

  • success、message、code、data(String key, Object value)、data(Map<String, Object> map)方法内部调用对应属性的set方法(data(String key, Object value)是个例外,它是调用Map集合的put方法进行赋值),给这些属性设置值(响应是否成功、状态码、返回消息、返回数据),并将赋值后的实例对象返回

    • 我不知道这些方法有啥意义,我直接用实例对象调用对应属性的set方法不是一样可以给属性赋值吗,无非就是直接用set方法赋值后set方法内部不会将赋值后的实例对象返回,可是方法内部将实例对象返回好像没有一点意义:

      现在我知道了这些方法内部将实例对象返回是有意义的:为了实现链式编程,比如:R.ok().code().message

5.3统一返回结果使用

1.在service模块的pom.xml中添加common_utils依赖(添加依赖后记得刷新maven)

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>common_utils</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

在这里插入图片描述

2.将service_edu模块的controller层中方法的返回结果都修改为R类(注意①导入R类一定要导入我们刚刚自己写的R类,千万别导成baomidou的了②以后controller层的方法返回值类型都是R类型)

在这里插入图片描述

在这里插入图片描述

我遇见的问题:

我在导包时:将鼠标放到R上然后按Alt+Enter,此时一切都是正常的,但是当我接下来点击"R(com.atguigu.commonutils)"时死活没有反应,根本就导不进来这个包

解决办法:清除idea的缓存

在这里插入图片描述

在这里插入图片描述

3.修改方法findAllTeacher和removeTeacher中的部分内部代码:修改后的方法findAllTeacher和removeTeacher的代码如下:

//1.查询讲师表所有数据
//rest风格
@ApiOperation(value = "所有讲师列表")
@GetMapping("findAll")
public R findAllTeacher() {
    
    
    List<EduTeacher> list = teacherService.list(null);
    return R.ok().data("items", list);
}
//2.逻辑删除讲师的方法
@ApiOperation(value = "逻辑删除讲师")
@DeleteMapping("{id}")
public R removeTeacher(
    @ApiParam(name = "id", value = "讲师ID", required = true)
    @PathVariable String id) {
    
    
    boolean flag = teacherService.removeById(id);
    if (flag) {
    
    
        return R.ok();
    } else {
    
    
        return R.error();
    }
}

在这里插入图片描述

4.启动项目,访问http://localhost:8001/swagger-ui.html重新进行测试

测试之前先在数据库中将id为1的数据的is_deleted字段由1改为0

findAllTeacher方法:

在这里插入图片描述

removeTeacher方法:

在这里插入图片描述

6.分页查询

1.在service_edu模块的配置类EduConfig中配置分页插件

/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
    
    
    return new PaginationInterceptor();
}

在这里插入图片描述

2.在控制层EduTeacherController类中编写分页查询的方法

//3.分页查询讲师的方法
@ApiOperation(value = "分页讲师列表")
@GetMapping("pageTeacher/{current}/{limit}")//current表示当前页,limit表示每页记录数
public R pageListTeacher(
    @ApiParam(name = "current", value = "当前页码", required = true)
    @PathVariable long current,

    @ApiParam(name = "limit", value = "每页记录数", required = true)
    @PathVariable long limit) {
    
    

    //创建page对象
    Page<EduTeacher> pageTeacher = new Page<>(current, limit);

    //调用方法实现分页
    teacherService.page(pageTeacher, null);

    long total = pageTeacher.getTotal();//总记录数
    List<EduTeacher> records = pageTeacher.getRecords();//该页数据的list集合

    return R.ok().data("total", total).data("rows", records);
}

在这里插入图片描述

  • 方法page的第二个参数是查询条件,这里我们暂且不设条件,所以第二参数为null
  • 调用mp的page方法时底层会帮我们做一个封装:底层会将分页的所有数据封装到Page类型的对象pageTeacher中
  • 可以将return R.ok().data(“total”, total).data(“rows”, records);改为:
Map map = new HashMap();
map.put("total", total);
map.put("rows", records);
return R.ok().data(map);

3.启动项目,访问http://localhost:8001/swagger-ui.html进行测试

在这里插入图片描述

7.多条件组合查询带分页

7.1分析

先大概绘制一下前端查询页面:

在这里插入图片描述

根据讲师名称name、讲师头衔level、讲师入驻时间gmt_create(时间段)查询

实现步骤:

第一步:把条件值封装到查询对象里面,把对象传递到接口里面

第二步:根据条件值进行判断,拼接条件

7.2创建查询对象

先在entity包下创建vo包,然后在vo包下创建类TeacherQuery用来封装查询条件,并在该类中编写代码:

@Data
public class TeacherQuery {
    
    

    @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;
}

在这里插入图片描述

注意:属性begin和属性end使用的是String类型,这样前端传过来的数据就无需进行类型转换

7.3控制层编写方法

在控制层EduTeacherController类中编写条件查询带分页的方法

//4.条件查询带分页的方法
@ApiOperation(value = "条件分页讲师列表")
@GetMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(
    @ApiParam(name = "current", value = "当前页码", required = true)
    @PathVariable long current,

    @ApiParam(name = "limit", value = "每页记录数", required = true)
    @PathVariable long limit,

    TeacherQuery teacherQuery) {
    
    

    //1.创建page对象
    Page<EduTeacher> pageTeacher = new Page<>(current, limit);

    //2.构建条件
    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 (!StringUtils.isEmpty(level)) {
    
    
        //构建条件
        wrapper.eq("level", level);
    }
    if (!StringUtils.isEmpty(begin)) {
    
    
        //构建条件
        wrapper.ge("gmt_create", begin);
    }
    if (!StringUtils.isEmpty(end)) {
    
    
        //构建条件
        wrapper.le("gmt_create", end);
    }

    //3.调用page方法实现条件查询分页
    teacherService.page(pageTeacher, wrapper);

    long total = pageTeacher.getTotal();//总记录数
    List<EduTeacher> records = pageTeacher.getRecords();//该页数据的list集合
    return R.ok().data("total", total).data("rows", records);
}

在这里插入图片描述

  • StringUtils.isEmpty()是org.springframework.util包中的,用来判断字符串是否为空(为空的标准是str == null或str.length() == 0,空格不算做是空)
  • 在wrapper.ge(“gmt_create”, begin);中,第一个参数应该是数据库中字段的名称而不是实体类的属性名称,所以第一个参数是gmt_create而不是gmtCreate
    • 我不明白:begin是String类型,怎么和数据库中gmt_create字段比较大小呢?

7.4测试

启动项目,访问http://localhost:8001/swagger-ui.html进行测试

在这里插入图片描述

在这里插入图片描述

7.5改进

我们已经实现了多条件组合查询带分页,但在实际开发中很多人都不喜欢传TeacherQuery teacherQuery这样的参数,人家都喜欢在TeacherQuery teacherQuery前面加一个@RequestBody注解,这个注解的作用:如果我们使用json传递数据,就会把json数据封装到对应对象里面,说到这里了我们回顾一下在"3.1准备工作"的第2步中说过的@ResponseBody注解:①如果此时执行的控制器中的这个方法返回值是字符串(比如"success"字符串),那么直接将字符串写到客户端(如果没有这个注解就会跳转到"success.html"页面);②如果是一个java对象,会将对象转化为json串,然后写到客户端

==注意:==如果使用@RequestBody注解:

①因为条件值可以都没有,所以需要给@RequestBody加属性required = false

②get请求是取不到值的,需要将get提交方式改为post提交方式

在这里插入图片描述

启动服务,访问http://localhost:8001/swagger-ui.html进行测试

在这里插入图片描述

在这里插入图片描述

8.添加讲师

8.1自动填充

1.给实体类EduTeacher的gmtCreate属性和gmtModified属性添加@TableField注解

在这里插入图片描述

2.下面我们要创建一个类实现MetaObjectHandler接口并实现该接口中的方法,从而实现自动填充,但是因为我们以后所有的模块都会用到自动填充来填充创建时间和修改时间,所以这里我们也将这个类放到公共模块common中:

①在子子模块service_base的servicebase包下创建包handler,在该包下创建类MyMetaObjectHandler并使其实现MetaObjectHandler接口

在这里插入图片描述

②在类MyMetaObjectHandler中实现接口MetaObjectHandler的方法。完整的MyMetaObjectHandler类的代码如下(别忘了给该类添加注解@Component将其交给Spring管理):

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    

    //使用mp实现添加操作时这个方法会执行
    @Override
    public void insertFill(MetaObject metaObject) {
    
    
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    //使用mp实现修改操作时这个方法会执行
    @Override
    public void updateFill(MetaObject metaObject) {
    
    
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}

在这里插入图片描述

注意:setFieldValByName方法的第一个参数是实体类的属性名称而不是数据库中的字段名称

3.需要给service_edu模块的启动类EduApplication添加注解@ComponentScan(basePackages = {“com.atguigu”}),这样的话才可以扫描到我们刚刚在service_base模块创建并编写的类MyMetaObjectHandler并将其交给Spring管理(这一步我们在"4.2.6添加注解@ComponentScan"的第2步的②中已经做过了)

在这里插入图片描述

8.2添加讲师接口的方法

在控制层的类EduTeacherController中编写添加讲师接口的方法

//4.添加讲师接口的方法
@ApiOperation(value = "新增讲师")
@PostMapping("addTeacher")
public R addTeacher(
    @ApiParam(name = "eduTeacher", value = "讲师对象", required = true)
    @RequestBody EduTeacher eduTeacher) {
    
    
    boolean save = teacherService.save(eduTeacher);
    if (save) {
    
    
        return R.ok();
    } else {
    
    
        return R.error();
    }
}

在这里插入图片描述

因为这里值必须要有,所以不能给注解@RequestBody添加属性required = false,并且我们还给注解@ApiParam添加了属性required = true

8.3测试

启动项目进行测试

在这里插入图片描述

在这里插入图片描述

注意:人家举例告诉我们可以给eduTeacher传什么参数,这些参数中:gmtCreate和gmtModified自动的填充的,不需要传;id是自动生成的,也不需要传

去控制台看一下底层的sql语句:

在这里插入图片描述

我不明白为啥给字段id_deleted赋值false,这个字段在数据库中明明是tinyint类型的呀,为什么实体类EduTeacher中的属性isDeleted是Boolean类型而不是Integer类型呢?

9.修改讲师

9.1分析

想要修改讲师数据,需要先根据id查询到这条数据,然后再根据刚刚查询到的含有id的讲师对象修改讲师。

复习一下为什么要先根据id查询到这条数据:在"demo02-MybatisPlus"的"6.4.4测试"的第3步我们提到过:mp为我们提供的修改数据的方法updateById参数是实例对象

9.2根据id查询

在控制层编写根据讲师id进行查询的方法

//5.根据讲师id进行查询
@ApiOperation(value = "根据ID查询讲师")
@GetMapping("getTeacher/{id}")
public R getTeacher(
    @ApiParam(name = "id", value = "讲师ID", required = true)
    @PathVariable String id){
    
    
    EduTeacher eduTeacher = teacherService.getById(id);
    return R.ok().data("teacher", eduTeacher);
}

在这里插入图片描述

9.3根据含有id的讲师对象修改讲师

在控制层编写根据含有id的讲师对象修改讲师的方法

//6.根据含有id的讲师对象修改讲师
@ApiOperation(value = "根据含有id的讲师对象修改讲师")
@PostMapping("updateTeacher")
public R updateTeacher(
    @ApiParam(name = "eduTeacher", value = "含有id的讲师对象", required = true)
    @RequestBody EduTeacher eduTeacher) {
    
    
    boolean flag = teacherService.updateById(eduTeacher);
    if (flag) {
    
    
        return R.ok();
    } else {
    
    
        return R.error();
    }
}

在这里插入图片描述

因为updateById方法的参数是实例对象,且该实例对象必须有id,所以我们在控制层编写的updateTeacher方法的参数必须是含有id的讲师对象

9.4测试

先从数据库中将id为1的数据的is_deleted字段由1改为0,然后启动项目进行测试

1.getTeacher方法测试结果:

在这里插入图片描述

去控制台看底层的sql语句:

SELECT id,name,intro,career,level,avatar,sort,is_deleted,gmt_create,gmt_modified FROM edu_teacher WHERE id=? AND is_deleted=0

在这里插入图片描述

2.updateTeacher方法测试结果:

在这里插入图片描述

在这里插入图片描述

注意:①id参数必须有②gmtCreate参数自动插入新数据后就不再需要了,所以这里不需要该参数③gmtModified参数会自动填充,这里不需要这个参数

去控制台看底层sql语句:

UPDATE edu_teacher SET name=?, intro=?, career=?, level=?, avatar=?, sort=?, gmt_modified=? WHERE id=? AND is_deleted=0

在这里插入图片描述

看了底层sql语句我们发现isDeleted这个参数用不上,所以也不需要这个参数

总结一下:id参数必须有;gmtCreate参数、gmtModified参数、isDeleted参数都不需要(我试了,加了gmtCreate参数和gmtModified参数会报错,这是因为我试的时候这两个参数都是形如2022-08-06T18:38:23.753Z,这种格式的json数据转换为对象的属性值时会报错:"JSON parse error: Cannot deserialize value of type `java.util.Date` from String “2022-08-06T18:38:23.753Z”: …所以呢,可以加isDeleted参数但这个参数不会派上用场;一定不能加gmtCreate参数和gmtModified参数,因为格式解析错误,至于什么格式的解析不会出错我也不知道,即使知道了也没必要加这两个参数,因为从底层sql语句我们能看出来了这两个参数也不会被派上用场)

10.统一异常处理

10.1什么是统一异常处理

1.制造异常

在控制层的findAllTeacher方法内部加一行代码:int a = 10/0;

在这里插入图片描述

启动服务测试findAllTeacher方法

在这里插入图片描述

2.什么是统一异常处理:让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息

10.2全局异常处理

10.2.1创建统一异常处理器

为了能通用,我们也是在service_base模块中创建统一异常处理类:先在servicebase包下创建包exceptionhandler,然后在该包下创建统一异常处理类GlobalExceptionHandler并在类中编写代码

/**
 * 统一异常处理类
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    
    

    //全局异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e) {
    
    
        e.printStackTrace();
        return R.error().message("执行了全局异常处理..");
    }
}

在这里插入图片描述

  • 注解@ExceptionHandler是用来指定出现什么异常时执行这个方法。@ExceptionHandler(Exception.class)表示所有异常都会执行这个方法
  • 在service_edu模块的控制层的类EduTeacherController中,因为该类上面有注解@RestController(以前说过,点进去看这个注解发现其内部有一个注解@ResponseBody),所以会将java对象转为json数据后返回。所以要给类GlobalExceptionHandler的error方法上加注解@ResponseBody以便能够将java对象转为json数据后返回

10.2.2引入依赖

1.刚刚在异常处理类中编写代码时使用到了我们在common_utils模块创建的实体类R,所以我们需要在service_base模块中引入common_utils模块的依赖(还是那句话,引入后别忘了刷新maven)

<dependencies>
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>common_utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

在这里插入图片描述

如果引入依赖后导包时:将鼠标放到R上然后按Alt+Enter,此时一切都是正常的,但是当我接下来点击"R(com.atguigu.commonutils)"时死活没有反应,根本就导不进来这个包。解决办法在"5.3统一返回结果使用"的第2步

2.此时会出现一种情况:我们以前就在service模块引入了service_base依赖和common_utils依赖,而现在我们又在service_base模块引入了common_utils依赖,如下图所示:

在这里插入图片描述

在这里插入图片描述

这样会导致service模块引入两次common_utils依赖:service模块引入一次;service模块引入的service_base依赖又引入一次(这种现象叫依赖传递)。所以我们需要将以前在service模块中引入的common_utils依赖删除掉(删掉后记得刷新maven):

在这里插入图片描述

3.何为依赖传递:比如说我在service_base中引入了common_utils依赖,那么我只需要在service_edu中引入service_base依赖就可以了,此时service_edu中既有service_base依赖又有common_utils依赖

10.2.3测试

启动项目再次测试findAllTeacher方法

在这里插入图片描述

10.3特定异常处理

1.在统一异常处理类GlobalExceptionHandler中编写如下代码:

//特定异常
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e) {
    
    
    e.printStackTrace();
    return R.error().message("执行了ArithmeticException特定异常处理..");
}

在这里插入图片描述

注解@ExceptionHandler(ArithmeticException.class)表示当遇到被除数为0的异常时会执行这个方法

2.启动服务,进行测试

在这里插入图片描述

可能有人会问:为什么这里执行特定异常处理方法而不全局异常处理方法?因为:**有特定异常处理方法先执行特定异常处理方法,没有特定异常处理方法才会去执行全局异常处理方法。**并且这里有异常ArithmeticException的特定异常处理方法,所以会执行这个特定异常处理方法

10.4自定义异常处理

1.因为自定义异常类别的模块也会用到的,所以将这个类放到service_base模块中:先在service_base模块的包exceptionhandler下创建自定义异常类GuliException使其继承RuntimeException,并在该类中编写代码:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException{
    
    

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

    private String msg;//异常信息
}

在这里插入图片描述

  • 注解@AllArgsConstructor用来生成有参构造方法(全参)
  • 注解@NoArgsConstructor用来生成无参构造方法

2.在service_base模块的异常处理类GlobalExceptionHandler中添加自定义异常处理方法:

//自定义异常
@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e) {
    
    
    e.printStackTrace();
    return R.error().code(e.getCode()).message(e.getMsg());
}

在这里插入图片描述

注意:e.getCode()和e.getMsg()都是上一步创建的自定义异常类GuliException中的方法,前者是为了获取自定义异常对象的状态码,后者是为了获取自定义异常对象的异常信息

3.因为这个异常是我们自己定义的,系统不会抛出这个异常,需要我们手动抛出这个异常:

还记不记得我们在"10.1什么是统一异常处理"中给findAllTeacher方法内部添加的代码:int a = 10/0?现在我们将这行代码替换为下面的代码:

try {
    
    
    int a = 10/0;
} catch (Exception e) {
    
    
    //抛出自定义异常
    throw new GuliException(20001, "执行了自定义异常处理..");
}

在这里插入图片描述

4.测试

启动项目进行测试

在这里插入图片描述

11.统一日志处理

11.1配置日志级别

日志记录器(Logger)的行为是分等级的,如下所示:

分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

默认情况下spring boot从控制台打印出来的日志级别只有INFO及以上级别

我们可以在service_edu模块的配置文件application.properties中配置日志级别:

# 设置日志级别
logging.level.root=WARN

在这里插入图片描述

11.2Logback日志

上面那种方式只能将日志打印在控制台上,如果想要既在控制台输出又在文件中输出需要用到日志工具(如log4j、Logback)

11.2.1配置Logback日志

1.注释/删掉application.properties中的日志配置:

在这里插入图片描述

2.在resources下创建logback-spring.xml并编写配置:

<?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:/study/log/guli_parent/edu" />
    
    <!-- 彩色日志 -->
    <!-- 配置格式变量: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.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>

在这里插入图片描述

第10行的<property name=“log.path” value=“D:/study/log/guli_parent/edu” />用来规定日志输出的位置

3.启动项目就可以看到d盘生成的日志文件夹:

在这里插入图片描述

11.2.2将异常信息输出到文件

如果程序运行出现异常,我们想把异常信息输出到文件中。具体步骤:

1.给统一异常处理类GlobalExceptionHandler添加注解@Slf4j

在这里插入图片描述

2.给自定义异常处理方法中添加一行代码:

log.error(e.getMessage());

在这里插入图片描述

这样的话,这个异常的信息就会输出到D:\study\log\guli_parent\edu\log_error.log文件中

启动项目,仍旧测试控制器中的findAllTeacher方法,然后我们去文件log_error.log中查看异常输出信息

在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/maxiangyu_/article/details/127018587