zoukankan      html  css  js  c++  java
  • .NetCore使用Grpc通信,简单的微服务+JWT认证

    首先创建一个客户端和服务端,服务端选择创建GRPC服务,客户端就用WebApi就可以了,也可以用控制台、MVC等

     服务端:

    先安装 Grpc.AspNetCore  和  protobuf-net  两个nuget包

    创建.proto文件。

    syntax ="proto3";
    
    option csharp_namespace="DataService01.protos";
    package WeService01.Controllers;
    
    message users{
    int32 ID=1;
    string name=2;
    string login_name=3;
    int32 roleid=4;
    bool is_man=5;
    }
    message getusers{
    int32 ID=1;
    string name=2;
    }
    message getusersresponse{
    int32 code=1;
    string msg=2;
    users usermodel =3;
    }
    message addphoto{
    bytes data=1;
    }
    message get_token{
    string login_name=1;
    string password=2;
    }
    message return_token{
    string token=1;
    string expire_time=2;
    }
    service userservice{
     rpc Getuser(getusers) returns (getusersresponse);
     rpc Add(stream addphoto) returns (getusers);
     rpc getall(getusers) returns (stream getusersresponse);
     rpc saveall(stream addphoto) returns (stream getusersresponse);
     rpc gettoken(get_token) returns (return_token);
    };
    user.proto

    proto文件我个人理解就像定义接口,文件中指定了方法名、接收参数类型、返回参数类型等。

    syntax="proto3" 表示proto3的版本,不写默认是proto2版本。

    option csharp_namespace="DataService01.protos"; 是指c#生成代码的命名空间,message users{} 表示传输的类型,可以是请求或者返回类型,{}内的 id=1; name=2; 为自定义,同一消息类型中12345 这些编号不能重复。

    service 服务名称{

    rpc 服务的接口名称(接收参数类型) returns (stream 返回参数类型);//stream 表示持续传输 一般是list 或者文件传输等,没有这个关键字请求后就结束了,称为一元请求

    }

    proto文件创建好之后,设置文件属性为的build Action=Protobuf compiler;grpc stub classes=Server Only; 这里服务端所以选Server Only 客户端就选 Client Only 

    将文件复制给客户端,客户端也安装开头说的两个nuget包,并且设置文件属性。【文件属性是依赖 protobuf-net 这个nuget包

    设置之后项目生成时会生成grpc需要的文件,默认在Debug文件夹下;proto文件配置的csharp_namespace 和package 也在文件中体现。生成的文档不建议修改。

     #region

    proto文件需要客户端和服务端一样,可以不用复制的方式。项目“依赖项” 右键 “添加连接的服务”会显示项目内的已编写的proto文件作为服务,这样就可以写一份,服务端和客户端公用

    #endregion

     接下来编写服务端的业务逻辑代码;【代码中加入了JWT认证,写在最后;暂时先不介绍】

    创建service文件例如ds01.cs 继承 proto定义的service;项目中proto文件中的 service 名称是 userservice,则创建的服务继承userservice.userserviceBase。在文件中override proto定义的方法

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Logging;
    using Grpc.AspNetCore.Server;
    using Grpc.AspNetCore;
    using DataService01.protos;
    using Grpc.Core;
    using System.IO;
    using Microsoft.AspNetCore.Authorization;
    using System.Configuration;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Options;
    using DataService01.Models;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    
    namespace DataService01.Services
    {
        [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
        public class ds01 : userservice.userserviceBase
        {
            private readonly ILogger<ds01> logger;
            private readonly IConfiguration configuration;
            private readonly IOptions<JWTDTO> jwt_Options;
    
            public ds01(ILogger<ds01> logger,IConfiguration configuration,IOptions<JWTDTO> Jwt_options)
            {
                this.logger = logger;
                this.configuration = configuration;
                jwt_Options = Jwt_options;
            }
            [AllowAnonymous]
            public override async Task<return_token> gettoken(get_token request, ServerCallContext context)
            {
                users _user = new users();
                _user.LoginName = request.LoginName;
                if (request.LoginName.Equals("admin") && request.Password.Equals("123456"))
                {
                   var jwttoken=await new JWTHelper().IssueJwt(_user, jwt_Options.Value);
                    return await Task.FromResult(new return_token() { Token = jwttoken.Token, ExpireTime = new DateTimeOffset(jwttoken.ExpireTime).ToUnixTimeSeconds().ToString() });
                }
                return await Task.FromResult(new return_token() { Token = "", ExpireTime = "" });
            }
            public override Task<getusersresponse> Getuser(getusers request, ServerCallContext context)
            {
                var matedata_md=context.RequestHeaders;
                foreach (var pire in matedata_md)
                {
                   logger.LogInformation($"{pire.Key}:{pire.Value}");
                    logger.LogInformation(pire.Key+":"+pire.Value);
                }
                users item = userdatas.userslist.SingleOrDefault(n => n.ID == request.ID);
                if (item != null)
                {
                    return Task.FromResult(new getusersresponse() { Code = 0, Msg = "成功", Usermodel = item });
                }
                else
                {
                    return Task.FromResult(new getusersresponse() { Code = -1, Msg = "失败" });
                }
            }
            public override async Task getall(getusers request, IServerStreamWriter<getusersresponse> responseStream, ServerCallContext context)
            {
                foreach (var item in userdatas.userslist)
                {
                    //逐步返回数据
                    await responseStream.WriteAsync(new getusersresponse()
                    {
                        Usermodel = item
                    }
                ) ;
                }
            }
            public override async Task<getusers> Add(IAsyncStreamReader<addphoto> requestStream, ServerCallContext context)
            {
                List<byte> bt = new List<byte>();
                while (await requestStream.MoveNext())//有数据进入
                {
                    bt.AddRange(requestStream.Current.Data);
                }
                //while 执行完之后表示没有数据再进来
                FileStream file = new FileStream(AppDomain.CurrentDomain.BaseDirectory+"01.png",FileMode.OpenOrCreate) ;
                file.Write(bt.ToArray(), 0, bt.Count);
                
                file.Flush();
                file.Close();
                return  new getusers() { Name = "成功",ID = 0 };
            }
            public override async Task saveall(IAsyncStreamReader<addphoto> requestStream, IServerStreamWriter<getusersresponse> responseStream, ServerCallContext context)
            {
                List<byte> bt = new List<byte>();
                while (await requestStream.MoveNext())//有数据进入
                {
                    bt.AddRange(requestStream.Current.Data);
                }
                
                //while 执行完之后表示没有数据再进来
                FileStream file = new FileStream("/01.png", FileMode.OpenOrCreate);
                file.Write(bt.ToArray(), 0, bt.Count);
    
                file.Flush();
                file.Close();
    
                //返回数据
                foreach (var item in userdatas.userslist)
                {
                    await responseStream.WriteAsync(new getusersresponse()
                    {
                        Msg = "成功",
                        Code = 0,
                        Usermodel = item
                    });
                }
            }
        }
        public class userdatas
        {
          public static IList<users> userslist = new List<users>() { 
            new users(){ID=1,Name="11",LoginName="111",Roleid=1,IsMan=true},
            new users(){ID=2,Name="22",LoginName="222",Roleid=2,IsMan=false},
            new users(){ID=3,Name="33",LoginName="333",Roleid=3,IsMan=true}
            };
        }
    }
    ds01.cs

     服务端代码编写完成后需要配置Statup.cs。主要代码就一行,将写好的ds01写进Endpoints  (终结点路由) 

    app.UseEndpoints(endpoints =>
    {
    endpoints.MapGrpcService<ds01>();
    });

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using DataService01.Models;
    using DataService01.protos;
    using DataService01.Services;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.IdentityModel.Tokens;
    using System.Text;
    
    namespace DataService01
    {
        public class Startup
        {
            private readonly IConfiguration configuration;
    
            public Startup(IConfiguration configuration)
            {
                this.configuration = configuration;
            }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddGrpc();
                services.AddAuthorization(option => option.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
                {
                    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
                    policy.RequireClaim("sub");
                }));
                //services.AddAuthorization();
                services.AddAuthentication().AddJwtBearer(options=> {
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidIssuer = "http://localhost:5001",
                        ValidAudience = "http://localhost:5000",
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("JWTDTO").GetSection("SecurityKey").Value))// "9e79234cd150108e5048d0e0cb4ca5e4"
                    };
                });
                
                services.Configure<JWTDTO>(configuration.GetSection("JWTDTO"));
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                app.UseRouting();
                app.UseHttpsRedirection();
                
                app.UseAuthentication();
                app.UseAuthorization();
    
               
    
                app.UseEndpoints(endpoints =>
                {
                    //endpoints.MapGet("/", async context =>
                    //{
                    //    await context.Response.WriteAsync("Hello World!");
                    //});
                    endpoints.MapGrpcService<ds01>();
                   // endpoints.MapGrpcService<ds02>();
                });
            }
        }
    }
    Statup

     到此服务端结束;

    客户端:

    客户端需要和服务端一样的proto文件,可以复制过来,改属性Client Only,也可以使用 “添加连接的服务”,上面有写。项目重新生成之后也就会生成gRPC文件了。

     using GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:5001");//创建服务链接 ,using 用完即销毁,可以不适用using

    var service01 = new userservice.userserviceClient(channel);//连接服务

    var md = new Metadata()//metadata 用于headers ,只能是数字字母字符,不能有中文,不然会报 Request headers must contain only ASCII characters 错误
    {
    { "Name","deven" },
    { "ID","37" }
    };

    getusersresponse us = await service01.GetuserAsync(new getusers() { ID = 2 },headers:md);//一元请求+传送元数据   //调用服务的接口//Metadata 用于传输的Headers 可以是一些数据,稍后在JWT中会用到

    以下是我测试使用的客户端代码,分段参考就行,整体逻辑不一定对

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Google.Protobuf;
    using Grpc.Net.Client;
    using Grpc.AspNetCore.Server;
    using Grpc.AspNetCore;
    using DataService01.protos;
    using Grpc.Core;
    using System.IO;
    using Microsoft.Extensions.Logging;
    
    namespace WeService01.Controllers
    {
        [ApiController]
        [Route("[controller]")]
        public class HomeController : ControllerBase
        {
            private readonly ILogger<HomeController> _logger;
    
            public HomeController(ILogger<HomeController> logger)
            {
                this._logger = logger;
            }
            [HttpGet(nameof(Index))]
            public async Task<IActionResult> Index()
            {
                // AppContext.SetSwitch("System.Net.Http.SockersHttpHandler.Http2UnencryptedSupport", true);
                #region 连接服务
                using GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:5001");
                var service01 = new userservice.userserviceClient(channel);
                #endregion
    
                #region 请求接口获取token,登录
                return_token rt_token = service01.gettoken(new get_token() { LoginName = "admin", Password = "123456" });//获取JWTtoken
                var md_add_token = new Metadata() ;
                if (!string.IsNullOrEmpty(rt_token.Token))
                {
                    md_add_token.Add("Authorization", $"Bearer {rt_token.Token}");//将Token 添加到 Hearers里,key和Value 是固定写法,value中 Bearer 与token中间 要加一个空格
                }
                #endregion
    
                #region 一元请求
                var md = new Metadata()//metadata 用于headers ,只能是数字字母字符,不能有中文,不然会报 Request headers must contain only ASCII characters 错误
                {
                    { "Name","deven"  },
                    { "ID","37" }
                };
    
                getusersresponse us = await service01.GetuserAsync(new getusers() { ID = 2 },headers:md_add_token);//一元请求+传送元数据// header是Jwttoken
                _logger.LogInformation(us.Msg);
                #endregion
    
                #region 请求接口,发送结合数据
                using var getall_response = service01.getall(new getusers());//stream 数据返回
                while (await getall_response.ResponseStream.MoveNext())
                {
                    //取出每次返回的数据
                    _logger.LogInformation(getall_response.ResponseStream.Current.Msg);
                   // return (IActionResult)Task.FromResult(Content(getall_response.ResponseStream.Current.Msg));//getall_response.ResponseStream.Current.Usermodel
                }
                #endregion
    
                #region 发送文件
                //Bytes 数据传输
                FileStream file = System.IO.File.OpenRead(AppDomain.CurrentDomain.BaseDirectory + "img/img01.png");
               using var add_call = service01.Add();
                var st = add_call.RequestStream;
                while (true)
                {
                    byte[] bt = new byte[1024];
                    int meleng = await file.ReadAsync(bt, 0, bt.Length);
                    if (meleng == 0)//=0表示读取完毕
                    { break; }
                    if (bt.Length > meleng)//最后一次,读取可能少于1024,修改bt数组的长度
                    {
                        Array.Resize(ref bt, meleng);
                    }
                    await st.WriteAsync(new addphoto() { Data = ByteString.CopyFrom(bt) });//传输数据
                }
                await st.CompleteAsync();//通知服务端 数据传送完毕
                getusers res = await add_call.ResponseAsync;//接受返回内容
                _logger.LogInformation(res.Name);//打印日志 响应值
                #endregion
    
                #region  双向Stream 数据传输
                //双向Stream 数据传输
                using var saveall_call= service01.saveall();
               var saveall_req= saveall_call.RequestStream;
              var saveall_resp= saveall_call.ResponseStream;
                //首先定义 接受返回内容并处理的逻辑,等发送结束后再执行
                var responsetask = Task.Run(async () =>
                {
                    while (await saveall_resp.MoveNext())//处理相应的内容
                    {
                        _logger.LogInformation(saveall_resp.Current.Msg);
                    }
                });
                #region 发送请求的Stream 数据
                while (true)
                {
                    byte[] bt = new byte[1024];
                    int meleng = await file.ReadAsync(bt, 0, bt.Length);//将文件 分批发送
                    if (meleng == 0)//=0表示读取完毕
                    { break; }
                    if (bt.Length > meleng)//最后一次,读取可能少于1024,修改bt数组的长度
                    {
                        Array.Resize(ref bt, meleng);
                    }
                    await saveall_req.WriteAsync(new addphoto() { Data = ByteString.CopyFrom(bt) });//传输数据
                }
                //先执行 发送数据的逻辑request, 再执行接受数据的逻辑,response;需要先执行 saveall_req.CompleteAsync() 通知服务端请求结束,服务端才能正确的返回 response
                await saveall_req.CompleteAsync();//通知服务端 数据传送完毕
                await responsetask;//执行接受返回并处理的逻辑
                #endregion
                return (IActionResult)Task.FromResult(Content(us.Msg));
            }
        }
    }
    HomeController

     客户端很简单,到这里就结束了。生产环境还需要使用到注册中心,我暂时还没了解该如何配置。

    问题:微服务是多个,而且单个服务也需要分布式部署,需要在微服务前做负载均衡,降低故障率,分摊压力,使服务课横向扩展并实现热插拔,还能监控各服务的运行状态,合理分流。怎么才能达到这个效果呢?

    通过了解 觉得 Consul组件 比较符合预期,另外还有ZooKeeper 等其他 服务注册中心 的组件 https://developer.aliyun.com/article/766176

    JWT认证

    接下来介绍一下JWT,用于客户端和服务端的身份认证。

    请看另外一篇文章,主要介绍jwt
    https://www.cnblogs.com/zeran/p/14481591.html

    仅供参考,内容中会引用部分博友的文章。(侵删)
  • 相关阅读:
    JPA注解 @DiscriminatorValue 使用
    随笔
    Win10 启用 FTP
    Java后端模拟前端请求
    ueditor上传路径改成绝对路径
    CSS Web Fonts 网络字体
    调试:'Object reference note set to an instance of an object.'
    类转json、 json转xml的方法,转SortedDictionary转 xml 的方法。
    xml的问题 XmlDocument 与json转换
    websocket
  • 原文地址:https://www.cnblogs.com/zeran/p/14481513.html
Copyright © 2011-2022 走看看