grpc使用小结

背景:
项目采用DDD架构,接口都是grpc风格,
所以拿到request后,需要将grpc对象转换为DDD中的domain对象;
在response中,将domain对象转换为grpc对象。
mapstuct版本

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>

1.grpc对象中的基本数据类型无法传递null值

解决办法:引入google的包装类
import "google/protobuf/wrappers.proto";

eg:

点击查看proto文件
syntax = "proto3";
package userService;
option java_package = "com.grpc.auto.UserService";
option java_multiple_files = true;

import "google/protobuf/wrappers.proto";

service UserService {
  rpc addUser(AddUserRequest) returns (AddUserResponse);
}

message AddUserRequest {
  string userName = 1;
  int32 age = 2;
  int64 registryTime = 3;
  google.protobuf.StringValue userNameWrapper = 4;
  google.protobuf.Int32Value ageWrapper = 5;
  google.protobuf.Int64Value registryTimeWrapper = 6;
}

message AddUserResponse {
  google.protobuf.StringValue code = 1;
  google.protobuf.StringValue message = 2;
  google.protobuf.StringValue data = 3;
}
点击查看grpc接口
import com.google.protobuf.StringValue;
import com.grpc.auto.UserService.AddUserRequest;
import com.grpc.auto.UserService.AddUserResponse;
import com.grpc.auto.UserService.AddUserResponse.Builder;
import com.grpc.auto.UserService.UserServiceGrpc.UserServiceImplBase;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
@Slf4j
public class UserInputAdapter extends UserServiceImplBase {

    @Override
    public void addUser(AddUserRequest request, StreamObserver<AddUserResponse> responseObserver) {
        Builder builder = AddUserResponse.newBuilder()
                .setCode(StringValue.of("200"))
                .setMessage(StringValue.of("success"))
                .setData(StringValue.of("user Data."));

        responseObserver.onNext(builder.build());
        responseObserver.onCompleted();
    }
}

正常调用
image
image

基础类型不管是传null还是不传递都有值(String是"",其他类型是默认值)
包装类型传null或者不传递就可以为null
image
image

2.统一的响应体

想要提供给调用方一个统一的响应体,那就要用到泛型 ~ grpc里的Any.
目前我还没想到更好的方式, 所以结果是list和 结果是单个对象的我暂时用两个字段来接收了。

eg:

点击查看proto文件
syntax = "proto3";
package userService;
option java_package = "com.grpc.auto.UserService";
option java_multiple_files = true;

import "google/protobuf/wrappers.proto";
import "google/protobuf/any.proto";

service UserService {
  rpc addUser(AddUserRequest) returns (AddUserResponse);
}

message AddUserRequest {
  string userName = 1;
  int32 age = 2;
  int64 registryTime = 3;
  google.protobuf.StringValue userNameWrapper = 4;
  google.protobuf.Int32Value ageWrapper = 5;
  google.protobuf.Int64Value registryTimeWrapper = 6;
}

message AddUserResponse {
  google.protobuf.StringValue code = 1;
  google.protobuf.StringValue message = 2;
  google.protobuf.Any data = 3;
  repeated google.protobuf.Any datas = 4;
}

点击查看grpc接口
    @Override
    public void addUser(AddUserRequest request, StreamObserver<AddUserResponse> responseObserver) {
        Builder builder = AddUserResponse.newBuilder()
                .setCode(StringValue.of("200"))
                .setMessage(StringValue.of("success"))
                .setData(Any.pack(StringValue.of("user Data.")))
                .addAllDatas(List.of(
                        Any.pack(StringValue.of("user Data111111111.")),
                        Any.pack(StringValue.of("user Data222222222."))
                ));

        responseObserver.onNext(builder.build());
        responseObserver.onCompleted();
    }
点击查看response
{
    "datas": [
        {
            "value": "user Data111111111.",
            "@type": "type.googleapis.com/google.protobuf.StringValue"
        },
        {
            "value": "user Data222222222.",
            "@type": "type.googleapis.com/google.protobuf.StringValue"
        }
    ],
    "code": {
        "value": "200"
    },
    "message": {
        "value": "success"
    },
    "data": {
        "value": "user Data.",
        "@type": "type.googleapis.com/google.protobuf.StringValue"
    }
}

3.mapstruct与grpc配合使用

  1. mapstruct公共配置
@MapperConfig(
        componentModel = "spring",
        unmappedTargetPolicy = ReportingPolicy.IGNORE,
        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
        nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,
        uses = MapstructUtil.class
)
public class MapstructConfig {
}
  1. mapstruct的一个工具类,提供了一些简单的转换方法
点击查看代码
package xxx;

