Grpc项目集成到java方式调用实践

背景:由于项目要对接到grcp 的框架,然后需要对接老外的东西,还有签名和证书刚开始没有接触其实有点懵逼。

gRPC 是由 Google 开发的高性能、开源的远程过程调用(RPC)框架。它建立在 HTTP/2 协议之上,使用 Protocol Buffers 作为其接口定义语言(IDL)。gRPC 被设计为可跨多种环境(包括客户端、服务器和各种语言)使用,支持多种编程语言,如 C、C++、Java、Go、Python、Ruby、Node.js 等。

gRPC 具有以下特点:

  1. 性能高效:gRPC 使用基于 HTTP/2 的传输协议,能够复用连接、多路复用请求、支持头部压缩等特性,从而提高了性能。

  2. 跨语言支持:gRPC 支持多种编程语言,使得客户端和服务器可以使用不同的编程语言实现,并且它们之间的通信依然高效。

  3. IDL 和自动代码生成:gRPC 使用 Protocol Buffers 作为接口定义语言(IDL),定义服务接口和消息格式,并提供了自动生成客户端和服务器代码的工具。

  4. 多种调用方式:gRPC 支持四种调用方式,包括简单 RPC、服务器流式 RPC、客户端流式 RPC 和双向流式 RPC,可以根据业务需求选择合适的调用方式。

  5. 安全性:gRPC 支持 TLS/SSL 加密传输,保障通信的安全性。

  6. 插件机制:gRPC 提供了插件机制,可以扩展其功能,例如添加认证、日志、监控等功能。

总的来说,gRPC 是一个强大的远程过程调用框架,适用于构建分布式系统中的各种服务,并且在性能、跨语言支持和安全性方面具有很多优势。

一,开发工具集成:

安装指定插件,网上说还要Protobuf buffer 安装,但是我用的idea2018的版本搜索不到,这个不是必须的,可以不用。

二 protobuf编译器一定要安装配置一个: Releases · protocolbuffers/protobuf · GitHub

三,安装好插件后就可以看的了这俩个就是生产java代码的。

四,grpc 的文件格式:

syntax = "proto3";

package c3_vanilla_app;

option java_package = "com.test.conncar.c3vanillaapp";
option java_outer_classname = "C3VanillaAppProtos";

import "google/protobuf/empty.proto";


message ExampleRequestMessage {
  string id = 1;
  int64 timestamp = 2;
  string message = 3;
}

message ExampleResponseMessage {
  string message = 1;
}

service ExampleService {
  rpc PostExampleMessages(ExampleRequestMessage) returns (ExampleResponseMessage) {}
}

生成出来的代码就是:

可以用命令生成,然后也可以用maven自动生成

pom加上这个插件就可以生产代码了:

 <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

完整的pom文件:

 <properties>
        <protoc.version>3.25.2</protoc.version>
        <grpc.version>1.61.1</grpc.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <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.grpc</groupId>
            <artifactId>grpc-core</artifactId>
            <version>${grpc.version}</version>

        </dependency>

        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.25.2</version> <!-- 或者与你的 protoc.version 相匹配的版本 -->
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <!-- necessary for Java 9+ -->
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
        </dependency>
    </dependencies>
    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}: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>3.10.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jfrog.buildinfo</groupId>
                <artifactId>artifactory-maven-plugin</artifactId>
                <version>3.3.0</version>
                <inherited>false</inherited>
                <executions>
                    <execution>
                        <id>build-info</id>
                        <goals>
                            <goal>publish</goal>
                        </goals>
                        <configuration>
                            <artifactory>
                                <includeEnvVars>false</includeEnvVars>
                                <timeoutSec>60</timeoutSec>
                                <propertiesFile>publish.properties</propertiesFile>
                            </artifactory>
                            <publisher>
                                <contextUrl>${env.ARTIFACTORY_HOST_POM}</contextUrl>
                                <username>${env.ARTIFACTORY_CCAR_USER}</username>
                                <password>${env.ARTIFACTORY_CCAR_APIKEY}</password>
                                <excludePatterns>*-tests.jar</excludePatterns>
                                <repoKey>${CI_PROJECT_NAMESPACE}</repoKey>
                                <snapshotRepoKey>${CI_PROJECT_NAMESPACE}</snapshotRepoKey>
                            </publisher>
                            <buildInfo>
                                <buildName>${CI_PROJECT_NAME}</buildName>
                                <buildNumber>${CI_PIPELINE_ID}</buildNumber>
                                <buildUrl>${CI_PROJECT_URL}</buildUrl>
                            </buildInfo>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>

        </plugins>
    </build>

