文章目录
一. 项目结构
项目的基本结构是有一个父项目(SpringBoot)用于管理依赖包的版本及放置共有的依赖,父项目下有各个子模块(Maven),子模块下又有各个子模块。
- 先创建父项目,新建一个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项目,则问题解决。
- 由于父项目主要用于管理依赖的版本,所以删除pom文件中原有的 <dependencies>,这些依赖之后将放入子模块中。
- 添加依赖的版本号:
<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>
- 版本管理:配置<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>
- 删除父项目中的 src 文件夹
- 创建service子模块:右键项目名称,new -> module…,之后选择Maven,填写子模块名称后完成创建。
- 该子模块还是一个pom,同样删除 src 文件夹,加上<packaging>pom</packaging>,将父项目中的依赖复制过来,但是此时不需要填写版本号了,自动使用父项目中的版本号。(注意:若出现Maven右侧依赖模块出现一大堆红色线条,但是本地仓库是有下好的jar包,可以将pom中的依赖先删掉,保存后,再撤销回来,重新import即可)
- 在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. 查询数据库返回中文乱码
查询数据库中的数据,发现查到的数据中文都变成了乱码!!!找了很久的解决方法,最终这样解决了:
- 查看IDEA的编码方式,改成UTF-8
- 打开数据库,输入【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,编码方式变为:
- 修改完编码方式后,发现数据库中的中文都变成了乱码,可能是数据表和字段的编码方式也没选对,之前选的是utf8mb4。重新修改数据表,及表中的各个字段,将编码方式改为utf8。
- 重新查询数据库中的数据,发现中文不再是乱码啦!开心 😃
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可以自动生成一个接口文档,同时方便进行后台调试。
- 在父项目下新建一个 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>
- 在 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();
}
}
- 在需要 swagger 的模块的pom中引入该 service_base 模块:
<dependency>
<groupId>com.example</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
- 由于 common模块在父项目下,与service模块处于平级状态,service_edu 启动时无法扫描到 common 模块中的配置类,因此需要在 service_edu 的启动类中手动添加组件扫描标注:
@ComponentScan(basePackages = {"com.atguigu"})
- 启动项目,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. 自定义异常处理
- 创建自定义异常类,继承RuntimeException;
- 在该类中定义状态码,和异常信息;
- 在异常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 为例讲解如何使用。
- 删除application.properties文件中的日志配置;
- 在resource文件夹下创建 logback-spring.xml 文件;
- 在设定的路径下可以找到自动生成的log文件。
如果想要将错误信息也输出到文件中:
- 在异常处理类上加上 @Slf4j 注解;
- 在想要输出日志的方法中写上: log.error(e.getMessage()); (它将输出到error文件中)