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

目录

          • 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.前后端分离概念

谷粒学院day02——讲师管理模块的后端实现_第1张图片

2.数据库建表

新建数据库。

CREATE DATABASE gulischool;

建表

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='讲师';

报错

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

原因是语法错误,改为

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.搭建项目工程

项目的工程结构如图。

谷粒学院day02——讲师管理模块的后端实现_第2张图片

具体建立过程如下。

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

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

谷粒学院day02——讲师管理模块的后端实现_第3张图片

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


        org.springframework.boot
        spring-boot-starter-parent
        2.2.1.RELEASE
         

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

 pom

删除pom文件中的dependencies。

 
        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
          
 

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

    
        1.8
        0.0.1-SNAPSHOT
        3.0.5
        2.0
        2.7.0
        2.8.3
        2.10.1
        3.17
        1.3.1
        2.6
        4.5.1
        0.7.0
        4.3.3
        3.1.0
        2.15.2
        1.4.11
        1.4.11
        1.2.28
        2.8.2
        20170516
        1.7
        1.1.0
        zx
        0.2.2.RELEASE
    

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

    
    
        
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                Hoxton.RELEASE
                pom
                import
            
            
                org.springframework.cloud
                spring-cloud-alibaba-dependencies
                ${cloud-alibaba.version}
                pom
                import
            
            
            
                com.baomidou
                mybatis-plus-boot-starter
                ${mybatis-plus.version}
            
            
            
                org.apache.velocity
                velocity-engine-core
                ${velocity.version}
            

            
            
                io.springfox
                springfox-swagger2
                ${swagger.version}
            
            
            
                io.springfox
                springfox-swagger-ui
                ${swagger.version}
            
            
            
                com.aliyun.oss
                aliyun-sdk-oss
                ${aliyun.oss.version}
            
            
            
                joda-time
                joda-time
                ${jodatime.version}
            
            
            
                org.apache.poi
                poi
                ${poi.version}
            
            
            
                org.apache.poi
                poi-ooxml
                ${poi.version}
            
            
            
                commons-fileupload
                commons-fileupload
                ${commons-fileupload.version}
            
            
            
                commons-io
                commons-io
                ${commons-io.version}
            
            
            
                org.apache.httpcomponents
                httpclient
                ${httpclient.version}
            
            
                com.google.code.gson
                gson
                ${gson.version}
            
            
            
                io.jsonwebtoken
                jjwt
                ${jwt.version}
            
            
            
                com.aliyun
                aliyun-java-sdk-core
                ${aliyun-java-sdk-core.version}
            
            
                com.aliyun.oss
                aliyun-sdk-oss
                ${aliyun-sdk-oss.version}
            
            
                com.aliyun
                aliyun-java-sdk-vod
                ${aliyun-java-sdk-vod.version}
            
            
                com.aliyun
                aliyun-java-vod-upload
                ${aliyun-java-vod-upload.version}
            
            
                com.aliyun
                aliyun-sdk-vod-upload
                ${aliyun-sdk-vod-upload.version}
            
            
                com.alibaba
                fastjson
                ${fastjson.version}
            
            
                org.json
                json
                ${json.version}
            
            
                commons-dbutils
                commons-dbutils
                ${commons-dbutils.version}
            
            
                com.alibaba.otter
                canal.client
                ${canal.client.version}
            
        
    

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

4.创建子模块service

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

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

pom

添加依赖。

    
        
            org.springframework.cloud
            spring-cloud-starter-netflix-ribbon
        
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        
        
        
            org.springframework.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
        
        
        
            mysql
            mysql-connector-java
        
        
        
            org.apache.velocity
            velocity-engine-core
        
        
        
            io.springfox
            springfox-swagger2
        
        
            io.springfox
            springfox-swagger-ui
        
        
        
            org.projectlombok
            lombok
        
        
        
            org.apache.poi
            poi
        
        
            org.apache.poi
            poi-ooxml
        
        
            commons-fileupload
            commons-fileupload
        
        
        
            org.apache.httpcomponents
            httpclient
        
        
        
            commons-io
            commons-io
        
        
        
            com.google.code.gson
            gson
        
        
            junit
            junit
            4.12
        
    

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

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

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

