文章目录

 

1. 基础知识介绍

1.1 Feign 概述

  • Feign是一个声明式WebService客户端。 使用Feign能让编写Web Service客户端更加简单。
  • 它的使用方法是定义一个服务接口然后在上面添加注解。
  • Feign也支持可拔插式的编码器和解码器。
  • Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

1.2 OpenFeign 与 Feign 的比较

SpringCloud 之 OpenFeign_spring boot

1.3 OpenFeign 的使用方法

在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口 上面标注Mapper注解现在是一个微服务接口 上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon,利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。

1.4 OpenFeign 的超时控制

OpenFeign 默认支持 Ribbon,可以进行超时控制,当发出请求后等待响应的时间超过设定的阈值的时候,就会报错。

1.5 OpenFeign 的日志打印

1.5.1 概述

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节,说白了就是对Feign接口的调用情况进行监控和输出。

1.5.2 级别
  • NONE: 默认的,不显示任何日志
  • BASIC:仅记录请求方法、URL、响应状态及执行时间
  • HEADERS:BASIC + 请求和响应头的信息
  • FULL:HEADERS + 请求和相应的正文及元数据

2. 环境准备

  • 操作系统:macOS Catalina 10.15.3

  • IDEA:IntelliJ IDEA 2019.1 (Ultimate Edition)

  • JDK:1.8

  • SpringBoot:2.2.6

  • SpringCloud:Hoxton.SR3

3. 项目结构

3.1 cloud-provider-payment8001

SpringCloud 之 OpenFeign_客户端_02

3.2 cloud-provider-payment8003

SpringCloud 之 OpenFeign_spring boot_03

3.3 cloud-consumer-feign-order80

SpringCloud 之 OpenFeign_客户端_04

4. 基本使用操作过程

3.1 新建微服务服务提供者cloud-provider-payment8001 和 8003

因为 OpenFeign 是用在客户端的,不是本篇的重点,所以服务提供者(服务端)的构建过程,此处略过,后面会贴上源码。

3.2 新建微服务服务消费者cloud-consumer-feign-order80

3.2.2 在父工程下构建Module
  • 使用 Maven 建立 Module

SpringCloud 之 OpenFeign_ide_05

  • 命名cloud-consumer-feign-order80

SpringCloud 之 OpenFeign_客户端_06

  • 完成创建 Module

SpringCloud 之 OpenFeign_ide_07

3.2.3 导入 POM 文件坐标

与其他的 SpringCloud 微服务模块的 POM 文件区别不是很大,这里最重要的是我们要导入 openfeign 的坐标。

<dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--api-->
        <dependency>            		 <groupId>com.atguigu.com.atguigu.com.atguigu.com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
3.2.4 在 resource 下新建 application.yml 配置文件
  • 设定端口80
server:
  port: 80
  • 注册进 Eureka 注册中心
eureka:
  client:
    register-with-eureka: true    #注册进注册中心
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集群版
3.2.5 编写主启动类 OrderFeignMain80
  • 在 main/java/com/atguigu/springcloud 目录下新建主启动类

SpringCloud 之 OpenFeign_客户端_08

  • 编写SpringBoot的应用入口
@SpringBootApplication
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
3.2.6 编写业务接口

cloud-consumer-feign-order80 是服务消费者,这里我们调用的服务的来自服务提供者的 8001 和 8003

  • 在 src/main/java/com/atguigu/springcloud 下新建 service 包并新建 PaymentFeignService 接口

SpringCloud 之 OpenFeign_spring_09

业务接口中我们要写的服务要对应到服务提供者8001和8003提供的服务,因为8001和8003提供的服务的一致,所以我们这里只需要来看 8001 的控制类提供了什么服务。

  • 查看 8001 服务提供者所提供的服务
    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody  Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入结果:"+result);

        if(result > 0){
            return new CommonResult(200,"插入数据成功, serverPort:"+serverPort,result);
        }else{
            return new CommonResult(444,"插入数据库失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("**********查询结果:"+payment+"\t"+"哈哈哈哈");

        if (payment != null){
            return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }

我们这里可以发现8001主要提供了 2 个服务,一个是 create,一个是 getPaymentById。

  • 在 order80 的 PaymentFeignService 接口上添加注解 @FeignClient,指明要使用的服务提供者的名称
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")

备注:这个名称的设定是在 8001 的 application.yml 文件中设定的。

SpringCloud 之 OpenFeign_spring_10

  • 在order80 的 PaymentFeignService 接口中编写服务方法
package com.atguigu.springcloud.service;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import feign.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author Hedon Wang
 * @create 2020-05-02 10:00
 */
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);

    @PostMapping(value = "/payment/create")
    public CommonResult create(@RequestBody  Payment payment);
}
3.2.7 编写控制器类

