SpringBoot实战分享讲义
目录
大纲
- 课程导读
- SpringBoot开发法环境搭建和启动
- 理解固化的Maven依赖
- 嵌入式web容器
- SpringBoot返回JSON数据
- 理解自动装配 [*]
- SpringBoot整合JPA
- SpringBoot整合Redis
- 实现一个简单的登录鉴权
- SpringBoot使用Sl4j进行日志记录 [*]
- SpringBoot自定义异常处理
- 基于docker进行发布
- SpringBoot整合单元测试 [*]
- 理解Production-Ready特性 [*]
- SpringBoot-Admin使用 [*]
我是来自AI中台AI平台研发部的郑毅我平时主要负责研发的项目是PaaSAI开放平台. 我们的PaaS AI开放平台后端代码框架是基于SpringBoot+SpringCloud实现的.
目前我们的网关每天接受的调用次数达到2亿+面对如此大的压力SpringBoot依然可以很稳定的运行足以说明这个套框架的健壮性(虽然我们也不断的在采坑)
开篇导语
哈哈 这里跟大家道个歉 我在做PPT的时候在排版上是在搞不好弄来弄去都是丑的不行, 所以我这里就吧重点的信息列了出来, 一些图片和需要展示的内容我这边直接在通过现场展示这样来展示给大家, 当然啦我也是希望通过这种方式来让这堂课变得有趣一点, 可以通过这种方式带着大家一起学习, 而不是单纯的照着PPT来讲.
本次课程我会讲一些SpringBoot的基础知识,然后带着大家一起构建一个简单的小项目,通过这个项目吧SpringBoot的功能串起来,本次课程比较适合刚接触Java web应用开发的同学们,希望大家通过这此课程可以有所收获.
SpringBoot是什么
我们知道, 从2002年开始, Spring一直在飞速发展, 如今已经成为Java EE开发中真正意义上的标准, 但是随着技术的发展, Java EE使用Spring逐渐变得笨重起来, 大量的XML文件存在于项目之中. 繁琐的配置, 整合第三方框架的配置问题, 导致了开发和部署效率的大大降低.
在2012年10月, Mike Youngstrom在Spring jira中创建了一个功能请求, 要求在Spring框架中支持无容器Web应用程序体系架构. 他谈到在主容器引导Spring容器内配置Web容器服务, 下面是jira中的摘录:
I think that Spring’s web application architecture can be significantly simplified if it were to provided tools and a reference architecture that leveraged the Spring component and configuration model from top to bottom. Embedding and unifying the configuration of those common web container services within a Spring Container bootstrapped from a simple main() method.
这段话的基本内容是表达了Mike Youngstrom的诉求他的想法是"让Spring从底层框架上提供一个简单的嵌入式的统一的可以配置的容器, 并且呢这个容器可以通过main方法快速的启动", 其实这就是我们SpringBoot的雏形了, 如果用过SpringBoot的小伙伴再清楚不过了, SptringBoot就是这样的一个框架.
这一要求最终促使了2013年初开始的SpringBoot项目的研发, 大家看下面这段话.
Rather than fix this as part of the core Spring Framework we have decided to start a new project called Spring Boot that addresses this and a number of other issues.
到今天SpringBoot的版本已经到了2.4.x. SpringBoot并不是用来替代Spring的解决方案, 而是和Spring框架紧密结合用户提升Spring开发者体验的工具.
它集成了大量常用的第三方库. Spring Boot 应用使用它们, 无需任何配置, 开箱即用(Out of the box). 即便有时需要配置, 也仅需非常少量的代码(基于 Java 的配置). 这样的设计有利于开发者专注于业务逻辑的开发.
为什么学习SpringBoot
打开Spring的官网可以看到在这里有如下几个模块我们竟然看不到SpringBoot, 这是为什么呢, 因为SpringBoot已经被认为是构建为服务的默认框架了, 所以我们进入第一个模块Microservices来看一看
进入微服务一栏可以看到对SpringBoot有如下的描述:
这里强调了SpringBoot可以快速的构建和迭代一个微服务, 并且SpringBoot已经成为了Java构建微服务的一种标准, 我们在构建项目的时候可以得到一个jar包, 通过这样的方式就可以快速的发布自己的项目.
其实这些内容已经告诉我们了作为一个Java程序猿如果你想要构建微服务那么SpringBoot就是你必不可少的工具了, 所以我们为什么要学呢好像已经不需要什么理由了吧_.
SpringBoot有哪些优点
良好的基因
SpringBoot是伴随着Spring4.0诞生的, 从字面理解, Boot是"引导"的意思, 因此SpringBoot旨在帮助开发者快速搭建Spring框架. SpringBoot继承了原有Spring框架的优秀基因, 使Spring在使用中更加方便快捷.
简化编码
使用过Spring的朋友都知道, 利用Spring创建Web项目, 需要在POM文件中添加多个依赖, 而SpringBoot则会帮助开发者快速启动一个Web容器. 在SpringBoot中, 我们只需要在POM文件中添加一个starter-web依赖即可, 代码如下.
简化配置
Spring虽然是JavaEE轻量级框架, 但由于其繁琐的配置, 一度被人认为是"配置地狱". 各种XML. Annotation配置让人眼花缭乱, 如果配置很多, 一旦发生错误也很难找出原因. SpringBoot更多地采用了JavaConfig的方式对Spring进行配置.
简化部署
使用Spring时, 项目部署时需要我们在服务器上部署Tomcat, 然后把项目打成War包扔到Tomcat里. 使用SpringBoot后, 我们不需要在服务器上去部署Tomcat, 因为SpringBoot内嵌了Tomcat, 我们只需要将项目打成Jar包, 使用java-jarxxx.jar一键式启动项目.
从未来发展的趋势来看
微服务是未来发展的趋势, 项目会从传统架构慢慢转向微服务架构, 因为微服务可以使不同的团队专注于更小范围的工作职责. 使用独立的技术. 更安全更频繁地部署. 而继承了 Spring的优良特性, 与Spring一脉相承, 而且支持各种 REST API 的实现方式. SpringBoot也是官方大力推荐的技术, 可以看出, SpringBoot是未来发展的一个大趋势.
本节课能学到什么
- SpringBoot的基本应用
- 使用SpringBoot快速构建一个Web应用
- 理解SpringBoot中的自动装配
- 在SpringBoot中集成Mybatis/Redis等常用中间件
- SpringBoot中的统一异常处理
- 使用Docker快速发布SpringBoot应用
SpringBoot开发法环境搭建和启动
init包的下载
这里我们直接使用官方的网站进行构建, 步骤如下:
- 访问: http://start.spring.io/
- 在页面上选择相应的SpringBoot版本以及需要使用到的相关依赖
这里我们先把需要的依赖添加进去, 我们需要的依赖如下:
- web
- jpa
- redis
- h2
- lombok
这里经过我们简单的配置之后直接点击generate生成想要的zip文件
了解包结构
我们对这个文件进行解压缩之后使用idea打开
- src/main/java 路径:主要编写业务程序;
- src/main/resources 路径:存放静态文件和配置文件;
- src/test/java 路径:主要编写测试程序。
如上图所示默认会创建一个启动类, 改启动类上包含@SpringBootApplication注解, 经过该注解标识只需要运行Run方法就可以启动SpringBoot项目.
我们这里直接启动这个项目 再启动之前让我们先把所有的pom文件都注释掉.
然后我们启动这个项目
可以看一下现在直接启动时不会加载什么组件的因为所以的Start都被我们注释掉了, 目前这个项目就是SpringBoot的简化版本了, 其内部只包含一个最小化的starter
第一次启动SpringBoot
这时候我们来把web的start放开继续启动一下SpringBoot看看效果如何
这个时候我们可以看到项目已经启动了, 并且坚挺了8080端口
写一个helloword
接下来简单写一个接口试试我们的web应用是否可以处理请求.
这里的编码方式和传统的S平ringMVC是一样的方式.
SpringBoot搭建一个的Web应用就是这么简单! 默认的端口号是8080, 如果我们想要修改则可以在application.yml文件中使用server.port=8081来修改
理解固化的Maven依赖
在使用SpringBoot的时候首先要做的一件是就是引入pom依赖, 通常的方式我们可以直接继承
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
但是由于maven只能采用单继承的方式, 所以限制了其固化Maven依赖(仅限于SpringBoot相关), 通过这种方式我们会引入很多额外的东西, 这是因为SpringBoot在定义spring-boot-starter-parent时默认的指定了很多参数比如默认的mvn打包方式, 包扫描的文件范围等等, 如果我们想要自己指定这些参数则, 或者很有可能有自定义Parent. 如果需要固话其他类型的依赖则比较繁琐, 可以使用如下这种方式进行引用
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
但是需要注意的是我们通过这种方式启动SpringBoot的时候会出现很多问题, 比如找不到主类或者是打包失败等问题, 这是因为在SpringBoot的parent中固化的一些依赖没有被引入, 这是我们就需要手动的添加了, 这些操作在这里就不做过多的赘述, 有兴趣的小伙伴可以课后继续交流或者是自己去网上寻找答案.
嵌入式web容器
在SpringBoot官网上, 明文提示开发人员SpringBoot应用直接嵌入Tomcat, Jetty或Undertow作为核心特性, 这几种容器的实现成为嵌入式容器, 不同的嵌入式容器在切换的时候无序修改代码, 只需要修改使用的容器依赖就可以了, 我们目前的大部分使用情况都是作为HTTP请求的收发, 所以让我们一起来看一下不同容器的实际性能对比
性能对比
这里的性能测试我简单说一下, 使用3000个线程进行测试, 判断请求的吞吐量和形影时延来决定哪个web容器性能更好一点.
Tomcat
Jetty
Undertown
通过这三个图我们可以对比出来对处理Http请求而言Undertown的性能要更胜一筹, 接下来我们尝试将demo的web容器从Tomcat换成Undertown
更换web容器
排除之前的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
增加新的Undertown容器依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
完成容器的替换之后我们直接重启项目可以发现实际加载的容器是Undertown
2020-12-29 01:48:15.507 INFO 52300 --- [ main] org.jboss.threads : JBoss Threads version 3.1.0.Final
2020-12-29 01:48:15.547 INFO 52300 --- [ main] o.s.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 8080 (http)
2020-12-29 01:48:15.557 INFO 52300 --- [ main] top.jbzm.demo.DemoApplication : Started DemoApplication in 4.599 seconds (JVM running for 6.141)
通过项目的启动日志我们可以直观的看到目前的容器已经替换成了Undertown
总结
本节主要讲一下SpringBoot中的嵌入式容器以及三种容器的性能对比, 我们在构建应用的时候大多数都是web应用, 其基本的协议也是http理论上来讲这三种容器都可以满足我们的使用需求, 但是通过数据对比我们也发现对于某些性能要求极高的场景我们可以考虑将容器进行替换.
比如网关服务, 秒杀系统等等.
返回Json数据
在项目开发中, 接口与接口之间, 前后端之间数据的传递都是使用JSON格式, 在SpringBoot中, 接口返回JSON格式的数据很简单, 在Controller中使用@RestController注解即可返回JSON格式的数据, @RestController也是SpringBoot新增的一个注解, 我们可以点进去看看该注解内部都包含那些东西.
可以看出, @RestController注解包含了原来的@Controller和@ResponseBody注解, 大家对于@Controller注解都是非常了解的了, @ResponseBody注解就是再返回的过程中将数据结构转换为JSON格式. 所以默认情况下, 使用了@RestController注解即可将返回的数据结构转换成JSON格式, Spring Boot 中默认使用的 JSON 解析技术框架是 Jackson。我们点开 pom.xml 中的 spring-boot-starter-web 依赖,可以看到一个 spring-boot-starter-json 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.4.1</version>
<scope>compile</scope>
</dependency>
继续跟进去我们可以看到其中包含了jackson的相关依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.11.3</version>
<scope>compile</scope>
</dependency>
到此为止,我们知道了 Spring Boot 中默认使用的 JSON 解析框架是 Jackson。下面我们看一下默认的 Jackson 框架对常用数据类型的转 JSON 处理。
这里我们来创建一个实体类用来演示JSON的使用
@Setter
@Getter
public class User {
private String username;
private String password;
private String email;
}
编写一个接口用来处理请求, 并且将数据返回给用户
@GetMapping("all")
public Object getAllUser() {
LinkedList<User> userDos = new LinkedList<>();
for (int i = 0; i < 5; i++) {
User userDo = new User();
userDo.setUsername("user" + i);
userDo.setPassword("test");
userDo.setEmail("email@tal.com");
userDos.add(userDo);
}
return userDos;
}
这里我们首先创建一个User实体类用来存放一些用户相关的数据, 然后我们在controller层写一个接口用来访问这个数据, 我们目前先不连接数据库所以通过for循环模拟写入一些数据, 然后我们直接将User返回出去让我们来看看结果:
curl --request GET 'localhost:8080/user/all'
[{"username":"user0","password":"test","email":"email@tal.com"},{"username":"user1","password":"test","email":"email@tal.com"},{"username":"user2","password":"test","email":"email@tal.com"},{"username":"user3","password":"test","email":"email@tal.com"},{"username":"user4","password":"test","email":"email@tal.com"}]
可以看到我们返回的是一个list类型的数据结构, 实际上这个list包含的entity也自动转换成了一个json
在实际生产中经常会遇到json中出现一些空值的情况, 这时默认情况下会返回一个null但在有些时候我们可能需要对返回值进行特殊的处理
下面我们来编写一个接口这个接口用来返回一个null值给用户
@GetMapping("null")
public Object getNullUser() {
User userDo = new User();
userDo.setUsername("user");
userDo.setPassword(null);
userDo.setEmail("email@tal.com");
return userDo;
}
可以看到默认情况下是一个null
curl --request GET 'localhost:8080/user/null'
{"username":"user","password":null,"email":"email@tal.com"}
这里如果想自定义空值是的返回内容则需要重写一个bean在JacksonAutoconfig中实现了很多bean, 其中jacksonObjectMapper这个方法上实现了@ConditionalOnMissingBean注解, 该注解的意思就是如果用户已经实现了这样的一个bean则不会去使用被添加了这个注解的bean.
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper build = builder.createXmlMapper(false).build();
build.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("null");
}
});
return build;
}
}
上面的这个方法会在遇到null值的时候将返回一个"null"字符串, 当然我们也可以使用其他的定义方式, 比如直接返回一个""
这时我们在看一下返回的json结果
curl --request GET 'localhost:8080/user/null'
{"username":"user","password":"null","email":"email@tal.com"}
定义统一的json结构
由于封装json数据的类型不确定, 所以在定义统一的json结构时我们需要用到泛型. 统一的json结构中属性包括数据, 状态码, 提示信息即可, 构造方法可以根据业务的需求去添加, 一般来说应该有默认的宝返回结构, 也应可以由开发人员制定返回结构
@Getter
@Setter
public class Result<T> {
private T data;
private String code;
private String msg;
public Result() {
this.code = "2000000";
this.msg = "success!";
}
public Result(T data) {
this.data = data;
this.code = "2000000";
this.msg = "success!";
}
public Result(String code, String msg) {
this.code = code;
this.msg = msg;
}
public Result(T data, String code, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
}
@GetMapping("result")
public Result<User> userResult() {
User userDo = new User();
userDo.setUsername("user");
userDo.setPassword("test");
userDo.setEmail("email@tal.com");
return new Result<>(userDo);
}
curl --request GET 'localhost:8080/user/result'
{"data":{"id":"null","username":"user","password":"test","email":"email@tal.com","role":"null"},"code":"2000000","msg":"success!"}
总结
本节我们对SpringBoot中如何处理json数据作了详细的分析, 之所以这里要讲的细一点是因为我们在平时的工作中基本离不开接口的编写和json数据的处理, 所以作为基础的知识我们必须对其有相对深入的了解.
理解自动装配
SpringBoot和传统的Spring最大的区别就是增加了自动装配的功能, 在使用SpringBoot编写我们的项目之前我来简单介绍一下SpringBoot是如何和实现自动装配功能的.
众所周知, SpringBoot如果的启动入口是一个main方法, 如果想要使用SpringBoot还要在main方法包含的类上添加@SpringBootApplication注解, 这个注解具体有什么用呢, 让我们一起来看一看.
理解@SpringBootApplication
大家可以看到上面这个图, 这个是SpringBoot官方给出的处理方案
@SpringBootApplication用于激活其他三个注解, 其功能分别是:
- 声明被标注的是配置类
- 激活自动装配机制
- 激活包扫描
文中指出, 自动装配类能够打包到外部的JAR文件中, 并且被SpringBoot装载. 溶蚀自动装配也能被关联到"starter"中, 这些"starer"提供自动装配的代码及相关依赖. 这里我们对于如何自定义start先不做讨论, 目前只需要理解如何完成自动装配的.
在49.1中说道SpringBoot的自动装配实现和@configuration与@contitional的联系
文中提到的@ConditionalOnClass和@ConditionalOnMissingBean 注解是最为常见的, 通过这种方式来判断如果目标类存在的时候采集与自动装配.
49.2寻找装配条件, 这里指出了SpringBoot通过检测外部jar包的META-INF/spring.factories文件来寻找对应的类去装载, 并且呢这个类应该实现了@EnableAutoConfiguration注解
spring-boot-sutoconfigure寻找自动装配
这些红色的就是缺失的了类如果他们存在则会帮助我们完成自动装配
总结
这一节的内容也是非常重要, SpringBoot的核心内容就是自动装配, 理解了自动装配才能知道SpringBoot是如何工作的, 才能在工作中不至于不知道项目里使用了那些start对应的start是如何加载的.
理论的内容到这里基本就结束了, 接下来我会带着大家快速的构建一个简单的应用, 这个应用其功能非常简单模拟一个拥有权限管理的仓库管理系统.
SpringBoot整合JPA
什么是JPA
JPA的全称是Java Persistence API, 可以通过注解或者XML描述对象-关系表之间的映射关系, 并将实体对象持久化到数据库中.
对于JPA而言它仅仅是一个规范, 也就是说JPA仅仅定义了一些接口, 而接口是需要实现才能工作的. 所以底层需要某种实现, 而Hibernate就是实现了JPA接口的ORM框架.
什么是SpringDataJPA
SpringDataJPA是spring提供的一高JPA开发框架, 按照约定号的方法命名规则写Dao层接口, 就可以在不写接口情况下对数据库实现访问和操作. 同时提供了很多除了CRUD之外的功能, 如分页, 排序, 复杂查询等等.
SpringData的主要操作特性
SpringData项目是在指为大家提供一种通用的编码模式. 数据访问对象实现了对物理层的抽象为编写查询方法提供了方便. 通过对象的映射实现了域对象和持久化存储之间的转换, 而模板提供的是对底层存储的访问实现他们之间的关系如下图所示.
jpa的优缺点
jpa的优点:
- 可持久化Java对象。JPA能够直接持久化复杂的Java对象;
- 使用简单。JPA使用注释(Annotation)定义Java对象与关系数据库之间的映射;
- 与Spring高度结合, 统一的编码风格, 可以让我们快速上手.
jpa的缺点:
- 复杂的查询难以构建, 有时使用注解会让代码开起来很奇怪;
- 由于是自动生成sql有时会存在性能问题;
- 超大型sql无法通过声明式编写.
结合H2使用
这里简单介绍一下h2, h2是一个用java开发的嵌入式数据库, 它本身知识一个类库, 即只有一个jar文件, 可以直接嵌入到应用项目中, 通常用作单元测试和demo编写, 这里由于篇幅问题我就不过多赘述了, 在本课程中h2只是用来代替mysql快速帮助我们完成demo的开发.
实战应用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
放开注解
配置对应的yaml
spring:
datasource:
password: tal
username: tal
url: jdbc:h2:./h2:dbtest
platform: h2
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: update
generate-ddl: true
open-in-view: false
h2:
console:
enabled: true
path: /h2
添加实体
@Setter
@Getter
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* 添加注解设置主键Id
* 添加注解设置为自增字段
*/
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
private String email;
}
添加Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByUsername(String username);
List<User> findByUsernameAndPassword(String username, String password);
}
在构建Repository的时候需要创建一个接口类并且继承JpaRepository
这里说明一下两个泛型变量以及为什么需要继承JpaRepository:
大家可以看一下这个接口的继承关系实际上是继承了PagingAndSorting和QueryByExampleExecutor这两个接口通过名字可以了解到我们直接继承JpaRepository可以获得CRUD, 分页, 排序, 查询模板等功能能
再说一下这两个泛型, 我们直接看一下这个接口的文档
/**
* Central repository marker interface. Captures the domain type to manage as well as the domain type's id type. General
* purpose is to hold type information as well as being able to discover interfaces that extend this one during
* classpath scanning for easy Spring bean creation.
* <p>
* Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the
* same signature as those declared in {@link CrudRepository}.
*
* @see CrudRepository
* @param <T> the domain type the repository manages
* @param <ID> the type of the id of the entity the repository manages
* @author Oliver Gierke
*/
@Indexed
public interface Repository<T, ID> {
}
通过文档可以得知第一个泛型变量传递的是域类型也就是我们的实体类型, 第二个泛型是实体主键的ID类型
创建controller
在使用jpa的时候我们实际调用的类就是我们刚刚创建的UserRepository接口通过这个接口类
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
我们可以看一下这个接口类给我们提供了那些方法, 我们跟到这个接口下来查看一下他基本包含的方法, 这里简单做一下介绍, 就像 常用的findAll() findById()等等 jpa的实现其实非常有意思, 有兴趣的话他家可以上他的官网进行学习了解, 我这里就不错过多的赘述了
这里就简单的写一个插入的逻辑
@PostMapping
public Result<User> createUser(@RequestBody User user) {
userRepository.save(user);
return new Result<>(user);
}
curl --request POST 'localhost:8080/user' --header 'Content-Type: application/json' --data-raw '{"username":"jbzm3","password":"jbzm","email":"jbzm@tal","role":"admin"}'
{"data":{"id":161,"username":"jbzm3","password":"jbzm","email":"jbzm@tal","role":"admin"},"code":"2000000","msg":"success!"}
然后我们再写一个查询的逻辑
@GetMapping("/name/{username}")
public Result<List<User>> getUserByUsername(@PathVariable String username) {
return new Result<>(userRepository.findByUsername(username));
}
curl --request GET 'localhost:8080/user/name/jbzm3'
[{"id":129,"username":"jbzm3","password":"jbzm","email":"jbzm@tal","role":"admin"},{"id":161,"username":"jbzm3","password":"jbzm","email":"jbzm@tal","role":"admin"}]
再写一个修改逻辑
@PutMapping
public Result<User> updateUser(@RequestBody User user) {
User userNew = userRepository.save(user);
return new Result<>(userNew);
}
curl --request PUT 'localhost:8080/user' --header 'Content-Type: application/json' --data-raw '{"id":2,"username":"jbzm-tal","password":"jbzm","email":"jbzm@tal"}'
{"data":{"id":2,"username":"jbzm-tal","password":"jbzm","email":"jbzm@tal","role":"null"},"code":"2000000","msg":"success!"}
SpringBoot整合Redis
讲到这里我们的项目已经有一些成色了, 我们加下来尝试吧Redis整合到我们的项目中, 这里我不对Redis做过的的介绍了, 如果这里不是很理解Redis是什么的同学可以去网上自行差一些线管的资料.
引入Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
再导入Redis的依赖同事我还添加了阿里巴巴的FastJson方便定位问题
Redis配置
导入了依赖之后我们开始对Redis的配置
在SpringBoot中可以对Redis进行很多定制化的配置, 比如设置账号密码, 选择可用连接池的大小等等, 我们这里就先使用默认配置进行连接
string:
redis:
host: {{ip}}
配置好了Redis之后我们就可以尝试想Redis中写入数据了, 接受下来我们来编写一个用户的登录验证功能, 当用户成功登录之后呢我们将用户的个人信息保存在Redis中.
Daemon编写
增加接口向redis中写入数据
创建鉴权controller
@RestController
@RequestMapping("/auth")
public class AuthController {
private final RedisTemplate redisTemplate;
private final UserRepository userRepository;
public AuthController(RedisTemplate redisTemplate, UserRepository userRepository) {
this.redisTemplate = redisTemplate;
this.userRepository = userRepository;
}
}
@PostMapping("/user")
public Result<String> logging(@RequestBody User user) {
List<User> userList = userRepository.findByUsernameAndPassword(user.getUsername(), user.getPassword());
if (userList.size() > 0) {
String token = UUID.randomUUID().toString().replaceAll("-", "");
redisTemplate.opsForValue().set(token, JSON.toJSONString(userList.get(0)));
return new Result<>(token);
}
return new Result<>("5000000", "Incorrect user name or password");
}
从Redis中查询数据
@GetMapping("/user/{token}")
public Result<User> getCacheByUserId(@PathVariable String token) {
String userString = (String) redisTemplate.opsForValue().get(token);
return new Result<>(JSON.parseObject(userString, User.class));
}
curl --request POST 'localhost:8080/auth/user' --header 'Content-Type: application/json' --data-raw '{"username":"jbzm3","password":"jbzm"}'
{"data":"150913e6bd964dc79c08636ac8fc7ae4","code":"2000000","msg":"success!"}
curl --request GET 'localhost:8080/auth/user/150913e6bd964dc79c08636ac8fc7ae4'
{"data":{"id":1,"username":"jbzm3","password":"jbzm","email":"jbzm@tal","role":"admin"},"code":"2000000","msg":"success!"}
创建商品仓库
完成了用户登录模块接下来我们来设计一个仓库实现权限管理下的库存CRUD
我们来创建一个新的entity用来存放我们的库存
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Inventory {
@Id
@GeneratedValue
private Long id;
private String name;
private Long num;
}
接着创建对应的repsonitory
public interface InventoryRepository extends JpaRepository<Inventory, Long> {
}
以及对应的 查看 新增 修改 接口
@RestController
@RequestMapping("/inventory")
public class InventoryController {
private final InventoryRepository inventoryRepository;
private final RedisTemplate redisTemplate;
public InventoryController(InventoryRepository inventoryRepository, RedisTemplate redisTemplate) {
this.inventoryRepository = inventoryRepository;
this.redisTemplate = redisTemplate;
}
@GetMapping
public Result<List<Inventory>> getAllInventory(@RequestHeader("token") String token) {
User user = getUserByToken(token);
if (user == null) {
return new Result<>("5000000", "Unauthorized access");
}
return new Result<>(inventoryRepository.findAll());
}
@PostMapping
public Result<Inventory> createInventory(@RequestBody Inventory inventory, @RequestHeader("token") String token) {
User user = getUserByToken(token);
if (user == null || !"admin".equals(user.getRole())) {
return new Result<>("5000000", "Unauthorized access");
}
return new Result<>(inventoryRepository.save(inventory));
}
@PutMapping
public Result<Inventory> updateInventory(@RequestBody Inventory inventory, @RequestHeader("token") String token) {
User user = getUserByToken(token);
if (user == null || !"admin".equals(user.getRole())) {
return new Result<>("5000000", "Unauthorized access");
}
return new Result<>(inventoryRepository.save(inventory));
}
private User getUserByToken(String token) {
String userString = (String) redisTemplate.opsForValue().get(token);
return JSON.parseObject(userString, User.class);
}
}
这套接口需要用户登录之后获取一个对应的tokne, 那到了token之后进行调用, 只有是admin权限的用户才能进行库存的新增和修改, 而非admin用户只能进行商品的查看
登录
使用错误的token登录
curl --request POST 'localhost:8080/inventory' --header 'Content-Type: application/json' --header 'token: sef' --data-raw '{"name":"apple","num":100}'
{"data":"null","code":"5000000","msg":"Unauthorized access"}
使用账号密码进行登录获取token
curl --request POST 'localhost:8080/auth/user' --header 'Content-Type: application/json' --data-raw '{"username":"jbzm3","password":"jbzm"}'
curl --request POST 'localhost:8080/inventory' --header 'Content-Type: application/json' --header 'token: 6f02c47dc57946d6acc6ef4a1df1a30e' --data-raw '{"name":"apple","num":100}'
{"data":{"id":34,"name":"apple","num":100},"code":"2000000","msg":"success!"}
可以看到有结果返回了, 说明我们鉴权通过了成功的项数据库中插入了数据.
使用token查询数据
curl --request GET 'localhost:8080/inventory' --header 'token: 6f02c47dc57946d6acc6ef4a1df1a30e'
{"data":[{"id":34,"name":"apple","num":100}],"code":"2000000","msg":"success!"}
创建库存
curl --request POST 'localhost:8080/inventory' --header 'Content-Type: application/json' --header 'token: a19d137908804e5ca61e876606be07d9' --data-raw '{"name":"apple","num":100}'
'
总结
这里我们就已经讲完了整个业务代码的编写, 接下来我们需要吧项目打包然后进行发布, 我们现在的开发模式都是基于容器进行部署, 所以我们需要将项目打包成一个镜像, SpringBoot天生就支持快速镜像构建.
基于docker进行发布
添加相关依赖
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.13</version>
<configuration>
<imageName>
tal/${groupId}-${artifactId}-image:${project.version}
</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
编写dockerfile
FROM openjdk:8-jdk-alpine
COPY *.jar /tmp/
CMD /usr/bin/java -jar /tmp/*.jar
打包发布
mvn package
mvn docker:build
运行容器
结束语
本次的SpringBoot课程到这里就结束了, 这里我在总结一下本堂课的主要内容:
- SpringBoot开发法环境搭建和启动
- 理解固化的Maven依赖
- 嵌入式web容器
- SpringBoot返回JSON数据
- SpringBoot整合JPA
- SpringBoot整合Redis
- 实现一个简单的登录鉴权
- 基于docker进行发布
并且将这些内容串到一个小的项目里, 希望可以通过这样的方式让对SpringBoot有需要的同学可以快速上手并且了解到在实际生产开发过程中SpringBoot是如何帮助我们快速构建一个微服务的有所了解, 本次课程的PPT, 逐字稿, 以及代码我后续会放到群里大家有需要的话可以自行下载.
感谢大家, 现在有什么需要提问的么?