SpringCloud-渡劫篇

前言

话语

SpringCloud 快速学习教程

SpringCloud 官网最新版本 Finchley SR2 + SpringBoot 2.0.6

叮~~~ 你有一个称号请你查收:

  1. 三天筑基 (springcloud-入门阶段)
  2. 四天渡劫 (springcloud-进阶阶段)
  3. 七天化神 (springcloud-项目实战)

编写人员

隐无为

修订版本

1.0

第十一章-健康监控-admin

Spring Boot Admin 是一个针对spring-boot的actuator接口进行UI美化封装的监控工具。他可以:在列表中浏览所有被监控spring-boot项目的基本信息,详细的Health信息、内存信息、JVM信息、垃圾回收信息、各种配置信息(比如数据源、缓存列表和命中率)等。

构建springcloud-admin-server项目

pom 文件 如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-monitor-admin</artifactId>
        <groupId>springcloud-monitor</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>springcloud-monitor-admin</groupId>
    <artifactId>springcloud-admin-server</artifactId>
    <!--服务监控服务器端 -->
    <dependencies>

        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
            <version>2.0.3</version>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-server-ui</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!-- 登录验证 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

</project>

配置application.yml

#将服务注册到注册中心
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:9000/eureka/
      #healthcheck:
      #enabled: true  #开启自定义健康检查
  instance:
    #eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s(开发环境)
    lease-expiration-duration-in-seconds: 10
    #eureka客户端需要向eureka服务器发送心跳的频率 默认30s (开发环境)
    lease-renewal-interval-in-seconds: 1
    metadata-map:
      user.name: ${spring.security.user.name}
      user.password: ${spring.security.user.password}
#配置服务名称及端口
server:
  port: 9015
spring:
  application:
    name: springcloud-admin-server
  # 登录管理
  security:
    user:
      name: 'admin'
      password: 'admin'
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS

编写启动类

@EnableAdminServer// 开启监控
@EnableDiscoveryClient
@SpringBootApplication
public class AdminServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AdminServerApplication.class, args);
    }
}

登录验证

可能你需要有权限,登录才能查看监控

如果不需要可以注释掉这个类,并去掉spring-boot-starter-security 依赖jar包

@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
 
    private final String adminContextPath;
 
    public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
        this.adminContextPath = adminServerProperties.getContextPath();
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
 
        http.authorizeRequests()
                .antMatchers(adminContextPath + "/assets/**").permitAll()
                .antMatchers(adminContextPath + "/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
                .logout().logoutUrl(adminContextPath + "/logout").and()
                .httpBasic().and()
                .csrf().disable();
    }
}
 

启动监控服务端

访问地址 http://localhost:9015

账号:admin 密码:admin

1

登录成功,显示如下:

2

监控客户端

监控服务端会监控注册中心的所有服务实例

启动 服务提供者 springcloud-provider-1项目

发现如下 3

4

第十二章-Api接口文档-swagger2

手写Api文档的几个痛点:
1.文档需要更新的时候,需要再次发送一份给前端,也就是文档更新交流不及时。
2.接口返回结果不明确
3.不能直接在线测试接口,通常需要使用工具,比如postman
4.接口文档太多,不好管理

Swagger也就是为了解决这个问题,当然也不能说Swagger就一定是完美的,
当然也有缺点,最明显的就是代码移入性比较强。

创建springcloud-swagger2-test项目

pom 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-doc-swagger2</artifactId>
        <groupId>springcloud-doc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>springcloud-doc-swagger2</groupId>
    <artifactId>springcloud-swagger2-test</artifactId>

    <properties>
       <swagger2-version>2.9.2</swagger2-version>
    </properties>

    <dependencies>
        <!-- swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger2-version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger2-version}</version>
        </dependency>
    </dependencies>

</project>

配置application.yml

#将服务注册到注册中心
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:9000/eureka/
      #healthcheck:
      #enabled: true  #开启自定义健康检查
  instance:
    #eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s(开发环境)
    lease-expiration-duration-in-seconds: 10
    #eureka客户端需要向eureka服务器发送心跳的频率 默认30s (开发环境)
    lease-renewal-interval-in-seconds: 1
    # Swagger2
    status-page-url: http://${spring.cloud.client.ip-address}:${server.port}/swagger-ui.html
