2-讲师管理模块后端接口


准备工作:

一、数据库设计

创建edu_teacher表,包含字段:id, name, intro, career, level, avatar, sort, is_deleted, gmt_create, gmt_modified。主键约束:id。sql语句详见:sql传送门

二、搭建项目工程:

  1. 父工程

    父工程(guli_parent)创建可以使用Spring Initilizer,子工程创建最好使用Maven。

    父工程的pom.xml:

    • SpringBoot版本:2.2.1.RELEASE
    • 添加pom
    • 删除dependencies标签中的所有内容
    • 添加 确定依赖的版本,对这个项目进行控版本控制
    • 配置 锁定依赖的版本(父工程的pom.xml只是对版本的依赖做了定义,并没有用。真正的使用是在子模块中)
    • 删除父工程的src目录。

    父工程的pom.xml全部内容见:pom.xml

  2. 子模块

    在父工程guli-parent下面创建子模块service(删除src目录),pom.xml见:pom.xml

  3. 子子模块

    在service模块下创建子模块service-edu(讲师管理模块)

    • 编写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=630586
      # mybatis日志
      mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    • 生成相关代码

      1-复习Mybatis-Plus专门讲了Mybatis-Plus(MP)的使用,就是因为使用MP可以大大简化开发效率,重复、固定的代码不需要再手写了。

      根据edu_teacher表,使用MP代码生成器生成entity层、controller层、service层、mapper层的骨架代码,代码生成器不要求会写,仅要求会改。

      官网:苞米豆

      代码生成器的作用:和MP的逆向工程差不多,根据表生成代码。

      代码生成器的依赖已在service模块的pom.xml中引过了,如下

      <!-- velocity模板引擎, Mybatis Plus代码生成器需要 -->
      <dependency>
          <groupId>org.apache.velocity</groupId>
          <artifactId>velocity-engine-core</artifactId>
      </dependency>
    • 在service_edu模块的测试包下建立CodeGenerator.java

      // 该代码只要求会改,不要求会写
      public class CodeGenerator {
          @Test
          public void main1() {
      
              // 1、创建代码生成器
              AutoGenerator mpg = new AutoGenerator();
      
              // 2、全局配置
              GlobalConfig gc = new GlobalConfig();
              String projectPath = System.getProperty("user.dir");
              gc.setOutputDir("C:\\Users\\87672\\Desktop\\桌面上的文件夹\\新建文件夹\\Java学习笔记\\guli_parent\\service\\service_edu" + "/src/main/java");    // 输出目录
      
              gc.setAuthor("atguigu");
              gc.setOpen(false); // 生成后是否打开资源管理器
              gc.setFileOverride(false); // 重新生成时文件是否覆盖
              /*
               * mp生成service层代码,默认接口名称第一个字母有 I
               * UserService
               * */
              gc.setServiceName("%sService");     // 去掉Service接口的首字母I(没有用)
              gc.setIdType(IdType.ID_WORKER_STR); // 主键策略,因为id是char类型,所以用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?serverTimezone=GMT%2B8");
              dsc.setDriverName("com.mysql.cj.jdbc.Driver");
              dsc.setUsername("root");
              dsc.setPassword("630586");
              dsc.setDbType(DbType.MYSQL);
              mpg.setDataSource(dsc);
      
              // 4、包配置
              PackageConfig pc = new PackageConfig();
      
              // 包名:com.atguigu.eduservice
              pc.setParent("com.atguigu");
              pc.setModuleName("eduservice"); //模块名
              // 包名:com.atguigu.eduservice.controller
              pc.setController("controller");
              // 包名:com.atguigu.eduservice.entity
              pc.setEntity("entity");
              // 包名:com.atguigu.eduservice.service
              pc.setService("service");
              // 包名:com.atguigu.eduservice.mapper
              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();
          }
      }

      上述代码执行完成后会在java包下生成:entity层、controller层、service层、mapper层

三、集成测试工具Swagger

浏览器仅支持post和get提交,不支持delete提交,因此需要借助工具进行测试(swagger、postman等)

