【案例实战】SpringBoot整合GRPC微服务远程通信

news2024/9/25 23:22:25

1.什么是GRPC

在这里插入图片描述

GRPC是RPC框架中的一种,是一个高性能,开源和通用的RPC框架,基于Protobuf序列化协议开发,且支持众多开发语言。

面向服务端和协议端,基于http/2设计,带来诸如双向流,流控,头部压缩,单TCP连接上的多路复用请求等特性。这些特性使得其在移动设备上表现的更好,更省电和节省空间。

在GRPC里客户端可以向调用本地对象一样直接调用另一台不同机器上服务端医用的方法,使得您能够更容易地创建分布式应用和服务。

与许多RPC系统类似,GRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法。在服务端实现这个接口。并运行一个GRPC服务器来处理客户端调用。在客户端拥有一个存根能够向服务端一样的方法。

2.GRPC特性以及应用场景

(1)特性:

  • grpc可以跨语言使用。支持多种语言 支持C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等编程语言。
  • 基于 IDL ( 接口定义语言)文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub。
  • 通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量。
  • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
  • 安装简单,扩展方便(用该框架每秒可达到百万个RPC)

(2)使用场景:

  • 微服务:gRPC 设计用于低延迟和高吞吐量通信。 gRPC 对于效率至关重要的轻量级微服务非常有用。
  • 点对点实时通信:gRPC 对双向流式传输提供出色的支持。 gRPC 服务可以实时推送消息而无需轮询。
  • 多语言环境:gRPC 工具支持所有常用的开发语言,因此,gRPC 是多语言环境的理想选择。
  • 网络受限环境:gRPC 消息使用 Protobuf(一种轻量级消息格式)进行序列化。 gRPC 消息始终小于等效的 JSON 消息。

3.GRPC大致请求流程

在这里插入图片描述

  • 客户端(gRPC Stub)调用 A 方法,发起 RPC 调用。
  • 对请求信息使用 Protobuf 进行对象序列化压缩(IDL)。
  • 服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。
  • 对响应结果使用 Protobuf 进行对象序列化压缩(IDL)。
  • 客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。

4.GRPC的优点和缺点

  • 优点:
    • protobuf二进制消息,性能好/效率高
    • proto文件生成目标代码,简单易用
    • 序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射
    • 支持向前兼容和向后兼容
    • 支持多种语言,底层采用Netty实现
  • 缺点:
    • GRPC尚未提供链接池,需要自己实现。
    • 尚未提供服务发现、负载均衡机制
    • Protobuf二进制可读性差

5.SpringBoot整合GRPC环境准备

(1)案例背景

统一下单业务,下单时会选择对应的商品和优惠券,那么根据选择的商品ID和优惠券ID分别去商品微服务和优惠券微服务进行调用。

在这里插入图片描述

(2)创建MAVEN聚合项目

在这里插入图片描述

