微服务

本文详细介绍了如何创建.NetCore WebApi项目,包括MsgService和ProductService,然后讲解了如何安装和使用Consul服务器进行服务注册与注销。接着,展示了如何编写服务消费者,包括创建RestTemplateCore类库项目,以及使用Consul和Newtonsoft.Json库进行REST调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、创建.NetCore WebApi项目

1、创建一个MsgService信息服务项目

1.1 在Controllers文件夹下新建两个WebApi控制器,分别为EmailController、SMSController

namespace MsgService.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmailController : ControllerBase
    {
        [HttpPost(nameof(Send_QQ))]
        public void Send_QQ(SendEmailRequest model)
        {
            Console.WriteLine($"通过QQ邮件接口向{model.Email}发送邮件,标题{model.Title},内容:{ model.Body}");
        }
        [HttpPost(nameof(Send_163))]
        public void Send_163(SendEmailRequest model)
        {
            Console.WriteLine($"通过网易邮件接口向{model.Email}发送邮件,标题{model.Title}, 内容:{ model.Body}");
        }
        [HttpPost(nameof(Send_Sohu))]
        public void Send_Sohu(SendEmailRequest model)
        {
            Console.WriteLine($"通过Sohu邮件接口向{model.Email}发送邮件,标题{model.Title},内容:{ model.Body}");
        }
    }
    public class SendEmailRequest
    {
        public string Email { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
    }
}
 public class SMSController : ControllerBase
    {
        //发请求,报文体为{phoneNum:"110",msg:"aaaaaaaaaaaaa"},
        [HttpPost(nameof(Send_MI))]
        public void Send_MI(dynamic model)
        {
            Console.WriteLine($"通过小米短信接口向{model.phoneNum}发送短信{model.msg}");
        }
        [HttpPost(nameof(Send_LX))]
        public void Send_LX(SendSMSRequest model)
        {
            Console.WriteLine($"通过联想短信接口向{model.PhoneNum}发送短信{model.Msg}");
        }
        [HttpPost(nameof(Send_HW))]
        public void Send_HW(SendSMSRequest model)
        {
            Console.WriteLine($"通过华为短信接口向{model.PhoneNum}发送短信{model.Msg}");
        }
    }
    public class SendSMSRequest
    {
        public string PhoneNum { get; set; }
        public string Msg { get; set; }
    }

2、创建一个ProductService产品信息服务项目

2.1 在Controllers文件夹下新建一个ProductController控制器

 public class ProductController : ControllerBase
    {
        //这显然是为了demo,这样放到内存中不能“集群”
        private static List<Product> products = new List<Product>();
        static ProductController()
        {
            products.Add(new Product
            {
                Id = 1,
                Name = "T430笔记本",
                Price = 8888,
                Description = "CPU i7标压版,1T硬盘"
            });
            products.Add(new Product
            {
                Id = 2,
                Name = "华为Mate10",
                Price = 3888,
                Description = "大猩猩屏幕,多点触摸"
            });
            products.Add(new Product
            {
                Id = 3,
                Name = "天梭手表",
                Price = 9888,
                Description = "瑞士经典款,可好了"
            });
        }
        [HttpGet]
        public IEnumerable<Product> Get()
        {
            return products;
        }
        [HttpGet("{id}")]
        public Product Get(int id)
        {
            var product = products.SingleOrDefault(p => p.Id == id);
            if (product == null)
            {
                Response.StatusCode = 404;
            }
            return product;
        }
        [HttpPost]
        public void Add(Product model)
        {
            if (products.Any(p => p.Id == model.Id))
            {
                Response.StatusCode = (int)HttpStatusCode.Conflict;//通过状态码而非响应体报错,是restful风格
                return;
            }
            products.Add(model);
        }
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
            var product = products.SingleOrDefault(p => p.Id == id);
            if (product != null)
            {
                products.Remove(product);
            }
        }
    }

2.2 在Controllers文件夹下新建一个Product类

public class Product
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public string Description { get; set; }
    }

二、consul 服务器安装

2.1 cmd命令执行  consul.exe agent -dev  (consul 的监控页面 http://127.0.0.1:8500/)

 

三、.Net Core连接consul

3.1 MsgService项目与ProductService项目皆都执行Nuget包命令:Install-Package Consul

3.2 Rest 服务的准备

MsgService项目与ProductService项目皆都新建一个HealthController控制器

public class HealthController : Controller
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("ok");
        }
    }

 

四、服务注册 Consul 及注销

4.1 在MsgService项目与ProductService项目Startup类中皆都添加服务注册代码