import com.google.protobuf.DoubleValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
import com.google.protobuf.StringValue;
import com.google.protobuf.StringValue.Builder;
import org.mapstruct.Named;
import org.springframework.stereotype.Component;

import java.sql.Timestamp;

/**
 * Author:yangkang
 * Date:2025/3/26  15:34
 * Description: 进入这个类的参数应该使用其他注解确保进入时已经进行了null检查,此处无需进行null校验。
 */

@Component
public class MapstructUtil {

    // =================  grpcValue类型装箱  ==========

    @Named("pack")
    public static StringValue pack(String string) {
        Builder builder = StringValue.newBuilder();
        builder.setValue(string);
        return builder.build();
    }

    @Named("pack")
    public static Int32Value pack(Integer integer) {
        Int32Value.Builder builder = Int32Value.newBuilder();
        builder.setValue(integer);
        return builder.build();
    }

    @Named("pack")
    public static Int64Value pack(Long longParam) {
        Int64Value.Builder builder = Int64Value.newBuilder();
        builder.setValue(longParam);
        return builder.build();
    }

    @Named("packTimestamp")
    public static Int64Value packTimestamp(Timestamp timestamp) {
        Int64Value.Builder builder = Int64Value.newBuilder();
        builder.setValue(timestamp.getTime());
        return builder.build();
    }

    @Named("pack")
    public static DoubleValue pack(Double doubleParam) {
        DoubleValue.Builder builder = DoubleValue.newBuilder();
        builder.setValue(doubleParam);
        return builder.build();
    }

    // =================  grpcValue类型拆箱  ==========


    @Named("unpack")
    public static String unpack(StringValue stringValue) {
        return stringValue.getValue();
    }

    @Named("unpack")
    public static Integer unpack(Int32Value int32Value) {
        return int32Value.getValue();
    }

    @Named("unpack")
    public static Long unpack(Int64Value int64Value) {
        return int64Value.getValue();
    }

    @Named("unpack")
    public static Double unpack(DoubleValue doubleValue) {
        return doubleValue.getValue();
    }

    @Named("unpackTimestamp")
    public static Timestamp unpackTimeStamp(Int64Value time) {
        return new Timestamp(time.getValue());
    }


    // =================  其他  ==========

    @Named("longToTimestamp")
    public Timestamp longToTimestamp(long time) {
        return time != 0L ? new Timestamp(time) : null;
    }

    @Named("longValueToTimestamp")
    public Timestamp longValueToTimestamp(Long time) {
        return time == null ? null : new Timestamp(time);
    }

    @Named("timestampToLong")
    public long timestampToLong(Timestamp timestamp) {
        return timestamp != null ? timestamp.getTime() : 0L;
    }

    @Named("timestampToLongValue")
    public Long timestampToLongValue(Timestamp timestamp) {
        return timestamp != null ? timestamp.getTime() : null;
    }

}

  1. 在mapper中使用配置类
点击查看代码
@Mapper(config = MapstructConfig.class, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface RealTimeModelChartDtoConvert {

    // 两个对象中都有一个double类型的字段 doubleField,不过都是包装类型
    @Mapping(target = "doubleField", qualifiedByName = "pack")
    TestGrpc toTestGrpc(TestDto testDto);
}

4.grpc对象的打印

点击查看代码
@Slf4j
public class GrpcUtil {

    /**
     * 转成有格式的json字符串
     */
    public static String toJsonStr(Object obj) {
        if (obj == null) {
            return null;
        }

        try {
            if (obj instanceof MessageOrBuilder message) {
                Printer printer = JsonFormat.printer().includingDefaultValueFields();

                TypeRegistry registry = TypeRegistry.newBuilder().add(message.getDescriptorForType()).build();
                printer = printer.usingTypeRegistry(registry);

                return printer.print(message);
            } else {
                log.warn("message is not a protobuf Message");
            }
        } catch (Exception e) {
            log.error("error when execute toJsonStr.", e);
        }
        return null;
    }

    /**
     * 转成无格式的单行json字符串
     */
    public static String toJsonStrSimple(Object obj) {
        if (obj == null) {
            return null;
        }

        try {
            if (obj instanceof MessageOrBuilder message) {
                Printer printer = JsonFormat.printer().includingDefaultValueFields().omittingInsignificantWhitespace();

                TypeRegistry registry = TypeRegistry.newBuilder().add(message.getDescriptorForType()).build();
                printer = printer.usingTypeRegistry(registry);

                return printer.print(message);
            } else {
                log.warn("message is not a protobuf Message");
            }
        } catch (Exception e) {
            log.error("error when execute toJsonStr.", e);
        }
        return null;
    }
}
posted @ 2025-08-28 20:09  记得看海  阅读(20)  评论(0)    收藏  举报