(3)父级工程pom.xml引入依赖,锁定版本

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <!-- grpc版本-->
        <grpc-version>1.42.2</grpc-version>
        <!-- service和client要使用的lib版本-->
        <common-version>1.0-SNAPSHOT</common-version>
        <!--  netty版本-->
        <netty-version>4.1.65.Final</netty-version>
        <!--  Springboot版本-->
        <spring-boot.version>2.6.4</spring-boot.version>
        <!-- Springboot-grpc版本,用于server服务注解使用-->
        <grpc-spring-boot-starter.version>2.13.1.RELEASE</grpc-spring-boot-starter.version>
        <!-- maven构建工具版本-->
        <maven-plugin-version>3.8.1</maven-plugin-version>
        <!-- lombok-->
        <lombok-version>1.18.16</lombok-version>
        <!--fastjson-->
        <fastjson.version>1.2.83</fastjson.version>
    </properties>

    <!--使用dependencyManagement声明得到依赖子模块不需要再进行版本指定,直接通过父模块指定即可,以此实现依赖的统一管理,防止出现依赖冲突-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.lixiang</groupId>
                <artifactId>common</artifactId>
                <version>${common-version}</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-netty</artifactId>
                <version>${grpc-version}</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-protobuf</artifactId>
                <version>${grpc-version}</version>
            </dependency>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-stub</artifactId>
                <version>${grpc-version}</version>
            </dependency>
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-common</artifactId>
                <version>${netty-version}</version>
            </dependency>
            <!-- spring boot grpc 依赖 -->
            <dependency>
                <groupId>net.devh</groupId>
                <artifactId>grpc-client-spring-boot-starter</artifactId>
                <version>${grpc-spring-boot-starter.version}</version>
            </dependency>
            <dependency>
                <groupId>net.devh</groupId>
                <artifactId>grpc-server-spring-boot-starter</artifactId>
                <version>${grpc-spring-boot-starter.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok-version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

(4)创建common模块

​ common中引入依赖

 <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <os.detected.classifier>windows-x86_64</os.detected.classifier>
    </properties>

    <dependencies>
        <!--        Spring-->
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-common</artifactId>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
        </dependency>
        <!--        other-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <!--配置protobuf插件 可参阅https://github.com/grpc/grpc-java-->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.0</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.19.1:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.43.1:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-plugin-version}</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

​ 这块注意这个值os.detected.classifier,获取当前系统信息,根据你们自己的电脑或者运行环境去填写,获取信息的主类如下:

public class GetClassifier {
    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        System.out.println(System.getProperty("os.arch"));
    }
}

在这里插入图片描述

common模块中创建一个统一返回工具类

在这里插入图片描述

/**
 * @description 统一返回格式工具类
 * @author lixiang
 * @Version 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseResponse {

    /**
     * 状态码 0 表示成功
     */

    private Integer code;
    /**
     * 数据
     */
    private Object data;
    /**
     * 描述
     */
    private String msg;


    /**
     *  获取远程调用数据
     *  注意事项:支持多单词下划线专驼峰(序列化和反序列化)
     * @param typeReference
     * @param <T>
     * @return
     */
    public <T> T getData(TypeReference<T> typeReference){
        return JSON.parseObject(JSON.toJSONString(data),typeReference);
    }

    /**
     * 成功,不传入数据
     * @return
     */
    public static BaseResponse buildSuccess() {
        return new BaseResponse(0, null, null);
    }

    /**
     *  成功,传入数据
     * @param data
     * @return
     */
    public static BaseResponse buildSuccess(Object data) {
        return new BaseResponse(0, data, null);
    }

    /**
     * 失败,传入描述信息
     * @param msg
     * @return
     */
    public static BaseResponse buildError(String msg) {
        return new BaseResponse(-1, null, msg);
    }

    /**
     * 自定义状态码和错误信息
     * @param code
     * @param msg
     * @return
     */
    public static BaseResponse buildCodeAndMsg(int code, String msg) {
        return new BaseResponse(code, null, msg);
    }

}

(5)创建coupon-server模块

​ 添加依赖

    <dependencies>
        <dependency>
            <groupId>com.lixiang</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-plugin-version}</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.lixiang.CouponApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

​ 创建SpringBoot运行主类

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

​ 创建application.properties

# 定义服务名
spring.application.name=coupon-server
# 定义服务端口
server.port=8081
# 定义GRPC端口
grpc.server.port=8071

​ 测试启动

在这里插入图片描述

(6)创建product-server模块

​ 添加依赖

<dependencies>
        <dependency>
            <groupId>com.lixiang</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-plugin-version}</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.lixiang.ProductApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

​ 创建SpringBoot运行主类

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

​ 创建application.properties

# 定义服务名
spring.application.name=product-server
# 定义服务端口
server.port=8083
# 定义GRPC端口
grpc.server.port=8073

​ 测试运行

在这里插入图片描述

​ 这里我们需要注意一点,我们在优惠券服务和商品服务都定义了一个grpc.server.port,这个是用来提供给其他服务调用是写的端口,不要和springboot本身的server.port重复,否则会报错。

(7)创建order-server模块

​ 添加maven依赖