#配置服务名称及端口
server:
  port: 9017
spring:
  application:
    name: springcloud-swagger2-test
# 自定义属性  app name
kd:
  app:
    name: springcloud-swagger2-test提供者-1

编写启动类

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

创建Swagger2 配置类

@Configuration
@EnableSwagger2 // 启用Swagger2
public class Swagger2 {
 
    @Bean
    public Docket createRestApi() {// 创建API基本信息
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("org.kd.controller"))// 扫描该包下的所有需要在Swagger中展示的API,@ApiIgnore注解标注的除外
                .paths(PathSelectors.any())
                .build();
    }
 
    private ApiInfo apiInfo() {// 创建API的基本信息,这些信息会在Swagger UI中进行显示
        return new ApiInfoBuilder()
                .title("springcloud-swagger2-test中使用Swagger2构建RESTful APIs")// API 标题
                .description("kd hello ")// API描述
                .contact("隐无为")// 联系人
                .version("1.0")// 版本号
                .build();
    }
 
}

编写接口测试类

@RestController
@Api(value = "测试接口", description = "描述", tags = {"测试接口标签"})
public class TestController {

    @Value("${server.port}")
    String port;

    @Value("${spring.application.name}")
    String name;
    @Value("${kd.app.name}")
    String appName;

    @RequestMapping("/test")
    @ApiOperation("doc接口测试方法")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "用户id", required = true,  paramType = "query"),
            @ApiImplicitParam(name = "age", value = "用户age", required = true,  paramType = "query")
    })
    public Object test(Integer id, Integer age) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("msg", "ok");
        return map;
    }

}

启动测试

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

5

注意 application.yml 这个属性代表的意思是你在点击注册中心的服务应用实例跳转链接的时候就可以查看有哪些接口 类似dubbo点击服务应用实例查看里面有哪些提供的接口

    # Swagger2
    status-page-url: http://${spring.cloud.client.ip-address}:${server.port}/swagger-ui.html

6

测试接口

7

填写参数值,并按ExecutE执行按钮,如下图所示 8

layui美化swagger-ui界面

只需要修改swagger-ui替换成swagger-ui-layer
还有 status-page-url: http://${spring.cloud.client.ip-address}:${server.port}/api-docs.html
因为swagger-ui-layer默认的地址是/api-docs.html

pom 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-doc-swagger2</artifactId>
        <groupId>springcloud-doc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>springcloud-swagger2-test</groupId>
    <artifactId>springcloud-swagger2-layui</artifactId>
    <properties>
        <swagger2-version>2.9.2</swagger2-version>
    </properties>

    <dependencies>
        <!-- swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger2-version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.ohcomeyes</groupId>
            <artifactId>swagger-ui-layer</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>
</project>

修改 配置 application.yml

    # Swagger2
    status-page-url: http://${spring.cloud.client.ip-address}:${server.port}/api-docs.html

效果如下: 9

第十三章-服务链路追踪-zipkin

随着微服务数量不断增长,它们之间的关系会越来越复杂, 如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败, 需要跟踪一个请求从一个微服务到下一个微服务的传播过程

创建项目zipkin-test

pom 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-sleuth-zipkin</artifactId>
        <groupId>SpringCloud-demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>springcloud-sleuth-zipkin</groupId>
    <artifactId>springcloud-zipkin-server</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>springcloud-zipkin-test</module>
        <module>springcloud-zipkin-mysql</module>
        <module>springcloud-zipkin-elk</module>
    </modules>

    <properties>

         <zipkin-version>2.9.4</zipkin-version>

    </properties>

    <!--  zipkin内存版本 重启数据清空  -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-server</artifactId>
            <version>${zipkin-version}</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-ui</artifactId>
            <version>${zipkin-version}</version>
        </dependency>
    </dependencies>
</project>

配置 application.yml