本项目全部、统一采用swagger进行测试。

下面先引入swagger:

在父工程(guli_parent)下创建common模块,该模块中包含公共的方法、依赖、工具类等。

  1. 在common模块的pom.xml中引入依赖:

    <!--swagger-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
    </dependency>
    
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
    </dependency>
  2. 在common模块下创建子模块service_base,项目结构如下图所示:

  3. 在service_base模块中创建SwaggerConfig类

    package com.atguigu.servicebase;
    @Configuration  // 表明这是一个配置类
    @EnableSwagger2 // 表明这是一个swagger注解
    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("Helen", "http://atguigu.com", "55317332@qq.com"))
                    .build();
        }
    }
  4. 在service的pom.xml中引入service_base(在其他模块使用本模块的方法,就得在其他模块中引入本模块的坐标)

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

四、edu-service模块准备工作

  1. 创建主启动类

    刚刚说了,父工程可以直接用SpringBoot创建,因为它只负责依赖管理,并不写实际代码,所以怎么快捷怎么创建;而子工程采用maven创建,因此并不自带XxxApplication

    @SpringBootApplication
    @ComponentScan(basePackages = {"com.atguigu"})  // 设置包扫描规则
    @EnableDiscoveryClient  // Nacos注册,该注解目前用不到
    @EnableFeignClients     // 服务调用,该注解目前用不到
    public class EduApplication {
        public static void main(String[] args) {
            SpringApplication.run(EduApplication.class,args);
        }
    }
  2. 创建配置类,配置mapper扫描范围

    @Configuration
    @MapperScan("com.atguigu.eduservice.mapper")
    public class EduConfig {
    }
  3. 统一返回json时间格式

    默认情况下json时间格式带有时区,并且是世界标准时间,和我们的时间差了八个小时。

    在application.properties中设置:

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

五、统一返回数据格式

之前在controller中写返回值时,要不写的是String,要不就是自定义的其他类,这样做会使得开发缺乏标准(既没有统一的返回值类型,也没有统一的状态码),所以在此先统一返回的数据格式(R),今后项目中所有controller层的返回值均采用此格式。定义:

R.ok():成功(状态码:20000R.error():失败(状态码:20001

例如:

json数据格式有两种:对象和数组。两种格式经常混合使用

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

在common模块中定义统一返回结果模块common_utils,创建R和ResultCode,定义返回数据的状态码和返回值类型,

public interface ResultCode {
    Integer SUCCESS = 20000;    // 操作成功
    Integer ERROR = 20001;      // 操作失败
}
/**
 * @Deception 统一返回结果
 * 写法固定
 */
@Data
public class R {
    // @ApiModelProperty()用于方法、字段。value属性对字段说明
    @ApiModelProperty(value = "是否成功")
    private Boolean success;

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

    @ApiModelProperty(value = "返回消息")
    private String message;

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

    // 把构造方法私有化
    // 私有空参构造去,让别人不能new R对象,而只能使用提供好的ok()和error()方法
    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;
    }

    // this:当前类的对象
    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;
    }
}

使用统一结果

  • 在service中引入common_utils的坐标

  • 把controller层的返回值类型都改成R

    public R findAllTeacher(){
        // 调用service里面的方法,实现查询全部的操作
        List<EduTeacher> list = teacherService.list(null);
        return R.ok().data("items",list);
    }
    
    public R removeTeacher(@ApiParam(name = "id", value = "讲师id", required = true) @PathVariable String id){
        boolean flag = teacherService.removeById(id);
        return flag ? R.ok() : R.error();
    }

