使用 Yarp 做網關

資料

GitHub:?https://github.com/microsoft/reverse-proxy

YARP 文檔:https://microsoft.github.io/reverse-proxy/articles/getting-started.html

主動和被動健康檢查 :?https://microsoft.github.io/reverse-proxy/articles/dests-health-checks.html#active-health-check

gRpc:https://microsoft.github.io/reverse-proxy/articles/grpc.html

實戰項目概覽

Yarp Gateway 示意圖
dd7cdbcb5100b28f86f61a4938dd8292.png

共享類庫

創建一個 .Net6.0 的類庫,項目名稱:Artisan.Shared.Hosting.AspNetCore, 其它項目公用方法放在這個項目。

Serilog 日志

需要的包:

<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" /><PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /><PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" /><PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

代碼清單:Artisan.Shared.Hosting.AspNetCore/SerilogConfigurationHelper.cs

using Serilog;
using Serilog.Events;namespace Artisan.Shared.Hosting.AspNetCore;public static class SerilogConfigurationHelper{public static void Configure(string applicationName){Log.Logger = new LoggerConfiguration()#if DEBUG.MinimumLevel.Debug()#else.MinimumLevel.Information()#endif.MinimumLevel.Override("Microsoft", LogEventLevel.Information).MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning).Enrich.FromLogContext().Enrich.WithProperty("Application", $"{applicationName}").WriteTo.Async(c => c.File($"{AppDomain.CurrentDomain.BaseDirectory}/Logs/logs.txt")).WriteTo.Async(c => c.Console()).CreateLogger();}
}

創建服務

IdentityService

創建一個【AspNetCore Web Api】項目,項目名稱為:IdentityService

Program

代碼清單:IdentityService/Program.cs

using Artisan.Shared.Hosting.AspNetCore;
using Microsoft.OpenApi.Models;
using Serilog;namespace IdentityService;public class Program{public static int Main(string[] args){var assemblyName = typeof(Program).Assembly.GetName().Name;SerilogConfigurationHelper.Configure(assemblyName);try{Log.Information($"Starting {assemblyName}.");var builder = WebApplication.CreateBuilder(args);builder.Host.UseSerilog();builder.Services.AddControllers(); //Web MVCbuilder.Services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo { Title = "Identity Service", Version = "v1" });options.DocInclusionPredicate((docName, description) => true);options.CustomSchemaIds(type => type.FullName);});var app = builder.Build();            if (app.Environment.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseRouting();app.UseSwagger();app.UseSwaggerUI();app.UseEndpoints(endpoints =>{endpoints.MapControllers(); //Web MVC});app.Run();            return 0;}catch (Exception ex){Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");            return 1;}finally{Log.CloseAndFlush();}}
}

其中:

SerilogConfigurationHelper.Configure(assemblyName);

是配置?Serilog?日志:引用上面創建的共享項目:【Artisan.Shared.Hosting.AspNetCore】

User 實體

代碼清單:IdentityService/Models/User.cs

public class User{public int Id { get; set; }public string Name { get; set; }}

UserController

代碼清單:IdentityService/Controlles/UserController.cs

using Microsoft.AspNetCore.Mvc;
using IdentityService.Models;
using System.Threading.Tasks;namespace IdentityService.Controllers
{[ApiController][Route("/api/identity/users")]public class UserController : Controller{private readonly ILogger<UserController> _logger;private static List<User> Users = new List<User>(){new User(){ Id = 1, Name = "Jack"},new User(){ Id = 2, Name = "Tom"},new User(){ Id = 3, Name = "Franck"},new User(){ Id = 4, Name = "Tony"},};public UserController(ILogger<UserController> logger){_logger = logger;}[HttpGet]public async Task<List<User>>  GetAllAsync(){            return await Task.Run(() => { return Users; });}[HttpGet][Route("{id}")]public async Task<User> GetAsync(int id){            return await Task.Run(() =>{var entity = Users.FirstOrDefault(p => p.Id == id);                if (entity == null){throw new Exception($"未找到用戶:{id}");}                return entity;});}[HttpPost]public async Task<User> CreateAsync(User user){            return await Task.Run(() =>{Users.Add(user);                return user;});}[HttpPut][Route("{id}")]public async Task<User> UpdateAsync(int id, User user){            return await Task.Run(() =>{var entity = Users.FirstOrDefault(p => p.Id == id);                if(entity == null){throw new Exception($"未找到用戶:{id}");}entity.Name = user.Name;                return entity;});}[HttpDelete][Route("{id}")]public async Task<User> DeleteAsync(int id){            return await Task.Run(() =>{var entity = Users.FirstOrDefault(p => p.Id == id);                if (entity == null){throw new Exception($"未找到用戶:{id}");}Users.Remove(entity);                return entity;});}}
}

OrderService

創建一個【AspNetCore Web Api】項目,項目名稱為:OrderService

Program

代碼清單:OrderService/Program.cs

using Artisan.Shared.Hosting.AspNetCore;
using Microsoft.OpenApi.Models;
using Serilog;namespace OrderService;
public class Program{public static int Main(string[] args){var assemblyName = typeof(Program).Assembly.GetName().Name;SerilogConfigurationHelper.Configure(assemblyName);try{Log.Information($"Starting {assemblyName}.");var builder = WebApplication.CreateBuilder(args);builder.Host.UseSerilog();builder.Services.AddControllers(); //Web MVCbuilder.Services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo { Title = "Order Service", Version = "v1" });options.DocInclusionPredicate((docName, description) => true);options.CustomSchemaIds(type => type.FullName);});var app = builder.Build();            if (app.Environment.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseRouting();app.UseSwagger();app.UseSwaggerUI();app.UseEndpoints(endpoints =>{endpoints.MapControllers(); //Web MVC});app.Run();            return 0;}catch (Exception ex){Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");            return 1;}finally{Log.CloseAndFlush();}}
}

Order 實體

代碼清單:OrderService/Models/Order.cs

public class Order{public string Id { get; set; }public string Name { get; set; }}

OrderController

代碼清單:OrderService/Controlles/OrderController.cs

using Microsoft.AspNetCore.Mvc;
using OrderService.Models;
using System.Diagnostics;namespace OrderService.Controllers
{[ApiController][Route("/api/ordering/orders")]public class OrderController : Controller{private readonly ILogger<OrderController> _logger;private static List<Order> Orders = new List<Order>(){new Order(){ Id = "1", Name = "Order #1"},new Order(){ Id = "2", Name = "Order #2"},new Order(){ Id = "3", Name = "Order #3"},new Order(){ Id = "4", Name = "Order #4"},};public OrderController(ILogger<OrderController> logger){_logger = logger;}[HttpGet]public async Task<List<Order>> GetAllAsync(){            return await Task.Run(() =>{                return Orders;});}[HttpGet][Route("{id}")]public async Task<Order> GetAsync(string id){            return await Task.Run(() =>{var entity = Orders.FirstOrDefault(p => p.Id == id);                if (entity == null){throw new Exception($"未找到訂單:{id}");}                return entity;});}[HttpPost]public async Task<Order> CreateAsync(Order order){            return await Task.Run(() =>{Orders.Add(order);                return order;});}[HttpPut][Route("{id}")]public async Task<Order> UpdateAsync(string id, Order Order){            return await Task.Run(() =>{var entity = Orders.FirstOrDefault(p => p.Id == id);                if (entity == null){throw new Exception($"未找到訂單:{id}");}entity.Name = Order.Name;                return entity;});}[HttpDelete][Route("{id}")]public async Task<Order> DeleteAsync(string id){            return await Task.Run(() =>{var entity = Orders.FirstOrDefault(p => p.Id == id);                if (entity == null){throw new Exception($"未找到訂單:{id}");}Orders.Remove(entity);                return entity;});}}
}

創建網關

創建一個【AspNetCore 空】項目,項目名稱為:YarpGateway

引用包

<PackageReference Include="Yarp.ReverseProxy" Version="1.1.0" />

添加 Yarp

代碼清單:YarpGateway/Program.cs

using Artisan.Shared.Hosting.AspNetCore;
using Serilog;
using YarpGateway.Extensions;namespace YarpGateway;public class Program{public static  int Main(string[] args){var assemblyName = typeof(Program).Assembly.GetName().Name;SerilogConfigurationHelper.Configure(assemblyName);try{Log.Information($"Starting {assemblyName}.");var builder = WebApplication.CreateBuilder(args);builder.Host.UseSerilog().AddYarpJson(); // 添加Yarp的配置文件// 添加Yarp反向代理ReverseProxybuilder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));var app = builder.Build();app.UseRouting();app.UseEndpoints(endpoints =>{                // 添加Yarp終端Endpointsendpoints.MapReverseProxy();});app.Run();            return 0;}catch (Exception ex){Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");            return 1;}finally{Log.CloseAndFlush();}}
}

其中:

方法AddYarpJson()?是為了把 Yarp 的有關配置從appsetting.json獨立處理,避免配置文件很長很長,其代碼如下:

代碼清單:YarpGateway/Extensions/GatewayHostBuilderExtensions.cs

namespace YarpGateway.Extensions;public static class GatewayHostBuilderExtensions{ public const string AppYarpJsonPath = "yarp.json";public static IHostBuilder AddYarpJson(this IHostBuilder hostBuilder,        bool optional = true,        bool reloadOnChange = true,        string path = AppYarpJsonPath){        return hostBuilder.ConfigureAppConfiguration((_, builder) =>{builder.AddJsonFile(path: AppYarpJsonPath,optional: optional,reloadOnChange: reloadOnChange).AddEnvironmentVariables();});}
}

其中:

reloadOnChange = true?保證配置文件修改時, Yarp 能重新讀取配置文件。

添加 Yarp配置文件 : yarp.json

記得保證文件的屬性:

  • 復制到輸出目錄:如果內容較新則復制

  • 生成操作:內容

代碼清單:YarpGateway/yarp.json

{  "ReverseProxy": {    "Routes": {      "Identity Service": {        "ClusterId": "identityCluster",        "Match": {          "Path": "/api/identity/{**everything}"}},      "Ordering Service": {        "ClusterId": "orderingCluster",        "Match": {          "Path": "/api/ordering/{**everything}"}}},    "Clusters": {      "identityCluster": {        "Destinations": {          "destination1": {            "Address": "http://localhost:7711"}}},      "orderingCluster": {        "Destinations": {          "destination1": {            "Address": "http://localhost:7721"}          "destination2": {            "Address": "http://localhost:7722"}}}}}
}

運行

Yarp Gateway 示意圖:

2f6202c368bc6cbb9831263288c2b6df.png

啟動網關

在項目的bin/net6.0目錄下打開 CMD,執行如下命令啟動網關:

dotnet YarpGateway.dll --urls "http://localhost:7700"

監聽端口:7700

IdentityService

在項目的bin/net6.0目錄下打開 CMD,執行如下命令啟動 Web API 服務:

dotnet IdentityService.dll --urls "http://localhost:7711"

監聽端口:7711

OrderService

開啟兩個 OrderServcie 的進程,

在?bin/net6.0目錄下打開 CMD,執行如下命令啟動 Web API 服務:

第一個監聽端口:7721

dotnet OrderService.dll --urls "http://localhost:7721"

第二個監聽端口:7722

dotnet OrderService.dll --urls "http://localhost:7722"

測試

路由功能

打開 PostMan,創建調用服務的各種請求。

IdentityService

創建?GET?請求調用網關:?http://localhost:7700/api/identity/users

請求會被轉發到 IdentityService的集群節點:http://localhost:7711/api/identity/users

OrderService

創建?GET?請求調用網關:?http://localhost:7700/api/ordering/orders

請求會被轉發到 OrderService?的集群中如下某個節點中的一個:

  1. http://localhost:7721/api/ordering/orders

  2. http://localhost:7722/api/ordering/orders

支持請求類型

Tips:

由于是兩個服務,每個服務的進程都是獨立的,數據也是獨立,數據并沒有共享,故測試結果可能不是你所預期的,比如:

第一步:增加數據,這次是由第一個服務處理的;

第二步:查詢數據,如果這次查詢是由第二個服務器處理的話,就會找不到剛才新增的數據。

當然在實際開發中,我們的數據都是從同一個數據庫中讀取,不會出現數據不一致的情況。

HTTP 1.0 / 2.0

創建?GET?請求:?http://localhost:7700/api/ordering/orders/1

創建?POST?請求:?http://localhost:7700/api/ordering/orders?參數:

{"id":"10","name":"Order #100"}

創建?PUT?請求:?http://localhost:7700/api/ordering/orders/10?參數:

{"id":"10","name":"Order #100-1"
}

創建?DELETE?請求:?http://localhost:7700/api/ordering/orders/10

結論

上述4種 HTTP 請求都支持。

gRpc

待測試...

結論

支持 gRpc

新增集群服務節點

Yarp 支持動態添加服務集群服務節點,只要在配置文件?yarp.json, 添加新的服務配置,Yarp會自動加載新的服務節點:

代碼清單:yarp.json

{"ReverseProxy": {"Routes": {"Identity Service": {"ClusterId": "identityCluster","Match": {"Path": "/api/identity/{**everything}"}},...    },"Clusters": {"orderingCluster": {"Destinations": {"destination1": {"Address": "http://localhost:7721"},+          "destination2": {+            "Address": "http://localhost:7722"+          }}}}}}

添加上述配置后,會看到如下日志信息:

14:51:11 DBG] Destination 'destination2' has been added.
[14:51:11 DBG] Existing client reused for cluster 'orderingCluster'.

結論

Yarp 會重新加載配置,使得新增的集群新服務節點生效。

刪除集群服務節點

刪除集群下的某個服務節點

-          "destination2": {
-            "Address": "http://localhost:7722"-          }

Yarp 會重新加載配置,該集群服務節點被刪除。

[14:41:26 DBG] Destination 'destination2' has been removed.
[14:41:26 DBG] Existing client reused for cluster 'orderingCluster'.

結論

Yarp 會重新加載配置,使得被刪除的集群服務節點配置失效。

某集群節點因故障離線

把監聽7722端口的服務終止,請求還是會發送到這個端口程序上!!!

結論

Yarp 默認不會做健康檢查

相關:
主動和被動健康檢查 :?https://microsoft.github.io/reverse-proxy/articles/dests-health-checks.html#active-health-check

完成上一節的練習后,還遺留了一個問題:
如何通過 YarpGateway 訪問內部服務的Swagger呢?

問題:無法訪問內部服務 Swagger

外部訪問 IdentityService 和 OrderService 是通過 網關:YarpGateway 訪問的,使用者這個并不知道這個兩個服務的具體地址,也就是不知道如何訪問它們的 Swagger,那么:

如何通過 YarpGateway 訪問這兩個服務的Swagger呢?

實現原理

使用網關內部服務的 Swagger 信息,其地址為:

http://ip:port/swagger/v1/swagger.json

例如,OrderService 服務的 Swagger 信息為:

http://localhost:7721/swagger/v1/swagger.json

在網關中使用內部服務的 Swagger 終點,再注冊 Swagger 終點。

訪問 OrderService 服務的 Swagger 信息地址:http://localhost:7711/swagger/v1/swagger.json

返回如下信息:(只列舉部分數據)

{"openapi": "3.0.1","info": {"title": "Identity Service","version": "v1"},"paths": {"/api/identity/users": {"get": {"tags": ["User"],"responses": {"200": {"description": "Success","content": {"text/plain": {"schema": {"type": "array","items": {"$ref": "#/components/schemas/IdentityService.Models.User"}}},"application/json": {"schema": {"type": "array","items": {"$ref": "#/components/schemas/IdentityService.Models.User"}}},"text/json": {"schema": {"type": "array","items": {"$ref": "#/components/schemas/IdentityService.Models.User"}}}}}}},.....

內部服務支持跨域

網關要請求內部服務的Swagger 信息,這是跨域請求,所以要求兩個服務支持對網關的跨域請求。

IdentityService?和?OrderService?項目中都做如下修改:

添加跨域配置

在?appsettins.json?文件中添加跨域配置:

{"App": {"CorsOrigins": "http://localhost:7700"      // 網關地址,支持網關的Yarp gatewary跨域請求}}

其中,這個地址http://localhost:7700?就是網關的地址

支持跨域

修改?Program.cs文件:

  1. 代碼清單:IdentityService/Program.cs

  2. 代碼清單:OrderService/Program.cs

......IConfiguration configuration = builder.Configuration;builder.Services.AddCors(options =>{options.AddDefaultPolicy(builder =>{builder.WithOrigins(configuration["App:CorsOrigins"].Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray()).SetIsOriginAllowedToAllowWildcardSubdomains().AllowAnyHeader().AllowAnyMethod().AllowCredentials();});});
......app.UseRouting();+          app.UseCors();  // 添加跨域支持app.UseSwagger();app.UseSwaggerUI();
.....

網關添加 Swagger

在網關項目【YarpGateway】中做如下修改:

代碼清單:YarpGateway/Program.cs

builder.Services.AddControllers(); //Web MVC......builder.Services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo { Title = "Gateway", Version = "v1" });options.DocInclusionPredicate((docName, description) => true);options.CustomSchemaIds(type => type.FullName);});......            // 添加內部服務的Swagger終點app.UseSwaggerUIWithYarp();            //訪問網關地址,自動跳轉到 /swagger 的首頁app.UseRewriter(new RewriteOptions() // Regex for "", "/" and "" (whitespace).AddRedirect("^(|\\|\\s+)$", "/swagger"));app.UseRouting();

其中,調用方法?app.UseSwaggerUIWithYarp();?的目的是:添加內部服務的Swagger終點,其代碼如下:

代碼清單:YarpGateway/Extensions/YarpSwaggerUIBuilderExtensions.cs

using Yarp.ReverseProxy.Configuration;namespace YarpGateway.Extensions;
public static class YarpSwaggerUIBuilderExtensions{public static IApplicationBuilder UseSwaggerUIWithYarp(this IApplicationBuilder app){var serviceProvider = app.ApplicationServices;app.UseSwagger();app.UseSwaggerUI(options =>{var configuration = serviceProvider.GetRequiredService<IConfiguration>();var logger = serviceProvider.GetRequiredService<ILogger<Program>>();var proxyConfigProvider = serviceProvider.GetRequiredService<IProxyConfigProvider>();var yarpConfig = proxyConfigProvider.GetConfig();var routedClusters = yarpConfig.Clusters.SelectMany(t => t.Destinations,(clusterId, destination) => new { clusterId.ClusterId, destination.Value });var groupedClusters = routedClusters.GroupBy(q => q.Value.Address).Select(t => t.First()).Distinct().ToList();foreach (var clusterGroup in groupedClusters){var routeConfig = yarpConfig.Routes.FirstOrDefault(q =>q.ClusterId == clusterGroup.ClusterId);                if (routeConfig == null){logger.LogWarning($"Swagger UI: Couldn't find route configuration for {clusterGroup.ClusterId}...");                    continue;}options.SwaggerEndpoint($"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API");options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);}});        return app;}
}

關鍵代碼:

options.SwaggerEndpoint($"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API");

通過?IProxyConfigProvider?得到內部服務的信息,如下圖所示:

80da4efdcf2580a7e5e5272d3597c064.png

然后,拼接出內部服務的 Swagger 信息地址,

$"{clusterGroup.Value.Address}/swagger/v1/swagger.json"

最終得到兩個服務的Swagger信息地址:

  • IdentityServer 的 Swagger 信息地址:

http://localhost:7711/swagger/v1/swagger.json
  • OrderService 的 Swagger 信息地址:

http://localhost:7721/swagger/v1/swagger.json

最后,根據信息添加Swagger終點:

options.SwaggerEndpoint($"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API");

其中,

routeConfig.RouteId: Identity Service 或 Ordering Service

訪問網關 Swagger

訪問網關地址:http://localhost:7700

自動跳轉到其 Swagger首頁:http://localhost:7700/swagger/index.html

e4863c8cd1a7835c0aaed0cab86703b5.png

右上角有個下拉框,可以選擇不同的服務的Swagger,這里切換到 OrderService 的Swagger,如下圖所示:

37846cad15782b43d65f40747dff9003.png

在網關 Swagger 調用服務接口

可以在網關 Swagger 調用內部服務接口,如下圖所示:

c20eb021fe563cfcc76b16d4dbf8a2b2.png

返回:

21861baa09244daebe8bd405cfc373f1.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/286496.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/286496.shtml
英文地址,請注明出處:http://en.pswp.cn/news/286496.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android之OkDownload里面的OKHttp提示java.lang.IllegalArgumentException: Invalid URL port: “image“

1 、問題 release版本線上奔潰如下 Fatal Exception: java.lang.IllegalArgumentException: Invalid URL port: "image"at okhttp3.t$a.a(HttpUrl.kt:63)at okhttp3.t$b.b(HttpUrl.kt:8)at okhttp3.y$a.b(Request.kt:5)at com.liulishuo.okdownload.j.e.b.<init…

【iVX 初級工程師培訓教程 10篇文拿證】05 畫布及飛機大戰游戲制作

目錄 【iVX 初級工程師培訓教程 10篇文拿證】01 了解 iVX 完成新年賀卡 【iVX 初級工程師培訓教程 10篇文拿證】02 數值綁定及自適應網站制作 【iVX 初級工程師培訓教程 10篇文拿證】03 事件及猜數字小游戲 【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照 【iV…

【WEB API項目實戰干貨系列】- API登錄與身份驗證(三)

這篇我們主要來介紹我們如何在API項目中完成API的登錄及身份認證. 所以這篇會分為兩部分, 登錄API&#xff0c; API身份驗證. 這一篇的主要原理是&#xff1a; API會提供一個單獨的登錄API, 通過用戶名&#xff0c;密碼來產生一個SessionKey, SessionKey具有過期時間的特點, 系…

mysql數據庫建立的數據庫在哪個文件夾?

為什么80%的碼農都做不了架構師&#xff1f;>>> 一般在安裝目錄下的data文件夾下&#xff0c;或者在C:\Documents and Settings\All Users\Application Data\MySQL\MySQL Server 5.1\data&#xff08;你的可能是C:\Documents and Settings\All Users\Application D…

python 學習筆記01

python學習過程遇到一些問題記錄&#xff1a; 1、 IndentationError:expected an indented block錯誤的解決辦法 一句話 有冒號的下一行往往要縮進&#xff0c;該縮進就縮進 參考資料&#xff1a;http://blog.csdn.net/hongkangwl/article/details/16344749 2、17個新手常見Pyt…

ArcGIS實驗教程——實驗二十四:人口密度制圖

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據)》 一、實驗分析 人口密度是指單位土地面積上居住的人口數,通常以每平方千米或每公頃內的常住人口為單位計算。人口密度同資源、經濟密切結合,因此,科學準確地分析人口密度的分布情況,對合理制定…

Navicat 遠程連接ubuntu出現的問題

2003-Cantt connect to Mysql server to xxxxxxx 解決&#xff1a; vim /etc/mysql/my.cnf 修改bind-address 0.0.0.0 然后重啟mysql&#xff1a; 這時進入mysql可能會報錯&#xff1a; ERROR 2002 (HY000): Cant connect to local MySQL server through socket /v…

WPF效果第一百八十八篇之再玩Expander

大端午節的在屋里吹著空調擼著代碼真是酸爽;閑話也不多扯,直接看今天要分享的效果:1、關于簡單的布局設計:2、前臺先來個死布局,回頭ListBox改模板:<Expander ExpandDirection"Left" Header"控制卡" VerticalAlignment"Bottom" HorizontalAli…

Android之實現長按Webview頁面文字自定義復制、全選、分享、搜索、翻譯功能(支持多語言,博文也有Demo下載地址)

1 需求和效果爆照 瀏覽器app封裝了Webview,然后實現實現長按Webview頁面文字自定義復制、全選、分享、搜索、翻譯功能(支持多語言),都在自己的瀏覽器app里面進行搜索和翻譯,不跳到系統瀏覽器里面去 效果爆照如下,oppo手機效果如下 華為手機效果如下 2 Demo下載地址 De…

中國西北地區專題地圖合集(高清)

1. 西北地區概況圖 2. 西北地區植被類型分布圖 3. NDVI變化趨勢圖 4. 氣候與NDVI的相關性

Apache、tomcat、Nginx常用配置合集

配置文件地址&#xff1a; Apache&#xff1a; /etc/httpd/conf/httpd.conf tomcat&#xff1a; /usr/local/tomcat/conf/server.xml Nginx &#xff1a; /usr/local/nginx/conf/nginx.conf 開機啟動文件&#xff1a;/etc/rc.d/rc.local 啟動方式&#xff1a; Apache&#xff…

使用putty連接linux

使用putty連接linux 快照的使用 &#xff0c;做快照相當于做備份&#xff0c;比如配置好IP&#xff0c;快照一下&#xff0c;下次就可以在回到這里&#xff01; putty下載 最好去官網下載 下載putty.zip如圖所示 如何使用putty 如圖設置好IP然后 save 保存 如…

【WEB API項目實戰干貨系列】- API訪問客戶端(WebApiClient適用于MVC/WebForms/WinForm)(四)

目前最新的代碼已經通過Sqlite NHibernate Autofac滿足了我們基本的Demo需求. 按照既定的要求&#xff0c;我們的API會提供給眾多的客戶端使用, 這些客戶端可以是各種Web站點, APP, 或者是WinForm, WPF, Silverlight等諸如此類的應用&#xff0c;將來還有可能是各種Iot等物聯…

基于 Roslyn 實現代碼動態編譯

基于 Roslyn 實現代碼動態編譯Intro之前做的一個數據庫小工具可以支持根據 Model 代碼文件生成創建表的 sql 語句&#xff0c;原來是基于 CodeDom 實現的&#xff0c;后來改成使用基于 Roslyn 去做了。實現的原理在于編譯選擇的Model 文件生成一個程序集&#xff0c;再從這個程…

【GIS風暴】GIS拓撲關系原理詳解

目 錄 1. 拓撲關系的概念2. 拓撲元素3. 拓撲關系4. 拓撲關系的意義5. 拓撲在ArcGIS中實現1. 拓撲關系的概念 地圖上的拓撲關系是指圖形在保持連續狀態下的變形(縮放、旋轉和拉伸等),但圖形關系不變的性質。 2. 拓撲元素 對二維而言,矢量數據可抽象為點(節點)、線(鏈、…

Android之簡單的文件夾選擇器實現

1、效果爆照 2、代碼實現 前提需要保證app有讀寫權限 activity_select_folder.xml文件如下 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layo…

【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照

目錄 【iVX 初級工程師培訓教程 10篇文拿證】01 了解 iVX 完成新年賀卡 【iVX 初級工程師培訓教程 10篇文拿證】02 數值綁定及自適應網站制作 【iVX 初級工程師培訓教程 10篇文拿證】03 事件及猜數字小游戲 【iVX 初級工程師培訓教程 10篇文拿證】04 畫布及我和 iVX 合照 【iV…

360極速瀏覽器使用postman

步驟如下&#xff1a;1、將crx文件打包成zip文件2、解壓打包的zip文件&#xff0c;并將_metadata文件夾修改為metadata3、打開360瀏覽器的擴展4、360瀏覽器加載postman插件5、創建快捷方式6、雙擊快捷方式打開postman下載地址&#xff1a;http://pan.baidu.com/s/1c1ZX8XE如果網…

centos 下安裝man手冊

安裝centos minimal版本&#xff0c;發現沒有man手冊 需要安裝一下&#xff0c;yum install man-pages 本文轉自 XDATAPLUS 51CTO博客&#xff0c;原文鏈接:http://blog.51cto.com/xdataplus/1796126

# javascript 總結

# javascript 總結 ## 語法1. 區分大小寫2. 命名規范1. 首字母必須是 字母 _ $2. 其他字符可以是 數字 字母 下劃線 $3. 避開系統的關鍵字4. 單詞和單詞連接方式推薦駝峰命名3. 注釋1. 單行注釋 //注釋的內容2. 多行注釋 /*注釋內容*/4. 語句1. 要用;結尾(推薦做法)2. 如果不寫…