#配置服务名称及端口
spring:
  application:
    name: springcloud-zipkin-server
server:
  port: 9009
#将服务注册到注册中心
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:9000/eureka/
        #healthcheck:
      #enabled: true  #开启自定义健康检查
  instance:
    #eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s(开发环境)
    lease-expiration-duration-in-seconds: 10
    #eureka客户端需要向eureka服务器发送心跳的频率 默认30s (开发环境)
    lease-renewal-interval-in-seconds: 1
    # ip地址
    #ip-address: 固定ip地址
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    preferIpAddress: true
# 屏蔽 多网卡 情况
#spring:
#  cloud:
#    inetutils:
#      ignoredInterfaces:
#      - docker
#      - VM.*
#      - Vir.*

---
#zipkin
spring:
  sleuth:
    enabled: false
    sampler:
      #采样率,推荐0.1, 测试 百分百
      percentage: 1

编写启动类

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

添加zipkin追踪地址

向服务提供者项目和消费者项目添加以下属性

# zipkin
spring:
  zipkin:
    base-url: http://localhost:9009
具体可以看springcloud-sleuth-zipkin项目代码

启动服务测试

管理地址:http://localhost:9009/zipkin/

10

11

12

第十四章-消息服务-stream-kafka

Spring Cloud Stream本质上就是整合了Spring Boot和Spring Integration,
实现了一套轻量级的消息驱动的微服务框架。通过使用Spring Cloud Stream,
可以有效地简化开发人员对消息中间件的使用复杂度,
让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。

安装kafka

kafka安装教程

创建消费者项目

springcloud-kafka-consumer

> pom 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloud-demo</artifactId>
        <groupId>SpringCloud-demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>SpringCloud-demo</groupId>
    <artifactId>springcloud-stream-kafka</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>springcloud-kafka-provider</module>
        <module>springcloud-kafka-consumer</module>
    </modules>


    <dependencies>
        <!-- 消息队列kafka  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-kafka</artifactId>
        </dependency>
        <!-- 服务发现客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- spring-boot-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

</project>

创建输入接受通道

 public interface InputChannel {

    // test 通道协议字段
    String INPUT = "kd";
    @Input(InputChannel.INPUT)
    public  SubscribableChannel input();
}

创建消息监听类

@EnableBinding(value = {InputChannel.class})
public class MyReceiver {

    @StreamListener(InputChannel.INPUT)
    public void messageListen(String message) {
        System.out.println("收到信息:" + message);
    }
}

消费者启动类

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

启动消费者,监听生产者发送过来的消息

创建生成者项目

springcloud-kafka-provider

> pom 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloud-demo</artifactId>
        <groupId>SpringCloud-demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>SpringCloud-demo</groupId>
    <artifactId>springcloud-stream-kafka</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>springcloud-kafka-provider</module>
        <module>springcloud-kafka-consumer</module>
    </modules>


    <dependencies>
        <!-- 消息队列kafka  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-kafka</artifactId>
        </dependency>
        <!-- 服务发现客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- spring-boot-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

</project>

配置application.yml

#将服务注册到注册中心
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:9000/eureka/
      #healthcheck:
      #enabled: true  #开启自定义健康检查
  instance:
    #eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s(开发环境)
    lease-expiration-duration-in-seconds: 10
    #eureka客户端需要向eureka服务器发送心跳的频率 默认30s (开发环境)
    lease-renewal-interval-in-seconds: 1
#配置服务名称及端口
server:
  port: 9014
spring:
  application:
    name: springcloud-provider
---
# provider 生产者
spring:
  cloud:
    stream:
      kafka:
        binder:
          brokers: 127.0.0.1:9092
          zkNodes: 127.0.0.1:2181
      bindings:
        # 通道协议字段
        kd:
          # 生产者和消费者要一样
          destination: topic-test
          # 传输格式
          #content-type: application/json

创建provider发送通道

public interface OutChannel {

    String OUTPUT = "kd";

    @Output(OutChannel.OUTPUT)
    MessageChannel output();
}

绑定发送通道

@EnableBinding(OutChannel.class)
public class KafkaSend {

