NetCore项目实战篇06---服务注册与发现之consul

至此,我们的解决方案中新建了三个项目,网关(Zhengwei.Gateway)、认证中心(Zhengwei.Identity)和用户资源API(Zhengwei.Use.Api)。当要访问用户API的某个资源先要访问网关,网关要对请求进行认证,然后要访问认证中心,认证通过后才能访问对应的资源。今天我们要讲的是在认证的时候我们需要较验用户的信息,这时就要访问用户服务(因该项目采用微服务,所有的模块都叫服务),这就涉及到服务之间的访问与发现了。所以这节重点在于使用consul注册服务与发现服务。多说也不益,直接上项目吧。

1、  下载consul 下载地址:https://www.consul.io/downloads.html

下载下来的文件是一个.exe文件,如下图:

 

 

2、  cmd打开我们的命令窗口,切换到对应的下载目录,输入命令consul agent –dev,看到如下的信息说明你的consul服务正常启动。

 

 

3、  consul提供了一个UI的访问界面,界面上可以看到注册的服务,地址:http://localhost:8500,界面如下,现在还没有服务注册:

 

 

4、 回到我们项目中开始注册服务吧。现在要求zhengwei.user.api这个项目在启动时就向consul中注册服务。引用consul包到zhengwei.user.api项目中。

5、增加配置,配置consul服务器的地址和要注册的服务名,配置的意思是向http://127.0.0.1:8500所在的服务器(也就是consul所在的服务器)上注册名为userapi的服务,如下图:

 

 

6、在项目启动时注册服务时自定义RegisterService()方法,在项目停止时取消服务自定义DeRegisterService()方法。下面贴出Startup.cs类完整代码。但是这里有一个小问题:我用的127.0.0.1无法访问,用localhost就可以,我的IP和别名映射没有问题的。但是就是访问报错,可能是跨域的问题吗,这里暂不讨论,有解决了的园友可以给我留言。

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddDbContext<UserContext>(options =>
            {
                options.UseMySQL(Configuration.GetConnectionString("MysqlUser"));

            });

            services.Configure<ServiceDisvoveryOptions>(Configuration.GetSection("ServiceDiscovery"));
            services.AddSingleton<IConsulClient>(p => new ConsulClient(cfg =>
            {
                var s = p.GetRequiredService<IOptions<ServiceDisvoveryOptions>>().Value;
                if(!string.IsNullOrEmpty(s.Consul.HttpEndpoint))
                {
                    cfg.Address = new Uri(s.Consul.HttpEndpoint);
                }
            }));
            services.AddMvc(p=>p.Filters.Add(typeof(GlobalExceptionFilter)));
            
              //services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, 
            IHostingEnvironment env,
            ILoggerFactory loggerFactory,
            IApplicationLifetime lifetime,
            IOptions<ServiceDisvoveryOptions> serviceOptions,
            IConsulClient consul)

        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
               // app.UseHsts();
            }

            //启动时注册服务   安装consul后,通过localhost:8500可以查看服务
            lifetime.ApplicationStarted.Register(()=> {
                RegisterService(app, serviceOptions,consul,lifetime);
            });
            //停止时注销服务
            lifetime.ApplicationStopped.Register(() => {
                DeRegisterService(app, serviceOptions, consul, lifetime);
            });
            app.UseMvc();
            //UserContextSeed.SeedAsync(app, loggerFactory).Wait();

        }


        private void DeRegisterService(IApplicationBuilder app, IOptions<ServiceDisvoveryOptions> serviceOptions, IConsulClient consul, IApplicationLifetime lifetime)
        {
            var features = app.Properties["server.Features"] as FeatureCollection;
            var addresses = features.Get<IServerAddressesFeature>()
                                    .Addresses
                                    .Select(p => new Uri(p));
            foreach (var address in addresses)
            {
                var serviceId = $"{serviceOptions.Value.ServiceName}_{address.Host}:{address.Port}";
                
                consul.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult();
               
            }


        }

        private void RegisterService(IApplicationBuilder app, IOptions<ServiceDisvoveryOptions> serviceOptions, IConsulClient consul, IApplicationLifetime lifetime)
        {
            
            var httpCheck = new AgentServiceCheck()
            {
                DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1),
                Interval = TimeSpan.FromSeconds(30),
                HTTP = $"http://127.0.0.1:33545/HealthCheck"
                };

            var anentReg = new AgentServiceRegistration()
            {
                ID = "userapi:33545",
                Check = httpCheck,
                Address = "127.0.0.1",
                Name = "userapi",
                Port = 33545
            };
            var serviceId = "userapi:127.0.0.1:33545";
            consul.Agent.ServiceRegister(anentReg).GetAwaiter().GetResult();
                lifetime.ApplicationStopping.Register(()=> {
                    consul.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult();
                });

        }
    }