<dependencies>
        <dependency>
            <groupId>com.lixiang</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-plugin-version}</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.lixiang.OrderApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

​ 创建SpringBoot运行主类

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

​ 创建application.properties

# 定义服务名
spring.application.name=order-server
# 定义服务端口
server.port=8082
# 定义GRPC端口
grpc.server.port=8072

# 定义调用商品服务的GRPC
product.server.address=localhost:8073
# 定义调用优惠券服务的GRPC
coupon.server.address=localhost:8071

在这里插入图片描述

6.SpringBoot整合GRPC业务开发

​ ok,到目前我们所有的环境准备已经完成了,那么下面我们就开始进入到业务的开发,首先我们在下单的时候,会传入一个商品ID和一个优惠券ID,那么我们要通过这两个ID去对应的服务查出具体的详细信息。

(1)开发coupon-server根据ID获取优惠卷详情信息

​ 准备CouponServer.proto文件,写在common模块中

在这里插入图片描述

​ CouponServer.proto内容如下:

/**
 * 编译工具版本
 */
syntax = "proto3";
/**
 * 指定生成实体
 */
option java_multiple_files = true;
/**
 * 指定生成接口
 */
option java_generic_services = true;
/**
 * 声明包
 */
package com.lixiang.grpc.server;

/**
 * 商品服务proto文件
 */
option java_outer_classname = "CouponServer";

/**
 * 统一返回实体
 */
message CouponServerResponse {
  string message = 1;
  int32 code = 2;
  string data=3;
}

/**
 * 声明接口
 */
service CouponService {
  rpc deductProductInventory (DeductCouponRequest) returns (CouponServerResponse);
}

/**
 * 声明扣减商品库存实体
 */
message DeductCouponRequest {
  int32 couponId = 1;
}

​ 编写好proto文件点开maven运行protobuf插件

在这里插入图片描述

​ 运行好之后,会发现target下多了一个这个包

在这里插入图片描述

​ 然后在coupon-server中开始编写获取优惠券的方法,这块我们先模拟一些优惠券的数据。

/**
 * 优惠券实体类
 */
@Data
public class Coupon {

    /**
     * 优惠券ID
     */
    private Integer id;

    /**
     * 优惠券金额
     */
    private BigDecimal amount;

    /**
     * 满减额度
     */
    private BigDecimal fullReduction;

    /**
     * 优惠券名称
     */
    private String name;

    /**
     * 优惠券可用开始时间
     */
    private LocalDateTime startTime;

    /**
     * 优惠券结束时间
     */
    private LocalDateTime endTime;

}
public class CouponUtil {

    private final static List<Coupon> couponList = new ArrayList<>();

