<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Jonty&#39;s Blog</title>
  
  <subtitle>记录搬砖生活</subtitle>
  <link href="https://jonty.top/atom.xml" rel="self"/>
  
  <link href="https://jonty.top/"/>
  <updated>2025-05-20T03:43:37.319Z</updated>
  <id>https://jonty.top/</id>
  
  <author>
    <name>JontyWang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>备份维护计划突然失效？一个 SQL Server 主机名引发的大坑</title>
    <link href="https://jonty.top/2025/05/20/fn-hadr-backup-is-preferred-replica-always-returning-zero/"/>
    <id>https://jonty.top/2025/05/20/fn-hadr-backup-is-preferred-replica-always-returning-zero/</id>
    <published>2025-05-20T03:40:36.000Z</published>
    <updated>2025-05-20T03:43:37.319Z</updated>
    
    <content type="html"><![CDATA[<p>在配置 SQL Server AlwaysOn 高可用性组的维护计划时，我们通常会使用如下 T-SQL 逻辑判断当前实例是否是“首选备份副本”：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">IF (<span class="keyword">SELECT</span> master.sys.fn_hadr_backup_is_preferred_replica(<span class="string">&#x27;MyDbName&#x27;</span>)) <span class="operator">=</span> <span class="number">1</span></span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line">    BACKUP LOG [MyDbName] <span class="keyword">TO</span> DISK <span class="operator">=</span> <span class="string">&#x27;D:\Backup\MyDbName.trn&#x27;</span></span><br><span class="line"><span class="keyword">END</span></span><br></pre></td></tr></table></figure><p>理论上，如果当前节点是备份首选副本（Preferred Replica），该函数会返回 <code>1</code>，否则返回 <code>0</code>。但我在某项目中遇到一个非常诡异的现象：<strong>始终返回 0，即使当前节点确实是主副本</strong>，导致维护计划中的备份逻辑无法触发。</p><hr><h2 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h2><p>在排查过程中，我尝试以下方法：</p><ul><li>用 <code>QUOTENAME()</code> 包裹数据库名，例如：<code>SELECT fn_hadr_backup_is_preferred_replica(&#39;[MyDbName]&#39;)</code>，结果返回 1；</li><li>用普通字符串 <code>SELECT fn_hadr_backup_is_preferred_replica(&#39;MyDbName&#39;)</code>，始终返回 0；</li><li>数据库确实位于主副本，AG 配置无误。</li></ul><p>最终，我通过查询 <code>@@SERVERNAME</code> 发现，<strong>SQL Server 内部注册的服务器名与实际主机名不一致！</strong></p><p>这是关键所在。</p><hr><h2 id="原因根源：SQL-Server-安装时记录的本地服务器名未更新"><a href="#原因根源：SQL-Server-安装时记录的本地服务器名未更新" class="headerlink" title="原因根源：SQL Server 安装时记录的本地服务器名未更新"></a>原因根源：SQL Server 安装时记录的本地服务器名未更新</h2><p>SQL Server 在安装时会把当时的主机名注册进系统目录 <code>sys.servers</code> 中，并在内部函数中用于判断“本地实例”是否等于主副本。</p><p>但如果你之后<strong>更改了主机名</strong>，比如从 <code>OLD-SERVER</code> 改为 <code>NEW-SERVER</code>，系统主机名虽然变了，<strong>但 SQL Server 并不会自动更新它的注册信息</strong>。</p><p>你可以用以下 SQL 验证这个不一致：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    <span class="string">&#x27;系统主机名（实际）&#x27;</span> <span class="operator">=</span> <span class="built_in">CAST</span>(SERVERPROPERTY(<span class="string">&#x27;ComputerNamePhysicalNetBIOS&#x27;</span>) <span class="keyword">AS</span> nvarchar),</span><br><span class="line">    <span class="string">&#x27;SQL Server 注册名（@@SERVERNAME）&#x27;</span> <span class="operator">=</span> @<span class="variable">@SERVERNAME</span>;</span><br></pre></td></tr></table></figure><hr><h2 id="解决方法：重新注册本地服务器名"><a href="#解决方法：重新注册本地服务器名" class="headerlink" title="解决方法：重新注册本地服务器名"></a>解决方法：重新注册本地服务器名</h2><p>只需执行以下三步：</p><h3 id="第一步：删除旧的注册名"><a href="#第一步：删除旧的注册名" class="headerlink" title="第一步：删除旧的注册名"></a>第一步：删除旧的注册名</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">EXEC</span> sp_dropserver <span class="string">&#x27;旧服务器名&#x27;</span>;</span><br></pre></td></tr></table></figure><p>其中 <code>&#39;旧服务器名&#39;</code> 替换为你当前 <code>@@SERVERNAME</code> 的返回值。</p><h3 id="第二步：添加新的服务器名"><a href="#第二步：添加新的服务器名" class="headerlink" title="第二步：添加新的服务器名"></a>第二步：添加新的服务器名</h3><p>如果你是默认实例：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">EXEC</span> sp_addserver <span class="string">&#x27;新主机名&#x27;</span>, <span class="string">&#x27;local&#x27;</span>;</span><br></pre></td></tr></table></figure><p>如果你是命名实例（如 <code>NEW-SERVER\SQL2022</code>）：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">EXEC</span> sp_addserver <span class="string">&#x27;新主机名\实例名&#x27;</span>, <span class="string">&#x27;local&#x27;</span>;</span><br></pre></td></tr></table></figure><p>可通过如下方式获取当前信息：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">    <span class="built_in">CAST</span>(SERVERPROPERTY(<span class="string">&#x27;ComputerNamePhysicalNetBIOS&#x27;</span>) <span class="keyword">AS</span> nvarchar) <span class="keyword">AS</span> HostName,</span><br><span class="line">    <span class="built_in">CAST</span>(SERVERPROPERTY(<span class="string">&#x27;InstanceName&#x27;</span>) <span class="keyword">AS</span> nvarchar) <span class="keyword">AS</span> InstanceName;</span><br></pre></td></tr></table></figure><h3 id="第三步：重启-SQL-Server-服务"><a href="#第三步：重启-SQL-Server-服务" class="headerlink" title="第三步：重启 SQL Server 服务"></a>第三步：重启 SQL Server 服务</h3><p>只有重启 SQL Server 实例后，这些更改才会生效。可以通过服务管理器或以下 PowerShell 命令执行：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Restart-Service</span> <span class="literal">-Name</span> <span class="string">&#x27;MSSQLSERVER&#x27;</span>  <span class="comment"># 默认实例</span></span><br><span class="line"><span class="built_in">Restart-Service</span> <span class="literal">-Name</span> <span class="string">&#x27;MSSQL$SQL2022&#x27;</span>  <span class="comment"># 命名实例</span></span><br></pre></td></tr></table></figure><hr><h2 id="验证结果"><a href="#验证结果" class="headerlink" title="验证结果"></a>验证结果</h2><p>重新启动后，再次执行：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> @<span class="variable">@SERVERNAME</span>;</span><br><span class="line"><span class="keyword">SELECT</span> master.sys.fn_hadr_backup_is_preferred_replica(<span class="string">&#x27;YourDbName&#x27;</span>);</span><br></pre></td></tr></table></figure><p>应该可以看到 <code>@@SERVERNAME</code> 与主机名一致，同时 <code>fn_hadr_backup_is_preferred_replica()</code> 正确返回 1。</p><hr><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><blockquote><p>✅ 如果你在使用 <code>fn_hadr_backup_is_preferred_replica</code> 时发现它始终返回 0，而当前实例确实是主副本，很可能是由于主机名变更后没有同步 SQL Server 内部注册信息。</p></blockquote><p>通过执行 <code>sp_dropserver</code> + <code>sp_addserver</code> + 重启 SQL Server，就可以修复这个问题，确保 AlwaysOn 的备份判断逻辑恢复正常。</p>]]></content>
    
    
    <summary type="html">SQL Server AlwaysOn 中 fn_hadr_backup_is_preferred_replica 始终返回 0 的原因及解决方案</summary>
    
    
    
    <category term="DBA" scheme="https://jonty.top/categories/DBA/"/>
    
    
    <category term="SQL Server" scheme="https://jonty.top/tags/SQL-Server/"/>
    
    <category term="AlwaysOn" scheme="https://jonty.top/tags/AlwaysOn/"/>
    
    <category term="HA高可用" scheme="https://jonty.top/tags/HA%E9%AB%98%E5%8F%AF%E7%94%A8/"/>
    
  </entry>
  
  <entry>
    <title>Docker Swarm Nginx 获取客户端RealIP</title>
    <link href="https://jonty.top/2025/01/06/docker-swarm-nginx-real-ip/"/>
    <id>https://jonty.top/2025/01/06/docker-swarm-nginx-real-ip/</id>
    <published>2025-01-06T03:22:59.000Z</published>
    <updated>2025-01-06T03:49:19.328Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 Docker Swarm 部署应用时，通常会使用 <strong>Overlay 网络</strong> 来实现跨主机的容器通信。然而，这种网络模式下，Nginx 作为反向代理时，只能获取到来自内部网络的 IP 地址（如 <code>10.0.0.2</code>），而无法获取到客户端的真实公网 IP。</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">10.0.0.2 - - [06/Jan/2025:11:30:56 +0800] &quot;GET /health-status HTTP/1.1&quot; 200 196 &quot;-&quot; &quot;-&quot;</span><br><span class="line">10.0.0.2 - - [06/Jan/2025:11:31:06 +0800] &quot;GET /health-status HTTP/1.1&quot; 200 196 &quot;-&quot; &quot;-&quot;</span><br><span class="line">10.0.0.2 - - [06/Jan/2025:11:31:16 +0800] &quot;GET /health-status HTTP/1.1&quot; 200 197 &quot;-&quot; &quot;-&quot;</span><br><span class="line">10.0.0.2 - - [06/Jan/2025:11:31:26 +0800] &quot;GET /health-status HTTP/1.1&quot; 200 196 &quot;-&quot; &quot;-&quot;</span><br></pre></td></tr></table></figure><p>要解决这一问题，核心思路是 <strong>绕过 Docker Swarm 的 Overlay 网络 NAT 转换</strong>，使 Nginx 能够直接获取到客户端的真实 IP。具体方法是将 Nginx 服务配置为 <strong>host 模式</strong>，即将宿主机的网络栈直接分配给容器。Nginx 容器直接监听宿主机的网络接口，能够接收到来自客户端的真实 IP。</p><h2 id="Nginx"><a href="#Nginx" class="headerlink" title="Nginx"></a>Nginx</h2><p>修改<code>docker-compose.yml</code></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">proxy:</span></span><br><span class="line">  <span class="attr">image:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">target:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">published:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">tcp</span></span><br><span class="line">      <span class="attr">mode:</span> <span class="string">host</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">target:</span> <span class="number">443</span></span><br><span class="line">      <span class="attr">published:</span> <span class="number">443</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">tcp</span></span><br><span class="line">      <span class="attr">mode:</span> <span class="string">host</span></span><br></pre></td></tr></table></figure><p>设置<code>proxy-set-header</code>：</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for<span class="comment">;</span></span><br><span class="line">proxy_set_header X-Forwarded-Proto $scheme<span class="comment">;</span></span><br><span class="line">proxy_set_header X-Real-IP $remote_addr<span class="comment">;</span></span><br></pre></td></tr></table></figure><h2 id="应用配置"><a href="#应用配置" class="headerlink" title="应用配置"></a>应用配置</h2><p>在 ASP.NET Core 应用中，需要配置 <strong>Forwarded Headers</strong> 中间件，以解析并使用 <code>X-Forwarded-For</code> 头部中的真实 IP。</p><p>在 <code>Program.cs</code> 中，添加以下配置：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 配置 Forwarded Headers</span></span><br><span class="line">builder.Services.Configure&lt;ForwardedHeadersOptions&gt;(options =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 设置信任的代理 IP 范围</span></span><br><span class="line">    options.KnownNetworks.Clear(); <span class="comment">// 清除默认配置</span></span><br><span class="line">    options.KnownProxies.Clear();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加 Nginx 所在的 Overlay 网络 IP 范围</span></span><br><span class="line">    <span class="comment">// options.KnownNetworks.Add(new IPNetwork(System.Net.IPAddress.Parse(&quot;10.0.0.0&quot;), 8));</span></span><br><span class="line">    <span class="comment">// options.KnownNetworks.Add(new IPNetwork(System.Net.IPAddress.Parse(&quot;172.16.0.0&quot;), 12));</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 Forwarded Headers 中间件，必须放在最前面</span></span><br><span class="line">app.UseForwardedHeaders();</span><br><span class="line"><span class="comment">// 其他中间件</span></span><br><span class="line">app.UseRouting();</span><br><span class="line">app.UseAuthentication();</span><br><span class="line">app.UseAuthorization();</span><br><span class="line"></span><br><span class="line">app.MapControllers();</span><br><span class="line"></span><br><span class="line">app.Run();</span><br></pre></td></tr></table></figure><p><code>ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto</code>: 配置应用解析 <code>X-Forwarded-For</code> 和 <code>X-Forwarded-Proto</code> 头部。</p><p><code>KnownNetworks</code> 和 <code>KnownProxies</code>: 明确指定信任的网络和代理，确保只有来自这些范围的头部信息会被解析</p><h3 id="检查Nginx-日志"><a href="#检查Nginx-日志" class="headerlink" title="检查Nginx 日志"></a>检查Nginx 日志</h3><p>其中<code>192.168.1.12</code>为客户端IP</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.12 - - [06/Jan/2025:11:20:33 +0800]</span><br><span class="line">192.168.1.12 - - [06/Jan/2025:11:20:33 +0800]</span><br><span class="line">192.168.1.12 - - [06/Jan/2025:11:20:33 +0800]</span><br></pre></td></tr></table></figure><p><img data-src="https://cdn.jonty.top/img/202501061146172.png" alt="image-20250106114630079"></p><h3 id="验证应用端获取的-IP"><a href="#验证应用端获取的-IP" class="headerlink" title="验证应用端获取的 IP"></a>验证应用端获取的 IP</h3><p>通过控制器或日志记录查看 <code>HttpContext.Connection.RemoteIpAddress</code> 是否为真实客户端 IP</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">HttpGet(<span class="meta-string">&quot;client-ip&quot;</span>)</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> IActionResult <span class="title">GetClientIp</span>(<span class="params"></span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> forwardedFor = HttpContext.Request.Headers[<span class="string">&quot;X-Forwarded-For&quot;</span>];</span><br><span class="line">    <span class="keyword">var</span> clientIp = HttpContext.Connection.RemoteIpAddress?.ToString();</span><br><span class="line">    <span class="keyword">return</span> Ok(<span class="keyword">new</span> &#123; ClientIp = clientIp, forwardedFor= forwardedFor &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>访问<code>httP://yourip:port/client-ip</code></p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;clientIp&quot;: &quot;192.168.1.12&quot;,</span><br><span class="line">  &quot;forwardedFor&quot;: []</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">使用Docker Swarm Nginx 部署.NET应用如何获取客户端RealIP</summary>
    
    
    
    <category term="DevOps" scheme="https://jonty.top/categories/DevOps/"/>
    
    
    <category term="Abp" scheme="https://jonty.top/tags/Abp/"/>
    
    <category term="Docker" scheme="https://jonty.top/tags/Docker/"/>
    
    <category term="Docker Swarm" scheme="https://jonty.top/tags/Docker-Swarm/"/>
    
    <category term="Nginx" scheme="https://jonty.top/tags/Nginx/"/>
    
  </entry>
  
  <entry>
    <title>.NET 应用打包Debian包</title>
    <link href="https://jonty.top/2024/12/29/dotnet-build-debian-package/"/>
    <id>https://jonty.top/2024/12/29/dotnet-build-debian-package/</id>
    <published>2024-12-29T01:32:03.000Z</published>
    <updated>2024-12-30T01:35:35.393Z</updated>
    
    <content type="html"><![CDATA[<h3 id="代码发布（编译为自包含的可执行文件，ARM64）"><a href="#代码发布（编译为自包含的可执行文件，ARM64）" class="headerlink" title="代码发布（编译为自包含的可执行文件，ARM64）"></a>代码发布（编译为自包含的可执行文件，ARM64）</h3><p>项目文件是 <strong>myproject.FaceCollect.csproj</strong>，发布后生成的可执行文件名称为 <strong>facecollect</strong>。在项目根目录执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">dotnet publish myproject.FaceCollect.csproj \</span><br><span class="line">  -c Release \</span><br><span class="line">  -r linux-arm64 \</span><br><span class="line">  -p:PublishSingleFile=<span class="literal">true</span> \</span><br><span class="line">  -p:PublishTrimmed=<span class="literal">false</span> \</span><br><span class="line">  -p:AssemblyName=facecollect \</span><br><span class="line">  -o publish</span><br></pre></td></tr></table></figure><blockquote><ul><li><strong>-c Release</strong> 指定发布模式为 Release。</li><li><strong>-r linux-arm64</strong> 指定目标运行环境为 Linux ARM64。</li><li><strong>-p:PublishSingleFile=true</strong> 表示发布为单文件可执行程序。</li><li><strong>-p:PublishTrimmed=false</strong> 是否裁剪程序集可根据实际需求选择（如果使用了动态反射较多，建议先关闭裁剪）。</li><li><strong>-p:AssemblyName=facecollect</strong> 让生成的可执行文件名为 facecollect。</li><li><strong>-o publish</strong> 指定输出目录。</li></ul></blockquote><p>发布成功后，<code>publish</code> 目录下会生成一个可执行文件 <code>facecollect</code> 以及一些运行所需文件（如果不是单文件，还有其他依赖文件）。</p><h3 id="准备打包目录结构"><a href="#准备打包目录结构" class="headerlink" title="准备打包目录结构"></a>准备打包目录结构</h3><p>创建一个临时打包工作目录（这里示例版本设为 1.0.0，可根据实际情况修改）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir facecollect_1.0.0_arm64</span><br></pre></td></tr></table></figure><p>在该目录下按照 Debian 打包规范创建必要的目录结构：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> facecollect_1.0.0_arm64</span><br><span class="line"></span><br><span class="line"><span class="comment"># 1) DEBIAN 目录：用于存放控制文件与脚本</span></span><br><span class="line">mkdir DEBIAN</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2) 程序安装路径目录：这里放在 /usr/local/facecollect 下</span></span><br><span class="line">mkdir -p usr/<span class="built_in">local</span>/facecollect</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3) systemd 服务文件放在 /etc/systemd/system</span></span><br><span class="line">mkdir -p etc/systemd/system</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> ..</span><br></pre></td></tr></table></figure><h3 id="拷贝发布产物到对应目录"><a href="#拷贝发布产物到对应目录" class="headerlink" title="拷贝发布产物到对应目录"></a>拷贝发布产物到对应目录</h3><p>将上一步生成的 <strong>publish</strong> 文件夹内容拷贝到 <code>usr/local/facecollect</code> 目录下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp -r publish/* facecollect_1.0.0_arm64/usr/<span class="built_in">local</span>/facecollect</span><br></pre></td></tr></table></figure><p>如果你已经把主可执行文件命名为 <code>facecollect</code>，可以给它加执行权限（有时编译生成默认已有执行权限）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod +x facecollect_1.0.0_arm64/usr/<span class="built_in">local</span>/facecollect/facecollect</span><br></pre></td></tr></table></figure><h3 id="编写-systemd-服务文件"><a href="#编写-systemd-服务文件" class="headerlink" title="编写 systemd 服务文件"></a>编写 systemd 服务文件</h3><p>在 <code>facecollect_1.0.0_arm64/etc/systemd/system/</code> 下创建服务文件 <strong>facecollect.service</strong>，内容如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">bashCopy codecat &lt;&lt; <span class="string">EOF &gt; facecollect_1.0.0_arm64/etc/systemd/system/facecollect.service</span></span><br><span class="line"><span class="string">[Unit]</span></span><br><span class="line"><span class="string">Description=FaceCollect Service</span></span><br><span class="line"><span class="string">After=network.target</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">[Service]</span></span><br><span class="line"><span class="string">Type=simple</span></span><br><span class="line"><span class="string">ExecStart=/usr/local/facecollect/facecollect</span></span><br><span class="line"><span class="string">WorkingDirectory=/usr/local/facecollect</span></span><br><span class="line"><span class="string">Restart=always</span></span><br><span class="line"><span class="string">User=root</span></span><br><span class="line"><span class="string">Group=root</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">[Install]</span></span><br><span class="line"><span class="string">WantedBy=multi-user.target</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><blockquote><ul><li><strong>ExecStart</strong> 指向可执行文件的绝对路径。</li><li><strong>WorkingDirectory</strong> 指定工作目录（可根据需要调整）。</li><li><strong>User=root / Group=root</strong> 指定以 root 身份运行（如需以其他用户运行可自行修改）。</li><li><strong>Restart=always</strong> 确保服务异常退出时自动重启。</li><li><strong>WantedBy=multi-user.target</strong> 配合 <code>systemctl enable</code> 实现开机自启动。</li></ul></blockquote><h3 id="编写-DEBIAN-控制文件"><a href="#编写-DEBIAN-控制文件" class="headerlink" title="编写 DEBIAN 控制文件"></a>编写 DEBIAN 控制文件</h3><p>创建控制文件 <strong>control</strong>（示例）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">bashCopy codecat &lt;&lt; <span class="string">EOF &gt; facecollect_1.0.0_arm64/DEBIAN/control</span></span><br><span class="line"><span class="string">Package: facecollect</span></span><br><span class="line"><span class="string">Version: 1.0.0</span></span><br><span class="line"><span class="string">Section: base</span></span><br><span class="line"><span class="string">Priority: optional</span></span><br><span class="line"><span class="string">Architecture: arm64</span></span><br><span class="line"><span class="string">Essential: no</span></span><br><span class="line"><span class="string">Maintainer: Jonty &lt;jonty.wang&gt;</span></span><br><span class="line"><span class="string">Description: FaceCollect .NET Service</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure><blockquote><ul><li><strong>Package</strong> 包名称。</li><li><strong>Version</strong> 包版本。</li><li><strong>Architecture</strong> 架构为 <code>arm64</code>。</li><li><strong>Maintainer</strong> 维护者信息，随意填写或按需填写。</li><li><strong>Description</strong> 包描述。</li></ul></blockquote><h3 id="编写安装后脚本（postinst）"><a href="#编写安装后脚本（postinst）" class="headerlink" title="编写安装后脚本（postinst）"></a>编写安装后脚本（postinst）</h3><p>在安装完成后自动执行一些操作，比如 <code>systemctl daemon-reload</code>、<code>systemctl enable</code> 和 <code>systemctl start</code>。<br>在 <code>DEBIAN</code> 目录下创建 <strong>postinst</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">cat &lt;&lt; <span class="string">&#x27;EOF&#x27;</span> &gt; facecollect_1.0.0_arm64/DEBIAN/postinst</span><br><span class="line"><span class="comment">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重新加载 systemd 配置</span></span><br><span class="line">systemctl daemon-reload</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开机自启动并立即启动</span></span><br><span class="line">systemctl <span class="built_in">enable</span> facecollect</span><br><span class="line">systemctl start facecollect</span><br><span class="line"></span><br><span class="line"><span class="built_in">exit</span> 0</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>给脚本加执行权限：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod +x facecollect_1.0.0_arm64/DEBIAN/postinst</span><br></pre></td></tr></table></figure><blockquote><p>也可以根据需要添加 <code>preinst</code>、<code>prerm</code>、<code>postrm</code> 等脚本进行安装前/卸载前/卸载后等自定义操作。</p></blockquote><h3 id="生成-DEB-包"><a href="#生成-DEB-包" class="headerlink" title="生成 DEB 包"></a>生成 DEB 包</h3><p>在 <code>facecollect_1.0.0_arm64</code> 同级目录下执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dpkg-deb --build facecollect_1.0.0_arm64</span><br></pre></td></tr></table></figure><p>成功后，会在当前目录下生成一个名为 <strong>facecollect_1.0.0_arm64.deb</strong> 的安装包。</p><h3 id="安装并验证服务"><a href="#安装并验证服务" class="headerlink" title="安装并验证服务"></a>安装并验证服务</h3><p>将 <strong>facecollect_1.0.0_arm64.deb</strong> 拷贝到目标机器（ARM64 Linux）上，然后执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo dpkg -i facecollect_1.0.0_arm64.deb</span><br></pre></td></tr></table></figure><p>如果安装过程无异常，会自动执行以下操作：</p><ul><li>安装程序到 <code>/usr/local/facecollect</code></li><li>安装服务文件到 <code>/etc/systemd/system/facecollect.service</code></li><li>执行 postinst 脚本，启用并启动服务</li></ul><p>安装完成后，可以用以下命令验证服务状态：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl status facecollect</span><br></pre></td></tr></table></figure><p>如果状态显示为 **active (running)**，则表示已经成功运行。</p><h3 id="其他操作"><a href="#其他操作" class="headerlink" title="其他操作"></a>其他操作</h3><ul><li><p>启动服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start facecollect</span><br></pre></td></tr></table></figure></li><li><p>停止服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl stop facecollect</span><br></pre></td></tr></table></figure></li><li><p>查看日志</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">journalctl -u facecollect -f</span><br></pre></td></tr></table></figure></li><li><p>卸载</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo dpkg -r facecollect</span><br></pre></td></tr></table></figure><blockquote><p>若还要清理所有配置文件，可用 <code>dpkg -P facecollect</code></p></blockquote></li></ul>]]></content>
    
    
    <summary type="html">.NET Core 应用打包ARM64架构Debian安装包</summary>
    
    
    
    <category term="DevOps" scheme="https://jonty.top/categories/DevOps/"/>
    
    
    <category term="Debian" scheme="https://jonty.top/tags/Debian/"/>
    
    <category term=".NET Core" scheme="https://jonty.top/tags/NET-Core/"/>
    
  </entry>
  
  <entry>
    <title>为 RabbitMQ 服务器启用 SSL/TLS</title>
    <link href="https://jonty.top/2024/11/15/docker-rabbit-mq-enable-ssl/"/>
    <id>https://jonty.top/2024/11/15/docker-rabbit-mq-enable-ssl/</id>
    <published>2024-11-15T12:43:45.000Z</published>
    <updated>2024-12-04T03:46:40.399Z</updated>
    
    <content type="html"><![CDATA[<h3 id="创建证书"><a href="#创建证书" class="headerlink" title="创建证书"></a>创建证书</h3><p>克隆仓库</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/rabbitmq/tls-gen tls-gen</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> tls-gen/basic/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果没有make则安装</span></span><br><span class="line">make</span><br><span class="line"><span class="comment"># make PASSWORD=123456 设置密码</span></span><br><span class="line">make verify</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">root@jonty:~/rabbitmq/tls-gen/basic<span class="comment"># make verify</span></span><br><span class="line">python3 profile.py verify --common-name <span class="string">&#x27;jonty&#x27;</span></span><br><span class="line">Will verify generated certificates against the CA...</span><br><span class="line">Will verify client_jonty certificate against root CA</span><br><span class="line">/root/rabbitmq/tls-gen/basic/result/client_jonty_certificate.pem: OK</span><br><span class="line">Will verify server_firefly certificate against root CA</span><br><span class="line">/root/rabbitmq/tls-gen/basic/result/server_jonty_certificate.pem: OK</span><br></pre></td></tr></table></figure><p>查看生成的证书文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">root@jonty:~/rabbitmq/tls-gen/basic<span class="comment"># ls -l ./result</span></span><br><span class="line">total 32</span><br><span class="line">-rw-r--r-- 1 root root 1281 Sep  9 08:53 ca_certificate.pem</span><br><span class="line">-rw------- 1 root root 1704 Sep  9 08:53 ca_key.pem</span><br><span class="line">-rw------- 1 root root 1346 Sep  9 08:53 client_jonty_certificate.pem</span><br><span class="line">-rw------- 1 root root 1704 Sep  9 08:53 client_jonty_key.pem</span><br><span class="line">-rw------- 1 root root 3651 Sep  9 08:53 client_jonty.p12</span><br><span class="line">-rw------- 1 root root 1346 Sep  9 08:53 server_jonty_certificate.pem</span><br><span class="line">-rw------- 1 root root 1704 Sep  9 08:53 server_jonty_key.pem</span><br><span class="line">-rw------- 1 root root 3651 Sep  9 08:53 server_jonty.p12</span><br></pre></td></tr></table></figure><h3 id="docker-compose"><a href="#docker-compose" class="headerlink" title="docker compose"></a>docker compose</h3><p>创建<code>rabbitmq</code>目录</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir rabbitmq</span><br></pre></td></tr></table></figure><p>复制证书</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mv tls-gen/basic/result/ ./certs</span><br><span class="line">chmod -R 777 certs/</span><br></pre></td></tr></table></figure><p>创建<code>rabbitmq.conf</code></p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## SSL configuration for AMQP (5671 port)</span></span><br><span class="line"><span class="attr">listeners.ssl.default</span> = <span class="number">0.0</span>.<span class="number">0.0</span>:<span class="number">5671</span></span><br><span class="line"><span class="attr">ssl_options.certfile</span> = /etc/rabbitmq/certs/server_jonty_certificate.pem</span><br><span class="line"><span class="attr">ssl_options.keyfile</span> = /etc/rabbitmq/certs/server_jonty_key.pem</span><br><span class="line"><span class="attr">ssl_options.cacertfile</span> = /etc/rabbitmq/certs/ca_certificate.pem</span><br><span class="line"><span class="attr">ssl_options.verify</span> = verify_peer</span><br><span class="line"><span class="attr">ssl_options.fail_if_no_peer_cert</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## SSL configuration for MQTT (8883 port):q</span></span><br><span class="line"><span class="attr">mqtt.listeners.ssl.default</span> = <span class="number">8883</span></span><br><span class="line"><span class="attr">mqtt.listeners.tcp.default</span> = <span class="number">1883</span></span><br><span class="line"></span><br><span class="line"><span class="attr">ssl_options.versions.1</span> = tlsv1.<span class="number">2</span></span><br><span class="line"><span class="attr">ssl_options.versions.2</span> = tlsv1.<span class="number">3</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## SSL configuration for management UI (HTTPS)</span></span><br><span class="line"><span class="attr">management.ssl.port</span> = <span class="number">15672</span></span><br><span class="line"><span class="attr">management.ssl.certfile</span> = /etc/rabbitmq/certs/server_jonty_certificate.pem</span><br><span class="line"><span class="attr">management.ssl.keyfile</span> = /etc/rabbitmq/certs/server_jonty_key.pem</span><br><span class="line"><span class="attr">management.ssl.cacertfile</span> = /etc/rabbitmq/certs/ca_certificate.pem</span><br><span class="line"></span><br><span class="line"><span class="comment">## use DETS (disk-based) store for retained messages</span></span><br><span class="line"><span class="attr">mqtt.retained_message_store</span> = rabbit_mqtt_retained_msg_store_dets</span><br><span class="line"><span class="comment">## only used by DETS store (in milliseconds)</span></span><br><span class="line"><span class="attr">mqtt.retained_message_store_dets_sync_interval</span> = <span class="number">2000</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>创建<code>docker-compose.yml</code></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3.8&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">rabbitmq:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">jonty/rabbitmq:3.11.10-management-mqtt</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">rabbitmq</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5671:5671&quot;</span>     <span class="comment"># AMQP over SSL 端口</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;5672:5672&quot;</span>     <span class="comment"># AMQP 端口</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;15672:15672&quot;</span>   <span class="comment"># 管理界面端口（HTTPS）</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;8883:8883&quot;</span>     <span class="comment"># MQTT over SSL 端口</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;1883:1883&quot;</span>     <span class="comment"># MQTT 端口</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">RABBITMQ_DEFAULT_USER=admin</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">RABBITMQ_DEFAULT_PASS=123456</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">RABBITMQ_DEFAULT_VHOST=/</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MQTT_DEFAULT_USER=admin</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MQTT_DEFAULT_PASS=123456</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">MQTT_VHOST=/</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./data:/var/lib/rabbitmq</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./certs:/etc/rabbitmq/certs</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf</span></span><br></pre></td></tr></table></figure><p>这里是启用了<code>mqtt</code>插件的自定义镜像</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> --platform=$TARGETPLATFORM rabbitmq:<span class="number">3.11</span>.<span class="number">10</span>-management</span><br><span class="line"><span class="keyword">RUN</span><span class="bash"> rabbitmq-plugins <span class="built_in">enable</span> --offline \</span></span><br><span class="line"><span class="bash">    rabbitmq_mqtt \</span></span><br><span class="line"><span class="bash">    rabbitmq_web_mqtt \</span></span><br><span class="line"><span class="bash">    rabbitmq_web_mqtt_examples \</span></span><br><span class="line"><span class="bash">    rabbitmq_auth_mechanism_ssl</span></span><br></pre></td></tr></table></figure><p>启动</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker compose up -d</span><br></pre></td></tr></table></figure><p>访问<code>https://your-ip:15672</code>，浏览器提示不安全，SSL证书需要由可信CA机构颁发，自签发证书需要浏览器信任可以安装<code>ca_certificate.pem</code>证书</p><p><img data-src="https://cdn.jonty.top/img/202412041142179.png" alt="image-20241204114225074"></p><blockquote><p>参考：<a href="https://www.rabbitmq.com/docs/ssl#automated-certificate-generation-transcript">TLS Support | RabbitMQ</a></p></blockquote>]]></content>
    
    
    <summary type="html">Docker部署RabbitMQ - 为 RabbitMQ 服务器启用 SSL/TLS</summary>
    
    
    
    <category term="DevOps" scheme="https://jonty.top/categories/DevOps/"/>
    
    
    <category term="Docker" scheme="https://jonty.top/tags/Docker/"/>
    
    <category term="RabbitMQ" scheme="https://jonty.top/tags/RabbitMQ/"/>
    
    <category term="SSL" scheme="https://jonty.top/tags/SSL/"/>
    
  </entry>
  
  <entry>
    <title>Azure-附加磁盘</title>
    <link href="https://jonty.top/2024/07/23/azure-attach-disk/"/>
    <id>https://jonty.top/2024/07/23/azure-attach-disk/</id>
    <published>2024-07-23T15:34:23.000Z</published>
    <updated>2024-07-23T09:34:27.815Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><a href="https://learn.microsoft.com/zh-cn/azure/virtual-machines/linux/attach-disk-portal">将数据磁盘附加到 Linux VM - Azure Virtual Machines</a></p></blockquote><p>对新磁盘进行分区、格式化和装载，通过 SSH 登录到 VM</p><h2 id="找到磁盘"><a href="#找到磁盘" class="headerlink" title="找到磁盘"></a>找到磁盘</h2><p>连接到 VM 后，需要找到该磁盘。使用 <code>lsblk</code> 来列出磁盘。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lsblk -o NAME,HCTL,SIZE,MOUNTPOINT | grep -i <span class="string">&quot;sd&quot;</span></span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">root@jonty:/home/jonty<span class="params">#</span> lsblk -o NAME,HCTL,SIZE,MOUNTPOINT | grep -i &quot;sd&quot;</span><br><span class="line">sda     0:0:0:0      30G </span><br><span class="line">├─sda1               29G /</span><br><span class="line">├─sda14               4M </span><br><span class="line">├─sda15             106M /boot/efi</span><br><span class="line">└─sda16             913M /boot</span><br><span class="line">sdb     0:0:0:1      32G </span><br><span class="line">└─sdb1               32G /mnt</span><br><span class="line">sdc     1:0:0:0       1T</span><br></pre></td></tr></table></figure><p>添加的磁盘是 <code>sdc</code> 。它是 LUN 0，容量为 1T</p><h2 id="准备空磁盘"><a href="#准备空磁盘" class="headerlink" title="准备空磁盘"></a>准备空磁盘</h2><p>如果要附加新磁盘，则需要对磁盘进行分区。</p><blockquote><p>如果磁盘大小为 2 TiB 或更大，则必须使用 GPT 分区。如果磁盘大小小于 2 TiB，则可以使用 MBR 或 GPT 分区</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo parted /dev/sdc --script mklabel gpt mkpart xfspart xfs 0% 100%</span><br><span class="line">sudo mkfs.xfs /dev/sdc1</span><br><span class="line">sudo partprobe /dev/sdc1</span><br></pre></td></tr></table></figure><p>使用 <code>partprobe</code> 确保内核知道新的分区和文件系统。使用 <code>partprobe</code> 失败可能会导致 blkid 或 lslbk 命令不会立即返回新文件系统的 UUID。</p><h2 id="挂载磁盘"><a href="#挂载磁盘" class="headerlink" title="挂载磁盘"></a>挂载磁盘</h2><p>创建一个目录以使用 <code>mkdir</code> 挂载文件系统。以下示例在以下位置 <code>/datadrive</code> 创建一个目录：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir /datadrive</span><br></pre></td></tr></table></figure><p>使用 <code>mount</code> 挂载文件系统。以下示例将 /dev/sdc1 分区挂载到 <code>/datadrive</code> 挂载点：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mount /dev/sdc1 /datadrive</span><br></pre></td></tr></table></figure><p>要确保驱动器在重新启动后自动重新挂载，须将其添加到 /etc/fstab 文件中。建议在 /etc/fstab 中使用 UUID（通用唯一标识符）来引用驱动器，而不仅仅是设备名称（例如 /dev/sdc1）。如果操作系统在启动过程中检测到磁盘错误，则使用 UUID 可以避免将不正确的磁盘装载到给定位置。然后，将为剩余的数据磁盘分配这些相同的设备 ID。要查找新驱动器的 UUID，请使用以下 <code>blkid</code> 实用程序：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo blkid</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">root@jonty:/home/jonty<span class="params">#</span> sudo blkid</span><br><span class="line">/dev/sdb1: UUID=&quot;ce3acb45-d4f3-4aff-a4e4-62e7bf2fb6cf&quot; BLOCK<span class="built_in">_</span>SIZE=&quot;4096&quot; TYPE=&quot;ext4&quot; PARTUUID=&quot;d69aecb3-01&quot;</span><br><span class="line">/dev/sda16: LABEL=&quot;BOOT&quot; UUID=&quot;9ca8192d-cb53-4d47-bedc-303f44456afb&quot; BLOCK<span class="built_in">_</span>SIZE=&quot;4096&quot; TYPE=&quot;ext4&quot; PARTUUID=&quot;15a42ecf-825e-480a-8044-f621f72a3f3f&quot;</span><br><span class="line">/dev/sda15: LABEL<span class="built_in">_</span>FATBOOT=&quot;UEFI&quot; LABEL=&quot;UEFI&quot; UUID=&quot;1775-A819&quot; BLOCK<span class="built_in">_</span>SIZE=&quot;512&quot; TYPE=&quot;vfat&quot; PARTUUID=&quot;c81a2b44-690c-4d28-bfd5-8162a9973144&quot;</span><br><span class="line">/dev/sda1: LABEL=&quot;cloudimg-rootfs&quot; UUID=&quot;8201f4f6-4d49-413d-ab1d-e1a6b19712cc&quot; BLOCK<span class="built_in">_</span>SIZE=&quot;4096&quot; TYPE=&quot;ext4&quot; PARTUUID=&quot;973eecfe-16bc-43c2-9230-e643aee9d216&quot;</span><br><span class="line">/dev/sdc1: UUID=&quot;0a6f3a47-8ba8-49b4-bc62-47343ac3f9b8&quot; BLOCK<span class="built_in">_</span>SIZE=&quot;4096&quot; TYPE=&quot;xfs&quot; PARTLABEL=&quot;xfspart&quot; PARTUUID=&quot;1f0ac95a-a755-48ff-a65f-dddc43009860&quot;</span><br><span class="line">/dev/sda14: PARTUUID=&quot;393a5d07-7213-4756-b50f-9c2a2d3e4af4&quot;</span><br></pre></td></tr></table></figure><blockquote><p>不正确地编辑 /etc/fstab 文件可能会导致系统无法启动，在编辑之前先对/etc/fstab进行备份</p></blockquote><p>在文本编辑器中打开 /etc/fstab 文件。使用在前面步骤中创建的 <code>/dev/sdc1</code> 设备的 UUID 值和 的 <code>/datadrive</code> 挂载点，在文件末尾添加一行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/fstab</span><br><span class="line">UUID=0a6f3a47-8ba8-49b4-bc62-47343ac3f9b8   /datadrive   xfs   defaults,nofail   1   2</span><br></pre></td></tr></table></figure><h2 id="验证磁盘"><a href="#验证磁盘" class="headerlink" title="验证磁盘"></a>验证磁盘</h2><p>再次使用 <code>lsblk</code> 来查看磁盘和装入点。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lsblk -o NAME,HCTL,SIZE,MOUNTPOINT | grep -i <span class="string">&quot;sd&quot;</span></span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">root@jonty:/home/jonty<span class="params">#</span> lsblk -o NAME,HCTL,SIZE,MOUNTPOINT | grep -i &quot;sd&quot;</span><br><span class="line">sda     0:0:0:0      30G </span><br><span class="line">├─sda1               29G /</span><br><span class="line">├─sda14               4M </span><br><span class="line">├─sda15             106M /boot/efi</span><br><span class="line">└─sda16             913M /boot</span><br><span class="line">sdb     0:0:0:1      32G </span><br><span class="line">└─sdb1               32G /mnt</span><br><span class="line">sdc     1:0:0:0       1T </span><br><span class="line">└─sdc1             1024G /datadrive</span><br></pre></td></tr></table></figure><p>可以看到 <code>sdc</code> 现在已装载到 <code>/datadrive</code></p>]]></content>
    
    
    <summary type="html">Azure 将数据磁盘附加到 Linux VM</summary>
    
    
    
    <category term="DevOps" scheme="https://jonty.top/categories/DevOps/"/>
    
    
    <category term="Azure" scheme="https://jonty.top/tags/Azure/"/>
    
    <category term="Disk" scheme="https://jonty.top/tags/Disk/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之数据关联</title>
    <link href="https://jonty.top/2024/06/30/abp-vNext-tutorials-part-10/"/>
    <id>https://jonty.top/2024/06/30/abp-vNext-tutorials-part-10/</id>
    <published>2024-06-30T08:49:32.000Z</published>
    <updated>2024-07-26T09:43:17.232Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2：图书列表页面">Part 2：图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3：创建,更新和删除图书">Part 3：创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5：授权">Part 5：授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6：作者: 领域层">Part 6：作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7：作者: 数据访问">Part 7：作者: 数据访问</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8：作者: 应用服务层">Part 8：作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9：作者: 用户界面">Part 9：作者: 用户界面</a></li><li>Part 10：图书关联作者（本节）</li></ul><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在前面的部分，我们创建了Book、Author功能，但是没有关联起来，在本节中，我们创建一个<strong>一对多</strong>的Author-Book关系</p><h2 id="Book添加关联"><a href="#Book添加关联" class="headerlink" title="Book添加关联"></a>Book添加关联</h2><p>在<code>Book</code>实体中添加以下属性：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Guid AuthorId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; </span><br></pre></td></tr></table></figure><blockquote><p>这里没有添加Author导航属性到Book实体中，遵循DDD最佳实践，仅通过Id应用其他聚合；取决与你自己，添加了导航属性，则在获取书籍作者时不需要关联查询，而是由导航属性自动带出</p></blockquote><h2 id="数据迁移"><a href="#数据迁移" class="headerlink" title="数据迁移"></a>数据迁移</h2><p>我们向Book添加了外键关联字段AuthorId，是不为空的，但是在数据库已有的数据中，没有AuthorId数据，运行时将会报错，这是一个典型的迁移问题：</p><ul><li>可以直接删除现有数据</li><li>用代码逻辑处理现有数据</li><li>手动修改数据库数据</li></ul><p>需要根据实际情况选择合适的处理方式，这里就直接将书籍数据删除</p><h3 id="更新EF-Core映射"><a href="#更新EF-Core映射" class="headerlink" title="更新EF Core映射"></a>更新EF Core映射</h3><p>在 <code>Acme.BookStore.EntityFrameworkCore</code> 项目文件夹下的 <code>BookStoreDbContext</code> 类中<code>OnModelCreating</code>方法，新增Book实体配置：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">builder.Entity&lt;Book&gt;(b =&gt;</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 其他配置</span></span><br><span class="line">    b.HasOne&lt;Author&gt;().WithMany().HasForeignKey(x =&gt; x.AuthorId).IsRequired();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="新增迁移"><a href="#新增迁移" class="headerlink" title="新增迁移"></a>新增迁移</h3><p>在 <code>Acme.BookStore.EntityFrameworkCore</code> 项目目录中打开命令行终端，执行以下命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet ef migrations add Added_AuthorId_To_Book</span><br></pre></td></tr></table></figure><h3 id="更新种子数据"><a href="#更新种子数据" class="headerlink" title="更新种子数据"></a>更新种子数据</h3><p>调整 <code>Acme.BookStore.Domain</code> 项目中<code>BookStoreDataSeederContributor</code> ，更改如下：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Authors;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Books;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Data;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.DependencyInjection;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreDataSeederContributor</span></span><br><span class="line">    : <span class="title">IDataSeedContributor</span>, <span class="title">ITransientDependency</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IRepository&lt;Book, Guid&gt; _bookRepository;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IAuthorRepository _authorRepository;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> AuthorManager _authorManager;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BookStoreDataSeederContributor</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        IRepository&lt;Book, Guid&gt; bookRepository,</span></span></span><br><span class="line"><span class="params"><span class="function">        IAuthorRepository authorRepository,</span></span></span><br><span class="line"><span class="params"><span class="function">        AuthorManager authorManager</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _bookRepository = bookRepository;</span><br><span class="line">        _authorRepository = authorRepository;</span><br><span class="line">        _authorManager = authorManager;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">SeedAsync</span>(<span class="params">DataSeedContext context</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">await</span> _bookRepository.GetCountAsync() &gt; <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> orwell = <span class="keyword">await</span> _authorRepository.InsertAsync(</span><br><span class="line">            <span class="keyword">await</span> _authorManager.CreateAsync(</span><br><span class="line">                <span class="string">&quot;George Orwell&quot;</span>,</span><br><span class="line">                <span class="keyword">new</span> DateTime(<span class="number">1903</span>, <span class="number">06</span>, <span class="number">25</span>),</span><br><span class="line">                <span class="string">&quot;Orwell produced literary criticism and poetry, fiction and polemical journalism; and is best known for the allegorical novella Animal Farm (1945) and the dystopian novel Nineteen Eighty-Four (1949).&quot;</span></span><br><span class="line">            )</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> douglas = <span class="keyword">await</span> _authorRepository.InsertAsync(</span><br><span class="line">            <span class="keyword">await</span> _authorManager.CreateAsync(</span><br><span class="line">                <span class="string">&quot;Douglas Adams&quot;</span>,</span><br><span class="line">                <span class="keyword">new</span> DateTime(<span class="number">1952</span>, <span class="number">03</span>, <span class="number">11</span>),</span><br><span class="line">                <span class="string">&quot;Douglas Adams was an English author, screenwriter, essayist, humorist, satirist and dramatist. Adams was an advocate for environmentalism and conservation, a lover of fast cars, technological innovation and the Apple Macintosh, and a self-proclaimed &#x27;radical atheist&#x27;.&quot;</span></span><br><span class="line">            )</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">await</span> _bookRepository.InsertAsync(</span><br><span class="line">            <span class="keyword">new</span> Book</span><br><span class="line">            &#123;</span><br><span class="line">                AuthorId = orwell.Id, <span class="comment">// 关联作者</span></span><br><span class="line">                Name = <span class="string">&quot;1984&quot;</span>,</span><br><span class="line">                Type = BookType.Dystopia,</span><br><span class="line">                PublishDate = <span class="keyword">new</span> DateTime(<span class="number">1949</span>, <span class="number">6</span>, <span class="number">8</span>),</span><br><span class="line">                Price = <span class="number">19.84f</span></span><br><span class="line">            &#125;,</span><br><span class="line">            autoSave: <span class="literal">true</span></span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="keyword">await</span> _bookRepository.InsertAsync(</span><br><span class="line">            <span class="keyword">new</span> Book</span><br><span class="line">            &#123;</span><br><span class="line">                AuthorId = douglas.Id, <span class="comment">// 关联作者</span></span><br><span class="line">                Name = <span class="string">&quot;The Hitchhiker&#x27;s Guide to the Galaxy&quot;</span>,</span><br><span class="line">                Type = BookType.ScienceFiction,</span><br><span class="line">                PublishDate = <span class="keyword">new</span> DateTime(<span class="number">1995</span>, <span class="number">9</span>, <span class="number">27</span>),</span><br><span class="line">                Price = <span class="number">42.0f</span></span><br><span class="line">            &#125;,</span><br><span class="line">            autoSave: <span class="literal">true</span></span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行<code>.Migrator</code>项目以执行迁移和创建种子数据</p><h2 id="Application"><a href="#Application" class="headerlink" title="Application"></a>Application</h2><p>调整<code>BookAppService</code></p><h3 id="Dto"><a href="#Dto" class="headerlink" title="Dto"></a>Dto</h3><p>调整以下Dto:</p><h4 id="BookDto"><a href="#BookDto" class="headerlink" title="BookDto"></a>BookDto</h4><p>添加以下属性</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Guid AuthorId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"><span class="keyword">public</span> <span class="built_in">string</span> AuthorName &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br></pre></td></tr></table></figure><h4 id="CreateUpdateBookDto"><a href="#CreateUpdateBookDto" class="headerlink" title="CreateUpdateBookDto"></a>CreateUpdateBookDto</h4><p>并添加一个 <code>AuthorId</code> 属性</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Guid AuthorId &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br></pre></td></tr></table></figure><h4 id="AuthorLookupDto"><a href="#AuthorLookupDto" class="headerlink" title="AuthorLookupDto"></a>AuthorLookupDto</h4><p>在 <code>Acme.BookStore.Application.Contracts</code> 项目<code>Books</code> 文件夹中新建类 <code>AuthorLookupDto</code>  ：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AuthorLookupDto</span> : <span class="title">EntityDto</span>&lt;<span class="title">Guid</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="IBookAppService"><a href="#IBookAppService" class="headerlink" title="IBookAppService"></a>IBookAppService</h3><p>在<code>IBookAppService</code>中新增<code>GetAuthorLookupAsync</code>接口，用于页面获取作者下拉数据。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Services;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IBookAppService</span> :</span><br><span class="line">    <span class="title">ICrudAppService</span>&lt;</span><br><span class="line">        <span class="title">BookDto</span>,</span><br><span class="line">        <span class="title">Guid</span>,</span><br><span class="line">        <span class="title">PagedAndSortedResultRequestDto</span>,</span><br><span class="line">        <span class="title">CreateUpdateBookDto</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 获取作者数据</span></span><br><span class="line">    Task&lt;ListResultDto&lt;AuthorLookupDto&gt;&gt; GetAuthorLookupAsync();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="BookAppService"><a href="#BookAppService" class="headerlink" title="BookAppService"></a>BookAppService</h3><ul><li>GetAuthorLookupAsync：用于查询所有作者提供给界面选择</li><li>重写<code>GetAsync</code>、<code>GetListAsync</code>，关联作者信息</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Collections.Generic;</span><br><span class="line"><span class="keyword">using</span> System.Linq;</span><br><span class="line"><span class="keyword">using</span> System.Linq.Dynamic.Core;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Authors;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Permissions;</span><br><span class="line"><span class="keyword">using</span> Microsoft.AspNetCore.Authorization;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Services;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Entities;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">Authorize(BookStorePermissions.Books.Default)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookAppService</span> :</span><br><span class="line">    <span class="title">CrudAppService</span>&lt;</span><br><span class="line">        <span class="title">Book</span>, //<span class="title">The</span> <span class="title">Book</span> <span class="title">entity</span></span><br><span class="line">        <span class="title">BookDto</span>, //<span class="title">Used</span> <span class="title">to</span> <span class="title">show</span> <span class="title">books</span></span><br><span class="line">        <span class="title">Guid</span>, //<span class="title">Primary</span> <span class="title">key</span> <span class="title">of</span> <span class="title">the</span> <span class="title">book</span> <span class="title">entity</span></span><br><span class="line">        <span class="title">PagedAndSortedResultRequestDto</span>, //<span class="title">Used</span> <span class="title">for</span> <span class="title">paging</span>/<span class="title">sorting</span></span><br><span class="line">        <span class="title">CreateUpdateBookDto</span>&gt;, <span class="comment">//Used to create/update a book</span></span><br><span class="line">    <span class="title">IBookAppService</span> <span class="comment">//implement the IBookAppService</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IAuthorRepository _authorRepository;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BookAppService</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        IRepository&lt;Book, Guid&gt; repository,</span></span></span><br><span class="line"><span class="params"><span class="function">        IAuthorRepository authorRepository</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">repository</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _authorRepository = authorRepository;</span><br><span class="line">        GetPolicyName = BookStorePermissions.Books.Default;</span><br><span class="line">        GetListPolicyName = BookStorePermissions.Books.Default;</span><br><span class="line">        CreatePolicyName = BookStorePermissions.Books.Create;</span><br><span class="line">        UpdatePolicyName = BookStorePermissions.Books.Edit;</span><br><span class="line">        DeletePolicyName = BookStorePermissions.Books.Delete;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">async</span> Task&lt;BookDto&gt; <span class="title">GetAsync</span>(<span class="params">Guid id</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 查询书籍</span></span><br><span class="line">        <span class="keyword">var</span> queryable = <span class="keyword">await</span> Repository.GetQueryableAsync();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 关联作者</span></span><br><span class="line">        <span class="keyword">var</span> query = <span class="keyword">from</span> book <span class="keyword">in</span> queryable</span><br><span class="line">            <span class="keyword">join</span> author <span class="keyword">in</span> <span class="keyword">await</span> _authorRepository.GetQueryableAsync() <span class="keyword">on</span> book.AuthorId <span class="keyword">equals</span> author.Id</span><br><span class="line">            <span class="keyword">where</span> book.Id == id</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; book, author &#125;;</span><br><span class="line">      </span><br><span class="line">        <span class="keyword">var</span> queryResult = <span class="keyword">await</span> AsyncExecuter.FirstOrDefaultAsync(query);</span><br><span class="line">        <span class="keyword">if</span> (queryResult == <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> EntityNotFoundException(<span class="keyword">typeof</span>(Book), id);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> bookDto = ObjectMapper.Map&lt;Book, BookDto&gt;(queryResult.book);</span><br><span class="line">        bookDto.AuthorName = queryResult.author.Name;</span><br><span class="line">        <span class="keyword">return</span> bookDto;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">async</span> Task&lt;PagedResultDto&lt;BookDto&gt;&gt; GetListAsync(PagedAndSortedResultRequestDto input)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 查询书籍</span></span><br><span class="line">        <span class="keyword">var</span> queryable = <span class="keyword">await</span> Repository.GetQueryableAsync();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 关联作者</span></span><br><span class="line">        <span class="keyword">var</span> query = <span class="keyword">from</span> book <span class="keyword">in</span> queryable</span><br><span class="line">            <span class="keyword">join</span> author <span class="keyword">in</span> <span class="keyword">await</span> _authorRepository.GetQueryableAsync() <span class="keyword">on</span> book.AuthorId <span class="keyword">equals</span> author.Id</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123;book, author&#125;;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 分页</span></span><br><span class="line">        query = query</span><br><span class="line">            .OrderBy(NormalizeSorting(input.Sorting))</span><br><span class="line">            .Skip(input.SkipCount)</span><br><span class="line">            .Take(input.MaxResultCount);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> queryResult = <span class="keyword">await</span> AsyncExecuter.ToListAsync(query);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Dto转换</span></span><br><span class="line">        <span class="keyword">var</span> bookDtos = queryResult.Select(x =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">var</span> bookDto = ObjectMapper.Map&lt;Book, BookDto&gt;(x.book);</span><br><span class="line">            bookDto.AuthorName = x.author.Name;</span><br><span class="line">            <span class="keyword">return</span> bookDto;</span><br><span class="line">        &#125;).ToList();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取数据总数</span></span><br><span class="line">        <span class="keyword">var</span> totalCount = <span class="keyword">await</span> Repository.GetCountAsync();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> PagedResultDto&lt;BookDto&gt;(</span><br><span class="line">            totalCount,</span><br><span class="line">            bookDtos</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">async</span> Task&lt;ListResultDto&lt;AuthorLookupDto&gt;&gt; GetAuthorLookupAsync()</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> authors = <span class="keyword">await</span> _authorRepository.GetListAsync();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ListResultDto&lt;AuthorLookupDto&gt;(</span><br><span class="line">            ObjectMapper.Map&lt;List&lt;Author&gt;, List&lt;AuthorLookupDto&gt;&gt;(authors)</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">NormalizeSorting</span>(<span class="params"><span class="built_in">string</span> sorting</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (sorting.IsNullOrEmpty())</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">$&quot;book.<span class="subst">&#123;<span class="keyword">nameof</span>(Book.Name)&#125;</span>&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (sorting.Contains(<span class="string">&quot;authorName&quot;</span>, StringComparison.OrdinalIgnoreCase))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> sorting.Replace(</span><br><span class="line">                <span class="string">&quot;authorName&quot;</span>,</span><br><span class="line">                <span class="string">&quot;author.Name&quot;</span>,</span><br><span class="line">                StringComparison.OrdinalIgnoreCase</span><br><span class="line">            );</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="string">$&quot;book.<span class="subst">&#123;sorting&#125;</span>&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="对象映射"><a href="#对象映射" class="headerlink" title="对象映射"></a>对象映射</h3><p>新增对象映射配置：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CreateMap&lt;Author, AuthorLookupDto&gt;();</span><br></pre></td></tr></table></figure><h3 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h3><p>调整<code>BookAppService_Tests</code>单元测试类：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line">using System;</span><br><span class="line">using System.Linq;</span><br><span class="line">using System.Threading.Tasks;</span><br><span class="line">using Acme.BookStore.Authors;</span><br><span class="line">using Shouldly;</span><br><span class="line">using Volo.Abp.Application.Dtos;</span><br><span class="line">using Volo.Abp.Modularity;</span><br><span class="line">using Volo.Abp.Validation;</span><br><span class="line">using Xunit;</span><br><span class="line"></span><br><span class="line">namespace Acme.BookStore.Books;</span><br><span class="line"></span><br><span class="line">public abstract class BookAppService_Tests&lt;TStartupModule&gt; : BookStoreApplicationTestBase&lt;TStartupModule&gt;</span><br><span class="line">    <span class="built_in">where</span> TStartupModule : IAbpModule</span><br><span class="line">&#123;</span><br><span class="line">    private <span class="built_in">readonly</span> IBookAppService _bookAppService;</span><br><span class="line">    private <span class="built_in">readonly</span> IAuthorAppService _authorAppService;</span><br><span class="line"></span><br><span class="line">    protected <span class="function"><span class="title">BookAppService_Tests</span></span>()</span><br><span class="line">    &#123;</span><br><span class="line">        _bookAppService = GetRequiredService&lt;IBookAppService&gt;();</span><br><span class="line">        _authorAppService = GetRequiredService&lt;IAuthorAppService&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [Fact]</span><br><span class="line">    public async Task <span class="function"><span class="title">Should_Get_List_Of_Books</span></span>()</span><br><span class="line">    &#123;</span><br><span class="line">        //Act</span><br><span class="line">        var result = await _bookAppService.GetListAsync(</span><br><span class="line">            new PagedAndSortedResultRequestDto()</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        //Assert</span><br><span class="line">        result.TotalCount.ShouldBeGreaterThan(0);</span><br><span class="line">        result.Items.ShouldContain(b =&gt; b.Name == <span class="string">&quot;1984&quot;</span> &amp;&amp;</span><br><span class="line">                                        b.AuthorName == <span class="string">&quot;George Orwell&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [Fact]</span><br><span class="line">    public async Task <span class="function"><span class="title">Should_Create_A_Valid_Book</span></span>()</span><br><span class="line">    &#123;</span><br><span class="line">        var authors = await _authorAppService.GetListAsync(new GetAuthorListDto());</span><br><span class="line">        var firstAuthor = authors.Items.First();</span><br><span class="line"></span><br><span class="line">        //Act</span><br><span class="line">        var result = await _bookAppService.CreateAsync(</span><br><span class="line">            new CreateUpdateBookDto</span><br><span class="line">            &#123;</span><br><span class="line">                AuthorId = firstAuthor.Id,</span><br><span class="line">                Name = <span class="string">&quot;New test book 42&quot;</span>,</span><br><span class="line">                Price = 10,</span><br><span class="line">                PublishDate = System.DateTime.Now,</span><br><span class="line">                Type = BookType.ScienceFiction</span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        //Assert</span><br><span class="line">        result.Id.ShouldNotBe(Guid.Empty);</span><br><span class="line">        result.Name.ShouldBe(<span class="string">&quot;New test book 42&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [Fact]</span><br><span class="line">    public async Task <span class="function"><span class="title">Should_Not_Create_A_Book_Without_Name</span></span>()</span><br><span class="line">    &#123;</span><br><span class="line">        var exception = await Assert.ThrowsAsync&lt;AbpValidationException&gt;(async () =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            await _bookAppService.CreateAsync(</span><br><span class="line">                new CreateUpdateBookDto</span><br><span class="line">                &#123;</span><br><span class="line">                    Name = <span class="string">&quot;&quot;</span>,</span><br><span class="line">                    Price = 10,</span><br><span class="line">                    PublishDate = DateTime.Now,</span><br><span class="line">                    Type = BookType.ScienceFiction</span><br><span class="line">                &#125;</span><br><span class="line">            );</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        exception.ValidationErrors</span><br><span class="line">            .ShouldContain(err =&gt; err.MemberNames.Any(m =&gt; m == <span class="string">&quot;Name&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>更改了 <code>Should_Get_List_Of_Books</code>中的断言条件，以检查是否已填充作者姓名</li><li>调整<code>Should_Create_A_Valid_Book</code>，获取Author信息以关联Book - AuthorId</li></ul><h2 id="用户界面"><a href="#用户界面" class="headerlink" title="用户界面"></a>用户界面</h2><h3 id="生成代理类"><a href="#生成代理类" class="headerlink" title="生成代理类"></a>生成代理类</h3><p>在<code>angular</code>目录下执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">abp generate-proxy -t ng</span><br></pre></td></tr></table></figure><h3 id="列表"><a href="#列表" class="headerlink" title="列表"></a>列表</h3><p>打开 <code>/src/app/book/book.component.html</code> ，在<code>ngx-datatable</code>新增Author展示字段：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ngx-datatable-column</span></span></span><br><span class="line"><span class="tag">  [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::Author&#x27; | abpLocalization&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">prop</span>=<span class="string">&quot;authorName&quot;</span></span></span><br><span class="line"><span class="tag">  [<span class="attr">sortable</span>]=<span class="string">&quot;false&quot;</span></span></span><br><span class="line"><span class="tag">&gt;</span><span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br></pre></td></tr></table></figure><p>效果如下：</p><p><img data-src="https://cdn.jonty.top/img/202407261737628.png" alt="book list"></p><h3 id="表单"><a href="#表单" class="headerlink" title="表单"></a>表单</h3><p>调整 <code>/src/app/book/book.component.ts</code> ，</p><ul><li>引入<code>AuthorLookupDto</code>和<code>Observable</code></li><li>新增<code>authors$: Observable&lt;AuthorLookupDto[]&gt;</code>字段</li><li>构造函数获取<code>authors$</code></li><li>调整<code>buildForm</code>，新增<code>authorId</code></li></ul><p>如下所示：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; ListService, PagedResultDto &#125; <span class="keyword">from</span> <span class="string">&#x27;@abp/ng.core&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Component, OnInit &#125; <span class="keyword">from</span> <span class="string">&#x27;@angular/core&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; BookService, BookDto, bookTypeOptions, AuthorLookupDto &#125; <span class="keyword">from</span> <span class="string">&#x27;@proxy/books&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; FormGroup, FormBuilder, Validators &#125; <span class="keyword">from</span> <span class="string">&#x27;@angular/forms&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; NgbDateNativeAdapter, NgbDateAdapter &#125; <span class="keyword">from</span> <span class="string">&#x27;@ng-bootstrap/ng-bootstrap&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; ConfirmationService, Confirmation &#125; <span class="keyword">from</span> <span class="string">&#x27;@abp/ng.theme.shared&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Observable &#125; <span class="keyword">from</span> <span class="string">&#x27;rxjs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; map &#125; <span class="keyword">from</span> <span class="string">&#x27;rxjs/operators&#x27;</span>;</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  <span class="attr">selector</span>: <span class="string">&#x27;app-book&#x27;</span>,</span><br><span class="line">  <span class="attr">templateUrl</span>: <span class="string">&#x27;./book.component.html&#x27;</span>,</span><br><span class="line">  <span class="attr">styleUrls</span>: [<span class="string">&#x27;./book.component.scss&#x27;</span>],</span><br><span class="line">  <span class="attr">providers</span>: [ListService, &#123; <span class="attr">provide</span>: NgbDateAdapter, <span class="attr">useClass</span>: NgbDateNativeAdapter &#125;],</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">BookComponent</span> <span class="title">implements</span> <span class="title">OnInit</span> </span>&#123;</span><br><span class="line">  book = &#123; <span class="attr">items</span>: [], <span class="attr">totalCount</span>: <span class="number">0</span> &#125; <span class="keyword">as</span> PagedResultDto&lt;BookDto&gt;;</span><br><span class="line"></span><br><span class="line">  form: FormGroup;</span><br><span class="line"></span><br><span class="line">  selectedBook = &#123;&#125; <span class="keyword">as</span> BookDto;</span><br><span class="line"></span><br><span class="line">  authors$: Observable&lt;AuthorLookupDto[]&gt;;</span><br><span class="line"></span><br><span class="line">  bookTypes = bookTypeOptions;</span><br><span class="line"></span><br><span class="line">  isModalOpen = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">    public readonly list: ListService,</span></span></span><br><span class="line"><span class="params"><span class="function">    private bookService: BookService,</span></span></span><br><span class="line"><span class="params"><span class="function">    private fb: FormBuilder,</span></span></span><br><span class="line"><span class="params"><span class="function">    private confirmation: ConfirmationService</span></span></span><br><span class="line"><span class="params"><span class="function">  </span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.authors$ = bookService.getAuthorLookup().pipe(map(<span class="function">(<span class="params">r</span>) =&gt;</span> r.items));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">ngOnInit</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> bookStreamCreator = <span class="function">(<span class="params">query</span>) =&gt;</span> <span class="built_in">this</span>.bookService.getList(query);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.list.hookToQuery(bookStreamCreator).subscribe(<span class="function">(<span class="params">response</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">this</span>.book = response;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">createBook</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.selectedBook = &#123;&#125; <span class="keyword">as</span> BookDto;</span><br><span class="line">    <span class="built_in">this</span>.buildForm();</span><br><span class="line">    <span class="built_in">this</span>.isModalOpen = <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">editBook</span>(<span class="params">id: string</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.bookService.get(id).subscribe(<span class="function">(<span class="params">book</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">this</span>.selectedBook = book;</span><br><span class="line">      <span class="built_in">this</span>.buildForm();</span><br><span class="line">      <span class="built_in">this</span>.isModalOpen = <span class="literal">true</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">buildForm</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.form = <span class="built_in">this</span>.fb.group(&#123;</span><br><span class="line">      <span class="attr">authorId</span>: [<span class="built_in">this</span>.selectedBook.authorId || <span class="literal">null</span>, Validators.required],</span><br><span class="line">      <span class="attr">name</span>: [<span class="built_in">this</span>.selectedBook.name || <span class="literal">null</span>, Validators.required],</span><br><span class="line">      <span class="attr">type</span>: [<span class="built_in">this</span>.selectedBook.type || <span class="literal">null</span>, Validators.required],</span><br><span class="line">      <span class="attr">publishDate</span>: [</span><br><span class="line">        <span class="built_in">this</span>.selectedBook.publishDate ? <span class="keyword">new</span> <span class="built_in">Date</span>(<span class="built_in">this</span>.selectedBook.publishDate) : <span class="literal">null</span>,</span><br><span class="line">        Validators.required,</span><br><span class="line">      ],</span><br><span class="line">      <span class="attr">price</span>: [<span class="built_in">this</span>.selectedBook.price || <span class="literal">null</span>, Validators.required],</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">save</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.form.invalid) &#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> request = <span class="built_in">this</span>.selectedBook.id</span><br><span class="line">      ? <span class="built_in">this</span>.bookService.update(<span class="built_in">this</span>.selectedBook.id, <span class="built_in">this</span>.form.value)</span><br><span class="line">      : <span class="built_in">this</span>.bookService.create(<span class="built_in">this</span>.form.value);</span><br><span class="line"></span><br><span class="line">    request.subscribe(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">this</span>.isModalOpen = <span class="literal">false</span>;</span><br><span class="line">      <span class="built_in">this</span>.form.reset();</span><br><span class="line">      <span class="built_in">this</span>.list.get();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="title">delete</span>(<span class="params">id: string</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.confirmation.warn(<span class="string">&#x27;::AreYouSureToDelete&#x27;</span>, <span class="string">&#x27;AbpAccount::AreYouSure&#x27;</span>).subscribe(<span class="function">(<span class="params">status</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (status === Confirmation.Status.confirm) &#123;</span><br><span class="line">        <span class="built_in">this</span>.bookService.delete(id).subscribe(<span class="function">() =&gt;</span> <span class="built_in">this</span>.list.get());</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>/src/app/book/book.component.html</code>中添加表单项：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;author-id&quot;</span>&gt;</span>Author<span class="tag">&lt;/<span class="name">label</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> * <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">select</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">id</span>=<span class="string">&quot;author-id&quot;</span> <span class="attr">formControlName</span>=<span class="string">&quot;authorId&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">option</span> [<span class="attr">ngValue</span>]=<span class="string">&quot;null&quot;</span>&gt;</span>Select author<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">option</span> [<span class="attr">ngValue</span>]=<span class="string">&quot;author.id&quot;</span> *<span class="attr">ngFor</span>=<span class="string">&quot;let author of authors$ | async&quot;</span>&gt;</span></span><br><span class="line">      &#123;&#123; author.name &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>最终效果如下：</p><p><img data-src="https://cdn.jonty.top/img/202407261741154.png" alt="form"></p><p><img data-src="https://cdn.jonty.top/img/202407261742383.png" alt="book store"></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 图书关联作者</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之用户界面</title>
    <link href="https://jonty.top/2024/06/30/abp-vNext-tutorials-part-9/"/>
    <id>https://jonty.top/2024/06/30/abp-vNext-tutorials-part-9/</id>
    <published>2024-06-30T06:57:55.000Z</published>
    <updated>2024-07-26T09:44:17.418Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2：图书列表页面">Part 2：图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3：创建,更新和删除图书">Part 3：创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5：授权">Part 5：授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6：作者: 领域层">Part 6：作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7：作者: 数据访问">Part 7：作者: 数据访问</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8：作者: 应用服务层">Part 8：作者: 应用服务层</a></li><li>Part 9：作者: 用户界面 （本节）</li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10：图书关联作者">Part 10：图书关联作者</a></li></ul><h2 id="Author管理"><a href="#Author管理" class="headerlink" title="Author管理"></a>Author管理</h2><p>在<code>angular</code>目录下，使用以下命令创建新的模块</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">➜ yarn ng generate module author --module app --routing --route authors</span><br><span class="line">yarn run v1.22.19</span><br><span class="line">$ ng generate module author --module app --routing --route authors</span><br><span class="line">CREATE src/app/author/author-routing.module.ts (354 bytes)</span><br><span class="line">CREATE src/app/author/author.module.ts (372 bytes)</span><br><span class="line">CREATE src/app/author/author.component.html (22 bytes)</span><br><span class="line">CREATE src/app/author/author.component.spec.ts (624 bytes)</span><br><span class="line">CREATE src/app/author/author.component.ts (210 bytes)</span><br><span class="line">CREATE src/app/author/author.component.scss (0 bytes)</span><br><span class="line">UPDATE src/app/app-routing.module.ts (2284 bytes)</span><br><span class="line">Done <span class="keyword">in</span> 1.21s.</span><br></pre></td></tr></table></figure><h3 id="AuthorModule"><a href="#AuthorModule" class="headerlink" title="AuthorModule"></a>AuthorModule</h3><p>打开<code>/src/app/author/author.module.ts</code>，新增<code>NgbDatepickerModule </code>导入：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">import &#123; NgModule &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; SharedModule &#125; from &#x27;../shared/shared.module&#x27;;</span><br><span class="line">import &#123; AuthorRoutingModule &#125; from &#x27;./author-routing.module&#x27;;</span><br><span class="line">import &#123; AuthorComponent &#125; from &#x27;./author.component&#x27;;</span><br><span class="line">import &#123; NgbDatepickerModule &#125; from &#x27;@ng-bootstrap/ng-bootstrap&#x27;;</span><br><span class="line"></span><br><span class="line">@NgModule(&#123;</span><br><span class="line">  declarations: [AuthorComponent],</span><br><span class="line">  imports: [SharedModule, AuthorRoutingModule, NgbDatepickerModule],</span><br><span class="line">&#125;)</span><br><span class="line">export class AuthorModule &#123;&#125;</span><br></pre></td></tr></table></figure><h3 id="添加菜单"><a href="#添加菜单" class="headerlink" title="添加菜单"></a>添加菜单</h3><p>在 <code>src/app/route.provider.ts</code> 中添加菜单：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  path: &#x27;/authors&#x27;,</span><br><span class="line">  name: &#x27;::Menu:Authors&#x27;,</span><br><span class="line">  parentName: &#x27;::Menu:BookStore&#x27;,</span><br><span class="line">  layout: eLayoutType.application,</span><br><span class="line">  requiredPolicy: &#x27;BookStore.Authors&#x27;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调整<code>/book-store</code>菜单权限，新增作者权限</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  path: &#x27;/book-store&#x27;,</span><br><span class="line">  name: &#x27;::Menu:BookStore&#x27;,</span><br><span class="line">  iconClass: &#x27;fas fa-book&#x27;,</span><br><span class="line">  order: <span class="number">2</span>,</span><br><span class="line">  layout: eLayoutType.application,</span><br><span class="line">  requiredPolicy: &#x27;BookStore.Books || BookStore.Authors&#x27;,</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>完整菜单配置如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">configureRoutes</span>(<span class="params">routes: RoutesService</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    routes.add([</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;::Menu:Home&#x27;</span>,</span><br><span class="line">        <span class="attr">iconClass</span>: <span class="string">&#x27;fas fa-home&#x27;</span>,</span><br><span class="line">        <span class="attr">order</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="attr">layout</span>: eLayoutType.application,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&#x27;/book-store&#x27;</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;::Menu:BookStore&#x27;</span>,</span><br><span class="line">        <span class="attr">iconClass</span>: <span class="string">&#x27;fas fa-book&#x27;</span>,</span><br><span class="line">        <span class="attr">order</span>: <span class="number">2</span>,</span><br><span class="line">        <span class="attr">layout</span>: eLayoutType.application,</span><br><span class="line">        <span class="attr">requiredPolicy</span>: <span class="string">&#x27;BookStore.Books || BookStore.Authors&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&#x27;/books&#x27;</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;::Menu:Books&#x27;</span>,</span><br><span class="line">        <span class="attr">parentName</span>: <span class="string">&#x27;::Menu:BookStore&#x27;</span>,</span><br><span class="line">        <span class="attr">layout</span>: eLayoutType.application,</span><br><span class="line">        <span class="attr">requiredPolicy</span>: <span class="string">&#x27;BookStore.Books&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&#x27;/authors&#x27;</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;::Menu:Authors&#x27;</span>,</span><br><span class="line">        <span class="attr">parentName</span>: <span class="string">&#x27;::Menu:BookStore&#x27;</span>,</span><br><span class="line">        <span class="attr">layout</span>: eLayoutType.application,</span><br><span class="line">        <span class="attr">requiredPolicy</span>: <span class="string">&#x27;BookStore.Authors&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ]);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="生成代理类"><a href="#生成代理类" class="headerlink" title="生成代理类"></a>生成代理类</h3><p>在<code>ng</code>目录下执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">abp generate-proxy -t ng</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">➜ abp generate-proxy -t ng</span><br><span class="line">ABP CLI 8.1.4</span><br><span class="line">A newer stable version of the ABP CLI is available: 8.2.0.</span><br><span class="line">Update Command: </span><br><span class="line">dotnet tool update -g Volo.Abp.Cli</span><br><span class="line">DELETE src/app/proxy/books</span><br><span class="line">CREATE src/app/proxy/authors/author.service.ts (1731 bytes)</span><br><span class="line">CREATE src/app/proxy/authors/models.ts (509 bytes)</span><br><span class="line">CREATE src/app/proxy/authors/index.ts (60 bytes)</span><br><span class="line">UPDATE src/app/proxy/generate-proxy.json (992802 bytes)</span><br><span class="line">UPDATE src/app/proxy/README.md (1000 bytes)</span><br><span class="line">UPDATE src/app/proxy/books/book.service.ts (1715 bytes)</span><br><span class="line">UPDATE src/app/proxy/books/models.ts (375 bytes)</span><br><span class="line">UPDATE src/app/proxy/books/book-type.enum.ts (299 bytes)</span><br><span class="line">UPDATE src/app/proxy/books/index.ts (92 bytes)</span><br><span class="line">UPDATE src/app/proxy/index.ts (99 bytes)</span><br></pre></td></tr></table></figure><p><img data-src="https://cdn.jonty.top/img/202407261511421.png" alt="service proxy"></p><h3 id="AuthorComponent"><a href="#AuthorComponent" class="headerlink" title="AuthorComponent"></a>AuthorComponent</h3><p>打开<code>/src/app/author/author.component.ts</code>：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line">import &#123; Component, OnInit &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; ListService, PagedResultDto &#125; from &#x27;@abp/ng.core&#x27;;</span><br><span class="line">import &#123; AuthorService, AuthorDto &#125; from &#x27;@proxy/authors&#x27;;</span><br><span class="line">import &#123; FormGroup, FormBuilder, Validators &#125; from &#x27;@angular/forms&#x27;;</span><br><span class="line">import &#123; NgbDateNativeAdapter, NgbDateAdapter &#125; from &#x27;@ng-bootstrap/ng-bootstrap&#x27;;</span><br><span class="line">import &#123; ConfirmationService, Confirmation &#125; from &#x27;@abp/ng.theme.shared&#x27;;</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: &#x27;app-author&#x27;,</span><br><span class="line">  templateUrl: &#x27;./author.component.html&#x27;,</span><br><span class="line">  styleUrls: [&#x27;./author.component.scss&#x27;],</span><br><span class="line">  providers: [ListService, &#123; provide: NgbDateAdapter, useClass: NgbDateNativeAdapter &#125;],</span><br><span class="line">&#125;)</span><br><span class="line">export class AuthorComponent implements OnInit &#123;</span><br><span class="line">  author = &#123; items: [], totalCount: 0 &#125; as PagedResultDto&lt;AuthorDto&gt;;</span><br><span class="line"></span><br><span class="line">  isModalOpen = false;</span><br><span class="line"></span><br><span class="line">  form: FormGroup;</span><br><span class="line"></span><br><span class="line">  selectedAuthor = &#123;&#125; as AuthorDto;</span><br><span class="line"></span><br><span class="line">  constructor(</span><br><span class="line">    public readonly list: ListService,</span><br><span class="line">    private authorService: AuthorService,</span><br><span class="line">    private fb: FormBuilder,</span><br><span class="line">    private confirmation: ConfirmationService</span><br><span class="line">  ) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  ngOnInit(): void &#123;</span><br><span class="line">    const authorStreamCreator = (query) =&gt; this.authorService.getList(query);</span><br><span class="line"></span><br><span class="line">    this.list.hookToQuery(authorStreamCreator).subscribe((response) =&gt; &#123;</span><br><span class="line">      this.author = response;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  createAuthor() &#123;</span><br><span class="line">    this.selectedAuthor = &#123;&#125; as AuthorDto;</span><br><span class="line">    this.buildForm();</span><br><span class="line">    this.isModalOpen = true;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  editAuthor(id: string) &#123;</span><br><span class="line">    this.authorService.get(id).subscribe((author) =&gt; &#123;</span><br><span class="line">      this.selectedAuthor = author;</span><br><span class="line">      this.buildForm();</span><br><span class="line">      this.isModalOpen = true;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  buildForm() &#123;</span><br><span class="line">    this.form = this.fb.group(&#123;</span><br><span class="line">      name: [this.selectedAuthor.name || &#x27;&#x27;, Validators.required],</span><br><span class="line">      birthDate: [</span><br><span class="line">        this.selectedAuthor.birthDate ? new Date(this.selectedAuthor.birthDate) : null,</span><br><span class="line">        Validators.required,</span><br><span class="line">      ],</span><br><span class="line">      shortBio: [this.selectedAuthor.shortBio || &#x27;&#x27;]</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  save() &#123;</span><br><span class="line">    if (this.form.invalid) &#123;</span><br><span class="line">      return;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (this.selectedAuthor.id) &#123;</span><br><span class="line">      this.authorService</span><br><span class="line">        .update(this.selectedAuthor.id, this.form.value)</span><br><span class="line">        .subscribe(() =&gt; &#123;</span><br><span class="line">          this.isModalOpen = false;</span><br><span class="line">          this.form.reset();</span><br><span class="line">          this.list.get();</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      debugger;</span><br><span class="line">      var input = this.form.value</span><br><span class="line">      this.authorService.create(this.form.value).subscribe(() =&gt; &#123;</span><br><span class="line">        this.isModalOpen = false;</span><br><span class="line">        this.form.reset();</span><br><span class="line">        this.list.get();</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  delete(id: string) &#123;</span><br><span class="line">    this.confirmation.warn(&#x27;::AreYouSureToDelete&#x27;, &#x27;::AreYouSure&#x27;)</span><br><span class="line">        .subscribe((status) =&gt; &#123;</span><br><span class="line">          if (status === Confirmation.Status.confirm) &#123;</span><br><span class="line">            this.authorService.delete(id).subscribe(() =&gt; this.list.get());</span><br><span class="line">          &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编辑<code>/src/app/author/author.component.html</code>：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card-header&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;col col-md-6&quot;</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">h5</span> <span class="attr">class</span>=<span class="string">&quot;card-title&quot;</span>&gt;</span></span><br><span class="line">            &#123;&#123; &#x27;::Menu:Authors&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">          <span class="tag">&lt;/<span class="name">h5</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;text-end col col-md-6&quot;</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;text-lg-end pt-2&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">button</span> *<span class="attr">abpPermission</span>=<span class="string">&quot;&#x27;BookStore.Authors.Create&#x27;&quot;</span> <span class="attr">id</span>=<span class="string">&quot;create&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> (<span class="attr">click</span>)=<span class="string">&quot;createAuthor()&quot;</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-plus me-1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#123; &#x27;::NewAuthor&#x27; | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card-body&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">ngx-datatable</span> [<span class="attr">rows</span>]=<span class="string">&quot;author.items&quot;</span> [<span class="attr">count</span>]=<span class="string">&quot;author.totalCount&quot;</span> [<span class="attr">list</span>]=<span class="string">&quot;list&quot;</span> <span class="attr">default</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ngx-datatable-column</span></span></span><br><span class="line"><span class="tag">          [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::Actions&#x27; | abpLocalization&quot;</span></span></span><br><span class="line"><span class="tag">          [<span class="attr">maxWidth</span>]=<span class="string">&quot;150&quot;</span></span></span><br><span class="line"><span class="tag">          [<span class="attr">sortable</span>]=<span class="string">&quot;false&quot;</span></span></span><br><span class="line"><span class="tag">        &gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">ng-template</span> <span class="attr">let-row</span>=<span class="string">&quot;row&quot;</span> <span class="attr">ngx-datatable-cell-template</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">ngbDropdown</span> <span class="attr">container</span>=<span class="string">&quot;body&quot;</span> <span class="attr">class</span>=<span class="string">&quot;d-inline-block&quot;</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">button</span></span></span><br><span class="line"><span class="tag">                <span class="attr">class</span>=<span class="string">&quot;btn btn-primary btn-sm dropdown-toggle&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">data-toggle</span>=<span class="string">&quot;dropdown&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">aria-haspopup</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">ngbDropdownToggle</span></span></span><br><span class="line"><span class="tag">              &gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-cog me-1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span>&#123;&#123; &#x27;::Actions&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">              <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">div</span> <span class="attr">ngbDropdownMenu</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">button</span> *<span class="attr">abpPermission</span>=<span class="string">&quot;&#x27;BookStore.Authors.Edit&#x27;&quot;</span> <span class="attr">ngbDropdownItem</span> (<span class="attr">click</span>)=<span class="string">&quot;editAuthor(row.id)&quot;</span>&gt;</span></span><br><span class="line">                  &#123;&#123; &#x27;::Edit&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">                <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">button</span> *<span class="attr">abpPermission</span>=<span class="string">&quot;&#x27;BookStore.Authors.Delete&#x27;&quot;</span> <span class="attr">ngbDropdownItem</span> (<span class="attr">click</span>)=<span class="string">&quot;delete(row.id)&quot;</span>&gt;</span></span><br><span class="line">                  &#123;&#123; &#x27;::Delete&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">                <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ngx-datatable-column</span> [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::Name&#x27; | abpLocalization&quot;</span> <span class="attr">prop</span>=<span class="string">&quot;name&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ngx-datatable-column</span> [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::BirthDate&#x27; | abpLocalization&quot;</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">ng-template</span> <span class="attr">let-row</span>=<span class="string">&quot;row&quot;</span> <span class="attr">ngx-datatable-cell-template</span>&gt;</span></span><br><span class="line">            &#123;&#123; row.birthDate | date &#125;&#125;</span><br><span class="line">          <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">ngx-datatable</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">  <span class="tag">&lt;<span class="name">abp-modal</span> [(<span class="attr">visible</span>)]=<span class="string">&quot;isModalOpen&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpHeader</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">h3</span>&gt;</span>&#123;&#123; (selectedAuthor.id ? &#x27;::Edit&#x27; : &#x27;::NewAuthor&#x27;) | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">    <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpBody</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">form</span> [<span class="attr">formGroup</span>]=<span class="string">&quot;form&quot;</span> (<span class="attr">ngSubmit</span>)=<span class="string">&quot;save()&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;author-name&quot;</span>&gt;</span>Name<span class="tag">&lt;/<span class="name">label</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> * <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">id</span>=<span class="string">&quot;author-name&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">formControlName</span>=<span class="string">&quot;name&quot;</span> <span class="attr">autofocus</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;mt-2&quot;</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">label</span>&gt;</span>Birth date<span class="tag">&lt;/<span class="name">label</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> * <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">input</span></span></span><br><span class="line"><span class="tag">            #<span class="attr">datepicker</span>=<span class="string">&quot;ngbDatepicker&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">name</span>=<span class="string">&quot;datepicker&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">formControlName</span>=<span class="string">&quot;birthDate&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">ngbDatepicker</span></span></span><br><span class="line"><span class="tag">            (<span class="attr">click</span>)=<span class="string">&quot;datepicker.toggle()&quot;</span></span></span><br><span class="line"><span class="tag">          /&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;author-shortBio&quot;</span>&gt;</span>ShortBio<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">id</span>=<span class="string">&quot;author-shortBio&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">formControlName</span>=<span class="string">&quot;shortBio&quot;</span>  /&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">    <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpFooter</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-secondary&quot;</span> <span class="attr">abpClose</span>&gt;</span></span><br><span class="line">        &#123;&#123; &#x27;::Close&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span> (<span class="attr">click</span>)=<span class="string">&quot;save()&quot;</span> [<span class="attr">disabled</span>]=<span class="string">&quot;form.invalid&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-check mr-1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">        &#123;&#123; &#x27;::Save&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">abp-modal</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="本地化"><a href="#本地化" class="headerlink" title="本地化"></a>本地化</h3><p>编辑<code>Acme.BookStore.Domain.Shared</code> 项目 <code>Localization/BookStore</code> 文件夹下 <code>en.json</code> 的文件，新增以下键值：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;Menu:Authors&quot;</span>: <span class="string">&quot;Authors&quot;</span>,</span><br><span class="line"><span class="string">&quot;Authors&quot;</span>: <span class="string">&quot;Authors&quot;</span>,</span><br><span class="line"><span class="string">&quot;AuthorDeletionConfirmationMessage&quot;</span>: <span class="string">&quot;Are you sure to delete the author &#x27;&#123;0&#125;&#x27;?&quot;</span>,</span><br><span class="line"><span class="string">&quot;BirthDate&quot;</span>: <span class="string">&quot;Birth date&quot;</span>,</span><br><span class="line"><span class="string">&quot;NewAuthor&quot;</span>: <span class="string">&quot;New author&quot;</span></span><br></pre></td></tr></table></figure><h2 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h2><p><img data-src="https://cdn.jonty.top/img/202407261605015.png" alt="image-20240726160527937"></p><blockquote><p>如果在定义新权限后运行 <code>.DbMigrator</code> 控制台应用程序，会自动将这些新权限授予管理员角色，不需要在自己手动授予权限</p></blockquote><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 用户界面</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之应用层</title>
    <link href="https://jonty.top/2024/06/29/abp-vNext-tutorials-part-8/"/>
    <id>https://jonty.top/2024/06/29/abp-vNext-tutorials-part-8/</id>
    <published>2024-06-29T01:00:35.000Z</published>
    <updated>2024-07-26T09:44:09.549Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2：图书列表页面">Part 2：图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3：创建,更新和删除图书">Part 3：创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5：授权">Part 5：授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6：作者: 领域层">Part 6：作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7：作者: 数据访问">Part 7：作者: 数据访问</a></li><li>Part 8：作者: 应用服务层（本节）</li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9：作者: 用户界面">Part 9：作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10：图书关联作者">Part 10：图书关联作者</a></li></ul><h2 id="IAuthorAppService"><a href="#IAuthorAppService" class="headerlink" title="IAuthorAppService"></a>IAuthorAppService</h2><p> 创建<code>Author</code> 应用层</p><p>在 <code>Acme.BookStore.Application.Contracts</code> 项目的 <code>Authors</code> 命名空间（文件夹）中创建 <code>IAuthorAppService</code> 接口：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Services;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IAuthorAppService</span> : <span class="title">IApplicationService</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function">Task&lt;AuthorDto&gt; <span class="title">GetAsync</span>(<span class="params">Guid id</span>)</span>;</span><br><span class="line"></span><br><span class="line">    Task&lt;PagedResultDto&lt;AuthorDto&gt;&gt; GetListAsync(GetAuthorListDto input);</span><br><span class="line"></span><br><span class="line">    <span class="function">Task&lt;AuthorDto&gt; <span class="title">CreateAsync</span>(<span class="params">CreateAuthorDto input</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Task <span class="title">UpdateAsync</span>(<span class="params">Guid id, UpdateAuthorDto input</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Task <span class="title">DeleteAsync</span>(<span class="params">Guid id</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>IApplicationService</code>所有应用服务接口都应继承，以标识ABP服务</li><li>定义标准CRUD方法</li><li><code>PagedResultDto</code> 是 ABP 中预定义的 DTO 类，它有一个 <code>Items</code> 集合和一个 <code>TotalCount</code> 属性来返回分页结果</li></ul><h2 id="Dtos"><a href="#Dtos" class="headerlink" title="Dtos"></a>Dtos</h2><h3 id="AuthorDto"><a href="#AuthorDto" class="headerlink" title="AuthorDto"></a>AuthorDto</h3><ul><li><code>EntityDto&lt;T&gt;</code> 只是具有给定泛型参数的 <code>Id</code> 属性。可以自己创建一个 <code>Id</code> 属性，而不是继承 <code>EntityDto&lt;T&gt;</code> 。</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AuthorDto</span> : <span class="title">EntityDto</span>&lt;<span class="title">Guid</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> DateTime BirthDate &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> ShortBio &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="GetAuthorListDto"><a href="#GetAuthorListDto" class="headerlink" title="GetAuthorListDto"></a>GetAuthorListDto</h3><ul><li>Filter：用于过滤搜索</li><li><code>PagedAndSortedResultRequestDto</code> 具有标准的分页和排序属性： <code>int MaxResultCount</code> 和 <code>int SkipCount</code> <code>string Sorting</code> 。</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">GetAuthorListDto</span> : <span class="title">PagedAndSortedResultRequestDto</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span>? Filter &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CreateAuthorDto"><a href="#CreateAuthorDto" class="headerlink" title="CreateAuthorDto"></a>CreateAuthorDto</h3><ul><li>数据注释属性可用于验证 DTO</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.ComponentModel.DataAnnotations;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CreateAuthorDto</span></span><br><span class="line">&#123;</span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    [<span class="meta">StringLength(AuthorConsts.MaxNameLength)</span>]</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="built_in">string</span>.Empty;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    <span class="keyword">public</span> DateTime BirthDate &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span>? ShortBio &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="UpdateAuthorDto"><a href="#UpdateAuthorDto" class="headerlink" title="UpdateAuthorDto"></a>UpdateAuthorDto</h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.ComponentModel.DataAnnotations;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">UpdateAuthorDto</span></span><br><span class="line">&#123;</span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    [<span class="meta">StringLength(AuthorConsts.MaxNameLength)</span>]</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="built_in">string</span>.Empty;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    <span class="keyword">public</span> DateTime BirthDate &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span>? ShortBio &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>创建和更新可以复用DTO，推荐是创建不同的DTO，随着业务的扩展可能会有区别，区分开来是比较合理的</p></blockquote><h2 id="AuthorAppService"><a href="#AuthorAppService" class="headerlink" title="AuthorAppService"></a>AuthorAppService</h2><p>在<code>Acme.BookStore.Application</code>项目中创建<code>Authors</code>文件夹，并新增<code>AuthorAppService</code>类：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Collections.Generic;</span><br><span class="line"><span class="keyword">using</span> System.Linq;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Permissions;</span><br><span class="line"><span class="keyword">using</span> Microsoft.AspNetCore.Authorization;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">Authorize(BookStorePermissions.Authors.Default)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AuthorAppService</span> : <span class="title">BookStoreAppService</span>, <span class="title">IAuthorAppService</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IAuthorRepository _authorRepository;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> AuthorManager _authorManager;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">AuthorAppService</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        IAuthorRepository authorRepository,</span></span></span><br><span class="line"><span class="params"><span class="function">        AuthorManager authorManager</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _authorRepository = authorRepository;</span><br><span class="line">        _authorManager = authorManager;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>[Authorize(BookStorePermissions.Authors.Default)]</code> 是一种声明性方式，用于检查权限（策略）以授权当前用户</p></li><li><p>派生自 <code>BookStoreAppService</code> ，服务基类，附带了启动模板，继承自标准 <code>ApplicationService</code> 类</p></li><li><p>实现定义的 <code>IAuthorAppService</code></p></li><li><p>注入 <code>IAuthorRepository</code> 和<code>AuthorManager</code> </p></li></ul><h3 id="GetAsync"><a href="#GetAsync" class="headerlink" title="GetAsync"></a>GetAsync</h3><p>根据Id查询单个作者信息</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task&lt;AuthorDto&gt; <span class="title">GetAsync</span>(<span class="params">Guid id</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> author = <span class="keyword">await</span> _authorRepository.GetAsync(id);</span><br><span class="line">    <span class="keyword">return</span> ObjectMapper.Map&lt;Author, AuthorDto&gt;(author);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="GetListAsync"><a href="#GetListAsync" class="headerlink" title="GetListAsync"></a>GetListAsync</h3><p>获取分页数据</p><ul><li>默认按作者姓名排序</li><li><code>IAuthorRepository.GetListAsync</code> 用于从数据库中获取分页、排序和过滤的作者列表</li><li>从 <code>AuthorRepository</code> 查询作者数，如果有过滤则添加过滤</li><li>返回<code>PagedResultDto</code>，映射Author、AuthorDto</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">async</span> Task&lt;PagedResultDto&lt;AuthorDto&gt;&gt; GetListAsync(GetAuthorListDto input)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (input.Sorting.IsNullOrWhiteSpace())</span><br><span class="line">    &#123;</span><br><span class="line">        input.Sorting = <span class="keyword">nameof</span>(Author.Name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> authors = <span class="keyword">await</span> _authorRepository.GetListAsync(</span><br><span class="line">        input.SkipCount,</span><br><span class="line">        input.MaxResultCount,</span><br><span class="line">        input.Sorting,</span><br><span class="line">        input.Filter</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> totalCount = input.Filter == <span class="literal">null</span></span><br><span class="line">        ? <span class="keyword">await</span> _authorRepository.CountAsync()</span><br><span class="line">        : <span class="keyword">await</span> _authorRepository.CountAsync(</span><br><span class="line">            author =&gt; author.Name.Contains(input.Filter));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> PagedResultDto&lt;AuthorDto&gt;(</span><br><span class="line">        totalCount,</span><br><span class="line">        ObjectMapper.Map&lt;List&lt;Author&gt;, List&lt;AuthorDto&gt;&gt;(authors)</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="CreateAsync"><a href="#CreateAsync" class="headerlink" title="CreateAsync"></a>CreateAsync</h3><p>创建作者信息</p><ul><li>CreateAsync需要声明<code>BookStorePermissions.Authors.Create</code>权限</li><li>使用<code>_authorManager</code>创建新作者</li><li>使用<code>_authorRepository.InsertAsync</code>将作者插入数据库</li><li>ObjectMapper返回创建完的AuthorDto</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Authorize(BookStorePermissions.Authors.Create)</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task&lt;AuthorDto&gt; <span class="title">CreateAsync</span>(<span class="params">CreateAuthorDto input</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> author = <span class="keyword">await</span> _authorManager.CreateAsync(</span><br><span class="line">        input.Name,</span><br><span class="line">        input.BirthDate,</span><br><span class="line">        input.ShortBio</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> _authorRepository.InsertAsync(author);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> ObjectMapper.Map&lt;Author, AuthorDto&gt;(author);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>也可以在_authorManager.CreateAsync中插入数据</p></blockquote><h3 id="UpdateAsync"><a href="#UpdateAsync" class="headerlink" title="UpdateAsync"></a>UpdateAsync</h3><p>更新作者信息</p><ul><li><p>UpdateAsync需要声明<code>BookStorePermissions.Authors.Edit</code>权限</p></li><li><p><code>IAuthorRepository.GetAsync</code> 用于从数据库中获取创作实体，如何author查询为<code>NULL</code>则会抛出<code>404</code></p></li><li><p>如果客户端请求更改作者姓名，使用 <code>AuthorManager.ChangeNameAsync</code> （领域服务方法） 更改作者姓名</p></li><li><p>直接更新了<code>BirthDate</code> 和 <code>ShortBio</code>， 因为它们没有任何业务规则，可以直接更改这些属性，接受任何值</p></li><li><p>调用 <code>IAuthorRepository.UpdateAsync</code> 方法以更新数据库上的实体</p></li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Authorize(BookStorePermissions.Authors.Edit)</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">UpdateAsync</span>(<span class="params">Guid id, UpdateAuthorDto input</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> author = <span class="keyword">await</span> _authorRepository.GetAsync(id);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (author.Name != input.Name)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">await</span> _authorManager.ChangeNameAsync(author, input.Name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    author.BirthDate = input.BirthDate;</span><br><span class="line">    author.ShortBio = input.ShortBio;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> _authorRepository.UpdateAsync(author);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>在ABP中，一个方法就是一个工作单元，当工作单元结束时，对实体的更改会自动调用SaveChange()，所以即使不调用await _authorRepository.UpdateAsync(author)，也会更新到数据库</p></blockquote><h3 id="DeleteAsync"><a href="#DeleteAsync" class="headerlink" title="DeleteAsync"></a>DeleteAsync</h3><p>删除作者</p><ul><li>DeleteAsync需要声明<code>BookStorePermissions.Authors.Delete</code>权限</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Authorize(BookStorePermissions.Authors.Delete)</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">DeleteAsync</span>(<span class="params">Guid id</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">await</span> _authorRepository.DeleteAsync(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="权限定义"><a href="#权限定义" class="headerlink" title="权限定义"></a>权限定义</h2><p>在<code>Acme.BookStore.Application.Contracts</code> 项目内（ <code>Permissions</code> 在文件夹中）的 <code>BookStorePermissions</code> 类，新增以下内容：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Permissions</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">BookStorePermissions</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> GroupName = <span class="string">&quot;BookStore&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Books</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Default = GroupName + <span class="string">&quot;.Books&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Create = Default + <span class="string">&quot;.Create&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Edit = Default + <span class="string">&quot;.Edit&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Delete = Default + <span class="string">&quot;.Delete&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// *** 新增Authors CLASS ***</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Authors</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Default = GroupName + <span class="string">&quot;.Authors&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Create = Default + <span class="string">&quot;.Create&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Edit = Default + <span class="string">&quot;.Edit&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Delete = Default + <span class="string">&quot;.Delete&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在<code>BookStorePermissionDefinitionProvider</code>定义权限</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Authors</span></span><br><span class="line"><span class="keyword">var</span> authorsPermission = bookStoreGroup.AddPermission(</span><br><span class="line">    BookStorePermissions.Authors.Default, L(<span class="string">&quot;Permission:Authors&quot;</span>));</span><br><span class="line">authorsPermission.AddChild(</span><br><span class="line">    BookStorePermissions.Authors.Create, L(<span class="string">&quot;Permission:Authors.Create&quot;</span>));</span><br><span class="line">authorsPermission.AddChild(</span><br><span class="line">    BookStorePermissions.Authors.Edit, L(<span class="string">&quot;Permission:Authors.Edit&quot;</span>));</span><br><span class="line">authorsPermission.AddChild(</span><br><span class="line">    BookStorePermissions.Authors.Delete, L(<span class="string">&quot;Permission:Authors.Delete&quot;</span>));</span><br></pre></td></tr></table></figure><p>在 <code>Acme.BookStore.Domain.Shared</code> 项目 <code>Localization/BookStore/en.json</code> 中新增多语言键值：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;Permission:Authors&quot;</span>: <span class="string">&quot;Author Management&quot;</span>,</span><br><span class="line"><span class="string">&quot;Permission:Authors.Create&quot;</span>: <span class="string">&quot;Creating new authors&quot;</span>,</span><br><span class="line"><span class="string">&quot;Permission:Authors.Edit&quot;</span>: <span class="string">&quot;Editing the authors&quot;</span>,</span><br><span class="line"><span class="string">&quot;Permission:Authors.Delete&quot;</span>: <span class="string">&quot;Deleting the authors&quot;</span></span><br></pre></td></tr></table></figure><h2 id="对象映射"><a href="#对象映射" class="headerlink" title="对象映射"></a>对象映射</h2><p>在 <code>Acme.BookStore.Application</code> 项目中打开 <code>BookStoreApplicationAutoMapperProfile</code> 类，新增以下配置：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CreateMap&lt;Author, AuthorDto&gt;();</span><br></pre></td></tr></table></figure><h2 id="数据种子"><a href="#数据种子" class="headerlink" title="数据种子"></a>数据种子</h2><p>就像之前对书籍所做的那样，最好在数据库中有一些初始作者数据。在首次运行应用程序时会很好，对于自动化测试也非常有用。</p><p>在 <code>Acme.BookStore.Domain</code> 项目 <code>BookStoreDataSeederContributor</code> 中新增以下代码：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Authors;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Books;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Data;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.DependencyInjection;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreDataSeederContributor</span></span><br><span class="line">        : <span class="title">IDataSeedContributor</span>, <span class="title">ITransientDependency</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">readonly</span> IRepository&lt;Book, Guid&gt; _bookRepository;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">readonly</span> IAuthorRepository _authorRepository;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">readonly</span> AuthorManager _authorManager;</span><br><span class="line"></span><br><span class="line">        <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">        <span class="comment"><span class="doctag">///</span> DI</span></span><br><span class="line">        <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">        <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;bookRepository&quot;&gt;</span><span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">        <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;authorRepository&quot;&gt;</span><span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">        <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;authorManager&quot;&gt;</span><span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">BookStoreDataSeederContributor</span>(<span class="params">IRepository&lt;Book, Guid&gt; bookRepository, </span></span></span><br><span class="line"><span class="params"><span class="function">            IAuthorRepository authorRepository, </span></span></span><br><span class="line"><span class="params"><span class="function">            AuthorManager authorManager</span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            _bookRepository = bookRepository;</span><br><span class="line">            _authorRepository = authorRepository;</span><br><span class="line">            _authorManager = authorManager;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">SeedAsync</span>(<span class="params">DataSeedContext context</span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">await</span> _bookRepository.GetCountAsync() &lt;= <span class="number">0</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">await</span> _bookRepository.InsertAsync(</span><br><span class="line">                    <span class="keyword">new</span> Book</span><br><span class="line">                    &#123;</span><br><span class="line">                        Name = <span class="string">&quot;1984&quot;</span>,</span><br><span class="line">                        Type = BookType.ScienceFiction,</span><br><span class="line">                        PublishDate = <span class="keyword">new</span> DateTime(<span class="number">2000</span>, <span class="number">6</span>, <span class="number">8</span>),</span><br><span class="line">                        Price = <span class="number">19.84f</span></span><br><span class="line">                    &#125;,</span><br><span class="line">                    autoSave: <span class="literal">true</span></span><br><span class="line">                );</span><br><span class="line"></span><br><span class="line">                <span class="keyword">await</span> _bookRepository.InsertAsync(</span><br><span class="line">                    <span class="keyword">new</span> Book</span><br><span class="line">                    &#123;</span><br><span class="line">                        Name = <span class="string">&quot;盗墓笔记&quot;</span>,</span><br><span class="line">                        Type = BookType.Adventure,</span><br><span class="line">                        PublishDate = <span class="keyword">new</span> DateTime(<span class="number">2005</span>, <span class="number">9</span>, <span class="number">27</span>),</span><br><span class="line">                        Price = <span class="number">42.0f</span></span><br><span class="line">                    &#125;,</span><br><span class="line">                    autoSave: <span class="literal">true</span></span><br><span class="line">                );</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">await</span> _authorRepository.GetCountAsync() &lt;= <span class="number">0</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">await</span> _authorRepository.InsertAsync(</span><br><span class="line">                    <span class="keyword">await</span> _authorManager.CreateAsync(</span><br><span class="line">                        <span class="string">&quot;Jonty Wang&quot;</span>,</span><br><span class="line">                        <span class="keyword">new</span> DateTime(<span class="number">1998</span>, <span class="number">03</span>, <span class="number">21</span>),</span><br><span class="line">                        <span class="string">&quot;the best develop engineer in Singapore&quot;</span> </span><br><span class="line">                    )</span><br><span class="line">                );</span><br><span class="line"></span><br><span class="line">                <span class="keyword">await</span> _authorRepository.InsertAsync(</span><br><span class="line">                    <span class="keyword">await</span> _authorManager.CreateAsync(</span><br><span class="line">                        <span class="string">&quot;Taylor Swift&quot;</span>,</span><br><span class="line">                        <span class="keyword">new</span> DateTime(<span class="number">1989</span>, <span class="number">03</span>, <span class="number">11</span>),</span><br><span class="line">                        <span class="string">&quot;the best singer in the world&quot;</span></span><br><span class="line">                    )</span><br><span class="line">                );</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>运行<code>.DbMigrator</code>项目生成种子数据</p></blockquote><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p>在<code>Acme.BookStore.Application.Tests</code>项目创建<code>Authors</code>文件夹，并新增<code>AuthorAppService_Tests</code>测试类</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Shouldly;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Modularity;</span><br><span class="line"><span class="keyword">using</span> Xunit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">AuthorAppService_Tests</span>&lt;<span class="title">TStartupModule</span>&gt; : <span class="title">BookStoreApplicationTestBase</span>&lt;<span class="title">TStartupModule</span>&gt;</span><br><span class="line">    <span class="keyword">where</span> <span class="title">TStartupModule</span> : <span class="title">IAbpModule</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IAuthorAppService _authorAppService;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">AuthorAppService_Tests</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _authorAppService = GetRequiredService&lt;IAuthorAppService&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Fact</span>]</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Get_All_Authors_Without_Any_Filter</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> result = <span class="keyword">await</span> _authorAppService.GetListAsync(<span class="keyword">new</span> GetAuthorListDto());</span><br><span class="line"></span><br><span class="line">        result.TotalCount.ShouldBeGreaterThanOrEqualTo(<span class="number">2</span>);</span><br><span class="line">        result.Items.ShouldContain(author =&gt; author.Name == <span class="string">&quot;Jonty Wang&quot;</span>);</span><br><span class="line">        result.Items.ShouldContain(author =&gt; author.Name == <span class="string">&quot;Taylor Swift&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Fact</span>]</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Get_Filtered_Authors</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> result = <span class="keyword">await</span> _authorAppService.GetListAsync(</span><br><span class="line">            <span class="keyword">new</span> GetAuthorListDto &#123; Filter = <span class="string">&quot;Jonty&quot;</span> &#125;);</span><br><span class="line"></span><br><span class="line">        result.TotalCount.ShouldBeGreaterThanOrEqualTo(<span class="number">1</span>);</span><br><span class="line">        result.Items.ShouldContain(author =&gt; author.Name == <span class="string">&quot;Jonty Wang&quot;</span>);</span><br><span class="line">        result.Items.ShouldNotContain(author =&gt; author.Name == <span class="string">&quot;Taylor Swift&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Fact</span>]</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Create_A_New_Author</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> authorDto = <span class="keyword">await</span> _authorAppService.CreateAsync(</span><br><span class="line">            <span class="keyword">new</span> CreateAuthorDto</span><br><span class="line">            &#123;</span><br><span class="line">                Name = <span class="string">&quot;Edward Bellamy&quot;</span>,</span><br><span class="line">                BirthDate = <span class="keyword">new</span> DateTime(<span class="number">1850</span>, <span class="number">05</span>, <span class="number">22</span>),</span><br><span class="line">                ShortBio = <span class="string">&quot;Edward Bellamy was an American author...&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        authorDto.Id.ShouldNotBe(Guid.Empty);</span><br><span class="line">        authorDto.Name.ShouldBe(<span class="string">&quot;Edward Bellamy&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Fact</span>]</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Not_Allow_To_Create_Duplicate_Author</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">await</span> Assert.ThrowsAsync&lt;AuthorAlreadyExistsException&gt;(<span class="keyword">async</span> () =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">await</span> _authorAppService.CreateAsync(</span><br><span class="line">                <span class="keyword">new</span> CreateAuthorDto</span><br><span class="line">                &#123;</span><br><span class="line">                    Name = <span class="string">&quot;Taylor Swift&quot;</span>,</span><br><span class="line">                    BirthDate = DateTime.Now,</span><br><span class="line">                    ShortBio = <span class="string">&quot;...&quot;</span></span><br><span class="line">                &#125;</span><br><span class="line">            );</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>Acme.BookStore.EntityFrameworkCore.Tests</code>项目<code>EntityFrameworkCore/Applications</code>中创建<code>Authors</code>文件夹，并创建<code>EfCoreAuthorAppService_Tests</code>类实现<code>AuthorAppService_Tests</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> Acme.BookStore.Authors;</span><br><span class="line"><span class="keyword">using</span> Xunit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.EntityFrameworkCore.Applications.Authors</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">Collection(BookStoreTestConsts.CollectionDefinitionName)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">EfCoreAuthorAppService_Tests</span> : <span class="title">AuthorAppService_Tests</span>&lt;<span class="title">BookStoreEntityFrameworkCoreTestModule</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img data-src="https://cdn.jonty.top/img/202407261231214.png" alt="run test"></p><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 应用层</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之数据访问</title>
    <link href="https://jonty.top/2024/06/28/abp-vNext-tutorials-part-7/"/>
    <id>https://jonty.top/2024/06/28/abp-vNext-tutorials-part-7/</id>
    <published>2024-06-28T03:21:08.000Z</published>
    <updated>2024-07-26T09:44:02.373Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2：图书列表页面">Part 2：图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3：创建,更新和删除图书">Part 3：创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5：授权">Part 5：授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6：作者: 领域层">Part 6：作者: 领域层</a></li><li>Part 7：作者: 数据访问 （本节）</li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8：作者: 应用服务层">Part 8：作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9：作者: 用户界面">Part 9：作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10：图书关联作者">Part 10：图书关联作者</a></li></ul><h2 id="DB-Context"><a href="#DB-Context" class="headerlink" title="DB Context"></a>DB Context</h2><p>在 <code>Acme.BookStore.EntityFrameworkCore</code> 项目中打开“ <code>BookStoreDbContext</code> 并添加以下 <code>DbSet</code> 属性：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> DbSet&lt;Author&gt; Authors &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br></pre></td></tr></table></figure><p>并在<code>OnModelCreating</code>中添加以下配置：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">builder.Entity&lt;Author&gt;(b =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    b.ToTable(BookStoreConsts.DbTablePrefix + <span class="string">&quot;Authors&quot;</span>,</span><br><span class="line">        BookStoreConsts.DbSchema);</span><br><span class="line">    </span><br><span class="line">    b.ConfigureByConvention();</span><br><span class="line">    </span><br><span class="line">    b.Property(x =&gt; x.Name)</span><br><span class="line">        .IsRequired()</span><br><span class="line">        .HasMaxLength(AuthorConsts.MaxNameLength);</span><br><span class="line"></span><br><span class="line">    b.HasIndex(x =&gt; x.Name);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Migration"><a href="#Migration" class="headerlink" title="Migration"></a>Migration</h2><p>新增实体后需要创建新的迁移以同步更新到数据库中。</p><p>将<code>.EntityFramework.Core</code>设为启动项目，打开命令行，输入以下命令新增迁移：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet ef migrations add Added_Authors</span><br></pre></td></tr></table></figure><p><img data-src="https://cdn.jonty.top/img/202407231829334.png" alt="new-migration"></p><p>更新到数据库：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet ef database update</span><br></pre></td></tr></table></figure><blockquote><p>如果使用的是 Visual Studio，则需要在包管理器控制台 （PMC） 中使用 <code>Add-Migration Added_Authors</code> and <code>Update-Database</code> 命令。在这种情况下，请确保 <code>Acme.BookStore.EntityFrameworkCore</code> 是 Visual Studio 中的启动项目，并且是 <code>Acme.BookStore.EntityFrameworkCore</code> PMC 中的默认项目。</p></blockquote><h2 id="实现IAuthorRepository"><a href="#实现IAuthorRepository" class="headerlink" title="实现IAuthorRepository"></a>实现IAuthorRepository</h2><p>在 <code>Acme.BookStore.EntityFrameworkCore</code> 项目内（ <code>Authors</code> 在文件夹中）创建 <code>EfCoreAuthorRepository.cs</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Collections.Generic;</span><br><span class="line"><span class="keyword">using</span> System.Linq;</span><br><span class="line"><span class="keyword">using</span> System.Linq.Dynamic.Core;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.EntityFrameworkCore;</span><br><span class="line"><span class="keyword">using</span> Microsoft.EntityFrameworkCore;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories.EntityFrameworkCore;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.EntityFrameworkCore;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">EfCoreAuthorRepository</span></span><br><span class="line">    : <span class="title">EfCoreRepository</span>&lt;<span class="title">BookStoreDbContext</span>, <span class="title">Author</span>, <span class="title">Guid</span>&gt;,</span><br><span class="line">        <span class="title">IAuthorRepository</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> DI</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;dbContextProvider&quot;&gt;</span><span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">EfCoreAuthorRepository</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        IDbContextProvider&lt;BookStoreDbContext&gt; dbContextProvider</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">dbContextProvider</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task&lt;Author&gt; <span class="title">FindByNameAsync</span>(<span class="params"><span class="built_in">string</span> name</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> dbSet = <span class="keyword">await</span> GetDbSetAsync();</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">await</span> dbSet.FirstOrDefaultAsync(author =&gt; author.Name == name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">async</span> Task&lt;List&lt;Author&gt;&gt; GetListAsync(</span><br><span class="line">        <span class="built_in">int</span> skipCount,</span><br><span class="line">        <span class="built_in">int</span> maxResultCount,</span><br><span class="line">        <span class="built_in">string</span> sorting,</span><br><span class="line">        <span class="built_in">string</span> filter = <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> dbSet = <span class="keyword">await</span> GetDbSetAsync();</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">await</span> dbSet</span><br><span class="line">            .WhereIf(</span><br><span class="line">                !filter.IsNullOrWhiteSpace(),</span><br><span class="line">                author =&gt; author.Name.Contains(filter)</span><br><span class="line">            )</span><br><span class="line">            .OrderBy(sorting)</span><br><span class="line">            .Skip(skipCount)</span><br><span class="line">            .Take(maxResultCount)</span><br><span class="line">            .ToListAsync();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>EfCoreAuthorRepository</code>继承自<code>EfCoreRepository</code></li><li><code>WhereIf</code> 是ABP框架的扩展方法。当第一个条件满足时，才会添加 <code>Where</code> 条件</li><li><code>sorting</code> 可以是类似 <code>Name</code> 、 <code>Name ASC</code> 或 <code>Name DESC</code> 的字符串。可以使用 System.Linq.Dynamic.Core NuGet 包。</li></ul><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 数据访问</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之领域层</title>
    <link href="https://jonty.top/2024/06/28/abp-vNext-tutorials-part-6/"/>
    <id>https://jonty.top/2024/06/28/abp-vNext-tutorials-part-6/</id>
    <published>2024-06-27T23:40:14.000Z</published>
    <updated>2024-07-26T09:43:55.277Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2：图书列表页面">Part 2：图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3：创建,更新和删除图书">Part 3：创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5：授权">Part 5：授权</a></li><li> Part 6：作者: 领域层（本节）</li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7：作者: 数据访问">Part 7：作者: 数据访问</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8：作者: 应用服务层">Part 8：作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9：作者: 用户界面">Part 9：作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10：图书关联作者">Part 10：图书关联作者</a></li></ul><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在前面的部分中，我们使用了ABP基础设施来快速创建了一些服务：</p><ul><li>使用 CrudAppService 基类，而不是手动开发用于标准创建、读取、更新和删除操作的应用程序服务。</li><li>使用通用存储库完全自动化数据库层。</li></ul><p>那么，对于后面的<code>Author</code>部分</p><ul><li>手动创建领域服务和应用服务接口，以了解如何手动实现。</li><li>实现领域驱动设计（DDD）最佳实践。</li></ul><blockquote><p>开发将逐层完成，以一次性专注于单个层。在实际项目中，可以像前面部分一样按功能（垂直）开发应用程序功能。</p></blockquote><h2 id="Entity"><a href="#Entity" class="headerlink" title="Entity"></a>Entity</h2><p>在 <code>Acme.BookStore.Domain</code> 项目中创建一个 <code>Authors</code> 文件夹（命名空间），并新建一个 <code>Author</code> 类：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Entities.Auditing;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 作者</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Author</span> : <span class="title">FullAuditedAggregateRoot</span>&lt;<span class="title">Guid</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Name of the author.</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Birth date of the author.</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">public</span> DateTime BirthDate &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Short biography of the author.</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> ShortBio &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Author</span> (<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">Author</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        Guid id,</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">string</span> name,</span></span></span><br><span class="line"><span class="params"><span class="function">        DateTime birthDate,</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">string</span>? shortBio = <span class="literal">null</span></span></span></span><br><span class="line"><span class="params"><span class="function">        </span>) : <span class="title">base</span>(<span class="params">id</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        SetName(name);</span><br><span class="line">        birthDate = BirthDate;</span><br><span class="line">        ShortBio = shortBio;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> Author <span class="title">ChangeName</span>(<span class="params"><span class="built_in">string</span> name</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        SetName(name);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">SetName</span>(<span class="params"><span class="built_in">string</span> name</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Name = Check.NotNullOrWhiteSpace(</span><br><span class="line">            name,</span><br><span class="line">            <span class="keyword">nameof</span>(name),</span><br><span class="line">            maxLength: AuthorConsts.MaxNameLength</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>继承自 <code>FullAuditedAggregateRoot&lt;Guid&gt;</code> ，使用soft delete，并记录所有审计信息</li><li><code>Name - private set</code>，限制从类中设置此属性，有两种方法可以设置名称：<ul><li>SetName</li><li>ChangeName</li></ul></li></ul><p>两种方法都会校验名称</p><ul><li>ChangeName方法设置为<code>internal</code>， 强制仅在领域层使用方法</li><li><code>Check</code>类为ABP帮助类，可以检查方法参数</li></ul><p>在<code>.Domain.Shared</code>项目中创建AuthorConsts类：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AuthorConsts</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">int</span> MaxNameLength = <span class="number">64</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Repository"><a href="#Repository" class="headerlink" title="Repository"></a>Repository</h2><p>在 <code>Acme.BookStore.Domain</code> 项目的 <code>Authors</code> 文件夹（命名空间）中创建<code>IAuthorRepository</code>接口：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System.Collections.Generic;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IAuthorRepository</span> : <span class="title">IRepository</span>&lt;<span class="title">Author</span>, <span class="title">Guid</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function">Task&lt;Author&gt; <span class="title">FindByNameAsync</span>(<span class="params"><span class="built_in">string</span> name</span>)</span>;</span><br><span class="line"></span><br><span class="line">    Task&lt;List&lt;Author&gt;&gt; GetListAsync(</span><br><span class="line">        <span class="built_in">int</span> skipCount,</span><br><span class="line">        <span class="built_in">int</span> maxResultCount,</span><br><span class="line">        <span class="built_in">string</span> sorting,</span><br><span class="line">        <span class="built_in">string</span> filter = <span class="literal">null</span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>IAuthorRepository</code> 扩展了标准 <code>IRepository&lt;Author, Guid&gt;</code> 接口，因此所有标准存储库方法也可用于 <code>IAuthorRepository</code> 。</li><li><code>FindByNameAsync</code> 用于 <code>AuthorManager</code> 按姓名查询作者。</li><li><code>GetListAsync</code> 将在应用程序层中用于获取要在 UI 上显示的列出、排序和筛选的作者列表。</li></ul><h2 id="Domain"><a href="#Domain" class="headerlink" title="Domain"></a>Domain</h2><p>在 <code>Acme.BookStore.Domain</code> 项目的 <code>Authors</code> 文件夹（命名空间）中创建一个 <code>AuthorManager</code> 类：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Services;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AuthorManager</span> : <span class="title">DomainService</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IAuthorRepository _authorRepository;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">AuthorManager</span>(<span class="params">IAuthorRepository authorRepository</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _authorRepository = authorRepository;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task&lt;Author&gt; <span class="title">CreateAsync</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">string</span> name,</span></span></span><br><span class="line"><span class="params"><span class="function">        DateTime birthDate,</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">string</span>? shortBio = <span class="literal">null</span></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Check.NotNullOrWhiteSpace(name, <span class="keyword">nameof</span>(name));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> existingAuthor = <span class="keyword">await</span> _authorRepository.FindByNameAsync(name);</span><br><span class="line">        <span class="keyword">if</span> (existingAuthor != <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> AuthorAlreadyExistsException(name);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Author(</span><br><span class="line">            GuidGenerator.Create(),</span><br><span class="line">            name,</span><br><span class="line">            birthDate,</span><br><span class="line">            shortBio</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">ChangeNameAsync</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        Author author,</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">string</span> newName</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Check.NotNull(author, <span class="keyword">nameof</span>(author));</span><br><span class="line">        Check.NotNullOrWhiteSpace(newName, <span class="keyword">nameof</span>(newName));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> existingAuthor = <span class="keyword">await</span> _authorRepository.FindByNameAsync(newName);</span><br><span class="line">        <span class="keyword">if</span> (existingAuthor != <span class="literal">null</span> &amp;&amp; existingAuthor.Id != author.Id)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> AuthorAlreadyExistsException(newName);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        author.ChangeName(newName);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>DDD 提示：除非确实需要领域服务方法并执行一些核心业务规则，否则不要引入领域服务方法</p></blockquote><p>这两种方法都会检查是否已经存在具有给定名称的作者，并抛出在 <code>Acme.BookStore.Domain</code> 项目（ <code>Authors</code> 文件夹中）中定义的特殊业务异常， <code>AuthorAlreadyExistsException</code> 如下所示：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> Volo.Abp;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Authors</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AuthorAlreadyExistsException</span> : <span class="title">BusinessException</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">AuthorAlreadyExistsException</span>(<span class="params"><span class="built_in">string</span> name</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">BookStoreDomainErrorCodes.AuthorAlreadyExists</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        WithData(<span class="string">&quot;name&quot;</span>, name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>BusinessException</code> 是一种特殊的异常类型。在需要时抛出与领域层相关的异常。它由ABP框架自动处理，方便本地化。<code>WithData(...)</code> 方法用于向异常对象提供其他数据，将用于本地化消息或用于某些其他操作。</p><p>在 <code>Acme.BookStore.Domain.Shared</code> 项目中打开并 <code>BookStoreDomainErrorCodes</code> 更改，如下所示：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">BookStoreDomainErrorCodes</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* You can add your business exception error codes here, as constants */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> AuthorAlreadyExists = <span class="string">&quot;BookStore:00001&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>打开 <code>Acme.BookStore.Domain.Shared</code> 项目 <code>Localization/BookStore/en.json</code> 并添加以下配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;BookStore:00001&quot;</span>: <span class="string">&quot;There is already an author with the same name: &#123;name&#125;&quot;</span></span><br></pre></td></tr></table></figure><p>抛出 <code>AuthorAlreadyExistsException</code> 时，用户会在 UI 上看到此错误消息。</p><h3 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h3><p>这部分涵盖了书店应用程序的作者功能的领域层。下图展示了此部分中创建/更新的主要文件：</p><p><img data-src="https://cdn.jonty.top/img/202407231816055.png" alt="folder"></p><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 领域层</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之授权</title>
    <link href="https://jonty.top/2024/06/27/abp-vNext-tutorials-part-5/"/>
    <id>https://jonty.top/2024/06/27/abp-vNext-tutorials-part-5/</id>
    <published>2024-06-27T05:40:14.000Z</published>
    <updated>2024-07-26T09:43:45.933Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2：图书列表页面">Part 2：图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3：创建,更新和删除图书">Part 3：创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li>Part 5：授权（本节）</li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6：作者: 领域层">Part 6：作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7：作者: 数据访问">Part 7：作者: 数据访问</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8：作者: 应用服务层">Part 8：作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9：作者: 用户界面">Part 9：作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10：图书关联作者">Part 10：图书关联作者</a></li></ul><h2 id="权限"><a href="#权限" class="headerlink" title="权限"></a>权限</h2><p>ABP框架提供了一个基于 ASP.NET Core授权基础设施的授权系统。在标准授权基础架构之上添加的一个主要功能是权限系统，它允许定义权限，并启用/禁用每个角色、用户或客户端。</p><h3 id="权限名称"><a href="#权限名称" class="headerlink" title="权限名称"></a>权限名称</h3><p>权限必须具有唯一的名称 （ <code>string</code> ），将其定义为 <code>const</code> ，这样可以复用权限名称。</p><p>打开 <code>Acme.BookStore.Application.Contracts</code> 项目内（ <code>Permissions</code> 在文件夹中）的 <code>BookStorePermissions</code> 类，并更改内容，如下所示：</p><p>这是定义权限名称的分层方式。例如，“创建书籍”权限名称定义为 <code>BookStore.Books.Create</code> 。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Permissions</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">BookStorePermissions</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> GroupName = <span class="string">&quot;BookStore&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Books</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Default = GroupName + <span class="string">&quot;.Books&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Create = Default + <span class="string">&quot;.Create&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Edit = Default + <span class="string">&quot;.Edit&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">string</span> Delete = Default + <span class="string">&quot;.Delete&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="权限定义"><a href="#权限定义" class="headerlink" title="权限定义"></a>权限定义</h3><p>在使用权限之前，应定义权限。</p><p>打开 <code>Acme.BookStore.Application.Contracts</code> 项目内（ <code>Permissions</code> 在文件夹中）的 <code>BookStorePermissionDefinitionProvider</code> 类，并更改内容，</p><p>如下所示：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> Acme.BookStore.Localization;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Authorization.Permissions;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Localization;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.MultiTenancy;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Permissions</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStorePermissionDefinitionProvider</span> : <span class="title">PermissionDefinitionProvider</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Define</span>(<span class="params">IPermissionDefinitionContext context</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L(<span class="string">&quot;Permission:BookStore&quot;</span>));</span><br><span class="line"></span><br><span class="line">        bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Host, L(<span class="string">&quot;Permission:Dashboard&quot;</span>), MultiTenancySides.Host);</span><br><span class="line">        bookStoreGroup.AddPermission(BookStorePermissions.Dashboard.Tenant, L(<span class="string">&quot;Permission:Dashboard&quot;</span>), MultiTenancySides.Tenant);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//Define your own permissions here. Example:</span></span><br><span class="line">        <span class="comment">//myGroup.AddPermission(BookStorePermissions.MyPermission1, L(&quot;Permission:MyPermission1&quot;));</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// BookStore</span></span><br><span class="line">        <span class="keyword">var</span> booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L(<span class="string">&quot;Permission:Books&quot;</span>));</span><br><span class="line">        booksPermission.AddChild(BookStorePermissions.Books.Create, L(<span class="string">&quot;Permission:Books.Create&quot;</span>));</span><br><span class="line">        booksPermission.AddChild(BookStorePermissions.Books.Edit, L(<span class="string">&quot;Permission:Books.Edit&quot;</span>));</span><br><span class="line">        booksPermission.AddChild(BookStorePermissions.Books.Delete, L(<span class="string">&quot;Permission:Books.Delete&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> LocalizableString <span class="title">L</span>(<span class="params"><span class="built_in">string</span> name</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> LocalizableString.Create&lt;BookStoreResource&gt;(name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>定义一个权限组（对 UI 上的权限进行分组，如下所示）和此组内的 4 个权限。创建、编辑和删除是 <code>BookStorePermissions.Books.Default</code> 权限的子级。</p><p>只有选择了父级时，才能选择子权限。</p><p>编辑多语言文件（在 <code>Acme.BookStore.Domain.Shared</code> 项目的 <code>Localization/BookStore</code> 文件夹 <code>en.json</code> 下），定义上面使用的本地化键：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;Permission:BookStore&quot;</span>: <span class="string">&quot;Book Store&quot;</span>,</span><br><span class="line"><span class="string">&quot;Permission:Books&quot;</span>: <span class="string">&quot;Book Management&quot;</span>,</span><br><span class="line"><span class="string">&quot;Permission:Books.Create&quot;</span>: <span class="string">&quot;Creating new books&quot;</span>,</span><br><span class="line"><span class="string">&quot;Permission:Books.Edit&quot;</span>: <span class="string">&quot;Editing the books&quot;</span>,</span><br><span class="line"><span class="string">&quot;Permission:Books.Delete&quot;</span>: <span class="string">&quot;Deleting the books&quot;</span></span><br></pre></td></tr></table></figure><h3 id="权限管理界面"><a href="#权限管理界面" class="headerlink" title="权限管理界面"></a>权限管理界面</h3><p>定义权限后，可以在权限管理模块上看到。</p><p>转到“管理”-&gt;“身份”-&gt;“角色”页面，选择管理员角色的“权限”操作以打开权限管理模式：</p><p>根据所需的权限进行设置并保存。</p><p><img data-src="https://cdn.jonty.top/img/202407231714209.png" alt="permissions"></p><h2 id="授权"><a href="#授权" class="headerlink" title="授权"></a>授权</h2><p>使用权限来对图书管理接口授权。</p><h3 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h3><p>打开 <code>BookAppService</code> 类并将策略名称设置为上面定义的权限名称：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Permissions;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Services;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookAppService</span> :</span><br><span class="line">    <span class="title">CrudAppService</span>&lt;</span><br><span class="line">        <span class="title">Book</span>, //<span class="title">The</span> <span class="title">Book</span> <span class="title">entity</span></span><br><span class="line">        <span class="title">BookDto</span>, //<span class="title">Used</span> <span class="title">to</span> <span class="title">show</span> <span class="title">books</span></span><br><span class="line">        <span class="title">Guid</span>, //<span class="title">Primary</span> <span class="title">key</span> <span class="title">of</span> <span class="title">the</span> <span class="title">book</span> <span class="title">entity</span></span><br><span class="line">        <span class="title">PagedAndSortedResultRequestDto</span>, //<span class="title">Used</span> <span class="title">for</span> <span class="title">paging</span>/<span class="title">sorting</span></span><br><span class="line">        <span class="title">CreateUpdateBookDto</span>&gt;, <span class="comment">//Used to create/update a book</span></span><br><span class="line">    <span class="title">IBookAppService</span> <span class="comment">//implement the IBookAppService</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BookAppService</span>(<span class="params">IRepository&lt;Book, Guid&gt; repository</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">repository</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        GetPolicyName = BookStorePermissions.Books.Default;</span><br><span class="line">        GetListPolicyName = BookStorePermissions.Books.Default;</span><br><span class="line">        CreatePolicyName = BookStorePermissions.Books.Create;</span><br><span class="line">        UpdatePolicyName = BookStorePermissions.Books.Edit;</span><br><span class="line">        DeletePolicyName = BookStorePermissions.Books.Delete;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里我们继承自<code>CrudAppService</code>，会自动对CRUD操作匹配这些权限，这里无需对每个接口进行权限配置。</p><h3 id="权限守卫"><a href="#权限守卫" class="headerlink" title="权限守卫"></a>权限守卫</h3><p>UI的第一步是防止未经授权的用户看到书籍菜单项并进入书籍管理页面。</p><p>在 <code>/src/app/book/book-routing.module.ts</code> 注册：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">import &#123; NgModule &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; RouterModule, Routes &#125; from &#x27;@angular/router&#x27;;</span><br><span class="line">import &#123; BookComponent &#125; from &#x27;./book.component&#x27;;</span><br><span class="line">import &#123; authGuard, permissionGuard &#125; from &#x27;@abp/ng.core&#x27;;</span><br><span class="line"></span><br><span class="line">const routes: Routes = [&#123; path: &#x27;&#x27;, component: BookComponent,canActivate: [authGuard, permissionGuard] &#125;];</span><br><span class="line"></span><br><span class="line">@NgModule(&#123;</span><br><span class="line">  imports: [RouterModule.forChild(routes)],</span><br><span class="line">  exports: [RouterModule]</span><br><span class="line">&#125;)</span><br><span class="line">export class BookRoutingModule &#123; &#125;</span><br></pre></td></tr></table></figure><ul><li>导入authGuard，permissionGuard，并添加到路由定义中</li></ul><p>打开 <code>/src/app/route.provider.ts</code> ，添加菜单权限<code>requiredPolicy</code>，如下：</p><blockquote><p>  requiredPolicy: ‘BookStore.Books’，定义菜单权限</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  path: &#x27;/books&#x27;,</span><br><span class="line">  name: &#x27;::Menu:Books&#x27;,</span><br><span class="line">  parentName: &#x27;::Menu:BookStore&#x27;,</span><br><span class="line">  layout: eLayoutType.application,</span><br><span class="line">  requiredPolicy: &#x27;BookStore.Books&#x27;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="按钮权限"><a href="#按钮权限" class="headerlink" title="按钮权限"></a>按钮权限</h3><p>在页面中使用<code>*abpPermission</code>校验权限</p><p>打开 <code>/src/app/book/book.component.html</code> ，给操作按钮加入权限</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">// Create Btn</span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> *<span class="attr">abpPermission</span>=<span class="string">&quot;&#x27;BookStore.Books.Create&#x27;&quot;</span> <span class="attr">id</span>=<span class="string">&quot;create&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> (<span class="attr">click</span>)=<span class="string">&quot;createBook()&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-plus mr-1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#123; &#x27;::NewBook&#x27; | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"></span><br><span class="line">// Edit Btn</span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> *<span class="attr">abpPermission</span>=<span class="string">&quot;&#x27;BookStore.Books.Edit&#x27;&quot;</span> <span class="attr">ngbDropdownItem</span> (<span class="attr">click</span>)=<span class="string">&quot;editBook(row.id)&quot;</span>&gt;</span></span><br><span class="line">  &#123;&#123; &#x27;::Edit&#x27; | abpLocalization &#125;&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"></span><br><span class="line">// Delete Btn</span><br><span class="line"><span class="tag">&lt;<span class="name">button</span>  *<span class="attr">abpPermission</span>=<span class="string">&quot;&#x27;BookStore.Books.Delete&#x27;&quot;</span> <span class="attr">ngbDropdownItem</span> (<span class="attr">click</span>)=<span class="string">&quot;delete(row.id)&quot;</span>&gt;</span></span><br><span class="line">  &#123;&#123; &#x27;::Delete&#x27; | abpLocalization &#125;&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>当用户没有权限时，按钮则会隐藏</p><p><img data-src="https://cdn.jonty.top/img/202407231733130.png" alt="abpPermission"></p><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 授权</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之集成测试</title>
    <link href="https://jonty.top/2024/06/24/abp-vNext-tutorials-part-4/"/>
    <id>https://jonty.top/2024/06/24/abp-vNext-tutorials-part-4/</id>
    <published>2024-06-24T14:35:21.000Z</published>
    <updated>2024-07-26T08:50:19.442Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2：图书列表页面">Part 2：图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3：创建,更新和删除图书">Part 3：创建,更新和删除图书</a></li><li>Part 4: 集成测试（本节）</li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5：授权">Part 5：授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6：作者: 领域层">Part 6：作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7：作者: 数据库集成">Part 7：作者: 数据库集成</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8：作者: 应用服务层">Part 8：作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9：作者: 用户界面">Part 9：作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10：图书关联作者">Part 10：图书关联作者</a></li></ul><h2 id="解决方案中的测试项目"><a href="#解决方案中的测试项目" class="headerlink" title="解决方案中的测试项目"></a>解决方案中的测试项目</h2><p>在解决方案目录中，<code>test</code>下包含了测试项目</p><p><img data-src="https://cdn.jonty.top/img/202406252306277.png" alt="image-20240625230642200"></p><p>每个项目用于测试相关的应用程序项目，测试项目使用以下库进行测试:</p><ul><li><a href="https://xunit.github.io/">xunit</a> 作为主测试框架</li><li><a href="http://shouldly.readthedocs.io/en/latest/">Shoudly</a> 作为断言库</li><li><a href="http://nsubstitute.github.io/">NSubstitute</a> 作为模拟库</li></ul><h2 id="添加测试数据"><a href="#添加测试数据" class="headerlink" title="添加测试数据"></a>添加测试数据</h2><p>如果已经按照<a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="第一部分">第一部分</a>中的操作创建了数据种子，则相同的数据也在测试中可用。 如果未创建种子数据，可以使用 <code>BookStoreTestDataSeedContributor</code> 为后续的测试准备种子数据</p><h2 id="测试-BookAppService"><a href="#测试-BookAppService" class="headerlink" title="测试 BookAppService"></a>测试 BookAppService</h2><p>在 <code>Acme.BookStore.Application.Tests</code> 项目的 <code>Books</code> 命名空间(文件夹)中创建一个名叫 <code>BookAppService_Tests</code> 的测试类：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Linq;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Shouldly;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Modularity;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Validation;</span><br><span class="line"><span class="keyword">using</span> Xunit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line">&#123; </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">BookAppService_Tests</span>&lt;<span class="title">TStartupModule</span>&gt; : <span class="title">BookStoreApplicationTestBase</span>&lt;<span class="title">TStartupModule</span>&gt;</span><br><span class="line">    <span class="keyword">where</span> <span class="title">TStartupModule</span> : <span class="title">IAbpModule</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IBookAppService _bookAppService;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">BookAppService_Tests</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _bookAppService = GetRequiredService&lt;IBookAppService&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Fact</span>]</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Get_List_Of_Books</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//Act</span></span><br><span class="line">        <span class="keyword">var</span> result = <span class="keyword">await</span> _bookAppService.GetListAsync(</span><br><span class="line">            <span class="keyword">new</span> PagedAndSortedResultRequestDto()</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">//Assert</span></span><br><span class="line">        result.TotalCount.ShouldBeGreaterThan(<span class="number">0</span>);</span><br><span class="line">        result.Items.ShouldContain(b =&gt; b.Name == <span class="string">&quot;1984&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>测试方法 <code>Should_Get_List_Of_Books</code> 直接使用 <code>BookAppService.GetListAsync</code> 方法来获取用户列表，并执行检查</li><li>可以检查 “1984” 这本书是否包含在结果中，因为之前在种子数据中已经创建</li></ul><p>在 <code>Acme.BookStore.EntityFrameworkCore.Tests</code> 项目 <code>EntityFrameworkCore\Applications\Books</code> 文件夹中添加 <code>BookAppService_Tests</code> 的实现类<code>EfCoreBookAppService_Tests.cs</code> ：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> Acme.BookStore.Books;</span><br><span class="line"><span class="keyword">using</span> Xunit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.EntityFrameworkCore.Applications.Books</span>;</span><br><span class="line"></span><br><span class="line">[<span class="meta">Collection(BookStoreTestConsts.CollectionDefinitionName)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">EfCoreBookAppService_Tests</span> : <span class="title">BookAppService_Tests</span>&lt;<span class="title">BookStoreEntityFrameworkCoreTestModule</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>新增测试方法，创建一个<code>Book</code>实体的场景：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Fact</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Create_A_Valid_Book</span>(<span class="params"></span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//Act</span></span><br><span class="line">    <span class="keyword">var</span> result = <span class="keyword">await</span> _bookAppService.CreateAsync(</span><br><span class="line">        <span class="keyword">new</span> CreateUpdateBookDto</span><br><span class="line">        &#123;</span><br><span class="line">            Name = <span class="string">&quot;New test book 42&quot;</span>,</span><br><span class="line">            Price = <span class="number">10</span>,</span><br><span class="line">            PublishDate = DateTime.Now,</span><br><span class="line">            Type = BookType.ScienceFiction</span><br><span class="line">        &#125;</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">//Assert</span></span><br><span class="line">    result.Id.ShouldNotBe(Guid.Empty);</span><br><span class="line">    result.Name.ShouldBe(<span class="string">&quot;New test book 42&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>新增测试方法，创建一个错误<code>book</code>实体失败的场景：</p><ul><li>由于 <code>Name</code> 是空值，ABP 抛出一个 <code>AbpValidationException</code> 异常</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Fact</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Not_Create_A_Book_Without_Name</span>(<span class="params"></span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> exception = <span class="keyword">await</span> Assert.ThrowsAsync&lt;AbpValidationException&gt;(<span class="keyword">async</span> () =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">await</span> _bookAppService.CreateAsync(</span><br><span class="line">            <span class="keyword">new</span> CreateUpdateBookDto</span><br><span class="line">            &#123;</span><br><span class="line">                Name = <span class="string">&quot;&quot;</span>,</span><br><span class="line">                Price = <span class="number">10</span>,</span><br><span class="line">                PublishDate = DateTime.Now,</span><br><span class="line">                Type = BookType.ScienceFiction</span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    exception.ValidationErrors</span><br><span class="line">        .ShouldContain(err =&gt; err.MemberNames.Any(mem =&gt; mem == <span class="string">&quot;Name&quot;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最终的测试类如下：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Linq;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Shouldly;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Validation;</span><br><span class="line"><span class="keyword">using</span> Xunit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span></span><br><span class="line">&#123; </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">BookAppService_Tests</span>&lt;<span class="title">TStartupModule</span>&gt; : <span class="title">BookStoreApplicationTestBase</span>&lt;<span class="title">TStartupModule</span>&gt;</span><br><span class="line">        <span class="keyword">where</span> <span class="title">TStartupModule</span> : <span class="title">IAbpModule</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">readonly</span> IBookAppService _bookAppService;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">BookAppService_Tests</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            _bookAppService = GetRequiredService&lt;IBookAppService&gt;();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        [<span class="meta">Fact</span>]</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Get_List_Of_Books</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//Act</span></span><br><span class="line">            <span class="keyword">var</span> result = <span class="keyword">await</span> _bookAppService.GetListAsync(</span><br><span class="line">                <span class="keyword">new</span> PagedAndSortedResultRequestDto()</span><br><span class="line">            );</span><br><span class="line"></span><br><span class="line">            <span class="comment">//Assert</span></span><br><span class="line">            result.TotalCount.ShouldBeGreaterThan(<span class="number">0</span>);</span><br><span class="line">            result.Items.ShouldContain(b =&gt; b.Name == <span class="string">&quot;1984&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        [<span class="meta">Fact</span>]</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Create_A_Valid_Book</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//Act</span></span><br><span class="line">            <span class="keyword">var</span> result = <span class="keyword">await</span> _bookAppService.CreateAsync(</span><br><span class="line">                <span class="keyword">new</span> CreateUpdateBookDto</span><br><span class="line">                &#123;</span><br><span class="line">                    Name = <span class="string">&quot;New test book 42&quot;</span>,</span><br><span class="line">                    Price = <span class="number">10</span>,</span><br><span class="line">                    PublishDate = DateTime.Now,</span><br><span class="line">                    Type = BookType.ScienceFiction</span><br><span class="line">                &#125;</span><br><span class="line">            );</span><br><span class="line"></span><br><span class="line">            <span class="comment">//Assert</span></span><br><span class="line">            result.Id.ShouldNotBe(Guid.Empty);</span><br><span class="line">            result.Name.ShouldBe(<span class="string">&quot;New test book 42&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        [<span class="meta">Fact</span>]</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">Should_Not_Create_A_Book_Without_Name</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">var</span> exception = <span class="keyword">await</span> Assert.ThrowsAsync&lt;AbpValidationException&gt;(<span class="keyword">async</span> () =&gt;</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">await</span> _bookAppService.CreateAsync(</span><br><span class="line">                    <span class="keyword">new</span> CreateUpdateBookDto</span><br><span class="line">                    &#123;</span><br><span class="line">                        Name = <span class="string">&quot;&quot;</span>,</span><br><span class="line">                        Price = <span class="number">10</span>,</span><br><span class="line">                        PublishDate = DateTime.Now,</span><br><span class="line">                        Type = BookType.ScienceFiction</span><br><span class="line">                    &#125;</span><br><span class="line">                );</span><br><span class="line">            &#125;);</span><br><span class="line"></span><br><span class="line">            exception.ValidationErrors</span><br><span class="line">                .ShouldContain(err =&gt; err.MemberNames.Any(mem =&gt; mem == <span class="string">&quot;Name&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>目录如下：</p><img data-src="https://cdn.jonty.top/img/202406252314984.png" alt="image-20240625231450946" style="zoom:67%;" /><p>打开<strong>测试资源管理器</strong>(测试 -&gt; Windows -&gt; 测试资源管理器)并<strong>执行所有</strong>测试</p><p><img data-src="https://cdn.jonty.top/img/202406252315599.png" alt="image-20240625231511560"></p><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 集成测试</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之增删改</title>
    <link href="https://jonty.top/2024/06/24/abp-vNext-tutorials-part-3/"/>
    <id>https://jonty.top/2024/06/24/abp-vNext-tutorials-part-3/</id>
    <published>2024-06-24T12:40:39.000Z</published>
    <updated>2024-07-26T08:50:14.554Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2： 图书列表页面">Part 2： 图书列表页面</a></li><li>Part 3：创建,更新和删除图书（本节）</li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4：集成测试">Part 4：集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5：授权">Part 5：授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6：作者: 领域层">Part 6：作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7：作者: 数据访问">Part 7：作者: 数据访问</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8：作者: 应用服务层">Part 8：作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9：作者: 用户界面">Part 9：作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10：图书关联作者">Part 10：图书关联作者</a></li></ul><h2 id="创建新书籍"><a href="#创建新书籍" class="headerlink" title="创建新书籍"></a>创建新书籍</h2><p>本节我们完成一个基本的增删改操作</p><img data-src="https://cdn.jonty.top/img/202406242122384.png" alt="book manager" style="width:67%;" /><h3 id="BookComponent"><a href="#BookComponent" class="headerlink" title="BookComponent"></a>BookComponent</h3><p>在<code>/src/app/book/book.component.ts</code>中新增以下内容：</p><ul><li>isModalOpen属性：用于判断模态框的打开关闭</li><li>createBook方法：新增按钮调用方法，打开弹窗</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">import &#123; ListService, PagedResultDto &#125; from &#x27;@abp/ng.core&#x27;;</span><br><span class="line">import &#123; Component, OnInit &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; BookService, BookDto&#125; from &#x27;@proxy/books&#x27;;</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: &#x27;app-book&#x27;,</span><br><span class="line">  templateUrl: &#x27;./book.component.html&#x27;,</span><br><span class="line">  styleUrls: [&#x27;./book.component.scss&#x27;],</span><br><span class="line">  providers: [ListService],</span><br><span class="line">&#125;)</span><br><span class="line">export class BookComponent implements OnInit &#123;</span><br><span class="line">  book = &#123; items: [], totalCount: 0 &#125; as PagedResultDto&lt;BookDto&gt;;</span><br><span class="line"></span><br><span class="line">  // 是否打开弹窗</span><br><span class="line">  isModalOpen = false; </span><br><span class="line"></span><br><span class="line">  constructor(</span><br><span class="line">    public readonly list: ListService, </span><br><span class="line">    private bookService: BookService,</span><br><span class="line">  ) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  ngOnInit() &#123;</span><br><span class="line">    const bookStreamCreator = (query) =&gt; this.bookService.getList(query);</span><br><span class="line"></span><br><span class="line">    this.list.hookToQuery(bookStreamCreator).subscribe((response) =&gt; &#123;</span><br><span class="line">      this.book = response;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  /**</span><br><span class="line">   * 创建图书</span><br><span class="line">   */</span><br><span class="line">  createBook() &#123;</span><br><span class="line">    this.isModalOpen = true;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>打开 <code>/src/app/book/book.component.html</code></p><ul><li>新增创建按钮</li><li>新增模态框</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card-header&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;col col-md-6&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h5</span> <span class="attr">class</span>=<span class="string">&quot;card-title&quot;</span>&gt;</span>&#123;&#123; &#x27;::Menu:Books&#x27; | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">h5</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span>        </span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;text-end col col-md-6&quot;</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 新增按钮在这里！！！ --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;text-lg-end pt-2&quot;</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">&quot;create&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> (<span class="attr">click</span>)=<span class="string">&quot;createBook()&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-plus mr-1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#123; &quot;::NewBook&quot; | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card-body&quot;</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 表格 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 模态框组件 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">abp-modal</span> [(<span class="attr">visible</span>)]=<span class="string">&quot;isModalOpen&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpHeader</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h3</span>&gt;</span>&#123;&#123; &#x27;::NewBook&#x27; | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpBody</span>&gt;</span> <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpFooter</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-secondary&quot;</span> <span class="attr">abpClose</span>&gt;</span></span><br><span class="line">      &#123;&#123; &#x27;::Close&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">abp-modal</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个时候，点击新增按钮弹窗如下：</p><img data-src="https://cdn.jonty.top/img/202406242129742.png" alt="modal" style="width:67%;" /><h3 id="添加弹窗表单"><a href="#添加弹窗表单" class="headerlink" title="添加弹窗表单"></a>添加弹窗表单</h3><p>打开 <code>/src/app/book/book.component.ts</code> 新增以下内容：</p><ul><li>从<code>@angular/forms</code>导入 <code>FormGroup</code>、 <code>FormBuilder</code>、<code>Validators</code></li><li>添加 <code>form: FormGroup</code> 变量</li><li>添加 <code>bookTypes</code> 属性作为 <code>BookType</code> 枚举成员列表，在表单图书类型选项中使用</li><li>注入<code>FormBuilder</code> 到构造函数</li><li>添加 <code>buildForm</code> 方法到文件末尾，在 <code>createBook</code> 方法调用 <code>buildForm()</code> 方法初始化</li><li>添加<code>save</code> 方法，用于保存表单填写数据到后台（发起请求）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line">import &#123; ListService, PagedResultDto &#125; from &#x27;@abp/ng.core&#x27;;</span><br><span class="line">import &#123; Component, OnInit &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; BookService, BookDto, bookTypeOptions &#125; from &#x27;@proxy/books&#x27;; // 导入 bookTypeOptions</span><br><span class="line">import &#123; FormGroup, FormBuilder, Validators &#125; from &#x27;@angular/forms&#x27;; // 导入</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: &#x27;app-book&#x27;,</span><br><span class="line">  templateUrl: &#x27;./book.component.html&#x27;,</span><br><span class="line">  styleUrls: [&#x27;./book.component.scss&#x27;],</span><br><span class="line">  providers: [ListService],</span><br><span class="line">&#125;)</span><br><span class="line">export class BookComponent implements OnInit &#123;</span><br><span class="line">  book = &#123; items: [], totalCount: 0 &#125; as PagedResultDto&lt;BookDto&gt;;</span><br><span class="line"></span><br><span class="line">  form: FormGroup; // 定义form</span><br><span class="line"></span><br><span class="line">  // 图书类型选项</span><br><span class="line">  bookTypes = bookTypeOptions;</span><br><span class="line"></span><br><span class="line">  isModalOpen = false;</span><br><span class="line"></span><br><span class="line">  constructor(</span><br><span class="line">    public readonly list: ListService,</span><br><span class="line">    private bookService: BookService,</span><br><span class="line">    private fb: FormBuilder // 注入 FormBuilder</span><br><span class="line">  ) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  ngOnInit() &#123;</span><br><span class="line">    const bookStreamCreator = (query) =&gt; this.bookService.getList(query);</span><br><span class="line"></span><br><span class="line">    this.list.hookToQuery(bookStreamCreator).subscribe((response) =&gt; &#123;</span><br><span class="line">      this.book = response;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  createBook() &#123;</span><br><span class="line">    this.buildForm(); // 打开模态框 初始化表单</span><br><span class="line">    this.isModalOpen = true;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // 初始化表单</span><br><span class="line">  buildForm() &#123;</span><br><span class="line">    this.form = this.fb.group(&#123;</span><br><span class="line">      name: [&#x27;&#x27;, Validators.required],</span><br><span class="line">      type: [null, Validators.required],</span><br><span class="line">      publishDate: [null, Validators.required],</span><br><span class="line">      price: [null, Validators.required],</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // 新增保存方法</span><br><span class="line">  save() &#123;</span><br><span class="line">    if (this.form.invalid) &#123;</span><br><span class="line">      return;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    this.bookService.create(this.form.value).subscribe(() =&gt; &#123;</span><br><span class="line">      this.isModalOpen = false;</span><br><span class="line">      this.form.reset();</span><br><span class="line">      this.list.get();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>打开 <code>/src/app/book/book.component.html</code>新增以下内容：</p><ul><li>表单</li><li>操作按钮</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">abp-modal</span> [(<span class="attr">visible</span>)]=<span class="string">&quot;isModalOpen&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpHeader</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h3</span>&gt;</span>&#123;&#123; (selectedBook.id ? &#x27;::Edit&#x27; : &#x27;::NewBook&#x27; ) | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpBody</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">form</span> [<span class="attr">formGroup</span>]=<span class="string">&quot;form&quot;</span> (<span class="attr">ngSubmit</span>)=<span class="string">&quot;save()&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;book-name&quot;</span>&gt;</span>Name<span class="tag">&lt;/<span class="name">label</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> * <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">id</span>=<span class="string">&quot;book-name&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">formControlName</span>=<span class="string">&quot;name&quot;</span> <span class="attr">autofocus</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;book-price&quot;</span>&gt;</span>Price<span class="tag">&lt;/<span class="name">label</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> * <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;number&quot;</span> <span class="attr">id</span>=<span class="string">&quot;book-price&quot;</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">formControlName</span>=<span class="string">&quot;price&quot;</span> /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span> <span class="attr">for</span>=<span class="string">&quot;book-type&quot;</span>&gt;</span>Type<span class="tag">&lt;/<span class="name">label</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> * <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">select</span> <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span> <span class="attr">id</span>=<span class="string">&quot;book-type&quot;</span> <span class="attr">formControlName</span>=<span class="string">&quot;type&quot;</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">option</span> [<span class="attr">ngValue</span>]=<span class="string">&quot;null&quot;</span>&gt;</span>Select a book type<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">option</span> [<span class="attr">ngValue</span>]=<span class="string">&quot;type.value&quot;</span> *<span class="attr">ngFor</span>=<span class="string">&quot;let type of bookTypes&quot;</span>&gt;</span>&#123;&#123; type.key &#125;&#125;<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;form-group&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">label</span>&gt;</span>Publish date<span class="tag">&lt;/<span class="name">label</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> * <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">input</span></span></span><br><span class="line"><span class="tag">          #<span class="attr">datepicker</span>=<span class="string">&quot;ngbDatepicker&quot;</span></span></span><br><span class="line"><span class="tag">          <span class="attr">class</span>=<span class="string">&quot;form-control&quot;</span></span></span><br><span class="line"><span class="tag">          <span class="attr">name</span>=<span class="string">&quot;datepicker&quot;</span></span></span><br><span class="line"><span class="tag">          <span class="attr">formControlName</span>=<span class="string">&quot;publishDate&quot;</span></span></span><br><span class="line"><span class="tag">          <span class="attr">ngbDatepicker</span></span></span><br><span class="line"><span class="tag">          (<span class="attr">click</span>)=<span class="string">&quot;datepicker.toggle()&quot;</span></span></span><br><span class="line"><span class="tag">        /&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line"></span><br><span class="line">  <span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpFooter</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;button&quot;</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-secondary&quot;</span> <span class="attr">abpClose</span>&gt;</span></span><br><span class="line">      &#123;&#123; &#x27;::Close&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;btn btn-primary&quot;</span> (<span class="attr">click</span>)=<span class="string">&quot;save()&quot;</span> [<span class="attr">disabled</span>]=<span class="string">&quot;form.invalid&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-check mr-1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">      &#123;&#123; &#x27;::Save&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">abp-modal</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Datepicker"><a href="#Datepicker" class="headerlink" title="Datepicker"></a>Datepicker</h3><p>在这个组件中使用了<code>NgBootstrap datepicker</code>，需要添加与此组件相关的依赖项，打开 <code>/src/app/book/book.module.ts</code> 新增以下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">import &#123; NgModule &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; SharedModule &#125; from &#x27;../shared/shared.module&#x27;;</span><br><span class="line">import &#123; BookRoutingModule &#125; from &#x27;./book-routing.module&#x27;;</span><br><span class="line">import &#123; BookComponent &#125; from &#x27;./book.component&#x27;;</span><br><span class="line">import &#123; NgbDatepickerModule &#125; from &#x27;@ng-bootstrap/ng-bootstrap&#x27;; // 导入</span><br><span class="line"></span><br><span class="line">@NgModule(&#123;</span><br><span class="line">  declarations: [BookComponent],</span><br><span class="line">  imports: [</span><br><span class="line">    BookRoutingModule,</span><br><span class="line">    SharedModule,</span><br><span class="line">    NgbDatepickerModule, // 注册</span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br><span class="line">export class BookModule &#123; &#125;</span><br></pre></td></tr></table></figure><p>在 <code>/src/app/book/book.component.ts</code> 中使用<code>NgbDateAdapter</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">import &#123; ListService, PagedResultDto &#125; from &#x27;@abp/ng.core&#x27;;</span><br><span class="line">import &#123; Component, OnInit &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; BookService, BookDto, bookTypeOptions &#125; from &#x27;@proxy/books&#x27;;</span><br><span class="line">import &#123; FormGroup, FormBuilder, Validators &#125; from &#x27;@angular/forms&#x27;;</span><br><span class="line"></span><br><span class="line">// 导入</span><br><span class="line">import &#123; NgbDateNativeAdapter, NgbDateAdapter &#125; from &#x27;@ng-bootstrap/ng-bootstrap&#x27;;</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: &#x27;app-book&#x27;,</span><br><span class="line">  templateUrl: &#x27;./book.component.html&#x27;,</span><br><span class="line">  styleUrls: [&#x27;./book.component.scss&#x27;],</span><br><span class="line">  providers: [</span><br><span class="line">    ListService,</span><br><span class="line">    &#123; provide: NgbDateAdapter, useClass: NgbDateNativeAdapter &#125; // 提供器</span><br><span class="line">  ],</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>打开弹窗</p><img data-src="https://cdn.jonty.top/img/202406242139676.png" alt="弹窗" style="width:67%;" /><h2 id="更新书籍"><a href="#更新书籍" class="headerlink" title="更新书籍"></a>更新书籍</h2><p>打开 <code>/src/app/book/book.component.ts</code> 新增以下内容：</p><ul><li>声明类型为 <code>BookDto</code> 的 <code>selectedBook</code> 变量，用于存储获取到的Book对象</li><li>添加了<code>editBook</code> 方法，根据选择的书籍 <code>Id</code> 设置 <code>selectedBook</code> 对象</li><li>替换 <code>buildForm</code> 方法使用 <code>selectedBook</code> 数据初始化表单</li><li>替换 <code>createBook</code> 方法，设置 <code>selectedBook</code> 为空</li><li>修改 <code>save</code> 方法，同时处理新增和更新操作</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><span class="line">import &#123; ListService, PagedResultDto &#125; from &#x27;@abp/ng.core&#x27;;</span><br><span class="line">import &#123; Component, OnInit &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; BookService, BookDto, bookTypeOptions &#125; from &#x27;@proxy/books&#x27;;</span><br><span class="line">import &#123; FormGroup, FormBuilder, Validators &#125; from &#x27;@angular/forms&#x27;;</span><br><span class="line">import &#123; NgbDateNativeAdapter, NgbDateAdapter &#125; from &#x27;@ng-bootstrap/ng-bootstrap&#x27;;</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: &#x27;app-book&#x27;,</span><br><span class="line">  templateUrl: &#x27;./book.component.html&#x27;,</span><br><span class="line">  styleUrls: [&#x27;./book.component.scss&#x27;],</span><br><span class="line">  providers: [ListService, &#123; provide: NgbDateAdapter, useClass: NgbDateNativeAdapter &#125;],</span><br><span class="line">&#125;)</span><br><span class="line">export class BookComponent implements OnInit &#123;</span><br><span class="line">  book = &#123; items: [], totalCount: 0 &#125; as PagedResultDto&lt;BookDto&gt;;</span><br><span class="line"></span><br><span class="line">  selectedBook = &#123;&#125; as BookDto; // 定义 selectedBook</span><br><span class="line"></span><br><span class="line">  form: FormGroup;</span><br><span class="line"></span><br><span class="line">  bookTypes = bookTypeOptions;</span><br><span class="line"></span><br><span class="line">  isModalOpen = false;</span><br><span class="line"></span><br><span class="line">  constructor(</span><br><span class="line">    public readonly list: ListService,</span><br><span class="line">    private bookService: BookService,</span><br><span class="line">    private fb: FormBuilder</span><br><span class="line">  ) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  ngOnInit() &#123;</span><br><span class="line">    const bookStreamCreator = (query) =&gt; this.bookService.getList(query);</span><br><span class="line"></span><br><span class="line">    this.list.hookToQuery(bookStreamCreator).subscribe((response) =&gt; &#123;</span><br><span class="line">      this.book = response;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  createBook() &#123;</span><br><span class="line">    this.selectedBook = &#123;&#125; as BookDto; // 创建时将selectedBook置空</span><br><span class="line">    this.buildForm();</span><br><span class="line">    this.isModalOpen = true;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // 编辑书籍根据选择的Id查询Book信息</span><br><span class="line">  editBook(id: string) &#123;</span><br><span class="line">    this.bookService.get(id).subscribe((book) =&gt; &#123;</span><br><span class="line">      this.selectedBook = book; </span><br><span class="line">      this.buildForm();</span><br><span class="line">      this.isModalOpen = true;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  buildForm() &#123;</span><br><span class="line">    // 判断selectedBook是否有值，区分是否编辑或新增</span><br><span class="line">    this.form = this.fb.group(&#123;</span><br><span class="line">      name: [this.selectedBook.name || &#x27;&#x27;, Validators.required],</span><br><span class="line">      type: [this.selectedBook.type || null, Validators.required],</span><br><span class="line">      publishDate: [</span><br><span class="line">        this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null,</span><br><span class="line">        Validators.required,</span><br><span class="line">      ],</span><br><span class="line">      price: [this.selectedBook.price || null, Validators.required],</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // 保存</span><br><span class="line">  save() &#123;</span><br><span class="line">    if (this.form.invalid) &#123;</span><br><span class="line">      return;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    // 判断selectedBook.id是否有值</span><br><span class="line">    // true:更新</span><br><span class="line">    // false:新增</span><br><span class="line">    const request = this.selectedBook.id</span><br><span class="line">      ? this.bookService.update(this.selectedBook.id, this.form.value)</span><br><span class="line">      : this.bookService.create(this.form.value);</span><br><span class="line"></span><br><span class="line">    request.subscribe(() =&gt; &#123;</span><br><span class="line">      this.isModalOpen = false;</span><br><span class="line">      this.form.reset();</span><br><span class="line">      this.list.get();</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="添加表格操作按钮"><a href="#添加表格操作按钮" class="headerlink" title="添加表格操作按钮"></a>添加表格操作按钮</h3><p>打开 <code>/src/app/book/book.component.html</code> 在 <code>ngx-datatable</code> 第一列添加 <code>ngx-datatable-column</code> 定义:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ngx-datatable-column</span></span></span><br><span class="line"><span class="tag">  [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::Actions&#x27; | abpLocalization&quot;</span></span></span><br><span class="line"><span class="tag">  [<span class="attr">maxWidth</span>]=<span class="string">&quot;150&quot;</span></span></span><br><span class="line"><span class="tag">  [<span class="attr">sortable</span>]=<span class="string">&quot;false&quot;</span></span></span><br><span class="line"><span class="tag">&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">ng-template</span> <span class="attr">let-row</span>=<span class="string">&quot;row&quot;</span> <span class="attr">ngx-datatable-cell-template</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">ngbDropdown</span> <span class="attr">container</span>=<span class="string">&quot;body&quot;</span> <span class="attr">class</span>=<span class="string">&quot;d-inline-block&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">button</span></span></span><br><span class="line"><span class="tag">        <span class="attr">class</span>=<span class="string">&quot;btn btn-primary btn-sm dropdown-toggle&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">data-toggle</span>=<span class="string">&quot;dropdown&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">aria-haspopup</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">ngbDropdownToggle</span></span></span><br><span class="line"><span class="tag">      &gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;fa fa-cog mr-1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span>&#123;&#123; &#x27;::Actions&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">ngbDropdownMenu</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">ngbDropdownItem</span> (<span class="attr">click</span>)=<span class="string">&quot;editBook(row.id)&quot;</span>&gt;</span></span><br><span class="line">          &#123;&#123; &#x27;::Edit&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在表格的第一列添加了一个 <code>Actions</code> 下拉菜单，如下图所示：</p><p><img data-src="https://cdn.jonty.top/img/202406242202315.png" alt="action"></p><p>同时如下所示更改 <code>ng-template #abpHeader</code> 部分:</p><ul><li>根据是否有选中的值判断弹窗展示文本</li></ul><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ng-template</span> #<span class="attr">abpHeader</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h3</span>&gt;</span>&#123;&#123; (selectedBook.id ? &#x27;::Edit&#x27; : &#x27;::NewBook&#x27; ) | abpLocalization &#125;&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="删除书籍"><a href="#删除书籍" class="headerlink" title="删除书籍"></a>删除书籍</h2><p>打开 <code>/src/app/book/book.component.ts</code> 注入 <code>ConfirmationService</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">// ...</span><br><span class="line"></span><br><span class="line">// 新增导入</span><br><span class="line">import &#123; ConfirmationService, Confirmation &#125; from &#x27;@abp/ng.theme.shared&#x27;;</span><br><span class="line"></span><br><span class="line">constructor(</span><br><span class="line">  public readonly list: ListService,</span><br><span class="line">  private bookService: BookService,</span><br><span class="line">  private fb: FormBuilder,</span><br><span class="line">  private confirmation: ConfirmationService // 注入</span><br><span class="line">) &#123;&#125;</span><br><span class="line"></span><br><span class="line">// 新增删除方法</span><br><span class="line">delete(id: string) &#123;</span><br><span class="line">  this.confirmation.warn(&#x27;::AreYouSureToDelete&#x27;, &#x27;::AreYouSure&#x27;).subscribe((status) =&gt; &#123;</span><br><span class="line">    if (status === Confirmation.Status.confirm) &#123;</span><br><span class="line">      this.bookService.delete(id).subscribe(() =&gt; this.list.get());</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="添加删除按钮"><a href="#添加删除按钮" class="headerlink" title="添加删除按钮"></a>添加删除按钮</h3><p>打开 <code>/src/app/book/book.component.html</code> 修改 <code>ngbDropdownMenu</code> 添加删除按钮</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">ngbDropdownMenu</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- add the Delete button --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">ngbDropdownItem</span> (<span class="attr">click</span>)=<span class="string">&quot;delete(row.id)&quot;</span>&gt;</span></span><br><span class="line">        &#123;&#123; &#x27;::Delete&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p><img data-src="https://cdn.jonty.top/img/202406242202315.png" alt="action"></p><p>点击 <code>delete</code> 操作调用 <code>delete</code> 方法，然后显示一个确认弹层如下图所示：</p><p><img data-src="https://cdn.jonty.top/img/202406242209267.png" alt="confirm"></p><p>至此就完成了对Book的增删改操作</p><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 增删改</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之页面</title>
    <link href="https://jonty.top/2024/06/23/abp-vNext-tutorials-part-2/"/>
    <id>https://jonty.top/2024/06/23/abp-vNext-tutorials-part-2/</id>
    <published>2024-06-23T06:51:38.000Z</published>
    <updated>2024-07-26T08:50:10.878Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li><a href="/2024/06/19/abp-vNext-tutorials-part-1/" title="Part1：创建服务端">Part1：创建服务端</a></li><li>Part 2：图书列表页面（本节）</li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3: 创建,更新和删除图书">Part 3: 创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5: 授权">Part 5: 授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6: 作者: 领域层">Part 6: 作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7: 作者: 数据访问">Part 7: 作者: 数据访问</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8: 作者: 应用服务层">Part 8: 作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9: 作者: 用户界面">Part 9: 作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10: 图书关联作者">Part 10: 图书关联作者</a></li></ul><h2 id="本地化"><a href="#本地化" class="headerlink" title="本地化"></a>本地化</h2><p>配置本地化多语言，位于<code>.Domain.Shared</code>项目<code>Localization/BookStore</code>目录下：</p><p>编辑<code>zh-Hans.json</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;culture&quot;</span>: <span class="string">&quot;zh-Hans&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;texts&quot;</span>: &#123;</span><br><span class="line">    <span class="attr">&quot;Menu:Home&quot;</span>: <span class="string">&quot;首页&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Welcome&quot;</span>: <span class="string">&quot;欢迎&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;LongWelcomeMessage&quot;</span>: <span class="string">&quot;欢迎使用本应用程序。这是一个基于 ABP 框架的启动项目。更多信息，请访问 abp.io。&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Menu:BookStore&quot;</span>: <span class="string">&quot;书店&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Menu:Books&quot;</span>: <span class="string">&quot;书籍&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Actions&quot;</span>: <span class="string">&quot;操作&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Close&quot;</span>: <span class="string">&quot;关闭&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Delete&quot;</span>: <span class="string">&quot;删除&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Edit&quot;</span>: <span class="string">&quot;编辑&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;PublishDate&quot;</span>: <span class="string">&quot;发布日志&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;NewBook&quot;</span>: <span class="string">&quot;新的书籍&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Name&quot;</span>: <span class="string">&quot;名称&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Type&quot;</span>: <span class="string">&quot;类型&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Price&quot;</span>: <span class="string">&quot;价格&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;CreationTime&quot;</span>: <span class="string">&quot;创建时间&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;AreYouSure&quot;</span>: <span class="string">&quot;是否确认?&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;AreYouSureToDelete&quot;</span>: <span class="string">&quot;是否确认删除?&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.0&quot;</span>: <span class="string">&quot;未定义&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.1&quot;</span>: <span class="string">&quot;冒险&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.2&quot;</span>: <span class="string">&quot;传记&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.3&quot;</span>: <span class="string">&quot;乌托邦&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.4&quot;</span>: <span class="string">&quot;奇幻&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.5&quot;</span>: <span class="string">&quot;恐怖&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.6&quot;</span>: <span class="string">&quot;科学&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.7&quot;</span>: <span class="string">&quot;科幻&quot;</span>,</span><br><span class="line">    <span class="attr">&quot;Enum:BookType.8&quot;</span>: <span class="string">&quot;诗歌&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过修改<code>DomainSharedModule.cs</code>配置默认语言</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreDomainSharedModule</span> : <span class="title">AbpModule</span></span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">ConfigureServices</span>(<span class="params">ServiceConfigurationContext context</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        Configure&lt;AbpLocalizationOptions&gt;(options =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            options.Resources</span><br><span class="line">                .Add&lt;BookStoreResource&gt;(<span class="string">&quot;zh-Hans&quot;</span>)</span><br><span class="line">                .AddBaseTypes(<span class="keyword">typeof</span>(AbpValidationResource))</span><br><span class="line">                .AddVirtualJson(<span class="string">&quot;/Localization/BookStore&quot;</span>);</span><br><span class="line"></span><br><span class="line">            options.DefaultResourceType = <span class="keyword">typeof</span>(BookStoreResource);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>本地化多语言的关键字Key可以是任意的，但是对于的特定的文本类型，ABP有如下约定：</p><ul><li>为按钮项添加<code>Menu:</code>前缀</li><li>使用 <code>Enum:&lt;enum-type&gt;:&lt;enum-name&gt;</code> 或 <code>&lt;enum-type&gt;.&lt;enum-name&gt;</code> 或 <code>&lt;enum-name&gt;</code> 命名约定来本地化枚举成员，ABP会自动将枚举本地化</li></ul><p>如果没有在本地化多语言文件中定义文本，将直接显示本地化键Key</p><h2 id="安装NPM包"><a href="#安装NPM包" class="headerlink" title="安装NPM包"></a>安装NPM包</h2><p>在 <code>angular</code> 目录下打开命令行窗口,选择 <code>yarn</code> 命令安装NPM包:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn</span><br></pre></td></tr></table></figure><p>使用以下命令启动前端：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">➜ yarn start</span><br><span class="line">yarn run v1.22.19</span><br><span class="line">$ ng serve --open</span><br><span class="line">✔ Browser application bundle generation complete.</span><br><span class="line"></span><br><span class="line">Initial Chunk Files </span><br></pre></td></tr></table></figure><h2 id="创建图书页面"><a href="#创建图书页面" class="headerlink" title="创建图书页面"></a>创建图书页面</h2><p>开发ABP Angular前端应用程序时，需要使用一些工具:</p><ul><li><a href="https://ng-bootstrap.github.io/#/home">Ng Bootstrap</a> 用做UI组件库</li><li><a href="https://swimlane.gitbook.io/ngx-datatable/">ngx-datatable</a> 用做 datatable 类库</li></ul><p>运行以下命令在angular应用程序根目录创建一个名为 <code>BookModule</code> 的新模块:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn ng generate module book --module app --routing --route books</span><br></pre></td></tr></table></figure><p>命令输出：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">➜ yarn ng generate module book --module app --routing --route books</span><br><span class="line">&gt; </span><br><span class="line">yarn run v1.22.19</span><br><span class="line"><span class="built_in">$</span> ng generate module book --module app --routing --route books</span><br><span class="line">CREATE src/app/book/book-routing.module.ts (346 bytes)</span><br><span class="line">CREATE src/app/book/book.module.ts (358 bytes)</span><br><span class="line">CREATE src/app/book/book.component.html (20 bytes)</span><br><span class="line">CREATE src/app/book/book.component.spec.ts (610 bytes)</span><br><span class="line">CREATE src/app/book/book.component.ts (202 bytes)</span><br><span class="line">CREATE src/app/book/book.component.scss (0 bytes)</span><br><span class="line">UPDATE src/app/app-routing.module.ts (1017 bytes)</span><br><span class="line">Done in 1.07s.</span><br></pre></td></tr></table></figure><p><img data-src="https://cdn.jonty.top/img/202406231846285.png" alt="image-20240623184420790"></p><h3 id="BookModule"><a href="#BookModule" class="headerlink" title="BookModule"></a>BookModule</h3><p>打开 <code>/src/app/book/book.module.ts</code> 并做如下调整：</p><ul><li>添加了 <code>SharedModule</code>，<code>SharedModule</code> 导出了一些创建用户界面所需的通用模块</li><li><code>SharedModule</code> 已经导出了 <code>CommonModule</code>，所以删除了 <code>CommonModule</code></li></ul><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">import &#123; NgModule &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line"><span class="deletion">-- import &#123; CommonModule &#125; from &#x27;@angular/common&#x27;;</span></span><br><span class="line"><span class="addition">++ import &#123; SharedModule &#125; from &#x27;../shared/shared.module&#x27;;</span></span><br><span class="line"></span><br><span class="line">import &#123; BookRoutingModule &#125; from &#x27;./book-routing.module&#x27;;</span><br><span class="line">import &#123; BookComponent &#125; from &#x27;./book.component&#x27;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">@NgModule(&#123;</span><br><span class="line">  declarations: [</span><br><span class="line">    BookComponent</span><br><span class="line">  ],</span><br><span class="line">  imports: [</span><br><span class="line"><span class="deletion">--  CommonModule,</span></span><br><span class="line"><span class="addition">++ SharedModule</span></span><br><span class="line">    BookRoutingModule</span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br><span class="line">export class BookModule &#123; &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="路由"><a href="#路由" class="headerlink" title="路由"></a>路由</h3><p>生成的代码将新的路由定义放在 <code>src/app/app-routing.module.ts</code> 文件中，如下所示:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">const routes: Routes = [</span><br><span class="line">  // other route definitions...</span><br><span class="line">  &#123; path: &#x27;books&#x27;, loadChildren: () =&gt; import(&#x27;./book/book.module&#x27;).then(m =&gt; m.BookModule) &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><p>打开 <code>src/app/route.provider.ts</code> 替换 <code>configureRoutes</code> 函数为以下代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">function configureRoutes(routes: RoutesService) &#123;</span><br><span class="line">  return () =&gt; &#123;</span><br><span class="line">    routes.add([</span><br><span class="line">      &#123;</span><br><span class="line">        path: &#x27;/&#x27;,</span><br><span class="line">        name: &#x27;::Menu:Home&#x27;,</span><br><span class="line">        iconClass: &#x27;fas fa-home&#x27;,</span><br><span class="line">        order: 1,</span><br><span class="line">        layout: eLayoutType.application,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        path: &#x27;/book-store&#x27;,</span><br><span class="line">        name: &#x27;::Menu:BookStore&#x27;,</span><br><span class="line">        iconClass: &#x27;fas fa-book&#x27;,</span><br><span class="line">        order: 2,</span><br><span class="line">        layout: eLayoutType.application,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        path: &#x27;/books&#x27;,</span><br><span class="line">        name: &#x27;::Menu:Books&#x27;,</span><br><span class="line">        parentName: &#x27;::Menu:BookStore&#x27;,</span><br><span class="line">        layout: eLayoutType.application,</span><br><span class="line">      &#125;,</span><br><span class="line">    ]);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>RoutesService</code> 是ABP框架提供的用于配置主菜单和路由的服务</p><ul><li><code>path</code>： 路由的URL</li><li><code>name</code> ：菜单项的名称</li><li><code>iconClass</code>： 菜单项的图标</li><li><code>order</code> ：菜单项的排序</li><li><code>layout</code> ：路由的布局(有三个预定义的布局类型: <code>eLayoutType.application</code>，<code>eLayoutType.account</code> 或 <code>eLayoutType.empty</code>)</li></ul><h3 id="生成服务代理"><a href="#生成服务代理" class="headerlink" title="生成服务代理"></a>生成服务代理</h3><p><a href="https://docs.abp.io/zh-Hans/abp/latest/CLI">ABP CLI</a> 提供 <code>generate-proxy</code> 命令为HTTP APIs生成客户端代理类，生成代理后，在客户端使用HTTP APIs会更方便</p><p> 运行 <code>generate-proxy</code> 命令前，请先运行<code>HostApi</code></p><blockquote><p><strong>警告</strong>: 使用IIS Express时有一个问题；它不允许从另一个进程连接应用程序，如果使用Visual Studio,，在运行按钮的下拉框中选择<code>Acme.BookStore.HttpApi.Host</code>，不要选择IIS Express，如下图：</p></blockquote><p><img data-src="https://cdn.jonty.top/img/202406231854967.png" alt="image-20240623185429925"></p><p>启动<code>Host</code>后，在<code>angular</code>目录运行命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">abp generate-proxy -t ng</span><br></pre></td></tr></table></figure><p>命令输出如下：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">➜ abp generate-proxy -t ng</span><br><span class="line">ABP CLI 8.1.4</span><br><span class="line">CREATE src/app/proxy/generate-proxy.json (311016 bytes)</span><br><span class="line">CREATE src/app/proxy/README.md (1000 bytes)</span><br><span class="line">CREATE src/app/proxy/books/book.service.ts (1715 bytes)</span><br><span class="line">CREATE src/app/proxy/books/models.ts (375 bytes)</span><br><span class="line">CREATE src/app/proxy/books/book-type.enum.ts (299 bytes)</span><br><span class="line">CREATE src/app/proxy/books/index.ts (92 bytes)</span><br><span class="line">CREATE src/app/proxy/index.ts (52 bytes)</span><br></pre></td></tr></table></figure><p>可以看到，该命令在<code>/src/app/proxy/books</code>文件夹下生成以下文件：</p><p><img data-src="https://cdn.jonty.top/img/202406231856965.png" alt="image-20240623185641930"></p><h3 id="BookComponent"><a href="#BookComponent" class="headerlink" title="BookComponent"></a>BookComponent</h3><p>编辑 <code>/src/app/book/book.component.ts</code> </p><ul><li>引入并注入了生成的 <code>BookService</code>.</li><li>使用 <a href="https://docs.abp.io/zh-Hans/abp/latest/UI/Angular/List-Service">ListService</a>，它是一个工具服务，提供了易用的分页，排序和搜索</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">import &#123; ListService, PagedResultDto &#125; from &#x27;@abp/ng.core&#x27;;</span><br><span class="line">import &#123; Component, OnInit &#125; from &#x27;@angular/core&#x27;;</span><br><span class="line">import &#123; BookService, BookDto &#125; from &#x27;@proxy/books&#x27;;</span><br><span class="line"></span><br><span class="line">@Component(&#123;</span><br><span class="line">  selector: &#x27;app-book&#x27;,</span><br><span class="line">  templateUrl: &#x27;./book.component.html&#x27;,</span><br><span class="line">  styleUrls: [&#x27;./book.component.scss&#x27;],</span><br><span class="line">  providers: [ListService],</span><br><span class="line">&#125;)</span><br><span class="line">export class BookComponent implements OnInit &#123;</span><br><span class="line">  book = &#123; items: [], totalCount: 0 &#125; as PagedResultDto&lt;BookDto&gt;;</span><br><span class="line"></span><br><span class="line">  constructor(public readonly list: ListService, private bookService: BookService) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  ngOnInit() &#123;</span><br><span class="line">    const bookStreamCreator = (query) =&gt; this.bookService.getList(query);</span><br><span class="line"></span><br><span class="line">    this.list.hookToQuery(bookStreamCreator).subscribe((response) =&gt; &#123;</span><br><span class="line">      this.book = response;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编辑 <code>/src/app/book/book.component.html</code> </p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card-header&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;col col-md-6&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h5</span> <span class="attr">class</span>=<span class="string">&quot;card-title&quot;</span>&gt;</span></span><br><span class="line">          &#123;&#123; &#x27;::Menu:Books&#x27; | abpLocalization &#125;&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">h5</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;text-end col col-md-6&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;card-body&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ngx-datatable</span> [<span class="attr">rows</span>]=<span class="string">&quot;book.items&quot;</span> [<span class="attr">count</span>]=<span class="string">&quot;book.totalCount&quot;</span> [<span class="attr">list</span>]=<span class="string">&quot;list&quot;</span> <span class="attr">default</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">ngx-datatable-column</span> [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::Name&#x27; | abpLocalization&quot;</span> <span class="attr">prop</span>=<span class="string">&quot;name&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">ngx-datatable-column</span> [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::Type&#x27; | abpLocalization&quot;</span> <span class="attr">prop</span>=<span class="string">&quot;type&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ng-template</span> <span class="attr">let-row</span>=<span class="string">&quot;row&quot;</span> <span class="attr">ngx-datatable-cell-template</span>&gt;</span></span><br><span class="line">          &#123;&#123; &#x27;::Enum:BookType.&#x27; + row.type | abpLocalization &#125;&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">ngx-datatable-column</span> [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::PublishDate&#x27; | abpLocalization&quot;</span> <span class="attr">prop</span>=<span class="string">&quot;publishDate&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ng-template</span> <span class="attr">let-row</span>=<span class="string">&quot;row&quot;</span> <span class="attr">ngx-datatable-cell-template</span>&gt;</span></span><br><span class="line">          &#123;&#123; row.publishDate | date &#125;&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">ngx-datatable-column</span> [<span class="attr">name</span>]=<span class="string">&quot;&#x27;::Price&#x27; | abpLocalization&quot;</span> <span class="attr">prop</span>=<span class="string">&quot;price&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ng-template</span> <span class="attr">let-row</span>=<span class="string">&quot;row&quot;</span> <span class="attr">ngx-datatable-cell-template</span>&gt;</span></span><br><span class="line">          &#123;&#123; row.price | currency &#125;&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">ng-template</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">ngx-datatable-column</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ngx-datatable</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>查看页面</p><p><img data-src="https://cdn.jonty.top/img/202406231919086.png" alt="image-20240623191950019"></p><p>到这里我们就完成了前端启动和开发</p><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 创建页面</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 应用开发系列之服务端</title>
    <link href="https://jonty.top/2024/06/19/abp-vNext-tutorials-part-1/"/>
    <id>https://jonty.top/2024/06/19/abp-vNext-tutorials-part-1/</id>
    <published>2024-06-19T14:23:23.000Z</published>
    <updated>2024-07-26T08:50:07.124Z</updated>
    
    <content type="html"><![CDATA[<p>在系列教程中，我们会构建一个基于ABP的Web应用程序，用于管理书籍及其作者列表</p><p>使用到的技术：</p><ul><li>Entity Framework Core</li><li>Angular</li></ul><p>本教程分为以下部分：</p><ul><li>Part1：创建服务端（本节）</li><li><a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="Part 2: 图书列表页面">Part 2: 图书列表页面</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-3/" title="Part 3: 创建,更新和删除图书">Part 3: 创建,更新和删除图书</a></li><li><a href="/2024/06/24/abp-vNext-tutorials-part-4/" title="Part 4: 集成测试">Part 4: 集成测试</a></li><li><a href="/2024/06/27/abp-vNext-tutorials-part-5/" title="Part 5: 授权">Part 5: 授权</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-6/" title="Part 6: 作者: 领域层">Part 6: 作者: 领域层</a></li><li><a href="/2024/06/28/abp-vNext-tutorials-part-7/" title="Part 7: 作者: 数据访问">Part 7: 作者: 数据访问</a></li><li><a href="/2024/06/29/abp-vNext-tutorials-part-8/" title="Part 8: 作者: 应用服务层">Part 8: 作者: 应用服务层</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-9/" title="Part 9: 作者: 用户界面">Part 9: 作者: 用户界面</a></li><li><a href="/2024/06/30/abp-vNext-tutorials-part-10/" title="Part 10: 图书关联作者">Part 10: 图书关联作者</a></li></ul><h2 id="创建解决方案"><a href="#创建解决方案" class="headerlink" title="创建解决方案"></a>创建解决方案</h2><p>在开始开发之前，请先按照<a href="/2024/06/19/getting-start-abp/" title="让项目跑起来">让项目跑起来</a>创建项目</p><h2 id="创建Book实体"><a href="#创建Book实体" class="headerlink" title="创建Book实体"></a>创建Book实体</h2><p>项目中的<strong>领域层</strong>分为两个项目：</p><ul><li><code>Acme.BookStore.Domain</code>：实体、领域服务</li><li><code>Acme.BookStore.Domain.Shared</code>：共享的常量、枚举</li></ul><blockquote><p>这里相较于原有<code>ASP.NET Boilerplate</code>多了<code>.Domian.Shared</code>项目，相当于向上抽取一层，实际上是领域服务的一部分，其他项目都会使用到，该项目不依赖解决方案中的其他项目，其他项目直接或间接依赖该项目。</p><p>例如 <code>BookType</code> 枚举和 <code>BookConsts</code> 类 (可能是 <code>Book</code> 实体用到的常数字段,像<code>MaxNameLength</code>)都适合放在这个项目中。</p></blockquote><p>在领域层中定义实体，在<code>.Domain</code>项目中创建一个Books文件夹，添加一个<code>Book</code>的类</p><p><img data-src="https://cdn.jonty.top/img/202406231242055.png" alt="Book"></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.ComponentModel.DataAnnotations.Schema;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Entities.Auditing;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Book</span>: <span class="title">AuditedAggregateRoot</span>&lt;<span class="title">Guid</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> BookType Type &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> DateTime PublishDate &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">float</span> Price &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ABP为实体提供了两个基类：AggregateRoot和Entity</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> AggregateRoot.cs</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">AggregateRoot</span>&lt;<span class="title">TKey</span>&gt; : <span class="title">BasicAggregateRoot</span>&lt;<span class="title">TKey</span>&gt;,</span><br><span class="line">    <span class="title">IHasExtraProperties</span>,</span><br><span class="line">    <span class="title">IHasConcurrencyStamp</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">virtual</span> ExtraPropertyDictionary ExtraProperties &#123; <span class="keyword">get</span>; <span class="keyword">protected</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">DisableAuditing</span>]</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">virtual</span> <span class="built_in">string</span> ConcurrencyStamp &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">AggregateRoot</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        ConcurrencyStamp = Guid.NewGuid().ToString(<span class="string">&quot;N&quot;</span>);</span><br><span class="line">        ExtraProperties = <span class="keyword">new</span> ExtraPropertyDictionary();</span><br><span class="line">        <span class="keyword">this</span>.SetDefaultsForExtraProperties();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">AggregateRoot</span>(<span class="params">TKey id</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">id</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        ConcurrencyStamp = Guid.NewGuid().ToString(<span class="string">&quot;N&quot;</span>);</span><br><span class="line">        ExtraProperties = <span class="keyword">new</span> ExtraPropertyDictionary();</span><br><span class="line">        <span class="keyword">this</span>.SetDefaultsForExtraProperties();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> Entity.cs</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">Entity</span>&lt;<span class="title">TKey</span>&gt; : <span class="title">Entity</span>, <span class="title">IEntity</span>&lt;<span class="title">TKey</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;inheritdoc/&gt;</span></span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">virtual</span> TKey Id &#123; <span class="keyword">get</span>; <span class="keyword">protected</span> <span class="keyword">set</span>; &#125; = <span class="literal">default</span>!;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">Entity</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">Entity</span>(<span class="params">TKey id</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Id = id;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Aggregate Root</strong>是领域驱动设计的一个概念，可视为可直接查询和处理的根实体，<code>AggregateRoot</code> 类实现了 <code>IHasExtraProperties</code> 和 <code>IHasConcurrencyStamp</code> 接口,这为派生类带来了两个属性 <code>IHasExtraProperties</code>和 <code>IHasConcurrencyStamp</code> </p><ul><li><code>IHasExtraProperties</code>： 使实体可扩展 </li><li><code>IHasConcurrencyStamp</code>：添加了由ABP框架管理的 <code>ConcurrencyStamp</code> 属性实现<a href="https://docs.microsoft.com/zh-cn/ef/core/saving/concurrency">乐观并发</a></li></ul><blockquote><p><code>ConcurrencyStamp</code>通过比较前后不同的Token值校验一致性</p><p>如果不需要这些功能，聚合根可以继承 <code>BasicAggregateRoot&lt;TKey&gt;</code>(或<code>BasicAggregateRoot</code>)</p></blockquote><p>Book实体继承<code>AuditedAggregateRoot</code>类在<code>AggregateRoot</code>的基础上新增了基础审计信息，如CreationTime、CreatorId、LastModificationTime等，框架自动处理这些审计信息，<code>AuditedAggregateRoot&lt;Guid&gt;</code>是泛型的，<code>&lt;Guid&gt;</code>定义了实体的主键类型</p><h3 id="BookType枚举"><a href="#BookType枚举" class="headerlink" title="BookType枚举"></a>BookType枚举</h3><p>Book实体引用了<code>BookType</code>枚举类，在<code>.Domain.Shared</code>项目中创建<code>Books</code>文件夹，并创建<code>BookType</code>枚举类</p><p><img data-src="https://cdn.jonty.top/img/202406231312953.png" alt="BookType"></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 书籍类型</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">enum</span> BookType</span><br><span class="line">&#123;</span><br><span class="line">    Undefined,</span><br><span class="line">    Adventure,</span><br><span class="line">    Biography,</span><br><span class="line">    Dystopia,</span><br><span class="line">    Fantastic,</span><br><span class="line">    Horror,</span><br><span class="line">    Science,</span><br><span class="line">    ScienceFiction,</span><br><span class="line">    Poetry</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最终文件目录如下：</p><p><img data-src="https://cdn.jonty.top/img/202406231313028.png" alt="领域层目录"></p><h3 id="实体注册"><a href="#实体注册" class="headerlink" title="实体注册"></a>实体注册</h3><p>EF Core需要将实体注册到<code>DbContext</code>中，在<code>.EntityFrameworkCore</code>项目<code>BookStoreDbContext.cs</code>中注册</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreDbContext</span> :</span><br><span class="line">    <span class="title">AbpDbContext</span>&lt;<span class="title">BookStoreDbContext</span>&gt;,</span><br><span class="line">    <span class="title">IIdentityDbContext</span>,</span><br><span class="line">    <span class="title">ITenantManagementDbContext</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* Add DbSet properties for your Aggregate Roots / Entities here. */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> DbSet&lt;Book&gt; Books &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>其他的类注册都来自于模块中</p></blockquote><h3 id="配置实体映射"><a href="#配置实体映射" class="headerlink" title="配置实体映射"></a>配置实体映射</h3><p>在<code>OnModelCreating</code>方法中配置实体映射代码</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.EntityFrameworkCore</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreDbContext</span> :</span><br><span class="line">    <span class="title">AbpDbContext</span>&lt;<span class="title">BookStoreDbContext</span>&gt;,</span><br><span class="line">    <span class="title">IIdentityDbContext</span>,</span><br><span class="line">    <span class="title">ITenantManagementDbContext</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">OnModelCreating</span>(<span class="params">ModelBuilder builder</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">base</span>.OnModelCreating(builder);</span><br><span class="line">        <span class="comment">//builder.Entity&lt;YourEntity&gt;(b =&gt;</span></span><br><span class="line">        <span class="comment">//&#123;</span></span><br><span class="line">        <span class="comment">//    b.ToTable(BookStoreConsts.DbTablePrefix + &quot;YourEntities&quot;, BookStoreConsts.DbSchema);</span></span><br><span class="line">        <span class="comment">//    b.ConfigureByConvention(); //auto configure for the base class props</span></span><br><span class="line">        <span class="comment">//    //...</span></span><br><span class="line">        <span class="comment">//&#125;);</span></span><br><span class="line"></span><br><span class="line">        builder.Entity&lt;Book&gt;(b =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            b.ToTable(BookStoreConsts.DbTablePrefix + <span class="string">&quot;Books&quot;</span>, BookStoreConsts.DbSchema);</span><br><span class="line">            b.ConfigureByConvention(); <span class="comment">//auto configure for the base class props</span></span><br><span class="line">            b.Property(x =&gt; x.Name).IsRequired().HasMaxLength(<span class="number">128</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>BookStoreConsts</code>：定义系统常量值，这里定义<code>DbTablePrefix</code>用于配置映射到数据库表的前缀，不强制使用，建议在统一的地方控制，保持一致性且易于后期维护</li><li><code>ConfigureByConvention</code>：自动配置映射关系，通过依赖约定配置，减少了手动配置的繁琐工作</li></ul><blockquote><p>为什么不直接配置实体字段属性？</p><p>ABP框架使用Fluent API配置映射关系是为了实现更好的解耦、灵活性和可维护性。通过集中管理映射配置，避免代码污染，并支持复杂映射关系，Fluent API为开发者提供了强大的工具来处理实体与数据库之间的映射。</p></blockquote><h3 id="添加数据迁移"><a href="#添加数据迁移" class="headerlink" title="添加数据迁移"></a>添加数据迁移</h3><p>新增实体或修改数据库映射配置后，需要创建一个新的迁移并更新到数据库，以保证实体与表字段保持一致。</p><p>两种方式新增数据库迁移：</p><p>1、终端命令行，右键<code>.EntityFrameworkCore</code>项目，选择在终端中打开</p><p><img data-src="https://cdn.jonty.top/img/202406231332144.png" alt="右键打开终端"></p><p>输入以下命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet ef migrations add Created_Book_Entity</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Acme.BookStore.EntityFrameworkCore&gt; dotnet ef migrations add Created_Book_Entity</span><br><span class="line">Build started...</span><br><span class="line">Build succeeded.</span><br><span class="line">The Entity Framework tools version <span class="string">&#x27;6.0.8&#x27;</span> is older than that of the runtime <span class="string">&#x27;8.0.0&#x27;</span>. Update the tools <span class="keyword">for</span> the latest features and bug fixes. See https://aka.ms/AAc1fbw <span class="keyword">for</span> more information.</span><br><span class="line">Done. To undo this action, use <span class="string">&#x27;ef migrations remove&#x27;</span></span><br></pre></td></tr></table></figure><p>2、使用程序包管理控制台（PMC）</p><p>打开程序包管理控制台，并选择项目为<code>.EntityFrameworkCore</code>项目</p><img data-src="https://cdn.jonty.top/img/202406231333426.png" alt="设置PMC默认项目" style="width:67%;" /><p>输入命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Add-Migration Created_Book_Entity</span><br></pre></td></tr></table></figure><blockquote><p>第二种方式需要在启动项目中新增<code>Microsoft.EntityFrameworkCore.Design</code>包</p></blockquote><p><img data-src="https://cdn.jonty.top/img/202406231341540.png" alt="迁移文件"></p><h3 id="种子数据"><a href="#种子数据" class="headerlink" title="种子数据"></a>种子数据</h3><p>在<code>.Domain</code>项目中创建<code>BookStoreDataSeederContributor.cs</code>，继承自<code>IDataSeedContributor</code>，代码如下：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Threading.Tasks;</span><br><span class="line"><span class="keyword">using</span> Acme.BookStore.Books;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Data;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.DependencyInjection;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreDataSeederContributor</span></span><br><span class="line">        : <span class="title">IDataSeedContributor</span>, <span class="title">ITransientDependency</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">readonly</span> IRepository&lt;Book, Guid&gt; _bookRepository;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">BookStoreDataSeederContributor</span>(<span class="params">IRepository&lt;Book, Guid&gt; bookRepository</span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            _bookRepository = bookRepository;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> Task <span class="title">SeedAsync</span>(<span class="params">DataSeedContext context</span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">await</span> _bookRepository.GetCountAsync() &lt;= <span class="number">0</span>)</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="keyword">await</span> _bookRepository.InsertAsync(</span><br><span class="line">                    <span class="keyword">new</span> Book</span><br><span class="line">                    &#123;</span><br><span class="line">                        Name = <span class="string">&quot;三体&quot;</span>,</span><br><span class="line">                        Type = BookType.Dystopia,</span><br><span class="line">                        PublishDate = <span class="keyword">new</span> DateTime(<span class="number">1949</span>, <span class="number">6</span>, <span class="number">8</span>),</span><br><span class="line">                        Price = <span class="number">19.84f</span></span><br><span class="line">                    &#125;,</span><br><span class="line">                    autoSave: <span class="literal">true</span></span><br><span class="line">                );</span><br><span class="line"></span><br><span class="line">                <span class="keyword">await</span> _bookRepository.InsertAsync(</span><br><span class="line">                    <span class="keyword">new</span> Book</span><br><span class="line">                    &#123;</span><br><span class="line">                        Name = <span class="string">&quot;The Hitchhiker&#x27;s Guide to the Galaxy&quot;</span>,</span><br><span class="line">                        Type = BookType.ScienceFiction,</span><br><span class="line">                        PublishDate = <span class="keyword">new</span> DateTime(<span class="number">1995</span>, <span class="number">9</span>, <span class="number">27</span>),</span><br><span class="line">                        Price = <span class="number">42.0f</span></span><br><span class="line">                    &#125;,</span><br><span class="line">                    autoSave: <span class="literal">true</span></span><br><span class="line">                );</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>如果数据库表中没有图书数据则创建种子数据，使用IRepository&lt;Book, Guid&gt;</p><p>在运行应用程序之前最好将初始数据添加到数据库中</p></blockquote><h3 id="更新数据库"><a href="#更新数据库" class="headerlink" title="更新数据库"></a>更新数据库</h3><p>通过执行迁移以更新迁移文件到数据库，以保证实体与表字段保持一致。</p><p>同<a href="#%E6%B7%BB%E5%8A%A0%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB">添加数据迁移</a>，更新迁移数据有相同方式</p><p>1、通过命令行执行迁移</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet ef database update</span><br></pre></td></tr></table></figure><p>2、通过程序包管理控制台（PMC）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Update-DataBase</span><br></pre></td></tr></table></figure><p>ABP提供了<code>.DbMigrator</code>控制台应用程序用来执行迁移操作和初始化种子数据：</p><p>将<code>.DbMigrator</code>项目设为启动项目并启动</p><p><img data-src="https://cdn.jonty.top/img/202406231356132.png" alt="启动项目设置"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[13:55:54 INF] Started database migrations...</span><br><span class="line">[13:55:54 INF] Migrating schema for host database...</span><br><span class="line">[13:55:57 INF] Executing host database seed...</span><br><span class="line">[13:55:59 INF] Successfully completed host database migrations.</span><br><span class="line">[13:56:03 INF] Successfully completed all database migrations.</span><br><span class="line">[13:56:03 INF] You can safely end this process...</span><br></pre></td></tr></table></figure><p>迁移完成，在数据库中查询</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> __EFMigrationsHistory</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> AppBooks</span><br></pre></td></tr></table></figure><p>可以看到，迁移记录和种子数据已创建</p><p><img data-src="https://cdn.jonty.top/img/202406231358303.png" alt="数据库查询结果"></p><h2 id="创建应用服务"><a href="#创建应用服务" class="headerlink" title="创建应用服务"></a>创建应用服务</h2><p>应用服务层包含两个项目：</p><ul><li><code>.Application.Contracts</code>：包含DTO和应用服务接口定义</li><li><code>.Application</code>：包含应用服务实现</li></ul><p>在这部分，将创建一个应用服务，使用ABP Framework的<code>CrudAppService</code>基类实现对<code>Book</code>的增删改查操作</p><h3 id="BookDto"><a href="#BookDto" class="headerlink" title="BookDto"></a>BookDto</h3><p><code>CrudAppService</code>基类需要定义实体的基本DTO，在<code>.Application.Contracts</code>项目中创建<code>Books</code>文件夹并添加<code>BookDto.cs</code></p><ul><li>DTO：Data Transfer Object 数据传输对象，用与应用层和表示层的数据传输，确保数据传输的安全性并减少冗余，减少不必要的数据传输以提高性能</li><li>定义BookDto用于传输在用户界面展示的书籍信息</li><li>BookDto继承自<code>AuditedEntityDto&lt;Guid&gt;</code>，同定义实体一样，定义审计信息，减少重复工作</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookDto</span> : <span class="title">AuditedEntityDto</span>&lt;<span class="title">Guid</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> BookType Type &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> DateTime PublishDate &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">float</span> Price &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在将书籍返回到表示层时，需要将<code>Book</code>实体转换为<code>BookDto</code>对象，<a href="https://automapper.org/">AutoMapper</a>库可以在定义了正确的映射时自动执行转换，启动模板配置了AutoMapper，在<code>.Application</code>项目的<code>BookStoreApplicationAutoMapperProfile.cs</code>中配置映射关系。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> Acme.BookStore.Books;</span><br><span class="line"><span class="keyword">using</span> AutoMapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreApplicationAutoMapperProfile</span> : <span class="title">Profile</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BookStoreApplicationAutoMapperProfile</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">/* You can configure your AutoMapper mapping configuration here.</span></span><br><span class="line"><span class="comment">         * Alternatively, you can split your mapping configurations</span></span><br><span class="line"><span class="comment">         * into multiple profile classes for a better organization. */</span></span><br><span class="line">        CreateMap&lt;Book, BookDto&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="CreateUpdateBookDto"><a href="#CreateUpdateBookDto" class="headerlink" title="CreateUpdateBookDto"></a>CreateUpdateBookDto</h3><p>新增<code>CreateUpdateBookDto.cs</code>，用于在创建或更新书籍的时候从用户界面传输图书信息。</p><p>这里定义了数据注释特性(如<code>[Required]</code>)来定义属性的验证规则，DTO由ABP框架自动验证。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System.ComponentModel.DataAnnotations;</span><br><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CreateUpdateBookDto</span></span><br><span class="line">&#123;</span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    [<span class="meta">StringLength(128)</span>]</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    <span class="keyword">public</span> BookType Type &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = BookType.Undefined;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    [<span class="meta">DataType(DataType.Date)</span>]</span><br><span class="line">    <span class="keyword">public</span> DateTime PublishDate &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = DateTime.Now;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">Required</span>]</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">float</span> Price &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配置实体映射关系：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreApplicationAutoMapperProfile</span> : <span class="title">Profile</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BookStoreApplicationAutoMapperProfile</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        CreateMap&lt;Book, BookDto&gt;();</span><br><span class="line">        CreateMap&lt;CreateUpdateBookDto, Book&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="IBookAppService"><a href="#IBookAppService" class="headerlink" title="IBookAppService"></a>IBookAppService</h3><p>定义应用服务接口，在<code>.Application.Contracts</code>项目中创建<code>Books</code>文件夹，并添加<code>IBookAppService</code>接口</p><ul><li>定义应用服务接口不是必须的，但建议作为<strong>最佳实践</strong></li><li><code>ICrudAppService</code>定义了常见的<strong>CRUD</strong>方法:<code>GetAsync</code>，<code>GetListAsync</code>，<code>CreateAsync</code>，<code>UpdateAsync</code>和<code>DeleteAsync</code>，也可以从空的<code>IApplicationService</code>接口继承并手动定义自己的方法。</li><li><code>ICrudAppService</code>中使用不同的DTO进行创建和更新</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Services;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IBookAppService</span> :</span><br><span class="line">        <span class="title">ICrudAppService</span>&lt; //<span class="title">Defines</span> <span class="title">CRUD</span> <span class="title">methods</span></span><br><span class="line">        <span class="title">BookDto</span>, //<span class="title">Used</span> <span class="title">to</span> <span class="title">show</span> <span class="title">books</span></span><br><span class="line">        <span class="title">Guid</span>, //<span class="title">Primary</span> <span class="title">key</span> <span class="title">of</span> <span class="title">the</span> <span class="title">book</span> <span class="title">entity</span></span><br><span class="line">        <span class="title">PagedAndSortedResultRequestDto</span>, //<span class="title">Used</span> <span class="title">for</span> <span class="title">paging</span>/<span class="title">sorting</span></span><br><span class="line">        <span class="title">CreateUpdateBookDto</span>&gt; <span class="comment">//Used to create/update a book</span></span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="BookAppService"><a href="#BookAppService" class="headerlink" title="BookAppService"></a>BookAppService</h3><p>实现<code>IBookAppService</code>接口，在<code>.Application</code>项目中创建<code>Books</code>文件夹，并添加<code>BookAppService</code>类</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Dtos;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Application.Services;</span><br><span class="line"><span class="keyword">using</span> Volo.Abp.Domain.Repositories;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">Acme.BookStore.Books</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookAppService</span> :</span><br><span class="line">    <span class="title">CrudAppService</span>&lt;</span><br><span class="line">        <span class="title">Book</span>, //<span class="title">The</span> <span class="title">Book</span> <span class="title">entity</span></span><br><span class="line">        <span class="title">BookDto</span>, //<span class="title">Used</span> <span class="title">to</span> <span class="title">show</span> <span class="title">books</span></span><br><span class="line">        <span class="title">Guid</span>, //<span class="title">Primary</span> <span class="title">key</span> <span class="title">of</span> <span class="title">the</span> <span class="title">book</span> <span class="title">entity</span></span><br><span class="line">        <span class="title">PagedAndSortedResultRequestDto</span>, //<span class="title">Used</span> <span class="title">for</span> <span class="title">paging</span>/<span class="title">sorting</span></span><br><span class="line">        <span class="title">CreateUpdateBookDto</span>&gt;, <span class="comment">//Used to create/update a book</span></span><br><span class="line">    <span class="title">IBookAppService</span> <span class="comment">//implement the IBookAppService</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BookAppService</span>(<span class="params">IRepository&lt;Book, Guid&gt; repository</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">repository</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>BookAppService</code>继承了<code>CrudAppService&lt;...&gt;</code>，并实现了 <code>ICrudAppService</code> 定义的CRUD方法.</li><li><code>BookAppService</code>注入<code>IRepository &lt;Book,Guid&gt;</code>，这是<code>Book</code>实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储</li><li><code>BookAppService</code>使用<code>IObjectMapper</code>将<code>Book</code>对象转换为<code>BookDto</code>对象，将<code>CreateUpdateBookDto</code>对象转换为<code>Book</code>对象</li></ul><p>目录如图：</p><p><img data-src="https://cdn.jonty.top/img/202406231440056.png" alt="应用层"></p><h3 id="自动生成API-Controllers"><a href="#自动生成API-Controllers" class="headerlink" title="自动生成API Controllers"></a>自动生成API Controllers</h3><p>在普通的ASP.NET Core应用程序中，通过创建<strong>API Controller</strong>将应用程序服务公开为<strong>HTTP API</strong>端点。提供给浏览器或第三方客户端通过HTTP调用。</p><p>ABP可以自动按照约定将应用程序服务配置为MVC API控制器，配置如下：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">DependsOn(BookStoreApplicationModule)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">BookStoreWebModule</span> : <span class="title">AbpModule</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">PreConfigureServices</span>(<span class="params">ServiceConfigurationContext context</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        PreConfigure&lt;AbpAspNetCoreMvcOptions&gt;(options =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            options</span><br><span class="line">                .ConventionalControllers</span><br><span class="line">                .Create(<span class="keyword">typeof</span>(BookStoreApplicationModule).Assembly);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Swagger-UI"><a href="#Swagger-UI" class="headerlink" title="Swagger UI"></a>Swagger UI</h3><p>启动模板使用<a href="https://github.com/domaindrivendev/Swashbuckle.AspNetCore">Swashbuckle.AspNetCore</a>运行<a href="https://swagger.io/tools/swagger-ui/">swagger UI</a>。</p><p>将<code>.HttpApi.Host</code>设为启动项目，并使用<code>Crtl+f5</code>运行，启动后打开浏览器访问<code>https://localhost:&lt;port&gt;/swagger/</code></p><img data-src="https://cdn.jonty.top/img/202406231439409.png" alt="swagger" style="width:67%;" /><p>使用<code>SwaggerUI</code>测试接口</p><img data-src="https://cdn.jonty.top/img/202406231442249.png" alt="image-20240623144230196" style="width:67%;" /><p>点击执行，返回数据如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;totalCount&quot;</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">&quot;items&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;盗墓笔记&quot;</span>,</span><br><span class="line">      <span class="attr">&quot;type&quot;</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="attr">&quot;publishDate&quot;</span>: <span class="string">&quot;2005-09-27T00:00:00&quot;</span>,</span><br><span class="line">      <span class="attr">&quot;price&quot;</span>: <span class="number">42</span>,</span><br><span class="line">      <span class="attr">&quot;lastModificationTime&quot;</span>: <span class="literal">null</span>,</span><br><span class="line">      <span class="attr">&quot;lastModifierId&quot;</span>: <span class="literal">null</span>,</span><br><span class="line">      <span class="attr">&quot;creationTime&quot;</span>: <span class="string">&quot;2024-06-23T13:55:59.2354244&quot;</span>,</span><br><span class="line">      <span class="attr">&quot;creatorId&quot;</span>: <span class="literal">null</span>,</span><br><span class="line">      <span class="attr">&quot;id&quot;</span>: <span class="string">&quot;afddb57d-1d66-d271-3bdb-3a1355d73e82&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">&quot;name&quot;</span>: <span class="string">&quot;1984&quot;</span>,</span><br><span class="line">      <span class="attr">&quot;type&quot;</span>: <span class="number">7</span>,</span><br><span class="line">      <span class="attr">&quot;publishDate&quot;</span>: <span class="string">&quot;2000-06-08T00:00:00&quot;</span>,</span><br><span class="line">      <span class="attr">&quot;price&quot;</span>: <span class="number">19.84</span>,</span><br><span class="line">      <span class="attr">&quot;lastModificationTime&quot;</span>: <span class="literal">null</span>,</span><br><span class="line">      <span class="attr">&quot;lastModifierId&quot;</span>: <span class="literal">null</span>,</span><br><span class="line">      <span class="attr">&quot;creationTime&quot;</span>: <span class="string">&quot;2024-06-23T13:55:58.9727875&quot;</span>,</span><br><span class="line">      <span class="attr">&quot;creatorId&quot;</span>: <span class="literal">null</span>,</span><br><span class="line">      <span class="attr">&quot;id&quot;</span>: <span class="string">&quot;d589b899-4033-3f76-9eea-3a1355d73d55&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这里我们就完成了基于ABP的后端服务接口学习</p><h2 id="下一节"><a href="#下一节" class="headerlink" title="下一节"></a>下一节</h2><p>教程<a href="/2024/06/23/abp-vNext-tutorials-part-2/" title="下一节">下一节</a></p>]]></content>
    
    
    <summary type="html">基于Abp vNext开发Web应用程序系列 - 创建服务端</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>Abp vNext - 让项目跑起来</title>
    <link href="https://jonty.top/2024/06/19/getting-start-abp/"/>
    <id>https://jonty.top/2024/06/19/getting-start-abp/</id>
    <published>2024-06-19T08:24:59.000Z</published>
    <updated>2024-06-19T08:41:54.020Z</updated>
    
    <content type="html"><![CDATA[<div class="note info"><p>Abp vNext是Abp的.NET Core 版本，但它不仅仅只是代码重写了。Abp团队在过去多年社区和商业版本的反馈上做了很多的改进。包括性能、底层的框架设计，它融合了更多优雅的设计实践。不管是自己需要快速上手项目、或者是公司的研发团队没有足够的能力去完整地开发一套稳定且功能全面的快速开发框架；对于.NET 系的开发者和公司来说，Abp目前就是目前最好的选择。</p></div><h2 id="设置开发环境"><a href="#设置开发环境" class="headerlink" title="设置开发环境"></a>设置开发环境</h2><p>安装以下工具：</p><ul><li><a href="https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0">.NET SDK 8.0+</a></li><li>Node v16 / v18</li><li>IDE：VS或者VS Code</li></ul><h2 id="安装ABP-CLI"><a href="#安装ABP-CLI" class="headerlink" title="安装ABP CLI"></a>安装ABP CLI</h2><p><a href="https://docs.abp.io/zh-Hans/abp/latest/CLI">ABP CLI</a> (命令行接口) 是一个命令行工具，用来执行基于ABP解决方案的一些常见操作。</p><p>使用以下命令安装 ABP CLI：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet tool install -g Volo.Abp.Cli</span><br></pre></td></tr></table></figure><p>如果已安装, 则可以使用以下命令对其进行更新：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dotnet tool update -g Volo.Abp.Cli</span><br></pre></td></tr></table></figure><h2 id="创建新项目"><a href="#创建新项目" class="headerlink" title="创建新项目"></a>创建新项目</h2><p>使用 ABP CLI 创建一个新的 ABP 项目。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">abp new Acme.BookStore -u angular</span><br></pre></td></tr></table></figure><img data-src="https://cdn.jonty.top/img/202406181037509.png" alt="image-20240618103736323" style="zoom:50%;" /><p>创建项目完成后，生成目录如下：</p><p><img data-src="https://cdn.jonty.top/img/202406191448791.png" alt="image-20240619144825909"></p><h2 id="生成数据库"><a href="#生成数据库" class="headerlink" title="生成数据库"></a>生成数据库</h2><h3 id="连接字符串"><a href="#连接字符串" class="headerlink" title="连接字符串"></a>连接字符串</h3><p>在<code>.HttpApi.Host</code>和<code>.DbMigrator</code>项目中修改<code>appsetting.json</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;ConnectionStrings&quot;</span>: &#123;</span><br><span class="line">  <span class="attr">&quot;Default&quot;</span>: <span class="string">&quot;Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="迁移数据库"><a href="#迁移数据库" class="headerlink" title="迁移数据库"></a>迁移数据库</h3><p>EF Core 提供两种主要方法来保持 EF Core 模型和数据库架构同步</p><p>如果没有数据库：<br>1、先写代码，自动创建数据库。<br>2、如果代码有变化，自动删除数据库重建，或者是使用迁移功能更改已有数据库。<br>如果已有数据库：<br>1、使用EF PowerTools反向工程生成模型。</p><blockquote><p>至于选用哪种方式，区分是以 EF Core 模型为准还是以数据库为准。</p><p>如果以 EF Core 模型为准，则使用<a href="https://learn.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/">迁移</a>。 对 EF Core 模型进行更改时，此方法会以增量方式将相应架构更改应用到数据库，以使数据库保持与 EF Core 模型兼容。</p><p>如果希望以数据库架构为准，则使用<a href="https://learn.microsoft.com/zh-cn/ef/core/managing-schemas/scaffolding/">反向工程</a>。 使用此方法，可通过将数据库架构反向工程到 EF Core 模型来生成相应的 DbContext 和实体类型。</p></blockquote><p>在ABP中使用的是<code>Code First</code>同步数据库，也就是通过代码迁移；ABP提供迁移程序执行迁移：</p><p>将<code>.DbMigrator</code>项目设置启动项目，确认数据库连接字符串</p><p><img data-src="https://cdn.jonty.top/img/202406191452253.png" alt="image-20240619145238797"></p><p>启动，输出如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[14:53:41 INF] Started database migrations...</span><br><span class="line">[14:53:41 INF] Migrating schema for host database...</span><br><span class="line">[14:53:44 INF] Executing host database seed...</span><br><span class="line">[14:53:45 INF] Successfully completed host database migrations.</span><br><span class="line">[14:53:49 INF] Successfully completed all database migrations.</span><br><span class="line">[14:53:49 INF] You can safely end this process...</span><br></pre></td></tr></table></figure><p>或者使用<strong>程序包管理控制台</strong>执行命令迁移：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Update-Database</span><br></pre></td></tr></table></figure><img data-src="https://cdn.jonty.top/img/202406191506527.png" alt="image-20240619150615715" style="zoom: 67%;" /><blockquote><p>初始的<a href="https://docs.abp.io/zh-Hans/abp/latest/Data-Seeding">种子数据</a>在数据库中创建了 <code>admin</code> 用户(密码为<code>1q2w3E*</code>) 用于登录应用程序。所以，对于新数据库至少使用 <code>.DbMigrator</code> 执行一次迁移</p></blockquote><h2 id="运行应用"><a href="#运行应用" class="headerlink" title="运行应用"></a>运行应用</h2><h3 id="运行Host-API"><a href="#运行Host-API" class="headerlink" title="运行Host API"></a>运行Host API</h3><p>将<code>.HttpApi.Host</code>项目设为启动项目，执行<code>ctrl+f5</code>，打开<code>swagger</code>如下：</p><img data-src="https://cdn.jonty.top/img/202406191514987.png" alt="image-20240619151443725" style="width:500px" /><h3 id="运行客户端"><a href="#运行客户端" class="headerlink" title="运行客户端"></a>运行客户端</h3><p>打开<code>angular</code>，还原依赖包</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn</span><br></pre></td></tr></table></figure><p>模块安装完成后运行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn start</span><br></pre></td></tr></table></figure><img data-src="https://cdn.jonty.top/img/202406191558444.png" alt="image-20240619155821450" style="width:67%;" />]]></content>
    
    
    <summary type="html">Abp vNext是Abp的.NET Core 版本，但它不仅仅只是代码重写了~</summary>
    
    
    
    <category term="Abp vNext" scheme="https://jonty.top/categories/Abp-vNext/"/>
    
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>【分享】5w的月薪很高吗</title>
    <link href="https://jonty.top/2024/03/17/prevent-brainwashing/"/>
    <id>https://jonty.top/2024/03/17/prevent-brainwashing/</id>
    <published>2024-03-17T13:59:02.000Z</published>
    <updated>2024-06-19T14:13:16.186Z</updated>
    
    <content type="html"><![CDATA[<p><img data-src="https://cdn.jonty.top/img/202406192203088.jpeg" alt="img"></p><p>在互联网里经常能看到一些“混得比较好的”同学发言，类似于“5w的月薪很高吗？”，“我身边年薪六七十w的人不少”之类的话，加上偶尔看到一些“年薪百万很简单”的标题党文章或者视频，其实对于我来说，我根本懒得去考证这些是真是假！</p><p>但是我觉得有必要去聊一聊！</p><h2 id="知识的贫乏"><a href="#知识的贫乏" class="headerlink" title="知识的贫乏"></a>知识的贫乏</h2><p>首先在说这个问题之前，我想引用罗翔老师的一句话。</p><blockquote><p>一个知识越贫乏的人，就越有莫名的优越感！</p></blockquote><p>一年多以前，我回老家，和以前的高中女同学出来聊天，彼此聊了聊自己现在的工作，然后她问我，“你现在一个月能赚三四万吧！”，我当时惊呆了，我回她：“瞧你说的，捡黄树叶也要赶上秋天呢”，我反问她你现在多少呢，她说两千八，我继续问，“你觉得工资多少才算高？”，她说最起码5万以上吧！我苦笑答：“我的妈呀，怎么都这么厉害呀！”。</p><p>事实是怎样的呢？</p><p>我们先不把事情说得太远，“脉脉上人均年薪百万”，“抖音上人手一台劳斯莱斯”这些不在叙述范围内，感情咱也不会那么不要脸去吹！</p><h2 id="大众才是真相"><a href="#大众才是真相" class="headerlink" title="大众才是真相"></a>大众才是真相</h2><p>像我们这种普通二本学校的学生现状应该最能接近真相了，往上不谈双一流，往下不谈专科，据我所知，我校2021年毕业的学生，如果继续做软件工程的话，现在一个月能拿两万以上的人没几个，还得是一线城市，在一线城市的大多都是一万多，所以一万多就是一个中位数。</p><p>不过要注意，软件工程专业毕业后从事本专业的人是很少的，就拿我们班来说，班上50人，但是从事软件的不超过20个，20个还是比较理想的。</p><p>那么就有一部分从事其它职业，一部分待业，一部分考公考编。</p><p>软件行业在整个市场来说工资高一点，就业相对来说简单一点，虽然近几年来行情越来越差，但是相比于其他行业来说，还是稍微好一点！</p><p>从事其他行业的人来说，如果家里有点关系的人，条件好一点的人，可能去到一个单位里面暂时上班，条件不好的，那就出来随便找一个班上，对于销售型的，在广州深圳，大多都是六七千，小城市的话，五千基本上已经很高了。对于待业的，那基本上没收入，考公考编的一般都回到了小县城，随便找个单位临时上班，一个月也就两千来块！</p><p>我们就不去分析双一流，专科，中职这些了，所以整体算下来，我们现在的年轻人的收入是很低的。</p><h2 id="时代特征"><a href="#时代特征" class="headerlink" title="时代特征"></a>时代特征</h2><p>努力在这个社会貌似已经不是一个正能量的词了，仿佛已经是一个调侃的词了，就像现在大多女孩子，她现在不会选择一个很努力的男孩子作为伴侣，因为努力后得到回报是一个概率事件，大多会选择有“存货”的人！当然，并不是人人都这样。</p><p>社会的发展就是这样，就像森林里面的狼越来越多，那么捕获到猎物的概率就越来越小，这和努力没多大的关系，这是时代特征！</p><p>八九十年代别说考上大学，考上中专谋个职位都不难，而现在一砖头下去都能打中几个研究生已经不是什么稀奇事了。</p><p>还有现在的经济形势如此严峻，企业和单位的寒冬一直在降临，无数的人蜂拥而至，导致形势更加紧张，本来在夹缝中已经难以生存了，现在变成了针眼！</p><p>所以前段时间网红带货主播李佳琦在网上说：“找找自己的原因，工资涨了没涨，有没有认真工作”，是因为的认知出现了谬误，所以才说出了这种言论，而他的成功完全靠努力吗？你怎么看！</p><h2 id="这和你没关系"><a href="#这和你没关系" class="headerlink" title="这和你没关系"></a>这和你没关系</h2><p>浮躁来自于你的认知水平，在这个信息爆炸的时代，如果不能分辨真假是非，那么就很容易陷入浮躁的状态！</p><p>网络上和现实中总是充斥着一股“赚钱很容易”的妖风，他们去编造一些故事，制造一些假象来迷惑人的双眼，如果你的甄别能力不够，那么你就会觉得为啥别人那么厉害，自己为啥混成这样，从而陷入浮躁和迷茫之中，当你进入这个状态后，等待你的要么是镰刀，要么是内耗！</p><p>做人过程中的一大蠢事就是自己啥也不是的时候，总是去炫耀自己拥有的那些八竿子打不着的人脉和资源，被那些不知真假的事物去影响，去自我否定，当一个人不能独立去思考问题，不站在现实角度去看待问题的时候，那么是永远不可能获得成长的。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>现实中，很多人都是很窘迫的，赚到钱的人永远在少数，这是时代特征和个人运气所决定的，努力只占了很小一部分，所以别被互联网上的一些妖风所影响！</p><p>这个时代我们虽然能决定的东西很少，事物都充满不确定性，但是依然要如罗曼罗兰说的那样”世界上只有一种英雄主义，看清生活的真相依然热爱生活”，正因为充满不确定性，所以才有“赌”的意义！</p>]]></content>
    
    
    <summary type="html">别让垃圾短视频废了可思化，多出去看看，别活在短视频和文字里！</summary>
    
    
    
    <category term="人生是一场未知的旅行" scheme="https://jonty.top/categories/%E4%BA%BA%E7%94%9F%E6%98%AF%E4%B8%80%E5%9C%BA%E6%9C%AA%E7%9F%A5%E7%9A%84%E6%97%85%E8%A1%8C/"/>
    
    
    <category term="Share" scheme="https://jonty.top/tags/Share/"/>
    
  </entry>
  
  <entry>
    <title>Swarm overlay网络详解</title>
    <link href="https://jonty.top/2023/12/21/docker-swarm-network-overlay/"/>
    <id>https://jonty.top/2023/12/21/docker-swarm-network-overlay/</id>
    <published>2023-12-21T12:54:01.000Z</published>
    <updated>2023-12-21T12:55:32.857Z</updated>
    
    <content type="html"><![CDATA[<h3 id="查看overlay网络"><a href="#查看overlay网络" class="headerlink" title="查看overlay网络"></a>查看overlay网络</h3><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">$</span> docker network ls</span><br><span class="line">NETWORK ID     NAME              DRIVER    SCOPE</span><br><span class="line">aba37840837a   bridge            bridge    local</span><br><span class="line">20a5dcd30faa   docker<span class="built_in">_</span>gwbridge   bridge    local</span><br><span class="line">97d548bf9e22   host              host      local</span><br><span class="line">yta4shlonx9v   ingress           overlay   swarm</span><br><span class="line">4644b3cc4c43   none              null      local</span><br></pre></td></tr></table></figure><p>对于理解swarm的网络来讲，个人认为最重要的两个点：</p><ul><li><p>第一是外部如何访问部署运行在swarm集群内的服务，可以称之为<code>入方向</code> 流量，在swarm里我们通过<code>ingress</code>来解决</p></li><li><p>第二是部署在swarm集群里的服务，如何对外进行访问，这部分又分为两块:</p><ul><li><p>第一，<code>东西向流量</code> ，也就是不同swarm节点上的容器之间如何通信，swarm通过 <code>overlay </code>网络来解决；</p></li><li><p>第二，<code>南北向流量</code> ，也就是swarm集群里的容器如何对外访问，比如互联网，这个是 <code>Linux bridge + iptables NAT</code> 来解决；</p></li></ul></li></ul><h3 id="创建-overlay-网络"><a href="#创建-overlay-网络" class="headerlink" title="创建 overlay 网络"></a>创建 overlay 网络</h3><p>这个网络会同步到所有的swarm节点上</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">$</span> docker network create -d overlay mynet</span><br><span class="line">4f05pvu8zzj36c2fwu0208wa4</span><br><span class="line"></span><br><span class="line"><span class="built_in">$</span> docker network ls</span><br><span class="line">NETWORK ID     NAME              DRIVER    SCOPE</span><br><span class="line">aba37840837a   bridge            bridge    local</span><br><span class="line">20a5dcd30faa   docker<span class="built_in">_</span>gwbridge   bridge    local</span><br><span class="line">97d548bf9e22   host              host      local</span><br><span class="line">yta4shlonx9v   ingress           overlay   swarm</span><br><span class="line">4f05pvu8zzj3   mynet             overlay   swarm</span><br><span class="line">4644b3cc4c43   none              null      local</span><br></pre></td></tr></table></figure><h3 id="创建服务"><a href="#创建服务" class="headerlink" title="创建服务"></a>创建服务</h3><p>创建一个服务连接到这个 overlay网络， name 是 test ， replicas 是 2</p><p>可以看到这两个容器分别被创建在worker1和worker2两个节点上</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">vagrant@swarm-manager:~<span class="built_in">$</span> docker service create --network mynet --name test --replicas 2 busybox ping 8.8.8.8</span><br><span class="line">vagrant@swarm-manager:~<span class="built_in">$</span> docker service ps test</span><br><span class="line">ID             NAME      IMAGE            NODE            DESIRED STATE   CURRENT STATE            ERROR     PORTS</span><br><span class="line">yf5uqm1kzx6d   test.1    busybox:latest   swarm-worker1   Running         Running 18 seconds ago</span><br><span class="line">3tmp4cdqfs8a   test.2    busybox:latest   swarm-worker2   Running         Running 18 seconds ago</span><br></pre></td></tr></table></figure><h3 id="网络查看"><a href="#网络查看" class="headerlink" title="网络查看"></a>网络查看</h3><p>到worker1和worker2上分别查看容器的网络连接情况</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">vagrant@swarm-worker1:~<span class="built_in">$</span> docker container ls</span><br><span class="line">CONTAINER ID   IMAGE            COMMAND          CREATED      STATUS      PORTS     NAMES</span><br><span class="line">cac4be28ced7   busybox:latest   &quot;ping 8.8.8.8&quot;   2 days ago   Up 2 days             test.1.yf5uqm1kzx6dbt7n26e4akhsu</span><br><span class="line">vagrant@swarm-worker1:~<span class="built_in">$</span> docker container exec -it cac sh</span><br><span class="line">/ <span class="params">#</span> ip a</span><br><span class="line">1: lo: &lt;LOOPBACK,UP,LOWER<span class="built_in">_</span>UP&gt; mtu 65536 qdisc noqueue qlen 1000</span><br><span class="line">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00</span><br><span class="line">    inet 127.0.0.1/8 scope host lo</span><br><span class="line">    valid<span class="built_in">_</span>lft forever preferred<span class="built_in">_</span>lft forever</span><br><span class="line">24: eth0@if25: &lt;BROADCAST,MULTICAST,UP,LOWER<span class="built_in">_</span>UP,M-DOWN&gt; mtu 1450 qdisc noqueue</span><br><span class="line">    link/ether 02:42:0a:00:01:08 brd ff:ff:ff:ff:ff:ff</span><br><span class="line">    inet 10.0.1.8/24 brd 10.0.1.255 scope global eth0</span><br><span class="line">    valid<span class="built_in">_</span>lft forever preferred<span class="built_in">_</span>lft forever</span><br><span class="line">26: eth1@if27: &lt;BROADCAST,MULTICAST,UP,LOWER<span class="built_in">_</span>UP,M-DOWN&gt; mtu 1500 qdisc noqueue</span><br><span class="line">    link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff</span><br><span class="line">    inet 172.18.0.3/16 brd 172.18.255.255 scope global eth1</span><br><span class="line">    valid<span class="built_in">_</span>lft forever preferred<span class="built_in">_</span>lft forever</span><br><span class="line">/ <span class="params">#</span></span><br></pre></td></tr></table></figure><p>这个容器有两个接口 <a href="https://so.csdn.net/so/search?q=eth0&spm=1001.2101.3001.7020">eth0</a>和eth1， 其中eth0是连到了mynet这个网络，eth1是连到docker_gwbridge这个网络</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">vagrant@swarm-worker1:~<span class="built_in">$</span> docker network ls</span><br><span class="line">NETWORK ID     NAME              DRIVER    SCOPE</span><br><span class="line">a631a4e0b63c   bridge            bridge    local</span><br><span class="line">56945463a582   docker<span class="built_in">_</span>gwbridge   bridge    local</span><br><span class="line">9bdfcae84f94   host              host      local</span><br><span class="line">14fy2l7a4mci   ingress           overlay   swarm</span><br><span class="line">lpirdge00y3j   mynet             overlay   swarm</span><br><span class="line">c1837f1284f8   none              null      local</span><br></pre></td></tr></table></figure><p>在这个容器里是可以直接ping通worker2上容器的IP 10.0.1.9的</p><p>结构图讲解<br>南北向：主要用于访问外部网络。通过eht1网卡，走veth的docker_gwbridge网络，根据NAT把容器地址转换成主机地址，访问到外部网络。<br>东西向：用于集群之间的网络访问。<code>192.168.200.10</code>上的容器通过eht0访问overlay的网络mynet，将原始数据加一个VXLAN的头，封装成数据包，这时会原始地址就是<code>192.168.200.10</code>，目标IP地址<code>192.168.200.11</code>，通过这个管道发送到目标机器上，再通过overlay网络的mynet接收解封，发送到<code>192.168.200.11</code>的容器上。</p><p>集群的两个节点之间 <code>10.0.1.8 - &gt; 10.0.1.9</code> 转换后两台机器 <code>192.168.200.10 -&gt; 192.168.200.11</code></p><p><img data-src="https://cdn.jonty.top/img/202311221100439.png" alt="image-20231122110010154"></p>]]></content>
    
    
    <summary type="html">Swarm overlay网络详解</summary>
    
    
    
    <category term="DevOps" scheme="https://jonty.top/categories/DevOps/"/>
    
    
    <category term="Docker" scheme="https://jonty.top/tags/Docker/"/>
    
    <category term="Docker Swarm" scheme="https://jonty.top/tags/Docker-Swarm/"/>
    
  </entry>
  
  <entry>
    <title>Lazy延迟初始化</title>
    <link href="https://jonty.top/2023/10/31/about-of-csharp-lazy-initialization/"/>
    <id>https://jonty.top/2023/10/31/about-of-csharp-lazy-initialization/</id>
    <published>2023-10-31T02:32:00.000Z</published>
    <updated>2023-10-31T02:34:32.945Z</updated>
    
    <content type="html"><![CDATA[<div class="note info"><p>从net 4.0开始，C#开始支持延迟初始化，通过Lazy关键字，可以声明某个对象在第一次使用的时候再初始化，如果一直没有调用，那就不初始化，省去了一部分不必要的开销，提升了效率，同时Lazy是天生<strong>线程安全</strong>的。</p></div><h2 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h2><ul><li><p>对象创建成本高且程序可能不会使用它。 </p><p>例如，假定内存中有具有 <code>Orders</code> 属性的 <code>Customer</code> 对象，该对象包含大量 <code>Order</code> 对象，初始化这些对象需要数据库连接。 如果用户不需要显示 Orders 或在计算中使用该数据，则无需使用系统内存或计算周期来创建它。 通过使用 <code>Lazy&lt;Orders&gt;</code> 来声明 <code>Orders</code> 对象用于迟缓初始化，可以避免在不使用该对象时浪费系统资源。</p></li><li><p>对象创建成本高，且希望将其创建推迟到其他高成本操作完成后。</p><p>例如，假定程序在启动时加载多个对象实例，但是只需立即加载其中一部分。 可以通过推迟初始化不需要的对象，直到创建所需对象，提升程序的启动性能。</p></li></ul><blockquote><p>建议使用 <a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.lazy-1">Lazy<T></a>。 <a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.lazy-1">Lazy<T></a> 及其相关的类型支持线程安全并提供一致的异常传播策略。</p><p><a href="https://learn.microsoft.com/zh-cn/dotnet/framework/performance/lazy-initialization">延迟初始化 - .NET Framework | Microsoft Learn</a></p></blockquote><p><img data-src="https://cdn.jonty.top/img/202310310935922.png" alt="image-20231031093457833"></p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><h3 id="默认初始化"><a href="#默认初始化" class="headerlink" title="默认初始化"></a>默认初始化</h3><p>在使用Lazy时，如果没有在构造函数中传入委托，则在首次访问值属性时，将会使用<code>Activator.CreateInstance</code>来创建类型的对象，如果此类型没有无参数的构造函数时将会引发运行时异常。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">LazyUsage</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">class</span> <span class="title">LazyDemo</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            Lazy&lt;Data&gt; lazyData = <span class="keyword">new</span> Lazy&lt;Data&gt;();</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Main-&gt;is lazyData Initialized? value = &quot;</span> + lazyData.IsValueCreated);</span><br><span class="line">            lazyData.Value.Print();<span class="comment">//此处访问时才会将Data真正的初始化</span></span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Main-&gt;is lazyData Initialized? value = &quot;</span> + lazyData.IsValueCreated);</span><br><span class="line"></span><br><span class="line">            Console.ReadKey();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">class</span> <span class="title">Data</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">Data</span>(<span class="params"></span>)</span> </span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Data::.ctor-&gt;Initialized&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Print</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Data::Print-&gt;println&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>执行结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Main-&gt;is lazyData Initialized? value = False</span><br><span class="line">Data::.ctor-&gt;Initialized</span><br><span class="line">Data::Print-&gt;println</span><br><span class="line">Main-&gt;is lazyData Initialized? value = True</span><br></pre></td></tr></table></figure><h3 id="委托初始化"><a href="#委托初始化" class="headerlink" title="委托初始化"></a>委托初始化</h3><p>指定委托来初始化</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">LazyUsage</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">class</span> <span class="title">LazyDemo</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//指定委托来初始化Data</span></span><br><span class="line">            Lazy&lt;Data&gt; lazyData = <span class="keyword">new</span> Lazy&lt;Data&gt;(</span><br><span class="line">                () =&gt;</span><br><span class="line">                &#123;</span><br><span class="line">                    Console.WriteLine(<span class="string">&quot;Main-&gt;lazyData will be Initialized!&quot;</span>);</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">new</span> Data(<span class="string">&quot;Test&quot;</span>);</span><br><span class="line">                &#125;);</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Main-&gt;is lazyData Initialized? value = &quot;</span> + lazyData.IsValueCreated);</span><br><span class="line">            lazyData.Value.Print();</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Main-&gt;is lazyData Initialized? value = &quot;</span> + lazyData.IsValueCreated);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">            Console.ReadKey();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">class</span> <span class="title">Data</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">Data</span>(<span class="params"><span class="built_in">string</span> name</span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            Name = name;</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Data::.ctor-&gt;Initialized,name = &quot;</span>+name);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Print</span>(<span class="params"></span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Data::Print-&gt;name = &quot;</span> + Name);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行结果：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Main-&gt;is lazyData Initialized? value = False</span><br><span class="line">Main-&gt;lazyData will be Initialized!</span><br><span class="line">Data::.ctor-&gt;Initialized,name = Test</span><br><span class="line">Data::Print-&gt;name = Test</span><br><span class="line">Main-&gt;is lazyData Initialized? value = True</span><br></pre></td></tr></table></figure><h3 id="线程安全初始化"><a href="#线程安全初始化" class="headerlink" title="线程安全初始化"></a>线程安全初始化</h3><p>默认情况下，<a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.lazy-1">Lazy</a> 对象是线程安全的。也就是说，如果构造函数没有指定线程安全性的类型，该函数创建的 <a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.lazy-1">Lazy</a> 对象是线程安全的。 </p><p>在多线程方案中，访问线程安全 <a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.lazy-1">Lazy</a> 对象的 <a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.lazy-1.value">Value</a> 属性的第一个线程会为所有线程上的所有后续访问对其初始化，且所有线程共享相同的数据。 因此，哪个线程初始化对象并不重要，争用条件是良性的。</p><p>以下示例演示了相同的 <code>Lazy&lt;int&gt;</code> 实例对于三个单独的线程输出相同的值。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Initialize the integer to the managed thread id of the</span></span><br><span class="line"><span class="comment">// first thread that accesses the Value property.</span></span><br><span class="line">Lazy&lt;<span class="built_in">int</span>&gt; number = <span class="keyword">new</span> Lazy&lt;<span class="built_in">int</span>&gt;(() =&gt; Thread.CurrentThread.ManagedThreadId);</span><br><span class="line"></span><br><span class="line">Thread t1 = <span class="keyword">new</span> Thread(() =&gt; Console.WriteLine(<span class="string">&quot;number on t1 = &#123;0&#125; ThreadID = &#123;1&#125;&quot;</span>,</span><br><span class="line">                                        number.Value, Thread.CurrentThread.ManagedThreadId));</span><br><span class="line">t1.Start();</span><br><span class="line"></span><br><span class="line">Thread t2 = <span class="keyword">new</span> Thread(() =&gt; Console.WriteLine(<span class="string">&quot;number on t2 = &#123;0&#125; ThreadID = &#123;1&#125;&quot;</span>,</span><br><span class="line">                                        number.Value, Thread.CurrentThread.ManagedThreadId));</span><br><span class="line">t2.Start();</span><br><span class="line"></span><br><span class="line">Thread t3 = <span class="keyword">new</span> Thread(() =&gt; Console.WriteLine(<span class="string">&quot;number on t3 = &#123;0&#125; ThreadID = &#123;1&#125;&quot;</span>, number.Value,</span><br><span class="line">                                        Thread.CurrentThread.ManagedThreadId));</span><br><span class="line">t3.Start();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Ensure that thread IDs are not recycled if the</span></span><br><span class="line"><span class="comment">// first thread completes before the last one starts.</span></span><br><span class="line">t1.Join();</span><br><span class="line">t2.Join();</span><br><span class="line">t3.Join();</span><br></pre></td></tr></table></figure><p>执行结果：</p><figure class="highlight tex"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">/* Sample Output:</span><br><span class="line">    number on t1 = 11 ThreadID = 11</span><br><span class="line">    number on t3 = 11 ThreadID = 13</span><br><span class="line">    number on t2 = 11 ThreadID = 12</span><br><span class="line">    Press any key to exit.</span><br><span class="line">*/</span><br></pre></td></tr></table></figure><blockquote><p>如果每个线程需要单独的数据，使用 <a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.threadlocal-1">ThreadLocal</a> 类型</p></blockquote><h3 id="Lazy-Value"><a href="#Lazy-Value" class="headerlink" title="Lazy.Value"></a>Lazy.Value</h3><p>Lazy对象创建后，并不会立即创建对应的对象，只有在变量的Value属性被首次访问时才会真正的创建，同时会将其缓存到Value中，以便将来访问。</p><p>Value属性是只读的，也就意味着如果Value存储了引用类型，将无法为其分配新对象，只可以更改此对象公共的属性或者字段等，如果Value存储的是值类型，那么就不能修改其值了，只能通过再次调用变量的函数使用新的参数来创建新的变量。</p><p>在Lazy对象创建后，在首次访问变量的Value属性前。</p><h3 id="延迟属性"><a href="#延迟属性" class="headerlink" title="延迟属性"></a>延迟属性</h3><p>要使用延迟初始化实现公共属性，则将该属性的支持字段定义为 Lazy，并从该属性的 <code>get</code> 访问器返回 <a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.lazy-1.value">Value</a> 属性。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title">Customer</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> Lazy&lt;Orders&gt; _orders;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> CustomerID &#123;<span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Customer</span>(<span class="params"><span class="built_in">string</span> id</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        CustomerID = id;</span><br><span class="line">        _orders = <span class="keyword">new</span> Lazy&lt;Orders&gt;(() =&gt;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Orders(<span class="keyword">this</span>.CustomerID);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Orders MyOrders</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">get</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> _orders.Value;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<a href="#Lazy.Value">Lazy.Value</a>中可以得知：Value的属性是只读的，所以示例中只提供了Get的访问器，并未提供Set的访问器。</p><p>如果需要支持读取与写入属性的话，则Set访问器必须创建一个新的Lazy对象，同时必须编写自己的线程安全代码才能执行此操作。</p><h2 id="循环依赖问题"><a href="#循环依赖问题" class="headerlink" title="循环依赖问题"></a>循环依赖问题</h2><p>在构建应用程序时，良好的设计应该避免服务之间的循环依赖，循环依赖是指某些组件直接或间接相互依赖。</p><p>比如下面这样：</p><p><img data-src="https://cdn.jonty.top/img/202310310951075.png" alt="Circular dependency"></p><p>在.NET Core中使用依赖注入，如果产生循环依赖关系，则会报一下错误：</p><blockquote><p><em><strong>System.InvalidOperationException</strong>: A circular dependency was detected for the service of type ‘Demo.IA’</em></p></blockquote><h3 id="注入IServiceProvider"><a href="#注入IServiceProvider" class="headerlink" title="注入IServiceProvider"></a>注入IServiceProvider</h3><p>当应用复杂度达到一定程度时，很难避免造成服务循环依赖的问题，理想的情况下，应该是选择重构。</p><blockquote><p>但是项目时间紧，任务重，没有时间重构代码</p></blockquote><p><img data-src="https://cdn.jonty.top/img/202310311009917.png" alt="image-20231031100909845"></p><p>我们可以通过注入<code>IServiceProvider </code>去获取服务</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title">C</span> : <span class="title">IC</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IA _a;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">C</span>(<span class="params">IA a</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _a = a;</span><br><span class="line">    &#125;</span><br><span class="line">  </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Bar</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        ...</span><br><span class="line">        _a.Foo()</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了避免依赖性循环，可以注入<code> IServiceProvider</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title">C</span> : <span class="title">IC</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IServiceProvider _services;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">C</span>(<span class="params">IServiceProvider services</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _services = services;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Bar</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">var</span> a = _services.GetRequiredService&lt;IA&gt;();</span><br><span class="line">        a.Foo();</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式有一定弊端，例如强制依赖IOC，并且很难看到类的依赖关系。</p><h3 id="巧用-Lazy-lt-T-gt"><a href="#巧用-Lazy-lt-T-gt" class="headerlink" title="巧用 Lazy&lt;T&gt;"></a><strong>巧用</strong> <code>Lazy&lt;T&gt;</code></h3><p>新建一个 <code>IServiceCollection</code> 的扩展类<code>AddLazyResolution</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> IServiceCollection <span class="title">AddLazyResolution</span>(<span class="params"><span class="keyword">this</span> IServiceCollection services</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> services.AddTransient(</span><br><span class="line">        <span class="keyword">typeof</span>(Lazy&lt;&gt;),</span><br><span class="line">        <span class="keyword">typeof</span>(LazilyResolved&lt;&gt;));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">class</span> <span class="title">LazilyResolved</span>&lt;<span class="title">T</span>&gt; : <span class="title">Lazy</span>&lt;<span class="title">T</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">LazilyResolved</span>(<span class="params">IServiceProvider serviceProvider</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">serviceProvider.GetRequiredService&lt;T&gt;</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>Startup.cs</code>中注册</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">services.AddLazyResolution();</span><br></pre></td></tr></table></figure><p>在依赖的类中IA，注入Lazy，当要使用IA时，只需访问Lazy的值 Value 即可：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title">C</span> : <span class="title">IC</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> Lazy&lt;IA&gt; _a;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">C</span>(<span class="params">Lazy&lt;IA&gt; a</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        _a = a;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Bar</span>(<span class="params"></span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        ...</span><br><span class="line">        _a.Value.Foo();</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>这个方法不是最完美的，但是解决了根本问题，并且依赖项在构造函数中有明确声明。</p><p>为了打破循环依赖关系，我们需要一个服务工厂，而不是实际的对象，在示例中，<code>IServiceProvider</code> 和 <code>Lazy</code>都被用作工厂。</p></blockquote><p>可以看到在<code>ABP</code>中也提供了<code>IAbpLazyServiceProvider</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">virtual</span> <span class="built_in">object</span>? GetService(Type serviceType)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> CachedServices.GetOrAdd(</span><br><span class="line">        serviceType,</span><br><span class="line">        _ =&gt; <span class="keyword">new</span> Lazy&lt;<span class="built_in">object</span>?&gt;(() =&gt; ServiceProvider.GetService(serviceType))</span><br><span class="line">    ).Value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img data-src="https://cdn.jonty.top/img/202310311028274.webp" alt="img"></p><blockquote><p>参考：<a href="https://stackoverflow.com/questions/44934511/does-net-core-dependency-injection-support-lazyt">c# - Does .net core dependency injection support Lazy - Stack Overflow</a></p><p><a href="https://thomaslevesque.com/2020/03/18/lazily-resolving-services-to-fix-circular-dependencies-in-net-core/">[Lazily resolving services to fix circular dependencies in .NET Core - Thomas Levesque’s .NET Blog]</a></p></blockquote>]]></content>
    
    
    <summary type="html">C#性能优化 - Lazy延迟初始化</summary>
    
    
    
    <category term="搬砖那些事儿" scheme="https://jonty.top/categories/%E6%90%AC%E7%A0%96%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/"/>
    
    
    <category term="C#" scheme="https://jonty.top/tags/C/"/>
    
    <category term="ASP.NET Core" scheme="https://jonty.top/tags/ASP-NET-Core/"/>
    
    <category term="ABP" scheme="https://jonty.top/tags/ABP/"/>
    
  </entry>
  
  <entry>
    <title>【分享】IP 基础知识“全家桶”</title>
    <link href="https://jonty.top/2023/08/27/about-of-IP/"/>
    <id>https://jonty.top/2023/08/27/about-of-IP/</id>
    <published>2023-08-27T01:58:00.000Z</published>
    <updated>2023-10-16T02:02:27.352Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>以下文章来源于小林coding ，作者小林coding</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前段时间，有读者希望我写一篇关于 IP 分类地址、子网划分等的文章，他反馈常常混淆，摸不着头脑。</p><p>那么，说来就来！而且要盘就盘全一点，顺便挑战下小林的图解功力，所以就来个 <strong>IP 基础知识全家桶</strong>。</p><p>吃完这个 IP 基础知识全家桶全家桶，包你撑着肚子喊出：“<strong>真香！</strong>”</p><p>不多说，直接上菜，共分为<strong>三道菜</strong>：</p><ul><li>首先是前菜 「 IP 基本认识 」</li><li>其次是主菜 「IP 地址的基础知识」</li><li>最后是点心 「IP 协议相关技术」</li></ul><p><img data-src="https://cdn.jonty.top/img/%E7%BB%93%E6%9E%84%E5%9B%BE.png" alt="IP基础知识全家桶"></p><hr><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><h3 id="前菜-——-IP-基本认识"><a href="#前菜-——-IP-基本认识" class="headerlink" title="前菜 —— IP 基本认识"></a>前菜 —— IP 基本认识</h3><p>IP 在 TCP/IP 参考模型中处于第三层，也就是<strong>网络层</strong>。</p><p>网络层的主要作用是：<strong>实现主机与主机之间的通信，也叫点对点（end to end）通信。</strong></p><p><img data-src="https://cdn.jonty.top/img/640.png" alt="IP的作用"></p><blockquote><p>网络层与数据链路层有什么关系呢？</p></blockquote><p>有的小伙伴分不清 IP（网络层） 和 MAC （数据链路层）之间的区别和关系。</p><p>其实很容易区分，在上面我们知道 IP 的作用是主机之间通信中的，而 <strong>MAC 的作用则是实现「直连」的两个设备之间通信，而 IP 则负责在「没有直连」的两个网络之间进行通信传输。</strong></p><p>举个生活的栗子，小林要去一个很远的地方旅行，制定了一个行程表，其间需先后乘坐飞机、地铁、公交车才能抵达目的地，为此小林需要买飞机票，地铁票等。</p><p>飞机票和地铁票都是去往特定的地点的，每张票只能够在某一限定区间内移动，此处的「区间内」就如同通信网络中数据链路。</p><p>在区间内移动相当于数据链路层，充当区间内两个节点传输的功能，区间内的出发点好比源 MAC 地址，目标地点好比目的 MAC 地址。</p><p>整个旅游行程表就相当于网络层，充当远程定位的功能，行程的开始好比源 IP，行程的终点好比目的 IP 地址。</p><p><img data-src="https://cdn.jonty.top/img/202308272207889.png" alt="IP 的作用与 MAC 的作用"></p><p>如果小林只有行程表而没有车票，就无法搭乘交通工具到达目的地。相反，如果除了车票而没有行程表，恐怕也很难到达目的地。因为小林不知道该坐什么车，也不知道该在哪里换乘。</p><p>因此，只有两者兼备，既有某个区间的车票又有整个旅行的行程表，才能保证到达目的地。与此类似，<strong>计算机网络中也需要「数据链路层」和「网络层」这个分层才能实现向最终目标地址的通信。</strong></p><p>还有重要一点，旅行途中我们虽然不断变化了交通工具，但是旅行行程的起始地址和目的地址始终都没变。其实，在网络中数据包传输中也是如此，<strong>源IP地址和目标IP地址在传输过程中是不会变化的，只有源 MAC 地址和目标 MAC 一直在变化。</strong></p><hr><h3 id="主菜-——-IP-地址的基础知识"><a href="#主菜-——-IP-地址的基础知识" class="headerlink" title="主菜 —— IP 地址的基础知识"></a>主菜 —— IP 地址的基础知识</h3><p>在 TCP/IP 网络通信时，为了保证能正常通信，每个设备都需要配置正确的 IP 地址，否则无法实现正常的通信。</p><p>IP 地址（IPv4 地址）由 <code>32</code> 位正整数来表示，IP 地址在计算机是以二进制的方式处理的。</p><p>而人类为了方便记忆采用了<strong>点分十进制</strong>的标记方式，也就是将 32 位 IP 地址以每 8 位为组，共分为 <code>4</code> 组，每组以「<code>.</code>」隔开，再将每组转换成十进制。</p><p><img data-src="https://cdn.jonty.top/img/202308272208690.png" alt="点分十进制"></p><p>那么，IP 地址最大值也就是</p><p><img data-src="https://cdn.jonty.top/img/202308272209488.png"></p><p>也就说，最大允许 43 亿台计算机连接到网络。</p><p>实际上，IP 地址并不是根据主机台数来配置的，而是以网卡。像服务器、路由器等设备都是有 2 个以上的网卡，也就是它们会有 2 个以上的 IP 地址。</p><p><img data-src="https://cdn.jonty.top/img/202309071006166.png" alt="每块网卡可以分配一个以上的IP地址"></p><p>因此，让 43 亿台计算机全部连网其实是不可能的，更何况 IP 地址是由「网络标识」和「主机标识」这两个部分组成的，所以实际能够连接到网络的计算机个数更是少了很多。</p><blockquote><p>可能有的小伙伴提出了疑问，现在不仅电脑配了 IP， 手机、IPad 等电子设备都配了 IP 呀，照理来说肯定会超过 43 亿啦，那是怎么能够支持这么多 IP 的呢？</p></blockquote><p>因为会根据一种可以更换 IP 地址的技术 <code>NAT</code>，使得可连接计算机数超过 43 亿台。<code>NAT</code> 技术后续会进一步讨论和说明。</p><h4 id="IP-地址的分类"><a href="#IP-地址的分类" class="headerlink" title="IP 地址的分类"></a>IP 地址的分类</h4><p>互联网诞生之初，IP 地址显得很充裕，于是计算机科学家们设计了<strong>分类地址</strong>。</p><p>IP 地址分类成了 5 种类型，分别是 A 类、B 类、C 类、D 类、E 类。</p><p><img data-src="https://cdn.jonty.top/img/202309071006808.png" alt="IP 地址分类"></p><p>上图中黄色部是分类号，用以区分 IP 地址类别。</p><blockquote><p>什么是 A、B、C 类地址？</p></blockquote><p>其中对于 A、B、C 类主要分为两个部分，分别是<strong>网络号和主机号</strong>。这很好理解，好比小林是 A 小区 1 栋 101 号，你是 B 小区 1 栋 101 号。</p><p>我们可以用下面这个表格， 就能很清楚的知道 A、B、C 分类对应的地址范围、最大主机个数。</p><p><img data-src="https://cdn.jonty.top/img/202309071006375.png" alt="A、B、C 分类地址"></p><blockquote><p>A、B、C 分类地址最大主机个数是如何计算的呢？</p></blockquote><p>最大主机个数，就是要看主机号的位数，如 C 类地址的主机号占 8 位，那么 C 类地址的最大主机个数：</p><p><img data-src="https://cdn.jonty.top/img/202309071007734.png"></p><p>为什么要减 2 呢？</p><p>因为在 IP 地址中，有两个 IP 是特殊的，分别是主机号全为 1 和 全为 0 地址。</p><p><img data-src="https://cdn.jonty.top/img/202309071007166.png"></p><ul><li>主机号全为 1 指定某个网络下的所有主机，用于广播</li><li>主机号全为 0 指定某个网络</li></ul><p>因此，在分配过程中，应该去掉这两种情况。</p><blockquote><p>广播地址用于什么？</p></blockquote><p>广播地址用于在<strong>同一个链路中相互连接的主机之间发送数据包</strong>。</p><p>学校班级中就有广播的例子，在准备上课的时候，通常班长会喊：“上课， 全体起立！”，班里的同学听到这句话是不是全部都站起来了？这个句话就有广播的含义。</p><p>当主机号全为 1 时，就表示该网络的广播地址。例如把 <code>172.20.0.0/16</code> 用二进制表示如下：</p><p>10101100.00010100.00000000.00000000</p><p>将这个地址的<strong>主机部分全部改为 1</strong>，则形成广播地址：</p><p>10101100.00010100.<strong>11111111.11111111</strong></p><p>再将这个地址用十进制表示，则为 <code>172.20.255.255</code>。</p><p>广播地址可以分为本地广播和直接广播两种。</p><ul><li><strong>在本网络内广播的叫做本地广播</strong>。例如网络地址为 192.168.0.0/24 的情况下，广播地址是 192.168.0.255 。因为这个广播地址的 IP 包会被路由器屏蔽，所以不会到达 192.168.0.0/24 以外的其他链路上。</li><li><strong>在不同网络之间的广播叫做直接广播</strong>。例如网络地址为 192.168.0.0/24 的主机向 192.168.1.255/24 的目标地址发送 IP 包。收到这个包的路由器，将数据转发给192.168.1.0/24，从而使得所有 192.168.1.1~192.168.1.254 的主机都能收到这个包（由于直接广播有一定的安全问题，多数情况下会在路由器上设置为不转发）。</li></ul><p><img data-src="https://cdn.jonty.top/img/202309071007094.jpg" alt="本地广播与直接广播"></p><blockquote><p>什么是 D、E 类地址？</p></blockquote><p>而 D 类和 E 类地址是没有主机号的，所以不可用于主机 IP，D 类常被用于<strong>多播</strong>，E 类是预留的分类，暂时未使用。</p><p><img data-src="https://cdn.jonty.top/img/202309071007132.png" alt="D、E 分类地址"></p><blockquote><p>多播地址用于什么？</p></blockquote><p>多播用于<strong>将包发送给特定组内的所有主机。</strong></p><p>还是举班级的栗子，老师说：“最后一排的同学，上来做这道数学题。”，老师是指定的是最后一排的同学，也就是多播的含义了。</p><p>由于广播无法穿透路由，若想给其他网段发送同样的包，就可以使用可以穿透路由的多播（组播）。</p><p><img data-src="https://cdn.jonty.top/img/202309071008044.png" alt="单播、广播、多播通信"></p><p>多播使用的 D 类地址，其前四位是 <code>1110</code> 就表示是多播地址，而剩下的 28 位是多播的组编号。</p><p>从 224.0.0.0 ~ 239.255.255.255 都是多播的可用范围，其划分为以下三类：</p><ul><li>224.0.0.0 ~ 224.0.0.255 为预留的组播地址，只能局域网中，路由器是不会进行转发的</li><li>224.0.1.0 ~ 238.255.255.255  为用户可用的组播地址，可以用于 Internet 上</li><li>239.0.0.0 ~ 239.255.255.255 为本地管理组播地址，可供内部网在内部使用，仅在特定的本地范围内有效</li></ul><blockquote><p>IP 分类的优点</p></blockquote><p>不管是路由器还是主机解析到一个 IP 地址时候，我们判断其 IP 地址的首位是否为 0，为 0 则为 A 类地址，那么就能很快的找出网络地址和主机地址。</p><p>其余分类判断方式参考如下图：</p><p><img data-src="https://cdn.jonty.top/img/202309071009420.png" alt="IP 分类判断"></p><p>所以，这种分类地址的优点就是<strong>简单明了、选路（基于网络地址）简单</strong>。</p><blockquote><p>IP 分类的缺点</p></blockquote><p><em>缺点一</em></p><p><strong>同一网络下没有地址层次</strong>，比如一个公司里用了 B 类地址，但是可能需要根据生产环境、测试环境、开发环境来划分地址层次，而这种 IP 分类是没有地址层次划分的功能，所以这就<strong>缺少地址的灵活性</strong>。</p><p><em>缺点二</em></p><p>A、B、C类有个尴尬处境，就是<strong>不能很好的与现实网络匹配</strong>。</p><ul><li>C 类地址能包含的最大主机数量实在太少了，只有 254 个，估计一个网吧都不够用。</li><li>而 B 类地址能包含的最大主机数量又太多了，6 万多台机器放在一个网络下面，一般的企业基本达不到这个规模，闲着的地址就是浪费。</li></ul><p>这两个缺点，都可以在 <code>CIDR</code> 无分类地址解决。</p><h4 id="无分类地址-CIDR"><a href="#无分类地址-CIDR" class="headerlink" title="无分类地址 CIDR"></a>无分类地址 CIDR</h4><p>正因为 IP 分类存在许多缺点，所有后面提出了无分类地址的方案，即 <code>CIDR</code>。</p><p>这种方式不再有分类地址的概念，32 比特的 IP 地址被划分为两部分，前面是<strong>网络号</strong>，后面是<strong>主机号</strong>。</p><blockquote><p>怎么划分网络号和主机号的呢？</p></blockquote><p>表示形式 <code>a.b.c.d/x</code>，其中 <code>/x</code> 表示前 x 位属于<strong>网络号</strong>， x 的范围是 <code>0 ~ 32</code>，这就使得 IP 地址更加具有灵活性。</p><p>比如 10.100.122.2/24，这种地址表示形式就是 CIDR，<code>/24</code> 表示前 24 位是网络号，剩余的 8 位是主机号。</p><p><img data-src="https://cdn.jonty.top/img/202309071010223.png"></p><p>还有另一种划分网络号与主机号形式，那就是<strong>子网掩码</strong>，掩码的意思就是掩盖掉主机号，剩余的就是网络号。</p><p><strong>将子网掩码和 IP 地址按位计算 AND，就可得到网络号。</strong></p><p><img data-src="https://cdn.jonty.top/img/202309071010949.png"></p><blockquote><p>为什么要分离网络号和主机号？</p></blockquote><p>因为两台计算机要通讯，首先要判断是否处于同一个广播域内，即网络地址是否相同。如果网络地址相同，表明接受方在本网络上，那么可以把数据包直接发送到目标主机，</p><p>路由器寻址工作中，也就是通过这样的方式来找到对应的网络号的，进而把数据包转发给对应的网络内。</p><p><img data-src="https://cdn.jonty.top/img/202309071017210.png" alt="IP地址的网络号"></p><blockquote><p>怎么进行子网划分？</p></blockquote><p>在上面我们知道可以通过子网掩码划分出网络号和主机号，那实际上子网掩码还有一个作用，那就是<strong>划分子网</strong>。</p><p><strong>子网划分实际上是将主机地址分为两个部分：子网网络地址和子网主机地址</strong>。形式如下：</p><p><img data-src="https://cdn.jonty.top/img/202309071010496.png" alt=" "></p><ul><li>未做子网划分的 ip 地址：网络地址＋主机地址</li><li>做子网划分后的 ip 地址：网络地址＋（子网网络地址＋子网主机地址）</li></ul><p>假设对 C 类地址进行子网划分，网络地址 192.168.1.0，使用子网掩码 255.255.255.192 对其进行子网划分。</p><p>C 类地址中前 24 位 是网络号，最后 8 位是主机号，根据子网掩码可知<strong>从 8 位主机号中借用 2 位作为子网号</strong>。</p><p><img data-src="https://cdn.jonty.top/img/202309071010498.png" alt=" "></p><p>由于子网网络地址被划分成 2 位，那么子网地址就有 4 个，分别是 00、01、10、11，具体划分如下图：</p><p><img data-src="https://cdn.jonty.top/img/202309071017676.png"></p><p>划分后的 4 个子网如下表格：</p><p><img data-src="https://cdn.jonty.top/img/202309071018562.png"></p><h4 id="公有-IP-地址与私有-IP-地址"><a href="#公有-IP-地址与私有-IP-地址" class="headerlink" title="公有 IP 地址与私有 IP 地址"></a>公有 IP 地址与私有 IP 地址</h4><p>在 A、B、C 分类地址，实际上有分公有 IP 地址和 私有 IP 地址。</p><p><img data-src="https://cdn.jonty.top/img/202309071018330.png"></p><p>平时我们办公室、家里、学校用的 IP 地址，一般都是私有 IP 地址。因为这些地址允许组织内部的 IT 人员自己管理、自己分配，而且可以重复。因此，你学校的某个私有 IP 地址和我学校的可以是一样的。</p><p>就像每个小区都有自己的楼编号和门牌号，你小区家可以叫  1 栋 101 号，我小区家也可以叫 1 栋 101，没有任何问题。但一旦出了小区，就需要带上中山路 666 号（公网 IP 地址），是国家统一分配的，不能两个小区都叫中山路 666。</p><p>所以，公有 IP 地址是有个组织统一分配的，假设你要开一个博客网站，那么你就需要去申请购买一个公有 IP，这样全世界的人才能访问。并且公有 IP 地址基本上要在整个互联网范围内保持唯一。</p><p><img data-src="https://cdn.jonty.top/img/202309071018546.png" alt="公有 IP 地址与私有 IP 地址"></p><blockquote><p>公有 IP 地址由谁管理呢？</p></blockquote><p>私有 IP 地址通常是内部的 IT 人员值管理，公有 IP 地址是由 <code>ICANN</code> 组织管理，中文叫「互联网名称与数字地址分配机构」。</p><p>IANA 是 ICANN 的其中一个机构，它负责分配互联网 IP 地址，是按州的方式层层分配。</p><p><img data-src="https://cdn.jonty.top/img/202309071018715.png"></p><ul><li>ARIN 北美地区</li><li>LACNIC 拉丁美洲和一些加勒比群岛</li><li>RIPE NCC 欧洲、中东和中亚</li><li>AfriNIC 非洲地区</li><li>APNIC 亚太地区</li></ul><p>其中，在中国是由 CNNIC 的机构进行管理，它是中国国内唯一指定的全局 IP 地址管理的组织。</p><h4 id="IP-地址与路由控制"><a href="#IP-地址与路由控制" class="headerlink" title="IP 地址与路由控制"></a>IP 地址与路由控制</h4><p>IP地址的<strong>网络地址</strong>这一部分是用于进行路由控制。</p><p>路由控制表中记录着网络地址与下一步应该发送至路由器的地址，在主机和路由器上都会有各自的路由器控制表。</p><p>在发送 IP 包时，首先要确定 IP 包首部中的目标地址，再从路由控制表中找到与该地址具有<strong>相同网络地址</strong>的记录，根据该记录将 IP 包转发给相应的下一个路由器。如果路由控制表中存在多条相同网络地址的记录，就选择相同位数最多的网络地址，也就是最长匹配。</p><p>下面以下图的网络链路作为例子说明：</p><p><img data-src="https://cdn.jonty.top/img/202309071018655.png" alt="IP 地址与路由控制"></p><ol><li>主机 A 要发送一个 IP 包，其源地址是 <code>10.1.1.30</code> 和目标地址是 <code>10.1.2.10</code>，由于没有在主机 A 的路由表找到与目标地址 <code>10.1.2.10</code> 的网络地址，于是把包被转发到默认路由（路由器 <code>1</code> ）</li><li>路由器 <code>1</code> 收到 IP 包后，也在路由器 <code>1</code> 的路由表匹配与目标地址相同的网络地址记录，发现匹配到了，于是就把 IP 数据包转发到了 <code>10.1.0.2</code> 这台路由器 <code>2</code></li><li>路由器 <code>2</code> 收到后，同样对比自身的路由表，发现匹配到了，于是把 IP 包从路由器 <code>2</code> 的 <code>10.1.2.1</code> 这个接口出去，最终经过交换机把 IP 数据包转发到了目标主机</li></ol><blockquote><p>环回地址是不会流向网络</p></blockquote><p>环回地址是在同一台计算机上的程序之间进行网络通信时所使用的一个默认地址。</p><p>计算机使用一个特殊的 IP 地址 <strong>127.0.0.1 作为环回地址，</strong>与该地址具有相同意义的是一个叫做 <code>localhost</code> 的主机名。</p><p>使用这个 IP 或主机名时，数据包不会流向网络。</p><h4 id="IP-分片与重组"><a href="#IP-分片与重组" class="headerlink" title="IP 分片与重组"></a>IP 分片与重组</h4><p>每种数据链路的最大传输单元 <code>MTU</code> 都是不相同的，如 FDDI 数据链路 MTU 4352、以太网的 MTU 是 1500 字节等。</p><p>每种数据链路的 MTU 之所以不同，是因为每个不同类型的数据链路的使用目的不同。使用目的不同，可承载的 MTU 也就不同。</p><p>其中，我们最常见数据链路是以太网，它的 MTU 是 <code>1500</code> 字节。</p><p>那么当 IP 数据包大小大于 MTU 时， IP 数据包就会被分片。</p><p>经过分片之后的 IP 数据报在被重组的时候，只能由目标主机进行，路由器是不会进行重组的。</p><p>假设发送方发送一个 4000 字节的大数据报，若要传输在以太网链路，则需要把数据报分片成 3 个小数据报进行传输，再交由接收方重组成大数据报。</p><p><img data-src="https://cdn.jonty.top/img/202309071018909.png" alt="分片与重组"></p><p>在分片传输中，一旦某个分片丢失，则会造成整个 IP 数据报作废，所以 TCP 引入了 <code>MSS</code> 也就是在 TCP 层进行分片不由 IP 层分片，那么对于 UDP 我们尽量不要发送一个大于 <code>MTU</code> 的数据报文。</p><h4 id="IPv6-基本认识"><a href="#IPv6-基本认识" class="headerlink" title="IPv6 基本认识"></a>IPv6 基本认识</h4><p>IPv4 的地址是 32 位的，大约可以提供 42 亿个地址，但是早在 2011 年 IPv4 地址就已经被分配完了。</p><p>但是 IPv6 的地址是 <code>128</code> 位的，这可分配的地址数量是大的惊人，说个段子 <strong>IPv6 可以保证地球上的每粒沙子都能被分配到一个 IP 地址。</strong></p><p>但 IPv6 除了有更多的地址之外，还有更好的安全性和扩展性，说简单点就是 IPv6 相比于 IPv4 能带来更好的网络体验。</p><p>但是因为 IPv4 和 IPv6 不能相互兼容，所以不但要我们电脑、手机之类的设备支持，还需要网络运营商对现有的设备进行升级，所以这可能是 IPv6 普及率比较慢的一个原因。</p><blockquote><p>IPv6 的亮点</p></blockquote><p>IPv6 不仅仅只是可分配的地址变多了，他还有非常多的亮点。</p><ul><li>IPv6 可自动配置，即使没有 DHCP 服务器也可以实现自动分配IP地址，真是<strong>便捷到即插即用</strong>啊。</li><li>IPv6 包头包首部长度采用固定的值 <code>40</code> 字节，去掉了包头校验和，简化了首部结构，减轻了路由器负荷，大大<strong>提高了传输的性能</strong>。</li><li>IPv6 有应对伪造 IP 地址的网络安全功能以及防止线路窃听的功能，大大<strong>提升了安全性</strong>。</li><li><strong>…</strong> （由你发现更多的亮点）</li></ul><blockquote><p>IPv6 地址的标识方法</p></blockquote><p>IPv4 地址长度共 32 位，是以每 8 位作为一组，并用点分十进制的表示方式。</p><p>IPv6 地址长度是 128 位，是以每 16 位作为一组，每组用冒号 「:」 隔开。</p><p><img data-src="https://cdn.jonty.top/img/202309071019953.png" alt="IPv6 地址表示方法"></p><p>如果出现连续的 0 时还可以将这些 0 省略，并用两个冒号 「::」隔开。但是，一个 IP 地址中只允许出现一次两个连续的冒号。</p><p><img data-src="https://cdn.jonty.top/img/202309071019678.png" alt="Pv6 地址缺省表示方"></p><blockquote><p>IPv6 地址的结构</p></blockquote><p>IPv6 类似 IPv4，也是通过 IP 地址的前几位标识 IP 地址的种类。</p><p>IPv6 的地址主要有一下类型地址：</p><ul><li>单播地址，用于一对一的通信</li><li>组播地址，用于一对多的通信</li><li>任播地址，用于通信最近的节点，最近的节点是由路由协议决定</li><li>没有广播地址</li></ul><p><img data-src="https://cdn.jonty.top/img/202309071020170.png" alt="IPv6地址结构"></p><blockquote><p>IPv6 单播地址类型</p></blockquote><p>对于一对一通信的 IPv6 地址，主要划分了三类单播地址，每类地址的有效范围都不同。</p><ul><li>在同一链路单播通信，不经过路由器，可以使用<strong>链路本地单播地址</strong>，IPv4 没有此类型</li><li>在内网里单播通信，可以使用<strong>唯一本地地址</strong>，相当于 IPv4 的私有 IP</li><li>在互联网通信，可以使用<strong>全局单播地址</strong>，相当于 IPv4 的公有 IP</li></ul><p><img data-src="https://cdn.jonty.top/img/202309071020294.png" alt="IPv6 中的单播通信"></p><h4 id="IPv4-首部与-IPv6-首部"><a href="#IPv4-首部与-IPv6-首部" class="headerlink" title="IPv4 首部与 IPv6 首部"></a>IPv4 首部与 IPv6 首部</h4><p>IPv4 首部与 IPv6 首部的差异如下图：</p><p><img data-src="https://cdn.jonty.top/img/202309071021885.png" alt="IPv4 首部与 IPv6 首部的差异"></p><p>IPv6 相比 IPv4 的首部改进：</p><ul><li><strong>取消了首部校验和字段。</strong> 因为在数据链路层和传输层都会校验，因此 IPv6 直接取消了 IP 的校验。</li><li><strong>取消了分片/重新组装相关字段。</strong> 分片与重组是耗时的过程，IPv6 不允许在中间路由器进行分片与重组，这种操作只能在源与目标主机，这将大大提高了路由器转发的速度。</li><li><strong>取消选项字段。</strong> 选项字段不再是标准 IP 首部的一部分了，但它并没有消失，而是可能出现在 IPv6 首部中的「下一个首部」指出的位置上。删除该选项字段是的 IPv6 的首部成为固定长度的 <code>40</code> 字节。</li></ul><hr><h3 id="点心-——-IP-协议相关技术"><a href="#点心-——-IP-协议相关技术" class="headerlink" title="点心 —— IP 协议相关技术"></a>点心 —— IP 协议相关技术</h3><p>跟 IP 协议相关的技术也不少，接下来说说与 IP 协议相关的重要且常见的技术。</p><ul><li>DNS 域名解析</li><li>ARP 与 RARP 协议</li><li>DHCP 动态获取 IP 地址</li><li>NAT 网络地址转换</li><li>ICMP 互联网控制报文协议</li><li>IGMP 因特网组管理协</li></ul><h4 id="DNS"><a href="#DNS" class="headerlink" title="DNS"></a>DNS</h4><p>我们在上网的时候，通常使用的方式域名，而不是 IP 地址，因为域名方便人类记忆。</p><p>那么实现这一技术的就是 <strong>DNS 域名解析</strong>，DNS 可以将域名网址自动转换为具体的 IP 地址。</p><blockquote><p>域名的层级关系</p></blockquote><p>DNS 中的域名都是用<strong>句点</strong>来分隔的，比如 <code>www.server.com</code>，这里的句点代表了不同层次之间的<strong>界限</strong>。</p><p>在域名中，<strong>越靠右</strong>的位置表示其层级<strong>越高</strong>。</p><p>毕竟域名是外国人发明，所以思维和中国人相反，比如说一个城市地点的时候，外国喜欢从小到大的方式顺序说起（如 XX 街道 XX 区 XX 市 XX 省），而中国则喜欢从大到小的顺序（如 XX 省 XX 市 XX 区 XX 街道）。</p><p>根域是在最顶层，它的下一层就是 com 顶级域，再下面是 server.com。</p><p>所以域名的层级关系类似一个树状结构：</p><ul><li>根 DNS 服务器</li><li>顶级域 DNS 服务器（com）</li><li>权威 DNS 服务器（server.com）</li></ul><p><img data-src="https://cdn.jonty.top/img/202309071021280.png" alt="DNS 树状结构"></p><p>根域的 DNS 服务器信息保存在互联网中所有的 DNS 服务器中。这样一来，任何 DNS 服务器就都可以找到并访问根域 DNS 服务器了。</p><p>因此，客户端只要能够找到任意一台 DNS 服务器，就可以通过它找到根域 DNS 服务器，然后再一路顺藤摸瓜找到位于下层的某台目标 DNS 服务器。</p><blockquote><p>域名解析的工作流程</p></blockquote><p>浏览器首先看一下自己的缓存里有没有，如果没有就向操作系统的缓存要，还没有就检查本机域名解析文件 <code>hosts</code>，如果还是没有，就会 DNS 服务器进行查询，查询的过程如下：</p><ol><li>客户端首先会发出一个 DNS 请求，问 <a href="http://www.server.com/">www.server.com</a> 的 IP 是啥，并发给本地 DNS 服务器（也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址）。</li><li>本地域名服务器收到客户端的请求后，如果缓存里的表格能找到 <a href="http://www.server.com,则它直接返回/">www.server.com，则它直接返回</a> IP 地址。如果没有，本地 DNS 会去问它的根域名服务器：“老大， 能告诉我 <a href="http://www.server.com/">www.server.com</a> 的 IP 地址吗？” 根域名服务器是最高层次的，它不直接用于域名解析，但能指明一条道路。</li><li>根 DNS 收到来自本地 DNS 的请求后，发现后置是 .com，说：“<a href="http://www.server.com/">www.server.com</a> 这个域名归 .com 区域管理”，我给你 .com 顶级域名服务器地址给你，你去问问它吧。”</li><li>本地 DNS 收到顶级域名服务器的地址后，发起请求问“老二， 你能告诉我 <a href="http://www.server.com/">www.server.com</a>  的 IP 地址吗？”</li><li>顶级域名服务器说：“我给你负责 <a href="http://www.server.com/">www.server.com</a> 区域的权威 DNS 服务器的地址，你去问它应该能问到”。</li><li>本地 DNS 于是转向问权威 DNS 服务器：“老三，<a href="http://www.server.com对应的IP是啥呀？”">www.server.com对应的IP是啥呀？”</a> server.com 的权威 DNS 服务器，它是域名解析结果的原出处。为啥叫权威呢？就是我的域名我做主。</li><li>权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。</li><li>本地 DNS 再将 IP 地址返回客户端，客户端和目标建立连接。</li></ol><p>至此，我们完成了 DNS 的解析过程。现在总结一下，整个过程我画成了一个图。</p><p><img data-src="https://cdn.jonty.top/img/202309071021815.png" alt="域名解析的工作流程"></p><p>DNS 域名解析的过程蛮有意思的，整个过程就和我们日常生活中找人问路的过程类似，<strong>只指路不带路</strong>。</p><h4 id="ARP"><a href="#ARP" class="headerlink" title="ARP"></a>ARP</h4><p>在传输一个 IP 数据报的时候，确定了源 IP 地址和目标 IP 地址后，就会通过主机「路由表」确定 IP 数据包下一跳。然而，网络层的下一层是数据链路层，所以我们还要知道「下一跳」的 MAC 地址。</p><p>由于主机的路由表中可以找到下一条的 IP 地址，所以可以通过 <strong>ARP 协议</strong>，求得下一跳的 MAC 地址。</p><blockquote><p>那么 ARP 又是如何知道对方 MAC 地址的呢？</p></blockquote><p>简单地说，ARP 是借助 <strong>ARP 请求与 ARP 响应</strong>两种类型的包确定 MAC 地址的。</p><p><img data-src="https://cdn.jonty.top/img/202309071021309.png" alt="ARP 广播"></p><ul><li>主机会通过<strong>广播发送 ARP 请求</strong>，这个包中包含了想要知道的 MAC 地址的主机 IP 地址。</li><li>当同个链路中的所有设备收到 ARP 请求时，会去拆开 ARP 请求包里的内容，如果 ARP 请求包中的目标 IP 地址与自己的 IP 地址一致，那么这个设备就将自己的 MAC 地址塞入 <strong>ARP 响应包</strong>返回给主机。</li></ul><p>操作系统通常会把第一次通过 ARP 获取的 MAC 地址缓存起来，以便下次直接从缓存中找到对应 IP 地址的 MAC 地址。</p><p>不过，MAC 地址的缓存是有一定期限的，超过这个期限，缓存的内容将被清除。</p><blockquote><p>RARP 协议你知道是什么吗？</p></blockquote><p>ARP 协议是已知 IP 地址 求 MAC 地址，那 RARP 协议正好相反。</p><p>它是<strong>已知 MAC 地址求 IP 地址</strong>。例如将打印机服务器等小型嵌入式设备接入到网络时就经常会用得到。</p><p>通常这需要架设一台 <code>RARP</code> 服务器，在这个服务器上注册设备的 MAC 地址及其 IP 地址。然后再将这个设备接入到网络，接着：</p><ul><li>该设备会发送一条「我的 MAC 地址是XXXX，请告诉我，我的IP地址应该是什么」的请求信息。</li><li>RARP 服务器接到这个消息后返回「MAC地址为 XXXX 的设备，IP地址为 XXXX」的信息给这个设备。</li></ul><p>最后，设备就根据从 RARP 服务器所收到的应答信息设置自己的 IP 地址。</p><p><img data-src="https://cdn.jonty.top/img/202309071022117.png" alt="RARP"></p><h4 id="DHCP"><a href="#DHCP" class="headerlink" title="DHCP"></a>DHCP</h4><p>DHCP 在生活中我们是很常见的了，我们的电脑通常都是通过 DHCP 动态获取 IP 地址，大大省去了配 IP 信息繁琐的过程。</p><p>接下来，我们来看看我们的电脑是如何通过 4 个步骤的过程，获取到 IP 的。</p><p><img data-src="https://cdn.jonty.top/img/202309071022333.png" alt="DHCP 工作流程"></p><p>先说明一点，DHCP 客户端进程监听的是 68 端口号，DHCP 服务端进程监听的是 67 端口号。</p><p>DHCP 交互的 4 个步骤：</p><ul><li>客户端首先发起 <strong>DHCP 发现报文（DHCP DISCOVER）</strong> 的 IP 数据报，由于客户端没有 IP 地址，也不知道 DHCP 服务器的地址，所以使用的是 UDP <strong>广播</strong>通信，其使用的广播目的地址是 255.255.255.255（端口 67） 并且使用 0.0.0.0（端口 68） 作为源 IP 地址。DHCP 客户端将该 IP 数据报传递给链路层，链路层然后将帧广播到所有的网络中设备。</li><li>DHCP 服务器收到 DHCP 发现报文时，用 <strong>DHCP 提供报文（DHCP OFFER）</strong> 向客户端做出响应。该报文仍然使用 IP 广播地址 255.255.255.255，该报文信息携带服务器提供可租约的 IP 地址、子网掩码、默认网关、DNS 服务器以及 <strong>IP 地址租用期</strong>。</li><li>客户端收到一个或多个服务器的 DHCP 提供报文后，从中选择一个服务器，并向选中的服务器发送 <strong>DHCP 请求报文（DHCP REQUEST</strong>进行响应，回显配置的参数。</li><li>最后，服务端用 <strong>DHCP ACK 报文</strong>对 DHCP 请求报文进行响应，应答所要求的参数。</li></ul><p>一旦客户端收到 DHCP ACK 后，交互便完成了，并且客户端能够在租用期内使用 DHCP 服务器分配的 IP 地址。</p><p>如果租约的 DHCP IP 地址快期后，客户端会向服务器发送 DHCP 请求报文：</p><ul><li>服务器如果同意继续租用，则用 DHCP ACK 报文进行应答，客户端就会延长租期。</li><li>服务器如果不同意继续租用，则用 DHCP NACK 报文，客户端就要停止使用租约的 IP 地址。</li></ul><p>可以发现，DHCP 交互中，<strong>全程都是使用 UDP 广播通信</strong>。</p><blockquote><p>咦，用的是广播，那如果 DHCP 服务器和客户端不是在同一个局域网内，路由器又不会转发广播包，那不是每个网络都要配一个 DHCP 服务器？</p></blockquote><p>所以，为了解决这一问题，就出现了 <strong>DHCP 中继代理</strong>。</p><p>有了 DHCP 中继代理以后，<strong>对不同网段的 IP 地址分配也可以由一个 DHCP 服务器统一进行管理。</strong></p><p><img data-src="https://cdn.jonty.top/img/202309071022612.png" alt="DHCP 中继代理"></p><ul><li>DHCP 客户端会向 DHCP 中继代理发送 DHCP 请求包，而 DHCP 中继代理在收到这个广播包以后，再以<strong>单播</strong>的形式发给 DHCP 服务器。</li><li>服务器端收到该包以后再向 DHCP 中继代理返回应答，并由 DHCP 中继代理将此包转发给 DHCP 客户端 。</li></ul><p>因此，DHCP 服务器即使不在同一个链路上也可以实现统一分配和管理IP地址。</p><h4 id="NAT"><a href="#NAT" class="headerlink" title="NAT"></a>NAT</h4><p>IPv4 的地址是非常紧缺的，在前面我们也提到可以通过无分类地址来减缓 IPv4 地址耗尽的速度，但是互联网的用户增速是非常惊人的，所以 IPv4 地址依然有被耗尽的危险。</p><p>于是，提出了一个种<strong>网络地址转换 NAT</strong> 的方法，再次缓解了 IPv4 地址耗尽的问题。</p><p>简单的来说 NAT 就是在同个公司、家庭、教室内的主机对外部通信时，把私有 IP 地址转换成公有 IP 地址。</p><p><img data-src="https://cdn.jonty.top/img/202309071022779.png" alt="NAT"></p><blockquote><p>那不是 N 个 私有 IP 地址，你就要 N 个公有 IP 地址？这怎么就缓解了 IPv4 地址耗尽的问题？这不瞎扯吗？</p></blockquote><p>确实是，普通的 NAT 转换没什么意义。</p><p>由于绝大多数的网络应用都是使用传输层协议 TCP 或 UDP 来传输数据的。</p><p>因此，可以把 IP 地址 + 端口号一起进行转换。</p><p>这样，就用一个全球 IP 地址就可以了，这种转换技术就叫<strong>网络地址与端口转换 NAPT。</strong></p><p>很抽象？来，看下面的图解就能瞬间明白了。</p><p><img data-src="https://cdn.jonty.top/img/202309071023670.png" alt="NAPT">NAPT</p><p>图中有两个客户端 192.168.1.10 和 192.168.1.11 同时与服务器 183.232.231.172 进行通信，并且这两个客户端的本地端口都是 1025。</p><p>此时，<strong>两个私有 IP 地址都转换 IP 地址为公有地址 120.229.175.121，但是以不同的端口号作为区分。</strong></p><p>于是，生成一个 NAPT 路由器的转换表，就可以正确地转换地址跟端口的组合，令客户端 A、B 能同时与服务器之间进行通信。</p><p>这种转换表在 NAT 路由器上自动生成。例如，在 TCP 的情况下，建立 TCP 连接首次握手时的 SYN 包一经发出，就会生成这个表。而后又随着收到关闭连接时发出 FIN 包的确认应答从表中被删除。</p><blockquote><p>NAT 那么牛逼，难道就没缺点了吗？</p></blockquote><p>当然有缺陷，肯定没有十全十美的方案。</p><p>由于 NAT/NAPT 都依赖于自己的转换表，因此会有以下的问题：</p><ul><li>外部无法主动与 NAT 内部服务器建立连接，因为 NAPT 转换表没有转换记录。</li><li>转换表的生产与转换操作都会产生性能开销。</li><li>通信过程中，如果 NAT 路由器重启了，所有的 TCP 连接都将被重置。</li></ul><blockquote><p>如何解决 NAT 潜在的问题呢？</p></blockquote><p>解决的方法主要两种方法。</p><p><em>第一种就是改用 IPv6</em></p><p>IPv6 可用范围非常大，以至于每台设备都可以配置一个公有 IP 地址，就不搞那么多花里胡哨的地址转换了，但是 IPv6 普及速度还需要一些时间。</p><p><em>第二种 NAT 穿透技术</em></p><p>NAT 穿越技术拥有这样的功能，它能够让网络应用程序主动发现自己位于 NAT 设备之后，并且会主动获得 NAT 设备的公有 IP，并为自己建立端口映射条目，注意这些都是 NAT设备后的应用程序自动完成的。</p><p>也就是说，在 NAT 穿越技术中，NAT 设备后的应用程序处于主动地位，它已经明确地知道 NAT 设备要修改它外发的数据包，于是它主动配合 NAT 设备的操作，主动地建立好映射，这样就不像以前由 NAT 设备来建立映射了。</p><p>说人话，就是客户端主动从 NAT 设备获取公有 IP 地址，然后自己建立端口映射条目，然后用这个条目对外通信，就不需要 NAT 设备来进行转换了。</p><h4 id="ICMP"><a href="#ICMP" class="headerlink" title="ICMP"></a>ICMP</h4><p>ICMP 全称是 <strong>Internet Control Message Protocol</strong>，也就是<strong>互联网控制报文协议</strong>。</p><p>里面有个关键词 —— <strong>控制</strong>，如何控制的呢？</p><p>网络包在复杂的网络传输环境里，常常会遇到各种问题。</p><p>当遇到问题的时候，总不能死个不明不白，没头没脑的作风不是计算机网络的风格。所以需要传出消息，报告遇到了什么问题，这样才可以调整传输策略，以此来控制整个局面。</p><blockquote><p>ICMP 功能都有啥？</p></blockquote><p><code>ICMP</code> 主要的功能包括：<strong>确认 IP 包是否成功送达目标地址、报告发送过程中 IP 包被废弃的原因和改善网络设置等。</strong></p><p>在 <code>IP</code> 通信中如果某个 <code>IP</code> 包因为某种原因未能达到目标地址，那么这个具体的原因将<strong>由 ICMP 负责通知</strong>。</p><p><img data-src="https://cdn.jonty.top/img/202309071023492.png" alt="ICMP 目标不可达消息"></p><p>如上图例子，主机 <code>A</code> 向主机 <code>B</code> 发送了数据包，由于某种原因，途中的路由器 <code>2</code> 未能发现主机 <code>B</code> 的存在，这时，路由器 <code>2</code> 就会向主机 <code>A</code> 发送一个 <code>ICMP</code> 目标不可达数据包，说明发往主机 <code>B</code> 的包未能成功。</p><p>ICMP 的这种通知消息会使用 <code>IP</code> 进行发送 。</p><p>因此，从路由器 <code>2</code> 返回的 ICMP 包会按照往常的路由控制先经过路由器 <code>1</code> 再转发给主机 <code>A</code> 。收到该 ICMP 包的主机 <code>A</code> 则分解 ICMP 的首部和数据域以后得知具体发生问题的原因。</p><blockquote><p>ICMP 类型</p></blockquote><p>ICMP 大致可以分为两大类：</p><ul><li>一类是用于诊断的查询消息，也就是「<strong>查询报文类型</strong>」</li><li>另一类是通知出错原因的错误消息，也就是「<strong>差错报文类型</strong>」</li></ul><p><img data-src="https://cdn.jonty.top/img/202309071023450.png" alt="常见的 ICMP 类型"></p><h4 id="IGMP"><a href="#IGMP" class="headerlink" title="IGMP"></a>IGMP</h4><p>ICMP 跟 IGMP 是一点关系都没有的，就好像周杰与周杰伦的区别，大家不要混淆了。</p><p>在前面我们知道了组播地址，也就是 D 类地址，既然是组播，那就说明是只有一组的主机能收到数据包，不在一组的主机不能收到数组包，怎么管理是否是在一组呢？那么，就需要 <code>IGMP</code> 协议了。</p><p><img data-src="https://cdn.jonty.top/img/202309071023807.png" alt="组播模型"></p><p><strong>IGMP 是因特网组管理协议，工作在主机（组播成员）和最后一跳路由之间</strong>，如上图中的蓝色部分。</p><ul><li>IGMP 报文向路由器申请加入和退出组播组，默认情况下路由器是不会转发组播包到连接中的主机，除非主机通过 IGMP 加入到组播组，主机申请加入到组播组时，路由器就会记录 IGMP 路由器表，路由器后续就会转发该组播地址的数据包了。</li><li>IGMP 报文采用 IP 封装，IP 头部的协议号为 2，而且 TTL 字段值通常 为 1，因为 IGMP 是工作在主机与连接的路由器之间。</li></ul><blockquote><p>IGMP 工作机制</p></blockquote><p>IGMP 分为了三个版本分别是，IGMPv1、IGMPv2、IGMPv3。</p><p>接下来，以 <code>IGMPv2</code> 作为例子，说说<strong>常规查询与响应和离开组播组</strong>这两个工作机制。</p><p><em>常规查询与响应工作机制</em></p><p><img data-src="https://cdn.jonty.top/img/202309071023560.png" alt="IGMP 常规查询与响应工作机制"></p><ol><li>路由器会周期性发送目的地址为 <code>224.0.0.1</code>（表示同一网段内所有主机和路由器） <strong>IGMP 常规查询报文；</strong></li><li>主机1 和 主机 3 收到这个查询，随后会启动「报告延迟计时器」，计时器的时间是随机的，通常是 0~10 秒，计时器超时后主机就会发送 <strong>IGMP 成员关系报告报文</strong>（源 IP 地址为自己主机的 IP 地址，目的 IP 地址为组播地址）。如果在定时器超时之前，收到同一个组内的其他主机发送的成员关系报告报文，则自己不再发送，这样可以减少网络中多余的 IGMP 报文数量；</li><li>路由器收到主机的成员关系报告报文后，就会在 IGMP 路由表中加入该组播组，后续网络中一旦该组播地址的数据到达路由器，它会把数据包转发出去；</li></ol><p><em>离开组播组工作机制</em></p><p>离开组播组的情况一，网段中仍有该组播组：</p><p><img data-src="https://cdn.jonty.top/img/202309071024282.png" alt="IGMPv2 离开组播组工作机制 情况1"></p><ol><li>主机 1 要离开组 224.1.1.1，发送 IGMPv2 离组报文，报文的目的地址是 224.0.0.2（表示发向网段内的所有路由器）；</li><li>路由器收到该报文后，以 1 秒为间隔连续发送 IGMP 特定组查询报文（共计发送 2 个），以便确认该网络是否还有 224.1.1.1 组的其他成员；</li><li>主机 3 仍然是组 224.1.1.1 的成员，因此它立即响应这个特定组查询。路由器知道该网络中仍然存在该组播组的成员，于是继续向该网络转发 224.1.1.1 的组播数据包；</li></ol><p>离开组播组的情况二，网段中没有该组播组：</p><p><img data-src="https://cdn.jonty.top/img/202309071024481.png" alt="IGMPv2 离开组播组工作机制 情况2"></p><ol><li>主机 1 要离开组播组 224.1.1.1，发送 IGMP 离组报文；</li><li>路由器收到该报文后，以 1 秒为间隔连续发送 IGMP 特定组查询报文（共计发送 2 个）。此时在该网段内，组 224.1.1.1 已经没有其他成员了，因此没有主机响应这个查询；</li><li>一定时间后，路由器认为该网段中已经没有 224.1.1.1 组播组成员了，将不会再向这个网段转发该组播地址的数据包；</li></ol><hr><h5 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h5><p>[1] 计算机网络-自顶向下方法.陈鸣 译.机械工业出版社</p><p>[2] TCP/IP详解 卷1：协议.范建华 译.机械工业出版社</p><p>[3] 图解TCP/IP.竹下隆史.人民邮电出版社</p>]]></content>
    
    
    <summary type="html">关于 IP 分类地址、子网划分等的文章</summary>
    
    
    
    <category term="搬砖那些事儿" scheme="https://jonty.top/categories/%E6%90%AC%E7%A0%96%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/"/>
    
    
    <category term="Share" scheme="https://jonty.top/tags/Share/"/>
    
  </entry>
  
</feed>