    @Autowired
    private OutChannel outChannel;

    /*
    *  发送消息
    * */
    public void sendMessage(String msg) {
        try {
            outChannel.output().send(MessageBuilder.withPayload(msg).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

编写页面controller测试类

@RestController
public class SendController {

    @Autowired
    KafkaSend kafkaSend;

    @RequestMapping("/")
    public String  test (){
        kafkaSend.sendMessage("===================我是一条消息的小尾巴=========================");
        return "发送测试";
    }

}

生产者启动类

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

启动生产者,并测试

http://localhost:9014/

13

消费者监听到消息

14

第十五章-分布式配置中心-apollo

apollo简介

随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、参数的配置、服务器的地址……
对程序配置的期望值也越来越高:配置修改后实时生效,灰度发布,分环境、分集群管理配置,完善的权限、审核机制……
在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求。
Apollo配置中心应运而生!
Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。

Apollo支持4个维度管理Key-Value格式的配置:

application (应用)
environment (环境)
cluster (集群)
namespace (命名空间)

apollo安装

请参考官网安装方式:https://github.com/ctripcorp/apollo/wiki/Quick-Start

我觉得已经官网安装已经很详细了

创建springcloud-apollo-demo项目

pom 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-config-apollo</artifactId>
        <groupId>SpringCloud-demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>SpringCloud-demo</groupId>
    <artifactId>springcloud-apollo-demo</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

配置application.yml和META-INF

server:
  port: 8093
name: a  #测试用的

创建 resources/META-INF文件下app.properties

# test
app.id=test
apollo.meta=http://localhost:8080

当然官网也介绍application.properties配置也是可以的

对于Spring Boot环境建议通过以下方式来接入Apollo(需要0.10.0及以上版本)。
使用方式很简单,只需要在application.properties/bootstrap.properties中按照如下样例配置即可

启动类

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

创建页面controller类测试

@RestController
public class ApolloTest {
    @Value("${name}")
    String name;

    @Value("${age:25}")
    int age;


    @Value("${timeout:200}")
    private int timeout;

    @RequestMapping("/")
    public  String  test(){
        return   "name:"+name+"->timeout:"+timeout+"->age:"+age;
    }

}

启动apollo测试

Git Bash 启动 /demo.sh start

Quick Start脚本会在本地启动3个服务,分别使用8070, 8080, 8090端口,
请确保这3个端口当前没有被使用。
也不要忘记修改编辑demo.sh,修改ApolloPortalDB和ApolloConfigDB
相关的数据库连接串信息。

15

浏览器地址:http://localhost:8070

输入用户名apollo,密码admin后登录

16

注意关闭服务命令是:./demo.sh stop

创建test配置项目

17

18

19

启动springcloud-apollo-demo

访问地址:http://localhost:8093/

20

springcloud-apollo-demo项目不重启, apollo服务端改变name为kd666再发布看看效果

21

可以看到,值已经变,不重启项目的情况下,
动态改变参数值就是我们想要的看到的

第十六章-定时任务调度-xxl-job

xxl-job 简介

XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、 学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

大众点评目前已接入XXL-JOB,内部别名《Ferrari》(Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。 据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数十个版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。

xxl-job安装

查看官网安装方式 http://www.xuxueli.com/xxl-job/

xxl-job 执行流程

  • 1、“调度中心”向“执行器”发送http调度请求: “执行器”中接收请求的服务,实际上是一台内嵌jetty服务器,默认端口9999;
  • 2、“执行器”执行任务逻辑;
  • 3、“执行器”http回调“调度中心”调度结果: “调度中心”中接收回调的服务,是针对执行器开放一套API服务;

创建 springcloud-xxl-job项目

pom 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-job-task</artifactId>
        <groupId>SpringCloud-demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>springcloud-job-task</groupId>
    <artifactId>springcloud-xxl-job</artifactId>


    <dependencies>

        <!-- spring-boot-starter-web  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- xxl-job-core -->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>1.9.2</version>
        </dependency>

    </dependencies>

</project>

配置 application.properties

# web port
server.port=9020

# log config
#logging.config=classpath:logback.xml

### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:9300/xxl-job-admin

### xxl-job executor address
xxl.job.executor.appname=springcloud-xxl-job
xxl.job.executor.ip=127.0.0.1
xxl.job.executor.port=9999

### xxl-job, access token
xxl.job.accessToken=

### xxl-job log path  windows  就会以项目所在的盘创建日志文件夹
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job log retention days
xxl.job.executor.logretentiondays=-1

xxl-job配置类

package org.kd.config;

import com.xxl.job.core.executor.XxlJobExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "org.kd.job")
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.executor.appname}")
    private String appName;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;


    @Bean(initMethod = "start", destroyMethod = "destroy")
    public XxlJobExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
        xxlJobExecutor.setAdminAddresses(adminAddresses);
        xxlJobExecutor.setAppName(appName);
        xxlJobExecutor.setIp(ip);
        xxlJobExecutor.setPort(port);
        xxlJobExecutor.setAccessToken(accessToken);
        xxlJobExecutor.setLogPath(logPath);
        xxlJobExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobExecutor;
    }

}

创建Job任务类

@JobHandler(value="demoJobHandler")
@Component
public class DemoJobHandler extends IJobHandler {