​
​
 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env,IApplicationLifetime applicationLifetime)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();

            //获取Ip
            string ip = Configuration["ip"];
            //获取端口号
            int port = Convert.ToInt32(Configuration["port"]);
            //定义服务名称
            string serviceName = "MsgService";
            //服务Id
            string serviceId = serviceName + Guid.NewGuid();
            //注册到consul
            using (var client = new ConsulClient(ConsulConfig))
            {
                //注册服务到 Consul
                client.Agent.ServiceRegister(new AgentServiceRegistration()
                {
                    ID = serviceId,//服务编号,不能重复,用 Guid 最简单
                    Name = serviceName,//服务的名字
                    Address = ip,//服务提供者的能被消费者访问的 ip 地址(可以被其他应用访问的地址,本地测试可以用 127.0.0.1,机房环境中一定要写自己的内网 ip 地址)
                    Port = port,// 服务提供者的能被消费者访问的端口
                    Check = new AgentServiceCheck
                    {
                        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务停止多久后反注册(注销)
                        Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔,或者称为心跳间隔
                        HTTP = $"https://{ip}:{port}/api/health",//健康检查地址
                        Timeout = TimeSpan.FromSeconds(5)
                    }
                }).Wait();//Consult 客户端的所有方法几乎都是异步方法,但是都没按照规范加上Async 后缀,所以容易误导。记得调用后要 Wait()或者 await
            }
            //程序正常退出的时候从 Consul 注销服务
            //要通过方法参数注入 IApplicationLifetime
            applicationLifetime.ApplicationStopped.Register(() =>
            {
                using (var client = new ConsulClient(ConsulConfig))
                {
                    client.Agent.ServiceDeregister(serviceId).Wait();
                }
            });
        }


        /// <summary>
        /// consul的配置
        /// </summary>
        /// <param name="c"></param>
        private void ConsulConfig(ConsulClientConfiguration c)
        {
            c.Address = new Uri("http://127.0.0.1:8500");
            c.Datacenter = "dc1";
        }

​

​

4.2 在MsgService项目中appsettings.json配置文件下获取Ip、端口号

