Admin Core

gRPC 远程通讯开发指南

本文档为开发者提供标准化的 gRPC 接口开发指南,涵盖从协议定义、服务实现到测试验证的全流程规范。适用于 .NET 技术栈的微服务场景,确保跨服务通信的高效性、兼容性和可维护性。

本文档为开发者提供标准化的 gRPC 接口开发指南,涵盖从协议定义、服务实现到测试验证的全流程规范。适用于 .NET 技术栈的微服务场景,确保跨服务通信的高效性、兼容性和可维护性。


1. 系统内置类型说明

1.1 基础类型示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/// <summary>
/// Proto 长整型(解决 gRPC 原生不支持 long 类型)
/// </summary>
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class ProtoLong
{
public long Value { get; set; }

public ProtoLong() { }

public ProtoLong(long value) => Value = value;

public static implicit operator ProtoLong(long value) => new(value);
public static implicit operator long(ProtoLong result) => result.Value;
}

/// <summary>
/// gRPC 输出(标准化响应包装器)
/// </summary>
[ProtoContract(ImplicitFields = ImplicitFields.None)]
public class GrpcOutput<T>
{
[ProtoMember(1)]
public bool Success { get; set; }

[ProtoMember(2)]
public string Code { get; set; }

[ProtoMember(3)]
public string Msg { get; set; }

[ProtoMember(4)]
public T Data { get; set; }
}

1.2 系统基础类型对照表

类型名称 原始类型 使用场景 序列化说明
ProtoBoolean bool 布尔值传输 直接映射true/false
ProtoDateTime DateTime 日期时间传输(UTC格式) 使用 ISO8601 字符串格式
ProtoDecimal decimal 高精度数值传输 转换为字符串避免精度丢失
ProtoInt int 整型数值传输 直接映射 32 位整数
ProtoList<T> List<T> 集合类型传输 支持泛型集合序列化
ProtoString string 字符串传输 UTF-8 编码
ProtoLong long 长整型传输 解决 int64 边界问题
GrpcOutput<T> T 标准化响应格式 包含业务状态和数据的包装器

2. 请求/响应模型

2.1 输入模型示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MyCompanyName.Modules.Member.Api.Contracts/GrpcServices/Module/Input/ModuleAddGrpcInput.cs

using ProtoBuf;

namespace MyCompanyName.Modules.Member.Api.Contracts.Services.Module.Input;

/// <summary>
/// 模块创建请求参数
/// </summary>
[ProtoContract(ImplicitFields = ImplicitFields.None)]
public class ModuleAddGrpcInput
{
/// <summary>
/// 模块名称
/// </summary>
[ProtoMember(1)]
public string Name { get; set; }

/// <summary>
/// 创建时间
/// </summary>
[ProtoMember(2)]
public ProtoDateTime CreateTime { get; set; }
}

2.2 ImplicitFields 枚举说明

枚举成员 描述 推荐场景
None 不进行隐式标记分配,所有成员需显式使用[ProtoMember] 属性指定标记 推荐,提供最高稳定性和可控性,即使成员名称变化也不会影响序列化
AllPublic 所有公共成员(属性和字段)按字母顺序隐式分配标记,从ImplicitFirstTag 开始 当需要将公共 API 作为契约、且成员名称稳定不变时使用
AllFields 所有字段(包括公共和非公共字段)按字母顺序隐式分配标记 用于序列化内部状态,通常用于实现序列化而非公共 API

使用建议:若需稳定的序列化和反序列化,推荐使用 [ProtoMember] 显式分配标记。若希望快速实现且成员名称不会频繁变化,可使用 ImplicitFields.AllPublic

2.3 模型定义规范

  • 所有 DTO 必须使用 [ProtoContract][ProtoMember] 标记
  • ProtoMember 编号必须连续且唯一,从 1 开始递增
  • 禁止修改已发布字段的编号,新增成员使用后续数字
  • 枚举类型必须指定 [ProtoContract][ProtoMember(1)]

3. 服务接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyCompanyName.Modules.Member.Api.Contracts/GrpcServices/Module/IModuleGrpcService.cs