	@Override
	public ReturnT<String> execute(String param) throws Exception {
		XxlJobLogger.log("XXL-JOB, Hello World.");
		System.out.println(new Date() + "测试");
		for (int i = 0; i < 5; i++) {
			XxlJobLogger.log("beat at:" + i);

			TimeUnit.SECONDS.sleep(2);
		}
		return SUCCESS;
	}

}

流程

 - 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
 - 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
 - 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,
 - 注解value值对应的是调度中心新建任务的JobHandler属性的值。
 - 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;

启动类

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

调度中心管理端admin

s

修改默认的调度器

改为 springcloud-xxl-job

22

执行器实际上是一个内嵌的Jetty服务器,默认端口9999(配置项:xxl.job.executor.port)。
在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。
“执行器”接收到“调度中心”的调度请求时,如果任务类型为“Bean模式”,将会匹配Spring容器中的“Bean模式任务”,然后调用其execute方法,执行任务逻辑。如果任务类型为“GLUE模式”,将会加载GLue代码,实例化Java对象,注入依赖的Spring服务(注意:Glue代码中注入的Spring服务,必须存在与该“执行器”项目的Spring容器中),然后调用execute方法,执行任务逻辑。

添加任务

23

启动项目测试

请点击任务右侧 “执行” 按钮,可手动触发一次任务执行(不会影响调度队列的触发)

24

注意:任务不是立即执行的,任务会进入调度队列
(也就是说不能指定时间执行,不过作者说可以用他的xxl-mq完成指定时间消费)

第十七章-springcloud-分布式事务(一)

什么是分布式事务?

大家都知道,数据库能实现本地事务,也就是在同一个数据库中,你可以允许一组操作要么全都正确执行, 要么全都不执行。出错回滚,这里特别强调了本地事务,也就是说数据库只能支持同一个数据库中的事务。 但现在的系统往往采用微服务架构,(dubbo、springcloud),业务系统拥有独立的数据库,因此就出现了跨多个数据库的事务需求, 这种事务就叫作分布式事务。

分布式解决方案

流行的分布式事务方案有三种:异步消息确保型、TCC事务补偿型、最大努力通知型。
三种解决方案均是基于柔性事务实现最终一致性。
当然我们不用自己造轮子,可以采用一些开源的分布式框架来解决分布式事务

分布式开源框架-hmily

项目github地址:https://github.com/yu199195/hmily

Hmily
高性能分布式事务tcc方案开源框架。基于java语言来开发(JDK1.8)
支持dubbo,springcloud,motan等rpc框架进行分布式事务。

框架特性

支持嵌套事务(Nested transaction support).
采用disruptor框架进行事务日志的异步读写,与RPC框架的性能毫无差别。
支持SpringBoot-starter 项目启动,使用简单。
RPC框架支持 : dubbo,motan,springcloud。
本地事务存储支持 : redis,mongodb,zookeeper,file,mysql。
事务日志序列化支持 :java,hessian,kryo,protostuff。
采用Aspect AOP 切面思想与Spring无缝集成,天然支持集群。

导入数据库脚本

CREATE DATABASE `tcc` ;
CREATE DATABASE `tcc-order`;
USE `tcc-order`;

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `id` varchar(255) NOT NULL,
  `no` varchar(255) DEFAULT NULL,
  `state` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE DATABASE `tcc-pay`;
USE `tcc-pay`;

DROP TABLE IF EXISTS `t_pay`;
CREATE TABLE `t_pay` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) DEFAULT NULL,
  `way` varchar(255) DEFAULT NULL,
  `money` double DEFAULT NULL,
  `state` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8;