六、异常处理

  1. 全局异常处理

    在service_base模块下新建exceptionhandler包,在里面写统一处理异常的类,在项目报错时,执行我们自己定义的异常。

    • 引入依赖

      <dependencies>
          <dependency>
              <groupId>com.atguigu</groupId>
              <artifactId>common_utils</artifactId>
              <version>0.0.1-SNAPSHOT</version>
          </dependency>
      </dependencies>
    • 统一异常处理

      @ControllerAdvice
      public class GlobalExceptionHandler {
          // 指定出现什么异常执行这个方法
          @ExceptionHandler(Exception.class)
          @ResponseBody   // 为了能够返回数据,相当于把该类放在controller中
          public R error(Exception e){
              e.printStackTrace();
              return R.error().message("执行了全局异常处理");
          }
      }
  2. 特定异常处理

    把Exception换成具体的XxxException即可

    • 创建自定义异常类,继承RuntimeException,在其中写异常属性

      // 自定义异常处理
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class GuliException extends RuntimeException{
          private Integer code;   // 异常状态码
          private String msg;     // 异常信息
      }
    • 添加规则

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

      // 手动模拟异常
      try {
          int i = 10 / 0;
      }catch (Exception e){
          throw new GuliException(20001,"执行了自定义异常处理...");
      }

七、统一日志处理

  1. 日志级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

    # 配置日志级别
    logging.level.root=WARN
  2. Logback日志:把日志不仅输出到控制台,也可以输出到文件中,使用日志工具

    # 1.把如下的日志配置全部去掉
    # mybatis日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    # 配置日志级别
    logging.level.root=WARN
    
    # 2.在resource中创建logback-spring.xml
  3. 使用

    // 1.在GlobalExceptionHandler上加注解@Slf4j
    // 2.在自定义异常类中,加:log.error(e.getMsg());
    // 3.手动模拟异常
    try {
        int i = 10 / 0;
    }catch (Exception e){
        throw new GuliException(20001,"执行了自定义异常处理...");
    }
    
    // 4.访问localhost:8001/swagger-ui.html#的 pageListTeacher
    // 5.可以看到 log_error.error中有错误日志

项目代码

一、查询所有讲师

  • controller层

    // controller调 service,service调 mapper
    @RestController     // 返回json数据
    @Api(description = "讲师管理")
    @RequestMapping("/eduservice/edu-teacher")
    @SuppressWarnings("all")
    public class EduTeacherController {
        // 把service注入
        @Autowired
        private EduTeacherService teacherService;
        @ApiOperation(value = "所有讲师列表")     // 名字提示
        // 1.查询讲师表中的所有数据
        // restful风格
        @GetMapping("/findAll")
        public List<EduTeacher> findAllTeacher(){
            // 调用service里面的方法,实现查询全部的操作
            List<EduTeacher> list = teacherService.list(null);
            return list;
        }
    }
  • Swagger测试

    http://localhost:8001/eduservice/teacher/findAll

二、讲师逻辑删除功能

  • 在实体类上添加注解@TableLogic、配置文件、配置类

    @TableLogic
    @ApiModelProperty(value = "逻辑删除 1(true)已删除,0(false)未删除")
    private Boolean isDeleted;
    # 逻辑删除配置
    mybatis-plus.global-config.db-config.logic-delete-value=1
    mybatis-plus.global-config.db-config.logic-not-delete-value=0
    @Configuration
    @MapperScan("com.atguigu.eduservice.mapper")
    public class EduConfig {
        // 配置逻辑删除
        @Bean
        public ISqlInjector sqlInjector() {
            return new LogicSqlInjector();
        }
    }
  • 逻辑删除讲师的方法

    // 通过路径传递id值
    @RestController     // 返回json数据
    @Api(description = "讲师管理")
    @RequestMapping("/eduservice/edu-teacher")
    @SuppressWarnings("all")
    public class EduTeacherController {
        // 把service注入
        @Autowired
        private EduTeacherService teacherService;
    
        @ApiOperation(value = "逻辑删除讲师")     // 名字提示
        // 2.逻辑删除讲师的方法
        // 通过路径传递id值
        @DeleteMapping("{id}")
        // @ApiParam:名字提示
        public boolean removeTeacher(@ApiParam(name = "id", value = "讲师id", required = true) @PathVariable String id){
            boolean flag = teacherService.removeById(id);
            return flag;
        }
    }
  • Swagger测试

    http://localhost:8001/swagger-ui.html