using MyCompanyName.Modules.Member.Api.Contracts.Services.Module.Input;
using MyCompanyName.Modules.Member.Api.Core.Consts;
using ProtoBuf.Grpc;
using System.ServiceModel;
using System.Threading.Tasks;
using ZhonTai.Admin.Core.Protos;

namespace MyCompanyName.Modules.Member.Api.Contracts.GrpcServices.Module;

/// <summary>
/// 模块 gRPC 服务接口
/// </summary>
[ServiceContract(ConfigurationName = ApiConsts.AreaName)]
public interface IModuleGrpcService
{
/// <summary>
/// 创建模块
/// </summary>
/// <param name="input">创建参数</param>
/// <param name="context">调用上下文</param>
/// <returns>模块ID</returns>
[OperationContract]
Task<GrpcOutput<ProtoLong>> AddAsync(ModuleAddGrpcInput input, CallContext context = default);
}

属性说明

属性 说明
ConfigurationName 用于自动化客户端生成和服务发现,值需与ApiConsts.AreaName 常量保持一致
OperationContract 可选,隐式继承自基类,标识可远程调用的服务方法

4. 服务实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// MyCompanyName.Modules.Member.Api/GrpcServices/Module/ModuleGrpcService.cs

using ProtoBuf.Grpc;
using System.Threading.Tasks;
using ZhonTai.Admin.Core.Protos;
using Mapster;
using MyCompanyName.Modules.Member.Api.Contracts.Services.Module;
using MyCompanyName.Modules.Member.Api.Contracts.Services.Module.Input;
using MyCompanyName.Modules.Member.Api.Contracts.GrpcServices.Module;

namespace MyCompanyName.Modules.Member.Api.GrpcServices.Module;

public class ModuleGrpcService : IModuleGrpcService
{
private readonly IModuleService _moduleService;

public ModuleGrpcService(IModuleService moduleService)
{
_moduleService = moduleService;
}

public async Task<GrpcOutput<ProtoLong>> AddAsync(ModuleAddGrpcInput input, CallContext context)
{
try
{
var id = await _moduleService.AddAsync(input.Adapt<ModuleAddInput>());
return new GrpcOutput<ProtoLong> { Success = true, Data = id };
}
catch (Exception ex)
{
return new GrpcOutput<ProtoLong> { Code = "", Msg = ex.Message };
}
}
}

5. 跨模块调用 gRPC 服务

5.1 添加项目引用

MyCompanyName.Modules.Biz.Api 接口库的 .csproj 文件中添加对目标模块契约项目的引用:

1
2
3
<ItemGroup>
<ProjectReference Include="..\..\MyCompanyName.Modules.Member\MyCompanyName.Modules.Member.Api.Contracts\MyCompanyName.Modules.Member.Api.Contracts.csproj" />
</ItemGroup>

5.2 配置 appsettings.json

MyCompanyName.Modules.Biz.Host 项目的 appsettings.json 中添加 gRPC 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"RpcConfig": {
"Http": {
"Enable": true,
"AssemblyNames": [ "ZhonTai.Admin.Contracts" ]
},
"Grpc": {
"Enable": true,
"AssemblyNames": [
"ZhonTai.Admin.Core",
"MyCompanyName.Modules.Member.Api.Contracts"
],
"ServerAssemblyNames": []
},
"Endpoints": [
{
"Name": "admin",
"HttpUrl": "http://localhost:18010",
"GrpcUrl": "http://localhost:18011"
},
{
"Name": "mem",
"HttpUrl": "http://localhost:18020",
"GrpcUrl": "http://localhost:18021"
}
]
}

配置项说明

配置项 说明
Grpc.AssemblyNames 添加要扫描的 gRPC 客户端程序集(gRPC 服务接口所在的程序集)
Grpc.ServerAssemblyNames 添加要扫描的 gRPC 服务端程序集(gRPC 服务实现所在的程序集)
Endpoints 声明目标服务的通信地址
Name 服务标识符,需与契约项目中的服务名称对应
GrpcUrl 目标服务的 Grpc 端点地址