生成的代码路径:

生成的代码,记得install的时候注释掉插件,然后进行install

然后本地写个client和sever就行了:

server:

package com.grpc.mistra.server;


import com.grpc.mistra.generate.MistraRequest;
import com.grpc.mistra.generate.MistraResponse;
import com.grpc.mistra.generate.MistraServiceGrpc;
import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;

/**
 * Time: 14:46
 * Description:
 */
public class MistraServer {

    private int port = 8001;
    private Server server;

    private void start() throws IOException {
        server = ServerBuilder.forPort(port)
                .addService((BindableService) new MistraHelloWorldImpl())
                .build()
                .start();

        System.out.println("------------------- 服务端服务已开启,等待客户端访问 -------------------");

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {

                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                MistraServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        final MistraServer server = new MistraServer();
        //启动服务
        server.start();
        //服务一直在线,不关闭
        server.blockUntilShutdown();
    }

    // 定义一个实现服务接口的类
    private class MistraHelloWorldImpl extends MistraServiceGrpc.MistraServiceImplBase {

        @Override
        public void sayHello(MistraRequest mistraRequest, StreamObserver<MistraResponse> responseObserver) {
            // 具体其他丰富的业务实现代码
            System.err.println("server:" + mistraRequest.getName());
            MistraResponse reply = MistraResponse.newBuilder().setMessage(("响应信息: " + mistraRequest.getName())).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        }
    }
}

client:

package com.grpc.mistra.client;


import com.grpc.mistra.generate.MistraRequest;
import com.grpc.mistra.generate.MistraResponse;
import com.grpc.mistra.generate.MistraServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

import java.util.concurrent.TimeUnit;

/**
 * Time: 14:46
 * Description:
 */
public class MistraClient {

    private final ManagedChannel channel;
    private final MistraServiceGrpc.MistraServiceBlockingStub blockingStub;

    public MistraClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext(true)
                .build();

        blockingStub = MistraServiceGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void greet(String name) {
        MistraRequest request = MistraRequest.newBuilder().setName(name).build();
        MistraResponse response = blockingStub.sayHello(request);
        System.out.println(response.getMessage());

    }

    public static void main(String[] args) throws InterruptedException {
        MistraClient client = new MistraClient("127.0.0.1", 8001);
        System.out.println("-------------------客户端开始访问请求-------------------");
        for (int i = 0; i < 10; i++) {
            client.greet("你若想生存,绝处也能缝生: " + i);
        }
    }
}

效果图:

grpc的调用类:

package com.grpc.mistra.generate;

import static io.grpc.MethodDescriptor.generateFullMethodName;
import static io.grpc.stub.ClientCalls.asyncUnaryCall;
import static io.grpc.stub.ClientCalls.blockingUnaryCall;
import static io.grpc.stub.ClientCalls.futureUnaryCall;
import static io.grpc.stub.ServerCalls.asyncUnaryCall;
import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;

/**
 * <pre>
 * 声明一个服务名称
 * </pre>
 */
@javax.annotation.Generated(
        value = "by gRPC proto compiler (version 1.11.0)",
        comments = "Source: helloworld.proto")
public final class MistraServiceGrpc {