7、从注册的代码中可以看到在注册服务前会进行健康检查也就是调用Zhengwei.Use.Api项目中HealthCheckController控制器的Ping()方法。那么我们现在将这个方法加上。代码简单,直接上截图吧。

 

 

8、我们在UserController还要加一个方法CheckOrCreate()供认证时调用

 

 9、启动项目,再次进入localhost:8500页面,会看到一个名叫userapi的服务已注册到我们的consul中。

 

 

10、服务注册到我们的consul中后,接下来就要在认证时(Zhengwei.Identity项目中)找到这个服务,并调用对应的CheckOrCreate()方法。

11、在这个系统的第四篇文章中(NetCore项目实战篇04---集成IdentityService4)已经写好了调用use.api的接口和实现类,并实现了方法CheckOrCreate()只不过当时我们这个方法是写死的,直接return 1;现在我们就要真正开始调用Zhengwei.Use.Api项目中的这个方法了。见代码:

public class UserService : IUserService
    {
        //private string _userServiceUrl = "http://localhost:33545";
        private string _userServiceUrl;
        private HttpClient _httpClient;
        public UserService(HttpClient httpClient,IOptions<Dtos.ServiceDisvoveryOptions> serOp,IDnsQuery dnsQuery)
        {
            _httpClient = httpClient;
            
            var address  = dnsQuery.ResolveService("service.consul",serOp.Value.ServiceName);
            var addressList = address.First().AddressList;
            var host = addressList.Any() ? addressList.First().ToString() : address.First().HostName;
            var port = address.First().Port;
            _userServiceUrl = $"http://{host}:{port}";

        }
        public async Task<int> CheckOrCreate(string phone)
        {
            var from = new Dictionary<string, string> { { "phone", phone } };
            var content = new FormUrlEncodedContent(from);

            var response = await _httpClient.PostAsync(_userServiceUrl + "/api/users/check-or-create", content);
            if(response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                var userId =await response.Content.ReadAsStringAsync();
                int.TryParse(userId, out int intuserId);
                return intuserId;
            }
            return 0;

        }
    }

在这个类中,我们先是发现了use.api服务的ip地址,并给_userServiceUrl字段赋值,在CheckOrCreate方法中就直接调用这个地址对应的方法了。

12、 所有编码完成生成没有问题后,同时启动这三个项目,再次打开postman,通过:http://localhost:4157/connect/token获取token,再用token值去访问:http://localhost:4157/users,如果一切正常,那就会返回对应的用户信息。

到此,我们网关、认证、服务注册与发现这条线就算是走通了。

但是,这里还会有一个问题,就是在UserService.cs类中CheckOrCreate方法是直接调用的另一个服务,对的,这里就是直接调用的,很粗暴,对调用后的错误没有作处理,如服务是否响应,调用时是否报错等,都没有处理。这里是否会有更好的处理方法呢,请看下篇《NetCore项目实战篇07---服务保护之polly》

posted @ 2020-05-15 20:44  爱生活,爱代码  阅读(588)  评论(0编辑  收藏  举报