5.3 服务调用示例

在业务服务中通过构造函数注入 gRPC 服务客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using FreeScheduler;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MyCompanyName.Modules.Biz.Api.Contracts.Domain.Module;
using MyCompanyName.Modules.Biz.Api.Contracts.Services.Module;
using MyCompanyName.Modules.Biz.Api.Contracts.Services.Module.Input;
using MyCompanyName.Modules.Biz.Api.Contracts.Services.Module.Output;
using MyCompanyName.Modules.Biz.Api.Core.Consts;
using MyCompanyName.Modules.Biz.Api.Core.Repositories;
using MyCompanyName.Modules.Member.Api.Contracts.GrpcServices.Module.Input;
using Newtonsoft.Json;
using System.Threading.Tasks;
using ZhonTai;
using ZhonTai.Admin.Core.Dto;
using ZhonTai.Admin.Services;
using ZhonTai.DynamicApi;
using ZhonTai.DynamicApi.Attributes;

namespace MyCompanyName.Modules.Biz.Api.Services.Module;

/// <summary>
/// 模块服务
/// </summary>
[Order(1010)]
[DynamicApi(Area = ApiConsts.AreaName)]
public class ModuleService : BaseService, IModuleService, IDynamicApi
{
private readonly AppRepositoryBase<ModuleEntity> _moduleRep;
private readonly IModuleGrpcService _moduleGrpcService;

public ModuleService(
AppRepositoryBase<ModuleEntity> moduleRep,
IModuleGrpcService moduleGrpcService)
{
_moduleRep = moduleRep;
_moduleGrpcService = moduleGrpcService;
}

/// <summary>
/// 查询模块
/// </summary>
public async Task<ModuleGetOutput> GetAsync(long id)
{
// 调用 gRPC 服务示例
var grpcResult = await _moduleGrpcService.AddAsync(new ModuleAddGrpcInput
{
Name = "Grpc测试"
});

var result = await _moduleRep.GetAsync<ModuleGetOutput>(id);
return result;
}
}

建议:实际开发中结合框架提供的中间件进行统一的服务治理(如熔断、限流等)。


6. 测试验证流程

6.1 服务启动验证

运行项目后,在命令台中看到以下内容则表示 Grpc 服务方法注册成功:

1
2
Added gRPC method 'Add' to service 'MyCompanyName.Modules.Member.Api.Contracts.GrpcServices.Module.ModuleGrpcService'.
Method type: Unary, HTTP method: , route pattern: '/MyCompanyName.Modules.Member.Api.Contracts.GrpcServices.Module.ModuleGrpcService/Add'.

6.2 Postman 测试步骤

  1. 点击 New 按钮,弹出对话框选择 gRPC
  2. 在 Enter URL 文本框中输入 grpc://localhost:18021(URL 格式:grpc://{host}:{port}
  3. 右侧下拉框中选择 Use Server Reflection,出现 Add 方法则表示反射成功
  4. 选择 ModuleGrpcService/Add 方法
  5. 构造请求报文:
1
2
3
4
5
6
{
"Name": "TestModule",
"CreateTime": {
"Value": "2024-03-20T08:00:00Z"
}
}
  1. 点击 Invoke 按钮,验证响应结构:
1
2
3
4
5
6
{
"Success": true,
"Data": {
"Value": 649219021422661
}
}

7. 版本兼容策略

7.1 变更类型处理

变更类型 允许性 处理方案
新增字段 ✅允许 客户端需处理字段缺失情况
删除字段 ⚠️禁止 标记Obsolete,保留至少两个版本周期
修改字段类型 ⛔禁止 必须创建新版本接口
重命名字段 ⛔禁止 使用[ProtoMember] 别名功能实现平滑过渡

7.2 版本标识规则

在服务契约中声明版本号:

1
2
3
4
5
[ServiceContract(Name = "MemberService_v1")]
public interface IModuleGrpcService
{
// ...
}

#中台/分布式微服务 #分布式 #分布式/消息传递 #API接口 #中台/配置文件