    private MistraServiceGrpc() {
    }

    public static final String SERVICE_NAME = "mistra.MistraService";

    // Static method descriptors that strictly reflect the proto.
    @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901")
    @Deprecated // Use {@link #getSayHelloMethod()} instead.
    public static final io.grpc.MethodDescriptor<MistraRequest,
            MistraResponse> METHOD_SAY_HELLO = getSayHelloMethodHelper();

    private static volatile io.grpc.MethodDescriptor<MistraRequest,
            MistraResponse> getSayHelloMethod;

    @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/1901")
    public static io.grpc.MethodDescriptor<MistraRequest,
            MistraResponse> getSayHelloMethod() {
        return getSayHelloMethodHelper();
    }

    private static io.grpc.MethodDescriptor<MistraRequest,
            MistraResponse> getSayHelloMethodHelper() {
        io.grpc.MethodDescriptor<MistraRequest, MistraResponse> getSayHelloMethod;
        if ((getSayHelloMethod = MistraServiceGrpc.getSayHelloMethod) == null) {
            synchronized (MistraServiceGrpc.class) {
                if ((getSayHelloMethod = MistraServiceGrpc.getSayHelloMethod) == null) {
                    MistraServiceGrpc.getSayHelloMethod = getSayHelloMethod =
                            io.grpc.MethodDescriptor.<MistraRequest, MistraResponse>newBuilder()
                                    .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
                                    .setFullMethodName(generateFullMethodName(
                                            "mistra.MistraService", "SayHello"))
                                    .setSampledToLocalTracing(true)
                                    .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
                                            MistraRequest.getDefaultInstance()))
                                    .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
                                            MistraResponse.getDefaultInstance()))
                                    .setSchemaDescriptor(new MistraServiceMethodDescriptorSupplier("SayHello"))
                                    .build();
                }
            }
        }
        return getSayHelloMethod;
    }

    /**
     * Creates a new async stub that supports all call types for the service
     */
    public static MistraServiceStub newStub(io.grpc.Channel channel) {
        return new MistraServiceStub(channel);
    }

    /**
     * Creates a new blocking-style stub that supports unary and streaming output calls on the service
     */
    public static MistraServiceBlockingStub newBlockingStub(
            io.grpc.Channel channel) {
        return new MistraServiceBlockingStub(channel);
    }

    /**
     * Creates a new ListenableFuture-style stub that supports unary calls on the service
     */
    public static MistraServiceFutureStub newFutureStub(
            io.grpc.Channel channel) {
        return new MistraServiceFutureStub(channel);
    }

    /**
     * <pre>
     * 声明一个服务名称
     * </pre>
     */
    public static abstract class MistraServiceImplBase implements io.grpc.BindableService {

        /**
         * <pre>
         * 请求参数MistraRequest   响应参数MistraResponse
         * </pre>
         */
        public void sayHello(MistraRequest request,
                             io.grpc.stub.StreamObserver<MistraResponse> responseObserver) {
            asyncUnimplementedUnaryCall(getSayHelloMethodHelper(), responseObserver);
        }

        @Override
        public final io.grpc.ServerServiceDefinition bindService() {
            return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
                    .addMethod(
                            getSayHelloMethodHelper(),
                            asyncUnaryCall(
                                    new MethodHandlers<
                                            MistraRequest,
                                            MistraResponse>(
                                            this, METHODID_SAY_HELLO)))
                    .build();
        }
    }

    /**
     * <pre>
     * 声明一个服务名称
     * </pre>
     */
    public static final class MistraServiceStub extends io.grpc.stub.AbstractStub<MistraServiceStub> {
        private MistraServiceStub(io.grpc.Channel channel) {
            super(channel);
        }

        private MistraServiceStub(io.grpc.Channel channel,
                                  io.grpc.CallOptions callOptions) {
            super(channel, callOptions);
        }

        @Override
        protected MistraServiceStub build(io.grpc.Channel channel,
                                          io.grpc.CallOptions callOptions) {
            return new MistraServiceStub(channel, callOptions);
        }

        /**
         * <pre>
         * 请求参数MistraRequest   响应参数MistraResponse
         * </pre>
         */
        public void sayHello(MistraRequest request,
                             io.grpc.stub.StreamObserver<MistraResponse> responseObserver) {
            asyncUnaryCall(
                    getChannel().newCall(getSayHelloMethodHelper(), getCallOptions()), request, responseObserver);
        }
    }

    /**
     * <pre>
     * 声明一个服务名称
     * </pre>
     */
    public static final class MistraServiceBlockingStub extends io.grpc.stub.AbstractStub<MistraServiceBlockingStub> {
        private MistraServiceBlockingStub(io.grpc.Channel channel) {
            super(channel);
        }

        private MistraServiceBlockingStub(io.grpc.Channel channel,
                                          io.grpc.CallOptions callOptions) {
            super(channel, callOptions);
        }

        @Override
        protected MistraServiceBlockingStub build(io.grpc.Channel channel,
                                                  io.grpc.CallOptions callOptions) {
            return new MistraServiceBlockingStub(channel, callOptions);
        }

        /**
         * <pre>
         * 请求参数MistraRequest   响应参数MistraResponse
         * </pre>
         */
        public MistraResponse sayHello(MistraRequest request) {
            return blockingUnaryCall(
                    getChannel(), getSayHelloMethodHelper(), getCallOptions(), request);
        }
    }

    /**
     * <pre>
     * 声明一个服务名称
     * </pre>
     */
    public static final class MistraServiceFutureStub extends io.grpc.stub.AbstractStub<MistraServiceFutureStub> {
        private MistraServiceFutureStub(io.grpc.Channel channel) {
            super(channel);
        }

        private MistraServiceFutureStub(io.grpc.Channel channel,
                                        io.grpc.CallOptions callOptions) {
            super(channel, callOptions);
        }

        @Override
        protected MistraServiceFutureStub build(io.grpc.Channel channel,
                                                io.grpc.CallOptions callOptions) {
            return new MistraServiceFutureStub(channel, callOptions);
        }

        /**
         * <pre>
         * 请求参数MistraRequest   响应参数MistraResponse
         * </pre>
         */
        public com.google.common.util.concurrent.ListenableFuture<MistraResponse> sayHello(
                MistraRequest request) {
            return futureUnaryCall(
                    getChannel().newCall(getSayHelloMethodHelper(), getCallOptions()), request);
        }
    }

    private static final int METHODID_SAY_HELLO = 0;

    private static final class MethodHandlers<Req, Resp> implements
            io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
            io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
            io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
            io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
        private final MistraServiceImplBase serviceImpl;
        private final int methodId;

        MethodHandlers(MistraServiceImplBase serviceImpl, int methodId) {
            this.serviceImpl = serviceImpl;
            this.methodId = methodId;
        }

        @Override
        @SuppressWarnings("unchecked")
        public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
            switch (methodId) {
                case METHODID_SAY_HELLO:
                    serviceImpl.sayHello((MistraRequest) request,
                            (io.grpc.stub.StreamObserver<MistraResponse>) responseObserver);
                    break;
                default:
                    throw new AssertionError();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public io.grpc.stub.StreamObserver<Req> invoke(
                io.grpc.stub.StreamObserver<Resp> responseObserver) {
            switch (methodId) {
                default:
                    throw new AssertionError();
            }
        }
    }

    private static abstract class MistraServiceBaseDescriptorSupplier
            implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
        MistraServiceBaseDescriptorSupplier() {
        }

        @Override
        public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
            return MistraProto.getDescriptor();
        }

        @Override
        public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
            return getFileDescriptor().findServiceByName("MistraService");
        }
    }

    private static final class MistraServiceFileDescriptorSupplier
            extends MistraServiceBaseDescriptorSupplier {
        MistraServiceFileDescriptorSupplier() {
        }
    }

    private static final class MistraServiceMethodDescriptorSupplier
            extends MistraServiceBaseDescriptorSupplier
            implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
        private final String methodName;

        MistraServiceMethodDescriptorSupplier(String methodName) {
            this.methodName = methodName;
        }

        @Override
        public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
            return getServiceDescriptor().findMethodByName(methodName);
        }
    }

    private static volatile io.grpc.ServiceDescriptor serviceDescriptor;

    public static io.grpc.ServiceDescriptor getServiceDescriptor() {
        io.grpc.ServiceDescriptor result = serviceDescriptor;
        if (result == null) {
            synchronized (MistraServiceGrpc.class) {
                result = serviceDescriptor;
                if (result == null) {
                    serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
                            .setSchemaDescriptor(new MistraServiceFileDescriptorSupplier())
                            .addMethod(getSayHelloMethodHelper())
                            .build();
                }
            }
        }
        return result;
    }
}