创建 springcloud-tcc-hmily-父项目

pom文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-transition</artifactId>
        <groupId>SpringCloud-demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>SpringCloud-demo</groupId>
    <artifactId>springcloud-tcc-hmily</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>springcloud-mybatis-pay</module>
        <module>springcloud-mybatis-order</module>
    </modules>
    <!-- 分布式事务  hmily -->
    <dependencies>
          <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>hmily-spring-boot-starter-springcloud</artifactId>
            <version>2.0.0-RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.43</version>
        </dependency>
        <!-- springboot-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

创建springcloud-mybatis-order子项目

yaml 配置信息

#配置服务名称及端口
spring:
  application:
    name: springcloud-mybatis-order
  ### 数据库连接信息
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tcc-order?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
server:
  port: 9024
#将服务注册到注册中心
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:9000/eureka/
      #healthcheck:
       #enabled: true  #开启自定义健康检查
  instance:
    #eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s(开发环境)
    lease-expiration-duration-in-seconds: 10
    #eureka客户端需要向eureka服务器发送心跳的频率 默认30s (开发环境)
    lease-renewal-interval-in-seconds: 1
#hmily
org:
  dromara:
    hmily :
      serializer : kryo
      recoverDelayTime : 128
      retryMax : 30
      scheduledDelay : 128
      scheduledThreadMax :  10
      repositorySupport : db
      hmilyDbConfig :
        driverClassName  : com.mysql.jdbc.Driver
        url :  jdbc:mysql://localhost:3306/tcc?useUnicode=true&amp;characterEncoding=utf8
        username : root
        password : root
#日志
logging:
  level:
    root: error
    org.springframework.web: info
    org.apache.ibatis: info
    com.hmily.tcc.bonuspoint: debug
    com.hmily.tcc.lottery: debug
    com.hmily.tcc: debug
    io.netty: info
  path: "./logs"

编写基础代码

controlelr

@RestController
public class OrderController {
    @Autowired
    OrderService orderService;
    @RequestMapping("/save")
    public String save(){
        Order order=new Order();
        order.setId(UUID.randomUUID().toString());
        order.setNo(UUID.randomUUID().toString());
        orderService.save(order);
        return "ok";
    }
}

实体类model

    public class Order {
    private String id; // 编号
    private String no; // 订单号
    private Integer state;// 状态

}

dao

public interface OrderDao {

   @Insert("insert into t_order(id,no,state) values(#{id},#{no},#{state})")
   @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
   public  void save(Order order);


   @Insert("update t_order set state=#{state} where id=#{id}")
   public  void update(Order order);
}

service

public interface OrderService {
    // 增
    void  save(Order order);
}

serviceimpl

@Service
    public class OrderServiceImpl implements OrderService {
    @Override
    @Hmily(confirmMethod = "confirm", cancelMethod = "cancel")
    @Transactional // 加事务并抛出运行时异常
    public void save(Order order) {
        try {
            System.out.println("Order======try阶段====");
            order.setState(0);
            // 本地任务
            orderDao.save(order);
            // 出错
            // int i=100/0;
        }catch (Exception e){
            throw  new RuntimeException("出错");
        }

    }
    @Autowired
        OrderDao orderDao;

        public void confirm(Order order) {
            order.setState(1);
            orderDao.update(order);
            System.out.println("Order======确认阶段====");
        }

        public void cancel(Order order) {
            order.setState(-1);
            orderDao.update(order);
            System.out.println("Order======取消阶段====");
        }
}