控制器类是要来接受浏览器端发过来的请求,然后做出相应的响应。我们这样里要写的就是暴露给浏览器对应这两个服务的两个路径,当浏览器通过这两个路径传过来请求的时候,控制器就调用 service 层接口中的方法,service 层接口中的方法就去查找服务提供者 8001 和 8003 提供的对应的方法并执行,然后将方法执行结果返回给浏览器。

  • 在 src/main/java/com/atguigu/springcloud 下新建 controller 包并新建 OrderFeignController 类

SpringCloud 之 OpenFeign_java_11

  • 为 OrderFeignController 类 添加 @RestController 注解,注明这是一个控制类
  • 为 OrderFeignController 类 添加 @Slf4j 注解,提供日志打印功能
  • 注入 PaymentFeignService 服务调用接口
//注入服务调用接口
@Resource
private PaymentFeignService paymentFeignService;
  • 编写调用两个服务的方法
@GetMapping(value = "/consumerFeign/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
  return paymentFeignService.getPaymentById(id);   //调用服务
}


@GetMapping(value ="/consumerFeign/payment/create")
public CommonResult<Payment> create(Payment payment){
  return paymentFeignService.create(payment);     //调用服务
}
3.2.8 主启动类添加@EnableFeignClients 注解激活并开启 Feign
@SpringBootApplication
@EnableFeignClients  //使用Feign激活并开启
public class OrderFeignMain80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
3.2.9 测试
  • 启动 7001、7002 服务注册中心(可选)

  • 启动服务提供者 8001、8003

  • 再启动服务消费者 80

  • 测试 create 服务:http://localhost:80/consumerFeign/payment/create

SpringCloud 之 OpenFeign_java_12
  • 测试 getPaymentById 服务:http://localhost:80/consumerFeign/payment/get/31
SpringCloud 之 OpenFeign_spring boot_13

5. OpenFeign 服务调用过程总结

SpringCloud 之 OpenFeign_spring_14

6. OpenFeign 与 Ribbon 调用过程的对比

6.1 OpenFeign

SpringCloud 之 OpenFeign_客户端_15

6.2 Ribbon

SpringCloud 之 OpenFeign_客户端_16

7. 超时控制演示

7.1 修改 Order80 的 application.yml 文件,设置 OpenFeign 客户端超时控制

#设置Feign客户端的超时时间(OpenFeign默认支持Ribbon)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
  ConnectTimeout: 5000   #改成5s
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 5000     #改成5秒

7.2 在服务提供者 8001 的控制类中添加超时方法

  • 第一次我们设置程序暂停10秒钟,超过了我们前面设定的阈值
/**
  * 故意设置暂停程序,造成超时
  */
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout(){
  try{
    //停10秒钟
    TimeUnit.SECONDS.sleep(10);
  }catch (InterruptedException e){
    e.printStackTrace();
  }
  return serverPort;
}

7.3 在服务消费者 80 的 service 接口中加入该超时方法

@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout();

7.4 在服务消费者 80 的 controller 类中添加该方法暴露给浏览器

@GetMapping(value = "/consumerFeign/payment/timeout")
public String paymentFeignTimeout(){
  //openfeign-client 客户端一般默认等待 1 秒钟,但是我们这里故意让它暂停了 3 秒钟
  return paymentFeignService.paymentFeignTimeout();
}

7.5 测试 http://localhost/consumerFeign/payment/timeout

  • 出现超时报错

SpringCloud 之 OpenFeign_spring boot_17