咋们就完成了grpc接入到java里面调用了本质上其实代码没有区别,小杨看了一下,他就是多了一些设计模式生产的代码,工厂构建等。 

 ————没有与生俱来的天赋,都是后天的努力拼搏(我是小杨,谢谢你的关注和支持)

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java + gRPC + grpc-gateway 的实践主要分为以下几个步骤: 1. 定义 proto 文件 在 proto 文件中定义需要调用的服务以及方法,同时指定请求和响应的数据类型。例如: ``` syntax = "proto3"; package example; service ExampleService { rpc ExampleMethod (ExampleRequest) returns (ExampleResponse) {} } message ExampleRequest { string example_field = 1; } message ExampleResponse { string example_field = 1; } ``` 2. 使用 protoc 编译 proto 文件 使用 protoc 编译 proto 文件,生成 Java 代码。例如: ``` protoc --java_out=./src/main/java ./example.proto ``` 3. 实现 gRPC 服务 在 Java 代码中实现定义的 gRPC 服务,例如: ``` public class ExampleServiceImpl extends ExampleServiceGrpc.ExampleServiceImplBase { @Override public void exampleMethod(ExampleRequest request, StreamObserver<ExampleResponse> responseObserver) { // 实现具体逻辑 ExampleResponse response = ExampleResponse.newBuilder().setExampleField("example").build(); responseObserver.onNext(response); responseObserver.onCompleted(); } } ``` 4. 启动 gRPC 服务器 使用 gRPC 提供的 ServerBuilder 构建 gRPC 服务器,并启动服务器。例如: ``` Server server = ServerBuilder.forPort(8080).addService(new ExampleServiceImpl()).build(); server.start(); ``` 5. 集成 grpc-gateway 使用 grpc-gateway 可以将 gRPC 服务转换为 HTTP/JSON API。在 proto 文件中添加以下内容: ``` import "google/api/annotations.proto"; service ExampleService { rpc ExampleMethod (ExampleRequest) returns (ExampleResponse) { option (google.api.http) = { post: "/example" body: "*" }; } } ``` 在 Java 代码中添加以下内容: ``` Server httpServer = ServerBuilder.forPort(8081).addService(new ExampleServiceImpl()).build(); httpServer.start(); String grpcServerUrl = "localhost:8080"; String httpServerUrl = "localhost:8081"; ProxyServerConfig proxyConfig = new ProxyServerConfig(grpcServerUrl, httpServerUrl, "/example"); HttpProxyServer httpProxyServer = new HttpProxyServer(proxyConfig); httpProxyServer.start(); ``` 6. 测试 使用 HTTP/JSON API 调用 gRPC 服务,例如: ``` POST http://localhost:8081/example Content-Type: application/json { "example_field": "example" } ``` 以上就是 Java + gRPC + grpc-gateway 的实践步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小杨互联网

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值