springcloud-mybatis-pay-子项目

基础代码和springcloud-mybatis-order一样

多了个feignClient客户端方便调用

@FeignClient(value = "springcloud-mybatis-order")
public interface OrderFeign {
    @RequestMapping(value = "/save")
    @Hmily
    String save();
}

serviceimpl

@Service
public class PayServiceImpl implements PayService {
    @Autowired
    PayDao payDao;
    @Autowired
    OrderFeign testFeign;
    // 测试事务
    @Override
    @Hmily(confirmMethod = "confirm", cancelMethod = "cancel")
    public void savePayInfo(Pay pay) {
        pay.setState(0);
        System.out.println("savePayInfo======try阶段====");

        // 支付信息保存
        payDao.savePayInfo(pay);

        // 订单保存
        testFeign.save();
        // 出错
      //int i=100/0;

    }

    public void confirm(Pay pay) {
        pay.setState(1);
        payDao.update(pay);
        System.out.println("savePayInfo======确认阶段====");
    }

    public void cancel(Pay pay) {
        pay.setState(-1);
        payDao.update(pay);
        System.out.println("savePayInfo======取消阶段====");
    }
}

测试事务发现

测试事务 数据库状态字段 0 代表try状态 1代表成功状态 -1 代表失败状态 pay 为调用方 order 为被调用方

测试第一种情况

pay 代码有错
order 代码无错
发现:
pay 流程      try-》cancle 状态
order 流程    try-》cancle 状态

测试第二种情况

pay 代码无错
order 代码有错(记住必须加事务抛运行异常)
发现:
pay 流程      try-》cancle 状态
order 流程    try-》 数据插入(没有执行cancle取消状态)

测试第三种情况

代码都无错
 pay 流程      try-》confirm
 order 流程    try-》confirm

第十八章-springcloud-分布式事务(二)

学习tcc的hmily还需要自己控制代码@Hmily(confirmMethod = "confirm", cancelMethod = "cancel"), 事务管理能不能更简单点,比如事务都交给程序统一控制,我们只关心业务,加个事务注解就完事了。 那接下来我们就来学习raincat

raincat分布式事务框架介绍

强一致性分布式事务,是基于二阶段提交+本地事务补偿机制来实现 基于java语言来开发(JDK1.8),支持dubbo,motan,springcloud进行分布式事务

特性

无缝集成spring 或 spring boot。

支持dubbo,motan,springcloud,等rpc框架进行分布式事务。

事务发起者,参与者与协调者底层基于netty长连接通信,稳定高效。

协调者采用eureka做注册中心,支持集群模式。

采用Aspect AOP 切面思想与Spring无缝集成。

配置简单,集成简单,源码简洁,稳定性高,已在生产环境使用。

内置经典的分布式事务场景demo工程,并有swagger-ui可视化界面可以快速体验。

事务角色

事务发起者(可理解为消费者 如:dubbo的消费者,springcloud的调用方),发起分布式事务

事务参与者(可理解为提供者 如:dubbo的提供者,springcloud的rest服务提供者),参与事务发起者的事务

事务协调者(tx-manager),协调分布式事务的提交,回滚等。

技术方案

协调者(tx-manager)采用eureka作为注册中心,集群配置,达到服务的高可用,采用redis集群来分布式存储事务数据, springboot 提供rest服务,采用netty与参与者,发起者进行长连接通信。

发起者与协调者,采用Aspect AOP 切面思想,SPI,多线程,异步回调,线程池,netty通信等技术。

SPI扩展

本地事务恢复,支持redis,mogondb,zookeeper,file,mysql等关系型数据库
本地事务序列化保存,支持java,hessian,kryo,protostuff
netty通信序列化方式,支持 hessian,kryo,protostuff

下载编译raincat并maven打本地包

项目地址:https://github.com/yu199195/Raincat (目前作者没提交maven中心仓库,那只能自己打包了,不过作者有计划开始提交了)