    static {
        Coupon coupon1 = new Coupon();
        coupon1.setAmount(new BigDecimal(50));
        coupon1.setFullReduction(new BigDecimal(300));
        coupon1.setId(1);
        coupon1.setName("满300减50券");
        coupon1.setStartTime(LocalDateTime.parse("2022-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        coupon1.setEndTime(LocalDateTime.parse("2023-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        Coupon coupon2 = new Coupon();
        coupon2.setAmount(new BigDecimal(100));
        coupon1.setFullReduction(new BigDecimal(500));
        coupon2.setId(2);
        coupon2.setName("满500减100券");
        coupon2.setStartTime(LocalDateTime.parse("2022-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        coupon2.setEndTime(LocalDateTime.parse("2023-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        Coupon coupon3 = new Coupon();
        coupon3.setAmount(new BigDecimal(200));
        coupon1.setFullReduction(new BigDecimal(800));
        coupon3.setId(3);
        coupon3.setName("满800减100券");
        coupon3.setStartTime(LocalDateTime.parse("2022-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        coupon3.setEndTime(LocalDateTime.parse("2023-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        couponList.add(coupon3);
        couponList.add(coupon2);
        couponList.add(coupon1);
    }

    /**
     * 获取具体某个优惠券
     * @param couponId
     * @return
     */
    public static Coupon getCouponById(Integer couponId){
        return couponList.stream().filter(obj-> Objects.equals(obj.getId(), couponId)).collect(Collectors.toList()).get(0);
    }

}

​ 创建CouponGrpcServer,继承CouponServiceGrpc.CouponServiceImplBase,CouponServiceGrpc.CouponServiceImplBase这个类是刚刚运行插件自动生成的。

@GrpcService
public class CouponGrpcServer extends CouponServiceGrpc.CouponServiceImplBase {

    @Override
    public void deductProductInventory(DeductCouponRequest request, StreamObserver<CouponServerResponse> responseObserver) {
        int couponId = request.getCouponId();

        //查找优惠券详细信息
        Coupon coupon = CouponUtil.getCouponById(couponId);
        String jsonData = JSON.toJSONString(coupon);
        CouponServerResponse couponServerResponse = CouponServerResponse.newBuilder()
                .setCode(200)
                .setMessage("")
                .setData(jsonData)
                .build();
        responseObserver.onNext(couponServerResponse);
        responseObserver.onCompleted();
    }

​ ok,这样我们一个根据ID查询优惠券的信息就定义好了。

​ 整体的目录结构:

在这里插入图片描述

(2)开发product-server根据ID获取优惠卷详情信息

​ product-server和coupon-server是一样的,这里就不一一细说了,直接上代码。

/**
 * 商品实体类
 */
@Data
public class Product {

    /**
     * 商品ID
     */
    private Integer id;

    /**
     * 商品名称
     */
    private String name;

    /**
     * 商品价格
     */
    private BigDecimal price;

}
public class ProductUtil {

    private final static List<Product> productList = new ArrayList<>();

    static {
        Product product1 = new Product();
        product1.setId(1);
        product1.setName("互联网JAVA架构师养成零基础到精通");
        product1.setPrice(new BigDecimal(12999));

        Product product2 = new Product();
        product2.setId(2);
        product2.setName("Python+大数据零基础到精通");
        product2.setPrice(new BigDecimal(15999));

        Product product3 = new Product();
        product3.setId(3);
        product3.setName("5G云计算运维架构零基础到精通");
        product3.setPrice(new BigDecimal(10999));

        productList.add(product1);
        productList.add(product2);
        productList.add(product3);
    }

    /**
     * 根据商品ID获取商品详情
     * @param productId
     * @return
     */
    public static Product getProductById(Integer productId){
        return productList.stream().filter(obj-> Objects.equals(obj.getId(), productId)).collect(Collectors.toList()).get(0);
    }

}
@GrpcService
public class ProductGrpcServer extends ProductServiceGrpc.ProductServiceImplBase {

    @Override
    public void deductProductInventory(DeductInventoryRequest request, StreamObserver<ProductServerResponse> responseObserver) {

        int productId = request.getProductId();
        Product product = ProductUtil.getProductById(productId);
        String jsonData = JSON.toJSONString(product);

        ProductServerResponse productServerResponse = ProductServerResponse.newBuilder()
                .setCode(200)
                .setMessage("")
                .setData(jsonData)
                .build();
        responseObserver.onNext(productServerResponse);
        responseObserver.onCompleted();
    }
}

​ 目录结构:

在这里插入图片描述

(3)开发order-server模块统一下单接口

​ 首先我们要先配置一下GRPC的地址

在这里插入图片描述

​ 配置GrpcClientConfig

/**
 * @description Grpc Client 配置类
 * @author lixiang
 */
@Configuration
public class GrpcClientConfig {

    /**
     * 商品服务地址
     */
    @Value("${product.server.address}")
    private String productServerAddress;

    /**
     * 优惠券服务地址
     */
    @Value("${coupon.server.address}")
    private String couponServerAddress;

    /**
     * 商品服务grpc-client
     * @return
     */
    @Bean
    public ProductServiceGrpc.ProductServiceBlockingStub getProductServerClient() {
        return ProductServiceGrpc.newBlockingStub(ManagedChannelBuilder.forTarget(productServerAddress).usePlaintext().build());
    }

    /**
     * 优惠卷服务grpc-client
     * @return
     */
    @Bean
    public CouponServiceGrpc.CouponServiceBlockingStub getCouponServerClient() {
        return CouponServiceGrpc.newBlockingStub(ManagedChannelBuilder.forTarget(couponServerAddress).usePlaintext().build());
    }

}

​ 编写统一下单接口请求类

@Data
public class OrderConfirmRequest {

    /**
     * 支付价格
     */
    private BigDecimal totalAmount;

    /**
     * 支付类型
     */
    private String payType;

    /**
     * 支付商品的ID
     */
    private Integer productId;

    /**
     * 支付时用的优惠券ID
     */
    private Integer couponRecordId;
}

​ 编写统一下单接口

@RestController
@RequestMapping(value = "/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/confirm")
    public BaseResponse confirm(@RequestBody OrderConfirmRequest orderConfirmRequest) {
        orderService.confirm(orderConfirmRequest);
        return BaseResponse.buildSuccess();
    }

}

​ 编写统一下单业务类

public interface OrderService {

    /**
     * 统一下单接口
     * @param orderConfirmRequest
     */
    void confirm(OrderConfirmRequest orderConfirmRequest);

}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService{

    @Autowired
    private ProductServiceGrpc.ProductServiceBlockingStub productService;

    @Autowired
    private CouponServiceGrpc.CouponServiceBlockingStub couponService;

    @Override
    public void confirm(OrderConfirmRequest orderConfirmRequest) {
        //1.调用优惠券服务获取优惠券详情
        DeductCouponRequest deductCouponRequest = DeductCouponRequest.newBuilder()
                .setCouponId(orderConfirmRequest.getCouponRecordId())
                .build();
        CouponServerResponse couponServerResponse = couponService.deductProductInventory(deductCouponRequest);
        String couponDataStr = couponServerResponse.getData();
        JSONObject couponData = JSON.parseObject(couponDataStr);
        log.info("调用优惠卷服务获取的优惠券信息:{}",couponData);

        //2.调用商品服务获取商品详细信息
        DeductInventoryRequest deductInventoryRequest = DeductInventoryRequest.newBuilder()
                .setProductId(orderConfirmRequest.getProductId())
                .build();
        ProductServerResponse productServerResponse = productService.deductProductInventory(deductInventoryRequest);
        String productDataStr = productServerResponse.getData();
        JSONObject productData = JSON.parseObject(productDataStr);
        log.info("调用商品服务获取的商品信息:{}",productData);

        //3.判断优惠券是否在使用时间范围内
        long today = new Date().getTime();
        long startTime = couponData.getDate("startTime").getTime();
        long endTime = couponData.getDate("endTime").getTime();

        if(startTime>today || endTime<today){
            throw new RuntimeException("当前优惠券不在可用范围内");
        }

        //4.验证价格
        BigDecimal amount = couponData.getBigDecimal("amount");
        BigDecimal price = productData.getBigDecimal("price");

        if(!price.subtract(amount).equals(orderConfirmRequest.getTotalAmount())){
            throw new RuntimeException("订单验价失败");
        }

        //5.生成订单
        log.info("当前订单购买的商品为:{},原价为:{},本次消耗优惠券:{},实际支付金额:{}",
                productData.getString("name"),productData.getBigDecimal("price"),couponData.getString("name"),orderConfirmRequest.getTotalAmount());

    }
}

​ 测试全流程,启动三个微服务

在这里插入图片描述
在这里插入图片描述

​ 至此,由GRPC整合微服务,实现远程通信就已经完成了,当然下单业务不止这么简单,中间还有防重提交,调用三方支付,延时关单等等一些列复杂的业务,这里只是给大家演示一下怎末用GRPC代替feign实现微服务之间的远程通信。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140204.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

足球视频AI(三)——YOLOV7目标检测自训练模型

一、基础概念 YoloV7提供的yolov7-tiny.onnx 对于图像中包含较大尺寸的足球检测准确率高。 但在实际应用中&#xff0c;足球视频中的足球非常小&#xff0c;默认的模型难于满足实际的足球检测需求。 1.1 识别目标 1&#xff09;固定机位的视频中足球的逐帧识别 1.2 实现思…

邮箱2023系统

邮箱2023系统 前言 VMMail作为一款开源的邮件系统&#xff0c;目前已经发布到了10.0版本。 该版本在 GitHub上是免费的&#xff0c;且代码也是开源的&#xff0c;所以该程序不会对 GitHub上的所有用户造成任何影响。 由于 VMMail开发时采用了开源代码&#xff0c;并在 GitHub上…

区块链之bolt数据库持久化与基本功能完善

文章目录bolt数据库安装使用bolt进行持久化存储bolt持久化的基本步骤创世区块的持久化新增区块的持久化完善区块链基本功能创世区块创建增加区块遍历区块链链接&#xff1a; 区块链项目github地址项目目前进度&#xff1a;bolt数据库安装 bolt数据库介绍&#xff1a; bolt数据…

Vue3过渡动画实现

文章目录P14Vue3过渡&动画实现过渡动画的使用过渡CSS动画效果同时设置过渡和动画mode和appearanimate.cssgsapgsap实现数字变化认识列表的过渡列表过渡的移动动画列表的交错过渡案例P14Vue3过渡&动画实现 过渡动画的使用 <template><button click"isShow…

进入新组织项目经理如何快速提升自己的影响力?

我们在工作中&#xff0c;经常以“对事不对人”来体现他们的专业性&#xff0c;但是这点并不符合人性。更多时候对人不对事&#xff0c;反倒能提高问题的解决能力。项目经理会发现&#xff0c;很多事情的推进&#xff0c;都建立在和对方的信任的基础上&#xff0c;所以先成为对…

使用Qt开发的linux嵌入式设备监控、管理框架,监测嵌入式设备运行状态,执行远程shell,远程升级,与客户端进行文件传输

linux SPY 简介 使用Qt开发的linux嵌入式设备监控、管理框架 [客户端]&#xff1a;aes_tcp_lib 完整代码下载地址&#xff1a;使用Qt开发的linux嵌入式设备监控、管理框架 开发环境 ubuntu 20Qt 5.12Qt Creator 4.13.1 核心功能 监测嵌入式设备运行状态转发客户端消息,…

划重点!企业在采购管理中应避免的10个错误

一家企业的采购能力在很大程度上取决于其采购管理系统的有效性。当系统运行良好时&#xff0c;那么采购就会相当顺利。如果你使用的是一个低效的系统&#xff0c;那就会导致一大堆常见的采购问题。 无论采购错误的根本原因是什么&#xff0c;任其发展&#xff0c;最终会给企业…

【ZooKeeper】第二章 JavaAPI 操作

【ZooKeeper】第二章 JavaAPI 操作 文章目录【ZooKeeper】第二章 JavaAPI 操作一、Curator 简介二、Curator API1.建立连接2.创建节点3.查询节点4.修改节点5.删除节点6.Watch 事件监听三、分布式锁四、案例&#xff1a;12306售票一、Curator 简介 Curator 是 Apache ZooKeeper…

【云原生进阶之容器】第二章Controller Manager原理2.3节--Reflector分析

1 Reflector组件 1.1 背景 Reflector 是保证 Informer 可靠性的核心组件,在丢失事件,收到异常事件,处理事件失败等多种异常情况下需要考虑的细节很多。单独的listwatcher缺少重新连接和重新同步机制,有可能出现数据不一致问题。其对事件响应是同步的,如果执行复杂的操作会…

把财务分析明白的BI软件有哪些?

有财务分析这一个地狱级的在&#xff0c;什么销售、采购、库存都是渣渣。在财务分析中&#xff0c;指标运算组合可以有多样化的改变&#xff0c;资金来来回回能把人绕晕&#xff0c;一个极不起眼的疏忽都可能导致所有工作推到重来的可怕后果。即使是在擅长做大数据智能可视化分…

MySQL基础命令

MySQL基础命令 创建数据库 Show databases;查看 选择数据库 Use test; 在test数据库中创建表 create table t_stu( id int, name varchar(20) ); 查看表信息 desc t_stu&#xff1b; 添加字段 alter table t_stu add age int; 字段age成功添加 修改字段 alter tab…

springBoot使用rabbitmq并保证消息可靠性

一、理论说明 1.1、数据的丢失问题&#xff0c;可能出现在生产者、MQ、消费者中 1、如下图 1.2、生产者弄丢了数据 1、生产者将数据发送到 RabbitMQ 的时候&#xff0c;可能数据就在半路给搞丢了&#xff0c;因为网络问题啥的&#xff0c;都有可能。此时可以选择用RabbitM…

VS2019打包程序变成带运行环境的安装包

背景 给外行客户写程序的时候&#xff0c;为了避免客户麻烦&#xff0c;我们在写完程序之后&#xff0c;需要把运行环境也打包进安装包中&#xff0c;这样客户就可以一键安装使用。给客户减少麻烦的同时&#xff0c;无疑让我们也有了更多的好评。 步骤 1.写好我们要打包的程…

推荐16个前端必备的实用工具与网站

1. GitHub Desktop 对于新手来说&#xff0c;要记住那么多git命令可能有点困难&#xff0c;建议新手用git可视化工具&#xff0c;会方便很多 2. 图片在线压缩 tinypng 是一个完全免费并且高压缩率的在线压缩图片网站&#xff0c;一般能满足日常大部分压缩图片的需求&#x…

Visual Studio 2022最全安装教程(+背景图设置),一步步教会你如何安装并运行

目录visual studio 2022最全安装教程一、官网下载二、安装启动三、项目测试四、背景图设置 一、官网下载1.点击蓝色链接—->Visual Studio官网&#xff0c;进入之后是这个界面&#xff0c;选择社区版Community下载&#xff08;社区版Community是对个人免费的&#xff0c;一共…

磨金石教育科技技能干货分享|顶级黑白照片长啥样?看后令人震撼

黑白摄影往往在于一些老旧照片中见到&#xff0c;平常的摄影作品往往会强调一些色彩。黑白色调不是常用的色调。但在一些艺术摄影或者创意摄影中&#xff0c;黑白摄影依然是摄影师常用的一种风格。黑白色调往往能够给人一种肃穆、压抑的感觉。那么今天我们就通过一些作品&#…

(一)redis之Nosql数据库简介

一、技术发展 解决功能性的问题&#xff1a;Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN 解决扩展性的问题&#xff1a;Struts、Spring、SpringMVC、Hibernate、Mybatis 解决性能的问题&#xff1a;NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch 1、Web1.0 W…

VMware 基本设置

VMware 基本设置 文章目录VMware 基本设置摘要全局优化内存优先级更新反馈虚拟机系统优化尽可能多的分配资源去掉多余的组件开启3D图形加速和增加显存大小高级设置优化显示关键字&#xff1a; 显示、 内存、 优化、 命令栏、 虚拟机摘要 做Qt开发 久了&#xff0c;跨平台应该…

【BSP视频教程】BSP视频教程第25期:CAN/CANFD/CANopen专题,CAN知识点干货分享, 收发执行过程和错误帧处理(2023-01-03)

视频教程汇总帖&#xff1a;【学以致用&#xff0c;授人以渔】2023视频教程汇总&#xff0c;DSP第10期&#xff0c;ThreadX第5期&#xff0c;BSP驱动第25期&#xff0c;USB实战第5期&#xff0c;GUI实战第3期&#xff08;2023-01-03&#xff09; - STM32F429 - 硬汉嵌入式论坛 …

梦之光芒Monyer (全关解析)

目录 前言: 第0关 描述: 过程: 第1关 描述: 过程: 第2关 描述: 过程: 第3关 描述: 过程: 第4关 描述: 过程: 第5关 描述: 过程: 第6关 描述: 过程: 第7关 描述: 过程: 第8关 描述: 过程: 第9关 描述: 过程: 第10关 描述: 过程: 第11关 描…