service_edu模块下src/main/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?serverTimezone=GMT%2B8
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 代码生成器。



    org.apache.velocity
    velocity-engine-core

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

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%2B8");
            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()方法则可以生成代码了。

谷粒学院day02——讲师管理模块的后端实现_第4张图片

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。

  // 注入service
  @Autowired
  private EduTeacherService eduTeacherService;

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

查询表中所有数据。

 // 查询表中所有表数据
    // Rest风格
    @GetMapping("findAll")
    public List findAllTeacher() {
        List list = eduTeacherService.list(null);
        return list;
    }

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

谷粒学院day02——讲师管理模块的后端实现_第5张图片

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

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

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

}

启动EduApplication。报错

java: java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @0x590)

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



    org.projectlombok
    lombok
    1.18.20
    provided

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

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数据)。显示数据如下。

[{"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中可以配置时区和时间格式。

#配置时间格式及时区
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

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

[{"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)配置一个逻辑删除插件。

 @Bean
 public ISqlInjector sqlInjector() {
     return new LogicSqlInjector();
 }

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

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

@DeleteMapping("/deleteTeacherById/{id}")
public boolean removeTeacher(@PathVariable String id) {
    eduTeacherService.removeById(id);
    return false;
}

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

谷粒学院day02——讲师管理模块的后端实现_第6张图片

8.swagger整合

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

使用swagger的作用是:

1.可以进行接口测试。

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

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

谷粒学院day02——讲师管理模块的后端实现_第7张图片

导入相关依赖。


    org.springframework.boot
    spring-boot-starter-web
    provided 



    com.baomidou
    mybatis-plus-boot-starter
    provided 



    org.projectlombok
    lombok
    provided 



    io.springfox
    springfox-swagger2
    provided 


    io.springfox
    springfox-swagger-ui
    provided 



    org.springframework.boot
    spring-boot-starter-data-redis



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

 pom

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

谷粒学院day02——讲师管理模块的后端实现_第8张图片

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

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",
                        "[email protected]"))
                .build();
    }
}

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

(1)引入依赖文件

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


       com.wangzhou
       service_base
       0.0.1-SNAPSHOT

(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".

谷粒学院day02——讲师管理模块的后端实现_第9张图片

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

谷粒学院day02——讲师管理模块的后端实现_第10张图片

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

谷粒学院day02——讲师管理模块的后端实现_第11张图片

测试结果出来了。

谷粒学院day02——讲师管理模块的后端实现_第12张图片

测试下逻辑删除功能。

谷粒学院day02——讲师管理模块的后端实现_第13张图片

查看数据库。成功了。

在这里插入图片描述

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

@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数据的格式没有固定格式,只要能够描述清楚数据的具体信息与状态,但一般包含状态码、返回消息、数据等,我们将本项目的返回数据格式统一如下。

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

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

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

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

谷粒学院day02——讲师管理模块的后端实现_第14张图片

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

package com.wangzhou.commonutils;

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

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

// lombok的注解,自动生成getter,setter等
@Data
public class R {
    // swagger的注解
    @ApiModelProperty("是否成功")
    private boolean success;

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

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

    @ApiModelProperty("返回数据")
    private Map data = new HashMap();

    //无参构造方法私有,其他类不可以创建该类的实例,只能使用其镜头方法
    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 data) {
        this.setData(data);
        return this;
    }
}

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

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


        com.wangzhou
        common_utils
        0.0.1-SNAPSHOT

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

  @ApiOperation("讲师列表")
    @GetMapping("findAll")
    public R findAllTeacher() {
        List 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请读者自测。

谷粒学院day02——讲师管理模块的后端实现_第15张图片

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

谷粒学院day02——讲师管理模块的后端实现_第16张图片

截取日志信息如下。

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中配置分页插件

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

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

    @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 teacherPage = new Page<>(page, limit);
        // mp会把结果封装到eduTeacherService中
        eduTeacherService.page(teacherPage, null);
        Long total = teacherPage.getTotal();
        List records = teacherPage.getRecords();
        Map map = new HashMap<>();
        map.put("total", total);
        map.put("records", records);
        return R.ok().data(map);
    }

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