部署步骤

快速体检,运行springcloud-sample( 使用者JDK必须为1.8)
步骤一:
配置txManaager, 修改application.properties中你自己的redis配置    
启动TxManagerApplication
注意:如果需要修改服务端口,则应该保证与eureka:client:serviceUrl:defaultZone中的端口一致
步骤二:
引入依赖包(sample已经引入)
   <dependency>
       <groupId>com.raincat</groupId>
       <artifactId>raincat-springcloud</artifactId>
       <version>${your.version}</version>
   </dependency>
执行 raincat-springcloud-sample 工程 sql文件 springcloud-sample.sql
在每个工程下 application.yml 中配置您的数据库连接(只需要改ip和端口)
在每个工程下 applicationContext.xml中的TxDbConfig 配置您的补偿数据库连接,提供单独的数据库来存储。
在需要做分布式事务的接口上加上注解 @TxTransaction (sample已经加上)
依次启动AliPayApplication,WechatApplication ,PayApplication
访问http://localhost:8881/pay-service/swagger-ui.html 进行体验测试

注意:要开启redis和创建tx数据库

准备测试项目

复制之前的springcloud-mybatis-order和springcloud-mybatis-pay项目

pom文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-transition</artifactId>
        <groupId>SpringCloud-demo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>SpringCloud-demo</groupId>
    <artifactId>springcloud-2PC-raincat</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>springcloud-mybatis-pay-2</module>
        <module>springcloud-mybatis-order-2</module>
    </modules>
    <!-- 分布式事务 raincat -->
    <dependencies>
        <dependency>
            <groupId>com.raincat</groupId>
            <artifactId>raincat-spring-boot-starter-springcloud</artifactId>
            <version>1.0.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>

            </exclusions>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.43</version>
        </dependency>
        <!-- springboot-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

修改原hmily信息为raincat

#配置服务名称及端口
spring:
  application:
    name: springcloud-mybatis-pay-2
  ### 数据库连接信息
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tcc-pay?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
server:
  port: 9023
#将服务注册到注册中心
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:9000/eureka/
      #healthcheck:
       #enabled: true  #开启自定义健康检查
  instance:
    #eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s(开发环境)
    lease-expiration-duration-in-seconds: 10
    #eureka客户端需要向eureka服务器发送心跳的频率 默认30s (开发环境)
    lease-renewal-interval-in-seconds: 1
#raincat
raincat:
  tx :
    txManagerUrl: http://localhost:8761
    serializer: kroy
    nettySerializer: kroy
    compensation: true
    compensationCacheType : db
    txDbConfig :
      driverClassName  : com.mysql.jdbc.Driver
      url :  jdbc:mysql://localhost:3306/tx?useUnicode=true&amp;characterEncoding=utf8
      username : root
      password : root

#日志
logging:
  level:
    root: info
    com.raincat.core : debug
  path: "./logs"

修改serviceimpl

@Hmily(confirmMethod = "confirm", cancelMethod = "cancel") 改为注解@TxTransaction 如下所示

    @Override
    @TxTransaction
    public void savePayInfo(Pay pay) {
        pay.setState(1);

        // 支付信息保存
        payDao.savePayInfo(pay);

        // 订单保存
        testFeign.save();
        // 出错
        //int i=100/0;
    }

feignClient 客户端

@FeignClient(value = "springcloud-mybatis-order-2")
public interface OrderFeign {
    @RequestMapping(value = "/save")
    String save();
}

orderserviceimpl

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    OrderDao orderDao;
    @Override
    @TxTransaction
    public void save(Order order) {
        try {
            order.setState(1);
            // 本地任务
            orderDao.save(order);
            // 出错
         // int i=100/0;
        }catch (Exception e){
            throw  new RuntimeException("出错");
        }

    }
}

启动测试事务

启动springcloud-Eureka、raincat-manage、springcloud-mybatis-order-2、springcloud-mybatis-pay-2

启动raincat-admin管理平台

账号:admin 密码:admin 25 26

Last Updated: 6/13/2019, 7:10:41 PM