Bootstrap

Day2-搭建后台讲师管理模块

一. 项目结构

项目的基本结构是有一个父项目(SpringBoot)用于管理依赖包的版本及放置共有的依赖,父项目下有各个子模块(Maven),子模块下又有各个子模块。

  1. 先创建父项目,新建一个Spring Boot项目,修改版本号为2.2.1,再添加 <packaging> 将打包类型改成pom。
    在这里插入图片描述
    配置 <packaging>pom</packaging> 的意思是使用maven分模块管理,都会有一个父级项目,pom文件一个重要的属性就是packaging(打包类型),一般来说所有的父级项目的packaging都为pom,packaging默认类型jar类型,如果不做配置,maven会将该项目打成jar包。
  • 出现Maven更新失败的错误:
    在项目使用maven 更新的时候,会遇到jar包无法下载到本地maven仓库的问题,idea 控制台将给出以下错误提示,如图所示:即下载目录中缺少程序运行的jar包文件。
    在这里插入图片描述
    解决方案:
    进入…\repository\org\apache\maven 所在位置,删除其中所有内容,然后在terminal中输入【mvn clean】和【mvn build】,再重新导入maven项目,则问题解决。
  1. 由于父项目主要用于管理依赖的版本,所以删除pom文件中原有的 <dependencies>,这些依赖之后将放入子模块中。
  2. 添加依赖的版本号:
<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>
  1. 版本管理:配置<dependencyManager>锁定依赖的版本
<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>
  1. 删除父项目中的 src 文件夹
  2. 创建service子模块:右键项目名称,new -> module…,之后选择Maven,填写子模块名称后完成创建。
    在这里插入图片描述
  3. 该子模块还是一个pom,同样删除 src 文件夹,加上<packaging>pom</packaging>,将父项目中的依赖复制过来,但是此时不需要填写版本号了,自动使用父项目中的版本号。(注意:若出现Maven右侧依赖模块出现一大堆红色线条,但是本地仓库是有下好的jar包,可以将pom中的依赖先删掉,保存后,再撤销回来,重新import即可)
  4. 在service模块下再新建一个讲师管理子模块用于真正的程序编写,这个模块无需引入依赖,将自动使用service中的依赖。

最后项目结构如下所示:
在这里插入图片描述

二. 讲师管理模块

1. 代码生成器

现在开始写讲师管理模块的接口,包括controller、service、mapper、entity等,但是这些不需要我们自己写,通过代码生成器可以自动实现。

生成后的代码,Mapper是extends BaseMapper,包含了基本的增删改查操作:
在这里插入图片描述
service 是 extends了ServiceImpl方法,也是自动包含了一些基本的增删改查操作:
在这里插入图片描述
所以我们只需要简单写下controller层即可。

2. 建立启动类

在controller和service包外,建立一个Application启动类,用于项目的启动:
在这里插入图片描述

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

3. 建立配置类

在controller同级目录下,建立一个config包,用于存放配置类,之后项目的一些配置都放在这个类里。
首先就是MapperScann注解,用于防止Mapper找不到对应的xml文件而报错:
在这里插入图片描述

4. 查询数据库返回中文乱码

查询数据库中的数据,发现查到的数据中文都变成了乱码!!!找了很久的解决方法,最终这样解决了:

  1. 查看IDEA的编码方式,改成UTF-8
    在这里插入图片描述
  2. 打开数据库,输入【show variables like ‘character_set%’;】,发现不是所有的编码方式都是utf8。
    在这里插入图片描述
    所以需要修改mysql的编码方式,找到【mysql.ini】文件,添加以下内容:
[client]
port=3306
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
port=3306
character-set-server=utf8
collation-server=utf8_general_ci
init_connect='SET collation_connection = utf8_unicode_ci' 
init_connect='SET NAMES utf8' 
collation-server=utf8_unicode_ci 
skip-character-set-client-handshake