7.6 更改超时方法的停止时间为 3 秒

    /**
     * 故意设置暂停程序,造成超时
     */
    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout(){
        try{
            //停3秒钟
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return serverPort;
    }

7.7 再次测试 http://localhost/consumerFeign/payment/timeout

  • 在阈值范围内,没有问题

SpringCloud 之 OpenFeign_客户端_18

8. 日志打印演示

8.1 在 Order80 中配置日志 Bean 对象

8.1.1 在在 src/main/java/com/atguigu/springcloud 下新建 config 包并新建 FeignConfig 类

SpringCloud 之 OpenFeign_客户端_19

8.1.2 添加 @Configuration 注解注明该类为注解类
8.1.3 配置日志打印等级的Bean对象

这里我们设置最高级 FULL

package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Hedon Wang
 * @create 2020-05-02 11:27
 */
@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

8.2 在 Order80 的 application.yml 文件中开启 Feign 日志支持

logging:
  level:
    #Feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeignService: debug

8.3 浏览器发出请求

SpringCloud 之 OpenFeign_spring_20

8.4 后台查看日志打印结果

SpringCloud 之 OpenFeign_客户端_21

9. 程序源码

9.1 cloud-provider-payment8001

9.1.1 PaymentController.java
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:33
 */

@RestController
//打日志
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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


    /**
     * 服务发现
     */
    @Resource
    private DiscoveryClient discoveryClient;

    /**
     * Controller是传给前端的,我们传CommonResult而不传Payment
     */
    @PostMapping(value = "/payment/create")
    //consumer是客户端,提交有关payment过来,我们服务端要搞一个RequestBody才能接收
    public CommonResult create(@RequestBody  Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入结果:"+result);

        if(result > 0){
            return new CommonResult(200,"插入数据成功, serverPort:"+serverPort,result);
        }else{
            return new CommonResult(444,"插入数据库失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("**********查询结果:"+payment+"\t"+"哈哈哈哈");

        if (payment != null){
            return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }


    /**
     * 通过服务发现来获得自己的信息
     */
    @GetMapping("/payment/discovery")
    public Object discovery(){
        //① 盘点一下在Eureka注册过的微服务有哪些
        List<String> services = discoveryClient.getServices();
        for(String element:services)
        {
            log.info("**********service: "+element);
        }
        //② 通过微服务的信息进一步获得微服务的相关信息
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for(ServiceInstance instance:instances){
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }
        return this.discoveryClient;
    }


    /**
     * 故意设置暂停程序,造成超时
     */
    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout(){
        try{
            //停3秒钟
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return serverPort;
    }

}
9.1.2 PaymentDao.java
package com.atguigu.springcloud.dao;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:12
 */

//推荐用Mybatis的@Mapper,而不用Spring的@Repository
@Mapper
public interface PaymentDao {

    /**
     * 写操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 读操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.1.3 PaymentService.java
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:29
 */
public interface PaymentService {
    /**
     * 写操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 读操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.1.4 PaymentServiceImpl.java
package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:30
 */

@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }
}
9.1.5 PaymentMain8001.java
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author Hedon Wang
 * @create 2020-04-22 20:48
 */

/**
 * 主程序
 */
@SpringBootApplication
@EnableEurekaClient   //注册进Eureka
@EnableDiscoveryClient
public class PaymentMain8001 {

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

}
9.1.6 PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
    <!--增
               前面有在application.yml配置了别名,所以这里只需要写Payment,而不需要写com.atguigu.com.atguigu.com.atguigu.com.atguigu.springcloud.entities.Payment

               useGenerateKeys = true 的原因:
                    因为我们插入数据库,必然会返回一个数字
                        如果这个数字大于0,说明我们插入成功
                        如果这个数据小于等于0,说明我们插入失败
                        这也是我们create返回值为int的原因

               keyProperty: 主键
        -->

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(serial) values (#{serial});
    </insert>


    <!--查-->
    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <result column="serial" property="serial" jdbcType="VARCHAR"></result>
    </resultMap>
    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
        SELECT * FROM payment where id=#{id};
    </select>
</mapper>
9.1.7 application.yml
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service    #微服务的名称,别改动,重要
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver              # mysql驱动包
    url: jdbc:mysql://localhost:3306/cloudDB?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root



mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity别名类所在包

#配置注册金eureka
eureka:
  client:
    register-with-eureka: true    #注册金Eureka
    #是否从EurekaServer抓取已有的注册信息,默认为true。
    #单节点无所谓,集群必须设为为true才能诶和ribbon使用负载均衡
    fetch-registry: true
    #入驻哪里
    service-url:
#      defaultZone: http://localhost:7001/eureka    单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集群版
  instance:
    instance-id: payment8001
    prefer-ip-address: true
    #Eureka客户端向服务端发送心跳的时间间隔:默认30秒
#    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限:默认90秒
#    lease-expiration-duration-in-seconds: 2

9.2 cloud-provider-payment8003

9.2.1 PaymentController.java
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:33
 */

@RestController
//打日志
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @Resource
    private DiscoveryClient discoveryClient;

    /**
     * Controller是传给前端的,我们传CommonResult而不传Payment
     */

    @PostMapping(value = "/payment/create")
    //consumer是客户端,提交有关payment过来,我们服务端要搞一个RequestBody才能接收
//  public CommonResult create(@RequestBody  Payment payment){
    public CommonResult create(Payment payment){
        int result = paymentService.create(payment);
        log.info("*****插入结果:"+result);

        if(result > 0){
            return new CommonResult(200,"插入数据成功, serverPort:"+serverPort,result);
        }else{
            return new CommonResult(444,"插入数据库失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        log.info("**********查询结果:"+payment+"\t"+"哈哈哈哈");

        if (payment != null){
            return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
        }else{
            return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
        }
    }

    @GetMapping("/payment/discovery")
    public Object discovery(){

        List<String> services = discoveryClient.getServices();
        for(String element:services){
            log.info(element);
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for(ServiceInstance instance:instances){
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }


        return this.discoveryClient;

    }
}
9.2.2 PaymentDao.java
package com.atguigu.springcloud.dao;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:12
 */

//推荐用Mybatis的@Mapper,而不用Spring的@Repository
@Mapper
public interface PaymentDao {

    /**
     * 写操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 读操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.2.3 PaymentService.java
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:29
 */
public interface PaymentService {
    /**
     * 写操作
     * @param payment
     * @return
     */
    public int create(Payment payment);


    /**
     * 读操作
     * @param id
     * @return
     */
    public Payment getPaymentById(@Param("id") Long id);
}
9.2.4 PaymentServiceImpl.java
package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author Hedon Wang
 * @create 2020-04-22 21:30
 */

@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    @Override
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }
}
9.2.5 PaymentMain8003.java
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @author Hedon Wang
 * @create 2020-04-22 20:48
 */

/**
 * 主程序
 */
@SpringBootApplication
@EnableEurekaClient   //注册进Eureka
@EnableDiscoveryClient
public class PaymentMain8003 {

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

}
9.2.6 PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
    <!--增
               前面有在application.yml配置了别名,所以这里只需要写Payment,而不需要写com.atguigu.com.atguigu.com.atguigu.com.atguigu.springcloud.entities.Payment

               useGenerateKeys = true 的原因:
                    因为我们插入数据库,必然会返回一个数字
                        如果这个数字大于0,说明我们插入成功
                        如果这个数据小于等于0,说明我们插入失败
                        这也是我们create返回值为int的原因

               keyProperty: 主键
        -->

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(serial) values (#{serial});
    </insert>


    <!--查-->
    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"></id>
        <result column="serial" property="serial" jdbcType="VARCHAR"></result>
    </resultMap>
    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
        SELECT * FROM payment where id=#{id};
    </select>
</mapper>
9.2.7 application.yml
server:
  port: 8003

spring:
  application:
    name: cloud-payment-service    #微服务的名称,别改动,重要
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver              # mysql驱动包
    url: jdbc:mysql://localhost:3306/cloudDB?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root



mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities    # 所有Entity别名类所在包

#配置注册金eureka
eureka:
  client:
    register-with-eureka: true    #注册金Eureka
    #是否从EurekaServer抓取已有的注册信息,默认为true。
    #单节点无所谓,集群必须设为为true才能诶和ribbon使用负载均衡
    fetch-registry: true
    #入驻哪里
    service-url:
#      defaultZone: http://localhost:7001/eureka    单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集群版
  instance:
    instance-id: payment8003
    prefer-ip-address: true

9.3 cloud-consumer-feign-order80

9.3.1 FeignConfig.java
package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Hedon Wang
 * @create 2020-05-02 11:27
 */
@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
9.3.2 OrderFeignController.java
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author Hedon Wang
 * @create 2020-05-02 10:04
 */

@RestController
@Slf4j
public class OrderFeignController {

    //注入服务调用接口
    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumerFeign/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);   //调用服务
    }


    @GetMapping(value ="/consumerFeign/payment/create")
    public CommonResult<Payment> create(Payment payment){
        return paymentFeignService.create(payment);     //调用服务
    }

    @GetMapping(value = "/consumerFeign/payment/timeout")
    public String paymentFeignTimeout(){
        //openfeign-client 客户端一般默认等待 1 秒钟,但是我们这里故意让它暂停了 3 秒钟
        return paymentFeignService.paymentFeignTimeout();
    }
}
9.3.3 PaymentFeignService.java
package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import feign.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author Hedon Wang
 * @create 2020-05-02 10:00
 */
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);


    //RequestBody要搭配PostMapping
    @PostMapping(value = "/payment/create")
    //consumer是客户端,提交有关payment过来,我们服务端要搞一个RequestBody才能接收
    public CommonResult create(@RequestBody  Payment payment);


    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout();
}
9.3.4 OrderFeignMain80.java
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @author Hedon Wang
 * @create 2020-05-02 09:54
 */

@SpringBootApplication
@EnableFeignClients  //使用Feign激活并开启
public class OrderFeignMain80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
    }
}
9.3.5 application.yml
server:
  port: 80

eureka:
  client:
    register-with-eureka: true    #注册进注册中心
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka   #集群版

#设置Feign客户端的超时时间(OpenFeign默认支持Ribbon)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
  ConnectTimeout: 5000   #改成5s
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 5000     #改成5秒
logging:
  level:
    #Feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeignService: debug

10. 附注说明

此处还用到 Eureka 注册中心,因为与本篇重点偏离太大,为避免偏离重点,且为了突出本篇的核心所在,就省略了这部分的讲解。