一、创建.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; }
}