完成后,重启msql,编码方式变为:
在这里插入图片描述

  1. 修改完编码方式后,发现数据库中的中文都变成了乱码,可能是数据表和字段的编码方式也没选对,之前选的是utf8mb4。重新修改数据表,及表中的各个字段,将编码方式改为utf8。
  2. 重新查询数据库中的数据,发现中文不再是乱码啦!开心 😃

5. 时间显示格式化

查询的时候,发现时间显示的有一些小问题,并不是按照时分秒的形式展示的,而是按照时区展示:
在这里插入图片描述
在application.properties文件中进行下配置:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

返回正常的时间格式:
在这里插入图片描述

6. Restful接口规范

项目中的接口都采用 restful 风格。RESTful API就是REST风格的API,即rest是一种架构风格,它跟编程语言无关,跟平台无关,采用HTTP作为传输协议。
RESTful:用URL定位资源,用HTTP动词(GET、POST、PUT、DELETE)描述操作。它是为了协调多种形式的前端和同一个后台的交互方式。

Rest 设计原则:

  • 前后端分离:服务端不再直接生成页面了,而是只返回数据(json),客户端渲染;
  • 无状态:从客户端到服务器的每个请求都必须包含理解请求所需的所有信息,并且不能利用服务器上任何存储的上下文。 这表示你应该尽可能的避免使用session,由客户端自己标识会话状态。(token)
  • 规范接口:REST接口约束定义:资源识别(URI); 请求动作(http method); 响应信息(状态码)。

再解释下无状态:无状态使得API更简单,服务端无需状态同步操作;API更易扩展,可以部署到多个服务器上,任何服务器都可以处理任何请求,因为没有与会话相关的依赖;每个请求都需要携带token来标明身份。

URI规范:

  • 路径中只使用名词;
  • http method对应不同的请求动作:GET:获取资源,POST:新增资源,PUT:全量更新;PATCH:局部修改;DELETE:删除
  • 使用连字符( - )而不是(_)来提高URI的可读性;
  • 在URI中使用小写字母;
  • 使用http状态码定义api执行结果

三. 项目中整合swagger

swagger可以自动生成一个接口文档,同时方便进行后台调试。

  1. 在父项目下新建一个 common maven模块,在其 pom 文件中引入 swagger 依赖(其他依赖就不说了),并修改打包方式为 pom:
<packaging>pom</packaging>

<!--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>
  1. 在 common 下再建一个 service_base 模块,在这个模块中写一个配置类:
@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("java", "http://atguigu.com", "[email protected]"))
                .build();
    }
}
  1. 在需要 swagger 的模块的pom中引入该 service_base 模块:
<dependency>
    <groupId>com.example</groupId>
    <artifactId>service_base</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
  1. 由于 common模块在父项目下,与service模块处于平级状态,service_edu 启动时无法扫描到 common 模块中的配置类,因此需要在 service_edu 的启动类中手动添加组件扫描标注:
@ComponentScan(basePackages = {"com.atguigu"})
  1. 启动项目,swagger地址在:http://localhost:端口号/swagger-ui.html
    在这里插入图片描述
    现在,我们就能通过 swagger 进行 delete、post 等请求方式的测试了。

为使得 swagger 看起来更加易懂,我们可以为类和方法添加注解,进行标识:

//标注类
@Api(tags = "讲师管理")
//标注方法
@ApiOperation("查找所有教师")

显示如下:
在这里插入图片描述

四. 统一结果返回

在实际项目开发过程中,往往都是多人协同开发,因此有一个统一的后台返回格式非常重要。

1. 返回格式

起码的返回格式中应该包含 状态码、消息及数据三种。其中除了数据之外,其他应该是固定格式的。如:

{
	"success": true,
	"code": 20000,
	"message": "成功""data": {
		//这里面可变
		"items": []
	}
}

2. 封装返回格式

由于每个模块都需要用到这个,因此还是在 common 模块下新建一个 common_result 模块,在里面先建一个接口,用于存储成功和失败两个状态码(当然也可以用枚举)