11.多条件组合查询

实现如下图功能。

谷粒学院day02——讲师管理模块的后端实现_第17张图片

实现步骤如下。

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

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

@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)编写多条件查询功能

    @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 teacherPage = new Page<>();
        QueryWrapper 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 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。

谷粒学院day02——讲师管理模块的后端实现_第18张图片

返回的Response Body如下:

{
  "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),下面快速做下。

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

@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,这个类用于实现自动填充的规则。

@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实现添加操作的接口

@ApiOperation("添加教师")
@PostMapping("/addTeacher")
public R addTeacher(@RequestBody EduTeacher eduTeacher) {
    boolean succuss = eduTeacherService.save(eduTeacher);
    if(succuss) {
       return R.ok();
    } else {
       return R.error();
    }
}

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

{
  "avatar": "string",
  "career": "string",
  "intro": "string",
  "isDeleted": 0,
  "level": 0,
  "name": "test001",
  "sort": 0
}
13.教师查询与更新

(1)查找教师

    @ApiOperation("查找教师")
    @GetMapping("/findTeacher/{id}")
    public R findTeacher(@PathVariable String id) {
       EduTeacher eduTeacher = eduTeacherService.getById(id);
        return R.ok().data("item", eduTeacher);
    }

(2)更新教师

 	@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 全局异常处理

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

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

  @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信息如下。

谷粒学院day02——讲师管理模块的后端实现_第19张图片

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

@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中引入依赖再导包。


    com.wangzhou
    common_utils
    0.0.1-SNAPSHOT

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

谷粒学院day02——讲师管理模块的后端实现_第20张图片

测试结果如下。

谷粒学院day02——讲师管理模块的后端实现_第21张图片

14.2 特定异常处理

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

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

测试结果如下。

谷粒学院day02——讲师管理模块的后端实现_第22张图片

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

14.3 自定义异常处理

(1)创建自定义异常类

在exceptionhandler包下新建GuliException类。

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

(2)自定义异常处理

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

(3)抛出异常

@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中进行设置。

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

(2)配置logback日志

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

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

# mybatis日志
# mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 设置日志级别
# logging.level.root = INFO

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



    
    
    
    
    logback
    
    
    

    
    
    
    
    
    
    
    
    
    
        
        
        
            INFO
        
        
            ${CONSOLE_LOG_PATTERN}
            
            UTF-8
        
    
    
    
    
        
        ${log.path}/log_info.log
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                %logger{50} - %msg%n
            
            UTF-8
        
        
        
            
            ${log.path}/info/log-info-%d{yyyy-MM-
                dd}.%i.log
            
            
                100MB
            
            
            15
        
        
        
            INFO
            ACCEPT
            DENY
        
    

    
    
        
        ${log.path}/log_warn.log
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                %logger{50} - %msg%n
            
            UTF-8 
        
        
        
            ${log.path}/warn/log-warn-%d{yyyy-MM-
                dd}.%i.log
            
            
                100MB
            
            
            15
        
        
        
            warn
            ACCEPT
            DENY
        
    

    
    
        
        ${log.path}/log_error.log
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
                %logger{50} - %msg%n
            
            UTF-8 
        
        
        
            ${log.path}/error/log-error-%d{yyyy-MM-
                dd}.%i.log
            
            
                100MB
            
            
            15
        
        
        
            ERROR
            ACCEPT
            DENY
        
    

    
    

    
    
        
        
        
        
            
            
            
            
        
    
    
    
        
            
            
            
            
            
        
    

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

谷粒学院day02——讲师管理模块的后端实现_第23张图片

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

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

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

你可能感兴趣的:(谷粒学院,java,数据库)