{
  "ip": "localhost",
  "port": "44392",
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

4.3 通过cmd命令行启动实例

dotnet MsgService.dll 

 

五、编写服务消费者

5.1、首先创建一个RestTemplateCore(.netcore)类库项目

5.2、在该项目中添加Nuget包(Consul、Newtonsoft.Json)

5.3、在RestTemplateCore项目中新建一个RestResponse类

 public class RestResponse
    {
        /// <summary>
        /// 响应状态码
        /// </summary>
        public HttpStatusCode StatusCode { get; set; }
        /// <summary>
        /// 响应的报文头
        /// </summary>
        public HttpResponseHeaders Headers { get; set; }
    }

5.4、在RestTemplateCore项目中新建一个RestResponseWithBody类

    /// <summary>
    /// 带响应报文的Rest响应结果,而且json报文会被自动反序列化
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RestResponseWithBody<T> : RestResponse
    {
        /// <summary>
        /// 响应报文体json反序列化的内容
        /// </summary>
        public T Body { get; set; }
    }

5.5、在RestTemplateCore项目中新建一个RestTemplate类

    /// <summary>
    /// 会自动到 Consul 中解析服务的 Rest 客户端,能把"http://ProductService/api/Product/"这样的虚拟地址
    /// 按照客户端负载均衡算法解析为 http://192.168.1.10:8080/api/Product/这样的真实地址
    /// </summary>
    public class RestTemplate
    {
        public String ConsulServerUrl { get; set; } = "http://127.0.0.1:8500";
        private HttpClient httpClient;
        public RestTemplate(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }
        /// <summary>
        /// 获取服务的第一个实现地址
        /// </summary>
        /// <param name="consulClient"></param>
        /// <param name="serviceName"></param>
        /// <returns></returns>
        private async Task<String> ResolveRootUrlAsync(String serviceName)
        {
            using (var consulClient = new ConsulClient(c => c.Address = new
            Uri(ConsulServerUrl)))
            {
                var services = (await consulClient.Agent.Services()).Response.Values
                .Where(s => s.Service.Equals(serviceName,
                StringComparison.OrdinalIgnoreCase));
                if (!services.Any())
                {
                    throw new ArgumentException($"找不到服务{serviceName }的任何实例");
                }
                else
                {
                    //根据当前时钟毫秒数对可用服务个数取模,取出一台机器使用
                    var service = services.ElementAt(Environment.TickCount %
                    services.Count());
                    return $"{service.Address}:{service.Port}";
                }
            }
        }
        //把 http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
        private async Task<String> ResolveUrlAsync(String url)
        {
            Uri uri = new Uri(url);
            String serviceName = uri.Host;//apiservice1
            String realRootUrl = await ResolveRootUrlAsync(serviceName);// 查 询 出 来apiservice1 对应的服务器地址 192.168.1.1:5000
                                                                        //uri.Scheme=http,realRootUrl =192.168.1.1:5000,PathAndQuery=/api/values
            return uri.Scheme + "://" + realRootUrl + uri.PathAndQuery;
        }
        /// <summary>
        /// 发出 Get 请求
        /// </summary>
        /// <typeparam name="T">响应报文反序列类型</typeparam>
        /// <param name="url">请求路径</param>
        /// <param name="requestHeaders">请求额外的报文头信息</param>
        /// <returns></returns>
        public async Task<RestResponseWithBody<T>> GetForEntityAsync<T>(String url,
        HttpRequestHeaders requestHeaders = null)
        {
            using (HttpRequestMessage requestMsg = new HttpRequestMessage())
            {
                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        requestMsg.Headers.Add(header.Key, header.Value);
                    }
                }
                requestMsg.Method = System.Net.Http.HttpMethod.Get;
                //http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
                requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url));
                RestResponseWithBody<T> respEntity = await
                SendForEntityAsync<T>(requestMsg);
                return respEntity;
            }
        }
        /// <summary>
        /// 发出 Post 请求
        /// </summary>
        /// <typeparam name="T">响应报文反序列类型</typeparam>
        /// <param name="url">请求路径</param>
        /// <param name="body">请求数据,将会被 json 序列化后放到请求报文体中</param>
        /// <param name="requestHeaders">请求额外的报文头信息</param>
        /// <returns></returns>
        public async Task<RestResponseWithBody<T>> PostForEntityAsync<T>(String url, object body = null, HttpRequestHeaders requestHeaders = null)
        {
            using (HttpRequestMessage requestMsg = new HttpRequestMessage())
            {
                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        requestMsg.Headers.Add(header.Key, header.Value);
                    }
                }
                requestMsg.Method = System.Net.Http.HttpMethod.Post;
                //http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
                requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url));
                requestMsg.Content = new
                StringContent(JsonConvert.SerializeObject(body));
                requestMsg.Content.Headers.ContentType = new
                MediaTypeHeaderValue("application/json");
                RestResponseWithBody<T> respEntity = await
                SendForEntityAsync<T>(requestMsg);
                return respEntity;
            }
        }
        /// <summary>
        /// 发出 Post 请求
        /// </summary>
        /// <param name="url">请求路径</param>
        /// <param name="requestHeaders">请求额外的报文头信息</param>
        /// <returns></returns>
        public async Task<RestResponse> PostAsync(String url, object body = null,
        HttpRequestHeaders requestHeaders = null)
        {
            using (HttpRequestMessage requestMsg = new HttpRequestMessage())
            {
                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        requestMsg.Headers.Add(header.Key, header.Value);
                    }
                }
                requestMsg.Method = System.Net.Http.HttpMethod.Post;
                //http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
                requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url));
                requestMsg.Content = new
                StringContent(JsonConvert.SerializeObject(body));
                requestMsg.Content.Headers.ContentType = new
                MediaTypeHeaderValue("application/json");
                var resp = await SendAsync(requestMsg);
                return resp;
            }
        }
        /// <summary>
        /// 发出 Put 请求
        /// </summary>
        /// <typeparam name="T">响应报文反序列类型</typeparam>
        /// <param name="url">请求路径</param>
        /// <param name="body">请求数据,将会被 json 序列化后放到请求报文体中</param>
        /// <param name="requestHeaders">请求额外的报文头信息</param>
        /// <returns></returns>
        public async Task<RestResponseWithBody<T>> PutForEntityAsync<T>(String url, object body = null, HttpRequestHeaders requestHeaders = null)
        {
            using (HttpRequestMessage requestMsg = new HttpRequestMessage())
            {
                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        requestMsg.Headers.Add(header.Key, header.Value);
                    }
                }
                requestMsg.Method = System.Net.Http.HttpMethod.Put;
                //http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
                requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url));
                requestMsg.Content = new
                StringContent(JsonConvert.SerializeObject(body));
                requestMsg.Content.Headers.ContentType = new
                MediaTypeHeaderValue("application/json");
                RestResponseWithBody<T> respEntity = await
                SendForEntityAsync<T>(requestMsg);
                return respEntity;
            }
        }
        /// <summary>
        /// 发出 Put 请求
        /// </summary>
        /// <param name="url">请求路径</param>
        /// <param name="body">请求数据,将会被 json 序列化后放到请求报文体中</param>
        /// <param name="requestHeaders">请求额外的报文头信息</param>
        /// <returns></returns>
        public async Task<RestResponse> PutAsync(String url, object body = null, HttpRequestHeaders requestHeaders = null)
        {
            using (HttpRequestMessage requestMsg = new HttpRequestMessage())
            {
                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        requestMsg.Headers.Add(header.Key, header.Value);
                    }
                }
                requestMsg.Method = System.Net.Http.HttpMethod.Put;
                //http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
                requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url));
                requestMsg.Content = new
                StringContent(JsonConvert.SerializeObject(body));
                requestMsg.Content.Headers.ContentType = new
                MediaTypeHeaderValue("application/json");
                var resp = await SendAsync(requestMsg);
                return resp;
            }
        }
        /// <summary>
        /// 发出 Delete 请求
        /// </summary>
        /// <typeparam name="T">响应报文反序列类型</typeparam>
        /// <param name="url">请求路径</param>
        /// <param name="requestHeaders">请求额外的报文头信息</param>
        /// <returns></returns>
        public async Task<RestResponseWithBody<T>> DeleteForEntityAsync<T>(String url,
        HttpRequestHeaders requestHeaders = null)
        {
            using (HttpRequestMessage requestMsg = new HttpRequestMessage())
            {
                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        requestMsg.Headers.Add(header.Key, header.Value);
                    }
                }
                requestMsg.Method = System.Net.Http.HttpMethod.Delete;
                //http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
                requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url));
                RestResponseWithBody<T> respEntity = await
                SendForEntityAsync<T>(requestMsg);
                return respEntity;
            }
        }
        /// <summary>
        /// 发出 Delete 请求
        /// </summary>
        /// <param name="url">请求路径</param>
        /// <param name="requestHeaders">请求额外的报文头信息</param>
        /// <returns></returns>
        public async Task<RestResponse> DeleteAsync(String url, HttpRequestHeaders
        requestHeaders = null)
        {
            using (HttpRequestMessage requestMsg = new HttpRequestMessage())
            {
                if (requestHeaders != null)
                {
                    foreach (var header in requestHeaders)
                    {
                        requestMsg.Headers.Add(header.Key, header.Value);
                    }
                }
                requestMsg.Method = System.Net.Http.HttpMethod.Delete;
                //http://apiservice1/api/values 转换为 http://192.168.1.1:5000/api/values
                requestMsg.RequestUri = new Uri(await ResolveUrlAsync(url));
                var resp = await SendAsync(requestMsg);
                return resp;
            }
        }
        /// <summary>
        /// 发出 Http 请求
        /// </summary>
        /// <typeparam name="T">响应报文反序列类型</typeparam>
        /// <param name="requestMsg">请求数据</param>
        /// <returns></returns>
        public async Task<RestResponseWithBody<T>>
        SendForEntityAsync<T>(HttpRequestMessage requestMsg)
        {
            var result = await httpClient.SendAsync(requestMsg);
            RestResponseWithBody<T> respEntity = new RestResponseWithBody<T>();
            respEntity.StatusCode = result.StatusCode;
            respEntity.Headers = respEntity.Headers;
            String bodyStr = await result.Content.ReadAsStringAsync();
            if (!string.IsNullOrWhiteSpace(bodyStr))
            {
                respEntity.Body = JsonConvert.DeserializeObject<T>(bodyStr);
            }
            return respEntity;
        }
        /// <summary>
        /// 发出 Http 请求
        /// </summary>
        /// <param name="requestMsg">请求数据</param>
        /// <returns></returns>
        public async Task<RestResponse> SendAsync(HttpRequestMessage requestMsg)
        {
            var result = await httpClient.SendAsync(requestMsg);
            RestResponse response = new RestResponse();
            response.StatusCode = result.StatusCode;
            response.Headers = result.Headers;
            return response;
        }
    }

5.6、再创建一个ConsulTest控制台测试项目

5.6.1 在该项目下Program类中添加代码

 static void Main(string[] args)
        {
            using (HttpClient httpClient = new HttpClient())
            {
                RestTemplate rest = new RestTemplate(httpClient);
                Console.WriteLine("---查询数据---------");
                var ret1 =
                rest.GetForEntityAsync<Product[]>("https://ProductService/api/Product/").Result;
                Console.WriteLine(ret1.StatusCode);
                if (ret1.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    foreach (var p in ret1.Body)
                    {
                        Console.WriteLine($"id={p.Id},name={p.Name}");
                    }
                }
                Console.WriteLine("---新增数据---------");
                Product newP = new Product();
                newP.Id = 888;
                newP.Name = "辛增";
                newP.Price = 88.8;
                var ret = rest.PostAsync("https://ProductService/api/Product/", newP).Result;
                Console.WriteLine(ret.StatusCode);
            }
            Console.ReadKey();
        }

5.6.2 在该项目下新建一个Product类

 public class Product
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public string Description { get; set; }
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值