三、分页和多条件查询

  1. 分页

    • 配置分页插件

      @Configuration
      @MapperScan("com.atguigu.eduservice.mapper")
      public class EduConfig {
          // 配置讲师分页插件
          @Bean
          public PaginationInterceptor paginationInterceptor(){
              return new PaginationInterceptor();
          }
      }
    • 编写讲师分页查询接口的方法

      /**
       * 讲师分页查询的方法
       * 通过路径传递
       * @param current 当前页
       * @param limit 每页记录数
       * @return
       */
      @GetMapping("/pageTeacher/{current}/{limit}")
      public R pageListTeacher(@PathVariable long current, @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);
      }
    • Swagger测试

  2. 多条件查询带分页功能

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

    • 把条件值封装到对象中,把对象传递到接口里面(vo)

      @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")
          // 注意:这里使用的是String类型,前端传过来的数据无需进行类型转换
          private String begin;
          @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
          private String end;
      }

      Q:为啥还专门造个实体类(TeacherQuery)来封装数据?原来的实体类(EduTeacher)还不够用吗?

      A:EduTeacher中包含数据库表中的全部字段,如果拿EduTeacher来封装数据,则除了name、level、begin、end四个需要的字段外,还多出了冗余字段,通过前端整合页面后的效果也能看出,只需要封装这四个字段。

    • 根据条件值进行判断,拼接条件

      /**
       * 多条件组合查询带分页功能
       * @param current 当前页
       * @param limit 每页记录数
       * @param teacherQuery 把条件值封装到对象中,把对象传递到接口里面
       * @return
       */
      @GetMapping("/pageTeacherCondition/{current}/{limit}")
      public R pageTeacherCondition(@PathVariable long current, @PathVariable long limit,
                                        TeacherQuery teacherQuery){
          // 创建page对象
          Page<EduTeacher> page = new Page<>(current,limit);
          // 构造条件
          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);  //参数1:字段名;参数2:具体值
          }
          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);
          }
      
          // 调用方法实现条件分页查询
          teacherService.page(page,wrapper);
          long total = page.getTotal();
          List<EduTeacher> records = page.getRecords();
          return R.ok().data("total",total).data("rows",records);
      }
    • Swagger测试:

四、添加讲师

  1. 在实体类的时间上加注解

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;
    
    @TableField(fill = FieldFill.DEFAULT)
    @ApiModelProperty(value = "更新时间")
    private Date gmtModified;
  2. 在service_base中创建handler包,在里面配置时间自动填充

    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
        @Override
        public void insertFill(MetaObject metaObject) {
            this.setFieldValByName("gmtCreate",new Date(),metaObject);
            this.setFieldValByName("gmtModified",new Date(),metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            this.setFieldValByName("gmtModified",new Date(),metaObject);
        }
    }
  3. 编写controller

    // 添加讲师的方法
    @PostMapping("/addTeacher")
    public R addTeacher(@RequestBody EduTeacher eduTeacher){
        boolean save = teacherService.save(eduTeacher);
        return save ? R.ok() : R.error();
    }
  4. Swagger测试

    {
      "avatar": "string",
      "career": "string",
      "intro": "string",
      "isDeleted": false,
      "level": 0,
      "name": "string1010",
      "sort": 0
    }

五、修改讲师

  1. 根据讲师id进行查询(回显)

    @GetMapping("/getTeacher/{id}")
    public R getTeacher(@PathVariable String id){
        EduTeacher eduTeacher = teacherService.getById(id);
        return R.ok().data("teacher",eduTeacher);
    }
  2. 讲师修改(先查再改)

    @PostMapping("/updateTeacher")
    public R updateTeacher(@RequestBody EduTeacher eduTeacher){
        boolen flg = teacherService.updateById(eduTeacher);
        return flg ? R.ok() : R.error();
    }
  3. Swagegr测试

    {
      "avatar": "string",
      "career": "string",
      "id": "1189390295668469762",
      "intro": "string",
      "isDeleted": false,
      "level": 0,
      "name": "1010upup",
      "sort": 0
    }

文章作者: Prannt
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Prannt !
评论
  目录