<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>文章 on ViatorLife Blog</title><link>/blog/</link><description>Recent content in 文章 on ViatorLife Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 06 Dec 2025 10:00:00 +0800</lastBuildDate><atom:link href="/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Blazor WebApp Server&amp;Cookie中AuthorizeAttribute的两重用途</title><link>/blog/2025-12-06-blazor-webapp-server-cookie-authorizeattribute-two-purposes/</link><pubDate>Sat, 06 Dec 2025 10:00:00 +0800</pubDate><guid>/blog/2025-12-06-blazor-webapp-server-cookie-authorizeattribute-two-purposes/</guid><description>&lt;h2 id="框架说明"&gt;框架说明&lt;/h2&gt;
&lt;p&gt;在使用Blazor WebApp Server模式下，设定以Cookie为鉴权机制。使用全局交互模式。&lt;/p&gt;
&lt;h2 id="authorizeattribute"&gt;AuthorizeAttribute&lt;/h2&gt;
&lt;p&gt;在页面上标记AuthorizeAttribute。其作用在于设定该页面需要鉴权完毕才能访问。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@page &lt;span style="color:#e6db74"&gt;&amp;#34;/counter&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@attribute [Microsoft.AspNetCore.Authorization.Authorize]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PageTitle&amp;gt;Counter&amp;lt;/PageTitle&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;没有它，当为Http请求时如果没有全局开启授权检查，则能够直接响应页面内容和执行页面上的操作。当为SignalR时，也可直接响应页面内容且执行操作。为了区分使用场景，以下按照Http还是SignalR使用区分开描述。&lt;/p&gt;
&lt;h2 id="http请求"&gt;Http请求&lt;/h2&gt;
&lt;p&gt;区分于是否启用了全局授权检查，其行为有所不同。&lt;/p&gt;
&lt;h3 id="启用全局授权检查"&gt;启用全局授权检查&lt;/h3&gt;
&lt;p&gt;在启用全局授权检查时，如下所示&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapRazorComponents&amp;lt;App&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddInteractiveServerRenderMode()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .RequireAuthorization(); &lt;span style="color:#75715e"&gt;//here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有些页面则需要标记匿名访问，比如Login Page。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@page &lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@layout EmptyLayout
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@attribute [AllowAnonymous]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种场景下，其他页面默认需要鉴权完毕才能访问。以Home为例&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@page &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PageTitle&amp;gt;Home&amp;lt;/PageTitle&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Welcome to your &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; app.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置鉴权服务和使用Cookie Scheme&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddCookie(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.Name = &lt;span style="color:#e6db74"&gt;&amp;#34;auth_token&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.MaxAge = TimeSpan.FromSeconds(&lt;span style="color:#ae81ff"&gt;15&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.LoginPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.AccessDeniedPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/access-denied&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddCascadingAuthenticationState();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在请求Web时，默认为Home，应全局开启需要授权，则按照Cookie Scheme要求跳转到重定向页面，其效果如下：
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20251206/200944009_b5ec563a-c504-4815-90ea-b377da5c27d3.png" alt="200944009_b5ec563a-c504-4815-90ea-b377da5c27d3"&gt;&lt;/p&gt;
&lt;h3 id="不启用全局授权检查"&gt;不启用全局授权检查&lt;/h3&gt;
&lt;p&gt;直接在page上标记AuthorizeAttribute，则通过Http访问该页面时，仍然按照鉴权中间件配置的Cookie Scheme规则重定向到登录页。&lt;/p&gt;
&lt;p&gt;比如Home页标记AuthorizeAttribute&lt;/p&gt;</description></item><item><title>Blazor WebApp Server&amp;Cookie登出跨Tab下SignalR连接残留</title><link>/blog/2025-11-25-blazor-webapp-server-cookie-logout-multitab-signalr-reconnect/</link><pubDate>Tue, 25 Nov 2025 10:00:00 +0800</pubDate><guid>/blog/2025-11-25-blazor-webapp-server-cookie-logout-multitab-signalr-reconnect/</guid><description>&lt;h2 id="框架说明"&gt;框架说明&lt;/h2&gt;
&lt;p&gt;在使用Blazor WebApp Server模式下，设定以Cookie为鉴权机制，且所有页面都设定了Authorize特性。&lt;/p&gt;
&lt;h2 id="问题陈述"&gt;问题陈述&lt;/h2&gt;
&lt;p&gt;当多开Tab页，其中一个Tab页执行退出操作，Cookie在浏览器中清除，可是其他Tab页仍然可以切换页面，执行请求。与传统Web程序工作机制不太相同。因其在首次访问页面后建立了SignalR信道连接，其通信过程中并不使用浏览器上存储的Cookie。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20251125/200458030_b198fb96-3b63-4a0a-8018-6430b8cb88bf.png" alt="200458030_b198fb96-3b63-4a0a-8018-6430b8cb88bf"&gt;
而当重新刷新Tab2或Tab3，则又立马跳转到登录页，因此时是走Http请求发起页面获取，没有Cookie。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20251125/200500290_a2d9d826-e2f7-43d7-acfb-9d27a86fa494.png" alt="200500290_a2d9d826-e2f7-43d7-acfb-9d27a86fa494"&gt;
总而言之便是，退出时无法跨Tab将其他页面一并退出，当前页面无法操作，其他页面仍然可以操作，其根源为无法清除SignalR信道内的Cookie信息。从通信方式上描述为如下场景，退出后只断掉了Http请求的鉴权检查，无法消除SignalR的通道。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20251125/200502176_22e55dac-91c5-4be5-95d8-bd2517670a91.png" alt="200502176_22e55dac-91c5-4be5-95d8-bd2517670a91"&gt;&lt;/p&gt;
&lt;h2 id="解决方案"&gt;解决方案&lt;/h2&gt;
&lt;p&gt;官方文档中提及了这种场景，也提供了方案来解决，需要自定义RevalidatingServerAuthenticationStateProvider类，这个类目的为服务端会按照其中属性值RevalidationInterval定期检查SignalR信道。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://source.dot.net/#Microsoft.AspNetCore.Components.Server/Circuits/RevalidatingServerAuthenticationStateProvider.cs,14"&gt;https://source.dot.net/#Microsoft.AspNetCore.Components.Server/Circuits/RevalidatingServerAuthenticationStateProvider.cs,14&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;其检查方法中，则可以根据当前信道内的信息去获取当前用户是否实际退出了，从而根据结果是断开SignalR信道还是仍然保持。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20251125/200503886_c1bc4c2a-0f2f-4eed-a9c0-49b260cbe2c8.png" alt="200503886_c1bc4c2a-0f2f-4eed-a9c0-49b260cbe2c8"&gt;
如下具体展开实现自定义类及应用。&lt;/p&gt;
&lt;h3 id="新建项目"&gt;新建项目&lt;/h3&gt;
&lt;p&gt;此处快速演示下其具体过程，搭建一个Server模式项目。启用全局交互模式&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; blazor -n BlazorServerLogoutDemo -f net10.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; --interactivity Server --all-interactive &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;修改App.razor，更换使用判断确定是哪种交互方式，以便于Login页使用表单发起Http请求。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;HeadOutlet @rendermode=&lt;span style="color:#e6db74"&gt;&amp;#34;PageRenderMode&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;Routes @rendermode=&lt;span style="color:#e6db74"&gt;&amp;#34;PageRenderMode&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@code {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [CascadingParameter]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; HttpContext HttpContext { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; IComponentRenderMode? PageRenderMode =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="设置鉴权授权"&gt;设置鉴权授权&lt;/h3&gt;
&lt;p&gt;修改Program.cs，注册鉴权授权服务，增加鉴权授权中间件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BlazorServerLogoutDemo.Components;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Authentication.Cookies;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddRazorComponents()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddInteractiveServerComponents();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddCookie(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.Name = &lt;span style="color:#e6db74"&gt;&amp;#34;auth_token&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.MaxAge = TimeSpan.FromMinutes(&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.LoginPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.AccessDeniedPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/access-denied&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddCascadingAuthenticationState();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!app.Environment.IsDevelopment())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    app.UseExceptionHandler(&lt;span style="color:#e6db74"&gt;&amp;#34;/Error&amp;#34;&lt;/span&gt;, createScopeForErrors: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    app.UseHsts();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseHttpsRedirection();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAntiforgery();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapStaticAssets();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapRazorComponents&amp;lt;App&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddInteractiveServerRenderMode();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="设置服务端模拟数据"&gt;设置服务端模拟数据&lt;/h3&gt;
&lt;p&gt;本文简化登录页和退出操作，关注于同浏览器跨Tab访问操作，新增模拟数据页面。&lt;/p&gt;</description></item><item><title>Blazor WebApp-Server&amp;AuthenticationSchemes报错</title><link>/blog/2025-10-15-blazor-webapp-server-authenticationschemes-error/</link><pubDate>Wed, 15 Oct 2025 10:00:00 +0800</pubDate><guid>/blog/2025-10-15-blazor-webapp-server-authenticationschemes-error/</guid><description>&lt;h1 id="背景描述"&gt;背景描述&lt;/h1&gt;
&lt;p&gt;集成到现有系统中，系统使用了Cookie作为鉴权方式，已有一个scheme正常业务使用。为接入开发工具，隔离现有scheme，另起一个DevTools Scheme，独立使用。集成Blazor为Blazor WebApp Server模式。&lt;/p&gt;
&lt;h1 id="项目设置"&gt;项目设置&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;在鉴权服务扩展，在已有scheme基础上扩展新scheme并简单设定其配置。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddCookie(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddCookie(&lt;span style="color:#e6db74"&gt;&amp;#34;DevTools&amp;#34;&lt;/span&gt;,options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.Name = &lt;span style="color:#e6db74"&gt;&amp;#34;DevTools&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.MaxAge = TimeSpan.FromMinutes(&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.LoginPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.AccessDeniedPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/access-denied&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Blazor页面设置Authorize特性，按照Blazor页面授权检查，在Blazor Page上标记Authorize。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@page &lt;span style="color:#e6db74"&gt;&amp;#34;/counter&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@attribute [Authorize]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@using Microsoft.AspNetCore.Authorization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@using Microsoft.AspNetCore.Components.Authorization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PageTitle&amp;gt;Counter&amp;lt;/PageTitle&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;h1&amp;gt;Counter&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;p role=&lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&amp;gt;Current count: @currentCount&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;从Authorize设定方式来讲，常规Asp.Net Core WebApi的用法应该也能够用于Blazor，因此设定如下方式。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@page &lt;span style="color:#e6db74"&gt;&amp;#34;/counter&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@attribute [Authorize(AuthenticationSchemes = &lt;span style="color:#e6db74"&gt;&amp;#34;DevTools&amp;#34;&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@using Microsoft.AspNetCore.Authorization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@using Microsoft.AspNetCore.Components.Authorization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PageTitle&amp;gt;Counter&amp;lt;/PageTitle&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;h1&amp;gt;Counter&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;p role=&lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&amp;gt;Current count: @currentCount&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;调试该页面，未登录下能够按照Schemes要求正常跳转到登录页。&lt;/p&gt;</description></item><item><title>Blazor WebApp-Server&amp;Cookie鉴权授权</title><link>/blog/2025-09-26-blazor-webapp-server-cookie-authentication-authorization/</link><pubDate>Fri, 26 Sep 2025 10:00:00 +0800</pubDate><guid>/blog/2025-09-26-blazor-webapp-server-cookie-authentication-authorization/</guid><description>&lt;p&gt;Blazor WebApp-Server模式，借助于SignalR，能够令前端的一些操作移交到后端处理，本文仅简单介绍登录完成和鉴权授权过程。&lt;/p&gt;
&lt;h1 id="新建项目"&gt;新建项目&lt;/h1&gt;
&lt;p&gt;项目为Blazor WebApp-Server模板，没有启用全局交互模式，由各个页面自行设定交互方式。本文使用DotNet CLI快速搭建。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;otnet new blazor -n ServerDemo &lt;span style="color:#f92672"&gt;-f&lt;/span&gt; net9.0 -int Server
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln add .\ServerDemo\
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="设置鉴权授权"&gt;设置鉴权授权&lt;/h1&gt;
&lt;p&gt;和Asp.Net Core的鉴权授权方式相同，本文使用Cookie来作为schema。在服务注册时设定鉴权和授权配置，添加鉴权授权中间件。注册AddCascadingAuthenticationState服务，在组件间共享身份状态。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Authentication.Cookies;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; ServerDemo.Components;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddRazorComponents()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddInteractiveServerComponents();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddCookie(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.Name = &lt;span style="color:#e6db74"&gt;&amp;#34;auth_token&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.Cookie.MaxAge = TimeSpan.FromMinutes(&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.LoginPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.AccessDeniedPath = &lt;span style="color:#e6db74"&gt;&amp;#34;/access-denied&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddCascadingAuthenticationState();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!app.Environment.IsDevelopment())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    app.UseExceptionHandler(&lt;span style="color:#e6db74"&gt;&amp;#34;/Error&amp;#34;&lt;/span&gt;, createScopeForErrors: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    app.UseHsts();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseHttpsRedirection();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAntiforgery();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapStaticAssets();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapRazorComponents&amp;lt;App&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .AddInteractiveServerRenderMode();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="创建登录页面"&gt;创建登录页面&lt;/h1&gt;
&lt;p&gt;简单实现用户名密码验证登录页面，同时在本页面上进行Cookie生成。此处，点击表单提交，使用的是Http请求，服务端对应AuthenticateUserAsync方法处理，等同于WebApi机制，只是使用的是另一种Http Api机制。&lt;/p&gt;</description></item><item><title>Blazor WebAssembly&amp;MudBlazor模板集成</title><link>/blog/2025-08-12-blazor-webassembly-mudblazor-integration/</link><pubDate>Tue, 12 Aug 2025 10:00:00 +0800</pubDate><guid>/blog/2025-08-12-blazor-webassembly-mudblazor-integration/</guid><description>&lt;p&gt;MudBlazor组件库丰富且其组件名辨识度极高，UI设计上很不错。在纯Blazor WebAssembly Standalone模板上缺少了MudBlazor模板，因此集成时缺少参考文档。&lt;/p&gt;
&lt;h1 id="新建项目"&gt;新建项目&lt;/h1&gt;
&lt;p&gt;项目为Blazor WebAssembly Standalone模板。本文使用DotNet CLI快速搭建。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; blazorwasm -n AppDemo -f net9.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; sln
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln &lt;span style="color:#66d9ef"&gt;add&lt;/span&gt; .&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;AppDemo\
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="集成mudblazor包"&gt;集成MudBlazor包&lt;/h1&gt;
&lt;p&gt;只需一个包即可，样式文件和组件都在包中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;add&lt;/span&gt; package MudBlazor --version &lt;span style="color:#ae81ff"&gt;8.14&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;服务注册&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddMudServices();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MudBlazor包方面完成了，接下来参照MudBlazor文档要求修改&lt;/p&gt;
&lt;h1 id="删除原有所有css文件"&gt;删除原有所有css文件&lt;/h1&gt;
&lt;p&gt;默认Blazor WebAssembly Standalone模板wwwroot下为Bootstrap样式，可以全部移除，&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250926/204052678_a21b618f-9eeb-4eb4-beed-5d782fe55932.png" alt="204052678_a21b618f-9eeb-4eb4-beed-5d782fe55932"&gt;&lt;/p&gt;
&lt;h1 id="修改indexhtml"&gt;修改index.html&lt;/h1&gt;
&lt;p&gt;替换Head&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;meta charset=&lt;span style="color:#e6db74"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;meta name=&lt;span style="color:#e6db74"&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; content=&lt;span style="color:#e6db74"&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;title&amp;gt;AppDemo&amp;lt;/title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;&lt;span style="color:#66d9ef"&gt;base&lt;/span&gt; href=&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;link href=&lt;span style="color:#e6db74"&gt;&amp;#34;https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&amp;amp;display=swap&amp;#34;&lt;/span&gt; rel=&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;link href=&lt;span style="color:#e6db74"&gt;&amp;#34;_content/MudBlazor/MudBlazor.min.css?v=1&amp;#34;&lt;/span&gt; rel=&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;link rel=&lt;span style="color:#e6db74"&gt;&amp;#34;icon&amp;#34;&lt;/span&gt; type=&lt;span style="color:#e6db74"&gt;&amp;#34;image/png&amp;#34;&lt;/span&gt; href=&lt;span style="color:#e6db74"&gt;&amp;#34;favicon.png&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;替换Script&lt;/p&gt;</description></item><item><title>Blazor WebAssembly&amp;JWT鉴权授权</title><link>/blog/2025-07-08-blazor-webassembly-jwt-authentication-authorization/</link><pubDate>Tue, 08 Jul 2025 10:00:00 +0800</pubDate><guid>/blog/2025-07-08-blazor-webassembly-jwt-authentication-authorization/</guid><description>&lt;p&gt;前后端分离下，后端专注于业务逻辑，前端专注于页面交互，Blazor WebAssembly令后端开发者降低学习成本，也能够玩转一些前端开发功能。本文仅简单介绍前后端交互，借助于Blazor WebAssembly如何调用接口，鉴权授权。&lt;/p&gt;
&lt;h1 id="新建项目"&gt;新建项目&lt;/h1&gt;
&lt;p&gt;前端项目为Blazor WebAssembly(Standalone)模板，后端为WebApi。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250708/203937537_887f7549-da3d-4be5-b948-5b8b112ee79e.png" alt="203937537_887f7549-da3d-4be5-b948-5b8b112ee79e"&gt;
本文使用DotNet CLI快速搭建。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new blazorwasm -n AppDemo &lt;span style="color:#f92672"&gt;-f&lt;/span&gt; net9.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new webapi -n ApiDemo &lt;span style="color:#f92672"&gt;-f&lt;/span&gt; net9.0 -controllers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln add .\AppDemo\
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln add .\ApiDemo\
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="服务端令牌接口"&gt;服务端令牌接口&lt;/h1&gt;
&lt;p&gt;安装Nuget包&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;dotnet&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;add&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;package&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;本次不使用数据库验证，直接模拟返回一个JWT。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;api/[controller]&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AccountController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; SecretKey = &lt;span style="color:#e6db74"&gt;&amp;#34;YourSuperSecretKeyThatIsAtLeast32CharactersLong!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Issuer = &lt;span style="color:#e6db74"&gt;&amp;#34;ApiDemo&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Audience = &lt;span style="color:#e6db74"&gt;&amp;#34;ApiDemo&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; ExpirationMinutes = &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [Authorize]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [HttpGet(&amp;#34;CurrentUserName&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetCurrentUserName()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [HttpPost(&amp;#34;Login&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Login([FromBody] LoginRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;// 示例：简单的硬编码验证（实际项目中应该从数据库验证）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (request.Username == &lt;span style="color:#e6db74"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt; &amp;amp;&amp;amp; request.Password == &lt;span style="color:#e6db74"&gt;&amp;#34;admin123&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; token = GenerateJwtToken(request.Username);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; expires = DateTime.UtcNow.AddMinutes(ExpirationMinutes);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; LoginResponse
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                Token = token,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                Expires = expires
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Unauthorized(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { message = &lt;span style="color:#e6db74"&gt;&amp;#34;用户名或密码错误&amp;#34;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GenerateJwtToken(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; username)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; key = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; credentials = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SigningCredentials(key, SecurityAlgorithms.HmacSha256);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; claims = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Claim(ClaimTypes.Name, username),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Claim(ClaimTypes.NameIdentifier, username),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Sub, username),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; token = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; JwtSecurityToken(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            issuer: Issuer,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            audience: Audience,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            claims: claims,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            expires: DateTime.UtcNow.AddMinutes(ExpirationMinutes),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            signingCredentials: credentials
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; JwtSecurityTokenHandler().WriteToken(token);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;LoginRequest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [Required(ErrorMessage = &amp;#34;用户名不能为空&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; required &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Username { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [Required(ErrorMessage = &amp;#34;密码不能为空&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; required &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Password { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;LoginResponse&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Token { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; DateTime Expires { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="服务端鉴权配置"&gt;服务端鉴权配置&lt;/h1&gt;
&lt;p&gt;简单使用明文方式配置鉴权服务。设定默认Schema为Jwt格式。&lt;/p&gt;</description></item><item><title>Blazor WebAssembly&amp;EdgeOne Pages部署记录</title><link>/blog/2025-06-19-blazor-webassembly-edgeone-page-deploy/</link><pubDate>Thu, 19 Jun 2025 10:00:00 +0800</pubDate><guid>/blog/2025-06-19-blazor-webassembly-edgeone-page-deploy/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;对于Blazor WebAssembly，因其特殊性，可以直接部署成站点，浏览器下载后，在浏览器中直接运行，总想薅点羊毛，能够免费部署这类应用，搜寻一方了解到，像是Github Page、Azure Static App等都可以白嫖来部署静态站点。如此，结合部署过程，进行记录一番。&lt;/p&gt;
&lt;h2 id="edgeone-pages"&gt;EdgeOne Pages&lt;/h2&gt;
&lt;p&gt;腾讯云提供的等同于Github Pages服务，目前阶段免费，可以部署静态站点和基于Next.js的全栈项目。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://edgeone.cloud.tencent.com/pages/document/162936635171454976"&gt;https://edgeone.cloud.tencent.com/pages/document/162936635171454976&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="新建项目"&gt;新建项目&lt;/h2&gt;
&lt;p&gt;此处新建一个Blazor WebAssembly(Standalone)的项目，注意是非Blazor WebApp类型的。此处上传到Github中，因此Github中提前建好仓库，默认克隆到本地好了，开始创建项目。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// 假定路径
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PS C:\demos\EdgeOnePagesBlazorDemo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行创建命令&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new blazorwasm -n EdgeOnePagesBlazorDemo -f net8.0 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n BlazorDemo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln add .\EdgeOnePagesBlazorDemo\
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;增加gitignore忽略bin/obj等文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; gitignore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行代码推送到Github仓库中，代码部分完事。&lt;/p&gt;
&lt;h2 id="构建与部署"&gt;构建与部署&lt;/h2&gt;
&lt;p&gt;为了将站点资源打包后部署到EdgeOne Pages，需要做一步打包工作，借助于Github Actions完成打包。部署方式上有两种方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从EdgeOne Pages上配置由其主动拉取。&lt;/li&gt;
&lt;li&gt;使用EdgeOne提供的CLI，将打包后的资源上传到EdgeOne Pages。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="流水线构建"&gt;流水线构建&lt;/h3&gt;
&lt;p&gt;可以直接借助Github Pages一样的构建方式，新建yml来构建Blazor Wasm项目。在Github仓库页面上，点击顶部Action菜单，创建工作流，搜索.NET，点击配置按钮&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250619/164209102_4a57f6dd-3d49-4803-a681-7d007c546494.png" alt="164209102_4a57f6dd-3d49-4803-a681-7d007c546494"&gt;
更换默认文件名，具体名字随意&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250619/164213449_b058a1ca-a6be-496e-b092-f79af0aa4cfa.png" alt="164213449_b058a1ca-a6be-496e-b092-f79af0aa4cfa"&gt;
文件内容如下，各步骤见第一个参考文档内描述&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# This workflow will build a .NET project
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;name: Deploy to Github Pages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;on:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  push:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    branches: [ &amp;#34;main&amp;#34; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  pull_request:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    branches: [ &amp;#34;main&amp;#34; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;jobs:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  deploy-to-github-pages:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    runs-on: ubuntu-latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    steps:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - uses: actions/checkout@v4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - name: Setup .NET
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      uses: actions/setup-dotnet@v4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      with:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        dotnet-version: 8.0.x
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - name: Publish DotNet Blazor Project
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      run: dotnet publish ./EdgeOnePageBlazorDemo/EdgeOnePageBlazorDemo.csproj -c Release -o release --nologo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - name: Commit wwwroot to Github Pages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      uses: JamesIves/github-pages-deploy-action@3.7.1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      with:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        BRANCH: gh-pages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        FOLDER: release/wwwroot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;保存开始执行流水线构建，正常构建完毕后，构建结果会存储在gh-pages分支中。再后续对main分支的改动都会触发流水线，并更新到gh-pages分支。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250619/164215166_3862f68c-6c0c-48de-9191-e7f4213ed836.png" alt="164215166_3862f68c-6c0c-48de-9191-e7f4213ed836"&gt;
如果Github Action运行中出现了403的错误信息，为配置中缺少权限。&lt;/p&gt;</description></item><item><title>Blazor WebAssembly&amp;Github Page部署记录</title><link>/blog/2025-05-28-blazor-webassembly-github-page-deploy/</link><pubDate>Wed, 28 May 2025 10:00:00 +0800</pubDate><guid>/blog/2025-05-28-blazor-webassembly-github-page-deploy/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;对于Blazor WebAssembly，因其特殊性，可以直接部署成站点，浏览器下载后，在浏览器中直接运行，总想薅点羊毛，能够免费部署这类应用，搜寻一方了解到，像是Github Page、Azure Static App等都可以白嫖来部署静态站点。如此，结合部署过程，进行记录一番。&lt;/p&gt;
&lt;h2 id="新建github仓库"&gt;新建Github仓库&lt;/h2&gt;
&lt;p&gt;按照文档新建Github Pages的仓储 &lt;a href="https://pages.github.com/"&gt;https://pages.github.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222401261_5204aa7a-9cfb-4102-a8b4-736b2099eb52.png" alt="222401261_5204aa7a-9cfb-4102-a8b4-736b2099eb52"&gt;
默认分支为main，先新建好一个gh-pages分支该分支用来存储静态站点文件。以避免后续main分支推了代码后不干净了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222403956_64bf9706-e183-427d-aa28-51809087b664.png" alt="222403956_64bf9706-e183-427d-aa28-51809087b664"&gt;&lt;/p&gt;
&lt;h2 id="新建项目"&gt;新建项目&lt;/h2&gt;
&lt;p&gt;此处选择Blazor WebAssembly Standalone项目，非Blazor WebApp类型。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222406007_4a171cf7-7d12-42ab-9de3-0f3ecc9acb4d.png" alt="222406007_4a171cf7-7d12-42ab-9de3-0f3ecc9acb4d"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222407480_94c069d3-1333-4ae3-8607-63a4d57eea6b.png" alt="222407480_94c069d3-1333-4ae3-8607-63a4d57eea6b"&gt;
增加gitignore忽略bin/obj等文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; gitignore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建完毕后推送代码到main，main分支用来存储源代码。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222408828_9ec634a1-841a-45bd-bcf0-fd5fe9f685eb.png" alt="222408828_9ec634a1-841a-45bd-bcf0-fd5fe9f685eb"&gt;&lt;/p&gt;
&lt;h2 id="手动发布部署"&gt;手动发布部署&lt;/h2&gt;
&lt;p&gt;先通过本地发布，将发布后的文件推送到分支，看效果，从而了解整个发布，部署过程。&lt;/p&gt;
&lt;h3 id="本地发布"&gt;本地发布&lt;/h3&gt;
&lt;p&gt;本地发布到文件夹&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222410155_ab2764be-3421-44c8-94c6-b9a9b22cbe55.png" alt="222410155_ab2764be-3421-44c8-94c6-b9a9b22cbe55"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222411659_cd261f2d-7d98-4070-9485-bc0bf17974fd.png" alt="222411659_cd261f2d-7d98-4070-9485-bc0bf17974fd"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222413027_cb323aac-4a60-4153-b0c5-5f1c747eba1c.png" alt="222413027_cb323aac-4a60-4153-b0c5-5f1c747eba1c"&gt;
发布完毕后点击导航到文件夹中&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222414777_d749ef16-e12a-4e0f-8e4c-0a13815387ea.png" alt="222414777_d749ef16-e12a-4e0f-8e4c-0a13815387ea"&gt;
后续部署只需要使用到wwwroot中的内容。&lt;/p&gt;
&lt;h3 id="本地部署"&gt;本地部署&lt;/h3&gt;
&lt;p&gt;新建gh-pages分支，这个分支用来存放Blazor资源文件，可以直接用于浏览器下载后访问的。复制出上一步发布后wwwroot内的文件到主目录下，剩余全部删除。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222416252_85a81d55-cb23-48b4-98fd-dd7927cf70e9.png" alt="222416252_85a81d55-cb23-48b4-98fd-dd7927cf70e9"&gt;
提交代码到gh-pages分支.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222417901_d80472e5-8caf-4ff5-943e-67d0682b0b0c.png" alt="222417901_d80472e5-8caf-4ff5-943e-67d0682b0b0c"&gt;
github设置中切换到gh-pages分支作为github pages的分支源头，切换完毕后等待，再访问仓库.github.io，例如我这的viatorlife.github.io，访问便可以看到blazor wasm应用了，尽管控制台报错，但确定是上去了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222419674_c38c48fd-bdd6-4a90-b9c6-c1c6ba68718f.png" alt="222419674_c38c48fd-bdd6-4a90-b9c6-c1c6ba68718f"&gt;&lt;/p&gt;
&lt;h3 id="修复问题"&gt;修复问题&lt;/h3&gt;
&lt;p&gt;开始修复问题，gh-pages分支使用的默认的Github Action,不能够允许有_framework这种带下划线的文件夹，但可以增加一个空文件命名为.nojelly到gh-pages分支来去除检查，再等待后访问便恢复正常。&lt;/p&gt;
&lt;p&gt;如果直接浏览器中输入url地址访问，会进入到404页面，因为Github Pages需要404.html&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222421342_54651dc1-0544-4140-bb78-4ccbdf86b2be.png" alt="222421342_54651dc1-0544-4140-bb78-4ccbdf86b2be"&gt;
简单一点，复制index.html文件改名404.html即可。再访问便能够输入url进入到页面。&lt;/p&gt;
&lt;p&gt;其余问题见参考文档中，此处只考虑这两个问题。&lt;/p&gt;
&lt;h2 id="自动发布部署"&gt;自动发布部署&lt;/h2&gt;
&lt;p&gt;如上是本地发布手动copy到gh-pages分支，再借助gh-pages分支的github action完成站点发布。这个过程可以借助Github Action来实现自动发布。&lt;/p&gt;
&lt;p&gt;整个流程变更为如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;本地推送代码到main分支&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;main: Github Action发布&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;产物推送到gh-pages分支&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;gh-pages: Github-Action发布&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;静态站点部署&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;点击顶部Action菜单，创建工作流，搜索.NET，选择&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222423028_4ff5f102-02ed-4c26-bc3e-2123a3686e55.png" alt="222423028_4ff5f102-02ed-4c26-bc3e-2123a3686e55"&gt;
更换默认文件名，具体名字随意&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250528/222424736_ecdc0325-0d83-4a0f-9283-6496f056050e.png" alt="222424736_ecdc0325-0d83-4a0f-9283-6496f056050e"&gt;
文件内容如下，各步骤见第一个参考文档内描述&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; This workflow will build a .NET project
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; For more information see: https:&lt;span style="color:#75715e"&gt;//docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;name: Deploy ti Github Pages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  push:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    branches: [ &lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  pull_request:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    branches: [ &lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;jobs:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  deploy-to-github-pages:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    runs-&lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;: ubuntu-latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    steps:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - uses: actions/checkout@v4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - name: Setup .NET
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      uses: actions/setup-dotnet@v4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      with:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        dotnet-version: &lt;span style="color:#ae81ff"&gt;8.0&lt;/span&gt;.x
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - name: Publish DotNet Blazor Project
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      run: dotnet publish ./src/ViatorLife.Blog/ViatorLife.Blog.csproj -c Release -o release --nologo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    - name: Commit wwwroot to GitHub Pages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      uses: JamesIves/github-pages-deploy-action&lt;span style="color:#960050;background-color:#1e0010"&gt;@&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3.7&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      with:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        GITHUB_TOKEN: &lt;span style="color:#960050;background-color:#1e0010"&gt;$&lt;/span&gt;{{ secrets.GITHUB_TOKEN }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        BRANCH: gh-pages
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        FOLDER: release/wwwroot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果Github Action运行中出现了403的错误信息，为配置中缺少权限，则参照打开权限即可。
&lt;a href="https://juejin.cn/post/7195220128995213368"&gt;https://juejin.cn/post/7195220128995213368&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Blazor WebAssembly&amp;Azure Static App部署记录</title><link>/blog/2025-05-26-blazor-webassembly-azure-staticapp-deploy/</link><pubDate>Mon, 26 May 2025 10:00:00 +0800</pubDate><guid>/blog/2025-05-26-blazor-webassembly-azure-staticapp-deploy/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;对于Blazor WebAssembly，因其特殊性，可以直接部署成站点，浏览器下载后，在浏览器中直接运行，总想薅点羊毛，能够免费部署这类应用，搜寻一方了解到，像是Github Page、Azure Static App等都可以白嫖来部署静态站点。如此，结合部署过程，进行记录一番。&lt;/p&gt;
&lt;h2 id="新建项目"&gt;新建项目&lt;/h2&gt;
&lt;p&gt;此处新建一个Blazor WebAssembly(Standalone)的项目，注意是非Blazor WebApp类型的。此处上传到Github中，因此Github中提前建好仓库，默认克隆到本地好了，开始创建项目。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// 假定路径
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PS C:\demos\ViatorLife.Blog&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行创建命令&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new blazorwasm -n ViatorLife.Blog -f net8.0 -o ./src/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n ViatorLife.Blog
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln add .\src\ViatorLife.Blog.csproj
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;增加gitignore忽略bin/obj等文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; gitignore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行代码推送到Github仓库中，代码部分完事。&lt;/p&gt;
&lt;h2 id="azure部署"&gt;Azure部署&lt;/h2&gt;
&lt;p&gt;访问 &lt;a href="https://portal.azure.com/"&gt;https://portal.azure.com/&lt;/a&gt; 登录 Azure，如果没有账户可以去注册一个。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250526/225652323_ccff4fde-da9f-4490-935e-7d07f0e22a01.png" alt="225652323_ccff4fde-da9f-4490-935e-7d07f0e22a01"&gt;
选择资源组，填写名称，选择免费模式，以此薅羊毛。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250526/225654587_3bc7498e-1a12-41a5-9102-5bde883280f9.png" alt="225654587_3bc7498e-1a12-41a5-9102-5bde883280f9"&gt;
选择仓库源，此处选择Github，找到仓库并选择分支后稍等几秒再往下挪，在生成预设中选择Blazor，会带出位置，但默认生成的应用位置(Client)不可用，修改将其对应于代码中的位置，改成仓库中BlazorWasm项目所在路径(src/ViatorLife.Blog)。纯部署Blazor wasm时不用管Api位置，直接删除API位置框中默认值。输出位置保持不变。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250526/225656544_07005b8f-87fb-40bb-a9e3-582ea914c20b.png" alt="225656544_07005b8f-87fb-40bb-a9e3-582ea914c20b"&gt;
点击查看+创建，默认会生成好Github Action的yml到Github仓库中。用作流水线部署脚本。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250526/225658061_5330ef46-f7d8-480b-8fe7-3d783bccb20f.png" alt="225658061_5330ef46-f7d8-480b-8fe7-3d783bccb20f"&gt;
进入到Github仓库中查看workflow，可见到app_location位置相对于代码中的csproj位置。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;name: Azure Static Web Apps CI/CD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  push:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    branches:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      - main
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  pull_request:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    types: [opened, synchronize, reopened, closed]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    branches:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      - main
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;jobs:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  build_and_deploy_job:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;: github.event_name == &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;push&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt; || (github.event_name == &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;pull_request&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt; &amp;amp;&amp;amp; github.&lt;span style="color:#66d9ef"&gt;event&lt;/span&gt;.action != &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;closed&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    runs-&lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;: ubuntu-latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    name: Build and Deploy Job
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    steps:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      - uses: actions/checkout@v3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        with:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          submodules: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          lfs: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      - name: Build And Deploy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        id: builddeploy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        uses: Azure/&lt;span style="color:#66d9ef"&gt;static&lt;/span&gt;-web-apps-deploy@v1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        with:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          azure_static_web_apps_api_token: &lt;span style="color:#960050;background-color:#1e0010"&gt;$&lt;/span&gt;{{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_CLIFF_0112DA81E }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          repo_token: &lt;span style="color:#960050;background-color:#1e0010"&gt;$&lt;/span&gt;{{ secrets.GITHUB_TOKEN }} &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; Used &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; Github integrations (i.e. PR comments)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          action: &lt;span style="color:#e6db74"&gt;&amp;#34;upload&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          &lt;span style="color:#960050;background-color:#1e0010"&gt;######&lt;/span&gt; Repository/Build Configurations - These values can be configured to match your app requirements. &lt;span style="color:#960050;background-color:#1e0010"&gt;######&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; For more information regarding Static Web App workflow configurations, please visit: https:&lt;span style="color:#75715e"&gt;//aka.ms/swaworkflowconfig&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          app_location: &lt;span style="color:#e6db74"&gt;&amp;#34;src/ViatorLife.Blog&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; App source code path
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          output_location: &lt;span style="color:#e6db74"&gt;&amp;#34;wwwroot&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; Built app content directory - optional
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          &lt;span style="color:#960050;background-color:#1e0010"&gt;######&lt;/span&gt; End of Repository/Build Configurations &lt;span style="color:#960050;background-color:#1e0010"&gt;######&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  close_pull_request_job:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;: github.event_name == &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;pull_request&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt; &amp;amp;&amp;amp; github.&lt;span style="color:#66d9ef"&gt;event&lt;/span&gt;.action == &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;closed&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    runs-&lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;: ubuntu-latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    name: Close Pull Request Job
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    steps:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;      - name: Close Pull Request
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        id: closepullrequest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        uses: Azure/&lt;span style="color:#66d9ef"&gt;static&lt;/span&gt;-web-apps-deploy@v1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        with:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          azure_static_web_apps_api_token: &lt;span style="color:#960050;background-color:#1e0010"&gt;$&lt;/span&gt;{{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_CLIFF_0112DA81E }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;          action: &lt;span style="color:#e6db74"&gt;&amp;#34;close&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建完毕后可在Github仓库中看到Github Action流水线正在执行中，执行完成便可以在Azure Static App中查看访问链接，直接点击便可看到效果。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250526/225659641_b6b9f4a9-8bf0-4d70-bc38-455215063b10.png" alt="225659641_b6b9f4a9-8bf0-4d70-bc38-455215063b10"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250526/225701650_6c9fdfd9-c7e7-4f5b-b670-a8ddccb6eb14.png" alt="225701650_6c9fdfd9-c7e7-4f5b-b670-a8ddccb6eb14"&gt;
后续本地代码更改，推送到Github后，自动触发Github Action发布，发布完毕后自动更新静态站点，如此一来很方便薅羊毛。&lt;/p&gt;</description></item><item><title>ABP Framework-Blazor快速实践</title><link>/blog/2025-05-24-abp-framework-blazor-practice/</link><pubDate>Sat, 24 May 2025 10:00:00 +0800</pubDate><guid>/blog/2025-05-24-abp-framework-blazor-practice/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;很多时候想要一个人快速搭建前端后端，但又不想基于MVC去自己写js开发页面，又或者使用SPA模板，写前端vue/angular/react代码，毕竟这块的学习成本还是有的，借助于Blazor，使用c#尽可能替代js，减少学习成本，并且blazor现在发展也稳定了，还有很多封装好的UI框架，省事许多。&lt;/p&gt;
&lt;p&gt;ABP作为后端开发中强有力的框架，也集成好了Blazor作为前端框架，基础版使用了Blazorise作为UI框架，该UI框架可扩展多套CSS框架，诸如Bootstrap、AntDesign、&lt;/p&gt;
&lt;p&gt;Material等。当然ABP也提供了更为丰富的LeptanX主题（&lt;a href="https://www.leptontheme.com"&gt;https://www.leptontheme.com&lt;/a&gt;）但有点使用受限。基础版Blazorise的集成也足够满足开发需求了。&lt;/p&gt;
&lt;h2 id="下载abpcli"&gt;下载AbpCli&lt;/h2&gt;
&lt;p&gt;我需要使用DotNet8.0版本，对应ABP同级最新版本为8.3.4，因此下载Abp Cli 8.3.4版本。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet tool install --global Volo.Abp.Cli --version 8.3.4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="创建项目"&gt;创建项目&lt;/h2&gt;
&lt;p&gt;创建基于Mysql，EfCore，Basic Theme(Blazorise),使用DotNet8.0的项目。此处使用不分层模板(AppNolayer)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blazor Server，具体为Blazor WebApp(Server)模式&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;abp new AbpBlazorDemo -t app-nolayers -u blazor-server -dbms MySQL -m none --theme basic -csf -v 8.3.4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Blazor WebAssembly，具体为Blazor WebApp(WebAssembly)模式&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;abp new AbpBlazorDemo -t app-nolayers -u blazor -dbms MySQL -m none --theme basic -csf -v 8.3.4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如想使用Auto模式（即运行时，先使用Server模式，等WebAssembly资产下载到浏览器后再使用WebAssembly模式）需要使用分层模板，也就是-t app。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;abp new AbpBlazorDemo -t app -u blazor-webapp -dbms MySQL -m none --theme basic -csf -v 8.3.4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="创建库表"&gt;创建库表&lt;/h2&gt;
&lt;p&gt;更新appsettings.json中的连接字符串&lt;/p&gt;</description></item><item><title>Asp.Net Core Blazor&amp;SignalR 后端消息推送</title><link>/blog/2025-04-12-aspnetcore-blazor-signalr-message-delivery/</link><pubDate>Sat, 12 Apr 2025 10:00:00 +0800</pubDate><guid>/blog/2025-04-12-aspnetcore-blazor-signalr-message-delivery/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;为了能够将后台Job的进度同步前端，借助SignalR和BackgroundJob很方便完成同步。Blazor三种模式下，都能很方便的完成，并且无需引入js包，写前端代码，很是方便。&lt;/p&gt;
&lt;h2 id="blazor-serverwebapp模式"&gt;Blazor Server(WebApp)模式&lt;/h2&gt;
&lt;p&gt;新建项目，选择Blazor WebApp Server模式&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250412/105815627_cecd9d7e-dabd-46ca-af61-3ef977902700.png" alt="105815627_cecd9d7e-dabd-46ca-af61-3ef977902700"&gt;
增加Hub，连接前端和后端通信&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class MessageHub : Hub
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task SendMessage(string message)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await Clients.All.SendAsync(&amp;#34;ReceiveMessage&amp;#34;, message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;program.cs中增加端点&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// Configure SignalR endpoint
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapHub&amp;lt;MessageHub&amp;gt;(&amp;#34;/messageHub&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Blazor Server基于SignalR，所以不需要如下SignalR注册&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSignalR();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;添加Nuget包，该包用于前端页面处理SignalR。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;Microsoft.AspNetCore.SignalR.Client&amp;#34; Version=&amp;#34;8.0.16&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;新建Blazor页，需要注意请求路径需要与端点路径保持一致。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@page &amp;#34;/message&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@using Microsoft.AspNetCore.SignalR.Client
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@implements IAsyncDisposable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@rendermode InteractiveServer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@inject NavigationManager NavigationManager
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PageTitle&amp;gt;Background Messages&amp;lt;/PageTitle&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;h3&amp;gt;Background Messages&amp;lt;/h3&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div class=&amp;#34;message-container&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    @foreach (var message in messages)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &amp;lt;div class=&amp;#34;message&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &amp;lt;span class=&amp;#34;message-time&amp;#34;&amp;gt;@message.TimeStamp.ToString(&amp;#34;HH:mm:ss&amp;#34;)&amp;lt;/span&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &amp;lt;span class=&amp;#34;message-content&amp;#34;&amp;gt;@message.Content&amp;lt;/span&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;style&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .message-container {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        max-height: 500px;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        overflow-y: auto;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        border: 1px solid #ddd;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        padding: 10px;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        margin-top: 20px;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .message {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        padding: 8px;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        margin-bottom: 8px;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        background-color: #f8f9fa;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        border-radius: 4px;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .message-time {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        color: #666;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        margin-right: 10px;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .message-content {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        color: #333;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/style&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@code {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    private HubConnection? hubConnection;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    private List&amp;lt;MessageModel&amp;gt; messages = new();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    private const int MaxMessages = 100;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    protected override async Task OnInitializedAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        hubConnection = new HubConnectionBuilder()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .WithUrl(NavigationManager.ToAbsoluteUri(&amp;#34;/messageHub&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .WithAutomaticReconnect()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        hubConnection.On&amp;lt;string&amp;gt;(&amp;#34;ReceiveMessage&amp;#34;, (message) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            messages.Insert(0, new MessageModel { Content = message, TimeStamp = DateTime.Now });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            if (messages.Count &amp;gt; MaxMessages)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                messages.RemoveAt(messages.Count - 1);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            InvokeAsync(StateHasChanged);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await hubConnection.StartAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async ValueTask DisposeAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        if (hubConnection is not null)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            await hubConnection.DisposeAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    private class MessageModel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        public string Content { get; set; } = string.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        public DateTime TimeStamp { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此，前后端通信即可配置完毕，接下来增加BackgroundJob，其内部发送消息，从而推送到前端展示。新增BackgroundJob&lt;/p&gt;</description></item><item><title>Asp.Net Core Blazor&amp;Blazorise快速实践</title><link>/blog/2025-03-18-aspnetcore-blazor-blazorise-practice/</link><pubDate>Tue, 18 Mar 2025 10:00:00 +0800</pubDate><guid>/blog/2025-03-18-aspnetcore-blazor-blazorise-practice/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;Blazor非常容易上手开发前端项目，结合一些封装好的UI框架更能省心省力。Blazorise（&lt;a href="https://blazorise.com/"&gt;https://blazorise.com/&lt;/a&gt;）应用广。ABP中，默认接入了Blazorise，个人非常喜欢使用ABP完成，整个模块化设计巧妙，因此也想要从UI层面同步使用Blazorise。&lt;/p&gt;
&lt;h2 id="blazorise"&gt;Blazorise&lt;/h2&gt;
&lt;p&gt;皮肤（CSS 框架）和骨骼（C#实现UI组件）分离，皮肤支持 Bootstrap、Tailwind、Bulma、AntDesign、Fluent 2 和 Material 等 CSS 框架。Blazorise提供了众多常用的组件，基于这些组件，可以隔离具体使用的CSS框架。令开发人员只关注于UI展示与交互。&lt;/p&gt;
&lt;p&gt;在官网中，提供了一些常用的构建块，能够节省不少开发工作。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blazorise.com/blocks"&gt;https://blazorise.com/blocks&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="搭建blazor项目"&gt;搭建Blazor项目&lt;/h2&gt;
&lt;p&gt;快速用命令行搭建Blazor WebApp(Server)项目。此处基于DotNet8.0&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new blazor -f net8.0 -int Server -n BlazorServerBlazoriseDemo.App
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="接入blazorise"&gt;接入Blazorise&lt;/h2&gt;
&lt;p&gt;参照Blazorise官方教程，此处简化几个步骤。&lt;/p&gt;
&lt;h3 id="增加css框架"&gt;增加CSS框架&lt;/h3&gt;
&lt;p&gt;此处使用Bootstrap5和图标库&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Bootstrap5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Icons.FontAwesome
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;还有其他包可以配套使用，除了Nuget包，还需要配合css和js文件，具体见官网使用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AntDesign&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.AntDesign
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Icons.FontAwesome
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Bootstrap4&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Bootstrap
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Icons.FontAwesome
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Bulma&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Bulma
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Icons.FontAwesome
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;FluentUI2&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.FluentUI2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Icons.FluentUI
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Material&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Material
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Icons.Material
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Tailwind&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Tailwind
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Blazorise.Icons.FontAwesome
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="配置css和js文件"&gt;配置css和js文件&lt;/h3&gt;
&lt;p&gt;因使用的DotNet8下WebApp的Server模式，在App.razor中删除存在的默认bootstrap.css和fontawesome文件，增加blazorise配套的系列css文件和js文件引用。&lt;/p&gt;</description></item><item><title>Asp.Net Core Blazor快速实践</title><link>/blog/2025-02-24-aspnetcore-blazor-practice/</link><pubDate>Mon, 24 Feb 2025 10:00:00 +0800</pubDate><guid>/blog/2025-02-24-aspnetcore-blazor-practice/</guid><description>&lt;p&gt;Blazor 是一个使用 C# 和.NET 技术构建交互式 Web 应用的框架，它允许开发者在服务器端和客户端使用相同的语言进行开发，从而提高了开发效率和代码的可维护性。可以说是.NET生态系统中的一个革命性框架。Blazor的出现，让开发人员仅通过一种编程语言，就可以实现前后端的全栈开发，很是方便。&lt;/p&gt;
&lt;h2 id="优势所在"&gt;优势所在&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用C#代替JavaScript来创建信息丰富的交互式UI。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;共享使用.NET编写的服务器端和客户端应用逻辑。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将UI呈现为HTML和CSS，以支持众多浏览器，其中包括移动浏览器。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;与新式托管平台（如Docker）集成。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="托管模式"&gt;托管模式&lt;/h2&gt;
&lt;p&gt;DotNet8.0后统一成如下两种模式，各自应用于不同的场景。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250224/105440745_538d9d85-d186-49bf-b7cc-0a8f646892c9.png" alt="105440745_538d9d85-d186-49bf-b7cc-0a8f646892c9"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Blazor Web App项目模板：应用程序在服务器端运行，并使用SignalR实时通讯框架与浏览器进行交互。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;None(Static SSR)，纯静态服务器呈现模板&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Server(Interactive Server)，交互式呈现模板，后端渲染&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WebAssembly(Interactive WebAssembly)，从服务端下载完WebAssembly后使用WebAssembly在浏览器中运行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Auto(Interactive Auto)，先使用Server模式，下载完WebAssembly后使用WebAssembly在浏览器中运行&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;独立 Blazor WebAssembly 应用项目模板：应用程序直接在客户端的WebAssembly中运行，允许C#代码在浏览器中执行，不依赖服务器。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="新建项目"&gt;新建项目&lt;/h2&gt;
&lt;p&gt;新建Blazor Server项目，此处以命令行形式创建，DotNet8.0, Server模式&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new blazor -f net8.0 -int Server -n BlazorServerDemo.App
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其项目结构中，包含
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250224/105442837_3c8642cf-a43e-4f85-b8ec-f4ca919b040b.png" alt="105442837_3c8642cf-a43e-4f85-b8ec-f4ca919b040b"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Layout 文件夹：包含以下布局组件和样式表：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MainLayout 组件 (MainLayout.razor)：应用的布局组件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MainLayout.razor.css：应用主布局的样式表。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NavMenu 组件 (NavMenu.razor)：实现边栏导航。 包括 NavLink 组件 (NavLink)，该组件可向其他 Razor 组件呈现导航链接。 NavLink 组件会在系统加载其组件时自动指示选定状态，这有助于用户了解当前显示的组件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NavMenu.razor.css：应用导航菜单的样式表。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pages 文件夹：包含 Blazor 应用的可路由 Razor 组件 (.razor)。 每个页面的路由都是使用 @page 指令指定的。 该模板包括以下组件：&lt;/p&gt;</description></item><item><title>DotNet&amp;Coverlet统计单元测试覆盖率</title><link>/blog/2025-01-12-dotnet-and-coverlet-unittest-coveragerate/</link><pubDate>Sun, 12 Jan 2025 10:00:00 +0800</pubDate><guid>/blog/2025-01-12-dotnet-and-coverlet-unittest-coveragerate/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;代码覆盖率是衡量代码质量的一个指标。较高的代码覆盖率能够显著提升开发者对代码的信心。从逻辑层面来看，代码经过的测试越详尽、越全面，其存在漏洞的概率也就越低。毕竟，在软件开发的实际过程中，要实现一个完全没有任何漏洞的项目，这几乎是一个难以企及的乌托邦式理想。&lt;/p&gt;
&lt;h2 id="coverlet"&gt;Coverlet&lt;/h2&gt;
&lt;p&gt;在日常开发中，像 Visual Studio、Rider 这类常用的集成开发环境（IDE），能够直接查看代码覆盖率。然而，若要将覆盖率管控纳入自动化的质量管控流程，例如 Github Actions、Azure Devops、Github CI 以及 Sonarqube 等系统时，就需要额外开展一些工作。在这种情况下，开源项目 Coverlet(&lt;a href="https://github.com/coverlet-coverage/coverlet"&gt;https://github.com/coverlet-coverage/coverlet&lt;/a&gt;) 能够发挥重要作用，帮助我们实现这一目标 。&lt;/p&gt;
&lt;h3 id="相关包"&gt;相关包&lt;/h3&gt;
&lt;p&gt;因执行工具的不同，使用不同的包。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;coverlet.collector：VSTest 引擎集成&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;coverlet.msbuild：MSBuild 任务集成&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;coverlet.console：作为 .NET 全局工具（支持独立集成测试）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从coverlet源码中也可看出，coverlet.collector/coverlet.msbuild/coverlet.console都是依赖coverlet.core，前三者之间则没有依赖关系。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250112/172313340_b7f570c7-3dce-474c-828e-dcd3a7487c34.png" alt="172313340_b7f570c7-3dce-474c-828e-dcd3a7487c34"&gt;&lt;/p&gt;
&lt;h3 id="更换默认包"&gt;更换默认包&lt;/h3&gt;
&lt;p&gt;默认场景下，xUnit测试项目自带的包为coverlet.collector&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250112/172315004_821713c5-e4f7-4876-bf1f-206df5484d26.png" alt="172315004_821713c5-e4f7-4876-bf1f-206df5484d26"&gt;
在如下示例中，我将使用coverlet.msbuild包(&lt;a href="https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md"&gt;https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md&lt;/a&gt;)，因此只装coverlet.msbuild包，新建单元测试项目时，将默认使用的包coverlet.collector删除。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250112/172316532_9ee62e21-843f-4a0b-b7fa-697acccf05dc.png" alt="172316532_9ee62e21-843f-4a0b-b7fa-697acccf05dc"&gt;&lt;/p&gt;
&lt;h2 id="reportgenerator"&gt;ReportGenerator&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/danielpalme/ReportGenerator"&gt;https://github.com/danielpalme/ReportGenerator&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;生成漂亮的报告，需要全局安装，这个包可以将coverlet生成的文件转换成精美的图表文档。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet tool install -g dotnet-reportgenerator-globaltool
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;使用时只需指定文件执行即可&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;reportgenerator -reports:coverage.opencover.xml -targetdir:report
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在当前目录下会创建report文件夹，其中index.html为更加明了易懂的报表文档。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20250112/172318369_9f957e22-c4c9-457f-acf1-2cc040ddf828.png" alt="172318369_9f957e22-c4c9-457f-acf1-2cc040ddf828"&gt;&lt;/p&gt;
&lt;h2 id="示例"&gt;示例&lt;/h2&gt;
&lt;h3 id="新建项目"&gt;新建项目&lt;/h3&gt;
&lt;p&gt;新建xUnit的测试项目ReportTestDemo.Tests，此处使用DotNet6.0，该项目中同时存在被测类和测试代码，如下移除默认包，增加coverlet.msbuild包。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir ReportTestDemo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ReportTestDemo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; xunit -n ReportTestDemo.Tests --framework net6.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;add&lt;/span&gt; .&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;ReportTestDemo.Tests&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt; package coverlet.msbuild --version &lt;span style="color:#ae81ff"&gt;6.0&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;remove&lt;/span&gt; .&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;ReportTestDemo.Tests&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt; package coverlet.collector
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="新建被测类和测试代码类"&gt;新建被测类和测试代码类&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// OrderService.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ReportTestDemo.Tests;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; GetCurrentValue()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; GetExpectedValue()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// OrderServiceUnitTest.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ReportTestDemo.Tests;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ExcludeFromCodeCoverage]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderServiceUnitTest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Test1()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; orderService = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderService();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Assert.Equal(orderService.GetCurrentValue(), orderService.GetExpectedValue());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="执行测试"&gt;执行测试&lt;/h3&gt;
&lt;p&gt;执行命令运行测试，当前目录下生成了coverage.opencover.xml文件。&lt;/p&gt;</description></item><item><title>ABP Framework-EventBus源码解析</title><link>/blog/2024-12-26-parse-the-source-code-for-eventbus/</link><pubDate>Thu, 26 Dec 2024 10:00:00 +0800</pubDate><guid>/blog/2024-12-26-parse-the-source-code-for-eventbus/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Event-Bus"&gt;https://abp.io/docs/6.0/Event-Bus&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="version"&gt;Version&lt;/h1&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h1 id="package"&gt;Package&lt;/h1&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.EventBus.Abstractions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.EventBus //依赖Volo.Abp.EventBus.Abstractions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.EventBus.Azure //依赖Volo.Abp.EventBus
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.EventBus.Kafka //依赖Volo.Abp.EventBus
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.EventBus.RabbitMQ //依赖Volo.Abp.EventBus
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.EventBus.Rebus //依赖Volo.Abp.EventBus
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="eventhandler"&gt;EventHandler&lt;/h1&gt;
&lt;p&gt;当某个事件发生，如果想要订阅事件，则是定义一个Handler来处理，ABP中经常使用的模式简要如下，以本地事件为例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyHandler&lt;/span&gt; : ILocalEventHandler&amp;lt;StockCountChangedEvent&amp;gt;, ITransientDependency
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task HandleEventAsync(StockCountChangedEvent eventData)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//TODO: your code that does something on the event&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;EventHandler从源头上便区分为LocalHandler和DistributedHandler以应付不同的使用场景。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241226/205030313_458ea547-4b76-4cbd-b8bc-478e661826b7.png" alt="205030313_458ea547-4b76-4cbd-b8bc-478e661826b7"&gt;
在项目启动阶段，在AbpEventBusModule中，会扫描所有LocalEventHandler和DistributedEventHandler分别注册到AbpLocalEventBusOptionsh和AbpDistributedEventBusOptions。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[DependsOn(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpEventBusAbstractionsModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpMultiTenancyModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpJsonModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpGuidsModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpBackgroundWorkersModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpDistributedLockingAbstractionsModule)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    )]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AbpEventBusModule&lt;/span&gt; : AbpModule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; PreConfigureServices(ServiceConfigurationContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        AddEventHandlers(context.Services);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddEventHandlers(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; localHandlers = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;Type&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; distributedHandlers = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;Type&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 扫描项目中的Handlers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.OnRegistred(context =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, &lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(ILocalEventHandler&amp;lt;&amp;gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                localHandlers.Add(context.ImplementationType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, &lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(IDistributedEventHandler&amp;lt;&amp;gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                distributedHandlers.Add(context.ImplementationType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 存储LocalHandlers和DistributedHandlers到各自Options中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.Configure&amp;lt;AbpLocalEventBusOptions&amp;gt;(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            options.Handlers.AddIfNotContains(localHandlers);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.Configure&amp;lt;AbpDistributedEventBusOptions&amp;gt;(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            options.Handlers.AddIfNotContains(distributedHandlers);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这两个Options将贯穿后续EventBus。&lt;/p&gt;</description></item><item><title>ABP Framework-EmailSending源码解析</title><link>/blog/2024-11-08-parse-the-source-code-for-email-sending/</link><pubDate>Fri, 08 Nov 2024 10:00:00 +0800</pubDate><guid>/blog/2024-11-08-parse-the-source-code-for-email-sending/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Emailing"&gt;https://abp.io/docs/6.0/Emailing&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="version"&gt;Version&lt;/h2&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h2 id="package"&gt;Package&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.Emailing
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其组件包为&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.MailKit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在Volo.Abp.Emailing中本身具有SmtpClient，这是微软提供的，如果可以满足使用，可以不需要在加入Volo.Abp.MailKit包。&lt;/p&gt;
&lt;h2 id="iemailsender"&gt;IEmailSender&lt;/h2&gt;
&lt;p&gt;其核心接口如下，其中Queue方法目的是用来做延迟发送，走BackgroundJob，再BackgroundJob中再调用IEmailSender的SendAsync方法发送。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IEmailSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// Sends an email.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task SendAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; to,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; subject,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; body,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; isBodyHtml = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        AdditionalEmailSendingArgs? additionalEmailSendingArgs = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// Sends an email.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task SendAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; to,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; subject,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; body,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; isBodyHtml = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        AdditionalEmailSendingArgs? additionalEmailSendingArgs = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// Sends an email.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;mail&amp;#34;&amp;gt;Mail to be sent&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;normalize&amp;#34;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// Should normalize email?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// If true, it sets sender address/name if it&amp;#39;s not set before and makes mail encoding UTF-8.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task SendAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        MailMessage mail,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; normalize = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// Adds an email to queue to send via background jobs.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task QueueAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; to,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; subject,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; body,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; isBodyHtml = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        AdditionalEmailSendingArgs? additionalEmailSendingArgs = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// Adds an email to queue to send via background jobs.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task QueueAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; to,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; subject,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; body,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; isBodyHtml = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        AdditionalEmailSendingArgs? additionalEmailSendingArgs = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其接口实现部分类图如下。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241108/204953959_df537d38-1a79-4e5f-92e7-9192ffd95f24.png" alt="204953959_df537d38-1a79-4e5f-92e7-9192ffd95f24"&gt;
Volo.Abp.Emailing库中包含了EmailSenderBase类，提供了一个核心的抽象方法SendEmailAsync&lt;/p&gt;</description></item><item><title>ABP Framework-ExceptionHanding源码解析</title><link>/blog/2024-10-25-parse-the-source-code-for-exception-handing/</link><pubDate>Fri, 25 Oct 2024 10:00:00 +0800</pubDate><guid>/blog/2024-10-25-parse-the-source-code-for-exception-handing/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Exception-Handling"&gt;https://abp.io/docs/6.0/Exception-Handling&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="version"&gt;Version&lt;/h2&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h2 id="package"&gt;Package&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.ExceptionHandling
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="aspnetcore异常体系"&gt;AspNetCore异常体系&lt;/h2&gt;
&lt;p&gt;Abp的异常体系基于Asp.Net Core的异常体系，后者简要概括分为两部分，第一个是全局异常处理即ExceptionHandlingMiddleware,还有一个是Mvc middleware中的ExceptionFilter，这部分可以参考文档：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0"&gt;https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/zh-cn/aspnet/core/web-api/handle-errors?view=aspnetcore-8.0"&gt;https://learn.microsoft.com/zh-cn/aspnet/core/web-api/handle-errors?view=aspnetcore-8.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://source.dot.net/#BasicWebSite/Filters/TestExceptionFilter.cs,9"&gt;https://source.dot.net/#BasicWebSite/Filters/TestExceptionFilter.cs,9&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="abp异常体系"&gt;Abp异常体系&lt;/h2&gt;
&lt;h3 id="abp异常处理"&gt;Abp异常处理&lt;/h3&gt;
&lt;p&gt;Abp在AspNetCore的基础上增加了与之对应的类，一个是AbpExceptionHandlingMiddleware.cs和AbpExceptionFilter.cs，其异常处理思路是一致的，当Controller/Action中出现异常时，AbpExceptionFilter先处理，如下代码所示&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task HandleAndWrapException(ExceptionContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exceptionHandlingOptions = context.GetRequiredService&amp;lt;IOptions&amp;lt;AbpExceptionHandlingOptions&amp;gt;&amp;gt;().Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//异常信息转换为错误信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exceptionToErrorInfoConverter = context.GetRequiredService&amp;lt;IExceptionToErrorInfoConverter&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//记录异常信息日志&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; logLevel = context.Exception.GetLogLevel();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; remoteServiceErrorInfoBuilder = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StringBuilder();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    remoteServiceErrorInfoBuilder.AppendLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;---------- {nameof(RemoteServiceErrorInfo)} ----------&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService&amp;lt;IJsonSerializer&amp;gt;().Serialize(remoteServiceErrorInfo, indented: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; logger = context.GetService&amp;lt;ILogger&amp;lt;AbpExceptionFilter&amp;gt;&amp;gt;(NullLogger&amp;lt;AbpExceptionFilter&amp;gt;.Instance);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    logger.LogException(context.Exception, logLevel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//异常信息消息通知&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; context.GetRequiredService&amp;lt;IExceptionNotifier&amp;gt;().NotifyAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ExceptionNotificationContext(context.Exception));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//异常错误码转换&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (context.Exception &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; AbpAuthorizationException)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; context.HttpContext.RequestServices.GetRequiredService&amp;lt;IAbpAuthorizationExceptionHandler&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .HandleAsync(context.Exception.As&amp;lt;AbpAuthorizationException&amp;gt;(), context.HttpContext);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        context.HttpContext.Response.StatusCode = (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)context
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .GetRequiredService&amp;lt;IHttpExceptionStatusCodeFinder&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .GetStatusCode(context.HttpContext, context.Exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ObjectResult(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; RemoteServiceErrorResponse(remoteServiceErrorInfo));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//标识异常被处理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    context.Exception = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;; &lt;span style="color:#75715e"&gt;//Handled!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中核心类为IExceptionToErrorInfoConverter和IHttpExceptionStatusCodeFinder，通过对应实现来将不同类型的异常转换成对应的错误信息以及响应的状态码，如Abp的UserFriendlyException便是在这其中处理从而有403的状态码。&lt;/p&gt;</description></item><item><title>ABP Framework-Cache源码解析</title><link>/blog/2024-09-16-parse-the-source-code-for-caching/</link><pubDate>Mon, 16 Sep 2024 10:00:00 +0800</pubDate><guid>/blog/2024-09-16-parse-the-source-code-for-caching/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Caching"&gt;https://abp.io/docs/6.0/Caching&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="version"&gt;Version&lt;/h2&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h2 id="package"&gt;Package&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.Caching
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;基于Redis的缓存包&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.Caching.StackExchangeRedis
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Abp的缓存包作为基础模块，在Abp模板中被很多模块所依赖，因此开发中，一般不用手动加入AbpCachingModule，但对于Redis的module，需要手动加入模块依赖中。&lt;/p&gt;
&lt;h2 id="缓存实现"&gt;缓存实现&lt;/h2&gt;
&lt;p&gt;ABP的缓存其本质上还是借助于Asp.Net Core的缓存接口与实现，包括IMemoryCache和IDistributedCache，从Abp.Caching的模块中可见&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[DependsOn(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpThreadingModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpSerializationModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpUnitOfWorkModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpMultiTenancyModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpJsonModule))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AbpCachingModule&lt;/span&gt; : AbpModule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ConfigureServices(ServiceConfigurationContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        context.Services.AddMemoryCache();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        context.Services.AddDistributedMemoryCache();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;了解Asp.Net Core中的缓存&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/zh-cn/aspnet/core/performance/caching/memory?view=aspnetcore-8.0"&gt;内存缓存&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed?view=aspnetcore-8.0"&gt;分布式缓存&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在该缓存上，ABP设计了IDistributedCache&amp;lt;,&amp;gt;, 提供了一些额外的方法扩展使用场景，如下简要列出几个方法。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IDistributedCache&lt;/span&gt;&amp;lt;TCacheItem, TCacheKey&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; TCacheItem : &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TCacheItem?&amp;gt; GetAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;KeyValuePair&amp;lt;TCacheKey, TCacheItem?&amp;gt;[]&amp;gt; GetManyAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TCacheItem?&amp;gt; GetOrAddAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;KeyValuePair&amp;lt;TCacheKey, TCacheItem?&amp;gt;[]&amp;gt; GetOrAddManyAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task SetAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task SetManyAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task RefreshAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task RefreshManyAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task RemoveAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task RemoveManyAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;该部分类图如下
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20240916/204829956_834edc61-f7e2-45de-9958-dd0c8a801e05.png" alt="204829956_834edc61-f7e2-45de-9958-dd0c8a801e05"&gt;
IDistributedCache&lt;!-- raw HTML omitted --&gt;实则是指定了Key类型为String的IDistributedCache&amp;lt;TCacheItem,TCacheKey&amp;gt;。&lt;/p&gt;</description></item><item><title>ABP Framework-Blob Storage源码解析</title><link>/blog/2024-08-13-parse-the-source-code-for-blob-storage/</link><pubDate>Tue, 13 Aug 2024 10:00:00 +0800</pubDate><guid>/blog/2024-08-13-parse-the-source-code-for-blob-storage/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Blob-Storing"&gt;https://abp.io/docs/6.0/Blob-Storing&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h3 id="package"&gt;Package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BlobStoring
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BlobStoring.Aliyun
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BlobStoring.Aws
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BlobStoring.Azure
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BlobStoring.FileSystem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BlobStoring.Minio
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//独立模块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BlobStoring.Database.*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="blobprovider"&gt;BlobProvider&lt;/h3&gt;
&lt;p&gt;当有一个Blob需要存储到存储介质上，因为存储介质各有不同，调用每一个存储介质的方式有所差异，ABP在各式各样的存储介质上抽象一层，提供基础方法来确保对于Blob的常规操作，如存储，查询，删除，判断是否存在等方法。代码简要如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IBlobContainer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task SaveAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; name, Stream stream, &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; overrideExisting = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; DeleteAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; ExistsAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;Stream&amp;gt; GetAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;Stream&amp;gt; GetOrNullAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时提供了Azure/Aws/Aliyun/File/Db/Minio等且支持扩展的Provider，来隔离实际的调用方式。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20240813/204746327_35dd9ce8-17d6-4091-ae75-95506fb23e0d.png" alt="204746327_35dd9ce8-17d6-4091-ae75-95506fb23e0d"&gt;
在不同的存储介质下，调用不同的包，此处以Minio为例，在MinioBlobProvider(Volo.Abp.BlobStoring.Minio包)中，依赖Minio包，将Blob移交到Minio的参数下。代码简要如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MinioBlobProvider&lt;/span&gt; : BlobProviderBase, ITransientDependency
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SaveAsync(BlobProviderSaveArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; blobName = MinioBlobNameCalculator.Calculate(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 生成MinioClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; client = GetMinioClient(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; containerName = GetContainerName(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 上传Blob到Minio中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.PutObjectAsync(containerName, blobName, args.BlobStream, args.BlobStream.Length);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; DeleteAsync(BlobProviderDeleteArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; blobName = MinioBlobNameCalculator.Calculate(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 生成MinioClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; client = GetMinioClient(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; containerName = GetContainerName(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 请求Minio删除文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; BlobExistsAsync(client, containerName, blobName))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.RemoveObjectAsync(containerName, blobName);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; ExistsAsync(BlobProviderExistsArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; blobName = MinioBlobNameCalculator.Calculate(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 生成MinioClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; client = GetMinioClient(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; containerName = GetContainerName(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 请求Minio查询文件是否存在&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.StatObjectAsync(containerName, blobName);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;(Exception)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; MinioClient GetMinioClient(BlobProviderArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; configuration = args.Configuration.GetMinioConfiguration();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; client = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; MinioClient()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .WithEndpoint(configuration.EndPoint)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            .WithCredentials(configuration.AccessKey, configuration.SecretKey);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (configuration.WithSSL)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            client.WithSSL();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; client.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/   
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于其他Provider来讲，大致过程相同。参照同样的过程，可以扩展需要的存储介质Provider。&lt;/p&gt;</description></item><item><title>ABP Framework-Feature源码解析</title><link>/blog/2024-07-05-parse-the-source-code-for-feature/</link><pubDate>Fri, 05 Jul 2024 10:00:00 +0800</pubDate><guid>/blog/2024-07-05-parse-the-source-code-for-feature/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Features"&gt;https://abp.io/docs/6.0/Features&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h3 id="package"&gt;Package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.Features
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//独立模块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.FeatureManagement.*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="featuredefinitionprovider"&gt;FeatureDefinitionProvider&lt;/h3&gt;
&lt;h4 id="内容定义"&gt;内容定义&lt;/h4&gt;
&lt;p&gt;在每个module中都可以定义Feature，遵从如下示例格式&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyFeatureDefinitionProvider&lt;/span&gt; : FeatureDefinitionProvider
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Define(IFeatureDefinitionContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; myGroup = context.AddGroup(&lt;span style="color:#e6db74"&gt;&amp;#34;MyApp&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; myGroup.AddFeature(&lt;span style="color:#e6db74"&gt;&amp;#34;MyApp.PdfReporting&amp;#34;&lt;/span&gt;, defaultValue: &lt;span style="color:#e6db74"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; myGroup.AddFeature(&lt;span style="color:#e6db74"&gt;&amp;#34;MyApp.MaxProductCount&amp;#34;&lt;/span&gt;, defaultValue: &lt;span style="color:#e6db74"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在AbpFeaturesModule中，基类FeatureDefinitionProvider在预初始化时会被扫描到。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[DependsOn(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpLocalizationModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpMultiTenancyModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpValidationModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpAuthorizationAbstractionsModule)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    )]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AbpFeaturesModule&lt;/span&gt; : AbpModule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; PreConfigureServices(ServiceConfigurationContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        context.Services.OnRegistred(FeatureInterceptorRegistrar.RegisterIfNeeded);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        AutoAddDefinitionProviders(context.Services);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AutoAddDefinitionProviders(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; definitionProviders = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;Type&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 收集所有继承自IFeatureDefinitionProvider的类&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.OnRegistred(context =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(IFeatureDefinitionProvider).IsAssignableFrom(context.ImplementationType))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                definitionProviders.Add(context.ImplementationType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 将所有SettingDefinitionProvider保存到AbpFeatureOptions中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.Configure&amp;lt;AbpFeatureOptions&amp;gt;(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            options.DefinitionProviders.AddIfNotContains(definitionProviders);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如上需要注意AbpFeatureOptions，所有的FeatureDefinitionProvider最终汇总到其内部的集合中，同时查看该类可知也存储着不同值来源的FeatureValueProvider。&lt;/p&gt;</description></item><item><title>ABP Framework-Setting源码解析</title><link>/blog/2024-06-28-parse-the-source-code-for-setting/</link><pubDate>Fri, 28 Jun 2024 10:00:00 +0800</pubDate><guid>/blog/2024-06-28-parse-the-source-code-for-setting/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Settings"&gt;https://abp.io/docs/6.0/Settings&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h3 id="package"&gt;Package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.Settings
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//独立模块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.SettingManagement.*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="settingdefinitionprovider"&gt;SettingDefinitionProvider&lt;/h3&gt;
&lt;h4 id="内容定义"&gt;内容定义&lt;/h4&gt;
&lt;p&gt;在每个module中都可以定义Setting，遵从如下示例格式&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;YourSettingDefinitionProvider&lt;/span&gt; : SettingDefinitionProvider
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Define(ISettingDefinitionContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Add(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SettingDefinition(&lt;span style="color:#e6db74"&gt;&amp;#34;Smtp.Host&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SettingDefinition(&lt;span style="color:#e6db74"&gt;&amp;#34;Smtp.Port&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;25&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SettingDefinition(&lt;span style="color:#e6db74"&gt;&amp;#34;Smtp.UserName&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SettingDefinition(&lt;span style="color:#e6db74"&gt;&amp;#34;Smtp.Password&amp;#34;&lt;/span&gt;, isEncrypted: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SettingDefinition(&lt;span style="color:#e6db74"&gt;&amp;#34;Smtp.EnableSsl&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在AbpSettingModule中，基类SettingDefinitionProvider在预初始化时会被扫描到。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[DependsOn(
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpLocalizationAbstractionsModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpSecurityModule),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    typeof(AbpMultiTenancyModule)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    )]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AbpSettingsModule&lt;/span&gt; : AbpModule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; PreConfigureServices(ServiceConfigurationContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        AutoAddDefinitionProviders(context.Services);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AutoAddDefinitionProviders(IServiceCollection services)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; definitionProviders = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;Type&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 收集所有继承自ISettingDefinitionProvider的类&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.OnRegistred(context =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(ISettingDefinitionProvider).IsAssignableFrom(context.ImplementationType))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                definitionProviders.Add(context.ImplementationType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 将所有SettingDefinitionProvider保存到AbpSettingOptions中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.Configure&amp;lt;AbpSettingOptions&amp;gt;(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            options.DefinitionProviders.AddIfNotContains(definitionProviders);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如上需要注意AbpSettingOptions，所有的SettingDefinitionProvider最终汇总到其内部的集合中，同时查看该类可知也存储着不同值来源的SettingValueProvider。&lt;/p&gt;</description></item><item><title>ABP Framework-MultiTenancy源码解析</title><link>/blog/2024-05-19-parse-the-source-code-for-multitenancy/</link><pubDate>Sun, 19 May 2024 10:00:00 +0800</pubDate><guid>/blog/2024-05-19-parse-the-source-code-for-multitenancy/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Multi-Tenancy"&gt;https://abp.io/docs/6.0/Multi-Tenancy&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h3 id="package"&gt;Package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.AspNetCore.MultiTenancy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.MultiTenancy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//独立模块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.TenantManagement.*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="多租户中间件"&gt;多租户中间件&lt;/h3&gt;
&lt;p&gt;如想要使用上多租户，中间件是必不可少的一部分，MultiTenancyMiddleware.cs。&lt;/p&gt;
&lt;p&gt;请求进入后，首先进行的是租户解析，简要查看租户中间件源码，省略与简化后如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MultiTenancyMiddleware&lt;/span&gt; : IMiddleware, ITransientDependency
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task InvokeAsync(HttpContext context, RequestDelegate next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 从请求信息中解析拿到租户信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; tenant = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _tenantConfigurationProvider.GetAsync(saveResolveResult: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 如果是新租户则切换上下文&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (tenant?.Id != _currentTenant.Id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 更新当前租户&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; (_currentTenant.Change(tenant?.Id, tenant?.Name))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (_tenantResolveResultAccessor.Result != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    _tenantResolveResultAccessor.Result.AppliedResolvers.Contains(QueryStringTenantResolveContributor.ContributorName))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 租户信息记录到Cookie中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    AbpMultiTenancyCookieHelper.SetTenantCookie(context, _currentTenant.Id, _options.TenantKey);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 下一个中间件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; next(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 下一个中间件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; next(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如上核心部分几个抽象接口：&lt;/p&gt;</description></item><item><title>ABP Framework-BackgroundJob源码解析</title><link>/blog/2024-04-05-parse-the-source-code-for-backgroundjob/</link><pubDate>Fri, 05 Apr 2024 10:00:00 +0800</pubDate><guid>/blog/2024-04-05-parse-the-source-code-for-backgroundjob/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Background-Jobs"&gt;https://abp.io/docs/6.0/Background-Jobs&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h3 id="package"&gt;Package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.HangFire &lt;span style="color:#75715e"&gt;//依赖Hangfire和Volo.Abp.BackgroundJobs.Abstractions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.Quartz &lt;span style="color:#75715e"&gt;//依赖Quartz和Volo.Abp.BackgroundJobs.Abstractions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.RabbitMQ &lt;span style="color:#75715e"&gt;//依赖RabbitMQ和Volo.Abp.BackgroundJobs.Abstractions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs &lt;span style="color:#75715e"&gt;//依赖BackgroundWorker和Volo.Abp.BackgroundJobs.Abstractions，与上第三方组件平级&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.Abstractions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//独立模块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.Domain
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.Domain.Shared
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.EntityFrameworkCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundJobs.MongoDB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="backgroundjob"&gt;BackgroundJob&lt;/h3&gt;
&lt;h4 id="jobargs"&gt;JobArgs&lt;/h4&gt;
&lt;p&gt;作为最小执行单元，使用时只需继承BackgroundJob或AsyncBackgroundJob。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EmailSendingJob&lt;/span&gt; : AsyncBackgroundJob&amp;lt;EmailSendingArgs&amp;gt;, ITransientDependency
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IEmailSender _emailSender;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; EmailSendingJob(IEmailSender emailSender)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _emailSender = emailSender;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExecuteAsync(EmailSendingArgs args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _emailSender.SendAsync(args.EmailAddress, args.Subject, args.Body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;前提是需要一个JobArgs，来传递Job执行需要的一些参数。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EmailSendingArgs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; EmailAddress { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Subject { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Body { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;文档中提到BackgroundJobName，在代码中体现为BackgroundJobNameAttribute。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20240405/204419812_a16ae82e-8133-4d8b-91ea-4557d06d2d11.png" alt="204419812_a16ae82e-8133-4d8b-91ea-4557d06d2d11"&gt;
实则是在当使用了RabbitMQ或者类似的第三方组件时才需要拿到名字作为队列名，如果在Hangfire或者Quartz或者ABP自身的BackgroundJobWorker管理，都无需要使用到BackgroundJobNameAttribute。当然如果加上了，也没有影响，只是加入Job时，会以加上的名字作为Job名，加入到执行队列中。以DefaultBackgroundJobManager为例&lt;/p&gt;</description></item><item><title>ABP Framework-BackgroundWorker源码解析</title><link>/blog/2024-03-17-parse-the-source-code-for-backgroundworker/</link><pubDate>Sun, 17 Mar 2024 10:00:00 +0800</pubDate><guid>/blog/2024-03-17-parse-the-source-code-for-backgroundworker/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Background-Workers"&gt;https://abp.io/docs/6.0/Background-Workers&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h3 id="package"&gt;Package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundWorkers.Hangfire &lt;span style="color:#75715e"&gt;//依赖Hangfire和Volo.Abp.BackgroundWorkers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundWorkers.Quartz &lt;span style="color:#75715e"&gt;//依赖Quartz和Volo.Abp.BackgroundWorkers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.BackgroundWorkers
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="backgroundworker"&gt;BackgroundWorker&lt;/h3&gt;
&lt;p&gt;和BackgroundJob不同，Worker更多在后台定期的工作，Job更多的是特定时间执行。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20240317/204253905_3e0c1e93-37b0-4f66-a375-d348c5289ffe.png" alt="204253905_3e0c1e93-37b0-4f66-a375-d348c5289ffe"&gt;
在源码中，IBackgroundWorker作为抽象，承担BackgroundWorkerManager和具体的BackgroundWork执行间的衔接，并且确定所有的Worker都为单例。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IBackgroundWorker&lt;/span&gt; : IRunnable, ISingletonDependency
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="backgroundworkerbase"&gt;BackgroundWorkerBase&lt;/h4&gt;
&lt;p&gt;在BackgroundWorkerBase也没有实际的处理，只是封装了下Logger服务和ServiceProvider，以便于在单例的Worker中使用IOC服务。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;abstract&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BackgroundWorkerBase&lt;/span&gt; : IBackgroundWorker
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IAbpLazyServiceProvider LazyServiceProvider { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IServiceProvider ServiceProvider { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; ILoggerFactory LoggerFactory =&amp;gt; LazyServiceProvider.LazyGetRequiredService&amp;lt;ILoggerFactory&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; ILogger Logger =&amp;gt; LazyServiceProvider.LazyGetService&amp;lt;ILogger&amp;gt;(provider =&amp;gt; LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; Task StartAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Logger.LogDebug(&lt;span style="color:#e6db74"&gt;&amp;#34;Started background worker: &amp;#34;&lt;/span&gt; + ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Task.CompletedTask;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; Task StopAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Logger.LogDebug(&lt;span style="color:#e6db74"&gt;&amp;#34;Stopped background worker: &amp;#34;&lt;/span&gt; + ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Task.CompletedTask;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ToString()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; GetType().FullName;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当项目中需要定义Worker时，直接继承BackgroundWorkerBase，这类Worker在应用程序启动时调用Start，应用关闭时调用Stop，使用场景较少。&lt;/p&gt;</description></item><item><title>ABP Framework-Permission&amp;Authorization源码解析</title><link>/blog/2024-02-20-parse-the-source-code-for-permission-and-authorization/</link><pubDate>Tue, 20 Feb 2024 10:00:00 +0800</pubDate><guid>/blog/2024-02-20-parse-the-source-code-for-permission-and-authorization/</guid><description>&lt;p&gt;&lt;a href="https://abp.io/docs/6.0/Authorization"&gt;https://abp.io/docs/6.0/Authorization&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="前提了解"&gt;前提了解&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-8.0"&gt;https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-8.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ABP基于Asp.Net Core的授权检查与权限声明体系，使用[AuthorizeAttribute]，而对于Asp.Net Core的权限声明简要描述下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Controller/Action上加AuthorizeAttribute,使用Role或Policy来限制方法的访问。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;请求到达时，Authorize判断前提默认为鉴权通过的标记了IsAuthenticated。其次会按照Role或Policy，从当前请求的HttpContext中或者查询数据库/服务中，拿到有关信息，如果符合Role或Policy则授权检查通过。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;请求方法，执行逻辑，返回结果。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一般场景很少直接使用Role，并且此文基于ABP的实践，采用ABP中的权限定义来作为Policy，具体便是，方法上标记Policy=PermissionDefinition.XXXName。在授权检查时，查询当前人的角色或自身具备了该权限，则授权检查通过。&lt;/p&gt;
&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;6.0.3&lt;/p&gt;
&lt;h3 id="package"&gt;Package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.Authorization
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.Authorization.Abstractions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//独立模块&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.PermissionManagement.*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="权限定义"&gt;权限定义&lt;/h3&gt;
&lt;h4 id="权限定义树"&gt;权限定义树&lt;/h4&gt;
&lt;p&gt;在各垂直业务的独立模块中，一般会定义各自的权限定义树，比如User/Tenant等，也比如订单模块，地址模块等，都会在模块内构建好自身的权限定义树。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WorkSpacePermissions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GroupName = &lt;span style="color:#e6db74"&gt;&amp;#34;WorkSpaceManagement&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Projects&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Default = GroupName + &lt;span style="color:#e6db74"&gt;&amp;#34;.Projects&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Create = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.Create&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Update = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.Update&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Delete = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.Delete&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ManageTypes = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.ManageTypes&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Members&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Default = Projects.Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.Members&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Create = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.Create&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Update = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.Update&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Delete = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.Delete&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ManageRoles = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.ManageRoles&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ManageInvites = Default + &lt;span style="color:#e6db74"&gt;&amp;#34;.ManageInvites&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WorkSpacePermissionDefinitionProvider&lt;/span&gt; : PermissionDefinitionProvider
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Define(IPermissionDefinitionContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; projectsPermission = workSpaceGroup.AddPermission(WorkSpacePermissions.Projects.Default);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectsPermission.AddChild(WorkSpacePermissions.Projects.Create);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectsPermission.AddChild(WorkSpacePermissions.Projects.Update);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectsPermission.AddChild(WorkSpacePermissions.Projects.Delete);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectsPermission.AddChild(WorkSpacePermissions.Projects.ManageTypes);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; projectMembersPermission = projectsPermission.AddChild(WorkSpacePermissions.Projects.Members.Default);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.Create);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.Update);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.Delete);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.ManageRoles);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;     projectMembersPermission.AddChild(WorkSpacePermissions.Projects.Members.ManageInvites);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以构建多级权限（顶级Group不参与权限）只用来权限分组。在ABP6.0版本，该部分权限定义不会持久化到数据库中，此处仅以6.0版本为主（8.0会持久化）。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20240220/204122669_040de5be-d22a-4994-9098-bf1c3d98c8e9.png" alt="204122669_040de5be-d22a-4994-9098-bf1c3d98c8e9"&gt;
IPermissionDefinitionProvider中定义了如下三个接口，但使用频繁的还是Define&lt;/p&gt;</description></item><item><title>ABP Framework-从零搭建</title><link>/blog/2024-01-15-build-the-abp-framework-from-the-ground-up/</link><pubDate>Mon, 15 Jan 2024 10:00:00 +0800</pubDate><guid>/blog/2024-01-15-build-the-abp-framework-from-the-ground-up/</guid><description>&lt;h3 id="version"&gt;Version&lt;/h3&gt;
&lt;p&gt;7.4.1&lt;/p&gt;
&lt;h3 id="创建空web项目"&gt;创建空Web项目&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; sln
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnew &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; web --name AspNetCoreAbp
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln &lt;span style="color:#66d9ef"&gt;add&lt;/span&gt; .&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;AspNetCoreAbp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="增加abp的基础包"&gt;增加Abp的基础包&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Volo.Abp.AspNetCore.Mvc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="增加启动模块"&gt;增加启动模块&lt;/h4&gt;
&lt;p&gt;考虑是使用WebApi模式，如下代码中就尽可能的精简，去除Mvc部分，Abp没有纯WebApi依赖模块，只有AbpAspNetCoreMvcModule模块。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Volo.Abp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Volo.Abp.AspNetCore.Mvc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Volo.Abp.Modularity;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; AspNetCoreAbp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[DependsOn(typeof(AbpAspNetCoreMvcModule))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AspNetCoreAbpModule&lt;/span&gt; : AbpModule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; OnApplicationInitialization(ApplicationInitializationContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = context.GetApplicationBuilder();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; env = context.GetEnvironment();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        app.UseRouting();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        app.UseConfiguredEndpoints();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="更改programcs"&gt;更改Program.cs&lt;/h4&gt;
&lt;p&gt;在7.0以上，ABP不用再单独弄一个Startup.cs文件，直接在Program.cs中启动模块就行。&lt;/p&gt;
&lt;p&gt;默认代码&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;, () =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Hello World!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;变更后代码&lt;/p&gt;</description></item><item><title>GenericRepository&amp;UnitOfWork基本实现</title><link>/blog/2023-12-29-genericrepository-unitofwork/</link><pubDate>Fri, 29 Dec 2023 10:00:00 +0800</pubDate><guid>/blog/2023-12-29-genericrepository-unitofwork/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;在DbContext中已经具备了事务，对于多个实体的操作，能够在一个事务中保证。借助仓储在基于DbContext上的封装，我们能够更好的扩展复用。泛型仓储的使用又能简化对于基础功能的依赖，但是当现有事务范围不足以覆盖或是多个仓储操作，多次调用SaveChange后，整体的事务范围便发生了变化，不能形成一个整体了。&lt;/p&gt;
&lt;h2 id="事务范围"&gt;事务范围&lt;/h2&gt;
&lt;h3 id="单次savechange"&gt;单次SaveChange&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002337221_eaeb2932-dc19-4c34-a187-93c99f2aeba3.png" alt="002337221_eaeb2932-dc19-4c34-a187-93c99f2aeba3"&gt;
新增Order及OrderItem，两者本身都没问题的话，一次SaveChange可以全部成功。而当SaveChange完毕后，再出现异常(如下直接抛出异常test)，已有的事务已经提交完毕，无法回滚之前的操作了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; CreateAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Name = name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        CreateDate = DateTime.UtcNow,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderItems = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;OrderItem&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderItem()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                ProductId = &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                Amount = &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    order = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _orderRepository.InsertAsync(order, autoSave: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//throw new Exception(&amp;#34;test&amp;#34;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; order.Id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="多次savechange"&gt;多次SaveChange&lt;/h3&gt;
&lt;p&gt;类似于直接抛出事务，实现多次调用SaveChange，&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002338280_74256c5d-9af4-40f8-984c-d43901a2f6a4.png" alt="002338280_74256c5d-9af4-40f8-984c-d43901a2f6a4"&gt;
第一次执行正常，执行SaveChange后数据入库，但在第二次操作时出现失败，第二次的入库操作能够回滚，但是第一次的SaveChange却是还是执行成功，从整个操作上，缺失了一致性。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public async Task&amp;lt;int&amp;gt; CreateMultiAsync(string name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var order = new Order()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Name = name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        CreateDate = DateTime.UtcNow,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderItems = new List&amp;lt;OrderItem&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            new OrderItem()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                ProductId = 1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                Amount = 1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    order = await _orderRepository.InsertAsync(order, autoSave: true);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var orderError = new Order()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Name = name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        CreateDate = DateTime.Now,//数据库中限制只允许utc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderItems = new List&amp;lt;OrderItem&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            new OrderItem()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                ProductId = 1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                Amount = 1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    orderError = await _orderRepository.InsertAsync(orderError, autoSave: true);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    return order.Id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种场景在结合领域事件，在handler中还执行一些仓储操作时很容易出现，当没有一个全局的事务保证下，不可避免有这种风险。&lt;/p&gt;</description></item><item><title>GenericRepository</title><link>/blog/2023-11-29-genericrepository/</link><pubDate>Wed, 29 Nov 2023 10:00:00 +0800</pubDate><guid>/blog/2023-11-29-genericrepository/</guid><description>&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;自定义仓储能够很大程度方便我们实现功能，但是对于自定义仓储中的公共部分，又是非常基础的功能，如基础增删改和列表查询，分页查询，单个查询等，对于大部分自定义仓储来讲都能够用的上，如果每个自定义仓储中都实现一套，代码冗余度太高，无效工作过滤耗费时间。&lt;/p&gt;
&lt;h3 id="构建泛型仓储"&gt;构建泛型仓储&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241128/002330855_a1128d23-4652-430a-97bc-30d481e40d7d.png" alt="002330855_a1128d23-4652-430a-97bc-30d481e40d7d"&gt;&lt;/p&gt;
&lt;h4 id="泛型仓储抽象接口"&gt;泛型仓储抽象接口&lt;/h4&gt;
&lt;p&gt;在自定义仓储接口基础上将常用的CRUD抽离出来到泛型仓储接口中，原有自定义变成泛型来代替，以使得自定义仓储接口集成泛型仓储接口后仍能够有基础常用功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241128/002332303_ad9db6f5-be88-4bc5-87c3-cf7c2c733aff.png" alt="002332303_ad9db6f5-be88-4bc5-87c3-cf7c2c733aff"&gt;
对于自定义仓储的设计，如果实体能够映射到数据库表，那么便可为其增加仓储以便实现读写功能。因此，实体便是成为大多数自定义仓储命名规则，此处增加一个实体接口，一方面标时实体，另一方面约束泛型仓储，以只允许具有实体接口标时的实体才能使用泛型仓储。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IEntity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IEntity&amp;lt;TKey&amp;gt; : IEntity
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    TKey Id { get; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;泛型仓储的设计上，参照ABP中泛型仓储，简化如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IRepository&amp;lt;TEntity, TKey&amp;gt; : IRepository
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    where TEntity : class, IEntity&amp;lt;TKey&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TEntity&amp;gt; GetAsync(TKey id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TEntity&amp;gt; GetAsync([NotNull] Expression&amp;lt;Func&amp;lt;TEntity, bool&amp;gt;&amp;gt; predicate);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TEntity&amp;gt; FindAsync(TKey id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TEntity&amp;gt; FindAsync([NotNull] Expression&amp;lt;Func&amp;lt;TEntity, bool&amp;gt;&amp;gt; predicate);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;List&amp;lt;TEntity&amp;gt;&amp;gt; GetListAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;List&amp;lt;TEntity&amp;gt;&amp;gt; GetListAsync([NotNull] Expression&amp;lt;Func&amp;lt;TEntity, bool&amp;gt;&amp;gt; predicate);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;List&amp;lt;TEntity&amp;gt;&amp;gt; GetPagedListAsync(int skipCount, int maxResultCount, string sorting);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;long&amp;gt; GetCountAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TEntity&amp;gt; InsertAsync([NotNull] TEntity entity, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task InsertManyAsync([NotNull] IEnumerable&amp;lt;TEntity&amp;gt; entities, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;TEntity&amp;gt; UpdateAsync([NotNull] TEntity entity, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task UpdateManyAsync([NotNull] IEnumerable&amp;lt;TEntity&amp;gt; entities, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task DeleteAsync(TKey id, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task DeleteAsync([NotNull] TEntity entity, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task DeleteAsync([NotNull] Expression&amp;lt;Func&amp;lt;TEntity, bool&amp;gt;&amp;gt; predicate, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task DeleteManyAsync([NotNull] IEnumerable&amp;lt;TEntity&amp;gt; entities, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task DeleteManyAsync([NotNull] IEnumerable&amp;lt;TKey&amp;gt; ids, bool autoSave = false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task SaveChangesAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;IQueryable&amp;lt;TEntity&amp;gt;&amp;gt; WithDetailsAsync(params Expression&amp;lt;Func&amp;lt;TEntity, object&amp;gt;&amp;gt;[] propertySelectors);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task&amp;lt;IQueryable&amp;lt;TEntity&amp;gt;&amp;gt; GetQueryableAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="泛型仓储抽象实现"&gt;泛型仓储抽象实现&lt;/h4&gt;
&lt;p&gt;对于泛型仓储实现，先实现一个RepositoryBase，实现中并没有涉及到具体的技术实现，而是泛型仓储实现的抽象。与泛型仓储可属于同一层，都是领域层。&lt;/p&gt;</description></item><item><title>DbContext&amp;Repository基本实现</title><link>/blog/2023-10-29-dbcontext-repository/</link><pubDate>Sun, 29 Oct 2023 10:00:00 +0800</pubDate><guid>/blog/2023-10-29-dbcontext-repository/</guid><description>&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;对于仓储模式，各有看法不同，直接使用DbContext简单方便，使用仓储模式扩展复用较好。受限于场景的差异，人员技能熟悉程度，交付时间，成本等选择哪种方式也有不同。&lt;/p&gt;
&lt;h3 id="controllerdbcontext"&gt;Controller&amp;amp;DbContext&lt;/h3&gt;
&lt;p&gt;当需要快速设计一个访问数据库Demo时，顺手便是Controller+DbContext，当然还有其他更为简便的方式，但是这套模式在使用频次上更高。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002319673_fb9d3cf4-1276-4dc1-a62a-3fba572f4339.png" alt="002319673_fb9d3cf4-1276-4dc1-a62a-3fba572f4339"&gt;
DbContext内部提供了常用的一些方法，直接操作DbContext很是方便，很自由，想怎么取怎么取，同时因为DbContext内部封装好了事务，因此多次变更一次提交之类的都很方便。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ApiController]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Route(&amp;#34;[controller]&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class OrderController : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    private readonly AppDbContext _dbContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public OrderController(AppDbContext dbContext)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        _dbContext = dbContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    [HttpGet(&amp;#34;{id}&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task&amp;lt;Order&amp;gt; GetAsync(int id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return await _dbContext.Orders.AsNoTracking().FirstAsync(o =&amp;gt; o.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    [HttpGet]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task&amp;lt;List&amp;lt;Order&amp;gt;&amp;gt; GetListAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return await _dbContext.Orders.AsNoTracking().ToListAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    [HttpPost]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task&amp;lt;int&amp;gt; CreateAsync(string name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var order = new Order()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Name = name,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            CreateDate = DateTime.UtcNow,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await _dbContext.Orders.AddAsync(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await _dbContext.SaveChangesAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return order.Id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    [HttpPut(&amp;#34;{id}&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task&amp;lt;int&amp;gt; UpdateAsync(int id, string name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var order = await _dbContext.Orders.FirstAsync(o=&amp;gt;o.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        order.Name = name;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await _dbContext.SaveChangesAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return order.Id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    [HttpDelete(&amp;#34;{id}&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task DeleteAsync(int id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var order = await _dbContext.Orders.FirstAsync(o =&amp;gt; o.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        _dbContext.Orders.Remove(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await _dbContext.SaveChangesAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可是使用方便的同时，有其他问题也需要考虑，如上代码如需要在其他Controller中查询，是不是又得写一遍，各种操作都得再来一遍，代码重复度很高。查询部分需要更加优化的查询性能等，受限EFCore的查询，需要更换到Dapper，那么纯查询操作的接口需要大面积改代码，相比下，引入仓储模式可能更加容易扩展，代码重用上也更佳。
&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002320875_472515c8-cfa1-441e-86cd-8d7f491b3f2a.png" alt="002320875_472515c8-cfa1-441e-86cd-8d7f491b3f2a"&gt;
对于仓储模式结合DbContext，可以见到很多观点，有人认为多此一举，DbContext本身已经具备仓储功能及工作单元，没必要再在DbContext基础上再去封装。也有人认为需要封装，能够减少耦合，可扩展性更强。&lt;a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implementation-entity-framework-core#using-a-custom-repository-versus-using-ef-dbcontext-directly"&gt;微软官方文档&lt;/a&gt;里也对此给了自己的看法，建议在复杂场景中使用仓储模式，以能更低成本的测试代码。&lt;/p&gt;</description></item><item><title>抽象CurrentUser适配Http和Job场景</title><link>/blog/2023-09-29-currentuser-http-job/</link><pubDate>Fri, 29 Sep 2023 10:00:00 +0800</pubDate><guid>/blog/2023-09-29-currentuser-http-job/</guid><description>&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;获取当前请求用户的基础信息是很常见的，诸如当前用户Id，角色，有无访问权限等。通常我们可以直接使用HttpContext.User来拿到当前经过认证后的请求人信息。但是这样对于分层应用不太友好，需要安装AspNetCore.Http.Abstractions的包，这样对于这层(非Web层)来讲也有所侵入了。&lt;/p&gt;
&lt;h3 id="currentuser"&gt;CurrentUser&lt;/h3&gt;
&lt;p&gt;很常用的一种方式是抽象与封装一层，比如ICurrentUser, ICurrentClient之类，具体实现放在Web层中以方便从HttpContext中获取用户或租户信息。而使用时，通过IOC在Domain中调用实例CurrentUser再获取详细信息。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241128/002312668_eff6f878-e07b-47b4-ac12-409a73d1e550.png" alt="002312668_eff6f878-e07b-47b4-ac12-409a73d1e550"&gt;&lt;/p&gt;
&lt;h4 id="代码示例"&gt;代码示例&lt;/h4&gt;
&lt;p&gt;新建一个Asp.Net Core WebApi项目并增加一个Domain类库以添加ICurrentUser。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace CurrentUserDemo.Domain.CurrentUsers;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface ICurrentUser
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    int? GetUserId();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    string? GetUserName();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在WebApi中实现CurrentUser，注入IHttpContextAccessor来获取当前用户信息。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using CurrentUserDemo.Api.Extensions;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using CurrentUserDemo.Domain.CurrentUsers;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace CurrentUserDemo.Api.Infrastructures.CurrentUsers;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class CurrentUser : ICurrentUser
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    private readonly IHttpContextAccessor _httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public CurrentUser(IHttpContextAccessor httpContextAccessor)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        _httpContextAccessor = httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public int? GetUserId()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return _httpContextAccessor.HttpContext?.User?.FindUserId();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public string? GetUserName()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return _httpContextAccessor.HttpContext?.User?.FindUserName();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当Domain层想要获取用户信息时，注入ICurrentUser。&lt;/p&gt;</description></item><item><title>整合MediatR&amp;Cap实现EventBus</title><link>/blog/2023-08-29-mediatr-cap-eventbus/</link><pubDate>Tue, 29 Aug 2023 10:00:00 +0800</pubDate><guid>/blog/2023-08-29-mediatr-cap-eventbus/</guid><description>&lt;p&gt;在软件开发中，事件早已被我们所熟悉，一个按钮按下，产生中断事件，一个回车，前端页面有侦听事件，在事件风暴建模活动中，事件也是作为领域建模的突破口，事件的重要性不言而喻。其本质是发生的事实到引发了相关事情，在这其中的传递的信息便是事件的内容。就如同猫叫了，引发着老鼠跑了，主人醒了，其中的事件便是猫叫了，而该事件是猫执行叫的动作后的结果。&lt;/p&gt;
&lt;h2 id="领域事件"&gt;领域事件&lt;/h2&gt;
&lt;p&gt;在领域驱动设计中，最开始的版本中并没有领域事件的概念，在 DDD 社区对领域驱动设计的内容不断的充实中，引入了领域事件。领域事件的命名遵循英语中的“名词 + 动词过去分词”格式，如，提交订单后发布的 OrderCreated 事件，订单完成后 OrderCompleted 事件，用以表示我们建模的领域中发生过的一件事情，也符合着事件本身是具有时间特征。对于事件所通知到的范围的不同，领域事件本身也分为两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;领域内事件，即领域事件，作用于进程内的通信。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;领域间事件，即集成事件，作用于进程间的通信。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241128/002223749_bd25652f-0d4d-4b91-ba2f-3474e71062da.png" alt="002223749_bd25652f-0d4d-4b91-ba2f-3474e71062da"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为实现事件的发布，可以借助一些现有的包集成到系统中，但又为了隔离使用上依赖了具体的技术实现，我们可以对其进行一定的封装。&lt;/p&gt;
&lt;h2 id="mediatr"&gt;MediatR&lt;/h2&gt;
&lt;p&gt;MediatR是.NET中的开源简单中介者模式实现，它通过一种进程内消息传递机制（无其他外部依赖），进行请求/响应、命令、查询、通知和事件的消息传递，并通过泛型来支持消息的智能调度。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jbogard/MediatR"&gt;https://github.com/jbogard/MediatR&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="cap"&gt;Cap&lt;/h2&gt;
&lt;p&gt;CAP 本身就是一个EventBus，能处理进程内消息，同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架。它有助于创建可扩展，可靠并且易于更改的微服务系统。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dotnetcore/CAP"&gt;https://github.com/dotnetcore/CAP&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="抽象eventbus"&gt;抽象EventBus&lt;/h2&gt;
&lt;p&gt;新建EventBus.Abstractions类库，用来抽象领域事件和集成事件的发布。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new classlib --name EventBusDemo.EventBus.Abstractions
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在MediatR中，有一个&lt;a href="https://github.com/jbogard/MediatR/tree/master/src/MediatR.Contracts"&gt;抽象的Contracts&lt;/a&gt;类库，方便我们构建一个抽象的EventBus。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241128/002225239_f35fd357-5ab4-44db-904c-d2778b4f6f07.png" alt="002225239_f35fd357-5ab4-44db-904c-d2778b4f6f07"&gt;
MediatR发布事件时，事件类上必须要有标记，比如如下INotification标记，因此，此处增加领域事件接口约束，来隔离INotification。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace EventBusDemo.EventBus.Abstractions.Local;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IDomainEvent : INotification
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;本地事件EventBus, 参照&lt;a href="https://github.com/jbogard/MediatR/blob/master/src/MediatR/IPublisher.cs"&gt;MediatR中已有接口&lt;/a&gt;，替换约束类。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace EventBusDemo.EventBus.Abstractions.Local;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface ILocalEventBUs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task Publish(IDomainEvent domainEvent, CancellationToken cancellationToken = default);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Task Publish&amp;lt;T&amp;gt;(T domainEvent, CancellationToken cancellationToken = default)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        where T : IDomainEvent;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同样，增加集成事件，Cap框架没有限制集成事件类的约束条件，而为了能够统一标识项目中的集成事件，还是有必要设置一个接口约束。&lt;/p&gt;</description></item><item><title>DotNetCore&amp;Channels</title><link>/blog/2023-07-23-dotnetcore-channels/</link><pubDate>Sun, 23 Jul 2023 10:00:00 +0800</pubDate><guid>/blog/2023-07-23-dotnetcore-channels/</guid><description>&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/17575349.html"&gt;https://www.cnblogs.com/CKExp/p/17575349.html&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;生活中可以见到很多传送带，河道，工厂流水线，快递服务等。去站点寄个快递，通过传送带，将快递从一端传递到另一端，再去站点收个快递。参照这种设计，我们可以将其融入到软件中，以实现许多功能。&lt;/p&gt;
&lt;p&gt;在.Net Core中实现了一个高效，线程安全的队列System.Threading.Channels，与RabbitMQ、Kafka这种跨进程的消息队列，使用场景上不同，Channels在进程内跨线程使用。从使用方式上，Channels也更简单，不需要额外引入包(.Net Core3.0后)。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002210891_aef657dc-aa14-4920-a2da-a820c9fccf94.png" alt="002210891_aef657dc-aa14-4920-a2da-a820c9fccf94"&gt;&lt;/p&gt;
&lt;h3 id="channels"&gt;Channels&lt;/h3&gt;
&lt;p&gt;Channel其本质上是一个高效的、线程安全的队列，支持在生产者和消费者之间存储和传递数据。一端(生产者)负责将数据写入Channel，另一端(消费者)可以从Channel中读取数据并进行处理。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/"&gt;https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Channel机制下一个消息只能由一个消费者来消费。&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="channel的两种模式"&gt;Channel的两种模式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bounded Channels: 有限存储空间的Channel，对传入消息的容量有限，限定生产者在满足空间数量之前只能发布特定的次数。超过了则需要等待(非阻塞等待)消费者消费减少空间内消息数量后再继续发布。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unbounded Channels: 无限存储空间的Channel，生产者可以一直发布，但如果消费者的消费跟不上节奏，那么资源则会不断增大，有可能耗尽服务器资源。两者都是通过&lt;a href="https://source.dot.net/#System.Threading.Channels/System/Threading/Channels/Channel.cs,7"&gt;Channel&lt;/a&gt;提供的工厂方法来创建。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public static class Channel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static Channel&amp;lt;T&amp;gt; CreateBounded(int capacity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static Channel&amp;lt;T&amp;gt; CreateBounded(BoundedChannelOptions options);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static Channel&amp;lt;T&amp;gt; CreateUnbounded();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static Channel&amp;lt;T&amp;gt; CreateUnbounded(UnboundedChannelOptions options);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于不同的模式会有不同的实现类。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public abstract class Channel&amp;lt;TWrite, TRead&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public ChannelReader&amp;lt;TRead&amp;gt; Reader { get; protected set; } = null!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public ChannelWriter&amp;lt;TWrite&amp;gt; Writer { get; protected set; } = null!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public abstract class Channel&amp;lt;T&amp;gt; : Channel&amp;lt;T, T&amp;gt; { }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class BoundedChannel&amp;lt;T&amp;gt; : Channel&amp;lt;T&amp;gt; { }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class UnboundedChannel&amp;lt;T&amp;gt; : Channel&amp;lt;T&amp;gt; { }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="写入到channel"&gt;写入到Channel&lt;/h4&gt;
&lt;p&gt;生产者可以通过&lt;a href="https://source.dot.net/#System.Threading.Channels/System/Threading/Channels/ChannelWriter.cs,12"&gt;ChannelWrite&lt;/a&gt;来将消息写入到Channel中。&lt;/p&gt;</description></item><item><title>DotNet472&amp;6.0在Razor编译时的小差异</title><link>/blog/2023-06-28-dotnet-razor-compilation-differences/</link><pubDate>Wed, 28 Jun 2023 10:00:00 +0800</pubDate><guid>/blog/2023-06-28-dotnet-razor-compilation-differences/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;几个月前在进行着.Net 472到6.0的升级，复用原有代码，在对Razor进行迁移中，发现原运行正常的代码，却存在报错，深入研究发现是Core下对Razor编译有一些变动。&lt;/p&gt;
&lt;h2 id="问题复现"&gt;问题复现&lt;/h2&gt;
&lt;h3 id="472-创建视图"&gt;472 创建视图&lt;/h3&gt;
&lt;p&gt;新建.Net Framework下Mvc，增加一个简单视图如下。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Layout = null;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;!DOCTYPE html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;meta charset=&amp;#34;utf-8&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width, initial-scale=1.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;title&amp;gt;@ViewData[&amp;#34;Title&amp;#34;] - RazorDemo472.Web&amp;lt;/title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var headContent = &amp;#34;headContent&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;h1&amp;gt;@headContent&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;暂不论这个变量定义的位置合不合适，或许应该全局定义，但是总归在472下是正常的，可运行的。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241127/142159610_e45ba903-b576-4a07-b658-66cc95abd5ba.png" alt="142159610_e45ba903-b576-4a07-b658-66cc95abd5ba"&gt;&lt;/p&gt;
&lt;h3 id="60-创建视图"&gt;6.0 创建视图&lt;/h3&gt;
&lt;p&gt;新建Asp.Net Core Mvc项目，同样使用如上简单视图&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Layout = null;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;!DOCTYPE html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;meta charset=&amp;#34;utf-8&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width, initial-scale=1.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;title&amp;gt;@ViewData[&amp;#34;Title&amp;#34;] - RazorDemo6.Web&amp;lt;/title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var headContent = &amp;#34;headContent&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;h1&amp;gt;@headContent&amp;lt;/h1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果只是这样，不做任何设置，编译时候就会报错。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241127/142201384_434df0b5-1cbf-4048-8764-ac688a051a8d.png" alt="142201384_434df0b5-1cbf-4048-8764-ac688a051a8d"&gt;&lt;/p&gt;</description></item><item><title>通过Serverless私有化部署ChatGPT-Web</title><link>/blog/2023-05-11-serverless-chatgptweb/</link><pubDate>Thu, 11 May 2023 10:00:00 +0800</pubDate><guid>/blog/2023-05-11-serverless-chatgptweb/</guid><description>&lt;p&gt;作为开发人员，想要自己部署一个渠道访问或是想随时访问但是奈何魔法有限，又或是海外服务器太贵，不想耗费这个钱，本文借助 &lt;a href="https://martinfowler.com/articles/serverless.html?spm=a2c6h.12873639.article-detail.4.76a13633bB375u"&gt;Serverless&lt;/a&gt; 来搭建一下私有 ChatGPT 服务，Serverless 按照使用量来计费，个人使用下(满足工作和生活)费用相当低。&lt;/p&gt;
&lt;p&gt;本文过程较为繁琐，也有更为简便的其他方式：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://rptzik3toh.feishu.cn/docx/XtrdduHwXoSCGIxeFLlcEPsdn8b"&gt;https://rptzik3toh.feishu.cn/docx/XtrdduHwXoSCGIxeFLlcEPsdn8b&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;本次搭建过程需要满足以下几个条件，需要预先满足，已经注册或是从一些渠道获取到了 Api key，有自己的备案域名。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;已有 Api key&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要开通阿里云 Serverless&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;需要开通阿里云容器镜像服务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;已有备案域名&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002101072_58e11ee6-cf25-4382-9c54-a72d5dedfc9e.png" alt="002101072_58e11ee6-cf25-4382-9c54-a72d5dedfc9e"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="云函数"&gt;云函数&lt;/h3&gt;
&lt;p&gt;开通服务(略过,免费开通)， &lt;a href="https://www.aliyun.com/product/fc"&gt;https://www.aliyun.com/product/fc&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;顶部选择地域，如硅谷&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;左侧选择服务及函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建服务，填写服务描述信息即可&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002102573_d1d8364b-2db7-44a6-902e-b28ab79f3dda.png" alt="002102573_d1d8364b-2db7-44a6-902e-b28ab79f3dda"&gt;&lt;/p&gt;
&lt;h3 id="容器镜像服务"&gt;容器镜像服务&lt;/h3&gt;
&lt;p&gt;开通服务(掠过，免费开通)， &lt;a href="https://www.aliyun.com/product/acr"&gt;https://www.aliyun.com/product/acr&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;顶部选择海外站点，如硅谷&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002103958_8a72f545-3c20-4b58-93d3-5044d62472b5.png" alt="002103958_8a72f545-3c20-4b58-93d3-5044d62472b5"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;进入个人实例。创建镜像仓库，下一步中选择本地仓库，保存。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002105551_d1895c1c-2af1-4dd2-8035-f6a6ee39af22.png" alt="002105551_d1895c1c-2af1-4dd2-8035-f6a6ee39af22"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;点击仓库名称进入仓库内部，该部分命令稍后会用到，此处只需看到即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002107167_2228557a-4a5d-453a-9cc8-5b18ef04d1b8.png" alt="002107167_2228557a-4a5d-453a-9cc8-5b18ef04d1b8"&gt;&lt;/p&gt;
&lt;h3 id="chatgptvnextweb"&gt;ChatGPTVNextWeb&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/Yidadaa/ChatGPT-Next-Web"&gt;https://github.com/Yidadaa/ChatGPT-Next-Web&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;该仓库实现了 ChatGPT 的交互 UI，提供了 Docker 镜像方便部署。可将该镜像上传到个人的容器镜像服务(供 Serverless 绑定部署使用)。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;拉取 VNextWeb 镜像到本地&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker pull yidadaa/chatgpt-next-web
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;本地登录容器镜像服务(见容器镜像服务节末图)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker login --username=用户名 registry.us-west-1.aliyuncs.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;标记本地 VNextWeb 镜像版本&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;查看镜像 id&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker images
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;eg：查看本地镜像，复制Id&lt;/em&gt;
&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241128/002109120_9f1456c3-49fc-4c5b-a22f-1756377f1758.png" alt="002109120_9f1456c3-49fc-4c5b-a22f-1756377f1758"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置镜像版本&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker tag [ImageId] registry.us-west-1.aliyuncs.com/partner/chatgptnextweb:[镜像版本号]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;eg：此处直接标记最新版本 latest&lt;/em&gt;&lt;/p&gt;</description></item><item><title>DotNetCore Console&amp;Cache</title><link>/blog/2023-04-30-dotnetcore-console-cache/</link><pubDate>Sun, 30 Apr 2023 10:00:00 +0800</pubDate><guid>/blog/2023-04-30-dotnetcore-console-cache/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;有时候想快速验证一些想法，新建一个控制台来弄，可控制台模板是轻量级的应用程序模板，不具备配置、日志、依赖注入等一些功能。&lt;/p&gt;
&lt;h2 id="缓存"&gt;缓存&lt;/h2&gt;
&lt;p&gt;在网站开发中，缓存无处不在，它能够极大地提高硬件和软件的运行速度。性能优化的第一步便是使用缓存，例如频繁的从数据库中读取，需要和底层IO交互，性能受限，如将常用数据加载到内存中，那么便可以极大的提升性能。&lt;/p&gt;
&lt;h2 id="nuget包"&gt;Nuget包&lt;/h2&gt;
&lt;p&gt;缓存抽象包&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Caching.Abstractions
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如使用内存做缓存&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Caching.Memory
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如使用Redis做缓存&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Caching.Redis
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;还有其他一些缓存包，此处省略&lt;/p&gt;
&lt;h2 id="缓存使用"&gt;缓存使用&lt;/h2&gt;
&lt;p&gt;新建控制台项目，此处使用Memory包，简单存取一个字符串到内存中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using Microsoft.Extensions.Caching.Memory;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using Microsoft.Extensions.DependencyInjection;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using Microsoft.Extensions.Hosting;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var host = Host.CreateDefaultBuilder(args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .ConfigureServices((hostContext, services) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        services.AddMemoryCache();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    .Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (var serviceScope = host.Services.CreateScope())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var services = serviceScope.ServiceProvider;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var cache = services.GetRequiredService&amp;lt;IMemoryCache&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    cache.Set(&amp;#34;key&amp;#34;, &amp;#34;value&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Console.WriteLine(cache.Get(&amp;#34;key&amp;#34;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;如上使用了通用主机包 Microsoft.Extensions.Hosting&lt;/p&gt;</description></item><item><title>DotNetCore Console&amp;GenericHostBuilder</title><link>/blog/2023-04-08-dotnetcore-console-generichostbuilder/</link><pubDate>Sat, 08 Apr 2023 10:00:00 +0800</pubDate><guid>/blog/2023-04-08-dotnetcore-console-generichostbuilder/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;有时候想快速验证一些想法，新建一个控制台来弄，可控制台模板是轻量级的应用程序模板，不具备配置、日志、依赖注入等一些功能。&lt;/p&gt;
&lt;h2 id="通用主机"&gt;通用主机&lt;/h2&gt;
&lt;p&gt;在Asp.Net Core中有WebHostBuilder来提供DI，Configuratio，日志等功能，很是齐全，如果是在控制台中使用呢，或是使用的场景不单单是Web呢，比如测试项目，独立Job，桌面应用等，除了手动一个一个搭建外，也可以使用Generic HostBuilder，类似于WebHostBuilder，也提供了基础的功能，免于我们再去基建。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://source.dot.net/#Microsoft.Extensions.Hosting/HostBuilder.cs,148"&gt;https://source.dot.net/#Microsoft.Extensions.Hosting/HostBuilder.cs,148&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Generic HostBuilder是在.Net Core2.1时候提出的并被设计成能够在非Http模式和Http模式下都可以用。&lt;/p&gt;
&lt;h2 id="安装nuget包"&gt;安装Nuget包&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Hosting
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="构建主机"&gt;构建主机&lt;/h2&gt;
&lt;p&gt;开始使用Generic HostBuilder，可以手动实例化HostBuilder，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var hostBuilder = new HostBuilder();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或是调用Host中的扩展方法来完成创建。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var hostBuilder = Host.CreateDefaultBuilder(args)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实际上，扩展方法中是在手动实例化的基础上包装并做好了一些基础配置与服务注册，源码位置：
&lt;a href="https://source.dot.net/#Microsoft.Extensions.Hosting/Host.cs,53"&gt;https://source.dot.net/#Microsoft.Extensions.Hosting/Host.cs,53&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;微软官网文档中也提及了CreateDefaultBuilder中完成的一些事情。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host#default-builder-settings"&gt;https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host#default-builder-settings&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="服务注册"&gt;服务注册&lt;/h3&gt;
&lt;p&gt;Generic HostBuilder很是方便我们去使用DI，在HostBuilder尚未执行Build方法前，先注册一些服务到容器中，此处注册个简单服务。&lt;/p&gt;
&lt;p&gt;以订单服务类作为示例，增加接口类与实现类。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IOrderService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    void AutoCancel();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class OrderService : IOrderService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    private readonly ILogger&amp;lt;OrderService&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public OrderService(ILogger&amp;lt;OrderService&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public void AutoCancel()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        _logger.LogInformation(&amp;#34;Start AutoCancel&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Console.WriteLine(&amp;#34;Order canceled!&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        _logger.LogInformation(&amp;#34;Finished AutoCancel&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注册到服务容器中，此处使用瞬时的生命周期。&lt;/p&gt;</description></item><item><title>DotNetCore Console&amp;Logger</title><link>/blog/2023-03-15-dotnetcore-console-logger/</link><pubDate>Wed, 15 Mar 2023 10:00:00 +0800</pubDate><guid>/blog/2023-03-15-dotnetcore-console-logger/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;有时候想快速验证一些想法，新建一个控制台来弄，可控制台模板是轻量级的应用程序模板，不具备配置、日志、依赖注入等一些功能。&lt;/p&gt;
&lt;h2 id="日志"&gt;日志&lt;/h2&gt;
&lt;p&gt;.Net Core自带了一个基础的logger框架Microsoft.Extensions.Logging提供记录日志功能，能够按日志不同级别记录日志信息(Information, Warning, Error等)。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;LogLevel&lt;/th&gt;
&lt;th style="text-align: left"&gt;值&lt;/th&gt;
&lt;th style="text-align: left"&gt;扩展方法&lt;/th&gt;
&lt;th style="text-align: left"&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Trace&lt;/td&gt;
&lt;td style="text-align: left"&gt;0&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.extensions.logging.loggerextensions.logtrace?view=dotnet-plat-ext-7.0&amp;amp;viewFallbackFrom=net-8.0"&gt;LogTrace&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;追踪：包含最详细的信息。可能包含机密信息。 预设为停用，且不应该在生产环境中启用。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Debug&lt;/td&gt;
&lt;td style="text-align: left"&gt;1&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.extensions.logging.loggerextensions.logdebug?view=dotnet-plat-ext-7.0&amp;amp;viewFallbackFrom=net-8.0"&gt;LogDebug&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;调试：开发调试并在调试工具中展示。 生产环境中小心使用，日志条数庞大。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Information&lt;/td&gt;
&lt;td style="text-align: left"&gt;2&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://docs.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.logging.loggerextensions.loginformation?view=dotnet-plat-ext-3.1"&gt;LogInformation&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;信息：跟踪程序执行的一般流程。 可能具有长期值。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Warning&lt;/td&gt;
&lt;td style="text-align: left"&gt;3&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://docs.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.logging.loggerextensions.logwarning?view=dotnet-plat-ext-3.1"&gt;LogWarning&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;警告：针对异常或或非预期的事件。 通常包含不会导致应用失败的错误或状况。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Error&lt;/td&gt;
&lt;td style="text-align: left"&gt;4&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://docs.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.logging.loggerextensions.logerror?view=dotnet-plat-ext-3.1"&gt;LogError&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;错误：发生无法处理的错误或异常。 这些记录表示目前的执行流程或任务失败，但不是整个程序失败。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Critical&lt;/td&gt;
&lt;td style="text-align: left"&gt;5&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://docs.microsoft.com/zh-tw/dotnet/api/microsoft.extensions.logging.loggerextensions.logcritical?view=dotnet-plat-ext-3.1"&gt;LogCritical&lt;/a&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;严重：发生需要立即关注的失败。 例如：磁盘空间不足。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;None&lt;/td&gt;
&lt;td style="text-align: left"&gt;6&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;指定记录类别不应写入任何信息。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;.Net Core中使用&lt;a href="https://source.dot.net/#Microsoft.Extensions.Logging.Abstractions/ILogger.cs,0976525f5d1b9e54"&gt;ILogger&lt;/a&gt;来记录日志，可以在不同模板应用上使用，如控制台应用，Asp.Net Core，WPF等，Asp.Net Core里使用十分简单，可以直接通过DI注册与获取到ILogger，但在控制台模式中，需要我们自己去配置。&lt;/p&gt;
&lt;h2 id="安装nuget包"&gt;安装Nuget包&lt;/h2&gt;
&lt;p&gt;新建控制台项目并安装Logger的包，此处使用日志输出渠道为Console中直接展示&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Logging.Console
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Logging基础包(Microsoft.Extensions.Logging)在Console已有依赖,此处不再额外安装。同时，Logging基础包中依赖了DI的基础包，使用上极其方便了。
&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241127/141916937_7d2d29d0-cf3d-4e44-9836-ce053de02f8b.png" alt="141916937_7d2d29d0-cf3d-4e44-9836-ce053de02f8b"&gt;&lt;/p&gt;
&lt;h2 id="服务注册"&gt;服务注册&lt;/h2&gt;
&lt;p&gt;将日志服务加入到服务容器中，此处设置Console作为日志输出渠道，还可以设置其他渠道。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://source.dot.net/#Microsoft.Extensions.Logging/LoggingServiceCollectionExtensions.cs"&gt;https://source.dot.net/#Microsoft.Extensions.Logging/LoggingServiceCollectionExtensions.cs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;额外注册了一个OrderJob，用来在OrderJob中输出日志，构建ServiceProvider获取容器提供的服务实例。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var services = new ServiceCollection();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddLogging(configure =&amp;gt; configure.AddConsole());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddTransient&amp;lt;OrderJob&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (ServiceProvider serviceProvider = services.BuildServiceProvider())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var orderJob = serviceProvider.GetService&amp;lt;OrderJob&amp;gt;()!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    orderJob.Execute();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在OrderJob中通过构造函数注入使用Logger，也可以注入ILoggerFactory创建Logger&lt;/p&gt;</description></item><item><title>DotNetCore Console&amp;DependencyInjection</title><link>/blog/2023-03-05-dotnetcore-console-dependencyinjection/</link><pubDate>Sun, 05 Mar 2023 10:00:00 +0800</pubDate><guid>/blog/2023-03-05-dotnetcore-console-dependencyinjection/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;有时候想快速验证一些想法，新建一个控制台来弄，可控制台模板是轻量级的应用程序模板，不具备配置、日志、依赖注入等一些功能。&lt;/p&gt;
&lt;h2 id="依赖注入"&gt;依赖注入&lt;/h2&gt;
&lt;p&gt;在Asp.Net Core应用程序中，可以通过依赖注入使用IConfiguration接口来使用配置。而控制台模板十分简单，没有内置依赖注入，应用程序所依赖的功能(如配置)不容易获得。使用IOC(控制反转)容器实现依赖注入简要步骤&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;创建Service Collection。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将服务注册到Service Collection。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Service Collection构建Service Provider。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用Service Provider获取服务&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="安装nuget包"&gt;安装Nuget包&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.DependencyInjection
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="创建服务容器"&gt;创建服务容器&lt;/h2&gt;
&lt;p&gt;需要准备一个Service Collection(服务容器)，用来存储服务的注册与从其中获取服务。微软已提供了ServiceCollection类。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://source.dot.net/#Microsoft.Extensions.DependencyInjection.Abstractions/ServiceCollection.cs,4219db14266c9d57"&gt;https://source.dot.net/#Microsoft.Extensions.DependencyInjection.Abstractions/ServiceCollection.cs,4219db14266c9d57&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在Asp.Net Core 源码中，Service Collection在WebHostBuilder中实例化好了。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://source.dot.net/#Microsoft.AspNetCore.Hosting/WebHostBuilder.cs,c278c6966995108c"&gt;https://source.dot.net/#Microsoft.AspNetCore.Hosting/WebHostBuilder.cs,c278c6966995108c&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;BuildCommonServices方法中，完成Service Collection实例化与应用需要的服务注册。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/SAssassin/document-img/main/img/20241127/141240531_694b6452-ad2d-461d-90df-06737c059531.png" alt="141240531_694b6452-ad2d-461d-90df-06737c059531"&gt;
在控制台模板中，需要自己去创建实例。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var services = new ServiceCollection();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="服务注册"&gt;服务注册&lt;/h2&gt;
&lt;p&gt;服务注册到服务容器中时需要指明服务本身的生命周期，有三种设置，全局单例(Singleton)，请求内单例(Scope)，瞬时(Transient)。以订单服务类作为示例，增加接口类与实现类。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IOrderService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    void AutoCancel();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class OrderService : IOrderService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public void AutoCancel()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Console.WriteLine(&amp;#34;Order canceled!&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注册到服务容器中，此处使用瞬时的生命周期。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddTransient&amp;lt;IOrderService, OrderService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="构建服务提供者"&gt;构建服务提供者&lt;/h2&gt;
&lt;p&gt;通过服务容器转换得到服务提供者，使用已有扩展方法&lt;/p&gt;</description></item><item><title>DotNetCore Console&amp;Configuration</title><link>/blog/2023-02-27-dotnetcore-console-configuration/</link><pubDate>Mon, 27 Feb 2023 10:00:00 +0800</pubDate><guid>/blog/2023-02-27-dotnetcore-console-configuration/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;有时候想快速验证一些想法，新建一个控制台来弄，可控制台模板是轻量级的应用程序模板，不具备配置、日志、依赖注入等一些功能。&lt;/p&gt;
&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;
&lt;p&gt;在Asp.Net Core应用程序中，可以通过依赖注入使用IConfiguration接口来使用配置。而控制台模板十分简单，没有内置依赖注入，应用程序所依赖的功能(如配置)不容易获得。因此需要一点点在控制台中搭建最为基本的功能，想要使用配置，可通过ConfigurationBuilder类来创建Configuration，还可以使用Json文件、机密文件、环境变量、命令行或自定义的配置提供程序来作为配置源获取配置信息。&lt;/p&gt;
&lt;h3 id="安装configuration包"&gt;安装Configuration包&lt;/h3&gt;
&lt;p&gt;安装相关NuGet包，每个包的用途看名字就很一目了然。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Configuration
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Configuration.Json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Configuration.CommandLine
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Configuration.UserSecrets
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Configuration.EnvironmentVariables
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="组装configuration"&gt;组装Configuration&lt;/h3&gt;
&lt;p&gt;如下代码中，添加了一个appsetings.json的配置文件并设置其属性复制到项目输出文件夹，还添加了环境变量、用户机密和命令行作为配置源。最后构建Configuration。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using Microsoft.Extensions.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IConfiguration Configuration = new ConfigurationBuilder()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJsonFile(&amp;#34;appsettings.json&amp;#34;, optional: true, reloadOnChange: true)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddUserSecrets&amp;lt;Program&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddEnvironmentVariables()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddCommandLine(args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Build();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如上配置源会由后者优先级最高覆盖前者相同配置。&lt;/p&gt;
&lt;h3 id="使用configuration"&gt;使用Configuration&lt;/h3&gt;
&lt;p&gt;已经构建了配置提供程序，可以按照下面的方式使用它们&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// 获取配置节
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var section = Configuration.GetSection(&amp;#34;ConnectionStrings&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// 获取值
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var value = Configuration.GetValue(&amp;#34;ConnectionStrings:Default&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="强类型对象"&gt;强类型对象&lt;/h2&gt;
&lt;p&gt;配置节可以映射到强类型对象(选项模式)，安装如下Nuget包，可以使用Bind 方法将配置节映射到强类型对象，又或是使用Get方法(推荐)同样的作用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-Package Microsoft.Extensions.Configuration.Binder
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;例如，有下面的类表示Jwt配置&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class JwtOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Name = &amp;#34;Jwt&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public string Audience { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public string Issuer { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public double ExpiresMinutes { get; set; } = 30d;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public string SymmetricSecurityKeyString { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Json配置文件中加入配置节点&lt;/p&gt;</description></item><item><title>Prometheus&amp;Alertmanager告警推送</title><link>/blog/2023-02-23-prometheus-alertmanager-basicusage/</link><pubDate>Thu, 23 Feb 2023 10:00:00 +0800</pubDate><guid>/blog/2023-02-23-prometheus-alertmanager-basicusage/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;尽管可以通过可视化数据监控系统运行状态，但我们无法时刻关注系统运行，因此需要一些实时运行的工具能够辅助监控系统运行，当系统出现运行问题时，能够通知我们，以此确保系统稳定性，告警便是作为度量指标监控中及其重要的一环。&lt;/p&gt;
&lt;h2 id="prometheus告警介绍"&gt;Prometheus告警介绍&lt;/h2&gt;
&lt;p&gt;在Prometheus中，告警模块为Alertmanager，可以提供多种告警通道、方式来使得系统出现问题可以推送告警消息给相关人员。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241127/141209821_0eb57afa-0366-4a7a-90af-4df9f70bf6e2.png" alt="141209821_0eb57afa-0366-4a7a-90af-4df9f70bf6e2"&gt;
Prometheus Server中的告警规则会向Alertmanager发送。Alertmanager管理这些告警，包括进行去重，分组和路由，以及告警的静默和抑制，通过电子邮件、实时通知系统和聊天平台等方法发送通知。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;定义告警规则(Alerting rule)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;告警模块消息推送(Alertmanager)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;告警推送(Prometheus server推送告警规则到Alertmanager)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="alertmanager"&gt;Alertmanager&lt;/h2&gt;
&lt;p&gt;Alertmanager是一个独立的告警模块，接收Prometheus server发来的告警规则，通过去重、分组、静默和抑制等处理，并将它们通过路由发送给正确的接收器。&lt;/p&gt;
&lt;h3 id="核心概念"&gt;核心概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;分组(Grouping): 是Alertmanager把同类型的告警进行分组，合并多条告警合并成一个通知。当系统中许多实例出问题时又或是网络延迟、延迟导致网络抖动等问题导致链接故障，会引发大量告警，在这种情况下使用分组机制，把这些被触发的告警合并为一个告警进行通知，从而避免瞬间突发性的接受大量告警通知，使得相关人员可以对问题快速定位，而不至于淹没在告警中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;抑制(Inhibition): 是当某条告警已经发送，停止重复发送由此告警引发的其他异常或故障的告警机制，以避免收到过多无用的告警通知。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;静默(Silences): 可对告警进行静默处理的简单机制。对传进来的告警进行匹配检查，如果告警符合静默的配置，Alertmanager 则不会发送告警通知。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="设置alertmanageryml"&gt;设置Alertmanager.yml&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /opt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir alertmanager
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd alertmanager
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;touch alertmanager.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置文件主要包含以下几个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;全局配置（global）：用于定义一些全局的公共参数，如全局的SMTP配置，Slack配置等内容；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模板（templates）：用于定义告警通知时的模板，如HTML模板，邮件模板，企业微信模板等；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;告警路由（route）：根据标签匹配，确定当前告警应该如何处理；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接收人（receivers）：接收人是一个抽象的概念，它可以是一个邮箱也可以是微信，Slack或者Webhook等，接收人一般配合告警路由使用；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配置文件内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;global:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resolve_timeout: 2m
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; smtp_smarthost: &amp;#39;smtp.qq.com:465&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; smtp_from: your@qq.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; smtp_auth_username: your@qq.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; smtp_auth_password: 授权码
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;templates:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /etc/alertmanager/template/*.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;route:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; group_by:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - alertname_wechat
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; group_wait: 10s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; group_interval: 10s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; receiver: wechat
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; repeat_interval: 1h
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;receivers:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: wechat
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; email_configs:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - to: otheremail@outlook.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; send_resolved: true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wechat_configs:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - corp_id: wechat_corp_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; to_party: wechat_to_party
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent_id: wechat_agent_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; api_secret: wechat_apisecret
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; send_resolved: true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此处设置推送消息到邮箱和企业微信，Alertmanager内置了对企业微信的支持。
&lt;a href="https://prometheus.io/docs/alerting/latest/configuration/#wechat_config"&gt;https://prometheus.io/docs/alerting/latest/configuration/#wechat_config&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Prometheus&amp;Grafana基本使用</title><link>/blog/2023-01-31-prometheus-grafana-basicusage/</link><pubDate>Tue, 31 Jan 2023 10:00:00 +0800</pubDate><guid>/blog/2023-01-31-prometheus-grafana-basicusage/</guid><description>&lt;h2 id="prometheus介绍"&gt;Prometheus介绍&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; 是一套开源的系统监控与报警框架，以便于我们能够监控生产环境下的应用与服务。启发于 Google 的 BorgMon 监控系统，由工作在 SoundCloud 的 google 前员工在 2012 年创建，作为社区开源项目进行开发，并于 2015 年正式发布。2016 年，Prometheus 正式加入CNCF(Cloud Native Computing Foundation)，成为受欢迎度仅次于 Kubernetes 的项目。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241127/141125071_5a3f7810-c010-4fc6-b5c7-00d41a921d4f.png" alt="141125071_5a3f7810-c010-4fc6-b5c7-00d41a921d4f"&gt;&lt;/p&gt;
&lt;h2 id="prometheus特点"&gt;Prometheus特点&lt;/h2&gt;
&lt;p&gt;作为新一代的监控框架，Prometheus 具有以下特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;多维度数据模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;时间序列数据通过 metric 名和键值对来区分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有的 metrics 都可以设置任意的多维标签。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据模型更随意，不需要刻意设置为以点分隔的字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以对数据模型进行聚合，切割和切片操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持双精度浮点类型，标签可以设为全 unicode。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;灵活而强大的查询语句（PromQL）：在同一个查询语句，可以对多个 metrics 进行乘法、加法、连接、取分数位等操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;易于管理： Prometheus server 是一个单独的二进制文件，可直接在本地工作，不依赖于分布式存储。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;高效：平均每个采样点仅占 3.5 bytes，且一个 Prometheus server 可以处理数百万的 metrics。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用 pull 模式采集时序数据，这样不仅有利于本机测试而且可以避免有问题的服务器推送坏的 metrics。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以采用 push gateway 的方式把时序数据推送至 Prometheus server 端。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以通过服务发现或者静态配置去获取监控的 targets。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持多种可视化图形界面，比如Grafana等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="prometheus组件"&gt;Prometheus组件&lt;/h2&gt;
&lt;h3 id="prometheus-server"&gt;Prometheus server&lt;/h3&gt;
&lt;p&gt;主要负责数据采集和存储，提供PromQL查询语言的支持。包含了三个组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Retrieval: 获取监控数据&lt;/p&gt;</description></item><item><title>AspNetCore&amp;Cookie认证授权</title><link>/blog/2023-01-15-aspnetcore-cookie-authentication-authorization/</link><pubDate>Sun, 15 Jan 2023 10:00:00 +0800</pubDate><guid>/blog/2023-01-15-aspnetcore-cookie-authentication-authorization/</guid><description>&lt;p&gt;有时想快速搭建一个简单Demo或是需要验证授权完成后的一些动作，总是需要去找一番，有时还要不断去翻找到适合的，或是copy过来又不能使用又或是过时的。&lt;/p&gt;
&lt;p&gt;认证与授权说来说去还是四个核心步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;登录(SignIn)，为了获得当前请求人是谁的标识&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;退出(SignOut)，令系统移除请求人的标识&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;请求资源时识别请求人是谁(Authentication)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;判断对资源请求的权限(Authorization)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;有身份信息但没有权限后拒绝操作(Challenge)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;没有身份信息则说未授权(Forbid)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;有身份信息且有权限则访问资源&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241127/141106428_872fb2fd-157d-4228-bc38-7fe7a38e8e37.png" alt="141106428_872fb2fd-157d-4228-bc38-7fe7a38e8e37"&gt;&lt;/p&gt;
&lt;h3 id="cookie"&gt;Cookie&lt;/h3&gt;
&lt;p&gt;Cookie 是用来在客户端存储会话信息的，它要求服务器在响应给客户端信息时，携带一个头部字段 Set-Cookie，其内容为键值对，保存一些需要存储在客户端本地的信息。在之后的每一次请求中，浏览器会将这些信息，携带在请求头部字段 Cookie 中，发送给服务器，用以标识客户端的身份。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241127/141108238_8d98ca85-5f69-40a9-b45a-3d99121ec395.png" alt="141108238_8d98ca85-5f69-40a9-b45a-3d99121ec395"&gt;&lt;/p&gt;
&lt;h3 id="快速实践"&gt;快速实践&lt;/h3&gt;
&lt;h4 id="项目准备"&gt;项目准备&lt;/h4&gt;
&lt;p&gt;准备一个Asp.Net Core 6.0的Web(前端简单展示)。按照如上几个用例挨个实现(退出用例不考虑)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20241127/141109567_3dd05954-2d2b-4f03-9479-7c2023870133.png" alt="141109567_3dd05954-2d2b-4f03-9479-7c2023870133"&gt;&lt;/p&gt;
&lt;h4 id="增加account控制器"&gt;增加Account控制器&lt;/h4&gt;
&lt;p&gt;简单实现，跳过账号验证之类的过程，主要是生成一个Cookie，方便请求其他需要授权验证的接口&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class AccountController : Controller
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task&amp;lt;IActionResult&amp;gt; GenerateCookie(string userName = &amp;#34;9527&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var claims = new List&amp;lt;Claim&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            new Claim(&amp;#34;id&amp;#34;, &amp;#34;1&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            new Claim(ClaimTypes.Name,userName),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            new Claim(ClaimTypes.Role,&amp;#34;Admin&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var claimsIdentity = new ClaimsIdentity(claims, &amp;#34;Custom&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new AuthenticationProperties
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            ExpiresUtc = DateTime.Now.AddMinutes(30)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return Content(HttpContext.Response.Headers.SetCookie);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="服务注册"&gt;服务注册&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using Microsoft.AspNetCore.Authentication.Cookies;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddControllersWithViews();//包含Authorize需要的服务
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();//使用Cookie作为鉴权Schema
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseHttpsRedirection();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseRouting();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();//使用鉴权中间件
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapDefaultControllerRoute();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="获取cookie"&gt;获取Cookie&lt;/h4&gt;
&lt;p&gt;简单点，直接Postman请求拿到Cookie，然后便方便对其他接口测试。&lt;/p&gt;</description></item><item><title>Jaeger&amp;ElasticSearch存储链路追踪数据</title><link>/blog/2022-12-26-jaeger-elasticsearch-basicusage/</link><pubDate>Mon, 26 Dec 2022 10:00:00 +0800</pubDate><guid>/blog/2022-12-26-jaeger-elasticsearch-basicusage/</guid><description>&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;Jaeger的all in one镜像下是用内存存储(或是临时文件格式存储)，容器重启，数据丢失。生产环境下更多是存储到es或是cassandra，这样对于查询或是系统扩展是比较方便的。此处介绍Jaeger的几个组件独立部署以及存储到ElasticSearch中。使用单个拉取镜像创建容器的方式，不通过docker compose批量创建。&lt;/p&gt;
&lt;h3 id="jaeger"&gt;Jaeger&lt;/h3&gt;
&lt;h4 id="组件部分"&gt;组件部分&lt;/h4&gt;
&lt;p&gt;all in one镜像中，如下几个部分全部打包到一个镜像中。此处对这几个部分使用各组件独立镜像单独部署。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221226/221246567_7bf0c5b5-07eb-49ca-b4a3-c710eda0e062.png" alt="221246567_7bf0c5b5-07eb-49ca-b4a3-c710eda0e062"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jaeger Agent: 一个监听在UDP端口上接收链路数据的网络守护进程，它从应用程序收集，批处理，并发送给Collector，(也可以没有这个，client直接上报)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jaeger Collector: 负责接收Jaeger-client或Jaeger-agent上报的调用链路数据，并通过处理管道运行它们，该管道验证跟踪、对它们进行索引、执行任何转换并最终保存到内存或外部存储系统中，供UI展示。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jaeger Query: 查询服务从存储中检索跟踪并呈现 UI 来显示它们。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DB: 采用EleasticSearch作为存储引擎。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jaeger Spark dependencies: 从存储中收集span数据，分析服务间的连接和依赖，并将它们存储起来以便在Jaeger UI 中显示。all in one镜像中不需要这个。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="组件镜像"&gt;组件镜像&lt;/h4&gt;
&lt;p&gt;官网提供了镜像的拉取命令，版本，用途等信息。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.jaegertracing.io/download/"&gt;https://www.jaegertracing.io/download/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221226/221248841_f64b7844-96b8-4597-906d-3752be223f97.png" alt="221248841_f64b7844-96b8-4597-906d-3752be223f97"&gt;&lt;/p&gt;
&lt;h3 id="jaeger-collector"&gt;Jaeger Collector&lt;/h3&gt;
&lt;h4 id="拉取镜像"&gt;拉取镜像&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker pull jaegertracing/jaeger-collector:1.40
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="创建容器"&gt;创建容器&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --name=StarCityJaegerCollector -p 9411:9411 -p 14250:14250 -p 14268:14268 -e SPAN_STORAGE_TYPE=elasticsearch -e ES_SERVER_URLS=http://es ip:9200 jaegertracing/jaeger-collector:1.40
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="端口说明"&gt;端口说明&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;端口号&lt;/th&gt;
&lt;th style="text-align: left"&gt;协议&lt;/th&gt;
&lt;th style="text-align: left"&gt;功能&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;9411&lt;/td&gt;
&lt;td style="text-align: left"&gt;HTTP&lt;/td&gt;
&lt;td style="text-align: left"&gt;能通过 JSON 或 Thrift 接收 Zipkin spans，默认关闭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;14250&lt;/td&gt;
&lt;td style="text-align: left"&gt;gRPC&lt;/td&gt;
&lt;td style="text-align: left"&gt;用于jaeger-agent发送model格式的span&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;14268&lt;/td&gt;
&lt;td style="text-align: left"&gt;HTTP&lt;/td&gt;
&lt;td style="text-align: left"&gt;能直接接收来自客户端的 jaeger.thrift 格式的 span&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;14269&lt;/td&gt;
&lt;td style="text-align: left"&gt;HTTP&lt;/td&gt;
&lt;td style="text-align: left"&gt;健康检查与自身服务的度量指标&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="jaeger-agent"&gt;Jaeger Agent&lt;/h3&gt;
&lt;h4 id="拉取镜像-1"&gt;拉取镜像&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker pull jaegertracing/jaeger-agent:1.40
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="创建容器-1"&gt;创建容器&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --name=StarCityJaegerAgent -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778/tcp -p 5775:5775/udp -e REPORTER_GRPC_HOST_PORT=Jaeger Collecor Ip:14250 -e LOG_LEVEL=debug jaegertracing/jaeger-agent:1.40
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中REPORTER_GRPC_HOST_PORT即为Agent要上报到Collector的地址&lt;/p&gt;</description></item><item><title> AspNet Core&amp;Jaeger实现链路追踪</title><link>/blog/2022-11-28-aspnetcore-jaeger-tracing/</link><pubDate>Mon, 28 Nov 2022 10:00:00 +0800</pubDate><guid>/blog/2022-11-28-aspnetcore-jaeger-tracing/</guid><description>&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;随着应用愈发复杂，请求的链路也愈发复杂，微服务化下，更是使得不同的服务分布在不同的机器，地域，语言也不尽相同。因此需要借助工具帮助分析，跟踪，定位请求中出现的若干问题，以此来保障服务治理，链路追踪也就出现了。&lt;/p&gt;
&lt;h3 id="opentracing协议"&gt;OpenTracing协议&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://opentracing.io/specification/"&gt;OpenTracing&lt;/a&gt;是一套分布式追踪协议，与平台，语言、厂商无关的Trace协议，统一接口，使得开发人员能够方便的添加或更换更换不同的分布式追踪系统。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/opentracing/specification/blob/master/specification.md"&gt;语义规范&lt;/a&gt; : 描述定义的数据模型 Tracer，Sapn 和 SpanContext 等；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://opentracing-contrib.github.io/opentracing-specification-zh/semantic_conventions.html"&gt;语义惯例&lt;/a&gt; : 罗列出 tag 和 logging 操作时，标准的key值；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样作为分布式追踪协议的还有&lt;a href="https://opencensus.io/"&gt;OpenCensus&lt;/a&gt;，以及两者的合并体&lt;a href="https://github.com/open-telemetry/opentelemetry-specification"&gt;OpenTelemetry&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id="jaeger介绍"&gt;Jaeger介绍&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.jaegertracing.io/"&gt;Jaeger&lt;/a&gt;[ˈdʒɛgər]是Uber推出的一款开源分布式追踪系统，兼容OpenTracing API，已在Uber大规模使用，且已加入&lt;a href="https://www.cncf.io/blog/2017/09/13/cncf-hosts-jaeger/"&gt;CNCF开源组织&lt;/a&gt;(Cloud Native Computing Foundation-云原生计算基金会)。其主要功能是聚合来自各个异构系统的实时监控数据。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221128/223511585_8015a0c6-49c1-47de-9743-3071dbc3f3aa.png" alt="223511585_8015a0c6-49c1-47de-9743-3071dbc3f3aa"&gt;
Jager提供了一套完整的追踪系统包括Jaeger-client、Jaeger-agent、Jaeger-collector、Database和Jaeger-query UI等基本组件。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Jaeger-client：为不同开发语言实现了符合OpenTracing协议的客户端。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jaeger-agent：一个监听在UDP端口上接收链路数据的网络守护进程，它从应用程序收集，批处理，并发送给Collector，(也可以没有这个，client直接上报)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jaeger-collector：负责接收Jaeger-client或Jaeger-agent上报的调用链路数据，并通过处理管道运行它们，该管道验证跟踪、对它们进行索引、执行任何转换并最终保存到内存或外部存储系统中，供UI展示。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jaeger-query：查询服务从存储中检索跟踪并呈现 UI 来显示它们。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="jaeger安装"&gt;Jaeger安装&lt;/h3&gt;
&lt;p&gt;在个人使用或者测试上，Jaeger提供了&lt;strong&gt;jaegertracing/all-in-one&lt;/strong&gt;镜像，搭建过程十分简单，数据存储在内存中，但需要注意容器挂了后数据就没了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d -p 6831:6831/udp -p 16686:16686 jaegertracing/all-in-one:latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建容器运行后，可以访问ip:16686查看Jaeger的仪表面板&lt;/p&gt;
&lt;h3 id="jaeger应用"&gt;Jaeger应用&lt;/h3&gt;
&lt;h4 id="服务设计"&gt;服务设计&lt;/h4&gt;
&lt;p&gt;简化大部分服务设计，整个结构上差不多是如下所示，服务层常见金字塔结构，服务上下游明确，以避免服务间的循环依赖。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221128/223513699_5a45fe2f-38d8-4159-8da9-3d393f2d77e5.png" alt="223513699_5a45fe2f-38d8-4159-8da9-3d393f2d77e5"&gt;
此处建立四个服务以及一个BFF网关层，以满足服务同步调用，服务间上下游调用，以及服务间事件通信。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;JaegerDemo.BFF.Host&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JaegerDemo.AService.Host&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JaegerDemo.BService.Host&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JaegerDemo.CService.Host&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JaegerDemo.DService.Host&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221128/223515651_5b76e385-5f16-4677-8fb2-ad3012730f1d.png" alt="223515651_5b76e385-5f16-4677-8fb2-ad3012730f1d"&gt;
为这几个服务设定期望如下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;执行Get请求时，从Gateway调用，请求A服务，在同步请求B和C服务，拿到结果组装后对外返回。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行Post请求时，从Gateway调用，请求A服务，在发布事件到MQ中，D服务订阅事件，数据写入到Sqlite中。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="nuget包引用"&gt;Nuget包引用&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jaeger，用来上传数据到Jaeger。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenTracing.Contrib.NetCore，基于&lt;a href="https://github.com/opentracing/opentracing-csharp"&gt;OpenTracing.Net&lt;/a&gt;的增强，用来采集应用数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MassTransit和MassTransit.RabbitMQ，用来完成事件的发布订阅。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;OpenTracing&amp;#34; Version=&amp;#34;0.12.1&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;Jaeger&amp;#34; Version=&amp;#34;1.0.3&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;MassTransit&amp;#34; Version=&amp;#34;8.0.8&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;MassTransit.RabbitMQ&amp;#34; Version=&amp;#34;8.0.8&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="服务注册"&gt;服务注册&lt;/h4&gt;
&lt;p&gt;将服务注册到容器中，设置上报地址，注意此处上报地址是UDP类型，因此在云服务器中开安全组时需要是UDP类型&lt;/p&gt;</description></item><item><title>DotNet Core&amp;RabbitMQ限制循环消费</title><link>/blog/2022-10-29-dotnetcore-rabbitmq-limitcyclicconsumption/</link><pubDate>Sat, 29 Oct 2022 10:00:00 +0800</pubDate><guid>/blog/2022-10-29-dotnetcore-rabbitmq-limitcyclicconsumption/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;当消费者端接收消息处理业务时，如果出现异常或是拒收消息将消息又变更为等待投递再次推送给消费者，这样一来，则形成循环的条件。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221029/223736374_0389a2bc-4763-41e2-a2aa-664deb8a10ce.png" alt="223736374_0389a2bc-4763-41e2-a2aa-664deb8a10ce"&gt;&lt;/p&gt;
&lt;h2 id="循环场景"&gt;循环场景&lt;/h2&gt;
&lt;p&gt;生产者发送100条消息到RabbitMQ中，消费者设定读取到第50条消息时，设置拒收，同时设定是否还留存在当前队列中(当requeue为false时，设置了死信队列则进入死信队列，否则移除消息)。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;consumer.Received += (model, ea) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var message = ea.Body;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Console.WriteLine(&amp;#34;接收到信息为:&amp;#34; + Encoding.UTF8.GetString(message.ToArray()));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    if (Encoding.UTF8.GetString(message.ToArray()).Contains(&amp;#34;50&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Console.WriteLine(&amp;#34;拒收&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        ((EventingBasicConsumer)model).Model.BasicReject(ea.DeliveryTag, requeue: true);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    ((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, multiple: false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当第50条消息拒收，则仍在队列中且处在队列头部，重新推送给消费者，再次拒收，再次推送，反反复复。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221029/223746012_aefbe513-2cab-4d7c-9e88-9f97bb35f926.png" alt="223746012_aefbe513-2cab-4d7c-9e88-9f97bb35f926"&gt;
最终其他消息全部消费完毕，仅剩第50条消息往复间不断消费，拒收，消费，这将可能导致RabbitMQ出现内存泄漏问题。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221029/223748085_4820aa3b-0500-45b4-bdf5-11696e29335d.png" alt="223748085_4820aa3b-0500-45b4-bdf5-11696e29335d"&gt;&lt;/p&gt;
&lt;h2 id="解决方案"&gt;解决方案&lt;/h2&gt;
&lt;p&gt;RabbitMQ及AMQP协议本身没有提供这类重试功能，但可以利用一些已有的功能来间接实现重试限定(以下只考虑基于手动确认模式情况)。此处只想到或是只查到了如下几种方案解决消息循环消费问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一次消费&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;无论成功与否，消费者都对外返回ack，将拒收原因或是异常信息catch存入本地或是新队列中另作重试。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消费者拒绝消息或是出现异常，返回Nack或Reject，消息进入死信队列或丢弃(requeue设定为false)。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;限定重试次数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在消息的头中添加重试次数，并将消息重新发送出去，再每次重新消费时从头中判断重试次数，递增或递减该值，直到达到限制，requeue改为false，最终进入死信队列或丢弃。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以在Redis、Memcache或其他存储中存储消息唯一键（例如Guid、雪花Id等，但必须在发布消息时手动设置它），甚至在mysql中连同重试次数一起存储，然后在每次重新消费时递增/递减该值，直到达到限制，requeue改为false，最终进入死信队列或丢弃。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;队列使用Quorum类型，限制投递次数，超过次数消息被删除。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;队列消息过期&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置过期时间，给队列或是消息设置TTL，重试一定次数消息达到过期时间后进入死信队列或丢弃(requeue设定为true)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;也许还有更多好的方案&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="一次消费"&gt;一次消费&lt;/h3&gt;
&lt;h4 id="对外总是ack"&gt;对外总是Ack&lt;/h4&gt;
&lt;p&gt;消息到达了消费端，可因某些原因消费失败了，对外可以发送Ack，而在内部走额外的方式去执行补偿操作，比如将消息转发到内部的RabbitMQ或是其他处理方式，终归是只消费一次。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var queueName = &amp;#34;alwaysack_queue&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;channel.BasicQos(0, 5, false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var consumer = new EventingBasicConsumer(channel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;consumer.Received += (model, ea) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    try
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var message = ea.Body;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Console.WriteLine(&amp;#34;接收到信息为:&amp;#34; + Encoding.UTF8.GetString(message.ToArray()));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        if (Encoding.UTF8.GetString(message.ToArray()).Contains(&amp;#34;50&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            throw new Exception(&amp;#34;模拟异常&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    catch (Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Console.WriteLine(ex.Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    finally
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        ((EventingBasicConsumer)model).Model.BasicAck(ea.DeliveryTag, multiple: false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;channel.BasicConsume(queue: queueName, autoAck: false, consumer: consumer);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当消费端收到消息，处理时出现异常，可以另想办法去处理，而对外保持着ack的返回，以避免消息的循环消费。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20221029/223750317_e3993a5f-cb33-4a20-b551-b729999e8072.png" alt="223750317_e3993a5f-cb33-4a20-b551-b729999e8072"&gt;&lt;/p&gt;</description></item><item><title>DotNet Core&amp;RabbitMQ优先级队列</title><link>/blog/2022-09-05-dotnetcore-rabbitmq-priorityqueue/</link><pubDate>Mon, 05 Sep 2022 10:00:00 +0800</pubDate><guid>/blog/2022-09-05-dotnetcore-rabbitmq-priorityqueue/</guid><description>&lt;h2 id="优先级队列"&gt;优先级队列&lt;/h2&gt;
&lt;p&gt;消息除了有生命周期长短，也有紧急与非紧急之分，承载了具有优先级消息的队列则为优先级队列。&lt;/p&gt;
&lt;h3 id="队列优先级设置"&gt;队列优先级设置&lt;/h3&gt;
&lt;p&gt;为消息设置优先级前，队列需要先具备优先级的能力，队列设置参数时，可添加x-max-priority来设置队列中消息能够达到的最高优先级(255封顶但推荐设置在1-10之间)。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var queueName = &amp;#34;priority_queue&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var arguments = new Dictionary&amp;lt;string, object&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    { &amp;#34;x-max-priority&amp;#34;, 3 }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: arguments);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Web管理页面中可以看到该队列打上了Pri的Tag。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220905/223856990_90a31066-2efa-40e9-9ba3-59a7bdd5d14c.png" alt="223856990_90a31066-2efa-40e9-9ba3-59a7bdd5d14c"&gt;&lt;/p&gt;
&lt;h3 id="消息优先级设置"&gt;消息优先级设置&lt;/h3&gt;
&lt;p&gt;为消息设置优先级，以此在队列中消息很多时，优先级高的消息可以被优先消费。发送消息时添加参数，设置Priority属性。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;for (int i = 0; i &amp;lt; 10; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var message = &amp;#34;第&amp;#34; + (i + 1) + &amp;#34;条消息&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    byte priority = 0;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    switch (i)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        case &amp;lt; 3:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            message += &amp;#34;-info&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            priority = 1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        case &amp;lt; 6:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            message += &amp;#34;-warn&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            priority = 2;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        default:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            message += &amp;#34;-error&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            priority = 3;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var body = Encoding.UTF8.GetBytes(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    var basicProperties = channel.CreateBasicProperties();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    basicProperties.Priority = priority;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    channel.BasicPublish(exchange: &amp;#34;&amp;#34;, routingKey: queueName, basicProperties: basicProperties, body: body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Console.WriteLine(&amp;#34;消息内容发送完毕:&amp;#34; + message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如上设置消息优先级随时间逐段增高，运行代码，优先级高的消息后进入队列，消费者优先获取高优先级消息。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220905/223859783_433d9ee1-1a36-44e0-8373-a1e329a4cc2a.png" alt="223859783_433d9ee1-1a36-44e0-8373-a1e329a4cc2a"&gt;
注：当消费者消费消息的速度快过生产者发送消息的速度并且队列中没有消息堆积下，优先级设置则失去意义，消息到达队列就被消费了。&lt;/p&gt;</description></item><item><title>DotNet Core&amp;RabbitMQ死信队列</title><link>/blog/2022-08-29-dotnetcore-rabbitmq-dlq/</link><pubDate>Mon, 29 Aug 2022 10:00:00 +0800</pubDate><guid>/blog/2022-08-29-dotnetcore-rabbitmq-dlq/</guid><description>&lt;h2 id="过期时间"&gt;过期时间&lt;/h2&gt;
&lt;p&gt;RabbitMQ可以为消息和队列设置过期时间Time To Live(TTL)。其目的即过期。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220829/223944337_53ad3c1c-7a7f-49cb-b5cd-8e316adb6180.png" alt="223944337_53ad3c1c-7a7f-49cb-b5cd-8e316adb6180"&gt;&lt;/p&gt;
&lt;h3 id="消息过期时间"&gt;消息过期时间&lt;/h3&gt;
&lt;p&gt;消息存储在队列中时，如果想为其设置一个有限的生命周期，而不是一直存储着，可以为其设置过期时间。比如，一条消息，我想要三分钟内有效，三分钟后再接收到该消息就算过时了，如果在队列中存储已经超过三分钟，消费者再去接收就是过时了，那便没有意义了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220829/223947216_f51a3105-002c-4438-b2e0-dc0bbbac07cc.png" alt="223947216_f51a3105-002c-4438-b2e0-dc0bbbac07cc"&gt;
为消息设置过期时间可以从两方面着手，一是为消息本身设置过期时间，二是为消息的承载体队列设置过期时间。两者同时设置情况下取最短生命周期。&lt;/p&gt;
&lt;h4 id="为消息设置"&gt;为消息设置&lt;/h4&gt;
&lt;p&gt;在BasicPublish方法中，可以设置BasicProperties中的Expiration来设置过期时间(单位为毫秒)。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var connFactory = new ConnectionFactory
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    HostName = &amp;#34;xxx.xxx.xxx.xxx&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Port = 5672,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    UserName = &amp;#34;rabbitmqdemo&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Password = &amp;#34;rabbitmqdemo@test&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    VirtualHost = &amp;#34;rabbitmqdemo&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (var conn = connFactory.CreateConnection())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    using (var channel = conn.CreateModel())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var queueName = &amp;#34;messagettl_queue&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        while (true)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Console.WriteLine(&amp;#34;消息内容(exit退出):&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var message = Console.ReadLine();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            if (message.Trim().ToLower() == &amp;#34;exit&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var body = Encoding.UTF8.GetBytes(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var basicProperties = channel.CreateBasicProperties();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            basicProperties.Expiration = &amp;#34;10000&amp;#34;;//10秒
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            channel.BasicPublish(exchange: &amp;#34;&amp;#34;, routingKey: queueName, basicProperties: basicProperties, body: body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Console.WriteLine(&amp;#34;消息内容发送完毕:&amp;#34; + message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如此一来，设置消息生命周期为10秒，当超过该时间后，再由消费者去获取该消息，则获取不到，可以直接从Web界面看到，经过10秒后，消息过期(未启动消费者)。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220829/223948447_90191544-7335-4591-9138-6471332dbe0d.png" alt="223948447_90191544-7335-4591-9138-6471332dbe0d"&gt;&lt;/p&gt;</description></item><item><title>DotNet Core&amp;RabbitMQ消息存储可靠机制</title><link>/blog/2022-08-25-dotnetcore-rabbitmq-reliablemessagestorage/</link><pubDate>Thu, 25 Aug 2022 10:00:00 +0800</pubDate><guid>/blog/2022-08-25-dotnetcore-rabbitmq-reliablemessagestorage/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;上篇讨论过消息投递和消息消费过程中如何确保可靠传输，也提及到消息到达RabbitMQ中到被消费前也需要可靠的留存，可因许多的不确定因素会影响着消息的存在与否。&lt;/p&gt;
&lt;h2 id="消息中转点"&gt;消息中转点&lt;/h2&gt;
&lt;p&gt;生产者发送消息到RabbitMQ中，如果交换机根据自身类型和RoutingKey能够匹配到队列，则存入相关队列，但当匹配不到队列时，遇到两种情况而使得消息走向不同的方向，消息可能会丢失或是发回给生产者，这取决于生产者对消息的配置。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220825/224056014_721e3972-d563-4631-94d2-f73b399ac20e.png" alt="224056014_721e3972-d563-4631-94d2-f73b399ac20e"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;生产者设置了Mandatory且为true，则消息回退给生产者。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当生产者为设置Mandatory或是设置为false时，为了避免消息丢失，可以由交换机路由给备份交换机负责去搞定存储。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220825/224058028_0daae9c3-3bf7-452e-9d88-601eb2097255.png" alt="224058028_0daae9c3-3bf7-452e-9d88-601eb2097255"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mandatory"&gt;Mandatory&lt;/h3&gt;
&lt;p&gt;生产者发送消息时，可以设置一个参数mandatory，来决定消息到达RabbitMQ后，如果出现交换机根据自身类型及RoutingKey找不到合适的队列情况下，消息的一个走向。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当mandatory为true时，消息则返回给生产者。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220825/224059496_8f9b634e-8508-4381-bf89-7d540a2805d2.png" alt="224059496_8f9b634e-8508-4381-bf89-7d540a2805d2"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当mandatory为false时，消息则被丢弃。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="生产者代码"&gt;生产者代码&lt;/h4&gt;
&lt;p&gt;当在BasicPublish方法参数中设置mandatory为true且队列暂不声明时，仅有一个交换机，消息将会被返回。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var connFactory = new ConnectionFactory
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    HostName = &amp;#34;xxx.xxx.xxx.xxx&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Port = 5672,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    UserName = &amp;#34;rabbitmqdemo&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Password = &amp;#34;rabbitmqdemo@test&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    VirtualHost = &amp;#34;rabbitmqdemo&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (var conn = connFactory.CreateConnection())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    using (var channel = conn.CreateModel())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var exchangeName = &amp;#34;mandatory_publishsubscribe_exchange&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        channel.ExchangeDeclare(exchange: exchangeName, type: &amp;#34;fanout&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        while (true)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Console.WriteLine(&amp;#34;消息内容(exit退出):&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var message = Console.ReadLine();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            if (message.Trim().ToLower() == &amp;#34;exit&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var body = Encoding.UTF8.GetBytes(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            channel.BasicPublish(exchange: exchangeName, routingKey: &amp;#34;&amp;#34;, mandatory: true, basicProperties: null, body: body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Console.WriteLine(&amp;#34;消息内容发送完毕:&amp;#34; + message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;生产者发送消息，交换机收到消息但无对应队列，消息被返回。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220825/224101092_bd88499c-d79e-4722-a6bf-e8e201b79286.png" alt="224101092_bd88499c-d79e-4722-a6bf-e8e201b79286"&gt;
为了直观的知道消息返回到了生产者，我们可以增加一个监听器，来监听返回的消息。&lt;/p&gt;</description></item><item><title>DotNet Core&amp;RabbitMQ消息转发可靠机制</title><link>/blog/2022-08-23-dotnetcore-rabbitmq-reliablemessageforward/</link><pubDate>Tue, 23 Aug 2022 10:00:00 +0800</pubDate><guid>/blog/2022-08-23-dotnetcore-rabbitmq-reliablemessageforward/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;生产者发送消息到了队列，队列推送数据给了消费者，这里存在一些问题需要思考下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;生产者如何确保消息一定投递到了队列中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RabbitMQ 丢失了消息(下文暂不涉及这块)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;队列如何确保消费者收到了消息呢&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220823/224207778_7e1bd085-c6f5-4fe0-b2ab-ee486cbbf8f4.png" alt="224207778_7e1bd085-c6f5-4fe0-b2ab-ee486cbbf8f4"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="生产者可靠发送"&gt;生产者可靠发送&lt;/h2&gt;
&lt;h3 id="执行流程"&gt;执行流程&lt;/h3&gt;
&lt;p&gt;当生产者将消息发送出去后，如果不进行特殊设置，默认情况下，发送消息操作后是没有返回任何消息给生产者的，这时生产者是不知道消息有没有真的到达了RabbitMQ，消息可能在到达RabbitMQ前已经丢了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220823/224209485_fde4ccd3-009c-4e5e-ab08-5e83666c5bde.png" alt="224209485_fde4ccd3-009c-4e5e-ab08-5e83666c5bde"&gt;
对于这种情况，有两种解决方案来应对，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;事务机制，该机制是AMQP协议层面提供的解决方案&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发送方确认机制，这是RabbitMQ提供的解决方案&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="事务机制"&gt;事务机制&lt;/h3&gt;
&lt;p&gt;可以将当前使用的channel设置成事务模式。借助如下与事务机制相关的三个方法来完成事务操作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;channel.TxSelect() 启动事务模式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;channel.TxCommit() 提交事务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;channel.TxRollback() 回滚事务&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="txcommit"&gt;TXCommit&lt;/h4&gt;
&lt;p&gt;事务模式下，时序图中发布消息前后增加了一点动作，设置信道为事务模式与提交事务。如果TXCommit()调用成功，则说明事务提交成功，消息一定到达了RabbitMQ中。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220823/224211063_feac9e4b-f340-45ad-b6cf-b8e0be096e55.png" alt="224211063_feac9e4b-f340-45ad-b6cf-b8e0be096e55"&gt;&lt;/p&gt;
&lt;h4 id="txrollback"&gt;TXRollback&lt;/h4&gt;
&lt;p&gt;当事务提交执行之前，由于RabbitMQ异常崩溃或其他原因抛出异常，则可以捕获异常，再执行TXRollback()，完成事务回滚。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220823/224212554_ec6f6115-03e3-4e64-be05-2c1cf13dfd81.png" alt="224212554_ec6f6115-03e3-4e64-be05-2c1cf13dfd81"&gt;&lt;/p&gt;
&lt;h4 id="生产者代码"&gt;生产者代码&lt;/h4&gt;
&lt;p&gt;在原有Basic代码基础上增加事务操作，此处先注释掉与事务有关的代码(16、34和40行)，并且在发送消息后设置异常模拟&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var connFactory = new ConnectionFactory
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    HostName = &amp;#34;xxx.xxx.xxx.xxx&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Port = 5672,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    UserName = &amp;#34;rabbitmqdemo&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Password = &amp;#34;rabbitmqdemo@test&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    VirtualHost = &amp;#34;rabbitmqdemo&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (var conn = connFactory.CreateConnection())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    using (var channel = conn.CreateModel())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var queueName = &amp;#34;helloworld_tx&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        //channel.TxSelect();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        while (true)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Console.WriteLine(&amp;#34;消息内容(exit退出):&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var message = Console.ReadLine();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            if (message.Trim().ToLower() == &amp;#34;exit&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            try
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                var body = Encoding.UTF8.GetBytes(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                channel.BasicPublish(exchange: &amp;#34;&amp;#34;, routingKey: queueName, basicProperties: null, body: body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                if (message.Trim().ToLower() == &amp;#34;9527&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    throw new Exception(&amp;#34;触发异常&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                //channel.TxCommit();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                Console.WriteLine(&amp;#34;消息内容发送完毕:&amp;#34; + message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            catch (Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                Console.WriteLine(&amp;#34;消息发送出现异常:&amp;#34; + ex.Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                //channel.TxRollback();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;消费端代码保持不变(仅改动下队列名称)
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220823/224214293_34b205c0-94eb-42e2-81a3-1ac13bd6beae.png" alt="224214293_34b205c0-94eb-42e2-81a3-1ac13bd6beae"&gt;
因生产者已经将消息发送到了队列中，后触发了异常，实则已经和消息能不能到达队列无关了，在消费端也就正常消费了。&lt;/p&gt;</description></item><item><title>DotNet Core&amp;RabbitMQ基本使用</title><link>/blog/2022-07-29-dotnetcore-rabbitmq-basicusage/</link><pubDate>Fri, 29 Jul 2022 10:00:00 +0800</pubDate><guid>/blog/2022-07-29-dotnetcore-rabbitmq-basicusage/</guid><description>&lt;h2 id="队列模式"&gt;队列模式&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.rabbitmq.com/getstarted.html"&gt;https://www.rabbitmq.com/getstarted.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220729/224250120_3972fed9-214f-4daa-8025-e76c291e4ee5.png" alt="224250120_3972fed9-214f-4daa-8025-e76c291e4ee5"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220729/224251608_7baa5ef6-dd1e-4ddf-97c8-b8d93194061b.png" alt="224251608_7baa5ef6-dd1e-4ddf-97c8-b8d93194061b"&gt;
对以上几种模式进行简要分类，可以分成如下三类(RPC暂不考虑)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;简单队列模式，单发单收，一对一模式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Worker模式，单发多收(一个消息一个接收者，多个消息多个接收者)，一对多模式&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发布订阅模式，包括发布订阅、路由、通配符模式，这三种只是交换机类型不同&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="简单队列模式"&gt;简单队列模式&lt;/h2&gt;
&lt;p&gt;队列作为中间件，在生产者和消费者中间承担消息传递的通道&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220729/224253250_709a078c-74b5-4834-bbea-28ebb36a2481.png" alt="224253250_709a078c-74b5-4834-bbea-28ebb36a2481"&gt;
创建两个控制台项目RabbitMQDemo.Basic.Producer和RabbitMQDemo.Basic.Consumer并安装Nuget包以支持对RabbitMQ操作&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;install-package rabbitmq.client
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="生产者代码"&gt;&lt;strong&gt;生产者代码&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;通过IConnectionFactory，IConnection和IModel来创建连接和信道。IConnection实例对象负责与RabbitMQ 服务端的连接，信道是在这连接基础上创建虚拟连接，通过复用来减少性能开销且便于管理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过QueueDeclare方法创建消息队列，设置消息队列本身的一些属性信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发送消息时调用BasicPublish来发送消息，通过exchange和routingkey参数满足大部分匹配消息队列的场景。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var connFactory = new ConnectionFactory
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    HostName = &amp;#34;xxx.xxx.xxx.xxx&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Port = 5672,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    UserName = &amp;#34;rabbitmqdemo&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Password = &amp;#34;rabbitmqdemo@test&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    VirtualHost = &amp;#34;rabbitmqdemo&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (var conn = connFactory.CreateConnection())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    using (var channel = conn.CreateModel())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var queueName = &amp;#34;helloworld&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        while (true)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Console.WriteLine(&amp;#34;消息内容(exit退出):&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var message = Console.ReadLine();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            if (message.Trim().ToLower() == &amp;#34;exit&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var body = Encoding.UTF8.GetBytes(message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            channel.BasicPublish(exchange: &amp;#34;&amp;#34;, routingKey: queueName, basicProperties: null, body: body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Console.WriteLine(&amp;#34;消息内容发送完毕:&amp;#34; + message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="消费者代码"&gt;&lt;strong&gt;消费者代码&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;消费端同样先创建连接和信道&lt;/p&gt;</description></item><item><title>AspNetCore&amp;云效Flow持续集成</title><link>/blog/2022-06-25-aspnetcore-aliyunxiao-continuousintegration/</link><pubDate>Sat, 25 Jun 2022 10:00:00 +0800</pubDate><guid>/blog/2022-06-25-aspnetcore-aliyunxiao-continuousintegration/</guid><description>&lt;p&gt;如今有了越来越多的持续集成工具，给的个人开发者的福利也是很足了，如无必要，自建工具有时只是作为练手了。&lt;/p&gt;
&lt;h2 id="众多持续集成工具"&gt;众多持续集成工具&lt;/h2&gt;
&lt;p&gt;现在可用的持续集成工具繁多，各大云服务商都推出了持续集成，甚至是一定条件内都是免费使用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Azure DevOps 提供每个月1800 分钟的免费时长，支持单项目并行构建，5人以下。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GitHub推出的GitHub Action 每月2000分钟的构建时长。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;华为云的 DevCloud 提供的编译构建5并发，每月600分钟，5人以下。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gitee 推出了Gitee Go 单个代码仓库均可直接获得200分钟免费构建时长，永久有效！ 企业/组织/个人每月1000分钟免费构建时长自动到账。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;腾讯云与 Coding 合作的Coding DevOps，初期Coding提供5人以下免费，每周200次持续集成次数，每次限时30分钟，并行任务数1个，中间变更到每月1000分钟持续集成时长，再如今&lt;a href="https://help.coding.net/docs/admin/pay/newprice.html"&gt;七月份后变更&lt;/a&gt;到每月300分钟&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;阿里云的&lt;a href="https://www.aliyun.com/product/yunxiao"&gt;云效DevOps&lt;/a&gt; 最大并发任务数 6，运行时长 5400 分钟/月。人数不限！&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224355732_7c561238-200b-41b1-a2f6-d3168efb1930.png" alt="224355732_7c561238-200b-41b1-a2f6-d3168efb1930"&gt;&lt;/p&gt;
&lt;h2 id="云效的功能"&gt;云效的功能&lt;/h2&gt;
&lt;p&gt;云效DevOps中提供了如下功能可以使用，其中可根据实际开发中所用到的工具去比对选择。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224357139_c8ed18e6-e0f4-45f0-a24a-96e52c2965e7.png" alt="224357139_c8ed18e6-e0f4-45f0-a24a-96e52c2965e7"&gt;
此处，我只想白嫖流水线用于持续集成构建Docker镜像。对于Docker镜像的存储，阿里云提供了&lt;a href="https://www.aliyun.com/product/acr"&gt;容器镜像服务&lt;/a&gt;(其本身也具备镜像构建功能，但受限)。对于个人开发者练手或是小微企业存储镜像应该算满足了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224358417_da40c410-5629-4d33-971d-9803402c2836.png" alt="224358417_da40c410-5629-4d33-971d-9803402c2836"&gt;
对于代码的存储，可以选择云效 DevOps的代码管理(CodeUp)或是其他平台，这不影响流水线功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224400180_1cb25c0c-c63f-4cf0-b62d-ee69342858f3.png" alt="224400180_1cb25c0c-c63f-4cf0-b62d-ee69342858f3"&gt;
如此，简单的持续集成流程需要的功能算是齐全了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224401626_d698e06e-50db-40cb-8f64-14733f66954a.png" alt="224401626_d698e06e-50db-40cb-8f64-14733f66954a"&gt;&lt;/p&gt;
&lt;h2 id="项目设置"&gt;项目设置&lt;/h2&gt;
&lt;p&gt;账号注册、申请这类就不提了，具体就是如何来快速完成一个持续集成流程的配置。&lt;/p&gt;
&lt;p&gt;1、首先在代码仓库中新建项目，对于代码仓库的选择随意，我更倾向于使用 Gitee，当然也可直接使用云效提供的代码仓库。此处新建了一个仓库方便对照： &lt;a href="https://gitee.com/530521314/Partner.TreasureChest/tree/master/Flow.Standard"&gt;https://gitee.com/530521314/Partner.TreasureChest/tree/master/Flow.Standard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2、新建 AspNetCore WebApi项目并增加 Dockerfile、gitignore文件，整理成常用的目录形式。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224402921_b08b7c78-c12c-4646-9bfb-36cf9717c063.png" alt="224402921_b08b7c78-c12c-4646-9bfb-36cf9717c063"&gt;&lt;/p&gt;
&lt;h2 id="容器镜像服务设置"&gt;容器镜像服务设置&lt;/h2&gt;
&lt;p&gt;1、创建命名空间(3个命名空间共存300个镜像仓库)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224404156_f7bef5e8-a08d-432b-99f2-af0b8f47a598.png" alt="224404156_f7bef5e8-a08d-432b-99f2-af0b8f47a598"&gt;
2、创建镜像仓库，选择对应的命名空间，填写仓库信息。(镜像仓库下存放具体的镜像)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224405968_bf1e71e4-abb4-4560-aaa1-05e194f679e4.png" alt="224405968_bf1e71e4-abb4-4560-aaa1-05e194f679e4"&gt;
3、下一步中选择本地仓库后创建即可。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224407307_958f3b4b-0f79-45b5-ba99-3a4a850ef356.png" alt="224407307_958f3b4b-0f79-45b5-ba99-3a4a850ef356"&gt;
4、创建完毕会引导到该仓库下，当流水线跑完后选择镜像版本查看构建完的镜像。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224408555_99c39df3-450f-473c-bbb0-80bf1ad2e220.png" alt="224408555_99c39df3-450f-473c-bbb0-80bf1ad2e220"&gt;&lt;/p&gt;
&lt;h2 id="云效流水线配置"&gt;云效流水线配置&lt;/h2&gt;
&lt;h3 id="开始使用流水线"&gt;开始使用流水线&lt;/h3&gt;
&lt;p&gt;新建流水线，可以按照语言类别选择，此处我使用空模板。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224409948_9e109ef4-1f20-437a-8ecf-396334b6942e.png" alt="224409948_9e109ef4-1f20-437a-8ecf-396334b6942e"&gt;&lt;/p&gt;
&lt;h3 id="设置流水线源步骤"&gt;设置流水线源步骤&lt;/h3&gt;
&lt;p&gt;页面跳转后会提示选择代码源，此处我使用Gitee中的仓库，选择分支，添加即可。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224411528_78a90eba-8689-4bc7-964f-1ae150b65875.png" alt="224411528_78a90eba-8689-4bc7-964f-1ae150b65875"&gt;&lt;/p&gt;
&lt;h3 id="增加镜像构建步骤"&gt;增加镜像构建步骤&lt;/h3&gt;
&lt;p&gt;1、点击空任务更改其为镜像构建(或是其他名字)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224413040_e03cb5d2-b155-41b8-a297-df2e23efb896.png" alt="224413040_e03cb5d2-b155-41b8-a297-df2e23efb896"&gt;
2、添加任务步骤，此处可供选择的步骤很多，当前使用构建并推送到镜像仓库个人版&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224414530_59c9a1df-c728-4690-9957-bb5cc8f3fa8c.png" alt="224414530_59c9a1df-c728-4690-9957-bb5cc8f3fa8c"&gt;
3、选择完毕会提示填写镜像仓库地址，该地址为上一步中的容器镜像服务地址，选择地域，镜像仓库。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220625/224416002_59055bbb-10f3-47f2-9c0a-8adcea893faf.png" alt="224416002_59055bbb-10f3-47f2-9c0a-8adcea893faf"&gt;
4、配置目标镜像版本，默认是用日期作为镜像的标签。&lt;/p&gt;</description></item><item><title>理解RESTful Api设计</title><link>/blog/2022-05-24-restfulapi-design/</link><pubDate>Tue, 24 May 2022 10:00:00 +0800</pubDate><guid>/blog/2022-05-24-restfulapi-design/</guid><description>&lt;h2 id="rest"&gt;REST&lt;/h2&gt;
&lt;p&gt;REST(REpresentational State Transfer）是 Roy Fielding 博士于 2000 年在他的博士论文中提出来的一种软件架构风格(一组架构约束条件和原则)。在该论文的 &lt;a href="https://www.infoq.cn/minibook/web-based-apps-archit-design"&gt;中文译本&lt;/a&gt; 中翻译是&amp;quot;表述性状态移交&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220524/224523965_fdc43ecb-2b59-4300-843a-8f1716ca01c8.png" alt="224523965_fdc43ecb-2b59-4300-843a-8f1716ca01c8"&gt;&lt;/p&gt;
&lt;h3 id="原则"&gt;原则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;网络上的所有事物都被抽象为资源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每个资源都有一个唯一的资源标识符&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同一个资源具有多种表现形式(xml，json 等)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对资源的各种操作不会改变资源标识符&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有的操作都是无状态的&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="资源resources"&gt;资源（Resources）&lt;/h3&gt;
&lt;p&gt;资源是一种信息实体或者说是一个具体信息，能够被想象出名字。比如多个图书馆，那么便是可使用的图书馆资源，而图书馆内，多个楼层，那么便拥有了多个楼层的资源，各楼层提供了不同服务，那么服务也是资源。在互联网中，可以用一个 URI（统一资源定位符）指向它，每种资源对应一个特定的 URI（如同一本书，按照书页码去定位哪一页，目的是定位资源）。访问这个特定 URI 便获取到了这个对应的资源。&lt;/p&gt;
&lt;h3 id="表述representations"&gt;表述（REpresentations）&lt;/h3&gt;
&lt;p&gt;资源的表述是一段对于资源在某个特定时刻的状态的描述，通过表述捕获资源，并在组件间(客户/服务器)移交该表述。表述有多种格式，如 HTML/XML/JSON/纯文本/图片/视频/音频等。具体的表述格式，可以在 HTTP 请求头信息中用 Accept 和 Content-Type 字段指定，请求/响应方向的表述通常使用不同的格式。&lt;/p&gt;
&lt;h3 id="状态移交state-transfer"&gt;状态移交（State Transfer）&lt;/h3&gt;
&lt;p&gt;对于组件间而言(客户/服务器)，资源的请求是一个互动过程。通过表述捕获资源当前或是预期的状态，相当于获得了资源的状态。通过移交代表资源的表述，来将资源在组件的两者之间进行传递，进而改变应用状态。如当客户端获取了资源后，自身状态处于稳定，当再次获取资源后自身状态再次处于稳定。客户端操作并对服务端发起请求，在资源上执行各种动作而打破资源自身状态，达到客户端操作所期望状态。&lt;/p&gt;
&lt;h2 id="restful-api"&gt;RESTful Api&lt;/h2&gt;
&lt;p&gt;与 REST 相比多了一个 ful，就英语层面来说是一个形容词，RESTful 翻译成中文为“REST 式的”，满足了 REST 架构风格的应用程序设计的 Api 则便是 RESTful Api，即 REST 式的 Api。&lt;/p&gt;
&lt;h3 id="以往-api-设计"&gt;以往 Api 设计&lt;/h3&gt;
&lt;p&gt;在 MVC 项目中，经常都是设计成动宾结构给 ajax 调用&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/getCustomers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/getCustomersByName
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/getCustomersByPhone
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/getNewCustomers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/verifyCredit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/saveCustomer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/updateCustomer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/deleteCustomer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可有时却因为没有统一的规范，多人协作时，对于动词的描述上也没有统一，时长出现了类似如下的各类叫法，不能说这种情况有什么弊端，毕竟这种方式也是正常工作着。&lt;/p&gt;</description></item><item><title>AspNetCore&amp;JWT认证授权</title><link>/blog/2022-04-17-aspnetcore-jwt-authentication-authorization/</link><pubDate>Sun, 17 Apr 2022 10:00:00 +0800</pubDate><guid>/blog/2022-04-17-aspnetcore-jwt-authentication-authorization/</guid><description>&lt;p&gt;有时想快速搭建一个简单应用，并集成登录功能时，总是会被认证授权绕来绕去，一直想着要搞个授权中心，却把最为简单快捷的方式抛掷脑后。&lt;/p&gt;
&lt;p&gt;认证与授权说来说去还是四个核心步骤，登录退出，登录有效后请求资源，请求人是谁与请求人有没有权限请求。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220417/224556784_e0809c3b-d2ab-4e55-848c-e1bcb463866f.png" alt="224556784_e0809c3b-d2ab-4e55-848c-e1bcb463866f"&gt;&lt;/p&gt;
&lt;h2 id="jwt"&gt;JWT&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://jwt.io/"&gt;JSON Web Token&lt;/a&gt;（JWT）是目前最流行的跨域身份验证解决方案。其本身只是一种格式或是协议，集成到框架中，然后便按照这种格式或协议来传递信息。&lt;/p&gt;
&lt;p&gt;当使用认证授权时，将具有用户信息的令牌以JWT格式的呈现，命名为Id token或是Access token。&lt;/p&gt;
&lt;h2 id="项目准备"&gt;项目准备&lt;/h2&gt;
&lt;p&gt;准备一个Asp.Net Core 6.0的WebApi(前端实现不考虑)。按照如上几个用例挨个实现(退出用例不考虑)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220417/224558257_fdba0ffc-e413-4e86-8c75-de1e32b934e3.png" alt="224558257_fdba0ffc-e413-4e86-8c75-de1e32b934e3"&gt;&lt;/p&gt;
&lt;h2 id="安装nuget包"&gt;安装Nuget包&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;Swashbuckle.AspNetCore&amp;#34; Version=&amp;#34;6.2.3&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;PackageReference Include=&amp;#34;Microsoft.AspNetCore.Authentication.JwtBearer&amp;#34; Version=&amp;#34;6.0.3&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;PackageReference Include=&amp;#34;IdentityModel&amp;#34; Version=&amp;#34;6.0.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="生成jwt格式令牌"&gt;生成JWT格式令牌&lt;/h2&gt;
&lt;h3 id="增加account控制器"&gt;增加Account控制器&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ApiController]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Route(&amp;#34;[controller]&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class AccountController : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="增加jwtoption"&gt;增加JwtOption&lt;/h3&gt;
&lt;p&gt;该部分信息用户Jwt格式中所需要的&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class JwtOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public const string Name = &amp;#34;Jwt&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public string Audience { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public string Issuer { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public double ExpiresMinutes { get; set; } = 30d;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public Encoding Encoding { get; set; } = Encoding.UTF8;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public string SymmetricSecurityKeyString { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public SymmetricSecurityKey SymmetricSecurityKey =&amp;gt; new(Encoding.GetBytes(SymmetricSecurityKeyString));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="服务注册"&gt;服务注册&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.Configure&amp;lt;AuthConfigOptions&amp;gt;(builder.Configuration.GetSection(AuthConfigOptions.Name));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="配置信息"&gt;配置信息&lt;/h3&gt;
&lt;p&gt;appsettings.json中增加该块配置&lt;/p&gt;</description></item><item><title>理解OIDC协议和认证机制</title><link>/blog/2022-03-31-oidc-protocol/</link><pubDate>Thu, 31 Mar 2022 10:00:00 +0800</pubDate><guid>/blog/2022-03-31-oidc-protocol/</guid><description>&lt;p&gt;当互联网应用越来越多，每个应用程序都实现了自己的身份存储、认证和授权，用户需要在应用上反复的注册与登录，体验糟糕，用户身份信息无法在多个应用间共享与同步。当使用企业应用时，企业提供了一系列应用，尽管是同一用户操作，但还是不得不切换注册与登录。&lt;/p&gt;
&lt;p&gt;是否可以有那么一种方式，我在某个代理网站注册一个账号，如果登录其他网站时，其他网站可以到代理网站去获取信息。只要代理网站足够富有生命力，那么总是可以省略很多乏味的注册登录流程。&lt;/p&gt;
&lt;p&gt;这些想法在技术的发展过程中出现了，如WS-Federation、SAML2.0、OpenID、OAuth、OIDC&amp;hellip;&lt;/p&gt;
&lt;h2 id="openidoauth发展历史"&gt;OpenID&amp;amp;OAuth发展历史&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220331/224647287_336f04e9-aa2e-4afd-9c8f-711d162ceeab.png" alt="224647287_336f04e9-aa2e-4afd-9c8f-711d162ceeab"&gt;
还有一些修订和扩展，此处就不体现了，主要是想聚焦于OpenID和OAuth是不一样的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenID是面向身份认证，对用户身份进行认证判断是否有效。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OAuth是面向授权过程，在已知当前用户身份合法后，控制用户能够访问的资源，考虑保护被访问的资源。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="openid"&gt;OpenID&lt;/h2&gt;
&lt;p&gt;OpenID是一个去中心化的网上身份认证系统。对于支持OpenID的网站，用户不需要记住像用户名和密码这样的传统认证标记。取而代之的是，他们只需要预先在一个作为OpenID身份提供者（identity provider, IdP）的网站上注册。OpenID是去中心化的，任何网站都可以使用OpenID来作为用户登录的一种方式，任何网站也都可以作为OpenID身份提供者。OpenID既解决了注册问题而又不需要依赖于中心性的网站来确认数字身份。&lt;/p&gt;
&lt;p&gt;OpenID给用户提供一个统一网络身份，遵守OpenID网络身份，遵守OpenID协议的应用网站**（在OpenId术语里叫Relying Parties，即RP）&lt;strong&gt;需要用户登录时，引导用户到第三方OpenID身份提供方&lt;/strong&gt;（OpenId术语里叫OpenID identity provider，即OP）**去认证，认证通过即登录成功。这样就省去了每个应用网站注册、登录的烦恼与乏味。解决了最开始提到的一个问题，各应用需要独立完成注册与登录流程，需要实现与维护独立的用户数据库。&lt;/p&gt;
&lt;h2 id="oauth"&gt;OAuth&lt;/h2&gt;
&lt;p&gt;OAuth是一个授权协议，解决的是应用可以访问用户在另一应用上的某些授权资源，但此应用不需要知道用户在另一应用上的账户密码，即无需向另一应用透露用户的凭据便可以访问另一应用上受控的资源。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2006年11月，当时布莱恩·库克正在开发Twitter的OpenID实现。与此同时，社交书签网站Ma.gnolia需要一个解决方案允许使用OpenID的成员授权Dashboard访问他们的服务。这样库克、克里斯·梅西纳和来自Ma.gnolia的拉里·哈尔夫（Larry Halff）与戴维·雷科尔顿会面讨论在Twitter和Ma.gnolia API上使用OpenID进行委托授权。但他们讨论得出结论，认为OpenID没有完成API访问委托的开放标准。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2007年4月，成立了OAuth讨论组，这个由实现者组成的小组撰写了一个开放协议的提议草案。来自Google的德维特·克林顿获悉OAuth项目后，表示他有兴趣支持这个工作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2007年7月，团队起草了最初的规范。随后，Eran Hammer-Lahav加入团队并协调了许多OAuth的稿件，创建了更为正式的规范。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2007年10月，OAuth Core 1.0最后的草案发布了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2008年11月，在明尼阿波利斯举行的互联网工程任务组第73次会议上，举行了OAuth的BoF讨论将该协议纳入IETF做进一步的规范化工作。这个会议参加的人很多，关于正式地授权在IETF设立一个OAuth工作组这一议题得到了广泛的支持。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2010年4月，OAuth 1.0协议发表为RFC 5849，一个非正式RFC。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2012年10月，OAuth 2.0发布，正式发表为RFC 6749。OAuth 2.0是OAuth协议的下一版本，但不向下兼容OAuth 1.0。OAuth 2.0关注客户端开发者的简易性，同时为Web应用、桌面应用、手机和智能设备提供专门的认证流程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="open-id-connect"&gt;Open ID Connect&lt;/h2&gt;
&lt;p&gt;OpenID团队本来希望用户使用OpenID来进行身份认证， 但因为授权过程包括认证过程作为一部分，所以授权意味着经过了认证这一过程。由此，一些用户选择了OAuth身份认证的简易性，并且由于“管理用户凭据的任务可以委托给外部服务”以及“因为新用户注册过程可以省略，使用该服务的障碍变得更低”等优点而迅速占据主导地位，使用OAuth2.0作为身份认证的趋势变得流行。&lt;/p&gt;
&lt;p&gt;OpenID团队不得不承认用户更喜欢OAuth2.0，因此，他们在OAuth之上定义了一个新的身份认证规范&lt;strong&gt;O&lt;/strong&gt;pen&lt;strong&gt;ID&lt;/strong&gt; &lt;strong&gt;C&lt;/strong&gt;onnect(OIDC)，这个规范基于OAuth2.0进行了小幅扩展，在OAuth2.0上构建了身份层，使其可用作身份认证协议。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220331/224649047_380da7bb-8470-4ee7-8c7b-5fb2f55fa877.png" alt="224649047_380da7bb-8470-4ee7-8c7b-5fb2f55fa877"&gt;
即OpenID Connect (OIDC) 是基于 OAuth 2.0 构建的身份认证协议。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenID+OAuth 2.0=OpenID Connect&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="oidc主要术语"&gt;OIDC主要术语&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Claim: 声明信息，简要格式展示(如身份证上格式)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Claims Provider: 声明信息提供方(如OAuth2.0中的Authorization Server能够返回Id token，其中携带着声明信息)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;End-User(EU)&lt;/strong&gt;: 人类参与者(完成认证活动的人)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ID Token: 包含EU身份信息的声明(Claim)，以JWT(Json Web Token)格式进行存储传递。JWT具有自包含性、紧凑性和防篡改机制等特点。使得ID Token可以安全的传递给第三方应用并且容易被验证。&lt;/p&gt;</description></item><item><title>理解OAuth2.0协议和授权机制</title><link>/blog/2022-02-26-oauth2_0-protocal/</link><pubDate>Sat, 26 Feb 2022 10:00:00 +0800</pubDate><guid>/blog/2022-02-26-oauth2_0-protocal/</guid><description>&lt;p&gt;无论是自然资源还是互联网上的资源，需要控制使用权与被使用权，以保护资源的安全、合理的使用和有效的管控。&lt;/p&gt;
&lt;p&gt;项目中，我们需要控制的是用户资源，既要保证有效用户的合理使用，又要防范非法用户的攻击。如此，如何区分有效和非法就是我们需要考虑的问题，简单点，通过账号密码来区分，能够通过检测的便是有效用户。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220226/224740451_c3ba25c6-c1b5-4aaf-8824-1475999dfc98.png" alt="224740451_c3ba25c6-c1b5-4aaf-8824-1475999dfc98"&gt;
可当项目越来越复杂，用户还想使用第三方应用，那么不能将账号密码交给第三方吧，同时，尽管通过账号密码检测了是有效用户，那么是否就能够访问资源、使用资源呢，这也未必。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220226/224741745_f9f9f0bb-8b9b-4352-9b2f-b0dc89f2d6a4.png" alt="224741745_f9f9f0bb-8b9b-4352-9b2f-b0dc89f2d6a4"&gt;
种种因素下，OAuth(Open Authorization)作为一个安全、开放且简易的标准，方便我们在项目中协调资源、控制授权，以在用户、第三方应用和用户资源三者间形成一个有效的控制机制，而无需将用户的账号密码提供给第三方应用，且还能控制第三方应用对资源的访问与使用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An &lt;strong&gt;open protocol&lt;/strong&gt; to allow &lt;strong&gt;secure authorization&lt;/strong&gt;in a &lt;strong&gt;simple&lt;/strong&gt; and &lt;strong&gt;standard&lt;/strong&gt; method from web, mobile and desktop applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="认证与授权"&gt;认证与授权&lt;/h2&gt;
&lt;p&gt;对于第三方应用想要访问被保护的资源，应当是使用授权访问，而这前提是要经过认证环节，毕竟认证成功，但不代表就有权限访问资源。OAuth更多的是侧重于如何实现授权来控制第三方应用能够访问资源。认证协议是基于其上的Open ID Connect。尽管OAuth的流程中存在认证部分内容，但不能说OAuth就是认证协议。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Authentication（认证） 是验证用户(是第三方应用的使用者)的身份的凭据（账号和密码），通过这个凭据，系统得以知道你就是你，也就是说系统存在你这个用户。所以，Authentication 被称为身份/用户验证。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authorization（授权） 发生在 Authentication（认证） 之后。其本身意思就很明了，它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定的权限才能访问。比如手机浏览器想要访问底层摄像头，需要经用户允许访问才能获得授权，以此才能获取到具体资源。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="oauth历史"&gt;OAuth历史&lt;/h2&gt;
&lt;p&gt;OAuth 的历史版本中有 OAuth 1.0 和 OAuth 2.0，其中 OAuth 1.0 在 2010 年发表为 &lt;a href="https://www.rfc-editor.org/rfc/rfc5849.html"&gt;RFC5849&lt;/a&gt;，OAuth 2.0 为 &lt;a href="https://www.rfc-editor.org/rfc/rfc6749.html"&gt;RFC6749&lt;/a&gt;，并且 OAuth 2.0 不向下兼容 OAuth 1.0，目前 OAuth 2.0 被广泛使用，因此这里不再对 OAuth 1.0 进行更多的描述。&lt;/p&gt;
&lt;h2 id="oauth的几种角色"&gt;OAuth的几种角色&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Resource Owner：资源所有者，拥有资源的人，简单说就是谁在操作浏览器/App/小程序..&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resource Server：资源服务器，资源拥有者拥有的资源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Client：第三方应用，从资源所有者处获取授权，然后从资源服务器获取资源当不考虑OAuth协议，我们常见便是如上三种，想要第三方应用可以使用资源服务器的资源，又得把自身的账号密码交付给第三方应用，带来不安全。如此再加入一个角色，以平衡控制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authorization Server：授权服务器，提供给资源所有者完成授权并确定允许给第三方应用可以使用的资源范围与操作。由资源服务器方负责实现。如此一来，资源所有者将账号密码提供给授权服务器来完成验证，验证有效由授权服务器来负责提供能够代表身份的标识给第三方应用，这样一来降低了账号密码泄露的可能。&lt;/p&gt;</description></item><item><title>SpecFlow&amp;环境搭建</title><link>/blog/2022-01-23-specflow-basicusage/</link><pubDate>Sun, 23 Jan 2022 10:00:00 +0800</pubDate><guid>/blog/2022-01-23-specflow-basicusage/</guid><description>&lt;h2 id="bdd"&gt;BDD&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;B&lt;/strong&gt;ehavior &lt;strong&gt;D&lt;/strong&gt;riven &lt;strong&gt;D&lt;/strong&gt;evelopment，即行为驱动开发，是敏捷开发技术之一，通过&lt;strong&gt;自然语言&lt;/strong&gt;定义系统行为，以功能使用者的角度，编写需求场景，且这些行为描述可以直接形成需求文档，同时也是测试标准。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;BDD 的目的不是自动化测试，但是 BDD 可以有效指导自动化测试，基于 BDD 的自动化测试相当于维护了一份需求&lt;strong&gt;活文档&lt;/strong&gt;，对项目需求的维护和管理非常有价值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="specflow"&gt;SpecFlow&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://specflow.org/"&gt;SpecFlow&lt;/a&gt;是一个测试框架，支持BDD的指导思想。SpecFlow源于Cucumber，概念和用途几乎一样，可以理解为SpecFlow是.Net平台下的Cucumber。基于&lt;a href="https://cucumber.io/docs/gherkin/reference"&gt;Gherikin语法&lt;/a&gt;来定义用户场景。&lt;/p&gt;
&lt;h2 id="安装扩展插件"&gt;安装扩展插件&lt;/h2&gt;
&lt;p&gt;打开Visual Studio 2022，顶部菜单扩展-&amp;gt;管理扩展，安装Specflow插件&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224817538_f9567cb2-41d7-4f77-9656-63fbee5fb8dc.png" alt="224817538_f9567cb2-41d7-4f77-9656-63fbee5fb8dc"&gt;
下载完毕，关闭VS，插件将提示被安装&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224819451_4482754b-ae5f-4d00-a820-435a40e9e34d.png" alt="224819451_4482754b-ae5f-4d00-a820-435a40e9e34d"&gt;
安装完毕&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224820919_882b6625-dc85-4683-8a0c-959468f90366.png" alt="224820919_882b6625-dc85-4683-8a0c-959468f90366"&gt;&lt;/p&gt;
&lt;h2 id="创建测试项目"&gt;创建测试项目&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;新建空白解决方案&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224822168_9fdf9c65-bdfb-4213-a59f-294d86123c6f.png" alt="224822168_9fdf9c65-bdfb-4213-a59f-294d86123c6f"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建一个待测类库&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224824289_00c77637-c74f-44c0-889b-cc97cd74b489.png" alt="224824289_00c77637-c74f-44c0-889b-cc97cd74b489"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建一个SpecFlow测试项目&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224826743_09c78d54-2fed-44c2-b540-4d8ae2c3aa86.png" alt="224826743_09c78d54-2fed-44c2-b540-4d8ae2c3aa86"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在选择Test Framework时提供了四种选项，可依据实际使用选用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;NUnit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;xUnit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MSTest&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SpecFlow+Runner 该方式需要跳转到微软账号获取认证，否则无法使用模板中已附带了相关的Nuget包，如上我选择NUnit&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;Microsoft.NET.Test.Sdk&amp;#34; Version=&amp;#34;17.0.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;SpecFlow.Plus.LivingDocPlugin&amp;#34; Version=&amp;#34;3.9.57&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;SpecFlow.NUnit&amp;#34; Version=&amp;#34;3.9.40&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;nunit&amp;#34; Version=&amp;#34;3.13.2&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;NUnit3TestAdapter&amp;#34; Version=&amp;#34;4.1.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &amp;lt;PackageReference Include=&amp;#34;FluentAssertions&amp;#34; Version=&amp;#34;6.2.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/ItemGroup&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建好的SpecFlow默认带了一个Demo
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224829177_780ce66b-3996-4480-8ba1-9c2d2cb82d15.png" alt="224829177_780ce66b-3996-4480-8ba1-9c2d2cb82d15"&gt;
注意：默认创建的SpecFlow使用C#10，本地使用的C#9，有些功能不能使用，因此我将全局的命名空间及可空功能都关闭了，更改如下部分，删除即可&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224830492_a2557402-2ef0-4690-bdb0-ff999e64a2e3.png" alt="224830492_a2557402-2ef0-4690-bdb0-ff999e64a2e3"&gt;
在ImplicitUsings中的全局命名空间，Copy到其他类中，删除ImplicitUsings.cs文件&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20220123/224831949_2a22f0e7-d1b5-47eb-879a-0a604229f6ea.png" alt="224831949_2a22f0e7-d1b5-47eb-879a-0a604229f6ea"&gt;
为保持结构简洁，先删除Drivers和Support文件夹&lt;/p&gt;</description></item><item><title>AspNet Core&amp;Agile Config配置中心</title><link>/blog/2021-12-27-aspnetcore-agileconfig-basicusage/</link><pubDate>Mon, 27 Dec 2021 10:00:00 +0800</pubDate><guid>/blog/2021-12-27-aspnetcore-agileconfig-basicusage/</guid><description>&lt;p&gt;当服务逐渐的增多，对各服务的配置管理愈加重要，轻量级的配置中心，入手或是搭建都简单许多，基于.net core开发的轻量级配置中心AgileConfig，功能强大，上手简单。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dotnetcore/AgileConfig"&gt;https://github.com/dotnetcore/AgileConfig&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="agileconfig架构图"&gt;AgileConfig架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211227/225420213_b5216ea3-e01b-41ce-87fa-d135c4f94674.png" alt="225420213_b5216ea3-e01b-41ce-87fa-d135c4f94674"&gt;&lt;/p&gt;
&lt;h2 id="agileconfig配置端搭建"&gt;AgileConfig配置端搭建&lt;/h2&gt;
&lt;p&gt;1、新建一个文件夹用来作为配置存储&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir agileconfig
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;2、新开数据库&amp;amp;配置账户名
3、下载镜像&amp;amp;创建容器&lt;/p&gt;
&lt;p&gt;AgileConfig支持sqlserver，mysql，sqlite, PostgreSql，Oracle 五种数据库。依照实际使用的数据库类型选择,此处我这使用mysql。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo docker run \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name StarCityAgileConfig \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e TZ=Asia/Shanghai \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e adminConsole=true \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e db:provider=mysql \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e db:conn=&amp;#34;Server=xxx; Database=xxx;Port=xxx;charset=utf8;uid=xxx;pwd=xxx;&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -p 9527:5000 \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v /agileconfig:/app/db \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kklldog/agile_config:latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;name：容器名，给定个容器名。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TZ：指定时区。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;adminConsole：配置程序是否使用管理控制台。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果为true则启用控制台功能，访问该实例会出现管理界面。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每个实例都可以选择使用管理界面，共用一套数据源只是呈现端口不同&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;默认账号为admin，首次登录需要设置密码，设置后多个管理界面都可以通用&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;db:provider：配置程序的数据库类型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;目前程序支持：sqlite，mysql，sqlserver，npgsql, oracle 五种数据库。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;按照项目中允许的数据库使用即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;首个节点启动后会创建数据表(相当好~)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;db:env:{env}:provider，可以指定特定环境下使用某个数据库，如db:env:PROD.provider=sqlserver， db:env:DEVELOPMENT.provider=mysql&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;db:conn：配置数据库连接串。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;按照不同的数据库类型设置不同的数据库连接字符串。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据库使用第二步创建的库。&lt;/p&gt;</description></item><item><title>MinIO设置永久访问链接</title><link>/blog/2021-11-25-minio-permanentaccesslink/</link><pubDate>Thu, 25 Nov 2021 10:00:00 +0800</pubDate><guid>/blog/2021-11-25-minio-permanentaccesslink/</guid><description>&lt;h2 id="minio-介绍"&gt;MinIO 介绍&lt;/h2&gt;
&lt;p&gt;MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口，非常适合于存储大容量非结构化的数据，例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等，而一个对象文件可以是任意大小，从几 kb 到最大 5T 不等。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.min.io/cn/"&gt;https://docs.min.io/cn/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="有效期限制"&gt;有效期限制&lt;/h2&gt;
&lt;p&gt;文件上传到minio后，要进行分享时，从管理界面点击分享只能生成最多7 天有效期的链接。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211125/225502503_74aae5ee-5ef4-4d3e-8aa1-3fc22c463044.png" alt="225502503_74aae5ee-5ef4-4d3e-8aa1-3fc22c463044"&gt;
或是使用sdk通过代码调用minio的Api的请求参数也是最大7天有效期。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211125/225504091_c3bdd60c-eb6d-477e-9fa6-90a14474f712.png" alt="225504091_c3bdd60c-eb6d-477e-9fa6-90a14474f712"&gt;
超过期限再次访问便看不到了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211125/225505791_dc154700-dfea-41f0-a4d8-1f68bdbce8ad.png" alt="225505791_dc154700-dfea-41f0-a4d8-1f68bdbce8ad"&gt;
有些资源是需要一直可以访问(公共资源)，需要对存储空间(Bucket)进行一番设置方可。&lt;/p&gt;
&lt;h2 id="bucket访问策略"&gt;Bucket访问策略&lt;/h2&gt;
&lt;p&gt;存储空间(Bucket)默认策略有如下几种&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Read Only - download&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write Only - upload&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Read and Write - public&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不设置 - none&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从管理界面新建一个存储空间，然后查看策略，默认是为空的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211125/225507178_5018275a-e20c-4ef3-9532-411023042305.png" alt="225507178_5018275a-e20c-4ef3-9532-411023042305"&gt;
对存储空间(Bucket)的策略更改，可通过管理界面或是命令行形式更改目标桶的访问策略，当然了本质上最终都是对minio server的设置。&lt;/p&gt;
&lt;h3 id="解决方案"&gt;解决方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;minio server管理界面直接操作修改&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;minio相关的各语言SDK使用Api请求修改&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;minio提供的客户端修改&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="使用效果"&gt;使用效果&lt;/h3&gt;
&lt;p&gt;当对存储空间(Bucket)按照如上三种之一设置访问策略后，直接通过如下格式访问即可&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;域名或ip:端口/桶名称/[文件前缀]/文件名
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;与直接点击分享链接相比，后缀那节加密算法和凭证参数就不用了
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211125/225508876_5fde636a-d16e-488e-91a5-93b2c075c3fb.png" alt="225508876_5fde636a-d16e-488e-91a5-93b2c075c3fb"&gt;&lt;/p&gt;
&lt;h2 id="方案一管理界面"&gt;方案一：管理界面&lt;/h2&gt;
&lt;p&gt;新增一条策略，设置匹配前缀，再选择&lt;strong&gt;Read Only&lt;/strong&gt; 或 &lt;strong&gt;Read and Write&lt;/strong&gt;后点击添加即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当填写前缀 * 默认该存储空间(Bucket)下所有文件都可以访问&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当填写特殊前缀对应该存储空间(Bucket)下特定这个前缀开头文件可以访问如此处设置以下d开头的前缀，那么便只有d开头的文件可以公开访问。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211125/225510987_3c176cc7-5a2f-4091-9024-682d15fad790.png" alt="225510987_3c176cc7-5a2f-4091-9024-682d15fad790"&gt;
同时访问day.png和autumn.jpg，d前缀访问成功，其他跳转到文件目录下&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211125/225512236_d301061b-423c-4089-b7b2-b72e01e0c578.png" alt="225512236_d301061b-423c-4089-b7b2-b72e01e0c578"&gt;&lt;/p&gt;
&lt;h2 id="方案二sdk请求api"&gt;方案二：SDK请求Api&lt;/h2&gt;
&lt;p&gt;此处以.Net为主，安装MinIO包，快速创建一个Bucket并设置下访问策略。&lt;/p&gt;</description></item><item><title>使用Synergy一套键鼠控制多台电脑</title><link>/blog/2021-10-17-synergy-multiplecomputers/</link><pubDate>Sun, 17 Oct 2021 10:00:00 +0800</pubDate><guid>/blog/2021-10-17-synergy-multiplecomputers/</guid><description>&lt;p&gt;苦恼于不想买个无线鼠标操控两台笔记本，也不习惯用向日葵在两个电脑里切来切去，后来找到了这款软件，超方便解决我的需求。这篇文章通过使用跨平台Synergyc软件，实现一套键鼠同时操控两台电脑，条件是两台电脑连接在同一个局域网下（有线和无线皆可）。&lt;/p&gt;
&lt;h2 id="synergyc介绍"&gt;Synergyc介绍&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://symless.com/synergy"&gt;Synergy&lt;/a&gt;是一款能够让使用者仅用一套键鼠，就同时操控多部计算机的免费工具软件。可以在多台电脑之间共享鼠标、键盘、剪贴板。跨 Win、Linux、Mac。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/symless/synergy-core"&gt;https://github.com/symless/synergy-core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;注：使用过程中，用起来是很方便的，但是由于Synergy开始转向收费了，下载仅是试用版，但它的分支Barrier保留了现有功能，也提供了更多功能，保持免费。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/debauchee/barrier"&gt;https://github.com/debauchee/barrier&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Synergyc分为两个部分，Server端和Client端，Server作为主机，Client进行连接，达到使用主机的键鼠来控制从机的功能。这里举的例子是Window和linux，操作系统分别为Win10和Deepin。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225542161_bb1f121c-781e-4c5f-8ed3-9cf6e7b12344.png" alt="225542161_bb1f121c-781e-4c5f-8ed3-9cf6e7b12344"&gt;&lt;/p&gt;
&lt;h2 id="windows端"&gt;Windows端&lt;/h2&gt;
&lt;p&gt;下载相应Windows版本，执行安装&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.brahma.world/synergy-stable-builds/"&gt;https://www.brahma.world/synergy-stable-builds/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225543374_6e7fae3c-f9f7-47a9-a240-7f48a0287435.png" alt="225543374_6e7fae3c-f9f7-47a9-a240-7f48a0287435"&gt;
安装完毕后，进入程序，配置语言，注册码（直接略过了)。&lt;/p&gt;
&lt;h3 id="配置服务端"&gt;配置服务端&lt;/h3&gt;
&lt;p&gt;此处我将常用的电脑作为服务端，执行配置，选择Server-&amp;gt;设置服务端-&amp;gt;拖动顶部电脑图标，按照实际使用的另一台或是几台电脑摆放位置，这是鼠标移出主屏的位置。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225544701_ecf58e31-b6e7-439a-9532-01759ffc0941.png" alt="225544701_ecf58e31-b6e7-439a-9532-01759ffc0941"&gt;
点击新拖动进来的电脑图标，弹框内设置该客户端名称（客户端安装synergy时，便是用到的这个名称，所以保持一致）&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225546007_26cccf76-b2db-49bc-b058-a6c2fdae1673.png" alt="225546007_26cccf76-b2db-49bc-b058-a6c2fdae1673"&gt;
设置完毕后，点击底部开始按钮&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225547386_8c62b8e2-182e-4667-9928-70672e29576d.png" alt="225547386_8c62b8e2-182e-4667-9928-70672e29576d"&gt;
完毕后服务端Synergy便正常运行了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225549031_a8a5e52b-2240-4cf4-9007-e2c8a1dd87e3.png" alt="225549031_a8a5e52b-2240-4cf4-9007-e2c8a1dd87e3"&gt;&lt;/p&gt;
&lt;h2 id="deepin端"&gt;Deepin端&lt;/h2&gt;
&lt;p&gt;执行命令安装即可，如果如下命令提示找不到相应的synergyc，请更新apt-get或者更换apt-get源。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo apt-get install synergyc&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Deepin内提示我可以使用org.sys.synergyc，如此可简单了，输入命令按照提示Y就行了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo apt-get install org.sys.synergyc&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="配置客户端"&gt;配置客户端&lt;/h3&gt;
&lt;p&gt;打开程序，选择语言，开始客户端配置，选择客户端，输入服务端IP地址，也就是上一步常用电脑的ip地址，完毕点击开始按钮即可。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225550290_4f29e6a0-57ba-4352-aa61-17acdb9b7d58.png" alt="225550290_4f29e6a0-57ba-4352-aa61-17acdb9b7d58"&gt;
客户端Synergy便在运行了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20211017/225552203_d0a95190-1c0d-46fe-a873-44eb981f5bba.png" alt="225552203_d0a95190-1c0d-46fe-a873-44eb981f5bba"&gt;
此时，按照配置客户图标时的方向移出主屏，便可以滑动到客户端，一套键鼠控制多台电脑。&lt;/p&gt;
&lt;h2 id="参考链接"&gt;参考链接&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://help.ubuntu.com/community/SynergyHowto"&gt;https://help.ubuntu.com/community/SynergyHowto&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2021-10-17，望技术有成后能回来看见自己的脚步。&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>Docker&amp;Nginx 设置SSL</title><link>/blog/2021-09-20-docker-nginx-ssl/</link><pubDate>Mon, 20 Sep 2021 10:00:00 +0800</pubDate><guid>/blog/2021-09-20-docker-nginx-ssl/</guid><description>&lt;h2 id="ssl证书下载"&gt;SSL证书下载&lt;/h2&gt;
&lt;p&gt;此处从腾讯云处申请了个免费证书，申请流程很快，几分钟便搞定了。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://console.cloud.tencent.com/ssl"&gt;https://console.cloud.tencent.com/ssl&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在之前设置映射的路径处，创建一个文件夹用来保存证书文件&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir /docker/nginx/cert
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后将下载的证书上传并挪动到这个cert文件夹中(此处使用winscp工具直接挪动了)，稍后用于Nginx容器使用。&lt;/p&gt;
&lt;h2 id="创建容器配置"&gt;创建容器配置&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;创建一个新的容器并配置数据卷，注意端口号增加了443，且增加了证书的数据卷。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;--name nginxweb \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-p 8080:80 \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-p 443:443 \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-v /docker/nginx/nginx.conf:/etc/nginx/nginx.conf \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-v /docker/nginx/conf/conf.d:/etc/nginx/conf.d \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-v /docker/nginx/www:/usr/share/nginx \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-v /docker/nginx/logs:/var/log \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-v /docker/nginx/cert:/etc/nginx/cert \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-d \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;更改Nginx.Conf&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;更改nginx.conf文件配置，增加一个server的块，增加对443的侦听和证书位置的配置，此处的位置是容器内的位置，也就是创建容器时指定数据卷那里的位置加上证书信息。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;server {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen 443;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server_name aspnetcore.online;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl on;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_certificate /etc/nginx/cert/1_aspnetcore.online_bundle.crt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_certificate_key /etc/nginx/cert/2_aspnetcore.online.key;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_session_timeout 5m;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssl_prefer_server_ciphers on;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; location / {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_pass http://119.91.109.91:80/;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_redirect default;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;再次请求，将http替换为https，带上域名重新访问，重定向到目标页中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210920/225618473_6d5b712f-d394-45c4-b7bd-e1254b45538a.png" alt="225618473_6d5b712f-d394-45c4-b7bd-e1254b45538a"&gt;
这样一来，反向代理和Https都设置成功了。&lt;/p&gt;</description></item><item><title>Docker&amp;Nginx 安装与配置</title><link>/blog/2021-09-19-docker-nginx-basicusage/</link><pubDate>Sun, 19 Sep 2021 10:00:00 +0800</pubDate><guid>/blog/2021-09-19-docker-nginx-basicusage/</guid><description>&lt;h2 id="创建nginx容器"&gt;创建Nginx容器&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;下载nginx镜像&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker pull nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;创建nginx容器&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --name nginxweb -p 8080:80 -d nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;这样一来nginx就创建好了，直接访问ip:8080就可以看到nginx安装好的页面&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210919/225654456_92d4c0d9-c758-48b5-9399-0e99b44e321a.png" alt="225654456_92d4c0d9-c758-48b5-9399-0e99b44e321a"&gt;
4、改变配置实现更多功能，这种情况下得进入容器更改配置信息&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it nginxweb /bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置文件在容器内/etc/nginx/路径下的nginx.conf和conf.d文件夹内的default.conf文件
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210919/225655831_6d01b29e-2e0d-44d2-87b2-bcdbb5e678fe.png" alt="225655831_6d01b29e-2e0d-44d2-87b2-bcdbb5e678fe"&gt;
但是默认使用vi或vim去修改是没有用的，因为没得，得去安装，这种情况下就很麻烦，更常用的是做个数据卷，将容器内文件映射到容器外，这样改变主机文件就改变了容器内文件，方便些。&lt;/p&gt;
&lt;p&gt;如果不嫌麻烦在容器内改完配置文件后，重启容器&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker restart nginxweb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样不是很方便，先删除容器。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker stop nginxweb
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker rm nginxweb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="挂载配置文件"&gt;挂载配置文件&lt;/h2&gt;
&lt;p&gt;在主机上设置配置文件，以方便主机直接修改nginx容器内的配置信息。&lt;/p&gt;
&lt;h3 id="建立映射文件夹"&gt;建立映射文件夹&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;先建立几个文件夹用于存放数据卷的&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /docker/nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /docker/nginx/conf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /docker/nginx/www
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /docker/nginx/logs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这几个文件对应着nginx内的配置文件，如此下来，重新创建nginx容器并指定数据卷，这样当我们修改本地文件时候，也就是nginx容器内部文件修改了。
2. 创建一个临时容器用来复制配置信息&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --name nginxweb -p 8080:80 -d nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;先将临时容器内已有的配置信息拷贝到主机文件中&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker cp nginxweb:/etc/nginx/nginx.conf /docker/nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker cp nginxweb:/etc/nginx/conf.d /docker/nginx/conf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker cp nginxweb:/usr/share/nginx/html /docker/nginx/www
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker cp nginxweb:/var/log/nginx /docker/nginx/logs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;详细描述&lt;/p&gt;</description></item><item><title>精简ABP的模块依赖</title><link>/blog/2021-08-19-abp-module-dependency/</link><pubDate>Thu, 19 Aug 2021 10:00:00 +0800</pubDate><guid>/blog/2021-08-19-abp-module-dependency/</guid><description>&lt;p&gt;ABP的模块非常方便我们扩展自己的或使用ABP提供的模块功能，对于ABP自身提供的模块间的依赖关系想一探究竟，并且试着把不必要的模块拆掉，找到那部分核心模块。本次使用的是&lt;a href="https://aspnetboilerplate.com/Pages/Documents/Introduction"&gt;AspNetBoilerplate&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="源码下载"&gt;源码下载&lt;/h2&gt;
&lt;p&gt;从Github下载&lt;a href="https://github.com/aspnetboilerplate/aspnetboilerplate"&gt;ABP源码&lt;/a&gt;后，进入src文件夹，提供的大部分模块都在此中。很多工具也经ABP封装，个人认为是方便了集成使用且融入到ABP这个框架。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210819/195156446_89264eac-83be-4e26-a874-7a54286cf606.png" alt="195156446_89264eac-83be-4e26-a874-7a54286cf606"&gt;
src文件夹中的类库有几种类型如.net standard(为了兼容.net framework)、.net 5 、.net framework的。&lt;/p&gt;
&lt;p&gt;我的想法是最小的模块引用，因此按照如下两个条件移除&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;移除.net framework的，只专注于.net core所需要的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移除Zero模块。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="精简模块"&gt;精简模块&lt;/h2&gt;
&lt;p&gt;整理后也就是剩下这部分，这样看仍然感觉有很多模块，但对于其中大部分来讲更多的是将一些工具封装了下，比如从SignalR，AutoMapper，Log4Net，Dapper，EFCore等等。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210819/195158600_c71a09eb-009c-45a6-875f-4eb26f966a5a.png" alt="195158600_c71a09eb-009c-45a6-875f-4eb26f966a5a"&gt;
通过整理这部分类库间的依赖关系，(此图中去掉了Dapper、NHibernate、MemoryDB和MongoDb)，如此一来分类便清晰许多，一眼便可以识别出Abp这一核心模块。哪些模块的提供的功能是什么，这样也方便查看。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210819/195200241_1cf1d675-b5ef-42b4-b673-e13d2103d1d1.png" alt="195200241_1cf1d675-b5ef-42b4-b673-e13d2103d1d1"&gt;
我们再来建立一个项目来直接对该部分类库引用，而不是走Nuget包的方式。先依赖需要的最基本的模块，比如Abp、Abp.WebCommon和Abp.AspNetCore等几个模块，能够保障最基本的请求功能，甚至于读取数据库的ORM工具都不纳入进来。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210819/195201892_9088e64c-29ec-4fa4-ac3d-0dcff8a2b012.png" alt="195201892_9088e64c-29ec-4fa4-ac3d-0dcff8a2b012"&gt;&lt;/p&gt;
&lt;h2 id="依赖核心模块"&gt;依赖核心模块&lt;/h2&gt;
&lt;p&gt;此处将Log4Net作为需要的模块纳入进来，因为Abp中部分地方记录日志时候需要相应的实现类，当然也可以使用其他日志组件如NLog等，但需要实现Abp中封装好的抽象类，可参考如下链接&lt;a href="https://www.cnblogs.com/donaldtdz/p/8213440.html"&gt;：https://www.cnblogs.com/donaldtdz/p/8213440.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;新建一个空白解决方案AbpIntegrationDemo.sln&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;建立一个module文件夹用于放置上述最基本模块。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;建立一个src文件夹中用于搭建分层结构。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210819/195203281_251522da-31e1-42c5-be3e-e2eb51f85e6f.png" alt="195203281_251522da-31e1-42c5-be3e-e2eb51f85e6f"&gt;
对于src中的每一层所依赖的项目按照层次关系及依赖关系，其中核心层Domain依赖Abp模块，Application和Infrastructure都依赖核心层也就都对Abp模块有了依赖关系了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210819/195204436_8fec0b30-85af-49f4-8573-f316fbfd5eac.png" alt="195204436_8fec0b30-85af-49f4-8573-f316fbfd5eac"&gt;
如此一来，最为基本的依赖便完成了，各层内增加相应的Module，按照Abp模块化构建方式将这些模块贯穿起来。比如AbpIntegrationDemo.Admin，将依赖的模块，需要注入的、配置的东西加入进来，此处部分直接从Abp start up中搬过来的，还可以再精简一下，部分内容目前是没有用到的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[DependsOn(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; typeof(AbpIntegrationApplicationModule),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; typeof(AbpIntegrationInfrastructureModule),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; typeof(AbpAspNetCoreModule)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class AbpIntegrationAdminModule : AbpModule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private readonly IWebHostEnvironment _env;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private readonly IConfiguration _appConfiguration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public AbpIntegrationAdminModule(IWebHostEnvironment env, IConfiguration appConfiguration)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _env = env;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _appConfiguration = appConfiguration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void PreInitialize()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Configuration.DefaultNameOrConnectionString = _appConfiguration.GetConnectionString(AbpIntegrationDomainConsts.ConnectionStringName);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Configuration.Modules.AbpWebCommon().SendAllExceptionsToClients = false;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void Initialize()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IocManager.RegisterAssemblyByConvention(typeof(AbpIntegrationAdminModule).GetAssembly());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void PostInitialize()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IocManager.Resolve&amp;lt;applicationpartmanager&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddApplicationPartsIfNotAddedBefore(typeof(AbpIntegrationAdminModule).Assembly);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void Shutdown()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; base.Shutdown();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在此基础上，按照需要的模块，如EF Core、Redis、Hangfire、AutoMapper等等，也只需要引用进来了，至少到现有地步，对于Abp模块的依赖及最为核心的模块应该是清楚了，而对于看到很多人说Abp重的说法，个人感觉是不是看到依赖的模块太多而有种复杂的感觉，实则有很多是工具模块，即使不使用Abp，实际开发中也会使用进来。
&lt;a href="https://gitee.com/530521314/Partner.TreasureChest/tree/master/AbpIntegrateDemo"&gt;https://gitee.com/530521314/Partner.TreasureChest/tree/master/AbpIntegrateDemo&lt;/a&gt;&lt;/p&gt;</description></item><item><title>AspNetCore&amp;MassTransit Courier实现分布式事务</title><link>/blog/2021-07-19-aspnetcore-masstransitcourier-distributedtransactions/</link><pubDate>Mon, 19 Jul 2021 10:00:00 +0800</pubDate><guid>/blog/2021-07-19-aspnetcore-masstransitcourier-distributedtransactions/</guid><description>&lt;p&gt;在之前的一篇博文中，CAP框架可以方便我们实现非实时、异步场景下的最终一致性，而有些用例总是无法避免的需要在实时、同步场景下进行，可以借助Saga事务来解决这一困扰。在一些博文和仓库中也搜寻到了.Net下实现Saga模式的解决方案&lt;a href="http://masstransit-project.com/"&gt;MassTransit&lt;/a&gt;，这就省得自己再造轮子了。&lt;/p&gt;
&lt;h2 id="分布式事务"&gt;分布式事务&lt;/h2&gt;
&lt;p&gt;分布式系统中，分布式事务是一个不能避免的问题，如何保证不同节点间的数据一致性。举个常见的例子，下订单、减库存、扣余额，三者在单个节点时，可以借助本地事务，实现要么成功要么失败。而当三者处于不同节点时，又参杂了如网络环境、节点自身环境、服务环境等各种因素，使得三个节点想要实现要么成功、要么失败就增加了许多困难。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210719/225842035_2eb9a276-e771-41bf-841b-3c2e9fb0f886.png" alt="225842035_2eb9a276-e771-41bf-841b-3c2e9fb0f886"&gt;
CAP理论和BASE理论很好的诠释了这一问题，也有了许多的解决分布式事务的方案，如2PC、3PC、TCC、本地消息表、Saga等一系列解决方案，面对不同场景、不同要求等可选择不同的解决方案。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;数据一致性&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;容错性&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;复杂性&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;性能&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;维护成本&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;2PC&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;强&lt;/td&gt;
&lt;td style="text-align: left"&gt;低&lt;/td&gt;
&lt;td style="text-align: left"&gt;中&lt;/td&gt;
&lt;td style="text-align: left"&gt;低&lt;/td&gt;
&lt;td style="text-align: left"&gt;低&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;3PC&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;强&lt;/td&gt;
&lt;td style="text-align: left"&gt;低&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;低&lt;/td&gt;
&lt;td style="text-align: left"&gt;中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;TCC&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;弱&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;中&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;本地消息表&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;弱&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;低&lt;/td&gt;
&lt;td style="text-align: left"&gt;中&lt;/td&gt;
&lt;td style="text-align: left"&gt;中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;MQ事务&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;弱&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;低&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Saga事务&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;弱&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;td style="text-align: left"&gt;中&lt;/td&gt;
&lt;td style="text-align: left"&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在之前提到过一个基于本地消息表的CAP框架，借助最终一致性很方便的解决了异步非实时请求下的分布式事务，而对于大部分场景虽然可以直接或者妥协方式使用着异步非实时，如同步实时场景的下订单且减库存变更到异步非实时场景的下订单后发事件减库存，但是总有那么一些场景，不得不去考虑同步实时请求下的分布式事务。&lt;/p&gt;
&lt;h2 id="saga模式"&gt;Saga模式&lt;/h2&gt;
&lt;p&gt;Saga模式又叫做长时间运行事务（Long-running-transaction）, 由普林斯顿大学的 Hector Garcia-Molina和Kenneth Salem 1987年发表的论文&lt;a href="https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf"&gt;《Sagas》&lt;/a&gt;。核心思想是将长事务拆分为多个本地短事务，通过保证所有短事务的成功或失败来决定整体的成功或失败，由Saga事务协调器协调管理，所有节点执行成功，则成功，如有节点失败，则反向执行前置节点的补偿操作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;每个Saga事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每个Ti 都有对应的幂等补偿动作Ci，补偿动作用于撤销Ti造成的结果。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="执行过程"&gt;执行过程&lt;/h3&gt;
&lt;p&gt;当正常执行时，依照T1、T2、T3三个短事务正常执行下去，直到最后一个Tn事务执行完毕，宣告整个事务的成功。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210719/225843584_ef4e0249-d4e7-4268-9352-f1b9dc0eeb50.png" alt="225843584_ef4e0249-d4e7-4268-9352-f1b9dc0eeb50"&gt;
而当执行到某个Tj出现故障时，则反向补偿之前的Tj-1..T1,每个对应的补偿操作Cj-1&amp;hellip;C1，其中Tj事务由于在执行阶段就已失败，所以Tj对应的补偿动作Cj不需要执行，即也确定了最后一个Tn事务可以不设置补偿动作Cn。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210719/225845139_83fa59ef-8560-4a9d-952e-8259be7e9763.png" alt="225845139_83fa59ef-8560-4a9d-952e-8259be7e9763"&gt;&lt;/p&gt;
&lt;h3 id="恢复策略"&gt;恢复策略&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;向前恢复(forward recovery)：对于Ti事务的执行，部分场景下可能因为数据库的连接、网络的波动等导致短暂的失败，对Ti事务重试执行，以确保整个事务的执行，如执行T1, T2, T3，当执行T3失败时，不直接宣告失败，对T3执行重试以排除部分不稳定因素，如在若干次重试无效后，再考虑向后恢复。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210719/225846426_39b5656c-6218-4474-b67e-41a9aea38ae6.png" alt="225846426_39b5656c-6218-4474-b67e-41a9aea38ae6"&gt;&lt;/p&gt;</description></item><item><title>MassTransit中Request&amp;Response基本使用</title><link>/blog/2021-06-29-masstransit-request-response-basicusage/</link><pubDate>Tue, 29 Jun 2021 10:00:00 +0800</pubDate><guid>/blog/2021-06-29-masstransit-request-response-basicusage/</guid><description>&lt;p&gt;MassTransit 是一个自由、开源、轻量级的消息总线基于.Net框架, 用于创建分布式应用程序。方便搭建基于消息的松耦合异步通信的应用程序和服务。MassTransit 在现有消息传输上提供了一组广泛的功能, 从而使开发人员能够友好地使用基于消息的会话模式异步连接服务。基于消息的通信是实现面向服务的体系结构的可靠和可扩展的方式。&lt;/p&gt;
&lt;p&gt;官网地址：&lt;a href="http://masstransit-project.com/"&gt;http://masstransit-project.com/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="发布订阅模式"&gt;发布订阅模式&lt;/h3&gt;
&lt;p&gt;这种场景十分常见，发送一个消息(或事件)到消息队列中，有一个或是多个订阅方对预期的消息接收处理。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210629/195247327_32a32170-9690-4de4-90ca-f717f13eeebc.png" alt="195247327_32a32170-9690-4de4-90ca-f717f13eeebc"&gt;
基于需要搭建了两个WebApi程序，用于模拟发送方和订阅方，其中的RabbitMQ已预先搭建好了，只在程序中引用包配置下即可。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PackageReference Include=&amp;#34;MassTransit&amp;#34; Version=&amp;#34;7.2.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PackageReference Include=&amp;#34;MassTransit.AspNetCore&amp;#34; Version=&amp;#34;7.2.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;PackageReference Include=&amp;#34;MassTransit.RabbitMQ&amp;#34; Version=&amp;#34;7.2.0&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="发布端配置"&gt;发布端配置&lt;/h4&gt;
&lt;p&gt;在Startup中增加MassTransit需要的服务及初始化配置。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对RabbitMQ的连接地址端口、虚拟主机、访问账号密码等系列配置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对发送方需要发送的消息初始化一个请求客户端，配置请求信息及推送到MQ的地址。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddMassTransit(x =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    x.UsingRabbitMq((context, cfg) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        cfg.Host(Configuration[&amp;#34;RabbitmqConfig:HostIP&amp;#34;], ushort.Parse(Configuration[&amp;#34;RabbitmqConfig:HostPort&amp;#34;]), Configuration[&amp;#34;RabbitmqConfig:VirtualHost&amp;#34;], h =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            h.Username(Configuration[&amp;#34;RabbitmqConfig:Username&amp;#34;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            h.Password(Configuration[&amp;#34;RabbitmqConfig:Password&amp;#34;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    x.AddRequestClient&amp;lt;ValueEntered&amp;gt;(new Uri(GetServiceAddress(&amp;#34;events-valueentered&amp;#34;)));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddMassTransitHostedService();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为了快速了解，使用Controller在Action中发起对MQ的消息推送&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[ApiController]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Route(&amp;#34;[controller]&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class ValueController : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    readonly IPublishEndpoint _publishEndpoint;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public ValueController(IPublishEndpoint publishEndpoint)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        _publishEndpoint = publishEndpoint;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    [HttpPost]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public async Task&amp;lt;ActionResult&amp;gt; Post(string value)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        await _publishEndpoint.Publish&amp;lt;ValueEntered&amp;gt;(new
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Value = value
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        return Ok();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="订阅端配置"&gt;订阅端配置&lt;/h4&gt;
&lt;p&gt;订阅端也创建一个WebApi应用，在Startup中增加MassTransit的服务，使用到的Nuget包和发布端一样。&lt;/p&gt;</description></item><item><title>Docker&amp;Portainer升级版本</title><link>/blog/2021-05-30-docker-portainer-upgrade/</link><pubDate>Sun, 30 May 2021 10:00:00 +0800</pubDate><guid>/blog/2021-05-30-docker-portainer-upgrade/</guid><description>&lt;h3 id="前言"&gt;前言&lt;/h3&gt;
&lt;p&gt;为了备考系分，好久没对服务器操作了，空闲了很长时间，之前看着 Portainer 一直提示更新也没有理会，现在解放了，开始升级一波。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210530/195342416_01797122-a90b-418c-b560-f4f8c23863c3.png" alt="195342416_01797122-a90b-418c-b560-f4f8c23863c3"&gt;&lt;/p&gt;
&lt;h3 id="当前环境"&gt;当前环境&lt;/h3&gt;
&lt;p&gt;系统：CentOS&lt;/p&gt;
&lt;p&gt;Portainer 原有版本：1.24.0&lt;/p&gt;
&lt;p&gt;在 Portainer 安装时候是指定了数据卷的，这样一来，更新 Portainer 只需要下载新的 Portainer 的镜像，删除原有容器即可，原先的记录信息都在数据卷中。&lt;/p&gt;
&lt;h3 id="部署方式"&gt;部署方式&lt;/h3&gt;
&lt;p&gt;先确定是直接用单个容器安装命令启的容器还是走的集群命令起的容器&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker service list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;比如我本地是用集群部署方式
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210530/195343728_2f97c482-dbec-44ab-a5d8-894e80da9dde.png" alt="195343728_2f97c482-dbec-44ab-a5d8-894e80da9dde"&gt;&lt;/p&gt;
&lt;h3 id="单机部署"&gt;单机部署&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;如直接使用镜像启动，则使用单个容器名关闭&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker stop portainer的容器名或容器Id
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;删除容器&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker rm portainer的容器名或容器id
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;可查看下容器确定下是否已经删除&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker ps -a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;删除镜像&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker rmi portainer的镜像名或镜像Id
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;如仍沿用单机部署方式，则直接拉取新版本镜像&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker pull portainer/portainer-ce
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210530/195345120_147751c6-98d5-4fe2-a4b4-2e2321b07dd4.png" alt="195345120_147751c6-98d5-4fe2-a4b4-2e2321b07dd4"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启动镜像，登录输入账号密码即可&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --restart=always --name portainer portainer/portainer-ce
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="集群部署"&gt;集群部署&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;如采用服务方式启动，则移除服务一并删除容器&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;页面上选中 portainer 相关的服务删除后弹框如上，一并将容器也删除。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210530/195346346_b8549273-eb82-4b9f-8d9d-6acdc201c01c.png" alt="195346346_b8549273-eb82-4b9f-8d9d-6acdc201c01c"&gt;
或是使用命令删除&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker service rm portainer的服务名或服务Id
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;可查看下容器和服务情况确定下是否已经删除 portainer&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker ps -a
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker service list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;删除镜像&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker rmi portainer的镜像名或镜像Id
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;如采用集群部署，则使用集群部署的命令(单机也可采用，方便使用到提供的 webhook 功能)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -L https://downloads.portainer.io/portainer-agent-stack.yml -o portainer-agent-stack.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker stack deploy -c portainer-agent-stack.yml portainer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;查看当前服务、镜像和容器，可以发现都存在了，容器直接启动&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210530/195347612_527112b3-05d5-4389-8ec2-624837272b00.png" alt="195347612_527112b3-05d5-4389-8ec2-624837272b00"&gt;&lt;/p&gt;</description></item><item><title>AspNet Core&amp;CAP实现分布式事务</title><link>/blog/2021-04-28-aspnetcore-cap-distributedtransactions/</link><pubDate>Wed, 28 Apr 2021 10:00:00 +0800</pubDate><guid>/blog/2021-04-28-aspnetcore-cap-distributedtransactions/</guid><description>&lt;p&gt;需要注意的是标题中的CAP不是指的CAP理论，而是园区大神&lt;a href="https://www.cnblogs.com/savorboard/?fileGuid=wwYhyjkgXDqG3TTY"&gt;杨晓东&lt;/a&gt;实现的框架，&lt;a href="https://github.com/dotnetcore/CAP?fileGuid=wwYhyjkgXDqG3TTY"&gt;CAP&lt;/a&gt;框架基于本地消息表用最终一致性实现分布式事务。&lt;/p&gt;
&lt;h2 id="本地消息表"&gt;本地消息表&lt;/h2&gt;
&lt;p&gt;首先我们考虑一个场景，在将用户信息更改后，需要发送一条消息到消息队列、缓存或是写入到其他库中。这个过程涉及到一个&lt;strong&gt;本地库与MQ、本地库与Cache或是本地库与其他库&lt;/strong&gt;两者之间的事务问题，不能用简单的数据库事务控制了。&lt;/p&gt;
&lt;p&gt;这种分布式事务下，常用的解决方案有2PC、3PC等强一致性保证的，也有TCC、Sagas模型、本地消息表、内嵌本地消息表的MQ等最终一致性保证的。&lt;/p&gt;
&lt;p&gt;而在很多异步场景下，允许系统存在短暂的不一致，只需达到最终一致，比起强一致性那种刚性事务，采用柔性事务，在很多场景下更有利于我们去实现。&lt;/p&gt;
&lt;h3 id="执行过程"&gt;执行过程&lt;/h3&gt;
&lt;p&gt;在使用CAP框架前，先熟悉下作为分布式事务解决方案之一的&lt;strong&gt;本地消息表&lt;/strong&gt;工作过程。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195434940_b38c257d-25f8-456b-8427-88ef4d1d0d9c.png" alt="195434940_b38c257d-25f8-456b-8427-88ef4d1d0d9c"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;消息发起方(如图左侧部分)和消息接收方(如图右侧部分)，先额外建一套消息表，用来记录及跟踪消息内容及状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当有请求到消息发起方时，处理完业务逻辑发布消息将业务数据和消息数据一同提交到本地表中，此时为本地事务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;本地事务没有问题后，将消息发送到MQ传递给消息消费方。如果消息发送失败，会进行重试发送。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息消费方，接收并处理消息，完成自己的业务逻辑，此时为消息消费方本地事务，如果本地事务完成，则更改接收消息的状态，更改本地，如果处理失败，那么可再次重试执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最终，左侧事务与右侧事务达到最终一致。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="cap框架"&gt;CAP框架&lt;/h2&gt;
&lt;p&gt;CAP是一个在分布式系统中（SOA，MicroService）实现事件总线及最终一致性（分布式事务）的一个开源的 C# 库，具有轻量级，高性能，易使用等特点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;具有 Event Bus 的所有功能，提供了更加简化的方式来处理EventBus中的发布/订阅。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;具有消息持久化的功能，当服务进行重启或者宕机时，可以保证消息的可靠性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基于&lt;strong&gt;本地消息表&lt;/strong&gt;实现了分布式事务中的最终一致性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195436709_3aa522ba-f971-448f-9408-c468bf056a3a.png" alt="195436709_3aa522ba-f971-448f-9408-c468bf056a3a"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集成了可视化页面方便观察消息状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195438242_9754574d-d69f-4304-bf0a-081f5403f8fa.png" alt="195438242_9754574d-d69f-4304-bf0a-081f5403f8fa"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提供了一系列Nuget包以选择需要的工具接入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195440006_7263d6c5-c3b3-4c15-a479-ec8eeb26afbf.png" alt="195440006_7263d6c5-c3b3-4c15-a479-ec8eeb26afbf"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;第一个包DotNetCore.CAP为必须要安装的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以依据消息队列的不同选择用RabbitMQ、Kafka或是AzureServiceBus等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;根据服务使用的数据库情况选择需要将本地消息表落库，可以选择SqlServer、MySql、PostgreSql、MongoDB等，或是直接使用内存存储，方便快速实践。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="场景案例"&gt;场景案例&lt;/h2&gt;
&lt;p&gt;依照EShopOnContainers中的一张图来实现一个例子，用户更新用户信息，将更新的部分通过事件发送到消息队列中，下游的购物车和订单服务侦听到消息，更改买家信息。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195441988_c2eefd6a-8e1e-4524-b7a0-196e6951d9e0.png" alt="195441988_c2eefd6a-8e1e-4524-b7a0-196e6951d9e0"&gt;
在此基础上行，设计三个上下文，并分别集成CAP,借助RabbitMQ作为消息队列，对于UserService、BasketService和OrderService,都直接使用了数据库（当然可以不仅限于数据库）。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195443938_333a1af7-c3ad-4a6c-beca-8e44b774a4f0.png" alt="195443938_333a1af7-c3ad-4a6c-beca-8e44b774a4f0"&gt;&lt;/p&gt;
&lt;h2 id="服务建立"&gt;服务建立&lt;/h2&gt;
&lt;h3 id="项目创建"&gt;项目创建&lt;/h3&gt;
&lt;p&gt;开始建立几个服务，新建空白解决方案，依次建立三个WebApi项目，并移除默认的控制器。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195445867_5caa599c-fdaf-4617-a467-02a6a1319ad1.png" alt="195445867_5caa599c-fdaf-4617-a467-02a6a1319ad1"&gt;
简单设计下，在三个服务中创建三个DbContext，对应三个独立的数据库。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;UserService中创建UserInfo实体及UserDbContext&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BasketService中创建Basket实体及BasketDbContext&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OrderService中创建Order实体及OrderDbContext&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="安装nuget包"&gt;安装Nuget包&lt;/h3&gt;
&lt;p&gt;在三个服务中均安装完如下选中的包，此次Demo中为方便快速实践，选择RabbitMQ作为消息队列，MySql作为数据库存储。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195447148_6c31e43f-d994-438f-b7a1-8863ae77bf80.png" alt="195447148_6c31e43f-d994-438f-b7a1-8863ae77bf80"&gt;
对于EFCore及MySql包，安装了如下几个包&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Microsoft.EntityFrameworkCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Microsoft.EntityFrameworkCore.Design
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Microsoft.EntityFrameworkCore.Tools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Pomelo.EntityFrameworkCore.MySql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意此处EFCore中MySql版本和CAP中MySql版本两者间依赖的MySqlConnector不一致会有点问题&lt;/p&gt;
&lt;h3 id="配置服务"&gt;配置服务&lt;/h3&gt;
&lt;p&gt;需要对CAP进行设置，比如使用的是什么数据库、什么消息队列及配置下消息队列参数，这一系列初始化设置在Startup.cs中配置好。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ConfigureService中配置DbContext和CAP服务&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195448554_22abd5ce-d655-49b5-8dbc-dc65231b6184.png" alt="195448554_22abd5ce-d655-49b5-8dbc-dc65231b6184"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure中CAP的引入中间件&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195450373_18d71cc1-da4c-4b40-85a9-5a910789330e.png" alt="195450373_18d71cc1-da4c-4b40-85a9-5a910789330e"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;利用EFCore的迁移命令生成下数据库迁移脚本，将DbContext内实体生成到数据库中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单个服务启动后，CAP组件会将内置表创建到数据库中。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195451556_b303b7f5-76b5-4ab6-94ef-4f3768e082d2.png" alt="195451556_b303b7f5-76b5-4ab6-94ef-4f3768e082d2"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;服务全部启动后，RabbitMQ Client会自动注册到RabbitMQ Server中同时创建好给定的Exchange(不给定则使用默认值)，存在订阅的服务则注册队列绑定到给定的Exchange下。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195452749_51f5f53c-38e4-4811-90e2-33dc648cd182.png" alt="195452749_51f5f53c-38e4-4811-90e2-33dc648cd182"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="发布事件"&gt;发布事件&lt;/h3&gt;
&lt;p&gt;在 UserService中UserController 中注入ICapPublisher，使用Patch接口更新一个Address，然后使用ICapPublisher发布一条消息。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210428/195454509_720d4d89-fbf3-4702-95c2-b7b054b5d914.png" alt="195454509_720d4d89-fbf3-4702-95c2-b7b054b5d914"&gt;&lt;/p&gt;</description></item><item><title>RabbitMQ介绍&amp;Docker安装</title><link>/blog/2021-03-28-rabbitmq-docker-basicusage/</link><pubDate>Sun, 28 Mar 2021 10:00:00 +0800</pubDate><guid>/blog/2021-03-28-rabbitmq-docker-basicusage/</guid><description>&lt;h2 id="mq介绍"&gt;MQ介绍&lt;/h2&gt;
&lt;h3 id="mq概念"&gt;MQ概念&lt;/h3&gt;
&lt;p&gt;MQ: Message Queue，译为消息队列，本质是个队列，具有先进先出特性，应存放的是消息，因此对其称呼为消息队列。&lt;/p&gt;
&lt;h3 id="mq作用"&gt;MQ作用&lt;/h3&gt;
&lt;h4 id="解耦利器"&gt;解耦利器&lt;/h4&gt;
&lt;p&gt;借助MQ，将多个服务彼此隔离，服务间只清楚MQ的存在，而不需要知道彼此的存在。&lt;/p&gt;
&lt;h4 id="异步"&gt;异步&lt;/h4&gt;
&lt;p&gt;如抢购场景，同步请求时，需要完成订单，减库存，付款一系列操作，实时性要求强，技术要求也更高，而在异步请求下，下了订单后，立马将请求返回，等待查询处理结果，当MQ另一侧的服务处理完毕后，才会知晓是否购买成功。此时对于下订单这块的服务，异步下可大幅提高可用性。&lt;/p&gt;
&lt;h4 id="削峰限流"&gt;削峰/限流&lt;/h4&gt;
&lt;p&gt;继续考虑如上场景，当抢购量大时，存放在MQ内的消息众多，而处理消息的服务可以依据自身机器性能，程序性能去处理符合自身处理能力的消息数量，如同步场景下，可能出现的局面是将洪水引入小沟渠。&lt;/p&gt;
&lt;h3 id="mq局限"&gt;MQ局限&lt;/h3&gt;
&lt;p&gt;当上游服务需实时关注执行结果时，使用MQ反而不利，借助MQ更多的是完成时间换空间的活。&lt;/p&gt;
&lt;h3 id="常见应用案例"&gt;常见应用案例&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;注册时发送邮件或发送短信&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;日志分析使用，多个服务产生的数据发送到中间件发送到分析服务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息复制，用于跨机房数据传输、搜索、离线数据计算等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;延迟消息发送和暂存，把中间件当成可靠的消息暂存地。接受消息，暂时先不发送。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="常见mq中间件"&gt;常见MQ中间件&lt;/h3&gt;
&lt;p&gt;比较流行的有 RabbitMQ、ActiveMQ、ZeroMQ、kafka，阿里开源的RocketMQ，微软的Service Bus等。&lt;/p&gt;
&lt;h2 id="rabbitmq介绍"&gt;RabbitMQ介绍&lt;/h2&gt;
&lt;h3 id="rabbitmq"&gt;RabbitMQ&lt;/h3&gt;
&lt;p&gt;RabbitMQ 是实现 AMQP（高级消息队列协议）的消息中间件的一种，最初起源于金融系统，用于在分布式系统中存储转发消息，在易用性、扩展性、高可用性等方面表现不俗。 RabbitMQ 主要是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时，消费者无法快速消费，那么需要一个中间层。保存这个数据。&lt;/p&gt;
&lt;h3 id="amqp协议"&gt;AMQP协议&lt;/h3&gt;
&lt;p&gt;AMQP，即 Advanced Message Queuing Protocol，高级消息队列协议，是应用层协议的一个开放标准，为面向消息的中间件设计。消息中间件主要用于组件之间的解耦，消息的发送者无需知道消息使用者的存在，反之亦然。AMQP 的主要特征是面向消息、队列、路由（包括点对点和发布/订阅）、可靠性、安全。&lt;/p&gt;
&lt;h3 id="rabbitmq特点"&gt;RabbitMQ特点&lt;/h3&gt;
&lt;p&gt;RabbitMQ 是一个开源的 AMQP 实现，服务器端用Erlang语言编写，支持多种客户端，如：Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等，支持 AJAX。用于在分布式系统中存储转发消息，在易用性、扩展性、高可用性等方面表现不俗。&lt;/p&gt;
&lt;p&gt;具体特点包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可靠性（Reliability）RabbitMQ 使用一些机制来保证可靠性，如持久化、传输确认、发布确认。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;灵活的路由（Flexible Routing）在消息进入队列之前，通过 Exchange 来路由消息的。对于典型的路由功能，RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能，可以将多个 Exchange 绑定在一起，也通过插件机制实现自己的 Exchange 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息集群（Clustering）多个 RabbitMQ 服务器可以组成一个集群，形成一个逻辑 Broker 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;高可用（Highly Available Queues）队列可以在集群中的机器上进行镜像，使得在部分节点出问题的情况下队列仍然可用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多种协议（Multi-protocol）RabbitMQ 支持多种消息队列协议，比如 STOMP、MQTT 等等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多语言客户端（Many Clients）RabbitMQ 几乎支持所有常用语言，比如 Java、.NET、Ruby 等等。&lt;/p&gt;</description></item><item><title>Lambda&amp;Expression Tree</title><link>/blog/2021-02-27-lambda-expressiontree/</link><pubDate>Sat, 27 Feb 2021 10:00:00 +0800</pubDate><guid>/blog/2021-02-27-lambda-expressiontree/</guid><description>&lt;h1 id="lambda演变历史"&gt;Lambda演变历史&lt;/h1&gt;
&lt;h2 id="net-framework-1011"&gt;.Net Framework 1.0/1.1&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class EvolutionHistory
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public delegate void CustomDelegate(string param);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public void Test()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CustomDelegate customDelegate = new CustomDelegate(DoWork);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; customDelegate.Invoke(&amp;#34;test&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private void DoWork(string param)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(param);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="net-framework-20"&gt;.Net Framework 2.0&lt;/h2&gt;
&lt;p&gt;匿名方法，delegate关键字出现，简化了委托实例化参数&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class EvolutionHistory
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public delegate void CustomDelegate(string param);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public void Test()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CustomDelegate customDelegate = new CustomDelegate(delegate (string param)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(param);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; customDelegate.Invoke(&amp;#34;test&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="net-framework-30"&gt;.Net Framework 3.0&lt;/h2&gt;
&lt;p&gt;Lambda表达式出现&lt;/p&gt;</description></item><item><title>理解领域驱动设计</title><link>/blog/2021-01-18-understand-domaindrivendesign/</link><pubDate>Mon, 18 Jan 2021 10:00:00 +0800</pubDate><guid>/blog/2021-01-18-understand-domaindrivendesign/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;什么是领域，我习惯描述的是制药领域、环境领域、建筑领域、金融领域等，而在领域内，各种业务规则、业务知识盛行，如何有效的把控规则的变化，应对复杂知识，有一个很关键的四字词语，分而治之。分治法在很多场景下体现了其强大的作用力。领域本身很大，那就拆分，得到更小的领域，也即子域，如同递归调用一般，将一个复杂问题拆分单独求解，而最终将解汇总得到复杂问题解。&lt;/p&gt;
&lt;p&gt;怎么拆，拆成怎么样合适，依据什么拆，这些在领域驱动设计中有了一套答案，虽然领域驱动设计不是银弹，但可以说的上是一套极好的系统方法论或称为架构设计的方法论。&lt;/p&gt;
&lt;p&gt;领域驱动设计常以战略设计与战术设计来将整个领域展现的淋漓尽致，其作用范围既面向业务也面向技术。从战略角度(个人更喜欢称其为上帝视角)去规划系统、划分领域。而从战术角度则从技术层面来指导我们该如何去设计。&lt;/p&gt;
&lt;h2 id="战略设计"&gt;战略设计&lt;/h2&gt;
&lt;p&gt;战略设计主要从高层俯视(上帝视角)我们的软件系统，就如同玩即时战略游戏般，可以一览地图全貌，以此来决定我们是要进攻还是防守哪个方向，同样，在软件中我们也可以以此来划分领域，确定权重方向。&lt;/p&gt;
&lt;h3 id="统一语言"&gt;统一语言&lt;/h3&gt;
&lt;p&gt;提炼领域知识，怎么个提炼法，千万条罗马路，各有各的看家本领。像事件风暴方法，用例分析方法，用户故事，甚至是开大会，各种讨论会等，最终目的都是提炼出领域知识，而提炼过程中，达成描述上的一致性，包括系统目标、系统范围及系统所具有的功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195800138_010ba08b-3440-4c57-88b5-8fcbb55b1fed.png" alt="195800138_010ba08b-3440-4c57-88b5-8fcbb55b1fed"&gt;
这不是领域驱动设计所独有的，但却是软件开发中所必须的，为领域专家、业务分析人员、编码人员和测试人员等团队所有成员交流时构建统一频道。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195802586_d370b92d-b261-48bf-9954-62c0a95becab.png" alt="195802586_d370b92d-b261-48bf-9954-62c0a95becab"&gt;&lt;/p&gt;
&lt;h3 id="领域子域"&gt;领域/子域&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195805464_33cdde40-5bcb-491d-a7e4-433885013b06.png" alt="195805464_33cdde40-5bcb-491d-a7e4-433885013b06"&gt;&lt;/p&gt;
&lt;h4 id="领域拆分"&gt;领域拆分&lt;/h4&gt;
&lt;p&gt;对于领域这个概念，习惯性会想到制药领域、环境领域、金融领域等这些概念，而领域本身所描述的是范围，是如同现实世界般的复杂，无边际。借助分治法，将问题逐级细分来降低业务和技术复杂度，将这复杂的世界划分出清晰的边界来，反过来控制着划分后不那么复杂的世界，也既领域拆分出细化后的子领域。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195806717_16360827-60e1-4773-98ea-568ebbac2f0f.png" alt="195806717_16360827-60e1-4773-98ea-568ebbac2f0f"&gt;&lt;/p&gt;
&lt;h3 id="子域划分"&gt;子域划分&lt;/h3&gt;
&lt;p&gt;在实际解决问题时，我们也习惯将问题拆分，而怎么拆，基于什么原则拆，可能会依据相关性，权重，甚至分类原则等，对于系统而言，会从架构方面考虑，基础设施考虑等，在领域驱动设计中，更偏向基于业务拆分，降低业务复杂度，也分离技术实现的复杂度，依照业务拆分后的子领域，本身存在权重上的差异，依照重要性和功能划分为三类，投资占比也就有所不同。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;核心域&lt;/strong&gt;：其所体现的是核心服务，是代表着产品的核心竞争力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;支撑域&lt;/strong&gt;：其所体现的是支撑服务，没它不行，但又达不到核心的价值，围绕着产品内部所需要，但又不能单独变更为第三方服务，即它不是一个通用的服务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;通用域&lt;/strong&gt;：其所体现的中间件服务或第三方服务。本身可以通过现有的解决方案集成来完成的服务。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195807994_4e27d27c-45d9-4b83-a176-875dee068f57.png" alt="195807994_4e27d27c-45d9-4b83-a176-875dee068f57"&gt;&lt;/p&gt;
&lt;h2 id="限界上下文"&gt;限界上下文&lt;/h2&gt;
&lt;p&gt;深入到一个子域中，又是一片小天地，在这天地中，却又还是存在着因语义与语境上的差异，让一些概念在这子域中显得额外尴尬。在一个领域 / 子域中，我们会创建一个概念上的领域边界，在这个边界中，任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。&lt;/p&gt;
&lt;p&gt;其本质上是限界+上下文，引用到张逸老师的一句话&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;上下文（Context）其实是动态的业务流程被边界（Bounded）静态切分的产物&lt;/strong&gt;。
对于子域与上下文间的关系，看到很多书籍或是文章中所描述的都不一样，这块的争论也没有一个最终答案，个人更倾向于子域中划分上下文，从拆分角度来讲，这样理解更加简单。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195809158_d056b2ab-b8eb-465b-bc31-b66e5613adc3.png" alt="195809158_d056b2ab-b8eb-465b-bc31-b66e5613adc3"&gt;&lt;/p&gt;
&lt;h3 id="上下文识别"&gt;上下文识别&lt;/h3&gt;
&lt;p&gt;对于上下文的识别，没有可遵循的标准可走，从不同的角度切入将会识别到不同的上下文，可从张逸老师的领域驱动设计实践中窥之一二，以业务复杂度、管理复杂度和技术复杂度出发，面对这三个角度去依次分析，从业务视角、工作视角、应用视角去识别，进而识别出准确的上下文，通过不断的分析斟酌考虑，逐渐识别出符合当前预期的上下文，如在实际操作环节发觉当前上下文的设计显得不那么合理，还可再进行变动、拆分上下文。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195810613_6a26298b-ca37-4421-98d5-630d10b2da4c.png" alt="195810613_6a26298b-ca37-4421-98d5-630d10b2da4c"&gt;
但需注意的一个是，我们识别上下文的目的是什么，是为了控制上下文，准确的说是为了控制上下文的边界、大小，是为了保住我们所守护的上下文不会因过度成长变大而奔溃，亦或因上下文过度缩减而失去价值，保证上下文内一切的稳定，上下文与上下文间交互的可用性，也或者是当我们退出上下文时，交付出来的上下文是非常可观的，而不是一个烂摊子。&lt;/p&gt;
&lt;h3 id="上下文映射"&gt;上下文映射&lt;/h3&gt;
&lt;p&gt;规划了这么多限界上下文，该如何穿针引线将这些上下文串起来便是一个问题了，用例场景的完整实现往往是由多个上下文的协作完成的，怎么去组织这些上下文，领域驱动设计提到的几种方式及软件工程中常用模式。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;合作关系：一荣俱荣，一损俱损。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;共享内核：上下文间共享领域实体。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客户方-供应方：下游客户依赖于上游供应方。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;遵奉者：下游客户顺应上游供应方。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;各行其道：没有关系的关系，相互隔离。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;防腐层：在下游上下文与上游间增加一道屏障，以此来隔绝与上游的直接交互保护下游。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开放主机服务：在上游与下游上下文间增加一道协议，以此来规范下游对上游的集成。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;已发布语言：发布方上下文发布一份包含丰富文档的信息交换语言，消费方上下文翻译并使用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些模式其本质是为了&lt;strong&gt;协作&lt;/strong&gt;，为了满足用例场景下对多个限界上下文的调用，通过上下文映射图，可以清楚知晓运行逻辑。为了实现上下文映射，简单讲就是如何将两个上下文连贯起来，常借助的方式是诸如 RPC、HTTP、消息队列等，依照上下文间映射类型，挑选一件趁手的工具。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195812270_20ced535-11be-4ed5-bd00-4903b8296d51.png" alt="195812270_20ced535-11be-4ed5-bd00-4903b8296d51"&gt;&lt;/p&gt;
&lt;h2 id="分层架构"&gt;分层架构&lt;/h2&gt;
&lt;p&gt;我们通常喜欢对各种事情归纳总结，如文章的层次分明，如建筑结构高低有序、疏密有致，给人一种各处所关注的信息视角不同，而组合起来显得如此美妙。软件中同样运用着分层来隔离关注点，以此来隔离每层的演进速率。&lt;/p&gt;
&lt;p&gt;当我们考虑限界上下文时，不仅需要去考虑其内部的领域设计，还得从其应用边界本身考虑，限界上下文是属于架构设计层次，主要针对的是后端架构层次的垂直切分，按照经典 DDD 的分层结构来看，共分为如下四层：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20210118/195813476_4e3c52a1-68b1-4457-a645-00d5c14d9cb6.png" alt="195813476_4e3c52a1-68b1-4457-a645-00d5c14d9cb6"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;User Interface 为用户界面层，向用户展示信息和传入用户命令。这里指的用户不单单只使用用户界面的人，也可能是外部系统，诸如用例中的参与者。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Application 为应用层，用来协调应用的活动，不包含业务逻辑，通过编排领域模型，包括领域对象及领域服务，使它们互相协作。不保留业务对象的状态，但它保有应用任务的进度状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain 为领域层，负责表达业务概念，业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的，但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心，领域模型位于这一层。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Infrastructure 为基础实施层，提供公共的基础设施组件，如持久化机制、消息管道的读取写入、文件服务的读取写入、调用邮件服务、对外部系统的调用等等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;值得注意的是，给定的分层方式仅仅是逻辑上的分层，而对于实际的物理分层，却又有所不同，但遵守一个前提为好，即限界上下文的边界高于分层的边界。诸如如下两种开发中常见的代码组织方式，都可见到。一种是基于技术分层，而另一种更偏向基于业务分层。&lt;/p&gt;
&lt;p&gt;方式一&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- application
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - productcontext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ordercontext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- domain
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - productcontext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ordercontext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- infrastructure
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - productcontext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ordercontext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;方式二&lt;/p&gt;</description></item><item><title>Electron安装打包指南</title><link>/blog/2020-12-04-electron-installation-and-packaging/</link><pubDate>Fri, 04 Dec 2020 10:00:00 +0800</pubDate><guid>/blog/2020-12-04-electron-installation-and-packaging/</guid><description>&lt;p&gt;当前环境Debian Linux-Deepin&lt;/p&gt;
&lt;h2 id="安装node"&gt;安装Node&lt;/h2&gt;
&lt;h3 id="直接下载"&gt;直接下载&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20201204/200615966_b484de4e-13aa-4583-bd0d-862741850557.png" alt="200615966_b484de4e-13aa-4583-bd0d-862741850557"&gt;&lt;/p&gt;
&lt;h3 id="命令下载"&gt;命令下载&lt;/h3&gt;
&lt;p&gt;下载&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;wget https://nodejs.org/dist/v14.15.1/node-v14.15.1-linux-x64.tar.xz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;解压，解压后在当前盘多了个文件夹node-v14.15.1-linux-x64.(可自行改名)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tar -zxvf node-v14.15.1-linux-x64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;建立软链接&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ln -s /盘路径/node-v14.15.1-linux-x64/bin/node /usr/local/bin/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ln -s /盘路径/node-v14.15.1-linux-x64/bin/npm /usr/local/bin/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查看版本&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;node -v
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="卸载node"&gt;卸载Node&lt;/h2&gt;
&lt;p&gt;1、卸载npm&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo npm uninstall npm -g
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;2、卸载node&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt-get remove nodejs npm -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;3、检查是否还存在&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;进入/usr/local/lib 删除所有 node 和 node_modules文件夹&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;进入/usr/local/include 删除所有 node 和 node_modules 文件夹&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;进入 /usr/local/bin 删除 node 的可执行文件&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo rm -rf /usr/local/lib/node*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo rm -rf /usr/local/include/node*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo rm -rf /usr/local/bin/node*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo rm -rf /usr/local/node*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="设置淘宝镜像源"&gt;设置淘宝镜像源&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm config set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查看当前镜像源&lt;/p&gt;</description></item><item><title>代码模型组织方式</title><link>/blog/2020-11-22-how-the-code-model-is-organized/</link><pubDate>Sun, 22 Nov 2020 10:00:00 +0800</pubDate><guid>/blog/2020-11-22-how-the-code-model-is-organized/</guid><description>&lt;h2 id="结构划分迷雾"&gt;结构划分迷雾&lt;/h2&gt;
&lt;p&gt;Asp.Net Boilerplate中默认项目结构是如下所示(项目代号Gravel)，每层都是一个单独的类库或是应用程序。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- Gravel.sln
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Web
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Application
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.EFCore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在这种固有分层方式下，ABP没有指明要采用什么形式去划分限界上下文，是采用文件夹隔离形式还是采用类库隔离形式，并没有严格说明。
&lt;strong&gt;注意：具体是在应用层领域层基础设施层中需要隔离限界上下文。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="文件夹隔离"&gt;文件夹隔离&lt;/h3&gt;
&lt;p&gt;如采用文件夹隔离形式，则解决方案结构如下，在每一层中建立文件夹形式以此来划分限界上下文的边界。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- Gravel.sln
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Web
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Application
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - OrderCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ProductCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - CustomerCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - OrderCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ProductCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - CustomerCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.EFCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - OrderCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ProductCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - CustomerCenter
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="类库隔离"&gt;类库隔离&lt;/h3&gt;
&lt;p&gt;如采用类库隔离形式(即依照限界上下文拆分类库)，则解决方案结构如下，Gravel作为承载体，其余三个作为上下文独立出来，按照上下文的边界高于分层的边界(这种划分也更加合理)。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- Gravel.sln
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Web
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Application
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Gravel.EFCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - OrderCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Application
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - EFCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ProductCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Application
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - EFCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - CustomerCenter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Application
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - EFCore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于Asp.Net Boilerplate，见到的几个Demo及博客园内一些作者，都是采用的第一种风格形式，即分层边界高于上下文的边界。
而对于第二种形式，在ABP VNext的MicroserviceDemo中采用的是这种形式。并且在ABP VNext中，这种形式的变体是可以从单体切换到微服务的。&lt;/p&gt;</description></item><item><title>MinIO的搭建与应用</title><link>/blog/2020-10-19-minio-construction-and-application/</link><pubDate>Mon, 19 Oct 2020 10:00:00 +0800</pubDate><guid>/blog/2020-10-19-minio-construction-and-application/</guid><description>&lt;h2 id="minio-介绍"&gt;MinIO 介绍&lt;/h2&gt;
&lt;p&gt;MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口，非常适合于存储大容量非结构化的数据，例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等，而一个对象文件可以是任意大小，从几 kb 到最大 5T 不等。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.min.io/cn/"&gt;https://docs.min.io/cn/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="minio-安装"&gt;MinIO 安装&lt;/h2&gt;
&lt;p&gt;MinIO 的官方文档提供了相当全面的安装信息，使用 docker 安装 MinIO，不考虑 MinIO 的分布式，毕竟对外使用来讲，MinIO 更像是个黑盒子，只关心与黑盒子的上传下载操作，而不关心是单机存储还是分布式存储。&lt;/p&gt;
&lt;h3 id="docker-中创建并运行-minio"&gt;Docker 中创建并运行 MinIO&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 9800:9000 --name minioservice \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e &amp;#34;MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e &amp;#34;MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v /mnt/data:/data \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -v /mnt/config:/root/.minio \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; minio/minio server /data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;依据 MinIO Docker 快速入门，下载 MinIO 镜像并创建一个 MinIO 容器。
&lt;a href="https://docs.min.io/cn/minio-docker-quickstart-guide.html"&gt;https://docs.min.io/cn/minio-docker-quickstart-guide.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;-p：对外端口可以依据使用自定义配置，对内端口保持 9000 就行，因本地使用了 portainer，占用了 9000 的对外端口，因此更换使用 9800 端口。(MinIO 文档中使用的是 9000 的)&lt;/p&gt;</description></item><item><title>ABP模块系统</title><link>/blog/2020-10-18-abp-modular-system/</link><pubDate>Sun, 18 Oct 2020 10:00:00 +0800</pubDate><guid>/blog/2020-10-18-abp-modular-system/</guid><description>&lt;h2 id="模块系统简介"&gt;&lt;strong&gt;模块系统简介&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;ABP 为创建模块及组织它们提供基础框架。一个模块可依赖于另一个模块。通常地，一个程序集做为一个模块。如果你的应用是多个程序集，建议为每个程序集定义一个模块。&lt;/p&gt;
&lt;p&gt;模块的本质就是可重用性，你可以在任意的地方去调用，而且通过实现模块，你写的模块也可以给别人用。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20201018/200941480_f1c84fd3-d726-4e8f-a5e9-c50664c3a387.png" alt="200941480_f1c84fd3-d726-4e8f-a5e9-c50664c3a387"&gt;&lt;/p&gt;
&lt;h2 id="模块定义"&gt;&lt;strong&gt;模块定义&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;一个模块定义成一个继承于 AppModule 类的类。 假设我们正在开发一个能用于不同应用的 Blog 模块，最简单的模块定义可能像如下所示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class MyBlogApplicationModule : AbpModule
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    public override void Initialize()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IocManager.RegisterAssemblyByConvention(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Assembly.GetExecutingAssembly());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;模块定义类负责把自己的类注册到依赖注入里（约定做法如上所示）、配置应用和其它模块、为应用添加新的特性等&amp;hellip;&lt;/p&gt;
&lt;h2 id="生命周期方法"&gt;&lt;strong&gt;生命周期方法&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;ABP 在应用启动和关闭时，为模块调用一些特定的方法。你可以重写这些方法来执行一些特定的任务。&lt;/p&gt;
&lt;p&gt;ABP 按依赖关系顺序调用这些方法。如果模块 A 依赖于模块 B，模块 B 会在模块 A 前初始化，确切的启动方法顺序是：预初始化 B，预初始化 A，初始化 B，提交 B 和提交 A。这就是依赖路线。Shutdown 方法类似，不过顺序是相反的。&lt;/p&gt;
&lt;h3 id="preinitialize预初始化"&gt;&lt;strong&gt;PreInitialize（预初始化）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;应用启动时最先调用这个方法，它通常在初始化前将模块内的配置信息添加到框架或是处理其它模块所需执行的操作。&lt;/p&gt;
&lt;p&gt;也可以在这里写一些特定的代码运行在依赖注入注册之前。例如，你想创建一个约定注册(Conventional Registration)类，你应该在这里使用 IocManager.AddConventionalRegisterer 方法来注册这个类。&lt;/p&gt;
&lt;h3 id="initialize初始化"&gt;&lt;strong&gt;Initialize（初始化）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;依赖注入注册一般都在这里完成，一般都使用 IocManager.RegisterAssemblyByConvention 方法。如果想自定义依赖注册，可以依照 ABP 提供的其他方式完成注册(手动注册)，如使用 IocManager.Register 方法直接注册或是Castle Windsor 注册.&lt;/p&gt;
&lt;h3 id="postinitialize提交初始化"&gt;&lt;strong&gt;PostInitialize（提交初始化）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在启动过程中最后调用这个方法，此时可以安全的解析依赖。&lt;/p&gt;
&lt;h3 id="shutdown关闭"&gt;&lt;strong&gt;Shutdown（关闭）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;当应用关闭时调用这个方法。&lt;/p&gt;</description></item><item><title>ABP Framework从单体切换到微服务</title><link>/blog/2020-09-26-abpframework-switches-from-monolith-to-microservices/</link><pubDate>Sat, 26 Sep 2020 10:00:00 +0800</pubDate><guid>/blog/2020-09-26-abpframework-switches-from-monolith-to-microservices/</guid><description>&lt;p&gt;注：此处的微服务只考虑服务部分，不考虑内外层网关、认证等。&lt;/p&gt;
&lt;p&gt;ABP VNext从单体切换到微服务，提供了相当大的便利性，对于各模块内部不要做任何调整，仅需要调整承载体即可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ABP can help you in that point by offerring a microservice-compatible, strict module architecture where your module is splitted into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="分层架构"&gt;分层架构&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;ABP VNext自身提供的分层方式如下图，核心部分是Application、Domain与EntityFrameworkCore层，至于HttpApi.Client与HttpApi都是属于将应用去承载到其他系统之上的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200926/201215838_cdd28f13-c1f3-4cfe-8ca0-a688f183c17a.png" alt="201215838_cdd28f13-c1f3-4cfe-8ca0-a688f183c17a"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller层在ABP VNext中被削弱了，VNext提供了将Application提供动态Controller，所以可能习惯了将部分逻辑写在控制器中的会有些唐突，但理解就行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200926/201217709_bfc284d7-0b0a-45c1-9ac4-a728aae6ae53.png" alt="201217709_bfc284d7-0b0a-45c1-9ac4-a728aae6ae53"&gt;&lt;/p&gt;
&lt;h2 id="规划层次结构"&gt;规划层次结构&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;新建空文件夹，取名随意(本次取名GravelService)，用于存放整体项目。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在上一步的空文件夹内新建空白解决方案，可以用VS去生成或是按照dotnet的命令生成。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200926/201219053_4ea9e5ce-2c8a-4832-b77e-4d3afdd3e34d.png" alt="201219053_4ea9e5ce-2c8a-4832-b77e-4d3afdd3e34d"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;习惯上按照ABP VNext给定的微服务Demo中的格式创建三个文件夹，思路清晰。当然提供的微服务Demo还有更多文件夹，但是这次只关注这三个。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200926/201220123_8fb1c760-16e2-4ba2-b592-3e625900331f.png" alt="201220123_8fb1c760-16e2-4ba2-b592-3e625900331f"&gt;&lt;/p&gt;</description></item><item><title>AddMvcAddMvcCoreAddControllers等区别</title><link>/blog/2020-09-05-distinguish-between-mvc-service-registrations/</link><pubDate>Sat, 05 Sep 2020 10:00:00 +0800</pubDate><guid>/blog/2020-09-05-distinguish-between-mvc-service-registrations/</guid><description>&lt;p&gt;比较一下AddMvc、AddMvcCore、AddController等在服务配置时的区别及各自拥有的功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.services.AddMvcCore()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;只注册路由请求和执行控制器所必要的核心服务，确保 Pipeline 程序可运转。除非是有能力并想完全去自主DIY，一般不建议直接使用这个。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.services.AddControllers()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;除包含了 AddMvcCore() 所有功能，再加上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Authorization&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ApiExplorer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data Annotation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Formatter Mapping&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CORS&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要用 Controller 但不用View，新建WebAPI时，默认采用的就是这个，使用这个时，与SwashBuckle配合时，无需再额外引入ApiExplorer,自身已经依赖。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.services.AddRazorPages()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;包含 AddMvcCore() 所有功能，再加上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Razor Pages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authorization&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data Annotation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cache Tag Helper&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4.services.AddControllersWithViews()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;包含 AddControllers() 所有功能，再加上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;cshtml和Razor View&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cache Tag Helper&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;标准MVC模式，常用Razor视图，使用这个就够了&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5.services.AddMvc()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;包含 AddControllersWithViews() 及 AddRazorPages() 功能。 包含的功能最为齐全，如果不想遗漏功能，直接使用这个就行&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200905/201602196_9ec60b77-1c05-4dc4-918e-5cb5e23307fa.png" alt="201602196_9ec60b77-1c05-4dc4-918e-5cb5e23307fa"&gt;
 &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-09-05，望技术有成后能回来看见自己的脚步。 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>AspNetCore&amp;Coding持续集成</title><link>/blog/2020-08-09-aspnetcore-coding-continuousintegration/</link><pubDate>Sun, 09 Aug 2020 10:00:00 +0800</pubDate><guid>/blog/2020-08-09-aspnetcore-coding-continuousintegration/</guid><description>&lt;p&gt;对于现有很多持续集成工具来讲，功能越来越高级，使用了 Coding 有大半年时间，越发觉好用，特别是没钱续费服务器时，找到了新的羊毛。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="众多持续集成工具"&gt;&lt;strong&gt;众多持续集成工具&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;现在可用的持续集成工具繁多，各大云服务商都推出了持续集成，甚至是一定条件内都是免费使用。比如 Azure 提供每个月 1800 分钟的免费时长，支持单项目并行构建，GitHub的GitHubActions，华为云的 DevCloud，阿里云的云效，腾讯云与 Coding 合作的Coding.DevOps 等等。在此推荐一波 &lt;strong&gt;Coding&lt;/strong&gt;： &lt;a href="https://coding.net/"&gt;https://coding.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;对于个人开发者而言，能够薅到羊毛，是最为实在的。最近，自己买的的两台羊毛机将到期，没钱续费，并且看到各大云服务商的提供的各种福利，干脆放弃自己搭建持续集成服务。特别是半年前开始接触到 Coding，看到其中许多 Azure 的影子，并且上手后发现很好用(关键是免费)，当时只是想要提供的制品库功能用来保存 Docker 镜像(源于 TencentHub 服务的关闭)。&lt;/p&gt;
&lt;p&gt;Coding提供5人以下免费，每周200次持续集成次数，每次限时30分钟，并行任务数1个。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090137039_1cbf5cb9-fdaf-4a54-98f9-7433b47b0e43.png" alt="090137039_1cbf5cb9-fdaf-4a54-98f9-7433b47b0e43"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="coding-的使用"&gt;&lt;strong&gt;Coding 的使用&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;注册团队账号，填写团队域名，5 人以下团队免费。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090138776_936d6819-afd3-4050-b7bb-098ac1079401.png" alt="090138776_936d6819-afd3-4050-b7bb-098ac1079401"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立项目，选择模板，提供了各种场景下需要的模板，都是在完整模板上依据功能精简的，不管选择哪个模板，都可以通过功能开关获取到所有功能。 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090140687_9a08d1aa-7ea8-4a46-9704-448155fd1f13.png" alt="090140687_9a08d1aa-7ea8-4a46-9704-448155fd1f13"&gt;
选择第二个项目模板，等会更改功能开关获取其他功能。填写项目名称，如 &lt;strong&gt;StarCity.Standard&lt;/strong&gt;。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090142221_6c093b03-de44-494b-9758-069ff62d2557.png" alt="090142221_6c093b03-de44-494b-9758-069ff62d2557"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置功能开关，开放更多功能。左下角选择并点击项目设置，跳转到设置页面中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090143965_8df12507-08b0-402a-af10-2e1488fb9b94.png" alt="090143965_8df12507-08b0-402a-af10-2e1488fb9b94"&gt;
选择功能开关 Tab，开放更多功能，这些操作和 Azure 中的操作都是差不多的。本次只选择持续集成和制品库，其他关闭。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090145519_4714006d-0b8f-49d8-b2e0-31de3053aa4d.png" alt="090145519_4714006d-0b8f-49d8-b2e0-31de3053aa4d"&gt;&lt;/p&gt;
&lt;h3 id="项目搭建及基础配置"&gt;&lt;strong&gt;项目搭建及基础配置&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先在代码仓库中新建项目，对于代码仓库的选择随意，我更倾向于使用 Gitee，当然也可直接使用 Coding 提供的代码仓库。此处新建了一个仓库方便对照： &lt;a href="https://gitee.com/530521314/Partner.TreasureChest/tree/master/StarCity.Standard"&gt;StarCity.Standard&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;新建 AspNetCore 并增加 Dockerfile，整理成常用的目录形式。增加 gitignore、Dockerfile。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090147682_72c2ff55-8892-4c4a-8d0d-53b260fcb11d.png" alt="090147682_72c2ff55-8892-4c4a-8d0d-53b260fcb11d"&gt;
注意：此处 Dockerfile 中我使用腾讯云 Hub 中的一个源，方便构建镜像。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090148759_fa37fe71-fee5-4c26-a315-30b38e8fedef.png" alt="090148759_fa37fe71-fee5-4c26-a315-30b38e8fedef"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新建制品库，用来保存持续集成后我们需要的目标产物。依据具体用途选择仓库及权限范围。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090150444_53710388-dcb6-499e-bdfb-29732e02d366.png" alt="090150444_53710388-dcb6-499e-bdfb-29732e02d366"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开始配置持续集成，点击构建计划，新建计划配置，开始进行持续集成基础配置工作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090151800_af2dc9c5-732d-43f1-9e9b-ca1a7cba4a20.png" alt="090151800_af2dc9c5-732d-43f1-9e9b-ca1a7cba4a20"&gt;
选择持续集成模板，Coding 提供了十几种模板类型，但是没得 AspNetCore 的模板，选择自定义模板，进行配置，当然，已有的这些模板可以点进去看看，学习其中的配置方式。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200809/090153685_c6370813-d4ad-4ad2-8a95-099ee294fa8f.png" alt="090153685_c6370813-d4ad-4ad2-8a95-099ee294fa8f"&gt;&lt;/p&gt;
&lt;p&gt;配置自定义模板，选择 Gitee 中的代码仓库，注意需要按照提示配置 OAuth，此处主要是依赖 Jenkinsfile 的完成持续集成功能。通过在 Coding 中配置 Jenkinsfile 减少对项目内的倾入(我个人更喜欢这种方式)。&lt;/p&gt;</description></item><item><title>初探Redis-基础类型SortedSet</title><link>/blog/2020-07-05-redis-sortedset/</link><pubDate>Sun, 05 Jul 2020 10:00:00 +0800</pubDate><guid>/blog/2020-07-05-redis-sortedset/</guid><description>&lt;p&gt;Redis存在五种基础类型：字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。本次列举出Sorted Set的常用操作。&lt;/p&gt;
&lt;p&gt;Redis官网：&lt;a href="https://redis.io/"&gt;https://redis.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="有序集合sortedset介绍"&gt;&lt;strong&gt;有序集合(SortedSet)介绍&lt;/strong&gt; &lt;/h3&gt;
&lt;p&gt;Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个float类型的分数(score)。通过分数来为集合中的成员进行从小到大的排序。有序集合的&lt;strong&gt;成员是唯一的,&lt;strong&gt;但&lt;/strong&gt;分数(score)可以重复&lt;/strong&gt;。集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161451974_bc2e712b-1661-47b4-ad42-83aba09d56f4.png" alt="161451974_bc2e712b-1661-47b4-ad42-83aba09d56f4"&gt;
 &lt;/p&gt;
&lt;h3 id="有序集合sortedset相关操作api"&gt;&lt;strong&gt;有序集合(SortedSet)相关操作Api&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="添加值--161453733_"&gt;&lt;strong&gt;添加值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161453733_49438b18-9023-434d-8992-c6b2182c99fa.png" alt="161453733_49438b18-9023-434d-8992-c6b2182c99fa"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161455012_b0454916-b9b3-4f60-8b52-d7629d42ff6f.png" alt="161455012_b0454916-b9b3-4f60-8b52-d7629d42ff6f"&gt;&lt;/p&gt;
&lt;h4 id="删除值--161456191_"&gt;&lt;strong&gt;删除值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161456191_1879769d-4bfd-4f73-b72d-768450e0d00b.png" alt="161456191_1879769d-4bfd-4f73-b72d-768450e0d00b"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161457581_d0c0e188-c9f4-4921-aaf4-8f4f13c34e11.png" alt="161457581_d0c0e188-c9f4-4921-aaf4-8f4f13c34e11"&gt;&lt;/p&gt;
&lt;h4 id="删除key下指定索引范围内的值--161458714_"&gt;&lt;strong&gt;删除key下指定索引范围内的值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161458714_cf89aaae-2c9d-4ac6-a3fb-907d38721306.png" alt="161458714_cf89aaae-2c9d-4ac6-a3fb-907d38721306"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161459851_3d0e7197-0b8c-4212-9b47-023391c42b07.png" alt="161459851_3d0e7197-0b8c-4212-9b47-023391c42b07"&gt; &lt;/p&gt;
&lt;h4 id="删除key下指定字典区间范围内的值--161501007_"&gt;&lt;strong&gt;删除key下指定字典区间范围内的值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161501007_be77ba7c-18c5-440b-bbec-2963c71760ac.png" alt="161501007_be77ba7c-18c5-440b-bbec-2963c71760ac"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161502365_4a1f7b62-fb89-4c76-8a0c-2cfd92a88ea5.png" alt="161502365_4a1f7b62-fb89-4c76-8a0c-2cfd92a88ea5"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;删除key下指定分数范围内的值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161503759_3c339089-8bc6-4466-8a8e-21d3f223b23a.png" alt="161503759_3c339089-8bc6-4466-8a8e-21d3f223b23a"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161504998_323c175c-023b-4b59-bbd5-0db55f28e095.png" alt="161504998_323c175c-023b-4b59-bbd5-0db55f28e095"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;获取key下值的总数量&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161506125_9b7d0c36-6f3b-4809-8e84-1bd1f9f1f5af.png" alt="161506125_9b7d0c36-6f3b-4809-8e84-1bd1f9f1f5af"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161507281_69fb3e76-08a8-4b3f-bc11-24030b460538.png" alt="161507281_69fb3e76-08a8-4b3f-bc11-24030b460538"&gt; &lt;/p&gt;
&lt;h4 id="获取key下指定分数范围内值的总数量--161508446_"&gt;&lt;strong&gt;获取key下指定分数范围内值的总数量&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161508446_1c66c9e8-c736-4284-89b6-320c598b6ab8.png" alt="161508446_1c66c9e8-c736-4284-89b6-320c598b6ab8"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161509613_2e408650-1aae-448b-b98a-7dda40567fa7.png" alt="161509613_2e408650-1aae-448b-b98a-7dda40567fa7"&gt;&lt;/p&gt;
&lt;h4 id="获取key下指定字典范围内值的总数量--161510788_"&gt;&lt;strong&gt;获取key下指定字典范围内值的总数量&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161510788_f7a2c810-3fe5-4d2b-bc17-11e6e8aff46c.png" alt="161510788_f7a2c810-3fe5-4d2b-bc17-11e6e8aff46c"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161511980_24cad29d-71e8-4045-b61b-8d19b4e12bc3.png" alt="161511980_24cad29d-71e8-4045-b61b-8d19b4e12bc3"&gt;&lt;/p&gt;
&lt;h4 id="获取key下索引范围内的值--161513273_"&gt;&lt;strong&gt;获取key下索引范围内的值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161513273_ceb46cfb-eff0-4a62-aaca-ac2c9f595a4d.png" alt="161513273_ceb46cfb-eff0-4a62-aaca-ac2c9f595a4d"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161514973_69fa9eba-8ea8-462c-94d7-49b47bc5ae19.png" alt="161514973_69fa9eba-8ea8-462c-94d7-49b47bc5ae19"&gt;&lt;/p&gt;
&lt;h4 id="获取key下指定分数范围内的值--161516152_"&gt;&lt;strong&gt;获取key下指定分数范围内的值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161516152_3a0c3f25-682a-4a79-829d-815fbf5bda21.png" alt="161516152_3a0c3f25-682a-4a79-829d-815fbf5bda21"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161517267_17fb9062-8771-48f4-a625-12477fbf17f1.png" alt="161517267_17fb9062-8771-48f4-a625-12477fbf17f1"&gt;&lt;/p&gt;
&lt;h4 id="获取key下指定字典区间范围内的值--161518367_"&gt;&lt;strong&gt;获取key下指定字典区间范围内的值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161518367_d62dd623-a607-4fc2-89e6-382994fc2589.png" alt="161518367_d62dd623-a607-4fc2-89e6-382994fc2589"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161519813_4ee6a4f6-f396-43ad-aeb6-7252a37b9e3e.png" alt="161519813_4ee6a4f6-f396-43ad-aeb6-7252a37b9e3e"&gt;&lt;/p&gt;
&lt;h4 id="返回值相应索引--161521010_"&gt;&lt;strong&gt;返回值相应索引&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161521010_a9db48dc-5fb4-4cc1-93d8-5ba8ac960671.png" alt="161521010_a9db48dc-5fb4-4cc1-93d8-5ba8ac960671"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161522210_555bb32f-f0cd-4a3e-b6e5-3bc642453623.png" alt="161522210_555bb32f-f0cd-4a3e-b6e5-3bc642453623"&gt;&lt;/p&gt;
&lt;h4 id="返回值相应分数--161523359_"&gt;&lt;strong&gt;返回值相应分数&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161523359_3c7a904b-e13a-44d6-832e-171f89a71204.png" alt="161523359_3c7a904b-e13a-44d6-832e-171f89a71204"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161524463_3ace03f0-c5f5-42d0-86f5-e9f8b57d358d.png" alt="161524463_3ace03f0-c5f5-42d0-86f5-e9f8b57d358d"&gt;&lt;/p&gt;
&lt;h4 id="增加分数"&gt;&lt;strong&gt;增加分数&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161525582_7eebd79b-2192-4182-ba71-05dfeb3987da.png" alt="161525582_7eebd79b-2192-4182-ba71-05dfeb3987da"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161526872_6baa1cf3-fcc2-49fd-966e-3b56be456d11.png" alt="161526872_6baa1cf3-fcc2-49fd-966e-3b56be456d11"&gt;
 &lt;/p&gt;
&lt;h3 id="有序集合sortedset间相关操作api"&gt;有序集合(SortedSet)间相关操作Api&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;多个key做并集并存储在新key中&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161528068_8e3c1773-1fc2-4852-8d09-c3957ed35551.png" alt="161528068_8e3c1773-1fc2-4852-8d09-c3957ed35551"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161529907_03fe577f-1140-4784-86c1-a0fb198bd501.png" alt="161529907_03fe577f-1140-4784-86c1-a0fb198bd501"&gt;&lt;/p&gt;
&lt;h4 id="多个key做交集并存储在新key中--161531072_"&gt;&lt;strong&gt;多个key做交集并存储在新key中&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161531072_0474aa9e-8d75-4224-8fa1-417abc010aec.png" alt="161531072_0474aa9e-8d75-4224-8fa1-417abc010aec"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161532637_0d4530ba-95cd-4a0c-bf7e-e4d4e83840e7.png" alt="161532637_0d4530ba-95cd-4a0c-bf7e-e4d4e83840e7"&gt;&lt;/p&gt;
&lt;h4 id="迭代获取集合中匹配的数据--161533796_"&gt;&lt;strong&gt;迭代获取集合中匹配的数据&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161533796_84578815-77be-465a-94f4-408a9faa673c.png" alt="161533796_84578815-77be-465a-94f4-408a9faa673c"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161535034_07b5f1bf-9b15-4a82-ae71-6403d7e38bf3.png" alt="161535034_07b5f1bf-9b15-4a82-ae71-6403d7e38bf3"&gt;
 &lt;/p&gt;
&lt;h3 id="有序集合sortedset简单应用场景"&gt;有序集合(SortedSet)简单应用场景&lt;/h3&gt;
&lt;p&gt;简单模拟排行榜场景，对于蜀国贡献值排行高低，有蜀国成员负责实时生成贡献值，通过查看蜀国贡献排行榜可以实时获取排行情况。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启用一个线程生存各成员贡献值&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Task.Run(() =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; while (true)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; foreach (var user in UserList)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(10);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.SortedSetAdd(&amp;#34;蜀国贡献值&amp;#34;, user, new Random().Next(1, 100));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(20 * 1000);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;启用一个线程实时展示贡献值排行榜&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Task.Run(() =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; while (true)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(12 * 1000);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&amp;#34;**********当前排行************&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; int i = 1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var items = service.SortedSetRangeByRankWithScores&amp;lt;string&amp;gt;(&amp;#34;蜀国贡献值&amp;#34;, 0, -1, true);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; foreach (var item in items)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine($&amp;#34;第{i++}名 {item.Key} 分数{item.Value}&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;效果展示，可以看到实时排行情况，结合UI呈现的话，可以做到实时榜单。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200705/161536327_6c27f60a-83c7-47e8-93ca-961754076113.png" alt="161536327_6c27f60a-83c7-47e8-93ca-961754076113"&gt;&lt;/p&gt;</description></item><item><title>初探Redis-基础类型Set</title><link>/blog/2020-06-14-redis-set/</link><pubDate>Sun, 14 Jun 2020 10:00:00 +0800</pubDate><guid>/blog/2020-06-14-redis-set/</guid><description>&lt;p&gt;Redis存在五种基础类型：字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。本次列举出Set的常用操作。&lt;/p&gt;
&lt;p&gt;Redis官网：&lt;a href="https://redis.io/"&gt;https://redis.io/&lt;/a&gt; &lt;/p&gt;
&lt;h2 id="集合set介绍"&gt;&lt;strong&gt;集合(Set)介绍&lt;/strong&gt; &lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt; 中的集合是一个字符串类型的无序集合，即&lt;strong&gt;集合中不存在重复的元素&lt;/strong&gt;。集合中最大的元素个数为 &lt;code&gt;2^32 -1&lt;/code&gt; 个，集合内部的操作是基于哈希表实现的，一次添加、查找、删除操作的时间复杂度都为 O(1)。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161645306_c383dbff-9056-4292-ae11-0bd234cf1886.png" alt="161645306_c383dbff-9056-4292-ae11-0bd234cf1886"&gt;&lt;/p&gt;
&lt;h2 id="集合set相关操作api"&gt;&lt;strong&gt;集合(Set)相关操作Api&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id="添加值"&gt;&lt;strong&gt;添加值&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161646618_f96c9d35-8bc5-444e-a9f4-408e77c53366.png" alt="161646618_f96c9d35-8bc5-444e-a9f4-408e77c53366"&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161647872_ad89db65-270f-428a-9ccb-967ab2f4e886.png" alt="161647872_ad89db65-270f-428a-9ccb-967ab2f4e886"&gt;  &lt;/p&gt;
&lt;h3 id="删除值"&gt;&lt;strong&gt;删除值&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161649069_533c4c47-e185-41fc-be7d-4880c18980a1.png" alt="161649069_533c4c47-e185-41fc-be7d-4880c18980a1"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161650273_fa885889-0cfb-4c20-8d7d-b221bb567f50.png" alt="161650273_fa885889-0cfb-4c20-8d7d-b221bb567f50"&gt;&lt;/p&gt;
&lt;h3 id="查看集合内所有值"&gt;&lt;strong&gt;查看集合内所有值&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161651497_41947ba6-ea81-4e7e-8bc4-a99c0cc0e35d.png" alt="161651497_41947ba6-ea81-4e7e-8bc4-a99c0cc0e35d"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161652634_b3f77b8a-8329-4572-a522-4d637d15263f.png" alt="161652634_b3f77b8a-8329-4572-a522-4d637d15263f"&gt;&lt;/p&gt;
&lt;h3 id="查看集合中是否有给定值"&gt;&lt;strong&gt;查看集合中是否有给定值&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161653816_c18140c9-32b1-4f88-ac52-32a711504ff3.png" alt="161653816_c18140c9-32b1-4f88-ac52-32a711504ff3"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161655053_39bf013d-a792-4794-81b9-576a48a93955.png" alt="161655053_39bf013d-a792-4794-81b9-576a48a93955"&gt;&lt;/p&gt;
&lt;h3 id="计算集合中值总量"&gt;&lt;strong&gt;计算集合中值总量&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161656502_c385bfbe-b816-4d50-ac67-dff350ce2a80.png" alt="161656502_c385bfbe-b816-4d50-ac67-dff350ce2a80"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161657658_1225ed97-6b95-4e7d-9eb3-17cd2c900f01.png" alt="161657658_1225ed97-6b95-4e7d-9eb3-17cd2c900f01"&gt;&lt;/p&gt;
&lt;h3 id="集合中随机获取n个数据"&gt;&lt;strong&gt;集合中随机获取n个数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161658829_5d0a9add-312a-4f17-a910-792f30856d95.png" alt="161658829_5d0a9add-312a-4f17-a910-792f30856d95"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161659942_840b39c7-d906-48c7-8600-c96ea872558f.png" alt="161659942_840b39c7-d906-48c7-8600-c96ea872558f"&gt;&lt;/p&gt;
&lt;h3 id="集合中随机移除n个值并返回"&gt;&lt;strong&gt;集合中随机移除n个值并返回&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161701047_99d313fd-f925-49b4-8efe-593cc8d3ac07.png" alt="161701047_99d313fd-f925-49b4-8efe-593cc8d3ac07"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161702520_5c137001-85af-4a37-8067-a35b80a2532c.png" alt="161702520_5c137001-85af-4a37-8067-a35b80a2532c"&gt;&lt;/p&gt;
&lt;h3 id="迭代获取集合中匹配的数据"&gt;&lt;strong&gt;迭代获取集合中匹配的数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161703804_4beea89a-6727-4f73-8d15-7d2c760ff48b.png" alt="161703804_4beea89a-6727-4f73-8d15-7d2c760ff48b"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161705652_f9ac9c23-6e34-4783-b239-1e3ebf6ae7a5.png" alt="161705652_f9ac9c23-6e34-4783-b239-1e3ebf6ae7a5"&gt;&lt;/p&gt;
&lt;h2 id="集合set间相关操作api"&gt;&lt;strong&gt;集合(Set)间相关操作Api&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id="获取多集合交集结果"&gt;&lt;strong&gt;获取多集合交集结果&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161707106_4545ff3c-f41b-4952-bd75-1c187cff96f3.png" alt="161707106_4545ff3c-f41b-4952-bd75-1c187cff96f3"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161708193_126fc31e-92a2-4e23-9b1b-331420dc5439.png" alt="161708193_126fc31e-92a2-4e23-9b1b-331420dc5439"&gt;&lt;/p&gt;
&lt;h3 id="获取多集合交集结果并存在新集合中"&gt;&lt;strong&gt;获取多集合交集结果并存在新集合中&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161709387_af376fae-0dfa-4299-bf48-b11943cf9459.png" alt="161709387_af376fae-0dfa-4299-bf48-b11943cf9459"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161710493_8f14d8c2-5390-4384-a601-43f75c2c4fd3.png" alt="161710493_8f14d8c2-5390-4384-a601-43f75c2c4fd3"&gt;&lt;/p&gt;
&lt;h3 id="获取多集合差集结果"&gt;&lt;strong&gt;获取多集合差集结果&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161711785_c8ec0ce7-9455-4758-8625-7cca2d9fe490.png" alt="161711785_c8ec0ce7-9455-4758-8625-7cca2d9fe490"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161713018_45b300b6-4ba9-49c2-821c-65709471dd51.png" alt="161713018_45b300b6-4ba9-49c2-821c-65709471dd51"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161714314_6eaff2fa-4ed6-4709-80f3-7d7dd5fb9686.png" alt="161714314_6eaff2fa-4ed6-4709-80f3-7d7dd5fb9686"&gt;&lt;/p&gt;
&lt;h3 id="获取多集合差集结果并存在新集合中"&gt;&lt;strong&gt;获取多集合差集结果并存在新集合中&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161715479_19d9afb0-2047-4496-b25e-36a8c30df41f.png" alt="161715479_19d9afb0-2047-4496-b25e-36a8c30df41f"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161716576_8bc59e2f-d547-43c2-bce6-91cedf875d20.png" alt="161716576_8bc59e2f-d547-43c2-bce6-91cedf875d20"&gt;&lt;/p&gt;
&lt;h3 id="获取多集合并集结果"&gt;&lt;strong&gt;获取多集合并集结果&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161717761_7bdc40b9-0646-4ebc-995b-8086cab46522.png" alt="161717761_7bdc40b9-0646-4ebc-995b-8086cab46522"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161718885_77a71b4b-d255-47f2-9085-47e6922755f8.png" alt="161718885_77a71b4b-d255-47f2-9085-47e6922755f8"&gt; &lt;/p&gt;
&lt;h3 id="获取多集合并集结果并存在新集合中"&gt;&lt;strong&gt;获取多集合并集结果并存在新集合中&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161720011_21687d63-fd4d-4c83-bfa5-a5a7b006ce6a.png" alt="161720011_21687d63-fd4d-4c83-bfa5-a5a7b006ce6a"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161721108_59277fb0-986b-407d-b3ba-191f9a9534f6.png" alt="161721108_59277fb0-986b-407d-b3ba-191f9a9534f6"&gt;&lt;/p&gt;
&lt;h3 id="集合内数据移动到新集合中"&gt;&lt;strong&gt;集合内数据移动到新集合中&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161722311_35680c8e-2d8a-4803-b678-071ab6e09058.png" alt="161722311_35680c8e-2d8a-4803-b678-071ab6e09058"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161723574_dd86ce45-2fdc-4d91-a07b-42ebe2a7c44d.png" alt="161723574_dd86ce45-2fdc-4d91-a07b-42ebe2a7c44d"&gt;&lt;/p&gt;
&lt;h2 id="集合set简单应用场景"&gt;&lt;strong&gt;集合(Set)简单应用场景&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;简单模拟抽奖场景，利用Set中随机获取集合中一个值来完成抽奖场景，具体场景为，年会活动，用户扫码加入抽奖活动，每个用户有独自的id，抽奖项为一等奖，抽中者不能再参与抽奖活动。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始用户id&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.SetAdd(&amp;#34;FirstPrize&amp;#34;, &amp;#34;userId1&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.SetAdd(&amp;#34;FirstPrize&amp;#34;, &amp;#34;userId2&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.SetAdd(&amp;#34;FirstPrize&amp;#34;, &amp;#34;userId3&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.SetAdd(&amp;#34;FirstPrize&amp;#34;, &amp;#34;userId4&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.SetAdd(&amp;#34;FirstPrize&amp;#34;, &amp;#34;userId5&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.SetAdd(&amp;#34;FirstPrize&amp;#34;, &amp;#34;userId6&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;完成一轮抽奖活动，随机从参与集合中获取一个用户id。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.SetPop&amp;lt;string&amp;gt;(&amp;#34;FirstPrize&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;效果展示一下,每轮获取一个数据，并从原有集合中删除，保证了下一次抽奖活动不会再抽中原有获奖者。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200614/161724947_a64e4479-032e-4348-92aa-5a7c6161ffbe.png" alt="161724947_a64e4479-032e-4348-92aa-5a7c6161ffbe"&gt;&lt;/p&gt;</description></item><item><title>初探Redis-基础类型List</title><link>/blog/2020-05-24-redis-list/</link><pubDate>Sun, 24 May 2020 10:00:00 +0800</pubDate><guid>/blog/2020-05-24-redis-list/</guid><description>&lt;p&gt;Redis存在五种基础类型：字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。本次列举出List的常用操作。&lt;/p&gt;
&lt;p&gt;Redis官网：&lt;a href="https://redis.io/"&gt;https://redis.io/&lt;/a&gt; &lt;/p&gt;
&lt;h2 id="列表list介绍"&gt;&lt;strong&gt;列表(List)介绍&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Redis列表是简单的字符串列表，按照插入顺序排序。可以添加一个元素到列表的头部（左边）或者尾部（右边）。一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。可按照如下格式查看，整体为链式结构，c语言中，链表功能十分强大，通过指针取得内部存储地址，再通过内部存储地址去取得实际数据(具体怎么操作已还给老师)。可以方便的完成先进先出，先进后出等功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161832982_c3f7120b-6fc2-457a-a553-254cd7d527c1.png" alt="161832982_c3f7120b-6fc2-457a-a553-254cd7d527c1"&gt;
 &lt;/p&gt;
&lt;h2 id="列表list相关操作api"&gt;&lt;strong&gt;列表(List)相关操作Api&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id="lpush添加数据"&gt;&lt;strong&gt;LPush添加数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161834245_878c13a8-4c9d-4199-b2ee-17bef9fa2854.png" alt="161834245_878c13a8-4c9d-4199-b2ee-17bef9fa2854"&gt;&lt;/p&gt;
&lt;p&gt;将链表看成整体，进行Lpush操作，使得新添加的元素作为起始元素。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161835568_696375dd-e63f-4206-8abb-bd82f55ee469.png" alt="161835568_696375dd-e63f-4206-8abb-bd82f55ee469"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161836812_e72cbff0-061c-4a94-bbf5-b5637dffa78d.png" alt="161836812_e72cbff0-061c-4a94-bbf5-b5637dffa78d"&gt;&lt;/p&gt;
&lt;h3 id="rpush添加数据"&gt;&lt;strong&gt;RPush添加数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161837927_2f326795-80cd-491b-9ecb-e2d9ba1570bc.png" alt="161837927_2f326795-80cd-491b-9ecb-e2d9ba1570bc"&gt;&lt;/p&gt;
&lt;p&gt;将链表看成整体，进行Rpush操作，使得新添加的元素作为末位元素。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161839087_e2aa2ebd-8434-4242-b8e4-e900adc5c2da.png" alt="161839087_e2aa2ebd-8434-4242-b8e4-e900adc5c2da"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161840411_e41ff1f5-0cab-479a-a1eb-0da135e105db.png" alt="161840411_e41ff1f5-0cab-479a-a1eb-0da135e105db"&gt;&lt;/p&gt;
&lt;h3 id="限定key存在执行添加数据"&gt;&lt;strong&gt;限定key存在执行添加数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161841640_82566b69-cb74-4eff-abf7-a4e798af9d24.png" alt="161841640_82566b69-cb74-4eff-abf7-a4e798af9d24"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161842752_d5023d63-0eae-4b51-93d8-fa5708de7cb1.png" alt="161842752_d5023d63-0eae-4b51-93d8-fa5708de7cb1"&gt;&lt;/p&gt;
&lt;h3 id="list中指定索引处插入数据替换原有数据"&gt;&lt;strong&gt;List中指定索引处插入数据替换原有数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161843907_6de3e69c-2e7f-42cd-a491-8609be6c2b68.png" alt="161843907_6de3e69c-2e7f-42cd-a491-8609be6c2b68"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161845095_f638e938-60d8-4519-bfb8-e1abb768f89b.png" alt="161845095_f638e938-60d8-4519-bfb8-e1abb768f89b"&gt;&lt;/p&gt;
&lt;h3 id="对list中匹配的数据前或后插入新数据"&gt;&lt;strong&gt;对List中匹配的数据前或后插入新数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161846187_078e7531-389b-42b9-9198-ffac3075c8d7.png" alt="161846187_078e7531-389b-42b9-9198-ffac3075c8d7"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161847341_3b37b641-2591-43ac-82fc-4009a3e762a4.png" alt="161847341_3b37b641-2591-43ac-82fc-4009a3e762a4"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161848457_cc7be2dc-34d0-4901-8855-38308cad681f.png" alt="161848457_cc7be2dc-34d0-4901-8855-38308cad681f"&gt; &lt;/p&gt;
&lt;h3 id="读取list中索引范围内已有数据"&gt;&lt;strong&gt;读取List中索引范围内已有数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161849620_149ca886-afd1-4906-b67f-522a387dc4a5.png" alt="161849620_149ca886-afd1-4906-b67f-522a387dc4a5"&gt;&lt;/p&gt;
&lt;p&gt;通过索引范围读取范围内的数据信息 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161850769_5429203d-2eb5-4925-a025-82d711ddfff0.png" alt="161850769_5429203d-2eb5-4925-a025-82d711ddfff0"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161852010_d946426b-e09c-4cd6-a1a7-d88474ee4e3c.png" alt="161852010_d946426b-e09c-4cd6-a1a7-d88474ee4e3c"&gt;&lt;/p&gt;
&lt;h3 id="读取list中指定索引下已有数据"&gt;&lt;strong&gt;读取List中指定索引下已有数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161853094_7baf77fa-6193-43e1-9300-395bb39d9f72.png" alt="161853094_7baf77fa-6193-43e1-9300-395bb39d9f72"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161854317_f06bac19-5bd4-4d09-a7be-6408ad1b0461.png" alt="161854317_f06bac19-5bd4-4d09-a7be-6408ad1b0461"&gt; &lt;/p&gt;
&lt;h3 id="获取list中数据总数"&gt;&lt;strong&gt;获取List中数据总数&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161855510_62118fdb-2c6e-4bd2-9c95-515afe6e1dbe.png" alt="161855510_62118fdb-2c6e-4bd2-9c95-515afe6e1dbe"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161856694_eee868f1-afac-4a22-a39a-7ec58b712641.png" alt="161856694_eee868f1-afac-4a22-a39a-7ec58b712641"&gt;&lt;/p&gt;
&lt;h3 id="移除list前部数据"&gt;&lt;strong&gt;移除List前部数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161857959_f38c3cb2-f909-4b1c-b950-83baae22b11c.png" alt="161857959_f38c3cb2-f909-4b1c-b950-83baae22b11c"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161859040_68723482-5db9-4fa7-bcd6-8e638c5b9370.png" alt="161859040_68723482-5db9-4fa7-bcd6-8e638c5b9370"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161900259_138099e3-078f-4485-87de-9a1a9a936172.png" alt="161900259_138099e3-078f-4485-87de-9a1a9a936172"&gt;&lt;/p&gt;
&lt;h3 id="移除list尾部数据"&gt;&lt;strong&gt;移除List尾部数据&lt;/strong&gt; &lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161901392_3b094139-a674-48a8-9f01-b13830054d0f.png" alt="161901392_3b094139-a674-48a8-9f01-b13830054d0f"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161902651_368fae08-2c87-4ee4-a7a6-265bfc1b4bdf.png" alt="161902651_368fae08-2c87-4ee4-a7a6-265bfc1b4bdf"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161903761_c84025a4-37f7-4535-b612-1d4061968726.png" alt="161903761_c84025a4-37f7-4535-b612-1d4061968726"&gt;&lt;/p&gt;
&lt;h3 id="精简list只保留索引范围内数据"&gt;&lt;strong&gt;精简List只保留索引范围内数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161904966_68b11746-b92c-440a-84bf-32e77aa036d4.png" alt="161904966_68b11746-b92c-440a-84bf-32e77aa036d4"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161906092_7a53735e-34fe-4176-88ad-6085359c578c.png" alt="161906092_7a53735e-34fe-4176-88ad-6085359c578c"&gt; &lt;/p&gt;
&lt;h3 id="移除list中指定数量的匹配数据"&gt;&lt;strong&gt;移除List中指定数量的匹配数据&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161907204_932013be-7d53-4f9e-b2a3-cd58a682ce6e.png" alt="161907204_932013be-7d53-4f9e-b2a3-cd58a682ce6e"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161908537_f9efa71d-5c61-4949-b829-f40820d8ba3b.png" alt="161908537_f9efa71d-5c61-4949-b829-f40820d8ba3b"&gt;&lt;/p&gt;
&lt;h3 id="等待匹配数据直到获取或超时"&gt;&lt;strong&gt;等待匹配数据直到获取或超时&lt;/strong&gt; &lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161909734_76a87e92-3556-46b1-9fd2-b1a202123c9b.png" alt="161909734_76a87e92-3556-46b1-9fd2-b1a202123c9b"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161911491_25b16ca5-f1c9-458e-8df6-e9b98429e8c9.png" alt="161911491_25b16ca5-f1c9-458e-8df6-e9b98429e8c9"&gt;&lt;/p&gt;
&lt;h3 id="从列表中移除并移入新列表"&gt;&lt;strong&gt;从列表中移除并移入新列表&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161912894_0adc380f-188d-452c-bfab-c65371c85c62.png" alt="161912894_0adc380f-188d-452c-bfab-c65371c85c62"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161914493_d8aaa2a3-0b5e-4195-88a2-31d10584c24d.png" alt="161914493_d8aaa2a3-0b5e-4195-88a2-31d10584c24d"&gt;&lt;/p&gt;
&lt;h3 id="等待从列表中移除并移入新列表直至成功或超时"&gt;&lt;strong&gt;等待从列表中移除并移入新列表直至成功或超时&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161915681_f1cec89a-3d97-42af-9220-5dd8071b9116.png" alt="161915681_f1cec89a-3d97-42af-9220-5dd8071b9116"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161917063_a0ac1937-f7c5-4aa5-94f4-2f2078dcfe31.png" alt="161917063_a0ac1937-f7c5-4aa5-94f4-2f2078dcfe31"&gt;&lt;/p&gt;
&lt;h3 id="列表list简单应用场景"&gt;&lt;strong&gt;列表(List)简单应用场景&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;以哈希用法时的文章列表为例，网站首页分页展示文章信息，而展示前先从list中取得分页后的文章Id集合再从Hash中获取文章概要信息并展示。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始列表数据添加一些种子数据&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.ListLeftPush(&amp;#34;blogId&amp;#34;, &amp;#34;9527&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.ListLeftPush(&amp;#34;blogId&amp;#34;, &amp;#34;9528&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.ListLeftPush(&amp;#34;blogId&amp;#34;, &amp;#34;9529&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.ListLeftPush(&amp;#34;blogId&amp;#34;, &amp;#34;9530&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;模拟增加一条数据并裁剪已有的种子数据集合使得总体数量在给定范围内。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.ListLeftPush(&amp;#34;blogId&amp;#34;, &amp;#34;9547&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service.ListRange&amp;lt;string&amp;gt;(&amp;#34;blogId&amp;#34;, 0, 20);//一个list最多2的32次方-1，范围外的被移除
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;模拟分页获取，从Redis-List中取出等量数据并打印展示。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var page = 1;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var size = 10;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var blogIdsPagedResult = service.ListRange&amp;lt;string&amp;gt;(&amp;#34;blogId&amp;#34;, (page - 1) * size, page * size - 1);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;运行效果，达到预想功能。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200524/161918198_fd09cf98-8b76-45fb-9e0a-e5f9edf2e3aa.png" alt="161918198_fd09cf98-8b76-45fb-9e0a-e5f9edf2e3aa"&gt;&lt;/p&gt;</description></item><item><title>初探Redis-基础类型Hash</title><link>/blog/2020-05-13-redis-hash/</link><pubDate>Wed, 13 May 2020 10:00:00 +0800</pubDate><guid>/blog/2020-05-13-redis-hash/</guid><description>&lt;p&gt;Redis存在五种基础类型：字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。本次列举出Hash的常用操作。&lt;/p&gt;
&lt;p&gt;Redis官网：&lt;a href="https://redis.io/"&gt;https://redis.io/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="哈希hash介绍"&gt;&lt;strong&gt;哈希(Hash)介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;哈希(Hash)是Redis 中基本的类型，一个 key 对应着一个集合，其中集合中以field-value形式组成一个字典。可以理解为一个字典中，单项值部分又是一个字典。每个哈希(Hash)可以存储2^32-1个键值对。数据序列化成json格式为{key:{field1:value1,field2:value2&amp;hellip;&amp;hellip;fieldN:valueN}}。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;内部用zipmap结构存储，以节约空间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;更新方便，只需更新field对应value值即可更新。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/161959945_477aacd6-0cab-48e9-88f8-43316dbbac0c.png" alt="161959945_477aacd6-0cab-48e9-88f8-43316dbbac0c"&gt;
 &lt;/p&gt;
&lt;h3 id="哈希hash常用api"&gt;&lt;strong&gt;哈希(Hash)常用Api&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="设置field-value"&gt;&lt;strong&gt;设置field-value&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162001254_f85795d5-1cdd-4a37-8b43-de029ee87595.png" alt="162001254_f85795d5-1cdd-4a37-8b43-de029ee87595"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162002487_fc59af6e-58fc-4d0d-9555-3cd8ce565519.png" alt="162002487_fc59af6e-58fc-4d0d-9555-3cd8ce565519"&gt;&lt;/p&gt;
&lt;h4 id="设置初次添加的field-value"&gt;&lt;strong&gt;设置初次添加的field-value&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162003690_b91a8830-b1fe-46c5-87d6-3640bddf3295.png" alt="162003690_b91a8830-b1fe-46c5-87d6-3640bddf3295"&gt;&lt;/p&gt;
&lt;h4 id="获取值"&gt;&lt;strong&gt;获取值&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162005232_ffa787dd-3df2-46cd-8ae0-8221182ece3d.png" alt="162005232_ffa787dd-3df2-46cd-8ae0-8221182ece3d"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162006327_71a7342d-552c-4e0b-9ae2-019cba270a52.png" alt="162006327_71a7342d-552c-4e0b-9ae2-019cba270a52"&gt; &lt;/p&gt;
&lt;h4 id="批量设置值"&gt;&lt;strong&gt;批量设置值&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162007498_71c4e3c7-6a98-4689-a315-781d334151ac.png" alt="162007498_71c4e3c7-6a98-4689-a315-781d334151ac"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162008678_c0320c32-8137-4d7c-98a9-b5b75d16ddee.png" alt="162008678_c0320c32-8137-4d7c-98a9-b5b75d16ddee"&gt;&lt;/p&gt;
&lt;h4 id="批量获取值"&gt;&lt;strong&gt;批量获取值&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162009834_dd2644ab-1c22-4ff2-9432-ea57e591c18d.png" alt="162009834_dd2644ab-1c22-4ff2-9432-ea57e591c18d"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162010970_d011da8e-3083-43ab-88c9-3f52c4a0ae65.png" alt="162010970_d011da8e-3083-43ab-88c9-3f52c4a0ae65"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;获取key下集合信息&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162012304_4ad2c682-2a98-4b37-82ba-1f6be39cabb9.png" alt="162012304_4ad2c682-2a98-4b37-82ba-1f6be39cabb9"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162013688_48237035-35a9-4ebd-ae87-5529f3cecea8.png" alt="162013688_48237035-35a9-4ebd-ae87-5529f3cecea8"&gt; &lt;/p&gt;
&lt;h4 id="获取key下所有field"&gt;&lt;strong&gt;获取key下所有field&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162014856_e3ad5033-d46e-4cf4-a1e1-90851bbe40b8.png" alt="162014856_e3ad5033-d46e-4cf4-a1e1-90851bbe40b8"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162016140_4ef52644-30bb-4a14-ae92-e41799932655.png" alt="162016140_4ef52644-30bb-4a14-ae92-e41799932655"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;获取key下所有value&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162017331_47d097ee-ced2-4551-8ed6-5c3b58c5e605.png" alt="162017331_47d097ee-ced2-4551-8ed6-5c3b58c5e605"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162018481_2cd29e9c-ddea-40f6-b8bf-d5fe91dcc629.png" alt="162018481_2cd29e9c-ddea-40f6-b8bf-d5fe91dcc629"&gt; &lt;/p&gt;
&lt;h4 id="删除key下的field"&gt;&lt;strong&gt;删除key下的field&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162019607_9199de11-9569-4724-a148-e3063a63386d.png" alt="162019607_9199de11-9569-4724-a148-e3063a63386d"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162020784_8519a311-4cf3-4db8-a6b9-c40148b8e5b9.png" alt="162020784_8519a311-4cf3-4db8-a6b9-c40148b8e5b9"&gt;
 &lt;/p&gt;
&lt;h3 id="哈希hash不常用api"&gt;&lt;strong&gt;哈希(Hash)不常用Api&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="查询key下field是否存在"&gt;&lt;strong&gt;查询key下field是否存在&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162021939_e0f1f39a-e47d-4291-8ecf-e519bc975977.png" alt="162021939_e0f1f39a-e47d-4291-8ecf-e519bc975977"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162023091_e69400bb-2706-44dd-9135-72dd5cc5afd7.png" alt="162023091_e69400bb-2706-44dd-9135-72dd5cc5afd7"&gt;&lt;/p&gt;
&lt;h4 id="递增key下field映射值n"&gt;&lt;strong&gt;递增key下field映射值(△n)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162024218_720fbf6b-a962-4850-8733-f51e7e967fdb.png" alt="162024218_720fbf6b-a962-4850-8733-f51e7e967fdb"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162025409_4e95728d-b626-4ff5-89dd-ae0450ce372d.png" alt="162025409_4e95728d-b626-4ff5-89dd-ae0450ce372d"&gt;&lt;/p&gt;
&lt;h4 id="递增key下field映射值n-浮点数"&gt;&lt;strong&gt;递增key下field映射值(△n 浮点数)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162026505_cc7965df-4705-4c3a-acf1-d3cd0bce187b.png" alt="162026505_cc7965df-4705-4c3a-acf1-d3cd0bce187b"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162027631_f98645e8-2f11-469a-bc56-b074f71ca479.png" alt="162027631_f98645e8-2f11-469a-bc56-b074f71ca479"&gt;&lt;/p&gt;
&lt;h4 id="获取key下field数量"&gt;&lt;strong&gt;获取key下field数量&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162028753_13aae06b-870e-4284-9ec1-5c8f2ecdd618.png" alt="162028753_13aae06b-870e-4284-9ec1-5c8f2ecdd618"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162029907_652916eb-0ed8-4fd1-883b-1b47bd440589.png" alt="162029907_652916eb-0ed8-4fd1-883b-1b47bd440589"&gt;&lt;/p&gt;
&lt;h4 id="获取key下field映射值的长度"&gt;&lt;strong&gt;获取key下field映射值的长度&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162031070_f3519524-3998-430e-addb-2e0a338e7159.png" alt="162031070_f3519524-3998-430e-addb-2e0a338e7159"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162032218_083bb51e-aa6e-4d63-be47-92f96b7bde2f.png" alt="162032218_083bb51e-aa6e-4d63-be47-92f96b7bde2f"&gt;&lt;/p&gt;
&lt;h4 id="迭代哈希表中的键值对"&gt;&lt;strong&gt;迭代哈希表中的键值对&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162033490_0917d8ca-10e8-46c4-b8e2-24b953088cdd.png" alt="162033490_0917d8ca-10e8-46c4-b8e2-24b953088cdd"&gt;
数量有限，返回游标仍然是0。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162034590_29be5f7c-606d-4b99-8065-f85b9da60ad3.png" alt="162034590_29be5f7c-606d-4b99-8065-f85b9da60ad3"&gt;
 &lt;/p&gt;
&lt;h3 id="哈希hash简单应用场景"&gt;哈希(&lt;strong&gt;Hash&lt;/strong&gt;)简单应用场景&lt;/h3&gt;
&lt;p&gt;模拟文章概要信息的Hash存储并快速获取，场景如下，网站首页分页展示文章概要信息，定义为热区文章，如某文章更改内容，则先判定是否该文章id属于热区，如属于则更改Hash中field对应的值。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;模拟文章数据，设置一堆种子&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var blogOutlineInfoList = new List&amp;lt;BlogOutlineInfo&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; new BlogOutlineInfo()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = &amp;#34;9527&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = &amp;#34;CSharp&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Author = &amp;#34;微笑刺客&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CreateTime = DateTime.Now,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Content = &amp;#34;CSharp从入门到升仙&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CommentCount =0,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ReadCount = 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecommendCount = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; new BlogOutlineInfo()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = &amp;#34;9528&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = &amp;#34;Mysql&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Author = &amp;#34;微笑刺客&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CreateTime = DateTime.Now,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Content = &amp;#34;Mysql从入门到遁地&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CommentCount =0,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ReadCount = 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecommendCount = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; new BlogOutlineInfo()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = &amp;#34;9529&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = &amp;#34;Docker&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Author = &amp;#34;微笑刺客&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CreateTime = DateTime.Now,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Content = &amp;#34;Docker从入门到转行&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CommentCount =0,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ReadCount = 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecommendCount = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;将种子数据加入到Redis中(缓存预热)&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;foreach (var blogOutlineInfo in blogOutlineInfoList)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //设置Redis_key
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var blogOutlineInfoKey = $&amp;#34;blogOutlineInfo_{blogOutlineInfo.Id}&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //初始化属性值
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.HashSet(blogOutlineInfoKey, nameof(BlogOutlineInfo.Title), blogOutlineInfo.Title);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.HashSet(blogOutlineInfoKey, nameof(BlogOutlineInfo.Content), blogOutlineInfo.Content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.HashSet(blogOutlineInfoKey, nameof(BlogOutlineInfo.Author), blogOutlineInfo.Author);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.HashSet(blogOutlineInfoKey, nameof(BlogOutlineInfo.CreateTime), blogOutlineInfo.CreateTime);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.HashSet(blogOutlineInfoKey, nameof(BlogOutlineInfo.CommentCount), blogOutlineInfo.CommentCount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.HashSet(blogOutlineInfoKey, nameof(BlogOutlineInfo.ReadCount), blogOutlineInfo.ReadCount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service.HashSet(blogOutlineInfoKey, nameof(BlogOutlineInfo.RecommendCount), blogOutlineInfo.RecommendCount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;网站中对于某页需要的数据，根据当前页面，先从Redis的List中获取相关文章Id，然后再从Hash中获取文章概要信息。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200513/162035711_b38c0758-5174-4b4b-ac60-a5acb3f1555a.png" alt="162035711_b38c0758-5174-4b4b-ac60-a5acb3f1555a"&gt;&lt;/p&gt;</description></item><item><title>初探Redis-基础类型String</title><link>/blog/2020-04-26-redis-string/</link><pubDate>Sun, 26 Apr 2020 10:00:00 +0800</pubDate><guid>/blog/2020-04-26-redis-string/</guid><description>&lt;p&gt;Redis存在五种基础类型：字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。String的出镜率算是最高的。本次列举出String的常用操作。&lt;/p&gt;
&lt;p&gt;Redis官网：&lt;a href="https://redis.io/"&gt;https://redis.io/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="字符串string介绍"&gt;&lt;strong&gt;字符串(String)介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;字符串(String)是Redis 中最基本的类型，一个 key 对应着一个 value**，**可以理解为字典类型，key-value形式。该类型二进制安全的，对应值可以存储任何数据，如图片或者序列化后的对象，对应值最大可以存储 &lt;code&gt;512MB&lt;/code&gt; 的数据。但不是建议去最大化存储，存储数据尽量小为最佳使用。从操作出发，常用操作为这两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;存储和操作字符串；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;存储和计算int和float类型的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162131816_4953148f-9991-4dbf-a4aa-7e5f37aec5b7.png" alt="162131816_4953148f-9991-4dbf-a4aa-7e5f37aec5b7"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="字符串string常用api"&gt;&lt;strong&gt;字符串(String)常用Api&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="设置值"&gt;&lt;strong&gt;设置值&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162133119_8ff91d7d-7ebe-42aa-97a9-b58344876e7c.png" alt="162133119_8ff91d7d-7ebe-42aa-97a9-b58344876e7c"&gt;
设置30秒生存时间，且必须firstkey不存在。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162134227_4dbaa4b7-c822-4369-b6f7-6787db9c329a.png" alt="162134227_4dbaa4b7-c822-4369-b6f7-6787db9c329a"&gt;&lt;/p&gt;
&lt;h4 id="获取值"&gt;&lt;strong&gt;获取值&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162135455_9bf2fd64-4d92-4be8-8e85-63187f831738.png" alt="162135455_9bf2fd64-4d92-4be8-8e85-63187f831738"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162136536_c3c031fe-84fa-41bf-9e9d-b1da798226a9.png" alt="162136536_c3c031fe-84fa-41bf-9e9d-b1da798226a9"&gt;&lt;/p&gt;
&lt;h4 id="批量设置值与批量获取值--162137696_"&gt;&lt;strong&gt;批量设置值与批量获取值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162137696_9a3dadac-05bb-4406-915b-d081f54ebc0f.png" alt="162137696_9a3dadac-05bb-4406-915b-d081f54ebc0f"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162138837_995187b9-1bd3-43ee-9a0c-00a4e67dad9c.png" alt="162138837_995187b9-1bd3-43ee-9a0c-00a4e67dad9c"&gt;&lt;/p&gt;
&lt;h4 id="递增或递减值1"&gt;&lt;strong&gt;递增或递减值(△1)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162140012_cdd5a955-8d29-45e8-90e8-687f7b16c18f.png" alt="162140012_cdd5a955-8d29-45e8-90e8-687f7b16c18f"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162141154_f6f5eec0-54c6-4fe5-9b29-fa3df13e746b.png" alt="162141154_f6f5eec0-54c6-4fe5-9b29-fa3df13e746b"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;递增或递减值(△n)&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162142349_ebfba8c5-d516-49cd-a974-a036eb7337c0.png" alt="162142349_ebfba8c5-d516-49cd-a974-a036eb7337c0"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162143495_36f0228b-bbef-4ecd-9e90-841c51bcd4d6.png" alt="162143495_36f0228b-bbef-4ecd-9e90-841c51bcd4d6"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;递增浮点数值(△n)&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162144591_6d26dbe5-4130-42fe-a66a-e11b610f789f.png" alt="162144591_6d26dbe5-4130-42fe-a66a-e11b610f789f"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162145830_afad1400-8a30-4c65-8ff6-a144b58843cc.png" alt="162145830_afad1400-8a30-4c65-8ff6-a144b58843cc"&gt;&lt;/p&gt;
&lt;h4 id="获取旧值并设置新值--162146897_"&gt;&lt;strong&gt;获取旧值并设置新值&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162146897_525c2c46-0df4-4a53-a840-99518ef1f777.png" alt="162146897_525c2c46-0df4-4a53-a840-99518ef1f777"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162148031_73af0329-fc5b-42e1-9364-bc67121771ba.png" alt="162148031_73af0329-fc5b-42e1-9364-bc67121771ba"&gt;
 &lt;/p&gt;
&lt;h3 id="字符串strng不常用api"&gt;&lt;strong&gt;字符串(Strng)不常用Api&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="追加值162149329_"&gt;&lt;strong&gt;追加值&lt;/strong&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162149329_370d7b51-e060-47a2-b3dc-b1c70f2e6d04.png" alt="162149329_370d7b51-e060-47a2-b3dc-b1c70f2e6d04"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162150421_57c628b3-ad98-48ea-88e7-a2e4a20de4e9.png" alt="162150421_57c628b3-ad98-48ea-88e7-a2e4a20de4e9"&gt;&lt;/p&gt;
&lt;h4 id="查询key162151917_"&gt;&lt;strong&gt;查询key&lt;/strong&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162151917_65cbc0a8-906f-4169-bfdf-587ad58f99b9.png" alt="162151917_65cbc0a8-906f-4169-bfdf-587ad58f99b9"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162153006_86637f92-4658-40d4-aa8d-f6041fb6d8a5.png" alt="162153006_86637f92-4658-40d4-aa8d-f6041fb6d8a5"&gt;&lt;/p&gt;
&lt;h4 id="删除key162154060_"&gt;&lt;strong&gt;删除key&lt;/strong&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162154060_e703a57b-851d-45b8-accd-5c4e8095f2dd.png" alt="162154060_e703a57b-851d-45b8-accd-5c4e8095f2dd"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162155235_0607e72f-e079-4dcd-afe9-ec374664254d.png" alt="162155235_0607e72f-e079-4dcd-afe9-ec374664254d"&gt;&lt;/p&gt;
&lt;h4 id="设置剩余生存时间--162156387_"&gt;&lt;strong&gt;设置剩余生存时间&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162156387_bb0f9abf-24e8-4e66-bcd7-4801d1749356.png" alt="162156387_bb0f9abf-24e8-4e66-bcd7-4801d1749356"&gt;&lt;/h4&gt;
&lt;p&gt;设置firstkey生存时间30秒，并通过ttl查看剩余生存时间。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162157525_96823b39-f87f-4d83-b7d4-1153216f68a5.png" alt="162157525_96823b39-f87f-4d83-b7d4-1153216f68a5"&gt;&lt;/p&gt;
&lt;h4 id="获取剩余生存时间--162158654_"&gt;&lt;strong&gt;获取剩余生存时间&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162158654_e0a9c512-5956-4d4b-a178-ef70d4eaccae.png" alt="162158654_e0a9c512-5956-4d4b-a178-ef70d4eaccae"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162159705_0145bc7f-29b1-461b-ae17-e8b9d8e1d203.png" alt="162159705_0145bc7f-29b1-461b-ae17-e8b9d8e1d203"&gt;&lt;/p&gt;
&lt;h4 id="设置永久生存"&gt;&lt;strong&gt;设置永久生存&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162200882_f9ea62dc-830b-443c-902f-55e09f3f7a8c.png" alt="162200882_f9ea62dc-830b-443c-902f-55e09f3f7a8c"&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162202031_c2e695dc-3517-4cc3-a713-6b9b0fe06205.png" alt="162202031_c2e695dc-3517-4cc3-a713-6b9b0fe06205"&gt;&lt;/p&gt;
&lt;h4 id="获取字符串长度--162203240_"&gt;&lt;strong&gt;获取字符串长度&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162203240_de3c1334-9a15-49b1-aa71-84b9b342e8d0.png" alt="162203240_de3c1334-9a15-49b1-aa71-84b9b342e8d0"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162204483_abcdb900-b0c3-41fa-bbf0-6e400b171abc.png" alt="162204483_abcdb900-b0c3-41fa-bbf0-6e400b171abc"&gt;&lt;/p&gt;
&lt;h4 id="覆盖指定位置开始处的字符--162205620_"&gt;&lt;strong&gt;覆盖指定位置开始处的字符&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162205620_5e7ef337-11ae-4f9f-a235-dd1bf3199eaa.png" alt="162205620_5e7ef337-11ae-4f9f-a235-dd1bf3199eaa"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162206833_f5a66007-8d4d-48ab-bd81-d529e1273397.png" alt="162206833_f5a66007-8d4d-48ab-bd81-d529e1273397"&gt;&lt;/p&gt;
&lt;h4 id="获取指定位置范围字符串--162208384_"&gt;&lt;strong&gt;获取指定位置范围字符串&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162208384_f4c99b01-9003-4fc4-8dfe-d3b2b5e6c095.png" alt="162208384_f4c99b01-9003-4fc4-8dfe-d3b2b5e6c095"&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162209677_2abeb87d-06a2-4186-a4bc-1d61ede76a6f.png" alt="162209677_2abeb87d-06a2-4186-a4bc-1d61ede76a6f"&gt;
 &lt;/p&gt;
&lt;h3 id="字符串string简单应用场景"&gt;字符串(String)简单应用场景&lt;/h3&gt;
&lt;p&gt;模拟限量商品秒杀场景，来运用一下redis。采用多线程随机请求限量商品，并且用redis控制商品数量的减少。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;采用StackExchange.Redis的Nuget包来连接和管理Redis。并封装一套Redis的操作类。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162210890_7f36ce72-2ca5-4193-bc95-36d7be24c29f.png" alt="162210890_7f36ce72-2ca5-4193-bc95-36d7be24c29f"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;采用单例模式管理Redis的实例。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162212018_ceff8632-a895-4900-bd1b-e0fdc700dfc8.png" alt="162212018_ceff8632-a895-4900-bd1b-e0fdc700dfc8"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;模拟多用户限量秒杀模型。通过Redis的incr和decr操作控制商品数量。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 超卖模型
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class OverSell
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private static bool IsGoOn = true;//秒杀活动是否结束
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static void Run()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RedisBase.StringService.StringSet(&amp;#34;Stock&amp;#34;, 10);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; for (int i = 0; i &amp;lt; 5000; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; int k = i;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task.Run(() =&amp;gt;//每个线程就是一个模拟用户请求
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (IsGoOn)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var index = RedisBase.StringService.StringDecrement(&amp;#34;Stock&amp;#34;);//-1并且返回
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (index &amp;gt;= 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine($&amp;#34;{k.ToString(&amp;#34;000&amp;#34;)} Success，Product Index:{index}&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; else
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (IsGoOn)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IsGoOn = false;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine($&amp;#34;{k.ToString(&amp;#34;000&amp;#34;)} Failed，Product Index:{index}&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; else
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine($&amp;#34;{k.ToString(&amp;#34;000&amp;#34;)} Finish......&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;运行效果，部分线程抢购到商品，部分线程同步抢购时，商品没有了返回负数，部分线程抢购时，活动已结束。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162213352_ed0cae11-a74a-4814-aee7-20294968bcea.png" alt="162213352_ed0cae11-a74a-4814-aee7-20294968bcea"&gt;
 &lt;/p&gt;</description></item><item><title>初探Redis-基础篇</title><link>/blog/2020-04-25-redis-basicusage/</link><pubDate>Sat, 25 Apr 2020 10:00:00 +0800</pubDate><guid>/blog/2020-04-25-redis-basicusage/</guid><description>&lt;p&gt;作为向Web而生的Redis，现已经使用得十分广泛了。依靠其高性能、简洁设计等深受开发者们喜欢。对Redis从基础学起，抱着知其然到知其所以然的想法，先学会怎么用，再去深入了解内部运转。&lt;/p&gt;
&lt;p&gt;Redis官网：&lt;a href="https://redis.io/"&gt;https://redis.io/&lt;/a&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="redis简单介绍"&gt;Redis简单介绍&lt;/h3&gt;
&lt;p&gt;Redis英文全称为Remote Dictionary Server，采用C语言开发的开源，基于内存的键值存储器。常被用来作为数据库(非关系型)，缓存，消息队列等。Redis本身是单进程单线程的，单机每秒读处理11万次+，写处理8万次+，提供插入、查询、固化和集群功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162243579_c86e31d2-4c4e-4529-95e7-552474abf407.png" alt="162243579_c86e31d2-4c4e-4529-95e7-552474abf407"&gt;&lt;/p&gt;
&lt;h3 id="redis安装"&gt;Redis安装&lt;/h3&gt;
&lt;p&gt;Redis安装方式有多种，本着快捷，方便原则(实则为懒)，依照本地环境进行快速搭建。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;本地环境为Deepin 15.11，直接采用Docker安装，操作简单且管理方便。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如本地没有安装Portainer管理工具，则可以通过命令形式拉取redis镜像。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo docker pull redis
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;再通过命令创建一个redis的容器，即可本地开始玩转redis了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo docker run --name rediscontainer -d -p 6379:6379 redis
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162244732_a41404d1-70cb-4cb8-9c26-46e9bcd60834.png" alt="162244732_a41404d1-70cb-4cb8-9c26-46e9bcd60834"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如本地有安装Portainer管理工具，则直接通过工具内完成redis的镜像下载与容器创建。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162246484_d26ca5da-2dc7-499e-b895-939581746570.png" alt="162246484_d26ca5da-2dc7-499e-b895-939581746570"&gt;
2、再通过安装面板工具方便管理Redis Server。Deepin商店自带Redis管理面板RedisPlus。依靠面板，方便监控及查询相关数据。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162248274_6cf51dbe-1764-498a-96e8-9a37de322a63.png" alt="162248274_6cf51dbe-1764-498a-96e8-9a37de322a63"&gt;
或通过redis-cli处理，输入以下命令，通过命令行形式操控redis。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo docker run -it rediscontainer redis-cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162250300_150eb001-f700-456b-9084-3c8f16fd2a19.png" alt="162250300_150eb001-f700-456b-9084-3c8f16fd2a19"&gt;
  &lt;/p&gt;
&lt;h3 id="redis内置数据类型"&gt;Redis内置数据类型&lt;/h3&gt;
&lt;p&gt;Redis作为键值存储器，键的类型只允许为字符串。值的类型有五种基础类型：字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)，还有一些高级类型如bitmap、布隆过滤器、stream等。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200425/162251402_810fc15e-5d42-4963-9991-6a4ffadebf7f.png" alt="162251402_810fc15e-5d42-4963-9991-6a4ffadebf7f"&gt;&lt;/p&gt;
&lt;h3 id="redis应用场景"&gt;Redis应用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;缓存热点数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实时排行版&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息队列&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分布式锁&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据缓存&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;限量控制，计数器等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;任务队列&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;过滤器、条件排序等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&amp;hellip;&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;应用场景十分丰富，对Redis的掌握也逐渐成为后端工程师必备技能之一了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-04-25，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>ABP框架开发-版本管理</title><link>/blog/2020-04-12-abp-versionmanagement/</link><pubDate>Sun, 12 Apr 2020 10:00:00 +0800</pubDate><guid>/blog/2020-04-12-abp-versionmanagement/</guid><description>&lt;p&gt;多租户系统中，针对于不同租户开放不同功能，或是按照不同功能进行收费管理，需要从宿主本身去管理租户的版本信息，如同酒店人员对不同房间收取不同费用，依据房间内部设施，房间大小等设置不同收费标准。Abp系统中默认是多租户的，并且在Zero模块中实现了版本管理功能。 &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="设计前提"&gt;&lt;strong&gt;设计前提&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;基于Abp进行了相关限制，我将多租户变成了单租户，不允许添加新的租户，由于日常接触中，发现除了云平台这种SaaS需要多租户，对于企业客户来讲，自备物理服务器或自购云服务器是常有的事情，因此对于该部分客户而言，多租户也就没有太多意义，但是从软件公司本身考虑，一套软件能够销售多家客户，能够通过简单配置，开放关闭某些功能，以此来适应客户功能需求，是最佳选项了。因此对于这两种情况考虑后，对于本系统而言，采用的是单租户+宿主形式的，企业客户使用单租户，宿主形式留给软件公司方便配置单租户实际需要的功能。&lt;/p&gt;
&lt;p&gt;在Zero中，已经默认实现了版本管理，但是对于非收费版本的页面管理，应用服务等没有具体代码实现。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="版本管理"&gt;&lt;strong&gt;版本管理&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;应用层增加版本应用服务，对于版本需要进行的用户操作归纳为三个。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可查看现有版本列表；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可对现有版本信息及版本拥有的功能项进行编辑更改；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可增加或是删除版本；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 版本管理应用服务接口
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IEditionAppService : IApplicationService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 获取全部版本列表
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;ListResultDto&amp;lt;EditionListDto&amp;gt;&amp;gt; GetEditionsList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 获取版本用于编辑
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;param name=&amp;#34;input&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;GetEditionForEditOutput&amp;gt; GetEditionForEdit(NullableIdDto input);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 创建或更新版本
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;param name=&amp;#34;input&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task CreateOrUpdateEdition(CreateOrUpdateEditionInput input);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 删除版本
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;param name=&amp;#34;input&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task DeleteEdition(EntityDto input);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 租户更换版本
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;param name=&amp;#34;input&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task MoveTenantsToAnotherEdition(MoveTenantsToAnotherEditionDto input);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;实现应用服务&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;实现版本应用服务接口，以创建版本为例，顶部权限验证，增加版本信息，并且设置该版本所拥有的功能项，对于版本领域服务，Zero模块提供了完整实现AbpEditionManager，只需调用即可。&lt;/p&gt;</description></item><item><title>UML图例之时序图</title><link>/blog/2020-03-20-uml-sequence/</link><pubDate>Fri, 20 Mar 2020 10:00:00 +0800</pubDate><guid>/blog/2020-03-20-uml-sequence/</guid><description>&lt;p&gt;利用时序图显示对象之间交互，通过描述对象之间发送消息的时间顺序显示多个对象之间的动态协作，用来表示用例中的行为顺序。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="时序图特点"&gt;&lt;strong&gt;时序图特点&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;描述用例的具体细节。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对复杂的调用、函数和操作的逻辑关系进行建模。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;表明对象、组件之间是如何交互的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;理解旧代码，规划新场景。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="基本元素"&gt;&lt;strong&gt;基本元素&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;角色(Actor)、对象(Object)、生命线(Lifeline)、控制焦点(Activation)、消息(Message)、自关联消息、组合片段。前6种较为常用，也更为重要。&lt;/p&gt;
&lt;h4 id="角色"&gt;角色&lt;/h4&gt;
&lt;p&gt;系统角色，可以是人、及其甚至其他的系统或者子系统。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162432722_6f2cf4ec-ad1e-4b75-ae29-c3f483ea70ad.png" alt="162432722_6f2cf4ec-ad1e-4b75-ae29-c3f483ea70ad"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h4 id="对象"&gt;对象&lt;/h4&gt;
&lt;p&gt;整个交互过程中的实体，可以是一个具体的类也可以是另外一个系统。对象可以用下面的图表示：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162433964_650e507b-d92d-435f-8e8e-7b85d94ac183.png" alt="162433964_650e507b-d92d-435f-8e8e-7b85d94ac183"&gt;
对象包括三种命名方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对象名和类名；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只显示类名不显示对象名，匿名类；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只显示对象名不显示类名；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="生命线"&gt;生命线&lt;/h4&gt;
&lt;p&gt;时序图中每个对象和底部中心都有一条垂直的虚线，这就是对象的生命线(对象的时间线)。以一条垂直的虚线表示。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162435038_c2001ed7-0303-444a-8fff-d22cea451a32.png" alt="162435038_c2001ed7-0303-444a-8fff-d22cea451a32"&gt;&lt;/p&gt;
&lt;h4 id="控制焦点"&gt;控制焦点&lt;/h4&gt;
&lt;p&gt;控制焦点代表时序图中在对象时间线上某段时期执行的操作，表示该操作的有效时长。以一个很窄的矩形表示。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162436194_24b73d3a-f3dd-41b3-aa72-3270e4f3a776.png" alt="162436194_24b73d3a-f3dd-41b3-aa72-3270e4f3a776"&gt;&lt;/p&gt;
&lt;h4 id="消息"&gt;消息&lt;/h4&gt;
&lt;p&gt;表示对象间相互发送的信息。消息分为三种类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;同步消息(Synchronous Message)：消息的发送者把控制传递给消息的接收者，然后停止活动，等待消息的接收者放弃或者返回控制。用来表示同步的意义。以一条实线+实心箭头表示。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;异步消息(Asynchronous Message)：消息发送者通过消息把信号传递给消息的接收者，然后继续自己的活动，不等待接受者返回消息或者控制。异步消息的接收者和发送者是并发工作的。以一条实线+大于号表示。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;返回消息(Return Message)：返回消息表示从过程调用返回。以小于号+虚线表示。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162437349_7f2db185-c7c1-486d-ad5d-26333b7bbbe6.png" alt="162437349_7f2db185-c7c1-486d-ad5d-26333b7bbbe6"&gt;&lt;/p&gt;
&lt;h4 id="自关联消息"&gt;自关联消息&lt;/h4&gt;
&lt;p&gt;表示方法的自身调用或者对象内一个方法调用另一个方法。以一个半闭合的长方形+下方实心剪头表示。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162438456_9d103704-93b6-4ef0-92a0-996de8de17f2.png" alt="162438456_9d103704-93b6-4ef0-92a0-996de8de17f2"&gt;&lt;/p&gt;
&lt;h4 id="组合片段"&gt;组合片段&lt;/h4&gt;
&lt;p&gt;组合片段用来解决交互执行的条件和方式，它允许在序列图中直接表示逻辑组件，用于通过指定条件或子进程的应用区域，为任何生命线的任何部分定义特殊条件和子进程。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ref：引用其他地方定义的组合片段；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;alt：在一组行为中根据特定的条件选择某个交互；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;opt：表示一个可选的行为；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;break：提供了和编程语言中的break类拟的机制；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;par：支持交互片段的并发执行；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;seq：强迫交互按照特定的顺序执行；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;strict：明确定义了一组交互片段的执行顺序；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;neg：用来标志不应该发生的交互；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;region：标志在组合片段中先于其他交互片断发生的交互；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ignore：明确定义了交互片段不应该响应的消息；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;consider：明确标志了应该被处理的消息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;assert：标志了在交互片段中作为事件唯一的合法继续者的操作数；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;loop：说明交互片段会被重复执行&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162439618_e7085ed6-d9ff-4a82-96d8-7bd61549e006.png" alt="162439618_e7085ed6-d9ff-4a82-96d8-7bd61549e006"&gt;
 &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对接WPS在线编辑服务时，采用的时序图，依照时间顺序执行，最终获取后台服务器中文件，在前端js中在线编辑word文档。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200320/162441143_51517357-6ae0-49e4-b0fc-3e0f5457fb7d.png" alt="162441143_51517357-6ae0-49e4-b0fc-3e0f5457fb7d"&gt;&lt;/p&gt;
&lt;p&gt;至此，对于UML时序图的相关内容做了大概的总结，对于设计时序图还是查看其他系统、对接其他系统时，提供的时序图，知道如何识别，如何设计。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-03-20，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>UML图例之状态图</title><link>/blog/2020-02-06-uml-state/</link><pubDate>Thu, 06 Feb 2020 10:00:00 +0800</pubDate><guid>/blog/2020-02-06-uml-state/</guid><description>&lt;p&gt;利用状态图模拟响应系统，反应系统模型声明周期，将静态的业务可以动态流动起来，按照预定的状态进行业务描述。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="基本概念"&gt;&lt;strong&gt;基本概念&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;状态(state)是指在对象的生命期中的某个条件或状况，在此期间对象将满足某些条件、执行某些活动或等待某些事件。所有对象都具有状态，状态是对象执行了一系列活动的结果，当某个事件发生后，对象的状态发生变化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;状态图(statechart diagram)用来描述一个特定的对象所有可能的状态,以及由于各种事件的发生而引起的状态之间的转移和变化。并不是所有的类都需要画状态图，有明确意义的状态，在不同状态下行为有所不同的类才需要画状态图。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;状态机是计算机科学理论的一部分，但UML中的状态机模型主要是基于David Harel所做的扩展，是用来展示状态与状态之间转换的图。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="基本元素"&gt;&lt;strong&gt;基本元素&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="状态的组成部分"&gt;&lt;strong&gt;状态的组成部分&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;状态名(name)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;进入／退出动作(entry／exit action)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内部转换(internal transition)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;子状态(substate)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;延迟事件(defferred event)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h4 id="状态图的要素"&gt;&lt;strong&gt;状态图的要素&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;**椭圆或圆角矩形：**表示对象的一种状态，椭圆内部填写状态名&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**箭头：**表示从箭头出发的状态可以转换到箭头指向的状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**事件：**引起状态转换的原因。事件名可在箭头线上方标出&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**条件：**事件名后加方括号，括号内写状态转换条件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**动作：**转换激活时的操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**实心圆：**初始状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**内部实心的同心圆：**最终状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h4 id="状态的类型"&gt;&lt;strong&gt;状态的类型&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;①按状态过程分：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162512512_1a656819-421e-4aab-9133-f6e26eadec50.png" alt="162512512_1a656819-421e-4aab-9133-f6e26eadec50"&gt;
一个状态图只能有一个初态，但终态可以有一个或多个，也可以没有终态。&lt;/p&gt;
&lt;p&gt;中间状态包括两个区域：名字域和内部转移域，如图所示。其中内部转移域是可选的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162513674_eb66f105-e8f7-45d1-8f18-7764024c3087.png" alt="162513674_eb66f105-e8f7-45d1-8f18-7764024c3087"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;**entry/turnOn：**当转入该状态时，做开灯动作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**do/blinkFivetimes：**当处于该状态时，灯闪烁5次。do活动是只在状态内出现的活动，不能附加到转换上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**event poweroff/powerSupplySelf：**当电源关闭事件发生时，用自备电源。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**exit/turnOff：**当转出该状态时，做关灯动作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**event selfTest/defer：**当selfTest事件发生时，对象将延迟响应，到别的状态中再处理，用defer这个特定动作表示延迟。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;②含有子状态的状态被称为组合或嵌套状态&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162515411_c3061ee0-f378-4936-9bcd-690b4f5dd19b.png" alt="162515411_c3061ee0-f378-4936-9bcd-690b4f5dd19b"&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;组合状态可以使用“与”关系分解为并发子状态，或者通过“或”关系分解为互相排斥的顺序子状态。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**顺序子状态:**如果一个组成状态的子状态对应的对象在其生命期内的任何时刻都只能处于一个子状态，即多个子状态之间是互斥的，不能同时存在，这种子状态称为顺序子状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162517146_80e462d2-5e69-4a25-9bd0-50b8ea1d398f.png" alt="162517146_80e462d2-5e69-4a25-9bd0-50b8ea1d398f"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**并发子状态:**有时组合状态有两个或者多个并发的子状态机，此时称组成状态的子状态为并发子状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162518209_4612d06b-a2a2-44ee-8f2b-871654b00fa3.png" alt="162518209_4612d06b-a2a2-44ee-8f2b-871654b00fa3"&gt;
③&lt;strong&gt;历史状态&lt;/strong&gt;是伪状态, 其目的是记住从组合状态中退出时所处的子状态, 当再次进入组合状态时, 可以直接进入这个子状态, 而不是再从组合状态的初态开始。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162519406_7d919e68-7b00-460d-b892-ba549d7a0c54.png" alt="162519406_7d919e68-7b00-460d-b892-ba549d7a0c54"&gt;浅(shallow)历史状态, 只记住最外层组合状态的历史。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162520591_df6eb9e8-aad4-4125-a05b-49832f174116.png" alt="162520591_df6eb9e8-aad4-4125-a05b-49832f174116"&gt;深(deep)历史状态, 可以记住任意深度的组合状态的历史。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如下图，running状态被打断到power on状态，再转回到running状态时，希望直接进入历史状态&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162521744_27aff948-1235-4a44-9201-012761937365.png" alt="162521744_27aff948-1235-4a44-9201-012761937365"&gt;
 &lt;/p&gt;
&lt;h4 id="转换transition"&gt;&lt;strong&gt;转换（Transition）&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;转换是两个状态间的一种关系，表示对象将在当前状态中执行动作，并在某个特定事件发生或某个特定的条件满足时进入后继状态。 每个转换只允许有一个事件触发，一个事件只允许有一个动作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;转换的五要素：源状态、目标状态、触发事件、监护条件和动作。（触发事件：如果箭头上不带任何事件名，表示是一个自动转换，当与源状态相关的活动完成时就会自动触发）。格式：触发事件（参数）[条件]/动作&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162523494_f10ccaf0-3aee-4b31-8a5c-d4adb82c2317.png" alt="162523494_f10ccaf0-3aee-4b31-8a5c-d4adb82c2317"&gt;
举例：与状态off相关的转换有两个，其触发事件都是turnOn，只不过其监护条件不同。如果对象收到事件turnOn，那么将判断壶中是否有水；如果[没水]，则仍然处于off状态；如果[有水]则转为on状态，并执行“烧水”动作。而与状态on相关的转换也有两个，如果“水开了”就执行turnOff，关掉开关；如果烧坏了，就进入了终态了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162524928_0b2385f7-d118-4fea-9781-005aefad35d7.png" alt="162524928_0b2385f7-d118-4fea-9781-005aefad35d7"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;转换的类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;转换类型&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;描述&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;语法&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;外部转换&lt;/td&gt;
&lt;td style="text-align: left"&gt;对事件做出响应，引起状态变化或自身转换，同时引发一个特定动作，如果离开或进入状态将引发进入转换、离开转换&lt;/td&gt;
&lt;td style="text-align: left"&gt;事件(参数)[监护条件]/动作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;内部转换&lt;/td&gt;
&lt;td style="text-align: left"&gt;对事件做出响应，并执行一个特定的活动，但并不引起状态变化或进入转换、离开转换&lt;/td&gt;
&lt;td style="text-align: left"&gt;事件(参数)[监护条件]/动作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;进入转换&lt;/td&gt;
&lt;td style="text-align: left"&gt;当进入某一状态时，执行相应活动&lt;/td&gt;
&lt;td style="text-align: left"&gt;entry/活动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;退出转换&lt;/td&gt;
&lt;td style="text-align: left"&gt;当离开某一状态时，执行相应活动&lt;/td&gt;
&lt;td style="text-align: left"&gt;exit/活动&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200206/162525980_7a6e3d8e-8736-4574-8e12-f15bc43424f1.png" alt="162525980_7a6e3d8e-8736-4574-8e12-f15bc43424f1"&gt;
 &lt;/p&gt;</description></item><item><title>UML图例之用例图</title><link>/blog/2020-01-22-uml-usecase/</link><pubDate>Wed, 22 Jan 2020 10:00:00 +0800</pubDate><guid>/blog/2020-01-22-uml-usecase/</guid><description>&lt;p&gt;用例图主要用来描述“用户、需求、系统功能单元”之间的关系，在需求分析阶段，常会借助用例图，从用户的角度描述系统的功能，以图形可视化的方式作为开发团队与客户的交流，同时也有助于形成统一语言。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="用例图描述"&gt;&lt;strong&gt;用例图描述&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;用例图（Use Case Diagrame）：描述了人们希望如何使用一个系统，将相关用户、用户需要系统提供的服务以及系统需要用户提供的服务更清晰的显示出来，以便使系统用户更容易理解这些元素的用途，也便于开发人员最终实现这些元素。之所以说用例图至关重要，是由于用户并不关心系统的实现和内部结构，只关心产品所呈现出来的外部特征动态。而用例图恰好就是描述软件产品外部特性的视图，它从用户的角度而不是从开发者的角度来描述需求，分析产品的功能和动态行为。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="基本元素"&gt;基本元素&lt;/h3&gt;
&lt;h4 id="参与者actor"&gt;&lt;strong&gt;参与者(Actor)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;在&lt;strong&gt;系统外部&lt;/strong&gt;与系统直接交互的角色或外部系统。可通过与客户的沟通交流，确定利益相关人，进而确定参与者。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;**角色：**通常是具体人承担着角色，这是最常见的参与者。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**外部系统：**如CRM系统要操作OA系统，以方便发送通知，那么针对OA系统的调用，CRM系统作为外部系统这一参与者。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**时间：**如存在定时任务操作或者类似操作等，则时间作为参与者&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162557373_02d6c4c6-4994-47be-8a67-0b8e2b68e4fa.png" alt="162557373_02d6c4c6-4994-47be-8a67-0b8e2b68e4fa"&gt;&lt;/p&gt;
&lt;h4 id="用例"&gt;&lt;strong&gt;用例&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;客户通过对需求的描述(主要为功能需求)，开发团队通过用例来体现系统功能和服务，通过参与者与用例的交互，来达到客户与开发团队的目标一致。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162558630_ef5f9fe5-4834-423f-a614-57196e065f37.png" alt="162558630_ef5f9fe5-4834-423f-a614-57196e065f37"&gt;&lt;/p&gt;
&lt;h4 id="关联关系"&gt;&lt;strong&gt;关联关系&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;1）参与者与参与者间的泛化关系&lt;/p&gt;
&lt;p&gt;比如腾讯用户，包括微信用户和QQ用户两部分，但是使用腾讯业务时，只需要是腾讯用户即可，此时，可以采用泛化关系，采用三角空心箭头作为指向。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162559762_89f7a55a-5e78-4972-b596-3911fcb88c91.png" alt="162559762_89f7a55a-5e78-4972-b596-3911fcb88c91"&gt;
2）参与者与用例间的关联关系&lt;/p&gt;
&lt;p&gt; 参与者与用例间是简单的关联关系，一个参与者可以有着多个用例&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162600979_f536c2ae-8443-4cae-85e7-5a33531ffd5f.png" alt="162600979_f536c2ae-8443-4cae-85e7-5a33531ffd5f"&gt;
3）用例与用例间的泛化关系&lt;/p&gt;
&lt;p&gt;用例之间可以存在泛化关系，比如常见的支付，可以选择微信支付、支付宝支付等等，但是这个操作就是支付。泛化关系采用三角空心箭头。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162602401_e90745d0-d9cf-4088-9860-b85cb9ba46ba.png" alt="162602401_e90745d0-d9cf-4088-9860-b85cb9ba46ba"&gt;
4）用例与用例间的包含关系&lt;/p&gt;
&lt;p&gt;包含关系用来把一个较复杂用例所表示的功能分解成较小的步骤。通常可以这么理解，由基础用例向复杂用例转换的过程。但是最终被参与者直接操作的还是基础用例。包含关系的图形为虚线箭头加&amp;laquo;include&amp;raquo;，箭头指向复杂用例。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162603577_237e4557-68ce-4300-b35b-01304ca7a980.png" alt="162603577_237e4557-68ce-4300-b35b-01304ca7a980"&gt;
5）用例与用例间的扩展关系&lt;/p&gt;
&lt;p&gt;扩展关系是指用例功能的延伸，相当于为基础用例提供一个附加功能。当&lt;strong&gt;特定条件出现&lt;/strong&gt;时，该扩展用例的行为才会被执行。扩展关系的图形为虚线箭头加上&amp;laquo;&lt;!-- raw HTML omitted --&gt;&amp;raquo;，箭头指向基础用例。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162604703_2cdb4298-302d-4e43-84bf-86c39e6226de.png" alt="162604703_2cdb4298-302d-4e43-84bf-86c39e6226de"&gt;
 &lt;/p&gt;
&lt;h3 id="简单案例"&gt;&lt;strong&gt;简单案例&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;依照常见的购买操作，设计简单的用例操作，当然，实际中的用例远不止这么简单，本次只是将以上几种关系融入进来，设计简单的案例。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200122/162606035_1331b62d-ecf4-40e3-9b8e-82972d0fc5e9.png" alt="162606035_1331b62d-ecf4-40e3-9b8e-82972d0fc5e9"&gt;
 &lt;/p&gt;
&lt;p&gt;至此，针对UML用例图的相关内容做了大概的总结，需求分析阶段，利用用例图，来方便与客户形成统一语言，也方便活动图的设计。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-01-22，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>UML图例之活动图</title><link>/blog/2020-01-21-uml-activity/</link><pubDate>Tue, 21 Jan 2020 10:00:00 +0800</pubDate><guid>/blog/2020-01-21-uml-activity/</guid><description>&lt;p&gt;作为UML图例中的重要角色，活动图描述的是对象活动的顺序关系所遵循的规则，作为用例实现的描述，非常适合于和用户沟通，同时也有助于为开发提供指导。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="活动图简介"&gt;&lt;strong&gt;活动图简介&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;活动图（activity diagram）是阐明了业务用例实现的工作流程。业务工作流程说明了业务为向所服务的业务主角提供其所需要的价值而必须完成的工作，业务用例由一系列活动组成，他们共同为业务主角生成某些工件。工作流程通常包括一个基本的工作流程和一个或多个备选工作流程。工作流程的结构使用活动图来进行说明。是UML用于对系统的动态行为建模的另一种常用工具，它描述活动的顺序，展现从一个活动到另一个活动的控制流程。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="基本元素"&gt;&lt;strong&gt;基本元素&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;活动图起点：活动的开始节点，实心黑色圆点，只能有一个。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162713224_bbe6fddc-c49d-4d71-97f5-bd33e46a7a0a.png" alt="162713224_bbe6fddc-c49d-4d71-97f5-bd33e46a7a0a"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;活动图终点：表示整个活动的结束，圆圈加内部实心黑色圆点，可以有多个结束点。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162714489_1a87e0b8-34d6-431a-959c-2f57ce57af3b.png" alt="162714489_1a87e0b8-34d6-431a-959c-2f57ce57af3b"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;流终点：表示子流程的结束，圆圈加内部叉。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162715699_0c731c43-7353-4043-a388-d1b9e8f04cb5.png" alt="162715699_0c731c43-7353-4043-a388-d1b9e8f04cb5"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;活动状态（activity）：用于表达状态机中的非原子的运行&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(1)、活动状态可以分解成其他子活动或者动作状态。&lt;/p&gt;
&lt;p&gt;(2)、活动状态的内部活动可以用另一个活动图来表示。&lt;/p&gt;
&lt;p&gt;(3)、和动作状态不同，活动状态可以有入口动作和出口动作，也可以有内部转移。&lt;/p&gt;
&lt;p&gt;(4)、动作状态是活动状态的一个特例，如果某个活动状态只包括一个动作，那么它就是一个动作状态。&lt;/p&gt;
&lt;p&gt;活动状态和动作状态的图标相同，但是活动状态可以在图标中给出入口动作和出口动作等信息。 &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162716792_fab0b393-9bf2-40ec-be96-cdada77c75af.png" alt="162716792_fab0b393-9bf2-40ec-be96-cdada77c75af"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;动作状态（actions）：是指原子的不可中断的动作，并在此动作完成后通过完成转换转向另一个状态&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(1)、动作状态是原子的，它是构造活动图的最小单位。&lt;/p&gt;
&lt;p&gt;(2)、动作状态是不可中断的。&lt;/p&gt;
&lt;p&gt;(3)、动作状态是瞬时的行为。&lt;/p&gt;
&lt;p&gt;(4)、动作状态可以有入转换，入转换既可以是动作流，也可以是对象流。动作状态至少有一条出转换，这条转换以内部的完成为起点，与外部事件无关。&lt;/p&gt;
&lt;p&gt;(5)、动作状态与状态图中的状态不同，它不能有入口动作和出口动作，更不能有内部转移。&lt;/p&gt;
&lt;p&gt;(6)、在一张活动图中，动作状态允许多处出现。 &lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162717896_4acebaae-95a2-4458-b48b-862b2d1b67ca.png" alt="162717896_4acebaae-95a2-4458-b48b-862b2d1b67ca"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;动作状态约束（action constraints）：用来约束动作状态，有前置条件和后置条件之分&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162719065_d819fe04-d2fc-41c8-85fc-021a5bf0c058.png" alt="162719065_d819fe04-d2fc-41c8-85fc-021a5bf0c058"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;动作流（control flow）：动作之间的转换，用带箭头的直线表示，箭头的方向指向转入的方向，简单来讲，就是让动作状态流动起来。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162720369_dcd81ad3-5750-4145-91c8-30b463275d87.png" alt="162720369_dcd81ad3-5750-4145-91c8-30b463275d87"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对象：矩形的方框，如果一个操作执行结束之后生成了某些数据需要传递给下一个目标操作，此时需要用对象节点表示。作为动作状态的输出或是动作状态的输入。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162721503_c23fcf9b-2f3a-4bce-be8e-ab0386e6abff.png" alt="162721503_c23fcf9b-2f3a-4bce-be8e-ab0386e6abff"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对象流：是动作状态或活动状态与对象之间的依赖关系，表示动作使用对象或动作对对象的影响。简单来说就是对象的产生或者使用过程。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(1)、一个对象可以由多个动作操作。&lt;/p&gt;
&lt;p&gt;(2)、一个动作输出的对象可以作为另一个动作输入的对象。&lt;/p&gt;
&lt;p&gt;(3)、在活动图中，同一个对象可以多次出现，它的每一次出现表面该对象正处于对象生存期的不同时间点。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162722635_a178c225-a0df-464d-abc0-d687893e113f.png" alt="162722635_a178c225-a0df-464d-abc0-d687893e113f"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分支与合并：用菱形表示，它有一个转换（箭头从外指向分支符号），一个或多个离开转换（箭头从分支符号指向外）。每个离开转换都会有一个监护条件，用来表示满足什么条件的时候执行该转换.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162723991_193c2221-8ef8-4b52-b9dc-bce3fb2b1228.png" alt="162723991_193c2221-8ef8-4b52-b9dc-bce3fb2b1228"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分叉与汇合：分叉用于将动作流分为两个或多个并发运行的分支，而汇合则用于同步这些并发分支，以达到共同完成一项事务的目的，对象在运行时可能会存在两个或多个并发的控制流，为对并发的控制流建模，UML中引入了交叉与汇合的概念&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;分叉形式&lt;/p&gt;
&lt;p&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162725711_d4ad4222-fc28-4beb-b936-8e448c005452.png" alt="162725711_d4ad4222-fc28-4beb-b936-8e448c005452"&gt;     &lt;/p&gt;
&lt;p&gt;汇合形式&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162726941_a1abfbe3-4f23-43c5-87b2-1796d08277fe.png" alt="162726941_a1abfbe3-4f23-43c5-87b2-1796d08277fe"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;泳道：将活动图中的活动划分为若干组，每一个活动只允许属于一个组，并把每一个组指定个负责这组活动的业务组织，设置一个组(业务组织)名称作为泳道的名称。利用泳道图可以很方便描述多个角色之间的协作、部门之间的协作等非常有效。泳道形式上有纵向和横向两种。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162728355_257b7f66-b49a-46fb-a460-4cb1b9b83ddf.png" alt="162728355_257b7f66-b49a-46fb-a460-4cb1b9b83ddf"&gt;
 &lt;/p&gt;
&lt;h3 id="活动图流程图状态图的区别"&gt;&lt;strong&gt;活动图、流程图、状态图的区别&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="活动图与流程图区别"&gt;活动图与流程图区别&lt;/h4&gt;
&lt;p&gt;(1)、流程图着重描述处理过程，它的主要控制结构是顺序、分支和循环，各个处理过程之间有严格的顺序和时间关系。而活动图描述的是对象活动的顺序关系所遵循的规则，它着重表现的是系统的行为，而非系统的处理过程，但活动图在本质上是一种流程图。&lt;/p&gt;
&lt;p&gt;(2)、活动图能够表示并发活动的情形，而流程图不行。&lt;/p&gt;
&lt;p&gt;(3)、活动图是面向对象的，而流程图是面向过程的。&lt;/p&gt;
&lt;h4 id="活动图与状态图区别"&gt;活动图与状态图区别&lt;/h4&gt;
&lt;p&gt;(1)、描述对象不同，状态图是描述某一对象的状态转化的，它主要是展示的是对象的状态，描述的是一个对象的事情。利用状态图我们可以看出，对象在接受了事件刺激后，会做出什么样的反应。&lt;/p&gt;
&lt;p&gt;(2)、使用场合不同，状态图描述对象在生命期中的自身状态变化，描述用例中的业务流程变化。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="简单案例"&gt;简单案例&lt;/h3&gt;
&lt;p&gt;结合业务中一个添加报价单的用例，设计活动图，泳道中的业务组织分为业务员和应用系统。业务员填写一份报价单并提交,应用系统执行参数判定，如参数均正确则开始处理计算报价单价格并发送消息通知给相关人员,如不正确，则提示给业务人员修改报价单。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200121/162729594_30d47779-989b-48f0-8c81-864550f156ea.png" alt="162729594_30d47779-989b-48f0-8c81-864550f156ea"&gt;
 &lt;/p&gt;
&lt;p&gt;至此，针对UML活动图的相关内容做了大概的总结，结合开发中的场景，先规划好用例，再针对具体用例设计活动图，方便开发过程。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-01-21，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>UML图例之类图</title><link>/blog/2020-01-19-uml-class/</link><pubDate>Sun, 19 Jan 2020 10:00:00 +0800</pubDate><guid>/blog/2020-01-19-uml-class/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;作为UML图例中的重要角色，通过许多（静态）说明性的模型元素（例如类、包和它们之间的关系，这些元素和它们的内容互相连接）描述系统静态结构，帮助我们快速了解、理清系统中对象之间的关系。&lt;/p&gt;
&lt;h2 id="类图中的几种关系"&gt;类图中的几种关系&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162813319_af18f359-db4f-4b6d-8454-32ec30a3045e.png" alt="162813319_af18f359-db4f-4b6d-8454-32ec30a3045e"&gt;&lt;/p&gt;
&lt;h3 id="泛化关系"&gt;泛化关系&lt;/h3&gt;
&lt;p&gt;在代码中体现为&lt;strong&gt;继承关系&lt;/strong&gt;，设计上用空心箭头实线表示。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162814878_de17b532-460a-4041-8362-081aa41f2fc7.png" alt="162814878_de17b532-460a-4041-8362-081aa41f2fc7"&gt;&lt;/p&gt;
&lt;h3 id="实现关系"&gt;实现关系&lt;/h3&gt;
&lt;p&gt;在代码中体现在&lt;strong&gt;接口和实现关系&lt;/strong&gt;，设计上用空心箭头虚线表示。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162816005_325008d6-74a4-4fd6-89a9-1f66c8e246d1.png" alt="162816005_325008d6-74a4-4fd6-89a9-1f66c8e246d1"&gt;&lt;/p&gt;
&lt;h3 id="关联关系"&gt;关联关系&lt;/h3&gt;
&lt;p&gt;在代码中体现为两个类之间存在语义上的关系，体现在类之间存在的隐约关系。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;单向关联：学生可以拥有书籍，学生知道书籍的存在，但是书籍无需知道学生的存在，学生可以拥有多本书籍，因此从学生指向书籍关联。设计上用箭头实线表示。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162817315_e2236048-b4ee-41d5-b7cb-ccbeda57461d.png" alt="162817315_e2236048-b4ee-41d5-b7cb-ccbeda57461d"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;双向关联：学生知道老师的存在，老师知道学生的存在。设计上用实线表示。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162818431_89a25388-ec12-483a-8097-c3f32b84fdaf.png" alt="162818431_89a25388-ec12-483a-8097-c3f32b84fdaf"&gt;
关联关系常用于数据库表的设计，与数据库范式相对应。&lt;/p&gt;
&lt;h3 id="聚合关系"&gt;聚合关系&lt;/h3&gt;
&lt;p&gt;特殊的关联关系，体现整体和部分的关系，设计上用空心菱形实线箭头表示。&lt;/p&gt;
&lt;p&gt;部门不存在，员工仍然可以存在，员工离职，部门仍然存在。体现的是0和n，整体和部分，整体不存在，部分仍然可以存在。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162819635_f7d7b756-f700-4232-b883-556816c636ea.png" alt="162819635_f7d7b756-f700-4232-b883-556816c636ea"&gt;&lt;/p&gt;
&lt;h3 id="组合关系"&gt;组合关系&lt;/h3&gt;
&lt;p&gt;特殊的关联关系，和聚合关系类似，体现整体与部分关系，设计上用实心菱形实现箭头表示。&lt;/p&gt;
&lt;p&gt;公司和部门关系体现在公司不存在，则部门也不存在了，前者掌握整体的生命周期。体现在0和0、1和n，整体不存在，则部分不存在，整体存在，则部分存在。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162820742_51181c1e-4b07-4c7a-b243-ce5859a0fd8d.png" alt="162820742_51181c1e-4b07-4c7a-b243-ce5859a0fd8d"&gt;&lt;/p&gt;
&lt;h3 id="依赖关系"&gt;依赖关系&lt;/h3&gt;
&lt;p&gt;没有直接的关系，仅仅在代码运行期间，产生的依赖，如将A类中间的时间属性赋值到B类中的时间属性。A类中调用类中类型为B类的属性，A类中调用的方法，需要用到B类的信息等。设计上用箭头虚线表示。箭头指向方为被调用方。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200119/162821848_da32ccb6-d13c-45b9-aa19-78dde0363d66.png" alt="162821848_da32ccb6-d13c-45b9-aa19-78dde0363d66"&gt;&lt;/p&gt;
&lt;h2 id="uml类图与数据表"&gt;UML类图与数据表&lt;/h2&gt;
&lt;p&gt;类图到表结构的映射中只需考虑泛化、关联关系。&lt;/p&gt;
&lt;p&gt;1、泛化关系&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;父类和子类映射为表，结构简单，更改方便，但数据分散，子类众多情况下，表数量也众多。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只将子类映射为表，数据集中，方便查询，但如果父类字段扩充，需要修改子类下所有的表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只将父类映射为表，表的数量少，读写方便，但列数量增多，所有子类的列都在父类表中存在，一行数据中，关注的信息量变少，空闲列增加。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2、关联关系&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一对一关联关系，相关两个类分别映射成两张表，尽管两张表权重一样，但是实际上还是会区分成主从表形式，从表保存主表主键，需查询从表数据时，以主表id为参数，用的较少，除非从表是使用率较低的信息存储，否则会合并成一张表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一对多关联关系，与一对一关联关系设计方式一样，设计主从表，从表保存主表主键，连表查询时，采用主键Id连从表外键查询。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多对多关联关系，设计时，采用中间表形式，多对多拆分成两个一对多，中间表负责存储两个主表主键及中间表可能拥有的自身属性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;针对于UML类图部分知识进行了一次整理，方便类图设计时，关系设计的选择。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-01-19，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Deepin安装NodeJS和NPM</title><link>/blog/2020-01-12-deepin-install-nodejs-and-npm/</link><pubDate>Sun, 12 Jan 2020 10:00:00 +0800</pubDate><guid>/blog/2020-01-12-deepin-install-nodejs-and-npm/</guid><description>&lt;p&gt;NodeJS使用范围越来越广，从想尝试看看typescript怎么用到electron想玩一玩，看个前后端分离的项目前端模块都还得用到NodeJS。因此，特意去百度一波安装教程，可是没找到几个可以一次点亮的，特此记录下本次点亮之旅。&lt;/p&gt;
&lt;p&gt;系统环境：Deepin v15.11&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="官网下载安装包"&gt;&lt;strong&gt;官网下载安装包&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;百度搜索NodeJS,选择Linux版本，安装方式有两种，一种是源码下载去安装，一种是编译后的文件下载安装，此处采用下载文件形式。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200112/162859624_a06e010d-d56e-443a-9d97-73f9593dd829.png" alt="162859624_a06e010d-d56e-443a-9d97-73f9593dd829"&gt;
 &lt;/p&gt;
&lt;h3 id="解压下载的安装包"&gt;&lt;strong&gt;解压下载的安装包&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;可以采用右键形式解压到指定常用文件夹即可，或是采用命令形式解压，此处采用右键形式解压，本次直接解压到当前路径下。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200112/162901647_8da2d40c-fd40-4e06-95af-96e176d0ce74.png" alt="162901647_8da2d40c-fd40-4e06-95af-96e176d0ce74"&gt;
 &lt;/p&gt;
&lt;h3 id="为nodejs添加软链接"&gt;&lt;strong&gt;为NodeJS添加软链接&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;添加软链接方式：ln -s 源文件 目标文件&lt;/p&gt;
&lt;p&gt;我本地存放的NodeJS解压后的地址 /media/yangyong/0A6A590D6A58F743/node-v12.14.1-linux-x64，看实际文件夹位置而更改&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ln -s /media/yangyong/0A6A590D6A58F743/node-v12.14.1-linux-x64/bin/node /usr/local/bin/node
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo ln -s /media/yangyong/0A6A590D6A58F743/node-v12.14.1-linux-x64/bin/npm /usr/local/bin/npm
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo chmod -R 777 /usr/local/bin/node
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo chmod -R 777 /usr/local/bin/npm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行完毕后用node -v、npm -v查看当前版本信息，以确定是否安装成功。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200112/162902996_21013c3b-ea43-423c-8989-341bb6cbcf69.png" alt="162902996_21013c3b-ea43-423c-8989-341bb6cbcf69"&gt;
 &lt;/p&gt;
&lt;p&gt;至此，安装NodeJS和NPM便可正常使用了，开始玩转前端的一些技术。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-01-12，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>ABP框架开发-租户管理</title><link>/blog/2020-01-11-abp-tenantmanagement/</link><pubDate>Sat, 11 Jan 2020 10:00:00 +0800</pubDate><guid>/blog/2020-01-11-abp-tenantmanagement/</guid><description>&lt;p&gt;软件即服务概念的推动，定制化到通用化的发展，用一套代码完成适应不同企业的需求，利用多租户技术可以去做到这一点。ABP里提供了多租户这一概念并且也在Zero模块中实现了这一概念。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="多租户的概念"&gt;&lt;strong&gt;多租户的概念&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="单部署-单数据库"&gt;&lt;strong&gt;单部署-单数据库&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;部署应用程序的单个实例和单个数据库。在每个数据表（关系型数据库）里用一个TenantId（租户Id或类似的如企业Id）字段来隔离区分每个租户数据。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200111/162946687_d91e5d4c-f562-449b-80e5-c958bdac8f3f.png" alt="162946687_d91e5d4c-f562-449b-80e5-c958bdac8f3f"&gt;
 &lt;/p&gt;
&lt;h4 id="单部署-多数据库"&gt;&lt;strong&gt;单部署-多数据库&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;部署应用的单一实例，用一个主（宿主）数据库存储租户元数据（像租户名和子域），并为每个租户建立并维护一个隔离的数据库。操作上通过识别当前租户并从主数据库中读取相对应的存储数据库地址，切换到该租户独有的数据库中执行操作。&lt;/p&gt;
&lt;p&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200111/162947924_a974f74b-37bd-4b75-9814-4bd8622d2397.png" alt="162947924_a974f74b-37bd-4b75-9814-4bd8622d2397"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h4 id="多部署-多数据库"&gt;&lt;strong&gt;多部署-多数据库&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;为每个租户部署应用的一个实例并使用一个独立的数据库，那么我们就可以在一台服务器上为多个租户服务，只需确保相同应用的多个实例在一个服务器的环境下不会互相冲突就行。&lt;/p&gt;
&lt;p&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200111/162949133_f2bea5f9-1147-4345-86ef-6af6f0db6414.png" alt="162949133_f2bea5f9-1147-4345-86ef-6af6f0db6414"&gt;&lt;/p&gt;
&lt;p&gt;还有两种多租户形式，诸如单部署-混搭数据库、集群部署-单/多/混搭数据库，不再提及，ABP针对于这五种多租户形式都可以使用。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h4 id="宿主与租户"&gt;&lt;strong&gt;宿主与租户&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;对于这两个概念，最好的理解方式便是酒店，酒店老板即是宿主，租户只拥有房间权限，酒店老板可以提供管理所有租户和房间。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200111/162950323_0e308334-63b0-44eb-aa1d-5581c3567e28.png" alt="162950323_0e308334-63b0-44eb-aa1d-5581c3567e28"&gt;
 &lt;/p&gt;
&lt;h3 id="abp中多租户配置"&gt;&lt;strong&gt;ABP中多租户配置&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;启用/禁用多租户&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果不需要多租户，比如说没有多租户情形，应用部署在企业私有服务器上，那么也可以不考虑多租户的使用，可以在ABP中关闭多租户(尽管关闭了，但默认还是会使用一个租户，默认租户Id为1，此时只有这个默认租户没有宿主)。在WebCoreModule中PreInitialize方法内可以添加如下代码启用或关闭多租户，默认是启用的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public override void PreInitialize()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;　　 ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Configuration.MultiTenancy.IsEnabled = true;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;侦测当前租户并在Session中获取租户&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ABP中租户名称是唯一的，对于识别当前租户或是宿主身份，ABP没有使用Asp.Net 提供的Session，声明了IAbpSession接口并提供了默认的实现(ClaimAbpSession)去测定当前租户信息，按照如下思路去确定租户。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果当前用户登录了系统，那么可以从当前用户的声明信息中读取到当前租户信息，如果没有读取到租户信息，那么可以断定是宿主。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果当前用户没有登录系统，那么会有几种方式去获取,如果以下几种方式仍未获取到租户Id，则认为是使用宿主登录。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从当前域名或是子域名去获取域名名称，然后通过租户仓储去查询是否存在相关的域名或子域名存在则可以确定租户Id。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从Http请求头中获取在ABP中默认配置项Abp.TenantId(该配置项可更换名称)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从Http请求的cookie中获取Abp.TenantId&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IAbpSession声明了获取当前用户和租户信息的方法，该方法允许我们获取当前登录的用户及当前的租户信息。并且获取到的信息按照不同的规则，有着不同的作用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果获取到的用户和租户Id都是空的，那么意味着当前用户没有登录系统，因此也无法断定出当前是宿主还是租户。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果用户Id是空的，但是租户Id不是空的，那么可以知道是哪个租户，但是用户仍然是没有登录的，只是选择了租户。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果用户Id不是空的，但是租户Id是空的，那么可以知道是用户使用宿主登录了系统。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果用户Id且租户Id不是空的，那么就知道是选择了租户并且是租户中的某个用户登录了系统。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据过滤&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果使用了多租户，那么在读取数据时，会依据当前租户Id加上额外的过滤条件，这一点ABP已经处理好了，我们无需在linq中敲代码，但是有个前提条件是，读取数据的这个实体有设置多租户。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;如果实体使用的是IMustHaveTenant接口，那么读取时会依照当前租户Id进行条件过滤。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果实体使用的是IMayHaveTenant接口，那么读取到的数据会依照当前租户Id的有无值进行区分，如果当前租户Id为空，那么将读取到宿主的数据，如果租户Id不为空，则读取相应租户数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果实体没使用这两个接口，则读取到的数据不区分宿主和租户。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这两个接口使用场景：如果是宿主和租户都需要的，比如角色、用户、部门等，那么使用IMayHaveTenant接口，如果仅是租户所需要的那只需使用IMustHaveTenant接口。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;宿主与租户间切换&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此处切换可以这么理解，给我一个其它租户Id，我可以在我的租户中获取到其它租户的数据，相应的，其它租户也可以获取到我租户的数据，或是宿主获取租户数据。如果不给定租户Id,租户可以获取宿主数据。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class ProductService : ITransientDependency
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private readonly IRepository&amp;lt;Product&amp;gt; _productRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private readonly IUnitOfWorkManager _unitOfWorkManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public ProductService(IRepository&amp;lt;Product&amp;gt; productRepository, IUnitOfWorkManager unitOfWorkManager)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _productRepository = productRepository;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _unitOfWorkManager = unitOfWorkManager;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [UnitOfWork]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public virtual List&amp;lt;Product&amp;gt; GetProducts(int tenantId)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; using (_unitOfWorkManager.Current.SetTenantId(tenantId))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return _productRepository.GetAllList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="配置一个多租户实体"&gt;&lt;strong&gt;配置一个多租户实体&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;基于之前的数据字典进行改造，以便适用于多个租户使用，并且考虑到宿主无需使用数据字典，将其继承IMustHaveTenant接口后，更新数据库便可。&lt;/p&gt;</description></item><item><title>ABP框架开发-代码生成器</title><link>/blog/2020-01-01-abp-codegenerator/</link><pubDate>Wed, 01 Jan 2020 10:00:00 +0800</pubDate><guid>/blog/2020-01-01-abp-codegenerator/</guid><description>&lt;p&gt;在日常开发中，有时会遇到一些相似的代码，甚至是只要CV一次，改几个名称，就可以实现功能了，而且总归起来，都可以由一些公用的页面更改而来，因此，结合我日常开发中使用到的页面，封装一个适合自己的代码生成器，仅处于入门阶段，包括生成的代码结构都仅是把框架展示出来，内部详细暂时没得，针对于应用服务中的接口和实现，相关Dto，MVC中的控制器、视图及视图模型进行了模板制作及生成相关的文件。&lt;/p&gt;
&lt;h3 id="设计思路"&gt;&lt;strong&gt;设计思路&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;方案一：开始想到的是，搞个控制台，然后给一个.cs文件，然后控制台去解析其中的命名空间，类名，属性，再用配置好的razor模板去替换，再生成相关的一些文件出来，但是发现，万事开头难，第一步去解析cs文件就不好搞，找了网上的资料，不太好弄，干脆想了下，放弃这种方案，因为想到了另一种常用的方案。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200101/163043117_d9f79d70-bfde-494b-af6e-20e01f512530.png" alt="163043117_d9f79d70-bfde-494b-af6e-20e01f512530"&gt;
 &lt;/p&gt;
&lt;p&gt;方案二：直接在控制台中，配置控制台去访问数据库，然后给定指定表名，去读取数据库中的表和字段，再反过来去生成相关文件，但是这里会遇到一个这样的问题，比如我使用的是mysql，mysql本身有个配置表名大小写忽视的，这样一来，获取到的表名都将是小写打头，尽管可能配置了是区分大小写，但是，我设计表时，采用Pre_table，形式区分业务表，比如是CRM模块需要用到的CRM_Client，那将用CRM打头，后面这部分Client才是实际代码中的类名，种种问题都有可能，但是作为没有那么多可能性下，比如没得前缀，不区分大小写，形式简单，那么可以考虑使用。此时，想到了abp中的Migrator控制台并想到了方案三。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200101/163044370_2d1e9401-4469-4e12-bef6-af31a61a94cb.png" alt="163044370_2d1e9401-4469-4e12-bef6-af31a61a94cb"&gt;
 &lt;/p&gt;
&lt;p&gt;方案三：如果说直接搞一个控制台在代码中，模仿Abp自带的Migrator一样，启动后，给定类名，通过反射去取得该类的属性名，岂不是美滋滋，需要哪个类的相关文件，只需启动，然后输入类名，即可得到相关的文件。这几种方案的前提都是在Dto文件中会展示所有实体字段，如果需要选择性的使用字段，则还需借助人工智能，以人力去完成更改生成的文件。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200101/163045424_93b3653a-cc2e-4759-83cd-9c935ff2537a.png" alt="163045424_93b3653a-cc2e-4759-83cd-9c935ff2537a"&gt;
 &lt;/p&gt;
&lt;h3 id="razor引擎的使用"&gt;&lt;strong&gt;Razor引擎的使用&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;我选择了方案二作为入手去实现，并且采用&lt;a href="https://github.com/Antaris/RazorEngine"&gt;Razor引擎&lt;/a&gt;作为模板解析的工具。Nuget引入&lt;strong&gt;RazorEngine.NetCore&lt;/strong&gt;包，开始实现依靠模板生成代码。 &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先尝试下Razor引擎，控制台中CV下Razor引擎提供的Demo，引入相关命名空间，学习下如何去使用。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;string template = &amp;#34;Hello @Model.Name, welcome to RazorEngine!&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var result = Engine.Razor.RunCompile(template, &amp;#34;templateKey&amp;#34;, null, new { Name = &amp;#34;World&amp;#34; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Console.WriteLine(result);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;运行完毕，可以获取到运行结果，需要注意的是，如果是在linux或是mac跑会得到错误，该问题是Razor引擎本身的问题，暂时只能在window下跑。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200101/163046660_2c8e930b-05da-4e6e-8561-015259792216.png" alt="163046660_2c8e930b-05da-4e6e-8561-015259792216"&gt;
 &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;熟悉了下Razor的使用方式后，开始使用简单文件形式填充一些数据模拟生成过程。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20200101/163048096_e0ff3bd8-5343-4da7-9a56-1ddb1d7389e0.png" alt="163048096_e0ff3bd8-5343-4da7-9a56-1ddb1d7389e0"&gt;
 &lt;/p&gt;
&lt;p&gt;首先，一个文件作为填充模板，一个文件内存储Json数据作为数据源，程序启动时加载两个文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var templatePage = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, &amp;#34;SimpleCOders\\Templates&amp;#34;, &amp;#34;templatePage.txt&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TemplatePage = File.ReadAllText(templatePage);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var templatePageJson = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, &amp;#34;SimpleCOders\\Templates&amp;#34;, &amp;#34;templatePageJson.json&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TemplatePageJson = File.ReadAllText(templatePageJson);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其次，数据源整理成相应类结构，得到批量待解析数据。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var templatePageJsonList = JsonConvert.DeserializeObject&amp;lt;List&amp;lt;PageDataModel&amp;gt;&amp;gt;(TemplatePageJson);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;foreach (var templatePageJson in templatePageJsonList)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RazorParse(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; templatePageJson.Index ?? 1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; templatePageJson.Date,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; templatePageJson.Index - 1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; templatePageJson.Index + 1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; templatePageJson.Content
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最后，设计一下解析器，将读取到的数据源，进行解析成相关的类，然后依次按照模板生成文件&lt;/p&gt;</description></item><item><title>DotNet Core Console&amp;EFCore连接Mysql</title><link>/blog/2019-12-06-dotnetcore-console-efcore-mysql/</link><pubDate>Fri, 06 Dec 2019 10:00:00 +0800</pubDate><guid>/blog/2019-12-06-dotnetcore-console-efcore-mysql/</guid><description>&lt;p&gt;在用惯了Asp.Net Core提供的基架后，反过来想一想，貌似忘记了控制台如何去连接数据库了，因此，写一篇文章来借此巩固下并以后再来回顾时，加快步骤。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;新建一个.Net Core控制台，然后安装Nuget包&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Microsoft.EntityFrameworkCore.Tool
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Microsoft.EntityFrameworkCore.Design
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Pomelo.EntityFrameworkCore.MySql
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Microsoft.Extensions.Configuration.Json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;添加一个文件夹Models，并增加一个实体类Company，增加该实体类一系列属性&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace FastConnect.Tool.Models
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 公司信息
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public class Company
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// Id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public int Id { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 公司名称
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public string Name { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 公司地址
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public string Address { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;再增加一个配置文件appsettings.json，按照数据库具体配置去弄，比如接口，用户名密码等，并且本次想用的数据库名。需右键设置该文件的输出方式为始终复制。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;ConnectionStrings&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;Default&amp;#34;: &amp;#34;Server=xxx;Database=fastconnectdb;Port=10143;charset=utf8;uid=tester;pwd=xxx@yy;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;增加DbContext，并继承DbContext，该父类在EFCore中，将之前建立的类加入到该DbContext中，配置这个数据库上下文需要的连接字符串，通过配置类去读取第二步骤中的配置项文件，在OnConfiguring中使用UseMysql，该方法在Pomelo的dll中并读取其中的默认连接字符串节点。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using FastConnect.Tool.Models;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using Microsoft.EntityFrameworkCore;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using Microsoft.Extensions.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using System.IO;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace FastConnect.Tool
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public class FastConnectDbContext : DbContext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public DbSet&amp;lt;Company&amp;gt; Companys { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private IConfiguration configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public FastConnectDbContext()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(&amp;#34;appsettings.json&amp;#34;).Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; optionsBuilder.UseMySql(configuration.GetConnectionString(&amp;#34;Default&amp;#34;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;在程序包管理器控制台中选择该项目，必须要选择DbContext所在项目，本次DbContext所在项目为控制台本部。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20191206/192241749_33ed26a6-b9de-40da-ba74-df2f3a933717.png" alt="192241749_33ed26a6-b9de-40da-ba74-df2f3a933717"&gt;
并键入如下两行,如无错误，则数据库已经生成，可以查看项目文件夹中，将会多出一个文件夹和若干文件，这是记录迁移信息的。&lt;/p&gt;</description></item><item><title>ABP框架开发-RBAC</title><link>/blog/2019-11-17-abp-rbac/</link><pubDate>Sun, 17 Nov 2019 10:00:00 +0800</pubDate><guid>/blog/2019-11-17-abp-rbac/</guid><description>&lt;p&gt;在业务系统需求规划过程中，通常对于诸如组织机构、用户和角色等这种基础功能，通常是将这部分功能规划到通用子域中，这也说明了，对于这部分功能来讲，是系统的基石，整个业务体系是建立于这部分基石之上的，当然，还有诸如多语言、设置管理、认证和授权等。对于这部分功能，ABP中存在这些概念，并且通过Module Zero模块完成了这些概念。&lt;/p&gt;
&lt;h2 id="角色访问控制之rbac"&gt;&lt;strong&gt;角色访问控制之RBAC&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;RBAC：Role Based Access Control，基于角色的访问控制，这在目前大多数软件中来讲已经算得上是普遍应用了，最常见的结构如下，结构简单，设计思路清晰。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20191117/192328265_0d21836d-aa0f-4c84-a2ad-db6627dbe85f.png" alt="192328265_0d21836d-aa0f-4c84-a2ad-db6627dbe85f"&gt;
但是也存在其它升级版的设计，诸如用户权限表、角色组、用户组的概念等，具体分类有RBAC0、RBAC1、RBAC2等，后者功能越来越强大，也越来越复杂。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RBAC0：是RBAC的核心思想。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RBAC1：是把RBAC的角色分层模型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RBAC2：增加了RBAC的约束模型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RBAC3：整合RBAC2 + RBAC1。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="abp中的rbac"&gt;&lt;strong&gt;ABP中的RBAC&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;在Abp中，已经集成了这些概念，并在ModuleZero模块中实现了这些概念，基于IdentityServer4的ModuleZero模块完成了封装。对于我们大多数以业务为中心的开发人员来讲，不应该又去造一个轮子，而是应该开好这辆车。首先看下Abp中的RBAC模型。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20191117/192329660_43d562ee-3969-4204-99fd-0137b7857620.png" alt="192329660_43d562ee-3969-4204-99fd-0137b7857620"&gt;
在这其中权限表中记录了用户与权限，角色与权限两部分。对于权限通常指的是功能权限和数据权限两部分，一般来讲，大多指的是功能权限，这种通过角色与权限进行管理即可，如还有用户部分的功能区分，则可以再使用上用户与权限，而对于数据权限，可以利用用户与权限部分，个人用的比较少，但是，可以想象到这么一个场景，针对于一家门店内的多个店长，角色相同即相应的权限相同，但各自关心的数据来源不同，关心东部、南部等数据，而不关心西部、北部数据，因此可以在数据层面进行划分，比如设置数据来源，东南西北，对于数据来源进行权限关联，这样一来用户本身如果拥有东部数据权限，则只能看到东部数据。&lt;/p&gt;
&lt;h3 id="权限声明及应用"&gt;&lt;strong&gt;权限声明及应用&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在Abp中，需要首先在Core层/Authorization/PermissionNames.cs中声明权限，Abp权限部分设计原则是：&lt;strong&gt;先声明再使用&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 权限命名
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public static class PermissionNames
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 顶级权限
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages = &amp;#34;Pages&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 基础支撑平台
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame = &amp;#34;Pages.Frame&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 租户管理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Tenants = &amp;#34;Pages.Frame.Tenants&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 组织机构
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_OrganizationUnits = &amp;#34;Pages.Frame.OrganizationUnits&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_OrganizationUnits_Create = &amp;#34;Pages.Frame.OrganizationUnits.Create&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_OrganizationUnits_Update = &amp;#34;Pages.Frame.OrganizationUnits.Update&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_OrganizationUnits_Delete = &amp;#34;Pages.Frame.OrganizationUnits.Delete&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 用户管理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Users = &amp;#34;Pages.Frame.Users&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Users_Create = &amp;#34;Pages.Frame.Users.Create&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Users_Update = &amp;#34;Pages.Frame.Users.Update&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Users_Delete = &amp;#34;Pages.Frame.Users.Delete&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Users_ResetPassword = &amp;#34;Pages.Frame.Users.ResetPassword&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 角色管理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Roles = &amp;#34;Pages.Roles&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Roles_Create = &amp;#34;Pages.Frame.Roles.Create&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Roles_Update = &amp;#34;Pages.Frame.Roles.Update&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string Pages_Frame_Roles_Delete = &amp;#34;Pages.Frame.Roles.Delete&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后在Core层/Authorization/XXXAuthorizationProvider.cs中设置具体权限，在此处设置权限时，可以根据权限设计时候的职责划分，比如如果仅仅是多租户需要这部分，那便设置权限范围为多租户即可。&lt;/p&gt;</description></item><item><title>DotNet轻量状态机Stateless的简单应用</title><link>/blog/2019-10-31-dotnet-stateless-basicusage/</link><pubDate>Thu, 31 Oct 2019 10:00:00 +0800</pubDate><guid>/blog/2019-10-31-dotnet-stateless-basicusage/</guid><description>&lt;p&gt;对于大部分系统中流程的变更，是十分正常的事情，小到一个状态的切换，大到整个系统都是围绕业务流再走，复杂点的有工作流引擎，简单点的几个if/else收工，但是往往有那种，心有余而力不足的，比简单复杂，比复杂简单，最近，对业务流程的变更这一块一直再琢磨，没有找到一些让我豁然开朗的资料，本次只能是讲讲我的设计过程，作为反面教材去对比的，同时借鉴staleless去简化一下日常中的设计。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="常用的流程变更"&gt;&lt;strong&gt;常用的流程变更&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;采用状态控制数据操作，通过操作变更状态，通过switch、if/else去区分状态，然后依据状态做出不同的指示，从一个大家十分熟悉，但又及其简单的例子着手，思路更加清晰。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20191031/192447788_c2acf390-4b03-4f58-ba4c-09e32ebd2a84.png" alt="192447788_c2acf390-4b03-4f58-ba4c-09e32ebd2a84"&gt;
对于订单的状态，一开始用几种描述的方式固定下来，代码实现中，有人喜欢枚举，有人喜欢字符串，这都无所谓了，罗马路条条。只是这个流程是相对固定的，与一些业务系统中，十几个转折或是几十个流程节点那样，那种貌似大多使用的都是工作流吧，存在变更，还要容易变更。&lt;/p&gt;
&lt;p&gt;先定义个简单的订单类及用到的枚举值，这些信息应该是很熟悉且常见的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Order&lt;/span&gt;(long orderId, string orderName, string orderNo, double price)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderId &lt;span style="color:#f92672"&gt;=&lt;/span&gt; orderId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderName &lt;span style="color:#f92672"&gt;=&lt;/span&gt; orderName;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderNo &lt;span style="color:#f92672"&gt;=&lt;/span&gt; orderNo;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Price &lt;span style="color:#f92672"&gt;=&lt;/span&gt; price;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        CreateDate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; DateTime.Now;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        InitOrderState();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;订单&lt;/span&gt;Id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; long OrderId &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;订单名称&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; string OrderName &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;订单编号&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; string OrderNo &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;创建日期&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; DateTime CreateDate &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;订单价格&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; double Price &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;订单状态&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; OrderState OrderState &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; private &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;初始订单状态&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; void InitOrderState()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderState &lt;span style="color:#f92672"&gt;=&lt;/span&gt; OrderState.OrderCreated;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;设置订单状态&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/&lt;/span&gt;summary&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;///&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;param name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;orderState&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;param&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; void SetOrderState(OrderState orderState)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        OrderState &lt;span style="color:#f92672"&gt;=&lt;/span&gt; orderState;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于枚举值而言，我这貌似没太大含义，但是有些场景下可能会用的到，暂时先搞上&lt;/p&gt;</description></item><item><title>DotNet轻量状态机Stateless介绍</title><link>/blog/2019-09-22-dotnet-stateless-introduce/</link><pubDate>Sun, 22 Sep 2019 10:00:00 +0800</pubDate><guid>/blog/2019-09-22-dotnet-stateless-introduce/</guid><description>&lt;p&gt;很多业务系统开发中，不可避免的会出现状态变化，通常采用的情形可能是使用工作流去完成，但是对于简单场景下，用工作流有点大财小用感觉，比如订单业务中，订单状态的变更，涉及到的状态量不是很多，即使通过简单的if-else也能足够使用，甚至是用上switch去减少if-else的使用，都是可以的，尽管这会丧失某些东西。为更好的优化整个流程，此时会考虑到使用状态模式来解决一些问题。&lt;/p&gt;
&lt;p&gt;Stateless状态机GitHub：&lt;a href="https://github.com/dotnet-state-machine/stateless"&gt;https://github.com/dotnet-state-machine/stateless&lt;/a&gt; &lt;/p&gt;
&lt;h2 id="状态模式与状态机"&gt;&lt;strong&gt;状态模式与状态机&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;状态模式&amp;quot;允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类 &amp;ldquo;。 (State Pattern: &amp;ldquo;Allow an object to alter its behavior when its internal state changes. The object will appear to change its class &amp;ldquo;.)&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于这个定义，有点抽象，变通理解一下可以这么理解：状态拥有者将变更行为委托给状态对象，状态拥有者本身只拥有状态(当然也可以抛弃状态对象)，状态对象履行变更职责。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;状态机：&amp;ldquo;依照指定的状态流程图，根据当前执行的动作，将当前状态按照预定的条件变更到新的状态 &amp;ldquo;。&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;状态机有4个要素，即现态、条件、动作、次态。其中，现态和条件是“因”， 动作和次态是“果”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;现态 - 是指当前对象的状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;条件 - 当一个条件满足时，当前对象会触发一个动作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;动作 - 条件满足之后，执行的动作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;次态 - 条件满足之后，当前对象的新状态。次态是相对现态而言的，次态一旦触发，就变成了现态&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;状态迁移图：&amp;ldquo;在UML建模中，常常可见，用来描述一个特定的对象所有可能的状态,以及由于各种事件的发生而引起的状态之间的转移和变化，也是配置状态机按照何种行径的前提 &amp;ldquo;。&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190922/192550330_9aef98bb-492f-476e-b95b-c924cd5401e8.png" alt="192550330_9aef98bb-492f-476e-b95b-c924cd5401e8"&gt;
 &lt;/p&gt;
&lt;h2 id="stateless功能介绍"&gt;&lt;strong&gt;Stateless功能介绍&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Stateless是一个基于C#创建状态机的简单库。基于.Net Standard实现，在.Net Framework和.Net Core项目中都可以使用。源码地址：&lt;a href="https://github.com/dotnet-state-machine/stateless"&gt;https://github.com/dotnet-state-machine/stateless&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;以一个打电话的使用案例来讲讲Stateless的功能：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;//初始化状态机
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;var phoneCall = new StateMachine&amp;lt;State, Trigger&amp;gt;(State.OffHook);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;//流程配置
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;phoneCall.Configure(State.OffHook)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Permit(Trigger.CallDialled, State.Ringing);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;phoneCall.Configure(State.Ringing)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Permit(Trigger.CallConnected, State.Connected);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;phoneCall.Configure(State.Connected)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .OnEntry(() =&amp;gt; StartCallTimer())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .OnExit(() =&amp;gt; StopCallTimer())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Permit(Trigger.LeftMessage, State.OffHook)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Permit(Trigger.PlacedOnHold, State.OnHold);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;//触发行为
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;phoneCall.Fire(Trigger.CallDialled);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Assert.AreEqual(State.Ringing, phoneCall.Stat 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="功能特性"&gt;&lt;strong&gt;功能特性&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;状态机常见功能：&lt;/p&gt;</description></item><item><title>KnockoutJS-自定义属性绑定</title><link>/blog/2019-08-29-knockoutjs-customproperty-bindings/</link><pubDate>Thu, 29 Aug 2019 10:00:00 +0800</pubDate><guid>/blog/2019-08-29-knockoutjs-customproperty-bindings/</guid><description>&lt;p&gt;在knockoutjs中，已有的绑定功能已经十分强大，基本上可以不需要再去考虑扩展了，但是，也有例外的场景，面对这种场景，还是得去完成，knockoutJS提供了自定义绑定来扩展绑定功能。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="新建绑定"&gt;&lt;strong&gt;新建绑定&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;新建一个js文件来尝试绑定功能，按照给定的格式ko.bindingHandlers.xxx来扩展一个绑定。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ko.bindingHandlers.yourBindingName = {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // This will be called when the binding is first applied to an element
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // Set up any initial state, event handlers, etc. here
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // This will be called once when the binding is first applied to an element,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // and again whenever the associated observable changes value.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // Update the DOM element based on the supplied values here.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其次，在html中完成绑定即可，用法同自带的绑定。&lt;/p&gt;</description></item><item><title>ABP框架开发-设置管理</title><link>/blog/2019-08-11-abp-settingmanagement/</link><pubDate>Sun, 11 Aug 2019 10:00:00 +0800</pubDate><guid>/blog/2019-08-11-abp-settingmanagement/</guid><description>&lt;p&gt;在网站开发中，设置是不可缺少的一环，如用户设置、系统设置、甚至是租户设置等。ABP对于设置的管理已经做了很好的处理，我们可以借助巨人的力量来完成我们的冒险。&lt;/p&gt;
&lt;p&gt;ABP官网地址：&lt;a href="https://aspnetboilerplate.com/"&gt;https://aspnetboilerplate.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="设置的层级划分"&gt;&lt;strong&gt;设置的层级划分&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;ABP中提供了三种类型的设置，用户级别、应用级别、租户级别，针对于不同级别有着不同的侧重点，比如用户级别，针对于用户的一些设置，如主题设置，接收通知设置等；针对应用级别，该级别也能在用户层级上进行影响，好比如设置统一的主题皮肤，而它主要体现在整个应用程序上的一些设置，如业务开发中用到的流水号自定义等，在应用程序级别进行配置；而针对租户级别，可以为特定租户完成设置(租户级别个人暂没有涉及到)。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190811/192839868_be909066-fb86-4df1-89ee-c04464977bac.png" alt="192839868_be909066-fb86-4df1-89ee-c04464977bac"&gt;
 &lt;/p&gt;
&lt;h3 id="定义设置"&gt;&lt;strong&gt;定义设置&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;先得定义设置才能使用设置，首先创建一个类并继承SettingProvider类，ABP已经在Core层创建了一个AppSettingProvider类，直接使用该类即可，如果需要在其它模块中(ABP是模块化的)定义设置，可以直接参照这个类，并且在模块中注册即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 设置命名
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public static class AppSettingNames
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 系统管理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static class HostManagement
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string CompanyName = &amp;#34;App.CompanyName&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string CompanyAddress = &amp;#34;App.CompanyAddress&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string UiTheme = &amp;#34;App.UiTheme&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为保证后期业务上的设置不产生影响，还是先进行区分好了，建立一个系统设置来维护一些相对于用户和租户而言独立的应用层级的设置。
 &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开始定义设置项信息，从ABP官网文档中看到有一个SettingDefinition类，用于定义设置项信息，查看该类创建时需要的参数&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;name：命名 (必填)，命名必须系统内唯一，建议是用常量字符串；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;defaultValue：默认值(选填)，能为null或空字符串；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;displayName：显示名称(选填)，UI呈现上显示的名称；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;group：分组(选填)，可对同一类别的设置进行分组，用于UI上分组展示；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;description: 设置项描述(选填)，UI上呈现该设置项的描述信息；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;scopes：设置范围(默认是应用层级)，用于定义该设置项是用于哪一层级，该设置范围可以归属多个层级；&lt;/p&gt;</description></item><item><title>ABP框架开发-系统日志</title><link>/blog/2019-08-03-abp-auditlog/</link><pubDate>Sat, 03 Aug 2019 10:00:00 +0800</pubDate><guid>/blog/2019-08-03-abp-auditlog/</guid><description>&lt;p&gt;网站正常运行中有时出现异常在所难免，查看系统运行日志分析问题并能够根据错误信息快速解决问题尤为重要，ABP对于系统运行日志这块已经做了很好的处理，默认采用的Log4Net已经足够满足开发过程中的需要了(当然有需要的话也可以更换为其它日志组件)。&lt;/p&gt;
&lt;p&gt;ABP官网地址：&lt;a href="https://aspnetboilerplate.com/"&gt;https://aspnetboilerplate.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="日志文件"&gt;&lt;strong&gt;日志文件&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;ABP框架默认使用了Log4Net日志组件，日志记录在txt文件中，也可以替换成其它日志组件诸如Nlog，方便将日志文件信息直接记录到数据库中，具体情形使用具体组件。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190803/192941201_e0624428-de54-403f-ad03-35834383e800.png" alt="192941201_e0624428-de54-403f-ad03-35834383e800"&gt;
当一个文件达到了在Log4Net配置中设置好的文件大小上限时，在文件名后按照数字倒排后开始继续增加文件。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190803/192942425_a7a8df1d-9a53-4039-bda7-7abdf16d1ae2.png" alt="192942425_a7a8df1d-9a53-4039-bda7-7abdf16d1ae2"&gt;
当需要查看错误信息时，直接在日期最近的文件中找出错误信息即可，但是这个过程比较繁琐，还需要从日志文件中去查看，并且日志文件中虽然做了分类，哪些是正常信息，哪些是错误信息，但是不太直观，因此，可以考虑直接将日志文件在页面中呈现，对信息进一步加工，方便直接查看。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190803/192943484_3c656223-08ce-44c1-8126-706e7c5cf1cd.png" alt="192943484_3c656223-08ce-44c1-8126-706e7c5cf1cd"&gt;
参考了AbpZero中的部分代码并根据实际需要进行整合，开始在页面中设计日志展示层。 &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="页面展示日志信息"&gt;&lt;strong&gt;页面展示日志信息&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;系统日志服务应属于整个系统中相对其他业务模块独立的一部分，因此，首先在应用层中新建一个Logging文件夹并创建一个日志应用层服务接口与其实现。在接口中声明两个方法，直接查看当前最近的日志文件中的日志信息以及从服务器下载所有的日志文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 网站运行日志应用层服务
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public interface IWebSiteLogAppService : IApplicationService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 获取最近的一个日志文件
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; GetLatestWebLogsOutput GetLatestWebLogs();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 下载所有的日志文件
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileDto DownloadWebLogs();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;首先考虑直接获取最近的日志文件信息，直接读取即可，遵循的规则是读取指定文件夹下指定文件后缀名更改日期为最大的文件然后从中读取日志信息，并返回到前端。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public GetLatestWebLogsOutput GetLatestWebLogs()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var directory = new DirectoryInfo(AppConsts.LogFilePath);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (!directory.Exists)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return new GetLatestWebLogsOutput
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LatestWebLogLines = new List&amp;lt;string&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var lastLogFile = directory.GetFiles(&amp;#34;*.txt&amp;#34;, SearchOption.AllDirectories)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .OrderByDescending(f =&amp;gt; f.LastWriteTime)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .FirstOrDefault();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (lastLogFile == null)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return new GetLatestWebLogsOutput();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var lines = AppFileHelper.ReadLines(lastLogFile.FullName).Reverse().Take(1000).ToList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var logLineCount = 0;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var lineCount = 0;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; foreach (var line in lines)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (line.StartsWith(&amp;#34;DEBUG&amp;#34;) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line.StartsWith(&amp;#34;INFO&amp;#34;) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line.StartsWith(&amp;#34;WARN&amp;#34;) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line.StartsWith(&amp;#34;ERROR&amp;#34;) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; line.StartsWith(&amp;#34;FATAL&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; logLineCount++;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lineCount++;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (logLineCount == 100) break;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return new GetLatestWebLogsOutput
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LatestWebLogLines = lines.Take(lineCount).Reverse().ToList()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;在前端处理日志信息，Mvc层中新增一个控制器，并写一个方法调用日志服务获取最近的日志文件信息，并处理好权限问题及页面左侧菜单的展示。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 系统维护控制器
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[AbpMvcAuthorize]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class MaintenanceController : SurroundControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private readonly IWebSiteLogAppService _webSiteLogAppService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public MaintenanceController(IWebSiteLogAppService webSiteLogAppService)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _webSiteLogAppService = webSiteLogAppService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 首页
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public IActionResult Index()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return View();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 获取最近日志信息
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public JsonResult GetLatestWebLogs()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var getLatestWebLogsOutput = _webSiteLogAppService.GetLatestWebLogs();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return Json(getLatestWebLogsOutput);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;增加一个视图文件并开始编写前端代码获取日志文件，利用abp前端封装好的ajax请求快速的获取日志文件，然后通过layui中提供的徽章进行加工处理，如此一来，通过颜色快速区分哪些是错误信息，哪些信息权重更大，更值得关注，此处引用了一个lodash.js，该js中提供了许多的辅助方法。&lt;/p&gt;</description></item><item><title>ABP框架开发-消息通知</title><link>/blog/2019-07-27-abp-notification/</link><pubDate>Sat, 27 Jul 2019 10:00:00 +0800</pubDate><guid>/blog/2019-07-27-abp-notification/</guid><description>&lt;p&gt;业务型网站使用过程中，消息通知是一个不可或缺的功能，采用站内通知、短信通知、邮件通知、微信通知等等各种方式都有，ABP框架对这部分工作已经封装的很好了，站在巨人的肩膀上，一览全貌，带来的就是心情舒畅。&lt;/p&gt;
&lt;p&gt;ABP官网地址：&lt;a href="https://aspnetboilerplate.com/"&gt;https://aspnetboilerplate.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="明确概念及设计"&gt;&lt;strong&gt;明确概念及设计&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;一次完整的消息发送/接收过程中，会存在几个必要的点，也正如同现实生活中的场景，有人发送，有人接收，发送的消息本身也有类型。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190727/193048657_50c15ef1-0f27-4ebf-bb68-b927ac59a28f.png" alt="193048657_50c15ef1-0f27-4ebf-bb68-b927ac59a28f"&gt;&lt;/p&gt;
&lt;h4 id="消息类型定义"&gt;&lt;strong&gt;消息类型定义&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;在ABP中已经提供了消息类型定义的相关类，但是需要我们去实现，在领域层新建一个Notifications文件夹并添加一个定义常用&lt;strong&gt;消息类型名称&lt;/strong&gt;的静态类如AppNotificationNames(名称可随意)，在其中开始定义消息类型名称。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 设置应用程序中常用通知唯一的名称常量
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public static class AppNotificationNames
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 新的任务
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string NewTask = &amp;#34;App.NewTask&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 简单消息
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const string SimpleMessage = &amp;#34;App.SimpleMessage&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其次，开始&lt;strong&gt;消息类型定义&lt;/strong&gt;，新建一个NotificationProvider的类，完成消息类型定义，管理消息类型时，可以针对消息设置权限，这样方便不同的角色拥有不同的消息通知。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 消息类型定义
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class AppNotificationProvider : NotificationProvider
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 设置消息类型
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;param name=&amp;#34;context&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void SetNotifications(INotificationDefinitionContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #region 任务提醒
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Manager.Add(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; new NotificationDefinition(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AppNotificationNames.NewTask,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; displayName: L(&amp;#34;NewTask&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; permissionDependency: new SimplePermissionDependency(PermissionNames.Pages_TaskManage)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; #endregion
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private static ILocalizableString L(string name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return new LocalizableString(name, SurroundConsts.LocalizationSourceName);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最后在领域层的Module.PreInitialize()中完成加入。&lt;/p&gt;</description></item><item><title>ABP框架开发-数据字典</title><link>/blog/2019-07-07-abp-datadictionary/</link><pubDate>Sun, 07 Jul 2019 10:00:00 +0800</pubDate><guid>/blog/2019-07-07-abp-datadictionary/</guid><description>&lt;p&gt;在业务型的系统开发中，我们需要维护各种个样的类型，比如客户类型、客户行业、商品类型等等，这些类型往往信息量不多，并且相似度极高，如果采用一类型一表去设计，将会造成极大的工作量，通过将这部分类型的信息进行抽象，利用字段去存储类型区分，共用表结构，来达到兼容各种类型的功能，也就是设计一个数据字典，而对于一个具体类型来讲，是有多个选项的，比如性别，有男女，行业有工农商等，对于这部分选项，可抽象为某个类型下的字典项，即数据字典项。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="数据字典设计思路"&gt;&lt;strong&gt;数据字典设计思路&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;从客户类型、商品类型、行业类型来抽象考虑，首先三者都存在一个类型描述，即客户、商品、行业，同时，三者是本质是不同的，并且，随着业务上的需求越来越多，更多的xx类型将会加入，因此，单从类型考虑出发，就存在三个点了，如类型名称、类型独立、数量扩展，因此在考虑表结构设计时，就可以先考虑到这三点了，同时还有一个关键的信息，便是，在系统设计过程中，这些类型其实便已经确定完毕了，而不是说，在开发完毕，再去系统中增加类型。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190707/193136081_53cee25e-aceb-47f4-a519-ac5a2600a982.png" alt="193136081_53cee25e-aceb-47f4-a519-ac5a2600a982"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从具体的某个类型出发考虑，比如以商品类型为例，存在日用品、电子产品、化妆品等，同样是存在几个关键信息，比如类型项名称、类型项独立、类型项数量扩展，类型项的归属，而这部分信息，往往是由客户去维护的，属于系统开发完毕后期的信息维护，在此，不考虑类型项的先后顺序问题，如有需要可以扩展。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190707/193137510_2bf2f3c5-10af-4610-b7fa-3c2e619bbeae.png" alt="193137510_2bf2f3c5-10af-4610-b7fa-3c2e619bbeae"&gt;
按照这些信息点，可以对数据字典设计一些必要的字段，如类型名称即TypeName、 类型独立便是类型间相互独立，但是这里也存在一个类型间的上下父子问题，暂不加入进来，该父子问题使用场景较少，但又存在，如果按照“二八原则”的话，我还是喜欢把“八”的部分完成。对于数据字典项而言，按照给定的必要信息，设计成如下结构，其中的业务代码，是需要唯一的，比如对于性别来将，业务代码便是1或0，来代表男女，这部分可由客户的系统管理员进行维护。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190707/193138540_5350071f-0870-480d-9674-c7058eb1e48a.png" alt="193138540_5350071f-0870-480d-9674-c7058eb1e48a"&gt;
 &lt;/p&gt;
&lt;h3 id="完成数据字典设计"&gt;&lt;strong&gt;完成数据字典设计&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在明确了这些基础信息后，开始在项目中完成设计过程，首先得明确数据字典本身的归属，数据字典是为整个业务而服务的，因此我把它划分到核心层这一级别中，首先在领域层设置Core层文件夹，用来存放为整个业务提供基础设施的功能模块。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190707/193139617_f2f0703c-01d2-4580-a61e-aa29f0e261f2.png" alt="193139617_f2f0703c-01d2-4580-a61e-aa29f0e261f2"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在Core层中加入数据字典模块，结构设计如：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190707/193140646_451ea04f-6ffa-4196-9761-093a451fec24.png" alt="193140646_451ea04f-6ffa-4196-9761-093a451fec24"&gt;
开始创建数据字典类，并添加设计的字段，以保证够用为前提，或许更多场景下会出现诸如父子字典情形，或是对字典内容的描述等，暂不考虑。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 核心_数据字典
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Table(&amp;#34;Core_DataDictionary&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class DataDictionary : Entity&amp;lt;long&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public const int MaxNameLength = 30;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 字典类型
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [StringLength(MaxNameLength)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public string TypeName { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 关联数据字典项
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public virtual ICollection&amp;lt;DataDictionaryItem&amp;gt; DataDictionaryItem { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在增加数据字典项类，并添加设计时的字段信息，这里我通过数据注解完成对字段的一些约束，如长度约束，表名的映射等。&lt;/p&gt;</description></item><item><title>数据库扩展表设计过程记录</title><link>/blog/2019-05-27-database-table-extension-design/</link><pubDate>Mon, 27 May 2019 10:00:00 +0800</pubDate><guid>/blog/2019-05-27-database-table-extension-design/</guid><description>&lt;p&gt;这两天需要实现一个动态表单设计，面对着属性的不确定，要能够容纳不同的属性进来，之前也接触过这方面的设计，但是没有设计好，导致问题太多，这一次参考一些前辈们的经验后，再次尝试一番，通过动态设计表结构，以达到任务要求。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="常用动态表结构设计方式"&gt;&lt;strong&gt;常用动态表结构设计方式&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;动态修改表，适应变化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;预留字段实现动态表结构(伪动态)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将动态属性全部保存在一个字段中，xml或是json格式保存(版本号+通用列)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;表结构和表数据分离，xml形式分别保存表结构和表数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;横向表转纵向表(属性字段行存储)。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于这几种方式，或许不同的选用适用不同的形式，我选择了最后一种来实现我现有的设计，这种方式个人感觉更加灵活，可以更方便的扩展属性(适合的才是最好的)。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="横向表转纵向表初步设计"&gt;&lt;strong&gt;横向表转纵向表初步设计&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先看下横向表的设计，如果采用横向表，因为业务的需要，要容纳好几种行业的信息进来，这样一来整张表的字段数将会非常多，从设计或是维护角度来讲，这都是一个棘手的芋头，因此传统的横向表设计不能满足现有的需求了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190527/193326873_a5c7b091-d00b-4c23-ba9f-c5615d7ad10b.png" alt="193326873_a5c7b091-d00b-4c23-ba9f-c5615d7ad10b"&gt;
按照横向表转纵向表的思路对表结构进行更改，通过设置成键值对形式，得到如下表结构。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190527/193328260_1fe25ca2-0ff1-4e21-a226-db7ff22c3142.png" alt="193328260_1fe25ca2-0ff1-4e21-a226-db7ff22c3142"&gt;
在这里可能有一个情况得想清楚了，我们每一次增加一条记录的时候，记录内的信息是作为同一批添加进来的，反过来，当我从数据库中取出数据时，也应该需要把同一批的记录信息取出来，因此在上面的设计中再加入一个GroupId用来区分同一批次的数据，而至于属性的重复量很大，之后将进行优化处理。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190527/193329490_7e715298-b5ce-4cee-83e3-e076fd36a3dd.png" alt="193329490_7e715298-b5ce-4cee-83e3-e076fd36a3dd"&gt;
现在看到这个结构时，对于动态扩充属性来讲，已经是达到了我的预期了，对于数据库设计时，将检测指标及限值均设置成字符串的，分组号我采用时间戳的形式进行存储，当然也可以采用其它更为稳妥的方式，如Guid或是自定义ID等。&lt;/p&gt;
&lt;p&gt;对于好多检测指标名称出现重复情况，我将这部分单独抽出来一张表用于存储检测指标属性，需要注意的是此处的名称需要在某个检测项目编号下唯一，该部分属性先在界面上呈现，其次呈现对应的数据，如果某列增加或删除了，对应展示行也就空着了一个数据或是消失了一个数据单元，而对于改了指标名称或是对外的展示名称，都不会影响数据的存储，通过默认值可以使得有些常用值不要再二次输入，减少工作量。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190527/193330446_977ebfc0-85cd-49d8-b706-f72863f7ad1e.png" alt="193330446_977ebfc0-85cd-49d8-b706-f72863f7ad1e"&gt;
在具体检测限值中完成对检测指标的关联，加入一列完成外键关联，同时对原有表内存在的列可以进行优化，因为这些信息都在检测指标中存在了，不必要的数据冗余还是不存在为好。单从现在的表结构来看，当我们按照在增加一些额外的属性时，可以做到不要去修改表结构，而只需要对表内数据进行管理即可。  &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190527/193331498_635d5468-38d7-441b-9d95-107b4173f077.png" alt="193331498_635d5468-38d7-441b-9d95-107b4173f077"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="代码实现过程"&gt;&lt;strong&gt;代码实现过程&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;此处我采用Asp.Net Core MVC并利用Razor语法，更为方便的完成表单展示工作，当然对于这部分工作，采用js或模板等等都是可以快速完成的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先对于界面添加检测指标的设计，遵循普通的表单设计方式即可，此处增加了两个隐藏元素，为适用于编辑场景而存在，此处快速略过提交到后台并保存到数据库的过程，可能需要在后台验证提交的名称的唯一性。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div class=&amp;#34;layui-fluid&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;form class=&amp;#34;layui-form&amp;#34; lay-filter=&amp;#34;layuiadmin-form-evaluationIndex&amp;#34; style=&amp;#34;padding: 15px 0 0 0;&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;hidden&amp;#34; name=&amp;#34;id&amp;#34; value=&amp;#34;@Model.Id&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;hidden&amp;#34; name=&amp;#34;evaluationStandardId&amp;#34; value=&amp;#34;@Model.EvaluationStandardId&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-form-item&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;label class=&amp;#34;layui-form-label&amp;#34;&amp;gt;名称&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-input-block&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;text&amp;#34; name=&amp;#34;name&amp;#34; lay-verify=&amp;#34;required&amp;#34; placeholder=&amp;#34;请输入名称&amp;#34; autocomplete=&amp;#34;off&amp;#34; class=&amp;#34;layui-input&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-form-item&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;label class=&amp;#34;layui-form-label&amp;#34;&amp;gt;显示名称&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-input-block&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;text&amp;#34; name=&amp;#34;displayName&amp;#34; lay-verify=&amp;#34;required&amp;#34; placeholder=&amp;#34;请输入显示名称&amp;#34; autocomplete=&amp;#34;off&amp;#34; class=&amp;#34;layui-input&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-form-item&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;label class=&amp;#34;layui-form-label&amp;#34;&amp;gt;默认值&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-input-block&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;text&amp;#34; name=&amp;#34;defaultValue&amp;#34; placeholder=&amp;#34;请输入默认值&amp;#34; autocomplete=&amp;#34;off&amp;#34; class=&amp;#34;layui-input&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-form-item&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;layui-input-block&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;button class=&amp;#34;layui-btn layui-hide&amp;#34; lay-submit lay-filter=&amp;#34;LAY-evaluationIndex-front-submit&amp;#34; id=&amp;#34;LAY-evaluationIndex-front-submit&amp;#34;&amp;gt;立即提交&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/form&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;对于增加具体的检测记录，需要先读取到整个检测项目下的所有检测指标，然后实现生成表单的过程，按照如下的思路一步一步实现：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190527/193332530_b0d8a4aa-cc08-4cc1-9a0c-a2dd6eed9388.png" alt="193332530_b0d8a4aa-cc08-4cc1-9a0c-a2dd6eed9388"&gt;
对于第一步，从数据库获取指定检测项目的检测指标，该步可以直接利用提供的id做一次查询即可得到相应的指标集合。然后在前端循环输出时，利用Razor语法完成动态渲染Html，生成label和input元素，依照之前设计检测指标时的name唯一，可以在此处设计表单时指定name属性。&lt;/p&gt;</description></item><item><title>ABP框架开发-异常信息</title><link>/blog/2019-04-27-abp-exceptionmessage/</link><pubDate>Sat, 27 Apr 2019 10:00:00 +0800</pubDate><guid>/blog/2019-04-27-abp-exceptionmessage/</guid><description>&lt;p&gt;接触ABP框架有一段时间了，也遇到了一些问题，看了官网文档，但是或许是看的不够细致的原因，实际开发中还是遇到了一些问题，耗费了时间去处理，回头一看，原来文档中早已提及。&lt;/p&gt;
&lt;p&gt;开发环境：ABP+MPA模式+Asp.Net Core&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="异常信息处理"&gt;&lt;strong&gt;异常信息处理&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;犹如ABP官网文档所介绍的，ABP已经帮我们把异常这块处理的很完善了，我们要做的就是利用好ABP处理异常的功能。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190427/193450408_4fb0916b-d0aa-4366-aebe-8d03ce41a304.png" alt="193450408_4fb0916b-d0aa-4366-aebe-8d03ce41a304"&gt;
ABP提供了直接将应用层对外服务的功能，通过ABP运行时所创建的动态API层，我们可以使用js方法去直接调用应用层服务，当然直接访问控制器下的方法并没有被舍弃。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190427/193452042_f64148ac-b5df-4996-9556-6763f4a4467c.png" alt="193452042_f64148ac-b5df-4996-9556-6763f4a4467c"&gt;
在ABP内自动封装好了一批处理异常的类，配合着这些异常类的使用，ABP在前端也封装了一些方法，方便我们处理异常信息，同时我们也可以改造前端在展示异常的方式，ABP封装的是使用Message API展示异常信息并使用的是sweetAlert插件，ABP前端js中提供了方式可以使得我们阻止默认的展示，进而使用自定义的展示插件和展示方式。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190427/193453045_ded04ee0-e561-4adc-8876-3c2309189d04.png" alt="193453045_ded04ee0-e561-4adc-8876-3c2309189d04"&gt;
在封装的类中，UserFriendlyException是对用户友好的，对于一些操作可能产生的问题，可以通过抛出UserFriendlyException直接展示给用户看，而对于其它异常，前端会将详细信息的隐藏，因为用户并不会关心具体报错原因，只知道是报错了。在代码中，有些操作可能需要我们进行判断，然后反馈给用户，以便提示用户更改相关数据。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[AbpAuthorize(AppPermissions.Pages_Standard_ItemCode_Create)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;private async Task CreateItemCodeAsync(CreateOrUpdateItemCodeInput input)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var itemCode = ObjectMapper.Map&amp;lt;ItemCode&amp;gt;(input.ItemCode);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; itemCode.Id = itemCode.CreateUniqueItemCode();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var existedItemCode = await _itemCodeRepository.FirstOrDefaultAsync(t =&amp;gt; t.Id.Equals(itemCode.Id));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (existedItemCode != null)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; throw new UserFriendlyException(L(&amp;#34;该检测项目已经存在.&amp;#34;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; await _itemCodeRepository.InsertAsync(itemCode);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如上，在做一个操作前，可能需要判断是否有相同的记录，如果有，需要提示给用户，通过直接抛出UserFriendlyException，在控制器内或在应用层抛出异常都行，可以将信息直接呈现给用户，但是需要注意的是有一些条件限制，需要满足相关的条件才能获取到该错误信息，否则很有可能拿到如下结果或是英文错误：&lt;strong&gt;&amp;ldquo;An error has occurred! Error detail not sent by server.&amp;rdquo;&lt;/strong&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190427/193454187_1c21c926-48cf-470c-87cf-b5db740d03ca.png" alt="193454187_1c21c926-48cf-470c-87cf-b5db740d03ca"&gt;
 &lt;/p&gt;
&lt;h3 id="展示异常信息的方式"&gt;&lt;strong&gt;展示异常信息的方式&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在ABP文档中，专门有一篇文章是处理异常的，&lt;a href="https://aspnetboilerplate.com/Pages/Documents/Handling-Exceptions"&gt;https://aspnetboilerplate.com/Pages/Documents/Handling-Exceptions&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190427/193455262_994a08a8-0dc7-4239-8a7e-088dd0a0fd96.png" alt="193455262_994a08a8-0dc7-4239-8a7e-088dd0a0fd96"&gt;&lt;/p&gt;</description></item><item><title>AspNet Core&amp;钉钉开发系列</title><link>/blog/2019-04-18-aspnetcore-dingding/</link><pubDate>Thu, 18 Apr 2019 10:00:00 +0800</pubDate><guid>/blog/2019-04-18-aspnetcore-dingding/</guid><description>&lt;p&gt;阿里钉钉在商业领域的规模越来越大，基于钉钉办公的企业越来越多，将一个企业内现有用到的工具(如钉钉)能够更融入到他们的工作中，提高工作效率，那便需要开发者不断的学习、应用了，同时，个人也有一个预感，未来围绕钉钉作为企业办公的必备利器，将会减少我们在软件开发过程中一些基础设施的建设，如部门、用户等这些的管理，更加偏重业务的价值了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190418/193604234_0cff72f3-d76f-432e-8de3-22a4220b57e8.png" alt="193604234_0cff72f3-d76f-432e-8de3-22a4220b57e8"&gt;
 &lt;/p&gt;
&lt;h3 id="接入钉钉初衷"&gt;&lt;strong&gt;接入钉钉初衷&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;钉钉内部的一些功能越来越丰富，如果结合到现有业务功能中，可以减少部分功能的再次开发，而是直接利用钉钉完成，比如，新上线的一个审批签名功能，如果能够引入到现有公司业务中，便更加方便了业务员在外的一些操作。可能有人喜欢自己造轮子，或许我的观点不太一样，我更喜欢站在巨人的肩膀上，毕竟人的精力是有限的。注册一个企业账号即可接入平台，企业账号不一定是要实际的企业账号(作为程序员来讲，创业也是一个梦想，直接用心仪的创业公司名称来练手那肯定是不错的了)。&lt;/p&gt;
&lt;p&gt;钉钉开发平台地址：&lt;a href="https://open.dingtalk.com/?spm=a3140.8736650.2231602.6.35653a1aJxWjAp&amp;amp;source=1001&amp;amp;lwfrom=2017120202091367000000111"&gt;https://open.dingtalk.com/?spm=a3140.8736650.2231602.6.35653a1aJxWjAp&amp;amp;source=1001&amp;amp;lwfrom=2017120202091367000000111&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="认清需要哪种形式的服务"&gt;&lt;strong&gt;认清需要哪种形式的服务&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;参考了钉钉开发文档：&lt;a href="https://open-doc.dingtalk.com/"&gt;https://open-doc.dingtalk.com/&lt;/a&gt;，看完后感觉有点绕，针对一个类型来讲有多种方式，而针对多个类型，有更多方式。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190418/193605647_3d2efce5-691f-4635-89ca-3dd7633a9a13.png" alt="193605647_3d2efce5-691f-4635-89ca-3dd7633a9a13"&gt;
因此特意汇总成一图解释，其中移动开发部分进行了过滤，对移动开发部分经验空白，不敢引入其中。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190418/193606907_a870d024-7620-4dce-94e1-8e4a5fa3655d.png" alt="193606907_a870d024-7620-4dce-94e1-8e4a5fa3655d"&gt;
如果是直接利用钉钉开发应用然后在钉钉上面使用，那么选择快速入门中的资料即可，具体是用于企业内部，还是用于第三方企业，还是第三方个人，再看场景。&lt;/p&gt;
&lt;p&gt;如果是在企业现有系统中去加入一些钉钉的功能，比如消息通知等等，调用钉钉服务端接口即可，我使用该部分的频率较高，可以在现有企业系统使用到钉钉内的一些功能，但不需要完全接入到钉钉中。&lt;/p&gt;
&lt;p&gt;如果是使用前端通过JS调用或展示一些信息，通过前端API的形式实现即可，其中E应用需要为移动端，PC端暂不支持，H5微应用支持PC、移动端。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="后期接入工作"&gt;&lt;strong&gt;后期接入工作&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;参照&lt;strong&gt;企业内部应用&lt;/strong&gt;中服务端Api接入方式，将现有Asp.Net Core应用程序与钉钉完成一系列交互工作，利用钉钉提供的一些功能，在此基础上尝试一些能够促进工作效率的功能。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="系列文章"&gt;&lt;strong&gt;系列文章&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/10416897.html"&gt;Asp.Net Core对接钉钉群机器人&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2019-04-18，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>AspNet Core&amp;Jenkins持续交付到Windows Server</title><link>/blog/2019-03-21-aspnetcore-jenkins-ci-windowsserver/</link><pubDate>Thu, 21 Mar 2019 10:00:00 +0800</pubDate><guid>/blog/2019-03-21-aspnetcore-jenkins-ci-windowsserver/</guid><description>&lt;p&gt;对于Linux平台上的持续集成和持续交付可以使用Docker快捷的完成任务，但是Windows Server下却不好使用，一般来讲，windows Server下最简单的发布方式是直接手动打包发布或是利用webDeploy完成，但是手动发布太麻烦，webDeploy配置也麻烦，因此尝试使用Jenkins+Powershell，将代码拉取到Windows Server上然后通过dotnet命令发布，并通过Powershell更新相应的网站，但是首次网站配置工作，还是得手动进行发布工作，对于在公司内部服务器测试来讲，可以减轻许多工作量，做到代码合并到主干即可更新测试服务器。&lt;/p&gt;
&lt;p&gt;本地环境及工具：Windows Server 2008R2+Jenkins+Powershell+.Net Core2.2 &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="服务器安装jenkins"&gt;&lt;strong&gt;服务器安装Jenkins&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;从&lt;a href="https://jenkins.io/download/"&gt;Jenkins官网&lt;/a&gt;下载Windows下的Jenkins程序,下载的压缩包解压后为msi版本，作为系统服务在后台运行，移入到服务器中&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193705541_88d73172-1257-4695-889e-2b46a30ce1f6.png" alt="193705541_88d73172-1257-4695-889e-2b46a30ce1f6"&gt;
执行安装，按照正常的安装步骤安装即可&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193706926_862d7d1a-f4d6-4399-a899-22bb1ee7fbac.png" alt="193706926_862d7d1a-f4d6-4399-a899-22bb1ee7fbac"&gt;
安装完毕会自动打开网站或手动访问localhost:8080，开始配置Jenkins，找到网站中指定的路径中，拿到生成的密码解锁Jenkins。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193707957_abc8574a-dd5b-44eb-9068-d92db3159a82.png" alt="193707957_abc8574a-dd5b-44eb-9068-d92db3159a82"&gt;&lt;/p&gt;
&lt;p&gt;下一步安装推荐的插件即可,如果知道哪些插件要安装则选择另一个选项，此处我选择第一个也是默认选项，然后等待安装即可。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193708947_0597f8dd-ebc0-44d6-a29d-0fecef5d365b.png" alt="193708947_0597f8dd-ebc0-44d6-a29d-0fecef5d365b"&gt;
安装完毕，配置管理员账号密码完毕即可，完成后自动跳转到Jenkins中，下次登录时需要用刚配置好的账号密码登录。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193709931_a8fc24a8-0839-45a3-bc21-04652b3bbc43.png" alt="193709931_a8fc24a8-0839-45a3-bc21-04652b3bbc43"&gt;
 &lt;/p&gt;
&lt;h3 id="服务器配置iis安装net-core-sdk及aspnetcoremodule"&gt;&lt;strong&gt;服务器配置IIS、安装.Net Core SDK及AspNetCoreModule&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="配置iis及相关需要的服务"&gt;&lt;strong&gt;配置IIS及相关需要的服务&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;通过&lt;strong&gt;图形界面配置IIS&lt;/strong&gt;并安装需要的服务,可参考Windows ServerR2配置教程：&lt;a href="https://jingyan.baidu.com/article/5d6edee2e034f099eadeec3c.html"&gt;https://jingyan.baidu.com/article/5d6edee2e034f099eadeec3c.html&lt;/a&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193711047_c3faa66b-d193-46f2-a523-7a9a764fa2cb.png" alt="193711047_c3faa66b-d193-46f2-a523-7a9a764fa2cb"&gt;&lt;/p&gt;
&lt;p&gt;或通过&lt;strong&gt;脚本配置IIS&lt;/strong&gt;，打开Windows Powershell Modules工具，先通过命令查看本地的功能。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;get-command -module servermanager
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193712336_8f34b45a-c28e-4ae1-bfc7-421cec91487c.png" alt="193712336_8f34b45a-c28e-4ae1-bfc7-421cec91487c"&gt;
如我本地是Add-开头，则需要将&lt;strong&gt;Install-WindowsFeature&lt;/strong&gt;更换成&lt;strong&gt;Add-WindowsFeature，否则稍后的操作会得到如下错误&lt;/strong&gt;，在2008R2上是Add-开头，而在2008R2以后的版本是Install-开头，此部分可以查看&lt;a href="https://docs.microsoft.com/zh-cn/previous-versions/windows/powershell-scripting/jj205467%28v=wps.630%29"&gt;微软官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193713368_015d8526-69c3-4392-8f5d-226e4f07f285.png" alt="193713368_015d8526-69c3-4392-8f5d-226e4f07f285"&gt;
再次查看IIS服务并得到展示可以选择的服务，该部分服务就是图形化界面中的选项菜单，选择好需要的服务后&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;get-windowsfeature web*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193714350_7e0433f8-ac6c-4eda-9510-32a2f845eaf7.png" alt="193714350_7e0433f8-ac6c-4eda-9510-32a2f845eaf7"&gt;
执行以下命令完成脚本配置IIS，可以比对脚本内的服务和powershell中的服务，直接继续按照命令安装即可，至此IIS配置及安装需要的服务完成。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$IISFeatures = &amp;#34;Web-WebServer&amp;#34;,&amp;#34;Web-Common-Http&amp;#34;,&amp;#34;Web-Default-Doc&amp;#34;,&amp;#34;Web-Dir-Browsing&amp;#34;,&amp;#34;Web-Http-Errors&amp;#34;,&amp;#34;Web-Static-Content&amp;#34;,&amp;#34;Web-Http-Redirect&amp;#34;,&amp;#34;Web-Health&amp;#34;,&amp;#34;Web-Http-Logging&amp;#34;,&amp;#34;Web-Custom-Logging&amp;#34;,&amp;#34;Web-Log-Libraries&amp;#34;,&amp;#34;Web-ODBC-Logging&amp;#34;,&amp;#34;Web-Request-Monitor&amp;#34;,&amp;#34;Web-Http-Tracing&amp;#34;,&amp;#34;Web-Performance&amp;#34;,&amp;#34;Web-Stat-Compression&amp;#34;,&amp;#34;Web-Security&amp;#34;,&amp;#34;Web-Filtering&amp;#34;,&amp;#34;Web-Basic-Auth&amp;#34;,&amp;#34;Web-Client-Auth&amp;#34;,&amp;#34;Web-Digest-Auth&amp;#34;,&amp;#34;Web-Cert-Auth&amp;#34;,&amp;#34;Web-IP-Security&amp;#34;,&amp;#34;Web-Windows-Auth&amp;#34;,&amp;#34;Web-App-Dev&amp;#34;,&amp;#34;Web-Net-Ext&amp;#34;,&amp;#34;Web-Asp-Net&amp;#34;,&amp;#34;Web-ISAPI-Ext&amp;#34;,&amp;#34;Web-ISAPI-Filter&amp;#34;,&amp;#34;Web-Mgmt-Tools&amp;#34;,&amp;#34;Web-Mgmt-Console&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Install-WindowsFeature -Name $IISFeatures
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193715497_da34fe72-5e36-419d-a99c-d1398f95318d.png" alt="193715497_da34fe72-5e36-419d-a99c-d1398f95318d"&gt;
可能会得到如下错误信息，需要修改Powershell的执行策略。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193716440_0673d00f-0ac3-4203-a085-c4319545cd37.png" alt="193716440_0673d00f-0ac3-4203-a085-c4319545cd37"&gt;&lt;/p&gt;
&lt;p&gt;先通过命令查看当前策略&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;get-executionpolicy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;设置当前计算机允许执行脚本&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;set-executionpolicy remotesigned
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;再次执行配置IIS的脚本等待安装直到成功。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193717415_c8a5cfb1-caac-49d4-82a1-bed6a4f15562.png" alt="193717415_c8a5cfb1-caac-49d4-82a1-bed6a4f15562"&gt;
 &lt;/p&gt;
&lt;h4 id="安装net-core环境"&gt;&lt;strong&gt;安装.Net Core环境&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;先从官网下载.Net Core SDK和用于与IIS打交道的Runtime &amp;amp; Hosting Bundle，.Net Core SDK不是必须的，如果服务器上只做运行环境的话，但是考虑到做持续交付，得用到SDK完成编译、发布，因此还是装上SDK。此处以&lt;a href="https://dotnet.microsoft.com/download/dotnet-core/2.2"&gt;.Net Core2.2&lt;/a&gt;版本为例下载，然后执行安装，可能得注意下安装顺序，先安装SDK程序，后安装Runtime&amp;amp;HostingBundle程序，安装完毕后在IIS的模块页中查看刚刚安装的模块。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190321/193718374_872971f4-cab6-44f8-aecd-426ee61cd362.png" alt="193718374_872971f4-cab6-44f8-aecd-426ee61cd362"&gt;
 &lt;/p&gt;
&lt;h4 id="脚本一键安装并配置"&gt;&lt;strong&gt;脚本一键安装并配置&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;通过Powershell中执行一整套命令完成IIS、.Net Core SDK和Runtime &amp;amp; Hosting Bundle的安装及配置工作，需要注意的是，该脚本中部分命令需要在Windows Server2008R2后的版本才能运行。&lt;/p&gt;</description></item><item><title>AspNet Core对接钉钉群机器人</title><link>/blog/2019-03-02-aspnetcore-dingtalk-grouprobot/</link><pubDate>Sat, 02 Mar 2019 10:00:00 +0800</pubDate><guid>/blog/2019-03-02-aspnetcore-dingtalk-grouprobot/</guid><description>&lt;p&gt;钉钉作为企业办公越来越常用的软件，对于企业内部自研系统提供接口支持，以此来打通多平台下的数据，本次先使用最简单的钉钉群机器人完成多种形式的消息推送，参考钉钉开发文档中&lt;a href="https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq"&gt;自定义机器人环节&lt;/a&gt;，此次尝试所花的时间不多，但有几个地方是需要注意的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190302/193837407_d20486c4-d56b-4512-824c-509c6e8e87cd.png" alt="193837407_d20486c4-d56b-4512-824c-509c6e8e87cd"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="钉钉群中建立机器人获取webhook地址"&gt;&lt;strong&gt;钉钉群中建立机器人获取WebHook地址&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先得有一个钉钉群，如果没有得自行创建一个了，通过群内右上角菜单中找到群机器人然后添加一个自定义机器人&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190302/193838740_1bf92f75-3ecf-4b8c-b0e5-a824ab31e0d7.png" alt="193838740_1bf92f75-3ecf-4b8c-b0e5-a824ab31e0d7"&gt;
并设置消息推送开启(默认是开启)，复制下一行的webhook地址，该地址将作为后面消息推送的地址&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190302/193839983_971964f7-8316-429f-9343-d52cebd0196e.png" alt="193839983_971964f7-8316-429f-9343-d52cebd0196e"&gt;
完成即可，如果不确定该地址是否有效可以用命令测试一下，比如在Linux平台下，通过该命令并将自己的webhook_token替换&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl &amp;#39;https://oapi.dingtalk.com/robot/send?access_token=cd1eb120c459ced6a65491af7b1eebbc84790fb672077a969bae8bb203aa1c52&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &amp;#39;Content-Type: application/json&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&amp;#34;msgtype&amp;#34;: &amp;#34;text&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;text&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;content&amp;#34;: &amp;#34;我就是我, 是不一样的烟火&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后回车进行测试，即刻收到群机器人推送的消息&lt;br&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190302/193841169_5cd6141d-deda-443b-a61e-3e9f2d81db35.png" alt="193841169_5cd6141d-deda-443b-a61e-3e9f2d81db35"&gt;
 &lt;/p&gt;
&lt;h3 id="参考文档完成基础类的封装并处理相关字段"&gt;&lt;strong&gt;参考文档完成基础类的封装并处理相关字段&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;接下来开始在代码中完成对WebHook地址的调用，可以先分析一下&lt;a href="https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq"&gt;群机器人的文档&lt;/a&gt;，可以获悉有五种消息类型：&lt;strong&gt;文本 (text)、连接 (link)、markdown (markdown)、ActionCard、FeedCard消息类型&lt;/strong&gt;，其中的actionCard分整体和独立两类。针对这些类型及给出的参数要求完成基础类的设计和封装，&lt;strong&gt;以text类型为例：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190302/193842253_06e5f98d-76ec-4e14-929e-3229058ae28e.png" alt="193842253_06e5f98d-76ec-4e14-929e-3229058ae28e"&gt;
其中的msgtype是五种消息类型的字符串，因此针对该部分设计一个枚举，作为消息类型的区分。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 钉钉群机器人消息类型枚举
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public enum MsgTypeEnum
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; text,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; link,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; markdown,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; actionCard,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; feedCard
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;设计一个text类并给定一个属性Content，在设计时我们喜欢使用帕斯卡命名法，但是钉钉接口却不允许，如果不做一些处理，直接使用Content属性将会调用不通接口，&lt;strong&gt;这点需要注意&lt;/strong&gt;，使用Newtonjson提供的打包成json时用指定的名称替换来满足钉钉接口需求。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// 文本类型
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class Text
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 文本内容
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [JsonProperty(PropertyName = &amp;#34;content&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public string Content { get; set; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其次对指定人群做一个类的封装，同样需要处理其中的属性在序列化时的替换名称。&lt;/p&gt;</description></item><item><title>AspNet Core中HttpClient的使用方式</title><link>/blog/2019-02-26-aspnetcore-httpclient-basicusage/</link><pubDate>Tue, 26 Feb 2019 10:00:00 +0800</pubDate><guid>/blog/2019-02-26-aspnetcore-httpclient-basicusage/</guid><description>&lt;p&gt;在.Net Core应用开发中，调用第三方接口也是常有的事情，HttpClient使用人数、使用频率算是最高的一种了，在.Net Core中，HttpClient的使用方式随着版本的升级也发生了一些变化，本次就讲解一下Asp.Net Core2.1前后使用的两种方式。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="原先httpclient使用方式"&gt;&lt;strong&gt;原先HttpClient使用方式&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;一般来讲，喜欢要用的时候才会选择去获取资源，因此，当在有需求时才会用HttpClient去调用资源，便会使用如下这种方式或其它方式获取资源。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;//do something...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (var httpClient = new HttpClient())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var requestUri = &amp;#34;http://aspnetcore.online/api/resource/getresource&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var httpResponseMessage = await httpClient.GetAsync(requestUri);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //do something...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return Ok(httpResponseMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果可以正常访问目标地址的话，则会返回相应的资源信息。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190226/193934620_e7a2cda5-5116-4dff-83b6-154063d1442e.png" alt="193934620_e7a2cda5-5116-4dff-83b6-154063d1442e"&gt;
又如Post方式提交并返回相应的内容，都是可以直接使用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;//do something...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using (var httpClient = new HttpClient())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var requestUri = &amp;#34;http://aspnetcore.online/api/resource/postresource&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var httpResponseMessage = await httpClient.PostAsJsonAsync(requestUri,&amp;#34;星城软件&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //do something...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return Ok(httpResponseMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但是这种情况下会出现一个严重的问题，在不停的调用情形下，tcp连接数会被耗尽，虽然使用using方式调用HttpClient并在退出前调用Dispose()方法将HttpClient释放了，但是tcp连接仍然处于保持状态，在240s后才会自动断开，这里就涉及到一个连接状态了，首先得理解下http的工作原理，http协议是建立在tcp协议基础之上，当浏览器需要从服务器获取数据的时候，会发出一次http请求。http会通过tcp建立起一个到服务器的连接通道，当本次请求需要的数据完毕后，http会立即将tcp连接断开，这个过程是很短的。所以http连接是一种短连接，是一种无状态的连接。但是tcp的连接只要我们不通过代码把连接关闭，这个连接就会在客户端和服务端的进程中一直存在，相关状态数据会一直保存着，直到无响应状态持续了默认关闭时间后自动断开。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190226/193937220_ad081e12-7fb1-41dc-ac53-51c75bbe00ae.png" alt="193937220_ad081e12-7fb1-41dc-ac53-51c75bbe00ae"&gt;
当短期请求量过大时，这就可能导致了&amp;quot;套接字资源耗尽异常&amp;quot;，因此，为了解决这个问题，想到不释放HttpClient，将它作为单例一直使用，实现单例方式有很多种。&lt;/p&gt;
&lt;p&gt;如使用单例模式，只生成一个HttpClient&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;private static HttpClient _httpClient = null;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public HttpClient CreateHttpClient()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (_httpClient == null) _httpClient = new HttpClient();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return _httpClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;亦或是在初始化时完成单例注入，创建一个IHttpClient接口，及相应的实现StandardHttpClient，实现类种加入HttpClient属性，在实现类构造函数中完成初始化后便可直接使用该实现类完成资源请求工作。&lt;/p&gt;</description></item><item><title>ABP框架开发-XAdmin前端</title><link>/blog/2019-02-21-abp-xadmin/</link><pubDate>Thu, 21 Feb 2019 10:00:00 +0800</pubDate><guid>/blog/2019-02-21-abp-xadmin/</guid><description>&lt;p&gt;“站在巨人的肩膀上”，这样一来，不要万事亲恭，在值得的方向上节约时间，毕竟人生就这么一次。在接触ABP以来，一直想花点时间整合LayUI前端框架到ABP中，进而能够逐渐打磨出一套适合自己的框架，开发习惯、项目架构、界面风格等等。在年前花了点时间了解了LayUI现有的后台模板，虽然LayUI自家的LayUIAdmin很不错，但奈何于贫穷，因此绕道看看是否有前端同胞已经弄出来一份免费版的，比较了几份后台模板后，找到了合适的X-Admin。&lt;/p&gt;
&lt;p&gt;演示地址：&lt;a href="http://123.207.15.233:9527/"&gt;http://123.207.15.233:9527/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;码云地址：&lt;a href="https://gitee.com/530521314/Partner.Surround.git"&gt;https://gitee.com/530521314/Partner.Surround.git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="x-admin前端框架和abp框架"&gt;&lt;strong&gt;X-Admin前端框架和ABP框架&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;X-Admin是基于LayUI打造的一套前端框架，比起Bootstrap那种粗犷的风格，LayUI更加符合国人的审美观，同时LayUI也对后端开发人员友好，在不完全掌握前端知识的前提下也能够进行项目开发，官网地址：&lt;a href="http://x.xuebingsi.com/"&gt;http://x.xuebingsi.com/&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;ABP框架是一个用最佳实践和流行技术开发现代Web应用程序的新起点，旨在快速搭建一个通用的Web应用框架和项目模板，集成着一些优秀的工具和思想，官网地址：&lt;a href="https://aspnetboilerplate.com/"&gt;https://aspnetboilerplate.com/&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;如果觉得自己组合一套框架觉得繁琐，可以试试使用52ABP框架，该套优秀的前后端框架由梁桐铭在ABP框架的基础上进行整合，对于前后端相关功能进行改造，使满足国人的开发习惯，官网地址：&lt;a href="https://www.52abp.com/"&gt;https://www.52abp.com/&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id="开始整合两个框架"&gt;&lt;strong&gt;开始整合两个框架&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;先从ABP官网下载项目模板。经过文件结构调整后，项目文件路径如下，之所以项目文件路径和官网下载下来的不完全一致，是经过了一些调整，适用自己的开发习惯，毕竟适合的才是最好的，在之前的&lt;a href="https://www.cnblogs.com/CKExp/p/9860427.html"&gt;生成流水线搭建中&lt;/a&gt;也提到过项目结构，目的是使之标准化，而不需要每次都要重新设置一下项目文件结构。(&lt;strong&gt;注意，此处我下载的是.Net Core+MPA版+去掉登录、注册、用户、角色和多租户功能&lt;/strong&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194105734_0412cd5b-4a34-423c-b522-6bb9b9bfe154.png" alt="194105734_0412cd5b-4a34-423c-b522-6bb9b9bfe154"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;下载X-Admin源码包，解压后，开始着手Ctrl+C、V操作，将需要的一些js、css等文件移入到ABP中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194107430_9997c6bf-a4d4-45b5-9828-d0b7d8f09936.png" alt="194107430_9997c6bf-a4d4-45b5-9828-d0b7d8f09936"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;去除在本次整合后部分在ABP中的不再需要的功能，如Boostrap及其相关的插件，并不是Boostrap不优秀而是LayUI是我的目的，功能相似下，没必要在冗余存在。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194108414_6e9a7bed-6128-4124-8144-377f50327f8a.png" alt="194108414_6e9a7bed-6128-4124-8144-377f50327f8a"&gt;
并在bower包管理中删除对Boostrap的依赖，尽可能的使用大部分LayUI及LayUI社区提供的插件和功能，以减少后端开发人员的学习和使用成本。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194109452_f794e14c-e8b0-44c1-9814-04427efdb765.png" alt="194109452_f794e14c-e8b0-44c1-9814-04427efdb765"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将X-Admin文件夹中的js文件及css文件移入到ABP中，部分ABP分装了一层的js、css保持不变，仍然先存放着，如果后期确定不再使用，再进行移除ABP下载后自带了一个main.css文件，可以使用这个文件或直接使用X-Admin中的css文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194110562_72d8a716-3e90-4f98-8af2-58da52a6881b.png" alt="194110562_72d8a716-3e90-4f98-8af2-58da52a6881b"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以现有的Layout母版页复制一个(此处我命名为_LayoutBase)，从X-Admin文件夹中查看index.html并复制Body标签内的代码到_Layout母版页的Body中(我对底部的footer删除了，位置不太合适)，整理引用的js、css文件，具体可见我的Git仓库中，使用控制器Home,方法Index的视图作为使用_LayoutBase母版页的视图，其余页面都将在Index界面中以iframe的形式出现，到现在，直接启动项目后，初步的界面可以展现出来了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194111581_c12263b3-931c-4a60-b8d8-2ab268b28fec.png" alt="194111581_c12263b3-931c-4a60-b8d8-2ab268b28fec"&gt;
 &lt;/p&gt;
&lt;h3 id="优化展示效果"&gt;&lt;strong&gt;优化展示效果&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;X-Admin中，对于侧边栏收缩采用的是直接全部隐藏，在该功能的基础上改造一些功能，使得收缩后可以留下一列图标，实现方式可以有很多种，此处采取使用js脚本去控制左侧菜单宽度实现，在xadmin.js脚本中，找到**$(&amp;rsquo;.container .left_open&amp;rsquo;).click()**方法，然后在此基础上进行改造，如以下改造，注意，部分css也需要改造，具体见仓库源代码中。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$(&amp;#39;.container .left_open&amp;#39;).click(function (event) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (!$(&amp;#39;.left-nav #nav .layui-nav-item&amp;#39;).hasClass(&amp;#39;nav-mini&amp;#39;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //左侧菜单
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.left-nav #nav .layui-nav-item&amp;#39;).addClass(&amp;#39;nav-mini&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.left-nav&amp;#39;).animate({ left: &amp;#39;0px&amp;#39;, width: &amp;#39;60px&amp;#39; }, 100);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.sub-menu li&amp;#39;).each(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(this).hide();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //右侧内容
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.page-content&amp;#39;).animate({ left: &amp;#39;60px&amp;#39; }, 100);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.page-content-bg&amp;#39;).hide();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } else {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //左侧菜单
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.left-nav .layui-nav-item&amp;#39;).removeClass(&amp;#39;nav-mini&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.left-nav&amp;#39;).animate({ left: &amp;#39;0px&amp;#39;, width: &amp;#39;220px&amp;#39; }, 100, function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.sub-menu li&amp;#39;).each(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(this).show();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; //右侧内容
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.page-content&amp;#39;).animate({ left: &amp;#39;220px&amp;#39; }, 100);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if ($(window).width() &amp;lt; 768) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#39;.page-content-bg&amp;#39;).show();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;加入语言为简体中文，在领域层中的本地资源配置中加入中文及中文语言包，此处我将中文设置为默认语言，更改原有默认的英文包的PithyDesign为PithyDesign-en，并增加一个PithyDesign.json内容与英文包一样，将英文译成中文即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194112641_d89b73ae-1e66-48c2-a07f-f8645f7a6721.png" alt="194112641_d89b73ae-1e66-48c2-a07f-f8645f7a6721"&gt;
增加中文并设置为默认选项，如有更多语言，可以在此基础上进行扩展。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190221/194113730_383b0b3e-365d-4816-b6a5-ad886ba3dcd5.png" alt="194113730_383b0b3e-365d-4816-b6a5-ad886ba3dcd5"&gt;&lt;/p&gt;</description></item><item><title>Mysql常用基础操作(备忘录)</title><link>/blog/2019-01-13-mysql-basicusage/</link><pubDate>Sun, 13 Jan 2019 10:00:00 +0800</pubDate><guid>/blog/2019-01-13-mysql-basicusage/</guid><description>&lt;p&gt;常常忘记mysql的一些命令行操作，甚至于说，比较复杂的sql格式记不住或忘记了，也可能根本不会考虑去记，因此，做一下汇总，当下次出现恍惚时不至于去百度挨个找，有时就是记不起来，但是只要给点药引子，立马会想起来。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="基础操作"&gt;&lt;strong&gt;基础操作&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;mysql命令规则：每行sql后边都跟个分号表示一个命令语句结束&lt;/p&gt;
&lt;p&gt;1、登录本地mysql数据库&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ mysql -u root -p
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ password
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;2、更改root密码&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;mysqladmin -uroot password &amp;#39;yourpassword&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt; 3、登录远程mysql数据库(主机地址，端口号，用户，密码)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;mysql -uroot -p -h192.168.137.10 -P3306
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt; 4、退出mysql&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;exit;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;5、启动mysql服务(CentOS下)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl start mysqld.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt; 6、结束mysql服务(CentOS下)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl stop mysqld.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt; 7、启动mysql服务(CentOS下)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl restart mysqld.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;  &lt;/p&gt;
&lt;h3 id="数据库操作"&gt;&lt;strong&gt;数据库操作&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;1、创建数据库&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;create databases 数据库名;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;2、删除数据库&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;drop database 数据库名;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;3、修改数据库编码方式&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;alter database 数据库名 default character set 编码方式 collate 编码方式_bin;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;4、查看所有数据库&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;show database;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;5、切换数据库&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;use &amp;#39;数据库名&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;6、查看当前数据库所支持的字符集&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql&amp;gt;show charset;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt; &lt;/p&gt;</description></item><item><title>AspNet Core&amp;Docker部署到树莓派3B中</title><link>/blog/2019-01-07-aspnetcore-docker-raspberrypi3b/</link><pubDate>Mon, 07 Jan 2019 10:00:00 +0800</pubDate><guid>/blog/2019-01-07-aspnetcore-docker-raspberrypi3b/</guid><description>&lt;p&gt;花了一点时间将吃灰数月的树莓派装上了Docker，并在容器中部署了一个Asp.Net Core应用程序，通过花生壳映射树莓派中的程序，可以使用外网访问树莓派，玩起来很有意思(外网访问地址：&lt;a href="http://aspnetcore.online/"&gt;http://aspnetcore.online/&lt;/a&gt;，访问不通可能树莓派断电了)，过程中发现了一点问题，或许对于同样路线的朋友有所帮助，特此写出玩转过程。&lt;/p&gt;
&lt;p&gt;码云Demo地址：&lt;a href="https://gitee.com/530521314/ControlEvent.git"&gt;https://gitee.com/530521314/ControlEvent.git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="树莓派安装操作系统"&gt;&lt;strong&gt;树莓派安装操作系统&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;进入官网下载操作系统：&lt;a href="https://www.raspberrypi.org/downloads/"&gt;https://www.raspberrypi.org/downloads/&lt;/a&gt;，我使用了官方的Raspbian系统，并且选择了无界面的轻量版&lt;strong&gt;Raspbian Stretch Lite.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190107/194340160_0b6a482a-b309-43b2-b1c4-2ea2307f28b5.png" alt="194340160_0b6a482a-b309-43b2-b1c4-2ea2307f28b5"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;下载格式化SD卡的工具：&lt;a href="https://www.sdcard.org/downloads/formatter_4/eula_windows/"&gt;https://www.sdcard.org/downloads/formatter_4/eula_windows/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下载将系统烧录到SD卡中的工具Win32 DiskImager：&lt;a href="http://sourceforge.net/projects/win32diskimager/"&gt;http://sourceforge.net/projects/win32diskimager/&lt;/a&gt;，选择系统磁盘路径地址，选择卡，然后执行Write即可，等待几分钟，便完成烧录工作&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="194341790_"&gt;  &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190107/194341790_f5040a48-304d-44e4-99da-40248ad0e4af.png" alt="194341790_f5040a48-304d-44e4-99da-40248ad0e4af"&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;做一些配置工作，方便启动树莓派后可以快速玩转&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;启动SSH，在卡分区内创建一个名为ssh的文件夹即可&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置无线连接，本次我没有使用外接显示器到树莓派中，使用Putty工具直接无线连接，个人感觉更为方便，在卡内boot文件夹内，创建一个wpa_supplicant.conf的文件，并写下如下内容，该内容在树莓派通电后会由系统自动添加到/etc/wpa_supplicant/wpa_supplicant.conf中，设置完毕后便可以插入到树莓派，开始通电了&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;country&lt;span style="color:#f92672"&gt;=&lt;/span&gt;CN
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ctrl_interface&lt;span style="color:#f92672"&gt;=&lt;/span&gt;DIR&lt;span style="color:#f92672"&gt;=/&lt;/span&gt;var&lt;span style="color:#f92672"&gt;/&lt;/span&gt;run&lt;span style="color:#f92672"&gt;/&lt;/span&gt;wpa_supplicant &lt;span style="color:#66d9ef"&gt;GROUP&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;netdev
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;update_config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;//&lt;/span&gt;WiFi没有密码
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;network&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;你的无线网络名称（ssid）&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key_mgmt&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;NONE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;//&lt;/span&gt;WiFi使用WEP加密
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;network&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;无线网络名称（ssid）&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key_mgmt&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;NONE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wep_key0&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;wifi密码&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;//&lt;/span&gt;WiFi使用WPA&lt;span style="color:#f92672"&gt;/&lt;/span&gt;WPA2加密
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;network&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;无线网络名称（ssid）&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key_mgmt&lt;span style="color:#f92672"&gt;=&lt;/span&gt;WPA&lt;span style="color:#f92672"&gt;-&lt;/span&gt;PSK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; psk&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;wifi密码&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;//&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;以我的举例&lt;/span&gt;(priority连接优先级最高)&lt;span style="color:#960050;background-color:#1e0010"&gt;：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;network&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ssid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Yang1802&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key_mgmt&lt;span style="color:#f92672"&gt;=&lt;/span&gt;WPA&lt;span style="color:#f92672"&gt;-&lt;/span&gt;PSK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; psk&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;xxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; priority&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="3"&gt;
&lt;li&gt;使用Putty连接树莓派，树莓派ip地址可以找办法查看（如我使用的华为路由器，则可以登录路由器管理平台查看），使用账号pi，密码raspberry完成登录，开始校正树莓派时间&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// 设置时区为 亚洲（Asia） 上海（Shanghai）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo dpkg-reconfigure tzdata
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// 启动 NTP 使计算机时钟与 Internet 时间服务器同步
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo timedatectl set-ntp true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;切换到root账号，方便操作&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo su
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;  &lt;/p&gt;</description></item><item><title>KnockoutJS-与服务端交互</title><link>/blog/2019-01-02-knockoutjs-http/</link><pubDate>Wed, 02 Jan 2019 10:00:00 +0800</pubDate><guid>/blog/2019-01-02-knockoutjs-http/</guid><description>&lt;p&gt;几乎所有Web应用程序都要和服务器端交换数据，交换数据时最方便的就是使用&lt;a href="http://json.org/"&gt;JSON格式&lt;/a&gt;。Knockout可以实现很复杂的客户端交互，对于前后端交互使用的技术最为基本且常用的是Ajax，本次利用Ajax和ko的双向绑定完成一些简单的功能，可以快速展示出ko的方便之处。&lt;/p&gt;
&lt;p&gt;代码地址： &lt;a href="https://gitee.com/530521314/Partner.TreasureChest.git"&gt;https://gitee.com/530521314/Partner.TreasureChest.git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="服务端返回数据绑定到客户端"&gt;&lt;strong&gt;服务端返回数据绑定到客户端&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;准备好一些初始数据，直接通过View方法将视图实体返回到视图文件中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194450194_9554126d-bf68-4847-ac78-614b0a752a0a.png" alt="194450194_9554126d-bf68-4847-ac78-614b0a752a0a"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;前端设置为强类型，初始化ko并从Model中获取数据并加载到ko对象中，同时完成了页面渲染。(注意暂时未使用Mapping插件)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194451799_147a064d-ade2-4f4a-97da-55ee4c99347c.png" alt="194451799_147a064d-ade2-4f4a-97da-55ee4c99347c"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;效果实现图&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194453018_eba2c313-638c-4466-a3d2-bcda1e0b7a8b.png" alt="194453018_eba2c313-638c-4466-a3d2-bcda1e0b7a8b"&gt;&lt;/p&gt;
&lt;h3 id="客户端通过get请求服务端数据"&gt;&lt;strong&gt;客户端通过Get请求服务端数据&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;通过前端请求，完成将服务端数据加载到当前页面中，使用$.get方法或是$.getjson方法或是$.ajax方法都行，本次直接使用$.get方法，当获取服务端数据成功后，清除界面上的已有数据，并将新数据循环绑定到viewModel中&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194454395_ed7b64cf-55f7-4477-92cc-b01d4676deab.png" alt="194454395_ed7b64cf-55f7-4477-92cc-b01d4676deab"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;服务端编写简单的方法，在原来基础数据的基础上增加一条新数据，并将数据打包成Json格式，注意将获取成功与否的操作结果返回&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194456060_cda31eaf-8abb-432c-b42b-0a8d4744ca71.png" alt="194456060_cda31eaf-8abb-432c-b42b-0a8d4744ca71"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;效果实现图&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194457679_a748d173-a01b-4bd0-934f-399298c6c7b1.png" alt="194457679_a748d173-a01b-4bd0-934f-399298c6c7b1"&gt;&lt;/p&gt;
&lt;h3 id="客户端post提交数据到服务端手动绑定"&gt;&lt;strong&gt;客户端POST提交数据到服务端(手动绑定)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;前端数据提交到后端的方式可以有简单的get提交，提交几个参数跟在url后面，用于查询相关信息，也有使用post提交表单数据或是json或xml格式的数据，本次只使用一下post提交json数据&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;post提交单条数据，将单条数据转换为json格式，使用ko提供的直接将viewModel中的集合转换成json格式的数据，该过程也可以使用其它方式完成，并非完全依赖ko&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194458878_f65a3eb3-b870-4b22-87b9-ac10b1cc0091.png" alt="194458878_f65a3eb3-b870-4b22-87b9-ac10b1cc0091"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;服务端指定接收参数，完成相应操作返回结果，注意使用了FromBody特性，只允许最多一个参数，如果有多余参数要使用，则将参数打包成一个新的类，大多使用dto&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194500133_80be1769-bc3b-46d9-9b78-e7ed8967a9ac.png" alt="194500133_80be1769-bc3b-46d9-9b78-e7ed8967a9ac"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;前端提交整个集合到后台，先将集合打包成Json格式，然后使用ajax完成提交，操作过程中，由于集合是存在于observableArray的监控数组中，不能直接序列化，因此需要使用其它方式进行转换&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194501482_91180e00-57a5-4feb-a776-cb505f565a5a.png" alt="194501482_91180e00-57a5-4feb-a776-cb505f565a5a"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在服务端使用List&amp;lt;&amp;gt;来接收，通过asp.net core提供的模型绑定，将前端请求中的json数据完成绑定到List集合中&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194503525_941cc4f2-6471-4aba-b25f-b36bc640b2d9.png" alt="194503525_941cc4f2-6471-4aba-b25f-b36bc640b2d9"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;效果实现图&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194505165_81e04349-5fe3-4bce-8e3a-6c584f723ec6.png" alt="194505165_81e04349-5fe3-4bce-8e3a-6c584f723ec6"&gt;&lt;/p&gt;
&lt;h3 id="客户端post提交数据到服务端自动绑定"&gt;&lt;strong&gt;客户端POST提交数据到服务端(自动绑定)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;对于之前的一些操作，有些操作可以使用ko提供的插件直接帮助我们完成绑定工作，而无需进行大量属性的绑定工作和处理复杂的嵌套结构，如同在后端的AutoMapper工具，在ko中有一个ko.mapping插件，可以很大程度上提高我们的开发效率。&lt;/p&gt;
&lt;p&gt;mapping插件使得JavaScript简单对象（或JSON结构）转换成Observable的view model，这样一来，无需在前端声明一些view Model类了。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从后台返回的List集合通过mapping插件绑定到前端viewModel实例属性中&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194507171_0133a812-ade1-47ff-ad96-54d15a15d8b9.png" alt="194507171_0133a812-ade1-47ff-ad96-54d15a15d8b9"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;获取服务端的List集合绑定到前端页面中，使用ko插件直接绑定&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194508430_98d4089a-f94a-49de-9b30-fc68f7f10639.png" alt="194508430_98d4089a-f94a-49de-9b30-fc68f7f10639"&gt;
**注意：**在使用asp.net core时，服务端返回的json格式为驼峰式，当在上一步操作中，初始化时为首字母大写使用的是帕斯卡命名方式，并且在页面元素中也是使用首字母大写的形式，因此需要改变服务端默认的驼峰式规则。&lt;/p&gt;
&lt;p&gt;在StartUp中，修改mvc的默认规则，使用Newtonsoft提供的转换方式，这样一来，前后端交互，前端绑定，传递到后端都是使用帕斯卡命名规则，对于前端来讲最好是使用驼峰式，同样也可以做相应的改变，比如对服务端视图模型属性中使用JsonProperty特性。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194510005_826750e6-80ad-479b-8e45-fb20c1fed68c.png" alt="194510005_826750e6-80ad-479b-8e45-fb20c1fed68c"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;POST提交前端数组并序列化后到服务端中，后端控制器中并无修改，只在前端相应变化即可&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20190102/194511396_df1c723f-5e49-490c-a4bf-0b391dbf2872.png" alt="194511396_df1c723f-5e49-490c-a4bf-0b391dbf2872"&gt;&lt;/p&gt;
&lt;p&gt;至此，已完成了knockoutJS前后端交互的方式，对于其它表单提交等等，都比较简单，最后，祝大家新年快乐！。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2019-01-02，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>KnockoutJS知识规整目录</title><link>/blog/2018-12-25-knockoutjs-catalog/</link><pubDate>Tue, 25 Dec 2018 10:00:00 +0800</pubDate><guid>/blog/2018-12-25-knockoutjs-catalog/</guid><description>&lt;p&gt;对于Web开发来讲，前端接触是避免不了的，特别是对于中小公司，没有严格的职位区分，前后端人员互相身兼是常有的事情，使用一些好的框架，能够帮助我们快速开发并完成需要的功能，对于前端的JS框架来讲MVVM框架一直处于火热状态，现有名的三大巨头，Angular、Vue和React受众多人喜爱并且也用于公司项目或个人项目中，对于这三个框架，我接触的不多，只片面了解，从最早接触了WPF中的MVVM到转向Web时接触了一个由微软支持的MVVM框架：&lt;a href="https://knockoutjs.com/"&gt;KnockoutJS&lt;/a&gt;，直到现在任然还是用这个顺手，借此还是巩固所学所用：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181225/200124269_c104ffa2-be5b-46c8-bb06-32f25605e583.png" alt="200124269_c104ffa2-be5b-46c8-bb06-32f25605e583"&gt;
 &lt;/p&gt;
&lt;h3 id="knockoutjs知识自我总结"&gt;&lt;strong&gt;KnockoutJS知识自我总结：&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9296333.html"&gt;KnockoutJS-快速入门&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9296339.html"&gt;KnockoutJS-绑定元素&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9296345.html"&gt;KnockoutJS-模板绑定&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9296350.html"&gt;KnockoutJS-与服务端交互&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/10889927.html"&gt;KnockoutJS-自定义属性绑定&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;KnockoutJS-自定义函数&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;KnockoutJS-实现模块化&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;KnockoutJS-购物车实例&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9218904.html"&gt;利用Boostrap Paginator插件和KnockoutJS完成分页功能&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-12-25，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>服务部署到Swarm Cluster中</title><link>/blog/2018-12-23-serviceinstance-deploy-to-swarmcluster/</link><pubDate>Sun, 23 Dec 2018 10:00:00 +0800</pubDate><guid>/blog/2018-12-23-serviceinstance-deploy-to-swarmcluster/</guid><description>&lt;p&gt;对于已存在的镜像，将其部署到服务器中并开始对外服务，便是它的职责，而我们要做的便是帮助它完成职责，前两个应用环节都已产生了相应的镜像，在这一环节，将完成服务部署到容器集群的工作，对于这一过程，实际执行中有很多种方式，毕竟一山比一山高，整个过程比较简单，镜像以任务形式下发到各容器主机即可，本次部署过程较为简单，只作为示例来讲，无需太多复杂过程，更强调过程实现。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="选用部署方式"&gt;&lt;strong&gt;选用部署方式&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在之前的一篇文章：约定Service构建方式(&lt;a href="https://www.cnblogs.com/CKExp/p/9940469.html"&gt;https://www.cnblogs.com/CKExp/p/9940469.html)&lt;/a&gt;)中提到了几种部署方式，对于我来讲，比较喜欢&amp;quot;借助工具手动交付&amp;quot;这种形式，一来，无需要面对命令行，借助工具去调用Docker Api，这样对于不太熟练使用的人也可以快速完成服务部署。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181223/195301012_5f749516-933e-40ca-b41e-64e18f7fcd6e.png" alt="195301012_5f749516-933e-40ca-b41e-64e18f7fcd6e"&gt;
这里涉及到Portainer工具的安装，以及Portainer集群管理的安装：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;单节点安装：Manager节点上安装Portainer，Swarm Cluster中没有Worker节点时直接使用这个即可，访问9000端口，配置账号密码即可管理。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker volume create portainer_data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker run -d -p 9000:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;集群模式下安装，采用docker stack形式，完成所有节点的安装，这样一来可以在Manager节点中统一管理整个集群**(推荐)**&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -L https://downloads.portainer.io/portainer-agent-stack.yml -o portainer-agent-stack.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker stack deploy --compose-file=portainer-agent-stack.yml portainer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Portainer文档：&lt;a href="https://portainer.readthedocs.io/en/stable/index.html"&gt;https://portainer.readthedocs.io/en/stable/index.html&lt;/a&gt;
配置镜像仓库地址：选中左侧菜单:&lt;strong&gt;Registries-&amp;gt;Add registry选择Custom registry&lt;/strong&gt;，添加一个镜像仓库地址，本次我使用的是腾讯云镜像仓库，因此仓库地址填写腾讯云仓库地址，同时使用身份认证，打开Authentication选项，输入腾讯云提供的账号密码：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181223/195302904_a2b8fce6-bccc-4294-b011-b8f14bf89c3f.png" alt="195302904_a2b8fce6-bccc-4294-b011-b8f14bf89c3f"&gt;
腾讯云镜像仓库教程：&lt;a href="https://cloud.tencent.com/document/product/457/9117"&gt;https://cloud.tencent.com/document/product/457/9117&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="完成部署工作"&gt;完成部署工作&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;创建Service&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;左侧菜单选择**Service，**点击增加一个Service，来开始服务部署工作。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181223/195304138_e666a6c8-b4b5-4dc1-acf9-93e4045fdca6.png" alt="195304138_e666a6c8-b4b5-4dc1-acf9-93e4045fdca6"&gt;
然后填写一系列信息，如服务名称、镜像名称及版本号、选择镜像仓库地址，在Scheduling mode处如果选择Relicated则填写实例数量，可用于动态控制，如果选择Global则一台容器主机生成一个容器，填写对外端口(推荐填写)，不填则会被默认分配端口，开启钩子，当镜像版本进行更新后，通过生成的Url地址可以控制自动更新服务**(重点)**，镜像仓库大多提供触发器功能，通过镜像仓库中版本的更新，触发服务对新版本的获取从而达到持续部署功能，在腾讯云镜像仓库中提供了这一功能，通过webhook,可以帮我们实现持续部署，因此，当开启了钩子功能后，从代码的更改到镜像的更新再到服务的更新这一整个环节便能很流畅的进行了,当然这个前提是，需要容器集群在腾讯云中部署，如果是混合云等或是没有在腾讯云中建立容器集群，则这一功能是用不了的，只能另想其它办法触发服务更新。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181223/195305570_b326d927-ab65-4724-b805-d1bdc34ff09f.png" alt="195305570_b326d927-ab65-4724-b805-d1bdc34ff09f"&gt;
创建完毕，服务部署便完成了，点击左侧Swarm菜单可以查看集群及服务运行情况。通过其它操作，如动态扩容、版本更新、版本回滚等操作，完成对服务的控制。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181223/195306878_fc6d5dc5-2a29-460d-8448-323c8f951e50.png" alt="195306878_fc6d5dc5-2a29-460d-8448-323c8f951e50"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用WebHook完成镜像更新则立马更新服务&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本次我使用腾讯提供的TencentHub中的WebHook功能，腾讯云镜像仓库中的WebHook仅限把服务部署到腾讯云容器服务中，有点局限性，因此将镜像仓库转移到TencentHub中，在Service创建完毕后，得到提供的WebHook回调地址&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181223/195308180_fc4e59fe-23b6-4d09-9794-d69dacf52cab.png" alt="195308180_fc4e59fe-23b6-4d09-9794-d69dacf52cab"&gt;
然后在TencentHub中新增加一个**WebHook通知,**填写相应的信息和回调地址，选择镜像更新时则触发回调，便可以达到镜像更新则服务更新功能了&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181223/195309609_37167096-e414-4ce0-994d-2341412a9f69.png" alt="195309609_37167096-e414-4ce0-994d-2341412a9f69"&gt;
 &lt;/p&gt;
&lt;p&gt;至此，服务部署到容器集群中算是完成了，操作过程较为简单，或许对于现有出色的生成流水线，如Azure DevOps等来讲，遥不可及，算是完成了一定的功能，旨在掌握整个生成流水线的过程，重在学习！&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-12-23，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>新建项目加入到生成流水线中</title><link>/blog/2018-12-20-add-newproject-to-ci-cd/</link><pubDate>Thu, 20 Dec 2018 10:00:00 +0800</pubDate><guid>/blog/2018-12-20-add-newproject-to-ci-cd/</guid><description>&lt;p&gt;整个生成流水线已经搭建完毕了，这一次来从零开始融入到生成流水线中，从仓库建立到镜像生成整个过程，对于之前几篇文章的一次应用过程，注意，本次建立过程是保证在前期基础搭建完毕后的应用阶段，本次搭建过程较为简单，只作为参考示例，并无技术含量。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="码云中新建项目"&gt;&lt;strong&gt;码云中新建项目&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在码云中新建一个项目，取名为Standard，作为一个示例项目名，仓库地址：&lt;a href="https://gitee.com/530521314/Standard.git"&gt;https://gitee.com/530521314/Standard.git&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195402109_9d1b6cc2-a85c-477d-9eda-5926b5622b02.png" alt="195402109_9d1b6cc2-a85c-477d-9eda-5926b5622b02"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将仓库拉取到本地，开始在本地构建项目，我直接使用VS的Git工具&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195403757_ca9562a6-f01d-4bc4-a4d5-fcb21ea0eaf8.png" alt="195403757_ca9562a6-f01d-4bc4-a4d5-fcb21ea0eaf8"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="新建解决方案项目"&gt;&lt;strong&gt;新建解决方案+项目&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;新建空白解决方案，指定方案名和路径地址&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195405131_2919d071-7cce-49de-b7d4-d91736fedace.png" alt="195405131_2919d071-7cce-49de-b7d4-d91736fedace"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;添加src文件夹到解决方案下(用于项目结构上区分)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195406628_6ab94d0d-4b57-42f4-9639-cee9dd838114.png" alt="195406628_6ab94d0d-4b57-42f4-9639-cee9dd838114"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;添加应用程序，选择Asp.Net Core Web应用程序&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195407956_f71c92ea-2840-4852-9c6c-8143cb45351e.png" alt="195407956_f71c92ea-2840-4852-9c6c-8143cb45351e"&gt;
**注意：**此处直接新建项目的位置为src文件夹下，主要是为了标准化搭建，前期文章也提及了。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="配置需要的脚本文件"&gt;&lt;strong&gt;配置需要的脚本文件&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;增加docker compose，选中项目右键添加-&amp;gt;容器业务流程协助程序支持，弹框内选择目标平台，此处我选择为Linux，将dockerfile和docker-compose文件都一并加入到项目中了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195409500_a87a36e6-f0d1-4889-8a01-4ac15ab5d4bd.png" alt="195409500_a87a36e6-f0d1-4889-8a01-4ac15ab5d4bd"&gt;
加入进来后的项目结构&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195411207_acf26711-a508-4c25-9e3f-76e59f70cc78.png" alt="195411207_acf26711-a508-4c25-9e3f-76e59f70cc78"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对于docker compose脚本，没有必要在项目结构中展现，选中后右键将其卸载并移除。同时处理一下文件夹中的docker-compose，修改一下项目结构，增加docker文件夹，并将obj文件夹、docker相关的4个文件移入到docker文件夹中&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195412497_45f980d1-95de-4871-85a2-ad6dcbf1675b.png" alt="195412497_45f980d1-95de-4871-85a2-ad6dcbf1675b"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最终项目结构：gitIgnore文件(手动配置或使用模板)，docker文件夹主要负责docker-compose脚本，src主要为应用程序及其相关，如有更多其它项目，如单元测试、集成测试，则还可以加入test文件夹，如还有文档资料，则还可以加入Document文件夹&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195413818_2545d46c-5ca9-408d-b4ef-3cf25d647047.png" alt="195413818_2545d46c-5ca9-408d-b4ef-3cf25d647047"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;修改docker-compose.yml中dockerfile位置(主要修改content)，修改image地址为镜像仓库地址，镜像版本前期使用latest，后期稳定部署时改为数字值&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195414987_b928dd9d-de79-4170-bb3a-bf901d52b8dd.png" alt="195414987_b928dd9d-de79-4170-bb3a-bf901d52b8dd"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;全部整理完毕后，执行代码提交并推送到仓库中&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195416118_f1a17295-5f10-44ef-af2c-f71cd9c80615.png" alt="195416118_f1a17295-5f10-44ef-af2c-f71cd9c80615"&gt;
 &lt;/p&gt;
&lt;h3 id="jenkins中新建项目"&gt;&lt;strong&gt;Jenkins中新建项目&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;具体的在Jenkins中新建项目可以参见该文章：&lt;a href="https://www.cnblogs.com/CKExp/p/9940479.html"&gt;https://www.cnblogs.com/CKExp/p/9940479.html&lt;/a&gt;，在此只处理执行脚本：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;镜像生产&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;#!/&lt;/span&gt;bin&lt;span style="color:#f92672"&gt;/&lt;/span&gt;bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;#&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;获取短版本号&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GITHASH&lt;span style="color:#f92672"&gt;=`&lt;/span&gt;git rev&lt;span style="color:#f92672"&gt;-&lt;/span&gt;parse &lt;span style="color:#75715e"&gt;--short HEAD`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose &lt;span style="color:#f92672"&gt;-&lt;/span&gt;f .&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose.yml &lt;span style="color:#f92672"&gt;-&lt;/span&gt;f .&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose.override.yml &lt;span style="color:#f92672"&gt;-&lt;/span&gt;p standard down &lt;span style="color:#75715e"&gt;--rmi local --remove-orphans
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;#&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;生成容器及镜像等并对外提供服务&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;#&lt;/span&gt; docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose &lt;span style="color:#f92672"&gt;-&lt;/span&gt;f .&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose.yml &lt;span style="color:#f92672"&gt;-&lt;/span&gt;f .&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose.override.yml &lt;span style="color:#f92672"&gt;-&lt;/span&gt;p standard up &lt;span style="color:#f92672"&gt;-&lt;/span&gt;d &lt;span style="color:#75715e"&gt;--build
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;#&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;只生产镜像&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose &lt;span style="color:#f92672"&gt;-&lt;/span&gt;f .&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose.yml &lt;span style="color:#f92672"&gt;-&lt;/span&gt;f .&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;/&lt;/span&gt;docker&lt;span style="color:#f92672"&gt;-&lt;/span&gt;compose.override.yml &lt;span style="color:#f92672"&gt;-&lt;/span&gt;p standard build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;镜像推送到镜像仓库中&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker login -u=xxx用户名xxx -p=xxx密码xxx ccr.ccs.tencentyun.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;保存并构建，查看控制台输出，构建成功&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181220/195417327_04a236ca-32bc-439e-a744-487fc67dd5d2.png" alt="195417327_04a236ca-32bc-439e-a744-487fc67dd5d2"&gt;
 &lt;/p&gt;</description></item><item><title>约定Service构建方式</title><link>/blog/2018-12-10-how-to-build-a-service/</link><pubDate>Mon, 10 Dec 2018 10:00:00 +0800</pubDate><guid>/blog/2018-12-10-how-to-build-a-service/</guid><description>&lt;p&gt;对于DevOps中，将开发好的软件交付给运维人员去部署与维护，过程中参杂着诸多不可控制的变量，如环境问题、版本问题等等，而Docker容器极大程度上解决了这些问题，同时对于服务的持续交付，也变得方便和简洁，本次讲讲我的整个生成流水线中服务部署方面的一些想法和执行方式，或许不是很中意的想法，并且还可能被认为存在漏洞和错误，但是，至少是这个环节是通了的。最完美的前提至少是运行完成，在设计过程中，考虑到了几种情况，主要是针对服务器中镜像生产者和服务承载者之间的关系，也有不同的实现方式。 &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="无需交付"&gt;&lt;strong&gt;无需交付&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;镜像生产和服务部署共用服务器集群，服务器之间通过Docker Swarm完成集群，同时Docker Swarm中的Manager与镜像生产者在同一台服务器上，管理整个服务集群。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181210/195455508_055f74c6-5cb0-4c33-9394-850b7758e705.png" alt="195455508_055f74c6-5cb0-4c33-9394-850b7758e705"&gt;&lt;/p&gt;
&lt;p&gt;**注意：**仍然需要将生产完毕的镜像推送到镜像仓库中，因为在同一个集群中的其他Worker节点需要指定镜像下载，镜像生产结束后，推送到仓库，此时发起一个创建服务或是更新服务的命令，指定本服务器上的集群完成相应工作。在交付服务时，可以有两种方式进行交付，采用单个服务的创建脚本docker service create或是采用多个服务的批量创建脚本docker stack deploy指定.yaml文件，将.yaml文件中的服务进行批量创建或更新。&lt;/p&gt;
&lt;p&gt;对于多个服务的创建脚本，我没有去尝试，有兴趣的可以查看&lt;a href="https://docs.docker.com/"&gt;Docker官网&lt;/a&gt;相关资料，对于单个服务的创建或更新脚本可以采用命令行方式或是使用UI工具去完成创建和更新，如使用Portainer工具，在Portainer中操作是很方便的。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="手动交付"&gt;&lt;strong&gt;手动交付&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;当镜像生产者和服务部署分离时，通常也是这种情形，镜像生产者只作为镜像的生产，职责便是生产镜像，而作为部署服务器，职责便是承载服务，对外提供服务。这个过程中就存在着，服务由谁创建，什么时候更新，由谁更新的一些问题，对于这些问题，也在一步一步设计中解答，本次先使用手动交付的形式，使用命令行来完成服务创建和更新，当镜像生产完毕后，便是交付环节，手动交付是最为基本的交付方式了。&lt;/p&gt;
&lt;p&gt;通过&lt;strong&gt;命令行方式&lt;/strong&gt;完成手动交付：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;在Jenkins中使用命令行完成交付，当镜像生产完毕，执行创建服务或是更新服务，这有点随着镜像的变更而变更，无需人为操作，但是也是有问题的，就单个来讲，镜像的变更往往是由开发人员将代码合并到主干中，才触动镜像更新，这也在一定程度上受限于合并到主干的时机，如果合并太过频繁，则镜像生产者需要连续生产，并且中间的镜像生产过程变得毫无意义了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在Swarm Cluster内使用命令行创建服务完成交付，在Swarm集群中的Manager节点上单个操作，是可行的，如果集群数量少，且没有安装图形管理工具之类的，可以使用这种方式，只是如果Swarm Cluster数量过多，需要一个一个切换\登录，也是比较繁琐的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在其他主机上操控Swarm Cluster使用命令行完成交付，这个过程同直接操作Swarm Cluster也是差不多的，只是可以使用额外的管理主机管理多个Swarm Cluster的Manager节点，这样一来，也较为方便，直接在一台非Swarm Cluster内的主机上即可完成所有Swarm Cluster的创建和更新过程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181210/195457259_dfe621df-22ef-4e36-84aa-5938f5a2084d.png" alt="195457259_dfe621df-22ef-4e36-84aa-5938f5a2084d"&gt;&lt;/p&gt;
&lt;p&gt;图例：直接在Jenkins所在主机上操控Swarm Cluster完成交付&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="借助工具手动交付"&gt;&lt;strong&gt;借助工具手动交付&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;对于命令行来讲，多条复杂命令总是难记，有可以直接操作的工具往往是更受欢迎的，而对于Docker来讲，Portainer工具是极受欢迎的，快速安装，简单的操作界面，丰富的功能等，同样还有其他不错的图形化容器管理工具，如Seagull、Shipyard等。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用Portainer工具完成手动交付，在Portainer界面中，配置好仓库地址和用户名密码，便于私有镜像的拉取，至于仓库，可以是自己搭建的镜像仓库，也可以使用第三方提供的镜像仓库，如阿里云、腾讯云镜像仓库。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181210/195459090_65f8fc40-b7c7-4909-a54f-8bea0912e723.png" alt="195459090_65f8fc40-b7c7-4909-a54f-8bea0912e723"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用其他Docker及Docker Swarm集群管理工具完成手动交付，至于使用其他的工具，我也没有使用过，但是工具只不过是将命令行进行了分装，因此，其他图形化管理工具也是应该存在这些功能。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181210/195500626_f5518247-3898-499d-9e65-c751806d470b.png" alt="195500626_f5518247-3898-499d-9e65-c751806d470b"&gt;
图例：利用Portainer工具操控Swarm Cluster完成交付&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="借助工具自动交付"&gt;&lt;strong&gt;借助工具自动交付&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;对于自动交付，这个功能，只是见到过其他人这么玩过，猜想了一下工作方式，至于实际尝试，并没有去做，因为思考了一些操作过程，感觉我对这个自动交付的环节并不太感冒，因为按照生产来讲，追求稳定才是重要的，如果存在测试人员，测试相应服务完毕后，推送测试好的镜像，这是运维人员将镜像交付到生产环境中，能够保证不出差错，虽然失去了效率，但是也在是心安理得。至此，我只能简单描述下借助工具完成自动交付过程，在Docker Swarm集群中的Manager节点或单独弄一台服务器作为镜像更新后的交付服务器，在服务器中加入Jenkins工具，指定镜像版本更新，则拉取最新镜像完成服务更新，镜像首次推送，则完成服务创建，对于使用批量创建/更新服务的脚本文件，没有使用的太深，但是那个是非常有价值的。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181210/195501803_d16cc00e-bca3-4814-b5d5-cc7ae2eaf866.png" alt="195501803_d16cc00e-bca3-4814-b5d5-cc7ae2eaf866"&gt;
 &lt;/p&gt;
&lt;p&gt;至此，几种我用过的方式也讲完了，在其中对于docker stack deploy使用的较少，因为docker stack deploy使用场景是为了批量服务的创建和更新而存在的，如果对于单个服务我都使用这个命令，有点杀鸡用牛刀的感觉，而对于以后的k8s学习，使用批量服务创建更新脚本文件，将会是常态。目前我现在采用的是“借助工具手动交付“这种方式，原因有几点，主要是，思考了下，服务交付的意义，主要是为了稳定，少出问题，必须在确保稳定后才能交付部署，经测试人员测试完毕后完成交付到生产环境，这应该是我们所希望能够见到的，无论开发人员每天或每周有多少个版本更新，经测试人员测试后的稳定版本，才能交付到生产环境中，而不是说开发人员一将分支代码合并到主干中，有新的镜像生成了，就直接将新的镜像推送到生产环境中，而做到所谓的持续交付的目的，实际的持续交付应该是保证测试完毕到生产部署这个环节具有连续性，稳定性，每一次交付都经得起推敲，具体其中发生了什么，稳定性如何等，虽然这种方式效率较低，但对于持续交付来讲，这个效率也算是可以接受的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-12-10，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>约定Jenkins构建脚本</title><link>/blog/2018-12-01-jenkins-build-scripts/</link><pubDate>Sat, 01 Dec 2018 10:00:00 +0800</pubDate><guid>/blog/2018-12-01-jenkins-build-scripts/</guid><description>&lt;p&gt;对于Jenkins的使用，我感觉只用到其中一小部分功能，但也就是这一小部分功能，也推动了整个CI/CD的过程，Jenkins的使用方式有很多中，可能我用到的只是其中一种，但是已经满足我的需求，便不再贪婪，本次要约定好Jenkins中的脚本规则，对于我的整个生成流水线来讲，约定至高无上，遵从约定，或许会出现错误，但出现的几率肯定低于不遵从约定，随意设计好的多。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181201/195539980_b066c5e0-090b-499e-9698-17f135a5090e.png" alt="195539980_b066c5e0-090b-499e-9698-17f135a5090e"&gt;
 &lt;/p&gt;
&lt;h3 id="jenkins安装及项目配置"&gt;&lt;strong&gt;Jenkins安装及项目配置&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="jenkins安装"&gt;Jenkins安装&lt;/h4&gt;
&lt;p&gt;在以前的博客中已经介绍过Jenkins的安装，我使用的是在Docker中安装Jenkins，利弊明显，在容器中安装方便，但是也带来一些问题，如不能很好的和Docker Machie结合使用，如果想在Jenkins脚本中分发服务到其他主机上，那必须要在Jenkins中额外安装SSH插件。而在主机上直接安装Jenkins，则步骤要多一点，需要安装Java环境等操作，但是可以在Jenkins中直接使用Docker Machine的功能，直接将服务分发到Swarm服务器集群中，这便带来了优势。&lt;/p&gt;
&lt;p&gt;Docker中完成Jenkins安装：&lt;a href="https://www.cnblogs.com/CKExp/p/9536864.html"&gt;https://www.cnblogs.com/CKExp/p/9536864&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="jenkins集群搭建"&gt;Jenkins集群搭建&lt;/h4&gt;
&lt;p&gt;对于Jenkins的作用需要重申重要一点就是，在我的整个生成流水线中Jenkins的最大目的是担任奶妈角色，也就是镜像的创建，甚至整个Jenkins集群的目的都是镜像的创建，在聊天时发现有人在自己的每一台机器上安装Jenkins,然后搭建Jenkins集群，然后将构建任务下发至子节点中并构建完毕后生成服务，我的思路在刚开始使用Jenkins时甚至也是这样，但是慢慢的我发现不对，这整个思路就是荒谬，难不成我的每一台机器上都得安装Jenkins，逐步探索，总结出来就是，Jenkins只是奶妈，最终目的只是镜像生产者。&lt;/p&gt;
&lt;p&gt;Jenkins集群搭建过程：&lt;a href="https://www.cnblogs.com/CKExp/p/9541137.html"&gt;https://www.cnblogs.com/CKExp/p/9541137.html&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="jenkins中新建项目"&gt;Jenkins中新建项目&lt;/h4&gt;
&lt;p&gt;在我的生成流水线中，Jenkins的主要职责便是将代码仓库中的代码拉取过来，完成镜像的构建，而对于Jenkins中，构建项目，有一些需要设置的地方，如Git地址，凭证，构建时间，构建脚本等。&lt;/p&gt;
&lt;p&gt;Jenkins中新建项目：&lt;a href="https://www.cnblogs.com/CKExp/p/9940479.html"&gt;https://www.cnblogs.com/CKExp/p/9940479.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="约定jenkins的构建脚本"&gt;&lt;strong&gt;约定Jenkins的构建脚本&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;利用在构建Jenkins脚本时加入的Docker Compose工具，可以在脚本构建时生成多个镜像，且可以有依赖关系，方便相互关联的服务部署。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用Docker Compose中的参数 -f 指定项目中的compose.yaml文件，这个文件的位置在之前约定项目结构中设置好了，在docker文件夹下，对于docker compose，会将这两个yaml文件合并为一个，因此，可以设置多个yaml文件，用于不同的作用。通过制定-p 参数来设置项目名称，down 参数将删除指定的镜像、服务、容器、网关等，对于构建镜像的命令，可以使用up命令或是build命令，两者区别是，使用up命令时会在本机上生成指定镜像的容器，而build命令则只负责构建镜像。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;约定项目文件结构：&lt;a href="https://www.cnblogs.com/CKExp/p/9940457.html"&gt;https://www.cnblogs.com/CKExp/p/9940457.html&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# 获取短版本号
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GITHASH=`git rev-parse --short HEAD`
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml -p multimap down --rmi local --remove-orphans
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml -p multimap up -d --build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml -p multimap build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意：如果不指定-p 参数，则会使用默认文件夹名，在此处为docker文件夹名，则会带来一系列问题，如对于默认网关名称，镜像名称等，都是使用docker开头，这就导致多个项目会发生冲突，都是使用docker开头的网关，多个项目只能一个有效，因此约定每个项目在指定-p参数时以项目名称为主。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;镜像构建完毕，将镜像推送到镜像仓库中，指定仓库地址，用户名和密码(我这里使用的是腾讯云的镜像仓库，如果使用的私有仓库或其他云的，则需要替换)，可以利用docker-compose提供的push命令，将docker compose中生成的镜像都推送到镜像仓库中。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo ---------------Push-Images...------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker login -u=xxx用户名xxx -p=xxx密码xxx ccr.ccs.tencentyun.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;(可选)如果需要将创建服务到Swarm集群(或是k8s中)，也可以利用Jenkins完成，如果是将Jenkins安装在宿主机上，则可以使用如下命令创建服务：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;首先执行登录仓库功能：&lt;/p&gt;</description></item><item><title>约定新项目的搭建流程</title><link>/blog/2018-11-27-how-to-build-a-new-project/</link><pubDate>Tue, 27 Nov 2018 10:00:00 +0800</pubDate><guid>/blog/2018-11-27-how-to-build-a-new-project/</guid><description>&lt;p&gt;对于个人而言，项目文件结构我比较关心，一方面我能熟练操作各块文件夹，当需要改动时，能够立马进入到相应文件夹进行修改，另一方面，有新的成员后，能够看到项目文件结构，可以快速适应到环境中来，参照了一些名气很高的框架如Abp，EshopOnContainers等，也从中提炼一些项目文件结构，来约定好自己的搭建习惯，或许不是每个人的预期想法，也或许感觉我的项目文件结构很怪，不要感到奇怪，毕竟没有一劳永逸的事情，每件事情都在动态发展中(强力甩锅)。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="约定项目文件结构"&gt;&lt;strong&gt;约定项目文件结构&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;预期项目文件结构如下，约定之后，或许我的每一个demo都会遵从如下的结构来设计，如果说开发过程中发现有些问题，也将慢慢调整，参照了Abp来设计，但是只是用到里面最深的那一级，对于大小项目而言，能够先在标准化的道路上走，才有可能实现超车或起飞，因此先跑起来。&lt;/p&gt;
&lt;p&gt;结构说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对于docker文件夹，目标是使用到其中的docker compose相关的一些文件，如使用docker-compose.yaml再加一个.yaml文件组合使用用来构建多个服务所需要的镜像，同时使用docker-compose.yaml再加一个service.yaml来构建服务，在docker中可以通过docker stack deploy来启动多个服务，同样在k8s中也是使用的相应的.yaml文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于src文件夹，主要是用来放项目相关的，如接口或是mvc之类的应用程序及其相关的类库等，对于对外服务的应用程序，每一个中都指定了Dockerfile,这个Dockerfile文件无需手动配置，虽然也可以锦上添花，但我觉得用VS生成的已满足我的需求了，然后约定expose的端口为80，虽然有了compose文件后，这个端口指定与不指定已经意义不大了，毕竟优先级有限。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于test文件夹，针对测试人员或是开发人员好单独管理单元测试或集成测试而单独分割出来的一部分，方便管理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于tools文件夹，针对运维人员执行数据库的迁移准备的，至于可能有更多的其他辅助类的项目加入进来也是有可能的，比如监控工具等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于其他文件，对于整个项目权重不占比太大，故不再提及。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181127/195648300_7de2ebdd-3765-4453-91a4-f5cd648c375c.png" alt="195648300_7de2ebdd-3765-4453-91a4-f5cd648c375c"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="搭建一个标准化项目"&gt;&lt;strong&gt;搭建一个标准化项目&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先重申下这句话，对于项目搭建按照标准化的流程去设计，不管项目多大，按照固定标准化去设计，规避一些错误，使得针对项目开发的速度提高。&lt;/p&gt;
&lt;p&gt;或许每个人的搭建方式都会不一样，按照我的习惯来搭建会是如下过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先在码云或是GitHub中先建好一个仓库，并克隆仓库到本地；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181127/195649908_a0d96673-c2c6-4bac-b578-b0b91a3ba133.png" alt="195649908_a0d96673-c2c6-4bac-b578-b0b91a3ba133"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;其次，新建一个空白解决方案，这是很重要的一环，不要先建设Web然后再来改解决方案名字，会出现适得其反的效果；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181127/195652161_422728dd-04e8-4170-9bd2-aac60968a5fb.png" alt="195652161_422728dd-04e8-4170-9bd2-aac60968a5fb"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在空白方案下新建解决方案文件夹src和test的文件夹，建完后直接查看文件夹内发现并没有生成文件夹;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181127/195653429_b818421d-c771-4d37-b2dc-49e140832611.png" alt="195653429_b818421d-c771-4d37-b2dc-49e140832611"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;然后在解决方案中选中src文件夹，右键新增项目，选中Asp.Net Core应用程序，取完应用程序名字，对默认路径做修改，在尾巴上加上\src，将其放置在src文件夹下，选择完毕后，应用程序加入，相应的文件夹下也出现了src文件夹，同样test文件夹也是如此;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181127/195654714_723181ef-f40c-440c-9a08-68164559ce1f.png" alt="195654714_723181ef-f40c-440c-9a08-68164559ce1f"&gt;
**注意：**虽然在解决方案下添加也可以指定文件存放路径，同样在相应的文件夹内，但是生成的应用程序是在解决方案下，需要手动拖入，为避免这一烦扰，最好使用固定流程，先建立文件夹，然后再文件夹内选择新建项目，这样一来生成的应用程序将直接是处于该文件夹下，标准化软件开发设计必备的文件夹，具体的app项目和类库分别建设到这两个文件夹下;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;其次，在src和test文件夹同级建立docker文件夹，不将其引用到解决方案中，避免在window下调试，docker文件夹中应包含整个解决方案的docker compose相关的脚本信息,如有必要可以在docker下在建立mvc文件夹，在其中放置脚本，当有多个版本出现时，可以用来区分，但是为了标准化来讲，如果这么做了，在Jenkins构建脚本时得注意文件夹路径(我不会选择分成多个文件夹)，建议是可以分成多个.yaml，取成不同名字来处理镜像和服务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;选择Web.MVC或Web.Host点击右键添加Docker支持，加入Dockerfile文件，内部原有端口改为80端口，如果有端口需求可以改为其他，默认的话就统一约定为80端口;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于docker文件加入.yaml文件，点解决方案选择添加容器业务流程协调程序支持，选择Docker Compose，加入到指定文件夹中，(本地我没有这个项，我是采用手工方式直接加入到docker文件夹中，很low)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于docker-compose.yaml中针对镜像生成的部分，按照现在的搭建流程来将，其中的build参数的相关信息得指定成如下所示，注意不要在.yaml文件中使用Tab按键：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;build:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context: ../
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dockerfile: src/Surround.Web.Host/Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;**路径说明：**context的路径指定为本yaml文件上两级，为项目文件夹，然后指定为src下具体程序然后项目内Dockerfile。&lt;/p&gt;
&lt;p&gt;最后，再次重申这句话：对于项目搭建按照标准化的流程去设计，不管项目多大，按照固定标准化去设计，规避一些错误，使得针对项目开发的速度提高。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-11-27，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>设计生成自动化流水线</title><link>/blog/2018-11-24-design-and-generate-ci-cd-pipelines/</link><pubDate>Sat, 24 Nov 2018 10:00:00 +0800</pubDate><guid>/blog/2018-11-24-design-and-generate-ci-cd-pipelines/</guid><description>&lt;p&gt;现在已经有很多成熟的生成流水线了，如Azure DevOps，直接在界面中配置好即可，相当便捷，生成流水线的工作方式、工作过程也可以简单了解，我在空闲时间搭建了一个简单生成流水线旨在巩固个人对生成流水线的理解。&lt;/p&gt;
&lt;h3 id="生成流水线设计图--195733157_"&gt;&lt;strong&gt;生成流水线设计图&lt;/strong&gt; &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181124/195733157_73e92613-f7f7-4c39-a5c8-5b0791c70f6f.png" alt="195733157_73e92613-f7f7-4c39-a5c8-5b0791c70f6f"&gt;&lt;/h3&gt;
&lt;p&gt;平时，在腾讯云、阿里云搞活动时薅了点羊毛 ，搞了几天便宜的服务器，不能让这些服务器闲置了，也借此来试试搭建一条流水线，目的是在于，一个新项目要开始弄，在我选择一个Asp.Net Core并推送到码云中，然后在Jenkins中做一些相关的配置即可，后期代码上的变更不会影响部署环境的变更。主要原因在于，看到过几次先开发后部署，然后遇到的一些部署方面的问题出现耽搁，因此，先把环境部署完毕，后期要做的只是将业务代码的不断加入进来，并且对上可以看到工作成果，如同高楼建筑，可以看到一层一层的往上叠加。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;代码方面：Asp.Net Core、Git、Gitee&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务器方面：Jenkins、Docker Machine、Docker Compose、Docker Swarm&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;镜像仓库：腾讯云镜像仓库&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="设计过程中见到的多条道路"&gt;&lt;strong&gt;设计过程中见到的多条道路&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在设计过程中发现有些地方可以有多种方式实现，谈谈我所见到的几种方式：&lt;/p&gt;
&lt;h4 id="单台服务器"&gt;单台服务器&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;代码存储在码云或是GitHub中，
可安装Jenkins可以安装到容器中或主机上，
可安装Docker Compose到容器中或主机上，更方便的启动/停止批量容器，
在Jenkins中使用脚本时：
可以选择用简单脚本启动/终止容器(docker run/stop)。
可以选择用Docker Compose启动/停止批量容器(需要代码中提供docker.compose.yaml文件)。
可以使用代码或是主机内的脚本文件启动/终止容器(使用.sh脚本文件)。
代码变更，利用jenkins定时拉取变动，然后重新部署容器，很简单和方便的实现持续交付与部署。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181124/195734786_a4f00cb2-de10-4ead-83d2-8862153bd242.png" alt="195734786_a4f00cb2-de10-4ead-83d2-8862153bd242"&gt;&lt;/p&gt;
&lt;h4 id="多台服务器"&gt;多台服务器&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;代码存储在码云或是GitHub中，
可安装Docker Machine到一台服务器上，然后快速的通过这一台服务器把其他服务器的docker环境搭建好。
可安装Docker Swarm,我暂时还只是用Docker Swarm，对于k8s没有涉及，所以暂不应用，对于Swarm的使用，我之前是有4台主机，因此在安装了Docker Machine的那一台就直接发起了一个集群，并把其他服务器也加入了进来，形成了如下局面：
![195736023_d9dfc3e2-77c0-4a4e-ba47-3be5fac20d83](https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181124/195736023_d9dfc3e2-77c0-4a4e-ba47-3be5fac20d83.png)
虽然，看起来没得什么问题，同样也可以使用，但是对于Host1来讲，负担太大，既作为奶妈(镜像生产者)又要作为奶爸(集群管理者)。因此为将压力分离一部分，也就有了下面的局面：
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181124/195737083_0250e19f-bc46-41a6-88a3-8518ad5feabf.png" alt="195737083_0250e19f-bc46-41a6-88a3-8518ad5feabf"&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;可安装Jenkins可以安装到容器中或主机上，
可安装Docker Compose到容器中或主机上，更方便的启动/停止批量容器，
在Jenkins中使用脚本用于构建镜像时：
可以选择用简单脚本删除原有镜像、构建镜像、启动、终止容器等(docker rmi/build/run/stop)。
可以选择用Docker Compose删除镜像、重新构建镜像、启动/停止批量容器等(需要代码中提供docker.compose.yaml文件，在该文件中指明build参数，Dockerfile位置)。(注意docker compose文件区别，一种是用来构建镜像的docker compose.yaml文件，这种指明了build和context，一种是镜像名称是指定了的，这种用于docker stack deploy，必须要在镜像存在的情况下才能用的。)
可以使用代码或是主机内的脚本文件删除镜像、重新构建镜像、启动/终止容器等(使用.sh脚本文件)。
我使用的是腾讯云的镜像仓库，用于存储构建后的镜像，并在swarm集群中拉取镜像部署服务。
代码变更，利用jenkins定时拉取变动，然后重新构建镜像，并将镜像推送到镜像仓库中，同时将更新swarm中的service，使用最新的镜像，滚动更新集群中的容器。在这里更新swarm中的service有多种方式，如果Jenkins是安装在主机上，那么可以直接在脚本中通过docker-machine ssh host2 相关更新命令，完成通知即可，如果Jenkins是安装在Docker中，那么Jenkins中需要安装SSH Plugin插件用来连接远程主机，然后执行更新。还有的方式就是，我不通知swarm集群，Jenkins服务器可以充当测试服务器，当测试人员测试完毕后，在swarm集群内通过命令手动更新service或是借助UI工具如portainer来手动更新service，更有甚者，在swarm manager节点上在安装一个Jenkins，用来拉取最新镜像(镜像更新触发拉取动作)，然后更新service,这种方式我暂时没有尝试过，应该也是行的通的。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h4 id="现有集群环境如下"&gt;&lt;strong&gt;现有集群环境如下&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;目前，搭建好了如下环境，对于镜像构建方如何通知swarm集群更新，还没有做到自动更新，暂时使用手动在swarm集群中更新，而对于镜像构建方的自动构建和镜像推送到镜像仓库现已完成。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181124/195738082_ef552975-45af-4bd3-a67d-6376d5a3f03b.png" alt="195738082_ef552975-45af-4bd3-a67d-6376d5a3f03b"&gt;&lt;/p&gt;
&lt;p&gt;在Swarm集群中，安装&lt;a href="https://portainer.readthedocs.io/en/stable/index.html"&gt;Portainer&lt;/a&gt;集群版本，可以监控整个集群的信息，对于手动更新来讲是很方便的，在Portainer中，在左侧有一个registry的菜单，点击进入后选择自己的仓库地址，用来配置我在腾讯云的仓库信息。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181124/195739078_86660eab-67f6-461f-b0b1-7eef6693990e.png" alt="195739078_86660eab-67f6-461f-b0b1-7eef6693990e"&gt;&lt;/p&gt;
&lt;p&gt;  在Portainer菜单Service中可以创建或更新service，获取最新的镜像完成更新操作,相比用命令行去操作轻松许多。&lt;/p&gt;
&lt;p&gt;  &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181124/195740209_5b01be92-b548-4356-85e8-1ddcee487fd6.png" alt="195740209_5b01be92-b548-4356-85e8-1ddcee487fd6"&gt;&lt;/p&gt;
&lt;p&gt;后期将会完善自动通知swarm集群更新service功能(至于要不要这个功能，需要斟酌一番)。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-11-24，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>ABP框架开发-Docker部署</title><link>/blog/2018-11-11-abp-deploy-to-docker/</link><pubDate>Sun, 11 Nov 2018 10:00:00 +0800</pubDate><guid>/blog/2018-11-11-abp-deploy-to-docker/</guid><description>&lt;p&gt;本文旨在将Abp项目部署到Docker容器中，借助Gitee存储，Jenkins持续构建，利用Docker Compose生成镜像、启动镜像，在官网给定的Abp项目中，虽然用到了Dockerfile、Docker Compose，但是没有资料指明该如何使用这两个文件，借此我通过改造这两个文件来完成Abp部署任务。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="前期准备工作"&gt;&lt;strong&gt;前期准备工作&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;把从&lt;a href="https://aspnetboilerplate.com/"&gt;Abp官网&lt;/a&gt;下载下来的项目(我这取名为Surround方便讲解)修改成以下项目路径结构：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195943614_e1ecb226-702b-47b3-8bfe-cbb9038fc967.png" alt="195943614_e1ecb226-702b-47b3-8bfe-cbb9038fc967"&gt;&lt;/p&gt;
&lt;p&gt;其中的docker文件夹中加入Docker Compose项目，点击具体项目(如ABP中的Web.Mvc)右键添加菜单中的容器业务流程协助程序支持，将出现一个docker compose项目，对于整个docker compose项目的路径按照约定设计。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195945016_d535b8d0-2f98-4d55-8109-1a659d44bba9.png" alt="195945016_d535b8d0-2f98-4d55-8109-1a659d44bba9"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;处理docker compose中的脚本配置，在docker-compose.yaml中指定两个服务相关信息，镜像名称、dockerfile的路径地址，此处在build中指定context为本目录的上级目录，按照上图中的位置就是在/xxx项目这一级，然后指定dockerfile的路径为src/Surround.Web.Host/Dockerfile指明具体的Dockerfle路径，同样对于surroundmvc同样如此处理。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version: &amp;#39;3.4&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; surroundhost:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: ${DOCKER_REGISTRY}surroundhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; build:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context: ../
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dockerfile: src/Surround.Web.Host/Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; surroundmvc:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: ${DOCKER_REGISTRY}surroundmvc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; build:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context: ../
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dockerfile: src/Surround.Web.Mvc/Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于docker-compose.override.yaml文件，这是对前一个文件的补充，docker compose会将两个文件进行合并，在此文件中指定环境变量和对外端口此处docker compose设置的端口优先级高于dockerfile设置的端口，此处设置后dockerfile的端口则失效，此处设置内部端口为80端口，外部端口随机获取一个，如果想固定端口，则可以写 - &amp;ldquo;32100:80&amp;rdquo; 那么外部端口便固定为32100。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version: &amp;#39;3.4&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; surroundhost:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ASPNETCORE_ENVIRONMENT=Development
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#34;80&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; surroundmvc:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ASPNETCORE_ENVIRONMENT=Development
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#34;80&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;选中Surround.Web.Mvc点击右键选中添加，加入新的dockerfile文件，不再使用已有的dockerfile文件，同时建议修改dockerfile中的指定端口为80端口，这样就促成了约定所有的新项目加入进来容器内部都使用80端口，如有需要另改即可，同样对Surround.Web.Host也执行这样的操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195946078_e2b54193-2ce9-4ad6-94f6-01c8270b75e0.png" alt="195946078_e2b54193-2ce9-4ad6-94f6-01c8270b75e0"&gt;&lt;/p&gt;</description></item><item><title>新建项目到Jenkins中</title><link>/blog/2018-11-11-create-a-new-project-in-jenkins/</link><pubDate>Sun, 11 Nov 2018 10:00:00 +0800</pubDate><guid>/blog/2018-11-11-create-a-new-project-in-jenkins/</guid><description>&lt;p&gt;在以Jenkins为镜像创建Docker容器时，我们在jenkins的dockerfile文件中写明了要安装Docker Compose，目的也是在Jenkins容器中借助Docker Compose来完成相关的操作。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="容器中安装docker-compose"&gt;&lt;strong&gt;容器中安装Docker Compose&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;同时安装Jenkins和Docker Compose：参照之前写过的文章安装Jenkins的同时也安装Docker Compose: &lt;a href="https://www.cnblogs.com/CKExp/p/9536864.html"&gt;https://www.cnblogs.com/CKExp/p/9536864.html&lt;/a&gt;，安装完毕，开始安装jenkins内部的插件，如有安装失败的插件，点击Retry重新安装。如果发现jenkins缺少东西，需要重装，则把/var/jenkins_home文件夹整体删除，重新安装。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只安装Jenkins，再进入容器后安装Docker Compose,进入Jenkins容器中 $ docker exec -it 容器ID /binbash 然后参照官网文档进行安装：&lt;a href="https://docs.docker.com/compose/install/#install-compose"&gt;https://docs.docker.com/compose/install/#install-compose&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="在jenkins中新建项目"&gt;&lt;strong&gt;在Jenkins中新建项目&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先把系统时间修改一下，使用中国时区，在系统管理，脚本命令行中写入如下代码设置jenkins中的系统时间同步北京时间，方便后期做定时删除无效镜像使用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;System.setProperty(&amp;#39;org.apache.commons.jelly.tags.fmt.timeZone&amp;#39;, &amp;#39;Asia/Shanghai&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;点击新建项目按钮，输入一个项目名称。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195816082_9d779a25-8374-4ed4-97d0-c1f2cda74c9d.png" alt="195816082_9d779a25-8374-4ed4-97d0-c1f2cda74c9d"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置项目的Git路径，如为私有项目，则增加凭证(Credentials)。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195817613_7feccd58-e966-4603-9d4c-35c0b5080d2a.png" alt="195817613_7feccd58-e966-4603-9d4c-35c0b5080d2a"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;构建触发器,定时两分钟检查一次查看Git中是否有代码变更，&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195818870_a9e98751-73a4-4b2b-a117-31cc553e36c9.png" alt="195818870_a9e98751-73a4-4b2b-a117-31cc553e36c9"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设置构建时间(建议将时长分配多一点，如果构建复杂应用，构建过程将会耗时很长)。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195820160_a8451b4b-7d4d-4e4a-86cf-1a46a8d7299d.png" alt="195820160_a8451b4b-7d4d-4e4a-86cf-1a46a8d7299d"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;构建过程脚本，在这里使用docker compose完成相应的功能，&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181111/195821280_5896dee3-c9e1-4890-9d4b-b3ec1a147807.png" alt="195821280_5896dee3-c9e1-4890-9d4b-b3ec1a147807"&gt;
构建脚本具体如下，在此过程中指明了两个.yaml文件的具体位置，以项目的根目录为基准进入docker文件夹找到两个.yaml文件，然后docker compose将两个文件合并，停止并删除本地已有的容器、镜像、网桥、数据卷，然后重建：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# 获取短版本号
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GITHASH=`git rev-parse --short HEAD`
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml -p surround down --rmi local --remove-orphans
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml -p surround up -d --build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或可以使用如下命令，不在本地生成容器，而只生成镜像即可&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# 获取短版本号
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GITHASH=`git rev-parse --short HEAD`
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml -p surround down --rmi local --remove-orphans
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml -p multimap build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;将镜像推送到镜像仓库中，方便后期的服务部署工作。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo ---------------Push-Images...------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker login -u=xxx用户名xxx -p=xxx密码xxx ccr.ccs.tencentyun.com
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose -f ./docker/docker-compose.yml -f ./docker/docker-compose.override.yml push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;保存，配置结束，点击开始构建。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;</description></item><item><title>生成流水线搭建过程目录</title><link>/blog/2018-11-10-generate-a-pipeline-construction-process-catalog/</link><pubDate>Sat, 10 Nov 2018 10:00:00 +0800</pubDate><guid>/blog/2018-11-10-generate-a-pipeline-construction-process-catalog/</guid><description>&lt;p&gt;设计初衷，之前有过手动维护五个项目的经历，且项目之间存在关联关系，当时是用Docker承载，也没有使用Jenkins，直接在服务器上装了Git，把代码Pull下来，然后靠手动执行编译部署，每次新代码进来，需要进入到每一个Docker容器中执行编译然后运行，哪个有改动，就进哪个容器中，在那时感觉也没什么，无非是感觉加大了点工作量，至少是还能解决问题，可是当连续多次这样操作之后，繁琐感油然而生，如果项目在多一些，整个开发工作时间怕是要折半了，得花大把时间去维护这些部署工作，当时花了点时间找博客学习，也学到了一些新东西、新工具，多次磨合、使用后，慢慢地也就开始将学到的融合在一起，构建一个简单的生成流水线，至少确保了新代码加入后，不要手动去维护了，还获得了一些额外的好处。&lt;/p&gt;
&lt;p&gt;本次基于Asp.Net Core、Jenkins、Gitee、Docker、Docker Machine、Docker Swarm、腾讯云镜像仓库和Tencent Hub搭建生成流水线，并确保新项目按照标准化流程搭建并推送到码云中，简单配置Jenkins后便能够生成且可以直接浏览。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181110/200038675_28242c08-e365-4695-ad60-d76922d18cda.png" alt="200038675_28242c08-e365-4695-ad60-d76922d18cda"&gt;
 &lt;/p&gt;
&lt;h3 id="生成流水线搭建目录如下"&gt;&lt;strong&gt;生成流水线搭建目录如下：&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9940461.html"&gt;一、设计生成自动化流水线&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9940457.html"&gt;二、约定新项目的搭建流程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9940467.html"&gt;三、约定Jenkins构建脚本&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9940469.html"&gt;四、约定Service构建方式&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9552272.html"&gt;五、配置流水线中的Docker Machine&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9651244.html"&gt;六、配置流水线中的Docker Swarm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9940479.html"&gt;七、新建项目到Jenkins中&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/9765907.html"&gt;八、实例：把ABP加入到生成流水线中&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/10152992.html"&gt;九、实例：新建项目加入到生成流水线中&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/CKExp/p/10158970.html"&gt;十、实例：服务部署到Swarm Cluster中&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-11-10，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>通过Jenkins定期清除为None的镜像</title><link>/blog/2018-11-03-clean-none-image-by-jenkins/</link><pubDate>Sat, 03 Nov 2018 10:00:00 +0800</pubDate><guid>/blog/2018-11-03-clean-none-image-by-jenkins/</guid><description>&lt;p&gt;在代码持续交付过程中，依靠Jenkins生产Docker镜像时，会生成许多的名为None的中间镜像，这些镜像在整个项目生产过程完毕后意义不大，还占着空间，需要定期清理，通过手动方式实在是繁琐，也就有了定期清理。 &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="手动方式清理"&gt;&lt;strong&gt;手动方式清理&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;比较简便的清理方式，适合有单个none镜像产生时手动执行命令清理镜像，执行如下命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker rmi $(docker images -f &amp;#34;dangling=true&amp;#34; -q)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由于我本地已经没有了none的镜像，因此执行就只能看到如下效果  
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181103/200226496_a611703e-3cd2-4a6b-a042-db35ba92ba91.png" alt="200226496_a611703e-3cd2-4a6b-a042-db35ba92ba91"&gt;
 &lt;/p&gt;
&lt;h3 id="项目生产结束后清理"&gt;&lt;strong&gt;项目生产结束后清理&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在Jenkins中建立一个满足CI的项目时，生产过程完毕结束后清理可以在构建执行脚本内设置，如：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181103/200227917_81017526-8afb-43b2-99fe-8beb280e0f15.png" alt="200227917_81017526-8afb-43b2-99fe-8beb280e0f15"&gt;
其中的脚本为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo ---------------Clear-Images...------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clearImagesList=$(docker images -f &amp;#34;dangling=true&amp;#34; -q)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;if [ ! -n &amp;#34;$clearImagesList&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &amp;#34;no images need clean up.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;else
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker rmi $(docker images -f &amp;#34;dangling=true&amp;#34; -q)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &amp;#34;clear success.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fi
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当项目构建完毕后，执行这个，将本身构建过程中生成的None镜像清楚，做到自己清除自己的中间产物。
但是有一个严重问题会产生，当两个或多个项目在同时构建时，先构建完毕后执行清除的脚本将会影响正在构建过程中的项目，删除正在构建过程中所产生的none，但是删除失败进而引发错误，导致先构建的项目构建失败，在多个项目的情况下不推荐这种方式，如果在Jenkins中只有单个任务在跑，那没得问题。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="定时任务清理"&gt;&lt;strong&gt;定时任务清理&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;比较喜欢这种方式，新建一个Jenkins定时任务，比如我设置为晚上十二点清理为none的镜像，步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Jenkins中新建一个项目，名字随意，如我的为ClearImage。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;构建触发器，选择Build periodically，设置定时时间，如我设置的是凌晨1时清理，也可设置其他时间，具体设置规则参见右边问号。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181103/200229111_3ba73c26-2794-4fc8-ac14-426e9f216aa3.png" alt="200229111_3ba73c26-2794-4fc8-ac14-426e9f216aa3"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;执行构建脚本，脚本内容在之前已经给出，保存完毕即可，&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181103/200230160_f6fd1989-e8a8-4754-ad4f-de99ee833278.png" alt="200230160_f6fd1989-e8a8-4754-ad4f-de99ee833278"&gt;
手动执行立即构建，验证是否有效：&lt;/p&gt;
&lt;p&gt;查看控制台输出： &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181103/200231275_b0fd152b-ef6a-4033-a3b8-bb44da9cbb77.png" alt="200231275_b0fd152b-ef6a-4033-a3b8-bb44da9cbb77"&gt;&lt;/p&gt;
&lt;p&gt;输出完毕：构建有效。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181103/200233228_020b452c-5991-49ee-a042-ad0be09cecb5.png" alt="200233228_020b452c-5991-49ee-a042-ad0be09cecb5"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-11-03，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Docker Swarm搭建多服务器下Docker集群</title><link>/blog/2018-10-27-set-up-a-cluster-under-multiple-servers-by-docker-swarm/</link><pubDate>Sat, 27 Oct 2018 10:00:00 +0800</pubDate><guid>/blog/2018-10-27-set-up-a-cluster-under-multiple-servers-by-docker-swarm/</guid><description>&lt;p&gt;对于有多台服务器来讲，如果每一台都去手动操控，那将会是一件非常浪费时间的事情，毕竟时间这东西，于我们而言，十分宝贵，或许在开始搭建环境的时候耗费点时间，感觉是正常的，我也如此，花费大堆时间在采坑和填坑的路上，但是当你踩完后，还通过手动管理的方式去维护，那便是折磨，因此对于多台服务器下的容器编排管理，借助工具将会节省着大把时间，现如今有名的容器编排工具，K8s、Docker Swarm，甚至功能更强大的Rancher，本次我将使用且是我唯一使用过的Docker Swarm工具搭建Docker集群并部署服务。&lt;/p&gt;
&lt;p&gt;Docker Swarm官方文档地址：&lt;a href="https://docs.docker.com/engine/swarm/"&gt;https://docs.docker.com/engine/swarm/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="搭建前期工作"&gt;&lt;strong&gt;搭建前期工作&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在之前的文章中，对于一台新的服务器加入到服务器集群中，通过Docker Machine去管理，配置好Docker Enginee环境，简单也方便，同时在配置时，将本地的私钥发送给了新的服务器，也就配置好了关联关系，如有需要可以查看该文章：&lt;a href="https://www.cnblogs.com/CKExp/p/9552272.html"&gt;https://www.cnblogs.com/CKExp/p/9552272.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181027/200335421_05a95edd-054d-4928-9fde-fab0aa967e6a.png" alt="200335421_05a95edd-054d-4928-9fde-fab0aa967e6a"&gt;
加入进来后对于整个服务器集群来讲，可预见的Swarm方式是这样的，也是最为简便的方式管理，Manager作为容器集群发起者，也是管理者，一般来讲，我会尽可能少的在上面部署服务，通过一些方式过滤掉服务在上面的部署，毕竟它的职责更多的侧重于管理整个容器集群，但是对于一些开支小的且比较稳定的服务，也是可以i直接部署在Manager节点中的，这是一种建议。&lt;/p&gt;
&lt;p&gt;这是我当前的服务器下的容器集群，存在一个Leader也就是Manager，也可以设置多个，但是现有场景下，完全满足我的需求了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181027/200336778_84e9c612-6d71-4af1-9dd3-d69425f08520.png" alt="200336778_84e9c612-6d71-4af1-9dd3-d69425f08520"&gt;
我们利用Docker Swarm中的Docker CLI去创建集群、管理集群、动态扩容服务。而对于Swarm的版本，建议是Docker版本要高于1.13，在1.13之上，将Swarm功能集成到了Docker中，而对于1.13之下，需要单独设置Swarm模式，在此建议，使用1.13之上的版本。&lt;/p&gt;
&lt;p&gt;在Swarm中有几个名字概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Node: 节点，分为Manager Node和Worker Node，在上图中有一个Manager节点和三个Worker节点,这个概念很好理解。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Service: 服务，对于Swarm而言，部署服务是它对外的职责，对于整个集群来讲，都是通过部署服务的形式，并且通过扩容、缩放来动态的决定服务数量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Task: 服务的具体承载，在于Manager节点将服务分发到具体的Worker节点上，属于下达具体任务概念。相较于Service属于微观概念。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对于Swarm模式下，需要开启几个相关的端口，而在系统中，这几个端口是默认打开的：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;TCP Port 2377：用于集群间通信，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TCP 和UDP Port 7946：用于节点间通信，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UDP Port 4789：用于Overlay网络方式下通信，这个是在集群模式下的网络通信方式，单机下采用的是Bridge方式。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="搭建swarm"&gt;&lt;strong&gt;搭建Swarm&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在需要设置为Manager的节点上输入如下命令，开始容器集群搭建，IP地址写现在服务对外IP。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker swarm init --advertise-addr &amp;lt;MANAGER-IP&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以获得如下信息，集群创建完毕，复制该命令，在其他子节点上执行完毕，子节点便是加入了该容器集群(可通过docker-machine ssh xxx的方式快速进入到目的节点中执行)。
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181027/200337917_46adff24-8690-4fc8-821c-b396fd1946c5.png" alt="200337917_46adff24-8690-4fc8-821c-b396fd1946c5"&gt;
对所有节点加入完毕，通过docker node ls命令查看节点信息，加入的节点和本身节点将会显示出来。&lt;/p&gt;
&lt;p&gt;如果有新的服务器加入了，并且已经安装好了docker  enginee需要加入到节点中且忘记了这一条token的值，可以通过在manager上执行该命令再次获得这个值&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker swarm join-token worker
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181027/200338991_69605fd6-4573-4dc5-9860-2c551936de47.png" alt="200338991_69605fd6-4573-4dc5-9860-2c551936de47"&gt;
至此，整个容器集群就搭建完毕，Manager节点和Worker节点都接入进来了。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="部署服务到集群"&gt;&lt;strong&gt;部署服务到集群&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;进入到Manager节点，开始部署一个服务到容器集群中，通过如下命令创建一个服务：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker service create --replicas 1 --name helloworld alpine ping docker.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;命令解读:&lt;/p&gt;</description></item><item><title>AspNet Core中服务的生命周期选项区别和用法</title><link>/blog/2018-10-20-aspnetcore-servicescope-basicusage/</link><pubDate>Sat, 20 Oct 2018 10:00:00 +0800</pubDate><guid>/blog/2018-10-20-aspnetcore-servicescope-basicusage/</guid><description>&lt;p&gt;在做一个小的Demo中，在一个界面上两次调用视图组件，并且在视图组件中都调用了数据库查询，结果发现，一直报错，将两个视图组件的调用分离，单独进行，却又是正常的，寻找一番，发现是配置依赖注入服务时，对于服务的生命周期没有配置得当导致，特此做一次实验来认识三者之间(甚至是四者之间的用法及区别)。&lt;/p&gt;
&lt;p&gt;Demo地址: &lt;a href="https://gitee.com/530521314/Partner.TreasureChest/tree/master/ServiceLifetime"&gt;https://gitee.com/530521314/Partner.TreasureChest/tree/master/ServiceLifetime&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="服务的生命周期"&gt;&lt;strong&gt;服务的生命周期&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在Asp.Net Core中，内置容器负责管理服务的生命周期，从被依赖注入容器创建开始，等我们调用完服务时，到容器释放该服务的所有实力为止，有几种形式表现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Transient：每次请求服务时，都会创建一个新实例，这种生命周期适合用于轻量级服务（如Repository和ApplicationService服务）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scoped：为每个HTTP请求创建一个实例，生命周期将横贯整次请求。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SingleTon：在第一次请求服务时，为该服务创建一个实例，之后每次请求将会使用第一次创建好的服务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Instance：与SingleTon类似，但在应用程序启动时会将该实例注册到容器中，可以理解为比SingleTon还早存在。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;应用程序中相关服务的控制生命周期的方法时通过相应的Add*指定，如下三种，当然还可以通过扩展方法来简化ConfigurationServices方法中所见的代码数量。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddTransient&amp;lt;IApplicationService, ApplicationService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddScoped&amp;lt;IApplicationService, ApplicationService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddSingleton&amp;lt;IApplicationService, ApplicationService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="代码设计服务生命周期"&gt;&lt;strong&gt;代码设计服务生命周期&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先设计一些服务相关的操作接口&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetGuid&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationTransient&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationScoped&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationSingleton&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{      
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationInstance&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其次对这些操作类予以实现并生成相关服务&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// 常规服务
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Operation&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readonly&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Operation&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Operation&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Empty&lt;/span&gt;&lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;() &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetGuid&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// 瞬时服务
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationTransient&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationTransient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readonly&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationTransient&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationTransient&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Empty&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;() &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetGuid&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// 单次请求内服务固定
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationScoped&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationScoped&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readonly&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationScoped&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationScoped&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Empty&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;() &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetGuid&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// 所有请求内固定服务
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationSingleton&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationSingleton&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readonly&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationSingleton&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationSingleton&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Empty&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;() &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetGuid&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;       &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// 应用程序内固定服务
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationInstance&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IOperationInstance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;   &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readonly&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationInstance&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OperationInstance&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Empty&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewGuid&lt;/span&gt;() &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Guid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetGuid&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_guid&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对基础服务的聚合接口，提供统一服务接口&lt;/p&gt;</description></item><item><title>KnockoutJS-模板绑定</title><link>/blog/2018-10-13-knockoutjs-template-binding/</link><pubDate>Sat, 13 Oct 2018 10:00:00 +0800</pubDate><guid>/blog/2018-10-13-knockoutjs-template-binding/</guid><description>&lt;p&gt;对于knockoutJS来讲，模板绑定和Mapping插件绑定是十分重要的功能，虽然模板绑定在我工作中用的及其少，但模板绑定的重要性不可忽视，在其他前端框架中，如Angular、Vue等等，模板存在的意义十分重要，Mapping插件使得我们能够脱离手工绑定，及其方便我们快速绑定达到预期效果。&lt;/p&gt;
&lt;p&gt;KnockoutJS模板绑定更多用法：&lt;a href="https://knockoutjs.com/documentation/template-binding.html"&gt;https://knockoutjs.com/documentation/template-binding.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="模板绑定目的"&gt;&lt;strong&gt;模板绑定目的&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在mvc的开发中，对于常用的一些独立的Html，习惯将它变成分布视图或是视图组件，以此来方便调用以及重用，同时也达到解耦效果，同样在前端开发中，对于部分重用度很高、复杂度高的UI、嵌套UI等这些部分，我们也可以设计成模板。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20181013/200459354_e15f3c06-40aa-423d-bed0-36291b686ddd.png" alt="200459354_e15f3c06-40aa-423d-bed0-36291b686ddd"&gt;
在knockoutJS中通过template绑定将模板绑定到预期DOM元素中，将模板所依赖的数据呈现。模板绑定对于构建嵌套结构的页面十分方便，默认情况， Knockout使用jquery.tmpl模板引擎，使用模板绑定时，需引用jquery.tmpl和jQuery框架，或者你也可以集成其它的模板引擎(需要了解Knockout 内部知识)。&lt;/p&gt;
&lt;p&gt;模板语法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;${ someValue } &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;{{html someValue}}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;{{if someCondition}}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;{{else someCondition}}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;{{each someArray}}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体了解模板语法可以查看：&lt;a href="http://www.php.cn/js-tutorial-383558.html"&gt;http://www.php.cn/js-tutorial-383558.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="模板绑定应用"&gt;&lt;strong&gt;模板绑定应用&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;如果你仅仅是声明的字符串，KO将会使用它作为模板的ID去渲染。应用在模板上的数据是你当前的viewModel对象。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;构建一个简单模板&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;hr /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;label&amp;gt;标准模板&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div data-bind=&amp;#34;template: &amp;#39;personTemplate&amp;#39;&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;script id=&amp;#39;personTemplate&amp;#39; type=&amp;#39;text/html&amp;#39;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ${ name } 是 ${ age } 岁
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;button class=&amp;#34;btn btn-primary&amp;#34; data-bind=&amp;#39;click: makeOlder&amp;#39;&amp;gt;增长一岁&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@section Scripts{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;script type=&amp;#39;text/javascript&amp;#39;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var viewModel = {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: ko.observable(&amp;#39;刺客&amp;#39;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; age: ko.observable(20),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; makeOlder: function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; viewModel.age(viewModel.age() + 1);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ko.applyBindings(viewModel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过设计一个简单的模板，在viewModel中的name或age发生改变后，Knockout将自动重新render模板。在该例子里，每次点击按钮都会重新渲染该模板。&lt;/p&gt;</description></item><item><title>Jenkins集群搭建</title><link>/blog/2018-09-15-set-up-a-cluster-for-jenkins/</link><pubDate>Sat, 15 Sep 2018 10:00:00 +0800</pubDate><guid>/blog/2018-09-15-set-up-a-cluster-for-jenkins/</guid><description>&lt;p&gt;Jenkins的目的是加快CI/CD的步伐，集群的搭建也不是必须的，当一台服务器的构建速度受到限制下，可以考虑使用主从并发构建，来加快构建速度。作为一款超级管家的角色，Jenkins的资料非常多，&lt;a href="https://www.w3cschool.cn/jenkins/list/"&gt;Jenkins资料地址&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="配置jenkins-slave节点环境"&gt;&lt;strong&gt;配置Jenkins-Slave节点环境&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;要充当Jenkins从节点角色的主机需要满足两个条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;需要安装Java运行环境&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Master主机可免密登录&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;先完成第二个条件的设计，在Master主机免密登录到Slave主机中，在这篇文章中提到了ssh的配置 &lt;a href="https://www.cnblogs.com/CKExp/p/9552272.html"&gt;https://www.cnblogs.com/CKExp/p/9552272.html&lt;/a&gt; 便不再详细陈述。&lt;/p&gt;
&lt;p&gt;生成SSH密钥，一路回车即可。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh-keygen
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;发送密钥到Slave主机,需要填写Slave主机登录密码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh-copy-id root@118.126.xx.29
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;连接Slave主机,无需输入密码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh root@118.126.xx.29
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;现在直接登录到了Slave主机上，开始安装Java运行环境，我的服务器系统为CentOS7.2版本。
看一下Java的列表并检索Java1.8的列表（本地将安装JDK1.8）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum list java*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum list java-1.8*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200544453_4d5598e7-3f52-42e5-ba7f-b971ad9d3e92.png" alt="200544453_4d5598e7-3f52-42e5-ba7f-b971ad9d3e92"&gt;
开始安装。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install java-1.8.0-openjdk* -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200545988_cb0435e4-7d38-41f3-97d1-35c0b052c8e6.png" alt="200545988_cb0435e4-7d38-41f3-97d1-35c0b052c8e6"&gt;
安装完毕查看Java版本&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;java -version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200547445_10d0c1fc-bd1d-41e6-9415-198c9dd25f20.png" alt="200547445_10d0c1fc-bd1d-41e6-9415-198c9dd25f20"&gt;
 &lt;/p&gt;
&lt;h3 id="jenkins中配置slave从节点"&gt;&lt;strong&gt;Jenkins中配置Slave从节点&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;选择左侧菜单中的系统管理，然后选择管理节点,开始新建节点。(系统管理-&amp;gt;管理节点-&amp;gt;新建节点) &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200548612_7367168b-f03c-4648-8af2-bd811abef855.png" alt="200548612_7367168b-f03c-4648-8af2-bd811abef855"&gt;&lt;/p&gt;
&lt;p&gt;根据以下配置要求进行相关信息填写：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200549826_8b16b6b2-7dd1-4c24-8eea-410e76a6ab1c.png" alt="200549826_8b16b6b2-7dd1-4c24-8eea-410e76a6ab1c"&gt;&lt;/p&gt;
&lt;p&gt;开始信息的填写工作 ：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200551315_d05fc5b6-97e3-4ed6-9f9a-99d7c4fde7ef.png" alt="200551315_d05fc5b6-97e3-4ed6-9f9a-99d7c4fde7ef"&gt;
凭证配置：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200552530_aa762f7c-d582-455b-bdf0-c1569d89bd8d.png" alt="200552530_aa762f7c-d582-455b-bdf0-c1569d89bd8d"&gt;
完成后点击保存，开始进行手动验证从节点身份，有时会出现一些错误(不可避免，没有谁真的一次性就成功的)。&lt;/p&gt;
&lt;p&gt;看到这个界面后，等待一会，耗时比较长，  &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200554351_bda4f56a-fa2c-441c-a2e2-2e7015928149.png" alt="200554351_bda4f56a-fa2c-441c-a2e2-2e7015928149"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="将任务转移到slave从节点上构建"&gt;&lt;strong&gt;将任务转移到Slave从节点上构建&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;新建一个Jenkins任务，填入相应的信息，Git地址、执行脚本、执行相关配置，选择从节点。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180915/200555744_bb0ab0b4-32fb-436b-93fc-63a6852459a8.png" alt="200555744_bb0ab0b4-32fb-436b-93fc-63a6852459a8"&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;遇到的错误：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;提示私钥密码验证失败。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[05/15/14 09:37:32] [SSH] Opening SSH connection to xxx.xxx.xxx.xxx:xxx.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ERROR: Server rejected the 1 private key(s) for jenkins (credentialId:7265ff8a-1515-4745-a174-e6f550630818/method:publickey)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[05/15/14 09:37:32] [SSH] Authentication failed.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hudson.AbortException: Authentication failed.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at hudson.plugins.sshslaves.SSHLauncher.openConnection(SSHLauncher.java:1143)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at hudson.plugins.sshslaves.SSHLauncher$2.call(SSHLauncher.java:648)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at hudson.plugins.sshslaves.SSHLauncher$2.call(SSHLauncher.java:642)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at java.util.concurrent.FutureTask.run(FutureTask.java:166)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1146)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at java.lang.Thread.run(Thread.java:701)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[05/15/14 09:37:32] [SSH] Connection closed.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[05/15/14 09:37:32] Launch failed - cleaning up connection
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;是由于在Master这边没有对私钥文件进行授权 使用命令chmod 600 /root/.ssh/id_rsa&lt;/p&gt;</description></item><item><title>DotNet&amp;NPOI处理Excel文件</title><link>/blog/2018-09-11-dotnet-npoi-excel/</link><pubDate>Tue, 11 Sep 2018 10:00:00 +0800</pubDate><guid>/blog/2018-09-11-dotnet-npoi-excel/</guid><description>&lt;p&gt;NPOI作为开源免费的组件，功能强大，可用来读写Excel(兼容xls和xlsx两种版本)、Word、PPT文件。可是要让我们记住所有的操作，这便有点困难了，至此，总结一些在开发中常用的针对Excel的简单。&lt;a href="https://archive.codeplex.com/?p=npoi"&gt;NPOI官网地址&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="npoi的安装"&gt;&lt;strong&gt;NPOI的安装&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;下载NPOI或是通过Nuget包加入进来，然后在代码中引用如下命名空间，然后开始读写Excel文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using NPOI.SS.UserModel;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using NPOI.XSSF.UserModel;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using NPOI.HSSF.UserModel;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;using NPOI.SS.Util;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt; &lt;/p&gt;
&lt;h3 id="npoi写入excel文件"&gt;&lt;strong&gt;NPOI写入Excel文件&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在NPOI中，使用HSSFWorkbook类类来处理xls结尾的Excel文件(版本在2003及以前)，XSSFWorkbook类来处理xlsx结尾的Excel文件(版本在2007及以后)，都继承自接口IWorkbook，我们可以使用IWorkbook来统一处理两种不同格式的Excel文件。&lt;/p&gt;
&lt;p&gt;直接参考相关代码即可，对于合并单元格的跨行跨列操作，无需将被跨掉的行生成新行，合并单元格的信息是单独保存的。设置单元格样式时，请创建一个新的样式对象，不创建将使用默认的样式对象。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Datable导出到Excel
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DataTableToExcel&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//一些已有数据信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#a6e22e"&gt;bool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fileSaved&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#a6e22e"&gt;SaveFileDialog&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;saveDialog&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SaveFileDialog&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;DefaultExt&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;xls&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;Filter&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Excel文件|*.xls&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;FileName&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DateTime&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;yyyyMMdd&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;enterpriseTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;名称&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;委托书&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#a6e22e"&gt;saveDialog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ShowDialog&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#a6e22e"&gt;string&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;saveFileName&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;saveDialog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;FileName&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;saveFileName&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;IndexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;:&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;; &lt;span style="color:#75715e"&gt;//被点了取消
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;saveFileName&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;IWorkbook&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;workbook&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;string&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fileExt&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;System&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;IO&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GetExtension&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;saveFileName&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;ToLower&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;fileExt&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;.xlsx&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;workbook&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;XSSFWorkbook&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;fileExt&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;.xls&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;workbook&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HSSFWorkbook&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;ISheet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;workbook&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateSheet&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Sheet1&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));&lt;span style="color:#75715e"&gt;//合并单元格
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;IRow&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);&lt;span style="color:#75715e"&gt;//创建首行
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;ICell&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);&lt;span style="color:#75715e"&gt;//行中创建第一列
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;标题&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;ICellStyle&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;style&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;workbook&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCellStyle&lt;/span&gt;();&lt;span style="color:#75715e"&gt;//设置样式，创建新的style实例，脱离统一样式
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;style&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Alignment&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NPOI&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SS&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;UserModel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HorizontalAlignment&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Center&lt;/span&gt;;&lt;span style="color:#75715e"&gt;//设置单元格的样式：水平对齐居中
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;style&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;VerticalAlignment&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NPOI&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SS&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;UserModel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;VerticalAlignment&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Center&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;IFont&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;font&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;workbook&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateFont&lt;/span&gt;();&lt;span style="color:#75715e"&gt;//新建一个字体样式对象
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;font&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Boldweight&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;short&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;MaxValue&lt;/span&gt;; &lt;span style="color:#75715e"&gt;//设置字体加粗样式
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;style&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetFont&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;font&lt;/span&gt;); &lt;span style="color:#75715e"&gt;//使用SetFont方法将字体样式添加到单元格样式中
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CellStyle&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;style&lt;/span&gt;; &lt;span style="color:#75715e"&gt;//将新的样式赋给单元格
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;编号:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;planCode&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;日期：&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;taskPlantable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;编制日期&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;单位:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;enterpriseTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;名称&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;联系人：&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;enterpriseTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;联系人&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;传真:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;enterpriseTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;传真&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;联系电话：&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;enterpriseTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;电话&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;详细地址:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;enterpriseTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;详细地址&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#75715e"&gt;//数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;taskProjectTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Count&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;名称:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;taskProjectTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;名称&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;类型:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;taskProjectTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;项目类型&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;项目&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;方法&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;仪器&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#75715e"&gt;//获取数据信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;DataTable&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;taskDataTable&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mysql&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GetTableFromSQL&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;selstr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;selstr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Clear&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;j&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;j&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;taskDataTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Count&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;j&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;AddMergedRegion&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CellRangeAddress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateRow&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;taskDataTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;j&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;名称&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;taskDataTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;j&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;方法&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;row&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;CreateCell&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#a6e22e"&gt;cell&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetCellValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;taskDataTable&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Rows&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;j&lt;/span&gt;][&lt;span style="color:#e6db74"&gt;&amp;#34;仪器型号&amp;#34;&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;ToString&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#75715e"&gt;//转为字节数组
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;MemoryStream&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;stream&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MemoryStream&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;workbook&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Write&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;stream&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;stream&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ToArray&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#75715e"&gt;//保存为Excel文件
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;using&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;FileStream&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileStream&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;saveDialog&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;FileName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;FileMode&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;FileAccess&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Write&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Write&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Length&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Flush&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;fileSaved&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ex&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;fileSaved&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#a6e22e"&gt;MessageBox&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Show&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;导出文件时出错,文件可能正被打开！\n&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ex&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Message&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#a6e22e"&gt;GC&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Collect&lt;/span&gt;();&lt;span style="color:#75715e"&gt;//强行销毁
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;fileSaved&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;File&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;saveFileName&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;MessageBox&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Show&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;导出成功！&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;通知&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;Process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Start&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;saveFileName&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;MessageBox&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Show&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;导出失败！&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;通知&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="npoi读取excel文件"&gt;&lt;strong&gt;NPOI读取Excel文件&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;打开指定Excel文件并读取文件中的内容，加入到DataTable中，或是加入到其它的数据载体中。&lt;/p&gt;</description></item><item><title>Docker Machine搭建并加入节点</title><link>/blog/2018-09-01-docker-machine-sets-up-and-joins-the-node/</link><pubDate>Sat, 01 Sep 2018 10:00:00 +0800</pubDate><guid>/blog/2018-09-01-docker-machine-sets-up-and-joins-the-node/</guid><description>&lt;p&gt;对于集群服务器来讲，要在每台机器上手动安装Docker是一件及其痛苦的事情，还好有Docker Machine这一工具，Docker三剑客中的一角.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="docker-machine介绍"&gt;Docker Machine介绍&lt;/h3&gt;
&lt;p&gt;这个工具已经出了比较久了，Docker Machine官方介绍：&lt;a href="https://docs.docker.com/v1.13/machine/overview/"&gt;https://docs.docker.com/v1.13/machine/overview/&lt;/a&gt; ,按我的理解就是一个控制系的高手操作着地下一批人，犹如该图，面对不同的服务器，云、虚拟机、本地主机等。通过Docker Machine进行批量安装。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180901/200808204_bd0b9f2e-e199-4b18-83d2-9beb9f25a306.png" alt="200808204_bd0b9f2e-e199-4b18-83d2-9beb9f25a306"&gt;&lt;/p&gt;
&lt;h3 id="服务器环境介绍"&gt;服务器环境介绍&lt;/h3&gt;
&lt;p&gt;当前服务器版本CentOS7.2 64bit，Docker版本1.13.1，Docker Machine版本0.10.0。&lt;/p&gt;
&lt;p&gt;服务器列表：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Master:182.61.xxx.218  &amp;ndash; A&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Slave:106.12.xxx.4 &amp;ndash;B&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Slave:118.126.xxx.29 &amp;ndash;C&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Slave:123.207.xxx.233 &amp;ndash;D&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将通过服务器A完成在B、C和D上完成Docker环境的安装&lt;/p&gt;
&lt;h3 id="heading"&gt; &lt;/h3&gt;
&lt;h3 id="完成docker-machine搭建"&gt;&lt;strong&gt;完成Docker Machine搭建&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先我们在A服务器上安装Docker环境，安装过程可见：&lt;a href="https://www.cnblogs.com/CKExp/p/8159269.html"&gt;https://www.cnblogs.com/CKExp/p/8159269.html&lt;/a&gt;，在此直接开始Docker Machine的安装。&lt;/p&gt;
&lt;p&gt;在服务器A上开始安装Docker Machine，输入以下命令从docker仓库中下载下来，并完成权限配置，注意我这里下载的版本是0.10.0，如果需要更高的版本呢下载可以选择其它版本下载&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1 $ curl -L https://github.com/docker/machine/releases/download/v0.10.0/docker-machine-$(uname -s)-$(uname -m) &amp;gt;/tmp/docker-machine &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2 chmod +x /tmp/docker-machine &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3 sudo cp /tmp/docker-machine /usr/local/bin/docker-machine
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;搭建成功,查看服务器版本:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-machine -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180901/200809696_d306b005-7753-4114-8670-c29918bba522.png" alt="200809696_d306b005-7753-4114-8670-c29918bba522"&gt;
 &lt;/p&gt;
&lt;h3 id="用docker-machine配置子节点安装docker-engine"&gt;&lt;strong&gt;用Docker Machine配置子节点安装Docker engine&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先我们首先得保证两个服务器的正常通讯，因此先生成密钥对，保证免交互登录。&lt;/p&gt;
&lt;p&gt;创建密钥对（在A主机操作），输入命令并一直回车&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# ssh-keygen
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;拷贝公钥到B、C和D主机上,过程中需要确认连接、输入密码(直接登录目标主机的密码)等操作，正常进行即可。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# ssh-copy-id root@106.12.xxx.4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后通过该命令连接是否可以正常登录到目标主机&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# ssh root@106.12.xxx.4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下来，可以依次将docker环境安装到各子节点上，通过docker-machine提供的命令create及其各种附带指定， -d命令指定了引用的驱动类型，我这全是Linux下服务器，直接用generic即可，如果是其它类型，可以看一下官网提供的驱动类型。&lt;/p&gt;</description></item><item><title>Docker中完成Jenkins的安装</title><link>/blog/2018-08-31-docker-jenkins-installation/</link><pubDate>Fri, 31 Aug 2018 10:00:00 +0800</pubDate><guid>/blog/2018-08-31-docker-jenkins-installation/</guid><description>&lt;p&gt;去年就开始学习采用Docker+Jenkins+.Net Core搭建生成式流水线，一直拖到现在，也没有彻底的好好静下来去总结总结。趁着现在对自己的严格要求下，逐渐开始重视自我总结，以此来巩固逐渐失去的知识。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="安装docker"&gt;&lt;strong&gt;安装Docker&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;这里可以参考之前写过的一篇文章：&lt;a href="https://www.cnblogs.com/CKExp/p/8159269.html"&gt;https://www.cnblogs.com/CKExp/p/8159269.html&lt;/a&gt; 内容比较简单，但还算是能够实用，安装的Docker版本是1.13.1，服务器版本是CentOS7.2。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="安装jenkins"&gt;&lt;strong&gt;安装Jenkins&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;先说明一下，在Jenkins安装时，我们需要在里面配置号Docker Compose组件，以此来支撑我们在后面同时发布多个网站或服务。有关docker compose知识可以参见&lt;a href="https://yeasy.gitbooks.io/docker_practice/content/swarm/"&gt;https://yeasy.gitbooks.io/docker_practice/content/swarm/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;所以我们不直接拉取Jenkins镜像，通过Dockerfile的形式来拉取Jenkins镜像，并在容器内安装好Docker Compose组件。而不单独在宿主机上安装Docker Compose，尽管那样也没问题，只需将应用程序直接部署到宿主机上就行，但是就失去了我们想要设计成流水线的目的了。&lt;/p&gt;
&lt;p&gt;先创建一个Jenkins文件夹并编写Dockerfile文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1 # cd /
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2 # mkdir Jenkins
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3 # cd Jenkins
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4 # touch Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;5 # vim Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dockerfile文件内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FROM jenkins
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;USER root
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;#更新源并安装libltdl7
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get install -y libltdl7
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ARG dockerGid=999
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN echo &amp;#34;docker:x:${dockerGid}:jenkins&amp;#34; &amp;gt;&amp;gt; /etc/group
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# 安装 Docker-Compose
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-&amp;#39;uname -s&amp;#39;-&amp;#39;uname -m&amp;#39; -o /usr/local/bin/docker-compose
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# 授权
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RUN chmod +x /usr/local/bin/docker-compose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;开始构建该镜像&lt;/p&gt;</description></item><item><title>KnockoutJS-绑定元素</title><link>/blog/2018-08-26-knockoutjs-binding-element/</link><pubDate>Sun, 26 Aug 2018 10:00:00 +0800</pubDate><guid>/blog/2018-08-26-knockoutjs-binding-element/</guid><description>&lt;p&gt;工作变更，又走回了WPF，一个来月没有接触web开发了，之前的KnockoutJS却不想放弃，继续进行知识的巩固，下个月开始重新走回web开发之路，还是得用回一些习惯了的工具。本次开始接触各绑定元素功能、用法，这些绑定方式是在使用ko过程中用的最多的。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;绑定元素在KnockoutJS中可以分为三大类&lt;/p&gt;
&lt;p&gt;1、文本控制与显示隐藏&lt;/p&gt;
&lt;p&gt;2、流程控制&lt;/p&gt;
&lt;p&gt;3、配合表单使用&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;从个人使用过程中应用程度进行排序展示：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;**value：**在DOM元素与viewModel对象的属性间建立桥梁，用户编辑控件时viewModel中对应属性值将自动更新，同样在更新了对应属性后，用户控件展示数据也将自动更新。常用于input、select、textarea等控件；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**text：**绑定到DOM元素上，将viewModel对象的属性的值展示在绑定元素上。常用于p、pre、label、span、th、td等控件使用；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;click&lt;/strong&gt;：绑定到DOM元素上添加事件句柄，当点击事件被触发时，执行我们定义好的JS函数。常用于button、input、a等控件使用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;click说明：在绑定函数时，可以指定任意函数，即在viewModel中声明的函数和在viewModel之外声明的函数都可被调用，同时在调用viewModel之内的函数时可以直接写函数名，而无需带上viewModel对象名。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;**visible:**绑定到DOM元素上，该元素的隐藏或显示将有viewModel的属性状态、函数返回值或表达式来决定。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;针对前四个绑定元素，简单演示一下功能：&lt;/p&gt;
&lt;p&gt;Html:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;公司名称&lt;span style="color:#f92672"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;input&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;value:unitName&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;公司地址&lt;span style="color:#f92672"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;input&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;value:address&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;p&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text:&amp;#39;您的公司名称为:&amp;#39;+unitName()+&amp;#39;，公司地址是:&amp;#39;+address()+&amp;#39; .&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;pre&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text:&amp;#39;您的公司名称为:&amp;#39;+unitName()+&amp;#39;，公司地址是:&amp;#39;+address()+&amp;#39; .&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;label&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text:&amp;#39;您的公司名称为:&amp;#39;+unitName()+&amp;#39;，公司地址是:&amp;#39;+address()+&amp;#39; .&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;span&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text:&amp;#39;您的公司名称为:&amp;#39;+unitName()+&amp;#39;，公司地址是:&amp;#39;+address()+&amp;#39; .&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#f92672"&gt;&amp;lt;th&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text:unitName&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#f92672"&gt;&amp;lt;td&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text:address&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;visible:visibleButton&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;button&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;click: updateUnitName&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;修改公司名称为中部星城科技&lt;span style="color:#f92672"&gt;&amp;lt;/button&amp;gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;button&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;click: updateAddress&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;修改公司地址为湖南长沙雨花区&lt;span style="color:#f92672"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;button&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-bind=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;click:function(){viewModel.visibleButton(false);}&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;隐藏上面两个按钮&lt;span style="color:#f92672"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;JS代码:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;viewModel&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//value、text
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;unitName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;observable&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;address&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;observable&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//click
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//viewModel内JS函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;updateAddress&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;address&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;湖南长沙雨花区&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//visible
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;visibleButton&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;observable&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//任意JS函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;updateUnitName&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;viewModel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;unitName&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;中部星城科技&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#a6e22e"&gt;$&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;applyBindings&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;viewModel&lt;/span&gt;);&lt;span style="color:#75715e"&gt;//完成绑定
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;/script&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;动画效果图：
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180826/200924840_b7ca9dd8-4891-456b-8a6d-907a18f36957.png" alt="200924840_b7ca9dd8-4891-456b-8a6d-907a18f36957"&gt;&lt;/p&gt;</description></item><item><title>AspNet Core WebApi中接入Swagger组件(初级)</title><link>/blog/2018-08-18-aspnetcore-webapi-swagger-basic/</link><pubDate>Sat, 18 Aug 2018 10:00:00 +0800</pubDate><guid>/blog/2018-08-18-aspnetcore-webapi-swagger-basic/</guid><description>&lt;p&gt;开发WebApi时通常需要为调用我们Api的客户端提供说明文档。Swagger便是为此而存在的,能够提供在线调用、调试的功能和API文档界面。&lt;/p&gt;
&lt;p&gt;环境介绍：Asp.Net Core WebApi + Swagger。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="加入swagger组件"&gt;加入Swagger组件&lt;/h3&gt;
&lt;p&gt;Swashbuckle.AspNetCore 中分为3个组件。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Package&lt;/th&gt;
&lt;th style="text-align: left"&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Swashbuckle.AspNetCore.Swagger&lt;/td&gt;
&lt;td style="text-align: left"&gt;Exposes &lt;em&gt;SwaggerDocument&lt;/em&gt; objects as a JSON API. It expects an implementation of &lt;em&gt;ISwaggerProvider&lt;/em&gt; to be registered which it queries to retrieve Swagger document(s) before returning as serialized JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Swashbuckle.AspNetCore.SwaggerGen&lt;/td&gt;
&lt;td style="text-align: left"&gt;Injects an implementation of &lt;em&gt;ISwaggerProvider&lt;/em&gt; that can be used by the above component. This particular implementation automatically generates &lt;em&gt;SwaggerDocument&lt;/em&gt;(s) from your routes, controllers and models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Swashbuckle.AspNetCore.SwaggerUI&lt;/td&gt;
&lt;td style="text-align: left"&gt;Exposes an embedded version of the swagger-ui. You specify the API endpoints where it can obtain Swagger JSON and it uses them to power interactive docs for your API、&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;安装Swashbuckle.AspNetCore，在Nuget中搜索或是包管理器下输入命令都可，找到这个包，安装，其中有三个小弟是三个不同的组件，在第一个中都集成了。&lt;/p&gt;</description></item><item><title>ABP框架开发-连接Mysql</title><link>/blog/2018-08-09-abp-mysql/</link><pubDate>Thu, 09 Aug 2018 10:00:00 +0800</pubDate><guid>/blog/2018-08-09-abp-mysql/</guid><description>&lt;p&gt;开始想用Abp框架来搭建公司的新项目，虽然一切还没有定数，但是兵马未动，粮草先行，我先尝试一下整个过程，才能够更好的去争取机会。&lt;/p&gt;
&lt;p&gt;此次技术选型:Abp(Asp.Net core mvc)+mysql(8.0)+docker&lt;/p&gt;
&lt;p&gt;Abp官网地址：&lt;a href="https://aspnetboilerplate.com/"&gt;https://aspnetboilerplate.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Nuget中安装mysql相关包，oracle公司提供的mysql包用起来还有问题，所以现在都是用的第三方的mysql包。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;pomelo.entityframeworkcore.mysql&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pomelo.entityframeworkcore.mysql.design&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180809/201038657_555900b7-9c76-49e0-bef1-8ae4f6e9dc27.png" alt="201038657_555900b7-9c76-49e0-bef1-8ae4f6e9dc27"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在EntityFrameworkCore层中找到XXDbContextConfigurer，修改DbContext中数据库配置，默认使用的SqlServer，修改为mysql。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180809/201039951_5c2fcca1-bc6b-4de1-9302-3503b7ebc7dc.png" alt="201039951_5c2fcca1-bc6b-4de1-9302-3503b7ebc7dc"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;修改连接字符串，在Web.Host或是Web.MVC下找到appsetting.json文件并修改其中的连接字符串。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;Default&amp;#34;: &amp;#34;Server=xxx.xxx.xxx.xxx; Database=surrounddb;charset=utf8;uid=root;pwd=123456;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我直接在服务器中的Docker下安装了一个mysql数据库，因此在开发环境下也将直接使用该数据库。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开始更新数据库，可将EntityFrameworkCore层下的Migrations文件夹删掉不影响,其中的是该版本历史过程中数据库迁移所带来的,于我们而言不重要了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在程序包管理器控制台下选中EntityFrameworkCore，并开始输入命令&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PM&amp;gt;Add-Migration name(&lt;span style="color:#960050;background-color:#1e0010"&gt;取一个本次更新的名称&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;提示信息：To undo this action, use Remove-Migration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PM&amp;gt;Update-Database
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;没有差错下将会提示如下：
提示信息：Applying migration &amp;lsquo;20180809141445_InitAll&amp;rsquo;. Done.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180809/201040985_afcabe73-1d74-46d4-883c-c6e15a8f2e69.png" alt="201040985_afcabe73-1d74-46d4-883c-c6e15a8f2e69"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启动网站，成功打开并连接，如有表结构变化，更新数据库，只需再次运行之前两行命令即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1、我的mysql数据库版本已高于5.7，在mysql5.7时会报一些错误，诸如：Specified key was too long; max key length is 767 bytes之类的.&lt;/p&gt;
&lt;p&gt;是由于该版本的mysql使用的编码方式不同，选择提升版本、通过修改mysql的编码方式解决(&lt;a href="https://blog.csdn.net/NikoZhao/article/details/79521812"&gt;https://blog.csdn.net/NikoZhao/article/details/79521812&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;2、连接字符串中如果加入了Ssl=None;将会出错，去掉即可。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2018-08-09，望技术有成后能回来看见自己的脚步。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>KnockoutJS-快速入门</title><link>/blog/2018-07-14-knockoutjs-quickstart/</link><pubDate>Sat, 14 Jul 2018 10:00:00 +0800</pubDate><guid>/blog/2018-07-14-knockoutjs-quickstart/</guid><description>&lt;p&gt;虽然在WPF中接触过MVVM模式，可是刚开始在Web中接触到Knockout.JS让我大吃一惊，简化了好多工作量，原来可能需要一大堆的JS脚本完成的工作量，被释放许多。接触KnockoutJS一年多了，在好多个项目中也用到过，虽然每个项目中都有点重复造轮子的滋味，但是不得不说，越来越喜欢用KO完成前端的一些工作，虽然现在有许多流行的MVVM/MVC前端框架，如Angular、Vue、React等等，接触过Vue，简单用过一次，接触不深，不敢谈论，也就带过吧。工作之余，开始总结自己在使用的工具、框架上用到的，体会到的感受。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="简单例子"&gt;&lt;strong&gt;简单例子&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;先来看个最简单ko所带来的效果：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180714/201108357_8b4160e7-52a2-4e09-8dcd-3dc779c0e804.png" alt="201108357_8b4160e7-52a2-4e09-8dcd-3dc779c0e804"&gt;&lt;/p&gt;
&lt;p&gt;看看代码是如何简洁设计的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;label&amp;gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;公司名称&lt;/span&gt;&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;input type=&lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt; data-bind=&lt;span style="color:#e6db74"&gt;&amp;#34;value:unitName&amp;#34;&lt;/span&gt; /&amp;gt;&amp;lt;br /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;label&amp;gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;公司地址&lt;/span&gt;&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;input type=&lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt; data-bind=&lt;span style="color:#e6db74"&gt;&amp;#34;value:address&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;p data-bind=&lt;span style="color:#e6db74"&gt;&amp;#34;text:&amp;#39;您的公司名称为:&amp;#39;+unitName()+&amp;#39;，公司地址是:&amp;#39;+address()+&amp;#39; .&amp;#39;&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@section Scripts{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;script type=&lt;span style="color:#e6db74"&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; viewModel = {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            unitName: ko.observable(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            address: ko.observable(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#960050;background-color:#1e0010"&gt;$&lt;/span&gt;(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            ko.applyBindings(viewModel);&lt;span style="color:#75715e"&gt;//完成绑定&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="概念介绍"&gt;概念介绍&lt;/h3&gt;
&lt;p&gt;knockout.JS利用的是MVVM模式，不依赖任何第三方的、纯Javascript编写的UI类库。官网地址：&lt;a href="http://knockoutjs.com/"&gt;http://knockoutjs.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180714/201109875_580b9032-82c5-4f38-9e47-01c0cbe51d2e.png" alt="201109875_580b9032-82c5-4f38-9e47-01c0cbe51d2e"&gt;&lt;/p&gt;
&lt;p&gt;Model-View-View Model (MVVM) 是一种创建用户界面的设计模式。 描述的是如何将复杂的UI用户界面分成3个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Model：后台模型对象，存储着数据和操作，独立于前端UI的存在，与具体业务打交道。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ViewModel：前台数据展示，通常是用于对外交互的，ViewModel有两种不同的使用场景，一种是后台对外的ViewModel用于对外交互时接收的对象属于服务器端ViewModel，一种是前台Javscript对象在前台的数据交互模型.如ko中的ViewModel,当然这两种常常是互相转换的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;View：视图页面，用户能够见到的交互页面，存放着与ViewModel交互的UI，输入数据或操作对象完成对ViewModel的更新，&lt;/p&gt;
&lt;p&gt;Knockout有着三大核心功能：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;监控属性（Observables）和依赖跟踪（Dependency tracking）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;声明式绑定（Declarative bindings）&lt;/p&gt;</description></item><item><title>AspNet Core中利用Seq组件展示结构化日志功能</title><link>/blog/2018-06-30-aspnet-core-seq-logging/</link><pubDate>Sat, 30 Jun 2018 10:00:00 +0800</pubDate><guid>/blog/2018-06-30-aspnet-core-seq-logging/</guid><description>&lt;p&gt;在一次.Net Core小项目的开发中，掌握的不够深入，对日志记录并没有好好利用，以至于一出现异常问题，都得跑动服务器上查看，那时一度怀疑自己肯定没学好，不然这一块日志不可能需要自己扒服务器日志来查看，果然，很多东西没掌握，至此，花点时间看了下日志的相关操作。利用日志服务来查看日志数据。&lt;/p&gt;
&lt;p&gt;本文地址：&lt;a href="https://www.cnblogs.com/CKExp/p/9246788.html"&gt;https://www.cnblogs.com/CKExp/p/9246788.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文Demo的地址：&lt;a href="https://gitee.com/530521314/LogPanel.git"&gt;https://gitee.com/530521314/LogPanel.git&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="日志记录的原则"&gt;日志记录的原则&lt;/h3&gt;
&lt;p&gt;日志是为了方便我们观察应用程序是否正常运行，也是当运行不正常是，能够快速找到出现问题，定位问题的方式。&lt;/p&gt;
&lt;p&gt;有几条日志记录原则是我们大多都得遵循的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;日志记录清晰，内容得让我们能够知道应用程序运行正常或运行不正常下能够有关键信息指明哪里出问题了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不要过度记录，一些场景下我们需要预判是否需要记录日志信息，我们能够控制日志记录的数量，同样，日志内容，要精简记录，无关紧要的文字，废话等无需加入。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;控制日志记录级别，或许在一个日志级别下记录了很多信息，可是其中的部分信息才是最为重要的，当调高一个记录级别，这部分最为重要的信息就展示出来了，那就直接调高级别吧。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;隐私保护，日志虽然是让开发运维人员看到，可是我们也不能所有信息都记录下来，用户的隐私信息我们得保护好。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="内置日志组件"&gt;内置日志组件&lt;/h3&gt;
&lt;p&gt;Asp.Net Core中内置了日志组件，功能也很强大，首先来个简单示例：&lt;/p&gt;
&lt;p&gt;直接使用内置日志功能，无需在startup中加入另外的代码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;HomeController&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; HomeController(ILogger&amp;lt;HomeController&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Index()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    _logger.LogDebug(&lt;span style="color:#e6db74"&gt;$&amp;#34;开始监控整个网站的日志信息_{DateTime.Now}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//_logger.LogError($&amp;#34;开始监控整个网站的日志信息Error_{DateTime.Now}&amp;#34;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; View();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在调试框下看到输出的日志信息。 &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180630/201153475_a06ef302-7aaa-4c5f-8c0b-e9126403f53d.png" alt="201153475_a06ef302-7aaa-4c5f-8c0b-e9126403f53d"&gt;
开始控制日志记录级别：&lt;/p&gt;
&lt;p&gt;在StartUp下,对内置日志组件做一些处理，在配置文件中将日志记录的级别提高到Information级别：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//默认日志功能&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    loggerFactory.AddConsole(Configuration.GetSection(&lt;span style="color:#e6db74"&gt;&amp;#34;Logging&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    loggerFactory.AddDebug();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//loggerFactory.AddSerilog();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (env.IsDevelopment())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        app.UseBrowserLink();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        app.UseDeveloperExceptionPage();&lt;span style="color:#75715e"&gt;//更多错误信息的开发者异常详情页&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        app.UseExceptionHandler(&lt;span style="color:#e6db74"&gt;&amp;#34;/Home/Error&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    app.UseStaticFiles();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    app.UseMvc(routes =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        routes.MapRoute(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            name: &lt;span style="color:#e6db74"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            template: &lt;span style="color:#e6db74"&gt;&amp;#34;{controller=Home}/{action=Index}/{id?}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;appsetting.json文件中,将LogLevel的默认级别设为Information，这样一来在之前的示例中_logger.LogDebug将失去作用。&lt;/p&gt;</description></item><item><title>利用Bootstrap Paginator插件和KnockoutJS完成分页功能</title><link>/blog/2018-06-24-bootstrap-paginator-and-knockoutjs-page/</link><pubDate>Sun, 24 Jun 2018 10:00:00 +0800</pubDate><guid>/blog/2018-06-24-bootstrap-paginator-and-knockoutjs-page/</guid><description>&lt;p&gt;在最近一个项目中，需要结合一堆条件查询并对查询的结果数据完成一个简单分页功能，可是做着做着，自己的思路越来越模糊，做到心态崩溃！！！ 哈哈,特此花点时间重新总结，并从最简单的分页，然后向多条件查询分页慢慢过渡，或许有人觉得这个很简单(可以绕道啦，哈哈)，却是对基础知识的一次学习过程。&lt;/p&gt;
&lt;p&gt;Demo地址：&lt;a href="https://gitee.com/530521314/Partner.TreasureChest/tree/master/Pagination/src/Pagination.Web"&gt;https://gitee.com/530521314/Partner.TreasureChest/tree/master/Pagination/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文地址：&lt;a href="https://www.cnblogs.com/CKExp/p/9218904.html"&gt;https://www.cnblogs.com/CKExp/p/9218904.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="环境介绍"&gt;&lt;strong&gt;环境介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;分页功能很多已有的很完美的插件或是第三方应用包都能够完美实现，我在此利用了一些前端插件来完成分页功能。&lt;/p&gt;
&lt;p&gt;前端的&lt;a href="http://plugins.jquery.com/bootstrap-paginator/"&gt;Bootstrap Paginator&lt;/a&gt;插件完成前端分页数字之类的切换展示；&lt;/p&gt;
&lt;p&gt;利用&lt;a href="http://knockoutjs.com/"&gt;knockout.js&lt;/a&gt;插件完成分页数据的绑定；&lt;/p&gt;
&lt;p&gt;在后端，利用asp.net core mvc 完成分页信息的接收和处理工作。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="简单分页"&gt;&lt;strong&gt;简单分页&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;完成对已有数据的分页功能，不带条件查询。&lt;/p&gt;
&lt;p&gt;首先，一上来便是完成数据绑定工作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;bookList&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mapping&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;fromJS&lt;/span&gt;(&lt;span style="color:#960050;background-color:#1e0010"&gt;@&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Raw&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Newtonsoft&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;JsonConvert&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SerializeObject&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Model&lt;/span&gt;))),&lt;span style="color:#75715e"&gt;//展示数据
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;pageEntity&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mapping&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;fromJS&lt;/span&gt;(&lt;span style="color:#960050;background-color:#1e0010"&gt;@&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Html&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Raw&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;Newtonsoft&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;JsonConvert&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SerializeObject&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PageRequestViewModel&lt;/span&gt;()))),&lt;span style="color:#75715e"&gt;//分页信息
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;页面刚展示的时候得有分页数据吧，可以在当页面展示的时候，数据也带过来了，也可以页面展示后，再通过ajax去后台调用，我选择后者。
在页面启动时，调用该函数完成初始化页面数据。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;getBookData&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pageEntity&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mapping&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toJS&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;viewModel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;pageEntity&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ajax&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;@Url.Action(&amp;#34;SampleGetData&amp;#34;)&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;dataType&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;json&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pageEntity&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;success&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ko&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mapping&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;fromJS&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;viewModel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;bookList&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;totalPages&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;totalCount&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;totalCount&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;numberOfPages&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;totalCount&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;numberOfPages&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;totalCount&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;numberOfPages&lt;/span&gt;) &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;#pagination&amp;#39;&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;bootstrapPaginator&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;分页信息(当前页面，页面展示数据条数)带过去，后台根据分页信息完成数据查询，并获得总的记录条数用于前端页面计算总页数。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult SampleGetData(PageRequestViewModel pageEntity)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; bookList = PageDataSeed.GetPageDataList()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Skip((pageEntity.PageIndex - &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) * pageEntity.PageSize)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Take(pageEntity.PageSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; pageResultViewModel = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; PageResultViewModel&amp;lt;Book&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PageIndex = pageEntity.PageIndex,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PageSize = pageEntity.PageSize,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TotalCount = PageDataSeed.GetPageDataList().Count(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Data = bookList
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Json(pageResultViewModel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;根据分页信息查询也就搞定了，当点击底部的页面码的时候得改变当前分页信息，然后要切换当前分页信息所对应的数据出来，在Bootstrap paginator插件中，点击页面码有一个函数onPageClicked，点击具体的某一页后，根据参数page获得页面，改变当前展示的页面码数字，并对分页信息修改，然后再次调用ajax获得新数据。&lt;/p&gt;</description></item><item><title>图片在线裁剪和图片上传总结</title><link>/blog/2018-03-31-image-cropping-and-uploading/</link><pubDate>Sat, 31 Mar 2018 10:00:00 +0800</pubDate><guid>/blog/2018-03-31-image-cropping-and-uploading/</guid><description>&lt;p&gt;上周需要做一个图片上传并且将上传的图片在线可以裁剪展示,觉得这个功能很有用，但是找参考资料的时候却并不是很多，因此来将我用到的总结总结，也让有需要的博友们直接借鉴。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="环境介绍"&gt;&lt;strong&gt;环境介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;asp.net mvc网站&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;用到的前端插件是JCrop和Bootstrap-fileinput，在后端用框架自带的一些类库进行处理即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JCrop插件是用来裁剪图片的，页面上裁剪就是保存裁剪的位置信息，然后将位置信息转给后台在后台进行实际图片裁剪功能。插件地址：&lt;a href="http://code.ciaoca.com/jquery/jcrop/demo/"&gt;http://code.ciaoca.com/jquery/jcrop/demo/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bootstrap-fileinput插件是Bootstrap下的文件上传的插件，功能强大，我将依靠这个完成文件的上传，当然也能够使用其他的文件上传工具。插件地址：&lt;a href="http://plugins.krajee.com/file-input"&gt;http://plugins.krajee.com/file-input&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件上传后页面上展示的图片是以Data URI Scheme方式进行展示的。Data URI Scheme知识点：&lt;a href="https://blog.csdn.net/aoshilang2249/article/details/51009947"&gt;https://blog.csdn.net/aoshilang2249/article/details/51009947&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;asp.net core mvc网站，前端插件不变，但是在后端不能够使用自带类库了，core下面的图片处理相关的类库还没有完全移植过来，只能够借用第三方类库&lt;strong&gt;SixLabors.ImageSharp&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="快速浏览"&gt;快速浏览&lt;/h3&gt;
&lt;p&gt; git地址：&lt;a href="https://gitee.com/530521314/TailorImage.git"&gt;https://gitee.com/530521314/TailorImage.git&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="前端页面代码"&gt;前端页面代码&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    ViewBag.Title = &amp;#34;文件上传&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;@Url.Content(&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;~/Content/Jcrop/jquery.Jcrop.css&amp;#34;)&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;@Url.Content(&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;~/Content/bootstrap-fileinput/fileinput.css&amp;#34;)&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;button&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;btn btn-primary&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-toggle=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-target=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;#myModal&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;头像&lt;span style="color:#f92672"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal fade&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;myModal&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tabindex=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;-1&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;role=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;dialog&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aria-labelledby=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;myModalLabel&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aria-hidden=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal-dialog&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal-content&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal-header&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#f92672"&gt;&amp;lt;button&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;button&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;close&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data-dismiss=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aria-hidden=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;×&lt;span style="color:#f92672"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#f92672"&gt;&amp;lt;h4&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal-title&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;myModalLabel&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;图片上传&lt;span style="color:#f92672"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;modal-body&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;row&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;col-md-6&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;style=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;width: 300px;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        &lt;span style="color:#f92672"&gt;&amp;lt;img&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;cut-img&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;thumbnail&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;style=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;width: 300px;height:300px;&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;~/Content/defaultAvatar.jpg&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#f92672"&gt;&amp;lt;div&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;col-md-5&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        &lt;span style="color:#f92672"&gt;&amp;lt;input&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;file&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;txt_file&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;txt_file&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;multiple&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;file-loading&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&amp;lt;br&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        &lt;span style="color:#f92672"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;图片说明：&lt;span style="color:#f92672"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        &lt;span style="color:#f92672"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;1、图片格式需要jpg、gif、png为后缀名.&lt;span style="color:#f92672"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        &lt;span style="color:#f92672"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;2、图片可以在线裁剪大小,以裁剪后为最终结果.&lt;span style="color:#f92672"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        &lt;span style="color:#f92672"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;3、图片上传完毕即可关闭窗口.&lt;span style="color:#f92672"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@section scripts{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;@Url.Content(&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;~/Scripts/Jcrop/jquery.Jcrop.js&amp;#34;)&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;@Url.Content(&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;~/Scripts/bootstrap-fileinput/fileinput.js&amp;#34;)&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;@Url.Content(&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;~/Scripts/bootstrap-fileinput/zh.js&amp;#34;)&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        //http://code.ciaoca.com/jquery/jcrop/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        //http://code.ciaoca.com/jquery/jcrop/demo/animation
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        //http://plugins.krajee.com/file-input
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        //http://plugins.krajee.com/file-advanced-usage-demo#advanced-example-5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        var tailorInfo = &amp;#34;&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        //初始化fileinput
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        function FileInput() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var oFile = new Object();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            oFile.Init = function (ctrlName, uploadUrl) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                var control = $(&amp;#39;#&amp;#39; + ctrlName);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                //初始化上传控件的样式
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                control.fileinput({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    language: &amp;#39;zh&amp;#39;, //设置语言
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    browseLabel: &amp;#39;选择&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    browseIcon: &amp;#34;&lt;span style="color:#f92672"&gt;&amp;lt;i&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;\&amp;#34;glyphicon&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;glyphicon-picture\&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt; &amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    browseClass: &amp;#34;btn btn-primary&amp;#34;, //按钮样式
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    uploadUrl: uploadUrl, //上传的地址
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    allowedFileExtensions: [&amp;#39;jpg&amp;#39;, &amp;#39;gif&amp;#39;, &amp;#39;png&amp;#39;],//接收的文件后缀
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    showUpload: true, //是否显示上传按钮
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    showCaption: false,//是否显示标题
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    showPreview: false,//隐藏预览
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    dropZoneEnabled: false,//是否显示拖拽区域
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    uploadAsync: true,//采用异步
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    autoReplace: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    //minImageWidth: 50,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    //minImageHeight: 50,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    //maxImageWidth: 1000,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    //maxImageHeight: 1000,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    //maxFileSize: 0,//单位为kb，如果为0表示不限制文件大小
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    //minFileCount: 0,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    maxFileCount: 1, //表示允许同时上传的最大文件个数
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    enctype: &amp;#39;multipart/form-data&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    validateInitialCount: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    previewFileIcon: &amp;#34;&lt;span style="color:#f92672"&gt;&amp;lt;i&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;class=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;glyphicon glyphicon-king&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    msgFilesTooMany: &amp;#34;选择上传的文件数量({n}) 超过允许的最大数值{m}！&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    uploadExtraData: function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        return { &amp;#34;tailorInfo&amp;#34;: tailorInfo }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            return oFile;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        function PageInit() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var jcorp = null;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var _this = this;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var fileInput = new FileInput();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            fileInput.Init(&amp;#34;txt_file&amp;#34;, &amp;#34;@Url.Action(&amp;#34;UpLoadFile&amp;#34;)&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            var input = $(&amp;#39;#txt_file&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            //图片上传完成后
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            input.on(&amp;#34;fileuploaded&amp;#34;, function (event, data, previewId, index) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                if (data.response.success) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    jcorp.destroy();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    $(&amp;#39;#cut-img&amp;#39;).attr(&amp;#39;src&amp;#39;, data.response.newImage);//Data URI Scheme形式
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    //$(&amp;#39;#cut-img&amp;#39;).attr(&amp;#39;src&amp;#39;, data.response.newImage + &amp;#34;?t=&amp;#34; + Math.random());//加尾巴解决缓存问题
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                alert(data.response.message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            //选择图片后触发
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            input.on(&amp;#39;change&amp;#39;, function (event, data, previewId, index) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                var img = $(&amp;#39;#cut-img&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                if (input[0].files &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;amp;&amp;amp;&lt;/span&gt; input[0].files[0]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    var reader = new FileReader();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    reader.readAsDataURL(input[0].files[0]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    reader.onload = function (e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        img.removeAttr(&amp;#39;src&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        img.attr(&amp;#39;src&amp;#39;, e.target.result);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        img.Jcrop({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            setSelect: [0, 0, 260, 290],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            handleSize: 10,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            aspectRatio: 1,//选框宽高比
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            bgFade: false,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            bgColor: &amp;#39;black&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            bgOpacity: 0.3,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            onSelect: updateCords
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        }, function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                            jcorp = this;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    if (jcorp != undefined) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                        jcorp.destroy();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                function updateCords(obj) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    tailorInfo = JSON.stringify({ &amp;#34;PictureWidth&amp;#34;: $(&amp;#39;.jcrop-holder&amp;#39;).css(&amp;#39;width&amp;#39;), &amp;#34;PictureHeight&amp;#34;: $(&amp;#39;.jcrop-holder&amp;#39;).css(&amp;#39;height&amp;#39;), &amp;#34;CoordinateX&amp;#34;: obj.x, &amp;#34;CoordinateY&amp;#34;: obj.y, &amp;#34;CoordinateWidth&amp;#34;: obj.w, &amp;#34;CoordinateHeight&amp;#34;: obj.h });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    console.log(tailorInfo);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            //上传出现错误
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            input.on(&amp;#39;fileuploaderror&amp;#39;, function (event, data, msg) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                alert(msg);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                //jcorp.destroy();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                //$(&amp;#39;#cut-img&amp;#39;).attr(&amp;#39;src&amp;#39;, &amp;#39;/Content/defaultAvatar.jpg&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                return false;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            //移除图片
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            input.on(&amp;#39;fileclear&amp;#39;, function (event) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                console.log(&amp;#34;fileclear&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                jcorp.destroy();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                $(&amp;#39;#cut-img&amp;#39;).attr(&amp;#39;src&amp;#39;, &amp;#39;/Content/defaultAvatar.jpg&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        $(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            PageInit();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#f92672"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="后端控制器代码"&gt;后端控制器代码&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileInputController&lt;/span&gt; : Controller
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ActionResult UpLoadFile()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; View();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;    [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ActionResult UpLoadFile(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; tailorInfo)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; success = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; message = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; newImage = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; tailorInfoEntity = JsonConvert.DeserializeObject&amp;lt;TailorInfo&amp;gt;(tailorInfo);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            tailorInfoEntity.PictureWidth = tailorInfoEntity.PictureWidth.Replace(&lt;span style="color:#e6db74"&gt;&amp;#34;px&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            tailorInfoEntity.PictureHeight = tailorInfoEntity.PictureHeight.Replace(&lt;span style="color:#e6db74"&gt;&amp;#34;px&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; file = HttpContext.Request.Files[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (file != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; &amp;amp;&amp;amp; file.ContentLength != &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                newImage = ImageHelper.TailorImage(file.InputStream, tailorInfoEntity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                success = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                message = &lt;span style="color:#e6db74"&gt;&amp;#34;保存成功&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            message = &lt;span style="color:#e6db74"&gt;&amp;#34;保存失败&amp;#34;&lt;/span&gt; + ex.Message;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Json(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { success = success, message = message, newImage = newImage });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="裁剪信息类"&gt;裁剪信息类&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// 前端裁剪信息及前端图片展示规格&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TailorInfo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PictureWidth { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PictureHeight { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; CoordinateX { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; CoordinateY { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; CoordinateWidth { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; CoordinateHeight { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="图片辅助类"&gt;图片辅助类&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// 图片处理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ImageHelper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// 图片按照实际比例放大&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;content&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;tailorInfo&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; TailorImage(Stream content, TailorInfo tailorInfo)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; scaleWidth = Convert.ToInt16(tailorInfo.PictureWidth);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; scaleHeight = Convert.ToInt16(tailorInfo.PictureHeight);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        Bitmap sourceBitmap = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Bitmap(content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; scaleWidthPercent = Convert.ToDouble(sourceBitmap.Width) / scaleWidth;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; scaleHeightPercent = Convert.ToDouble(sourceBitmap.Height) / scaleHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; realX = scaleWidthPercent * tailorInfo.CoordinateX;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; realY = scaleHeightPercent * tailorInfo.CoordinateY;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; realWidth = scaleWidthPercent * tailorInfo.CoordinateWidth;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; realHeight = scaleHeightPercent * tailorInfo.CoordinateHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; CropImage(content, (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)realX, (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)realY, (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)realWidth, (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)realHeight);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// 生成新图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;content&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;x&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;y&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;cropWidth&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;cropHeight&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; CropImage(&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;[] content, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; x, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; y, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; cropWidth, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; cropHeight)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; (MemoryStream stream = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; MemoryStream(content))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; CropImage(stream, x, y, cropWidth, cropHeight);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// 生成新图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;content&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;x&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;y&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;cropWidth&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;cropHeight&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; CropImage(Stream content, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; x, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; y, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; cropWidth, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; cropHeight)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; (Bitmap sourceBitmap = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Bitmap(content))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Bitmap bitSource = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Bitmap(sourceBitmap, sourceBitmap.Width, sourceBitmap.Height);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            Rectangle cropRect = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Rectangle(x, y, cropWidth, cropHeight);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; (Bitmap newBitMap = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Bitmap(cropWidth, cropHeight))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                newBitMap.SetResolution(sourceBitmap.HorizontalResolution, sourceBitmap.VerticalResolution);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; (Graphics g = Graphics.FromImage(newBitMap))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    g.SmoothingMode = SmoothingMode.HighQuality;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    g.CompositingQuality = CompositingQuality.HighQuality;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    g.DrawImage(bitSource, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Rectangle(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, newBitMap.Width, newBitMap.Height), cropRect, GraphicsUnit.Pixel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                    &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; BitmapToBytes(newBitMap);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// 图片转到byte数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;source&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; BitmapToBytes(Bitmap source)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders()[&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        EncoderParameters parameters = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; EncoderParameters(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        parameters.Param[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;] = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; EncoderParameter(Encoder.Quality, &lt;span style="color:#ae81ff"&gt;100L&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; (MemoryStream tmpStream = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; MemoryStream())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            source.Save(tmpStream, codec, parameters);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;[] data = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;[tmpStream.Length];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            tmpStream.Seek(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, SeekOrigin.Begin);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            tmpStream.Read(data, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)tmpStream.Length);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;            &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; SaveImageToLocal(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// 存储图片到本地文件系统&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;/// &amp;lt;param name=&amp;#34;binary&amp;#34;&amp;gt;&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; SaveImageToLocal(&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;[] binary)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//根据用户信息生成图片名 todo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, &lt;span style="color:#e6db74"&gt;@&amp;#34;Content\avatar.png&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        File.WriteAllBytes(path, binary);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;data:images/png;base64,&amp;#34;&lt;/span&gt; + Convert.ToBase64String(binary);&lt;span style="color:#75715e"&gt;//Data URI Scheme形式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#75715e"&gt;//return @&amp;#34;/Content/avatar.png&amp;#34;;//Url形式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="具体步骤"&gt;&lt;strong&gt;具体步骤&lt;/strong&gt;&lt;/h3&gt;
&lt;h4 id="引用前端插件"&gt;&lt;strong&gt;引用前端插件&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;加入JCrop插件，在官网下载或是git上下载均可,引用这两个文件即可，注意引用地址正确哈。&lt;/p&gt;</description></item><item><title>DotNet Core和Jexus配置HTTPS服务</title><link>/blog/2018-02-25-dotnetcore-jexus-configure-https/</link><pubDate>Sun, 25 Feb 2018 10:00:00 +0800</pubDate><guid>/blog/2018-02-25-dotnetcore-jexus-configure-https/</guid><description>&lt;p&gt;花了几天时间，看了好多篇博客，终于搞定了网站的HTTPS服务，借此写篇博客，来让有需要的朋友少走弯路。&lt;/p&gt;
&lt;h3 id="环境介绍"&gt;&lt;strong&gt;环境介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Linux下在Docker容器中部署好了一个网站，该网站需要通过外部提供程序访问微软的登录平台，利用的是OAuth2.0协议，因此要求必须要使用SSL服务，也使得网站必须要加入HTTPS服务。&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180225/201505305_5eedd057-41d6-4155-95fd-124d231b956d.png" alt="201505305_5eedd057-41d6-4155-95fd-124d231b956d"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;容器外，宿主机上通过jexus做端口转发。(本没打算用到jexus,但是看到情形不对，还是必须把它拉入进来)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTPS服务需要证书，通过在腾讯云上申请免费证书&lt;a href="https://console.cloud.tencent.com/ssl"&gt;https://console.cloud.tencent.com/ssl&lt;/a&gt;，一天之内便可申请到，注意了！！！，假如说使用的和我一样也是用的jexus，而腾讯云提供的证书下载包里面没有jexus，只有IIS、Nginx、Apache..等等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180225/201506869_8a4949b7-8162-4a31-ab68-563f3bf7ea66.png" alt="201506869_8a4949b7-8162-4a31-ab68-563f3bf7ea66"&gt;
我们是可以直接利用Nginx的两个文件。第一个是crt文件，第二个是key文件**。**&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180225/201507983_75e5a7e5-6fcd-4bf5-b219-cab7b7348348.png" alt="201507983_75e5a7e5-6fcd-4bf5-b219-cab7b7348348"&gt;
假如说你需要pfx结尾的文件那么可以通过这两个文件进行合成生成pfx文件可以通过命令:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;将其中文件名更改为你的文件名即可。又或者度娘上也有将内容粘贴进去然后自动生成的网站，不要敲命令的，这就不再提供网站了。
 &lt;/p&gt;
&lt;h3 id="全配置"&gt;&lt;strong&gt;全配置&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;首先，我想说下，我在jexus中配置了HTTPS服务，在Docker容器中运行的网站中也配置好了HTTPS服务。如同此图：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180225/201509225_2accb70d-e724-4bfd-a94b-5ef895814d2f.png" alt="201509225_2accb70d-e724-4bfd-a94b-5ef895814d2f"&gt;
可能有人会想，为什么都要配置，只配置jexus就足够了啊。当然，配置jexus是足够了，但是我的网站里面如果需要调用比如OAuth2.0协议支持的网站时，回掉地址总是会是用的HTTP协议，开发环境下没问题，但是一旦弄上域名或是IP地址访问，那么生成的就是HTTP的协议了。&lt;/p&gt;
&lt;p&gt;具体就是在此处.我通过配置好了微软的外部提供程序，当点击按钮后将跳转到微软登录页面：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180225/201510347_4da602bd-1520-40a0-ae65-253a5eabdda4.png" alt="201510347_4da602bd-1520-40a0-ae65-253a5eabdda4"&gt;
跳转后，当前登录页面的url中写明了一些参数是我们想要的，比如redirect_url，此时我已经配置好了网站上的HTTPS协议，在没有配上时，该处的redirect_url一直都是HTTP的&lt;/p&gt;
&lt;p&gt;好了，开始配置HTTPS服务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在.Net Core中配置HTTPS协议&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;首先，这里可能要搞清楚一下，jexus和网站之间的请求转发，这也是我想弄清楚的一个问题，jexus端口转发请求的时候是不是还是HTTPS协议的，还是说只是请求转发，到了网站中又变成了HTTP了。就如这个模型,在jexus转发之后，进入kestrel后是HTTP协议的还是HTTPS协议的？ 希望各位博友帮忙，我没有找到参考资料。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180225/201511420_03ef8049-07b5-4683-b94b-4c3b131d19a1.png" alt="201511420_03ef8049-07b5-4683-b94b-4c3b131d19a1"&gt;
我能做的就是在我尝试在kestrel中不配置HTTPS协议下，访问网站虽然也是能够成功的，但是当通过微软的第三方登录成功后回调地址访问网站时，便会是使用的HTTP协议，也就会出问题，与OAuth要求使用SSL的服务必须使用HTTPS协议相矛盾，也就促使着我必须要在kestrel中配置好HTTPS协议。具体配置如下：&lt;/p&gt;
&lt;p&gt;在Program.cs文件中,加入前面所合成的pfx文件，即可，这种情形下，要求我的每一个容器中的运行的网站都需要使用证书了。同时注意，在此处不能够使用.UseUrls(&amp;ldquo;https:&amp;rdquo;//:443&amp;quot;)来设置HTTPS服务，在没有配置.UseKestrel()下是会报错的，望注意！。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.IO;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Linq;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Threading.Tasks;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Hosting;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.Extensions.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.Extensions.Logging;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Server.Kestrel.Core;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Security.Cryptography.X509Certificates;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Net;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Identity.Api.Web.Core;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Identity.Api
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Main(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;[] args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BuildWebHost(args).Run();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; IWebHost BuildWebHost(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;[] args) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WebHost.CreateDefaultBuilder(args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//.UseJexusIntegration()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseKestrel(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.Listen(IPAddress.Any, &lt;span style="color:#ae81ff"&gt;1101&lt;/span&gt;, listenOptions =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; certificate = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; X509Certificate2(&lt;span style="color:#e6db74"&gt;&amp;#34;hdshopserver.pfx&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;你的证书密码&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listenOptions.UseHttps(certificate);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseStartup&amp;lt;Startup&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;在jexus上配置HTTPS协议（假如说用的不是jexus或是没有用到jexus，则可跳过此处）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;首先，通过命令查看到我的jexus版本是5.8.3，是支持HTTPS的了。&lt;/p&gt;</description></item><item><title>DotNet Core&amp;Docker&amp;Jexus的实践</title><link>/blog/2018-02-13-dotnetcore-docker-jexus-practice/</link><pubDate>Tue, 13 Feb 2018 10:00:00 +0800</pubDate><guid>/blog/2018-02-13-dotnetcore-docker-jexus-practice/</guid><description>&lt;p&gt;本文基于上次尝试之后的进一步尝试，加入Docker容器、编写Dockerfile,并且jexus结合Docker的使用，总结下自己的个人感想。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="环境介绍"&gt;&lt;strong&gt;环境介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;当前的场景有两种方式将Demo实现运行，一种是我将Asp.Net Core项目通过自侦听方式启动，然后外网正常访问，第二种通过功能强大的jexus作为代理，将项目发布后部署到jexus配置下，通过修改配置文件，通过jexus进行反向代理，此时项目本身可以不需要自侦听方式。目前是直接在主机下进行的，并没有加入到Docker容器中。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="docker中启动网站暂未加入dockerfile"&gt;&lt;strong&gt;Docker中启动网站（暂未加入Dockerfile）&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180213/201618353_493d77b8-5561-40aa-823a-737f7d002ef2.png" alt="201618353_493d77b8-5561-40aa-823a-737f7d002ef2"&gt;
首先，摆在面前的就有一个问题，我是直接根据镜像建立容器，然后在容器中安装Git获取项目、安装jexus部署项目、安装vim修改配置文件，还是直接获取项目后自启动侦听呢。不得不承认这两种情形都是很糟糕的，或许来说功能实现了，但是这个实现的过程是很不值得的，恰巧，我就完完全全走了一遍。&lt;/p&gt;
&lt;h4 id="加入docker容器容器中加入jexus"&gt;加入Docker容器，容器中加入jexus&lt;/h4&gt;
&lt;p&gt;通过之前的一篇文章&lt;a href="http://www.cnblogs.com/CKExp/p/8159269.html"&gt;http://www.cnblogs.com/CKExp/p/8159269.html&lt;/a&gt;不难将Docker环境搭建起来，此处将不再制造轮子。一步一步分析，首先在容器中安装Git，也就意味着假如我要操作上百个容器那不是意味着我要安装上百次Git，同理jexus，也同理Vim，这是很不值得的，然后我在想，能否将这些个软件写到Dockerfile中呢，可以，但谁又会这么去做呢。不扯远了，单讲讲网站启动，我在容器中通过将网站发布后，重新启动jexus，通过外网访问（&lt;strong&gt;ip:端口&lt;/strong&gt;），可以访问的通，还是很开心的，至少没出什么问题。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet publish -o /var/www/HDShop
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sh /usr/jexus/jws restart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意，这里的端口已经有了映射关系，我在建立容器的时候就已经指定了容器对外访问端口，所以网站的默认端口已经不合适了，我直接在Programcs中加入了&lt;strong&gt;UseUrls(&amp;ldquo;http://*:3456&amp;rdquo;)&lt;/strong&gt;,容器对外访问端口是2345.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public static IWebHost BuildWebHost(string[] args) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WebHost.CreateDefaultBuilder(args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseUrls(&amp;#34;http://*:3456&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseStartup&amp;lt;Startup&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Build();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也可以通过curl命令（&lt;strong&gt;curl ip:端口&lt;/strong&gt;）查看能否正常访问，将返回网站页面信息。
 &lt;/p&gt;
&lt;h4 id="加入docker容器容器中网站启动自侦听"&gt;加入Docker容器，容器中网站启动自侦听&lt;/h4&gt;
&lt;p&gt;Demo获取完毕，直接通过自侦听方式启动网站&lt;strong&gt;dotnet run&lt;/strong&gt; 也是可以正常访问的。&lt;strong&gt;通过一组实验之后，我得到了一组信息，包含了我们想要的结果：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180213/201620930_7506783d-7e13-45de-8f4f-f67117de93f1.png" alt="201620930_7506783d-7e13-45de-8f4f-f67117de93f1"&gt;
2345：3456 -&amp;gt; 此方式为主机下项目直接共享到容器中，主机可改变，双方可见，容器改变，仅容器可见，对主机文件并无影响 。采用镜像为dotnet ,并未构造自身的镜像&lt;/p&gt;
&lt;p&gt;总结：不采用jexus仍然能够正常运行，jexus只是加强了更多的功能。既然有无jexus对我容器内部的网站启动作用不大，那么我就可以考虑在Docker中除非有特殊情况，否则我是坚决不会将jexus加入到Docker中。当然，主机上的jexus功能还是需要留着的，只是容器中jexus存在的必要性就要考虑了。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;直接通过主机上的Git获取项目&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180213/201622527_532dad75-3b9c-4fe1-8373-8732386d20ee.png" alt="201622527_532dad75-3b9c-4fe1-8373-8732386d20ee"&gt;
通过指令将项目共享到容器中，主机上的直接修改将影响中容器内部的修改，而容器中对文件的修改却是隔离主机的，不对实际的文件进行改变。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -dit -p 2345:3456 --privileged=true -v /HDShop:/HDShop microsoft/dotnet
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样一来，我在主机上获得项目，直接在容器中进行编译，运行，是不错的。那么容器中Git存在的必要性被我否决掉了。通过实际操作，这是很可行的，外网也能正常访问。甚至是说，我接下来的所有操作，都将依赖这种形式。&lt;/p&gt;
&lt;p&gt;同样来讲，我在主机上直接修改，容器中生效，那么容器中在去安装Vim也就不必要了，除非来说容器中的文件要相对主机上的要做一些改动，那么便下载一个Vim把，毕竟容器中可没有Vim。&lt;/p&gt;
&lt;p&gt;敲vim命令时提示说：vim: command not found，这个时候就需要安装vim，可是当你敲&lt;strong&gt;apt-get install vim&lt;/strong&gt;命令时，提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Reading package lists... Done
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Building dependency tree       
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Reading state information... Done
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;E: Unable to locate package vim
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这时候需要敲：&lt;strong&gt;apt-get update&lt;/strong&gt;，这个命令的作用是：同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引，这样才能获取到最新的软件包。等更新完毕以后再敲命令：&lt;strong&gt;apt-get install vim&lt;/strong&gt;命令即可。&lt;/p&gt;</description></item><item><title>DotNet Core部署到CentOS</title><link>/blog/2018-02-03-dotnetcore-deploy-to-centos/</link><pubDate>Sat, 03 Feb 2018 10:00:00 +0800</pubDate><guid>/blog/2018-02-03-dotnetcore-deploy-to-centos/</guid><description>&lt;p&gt;本文基于初次或再次尝试部署.Net Core应用到Linux服务器上，我尝试后自我总结的经验一个简单的Demo,尝试部署在Linux服务器上和跨服务器访问数据库。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="环境介绍"&gt;&lt;strong&gt;环境介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本地使用Visual Studio 2017开发，使用的.NetCore SDK版本为2.1.4；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据库使用的MSSQLServer，部署在阿里云服务器上，WindowServer；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Demo部署在腾讯云服务器上，CentOS系统;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CentOS中安装了.net CoreSDK 2.1.4（开发和部署的环境最好一致，我在这里掉过坑）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代码管理通过Git来进行，在本地安装了Git，在CentOS中也安装了Git；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;利用jexus进行反向代理；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="项目介绍"&gt;&lt;strong&gt;项目介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;建立一个Asp.Net Core项目，这个建立过程就不贴图了，步骤简单。此处还没有使用到Docker,建立项目时,没有勾选Docker支持。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180203/201703543_1b88d225-7cb9-45c8-b928-65663440be1f.png" alt="201703543_1b88d225-7cb9-45c8-b928-65663440be1f"&gt;
整个项目从搭建到运行的简略过程&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180203/201705199_37d1405f-4653-4b35-8042-4f2970137e43.png" alt="201705199_37d1405f-4653-4b35-8042-4f2970137e43"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立实体，只加了一个User类，里面就是基本的用户名、密码、地址和创建日期。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;User&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; User()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.CreateDate = DateTime.Now;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; UserName { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Password { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Address { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; DateTime CreateDate { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;接下来是建立DbContext&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HDShopDbContext&lt;/span&gt;:DbContext
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; HDShopDbContext(DbContextOptions&amp;lt;HDShopDbContext&amp;gt; options)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; :&lt;span style="color:#66d9ef"&gt;base&lt;/span&gt;(options)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; OnModelCreating(ModelBuilder modelBuilder)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//modelBuilder.Entity&amp;lt;User&amp;gt;().ToTable(&amp;#34;xxxx&amp;#34;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;virtual&lt;/span&gt; DbSet&amp;lt;User&amp;gt; User { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;配置服务，在项目中已经默认的将EFCore相关的Nuget包加入进来了，在StartUp.cs文件中进行服务配置，使用&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddDbContext&amp;lt;HDShopDbContext&amp;gt;(d =&amp;gt; d.UseSqlServer(Configuration.GetConnectionString(&amp;#34;Default&amp;#34;)));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Starup文件整体内容&lt;/p&gt;</description></item><item><title>Docker容器中开始_Net Core之路</title><link>/blog/2018-01-01-start-to-dotnetcore-in-docker/</link><pubDate>Mon, 01 Jan 2018 10:00:00 +0800</pubDate><guid>/blog/2018-01-01-start-to-dotnetcore-in-docker/</guid><description>&lt;p&gt;开始写这篇博客前，已经尝试练习过好多次Docker环境安装,.Net Core环境安装了，在这里替腾讯云做一个推广,假如我们想学习、练手.net core 或是Docker却苦于没有开发环境，服务器也不想买，那么我们可以使用腾讯云提供的开发者实验，每个实验项目每天可以免费使用一次，时常45分钟，学习技术单靠看几篇文章、几本书是完全不够的，”实践方能出真知“。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cloud.tencent.com/developer/labs/gallery"&gt;腾讯云开发者实验连接地址&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="环境介绍"&gt;&lt;strong&gt;环境介绍&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在练习平台上的环境是CentOS7.3版 或许你会说我不懂Linux怎么办，没关系，我也是小白，学点基本命令暂时能用起来就行了。毕竟我们不是专门在Linux下搞开发的。(Linux下专业人士见谅哈)。&lt;/p&gt;
&lt;p&gt;Docker容器,如果说Docker容器还不知道什么概念的话，我推荐先暂停，先学习下Docker的基本概念，弄清楚Docker是什么，总比越看越糊涂好.&lt;/p&gt;
&lt;p&gt;dotnet镜像(也可以是aspnetcore镜像这两者的关系我将稍后解释).&lt;/p&gt;
&lt;p&gt;OK，开始动手！&lt;/p&gt;
&lt;h3 id="启动实验安装docker"&gt;&lt;strong&gt;启动实验，安装Docker&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180101/201802568_9e4b9a25-f909-4cf0-8e5d-4bb258cbe8ff.png" alt="201802568_9e4b9a25-f909-4cf0-8e5d-4bb258cbe8ff"&gt;
我进入的搭建Docker环境，当然也可以从其他实验中开始着手。从搭建Docker环境中进入我们可以看到要求的步骤，方便我们快速安装，仅此而已。 &lt;/p&gt;
&lt;p&gt;1、先来安装Docker环境&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install docker-io -y //CentOS已经将Docker软件包放在了Extras软件源中，直接利用即可
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker -v //查看docker的版本 version
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl start docker.service //开启Docker服务
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl enable docker.service //开机启动Docker服务
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180101/201804139_7bc0ca10-72fc-4ae7-8f66-ba51aaae115f.png" alt="201804139_7bc0ca10-72fc-4ae7-8f66-ba51aaae115f"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180101/201805327_7f71d500-daf9-4996-a415-1d776a90b847.png" alt="201805327_7f71d500-daf9-4996-a415-1d776a90b847"&gt;
安装Docker环境时需要等待一到两分钟&amp;hellip;&amp;hellip;..安装完毕，接下来我们需要将Docker服务进行启动&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180101/201806495_0947fadd-d728-4da3-8492-6d1e241825cd.png" alt="201806495_0947fadd-d728-4da3-8492-6d1e241825cd"&gt;
到这里Docker环境的安装就已经结束了。当然我们还需要配置Docker，开始配置DockerHub：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &amp;#34;OPTIONS=&amp;#39;--registry-mirror=https://mirror.ccs.tencentyun.com&amp;#39;&amp;#34; &amp;gt;&amp;gt; /etc/sysconfig/docker
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl daemon-reload //重新载入 systemd，扫描新的或有变动的单元
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl restart docker.service //重启docker服务
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt; &lt;/p&gt;
&lt;h3 id="安装net-core环境"&gt;&lt;strong&gt;安装.Net Core环境&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;接下来我们将有两种方式来安装我们的.net core环境&lt;/p&gt;
&lt;h4 id="方式一在docker容器中获取dotnet镜像"&gt;方式一：在Docker容器中获取dotnet镜像&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker pull microsoft/dotnet //安装.net core镜像
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180101/201807674_d061d30c-24f2-4413-a109-c49aa58077c1.png" alt="201807674_d061d30c-24f2-4413-a109-c49aa58077c1"&gt;
这种方式提供给了我们.net core的整套模板，编译工具，RunTime等等,一应俱全。我们可以通过docker命令查看我们已经拥有的镜像，&lt;strong&gt;注意&lt;/strong&gt;这需要在Linux主机下查看，别跑到容器中去了&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker images // 查看当前已有的docker镜像文件
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180101/201808890_1ba0e43e-28e5-499a-9d6b-80346fca7fa3.png" alt="201808890_1ba0e43e-28e5-499a-9d6b-80346fca7fa3"&gt;
当前命令下我们可以看到dotnet镜像已经被我们拉取下来了。继续，我们开始新建并运行一个以dotnet镜像的容器&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -it microsoft/dotnet //启动一个dotnet镜像
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20180101/201810141_0df657dc-c20c-468d-bf4d-8bd81be86c7a.png" alt="201810141_0df657dc-c20c-468d-bf4d-8bd81be86c7a"&gt;
看到root后面变化了没，这说明我们已经进入了一个容器，Docker当新建一个容器后，会给这个容器分配一个id，也就是我们所看见的那串字符串了。继续，通过命令新建一个web应用&lt;/p&gt;</description></item><item><title>自我总结和学习表单提交的几种方式 (二)</title><link>/blog/2017-12-08-several-ways-to-submit-forms-2/</link><pubDate>Fri, 08 Dec 2017 10:00:00 +0800</pubDate><guid>/blog/2017-12-08-several-ways-to-submit-forms-2/</guid><description>&lt;p&gt;表单提交感觉项目中用到的次数越来越多了，但是遇到的问题也越来越多，使用的解决方式也层出不齐，利用周末时间，自己学习学习，然后继续总结下几种表单提交的方式。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【4】传统submit提交方式拦截，改造成ajax提交&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171208/202030271_b9e14b09-e81d-45fe-958a-d2898142a34d.png" alt="202030271_b9e14b09-e81d-45fe-958a-d2898142a34d"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;form id=&amp;#34;form&amp;#34; enctype=&amp;#34;multipart/form-data&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;label for=&amp;#34;name&amp;#34;&amp;gt;姓名：&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;text&amp;#34; id=&amp;#34;name&amp;#34; data-bind=&amp;#34;value:vm.name&amp;#34; /&amp;gt;&amp;lt;br /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;label for=&amp;#34;address&amp;#34;&amp;gt;住址：&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;text&amp;#34; id=&amp;#34;address&amp;#34; data-bind=&amp;#34;value:vm.address&amp;#34; /&amp;gt;&amp;lt;br /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;label for=&amp;#34;tel&amp;#34;&amp;gt;手机号：&amp;lt;/label&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;text&amp;#34; id=&amp;#34;tel&amp;#34; data-bind=&amp;#34;value:vm.tel&amp;#34; /&amp;gt;&amp;lt;br /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;submit&amp;#34; value=&amp;#34;添加&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/form&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171208/202030271_b9e14b09-e81d-45fe-958a-d2898142a34d.png" alt="202030271_b9e14b09-e81d-45fe-958a-d2898142a34d"&gt;
&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171208/202030271_b9e14b09-e81d-45fe-958a-d2898142a34d.png" alt="202030271_b9e14b09-e81d-45fe-958a-d2898142a34d"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#form&amp;#34;).submit(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var model = ko.mapping.toJS(vm.entity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $.ajax({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url: &amp;#39;@Url.Action(&amp;#34;AddCustomInfo&amp;#34;)&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: this.method,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dataType: &amp;#34;Json&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data: { fileInfo: model },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; success: function (data) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (data.result) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; layer.alert(&amp;#34;添加成功！&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#myModal&amp;#34;).modal(&amp;#34;hide&amp;#34;);//隐藏弹框
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; return false;//阻止表单提交
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171208/202030271_b9e14b09-e81d-45fe-958a-d2898142a34d.png" alt="202030271_b9e14b09-e81d-45fe-958a-d2898142a34d"&gt;&lt;/p&gt;</description></item><item><title>自我总结和学习表单提交的几种方式 (一)</title><link>/blog/2017-11-25-several-ways-to-submit-forms-1/</link><pubDate>Sat, 25 Nov 2017 10:00:00 +0800</pubDate><guid>/blog/2017-11-25-several-ways-to-submit-forms-1/</guid><description>&lt;p&gt;最近总是记不住表单提交的几种方式，并且各种方式的适应场景也不知道，干脆来总结一次，当再学习过程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;首先从最简单的开始练手：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;【1】纯form表单形式，无js和ajax ，提交路径有action决定，方式由method决定，如果需要传输文件加上enctype&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我的表单内容:两个下拉选择、一个文件选择和一个输入框&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171125/202127662_a6d72898-d31c-4067-85ce-9fe20bbbd85f.png" alt="202127662_a6d72898-d31c-4067-85ce-9fe20bbbd85f"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;form action=&amp;#34;@Url.Action(&amp;#34;AddFile&amp;#34;, &amp;#34;Assistant&amp;#34;)&amp;#34; id=&amp;#34;form&amp;#34; method=&amp;#34;post&amp;#34; class=&amp;#34;form-horizontal&amp;#34; enctype=&amp;#34;multipart/form-data&amp;#34; &amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;form-group&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.LabelFor(m =&amp;gt; m.FileDescription, new { @class = &amp;#34;col-md-2 control-label&amp;#34; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.TextAreaFor(m =&amp;gt; m.FileDescription, new { @class = &amp;#34;form-control col-md-10&amp;#34;, rows = 5 })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;form-group&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.LabelFor(m =&amp;gt; m.FileType, new { @class = &amp;#34;col-md-2 control-label&amp;#34; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.DropDownListFor(m =&amp;gt; m.FileType, (IEnumerable&amp;lt;SelectListItem&amp;gt;)ViewBag.FileType, new { @class = &amp;#34;form-control col-md-10&amp;#34; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;form-group&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.LabelFor(m =&amp;gt; m.File, new { @class = &amp;#34;col-md-2 control-label&amp;#34; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.TextBoxFor(m =&amp;gt; m.File, new { type = &amp;#34;file&amp;#34;, id = &amp;#34;file&amp;#34;, @class = &amp;#34;form-control col-md-10&amp;#34; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;form-group&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.LabelFor(m =&amp;gt; m.IsPublic, new { @class = &amp;#34;col-md-2 control-label&amp;#34; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; @Html.DropDownListFor(m =&amp;gt; m.IsPublic, (IEnumerable&amp;lt;SelectListItem&amp;gt;)ViewBag.IsPublic, new { @class = &amp;#34;form-control col-md-10&amp;#34; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;div class=&amp;#34;form-group&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;input type=&amp;#34;submit&amp;#34; id=&amp;#34;AddFile&amp;#34; class=&amp;#34;btn col-md-offset-2&amp;#34; value=&amp;#34;添加&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;button type=&amp;#34;button&amp;#34; class=&amp;#34;btn btn-default&amp;#34; data-dismiss=&amp;#34;modal&amp;#34;&amp;gt;关闭&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/form&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171125/202127662_a6d72898-d31c-4067-85ce-9fe20bbbd85f.png" alt="202127662_a6d72898-d31c-4067-85ce-9fe20bbbd85f"&gt;
后台成功接收到了需要的信息&lt;/p&gt;</description></item><item><title>AspNet MVC下自定义错误页和展示错误页的几种方式</title><link>/blog/2017-11-19-several-ways-to-customize-the-error-page/</link><pubDate>Sun, 19 Nov 2017 10:00:00 +0800</pubDate><guid>/blog/2017-11-19-several-ways-to-customize-the-error-page/</guid><description>&lt;p&gt;在网站运行中，错误是不可避免的,错误页的产生也是不可缺少的。这几天看了博友的很多文章，自己想总结下我从中学到的和实际中配置的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先得需要知道产生错误页的来源，一种是我们的.NET平台抛出的，一种是网站所依赖的宿主抛出的，一般来讲我们所依赖的宿主就是IIS了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;IIS中的错误页入口：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171119/202245008_89c23f58-8bac-450a-aaed-e76cffbc0c3f.png" alt="202245008_89c23f58-8bac-450a-aaed-e76cffbc0c3f"&gt;&lt;/p&gt;
&lt;p&gt;其中的错误码想必并不陌生&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171119/202246732_b28d4771-d0ef-47cb-8c55-a5a5f7622d67.png" alt="202246732_b28d4771-d0ef-47cb-8c55-a5a5f7622d67"&gt;&lt;/p&gt;
&lt;p&gt;这里是在服务器上找不到所需资源时抛出的错误页,在这里可以设置需要展示的错误页面，只需将预定的错误页面加入服务器中，然后在指定状态码下配置路径即可。&lt;/p&gt;
&lt;p&gt;  这是请求在IIS中时，还未完全进入到asp.net mvc中，这里需要理解什么是未完全进入，IIS7+的版本中,不依赖于请求路径末尾的标识信息，利用mvc中的urlRoutingModule进行处理，在我们配置mvc的路由时,首先的第一条：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;routes.IgnoreRoute(&amp;#34;{resource}.axd/{*pathInfo}&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;便是隔离非mvc内部的使用文件，如果请求的只是服务器上的文件，那么路由便会在这里进行过滤，使之不匹配具体路由信息。也就只是和mvc打了个招呼 然后就走了,没有进入mvc中搞事情。
第二种是,进入了asp.net mvc的管辖范围，然后在其中出错了，便是跳到我们在程序中配置的错误页了。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;讲讲我从博友那里学到的、看到的几种方式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一种&lt;/strong&gt;是在web.config中通过customError配置。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;customErrors mode=&amp;#34;On&amp;#34; defaultRedirect=&amp;#34;~/Error/ErrorPage&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;error statusCode=&amp;#34;404&amp;#34; redirect=&amp;#34;~/Error/ErrorPage404&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/customErrors&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但是这种方式不怎么令人接受，太过于简单，没有一点异常信息，并且有时候还不能起效果，我不太喜欢这种方式。这种是用框架封装好的，利用的是将要说的第三种的强大方式实现的,当有异常发生又没得捕获时，最终利用的第三种方式自动实现。
&lt;strong&gt;第二种&lt;/strong&gt;是利用HandlerErrorAttribute 特性，利用AOP的方式，当有异常出现时，便会进入具体实现了这个特性的，且被注册了的ExceptionAttribute职责中。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171119/202247890_5aa24148-dceb-40b5-925c-cce62b7f93cc.png" alt="202247890_5aa24148-dceb-40b5-925c-cce62b7f93cc"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace SAssassin.Web.Core.Filter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 异常处理之日志记载采用消息队列方式
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public class MyExceptionAttribute : HandleErrorAttribute
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static Queue&amp;lt;Exception&amp;gt; ExceptionQueue = new Queue&amp;lt;Exception&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void OnException(ExceptionContext filterContext)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ExceptionQueue.Enqueue(filterContext.Exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filterContext.HttpContext.Response.Redirect(&amp;#34;~/ErrorPage/CustomErrorPage&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; base.OnException(filterContext);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171119/202247890_5aa24148-dceb-40b5-925c-cce62b7f93cc.png" alt="202247890_5aa24148-dceb-40b5-925c-cce62b7f93cc"&gt;
在这里，我可以得到异常信息，也可以解析具体的异常报错原因，比如404，500&amp;hellip;  可以通过这种形势，将其转移到不同的自定义错误页面上，此处我增加了一个控制器CustomErrorPageController,专门用来存放错误页面，原有的Shared下的Error.cshtml错误页面也仍然存在着。我比较喜欢这种方式，一来可以看到异常信息，而来可以设计需要跳转的错误页面。&lt;/p&gt;</description></item><item><title>DotNet下发送邮件遇到问题及解决方案</title><link>/blog/2017-10-22-problems-with-sending-emails-and-solutions/</link><pubDate>Sun, 22 Oct 2017 10:00:00 +0800</pubDate><guid>/blog/2017-10-22-problems-with-sending-emails-and-solutions/</guid><description>&lt;p&gt;.NET后台代码利用QQ邮箱服务器发送邮件遇到的问题：&amp;ldquo;mail from address must be same as authorization user&amp;rdquo;&lt;/p&gt;
&lt;p&gt;首先，看下代码实现&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public class EmailHelper
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// 发送邮件
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;param name=&amp;#34;subject&amp;#34;&amp;gt;主题&amp;lt;/param&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; /// &amp;lt;param name=&amp;#34;body&amp;#34;&amp;gt;内容&amp;lt;/param&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public static void SendEmail(string subject,string body)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var mailFrom = ConfigurationManager.AppSettings[&amp;#34;mailFrom&amp;#34;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var mailTo = ConfigurationManager.AppSettings[&amp;#34;mailTo&amp;#34;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var mailCC = ConfigurationManager.AppSettings[&amp;#34;mailCC&amp;#34;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var smtp = ConfigurationManager.AppSettings[&amp;#34;smtp&amp;#34;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var smtpPort = Convert.ToInt32(ConfigurationManager.AppSettings[&amp;#34;smtpPort&amp;#34;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var emailAccount = ConfigurationManager.AppSettings[&amp;#34;emailAccount&amp;#34;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var emailPassword = ConfigurationManager.AppSettings[&amp;#34;emailPassword&amp;#34;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 　　　　 //邮件内容信息
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MailMessage mailMessage = new MailMessage
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; From = new MailAddress(mailFrom),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Subject = subject,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Body = body,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mailMessage.To.Add(mailTo);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mailMessage.CC.Add(mailCC);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 　　　　 //邮箱服务器配置
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SmtpClient smtpClient = new SmtpClient(smtp, smtpPort)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Credentials = new NetworkCredential(emailAccount, emailPassword),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EnableSsl = true,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 　　　　 //发送邮件
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; smtpClient.Send(mailMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171022/202338004_3517a045-ae4e-494b-99de-7f4f471d6959.png" alt="202338004_3517a045-ae4e-494b-99de-7f4f471d6959"&gt;
  配置文件：&lt;/p&gt;</description></item><item><title>AspNet MVC CodeFirst模式数据库迁移步骤</title><link>/blog/2017-10-15-migration-steps-for-databases-in-codefirst/</link><pubDate>Sun, 15 Oct 2017 10:00:00 +0800</pubDate><guid>/blog/2017-10-15-migration-steps-for-databases-in-codefirst/</guid><description>&lt;p&gt;利用Code First模式构建好基本的类后，项目也开始搭建完毕并成功运行，而且已经将数据库表结构自动生成了，但是，我有新的类要加入,有字段需要修改，就得将这部分修改同步到数据库中，利用数据库迁移，将原有结构不改动，将新建类进行单独建表操作，或者是已有数据库表，改变字段，那就修改表。&lt;/p&gt;
&lt;h3 id="迁移步骤"&gt;迁移步骤:&lt;/h3&gt;
&lt;p&gt;1、打开程序包管理器控制台：工具-&amp;gt;NuGet包管理器-&amp;gt;程序包管理器控制台.(当然还有其它方式也可以打开，我比较喜欢这种)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202433330_5bf5a490-e265-44f4-b272-93c94cea1ec5.png" alt="202433330_5bf5a490-e265-44f4-b272-93c94cea1ec5"&gt;&lt;/p&gt;
&lt;p&gt;点击后将弹出程序包管理器控制台&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202435057_6d7e5432-3748-47a1-affb-a93c0b8097de.png" alt="202435057_6d7e5432-3748-47a1-affb-a93c0b8097de"&gt;&lt;/p&gt;
&lt;p&gt;极其要注意的是默认项目！！！&lt;/p&gt;
&lt;p&gt;2、启动数据库迁移，执行命令：enable-migrations&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202436069_1bd96822-ac92-44d7-ae76-b7f9b5ca2e25.png" alt="202436069_1bd96822-ac92-44d7-ae76-b7f9b5ca2e25"&gt;&lt;/p&gt;
&lt;p&gt;由于Models不一定就是直接利用项目中已有的文件夹Models,从而当你选中启动项目为asp.net mvc 框架类型时，此处将会报错&lt;/p&gt;
&lt;p&gt;具体来看我当前项目的分布情况：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202437157_69edc25f-dc81-4bea-8ccb-a1d1c7bffc34.png" alt="202437157_69edc25f-dc81-4bea-8ccb-a1d1c7bffc34"&gt;&lt;/p&gt;
&lt;p&gt;我将模型分离出来了，以至于当我默认选中SearchEngine为启动项目时，在程序包管理器控制台中输入的第一个命令就会报错&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202438198_ee789f66-106b-4259-babf-82e27f973643.png" alt="202438198_ee789f66-106b-4259-babf-82e27f973643"&gt;&lt;/p&gt;
&lt;p&gt;解决方案：选中默认项目，选择为DbContext所在的项目，此处我的是类库&lt;/p&gt;
&lt;p&gt;成功操作会如下提示：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202439150_2721ef39-537e-4348-862c-255b65febe3e.png" alt="202439150_2721ef39-537e-4348-862c-255b65febe3e"&gt;&lt;/p&gt;
&lt;p&gt;并且在DbContext同级目录下增加了migrations目录&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202440131_d7e2fd07-d07d-4df1-b247-0f3ca06ed8ed.png" alt="202440131_d7e2fd07-d07d-4df1-b247-0f3ca06ed8ed"&gt;&lt;/p&gt;
&lt;p&gt;3、开始数据库迁移命令&lt;/p&gt;
&lt;p&gt;1.Add-migration [自定义版本名称]&lt;/p&gt;
&lt;p&gt;输入add-migration updateorderheader&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202441475_a4020cba-f027-40f8-8e28-3bf064366d98.png" alt="202441475_a4020cba-f027-40f8-8e28-3bf064366d98"&gt;&lt;/p&gt;
&lt;p&gt;2.update-database&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202442552_7986d0b2-81db-4981-b9fa-ec84a9fbe8c6.png" alt="202442552_7986d0b2-81db-4981-b9fa-ec84a9fbe8c6"&gt;&lt;/p&gt;
&lt;p&gt;此处如果和我一样将DbContext单独分割出来的需要注意，需要在当前类库的配置文件中加上数据库的连接地址&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202443459_3864bd16-9265-4f60-88ca-71a8db3d5b98.png" alt="202443459_3864bd16-9265-4f60-88ca-71a8db3d5b98"&gt;&lt;/p&gt;
&lt;p&gt;如果是直接在asp.net mvc框架中的Models中加入的DbContext，则可不必，会直接读取Web.Config文件中的连接字符串。&lt;/p&gt;
&lt;p&gt;等待命令执行中时出现异常了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202445046_40d1aa53-63f4-4e20-bd8f-6f2030791d97.png" alt="202445046_40d1aa53-63f4-4e20-bd8f-6f2030791d97"&gt;&lt;/p&gt;
&lt;p&gt;  开始寻找我电脑中的SQL Server Configuration Manage ，如果有则打开，找到&lt;/p&gt;
&lt;p&gt;  &lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202447205_30515980-0cf5-4999-9627-18e96a22e549.png" alt="202447205_30515980-0cf5-4999-9627-18e96a22e549"&gt;&lt;/p&gt;
&lt;p&gt; 配置TCP/IP协议为已启用即可。我的本地没有该工具，只能通过如下方式找到该处，点击我的电脑右键-&amp;gt;管理-&amp;gt;服务和应用程序-&amp;gt;SQLServer配置管理器     ( 两次找这工具都没找到，貌似都是直接从这解决的）， 重新输入命令，等待执行&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202448214_f0d3386c-ec2c-4ea4-b793-401625deb3d1.png" alt="202448214_f0d3386c-ec2c-4ea4-b793-401625deb3d1"&gt;&lt;/p&gt;
&lt;p&gt;初步成功，由于数据库中有些表无需更改，我们可以在migrations目录中，更改需要增加或修改的具体表&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202449180_0bbfae55-0319-4956-abea-a15a8a5c0f58.png" alt="202449180_0bbfae55-0319-4956-abea-a15a8a5c0f58"&gt;&lt;/p&gt;
&lt;p&gt;通过修改其中的CreateTable/DropTable，可以控制哪些表需要改动&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202450611_579c5cd6-e931-4853-8b3d-2b9ab9522b75.png" alt="202450611_579c5cd6-e931-4853-8b3d-2b9ab9522b75"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespace SAssassin.EF.Model.Migrations
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; using System;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; using System.Data.Entity.Migrations;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public partial class updateorderheader : DbMigration
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void Up()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CreateTable(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;dbo.MyFileInfoes&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c =&amp;gt; new
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = c.Int(nullable: false, identity: true),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileName = c.String(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileDescription = c.String(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FilePath = c.String(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileType = c.Byte(nullable: false),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileSize = c.String(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileStatus = c.Byte(nullable: false),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IsPublic = c.Byte(nullable: false),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserId = c.String(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserName = c.String(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CreateDate = c.DateTime(nullable: false),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LastModityDate = c.DateTime(nullable: false),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .PrimaryKey(t =&amp;gt; t.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public override void Down()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DropTable(&amp;#34;dbo.MyFileInfoes&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20171015/202450611_579c5cd6-e931-4853-8b3d-2b9ab9522b75.png" alt="202450611_579c5cd6-e931-4853-8b3d-2b9ab9522b75"&gt;
4、修改代码配置，实现之后的操作没那么麻烦&lt;/p&gt;</description></item><item><title>WebSocket部署服务器外网无法连接解决方案</title><link>/blog/2017-09-24-the-websocket-deployment-server-cannot-connect-to-the-external-network/</link><pubDate>Sun, 24 Sep 2017 10:00:00 +0800</pubDate><guid>/blog/2017-09-24-the-websocket-deployment-server-cannot-connect-to-the-external-network/</guid><description>&lt;p&gt;首先要说的是我遇见的问题：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WebSocket connection to 'ws://www.xxxx.com/xxx/xx' failed: Error during WebSocket handshake: Unexpected response code: 200&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;网站绑定到了服务器，直接在服务器上访问webSocket服务是可行的，但是当我用外网访问时，这个错误就是我最大的敌人，在这之前还遇到过几个小毛贼，可以轻松解决，不再提，直面boss吧&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;花费两个下午的时间寻找解决方案，但是并没有找到，各种解决方案，但是直接来描述.NET下使用这一问题的，却是不多，然而面对了这一问题的朋友，大把的有。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;这个无关浏览器问题，我有火狐，谷歌，IE测试过，该出的问题还是会有。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;开发平台：.NET,利用ASP.NET MVC搭建网站，在其中利用WebSocket服务，目的是在网站内设计一个讨论平台。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;code&gt;具体来说一说：&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;首先，看下我的网页代码：&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20170924/202609548_47ea6e7c-eacf-4866-ba52-36611f6516c8.png" alt="202609548_47ea6e7c-eacf-4866-ba52-36611f6516c8"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;script type=&amp;#34;text/javascript&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var url = &amp;#34;ws://xxxx:xx&amp;#34;+&amp;#34;@Url.Action(&amp;#34;RequestProcessCenter&amp;#34;)&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; var ws;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; function connect() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ws = new WebSocket(url);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#TopicContent&amp;#34;).append(&amp;#34;正在连接\n&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ws.onopen = function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#TopicContent&amp;#34;).append(&amp;#34;已经连接\n&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ws.onmessage = function (evt) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; console.log(evt.data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#TopicContent&amp;#34;).append(evt.data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ws.onclose = function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#TopicContent&amp;#34;).append(&amp;#34;已经关闭\n&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ws.onerror = function (evt) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; console.log(evt.data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#TopicContent&amp;#34;).append(evt.data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#send&amp;#34;).click(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; console.log(1);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; console.log(ws.readyState);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; console.log(WebSocket.OPEN);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if (ws.readyState == WebSocket.OPEN) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ws.send($(&amp;#34;#content&amp;#34;).val());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(this).val(&amp;#34;&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; else {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#TextMessge&amp;#34;).append(&amp;#34;连接已经关闭&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#open&amp;#34;).click(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; connect();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $(&amp;#34;#close&amp;#34;).click(function () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ws.close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20170924/202609548_47ea6e7c-eacf-4866-ba52-36611f6516c8.png" alt="202609548_47ea6e7c-eacf-4866-ba52-36611f6516c8"&gt;
后台代码：&lt;/p&gt;</description></item><item><title>利用微软认知服务实现语音识别功能</title><link>/blog/2017-08-20-microsoft-cognitive-services-implements-speech-recognition/</link><pubDate>Sun, 20 Aug 2017 10:00:00 +0800</pubDate><guid>/blog/2017-08-20-microsoft-cognitive-services-implements-speech-recognition/</guid><description>&lt;p&gt;想实现语音识别已经很久了，也尝试了许多次，终究还是失败了，原因很多，识别效果不理想，个人在技术上没有成功实现，种种原因，以至于花费了好多时间在上面。语音识别，我尝试过的有科大讯飞、百度语音，微软系。最终还是喜欢微软系的简洁高效。(勿喷，纯个人感觉)&lt;/p&gt;
&lt;p&gt;最开始自己的想法是我说一句话(暂且在控制台上做Demo)，控制台程序能识别我说的是什么，然后显示出来，并且根据我说的信息，执行相应的行为.(想法很美好，现实很糟心）初入语音识别，各种错误各种来，徘徊不定的选择哪家公司的api，百度上查找各种语音识别的demo，学习参考，可是真正在.NET平台上运行成功的却是寥寥无几，或许是我查找方向有问题，经历了许多的坑，没一次成功过，心灰且意冷，打了几次退堂鼓，却终究忍受不住想玩语音识别。&lt;/p&gt;
&lt;p&gt;可以看看我VS中的语音demo&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20170820/202655051_a26be3d0-e87a-4a03-907b-d54345472046.png" alt="202655051_a26be3d0-e87a-4a03-907b-d54345472046"&gt;&lt;/p&gt;
&lt;p&gt;第一个是今天的主角-稍后再提。&lt;/p&gt;
&lt;p&gt;第二个和第三个是微软系的系统自带的System.Speech.dll和看了微软博客里面的一篇文章而去尝试的Microsoft.Speech.dll 可惜文章写的挺好的，我尝试却是失败的，并且发现一个问题，就是英文版的微软语音识别是无效的(Microsoft.Speech.Recognition)，而中文版的语音合成是无效的(Microsoft.Speech.Synthesis).，因此，我不得不将两个dll混合使用，来达到我想要的效果，最终效果确实达到了，不过却是极其简单的，一旦识别词汇多起来，这识别率直接下降，我一直认为是采样频率的问题，可是怎么也找不到采样频率的属性或是字段，如有会的朋友可给我点信息，让我也飞起来，哈哈。&lt;/p&gt;
&lt;p&gt;第四个是百度语音识别demo，代码简洁许多，实现难度不难，可是小细节很多，需要注意，然后是雷区挺多的，但是呢，指导走出雷区的说明书却是太少了，我是踩了雷，很郁闷。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;首先来看看，现在市面上主流语音识别设计方式：&lt;/p&gt;
&lt;p&gt;1、离线语音识别&lt;/p&gt;
&lt;p&gt;离线语音识别很好理解，就是语音识别库在本地或是局域网内，无需发起远程连接。这个也是我当初的想法，自己弄一套语音识别库，然后根据里面的内容设计想要的行为请求。利用微软系的System.Speech.dll中的语音识别和语音合成功能。实现了简单的中文语音识别功能，但是一旦我将语音识别库逐渐加大，识别率就越来越低，不知是我电脑麦克风不行还是其它原因。最终受打击，放弃。当我试着学习百度语音时，也发现了离线语音识别库，但是呢官方并没有给出具体的操作流程和设计思路，我也没有去深入了解，有时间我要好好了解一番。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20170820/202656450_c0ded954-ed74-4ad3-8b18-84e33cc2e2c8.png" alt="202656450_c0ded954-ed74-4ad3-8b18-84e33cc2e2c8"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 1 using System;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 2 //using Microsoft.Speech.Synthesis;//中文版tts不能发声
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 3 using Microsoft.Speech.Recognition;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 4 using System.Speech.Synthesis;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 5 //using System.Speech.Recognition;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 6
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 7 namespace SAssassin.SpeechDemo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 8 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 9 /// &amp;lt;summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10 /// 微软语音识别 中文版 貌似效果还好点
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;11 /// &amp;lt;/summary&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;12 class Program
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;13 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;14 static SpeechSynthesizer sy = new SpeechSynthesizer();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;15 static void Main(string[] args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;16 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;17 //创建中文识别器
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;18 using (SpeechRecognitionEngine recognizer = new SpeechRecognitionEngine(new System.Globalization.CultureInfo(&amp;#34;zh-CN&amp;#34;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;19 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;20 foreach (var config in SpeechRecognitionEngine.InstalledRecognizers())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;21 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;22 Console.WriteLine(config.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;23 }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;24 //初始化命令词
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;25 Choices commonds = new Choices();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;26 string[] commond1 = new string[] { &amp;#34;一&amp;#34;, &amp;#34;二&amp;#34;, &amp;#34;三&amp;#34;, &amp;#34;四&amp;#34;, &amp;#34;五&amp;#34;, &amp;#34;六&amp;#34;, &amp;#34;七&amp;#34;, &amp;#34;八&amp;#34;, &amp;#34;九&amp;#34; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;27 string[] commond2 = new string[] { &amp;#34;很高兴见到你&amp;#34;, &amp;#34;识别率&amp;#34;, &amp;#34;assassin&amp;#34;, &amp;#34;长沙&amp;#34;, &amp;#34;湖南&amp;#34;, &amp;#34;实习&amp;#34; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;28 string[] commond3 = new string[] { &amp;#34;开灯&amp;#34;, &amp;#34;关灯&amp;#34;, &amp;#34;播放音乐&amp;#34;, &amp;#34;关闭音乐&amp;#34;, &amp;#34;浇水&amp;#34;, &amp;#34;停止浇水&amp;#34;, &amp;#34;打开背景灯&amp;#34;, &amp;#34;关闭背景灯&amp;#34; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;29 //添加命令词
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;30 commonds.Add(commond1);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;31 commonds.Add(commond2);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;32 commonds.Add(commond3);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;33 //初始化命令词管理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;34 GrammarBuilder gBuilder = new GrammarBuilder();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;35 //将命令词添加到管理中
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;36 gBuilder.Append(commonds);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;37 //实例化命令词管理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;38 Grammar grammar = new Grammar(gBuilder);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;39
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;40 //创建并加载听写语法(添加命令词汇识别的比较精准)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;41 recognizer.LoadGrammarAsync(grammar);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;42 //为语音识别事件添加处理程序。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;43 recognizer.SpeechRecognized += new EventHandler&amp;lt;SpeechRecognizedEventArgs&amp;gt;(Recognizer_SpeechRRecongized);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;44 //将输入配置到语音识别器。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;45 recognizer.SetInputToDefaultAudioDevice();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;46 //启动异步，连续语音识别。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;47 recognizer.RecognizeAsync(RecognizeMode.Multiple);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;48 //保持控制台窗口打开。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;49 Console.WriteLine(&amp;#34;你好&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;50 sy.Speak(&amp;#34;你好&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;51 Console.ReadLine();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;52 }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;53 }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;54
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;55 //speechrecognized事件处理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;56 static void Recognizer_SpeechRRecongized(object sender, SpeechRecognizedEventArgs e)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;57 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;58 Console.WriteLine(&amp;#34;识别结果：&amp;#34; + e.Result.Text + &amp;#34; &amp;#34; + e.Result.Confidence + &amp;#34; &amp;#34; + DateTime.Now);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;59 sy.Speak(e.Result.Text);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;60 }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;61 }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;62 }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20170820/202656450_c0ded954-ed74-4ad3-8b18-84e33cc2e2c8.png" alt="202656450_c0ded954-ed74-4ad3-8b18-84e33cc2e2c8"&gt;
2、在线语音识别。&lt;/p&gt;</description></item><item><title>AspNet MVC路由生成URL过程</title><link>/blog/2017-03-25-the-route-generates-the-url/</link><pubDate>Sat, 25 Mar 2017 10:00:00 +0800</pubDate><guid>/blog/2017-03-25-the-route-generates-the-url/</guid><description>&lt;p&gt;这次谈一谈Asp.Net MVC中所学到的路由生成URL的相关技术，顺便提一提遇到的一些坑，真的是掉坑掉多了，也就习以为常了，大不了从坑里再爬出来。初学者，包括我，都以为，mvc的核心是模型视图控制器，其余都是辅助，但是路由却也是可以独立成一个角色的。从ASP.NET发展来，路由已从一个小小角色演变成了独立的一方了，所以这个认识必须打破原有建立的思想。​​&lt;/p&gt;
&lt;p&gt;首先得知道一个概念，路由的功能是什么？ 简单的讲就是你拿给我一个地址，我通过路由来查询一下，然后转交给你结果，路由好比一个服务区，确实如此，MVC中，当用户提交一个请求URL，MVC中的URL和其它Web中的是有点区别的，此处的统一资源定位符更多的是侧重于某个控制器类上的某个操作，和磁盘上的资源打的交道是比较少的。服务器上接收到请求URL后，交由路由系统进行处理，通过查询已经注册的路由信息，查看是否有该URL请求，如果有，则进行相应的逻辑操作处理，最终返回需要的HTML和URL，如果找不到URL，那么进入默认页面，或者是直接报错。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;具体的细节在谈一谈：路由的核心是一些非常简单的算法，基于一个由RouteCollection类和RouteBase类组成的简单抽象对象。通过使用RouteCollection.GetVirtualPath()方法来处理相关逻辑并最终生成URL，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public VirtualPathData GetVirtualPath(RequestContext requestContext,RouteValueDictionary values);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public VirtualPathData GetVirtualPath(RequestContext requestContext,string name,RouteValueDictionary values);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;路由集合通过这些方法遍历每一个路由并通过已有参数匹配是否能生成URL​，如果有路由应答了，那么它就返回一个包含了URL的VirtualPathData实例以及其它信息，没有就返回一个空值，并查询下一个路由
如果有利用第二个重载方法的，那么更加简单了，不需要一个一个匹配，直接通过name参数，该参数是路由名称的字符串，可以直接匹配路由集合中该路由名称，一句话说就是直接定位，找到了并符合就生成URL，返回空值就不再匹配其它路由了。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;URL生成详解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1.通过RouteCollection.GetVirtualPath()方法,将RequestContext对象，一个包含值的字典，以及用来选择生成URL的路由名称传入方法中。&lt;/p&gt;
&lt;p&gt;2.路由机制查看要求的路由参数，这个参数是必须要存在的参数，并确保提供的路由值字典为每一个必须存在的参数提供了一个值，否则直接返回空值。&lt;/p&gt;
&lt;p&gt;3.一些路由可能包含没有对应路由参数的默认值，如果用户传入的路由值字典中提供了这个参数，并且提供了值，那么这个字典中的值必须和默认参数中给定的值相匹配，不然就返回空值。&lt;/p&gt;
&lt;p&gt;4.然后路由系统应用路由的约束，检查是否符合。&lt;/p&gt;
&lt;p&gt;5.匹配成功，尝试利用字典中的值填充对应参数，进而生成URL​​。&lt;/p&gt;
&lt;p&gt;到这里URL就生成完成了，这个过程中要求比较多，但是跟着步骤来，应该还是容易理解的。还要提及的是一个溢出参数：这是指在URL生成过程中使用但没有在路由定义中指定的路由值，具体指的是路由的URL 默认字典，约束字典中的值，外界值不能做为溢出参数使用。溢出参数会做为查询字符串参数附加在生成的URL之后，只要路由指定的参数达到满足，是否指定额外参数则无关紧要​​&lt;/p&gt;</description></item><item><title>针对于多线程概念的理解</title><link>/blog/2017-02-28-understand-multithreading/</link><pubDate>Tue, 28 Feb 2017 10:00:00 +0800</pubDate><guid>/blog/2017-02-28-understand-multithreading/</guid><description>&lt;p&gt;&lt;strong&gt;今天是2月28日，本来的想法是每月一两篇，但是时间有点太匆忙，在这里也想向大家说一说，时间很少，愿各位好好珍惜，今天谈一谈多线程，如同其文字描述一样，线程很多，这也是多线程存在的原因，那首先什么是多线程呢，在这里，表明一下我的理解方式和我对多线程学习采用的一种套路。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先，我是一个想象流，很多事情是可以通过想象空间进行理解的，文字性的东西总归有它的约束存在，不可能有什么是可以一句话概括其所有，其实.NET 甚至是很多的编程语言，内部都是由生活的抽象得到的，有时候，很多没有理解的技术点，或是没有遇到的，没有经历过的解决一件事情的办法，在程序中也可以得到认识，犹如读了很多感悟生活，感悟人生的书，只不过我们的许多见解是在程序中得到的。&lt;/p&gt;
&lt;p&gt;现在，谈一谈，多线程的想象空间，最初学习时，看到多线程的概念，和各种实现多线程的方法，我是拒绝的，感觉太难了，完全没有逻辑性可言，但是当我用想象空间进行学习时，压力几乎消失了，比如说一个进程中开了四个线程，其中一个主线程，三个子线程，可以好比如说四座相互平行的桥，桥下是水，其中主线程是作为主桥，来往的车辆最多，而其它三个副桥暂时封闭，当需要运行多个任务并且希望时间又不浪费，那么三个副桥就开通了，四桥同时通车，多线程的概念也就结束了，什么？ 这么简单，  对 就是这么简单，毕竟那是我们生活的抽象。&lt;/p&gt;
&lt;p&gt;而多线程还是没给一个定义呢，之前已经说了，文字性的东西不足以描述一个事情，我们只需要知道它的想象即可，桥与桥之间是相互独立的，桥上的资源是相互独立的，并且都是一样的，也就是在磁盘中，克隆出相同的资源，但是代码是只有一份的，运行的时候我们可以重复使用。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;说说.NET中多线程的实现方式：&lt;/p&gt;
&lt;p&gt;首先需要知道，多线程总共有的几种名词：Thread ThreadPool Task Parallel  await/async 就这么几种，并且由于前两种的使用是几年前的定义其中用的委托还不是.NET4.0定义的Action和Func这种委托，因此使用的一般是以前的应用程序，但是不是说他们已经淘汰了，只不过后面有更方面的使用形式，前期的只是失去了光辉，其中await/async理解上比较复杂，实际掌握的人还是不多，使用比较多的还是Task和Parallel,我比较喜欢使用Parallel，简单粗暴，并且主线程也在执行中，没有太大的浪费，顺便提一句，Task Parallel 都是基于线程池才有的。线程想要通过程序调试去理解，是很难的，记得听到一个朋友说，她想调试下线程，然后理解它，但是每一次都是不一样的结果，其实这也反应了一个问题，线程的不可预测性，并且当线程数量很多时，线程的启动和结束都是无序的，线程的执行能力也是不一样的，想要通过调试理解，由此也是很困难的​​&lt;/p&gt;
&lt;p&gt;再次提到想象空间，通过它，理解多线程来说不是什么难事，如前面的例子，四座副桥上尽管都是在跑着汽车，但是呢谁能保证哪座桥汽车最先没有呢，结果的不可预测性，在现实生活中也是的，假设你只能看到四座桥的风景，车来车往是否结束都不知道，能否保证那座桥上的汽车已经跑完了呢，答案也是无法确定。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;以上所说了这么多文字，也套用了我说的里面那句话，文字不足矣描述需要的信息，但是我想表达的想象空间，希望能帮助碰上瓶颈的朋友们过了多线程这道坎，并且由于每个人对多线程想象空间的建模不一样，这是我碰到过的，有人理解多线程为管道，有人理解为马路，都是不错的。并且，也许在一部分人中，不需要这样的想象空间，那也可以帮助下需要的人讲解下。&lt;/p&gt;
&lt;p&gt;好了到此结束了，时间很紧迫，估计下一次也不知道什么时候才有时间写下一篇自己的见解。珍惜时间吧，Everyone！&lt;/p&gt;</description></item><item><title>谈谈个人代码对团队合作影响</title><link>/blog/2017-01-01-the-impact-of-personal-codes-on-teamwork/</link><pubDate>Sun, 01 Jan 2017 10:00:00 +0800</pubDate><guid>/blog/2017-01-01-the-impact-of-personal-codes-on-teamwork/</guid><description>&lt;p&gt;&lt;strong&gt;这几天正接手一个项目，属于后期功能拓展，要拓展这个项目，一定程度上要看到源码的部分，然后烦心的事情就来了，这代码写的真是让人挺无语的，原先写完整个项目的是已有多年工作经验的开发者，但是整个代码下来，逻辑性不强，单看完整个流程，各个模块的作用，就花了一两天的时间，这还没涉及到代码部分，缺乏相应的注释。这也给了我很深的印象和感悟，不管是个人写代码，还是团队合作，对于框架要明确，有结构图，有相应的注释，命名规范相当重要，尽管项目不大，也要逻辑分明，这是对于日后拓展和维护都是有好处的，所谓在实战中学习，真的是学到了好多，写自己的代码改动日志，将任务，思路，想法，原先基础上改动地方全部记录下来，这对于自己来说真的是很有用的事情，或许写着日志的时候，项目中不足的地方也会一并解决。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;具体谈谈这次项目中看到的哪些不足之处：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;     首先，在整个框架设计上，就完全是依据要有功能就实现功能的思路来，结果如何呢，造成了很多代码上的冗余，比如说，在访问数据库上，由于这是一个WPF的窗体应用程序，在窗体后台，直接用上了访问数据库的语句，不是单单的SQL语句，而是实在的数据库访问，连接数据库，CRUD操作，一并封装到了所要的功能中，这里造成了代码的太多冗余，从框架的角度考虑，本应该是SQLHelper的活，却被一个人承包了，UI,BLL,DAL,SQLHelper全部集中在一个里面，真的是不敢想象。代码看完，就在那想，以后的自己真的是不要这样，毕竟现在就有了仇恨，哈哈。&lt;/p&gt;
&lt;p&gt;    其次，代码部分的注释是相当少，没有明确的解释，就连命名上也是大打折扣，使用中文拼英的缩写作为部分命名，看着这些东西，唉，心累，代码部分也只是看了我要拓展部分的相关代码，既然出自同一人之手，并且我看的还是整个项目的主干线，想也知道，其它部分的风格应该如出一辙，不管如何，这一次的拓展工程，真的是学到了太多太多，团队合作时，需提前进行规范，框架规范，命名规范，这些真的是很重要，想要一个可以日后维护，拓展轻松的项目，那么开始制定计划的时候就要提前规划好需要的东西，宁可费点时间，也是值得的。&lt;/p&gt;
&lt;p&gt;    最后，最想吐槽的一部分就是在于我的拓展需要用到已经搭建好的数据库，但是当我编写访问数据库的方法时，竟然发现，连数据库名字都是中文的，这就尴尬了，一个下午找着解决方案，我天呐，最后的办法居然还是利用原先项目中已经存在的连接数据库语句搞定的，代码也没深究了。&lt;/p&gt;</description></item><item><title>对于ASPNET MVC中页面强类型的个人理解</title><link>/blog/2016-11-13-understand-page-strong-typing/</link><pubDate>Sun, 13 Nov 2016 10:00:00 +0800</pubDate><guid>/blog/2016-11-13-understand-page-strong-typing/</guid><description>&lt;p&gt;进入ASP.NET MVC学习 发现很多和winform不同的东西，但是利用的C#语言还是没有变化，更多的是利用了新的语言，html jquery ajax&amp;hellip;..唉 心累，一本书一本书看的去 看完了 回想起来的时候是很快乐的，在此说一说学习之中遇到的比较深的坎:&lt;/p&gt;
&lt;p&gt; 在对于页面强类型学习的时候，刚开始感觉还是很轻松的，不就是直接在Controller中用一次viewdata[&amp;quot;***&amp;quot;]=value并且在View中通过@model **** 就可以了嘛，又或是直接利用ViewData.Model做更简单的事，还有viewbag,tempdata等就不说了，&lt;/p&gt;
&lt;p&gt;但是呢，自从遇到一次坎后才知道，并不是那么简单，​比如说viewdata使用的基本原理是利用键值对形式，但是呢这个object对应的保存的数据到底是什么类型的呢，是用object抽象形式保存还是用原生的形式呢，比如说我有一个ProductCategory的类，并且进行var pro = new ProductCategory(***)创建一些信息，在通过ViewData[&amp;ldquo;objPC&amp;rdquo;]=pro;那么这个pro保存在objPC保存的形式类型是哪种呢？object or  ProductCategory？&lt;/p&gt;
&lt;p&gt;如果是说保存为object那么在view里写入@model ProductCategory 也就说的通顺了，再次进行强制转换嘛。实际上呢，不引用这那句话，在view里直接写Model并且通过点的形式调用其成员，也是行得通的，只不过Model去调用的话必须要保证那个属性或是方法一定存在那个值的类型之中，也就证明了一点，value的值传给viewdata保存时并不是用object形式进行保存的，而是用它的原生形式，&lt;/p&gt;
&lt;p&gt;@model ProductCategory的引入只是为了在View中强制这个传入的值的类型，并且为了在编写代码的时候出现的错误少点，而@using ***更是和C#中的使用是一样的，毕竟@下就是c#代码了。​&lt;/p&gt;
&lt;p&gt;最后在说下在linq查询的条件下，通过controller传递过来的并不是确定的类型，什么意思呢，传过来的其实还是查询语句，并且此时查看Model的值时，也是显示一个查询语句，这归因于是使用延时加载的原因，当真正需要的时候再去查询，并且查询出来的类型大多数是利用的IQueryable&amp;lt;&amp;gt;​类型,因此在View中提前指定好强类型页面也是可以的，毕竟知道了这个传过来的集合的类型是已经确定的，怎么不利用它里面的属性做更多的事情呢。&lt;/p&gt;
&lt;p&gt;ASP.NET MVC第一次讲解在这里先告一段落，毕竟我也是刚刚学习不长时间的入门者，但是这种MVC的思想值得我们在生活中去借鉴,Separation of Concerns.​&lt;/p&gt;</description></item><item><title>对于序列化的相关学习与理解</title><link>/blog/2016-10-13-understand-serialization/</link><pubDate>Thu, 13 Oct 2016 10:00:00 +0800</pubDate><guid>/blog/2016-10-13-understand-serialization/</guid><description>&lt;p&gt;刚开始接触.NET的序列化时，完全不知道是什么东西，一脸茫然，在学习中，看到有人直接加一个[Serialization],然后说这是序列化的表示，顿时震惊了，这是什么？后来在图灵丛书的精通C#书中学习到了这是C#的一种特性，其目的是将对象持久化为二进制格式。比如以接下来这个案例作为学习：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 系统配置实体类&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Serializable]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SysConfig&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; COMno { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; VolValue { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; CurrentValue { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; EquipmentId { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;​此处在整个类的前面加上了[Serialization]代表着将整个类可以进行序列化成二进制格式​​如果不想对其中的某一属性或方法序列化，可以在该属性或方法前加上[nonSerialized]标识，这样一来只有该被指定的不会被序列化了，我们设置了四个属性，分别代表了端口号，电流值，电压值，及设备号。这是在工控开发中较为常见的配置。
在窗口相关代码下看其操作方式：&lt;img src="https://raw.githubusercontent.com/ViatorLife/document-img/main/img/20161013/203058591_a4f44bd0-88f8-4f19-92a6-b99ab91dae0f.png" alt="203058591_a4f44bd0-88f8-4f19-92a6-b99ab91dae0f"&gt;&lt;/p&gt;
&lt;p&gt;目的是很明确的，将信息持久化的保存在文件中，对于两种形式的保存方式也将体现出序列化的与众不同。​&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FrmConfig&lt;/span&gt; : Form
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SysConfig objSysConfig = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; FrmConfig()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; InitializeComponent();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 文本方式保存信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; btnTextToSave_Click(&lt;span style="color:#66d9ef"&gt;object&lt;/span&gt; sender, EventArgs e)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objSysConfig = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SysConfig()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; COMno = Convert.ToInt32(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCOMno.Text.Trim()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CurrentValue = Convert.ToDouble(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCurrent.Text.Trim()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EquipmentId = Convert.ToInt32(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtEquipmentId.Text.Trim()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VolValue = Convert.ToDouble(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtVol.Text.Trim())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【1】创建文件流对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileStream fs = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FileStream(&lt;span style="color:#e6db74"&gt;&amp;#34;SysConfigText.obj&amp;#34;&lt;/span&gt;, FileMode.Create);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【2】创建读写器(写)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StreamWriter sw = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamWriter(fs);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【3】写入文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sw.WriteLine(objSysConfig.COMno);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sw.WriteLine(objSysConfig.CurrentValue);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sw.WriteLine(objSysConfig.EquipmentId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sw.WriteLine(objSysConfig.VolValue);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【4】关闭读写器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sw.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【5】关闭文件流对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fs.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 文本方式读取信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; btnTextToRead_Click(&lt;span style="color:#66d9ef"&gt;object&lt;/span&gt; sender, EventArgs e)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【1】创建文件流对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileStream fs = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FileStream(&lt;span style="color:#e6db74"&gt;&amp;#34;SysConfigText.obj&amp;#34;&lt;/span&gt;, FileMode.Open);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【2】创建读写器(读)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StreamReader sr = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamReader(fs);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【3】读取文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objSysConfig = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SysConfig()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; COMno =Convert.ToInt32(sr.ReadLine()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CurrentValue =Convert.ToDouble(sr.ReadLine()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EquipmentId =Convert.ToInt32(sr.ReadLine()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VolValue =Convert.ToDouble(sr.ReadLine())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【4】关闭读写器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sr.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【5】关闭文件流对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fs.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCOMno.Text = objSysConfig.COMno.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtVol.Text = objSysConfig.VolValue.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtEquipmentId.Text = objSysConfig.EquipmentId.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCurrent.Text = objSysConfig.CurrentValue.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 序列化方式保存信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; btnSeriaToSave_Click(&lt;span style="color:#66d9ef"&gt;object&lt;/span&gt; sender, EventArgs e)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objSysConfig = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SysConfig()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; COMno = Convert.ToInt32(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCOMno.Text.Trim()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CurrentValue = Convert.ToDouble(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCurrent.Text.Trim()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EquipmentId = Convert.ToInt32(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtEquipmentId.Text.Trim()),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VolValue = Convert.ToDouble(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtVol.Text.Trim())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【1】创建文件流对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileStream fs = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FileStream(&lt;span style="color:#e6db74"&gt;&amp;#34;SysConfigSeria.obj&amp;#34;&lt;/span&gt;, FileMode.Create);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【2】创建二进制格式化器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BinaryFormatter bf = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BinaryFormatter();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【3】调用序列化方法，信息序列化&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bf.Serialize(fs, objSysConfig);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【4】关闭文件流&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fs.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 序列化方式读取信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; btnDeseriaToRead_Click(&lt;span style="color:#66d9ef"&gt;object&lt;/span&gt; sender, EventArgs e)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【1】创建文件流对象&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FileStream fs = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FileStream(&lt;span style="color:#e6db74"&gt;&amp;#34;SysConfigSeria.obj&amp;#34;&lt;/span&gt;, FileMode.Open);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【2】创建二进制格式化器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BinaryFormatter bf = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BinaryFormatter();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【3】调用反序列化方法，信息反序列化&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objSysConfig = (SysConfig)bf.Deserialize(fs);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【4】关闭文件流&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fs.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCOMno.Text = objSysConfig.COMno.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtVol.Text = objSysConfig.VolValue.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtEquipmentId.Text = objSysConfig.EquipmentId.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.txtCurrent.Text = objSysConfig.CurrentValue.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对于序列化方式中，binaryFormatter这个类是最关键的，其中的Serialize()和Deserialize()方法是序列化学习的关键所在，Serialize()方法通过给它一个文件流参数，相当于给定了笔和纸，再来一个参数是要序列化的对象，相当于要书写的信息，而反序列化就是将写好的作品在通过笔和纸在写回来而这次是看着那些写好的信息在写一份信息出来并且用一个容器保存这些信息，总而言之，言而总之，序列化可以想象成将一个对象转换成二进制形式保存，然后在将这些二进制对象封装，又可以变回一个对象的过程。
这比文本文件的好处在于，在文件中序列化是以二进制方式保存，十分难看懂，而文本文件是以明文形式保存，并且在处理过程上，文本文件方式更见繁琐，但是不是说文本文件毫无用处，比如说写日志文件，你用序列化它那对自己直接面对日志文件而言，毫无疑问是一种相当愚蠢的方式，说的虽然严重了，但是主要是想表达我们不是说为了序列化而序列化文件，一种刻意为之的方式去使用。而是说当我们需要使用的时候，就用，不要强求，一般在项目开发中，针对于实体类而言，我们通常是先加上[Serialization]的，以便更好的应对项目的需求。&lt;/p&gt;</description></item><item><title>反射在AdoNet方面的应用</title><link>/blog/2016-10-01-reflection-in-adonet/</link><pubDate>Sat, 01 Oct 2016 10:00:00 +0800</pubDate><guid>/blog/2016-10-01-reflection-in-adonet/</guid><description>&lt;p&gt;本来说通过传统的方式可以很方便的访问数据库不需要用到反射技术，但是为了将反射在ADO.NET中的作用体现出来，特意来试一下改良版的访问方式。&lt;/p&gt;
&lt;p&gt;​反射的学习需要一定的时间去理解，我学了有一阵子了，但也不敢保证是将反射学懂了，学无止境，反射的本身也体现了微软的才华。&lt;/p&gt;
&lt;p&gt;反射的命名空间是System.Reflectional;项目用到反射之前需要引入，这不像它本身的功能，哈哈，可以动态的加载程序集，而不需要引入的方式，反射尤其名字联想到物理学中的镜面反射，也可以懂其大概意思，就是说通过给你一个程序集的完整路径名，你可以反射出一整套该程序集的信息，这个完整路径名，在很多时候都是通过写在配置文件中的，通过这样也得以将程序实现可配置，通过改变不同的程序集名字，又能实现可扩展性。&lt;/p&gt;
&lt;p&gt;废话不多说，现在来看看反射是怎么改良ADO.NET的。&lt;/p&gt;
&lt;p&gt;这是一种简单的方式访问数据库：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; SysAdmins AdminLogin(SysAdmins objAdmin)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//【1】定义SQL语句&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; sql = &lt;span style="color:#e6db74"&gt;&amp;#34;select AdminName,StatusId,RoleId from SysAdmins where AdminId=@AdminId and LoginPwd=@LoginPwd&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//【2】封装参数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    SqlParameter[] param = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SqlParameter[]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SqlParameter(&lt;span style="color:#e6db74"&gt;&amp;#34;@AdminId&amp;#34;&lt;/span&gt;,objAdmin.AdminId),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SqlParameter(&lt;span style="color:#e6db74"&gt;&amp;#34;@LoginPwd&amp;#34;&lt;/span&gt;,objAdmin.LoginPwd)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//【3】执行查询&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    SqlDataReader objReader = SQLHelper.GetReader(sql, param);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//【4】处理结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (objReader.Read())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        objAdmin.AdminName = objReader[&lt;span style="color:#e6db74"&gt;&amp;#34;AdminName&amp;#34;&lt;/span&gt;].ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        objAdmin.StatusId = Convert.ToInt32(objReader[&lt;span style="color:#e6db74"&gt;&amp;#34;StatusId&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        objAdmin.RoleId = Convert.ToInt32(objReader[&lt;span style="color:#e6db74"&gt;&amp;#34;RoleId&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;        objAdmin = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    objReader.Close();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#75715e"&gt;//【5】返回查询结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;    &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; objAdmin;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过将表示层的信息进行封装，经过业务逻辑层，再到数据访问层，将信息解包，提取有用信息，并整合到SQL语句中，通过ADO.NET技术访问数据库，数据库内进行一系列项目需要的操作，得到想要的返回结果，并且又封装到原来的包中，整个过程的该部分已经结束，自动忽略一部分细节，毕竟我们主要讲的是该部分。
而后我们通过反射来实现一下：​&lt;/p&gt;</description></item><item><title>委托在窗体通信方面的见解</title><link>/blog/2016-09-06-understand-delegation/</link><pubDate>Tue, 06 Sep 2016 10:00:00 +0800</pubDate><guid>/blog/2016-09-06-understand-delegation/</guid><description>&lt;p&gt;首次博客内容是将自己对于委托在窗体通信方面的应用做一次总结，也算是将自己的见解用言语来表达出来吧。在开始接触委托时云里雾里，完全没有这个概念的存在，更不用提委托用于窗体通信之中这么高深的运用了尽管在现在看来我还是觉得委托用在窗体通信中是一件很神奇的事情，不是说没有弄懂，而是说这里面的逻辑思维也值得我们去学习(思维是个关键的东西，一旦理解了，也会顺水推舟)。&lt;/p&gt;
&lt;p&gt;首先委托的格式想必是一目了然 &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;public delegate void MessageDele(string Message);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;委托的五大步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;声明委托&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;根据委托创建具体方法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建委托变量&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;委托变量关联具体方法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用委托​&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然而在此处如果我们将窗体通信想象成&lt;strong&gt;主窗体和从窗体通信&lt;/strong&gt;的话，那么必然存在一个问题，委托变量的放置问题。（本次只考虑主从窗体通信这种特殊情形，其余情形照搬该逻辑思路即可了）。例如，如果是&lt;strong&gt;主窗体向从窗体发送信息&lt;/strong&gt;，是在&lt;strong&gt;主窗体里面定义委托变量&lt;/strong&gt;并且绑定相关从窗体的方法，还是&lt;strong&gt;从窗体内定义委托变量，&lt;strong&gt;并且将委托变量与自身的相关方法进行绑定（该方法好比说是将主窗体传过来的信息展现出来），然后在主窗体中创建从窗体对象后，调用从窗体对象的委托变量用来发送信息呢。又或是在&lt;/strong&gt;从窗体向主窗体​发送信息&lt;/strong&gt;情况下，委托变量是跟随谁的问题，&lt;strong&gt;一种&lt;/strong&gt;是将委托变量定义在从窗体中，在主窗体中创建从窗体对象，然后通过从窗体对象去调用内部成员委托变量，并且绑定主窗体中的相关方法，&lt;strong&gt;另一种&lt;/strong&gt;情况是主窗体中定义委托变量，并且绑定相关方法，再主窗体中创建从窗体对象，通过从窗体去调用主窗体中的委托（然而基本上这种方式完全不太推荐使用，不是说不可以用而是实现的步骤太过于复杂，付出的成本大于了其收益)。&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h3 id="主窗体向从窗体发送信息"&gt;&lt;strong&gt;主窗体向从窗体发送信息​&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;主窗体中定义委托变量：&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;partial&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FrmMain&lt;/span&gt; : Form
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//​【3】创建委托变量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; MessageDele objMessageDele;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; FrmMain()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; InitializeComponent();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FrmOther objFrmOther = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FrmOther();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objFrmOther.Show();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【4】委托变量绑定方法​&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objMessageDele += objFrmOther.Message;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//【5】调用​委托变量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; btnclick_Click(&lt;span style="color:#66d9ef"&gt;object&lt;/span&gt; sender, EventArgs e)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; objMessageDele(txt1.Text.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//【1】声明委托（定义一个函数原型：返回值+参数类型+个数）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;delegate&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ShowCount(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; message);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;从窗体提供委托实现方法：&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>