public interface ReturnCode {
    public static Integer SUCCESS = 20000;
    public static Integer ERROR = 20001;
}

再新建一个Result类,封装返回类型:

@Data
public class Result {

    private boolean success;

    private Integer status;

    private String message;

    private Map<String, Object> data = new HashMap<>();

    /**
     * 私有化构造方法
     */
    private Result() {}

    /**
     * 返回this, 链式编程
     */
    public static Result ok() {
        Result result = new Result();
        result.setSuccess(true);
        result.setMessage("成功");
        result.setStatus(ReturnCode.SUCCESS);
        return result;
    }

    public static Result error() {
        Result result = new Result();
        result.setSuccess(false);
        result.setMessage("失败");
        result.setStatus(ReturnCode.ERROR);
        return result;
    }

    public Result success(boolean success) {
        this.setSuccess(success);
        return this;
    }

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

    public Result status(Integer status) {
        this.setStatus(status);
        return this;
    }

    public Result data(String key, Object value) {
        this.data.put(key, value);
        return this;
    }
    public Result data(Map<String, Object> data) {
        this.setData(data);
        return this;
    }
}

3. 统一格式的使用

同样在需要使用统一返回格式的模块中添加 common_result 模块的依赖:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>common_result</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

修改 controller 接口的返回类型:

@ApiOperation("查找所有教师")
@GetMapping("findAll")
public Result findAllTeacher() {
    List list = teacherService.list(null);
    return Result.ok().data("items", list);
}

@ApiOperation("通过id删除教师")
@DeleteMapping("{id}")
public Result deleteById(@PathVariable String id) {
    boolean flag = teacherService.removeById(id);
    if (flag) {
        return Result.ok();
    } else {
        return Result.error();
    }
}

五. 异常处理

1. 全局异常处理

在 common 模块中定义一个全局异常处理类供各模块使用,当各模块抛出异常时,则可以被统一封装给前端,而不是直接报500的错误。

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class) //需要处理的异常
    @ResponseBody //为了能够返回给前端
    public Result error(Exception e) {
        e.printStackTrace();
        return Result.error().message("哎呀,系统开小差啦,请稍后重试~~");
    }
}

2. 特定异常处理

如果要针对指定异常进行处理,只需要将 Exception 细化到特定的异常类即可。注意,此时如果出现该特定异常,那么将执行特定异常处理,而不再执行全局异常处理。

@ExceptionHandler(NullPointerException.class)
@ResponseBody
public Result error(NullPointerException e) {
    e.printStackTrace();
    return Result.error().message("哎呀,空指针错误啦~~");
}

3. 自定义异常处理

  1. 创建自定义异常类,继承RuntimeException;
  2. 在该类中定义状态码,和异常信息;
  3. 在异常handler类中添加自定义异常处理方法;
@Data
@AllArgsConstructor //生成有参构造方法
@NoArgsConstructor //生成无参构造方法
public class GuliException extends RuntimeException{
    private Integer code;
    private String msg;
}
@ExceptionHandler(GuliException.class)
@ResponseBody
public Result error(GuliException e) {
    e.printStackTrace();
    return Result.error().status(e.getCode()).message(e.getMsg());
}

注意:自定义异常需要自己手动抛出,系统不会自动给抛出

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

六. 统一日志处理

日志级别由高到低:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
低级别的日志设置默认会显示高级别的日志。

在application.properties文件中设置控制台日志显示级别:

#日志级别
logging.level.root=WARN //默认为INFO

如果想要将日志输出到日志文件中,则需要特定的日志工具辅助(如log4j、logback),这里以 logback 为例讲解如何使用。

  1. 删除application.properties文件中的日志配置;
  2. 在resource文件夹下创建 logback-spring.xml 文件;
  3. 在设定的路径下可以找到自动生成的log文件。

如果想要将错误信息也输出到文件中:

  1. 在异常处理类上加上 @Slf4j 注解;
  2. 在想要输出日志的方法中写上: log.error(e.getMessage()); (它将输出到error文件中)
;