From 8242b0f4fd00700857bcaa23e6a6d443e2ed9491 Mon Sep 17 00:00:00 2001 From: WangJunZzz Date: Tue, 19 Sep 2023 02:47:22 +0000 Subject: [PATCH] Deployed 9777fed with MkDocs version: 1.4.2 --- .nojekyll | 0 404.html | 1255 +++ CNAME | 1 + about/6.0-7.0/index.html | 1394 ++++ about/contact-us/index.html | 1407 ++++ about/license/index.html | 1344 ++++ about/release-notes/index.html | 1389 ++++ assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.51d95adb.min.js | 29 + assets/javascripts/bundle.51d95adb.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.e5c33ebb.min.js | 42 + .../workers/search.e5c33ebb.min.js.map | 8 + assets/stylesheets/main.558e4712.min.css | 1 + assets/stylesheets/main.558e4712.min.css.map | 1 + assets/stylesheets/palette.2505c338.min.css | 1 + .../stylesheets/palette.2505c338.min.css.map | 1 + extra/javascripts/extra.js | 5 + extra/stylesheets/extra.css | 18 + img/auth.png | Bin 0 -> 59230 bytes img/dic.png | Bin 0 -> 59243 bytes img/donate.png | Bin 0 -> 39901 bytes img/favicon.ico | Bin 0 -> 10535 bytes img/file.png | Bin 0 -> 56025 bytes img/login.png | Bin 0 -> 13315 bytes img/logo.png | Bin 0 -> 2106 bytes img/logo.svg | 172 + img/migrating.png | Bin 0 -> 20066 bytes img/role.png | Bin 0 -> 55356 bytes img/send.png | Bin 0 -> 8379 bytes img/send1.png | Bin 0 -> 15259 bytes img/uo.png | Bin 0 -> 45814 bytes img/user.png | Bin 0 -> 77454 bytes index.html | 1414 ++++ search/search_index.json | 1 + sitemap.xml | 183 + sitemap.xml.gz | Bin 0 -> 574 bytes user-guide/zh/deploy/docker/index.html | 1615 ++++ user-guide/zh/deploy/github/index.html | 1683 +++++ .../zh/extension/MagicodesIE/index.html | 1446 ++++ .../index.html" | 1610 ++++ .../getting-started/contributing/index.html | 1420 ++++ .../getting-started/introduction/index.html | 1570 ++++ .../zh/getting-started/quick-start/index.html | 1555 ++++ .../zh/infrastructure/AutoMapper/index.html | 1661 ++++ .../zh/infrastructure/CorsOrigins/index.html | 1443 ++++ .../zh/infrastructure/CurrentUser/index.html | 1725 +++++ .../infrastructure/DataFiltering/index.html | 1696 +++++ .../zh/infrastructure/DataSeeding/index.html | 1515 ++++ .../zh/infrastructure/MultiTenancy/index.html | 1684 +++++ .../infrastructure/authorization/index.html | 1737 +++++ user-guide/zh/infrastructure/batch/index.html | 1737 +++++ user-guide/zh/infrastructure/cache/index.html | 1592 ++++ user-guide/zh/infrastructure/cap/index.html | 1468 ++++ .../zh/infrastructure/config/index.html | 1578 ++++ .../zh/infrastructure/elastic/index.html | 1654 ++++ .../exception-handler/index.html | 1723 +++++ .../zh/infrastructure/freesql/index.html | 1470 ++++ .../zh/infrastructure/frontend/index.html | 1688 +++++ user-guide/zh/infrastructure/log/index.html | 1646 ++++ user-guide/zh/infrastructure/login/index.html | 1456 ++++ user-guide/zh/modules/basic/index.html | 1421 ++++ user-guide/zh/modules/dic/index.html | 1598 ++++ user-guide/zh/modules/file/index.html | 1577 ++++ user-guide/zh/modules/setting/index.html | 1542 ++++ user-guide/zh/modules/signalr/index.html | 1708 +++++ user-guide/zh/problem/ef/index.html | 1702 +++++ user-guide/zh/problem/problem/index.html | 1432 ++++ 95 files changed, 65221 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 CNAME create mode 100644 about/6.0-7.0/index.html create mode 100644 about/contact-us/index.html create mode 100644 about/license/index.html create mode 100644 about/release-notes/index.html create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.51d95adb.min.js create mode 100644 assets/javascripts/bundle.51d95adb.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.e5c33ebb.min.js create mode 100644 assets/javascripts/workers/search.e5c33ebb.min.js.map create mode 100644 assets/stylesheets/main.558e4712.min.css create mode 100644 assets/stylesheets/main.558e4712.min.css.map create mode 100644 assets/stylesheets/palette.2505c338.min.css create mode 100644 assets/stylesheets/palette.2505c338.min.css.map create mode 100644 extra/javascripts/extra.js create mode 100644 extra/stylesheets/extra.css create mode 100644 img/auth.png create mode 100644 img/dic.png create mode 100644 img/donate.png create mode 100644 img/favicon.ico create mode 100644 img/file.png create mode 100644 img/login.png create mode 100644 img/logo.png create mode 100644 img/logo.svg create mode 100644 img/migrating.png create mode 100644 img/role.png create mode 100644 img/send.png create mode 100644 img/send1.png create mode 100644 img/uo.png create mode 100644 img/user.png create mode 100644 index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 user-guide/zh/deploy/docker/index.html create mode 100644 user-guide/zh/deploy/github/index.html create mode 100644 user-guide/zh/extension/MagicodesIE/index.html create mode 100644 "user-guide/zh/extension/\347\273\237\344\270\200\350\277\224\345\233\236\345\200\274\346\240\274\345\274\217/index.html" create mode 100644 user-guide/zh/getting-started/contributing/index.html create mode 100644 user-guide/zh/getting-started/introduction/index.html create mode 100644 user-guide/zh/getting-started/quick-start/index.html create mode 100644 user-guide/zh/infrastructure/AutoMapper/index.html create mode 100644 user-guide/zh/infrastructure/CorsOrigins/index.html create mode 100644 user-guide/zh/infrastructure/CurrentUser/index.html create mode 100644 user-guide/zh/infrastructure/DataFiltering/index.html create mode 100644 user-guide/zh/infrastructure/DataSeeding/index.html create mode 100644 user-guide/zh/infrastructure/MultiTenancy/index.html create mode 100644 user-guide/zh/infrastructure/authorization/index.html create mode 100644 user-guide/zh/infrastructure/batch/index.html create mode 100644 user-guide/zh/infrastructure/cache/index.html create mode 100644 user-guide/zh/infrastructure/cap/index.html create mode 100644 user-guide/zh/infrastructure/config/index.html create mode 100644 user-guide/zh/infrastructure/elastic/index.html create mode 100644 user-guide/zh/infrastructure/exception-handler/index.html create mode 100644 user-guide/zh/infrastructure/freesql/index.html create mode 100644 user-guide/zh/infrastructure/frontend/index.html create mode 100644 user-guide/zh/infrastructure/log/index.html create mode 100644 user-guide/zh/infrastructure/login/index.html create mode 100644 user-guide/zh/modules/basic/index.html create mode 100644 user-guide/zh/modules/dic/index.html create mode 100644 user-guide/zh/modules/file/index.html create mode 100644 user-guide/zh/modules/setting/index.html create mode 100644 user-guide/zh/modules/signalr/index.html create mode 100644 user-guide/zh/problem/ef/index.html create mode 100644 user-guide/zh/problem/problem/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..04d1aca79 --- /dev/null +++ b/404.html @@ -0,0 +1,1255 @@ + + + + + + + + + + + + + + + + + + + + + + Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..8d2756b43 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +doc.cncore.club diff --git a/about/6.0-7.0/index.html b/about/6.0-7.0/index.html new file mode 100644 index 000000000..38c34e670 --- /dev/null +++ b/about/6.0-7.0/index.html @@ -0,0 +1,1394 @@ + + + + + + + + + + + + + + + + + + + + + + + + 6.0 升级到 7.0 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

6.0 升级到 7.0

+

更新 .NET 版本

+
    +
  • 修改解决⽅案中 global.json
  • +
+
JSON
1
+2
+3
+4
+5
+6
{
+  "sdk": {
+    "version": "7.0.304",
+    "rollForward": "latestFeature"
+  }
+}
+
+

修改程序集版本

+
    +
  • Directory.Build.Volo.targets
  • +
  • 6.0.1 改为 7.0.0
  • +
  • Directory.Build.Microsoft.targets
  • +
  • 微软官方包升级到 7.0.2
  • +
  • Microsoft.Extensions.Hosting 6.0.1->7.0.0
  • +
  • Microsoft.NET.Test.Sdk 16.9.1->17.2.0
  • +
  • Directory.Build.targets
  • +
  • DotNetCore.CAP 升级到 7.0.2
  • +
  • 移除 Newtonsoft.Json
  • +
+

修改目标框架

+
    +
  • ⽬标框架(TargetFramework)为 net6.0 -> net7.0
  • +
  • 修改 Lion.AbpPro.CAP 目标框架(TargetFramework) netstandard2.1 -> net7.0
  • +
+

新增了四张表

+
    +
  • AbpFeatureGroups 功能组表
  • +
  • AbpFeatures 功能表
  • +
  • AbpPermissionGroups 权限组表
  • +
  • AbpPermissions 权限表
  • +
  • 执行 EF 命令生成迁移文件并更新数据库
  • +
+

动态权限带来的影响

+ +

前端

+
    +
  • 修改 package.json
  • +
  • "@microsoft/signalr": "^6.0.1" -> "@microsoft/signalr": "^7.0.2"
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/about/contact-us/index.html b/about/contact-us/index.html new file mode 100644 index 000000000..b61d1b536 --- /dev/null +++ b/about/contact-us/index.html @@ -0,0 +1,1407 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contact Us - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

Contact Us

+ +

Authors

+
    +
  • Author: @WangJunZzz
  • +
  • Email: 510423039@qq.com
  • +
  • Blogs: https://www.cnblogs.com/WangJunZzz
  • +
+

赞助

+

如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持! +

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/about/license/index.html b/about/license/index.html new file mode 100644 index 000000000..79bcb3a32 --- /dev/null +++ b/about/license/index.html @@ -0,0 +1,1344 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + License - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

License

+

MIT License

+

MIT License

+

Copyright © 2021 WangJunZzz

+

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+

The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.

+

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/about/release-notes/index.html b/about/release-notes/index.html new file mode 100644 index 000000000..681620839 --- /dev/null +++ b/about/release-notes/index.html @@ -0,0 +1,1389 @@ + + + + + + + + + + + + + + + + + + + + + + + + 发行说明 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

发行说明

+

5.3.2.7

+

功能

+
    +
  • 封装abp 自带模板到BasicManagement
  • +
+

5.3.2.5

+

功能

+
    +
  • 移除IdentityServer4 #45
  • +
+

Bug

+
    +
  • 导出用户权限错误
  • +
+

5.3.2.4

+

Bug

+
    +
  • 升级Vben2.8,组织机构编辑错误 #62
  • +
+

5.3.2.3

+

功能

+
    +
  • 权限菜单级联操作 #48
  • +
+

Bug

+
    +
  • 统一参数返回值过滤器,空指针异常 #61
  • +
+

5.3.2.2

+

功能

+
    +
  • 启用GlobalUsing功能 #56
  • +
  • 采用Directory.Build.targets管理 nuget包 #55
  • +
+

Bug

+
    +
  • Vben 分页组件总条数显示异常 #59
  • +
+

5.3.2.1

+

功能

+
    +
  • 调整NotificationManagement聚合设计 #51
  • +
  • 调整NotificationManagement,Redis配置 #50
  • +
  • 升级Abp5.3.2
  • +
+

Bug

+
    +
  • 多个hangfire定时任务,只执行单个问题 #54
  • +
  • vue客户端先启动,SignalR不尝试重连 #49
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.51d95adb.min.js b/assets/javascripts/bundle.51d95adb.min.js new file mode 100644 index 000000000..b20ec6835 --- /dev/null +++ b/assets/javascripts/bundle.51d95adb.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Hi=Object.create;var xr=Object.defineProperty;var Pi=Object.getOwnPropertyDescriptor;var $i=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Ii=Object.getPrototypeOf,Er=Object.prototype.hasOwnProperty,an=Object.prototype.propertyIsEnumerable;var on=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Er.call(t,r)&&on(e,r,t[r]);if(kt)for(var r of kt(t))an.call(t,r)&&on(e,r,t[r]);return e};var sn=(e,t)=>{var r={};for(var n in e)Er.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&an.call(e,n)&&(r[n]=e[n]);return r};var Ht=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Fi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $i(t))!Er.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=Pi(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Hi(Ii(e)):{},Fi(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var fn=Ht((wr,cn)=>{(function(e,t){typeof wr=="object"&&typeof cn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function f(T){var Ke=T.type,We=T.tagName;return!!(We==="INPUT"&&a[Ke]&&!T.readOnly||We==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function c(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&c(r.activeElement),n=!0)}function m(T){n=!1}function d(T){s(T.target)&&(n||f(T.target))&&c(T.target)}function h(T){s(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),B())}function B(){document.addEventListener("mousemove",z),document.addEventListener("mousedown",z),document.addEventListener("mouseup",z),document.addEventListener("pointermove",z),document.addEventListener("pointerdown",z),document.addEventListener("pointerup",z),document.addEventListener("touchmove",z),document.addEventListener("touchstart",z),document.addEventListener("touchend",z)}function re(){document.removeEventListener("mousemove",z),document.removeEventListener("mousedown",z),document.removeEventListener("mouseup",z),document.removeEventListener("pointermove",z),document.removeEventListener("pointerdown",z),document.removeEventListener("pointerup",z),document.removeEventListener("touchmove",z),document.removeEventListener("touchstart",z),document.removeEventListener("touchend",z)}function z(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,re())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),B(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var un=Ht(Sr=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},a=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(re,z){d.append(z,re)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+c+" due to "+T)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,B=!0,re=this;["append","delete","set"].forEach(function(T){var Ke=h[T];h[T]=function(){Ke.apply(h,arguments),v&&(B=!1,re.search=h.toString(),B=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var z=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==z&&(z=this.search,B&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},a=i.prototype,s=function(f){Object.defineProperty(a,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){s(f)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr)});var Qr=Ht((Lt,Kr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Lt=="object"&&typeof Kr=="object"?Kr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ki}});var a=i(279),s=i.n(a),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var w=p()(O);return m("cut"),w},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",w=document.createElement("textarea");w.style.fontSize="12pt",w.style.border="0",w.style.padding="0",w.style.margin="0",w.style.position="absolute",w.style[O?"right":"left"]="-9999px";var k=window.pageYOffset||document.documentElement.scrollTop;return w.style.top="".concat(k,"px"),w.setAttribute("readonly",""),w.value=j,w}var B=function(O,w){var k=v(O);w.container.appendChild(k);var F=p()(k);return m("copy"),k.remove(),F},re=function(O){var w=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},k="";return typeof O=="string"?k=B(O,w):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?k=B(O.value,w):(k=p()(O),m("copy")),k},z=re;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(w){return typeof w}:T=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},T(j)}var Ke=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},w=O.action,k=w===void 0?"copy":w,F=O.container,q=O.target,Le=O.text;if(k!=="copy"&&k!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(k==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(k==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Le)return z(Le,{container:F});if(q)return k==="cut"?h(q):z(q,{container:F})},We=Ke;function Ie(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ie=function(w){return typeof w}:Ie=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},Ie(j)}function Ti(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function nn(j,O){for(var w=0;w0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Ie(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var q=this;this.listener=c()(F,"click",function(Le){return q.onClick(Le)})}},{key:"onClick",value:function(F){var q=F.delegateTarget||F.currentTarget,Le=this.action(q)||"copy",Rt=We({action:Le,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Rt?"success":"error",{action:Le,text:Rt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return yr("action",F)}},{key:"defaultTarget",value:function(F){var q=yr("target",F);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(F){return yr("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return z(F,q)}},{key:"cut",value:function(F){return h(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof F=="string"?[F]:F,Le=!!document.queryCommandSupported;return q.forEach(function(Rt){Le=Le&&!!document.queryCommandSupported(Rt)}),Le}}]),w}(s()),ki=Ri},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,f){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(f))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof m=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(m))return c(m,d,h);if(a.nodeList(m))return u(m,d,h);if(a.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return s(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),a=f.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var f=this;function c(){f.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=s.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var is=/["'&<>]/;Jo.exports=as;function as(e){var t=""+e,r=is.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||s(m,d)})})}function s(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof Xe?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){s("next",m)}function u(m){s("throw",m)}function p(m,d){m(d),i.shift(),i.length&&s(i[0][0],i[0][1])}}function mn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof xe=="function"?xe(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,f){a=e[i](a),o(s,f,a.done,a.value)})}}function o(i,a,s,f){Promise.resolve(f).then(function(c){i({value:c,done:s})},a)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var $t=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Fe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=xe(a),f=s.next();!f.done;f=s.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof $t?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=xe(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{dn(h)}catch(v){i=i!=null?i:[],v instanceof $t?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new $t(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)dn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Or=Fe.EMPTY;function It(e){return e instanceof Fe||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function dn(e){A(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Or:(this.currentObservers=null,s.push(r),new Fe(function(){n.currentObservers=null,De(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new wn(r,n)},t}(U);var wn=function(e){ne(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Or},t}(E);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ne(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,f=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Ut);var On=function(e){ne(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Wt);var we=new On(Tn);var R=new U(function(e){return e.complete()});function Dt(e){return e&&A(e.schedule)}function kr(e){return e[e.length-1]}function Qe(e){return A(kr(e))?e.pop():void 0}function Se(e){return Dt(kr(e))?e.pop():void 0}function Vt(e,t){return typeof kr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function zt(e){return A(e==null?void 0:e.then)}function Nt(e){return A(e[ft])}function qt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ki(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=Ki();function Yt(e){return A(e==null?void 0:e[Qt])}function Gt(e){return ln(this,arguments,function(){var r,n,o,i;return Pt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,Xe(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,Xe(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,Xe(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return A(e==null?void 0:e.getReader)}function $(e){if(e instanceof U)return e;if(e!=null){if(Nt(e))return Qi(e);if(pt(e))return Yi(e);if(zt(e))return Gi(e);if(qt(e))return _n(e);if(Yt(e))return Bi(e);if(Bt(e))return Ji(e)}throw Kt(e)}function Qi(e){return new U(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Yi(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?_(function(o,i){return e(o,i,n)}):me,Oe(1),r?He(t):zn(function(){return new Xt}))}}function Nn(){for(var e=[],t=0;t=2,!0))}function fe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new E}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,f=s===void 0?!0:s;return function(c){var u,p,m,d=0,h=!1,v=!1,B=function(){p==null||p.unsubscribe(),p=void 0},re=function(){B(),u=m=void 0,h=v=!1},z=function(){var T=u;re(),T==null||T.unsubscribe()};return g(function(T,Ke){d++,!v&&!h&&B();var We=m=m!=null?m:r();Ke.add(function(){d--,d===0&&!v&&!h&&(p=jr(z,f))}),We.subscribe(Ke),!u&&d>0&&(u=new et({next:function(Ie){return We.next(Ie)},error:function(Ie){v=!0,B(),p=jr(re,o,Ie),We.error(Ie)},complete:function(){h=!0,B(),p=jr(re,a),We.complete()}}),$(T).subscribe(u))})(c)}}function jr(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function V(e,t=document){let r=se(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function se(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),N(e===_e()),Y())}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function Yn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,we),l(()=>Be(e)),N(Be(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,we),l(()=>rr(e)),N(rr(e)))}var Bn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),xa?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ya.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Jn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Zn=typeof WeakMap!="undefined"?new WeakMap:new Bn,eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Ea.getInstance(),n=new Ra(t,r,this);Zn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){eo.prototype[e]=function(){var t;return(t=Zn.get(this))[e].apply(t,arguments)}});var ka=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:eo}(),to=ka;var ro=new E,Ha=I(()=>H(new to(e=>{for(let t of e)ro.next(t)}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){return Ha.pipe(S(t=>t.observe(e)),x(t=>ro.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(()=>de(e)))),N(de(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var no=new E,Pa=I(()=>H(new IntersectionObserver(e=>{for(let t of e)no.next(t)},{threshold:0}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function sr(e){return Pa.pipe(S(t=>t.observe(e)),x(t=>no.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function oo(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=de(e),o=bt(e);return r>=o.height-n.height-t}),Y())}var cr={drawer:V("[data-md-toggle=drawer]"),search:V("[data-md-toggle=search]")};function io(e){return cr[e].checked}function qe(e,t){cr[e].checked!==t&&cr[e].click()}function je(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),N(t.checked))}function $a(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ia(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(N(!1))}function ao(){let e=b(window,"keydown").pipe(_(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:io("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),_(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!$a(n,r)}return!0}),fe());return Ia().pipe(x(t=>t?R:e))}function Me(){return new URL(location.href)}function ot(e){location.href=e.href}function so(){return new E}function co(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)co(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)co(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function fo(){return location.hash.substring(1)}function uo(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Fa(){return b(window,"hashchange").pipe(l(fo),N(fo()),_(e=>e.length>0),J(1))}function po(){return Fa().pipe(l(e=>se(`[id="${e}"]`)),_(e=>typeof e!="undefined"))}function Nr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(N(t.matches))}function lo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(N(e.matches))}function qr(e,t){return e.pipe(x(r=>r?t():R))}function ur(e,t={credentials:"same-origin"}){return ve(fetch(`${e}`,t)).pipe(ce(()=>R),x(r=>r.status!==200?Tt(()=>new Error(r.statusText)):H(r)))}function Ue(e,t){return ur(e,t).pipe(x(r=>r.json()),J(1))}function mo(e,t){let r=new DOMParser;return ur(e,t).pipe(x(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function pr(e){let t=M("script",{src:e});return I(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(x(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),C(()=>document.head.removeChild(t)),Oe(1))))}function ho(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function bo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(ho),N(ho()))}function vo(){return{width:innerWidth,height:innerHeight}}function go(){return b(window,"resize",{passive:!0}).pipe(l(vo),N(vo()))}function yo(){return Q([bo(),go()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(X("size")),o=Q([n,r]).pipe(l(()=>Be(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:a,size:s},{x:f,y:c}])=>({offset:{x:a.x-f,y:a.y-c+i},size:s})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(a=>{let s=document.createElement("script");s.src=i,s.onload=a,document.body.appendChild(s)})),Promise.resolve())}var r=class{constructor(n){this.url=n,this.onerror=null,this.onmessage=null,this.onmessageerror=null,this.m=a=>{a.source===this.w&&(a.stopImmediatePropagation(),this.dispatchEvent(new MessageEvent("message",{data:a.data})),this.onmessage&&this.onmessage(a))},this.e=(a,s,f,c,u)=>{if(s===this.url.toString()){let p=new ErrorEvent("error",{message:a,filename:s,lineno:f,colno:c,error:u});this.dispatchEvent(p),this.onerror&&this.onerror(p)}};let o=new EventTarget;this.addEventListener=o.addEventListener.bind(o),this.removeEventListener=o.removeEventListener.bind(o),this.dispatchEvent=o.dispatchEvent.bind(o);let i=document.createElement("iframe");i.width=i.height=i.frameBorder="0",document.body.appendChild(this.iframe=i),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

Abp Vnext Pro

+

+AppVeyor +GitHub license

+

Abp vNext Pro 是一个基于 Vben Admin, Abp vNext 打造的一个开箱即用的中后台管理系统解决方案。觉得该项目对您有帮助,请再 Github 上给我一颗

+

快速开始

+

Abp Vnext Pro

+

演示 +GitHub

+

Abp Vnext Pro Suite

+
+

基于 Abp Vnext Pro 开发的代码生成器,可自动生成前后端 CURD 代码,提供生产力。

+
+

演示 +GitHub

+

视频教程

+

B 站

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 000000000..43abd357a --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Abp Vnext Pro","text":"

Abp vNext Pro \u662f\u4e00\u4e2a\u57fa\u4e8e Vben Admin, Abp vNext \u6253\u9020\u7684\u4e00\u4e2a\u5f00\u7bb1\u5373\u7528\u7684\u4e2d\u540e\u53f0\u7ba1\u7406\u7cfb\u7edf\u89e3\u51b3\u65b9\u6848\u3002\u89c9\u5f97\u8be5\u9879\u76ee\u5bf9\u60a8\u6709\u5e2e\u52a9,\u8bf7\u518d Github \u4e0a\u7ed9\u6211\u4e00\u9897

\u5feb\u901f\u5f00\u59cb

"},{"location":"#abp-vnext-pro_1","title":"Abp Vnext Pro","text":"

\u6f14\u793a GitHub

"},{"location":"#abp-vnext-pro-suite","title":"Abp Vnext Pro Suite","text":"

\u57fa\u4e8e Abp Vnext Pro \u5f00\u53d1\u7684\u4ee3\u7801\u751f\u6210\u5668,\u53ef\u81ea\u52a8\u751f\u6210\u524d\u540e\u7aef CURD \u4ee3\u7801,\u63d0\u4f9b\u751f\u4ea7\u529b\u3002

\u6f14\u793a GitHub

"},{"location":"#_1","title":"\u89c6\u9891\u6559\u7a0b","text":"

B \u7ad9

"},{"location":"about/6.0-7.0/","title":"6.0 \u5347\u7ea7\u5230 7.0","text":""},{"location":"about/6.0-7.0/#net","title":"\u66f4\u65b0 .NET \u7248\u672c","text":"
  • \u4fee\u6539\u89e3\u51b3\u2f45\u6848\u4e2d global.json
JSON
{\n\"sdk\": {\n\"version\": \"7.0.304\",\n\"rollForward\": \"latestFeature\"\n}\n}\n
"},{"location":"about/6.0-7.0/#_1","title":"\u4fee\u6539\u7a0b\u5e8f\u96c6\u7248\u672c","text":"
  • Directory.Build.Volo.targets
  • 6.0.1 \u6539\u4e3a 7.0.0
  • Directory.Build.Microsoft.targets
  • \u5fae\u8f6f\u5b98\u65b9\u5305\u5347\u7ea7\u5230 7.0.2
  • Microsoft.Extensions.Hosting 6.0.1->7.0.0
  • Microsoft.NET.Test.Sdk 16.9.1->17.2.0
  • Directory.Build.targets
  • DotNetCore.CAP \u5347\u7ea7\u5230 7.0.2
  • \u79fb\u9664 Newtonsoft.Json
"},{"location":"about/6.0-7.0/#_2","title":"\u4fee\u6539\u76ee\u6807\u6846\u67b6","text":"
  • \u2f6c\u6807\u6846\u67b6(TargetFramework)\u4e3a net6.0 -> net7.0
  • \u4fee\u6539 Lion.AbpPro.CAP \u76ee\u6807\u6846\u67b6(TargetFramework) netstandard2.1 -> net7.0
"},{"location":"about/6.0-7.0/#_3","title":"\u65b0\u589e\u4e86\u56db\u5f20\u8868","text":"
  • AbpFeatureGroups \u529f\u80fd\u7ec4\u8868
  • AbpFeatures \u529f\u80fd\u8868
  • AbpPermissionGroups \u6743\u9650\u7ec4\u8868
  • AbpPermissions \u6743\u9650\u8868
  • \u6267\u884c EF \u547d\u4ee4\u751f\u6210\u8fc1\u79fb\u6587\u4ef6\u5e76\u66f4\u65b0\u6570\u636e\u5e93
"},{"location":"about/6.0-7.0/#_4","title":"\u52a8\u6001\u6743\u9650\u5e26\u6765\u7684\u5f71\u54cd","text":"
  • \u4fee\u6539 LionAbpApplicationConfigurationAppService.cs

  • \u52a8\u6001\u6743\u9650 UI \u540e\u7eed\u5728\u6dfb\u52a0,\u76ee\u524d\u662f\u4e0d\u4f1a\u5f71\u54cd\u4e4b\u524d\u7684\u529f\u80fd\u3002

"},{"location":"about/6.0-7.0/#_5","title":"\u524d\u7aef","text":"
  • \u4fee\u6539 package.json
  • \"@microsoft/signalr\": \"^6.0.1\" -> \"@microsoft/signalr\": \"^7.0.2\"
"},{"location":"about/contact-us/","title":"Contact Us","text":""},{"location":"about/contact-us/#authors","title":"Authors","text":"
  • Author: @WangJunZzz
  • Email: 510423039@qq.com
  • Blogs: https://www.cnblogs.com/WangJunZzz
"},{"location":"about/contact-us/#_1","title":"\u8d5e\u52a9","text":"

\u5982\u679c\u4f60\u89c9\u5f97\u8fd9\u4e2a\u9879\u76ee\u5bf9\u4f60\u6709\u5e2e\u52a9\uff0c\u4f60\u53ef\u4ee5\u5e2e\u4f5c\u8005\u4e70\u4e00\u676f\u5496\u5561\u8868\u793a\u652f\u6301!

"},{"location":"about/license/","title":"License","text":"

MIT License

MIT License

Copyright \u00a9 2021 WangJunZzz

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"},{"location":"about/release-notes/","title":"\u53d1\u884c\u8bf4\u660e","text":""},{"location":"about/release-notes/#5327","title":"5.3.2.7","text":"

\u529f\u80fd

  • \u5c01\u88c5abp \u81ea\u5e26\u6a21\u677f\u5230BasicManagement
"},{"location":"about/release-notes/#5325","title":"5.3.2.5","text":"

\u529f\u80fd

  • \u79fb\u9664IdentityServer4 #45

Bug

  • \u5bfc\u51fa\u7528\u6237\u6743\u9650\u9519\u8bef
"},{"location":"about/release-notes/#5324","title":"5.3.2.4","text":"

Bug

  • \u5347\u7ea7Vben2.8\uff0c\u7ec4\u7ec7\u673a\u6784\u7f16\u8f91\u9519\u8bef #62
"},{"location":"about/release-notes/#5323","title":"5.3.2.3","text":"

\u529f\u80fd

  • \u6743\u9650\u83dc\u5355\u7ea7\u8054\u64cd\u4f5c #48

Bug

  • \u7edf\u4e00\u53c2\u6570\u8fd4\u56de\u503c\u8fc7\u6ee4\u5668\uff0c\u7a7a\u6307\u9488\u5f02\u5e38 #61
"},{"location":"about/release-notes/#5322","title":"5.3.2.2","text":"

\u529f\u80fd

  • \u542f\u7528GlobalUsing\u529f\u80fd #56
  • \u91c7\u7528Directory.Build.targets\u7ba1\u7406 nuget\u5305 #55

Bug

  • Vben \u5206\u9875\u7ec4\u4ef6\u603b\u6761\u6570\u663e\u793a\u5f02\u5e38 #59
"},{"location":"about/release-notes/#5321","title":"5.3.2.1","text":"

\u529f\u80fd

  • \u8c03\u6574NotificationManagement\u805a\u5408\u8bbe\u8ba1 #51
  • \u8c03\u6574NotificationManagement,Redis\u914d\u7f6e #50
  • \u5347\u7ea7Abp5.3.2

Bug

  • \u591a\u4e2ahangfire\u5b9a\u65f6\u4efb\u52a1\uff0c\u53ea\u6267\u884c\u5355\u4e2a\u95ee\u9898 #54
  • vue\u5ba2\u6237\u7aef\u5148\u542f\u52a8\uff0cSignalR\u4e0d\u5c1d\u8bd5\u91cd\u8fde #49
"},{"location":"user-guide/zh/deploy/docker/","title":"Docker \u90e8\u7f72","text":""},{"location":"user-guide/zh/deploy/docker/#_1","title":"\u540e\u7aef","text":"
  • \u5728 aspnetcore \u76ee\u5f55\u4e0b\u6267\u884c
  • \u4fee\u6539 appsetting.Production.json \u914d\u7f6e
  • \u6570\u636e\u5e93\u8fde\u63a5
  • Redis \u8fde\u63a5
  • Rabbitmq \u8fde\u63a5(\u53ef\u9009)
"},{"location":"user-guide/zh/deploy/docker/#dockerfile","title":"Dockerfile","text":"YAML
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base\nWORKDIR /app\nEXPOSE 80\nEXPOSE 443\nENV TZ=Asia/Shanghai\nENV ASPNETCORE_ENVIRONMENT=Production\nFROM mcr.microsoft.com/dotnet/sdk:7.0 AS build\nWORKDIR /src\nCOPY . .\nWORKDIR \"/src/services/host/Lion.AbpPro.HttpApi.Host\"\nRUN dotnet build \"Lion.AbpPro.HttpApi.Host.csproj\" -c Release -o /app/build\nFROM build AS publish\nRUN dotnet publish \"Lion.AbpPro.HttpApi.Host.csproj\" -c Release -o /app/publish /p:UseAppHost=false\nFROM base AS final\nWORKDIR /app\nCOPY --from=publish /app/publish .\nENTRYPOINT [\"dotnet\", \"Lion.AbpPro.HttpApi.Host.dll\"]\n
"},{"location":"user-guide/zh/deploy/docker/#_2","title":"\u6784\u5efa\u955c\u50cf","text":"Bash
docker build -t Lion.AbpPro.HttpApi.Host .\n
"},{"location":"user-guide/zh/deploy/docker/#_3","title":"\u542f\u52a8\u5bb9\u5668","text":"Bash
docker run -itd --name Lion.AbpPro.HttpApi.Host -p 8011:80 Lion.AbpPro.HttpApi.Host\n
"},{"location":"user-guide/zh/deploy/docker/#_4","title":"\u524d\u7aef","text":"
  • \u4fee\u6539 env.production \u63a5\u53e3\u5730\u5740\u4e3a\u4ee5\u4e0a\u4f60\u53d1\u5e03\u7684\u5730\u5740
  • \u6253\u5305\u9879\u76ee
"},{"location":"user-guide/zh/deploy/docker/#dockerfile_1","title":"Dockerfile","text":"Text Only
FROM node:16-alpine as build-stage\nWORKDIR /app\nCOPY . ./\nENV NODE_OPTIONS=--max-old-space-size=16384\nRUN npm install pnpm -g\nRUN pnpm i\nRUN pnpm build\n\nFROM nginx:1.17.3-alpine as production-stage\nCOPY --from=build-stage app/_nginx/nginx.conf /etc/nginx/nginx.conf\nCOPY --from=build-stage app/_nginx/env.js /etc/nginx/env.js\nCOPY --from=build-stage app/_nginx/default.conf /etc/nginx/conf.d/default.conf\nCOPY --from=build-stage app/dist/ /usr/share/nginx/html\nEXPOSE 80\n\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n
"},{"location":"user-guide/zh/deploy/docker/#_5","title":"\u6784\u5efa\u955c\u50cf","text":"Bash
docker build -t Lion.AbpPro.Vue3 .\n
"},{"location":"user-guide/zh/deploy/docker/#_6","title":"\u542f\u52a8\u5bb9\u5668","text":"Bash
docker run -itd --name Lion.AbpPro.Vue3 -p 8012:80 Lion.AbpPro.Vue3\n

\u68c0\u67e5\u8de8\u57df\u8bbe\u7f6e,\u8bf7\u67e5\u770b\u8de8\u57df\u6587\u6863

"},{"location":"user-guide/zh/deploy/github/","title":"Github\u81ea\u52a8\u5316\u90e8\u7f72","text":""},{"location":"user-guide/zh/deploy/github/#yaml","title":"\u6dfb\u52a0\u90e8\u7f72 yaml","text":"
  • \u5728\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u6dfb\u52a0 .github/workflow/
"},{"location":"user-guide/zh/deploy/github/#_1","title":"\u540e\u7aef\u9879\u76ee","text":"YAML
name: \u540e\u7aef\u90e8\u7f72(API,IdentityServer4,Gateways) # \u6307\u5b9a\u540d\u79f0\non:\npush:\nbranches:\n- main # \u4ee3\u7801\u63a8\u9001\u5230main\u5206\u652f\u7684\u65f6\u5019\u89e6\u53d1jobs\njobs:\nbuild:\nruns-on: ubuntu-latest\nsteps:\n- uses: actions/checkout@v2\n- name: Install Dotnet 6.x\nuses: actions/setup-dotnet@v1\nwith:\ndotnet-version: \"6.0\" # \u5b89\u88c5Dotnet \u73af\u5883\ninclude-prerelease: True\n- name: \u7f16\u8bd1\nrun: dotnet build aspnet-core/Lion.AbpPro.sln # \u7f16\u8bd1\u9879\u76ee\n- name: \u5355\u5143\u6d4b\u8bd5\nrun: dotnet test aspnet-core/services/test/Lion.AbpPro.Domain.Tests/Lion.AbpPro.Domain.Tests.csproj # \u8fd0\u884c\u5355\u5143\u6d4b\u8bd5\n- name: \u53d1\u5e03->Lion.AbpPro.HttpApi.Host\nrun: dotnet publish aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj -o Lion.AbpPro.HttpApi.Host # \u53d1\u5e03Host\u9879\u76ee\n- name: \u53d1\u5e03->Lion.AbpPro.IdentityServer\nrun: dotnet publish aspnet-core/services/host/Lion.AbpPro.IdentityServer/Lion.AbpPro.IdentityServer.csproj -o Lion.AbpPro.IdentityServer # \u53d1\u5e03IdentityServer\u9879\u76ee\n- name: \u53d1\u5e03->Lion.AbpPro.IdentityServer\nrun: dotnet publish aspnet-core/gateways/Lion.AbpPro.WebGateway/Lion.AbpPro.WebGateway.csproj -o Lion.AbpPro.WebGateway # \u53d1\u5e03\u7f51\u5173\u9879\u76ee\n- name: \u90e8\u7f72->Lion.AbpPro.HttpApi.Host\nuses: easingthemes/ssh-deploy@v2.2.11\nenv:\nSSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} # \u670d\u52a1\u5668\u751f\u6210\u7684ssh key \u5728github \u4e0b\u6dfb\u52a0secret\nARGS: \"-avzr --delete --exclude 'appsettings.json'\" # \u628a\u53d1\u5e03\u597d\u7684\u9879\u76ee\u590d\u5236\u5230\u670d\u52a1\u5668\uff0c\u5e76\u4e14\u5220\u9664\u670d\u52a1\u5668\u4e0a\u7684/root/wwwroot/Lion.AbpPro.HttpApi.Host\u4e0b\u7684\u6587\u4ef6\u4f46\u662f\u4e0d\u5305\u62ecappsettings.json\nSOURCE: \"Lion.AbpPro.HttpApi.Host\" # \u5bf9\u5e94\u4e0a\u9762\u53d1\u5e03\u597d\u7684\u76ee\u5f55\nREMOTE_HOST: ${{ secrets.REMOTE_HOST }} #  \u670d\u52a1\u5668\u516c\u7f51ip\u5730\u5740\nREMOTE_USER: ${{ secrets.REMOTE_USER }} #  \u7528\u6237\u540d\nTARGET: \"/root/wwwroot\" # \u53d1\u5e03\u5230\u670d\u52a1\u5668\u6307\u5b9a\u76ee\u5f55\n- name: \u90e8\u7f72->Lion.AbpPro.IdentityServer\nuses: easingthemes/ssh-deploy@v2.2.11\nenv:\nSSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}\nARGS: \"-avzr --delete --exclude 'appsettings.json'\"\nSOURCE: \"Lion.AbpPro.IdentityServer\"\nREMOTE_HOST: ${{ secrets.REMOTE_HOST }}\nREMOTE_USER: ${{ secrets.REMOTE_USER }}\nTARGET: \"/root/wwwroot\"\n- name: \u90e8\u7f72->Lion.AbpPro.WebGateway\nuses: easingthemes/ssh-deploy@v2.2.11\nenv:\nSSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}\nARGS: \"-avzr --delete --exclude 'appsettings.json'\"\nSOURCE: \"Lion.AbpPro.WebGateway\"\nREMOTE_HOST: ${{ secrets.REMOTE_HOST }}\nREMOTE_USER: ${{ secrets.REMOTE_USER }}\nTARGET: \"/root/wwwroot\"\n
"},{"location":"user-guide/zh/deploy/github/#supervisor","title":"\u5b89\u88c5 supervisor","text":"Bash
yum install -y supervisor\nsystemctl start supervisord\nsystemctl enable supervisord # \u8bbe\u7f6e\u4e3a\u5f00\u673a\u542f\u52a8\n
  • \u9ed8\u8ba4\u914d\u7f6e\u76ee\u5f55\u5728 /etc/supervisord.d
Bash
yum install -y supervisor\nsystemctl start supervisord\nsystemctl enable supervisord # \u8bbe\u7f6e\u4e3a\u5f00\u673a\u542f\u52a8\n
  • \u5f00\u542f web \u7ba1\u7406\u754c\u9762
Bash
# vi vi /etc/supervisord.conf\n[inet_http_server]         ; inet (TCP) server disabled by default\nport=0.0.0.0:9001        ; (ip_address:port specifier, *:port for all iface)\nusername=admin              ; # \u7ba1\u7406web\u7aef\u767b\u5f55\u7528\u6237\u540d\npassword=1q2w3E*.               ; # \u7ba1\u7406web\u7aef\u767b\u5f55\u5bc6\u7801\n
  • \u67e5\u770b\u662f\u5426\u80fd\u8bbf\u95ee http://ip:9001

  • \u6dfb\u52a0 Lion.AbpPro.HttpApi.Host.ini

Bash
[program:Lion.AbpPro.HttpApi.Host]\ncommand=/bin/bash -c \"dotnet Lion.AbpPro.HttpApi.Host.dll --urls=http://*:8011\"\ndirectory=/root/wwwroot/Lion.AbpPro.HttpApi.Host\nautostart=true\nautorestart=true\nstderr_logfile=/root/wwwroot/Lion.AbpPro.HttpApi.Host/err.log\nstdout_logfile=/root/wwwroot/Lion.AbpPro.HttpApi.Host/out.log\nuser=root\n
  • \u6dfb\u52a0 Lion.AbpPro.IdentityServer.ini
Bash
[program:Lion.AbpPro.IdentityServer]\ncommand=/bin/bash -c \"dotnet Lion.AbpPro.IdentityServer.dll --urls=http://*:8013\"\ndirectory=/root/wwwroot/Lion.AbpPro.IdentityServer\nautostart=true\nautorestart=true\nstderr_logfile=/root/wwwroot/Lion.AbpPro.IdentityServer/err.log\nstdout_logfile=/root/wwwroot/Lion.AbpPro.IdentityServer/out.log\nuser=root\n
  • \u6dfb\u52a0 Lion.AbpPro.WebGateway.ini
Bash
[program:Lion.AbpPro.WebGateway]\ncommand=/bin/bash -c \"dotnet Lion.AbpPro.WebGateway.dll --urls=http://*:8014\"\ndirectory=/root/wwwroot/Lion.AbpPro.WebGateway\nautostart=true\nautorestart=true\nstderr_logfile=/root/wwwroot/Lion.AbpPro.WebGateway/err.log\nstdout_logfile=/root/wwwroot/Lion.AbpPro.WebGateway/out.log\nuser=root\n
  • \u91cd\u65b0\u52a0\u8f7d\u914d\u7f6e supervisorctl reload
"},{"location":"user-guide/zh/deploy/github/#_2","title":"\u524d\u7aef\u914d\u7f6e","text":"
  • \u5b89\u88c5 Nginx
Bash
sudo yum install -y nginx\nsystemctl start nginx # \u542f\u52a8 Nginx\nsystemctl enable nginx # \u542f\u7528\u5f00\u673a\u542f\u52a8 Nginx\n
  • \u8bbf\u95ee http://ip:80

-- \u914d\u7f6e Yml

YAML
name: \u524d\u7aef\u90e8\u7f72(vue)\non:\npush:\nbranches:\n- main\njobs:\nbuild:\nruns-on: ubuntu-latest\nsteps:\n- name: Checkout\nuses: actions/checkout@v2.3.1\nwith:\npersist-credentials: false\n- name: \u7f16\u8bd1|\u53d1\u5e03\nrun: |\ncd vben271\nyarn\nnpm run build\n- name: \u90e8\u7f72->Vue\nuses: easingthemes/ssh-deploy@v2.2.11\nenv:\nSSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}\nARGS: \"-avzr --delete\"\nSOURCE: \"vben271/dist\"\nREMOTE_HOST: ${{ secrets.REMOTE_HOST }}\nREMOTE_USER: ${{ secrets.REMOTE_USER }}\nTARGET: \"/root/wwwroot\"\n
  • \u914d\u7f6e Nginx
Bash
# vi /etc/nginx/nginx.conf\nserver {\nlisten       8012;\nlisten       [::]:8012;\nserver_name  _;\nroot         /root/wwwroot/dist;\n# Load configuration files for the default server block.\ninclude /etc/nginx/default.d/*.conf;\n#vue-router\u914d\u7f6e \u89e3\u51b3\u5237\u65b0\u6d4f\u89c8\u5668 404\u95ee\u9898\nlocation / {\ntry_files $uri $uri/ @router;\nindex index.html;\n}\nlocation @router {\nrewrite ^.*$ /index.html last;\n}\nerror_page 404 /404.html;\nlocation = /404.html {\n}\nerror_page 500 502 503 504 /50x.html;\nlocation = /50x.html {\n}\n}\n
"},{"location":"user-guide/zh/extension/MagicodesIE/","title":"\u914d\u7f6e","text":"C#
/// <summary>\n/// \u914d\u7f6eMagicodes.IE \u5bfc\u5165\u5bfc\u51fa\n/// </summary>\nprivate void ConfigureMagicodes(ServiceConfigurationContext context)\n{\ncontext.Services.AddTransient<IExporter, ExcelExporter>();\ncontext.Services.AddTransient<IExcelExporter, ExcelExporter>();\n}\n
"},{"location":"user-guide/zh/extension/MagicodesIE/#_2","title":"\u793a\u4f8b","text":"C#
/// <summary>\n/// \u7528\u6237\u5bfc\u51fa\u5217\u8868\n/// </summary>\n/// <returns></returns>\n[Authorize(AbpProPermissions.SystemManagement.UserExport)]\npublic async Task<ActionResult> ExportAsync(PagingUserListInput input)\n{\nvar request = new GetIdentityUsersInput\n{\nFilter = input.Filter?.Trim(),\nMaxResultCount = input.PageSize,\nSkipCount = input.SkipCount,\nSorting = \" LastModificationTime desc\"\n};\nList<Volo.Abp.Identity.IdentityUser> source = await _identityUserRepository\n.GetListAsync(request.Sorting, request.MaxResultCount, request.SkipCount, request.Filter);\nvar result = ObjectMapper.Map<List<Volo.Abp.Identity.IdentityUser>, List<ExportIdentityUserOutput>>(source);\nvar bytes = await _excelExporter.ExportAsByteArray<ExportIdentityUserOutput>(result);\nreturn new XlsxFileResult(bytes: bytes, fileDownloadName: $\"\u7528\u6237\u5bfc\u51fa\u5217\u8868{DateTime.Now:yyyyMMdd}\");\n}\n
"},{"location":"user-guide/zh/extension/%E7%BB%9F%E4%B8%80%E8%BF%94%E5%9B%9E%E5%80%BC%E6%A0%BC%E5%BC%8F/","title":"\u7edf\u4e00\u8fd4\u56de\u503c\u683c\u5f0f","text":"
  • \u5728\u4f7f\u7528 abp \u7684\u8fc7\u7a0b\u4e2d\uff0c\u5982\u679c\u63d0\u4f9b\u7ed9\u7b2c\u4e09\u65b9\u63a5\u53e3\u8981\u5b9e\u73b0\u8fd4\u56de\u503c\u7edf\u4e00\u9700\u8981\u600e\u4e48\u505a\uff1f
C#
{\n// \u8fd4\u56de\u683c\u5f0f\u7c7b\u4f3c\u8fd9\u79cd\n\"success\": false,\n\"message\": \"\u8bf7\u6c42\u5931\u8d25\",\n\"data\": null,\n\"code\": 500\n}\n
  • \u5b9a\u4e49\u8fd4\u56de\u7c7b\u578b
C#
public class WrapResult<T>\n{\npublic bool Success { get; private set; }\npublic string Message { get; private set; }\npublic T Data { get; private set; }\npublic int Code { get; private set; }\npublic WrapResult()\n{\nSuccess = true;\nMessage = \"Success\";\nData = default;\nCode = 200;\n}\npublic void SetSuccess(T data, string message = \"Success\", int code = 200)\n{\nSuccess = true;\nData = data;\nCode = code;\n}\npublic void SetFail(string message = \"Fail\", int code = 500)\n{\nSuccess = false;\nMessage = message;\nCode = code;\n}\n}\n
"},{"location":"user-guide/zh/extension/%E7%BB%9F%E4%B8%80%E8%BF%94%E5%9B%9E%E5%80%BC%E6%A0%BC%E5%BC%8F/#_2","title":"\u5b9e\u73b0\u601d\u8def","text":"
  • \u5b9a\u4e49 WrapResultAttribute
C#
public class WrapResultAttribute : Attribute\n{\n}\n
  • \u6dfb\u52a0\u5f02\u5e38\u8fc7\u6ee4\u5668(\u62e6\u622a\u5f02\u5e38,\u629b\u5f02\u5e38\u65f6\u6307\u5b9a\u8fd4\u56de\u683c\u5f0f)

  • LionExceptionFilter

  • \u6dfb\u52a0\u7ed3\u679c\u8fc7\u6ee4\u5668

  • LionResultFilter
"},{"location":"user-guide/zh/extension/%E7%BB%9F%E4%B8%80%E8%BF%94%E5%9B%9E%E5%80%BC%E6%A0%BC%E5%BC%8F/#filter","title":"\u6ce8\u518c Filter","text":"C#
    /// <summary>\n/// \u5f02\u5e38\u5904\u7406\n/// </summary>\nprivate void ConfigureAbpExceptions(ServiceConfigurationContext context)\n{\ncontext.Services.AddMvc\n(\noptions =>\n{\noptions.Filters.Add(typeof(LionExceptionFilter));\noptions.Filters.Add(typeof(LionResultFilter));\n}\n);\n}\npublic override void ConfigureServices(ServiceConfigurationContext context)\n{\nConfigureAbpExceptions(context);\n}\n
"},{"location":"user-guide/zh/extension/%E7%BB%9F%E4%B8%80%E8%BF%94%E5%9B%9E%E5%80%BC%E6%A0%BC%E5%BC%8F/#_3","title":"\u4f7f\u7528","text":"
  • \u5728 Controller \u4e0a\u6216\u8005 Action \u4e0a\u6253\u4e0a WrapResultAttribute \u7279\u6027
  • \u4f8b\u5982
C#
    [Route(\"Permissions\")]\n[WrapResult]\npublic class PermissionController : AbpProController,IRolePermissionAppService\n{\nprivate readonly IRolePermissionAppService _rolePermissionAppService;\npublic PermissionController(IRolePermissionAppService rolePermissionAppService)\n{\n_rolePermissionAppService = rolePermissionAppService;\n}\n[HttpPost(\"tree\")]\n[SwaggerOperation(summary: \"\u83b7\u53d6\u89d2\u8272\u6743\u9650\", Tags = new[] { \"Permissions\" })]\n[WrapResult] //\u63a7\u5236\u5668\u4e0a\u6253\u4e86 action\u4e0a\u5c31\u4e0d\u9700\u8981\npublic Task<PermissionOutput> GetPermissionAsync(GetPermissionInput input)\n{\nreturn _rolePermissionAppService.GetPermissionAsync(input);\n}\n}\n
  • \u4e0d\u7ba1\u63a5\u53e3\u65f6\u6709\u5f02\u5e38\u8fd8\u662f\u6210\u529f\u8fd4\u56de\u7ed3\u679c\u90fd\u662f WrapResult,PermissionOutput \u5728 WrapResult.Data \u4e2d\u3002
"},{"location":"user-guide/zh/getting-started/contributing/","title":"\u8d21\u732e","text":"

\u8d21\u732e\u6700\u7b80\u5355\u7684\u65b9\u5f0f\u4e4b\u4e00\u5c31\u662f\u53c2\u4e0e\u8ba8\u8bba\u548cissue\u8ba8\u8bba\u3002

\u5982\u679c\u60a8\u6709\u4efb\u4f55\u7591\u95ee\u6216\u95ee\u9898\uff0c\u8bf7\u5728Lion.AbpPro\u4ed3\u5e93\u4e2d\u62a5\u544a\uff1a

\u63d0\u4ea4Issue

"},{"location":"user-guide/zh/getting-started/contributing/#_2","title":"\u63d0\u4ea4\u66f4\u6539","text":"

\u60a8\u8fd8\u53ef\u4ee5\u901a\u8fc7\u63d0\u4ea4\u4ee3\u7801\u66f4\u6539PR\u6765\u505a\u51fa\u8d21\u732e\u3002

Pull requests \u53ef\u8ba9\u60a8\u544a\u8bc9\u5176\u4ed6\u4eba\u5df2\u63a8\u9001\u5230GitHub\u4e0a\u5b58\u50a8\u5e93\u7684\u66f4\u6539\u3002 \u6253\u5f00 Pull requests \u540e\uff0c\u60a8\u53ef\u4ee5\u4e0e\u534f\u4f5c\u8005\u8ba8\u8bba\u548c\u5ba1\u67e5\u505a\u51fa\u7684\u66f4\u6539\uff0c\u5e76\u5728\u66f4\u6539\u5408\u5e76\u5230\u5b58\u50a8\u5e93\u4e4b\u524d\u6dfb\u52a0\u540e\u7eed\u63d0\u4ea4\u3002

"},{"location":"user-guide/zh/getting-started/contributing/#_3","title":"\u5176\u4ed6\u8d44\u6e90","text":"
  • issue \u548c pull requests

  • \u4f7f\u7528\u641c\u7d22\u8fc7\u6ee4 issue \u548c pull requests

"},{"location":"user-guide/zh/getting-started/introduction/","title":"\u4ecb\u7ecd","text":"

Lion.AbpPro \u662f Abp Vnext \u7684 Vue3 \u7248\u672c\u5b9e\u73b0\uff0c\u540c\u65f6\u4e5f\u662f\u514d\u8d39\u5f00\u6e90\u3002\u5b83\u6709\u52a9\u4e8e\u63d0\u9ad8\u5f00\u53d1\u6548\u7387\uff0c\u5c5e\u4e8e\u5f00\u7bb1\u5373\u7528\u7684\u540e\u53f0\u7ba1\u7406\u7cfb\u7edf\uff0c\u4e5f\u80fd\u9002\u7528\u5fae\u670d\u52a1\u3002

"},{"location":"user-guide/zh/getting-started/introduction/#_2","title":"\u540e\u7aef\u9879\u76ee\u7ed3\u6784","text":"Bash
\u251c\u2500\u2500 Directory.Build.props nuget \u7248\u672c\u63a7\u5236\n\u251c\u2500\u2500 frameworks # \u516c\u5171\u6a21\u5757\n\u2502       \u251c\u2500\u2500 src #\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.Core  # \u6838\u5fc3\u6269\u5c55\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.CAP # dotnetcore.cap\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.CAP.EntityFrameworkCore # dotnetcore.cap ef \u6269\u5c55\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.Cli # cli\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.Cli.Core # cli \u6838\u5fc3\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.ElasticSearch # es \u6269\u5c55\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.EntityFrameworkCore # ef \u6269\u5c55 \u4e3b\u8981\u662f\u6279\u91cf\u65b0\u589e\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.EntityFrameworkCore.Mysql # mysql ef \u6269\u5c55 \u4e3b\u8981\u662f\u6279\u91cf\u65b0\u589e\n\u2502           \u251c\u2500\u2500 Lion.AbpPro.Starter # \u9996\u6b21\u542f\u52a8\n\u2502           \u2514\u2500\u2500 Lion.AbpPro.Localization # \u672c\u5730\u5316\n\u251c\u2500\u2500 gateways # \u7f51\u5173\n\u2502       \u2514\u2500\u2500 Lion.AbpPro.WebGateway # \u57fa\u4e8eocelot\u7f51\u5173\n\u251c\u2500\u2500 modules # \u6a21\u5757\n\u2502       \u251c\u2500\u2500 BasicManagement # abp \u57fa\u7840\u6a21\u5757\u5c01\u88c5\n\u2502       \u251c\u2500\u2500 DataDictionaryManagement # \u6570\u636e\u5b57\u5178\n\u2502       \u251c\u2500\u2500 LanguageManagement # \u591a\u8bed\u8a00\n\u2502       \u2514\u2500\u2500 NotificationManagement # \u901a\u77e5\u670d\u52a1\n\u251c\u2500\u2500 services # \u516c\u5171\u9759\u6001\u8d44\u6e90\u76ee\u5f55\n\u2502       \u251c\u2500\u2500 host # \u542f\u52a8\u6a21\u5757\n\u2502           \u251c\u2500\u2500 CompanyName.ProjectName.HttpApi.Host # admin ui host\n\u2502       \u251c\u2500\u2500 src  # \u6e90\u7801\n\u2502           \u2514\u2500\u2500 CompanyName.ProjectName.DbMigrator # \u8fc1\u79fb\u63a7\u5236\u53f0\u7a0b\u5e8f\n\u2502       \u2514\u2500\u2500 test # \u5355\u5143\u6d4b\u8bd5\n\u251c\u2500\u2500 shared # \u516c\u5171Host\n\u2502       \u251c\u2500\u2500 Lion.AbpPro.Shared.Hosting.Gateways # \u7f51\u5173host\u6a21\u5757\n\u2502       \u2514\u2500\u2500 Lion.AbpPro.Shared.Hosting.Microservices # \u670d\u52a1host\u6a21\u5757\n
"},{"location":"user-guide/zh/getting-started/introduction/#_3","title":"\u524d\u7aef\u9879\u76ee\u7ed3\u6784","text":"Bash
\u251c\u2500\u2500 _nginx # docker \u6253\u5305\n\u251c\u2500\u2500 build # \u6253\u5305\u811a\u672c\u76f8\u5173\n\u2502   \u251c\u2500\u2500 config # \u914d\u7f6e\u6587\u4ef6\n\u2502   \u251c\u2500\u2500 generate # \u751f\u6210\u5668\n\u2502   \u251c\u2500\u2500 script # \u811a\u672c\n\u2502   \u2514\u2500\u2500 vite # vite\u914d\u7f6e\n\u251c\u2500\u2500 mock # mock\u6587\u4ef6\u5939\n\u251c\u2500\u2500 public # \u516c\u5171\u9759\u6001\u8d44\u6e90\u76ee\u5f55\n\u251c\u2500\u2500 src # \u4e3b\u76ee\u5f55\n\u2502   \u251c\u2500\u2500 api # \u63a5\u53e3\u6587\u4ef6\n\u2502   \u251c\u2500\u2500 assets # \u8d44\u6e90\u6587\u4ef6\n\u2502   \u2502   \u251c\u2500\u2500 icons # icon sprite \u56fe\u6807\u6587\u4ef6\u5939\n\u2502   \u2502   \u251c\u2500\u2500 images # \u9879\u76ee\u5b58\u653e\u56fe\u7247\u7684\u6587\u4ef6\u5939\n\u2502   \u2502   \u2514\u2500\u2500 svg # \u9879\u76ee\u5b58\u653esvg\u56fe\u7247\u7684\u6587\u4ef6\u5939\n\u2502   \u251c\u2500\u2500 components # \u516c\u5171\u7ec4\u4ef6\n\u2502   \u251c\u2500\u2500 design # \u6837\u5f0f\u6587\u4ef6\n\u2502   \u251c\u2500\u2500 directives # \u6307\u4ee4\n\u2502   \u251c\u2500\u2500 enums # \u679a\u4e3e/\u5e38\u91cf\n\u2502   \u251c\u2500\u2500 hooks # hook\n\u2502   \u2502   \u251c\u2500\u2500 component # \u7ec4\u4ef6\u76f8\u5173hook\n\u2502   \u2502   \u251c\u2500\u2500 core # \u57fa\u7840hook\n\u2502   \u2502   \u251c\u2500\u2500 event # \u4e8b\u4ef6\u76f8\u5173hook\n\u2502   \u2502   \u251c\u2500\u2500 setting # \u914d\u7f6e\u76f8\u5173hook\n\u2502   \u2502   \u2514\u2500\u2500 web # web\u76f8\u5173hook\n\u2502   \u251c\u2500\u2500 layouts # \u5e03\u5c40\u6587\u4ef6\n\u2502   \u2502   \u251c\u2500\u2500 default # \u9ed8\u8ba4\u5e03\u5c40\n\u2502   \u2502   \u251c\u2500\u2500 iframe # iframe\u5e03\u5c40\n\u2502   \u2502   \u2514\u2500\u2500 page # \u9875\u9762\u5e03\u5c40\n\u2502   \u251c\u2500\u2500 locales # \u591a\u8bed\u8a00\n\u2502   \u251c\u2500\u2500 logics # \u903b\u8f91\n\u2502   \u251c\u2500\u2500 main.ts # \u4e3b\u5165\u53e3\n\u2502   \u251c\u2500\u2500 router # \u8def\u7531\u914d\u7f6e\n\u2502   \u251c\u2500\u2500 services # Nswag\u751f\u6210\u7684\u4ee3\u7406\n\u2502   \u2502   \u251c\u2500\u2500 ServiceProxies.ts # Nswag\u751f\u6210\u7684\u4ee3\u7406\n\u2502   \u2502   \u251c\u2500\u2500 ServiceProxyBase.ts # Nswag\u751f\u6210\u7684\u4ee3\u7406\u62e6\u622a\u5668\n\u2502   \u251c\u2500\u2500 settings # \u9879\u76ee\u914d\u7f6e\n\u2502   \u2502   \u251c\u2500\u2500 componentSetting.ts # \u7ec4\u4ef6\u914d\u7f6e\n\u2502   \u2502   \u251c\u2500\u2500 designSetting.ts # \u6837\u5f0f\u914d\u7f6e\n\u2502   \u2502   \u251c\u2500\u2500 encryptionSetting.ts # \u52a0\u5bc6\u914d\u7f6e\n\u2502   \u2502   \u251c\u2500\u2500 localeSetting.ts # \u591a\u8bed\u8a00\u914d\u7f6e\n\u2502   \u2502   \u251c\u2500\u2500 projectSetting.ts # \u9879\u76ee\u914d\u7f6e\n\u2502   \u2502   \u2514\u2500\u2500 siteSetting.ts # \u7ad9\u70b9\u914d\u7f6e\n\u2502   \u251c\u2500\u2500 store # \u6570\u636e\u4ed3\u5e93\n\u2502   \u251c\u2500\u2500 utils # \u5de5\u5177\u7c7b\n\u2502   \u2514\u2500\u2500 views # \u9875\u9762\n\u251c\u2500\u2500 test # \u6d4b\u8bd5\n\u2502   \u2514\u2500\u2500 server # \u6d4b\u8bd5\u7528\u5230\u7684\u670d\u52a1\n\u2502       \u251c\u2500\u2500 api # \u6d4b\u8bd5\u670d\u52a1\u5668\n\u2502       \u251c\u2500\u2500 upload # \u6d4b\u8bd5\u4e0a\u4f20\u670d\u52a1\u5668\n\u2502       \u2514\u2500\u2500 websocket # \u6d4b\u8bd5ws\u670d\u52a1\u5668\n\u251c\u2500\u2500 types # \u7c7b\u578b\u6587\u4ef6\n\u251c\u2500\u2500 vite.config.ts # vite\u914d\u7f6e\u6587\u4ef6\n\u2514\u2500\u2500 windi.config.ts # windcss\u914d\u7f6e\u6587\u4ef6\n
"},{"location":"user-guide/zh/getting-started/quick-start/","title":"\u5feb\u901f\u5f00\u59cb","text":""},{"location":"user-guide/zh/getting-started/quick-start/#_2","title":"\u5148\u51b3\u6761\u4ef6","text":"
  • dotnet core 7.0.401
  • nodejs 16+
  • pnpm
  • mysql
  • redis
  • rabbitmq \u53ef\u9009
"},{"location":"user-guide/zh/getting-started/quick-start/#cli","title":"\u5b89\u88c5 CLI \u5de5\u5177","text":"Bash
dotnet tool install Lion.AbpPro.Cli -g\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_3","title":"\u521b\u5efa\u9879\u76ee","text":"
  • \u9879\u76ee\u9009\u62e9
Bash
lion.abp new -t pro -c \u516c\u53f8\u540d\u79f0 -p \u9879\u76ee\u540d\u79f0  -o \u8f93\u51fa\u8def\u5f84(\u53ef\u9009)\nlion.abp new -t pro.all -c \u516c\u53f8\u540d\u79f0 -p \u9879\u76ee\u540d\u79f0  -o \u8f93\u51fa\u8def\u5f84(\u53ef\u9009)\nlion.abp new -t pro.simplify -c \u516c\u53f8\u540d\u79f0 -p \u9879\u76ee\u540d\u79f0  -o \u8f93\u51fa\u8def\u5f84(\u53ef\u9009)\nlion.abp new -t pro.module -c \u516c\u53f8\u540d\u79f0 -p \u9879\u76ee\u540d\u79f0  -m \u6a21\u5757\u540d\u79f0  -o \u8f93\u51fa\u8def\u5f84(\u53ef\u9009)\n
  • \u521b\u5efa\u793a\u4f8b\u9879\u76ee
Bash
# \u521b\u5efa\u4e00\u4e2a\u516c\u53f8\u540d\u79f0\u4e3aAcme\u9879\u76ee\u540d\u4e3aBookStore\u7684\u5355\u4f53\u9879\u76ee\nlion.abp new -t pro.simplify -c Acme -p BookStore\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_4","title":"\u540e\u7aef\u4fee\u6539\u914d\u7f6e","text":"
  • \u4fee\u6539 HttpApi.Host-> appsettings.json \u914d\u7f6e
  • Mysql \u8fde\u63a5\u5b57\u7b26\u4e32
  • Redis \u8fde\u63a5\u5b57\u7b26\u4e32
  • \u4fee\u6539 DbMigrator-> appsettings.json \u6570\u636e\u5e93\u8fde\u63a5\u5b57\u7b26\u4e32
  • \u53f3\u952e\u5355\u51fb.DbMigrator \u9879\u76ee,\u8bbe\u7f6e\u4e3a\u542f\u52a8\u9879\u76ee\u8fd0\u884c\uff0c\u6309 F5(\u6216 Ctrl + F5) \u8fd0\u884c\u5e94\u7528\u7a0b\u5e8f. \u5b83\u5c06\u5177\u6709\u5982\u4e0b\u6240\u793a\u7684\u8f93\u51fa:

Note

\u521d\u59cb\u7684\u79cd\u5b50\u6570\u636e\u5728\u6570\u636e\u5e93\u4e2d\u521b\u5efa\u4e86 admin \u7528\u6237(\u5bc6\u7801\u4e3a1q2w3E*) \u7528\u4e8e\u767b\u5f55\u5e94\u7528\u7a0b\u5e8f. \u6240\u4ee5, \u5bf9\u4e8e\u65b0\u6570\u636e\u5e93\u81f3\u5c11\u4f7f\u7528 .DbMigrator \u4e00\u6b21.

  • \u542f\u52a8 HttpApi.Host\uff0c\u5c31\u80fd\u770b\u5230\u540e\u53f0\u670d\u52a1\u767b\u9646\u9875\u9762\uff0c\u5982\u4e0b\uff1a
"},{"location":"user-guide/zh/getting-started/quick-start/#_5","title":"\u524d\u7aef","text":"
  • Vben Admin \u6587\u6863
"},{"location":"user-guide/zh/getting-started/quick-start/#_6","title":"\u5b89\u88c5\u4f9d\u8d56","text":"Bash
pnpm install\n
"},{"location":"user-guide/zh/getting-started/quick-start/#_7","title":"\u542f\u52a8\u9879\u76ee","text":"Bash
pnpm run dev\n
"},{"location":"user-guide/zh/infrastructure/AutoMapper/","title":"\u5bf9\u8c61\u5230\u5bf9\u8c61\u6620\u5c04","text":"

\u5c06\u5bf9\u8c61\u6620\u5c04\u5230\u53e6\u4e00\u4e2a\u5bf9\u8c61\u662f\u5e38\u7528\u5e76\u4e14\u7e41\u7410\u91cd\u590d\u7684\u5de5\u4f5c,\u5927\u90e8\u5206\u60c5\u51b5\u4e0b\u4e24\u4e2a\u7c7b\u90fd\u5177\u6709\u76f8\u540c\u6216\u76f8\u4f3c\u7684\u5c5e\u6027. ABP \u63d0\u4f9b\u4e86\u5bf9\u8c61\u5230\u5bf9\u8c61\u6620\u5c04\u7684\u62bd\u8c61\u5e76\u96c6\u6210\u4e86AutoMapper\u505a\u4e3a\u5bf9\u8c61\u6620\u5c04\u5668.

"},{"location":"user-guide/zh/infrastructure/AutoMapper/#automapper","title":"AutoMapper \u96c6\u6210","text":"

AutoMapper \u662f\u6700\u6d41\u884c\u7684\u5bf9\u8c61\u5230\u5bf9\u8c61\u6620\u5c04\u5e93\u4e4b\u4e00. Volo.Abp.AutoMapper\u7a0b\u5e8f\u5305\u4f7f\u7528 AutoMapper \u5b9e\u73b0\u4e86 IObjectMapper.

"},{"location":"user-guide/zh/infrastructure/AutoMapper/#_2","title":"\u5b9a\u4e49\u6620\u5c04","text":"

AutoMapper \u63d0\u4f9b\u4e86\u591a\u79cd\u5b9a\u4e49\u7c7b\u4e4b\u95f4\u6620\u5c04\u7684\u65b9\u6cd5. \u6709\u5173\u8be6\u7ec6\u4fe1\u606f\u8bf7\u53c2\u9605AutoMapper \u7684\u6587\u6863.

\u5176\u4e2d\u5b9a\u4e49\u4e00\u79cd\u6620\u5c04\u7684\u65b9\u6cd5\u662f\u521b\u5efa\u4e00\u4e2aProfile \u7c7b. \u4f8b\u5982:

C#
public class MyProfile : Profile\n{\npublic MyProfile()\n{\nCreateMap<User, UserDto>();\n}\n}\n

\u7136\u540e\u4f7f\u7528AbpAutoMapperOptions\u6ce8\u518c\u914d\u7f6e\u6587\u4ef6:

C#
[DependsOn(typeof(AbpAutoMapperModule))]\npublic class MyModule : AbpModule\n{\npublic override void ConfigureServices(ServiceConfigurationContext context)\n{\nConfigure<AbpAutoMapperOptions>(options =>\n{\n//Add all mappings defined in the assembly of the MyModule class\noptions.AddMaps<MyModule>();\n});\n}\n}\n

AddMaps \u6ce8\u518c\u7ed9\u5b9a\u7c7b\u7684\u7a0b\u5e8f\u96c6\u4e2d\u6240\u6709\u7684\u914d\u7f6e\u7c7b,\u901a\u5e38\u4f7f\u7528\u6a21\u5757\u7c7b. \u5b83\u8fd8\u4f1a\u6ce8\u518c attribute \u6620\u5c04.

"},{"location":"user-guide/zh/infrastructure/AutoMapper/#_3","title":"\u914d\u7f6e\u9a8c\u8bc1","text":"

AddMaps \u4f7f\u7528\u53ef\u9009\u7684 bool \u53c2\u6570\u63a7\u5236\u6a21\u5757\u7684\u914d\u7f6e\u9a8c\u8bc1:

C#
options.AddMaps<MyModule>(validate: true);\n

\u5982\u679c\u6b64\u9009\u9879\u9ed8\u8ba4\u662f false , \u4f46\u6700\u4f73\u5b9e\u8df5\u5efa\u8bae\u542f\u7528. \u53ef\u4ee5\u4f7f\u7528 AddProfile \u800c\u4e0d\u662f AddMaps \u6765\u63a7\u5236\u6bcf\u4e2a\u914d\u7f6e\u6587\u4ef6\u7c7b\u7684\u914d\u7f6e\u9a8c\u8bc1:

C#
options.AddProfile<MyProfile>(validate: true);\n

\u5982\u679c\u4f60\u6709\u591a\u4e2a\u914d\u7f6e\u6587\u4ef6,\u5e76\u4e14\u53ea\u9700\u8981\u4e3a\u5176\u4e2d\u51e0\u4e2a\u542f\u7528\u9a8c\u8bc1,\u90a3\u4e48\u9996\u5148\u4f7f\u7528AddMaps\u800c\u4e0d\u8fdb\u884c\u9a8c\u8bc1,\u7136\u540e\u4e3a\u4f60\u60f3\u8981\u9a8c\u8bc1\u7684\u6bcf\u4e2a\u914d\u7f6e\u6587\u4ef6\u4f7f\u7528AddProfile.

"},{"location":"user-guide/zh/infrastructure/AutoMapper/#_4","title":"\u6620\u5c04\u5bf9\u8c61\u6269\u5c55","text":"

\u5bf9\u8c61\u6269\u5c55\u7cfb\u7edf \u5141\u8bb8\u4e3a\u5df2\u5b58\u5728\u7684\u7c7b\u5b9a\u4e49\u989d\u5916\u5c5e\u6027. ABP \u6846\u67b6\u63d0\u4f9b\u4e86\u4e00\u4e2a\u6620\u5c04\u5b9a\u4e49\u6269\u5c55\u53ef\u4ee5\u6b63\u786e\u7684\u6620\u5c04\u4e24\u4e2a\u5bf9\u8c61\u7684\u989d\u5916\u5c5e\u6027.

C#
public class MyProfile : Profile\n{\npublic MyProfile()\n{\nCreateMap<User, UserDto>()\n.MapExtraProperties();\n}\n}\n

\u5982\u679c\u4e24\u4e2a\u7c7b\u90fd\u662f\u53ef\u6269\u5c55\u5bf9\u8c61(\u5b9e\u73b0\u4e86 IHasExtraProperties \u63a5\u53e3),\u5efa\u8bae\u4f7f\u7528 MapExtraProperties \u65b9\u6cd5. \u66f4\u591a\u4fe1\u606f\u8bf7\u53c2\u9605\u5bf9\u8c61\u6269\u5c55\u6587\u6863.

"},{"location":"user-guide/zh/infrastructure/AutoMapper/#_5","title":"\u5176\u4ed6\u6709\u7528\u7684\u6269\u5c55\u65b9\u6cd5","text":"

\u6709\u4e00\u4e9b\u6269\u5c55\u65b9\u6cd5\u53ef\u4ee5\u7b80\u5316\u6620\u5c04\u4ee3\u7801.

"},{"location":"user-guide/zh/infrastructure/AutoMapper/#_6","title":"\u5ffd\u89c6\u5ba1\u8ba1\u5c5e\u6027","text":"

\u5f53\u4f60\u5c06\u4e00\u4e2a\u5bf9\u8c61\u6620\u5c04\u5230\u53e6\u4e00\u4e2a\u5bf9\u8c61\u65f6,\u901a\u5e38\u4f1a\u5ffd\u7565\u5ba1\u6838\u5c5e\u6027.

\u5047\u8bbe\u4f60\u9700\u8981\u5c06 ProductDto (DTO)\u6620\u5c04\u5230 Product\u5b9e\u4f53,\u8be5\u5b9e\u4f53\u662f\u4ece AuditedEntity \u7c7b\u7ee7\u627f\u7684(\u8be5\u7c7b\u63d0\u4f9b\u4e86 CreationTime, CreatorId, IHasModificationTime \u7b49\u5c5e\u6027).

\u4ece DTO \u6620\u5c04\u65f6\u4f60\u53ef\u80fd\u60f3\u5ffd\u7565\u8fd9\u4e9b\u57fa\u672c\u5c5e\u6027,\u53ef\u4ee5\u4f7f\u7528 IgnoreAuditedObjectPropertie() \u65b9\u6cd5\u5ffd\u7565\u6240\u6709\u5ba1\u8ba1\u5c5e\u6027(\u800c\u4e0d\u662f\u624b\u52a8\u9010\u4e2a\u5ffd\u7565\u5b83\u4eec):

C#
public class MyProfile : Profile\n{\npublic MyProfile()\n{\nCreateMap<ProductDto, Product>()\n.IgnoreAuditedObjectProperties();\n}\n}\n

\u8fd8\u6709\u66f4\u591a\u6269\u5c55\u65b9\u6cd5, \u5982 IgnoreFullAuditedObjectProperties() \u548c IgnoreCreationAuditedObjectProperties(),\u4f60\u53ef\u4ee5\u6839\u636e\u5b9e\u4f53\u7c7b\u578b\u4f7f\u7528.

\u8bf7\u53c2\u9605\u5b9e\u4f53\u6587\u6863\u4e2d\u7684\"\u57fa\u7c7b\u548c\u63a5\u53e3\u7684\u5ba1\u8ba1\u5c5e\u6027\"\u90e8\u5206\u4e86\u89e3\u6709\u5173\u5ba1\u8ba1\u5c5e\u6027\u7684\u66f4\u591a\u4fe1\u606f\u3002

"},{"location":"user-guide/zh/infrastructure/AutoMapper/#_7","title":"\u5ffd\u89c6\u5176\u4ed6\u5c5e\u6027","text":"

\u5728 AutoMapper \u4e2d,\u901a\u5e38\u53ef\u4ee5\u7f16\u5199\u8fd9\u6837\u7684\u6620\u5c04\u4ee3\u7801\u6765\u5ffd\u7565\u5c5e\u6027:

C#
public class MyProfile : Profile\n{\npublic MyProfile()\n{\nCreateMap<SimpleClass1, SimpleClass2>()\n.ForMember(x => x.CreationTime, map => map.Ignore());\n}\n}\n

\u6211\u4eec\u53d1\u73b0\u5b83\u7684\u957f\u5ea6\u662f\u4e0d\u5fc5\u8981\u7684\u5e76\u4e14\u521b\u5efa\u4e86 Ignore() \u6269\u5c55\u65b9\u6cd5:

C#
public class MyProfile : Profile\n{\npublic MyProfile()\n{\nCreateMap<SimpleClass1, SimpleClass2>()\n.Ignore(x => x.CreationTime);\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/AutoMapper/#_8","title":"\u4f7f\u7528","text":"C#
// \u6ce8\u5165IObjectMapper\npublic virtual async Task<LanguageDto> GetAsync(string cultureName)\n{\nvar entity = await _languageRepository.FindAsync(cultureName);\nreturn ObjectMapper.Map<Language, LanguageDto>(entity);\n}\n
"},{"location":"user-guide/zh/infrastructure/CorsOrigins/","title":"\u8de8\u57df","text":""},{"location":"user-guide/zh/infrastructure/CorsOrigins/#cors","title":"\u8de8\u57df(CORS)","text":"
  • \u5141\u8bb8\u6307\u5b9a\u7b56\u7565
appsetting.json
\"App\": {\n// \u9017\u53f7\u5206\u9694\n\"CorsOrigins\": \"http://*.com,http://localhost:4200\"\n},\n
  • \u914d\u7f6e\u8de8\u57df
C#
private void ConfigureCors(ServiceConfigurationContext context)\n{\nvar configuration = context.Services.GetConfiguration();\ncontext.Services.AddCors(options =>\n{\noptions.AddPolicy(DefaultCorsPolicyName, builder =>\n{\nbuilder\n.WithOrigins(\nconfiguration[\"App:CorsOrigins\"]\n.Split(\",\", StringSplitOptions.RemoveEmptyEntries)\n.Select(o => o.RemovePostFix(\"/\"))\n.ToArray()\n)\n.WithAbpExposedHeaders()\n.SetIsOriginAllowedToAllowWildcardSubdomains()\n.AllowAnyHeader()\n.AllowAnyMethod()\n.AllowCredentials();\n});\n});\n}\n
"},{"location":"user-guide/zh/infrastructure/CurrentUser/","title":"\u5f53\u524d\u7528\u6237","text":"

\u5728 Web \u5e94\u7528\u7a0b\u5e8f\u4e2d\u68c0\u7d22\u6709\u5173\u5df2\u767b\u5f55\u7528\u6237\u7684\u4fe1\u606f\u662f\u5f88\u5e38\u89c1\u7684. \u5f53\u524d\u7528\u6237\u662f\u4e0e Web \u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u5f53\u524d\u8bf7\u6c42\u76f8\u5173\u7684\u6d3b\u52a8\u7528\u6237.

"},{"location":"user-guide/zh/infrastructure/CurrentUser/#icurrentuser","title":"ICurrentUser","text":"

ICurrentUser \u662f\u4e3b\u8981\u7684\u670d\u52a1,\u7528\u4e8e\u83b7\u53d6\u6709\u5173\u5f53\u524d\u6d3b\u52a8\u7684\u7528\u6237\u4fe1\u606f.

\u793a\u4f8b: [\u6ce8\u5165] ICurrentUser \u5230\u670d\u52a1\u4e2d:

C#
using System;\nusing Volo.Abp.DependencyInjection;\nusing Volo.Abp.Users;\nnamespace AbpDemo\n{\npublic class MyService : ITransientDependency\n{\nprivate readonly ICurrentUser _currentUser;\npublic MyService(ICurrentUser currentUser)\n{\n_currentUser = currentUser;\n}\npublic void Foo()\n{\nGuid? userId = _currentUser.Id;\n}\n}\n}\n

\u516c\u5171\u57fa\u7c7b\u5df2\u7ecf\u5c06\u6b64\u670d\u52a1\u4f5c\u4e3a\u57fa\u672c\u5c5e\u6027\u6ce8\u5165. \u4f8b\u5982\u4f60\u53ef\u4ee5\u76f4\u63a5\u5728\u5e94\u7528\u670d\u52a1\u4e2d\u4f7f\u7528 CurrentUser \u5c5e\u6027:

C#
using System;\nusing Volo.Abp.Application.Services;\nnamespace AbpDemo\n{\npublic class MyAppService : ApplicationService\n{\npublic void Foo()\n{\nGuid? userId = CurrentUser.Id;\n}\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/CurrentUser/#_2","title":"\u5c5e\u6027","text":"

\u4ee5\u4e0b\u662f ICurrentUser \u63a5\u53e3\u7684\u57fa\u672c\u5c5e\u6027:

  • IsAuthenticated \u5982\u679c\u5f53\u524d\u7528\u6237\u5df2\u767b\u5f55(\u5df2\u8ba4\u8bc1),\u5219\u8fd4\u56de true. \u5982\u679c\u7528\u6237\u5c1a\u672a\u767b\u5f55,\u5219 Id \u548c UserName \u5c06\u8fd4\u56de null.
  • Id (Guid?): \u5f53\u524d\u7528\u6237\u7684 Id,\u5982\u679c\u7528\u6237\u672a\u767b\u5f55,\u8fd4\u56de null.
  • UserName (string): \u5f53\u524d\u7528\u6237\u7684\u7528\u6237\u540d\u79f0. \u5982\u679c\u7528\u6237\u672a\u767b\u5f55,\u8fd4\u56de null.
  • TenantId (Guid?): \u5f53\u524d\u7528\u6237\u7684\u79df\u6237 Id. \u5bf9\u4e8e\u591a\u79df\u6237 \u5e94\u7528\u7a0b\u5e8f\u5f88\u6709\u7528. \u5982\u679c\u5f53\u524d\u7528\u6237\u672a\u5206\u914d\u7ed9\u79df\u6237,\u8fd4\u56de null.
  • Email (string): \u5f53\u524d\u7528\u6237\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740. \u5982\u679c\u5f53\u524d\u7528\u6237\u5c1a\u672a\u767b\u5f55\u6216\u672a\u8bbe\u7f6e\u7535\u5b50\u90ae\u4ef6\u5730\u5740,\u8fd4\u56de null.
  • EmailVerified (bool): \u5982\u679c\u5f53\u524d\u7528\u6237\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u5df2\u7ecf\u8fc7\u9a8c\u8bc1,\u8fd4\u56de true.
  • PhoneNumber (string): \u5f53\u524d\u7528\u6237\u7684\u7535\u8bdd\u53f7\u7801. \u5982\u679c\u5f53\u524d\u7528\u6237\u5c1a\u672a\u767b\u5f55\u6216\u672a\u8bbe\u7f6e\u7535\u8bdd\u53f7\u7801,\u8fd4\u56de null.
  • PhoneNumberVerified (bool): \u5982\u679c\u5f53\u524d\u7528\u6237\u7684\u7535\u8bdd\u53f7\u7801\u5df2\u7ecf\u8fc7\u9a8c\u8bc1,\u8fd4\u56de true.
  • Roles (string[]): \u5f53\u524d\u7528\u6237\u7684\u89d2\u8272. \u8fd4\u56de\u5f53\u524d\u7528\u6237\u89d2\u8272\u540d\u79f0\u7684\u5b57\u7b26\u4e32\u6570\u7ec4.
"},{"location":"user-guide/zh/infrastructure/CurrentUser/#methods","title":"Methods","text":"

ICurrentUser \u662f\u5728 ICurrentPrincipalAccessor \u4e0a\u5b9e\u73b0\u7684(\u8bf7\u53c2\u9605\u4ee5\u4e0b\u90e8\u5206),\u5e76\u53ef\u4ee5\u5904\u7406\u58f0\u660e. \u5b9e\u9645\u4e0a\u6240\u6709\u4e0a\u8ff0\u5c5e\u6027\u90fd\u662f\u4ece\u5f53\u524d\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u7684\u58f0\u660e\u4e2d\u68c0\u7d22\u7684.

\u5982\u679c\u4f60\u6709\u81ea\u5b9a\u4e49\u58f0\u660e\u6216\u83b7\u53d6\u5176\u4ed6\u975e\u5e38\u89c1\u58f0\u660e\u7c7b\u578b, ICurrentUser \u6709\u4e00\u4e9b\u76f4\u63a5\u4f7f\u7528\u58f0\u660e\u7684\u65b9\u6cd5.

  • FindClaim: \u83b7\u53d6\u7ed9\u5b9a\u540d\u79f0\u7684\u58f0\u660e,\u5982\u679c\u672a\u627e\u5230\u8fd4\u56de null.
  • FindClaims: \u83b7\u53d6\u5177\u6709\u7ed9\u5b9a\u540d\u79f0\u7684\u6240\u6709\u58f0\u660e(\u5141\u8bb8\u5177\u6709\u76f8\u540c\u540d\u79f0\u7684\u591a\u4e2a\u58f0\u660e\u503c).
  • GetAllClaims: \u83b7\u53d6\u6240\u6709\u58f0\u660e.
  • IsInRole: \u4e00\u79cd\u68c0\u67e5\u5f53\u524d\u7528\u6237\u662f\u5426\u5728\u6307\u5b9a\u89d2\u8272\u4e2d\u7684\u7b80\u5316\u65b9\u6cd5.

\u9664\u4e86\u8fd9\u4e9b\u6807\u51c6\u65b9\u6cd5,\u8fd8\u6709\u4e00\u4e9b\u6269\u5c55\u65b9\u6cd5:

  • FindClaimValue: \u83b7\u53d6\u5177\u6709\u7ed9\u5b9a\u540d\u79f0\u7684\u58f0\u660e\u7684\u503c,\u5982\u679c\u672a\u627e\u5230\u8fd4\u56de null. \u5b83\u6709\u4e00\u4e2a\u6cdb\u578b\u91cd\u8f7d\u5c06\u503c\u5f3a\u5236\u8f6c\u6362\u4e3a\u7279\u5b9a\u7c7b\u578b.
  • GetId: \u8fd4\u56de\u5f53\u524d\u7528\u6237\u7684 Id. \u5982\u679c\u5f53\u524d\u7528\u6237\u6ca1\u6709\u767b\u5f55\u5b83\u4f1a\u629b\u51fa\u4e00\u4e2a\u5f02\u5e38(\u800c\u4e0d\u662f\u8fd4\u56denull). \u4ec5\u5728\u4f60\u786e\u5b9a\u7528\u6237\u5df2\u7ecf\u5728\u4f60\u7684\u4ee3\u7801\u4e0a\u4e0b\u6587\u4e2d\u8fdb\u884c\u4e86\u8eab\u4efd\u9a8c\u8bc1\u65f6\u624d\u4f7f\u7528\u6b64\u9009\u9879.
"},{"location":"user-guide/zh/infrastructure/CurrentUser/#_3","title":"\u9a8c\u8bc1\u548c\u6388\u6743","text":"

ICurrentUser \u7684\u5de5\u4f5c\u65b9\u5f0f\u4e0e\u7528\u6237\u7684\u8eab\u4efd\u9a8c\u8bc1\u6216\u6388\u6743\u65b9\u5f0f\u65e0\u5173. \u5b83\u53ef\u4ee5\u4e0e\u4f7f\u7528\u5f53\u524d\u4e3b\u4f53\u7684\u4efb\u4f55\u8eab\u4efd\u9a8c\u8bc1\u7cfb\u7edf\u65e0\u7f1d\u5730\u914d\u5408\u4f7f\u7528(\u8bf7\u53c2\u9605\u4e0b\u9762\u7684\u90e8\u5206).

"},{"location":"user-guide/zh/infrastructure/CurrentUser/#icurrentprincipalaccessor","title":"ICurrentPrincipalAccessor","text":"

ICurrentPrincipalAccessor \u662f\u5f53\u9700\u8981\u5f53\u524d\u7528\u6237\u7684 Principal \u65f6\u4f7f\u7528\u7684\u670d\u52a1(\u7531 ABP \u6846\u67b6\u548c\u4f60\u7684\u5e94\u7528\u7a0b\u5e8f\u4ee3\u7801\u4f7f\u7528).

\u5bf9\u4e8e Web \u5e94\u7528\u7a0b\u5e8f, \u5b83\u83b7\u53d6\u5f53\u524d HttpContext \u7684 User \u5c5e\u6027,\u5bf9\u4e8e\u975e Web \u5e94\u7528\u7a0b\u5e8f\u5b83\u5c06\u8fd4\u56de Thread.CurrentPrincipal.

\u901a\u5e38\u4f60\u4e0d\u9700\u8981\u8fd9\u79cd\u4f4e\u7ea7\u522b\u7684 ICurrentPrincipalAccessor \u670d\u52a1,\u76f4\u63a5\u4f7f\u7528\u4e0a\u8ff0\u7684 ICurrentUser \u5373\u53ef.

"},{"location":"user-guide/zh/infrastructure/CurrentUser/#_4","title":"\u57fa\u672c\u7528\u6cd5","text":"

\u4f60\u53ef\u4ee5\u6ce8\u5165 ICurrentPrincipalAccessor \u5e76\u4e14\u4f7f\u7528 Principal \u5c5e\u6027\u83b7\u53d6\u5f53\u524d principal:

C#
public class MyService : ITransientDependency\n{\nprivate readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;\npublic MyService(ICurrentPrincipalAccessor currentPrincipalAccessor)\n{\n_currentPrincipalAccessor = currentPrincipalAccessor;\n}\npublic void Foo()\n{\nvar allClaims = _currentPrincipalAccessor.Principal.Claims.ToList();\n//...\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/CurrentUser/#principal","title":"\u66f4\u6539\u5f53\u524d Principal","text":"

\u9664\u4e86\u67d0\u4e9b\u9ad8\u7ea7\u573a\u666f\u5916,\u4f60\u4e0d\u9700\u8981\u8bbe\u7f6e\u6216\u66f4\u6539\u5f53\u524d Principal. \u5982\u679c\u9700\u8981\u53ef\u4ee5\u4f7f\u7528 ICurrentPrincipalAccessor \u7684 Change \u65b9\u6cd5. \u5b83\u63a5\u53d7\u4e00\u4e2a ClaimsPrincipal \u5bf9\u8c61\u5e76\u4f7f\u5176\u6210\u4e3a\u4f5c\u7528\u57df\u7684\"\u5f53\u524d\"\u5bf9\u8c61.

\u793a\u4f8b:

C#
public class MyAppService : ApplicationService\n{\nprivate readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;\npublic MyAppService(ICurrentPrincipalAccessor currentPrincipalAccessor)\n{\n_currentPrincipalAccessor = currentPrincipalAccessor;\n}\npublic void Foo()\n{\nvar newPrincipal = new ClaimsPrincipal(\nnew ClaimsIdentity(\nnew Claim[]\n{\nnew Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString()),\nnew Claim(AbpClaimTypes.UserName, \"john\"),\nnew Claim(\"MyCustomCliam\", \"42\")\n}\n)\n);\nusing (_currentPrincipalAccessor.Change(newPrincipal))\n{\nvar userName = CurrentUser.UserName; //returns \"john\"\n//...\n}\n}\n}\n

\u59cb\u7ec8\u5728 using \u8bed\u53e5\u4e2d\u4f7f\u7528 Change \u65b9\u6cd5,\u5728 using \u8303\u56f4\u7ed3\u675f\u540e\u5b83\u5c06\u6062\u590d\u4e3a\u539f\u59cb\u503c.

\u8fd9\u53ef\u4ee5\u662f\u4e00\u79cd\u6a21\u62df\u7528\u6237\u767b\u5f55\u7684\u5e94\u7528\u7a0b\u5e8f\u4ee3\u7801\u8303\u56f4\u7684\u65b9\u6cd5,\u4f46\u662f\u8bf7\u5c1d\u8bd5\u8c28\u614e\u4f7f\u7528\u5b83.

"},{"location":"user-guide/zh/infrastructure/CurrentUser/#abpclaimtypes","title":"AbpClaimTypes","text":"

AbpClaimTypes \u662f\u4e00\u4e2a\u9759\u6001\u7c7b\u5b83\u5b9a\u4e49\u4e86\u6807\u51c6\u58f0\u660e\u7684\u540d\u79f0\u88ab ABP \u6846\u67b6\u4f7f\u7528.

  • UserName, UserId, Role \u548c Email \u5c5e\u6027\u7684\u9ed8\u8ba4\u503c\u662f\u901a\u5e38System.Security.Claims.ClaimTypes\u7c7b\u8bbe\u7f6e\u7684, \u4f46\u4f60\u53ef\u4ee5\u6539\u53d8\u5b83\u4eec.

  • \u5176\u4ed6\u5c5e\u6027,\u5982 EmailVerified, PhoneNumber, TenantId ...\u662f\u7531 ABP \u6846\u67b6\u901a\u8fc7\u5c3d\u53ef\u80fd\u9075\u5faa\u6807\u51c6\u540d\u79f0\u6765\u5b9a\u4e49\u7684.

\u5efa\u8bae\u4f7f\u7528\u8fd9\u4e2a\u7c7b\u7684\u5c5e\u6027\u6765\u4ee3\u66ff\u58f0\u660e\u540d\u79f0\u7684\u9b54\u672f\u5b57\u7b26\u4e32.

"},{"location":"user-guide/zh/infrastructure/DataFiltering/","title":"\u6570\u636e\u8fc7\u6ee4","text":"

Volo.Abp.Data \u5305\u5b9a\u4e49\u4e86\u5728\u67e5\u8be2\u6570\u636e\u5e93\u65f6\u81ea\u52a8\u8fc7\u6ee4\u6570\u636e\u7684\u670d\u52a1.

"},{"location":"user-guide/zh/infrastructure/DataFiltering/#_2","title":"\u9884\u5b9a\u4e49\u7684\u8fc7\u6ee4","text":""},{"location":"user-guide/zh/infrastructure/DataFiltering/#isoftdelete","title":"ISoftDelete","text":"

\u5c06\u5b9e\u4f53\u6807\u8bb0\u4e3a\u5df2\u5220\u9664,\u5e76\u4e0d\u662f\u7269\u7406\u5220\u9664. \u5b9e\u73b0 ISoftDelete \u63a5\u53e3\u5c06\u4f60\u7684\u5b9e\u4f53\"\u8f6f\u5220\u9664\".

\u793a\u4f8b:

C#
namespace Acme.BookStore\n{\npublic class Book : AggregateRoot<Guid>, ISoftDelete\n{\npublic string Name { get; set; }\npublic bool IsDeleted { get; set; } //Defined by ISoftDelete\n}\n}\n

ISoftDelete \u5b9a\u4e49\u4e86 IsDeleted \u5c5e\u6027. \u5f53\u4f60\u4f7f\u7528\u4ed3\u50a8\u5220\u9664\u4e00\u6761\u8bb0\u5f55\u65f6, ABP \u4f1a\u81ea\u52a8\u5c06 IsDeleted \u8bbe\u7f6e\u4e3a true,\u5e76\u5c06\u5220\u9664\u64cd\u4f5c\u66ff\u6362\u4e3a\u4fee\u6539\u64cd\u4f5c(\u5982\u679c\u9700\u8981,\u4e5f\u53ef\u4ee5\u624b\u52a8\u5c06 IsDeleted \u8bbe\u7f6e\u4e3a true). \u5728\u67e5\u8be2\u6570\u636e\u5e93\u65f6\u4f1a\u81ea\u52a8\u8fc7\u6ee4\u8f6f\u5220\u9664\u7684\u5b9e\u4f53.

ISoftDelete \u8fc7\u6ee4\u9ed8\u8ba4\u542f\u7528, \u60f3\u8981\u771f\u6b63\u7684\u4ece\u6570\u636e\u5e93\u5220\u9664\u5b9e\u4f53\u9700\u8981\u663e\u793a\u7684\u7981\u7528\u8fc7\u6ee4. \u53c2\u89c1\u4e0b\u9762\u63d0\u5230\u7684 IDataFilter \u670d\u52a1.

"},{"location":"user-guide/zh/infrastructure/DataFiltering/#imultitenant","title":"IMultiTenant","text":"

[\u591a\u79df\u6237]\u662f\u521b\u5efa SaaS \u5e94\u7528\u7a0b\u5e8f\u7684\u6709\u6548\u65b9\u6cd5. \u591a\u79df\u6237\u5e94\u7528\u7a0b\u5e8f\u901a\u5e38\u9700\u8981\u5728\u79df\u6237\u95f4\u9694\u79bb\u6570\u636e. \u5b9e\u73b0 IMultiTenant \u63a5\u53e3\u4f7f\u4f60\u7684\u5b9e\u4f53\u652f\u6301 \"\u591a\u79df\u6237\".

\u793a\u4f8b:

C#
namespace Acme.BookStore\n{\npublic class Book : AggregateRoot<Guid>, ISoftDelete, IMultiTenant\n{\npublic string Name { get; set; }\npublic bool IsDeleted { get; set; } //Defined by ISoftDelete\npublic Guid? TenantId { get; set; } //Defined by IMultiTenant\n}\n}\n

IMultiTenant \u63a5\u53e3\u5b9a\u4e49\u4e86 TenantId \u5c5e\u6027\u7528\u4e8e\u81ea\u52a8\u8fc7\u6ee4\u5f53\u524d\u79df\u6237\u5b9e\u4f53. \u66f4\u591a\u4fe1\u606f\u53c2\u89c1[\u591a\u79df\u6237]\u6587\u6863.

"},{"location":"user-guide/zh/infrastructure/DataFiltering/#idatafilter","title":"IDataFilter \u670d\u52a1: \u542f\u7528/\u7981\u7528 \u6570\u636e\u8fc7\u6ee4","text":"

\u4f60\u53ef\u4ee5\u4f7f\u7528 IDataFilter \u670d\u52a1\u63a7\u5236\u6570\u636e\u8fc7\u6ee4.

\u793a\u4f8b:

C#
namespace Acme.BookStore\n{\npublic class MyBookService : ITransientDependency\n{\nprivate readonly IDataFilter _dataFilter;\nprivate readonly IRepository<Book, Guid> _bookRepository;\npublic MyBookService(\nIDataFilter dataFilter,\nIRepository<Book, Guid> bookRepository)\n{\n_dataFilter = dataFilter;\n_bookRepository = bookRepository;\n}\npublic async Task<List<Book>> GetAllBooksIncludingDeletedAsync()\n{\n//Temporary disable the ISoftDelete filter\nusing (_dataFilter.Disable<ISoftDelete>())\n{\nreturn await _bookRepository.GetListAsync();\n}\n}\n}\n}\n
  • [\u6ce8\u5165] IDataFilter \u670d\u52a1\u5230\u4f60\u7684\u7c7b\u4e2d.
  • \u5728 using \u8bed\u53e5\u4e2d\u4f7f\u7528 Disable \u65b9\u6cd5\u521b\u5efa\u4e00\u4e2a\u4ee3\u7801\u5757,\u5176\u4e2d\u7981\u7528\u4e86 ISoftDelete \u8fc7\u6ee4\u5668(\u59cb\u7ec8\u4e0e using \u642d\u914d\u4f7f\u7528,\u786e\u4fdd\u4ee3\u7801\u5757\u6267\u884c\u540e\u5c06\u8fc7\u6ee4\u91cd\u7f6e\u4e3a\u4e4b\u524d\u7684\u72b6\u6001).

IDataFilter.Enable \u65b9\u6cd5\u53ef\u4ee5\u542f\u7528\u8fc7\u6ee4. \u53ef\u4ee5\u5d4c\u5957\u4f7f\u7528 Enable \u548c Disable \u65b9\u6cd5\u5b9a\u4e49\u5185\u90e8\u4f5c\u7528\u57df.

"},{"location":"user-guide/zh/infrastructure/DataFiltering/#abpdatafilteroptions","title":"AbpDataFilterOptions","text":"

AbpDataFilterOptions \u7528\u4e8e\u8bbe\u7f6e\u6570\u636e\u8fc7\u6ee4\u7cfb\u7edf.

\u4e0b\u9762\u7684\u793a\u4f8b\u4ee3\u7801\u5728\u9ed8\u8ba4\u60c5\u51b5\u4e0b\u7981\u7528\u4e86 ISoftDelete \u8fc7\u6ee4,\u9664\u975e\u663e\u793a\u542f\u7528,\u5728\u67e5\u8be2\u6570\u636e\u5e93\u65f6\u4f1a\u5305\u542b\u6807\u8bb0\u4e3a\u5df2\u5220\u9664\u7684\u5b9e\u4f53:

C#
Configure<AbpDataFilterOptions>(options =>\n{\noptions.DefaultStates[typeof(ISoftDelete)] = new DataFilterState(isEnabled: false);\n});\n

\u66f4\u6539\u5168\u5c40\u8fc7\u6ee4\u7684\u9ed8\u8ba4\u503c\u9700\u8981\u5c0f\u5fc3,\u7279\u522b\u662f\u5728\u4f60\u4f7f\u7528\u9884\u6784\u5efa\u7684\u6a21\u5757\u65f6\u8be5\u6a21\u5757\u53ef\u80fd\u662f\u5728\u9ed8\u8ba4\u542f\u7528\u8f6f\u5220\u9664\u8fc7\u6ee4\u7684\u60c5\u51b5\u4e0b\u5f00\u53d1\u7684. \u4f46\u4f60\u53ef\u4ee5\u5b89\u5168\u7684\u4e3a\u81ea\u5df1\u5b9a\u4e49\u7684\u6570\u636e\u8fc7\u6ee4\u6267\u884c\u6b64\u64cd\u4f5c.

"},{"location":"user-guide/zh/infrastructure/DataFiltering/#_3","title":"\u81ea\u5b9a\u4e49\u6570\u636e\u8fc7\u6ee4","text":"

\u5b9a\u4e49\u548c\u5b9e\u73b0\u65b0\u7684\u8fc7\u6ee4\u5f88\u5927\u7a0b\u5e8f\u4e0a\u53d6\u51b3\u4e0e\u6570\u636e\u5e93\u63d0\u4f9b\u8005. ABP \u4e3a\u6240\u6709\u7684\u6570\u636e\u5e93\u63d0\u4f9b\u8005\u5b9e\u73b0\u4e86\u9884\u6784\u5efa\u7684\u8fc7\u6ee4.

\u9996\u5148\u4e3a\u8fc7\u6ee4\u5b9a\u4e49\u4e00\u4e2a\u63a5\u53e3 (\u5982 ISoftDelete \u548c IMultiTenant) \u7136\u540e\u7528\u5b9e\u4f53\u5b9e\u73b0\u5b83.

\u793a\u4f8b:

C#
public interface IIsActive\n{\nbool IsActive { get; }\n}\n

IIsActive \u63a5\u53e3\u53ef\u4ee5\u8fc7\u6ee4\u6d3b\u8dc3/\u6d88\u6781\u6570\u636e,\u4efb\u4f55[\u5b9e\u4f53]\u90fd\u53ef\u4ee5\u5b9e\u73b0\u5b83:

C#
public class Book : AggregateRoot<Guid>, IIsActive\n{\npublic string Name { get; set; }\npublic bool IsActive { get; set; } //Defined by IIsActive\n}\n
"},{"location":"user-guide/zh/infrastructure/DataFiltering/#entityframework-core","title":"EntityFramework Core","text":"

ABP \u4f7f\u7528EF Core \u7684\u5168\u5c40\u8fc7\u6ee4\u7cfb\u7edf\u7528\u4e8e[EF Core \u96c6\u6210]. \u6240\u4ee5\u5b83\u5f88\u597d\u7684\u96c6\u6210\u5230 EF Core \u4e2d,\u5373\u4f7f\u4f60\u76f4\u63a5\u4f7f\u7528 DbContext \u5b83\u4e5f\u53ef\u4ee5\u6b63\u5e38\u5de5\u4f5c.

\u5b9e\u73b0\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u7684\u6700\u4f73\u65b9\u6cd5\u662f\u4e3a\u91cd\u5199\u4f60\u7684 DbContext \u7684 ShouldFilterEntity \u548c CreateFilterExpression \u65b9\u6cd5. \u793a\u4f8b:

C#
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;\nprotected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)\n{\nif (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))\n{\nreturn true;\n}\nreturn base.ShouldFilterEntity<TEntity>(entityType);\n}\nprotected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()\n{\nvar expression = base.CreateFilterExpression<TEntity>();\nif (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))\n{\nExpression<Func<TEntity, bool>> isActiveFilter =\ne => !IsActiveFilterEnabled || EF.Property<bool>(e, \"IsActive\");\nexpression = expression == null\n? isActiveFilter\n: CombineExpressions(expression, isActiveFilter);\n}\nreturn expression;\n}\n
  • \u6dfb\u52a0 IsActiveFilterEnabled \u5c5e\u6027\u7528\u4e8e\u68c0\u67e5\u662f\u5426\u542f\u7528\u4e86 IIsActive . \u5185\u90e8\u4f7f\u7528\u4e86\u4e4b\u524d\u4ecb\u7ecd\u5230\u7684 IDataFilter \u670d\u52a1.
  • \u91cd\u5199 ShouldFilterEntity \u548c CreateFilterExpression \u65b9\u6cd5\u68c0\u67e5\u7ed9\u5b9a\u5b9e\u4f53\u662f\u5426\u5b9e\u73b0 IIsActive \u63a5\u53e3,\u5728\u5fc5\u8981\u65f6\u7ec4\u5408\u8868\u8fbe\u5f0f.
"},{"location":"user-guide/zh/infrastructure/DataSeeding/","title":"\u79cd\u5b50\u6570\u636e","text":""},{"location":"user-guide/zh/infrastructure/DataSeeding/#_2","title":"\u4ecb\u7ecd","text":"
  • \u4f7f\u7528\u6570\u636e\u5e93\u7684\u67d0\u4e9b\u5e94\u7528\u7a0b\u5e8f(\u6216\u6a21\u5757),\u53ef\u80fd\u9700\u8981\u6709\u4e00\u4e9b\u521d\u59cb\u6570\u636e\u624d\u80fd\u591f\u6b63\u5e38\u542f\u52a8\u548c\u8fd0\u884c. \u4f8b\u5982\u7ba1\u7406\u5458\u7528\u6237\u548c\u89d2\u8272\u5fc5\u987b\u5728\u4e00\u5f00\u59cb\u5c31\u53ef\u7528. \u5426\u5219\u4f60\u5c31\u65e0\u6cd5\u767b\u5f55\u5230\u5e94\u7528\u7a0b\u5e8f\u521b\u5efa\u65b0\u7528\u6237\u548c\u89d2\u8272.
  • \u6570\u636e\u79cd\u5b50\u4e5f\u53ef\u7528\u4e8e\u6d4b\u8bd5\u7684\u76ee\u7684,\u4f60\u7684\u81ea\u52a8\u6d4b\u8bd5\u53ef\u4ee5\u5047\u5b9a\u6570\u636e\u5e93\u4e2d\u6709\u4e00\u4e9b\u53ef\u7528\u7684\u521d\u59cb\u6570\u636e.
"},{"location":"user-guide/zh/infrastructure/DataSeeding/#idataseedcontributor","title":"IDataSeedContributor","text":"

\u5c06\u6570\u636e\u79cd\u5b50\u5316\u5230\u6570\u636e\u5e93\u9700\u8981\u5b9e\u73b0IDataSeedContributor\u63a5\u53e3.

\u793a\u4f8b: \u5982\u679c\u6ca1\u6709\u56fe\u4e66,\u5219\u5411\u6570\u636e\u5e93\u64ad\u79cd\u4e00\u4e2a\u521d\u59cb\u56fe\u4e66

C#
namespace Acme.BookStore\n{\npublic class BookStoreDataSeedContributor : IDataSeedContributor, ITransientDependency\n{\nprivate readonly IRepository<Book, Guid> _bookRepository;\nprivate readonly IGuidGenerator _guidGenerator;\nprivate readonly ICurrentTenant _currentTenant;\npublic BookStoreDataSeedContributor(\nIRepository<Book, Guid> bookRepository,\nIGuidGenerator guidGenerator,\nICurrentTenant currentTenant)\n{\n_bookRepository = bookRepository;\n_guidGenerator = guidGenerator;\n_currentTenant = currentTenant;\n}\npublic async Task SeedAsync(DataSeedContext context)\n{\nusing (_currentTenant.Change(context?.TenantId))\n{\nif (await _bookRepository.GetCountAsync() > 0)\n{\nreturn;\n}\nvar book = new Book(\nid: _guidGenerator.Create(),\nname: \"The Hitchhiker's Guide to the Galaxy\",\ntype: BookType.ScienceFiction,\npublishDate: new DateTime(1979, 10, 12),\nprice: 42\n);\nawait _bookRepository.InsertAsync(book);\n}\n}\n}\n}\n
  • IDataSeedContributor \u5b9a\u4e49\u4e86 SeedAsync \u65b9\u6cd5\u7528\u4e8e\u6267\u884c \u6570\u636e\u79cd\u5b50\u903b\u8f91.
  • \u901a\u5e38\u68c0\u67e5\u6570\u636e\u5e93\u662f\u5426\u5df2\u7ecf\u5b58\u5728\u79cd\u5b50\u6570\u636e.
  • \u4f60\u53ef\u4ee5\u6ce8\u5165\u670d\u52a1,\u68c0\u67e5\u6570\u636e\u64ad\u79cd\u6240\u9700\u7684\u4efb\u4f55\u903b\u8f91.

\u6570\u636e\u79cd\u5b50\u8d21\u732e\u8005\u7531 ABP \u6846\u67b6\u81ea\u52a8\u53d1\u73b0,\u5e76\u4f5c\u4e3a\u6570\u636e\u64ad\u79cd\u8fc7\u7a0b\u7684\u4e00\u90e8\u5206\u6267\u884c.

"},{"location":"user-guide/zh/infrastructure/DataSeeding/#_3","title":"\u6a21\u5757\u5316","text":"

\u4e00\u4e2a\u5e94\u7528\u7a0b\u5e8f\u53ef\u4ee5\u5177\u6709\u591a\u4e2a\u79cd\u5b50\u6570\u636e\u8d21\u732e\u8005(IDataSeedContributor)\u7c7b. \u4efb\u4f55\u53ef\u91cd\u7528\u6a21\u5757\u4e5f\u53ef\u4ee5\u5b9e\u73b0\u6b64\u63a5\u53e3\u64ad\u79cd\u5176\u81ea\u5df1\u7684\u521d\u59cb\u6570\u636e.

"},{"location":"user-guide/zh/infrastructure/MultiTenancy/","title":"\u591a\u79df\u6237","text":""},{"location":"user-guide/zh/infrastructure/MultiTenancy/#_2","title":"\u5b9a\u4e49\u5b9e\u4f53","text":"

\u4f60\u53ef\u4ee5\u5728\u4f60\u7684\u5b9e\u4f53\u4e2d\u5b9e\u73b0 IMultiTenant \u63a5\u53e3\u6765\u5b9e\u73b0\u591a\u79df\u6237,\u4f8b\u5982:

C#
namespace MyCompany.MyProject\n{\npublic class Product : AggregateRoot, IMultiTenant\n{\npublic Guid? TenantId { get; set; } //IMultiTenant \u5b9a\u4e49\u4e86 TenantId \u5c5e\u6027\npublic string Name { get; set; }\npublic float Price { get; set; }\n}\n}\n

\u5b9e\u73b0 IMultiTenant \u63a5\u53e3,\u9700\u8981\u5728\u5b9e\u4f53\u4e2d\u5b9a\u4e49\u4e00\u4e2a TenantId \u7684\u5c5e\u6027

"},{"location":"user-guide/zh/infrastructure/MultiTenancy/#_3","title":"\u83b7\u53d6\u5f53\u524d\u79df\u6237","text":"

\u4f60\u7684\u4ee3\u7801\u4e2d\u53ef\u80fd\u9700\u8981\u83b7\u53d6\u5f53\u524d\u79df\u6237(\u5148\u4e0d\u7ba1\u5b83\u5177\u4f53\u662f\u600e\u4e48\u53d6\u5f97\u7684).\u5bf9\u4e8e\u8fd9\u79cd\u60c5\u51b5\u4f60\u53ef\u4ee5\u6ce8\u5165\u5e76\u4f7f\u7528 ICurrentTenant \u63a5\u53e3.\u4f8b\u5982:

C#
namespace MyCompany.MyProject\n{\npublic class MyService : ITransientDependency\n{\nprivate readonly ICurrentTenant _currentTenant;\npublic MyService(ICurrentTenant currentTenant)\n{\n_currentTenant = currentTenant;\n}\npublic void DoIt()\n{\nvar tenantId = _currentTenant.Id;\n//\u5728\u4f60\u7684\u4ee3\u7801\u4e2d\u4f7f\u7528tenantId\n}\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/MultiTenancy/#_4","title":"\u6539\u53d8\u5f53\u524d\u79df\u6237","text":"C#
namespace MultiTenancyDemo.Products\n{\npublic class ProductManager : DomainService\n{\nprivate readonly IRepository<Product, Guid> _productRepository;\npublic ProductManager(IRepository<Product, Guid> productRepository)\n{\n_productRepository = productRepository;\n}\npublic async Task<long> GetProductCountAsync(Guid? tenantId)\n{\nusing (CurrentTenant.Change(tenantId))\n{\nreturn await _productRepository.GetCountAsync();\n}\n}\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/MultiTenancy/#_5","title":"\u786e\u5b9a\u5f53\u524d\u79df\u6237","text":"

\u591a\u79df\u6237\u7684\u5e94\u7528\u7a0b\u5e8f\u8fd0\u884c\u7684\u65f6\u5019\u9996\u5148\u8981\u505a\u7684\u5c31\u662f\u786e\u5b9a\u5f53\u524d\u79df\u6237. Volo.Abp.MultiTenancy \u53ea\u63d0\u4f9b\u4e86\u7528\u4e8e\u786e\u5b9a\u5f53\u524d\u79df\u6237\u7684\u62bd\u8c61(\u79f0\u4e3a\u79df\u6237\u89e3\u6790\u5668),\u4f46\u662f\u5e76\u6ca1\u6709\u73b0\u6210\u7684\u5b9e\u73b0. Volo.Abp.AspNetCore.MultiTenancy \u5df2\u7ecf\u5b9e\u73b0\u4e86\u4ece\u5f53\u524d Web \u8bf7\u6c42(\u4ece\u5b50\u57df\u540d,\u8bf7\u6c42\u5934,cookie,\u8def\u7531...\u7b49)\u4e2d\u786e\u5b9a\u5f53\u524d\u79df\u6237.\u672c\u6587\u540e\u9762\u4f1a\u4ecb\u7ecd Volo.Abp.AspNetCore.MultiTenancy.

"},{"location":"user-guide/zh/infrastructure/MultiTenancy/#_6","title":"\u81ea\u5b9a\u4e49\u79df\u6237\u89e3\u6790\u5668","text":"

\u4f60\u53ef\u4ee5\u50cf\u4e0b\u9762\u8fd9\u6837,\u5728\u4f60\u6a21\u5757\u7684 ConfigureServices \u65b9\u6cd5\u4e2d\u5c06\u81ea\u5b9a\u4e49\u89e3\u6790\u5668\u5e76\u6dfb\u52a0\u5230 AbpTenantResolveOptions \u4e2d:

C#
namespace MyCompany.MyProject\n{\n[DependsOn(typeof(AbpMultiTenancyModule))]\npublic class MyModule : AbpModule\n{\npublic override void ConfigureServices(ServiceConfigurationContext context)\n{\nConfigure<AbpTenantResolveOptions>(options =>\n{\noptions.TenantResolvers.Add(new MyCustomTenantResolveContributor());\n});\n//...\n}\n}\n}\n

MyCustomTenantResolveContributor\u5fc5\u987b\u50cf\u4e0b\u9762\u8fd9\u6837\u5b9e\u73b0 ITenantResolveContributor \u63a5\u53e3:

C#
namespace MyCompany.MyProject\n{\npublic class MyCustomTenantResolveContributor : ITenantResolveContributor\n{\npublic override Task ResolveAsync(ITenantResolveContext context)\n{\ncontext.TenantIdOrName = ... //\u4ece\u5176\u4ed6\u5730\u65b9\u83b7\u53d6\u79df\u6237id\u6216\u79df\u6237\u540d\u5b57...\n}\n}\n}\n

\u5982\u679c\u80fd\u786e\u5b9a\u79df\u6237 id \u6216\u79df\u6237\u540d\u5b57\u53ef\u4ee5\u5728\u79df\u6237\u89e3\u6790\u5668\u4e2d\u8bbe\u7f6e TenantIdOrName.\u5982\u679c\u4e0d\u80fd\u786e\u5b9a,\u90a3\u5c31\u7a7a\u7740\u8ba9\u4e0b\u4e00\u4e2a\u89e3\u6790\u5668\u6765\u786e\u5b9a\u5b83.

"},{"location":"user-guide/zh/infrastructure/MultiTenancy/#_7","title":"\u79df\u6237\u4fe1\u606f","text":"

ITenantStore \u8ddf TenantConfiguration \u7c7b\u4e00\u8d77\u5de5\u4f5c,\u5e76\u4e14\u5305\u542b\u4e86\u51e0\u4e2a\u79df\u6237\u5c5e\u6027:

  • Id:\u79df\u6237\u7684\u552f\u4e00 Id.
  • Name: \u79df\u6237\u7684\u552f\u4e00\u540d\u79f0.
  • ConnectionStrings:\u5982\u679c\u8fd9\u4e2a\u79df\u6237\u6709\u4e13\u95e8\u7684\u6570\u636e\u5e93\u6765\u5b58\u50a8\u6570\u636e.\u5b83\u53ef\u4ee5\u63d0\u4f9b\u6570\u636e\u5e93\u7684\u5b57\u7b26\u4e32(\u5b83\u53ef\u4ee5\u5177\u6709\u9ed8\u8ba4\u7684\u8fde\u63a5\u5b57\u7b26\u4e32\u548c\u6bcf\u4e2a\u6a21\u5757\u7684\u8fde\u63a5\u5b57\u7b26\u4e32).
"},{"location":"user-guide/zh/infrastructure/MultiTenancy/#_8","title":"\u591a\u79df\u6237\u4e2d\u95f4\u4ef6","text":"

Volo.Abp.AspNetCore.MultiTenancy \u5305\u542b\u4e86\u591a\u79df\u6237\u4e2d\u95f4\u4ef6...

C#
app.UseMultiTenancy();\n
"},{"location":"user-guide/zh/infrastructure/MultiTenancy/#web","title":"\u4ece Web \u8bf7\u6c42\u4e2d\u786e\u5b9a\u5f53\u524d\u79df\u6237","text":"

Volo.Abp.AspNetCore.MultiTenancy \u6dfb\u52a0\u4e86\u4e0b\u9762\u8fd9\u4e9b\u79df\u6237\u89e3\u6790\u5668,\u4ece\u5f53\u524d Web \u8bf7\u6c42(\u6309\u4f18\u5148\u7ea7\u6392\u5e8f)\u4e2d\u786e\u5b9a\u5f53\u524d\u79df\u6237.

  • CurrentUserTenantResolveContributor: \u5982\u679c\u5f53\u524d\u7528\u6237\u5df2\u767b\u5f55,\u4ece\u5f53\u524d\u7528\u6237\u7684\u58f0\u660e\u4e2d\u83b7\u53d6\u79df\u6237 Id. \u51fa\u4e8e\u5b89\u5168\u8003\u8651,\u5e94\u8be5\u59cb\u7ec8\u5c06\u5176\u505a\u4e3a\u7b2c\u4e00\u4e2a Contributor.
  • QueryStringTenantResolveContributor: \u5c1d\u8bd5\u4ece query string \u53c2\u6570\u4e2d\u83b7\u53d6\u5f53\u524d\u79df\u6237,\u9ed8\u8ba4\u53c2\u6570\u540d\u4e3a\"__tenant\".
  • RouteTenantResolveContributor:\u5c1d\u8bd5\u4ece\u5f53\u524d\u8def\u7531\u4e2d\u83b7\u53d6(URL \u8def\u5f84),\u9ed8\u8ba4\u662f\u53d8\u91cf\u540d\u662f\"__tenant\".\u6240\u4ee5,\u5982\u679c\u4f60\u7684\u8def\u7531\u4e2d\u5b9a\u4e49\u4e86\u8fd9\u4e2a\u53d8\u91cf,\u5c31\u53ef\u4ee5\u4ece\u8def\u7531\u4e2d\u786e\u5b9a\u5f53\u524d\u79df\u6237.
  • HeaderTenantResolveContributor: \u5c1d\u8bd5\u4ece HTTP header \u4e2d\u83b7\u53d6\u5f53\u524d\u79df\u6237,\u9ed8\u8ba4\u7684 header \u540d\u79f0\u662f\"__tenant\".
  • CookieTenantResolveContributor: \u5c1d\u8bd5\u4ece\u5f53\u524d cookie \u4e2d\u83b7\u53d6\u5f53\u524d\u79df\u6237.\u9ed8\u8ba4\u7684 Cookie \u540d\u79f0\u662f\"__tenant\".

\u5982\u679c\u4f60\u4f7f\u7528 nginx \u4f5c\u4e3a\u53cd\u5411\u4ee3\u7406\u670d\u52a1\u5668,\u8bf7\u6ce8\u610f\u5982\u679cTenantKey\u5305\u542b\u4e0b\u5212\u7ebf\u6216\u5176\u4ed6\u7279\u6b8a\u5b57\u7b26\u53ef\u80fd\u5b58\u5728\u95ee\u9898, \u8bf7\u53c2\u8003: http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers > http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers

\u53ef\u4ee5\u4f7f\u7528 AbpAspNetCoreMultiTenancyOptions \u4fee\u6539\u9ed8\u8ba4\u7684\u53c2\u6570\u540d\"__tenant\".\u4f8b\u5982:

C#
services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>\n{\noptions.TenantKey = \"MyTenantKey\";\n});\n
"},{"location":"user-guide/zh/infrastructure/authorization/","title":"\u6388\u6743","text":"

\u6388\u6743\u7528\u4e8e\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5224\u65ad\u662f\u5426\u5141\u8bb8\u7528\u6237\u6267\u884c\u67d0\u4e9b\u7279\u5b9a\u7684\u64cd\u4f5c.

ABP \u6269\u5c55\u4e86ASP.NET Core \u6388\u6743, \u5c06\u6743\u9650\u6dfb\u52a0\u4e3a\u81ea\u52a8\u7b56\u7565\u5e76\u4e14\u4f7f\u6388\u6743\u7cfb\u7edf\u5728 \u5e94\u7528\u670d\u52a1 \u540c\u6837\u53ef\u7528.

"},{"location":"user-guide/zh/infrastructure/authorization/#authorize-attribute","title":"Authorize Attribute","text":"

ASP.NET Core \u5b9a\u4e49\u4e86 Authorize\u7279\u6027\u7528\u4e8e\u5728\u63a7\u5236\u5668,\u63a7\u5236\u5668\u65b9\u6cd5\u4ee5\u53ca\u9875\u9762\u4e0a\u6388\u6743. \u73b0\u5728 ABP \u5c06\u5b83\u5e26\u5230\u4e86\u5e94\u7528\u670d\u52a1.

\u793a\u4f8b:

C#
using System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Volo.Abp.Application.Services;\nnamespace Acme.BookStore\n{\n[Authorize]\npublic class AuthorAppService : ApplicationService, IAuthorAppService\n{\npublic Task<List<AuthorDto>> GetListAsync()\n{\n...\n}\n[AllowAnonymous]\npublic Task<AuthorDto> GetAsync(Guid id)\n{\n...\n}\n[Authorize(\"BookStore_Author_Create\")]\npublic Task CreateAsync(CreateAuthorDto input)\n{\n...\n}\n}\n}\n
  • Authorize\u7528\u6237\u5fc5\u987b\u767b\u9646\u5230\u5e94\u7528\u7a0b\u5e8f\u624d\u53ef\u4ee5\u8bbf\u95ee AuthorAppService \u4e2d\u7684\u65b9\u6cd5. \u6240\u4ee5GetListAsync \u65b9\u6cd5\u4ec5\u53ef\u7528\u4e8e\u901a\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237.
  • AllowAnonymous \u7981\u7528\u8eab\u4efd\u9a8c\u8bc1. \u6240\u4ee5 GetAsync \u65b9\u6cd5\u4efb\u4f55\u4eba\u90fd\u53ef\u4ee5\u8bbf\u95ee,\u5305\u62ec\u672a\u6388\u6743\u7684\u7528\u6237.
  • [Authorize(\"BookStore_Author_Create\")] \u5b9a\u4e49\u4e86\u4e00\u4e2a\u7b56\u7565 (\u53c2\u9605 \u57fa\u4e8e\u7b56\u7565\u7684\u6388\u6743),\u5b83\u7528\u4e8e\u68c0\u67e5\u5f53\u524d\u7528\u6237\u7684\u6743\u9650.\"BookStore_Author_Create\" \u662f\u4e00\u4e2a\u7b56\u7565\u540d\u79f0. \u5982\u679c\u4f60\u60f3\u8981\u4f7f\u7528\u7b56\u7565\u7684\u6388\u6743\u65b9\u5f0f,\u9700\u8981\u5728 ASP.NET Core \u6388\u6743\u7cfb\u7edf\u4e2d\u9884\u5148\u5b9a\u4e49\u5b83.
"},{"location":"user-guide/zh/infrastructure/authorization/#_2","title":"\u5b9a\u4e49\u6743\u9650","text":"

\u521b\u5efa\u4e00\u4e2a\u7ee7\u627f\u81ea PermissionDefinitionProvider \u7684\u7c7b,\u5982\u4e0b\u6240\u793a:

C#
using Volo.Abp.Authorization.Permissions;\nnamespace Acme.BookStore.Permissions\n{\npublic class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider\n{\npublic override void Define(IPermissionDefinitionContext context)\n{\nvar myGroup = context.AddGroup(\"BookStore\");\nmyGroup.AddPermission(\"BookStore_Author_Create\");\n}\n}\n}\n

ABP \u4f1a\u81ea\u52a8\u53d1\u73b0\u8fd9\u4e2a\u7c7b,\u4e0d\u9700\u8981\u8fdb\u884c\u914d\u7f6e!

\u5728 Define \u65b9\u6cd5\u4e2d\u6dfb\u52a0\u6743\u9650\u7ec4\u6216\u8005\u83b7\u53d6\u5df2\u5b58\u5728\u7684\u6743\u9650\u7ec4,\u5e76\u5411\u6743\u9650\u7ec4\u4e2d\u6dfb\u52a0\u6743\u9650. \u5728\u5b9a\u4e49\u6743\u9650\u540e\u5c31\u53ef\u4ee5\u5728 ASP.NET Core \u6743\u9650\u7cfb\u7edf\u4e2d\u5f53\u505a\u7b56\u7565\u540d\u79f0\u4f7f\u7528. \u5728\u89d2\u8272\u7684\u6743\u9650\u7ba1\u7406\u6a21\u6001\u6846\u4e2d\u540c\u6837\u53ef\u4ee5\u770b\u5230:

"},{"location":"user-guide/zh/infrastructure/authorization/#_3","title":"\u591a\u79df\u6237","text":"

\u5728\u5b9a\u4e49\u65b0\u6743\u9650\u65f6\u53ef\u4ee5\u8bbe\u7f6e\u591a\u79df\u6237\u9009\u9879. \u6709\u4e0b\u9762\u4e09\u4e2a\u503c:

  • Host: \u6743\u9650\u4ec5\u9002\u7528\u4e8e\u5bbf\u4e3b.
  • Tenant: \u6743\u9650\u4ec5\u9002\u7528\u4e8e\u79df\u6237.
  • Both (\u9ed8\u8ba4): \u6743\u9650\u9002\u7528\u4e0e\u5bbf\u4e3b\u548c\u79df\u6237.

\u5982\u679c\u4f60\u7684\u5e94\u7528\u7a0b\u5e8f\u4e0d\u662f\u591a\u79df\u6237\u7684,\u53ef\u4ee5\u5ffd\u7565\u8fd9\u4e2a\u9009\u9879.

AddPermission \u65b9\u6cd5\u7684\u7b2c\u4e09\u4e2a\u53c2\u6570\u7528\u4e8e\u8bbe\u7f6e\u591a\u79df\u6237\u9009\u9879:

C#
myGroup.AddPermission(\n\"BookStore_Author_Create\",\nLocalizableString.Create<BookStoreResource>(\"Permission:BookStore_Author_Create\"),\nmultiTenancySide: MultiTenancySides.Tenant //set multi-tenancy side!\n);\n
"},{"location":"user-guide/zh/infrastructure/authorization/#_4","title":"\u524d\u7aef\u6743\u9650","text":""},{"location":"user-guide/zh/infrastructure/authorization/#_5","title":"\u83dc\u5355\u6743\u9650","text":"TypeScript
import type { AppRouteModule } from \"/@/router/types\";\nimport { LAYOUT } from \"/@/router/constant\";\nimport { t } from \"/@/hooks/web/useI18n\";\nconst tenant: AppRouteModule = {\npath: \"/tenant\",\nname: \"Tenant\",\ncomponent: LAYOUT,\nmeta: {\norderNo: 30,\nicon: \"ant-design:contacts-outlined\",\ntitle: t(\"routes.tenant.tenantManagement\"),\n},\nchildren: [\n{\npath: \"Tenant\",\nname: \"Tenant\",\ncomponent: () => import(\"/@/views/tenants/Tenant.vue\"),\nmeta: {\ntitle: t(\"routes.tenant.tenantList\"),\nicon: \"ant-design:switcher-filled\",\npolicy: \"AbpTenantManagement.Tenants\", //\u83dc\u5355\u6743\u9650\n},\n},\n],\n};\nexport default tenant;\n
"},{"location":"user-guide/zh/infrastructure/authorization/#_6","title":"\u6309\u94ae\u6743\u9650","text":"TypeScript
<template>\n<div>\n<BasicTable @register=\"registerTable\" size=\"small\">\n<template #action=\"{ record }\">\n<TableAction\n:actions=\"[\n            {\n              icon: 'ant-design:edit-outlined',\n              auth: 'AbpIdentity.Users.Update', // \u6309\u94ae\u6743\u9650\n              label: t('common.editText'),\n              onClick: handleEdit.bind(null, record),\n            },\n          ]\"\n:dropDownActions=\"[\n            {\n              auth: 'AbpIdentity.Users.Delete', // \u6309\u94ae\u6743\u9650\n              label: t('common.delText'),\n              onClick: handleDelete.bind(null, record),\n            },\n            {\n              auth: 'System.Users.Enable', // \u6309\u94ae\u6743\u9650\n              label: !record.isActive\n                ? t('common.enabled')\n                : t('common.disEnabled'),\n              onClick: handleLock.bind(null, record),\n            },\n          ]\"\n/>\n</template>\n</BasicTable>\n<CreateAbpUser\n@register=\"registerCreateAbpUserModal\"\n@reload=\"reload\"\n:bodyStyle=\"{ 'padding-top': '0' }\"\n/>\n<EditAbpUser\n@register=\"registerEditAbpUserModal\"\n@reload=\"reload\"\n:bodyStyle=\"{ 'padding-top': '0' }\"\n/>\n</div>\n</template>\n
"},{"location":"user-guide/zh/infrastructure/batch/","title":"\u6279\u91cf\u64cd\u4f5c","text":"

EFCore7.0 \u4e4b\u540e,\u63d0\u4f9b\u4e86\u6279\u91cf\u66f4\u65b0\u548c\u6279\u91cf\u5220\u9664,\u4f46\u662f\u4e0d\u63d0\u4f9b\u6279\u91cf\u65b0\u589e, \u5fae\u8f6f EFCore \u6279\u91cf\u64cd\u4f5c\u6587\u6863

"},{"location":"user-guide/zh/infrastructure/batch/#_2","title":"\u5b89\u88c5","text":"
  • \u6dfb\u52a0\u4ee5\u4e0b NuGet \u5305\u5230\u4f60\u7684\u9879\u76ee
  • Lion.AbpPro.EntityFrameworkCore.Mysql
  • \u6dfb\u52a0 [DependsOn(typeof(AbpProEntityFrameworkCoreMysqlModule))] \u5230\u4f60\u7684\u9879\u76ee\u6a21\u5757\u7c7b.
"},{"location":"user-guide/zh/infrastructure/batch/#_3","title":"\u539f\u7406","text":"
  • \u901a\u8fc7 MySqlBulkCopy \u6765\u5b9e\u73b0\u6279\u91cf\u65b0\u589e\u5b98\u65b9\u6587\u6863
  • \u5b9e\u73b0 Abp \u6279\u91cf\u64cd\u4f5c\u63a5\u53e3 IEfCoreBulkOperationProvider
C#
public class EfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency\n{\n/// <summary>\n/// \u6279\u91cf\u65b0\u589e\n/// </summary>\n///<remarks>\n/// <para>\n/// - mysql\u542f\u7528:SET GLOBAL local_infile = true;\n/// </para>\n/// <para>\n/// - \u6570\u636e\u5e93\u8fde\u63a5\u5b57\u7b26\u4e32\u9700\u8981\u52a0\u4e0a\uff1aAllowLoadLocalInfile=true\n/// </para>\n/// - abp\u7684\u5ba1\u8ba1\u5b57\u6bb5\u9700\u8981\u624b\u52a8\u8d4b\u503c\uff0c\u6bd4\u5982\u521b\u5efa\u4eba,\u521b\u5efa\u65f6\u95f4,\u6216\u8005\u4f7f\u7528AuditPropertySetter\n/// <para>\n/// - \u53ea\u652f\u6301\u5355\u8868,\u6bd4\u5982\u6709\u4e00\u4e2aBlog\u8868\u548cPost\u8868\u4e00\u5bf9\u591a\u5173\u7cfb,\u9700\u8981\u8c03\u7528\u4e24\u6b21 InsertManyAsync\n/// </para>\n/// </remarks>\npublic virtual async Task InsertManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)\nwhere TDbContext : IEfCoreDbContext where TEntity : class, IEntity\n{\nvar dbContext = await repository.GetDbContextAsync();\nvar dbTransaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();\nawait dbContext.BulkInsertAsync(entities, dbTransaction as MySqlTransaction, cancellationToken);\nif (autoSave)\n{\nawait dbContext.SaveChangesAsync(cancellationToken);\n}\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/batch/#_4","title":"\u793a\u4f8b","text":"C#
namespace Lion.AbpPro.EntityFrameworkCore.Tests.Services;\npublic class BlogAppService : ApplicationService\n{\nprivate readonly IBlogRepository _blogRepository;\nprivate readonly IRepository<Post, Guid> _postRepository;\nprivate readonly IRepository<Comment, Guid> _commentRepository;\nprivate readonly IdentityRoleManager _identityRoleManager;\npublic BlogAppService(IBlogRepository blogRepository, IdentityRoleManager identityRoleManager, IRepository<Post, Guid> postRepository, IRepository<Comment, Guid> commentRepository)\n{\n_blogRepository = blogRepository;\n_identityRoleManager = identityRoleManager;\n_postRepository = postRepository;\n_commentRepository = commentRepository;\n}\n/// <summary>\n/// \u6279\u91cf\u63d2\u516510000\u6761\u6570\u636e\n/// </summary>\npublic async Task CreateAsync(int qty = 10000)\n{\n// mock \u6570\u636e\nvar list = GenFu.GenFu.ListOf<Blog>(qty);\nvar stopwatch = new Stopwatch();\nstopwatch.Start();\nawait _blogRepository.InsertManyAsync(list);\nstopwatch.Stop();\nLogger.LogInformation($\"\u6279\u91cf\u63d2\u5165{list.Count}\u6761,\u8017\u65f6(\u5355\u4f4d:\u6beb\u79d2):{stopwatch.ElapsedMilliseconds}\");\n}\n/// <summary>\n/// \u6279\u91cf\u63d2\u516510000\u6761\u6570\u636e\n/// </summary>\npublic async Task CreateAllAsync(int qty = 10000)\n{\n// mock \u6570\u636e\nvar blogs = GenFu.GenFu.ListOf<Blog>(qty);\nvar posts = new List<Post>();\nvar comments = new List<Comment>();\n// blog\u548cpost\u4e00\u5bf9\u591a,post\u548ccomment\u4e00\u5bf9\u591a\n// \u6709\u4e3b\u5916\u952e\u5173\u7cfb,\u6240\u4ee5\u5faa\u73afmock\u6570\u636e\nforeach (var blog in blogs)\n{\nposts.Add(new Post(GuidGenerator.Create(), blog.Id, \"name\"));\n}\nforeach (var post in posts)\n{\ncomments.Add(new Comment(GuidGenerator.Create(), 1, post.Id, \"content\"));\n}\nvar stopwatch = new Stopwatch();\nstopwatch.Start();\n// \u9700\u8981\u6267\u884c\u4e09\u6b21,\u4e0d\u4f1a\u56e0\u4e3aef\u6709\u5b9a\u4e49\u5173\u7cfb\u800c\u4e00\u6b21\u6027\u63d2\u5165posts\u548ccomments\nawait _blogRepository.InsertManyAsync(blogs);\nawait _postRepository.InsertManyAsync(posts);\nawait _commentRepository.InsertManyAsync(comments);\nstopwatch.Stop();\nLogger.LogInformation($\"\u6279\u91cf\u63d2\u5165blogs:{blogs.Count},posts:{posts.Count},comments:{comments.Count}\u6761,\u8017\u65f6(\u5355\u4f4d:\u6beb\u79d2):{stopwatch.ElapsedMilliseconds}\");\n}\n/// <summary>\n/// \u6279\u91cf\u63d2\u516510000\u6761\u6570\u636e,\u5e76\u4e14\u6d4b\u8bd5\u4e8b\u52a1\u662f\u5426\u548c\u5176\u5b83\u4e1a\u52a1\u903b\u8f91\u4fdd\u6301\u4e00\u81f4\n/// \u6d4b\u8bd5\u7ed3\u679c\uff1a\u5728\u4e00\u4e2a\u4e8b\u52a1\u5185\n/// </summary>\npublic async Task CreateTransactionAsync(int qty = 10)\n{\nvar list = GenFu.GenFu.ListOf<Blog>(qty);\nvar stopwatch = new Stopwatch();\nstopwatch.Start();\nawait _blogRepository.InsertManyAsync(list);\nstopwatch.Stop();\nLogger.LogInformation($\"\u6279\u91cf\u63d2\u5165{list.Count}\u6761,\u8017\u65f6(\u5355\u4f4d:\u6beb\u79d2):{stopwatch.ElapsedMilliseconds}\");\nawait _identityRoleManager.CreateAsync(new IdentityRole(GuidGenerator.Create(), GuidGenerator.Create().ToString()));\nthrow new UserFriendlyException(\"test\");\n}\n/// <summary>\n/// \u6279\u91cf\u66f4\u65b0\n/// <see cref=\"https://learn.microsoft.com/zh-cn/ef/core/saving/execute-insert-update-delete\"/>\n/// </summary>\npublic async Task BatchUpdateAsync(int qty = 10000)\n{\nusing (var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions(true), true))\n{\nvar list = GenFu.GenFu.ListOf<Blog>(qty);\nawait _blogRepository.InsertManyAsync(list);\nawait uow.CompleteAsync();\n}\nvar stopwatch = new Stopwatch();\nstopwatch.Start();\nvar dbSet = await _blogRepository.GetDbSetAsync();\nawait dbSet.ExecuteUpdateAsync(setters => setters\n.SetProperty(x => x.IsDeleted, x => true)\n.SetProperty(x => x.Name, x => \"test\"));\nstopwatch.Stop();\nLogger.LogInformation($\"\u6279\u91cf\u66f4\u65b0{qty}\u6761,\u8017\u65f6(\u5355\u4f4d:\u6beb\u79d2):{stopwatch.ElapsedMilliseconds}\");\n}\n/// <summary>\n/// \u6279\u91cf\u5220\u9664\n/// <see cref=\"https://learn.microsoft.com/zh-cn/ef/core/saving/execute-insert-update-delete\"/>\n/// </summary>\npublic async Task BatchDeleteAsync(int qty = 10000)\n{\nusing (var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions(true), true))\n{\nvar list = GenFu.GenFu.ListOf<Blog>(qty);\nawait _blogRepository.InsertManyAsync(list);\nawait uow.CompleteAsync();\n}\nvar stopwatch = new Stopwatch();\nstopwatch.Start();\nvar dbSet = await _blogRepository.GetDbSetAsync();\nawait dbSet.ExecuteDeleteAsync();\nstopwatch.Stop();\nLogger.LogInformation($\"\u6279\u91cf\u5220\u9664{qty}\u6761,\u8017\u65f6(\u5355\u4f4d:\u6beb\u79d2):{stopwatch.ElapsedMilliseconds}\");\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/cache/","title":"\u7f13\u5b58","text":"

ABP \u6846\u67b6\u6269\u5c55\u4e86 ASP.NET Core \u7684\u5206\u5e03\u5f0f\u7f13\u5b58\u7cfb\u7edf. ABP vNext Pro \u5df2\u96c6\u6210 Redis \u505a\u4e3a\u7f13\u5b58\u3002

"},{"location":"user-guide/zh/infrastructure/cache/#_2","title":"\u914d\u7f6e","text":"appsetting.json
\"Redis\":\n{\n\"Configuration\": \"localhost,password=1q2w3E*,defaultdatabase=1\"\n}\n
"},{"location":"user-guide/zh/infrastructure/cache/#abpdistributedcacheoptions","title":"AbpDistributedCacheOptions","text":"

\u793a\u4f8b\uff1a\u4e3a\u5e94\u7528\u7a0b\u5e8f\u8bbe\u7f6e\u7f13\u5b58\u952e\u524d\u7f00

AbpProHttpApiHostModule.cs
Configure<AbpDistributedCacheOptions>(options =>\n{\noptions.KeyPrefix = \"MyApp1\";\n});\n
"},{"location":"user-guide/zh/infrastructure/cache/#_3","title":"\u53ef\u7528\u9009\u9879","text":"
  • HideErrors (bool, \u9ed8\u8ba4: true): \u542f\u7528/\u7981\u7528\u9690\u85cf\u4ece\u7f13\u5b58\u670d\u52a1\u5668\u5199\u5165/\u8bfb\u53d6\u503c\u65f6\u7684\u9519\u8bef.
  • KeyPrefix (string, \u9ed8\u8ba4: null): \u5982\u679c\u4f60\u7684\u7f13\u5b58\u670d\u52a1\u5668\u7531\u591a\u4e2a\u5e94\u7528\u7a0b\u5e8f\u5171\u540c\u4f7f\u7528, \u5219\u53ef\u4ee5\u4e3a\u5e94\u7528\u7a0b\u5e8f\u7684\u7f13\u5b58\u952e\u8bbe\u7f6e\u4e00\u4e2a\u524d\u7f00. \u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b, \u4e0d\u540c\u7684\u5e94\u7528\u7a0b\u5e8f\u4e0d\u80fd\u8986\u76d6\u5f7c\u6b64\u7684\u7f13\u5b58\u5185\u5bb9.
  • GlobalCacheEntryOptions (DistributedCacheEntryOptions): \u7528\u4e8e\u8bbe\u7f6e\u4fdd\u5b58\u7f13\u5185\u5bb9\u5374\u6ca1\u6709\u6307\u5b9a\u9009\u9879\u65f6, \u9ed8\u8ba4\u7684\u5206\u5e03\u5f0f\u7f13\u5b58\u9009\u9879 (\u4f8b\u5982 AbsoluteExpiration \u548c SlidingExpiration). SlidingExpiration\u7684\u9ed8\u8ba4\u503c\u8bbe\u7f6e\u4e3a 20 \u5206\u949f.
"},{"location":"user-guide/zh/infrastructure/cache/#_4","title":"\u4f7f\u7528\u65b9\u5f0f","text":"

\u793a\u4f8b: \u5728\u7f13\u5b58\u4e2d\u5b58\u50a8\u56fe\u4e66\u540d\u79f0\u548c\u4ef7\u683c

C#
namespace MyProject\n{\npublic class BookCacheItem\n{\npublic string Name { get; set; }\npublic float Price { get; set; }\n}\n}\n

\u4f60\u53ef\u4ee5\u6ce8\u5165 IDistributedCache<BookCacheItem> \u670d\u52a1\u7528\u4e8e get/set BookCacheItem \u5bf9\u8c61.

C#
using System;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Caching.Distributed;\nusing Volo.Abp.Caching;\nusing Volo.Abp.DependencyInjection;\nnamespace MyProject\n{\npublic class BookService : ITransientDependency\n{\nprivate readonly IDistributedCache<BookCacheItem> _cache;\npublic BookService(IDistributedCache<BookCacheItem> cache)\n{\n_cache = cache;\n}\npublic async Task<BookCacheItem> GetAsync(Guid bookId)\n{\nreturn await _cache.GetOrAddAsync(\nbookId.ToString(), //\u7f13\u5b58\u952e\nasync () => await GetBookFromDatabaseAsync(bookId),\n() => new DistributedCacheEntryOptions\n{\nAbsoluteExpiration = DateTimeOffset.Now.AddHours(1)\n}\n);\n}\nprivate Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)\n{\n//TODO: \u4ece\u6570\u636e\u5e93\u83b7\u53d6\u6570\u636e\n}\n}\n}\n
  • \u793a\u4f8b\u670d\u52a1\u4ee3\u7801\u4e2d\u7684 GetOrAddAsync() \u65b9\u6cd5\u4ece\u7f13\u5b58\u4e2d\u83b7\u53d6\u56fe\u4e66\u9879. GetOrAddAsync\u662f ABP \u6846\u67b6\u5728 ASP.NET Core \u5206\u5e03\u5f0f\u7f13\u5b58\u65b9\u6cd5\u4e2d\u6dfb\u589e\u7684\u9644\u52a0\u65b9\u6cd5.
  • \u5982\u679c\u6ca1\u6709\u5728\u7f13\u5b58\u4e2d\u627e\u5230\u56fe\u4e66,\u5b83\u4f1a\u8c03\u7528\u5de5\u5382\u65b9\u6cd5 (\u672c\u793a\u4f8b\u4e2d\u662f GetBookFromDatabaseAsync)\u4ece\u539f\u59cb\u6570\u636e\u6e90\u4e2d\u83b7\u53d6\u56fe\u4e66\u9879.
  • GetOrAddAsync \u6709\u4e00\u4e2a\u53ef\u9009\u53c2\u6570 DistributedCacheEntryOptions , \u53ef\u7528\u4e8e\u8bbe\u7f6e\u7f13\u5b58\u7684\u751f\u547d\u5468\u671f.
"},{"location":"user-guide/zh/infrastructure/cache/#_5","title":"\u6279\u91cf\u64cd\u4f5c","text":"

ABP \u7684\u5206\u5e03\u5f0f\u7f13\u5b58\u63a5\u53e3\u5b9a\u4e49\u4e86\u4ee5\u4e0b\u6279\u91cf\u64cd\u4f5c\u65b9\u6cd5,\u5f53\u4f60\u9700\u8981\u5728\u4e00\u4e2a\u65b9\u6cd5\u4e2d\u8c03\u7528\u591a\u6b21\u7f13\u5b58\u64cd\u4f5c\u65f6,\u8fd9\u4e9b\u65b9\u6cd5\u53ef\u4ee5\u63d0\u9ad8\u6027\u80fd

  • SetManyAsync \u548c SetMany \u65b9\u6cd5\u53ef\u4ee5\u7528\u6765\u5411\u7f13\u5b58\u4e2d\u8bbe\u7f6e\u591a\u4e2a\u503c.
  • GetManyAsync \u548c GetMany \u65b9\u6cd5\u53ef\u4ee5\u7528\u6765\u4ece\u7f13\u5b58\u4e2d\u83b7\u53d6\u591a\u4e2a\u503c.
  • GetOrAddManyAsync \u548c GetOrAddMany \u65b9\u6cd5\u53ef\u4ee5\u7528\u6765\u4ece\u7f13\u5b58\u4e2d\u83b7\u53d6\u5e76\u6dfb\u52a0\u7f3a\u5c11\u7684\u503c.
  • RefreshManyAsync \u548c RefreshMany \u65b9\u6cd5\u53ef\u4ee5\u6765\u7528\u91cd\u7f6e\u591a\u4e2a\u503c\u7684\u6eda\u52a8\u8fc7\u671f\u65f6\u95f4.
  • RemoveManyAsync \u548c RemoveMany \u65b9\u6cd5\u53ef\u4ee5\u7528\u6765\u4ece\u7f13\u5b58\u4e2d\u5220\u9664\u591a\u4e2a\u503c.

\u8fd9\u4e9b\u4e0d\u662f\u6807\u51c6\u7684 ASP.NET Core \u7f13\u5b58\u65b9\u6cd5, \u6240\u4ee5\u67d0\u4e9b\u63d0\u4f9b\u7a0b\u5e8f\u53ef\u80fd\u4e0d\u652f\u6301. [ABP Redis \u96c6\u6210\u5305]\u5b9e\u73b0\u4e86\u5b83\u4eec. \u5982\u679c\u63d0\u4f9b\u7a0b\u5e8f\u4e0d\u652f\u6301,\u4f1a\u56de\u9000\u5230 SetAsync \u548c GetAsync ... \u65b9\u6cd5(\u5faa\u73af\u8c03\u7528).

"},{"location":"user-guide/zh/infrastructure/cap/","title":"dotnetcore.cap","text":"

\u5206\u5e03\u5f0f\u4e8b\u4ef6\u603b\u7ebf\u7cfb\u7edf\u5141\u8bb8\u53d1\u5e03\u548c\u8ba2\u9605\u8de8\u5e94\u7528/\u670d\u52a1\u8fb9\u754c\u4f20\u8f93\u7684\u4e8b\u4ef6. \u4f60\u53ef\u4ee5\u4f7f\u7528\u5206\u5e03\u5f0f\u4e8b\u4ef6\u603b\u7ebf\u5728\u5fae\u670d\u52a1\u6216\u5e94\u7528\u7a0b\u5e8f\u4e4b\u95f4\u5f02\u6b65\u53d1\u9001\u548c\u63a5\u6536\u6d88\u606f.

"},{"location":"user-guide/zh/infrastructure/cap/#_1","title":"\u5b89\u88c5","text":"
  • \u6dfb\u52a0\u4ee5\u4e0b NuGet \u5305\u5230\u4f60\u7684\u9879\u76ee
  • Lion.AbpPro.CAP
  • Lion.AbpPro.CAP.EntityFrameworkCore
  • DotNetCore.CAP.MySql (\u5982\u679c\u662f\u5176\u5b83\u6570\u636e\u5e93,\u8bf7\u5b89\u88c5\u5bf9\u5e94\u7c7b\u578b)
  • DotNetCore.CAP.RabbitMQ (\u5982\u679c\u662f\u5176\u5b83\u4e2d\u95f4\u4ef6,\u8bf7\u5b89\u88c5\u5bf9\u5e94\u7c7b\u578b)
  • \u6dfb\u52a0 [DependsOn(typeof(AbpProCapEntityFrameworkCoreModule))] \u5230\u4f60\u7684\u9879\u76ee\u6a21\u5757\u7c7b.
"},{"location":"user-guide/zh/infrastructure/cap/#_2","title":"\u914d\u7f6e","text":"

Mysql,\u548c RabbitMq \u4e3a\u4f8b

C#
public override void ConfigureServices(ServiceConfigurationContext context)\n{\ncontext.AddAbpCap(capOptions =>\n{\n// \u6307\u5b9a\u6570\u636e\u6570\u636e\u5e93\u8fde\u63a5\u5b57\u7b26\u4e32\ncapOptions.SetCapDbConnectionString(configuration[\"ConnectionStrings:Default\"]);\ncapOptions.UseEntityFramework<AbpProDbContext>();\n// \u4f7f\u7528rabbitmq,\u914d\u7f6ehost,username,password\ncapOptions.UseRabbitMQ(option =>\n{\noption.HostName = configuration.GetValue<string>(\"Cap:RabbitMq:HostName\");\noption.UserName = configuration.GetValue<string>(\"Cap:RabbitMq:UserName\");\noption.Password = configuration.GetValue<string>(\"Cap:RabbitMq:Password\");\n});\nvar hostingEnvironment = context.Services.GetHostingEnvironment();\nbool auth = !hostingEnvironment.IsDevelopment();\n// \u542f\u7528\u9762\u677f\ncapOptions.UseDashboard(options =>\n{\noptions.UseAuth = auth;\noptions.AuthorizationPolicy = LionAbpProCapPermissions.CapManagement.Cap;\n});\n});\n}\n
  • \u5373\u53ef\u4f7f\u7528 Abp vNext \u6807\u51c6\u5199\u6cd5,\u53d1\u9001\u5206\u5e03\u5f0f\u4e8b\u4ef6\u548c\u8ba2\u9605\u4e8b\u4ef6
"},{"location":"user-guide/zh/infrastructure/config/","title":"\u914d\u7f6e","text":""},{"location":"user-guide/zh/infrastructure/config/#_2","title":"\u65e5\u5fd7","text":""},{"location":"user-guide/zh/infrastructure/config/#_3","title":"\u65e5\u5fd7\u7ea7\u522b","text":"

Debug \u2192 Information \u2192 Warning \u2192 Error \u2192 Fatal

JSON
\"Serilog\": {\n\"Using\": [\n\"Serilog.Sinks.Console\",\n\"Serilog.Sinks.File\"\n],\n\"MinimumLevel\": {\n// \u9ed8\u8ba4\u5168\u5c40\u65e5\u5fd7\u7ea7\u522b\n\"Default\": \"Information\",\n\"Override\": {\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Microsoft \u65e5\u5fd7\u7ea7\u522b\n\"Microsoft\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Volo.Abp \u65e5\u5fd7\u7ea7\u522b\n\"Volo.Abp\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Hangfire \u65e5\u5fd7\u7ea7\u522b\n\"Hangfire\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a DotNetCore.CAP \u65e5\u5fd7\u7ea7\u522b\n\"DotNetCore.CAP\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Serilog.AspNetCore \u65e5\u5fd7\u7ea7\u522b\n\"Serilog.AspNetCore\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Microsoft.EntityFrameworkCore \u65e5\u5fd7\u7ea7\u522b\n\"Microsoft.EntityFrameworkCore\": \"Warning\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Microsoft.AspNetCore \u65e5\u5fd7\u7ea7\u522b\n\"Microsoft.AspNetCore\": \"Information\"\n}\n},\n\"WriteTo\": [\n{\n// \u8f93\u51fa\u5230\u63a7\u5236\u53f0\u65e5\u5fd7\n\"Name\": \"Console\"\n},\n{\n// \u8f93\u51fa\u5230\u6587\u4ef6\n\"Name\": \"File\",\n\"Args\": {\n\"path\": \"logs/logs-.txt\",\n// \u6309\u5929\u8f93\u51fa\n\"rollingInterval\": \"Day\"\n}\n}\n]\n}\n
"},{"location":"user-guide/zh/infrastructure/config/#es","title":"\u5199\u5165 ES","text":"

\u5148\u51b3\u6761\u4ef6\uff1a\u642d\u5efa\u597d ES \u73af\u5883

  • Enabled:\u662f\u5426\u542f\u7528
  • Url:es \u5730\u5740
  • IndexFormat:es \u7d22\u5f15
  • UserName:\u7528\u6237\u540d
  • Password:\u5bc6\u7801
  • SearchIndexFormat:es \u65e5\u5fd7\u67e5\u8be2\u7d22\u5f15\u6a21\u5f0f
JSON
\"ElasticSearch\": {\n\"Enabled\": \"false\",\n\"Url\": \"http://es.cn\",\n\"IndexFormat\": \"Lion.AbpPro.development.{0:yyyy.MM.dd}\",\n\"UserName\": \"elastic\",\n\"Password\": \"aVVhjQ95RP7nbwNy\",\n\"SearchIndexFormat\": \"Lion.AbpPro.development*\"\n},\n
  • \u67e5\u770b Lion.AbpPro.HttpApi.Host.Program.cs
C#
public class Program\n{\npublic static void Main(string[] args)\n{\nCreateHostBuilder(args).Build().Run();\n}\nprivate static IHostBuilder CreateHostBuilder(string[] args) =>\nHost.CreateDefaultBuilder(args)\n.ConfigureWebHostDefaults(webBuilder =>\n{\nwebBuilder.ConfigureKestrel((context, options) => { options.Limits.MaxRequestBodySize = 1024 * 50; });\nwebBuilder.UseStartup<Startup>();\n})\n.UseSerilog((context, loggerConfiguration) =>\n{\n// \u914d\u7f6eES\u65e5\u5fd7\nSerilogToEsExtensions.SetSerilogConfiguration(\nloggerConfiguration,\ncontext.Configuration);\n}).UseAutofac();\n}\n
"},{"location":"user-guide/zh/infrastructure/config/#cors","title":"\u8de8\u57df(CORS)","text":"
  • \u5141\u8bb8\u6307\u5b9a\u7b56\u7565
JSON
\"App\": {\n// \u9017\u53f7\u5206\u9694\n\"CorsOrigins\": \"http://*.com,http://localhost:4200\"\n},\n
  • \u914d\u7f6e\u8de8\u57df
C#
private void ConfigureCors(ServiceConfigurationContext context)\n{\nvar configuration = context.Services.GetConfiguration();\ncontext.Services.AddCors(options =>\n{\noptions.AddPolicy(DefaultCorsPolicyName, builder =>\n{\nbuilder\n.WithOrigins(\nconfiguration[\"App:CorsOrigins\"]\n.Split(\",\", StringSplitOptions.RemoveEmptyEntries)\n.Select(o => o.RemovePostFix(\"/\"))\n.ToArray()\n)\n.WithAbpExposedHeaders()\n.SetIsOriginAllowedToAllowWildcardSubdomains()\n.AllowAnyHeader()\n.AllowAnyMethod()\n.AllowCredentials();\n});\n});\n}\n
"},{"location":"user-guide/zh/infrastructure/config/#accesstoken","title":"AccessToken","text":"
  • Audience:\u63a5\u6536\u5bf9\u8c61
  • Issuer:\u7b7e\u53d1\u4e3b\u4f53
  • SecurityKey:\u5bc6\u94a5
  • ExpirationTime:\u8fc7\u671f\u65f6\u95f4(\u5355\u4f4d\u5c0f\u65f6)
JSON
  \"Jwt\": {\n\"Audience\": \"Lion.AbpPro\",\n\"SecurityKey\": \"dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=\",\n\"Issuer\": \"Lion.AbpPro\",\n\"ExpirationTime\": 30\n}\n
"},{"location":"user-guide/zh/infrastructure/elastic/","title":"ElasticSearch","text":""},{"location":"user-guide/zh/infrastructure/elastic/#_1","title":"\u5b89\u88c5","text":"
  • \u6dfb\u52a0\u4ee5\u4e0b NuGet \u5305\u5230\u4f60\u7684\u9879\u76ee
  • Lion.AbpPro.ElasticSearch
  • \u6dfb\u52a0 [DependsOn(typeof(AbpProElasticSearchModule))] \u5230\u4f60\u7684\u9879\u76ee\u6a21\u5757\u7c7b.
"},{"location":"user-guide/zh/infrastructure/elastic/#_2","title":"\u914d\u7f6e","text":"JSON
{\n\"ElasticSearch\": {\n\"Host\": \"http://localhost:9200\",\n\"UserName\": \"admin\",\n\"Password\": \"1q2w3E*\"\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/elastic/#_3","title":"\u793a\u4f8b","text":"
  • \u5b9e\u73b0 Student \u7684\u589e\u5220\u67e5\u6539
"},{"location":"user-guide/zh/infrastructure/elastic/#_4","title":"\u5b9a\u4e49\u5b66\u751f\u7c7b","text":"
  • \u5b9e\u73b0 IElasticSearchEntity \u63a5\u53e3,\u6b64\u63a5\u53e3\u6709\u4e3b\u952e Id,\u521b\u5efa\u65f6\u95f4\u5b57\u6bb5,\u4e5f\u662f\u6cdb\u578b\u7ea6\u675f\u3002
C#
public class Student : IElasticSearchEntity\n{\npublic Guid Id { get; set; }\npublic DateTime CreationTime { get; set; }\npublic double Price { get; set; }\npublic string Name { get; set; }\npublic int Age { get; set; }\n}\n
"},{"location":"user-guide/zh/infrastructure/elastic/#_5","title":"\u5b9a\u4e49\u63a5\u53e3","text":"C#
public interface IStudentElasticSearchRepository : IBasicElasticSearchRepository<Student>\n{\n}\n
"},{"location":"user-guide/zh/infrastructure/elastic/#_6","title":"\u5b9e\u73b0\u63a5\u53e3","text":"C#
public class StudentElasticSearchRepository : ElasticSearchRepository<Student>, IStudentElasticSearchRepository, ITransientDependency\n{\npublic StudentElasticSearchRepository(IElasticsearchProvider elasticsearchProvider) : base(elasticsearchProvider)\n{\n}\n// index \u53ea\u80fd\u662f\u5c0f\u5199\nprotected override string IndexName => \"Students\".ToLower();\n}\n
"},{"location":"user-guide/zh/infrastructure/elastic/#curd","title":"CURD","text":"C#
// \u6ce8\u5165\nprivate readonly IStudentElasticSearchRepository _studentElasticSearchRepository;\npublic StudentElasticSearchRepositoryTests(IStudentElasticSearchRepository studentElasticSearchRepository)\n{\n_studentElasticSearchRepository = studentElasticSearchRepository;\n}\n// var student = new Student\n//          {\n//              Id = Guid.NewGuid(),\n//              Name = \"\u97e9\u7acb\",\n//              Age = 10,\n//              CreationTime = DateTime.Now,\n//              Price = 100.3,\n//          };\n// \u6839\u636e\u4e3b\u952eId\u67e5\u8be2\nvar result = await _studentElasticSearchRepository.FindAsync(student.Id);\n// \u65b0\u589e\nawait _studentElasticSearchRepository.InsertAsync(student);\n// \u6279\u91cf\u65b0\u589e\nawait _studentElasticSearchRepository.InsertManyAsync(students);\n// \u66f4\u65b0\nawait _studentElasticSearchRepository.UpdateAsync(student);\n// \u5220\u9664\nawait _studentElasticSearchRepository.DeleteAsync(student.Id);\n// \u5206\u9875\u67e5\u8be2\nvar mustFilters = new List<Func<QueryContainerDescriptor<Student>, QueryContainer>>();\nmustFilters.Add(e => e.Term(f => f.Field(b => b.Name.Suffix(\"keyword\")).Value(\"\u97e9\u7acb\")));\nvar result = await _studentElasticSearchRepository.PageAsync(mustFilters);\n
"},{"location":"user-guide/zh/infrastructure/elastic/#_7","title":"\u5176\u5b83\u64cd\u4f5c","text":"
  • \u5728 StudentElasticSearchRepository \u4e2d\u4f7f\u7528 Client \u5373\u53ef\u83b7\u53d6\u5230 es \u539f\u751f api \u5bf9\u8c61\u3002
  • \u66f4\u591a\u793a\u4f8b\u8bf7\u67e5\u770b\u5355\u5143\u6d4b\u8bd5(StudentElasticSearchRepositoryTests.cs)
"},{"location":"user-guide/zh/infrastructure/exception-handler/","title":"\u5f02\u5e38\u5904\u7406","text":"

ABP \u63d0\u4f9b\u4e86\u7528\u4e8e\u5904\u7406 Web \u5e94\u7528\u7a0b\u5e8f\u5f02\u5e38\u7684\u6807\u51c6\u6a21\u578b.

  • \u81ea\u52a8\u5904\u7406\u6240\u6709\u5f02\u5e38.\u5982\u679c\u662f API/AJAX \u8bf7\u6c42,\u4f1a\u5411\u5ba2\u6237\u7aef\u8fd4\u56de\u4e00\u4e2a\u6807\u51c6\u683c\u5f0f\u5316\u540e\u7684\u9519\u8bef\u6d88\u606f .
  • \u81ea\u52a8\u9690\u85cf\u5185\u90e8\u8be6\u7ec6\u9519\u8bef\u5e76\u8fd4\u56de\u6807\u51c6\u9519\u8bef\u6d88\u606f.
  • \u4e3a\u5f02\u5e38\u6d88\u606f\u7684\u672c\u5730\u5316\u63d0\u4f9b\u4e00\u79cd\u53ef\u914d\u7f6e\u7684\u65b9\u5f0f.
  • \u81ea\u52a8\u4e3a\u6807\u51c6\u5f02\u5e38\u8bbe\u7f6e*HTTP \u72b6\u6001\u4ee3\u7801,\u5e76\u63d0\u4f9b\u53ef\u914d\u7f6e\u9009\u9879,\u4ee5\u6620\u5c04\u81ea\u5b9a\u4e49\u5f02\u5e38.
"},{"location":"user-guide/zh/infrastructure/exception-handler/#_2","title":"\u81ea\u52a8\u5904\u7406\u5f02\u5e38","text":"

\u5f53\u6ee1\u8db3\u4e0b\u9762\u4efb\u610f\u4e00\u4e2a\u6761\u4ef6\u65f6,AbpExceptionFilter \u4f1a\u5904\u7406\u6b64\u5f02\u5e38:

  • \u5f53 controller action \u65b9\u6cd5\u8fd4\u56de\u7c7b\u578b\u662f object result(\u800c\u4e0d\u662f view result)\u5e76\u6709\u5f02\u5e38\u629b\u51fa\u65f6.
  • \u5f53\u4e00\u4e2a\u8bf7\u6c42\u4e3a AJAX(Http \u8bf7\u6c42\u5934\u4e2dX-Requested-With\u4e3aXMLHttpRequest)\u65f6.
  • \u5f53\u5ba2\u6237\u7aef\u63a5\u53d7\u7684\u8fd4\u56de\u7c7b\u578b\u4e3aapplication/json(Http \u8bf7\u6c42\u5934\u4e2daccept \u4e3aapplication/json)\u65f6.
"},{"location":"user-guide/zh/infrastructure/exception-handler/#_3","title":"\u9519\u8bef\u6d88\u606f\u683c\u5f0f","text":"JSON
{\n\"error\": {\n\"code\": \"App:010042\",\n\"message\": \"This topic is locked and can not add a new message\",\n\"details\": \"A more detailed info about the error...\"\n}\n}\n
  • \u9519\u8bef\u4ee3\u7801(code)\u662f\u5f02\u5e38\u4fe1\u606f\u4e2d\u4e00\u4e2a\u6709\u552f\u4e00\u503c\u5e76\u53ef\u9009\u7684\u5b57\u7b26\u4e32\u503c.\u629b\u51fa\u7684\u5f02\u5e38\u5e94\u5b9e\u73b0IHasErrorCode \u63a5\u53e3\u6765\u586b\u5145\u8be5\u5b57\u6bb5.
  • \u9519\u8bef\u7684\u8be6\u7ec6\u4fe1\u606f(Details) \u662f\u53ef\u9009\u5c5e\u6027.\u629b\u51fa\u7684\u5f02\u5e38\u5e94\u5b9e\u73b0IHasErrorDetails \u63a5\u53e3\u6765\u586b\u5145\u8be5\u5b57\u6bb5.
"},{"location":"user-guide/zh/infrastructure/exception-handler/#_4","title":"\u9a8c\u8bc1\u9519\u8bef","text":"

\u5f53\u629b\u51fa\u7684\u5f02\u5e38\u5b9e\u73b0IHasValidationErrors \u63a5\u53e3\u65f6,validationErrors \u662f\u4e00\u4e2a\u53ef\u88ab\u586b\u5145\u7684\u6807\u51c6\u5b57\u6bb5.\u793a\u4f8b JSON \u5982\u4e0b:

JSON
{\n\"error\": {\n\"code\": \"App:010046\",\n\"message\": \"Your request is not valid, please correct and try again!\",\n\"validationErrors\": [\n{\n\"message\": \"Username should be minimum length of 3.\",\n\"members\": [\"userName\"]\n},\n{\n\"message\": \"Password is required\",\n\"members\": [\"password\"]\n}\n]\n}\n}\n

AbpValidationException\u5df2\u7ecf\u5b9e\u73b0\u4e86IHasValidationErrors\u63a5\u53e3,\u5f53\u8bf7\u6c42\u8f93\u5165\u65e0\u6548\u65f6,\u6846\u67b6\u4f1a\u81ea\u52a8\u629b\u51fa\u6b64\u9519\u8bef. \u56e0\u6b64,\u9664\u975e\u4f60\u6709\u81ea\u5b9a\u4e49\u7684\u9a8c\u8bc1\u903b\u8f91,\u5426\u5219\u4e0d\u9700\u8981\u5904\u7406\u9a8c\u8bc1\u9519\u8bef.

"},{"location":"user-guide/zh/infrastructure/exception-handler/#_5","title":"\u4e1a\u52a1\u5f02\u5e38","text":"

\u5927\u591a\u6570\u5f02\u5e38\u90fd\u662f\u4e1a\u52a1\u5f02\u5e38.\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528IBusinessException \u63a5\u53e3\u6765\u6807\u8bb0\u5f02\u5e38\u4e3a\u4e1a\u52a1\u5f02\u5e38.

BusinessException \u9664\u4e86\u5b9e\u73b0IHasErrorCode,IHasErrorDetails ,IHasLogLevel \u63a5\u53e3\u5916,\u8fd8\u5b9e\u73b0\u4e86IBusinessException \u63a5\u53e3.\u5176\u9ed8\u8ba4\u65e5\u5fd7\u7ea7\u522b\u4e3aWarning.

\u901a\u5e38\u4f60\u4f1a\u5c06\u4e00\u4e2a\u9519\u8bef\u4ee3\u7801\u5173\u8054\u81f3\u7279\u5b9a\u7684\u4e1a\u52a1\u5f02\u5e38.\u4f8b\u5982:

C#
throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer);\n

QaErrorCodes.CanNotVoteYourOwnAnswer \u662f\u4e00\u4e2a\u5b57\u7b26\u4e32\u5e38\u91cf. \u5efa\u8bae\u4f7f\u7528\u4e0b\u9762\u7684\u9519\u8bef\u4ee3\u7801\u683c\u5f0f:

Text Only
<code-namespace>:<error-code>\n

code-namespace,\u5e94\u5728\u6307\u5b9a\u7684\u6a21\u5757/\u5e94\u7528\u5c42\u4e2d\u4fdd\u8bc1\u5176\u552f\u4e00.\u4f8b\u5982:

Text Only
Volo.Qa:010002\n

Volo.Qa\u5728\u8fd9\u662f\u4f5c\u4e3acode-namespace. code-namespace \u540c\u6837\u53ef\u4ee5\u5728 \u672c\u5730\u5316 \u5f02\u5e38\u4fe1\u606f\u65f6\u4f7f\u7528.

  • \u4f60\u53ef\u4ee5\u76f4\u63a5\u629b\u51fa\u4e00\u4e2a BusinessException \u5f02\u5e38,\u6216\u8005\u9700\u8981\u65f6\u53ef\u4ee5\u4ece\u8be5\u7c7b\u6d3e\u751f\u4f60\u81ea\u5df1\u7684 Exception \u7c7b\u578b.
  • \u5bf9\u4e8eBusinessException \u7c7b\u578b,\u5176\u6240\u6709\u5c5e\u6027\u90fd\u662f\u53ef\u9009\u7684.\u4f46\u662f\u901a\u5e38\u4f1a\u8bbe\u7f6eErrorCode\u6216Message\u5c5e\u6027.
"},{"location":"user-guide/zh/infrastructure/exception-handler/#_6","title":"\u4f7f\u7528\u9519\u8bef\u4ee3\u7801","text":"

\u901a\u8fc7\u4f7f\u7528\u9519\u8bef\u4ee3\u7801\u7684\u65b9\u5f0f\u6765\u5904\u7406\u672c\u5730\u5316,\u800c\u4e0d\u662f\u5728\u629b\u51fa\u5f02\u5e38\u7684\u65f6\u5019.

\u9996\u5148,\u5728\u6a21\u5757\u914d\u7f6e\u4ee3\u7801\u4e2d\u5c06 code-namespace \u6620\u5c04\u81f3 \u672c\u5730\u5316\u8d44\u6e90:

C#
services.Configure<AbpExceptionLocalizationOptions>(options =>\n{\noptions.MapCodeNamespace(\"Volo.Qa\", typeof(QaResource));\n});\n

\u7136\u540eVolo.Qa\u547d\u540d\u7a7a\u95f4\u4e0b\u7684\u6240\u6709\u5f02\u5e38\u90fd\u5c06\u88ab\u5bf9\u5e94\u7684\u672c\u5730\u5316\u8d44\u6e90\u8fdb\u884c\u672c\u5730\u5316\u5904\u7406. \u672c\u5730\u5316\u8d44\u6e90\u4e2d\u5e94\u5305\u542b\u5bf9\u5e94\u9519\u8bef\u4ee3\u7801\u7684\u6587\u672c. \u4f8b\u5982:

JSON
{\n\"culture\": \"en\",\n\"texts\": {\n\"Volo.Qa:010002\": \"You can not vote your own answer!\"\n}\n}\n

\u6700\u540e\u5c31\u53ef\u4ee5\u629b\u51fa\u4e00\u4e2a\u5305\u542b\u9519\u8bef\u4ee3\u7801\u7684\u4e1a\u52a1\u5f02\u5e38\u4e86:

C#
throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);\n
  • \u629b\u51fa\u6240\u6709\u5b9e\u73b0IHasErrorCode \u63a5\u53e3\u7684\u5f02\u5e38\u90fd\u5177\u6709\u76f8\u540c\u7684\u884c\u4e3a.\u56e0\u6b64,\u5bf9\u9519\u8bef\u4ee3\u7801\u7684\u672c\u5730\u5316,\u5e76\u4e0d\u662fBusinessException\u7c7b\u6240\u7279\u6709\u7684.
  • \u4e3a\u9519\u8bef\u6d88\u606f\u5b9a\u4e49\u672c\u5730\u5316\u6587\u672c\u5e76\u4e0d\u662f\u5fc5\u987b\u7684. \u5982\u679c\u672a\u5b9a\u4e49,ABP \u4f1a\u5c06\u9ed8\u8ba4\u7684\u9519\u8bef\u6d88\u606f\u53d1\u9001\u7ed9\u5ba2\u6237\u7aef. \u800c\u4e0d\u4f7f\u7528\u5f02\u5e38\u7684Message\u5c5e\u6027. \u5982\u679c\u4f60\u60f3\u8981\u53d1\u9001\u5f02\u5e38\u7684Message,\u4f7f\u7528UserFriendlyException(\u6216\u4f7f\u7528\u5b9e\u73b0IUserFriendlyException\u63a5\u53e3\u7684\u5f02\u5e38\u7c7b\u578b)
"},{"location":"user-guide/zh/infrastructure/exception-handler/#_7","title":"\u4f7f\u7528\u6d88\u606f\u7684\u683c\u5f0f\u5316\u53c2\u6570","text":"

\u5982\u679c\u6709\u53c2\u6570\u5316\u7684\u9519\u8bef\u6d88\u606f,\u5219\u53ef\u4ee5\u4f7f\u7528\u5f02\u5e38\u7684Data\u5c5e\u6027\u8fdb\u884c\u8bbe\u7f6e.\u4f8b\u5982:

C#
throw new BusinessException(\"App:010046\")\n{\nData =\n{\n{\"UserName\", \"john\"}\n}\n};\n

\u53e6\u5916\u6709\u4e00\u79cd\u66f4\u4e3a\u5feb\u6377\u7684\u65b9\u5f0f:

C#
throw new BusinessException(\"App:010046\")\n.WithData(\"UserName\", \"john\");\n

\u4e0b\u9762\u5c31\u662f\u4e00\u4e2a\u5305\u542bUserName \u53c2\u6570\u7684\u9519\u8bef\u6d88\u606f:

JSON
{\n\"culture\": \"en\",\n\"texts\": {\n\"App:010046\": \"Username should be unique. '{UserName}' is already taken!\"\n}\n}\n
  • WithData \u652f\u6301\u6709\u591a\u4e2a\u53c2\u6570\u7684\u94fe\u5f0f\u8c03\u7528 (\u5982.WithData(...).WithData(...)).
"},{"location":"user-guide/zh/infrastructure/exception-handler/#http","title":"HTTP \u72b6\u6001\u4ee3\u7801\u6620\u5c04","text":"

ABP \u5c1d\u8bd5\u6309\u7167\u4ee5\u4e0b\u89c4\u5219,\u81ea\u52a8\u6620\u5c04\u5e38\u89c1\u7684\u5f02\u5e38\u7c7b\u578b\u7684 HTTP \u72b6\u6001\u4ee3\u7801:

  • \u5bf9\u4e8e AbpAuthorizationException:
  • \u7528\u6237\u6ca1\u6709\u767b\u5f55,\u8fd4\u56de 401 (\u672a\u8ba4\u8bc1).
  • \u7528\u6237\u5df2\u767b\u5f55,\u4f46\u662f\u5f53\u524d\u8bbf\u95ee\u672a\u6388\u6743,\u8fd4\u56de 403 (\u672a\u6388\u6743).
  • \u5bf9\u4e8e AbpValidationException \u8fd4\u56de 400 (\u9519\u8bef\u7684\u8bf7\u6c42) .
  • \u5bf9\u4e8e EntityNotFoundException\u8fd4\u56de 404 (\u672a\u627e\u5230).
  • \u5bf9\u4e8e IBusinessException \u548c IUserFriendlyException (\u5b83\u662fIBusinessException\u7684\u6269\u5c55) \u8fd4\u56de403 (\u672a\u6388\u6743) .
  • \u5bf9\u4e8e NotImplementedException \u8fd4\u56de 501 (\u672a\u5b9e\u73b0) .
  • \u5bf9\u4e8e\u5176\u4ed6\u5f02\u5e38 (\u57fa\u7840\u67b6\u6784\u4e2d\u672a\u5b9a\u4e49\u7684) \u8fd4\u56de 500 (\u670d\u52a1\u5668\u5185\u90e8\u9519\u8bef) .

IHttpExceptionStatusCodeFinder \u662f\u7528\u6765\u81ea\u52a8\u5224\u65ad HTTP \u72b6\u6001\u4ee3\u7801.\u9ed8\u8ba4\u7684\u5b9e\u73b0\u662fDefaultHttpExceptionStatusCodeFinder.\u53ef\u4ee5\u6839\u636e\u9700\u8981\u5bf9\u5176\u8fdb\u884c\u66f4\u6362\u6216\u6269\u5c55.

"},{"location":"user-guide/zh/infrastructure/exception-handler/#_8","title":"\u81ea\u5b9a\u4e49\u6620\u5c04","text":"

\u53ef\u4ee5\u91cd\u5199 HTTP \u72b6\u6001\u4ee3\u7801\u7684\u81ea\u52a8\u6620\u5c04,\u793a\u4f8b\u5982\u4e0b:

C#
services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>\n{\noptions.Map(\"Volo.Qa:010002\", HttpStatusCode.Conflict);\n});\n
"},{"location":"user-guide/zh/infrastructure/exception-handler/#_9","title":"\u53d1\u9001\u5f02\u5e38\u8be6\u60c5\u5230\u5ba2\u6237\u7aef","text":"

\u4f60\u53ef\u4ee5\u901a\u8fc7 AbpExceptionHandlingOptions \u7c7b\u7684 SendExceptionsDetailsToClients \u5c5e\u6027\u5f02\u5e38\u53d1\u9001\u5230\u5ba2\u6237\u7aef:

C#
services.Configure<AbpExceptionHandlingOptions>(options =>\n{\noptions.SendExceptionsDetailsToClients = true;\n});\n
"},{"location":"user-guide/zh/infrastructure/freesql/","title":"CQRS","text":"

CQRS:\u547d\u4ee4\u67e5\u8be2\u804c\u8d23\u9694\u79bb,\u547d\u4ee4\u662f\u6307 \u63d2\u5165\u3001\u4fee\u6539\u3001\u5220\u9664\uff0c\u5c31\u662f\u66f4\u6539\u6570\u636e\u7684\u52a8\u4f5c.\u901a\u8fc7 Freesql \u89e3\u51b3\u5355\u4e00\u6570\u636e\u6a21\u578b\u5e26\u6765\u7684\u67e5\u8be2\u5c34\u5c2c\u573a\u9762\u3002 \u5f53\u524d\u67b6\u6784\u4e0b\uff0cFreesql \u548c ef \u4e0d\u5728\u4e00\u4e2a\u4e8b\u52a1\uff0c\u6700\u597d\u5b9e\u73b0\u5c31\u662f\u7528\u6765\u505a\u67e5\u8be2\uff0c\u6bd4\u5982\u5206\u9875\u67e5\u8be2\u3002

"},{"location":"user-guide/zh/infrastructure/freesql/#_1","title":"\u914d\u7f6e","text":"C#
public class AbpProFreeSqlModule : AbpModule\n{\npublic override void ConfigureServices(ServiceConfigurationContext context)\n{\nvar configuration = context.Services.GetConfiguration();\nvar connectionString = configuration.GetConnectionString(\"Default\");\nvar freeSql = new FreeSql.FreeSqlBuilder()\n.UseConnectionString(FreeSql.DataType.MySql, connectionString)\n.Build();\ncontext.Services.AddSingleton<IFreeSql>(freeSql);\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/freesql/#_2","title":"\u4f7f\u7528","text":"
  • \u5728 Domain \u5c42\u6dfb\u52a0\u63a5\u53e3
C#
public interface IUserFreeSqlBasicRepository\n{\nTask<List<UserOutput>> GetListAsync();\n}\n
  • \u5728 Freesql \u5c42\u6dfb\u52a0\u5b9e\u73b0
C#
public class UserFreeSqlBasicRepository : FreeSqlBasicRepository, IUserFreeSqlBasicRepository\n{\npublic async Task<List<UserOutput>> GetListAsync()\n{\nvar sql = \"select id from AbpUsers\";\nvar result = await FreeSql.Select<UserOutput>()\n.WithSql(sql)\n.ToListAsync();\nreturn result;\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/frontend/","title":"\u524d\u7aef","text":"

Vben Admin \u6587\u6863

"},{"location":"user-guide/zh/infrastructure/frontend/#_2","title":"\u4ee3\u7801\u751f\u6210","text":"

\u524d\u7aef\u63a5\u53e3\uff0c\u53c2\u6570\uff0c\u81ea\u52a8\u751f\u6210\uff0c\u5168\u90e8\u91c7\u7528 Post \u65b9\u5f0f

  • \u6240\u6709\u8bbf\u95ee\u540e\u7aef\u63a5\u53e3\u4ee3\u7801\u81ea\u52a8\u751f\u6210 NSwag
"},{"location":"user-guide/zh/infrastructure/frontend/#_3","title":"\u914d\u7f6e\u4ee3\u7406\u7684\u5730\u5740","text":"
  • nswag->nswag.json
JSON
  \"documentGenerator\": {\n\"fromDocument\": {\n// \u4ee3\u7406\u5730\u5740\uff0c\u53ea\u6709\u751f\u6210\u7684\u65f6\u5019\u7528\uff0c\u4e0d\u533a\u5206\u73af\u5883\n\"url\": \"http://localhost:44315/swagger/v1/swagger.json\",\n}\n}\n
  • \u5982\u679c\u63a5\u53e3\u53c2\u6570\u6216\u8005\u8fd4\u56de\u503c\u6709\u6539\u53d8\uff0c\u9700\u8981\u91cd\u65b0\u751f\u6210\u4ee3\u7406\uff0c\u6267\u884c:
Bash
npm run nswag\n
"},{"location":"user-guide/zh/infrastructure/frontend/#api","title":"\u540e\u7aef Api \u683c\u5f0f","text":"C#
// \u4e00\u5b9a\u8981\u6253Tags\uff0c\u56e0\u4e3a\u524d\u7aef\u4f1a\u6839\u636e\u8fd9\u4e2a\u751f\u6210\u4ee3\u7406\u7c7b\n// \u5efa\u8bae\u53c2\u6570\u90fd\u5c01\u88c5\u4e3a\u4e00\u4e2aInput\n[SwaggerOperation(summary: \"\u767b\u5f55\", Tags = new[] {\"Account\"})]\npublic Task<LoginOutput> LoginAsync(LoginInput input)\n{\nreturn _loginAppService.LoginAsync(input);\n}\n
"},{"location":"user-guide/zh/infrastructure/frontend/#_4","title":"\u524d\u7aef\u591a\u73af\u5883","text":"
  • .env.development \u548c.env.production
  • VITE_API_URL:\u540e\u7aef\u63a5\u53e3\u5730\u5740
  • VITE_WEBSOCKE_URL: WEBSOCKE \u5730\u5740
"},{"location":"user-guide/zh/infrastructure/frontend/#_5","title":"\u6743\u9650\u914d\u7f6e","text":""},{"location":"user-guide/zh/infrastructure/frontend/#_6","title":"\u83dc\u5355\u6743\u9650","text":"TypeScript
import type { AppRouteModule } from \"/@/router/types\";\nimport { LAYOUT } from \"/@/router/constant\";\nimport { t } from \"/@/hooks/web/useI18n\";\nconst tenant: AppRouteModule = {\npath: \"/tenant\",\nname: \"Tenant\",\ncomponent: LAYOUT,\nmeta: {\norderNo: 30,\nicon: \"ant-design:contacts-outlined\",\ntitle: t(\"routes.tenant.tenantManagement\"),\n},\nchildren: [\n{\npath: \"Tenant\",\nname: \"Tenant\",\ncomponent: () => import(\"/@/views/tenants/Tenant.vue\"),\nmeta: {\ntitle: t(\"routes.tenant.tenantList\"),\nicon: \"ant-design:switcher-filled\",\npolicy: \"AbpTenantManagement.Tenants\", //\u83dc\u5355\u6743\u9650\n},\n},\n],\n};\nexport default tenant;\n
"},{"location":"user-guide/zh/infrastructure/frontend/#_7","title":"\u6309\u94ae\u6743\u9650","text":"Text Only
<template>\n  <div>\n    <BasicTable @register=\"registerTable\" size=\"small\">\n      <template #action=\"{ record }\">\n        <TableAction\n          :actions=\"[\n            {\n              icon: 'ant-design:edit-outlined',\n              auth: 'AbpIdentity.Users.Update', // \u6309\u94ae\u6743\u9650\n              label: t('common.editText'),\n              onClick: handleEdit.bind(null, record),\n            },\n          ]\"\n          :dropDownActions=\"[\n            {\n              auth: 'AbpIdentity.Users.Delete', // \u6309\u94ae\u6743\u9650\n              label: t('common.delText'),\n              onClick: handleDelete.bind(null, record),\n            },\n            {\n              auth: 'System.Users.Enable', // \u6309\u94ae\u6743\u9650\n              label: !record.isActive\n                ? t('common.enabled')\n                : t('common.disEnabled'),\n              onClick: handleLock.bind(null, record),\n            },\n          ]\"\n        />\n      </template>\n    </BasicTable>\n    <CreateAbpUser\n      @register=\"registerCreateAbpUserModal\"\n      @reload=\"reload\"\n      :bodyStyle=\"{ 'padding-top': '0' }\"\n    />\n    <EditAbpUser\n      @register=\"registerEditAbpUserModal\"\n      @reload=\"reload\"\n      :bodyStyle=\"{ 'padding-top': '0' }\"\n    />\n  </div>\n</template>\n
"},{"location":"user-guide/zh/infrastructure/log/","title":"Serilog","text":"

ABP \u6846\u67b6\u6ca1\u6709\u5b9e\u73b0\u4efb\u4f55\u65e5\u5fd7\u57fa\u7840\u8bbe\u65bd. \u5b83\u4f7f\u7528 ASP.NET Core \u65e5\u5fd7\u7cfb\u7edf.

"},{"location":"user-guide/zh/infrastructure/log/#_1","title":"\u65e5\u5fd7\u7b49\u7ea7","text":"

Debug \u2192 Information \u2192 Warning \u2192 Error \u2192 Fatal

"},{"location":"user-guide/zh/infrastructure/log/#_2","title":"\u5982\u4f55\u96c6\u6210","text":"Program.cs
public class Program\n{\nprivate static IHostBuilder CreateHostBuilder(string[] args) =>\nHost.CreateDefaultBuilder(args)\n.ConfigureWebHostDefaults()\n// \u4f7f\u7528Serilog\n.UseSerilog((context, loggerConfiguration) =>\n{\nSerilogToEsExtensions.SetSerilogConfiguration(\nloggerConfiguration,\ncontext.Configuration);\n}).UseAutofac();\n}\n
SerilogToEsExtensions.cs
public static void SetSerilogConfiguration(LoggerConfiguration loggerConfiguration, IConfiguration configuration)\n{\n// \u9ed8\u8ba4\u8bfb\u53d6 configuration \u4e2d \"Serilog\" \u8282\u70b9\u4e0b\u7684\u914d\u7f6e\nloggerConfiguration\n.ReadFrom.Configuration(configuration)\n.Enrich.FromLogContext();\n// \u5982\u679c\u8981\u518d\u65e5\u5fd7\u52a0\u4e0a\u81ea\u5b9a\u4e49\u5b57\u6bb5\nloggerConfiguration.Enrich.WithProperty(\"Application\", applicationName);\n}\n
"},{"location":"user-guide/zh/infrastructure/log/#_3","title":"\u914d\u7f6e","text":"appsetting.json
\"Serilog\": {\n\"Using\": [\n\"Serilog.Sinks.Console\",\n\"Serilog.Sinks.File\"\n],\n\"MinimumLevel\": {\n// \u9ed8\u8ba4\u5168\u5c40\u65e5\u5fd7\u7ea7\u522b\n\"Default\": \"Information\",\n\"Override\": {\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Microsoft \u65e5\u5fd7\u7ea7\u522b\n\"Microsoft\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Volo.Abp \u65e5\u5fd7\u7ea7\u522b\n\"Volo.Abp\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Hangfire \u65e5\u5fd7\u7ea7\u522b\n\"Hangfire\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a DotNetCore.CAP \u65e5\u5fd7\u7ea7\u522b\n\"DotNetCore.CAP\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Serilog.AspNetCore \u65e5\u5fd7\u7ea7\u522b\n\"Serilog.AspNetCore\": \"Information\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Microsoft.EntityFrameworkCore \u65e5\u5fd7\u7ea7\u522b\n\"Microsoft.EntityFrameworkCore\": \"Warning\",\n//\u540d\u79f0\u7a7a\u95f4\u4e3a Microsoft.AspNetCore \u65e5\u5fd7\u7ea7\u522b\n\"Microsoft.AspNetCore\": \"Information\"\n}\n},\n\"WriteTo\": [\n{\n// \u8f93\u51fa\u5230\u63a7\u5236\u53f0\u65e5\u5fd7\n\"Name\": \"Console\"\n},\n{\n// \u8f93\u51fa\u5230\u6587\u4ef6\n\"Name\": \"File\",\n\"Args\": {\n\"path\": \"logs/logs-.txt\",\n// \u6309\u5929\u8f93\u51fa\n\"rollingInterval\": \"Day\"\n}\n}\n]\n}\n
"},{"location":"user-guide/zh/infrastructure/log/#elasticsearch","title":"\u5199\u5165 ElasticSearch","text":"

AbpPro \u5df2\u7ecf\u96c6\u6210 ElasticSearch \u53ea\u9700\u8981\u901a\u8fc7\u914d\u7f6e\u6587\u4ef6\u542f\u7528\u5373\u53ef\u3002

  • Enabled:\u662f\u5426\u542f\u7528
  • Url:es \u5730\u5740
  • IndexFormat:es \u7d22\u5f15
  • UserName:\u7528\u6237\u540d
  • Password:\u5bc6\u7801
appsetting.json
\"ElasticSearch\": {\n\"Enabled\": \"false\",\n\"Url\": \"http://es.cn\",\n// \u7d22\u5f15\u540d\u5fc5\u987b\u5c0f\u5199\n\"IndexFormat\": \"lion.abppro.development.{0:yyyy.MM.dd}\",\n\"UserName\": \"elastic\",\n\"Password\": \"aVVhjQ95RP7nbwNy\"\n},\n
"},{"location":"user-guide/zh/infrastructure/log/#_4","title":"\u4f7f\u7528","text":"SampleAppService.cs
public class SampleAppService : AbpProAppService,ISampleAppService\n{\nprivate readonly ILogger<SampleAppService> _logger;\npublic SampleAppService(ILogger<SampleAppService> logger)\n{\n_logger = logger;\n}\npublic async Task TestAsync()\n{\n_logger.LogDebug(\"LogDebug\");\n_logger.LogInformation(\"LogInformation\");\n_logger.LogWarning(\"LogWarning\");\n_logger.LogError(\"LogError\");\n_logger.LogTrace(\"LogTrace\");\nawait Task.CompletedTask;\n}\n}\n
"},{"location":"user-guide/zh/infrastructure/login/","title":"\u767b\u5f55","text":"

ABP vNext Pro \u6ca1\u6709\u96c6\u6210 IdentityServer4 \u6216\u8005 OpenIddict,\u800c\u662f\u76f4\u63a5\u4f7f\u7528\u9ed8\u8ba4\u7684 Asp Net Core Identity\u3002

  • \u51cf\u5c11\u7cfb\u7edf\u590d\u6742\u5ea6
  • \u5927\u90e8\u5206(IdentityServer4|OpenIddict)\u529f\u80fd\u7528\u4e0d\u4e0a
"},{"location":"user-guide/zh/infrastructure/login/#_2","title":"\u767b\u5f55\u63a5\u53e3","text":"AccountAppService.cs
public virtual async Task<LoginOutput> LoginAsync(LoginInput input)\n{\nvar result = await _signInManager.PasswordSignInAsync(input.Name, input.Password, false, true);\nif (result.IsNotAllowed)\n{\nthrow new BusinessException(BasicManagementErrorCodes.UserLockedOut);\n}\nif (!result.Succeeded)\n{\nthrow new BusinessException(BasicManagementErrorCodes.UserOrPasswordMismatch);\n}\nvar user = await _userManager.FindByNameAsync(input.Name);\nreturn await BuildResult(user);\n}\n
"},{"location":"user-guide/zh/infrastructure/login/#accesstoken","title":"\u914d\u7f6e AccessToken","text":"appsetting.json
 \"Jwt\": {\n\"Audience\": \"Lion.AbpPro\",\n\"SecurityKey\": \"dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=\",\n\"Issuer\": \"Lion.AbpPro\",\n\"ExpirationTime\": 30\n}\n
  • Audience:\u63a5\u6536\u5bf9\u8c61
  • Issuer:\u7b7e\u53d1\u4e3b\u4f53
  • SecurityKey:\u5bc6\u94a5
  • ExpirationTime:\u8fc7\u671f\u65f6\u95f4(\u5355\u4f4d\u5c0f\u65f6)
"},{"location":"user-guide/zh/modules/basic/","title":"\u57fa\u7840\u6a21\u5757","text":"
  • \u628aabp\u81ea\u5e26 \u8d26\u6237\u6a21\u5757\uff0c\u6743\u9650\u6a21\u5757\uff0cidentity\u6a21\u5757\uff0csetting\u6a21\u5757\uff0cfeature\u6a21\u5757\uff0c\u540e\u53f0\u4efb\u52a1\u6a21\u5757\uff0c\u79df\u6237\u6a21\u5757\u5c01\u88c5\u5230BasicManagement
"},{"location":"user-guide/zh/modules/basic/#_2","title":"\u5b89\u88c5","text":"
  • Lion.Abp.BasicManagement.Application
  • Lion.Abp.BasicManagement.Application.Contracts
  • Lion.Abp.BasicManagement.Domain
  • Lion.Abp.BasicManagement.Domain.Shared
  • Lion.Abp.BasicManagement.EntityFrameworkCore
  • Lion.Abp.BasicManagement.HttpApi
  • Lion.Abp.BasicManagement.HttpApi.Client
"},{"location":"user-guide/zh/modules/basic/#_3","title":"\u6a21\u5757\u4f9d\u8d56","text":"
  • \u6dfb\u52a0 DependsOn(typeof(BasicManagementXxxModule)) \u7279\u6027\u5230\u5bf9\u5e94\u6a21\u5757\u3002
  • \u5728EntityFrameworkCore\u5c42\u6dfb\u52a0\u6570\u636e\u5e93\u914d\u7f6e\u5728AbpProDbContext.cs\u7684OnModelCreating()\u65b9\u6cd5\u4e2d\u6dfb\u52a0builder.ConfigureBasicManagement();
"},{"location":"user-guide/zh/modules/dic/","title":"\u6570\u636e\u5b57\u5178\u6a21\u5757","text":"

Abp\u81ea\u5e26\u7684Setting\u6a21\u5757\u53ef\u80fd\u6ee1\u8db3\u4e0d\u4e86\u9700\u6c42\uff0c\u7279\u610f\u63d0\u4f9b\u6570\u636e\u5b57\u5178\u6a21\u5757\u3002

"},{"location":"user-guide/zh/modules/dic/#_2","title":"\u5b89\u88c5","text":"
  • Lion.Abp.DataDictionaryManagement.Application
  • Lion.Abp.DataDictionaryManagement.Application.Contracts
  • Lion.Abp.DataDictionaryManagement.Domain
  • Lion.Abp.DataDictionaryManagement.Domain.Shared
  • Lion.Abp.DataDictionaryManagement.EntityFrameworkCore
  • Lion.Abp.DataDictionaryManagement.HttpApi
  • Lion.Abp.DataDictionaryManagement.HttpApi.Client
"},{"location":"user-guide/zh/modules/dic/#_3","title":"\u6a21\u5757\u4f9d\u8d56","text":"
  • \u6dfb\u52a0 DependsOn(typeof(DataDictionaryManagementXxxModule)) \u7279\u6027\u5230\u5bf9\u5e94\u6a21\u5757\u3002

  • \u5728EntityFrameworkCore\u5c42\u6dfb\u52a0\u6570\u636e\u5e93\u914d\u7f6e\u5728AbpProDbContext.cs\u7684OnModelCreating()\u65b9\u6cd5\u4e2d\u6dfb\u52a0builder.ConfigureDataDictionaryManagement();

"},{"location":"user-guide/zh/modules/dic/#_4","title":"\u5b9e\u4f53","text":"

DataDictionary \u8868\u7ed3\u6784\uff1a

\u5b57\u6bb5\u540d \u63cf\u8ff0 \u7c7b\u578b Id Id Guid TenantId \u79df\u6237id Guid? Code \u5b57\u5178\u7f16\u7801 string DisplayText \u663e\u793a\u540d string Description \u63cf\u8ff0 DateTime Details \u5b57\u5178\u660e\u7ec6 List IsDeleted \u662f\u5426\u5220\u9664 bool DeleterId \u5220\u9664\u4eba Guid? DeletionTime \u5220\u9664\u65f6\u95f4 DateTime LastModifierId \u6700\u540e\u4fee\u6539\u4eba Guid? LastModificationTime \u6700\u540e\u4fee\u6539\u65f6\u95f4 DateTime CreatorId \u521b\u5efa\u4eba Guid? CreationTime \u521b\u5efa\u65f6\u95f4 DateTime

DataDictionaryDetail \u8868\u7ed3\u6784\uff1a

\u5b57\u6bb5\u540d \u63cf\u8ff0 \u7c7b\u578b Id Id Guid DataDictionaryId \u6240\u5c5e\u5b57\u5178Id Guid Order \u6392\u5e8f Int Code \u5b57\u5178\u7f16\u7801 string IsEnabled \u542f/\u505c\u7528(\u9ed8\u8ba4\u542f\u7528) bool DisplayText \u663e\u793a\u540d string Description \u63cf\u8ff0 DateTime IsDeleted \u662f\u5426\u5220\u9664 bool DeleterId \u5220\u9664\u4eba Guid? DeletionTime \u5220\u9664\u65f6\u95f4 DateTime LastModifierId \u6700\u540e\u4fee\u6539\u4eba Guid? LastModificationTime \u6700\u540e\u4fee\u6539\u65f6\u95f4 DateTime CreatorId \u521b\u5efa\u4eba Guid? CreationTime \u521b\u5efa\u65f6\u95f4 DateTime"},{"location":"user-guide/zh/modules/file/","title":"\u6587\u4ef6\u6a21\u5757","text":"
  • \u4e0e abp \u81ea\u5e26\u7684\u6587\u4ef6\u6a21\u5757\u4e0d\u4e00\u6837\uff0c\u6b64\u6a21\u5757\u63a5\u5165\u963f\u91cc\u4e91 oss \u4f5c\u4e3a\u4e91\u5b58\u50a8\u3002
  • \u524d\u7aef\u4e0a\u4f20\u6587\u4ef6\u5230 OSS,\u6587\u4ef6\u6a21\u5757\u4fdd\u5b58\u76f8\u5bf9\u8def\u5f84\u3002
"},{"location":"user-guide/zh/modules/file/#_2","title":"\u5b89\u88c5","text":"
  • Lion.Abp.FileManagement.Application
  • Lion.Abp.FileManagement.Application.Contracts
  • Lion.Abp.FileManagement.Domain
  • Lion.Abp.FileManagement.Domain.Shared
  • Lion.Abp.FileManagement.EntityFrameworkCore
  • Lion.Abp.FileManagement.HttpApi
  • Lion.Abp.FileManagement.HttpApi.Client
"},{"location":"user-guide/zh/modules/file/#_3","title":"\u6a21\u5757\u4f9d\u8d56","text":"
  • \u6dfb\u52a0 DependsOn(typeof(FileManagementXxxModule)) \u7279\u6027\u5230\u5bf9\u5e94\u6a21\u5757\u3002
  • \u5728 EntityFrameworkCore \u5c42\u6dfb\u52a0\u6570\u636e\u5e93\u914d\u7f6e\u5728 AbpProDbContext.cs \u7684 OnModelCreating()\u65b9\u6cd5\u4e2d\u6dfb\u52a0 builder.ConfigureFileManagement();
"},{"location":"user-guide/zh/modules/file/#_4","title":"\u5b9e\u4f53","text":"

File \u8868\u7ed3\u6784\uff1a

\u5b57\u6bb5\u540d \u63cf\u8ff0 \u7c7b\u578b Id Id Guid TenantId \u79df\u6237 id Guid? FileName \u6587\u4ef6\u540d\u79f0 string FilePath \u6587\u4ef6\u8def\u5f84 string IsDeleted \u662f\u5426\u5220\u9664 bool DeleterId \u5220\u9664\u4eba Guid? DeletionTime \u5220\u9664\u65f6\u95f4 DateTime LastModifierId \u6700\u540e\u4fee\u6539\u4eba Guid? LastModificationTime \u6700\u540e\u4fee\u6539\u65f6\u95f4 DateTime CreatorId \u521b\u5efa\u4eba Guid? CreationTime \u521b\u5efa\u65f6\u95f4 DateTime"},{"location":"user-guide/zh/modules/file/#oss","title":"OSS \u914d\u7f6e","text":"

\u963f\u91cc\u4e91 OSS \u914d\u7f6e

  • \u5c06 OSS \u914d\u7f6e\u6dfb\u52a0\u5230 AppSetting
"},{"location":"user-guide/zh/modules/file/#appsetting","title":"AppSetting \u914d\u7f6e","text":"JSON
  \"AliYun\": {\n\"OSS\": {\n\"AccessKeyId\": \"LTAI5tLkt3vvScGPVZ5qKJDc1S\",\n\"AccessKeySecret\": \"BixV8vP5uPrbsdwjYzzsEXOPjkxPST12S\",\n\"Endpoint\": \"oss-cn-shenzhen.aliyuncs.com\",\n\"ContainerName\": \"lion-abp-pro\",\n\"RegionId\": \"oss-cn-shenzhen\",\n\"RoleArn\": \"acs:ram::1846393972471789:role/ramosst1t\"\n}\n}\n
"},{"location":"user-guide/zh/modules/file/#_5","title":"\u4e0a\u4f20\u7ec4\u4ef6","text":"
  • \u524d\u7aef UploadOss.ts
"},{"location":"user-guide/zh/modules/setting/","title":"\u8bbe\u7f6e\u7ba1\u7406","text":"

\u5b98\u65b9 Setting \u6a21\u5757\u53c2\u8003\u6587\u6863

\u914d\u7f6e\u7cfb\u7edf\u662f\u5728\u542f\u52a8\u65f6\u914d\u7f6e\u5e94\u7528\u7a0b\u5e8f\u5f88\u597d\u7684\u65b9\u5f0f. \u9664\u4e86\u914d\u7f6e\u4e4b\u5916, ABP \u63d0\u4f9b\u4e86\u53e6\u5916\u4e00\u79cd\u8bbe\u7f6e\u548c\u83b7\u53d6\u5e94\u7528\u7a0b\u5e8f\u8bbe\u7f6e\u7684\u65b9\u5f0f. \u8bbe\u7f6e\u5b58\u50a8\u5728\u52a8\u6001\u6570\u636e\u6e90(\u901a\u5e38\u662f\u6570\u636e\u5e93)\u4e2d\u7684\u952e\u503c\u5bf9. \u8bbe\u7f6e\u7cfb\u7edf\u9884\u6784\u5efa\u4e86\u7528\u6237,\u79df\u6237,\u5168\u5c40\u548c\u9ed8\u8ba4\u8bbe\u7f6e\u65b9\u6cd5\u5e76\u4e14\u53ef\u4ee5\u8fdb\u884c\u6269\u5c55.

"},{"location":"user-guide/zh/modules/setting/#_2","title":"\u5b9a\u4e49\u8bbe\u7f6e","text":"

\u4f7f\u7528\u8bbe\u7f6e\u4e4b\u524d\u9700\u8981\u5b9a\u4e49\u5b83. ABP \u662f \u6a21\u5757\u5316\u7684, \u4e0d\u540c\u7684\u6a21\u5757\u53ef\u4ee5\u62e5\u6709\u4e0d\u540c\u7684\u8bbe\u7f6e. \u53ea\u9700\u8981\u5b9e\u73b0 SettingDefinitionProvider \u7c7b\u65e2\u53ef. \u793a\u4f8b\u5982\u4e0b:

\u548c\u5b98\u65b9 Setting \u6a21\u5757\u533a\u522b\uff0c\u503c\u6dfb\u52a0\u4e86 2 \u4e2a\u5c5e\u6027\uff0c\u4e00\u4e2a\u5206\u7ec4\uff0c\u4e00\u4e2a\u7ec4\u4ef6\u7c7b\u578b

C#
public class CustomSettingProvider : SettingDefinitionProvider\n{\npublic override void Define(ISettingDefinitionContext context)\n{\ncontext.Add(\nnew SettingDefinition(\nAbpProSettings.Other.Github,\n\"https://github.com/WangJunZzz/abp-vnext-pro\",\nL(\"DisplayName:\" + AbpProSettings.Other.Github),\nL(\"Description:\" + AbpProSettings.Other.Github)\n)\n// \u5206\u7ec4\n.WithProperty(AbpProSettings.Group.Default,AbpProSettings.Group.OtherManagement)\n// \u524d\u7aef\u7ec4\u4ef6\u7c7b\u578b\n.WithProperty(AbpProSettings.ControlType.Default,AbpProSettings.ControlType.TypeText));\n}\n}\n
  • SettingDefinition \u7c7b\u5177\u6709\u4ee5\u4e0b\u5c5e\u6027:

  • Name: \u5e94\u7528\u7a0b\u5e8f\u4e2d\u8bbe\u7f6e\u7684\u552f\u4e00\u540d\u79f0. \u662f\u5177\u6709\u7ea6\u675f\u7684\u552f\u4e00\u5c5e\u6027, \u5728\u5e94\u7528\u7a0b\u5e8f\u83b7\u53d6/\u8bbe\u7f6e\u6b64\u8bbe\u7f6e\u7684\u503c (\u8bbe\u7f6e\u540d\u79f0\u5b9a\u4e49\u4e3a\u5e38\u91cf\u800c\u4e0d\u662f\u9b54\u6cd5\u5b57\u7b26\u4e32\u662f\u4e2a\u597d\u4e3b\u610f).

  • DefaultValue: \u8bbe\u7f6e\u7684\u9ed8\u8ba4\u503c.
  • DisplayName: \u672c\u5730\u5316\u7684\u5b57\u7b26\u4e32,\u7528\u4e8e\u5728 UI \u4e0a\u663e\u793a\u540d\u79f0.
  • Description: \u672c\u5730\u5316\u7684\u5b57\u7b26\u4e32,\u7528\u4e8e\u5728 UI \u4e0a\u663e\u793a\u63cf\u8ff0.

  • \u4e0a\u9762\u6dfb\u52a0\u4e86 2 \u4e2a\u5c5e\u6027\uff0c\u4e3a\u4e86\u9002\u914d vue \u524d\u7aef\uff0c\u4e00\u4e2a\u8bbe\u7f6e Setting \u5c5e\u4e8e\u54ea\u4e2a\u5206\u7ec4\uff0c\u4e00\u4e2a\u662f\u6839\u636e Setting \u7684\u7c7b\u578b\u6307\u5b9a\u5bf9\u5e94\u7684\u524d\u7aef\u7ec4\u4ef6\uff0c\u6bd4\u5982\u5b57\u7b26\u4e32\u5c31\u662f,Input \u7ec4\u4ef6\u3002

  • \u652f\u6301\u4ee5\u4e0b\u7ec4\u4ef6\uff1aText\uff0cCheckBox\uff0cNumber
"},{"location":"user-guide/zh/modules/setting/#_3","title":"\u8bfb\u53d6\u8bbe\u7f6e\u503c","text":"

ISettingProvider \u7528\u4e8e\u83b7\u53d6\u6307\u5b9a\u8bbe\u7f6e\u7684\u503c\u6216\u6240\u6709\u8bbe\u7f6e\u7684\u503c. \u793a\u4f8b\u7528\u6cd5:

C#
public class MyService\n{\nprivate readonly ISettingProvider _settingProvider;\n//Inject ISettingProvider in the constructor\npublic MyService(ISettingProvider settingProvider)\n{\n_settingProvider = settingProvider;\n}\npublic async Task FooAsync()\n{\n//Get a value as string.\nstring userName = await _settingProvider.GetOrNullAsync(\"Smtp.UserName\");\n//Get a bool value and fallback to the default value (false) if not set.\nbool enableSsl = await _settingProvider.GetAsync<bool>(\"Smtp.EnableSsl\");\n//Get a bool value and fallback to the provided default value (true) if not set.\nbool enableSsl = await _settingProvider.GetAsync<bool>(\n\"Smtp.EnableSsl\", defaultValue: true);\n//Get a bool value with the IsTrueAsync shortcut extension method\nbool enableSsl = await _settingProvider.IsTrueAsync(\"Smtp.EnableSsl\");\n//Get an int value or the default value (0) if not set\nint port = (await _settingProvider.GetAsync<int>(\"Smtp.Port\"));\n//Get an int value or null if not provided\nint? port = (await _settingProvider.GetOrNullAsync(\"Smtp.Port\"))?.To<int>();\n}\n}\n

ISettingProvider \u662f\u975e\u5e38\u5e38\u7528\u7684\u670d\u52a1,\u4e00\u4e9b\u57fa\u7c7b\u4e2d(\u5982 IApplicationService)\u5df2\u7ecf\u5c06\u5176\u5c5e\u6027\u6ce8\u5165. \u8fd9\u79cd\u60c5\u51b5\u4e0b\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528 SettingProvider.

  • ISettingProvider \u4f7f\u7528\u8bbe\u7f6e\u503c\u63d0\u4f9b\u7a0b\u5e8f\u6765\u83b7\u53d6\u8bbe\u7f6e\u503c. \u5982\u679c\u503c\u63d0\u4f9b\u7a0b\u5e8f\u65e0\u6cd5\u83b7\u53d6\u8bbe\u7f6e\u503c,\u5219\u4f1a\u56de\u9000\u5230\u4e0b\u4e00\u4e2a\u503c\u63d0\u4f9b\u7a0b\u5e8f.
  • DefaultValueSettingValueProvider: \u4ece\u8bbe\u7f6e\u5b9a\u4e49\u7684\u9ed8\u8ba4\u503c\u4e2d\u83b7\u53d6\u503c.
  • ConfigurationSettingValueProvider: \u4ece IConfiguration \u670d\u52a1\u4e2d\u83b7\u53d6\u503c.
  • GlobalSettingValueProvider: \u83b7\u53d6\u8bbe\u7f6e\u7684\u5168\u5c40\u503c.
  • TenantSettingValueProvider: \u83b7\u53d6\u5f53\u524d\u79df\u6237\u7684\u8bbe\u7f6e\u503c.
  • UserSettingValueProvider: \u83b7\u53d6\u5f53\u524d\u7528\u6237\u7684\u8bbe\u7f6e\u503c.

\u8bbe\u7f6e\u56de\u9000\u7cfb\u7edf\u4ece\u5e95\u90e8 (\u7528\u6237) \u5230 \u9876\u90e8(\u9ed8\u8ba4) \u65b9\u5411\u8d77\u4f5c\u7528.

"},{"location":"user-guide/zh/modules/signalr/","title":"\u5b9e\u65f6\u901a\u4fe1","text":"

\u96c6\u6210 Abp SignalR,\u5b9e\u73b0\u7c7b\u4f3c\u7ad9\u5185\u4fe1\u6a21\u5757\u3002

  • \u53d1\u9001\u6d88\u606f\u4f1a\u5728\u524d\u7aef\u53f3\u4e0a\u89d2\uff0c\u6839\u636e\u4e0d\u540c\u6d88\u606f\u7b49\u7ea7\u6709\u4e0d\u540c\u7684\u7a97\u4f53\u63d0\u793a\u3002

  • \u5728\u53f3\u4e0a\u89d2\u706f\u6ce1\u6309\u94ae\u53ef\u4ee5\u770b\u5230\u63a5\u53d7\u7684\u6d88\u606f\u3002

"},{"location":"user-guide/zh/modules/signalr/#_2","title":"\u5b89\u88c5","text":"
  • Lion.Abp.NotificationManagement.Application
  • Lion.Abp.NotificationManagement.Application.Contracts
  • Lion.Abp.NotificationManagement.Domain
  • Lion.Abp.NotificationManagement.Domain.Shared
  • Lion.Abp.NotificationManagement.EntityFrameworkCore
  • Lion.Abp.NotificationManagement.HttpApi
  • Lion.Abp.NotificationManagement.HttpApi.Client
"},{"location":"user-guide/zh/modules/signalr/#_3","title":"\u6a21\u5757\u4f9d\u8d56","text":"
  • \u6dfb\u52a0 DependsOn(typeof(NotificationManagementXxxModule)) \u7279\u6027\u5230\u5bf9\u5e94\u6a21\u5757\u3002

  • \u5728 EntityFrameworkCore \u5c42\u6dfb\u52a0\u6570\u636e\u5e93\u914d\u7f6e\u5728 AbpProDbContext.cs \u7684 OnModelCreating()\u65b9\u6cd5\u4e2d\u6dfb\u52a0 builder.ConfigureNotificationManagement();

"},{"location":"user-guide/zh/modules/signalr/#_4","title":"\u5b9e\u4f53","text":"

Notification \u8868\u7ed3\u6784\uff1a

\u5b57\u6bb5\u540d \u63cf\u8ff0 \u7c7b\u578b Id Id Guid Title \u6d88\u606f\u6807\u9898 string Content \u6d88\u606f\u5185\u5bb9 string MessageType \u6d88\u606f\u7c7b\u578b MessageType MessageLevel \u6d88\u606f\u7b49\u7ea7 MessageLevel SenderId \u521b\u5efa\u4eba \u53d1\u9001\u4eba NotificationSubscriptions \u6d88\u606f\u8ba2\u9605\u8005\u96c6\u5408 List IsDeleted \u662f\u5426\u5220\u9664 bool DeleterId \u5220\u9664\u4eba Guid? DeletionTime \u5220\u9664\u65f6\u95f4 DateTime LastModifierId \u6700\u540e\u4fee\u6539\u4eba Guid? LastModificationTime \u6700\u540e\u4fee\u6539\u65f6\u95f4 DateTime CreatorId \u521b\u5efa\u4eba Guid? CreationTime \u521b\u5efa\u65f6\u95f4 DateTime

NotificationSubscription \u8868\u7ed3\u6784\uff1a

\u5b57\u6bb5\u540d \u63cf\u8ff0 \u7c7b\u578b Id Id Guid ReceiveId \u63a5\u6536\u4eba Guid Read \u662f\u5426\u5df2\u8bfb bool ReadTime \u5df2\u8bfb\u65f6\u95f4 DateTime? IsDeleted \u662f\u5426\u5220\u9664 bool DeleterId \u5220\u9664\u4eba Guid? DeletionTime \u5220\u9664\u65f6\u95f4 DateTime LastModifierId \u6700\u540e\u4fee\u6539\u4eba Guid? LastModificationTime \u6700\u540e\u4fee\u6539\u65f6\u95f4 DateTime CreatorId \u521b\u5efa\u4eba Guid? CreationTime \u521b\u5efa\u65f6\u95f4 DateTime"},{"location":"user-guide/zh/modules/signalr/#_5","title":"\u53d1\u9001\u6d88\u606f","text":"
  • \u6ce8\u5165 NotificationManager,NotificationAppService \u5373\u53ef\u53d1\u9001\u4e0d\u540c\u7ea7\u522b\uff0c\u4e0d\u540c\u7b49\u7ea7\u7684\u6d88\u606f\u3002
  • \u53d1\u9001\u7ed9\u6307\u5b9a\u4eba
C#
/// <summary>\n/// \u53d1\u9001\u8b66\u544a\u6587\u672c\u6d88\u606f\n/// </summary>\n/// <param name=\"title\">\u6807\u9898</param>\n/// <param name=\"content\">\u6d88\u606f\u5185\u5bb9</param>\n/// <param name=\"receiveIds\">\u63a5\u53d7\u4eba\uff0c\u53d1\u9001\u7ed9\u8c01\u3002</param>\nawait _notificationManager.SendCommonWarningMessageAsync(title,content,receiveIds);\n
  • \u53d1\u9001\u7ed9\u6240\u6709\u4eba
C#
/// <summary>\n/// \u53d1\u9001\u8b66\u544a\u6587\u672c\u6d88\u606f\n/// </summary>\n/// <param name=\"title\">\u6807\u9898</param>\n/// <param name=\"content\">\u6d88\u606f\u5185\u5bb9</param>\nawait _notificationManager.SendBroadCastInformationMessageAsync(title,content);\n
"},{"location":"user-guide/zh/modules/signalr/#_6","title":"\u4f9d\u8d56","text":"
  • \u5982\u679c\u670d\u52a1\u5206\u5e03\u5f0f\u90e8\u7f72\uff0c\u9700\u8981\u4f7f\u7528 Redis(\u9ed8\u8ba4\u4f9d\u8d56),\u89e3\u51b3 SignalR \u6d88\u606f\u91cd\u590d\u95ee\u9898\u3002
C#
private void ConfigurationSignalR(ServiceConfigurationContext context)\n{\nvar redisConnection = context.Services.GetConfiguration()[\"Redis:Configuration\"];\nif (redisConnection.IsNullOrWhiteSpace())\n{\nthrow new UserFriendlyException(message: \"Redis\u8fde\u63a5\u5b57\u7b26\u4e32\u672a\u914d\u7f6e.\");\n}\ncontext.Services.AddSignalR()\n.AddStackExchangeRedis(redisConnection, options => { options.Configuration.ChannelPrefix = \"Lion.AbpPro\"; });\n}\n
"},{"location":"user-guide/zh/modules/signalr/#vue","title":"Vue \u5ba2\u6237\u7aef\u8fde\u63a5","text":"
  • \u5728\u7528\u6237\u767b\u9646\u6210\u529f\u4e4b\u540e\uff0c\u8fde\u63a5 SignalR,\u5e76\u4e14\u5e26\u81ea\u52a8\u91cd\u8fde\u673a\u5236\u3002\u6e90\u7801
  • \u793a\u4f8b\u5982\u4e0b:
TypeScript
const { startConnect } = useSignalR();\nonMounted(() => {\nstartConnect();\n});\n
"},{"location":"user-guide/zh/problem/ef/","title":"EFCore","text":""},{"location":"user-guide/zh/problem/ef/#_1","title":"\u521b\u5efa\u65f6\u95f4,\u66f4\u65b0\u65f6\u95f4,\u5220\u9664\u65f6\u95f4","text":"
  • \u5f53\u5b9e\u4f53\u7ee7\u627f\u4e86\u8fd9\u4e09\u4e2a\u5c5e\u6027\u5f97\u65f6\u5019,\u53ea\u6709\u5728\u65b0\u589e\u624d\u4f1a\u53c8\u521b\u5efa\u65f6\u95f4,\u5982\u679c\u60f3\u8981\u4e5f\u6709\u66f4\u65b0\u65f6\u95f4\u5982\u4f55\u5904\u7406\u5462\uff1f

  • \u89e3\u51b3\u65b9\u5f0f:\u91cd\u5199 DbContext \u4e00\u4e0b\u65b9\u6cd5

C#
namespace Lion.AbpPro.EntityFrameworkCore\n{\n[ConnectionStringName(\"Default\")]\npublic class AbpProDbContext :\nAbpDbContext<AbpProDbContext>,\nIAbpProDbContext\n{\nprotected override void SetCreationAuditProperties(EntityEntry entry)\n{\nSetModificationAuditProperties(entry);\nbase.SetCreationAuditProperties(entry);\n}\nprotected override void SetDeletionAuditProperties(EntityEntry entry)\n{\nSetModificationAuditProperties(entry);\nbase.SetDeletionAuditProperties(entry);\n}\n}\n}\n
"},{"location":"user-guide/zh/problem/ef/#_2","title":"\u8bbe\u7f6e\u6570\u636e\u5e93\u5b57\u7b26\u96c6\u683c\u5f0f","text":"C#
namespace Lion.AbpPro.EntityFrameworkCore\n{\n[ConnectionStringName(\"Default\")]\npublic class AbpProDbContext :\nAbpDbContext<AbpProDbContext>,\nIAbpProDbContext\n{\nprotected override void OnModelCreating(ModelBuilder builder)\n{\nbuilder.UseCollation(\"utf8mb4_unicode_ci\");\nbuilder.UseGuidCollation(\"utf8mb4_unicode_ci\");\nbase.OnModelCreating(builder);\n}\n}\n}\n
"},{"location":"user-guide/zh/problem/ef/#_3","title":"\u5168\u5c40\u8bbe\u7f6e\u5b57\u7b26\u4e32\u957f\u5ea6","text":"
  • \u5f53\u6570\u636e\u7c7b\u578b\u662f string \u65f6,\u9700\u8981\u7ed9\u6bcf\u4e2a\u5b57\u6bb5\u6307\u5b9a\u957f\u5ea6\u5f88\u9ebb\u70e6,\u4ee5\u4e0b\u63d0\u4f9b\u7edf\u4e00\u5904\u7406\u65b9\u5f0f\u3002
C#
namespace Lion.Pro.EntityFrameworkCore;\n/// <summary>\n/// ef\u8fc1\u79fb\u5168\u5c40\u8bbe\u7f6e\n/// </summary>\npublic static class LionDbContextGlobalSettingExtensions\n{\nprivate const string Remark = \"Remark\";\nprivate const string Description = \"Description\";\nprivate const string CreationTime = \"CreationTime\";\nprivate const string IndexPrefix = \"IX_Default_\";\npublic static void ConfigureGlobalSetting(this ModelBuilder builder)\n{\nConfigureDefaultMaxLength(builder);\nConfigureIndexForCreationTime(builder);\n}\n/// <summary>\n/// \u914d\u7f6e\u9ed8\u8ba4\u5b57\u7b26\u4e32\u957f\u5ea6\n/// </summary>\nprivate static void ConfigureDefaultMaxLength(ModelBuilder builder)\n{\nvar rules = ConfigureEntityMaxLengthOptions.Configure();\n// \u5982\u679c\u662fabp\u8868\u4e0d\u5168\u5c40\u4fee\u6539\u957f\u5ea6\nforeach (var property in builder.Model\n.GetEntityTypes()\n.Where(e => !e.GetTableName().StartsWith(AbpCommonDbProperties.DbTablePrefix))\n.SelectMany(t => t.GetProperties())\n.Where(e => e.ClrType == typeof(string)))\n{\n// \u9ed8\u8ba4\u8bbe\u7f6e128\u957f\u5ea6\nif (property.GetMaxLength() == null)\n{\nproperty.SetMaxLength(128);\n}\n}\n}\n/// <summary>\n/// \u521b\u5efa\u65f6\u95f4\u6dfb\u52a0\u7d22\u5f15\n/// </summary>\nprivate static void ConfigureIndexForCreationTime(ModelBuilder builder)\n{\nforeach (var property in builder.Model\n.GetEntityTypes()\n.Where(e => !e.GetTableName().StartsWith(AbpCommonDbProperties.DbTablePrefix))\n.SelectMany(t => t.GetProperties())\n.Where(e => e.Name == CreationTime))\n{\nvar entityType = builder.Model.GetEntityTypes().Where(e => e.ClrType == property.DeclaringEntityType.ClrType).ToList().FirstOrDefault();\nvar indexName = IndexPrefix + entityType.GetTableName() + \"_\" + property.Name;\nif (entityType.FindIndex(indexName) == null)\n{\nentityType?.AddIndex(property, indexName);\n}\n}\n}\n}\n
C#
namespace Lion.AbpPro.EntityFrameworkCore\n{\n[ConnectionStringName(\"Default\")]\npublic class AbpProDbContext :\nAbpDbContext<AbpProDbContext>,\nIAbpProDbContext\n{\nprotected override void OnModelCreating(ModelBuilder builder)\n{\nbuilder.ConfigureGlobalSetting();\nbase.OnModelCreating(builder);\n}\n}\n}\n
"},{"location":"user-guide/zh/problem/problem/","title":"\u7f16\u8bd1","text":""},{"location":"user-guide/zh/problem/problem/#vs-256","title":"VS \u7f16\u8bd1\u9879\u76ee\u5b57\u7b26\u4e32\u8d85\u8fc7 256 \u4e2a\u5b57\u7b26","text":"
  • \u628a\u9879\u76ee\u62f7\u8d1d\u5230\u78c1\u76d8\u6839\u76ee\u5f55 OR \u4f7f\u7528 Rider \u5f00\u53d1
"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..c02a87227 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,183 @@ + + + + http://cap.dotnetcore.xyz/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/about/6.0-7.0/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/about/contact-us/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/about/license/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/about/release-notes/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/deploy/docker/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/deploy/github/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/extension/MagicodesIE/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/extension/%E7%BB%9F%E4%B8%80%E8%BF%94%E5%9B%9E%E5%80%BC%E6%A0%BC%E5%BC%8F/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/getting-started/contributing/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/getting-started/introduction/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/getting-started/quick-start/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/AutoMapper/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/CorsOrigins/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/CurrentUser/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/DataFiltering/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/DataSeeding/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/MultiTenancy/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/authorization/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/batch/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/cache/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/cap/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/config/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/elastic/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/exception-handler/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/freesql/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/frontend/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/log/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/infrastructure/login/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/modules/basic/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/modules/dic/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/modules/file/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/modules/setting/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/modules/signalr/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/problem/ef/ + 2023-09-19 + daily + + + http://cap.dotnetcore.xyz/user-guide/zh/problem/problem/ + 2023-09-19 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..523c96c603f7491893d25a1bdd986306886775c5 GIT binary patch literal 574 zcmV-E0>S+siwFq13khWc|8r?{Wo=<_E_iKh0M*yMZrd;r0N_1OVHn^jQAzV-6}Eyn zm4OaMhb9j|lV^ztP0Hl4{(1URu~Qg9mktGV8YJpOke?{w4xB#jH)iw;e9+F$#@Bf< z9)XokY1_=kuP=*iGJdSCrqP=KI7*#un2lS+?knfJ-7XIrVI#VbJKyji+jJB7ImnB= z99LJPbo2w9TJOiyI9Z)Xk)q3$LklRKhkSoH@+t41Uv?J(H%?VI#m$$jD6{J_bvef_ zy2)192G$<7l@i(OBW~y^4L1o?a)sQ&D37?a1l3QOmc}-SF|+u6>%`r^SvQ_)|o;6kSIliGHF#J0E^{ z-Dn%=*`xPh@ih=!_iqBlLK_56BG>#y{}Vt_v@fGU{Q_24c_4=u5nJc=QP4>8pY$t% zvL&aMB5h6ysyljSXV;ouMuFaz M2EevFFXI^i0Fp2dbpQYW literal 0 HcmV?d00001 diff --git a/user-guide/zh/deploy/docker/index.html b/user-guide/zh/deploy/docker/index.html new file mode 100644 index 000000000..8a522bb37 --- /dev/null +++ b/user-guide/zh/deploy/docker/index.html @@ -0,0 +1,1615 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Docker - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

Docker 部署

+

后端

+
    +
  • 在 aspnetcore 目录下执行
  • +
  • 修改 appsetting.Production.json 配置
  • +
  • 数据库连接
  • +
  • Redis 连接
  • +
  • Rabbitmq 连接(可选)
  • +
+

Dockerfile

+
YAML
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+ENV TZ=Asia/Shanghai
+ENV ASPNETCORE_ENVIRONMENT=Production
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+WORKDIR /src
+COPY . .
+WORKDIR "/src/services/host/Lion.AbpPro.HttpApi.Host"
+RUN dotnet build "Lion.AbpPro.HttpApi.Host.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "Lion.AbpPro.HttpApi.Host.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "Lion.AbpPro.HttpApi.Host.dll"]
+
+

构建镜像

+
Bash
docker build -t Lion.AbpPro.HttpApi.Host .
+
+

启动容器

+
Bash
docker run -itd --name Lion.AbpPro.HttpApi.Host -p 8011:80 Lion.AbpPro.HttpApi.Host
+
+

前端

+
    +
  • 修改 env.production 接口地址为以上你发布的地址
  • +
  • 打包项目
  • +
+

Dockerfile

+
Text Only
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
FROM node:16-alpine as build-stage
+WORKDIR /app
+COPY . ./
+ENV NODE_OPTIONS=--max-old-space-size=16384
+RUN npm install pnpm -g
+RUN pnpm i
+RUN pnpm build
+
+
+FROM nginx:1.17.3-alpine as production-stage
+COPY --from=build-stage app/_nginx/nginx.conf /etc/nginx/nginx.conf
+COPY --from=build-stage app/_nginx/env.js /etc/nginx/env.js
+COPY --from=build-stage app/_nginx/default.conf /etc/nginx/conf.d/default.conf
+COPY --from=build-stage app/dist/ /usr/share/nginx/html
+EXPOSE 80
+
+CMD ["nginx", "-g", "daemon off;"]
+
+

构建镜像

+
Bash
docker build -t Lion.AbpPro.Vue3 .
+
+

启动容器

+
Bash
docker run -itd --name Lion.AbpPro.Vue3 -p 8012:80 Lion.AbpPro.Vue3
+
+
+

检查跨域设置,请查看跨域文档

+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/deploy/github/index.html b/user-guide/zh/deploy/github/index.html new file mode 100644 index 000000000..d18469923 --- /dev/null +++ b/user-guide/zh/deploy/github/index.html @@ -0,0 +1,1683 @@ + + + + + + + + + + + + + + + + + + + + + + + + Github自动化部署 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

Github自动化部署

+

添加部署 yaml

+
    +
  • 在项目根目录下添加 .github/workflow/
  • +
+

后端项目

+
YAML
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
name: 后端部署(API,IdentityServer4,Gateways) # 指定名称
+on:
+  push:
+    branches:
+      - main # 代码推送到main分支的时候触发jobs
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install Dotnet 6.x
+        uses: actions/setup-dotnet@v1
+        with:
+          dotnet-version: "6.0" # 安装Dotnet 环境
+          include-prerelease: True
+      - name: 编译
+        run: dotnet build aspnet-core/Lion.AbpPro.sln # 编译项目
+
+      - name: 单元测试
+        run: dotnet test aspnet-core/services/test/Lion.AbpPro.Domain.Tests/Lion.AbpPro.Domain.Tests.csproj # 运行单元测试
+
+      - name: 发布->Lion.AbpPro.HttpApi.Host
+        run: dotnet publish aspnet-core/services/host/Lion.AbpPro.HttpApi.Host/Lion.AbpPro.HttpApi.Host.csproj -o Lion.AbpPro.HttpApi.Host # 发布Host项目
+
+      - name: 发布->Lion.AbpPro.IdentityServer
+        run: dotnet publish aspnet-core/services/host/Lion.AbpPro.IdentityServer/Lion.AbpPro.IdentityServer.csproj -o Lion.AbpPro.IdentityServer # 发布IdentityServer项目
+
+      - name: 发布->Lion.AbpPro.IdentityServer
+        run: dotnet publish aspnet-core/gateways/Lion.AbpPro.WebGateway/Lion.AbpPro.WebGateway.csproj -o Lion.AbpPro.WebGateway # 发布网关项目
+
+      - name: 部署->Lion.AbpPro.HttpApi.Host
+        uses: easingthemes/ssh-deploy@v2.2.11
+        env:
+          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} # 服务器生成的ssh key 在github 下添加secret
+          ARGS: "-avzr --delete --exclude 'appsettings.json'" # 把发布好的项目复制到服务器,并且删除服务器上的/root/wwwroot/Lion.AbpPro.HttpApi.Host下的文件但是不包括appsettings.json
+          SOURCE: "Lion.AbpPro.HttpApi.Host" # 对应上面发布好的目录
+          REMOTE_HOST: ${{ secrets.REMOTE_HOST }} #  服务器公网ip地址
+          REMOTE_USER: ${{ secrets.REMOTE_USER }} #  用户名
+          TARGET: "/root/wwwroot" # 发布到服务器指定目录
+
+      - name: 部署->Lion.AbpPro.IdentityServer
+        uses: easingthemes/ssh-deploy@v2.2.11
+        env:
+          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
+          ARGS: "-avzr --delete --exclude 'appsettings.json'"
+          SOURCE: "Lion.AbpPro.IdentityServer"
+          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
+          REMOTE_USER: ${{ secrets.REMOTE_USER }}
+          TARGET: "/root/wwwroot"
+
+      - name: 部署->Lion.AbpPro.WebGateway
+        uses: easingthemes/ssh-deploy@v2.2.11
+        env:
+          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
+          ARGS: "-avzr --delete --exclude 'appsettings.json'"
+          SOURCE: "Lion.AbpPro.WebGateway"
+          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
+          REMOTE_USER: ${{ secrets.REMOTE_USER }}
+          TARGET: "/root/wwwroot"
+
+

安装 supervisor

+
Bash
1
+2
+3
yum install -y supervisor
+systemctl start supervisord
+systemctl enable supervisord # 设置为开机启动
+
+
    +
  • 默认配置目录在 /etc/supervisord.d
  • +
+
Bash
1
+2
+3
yum install -y supervisor
+systemctl start supervisord
+systemctl enable supervisord # 设置为开机启动
+
+
    +
  • 开启 web 管理界面
  • +
+
Bash
1
+2
+3
+4
+5
# vi vi /etc/supervisord.conf
+[inet_http_server]         ; inet (TCP) server disabled by default
+port=0.0.0.0:9001        ; (ip_address:port specifier, *:port for all iface)
+username=admin              ; # 管理web端登录用户名
+password=1q2w3E*.               ; # 管理web端登录密码
+
+
    +
  • +

    查看是否能访问 http://ip:9001 +

    +
  • +
  • +

    添加 Lion.AbpPro.HttpApi.Host.ini

    +
  • +
+
Bash
1
+2
+3
+4
+5
+6
+7
+8
[program:Lion.AbpPro.HttpApi.Host]
+command=/bin/bash -c "dotnet Lion.AbpPro.HttpApi.Host.dll --urls=http://*:8011"
+directory=/root/wwwroot/Lion.AbpPro.HttpApi.Host
+autostart=true
+autorestart=true
+stderr_logfile=/root/wwwroot/Lion.AbpPro.HttpApi.Host/err.log
+stdout_logfile=/root/wwwroot/Lion.AbpPro.HttpApi.Host/out.log
+user=root
+
+
    +
  • 添加 Lion.AbpPro.IdentityServer.ini
  • +
+
Bash
1
+2
+3
+4
+5
+6
+7
+8
[program:Lion.AbpPro.IdentityServer]
+command=/bin/bash -c "dotnet Lion.AbpPro.IdentityServer.dll --urls=http://*:8013"
+directory=/root/wwwroot/Lion.AbpPro.IdentityServer
+autostart=true
+autorestart=true
+stderr_logfile=/root/wwwroot/Lion.AbpPro.IdentityServer/err.log
+stdout_logfile=/root/wwwroot/Lion.AbpPro.IdentityServer/out.log
+user=root
+
+
    +
  • 添加 Lion.AbpPro.WebGateway.ini
  • +
+
Bash
1
+2
+3
+4
+5
+6
+7
+8
[program:Lion.AbpPro.WebGateway]
+command=/bin/bash -c "dotnet Lion.AbpPro.WebGateway.dll --urls=http://*:8014"
+directory=/root/wwwroot/Lion.AbpPro.WebGateway
+autostart=true
+autorestart=true
+stderr_logfile=/root/wwwroot/Lion.AbpPro.WebGateway/err.log
+stdout_logfile=/root/wwwroot/Lion.AbpPro.WebGateway/out.log
+user=root
+
+
    +
  • 重新加载配置 supervisorctl reload
  • +
+

前端配置

+
    +
  • 安装 Nginx
  • +
+
Bash
1
+2
+3
sudo yum install -y nginx
+systemctl start nginx # 启动 Nginx
+systemctl enable nginx # 启用开机启动 Nginx
+
+
    +
  • 访问 http://ip:80 +
  • +
+

-- 配置 Yml

+
YAML
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
name: 前端部署(vue)
+on:
+  push:
+    branches:
+      - main
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2.3.1
+        with:
+          persist-credentials: false
+
+      - name: 编译|发布
+        run: |
+          cd vben271
+          yarn
+          npm run build
+
+      - name: 部署->Vue
+        uses: easingthemes/ssh-deploy@v2.2.11
+        env:
+          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
+          ARGS: "-avzr --delete"
+          SOURCE: "vben271/dist"
+          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
+          REMOTE_USER: ${{ secrets.REMOTE_USER }}
+          TARGET: "/root/wwwroot"
+
+
    +
  • 配置 Nginx
  • +
+
Bash
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
# vi /etc/nginx/nginx.conf
+    server {
+        listen       8012;
+        listen       [::]:8012;
+        server_name  _;
+        root         /root/wwwroot/dist;
+
+        # Load configuration files for the default server block.
+        include /etc/nginx/default.d/*.conf;
+
+        #vue-router配置 解决刷新浏览器 404问题
+        location / {
+            try_files $uri $uri/ @router;
+            index index.html;
+        }
+        location @router {
+            rewrite ^.*$ /index.html last;
+        }
+
+        error_page 404 /404.html;
+        location = /404.html {
+        }
+
+        error_page 500 502 503 504 /50x.html;
+        location = /50x.html {
+        }
+    }
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/extension/MagicodesIE/index.html b/user-guide/zh/extension/MagicodesIE/index.html new file mode 100644 index 000000000..3fe60ec6c --- /dev/null +++ b/user-guide/zh/extension/MagicodesIE/index.html @@ -0,0 +1,1446 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Magicodes.IE - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

配置

+
C#
1
+2
+3
+4
+5
+6
+7
+8
/// <summary>
+/// 配置Magicodes.IE 导入导出
+/// </summary>
+private void ConfigureMagicodes(ServiceConfigurationContext context)
+{
+    context.Services.AddTransient<IExporter, ExcelExporter>();
+    context.Services.AddTransient<IExcelExporter, ExcelExporter>();
+}
+
+

示例

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
/// <summary>
+/// 用户导出列表
+/// </summary>
+/// <returns></returns>
+[Authorize(AbpProPermissions.SystemManagement.UserExport)]
+public async Task<ActionResult> ExportAsync(PagingUserListInput input)
+{
+    var request = new GetIdentityUsersInput
+    {
+        Filter = input.Filter?.Trim(),
+        MaxResultCount = input.PageSize,
+        SkipCount = input.SkipCount,
+        Sorting = " LastModificationTime desc"
+    };
+    List<Volo.Abp.Identity.IdentityUser> source = await _identityUserRepository
+        .GetListAsync(request.Sorting, request.MaxResultCount, request.SkipCount, request.Filter);
+    var result = ObjectMapper.Map<List<Volo.Abp.Identity.IdentityUser>, List<ExportIdentityUserOutput>>(source);
+    var bytes = await _excelExporter.ExportAsByteArray<ExportIdentityUserOutput>(result);
+    return new XlsxFileResult(bytes: bytes, fileDownloadName: $"用户导出列表{DateTime.Now:yyyyMMdd}");
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git "a/user-guide/zh/extension/\347\273\237\344\270\200\350\277\224\345\233\236\345\200\274\346\240\274\345\274\217/index.html" "b/user-guide/zh/extension/\347\273\237\344\270\200\350\277\224\345\233\236\345\200\274\346\240\274\345\274\217/index.html" new file mode 100644 index 000000000..453cfbc97 --- /dev/null +++ "b/user-guide/zh/extension/\347\273\237\344\270\200\350\277\224\345\233\236\345\200\274\346\240\274\345\274\217/index.html" @@ -0,0 +1,1610 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 统一返回值格式 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

统一返回值格式

+
    +
  • 在使用 abp 的过程中,如果提供给第三方接口要实现返回值统一需要怎么做?
  • +
+
C#
1
+2
+3
+4
+5
+6
+7
{
+    // 返回格式类似这种
+    "success": false,
+    "message": "请求失败",
+    "data": null,
+    "code": 500
+}
+
+
    +
  • 定义返回类型
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
public class WrapResult<T>
+{
+        public bool Success { get; private set; }
+
+        public string Message { get; private set; }
+
+        public T Data { get; private set; }
+
+        public int Code { get; private set; }
+
+        public WrapResult()
+        {
+            Success = true;
+            Message = "Success";
+            Data = default;
+            Code = 200;
+        }
+
+        public void SetSuccess(T data, string message = "Success", int code = 200)
+        {
+            Success = true;
+            Data = data;
+            Code = code;
+        }
+
+        public void SetFail(string message = "Fail", int code = 500)
+        {
+            Success = false;
+            Message = message;
+            Code = code;
+        }
+}
+
+

实现思路

+
    +
  • 定义 WrapResultAttribute
  • +
+
C#
1
+2
+3
public class WrapResultAttribute : Attribute
+{
+}
+
+ +

注册 Filter

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
    /// <summary>
+    /// 异常处理
+    /// </summary>
+    private void ConfigureAbpExceptions(ServiceConfigurationContext context)
+    {
+        context.Services.AddMvc
+        (
+            options =>
+            {
+                options.Filters.Add(typeof(LionExceptionFilter));
+                options.Filters.Add(typeof(LionResultFilter));
+            }
+        );
+    }
+
+    public override void ConfigureServices(ServiceConfigurationContext context)
+    {
+        ConfigureAbpExceptions(context);
+    }
+
+

使用

+
    +
  • 在 Controller 上或者 Action 上打上 WrapResultAttribute 特性
  • +
  • 例如
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
    [Route("Permissions")]
+    [WrapResult]
+    public class PermissionController : AbpProController,IRolePermissionAppService
+    {
+        private readonly IRolePermissionAppService _rolePermissionAppService;
+
+        public PermissionController(IRolePermissionAppService rolePermissionAppService)
+        {
+            _rolePermissionAppService = rolePermissionAppService;
+        }
+
+        [HttpPost("tree")]
+        [SwaggerOperation(summary: "获取角色权限", Tags = new[] { "Permissions" })]
+        [WrapResult] //控制器上打了 action上就不需要
+        public Task<PermissionOutput> GetPermissionAsync(GetPermissionInput input)
+        {
+            return _rolePermissionAppService.GetPermissionAsync(input);
+        }
+
+    }
+
+
    +
  • 不管接口时有异常还是成功返回结果都是 WrapResult,PermissionOutput 在 WrapResult.Data 中。
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/getting-started/contributing/index.html b/user-guide/zh/getting-started/contributing/index.html new file mode 100644 index 000000000..75d7abf8f --- /dev/null +++ b/user-guide/zh/getting-started/contributing/index.html @@ -0,0 +1,1420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 贡献 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

贡献

+

贡献最简单的方式之一就是参与讨论和issue讨论。

+

如果您有任何疑问或问题,请在Lion.AbpPro仓库中报告:

+

提交Issue

+

提交更改

+

您还可以通过提交代码更改PR来做出贡献。

+
+

Pull requests 可让您告诉其他人已推送到GitHub上存储库的更改。 打开 Pull requests 后,您可以与协作者讨论和审查做出的更改,并在更改合并到存储库之前添加后续提交。

+
+

其他资源

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/getting-started/introduction/index.html b/user-guide/zh/getting-started/introduction/index.html new file mode 100644 index 000000000..3ee4e72d3 --- /dev/null +++ b/user-guide/zh/getting-started/introduction/index.html @@ -0,0 +1,1570 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 介绍 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

介绍

+

Lion.AbpPro 是 Abp Vnext 的 Vue3 版本实现,同时也是免费开源。它有助于提高开发效率,属于开箱即用的后台管理系统,也能适用微服务。

+

后端项目结构

+
Bash
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
├── Directory.Build.props nuget 版本控制
+├── frameworks # 公共模块
+       ├── src #
+           ├── Lion.AbpPro.Core  # 核心扩展
+           ├── Lion.AbpPro.CAP # dotnetcore.cap
+           ├── Lion.AbpPro.CAP.EntityFrameworkCore # dotnetcore.cap ef 扩展
+           ├── Lion.AbpPro.Cli # cli
+           ├── Lion.AbpPro.Cli.Core # cli 核心
+           ├── Lion.AbpPro.ElasticSearch # es 扩展
+           ├── Lion.AbpPro.EntityFrameworkCore # ef 扩展 主要是批量新增
+           ├── Lion.AbpPro.EntityFrameworkCore.Mysql # mysql ef 扩展 主要是批量新增
+           ├── Lion.AbpPro.Starter # 首次启动
+           └── Lion.AbpPro.Localization # 本地化
+├── gateways # 网关
+       └── Lion.AbpPro.WebGateway # 基于ocelot网关
+├── modules # 模块
+       ├── BasicManagement # abp 基础模块封装
+       ├── DataDictionaryManagement # 数据字典
+       ├── LanguageManagement # 多语言
+       └── NotificationManagement # 通知服务
+├── services # 公共静态资源目录
+       ├── host # 启动模块
+           ├── CompanyName.ProjectName.HttpApi.Host # admin ui host
+       ├── src  # 源码
+           └── CompanyName.ProjectName.DbMigrator # 迁移控制台程序
+       └── test # 单元测试
+├── shared # 公共Host
+       ├── Lion.AbpPro.Shared.Hosting.Gateways # 网关host模块
+       └── Lion.AbpPro.Shared.Hosting.Microservices # 服务host模块
+
+

前端项目结构

+
Bash
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
├── _nginx # docker 打包
+├── build # 打包脚本相关
+   ├── config # 配置文件
+   ├── generate # 生成器
+   ├── script # 脚本
+   └── vite # vite配置
+├── mock # mock文件夹
+├── public # 公共静态资源目录
+├── src # 主目录
+   ├── api # 接口文件
+   ├── assets # 资源文件
+      ├── icons # icon sprite 图标文件夹
+      ├── images # 项目存放图片的文件夹
+      └── svg # 项目存放svg图片的文件夹
+   ├── components # 公共组件
+   ├── design # 样式文件
+   ├── directives # 指令
+   ├── enums # 枚举/常量
+   ├── hooks # hook
+      ├── component # 组件相关hook
+      ├── core # 基础hook
+      ├── event # 事件相关hook
+      ├── setting # 配置相关hook
+      └── web # web相关hook
+   ├── layouts # 布局文件
+      ├── default # 默认布局
+      ├── iframe # iframe布局
+      └── page # 页面布局
+   ├── locales # 多语言
+   ├── logics # 逻辑
+   ├── main.ts # 主入口
+   ├── router # 路由配置
+   ├── services # Nswag生成的代理
+      ├── ServiceProxies.ts # Nswag生成的代理
+      ├── ServiceProxyBase.ts # Nswag生成的代理拦截器
+   ├── settings # 项目配置
+      ├── componentSetting.ts # 组件配置
+      ├── designSetting.ts # 样式配置
+      ├── encryptionSetting.ts # 加密配置
+      ├── localeSetting.ts # 多语言配置
+      ├── projectSetting.ts # 项目配置
+      └── siteSetting.ts # 站点配置
+   ├── store # 数据仓库
+   ├── utils # 工具类
+   └── views # 页面
+├── test # 测试
+   └── server # 测试用到的服务
+       ├── api # 测试服务器
+       ├── upload # 测试上传服务器
+       └── websocket # 测试ws服务器
+├── types # 类型文件
+├── vite.config.ts # vite配置文件
+└── windi.config.ts # windcss配置文件
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/getting-started/quick-start/index.html b/user-guide/zh/getting-started/quick-start/index.html new file mode 100644 index 000000000..e39cac4af --- /dev/null +++ b/user-guide/zh/getting-started/quick-start/index.html @@ -0,0 +1,1555 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 快速开始 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

快速开始

+

先决条件

+ +

安装 CLI 工具

+
Bash
dotnet tool install Lion.AbpPro.Cli -g
+
+

创建项目

+
    +
  • 项目选择
  • +
+
Bash
1
+2
+3
+4
lion.abp new -t pro -c 公司名称 -p 项目名称  -o 输出路径(可选)
+lion.abp new -t pro.all -c 公司名称 -p 项目名称  -o 输出路径(可选)
+lion.abp new -t pro.simplify -c 公司名称 -p 项目名称  -o 输出路径(可选)
+lion.abp new -t pro.module -c 公司名称 -p 项目名称  -m 模块名称  -o 输出路径(可选)
+
+
    +
  • 创建示例项目
  • +
+
Bash
1
+2
# 创建一个公司名称为Acme项目名为BookStore的单体项目
+lion.abp new -t pro.simplify -c Acme -p BookStore
+
+

后端修改配置

+
    +
  • 修改 HttpApi.Host-> appsettings.json 配置
  • +
  • Mysql 连接字符串
  • +
  • Redis 连接字符串
  • +
  • 修改 DbMigrator-> appsettings.json 数据库连接字符串
  • +
  • 右键单击.DbMigrator 项目,设置为启动项目运行,按 F5(或 Ctrl + F5) 运行应用程序. 它将具有如下所示的输出: +
  • +
+
+

Note

+

初始的种子数据在数据库中创建了 admin 用户(密码为1q2w3E*) 用于登录应用程序. 所以, 对于新数据库至少使用 .DbMigrator 一次.

+
+
    +
  • 启动 HttpApi.Host,就能看到后台服务登陆页面,如下: +
  • +
+

前端

+ +

安装依赖

+
Bash
pnpm install
+
+

启动项目

+
Bash
pnpm run dev
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/AutoMapper/index.html b/user-guide/zh/infrastructure/AutoMapper/index.html new file mode 100644 index 000000000..eb304a3c7 --- /dev/null +++ b/user-guide/zh/infrastructure/AutoMapper/index.html @@ -0,0 +1,1661 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + AutoMapper - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

对象到对象映射

+

将对象映射到另一个对象是常用并且繁琐重复的工作,大部分情况下两个类都具有相同或相似的属性. ABP 提供了对象到对象映射的抽象并集成了AutoMapper做为对象映射器.

+

AutoMapper 集成

+

AutoMapper 是最流行的对象到对象映射库之一. Volo.Abp.AutoMapper程序包使用 AutoMapper 实现了 IObjectMapper.

+

定义映射

+

AutoMapper 提供了多种定义类之间映射的方法. 有关详细信息请参阅AutoMapper 的文档.

+

其中定义一种映射的方法是创建一个Profile 类. 例如:

+
C#
1
+2
+3
+4
+5
+6
+7
public class MyProfile : Profile
+{
+    public MyProfile()
+    {
+        CreateMap<User, UserDto>();
+    }
+}
+
+

然后使用AbpAutoMapperOptions注册配置文件:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
[DependsOn(typeof(AbpAutoMapperModule))]
+public class MyModule : AbpModule
+{
+    public override void ConfigureServices(ServiceConfigurationContext context)
+    {
+        Configure<AbpAutoMapperOptions>(options =>
+        {
+            //Add all mappings defined in the assembly of the MyModule class
+            options.AddMaps<MyModule>();
+        });
+    }
+}
+
+

AddMaps 注册给定类的程序集中所有的配置类,通常使用模块类. 它还会注册 attribute 映射.

+

配置验证

+

AddMaps 使用可选的 bool 参数控制模块配置验证:

+
C#
options.AddMaps<MyModule>(validate: true);
+
+

如果此选项默认是 false , 但最佳实践建议启用. +可以使用 AddProfile 而不是 AddMaps 来控制每个配置文件类的配置验证:

+
C#
options.AddProfile<MyProfile>(validate: true);
+
+
+

如果你有多个配置文件,并且只需要为其中几个启用验证,那么首先使用AddMaps而不进行验证,然后为你想要验证的每个配置文件使用AddProfile.

+
+

映射对象扩展

+

对象扩展系统 允许为已存在的类定义额外属性. ABP 框架提供了一个映射定义扩展可以正确的映射两个对象的额外属性.

+
C#
1
+2
+3
+4
+5
+6
+7
+8
public class MyProfile : Profile
+{
+    public MyProfile()
+    {
+        CreateMap<User, UserDto>()
+            .MapExtraProperties();
+    }
+}
+
+

如果两个类都是可扩展对象(实现了 IHasExtraProperties 接口),建议使用 MapExtraProperties 方法. 更多信息请参阅对象扩展文档.

+

其他有用的扩展方法

+

有一些扩展方法可以简化映射代码.

+

忽视审计属性

+

当你将一个对象映射到另一个对象时,通常会忽略审核属性.

+

假设你需要将 ProductDto (DTO)映射到 Product实体,该实体是从 AuditedEntity 类继承的(该类提供了 CreationTime, CreatorId, IHasModificationTime 等属性).

+

从 DTO 映射时你可能想忽略这些基本属性,可以使用 IgnoreAuditedObjectPropertie() 方法忽略所有审计属性(而不是手动逐个忽略它们):

+
C#
1
+2
+3
+4
+5
+6
+7
+8
public class MyProfile : Profile
+{
+    public MyProfile()
+    {
+        CreateMap<ProductDto, Product>()
+            .IgnoreAuditedObjectProperties();
+    }
+}
+
+

还有更多扩展方法, 如 IgnoreFullAuditedObjectProperties()IgnoreCreationAuditedObjectProperties(),你可以根据实体类型使用.

+
+

请参阅实体文档中的"基类和接口的审计属性"部分了解有关审计属性的更多信息。

+
+

忽视其他属性

+

在 AutoMapper 中,通常可以编写这样的映射代码来忽略属性:

+
C#
1
+2
+3
+4
+5
+6
+7
+8
public class MyProfile : Profile
+{
+    public MyProfile()
+    {
+        CreateMap<SimpleClass1, SimpleClass2>()
+            .ForMember(x => x.CreationTime, map => map.Ignore());
+    }
+}
+
+

我们发现它的长度是不必要的并且创建了 Ignore() 扩展方法:

+
C#
1
+2
+3
+4
+5
+6
+7
+8
public class MyProfile : Profile
+{
+    public MyProfile()
+    {
+        CreateMap<SimpleClass1, SimpleClass2>()
+            .Ignore(x => x.CreationTime);
+    }
+}
+
+

使用

+
C#
1
+2
+3
+4
+5
+6
// 注入IObjectMapper
+public virtual async Task<LanguageDto> GetAsync(string cultureName)
+{
+    var entity = await _languageRepository.FindAsync(cultureName);
+    return ObjectMapper.Map<Language, LanguageDto>(entity);
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/CorsOrigins/index.html b/user-guide/zh/infrastructure/CorsOrigins/index.html new file mode 100644 index 000000000..a074dd7d3 --- /dev/null +++ b/user-guide/zh/infrastructure/CorsOrigins/index.html @@ -0,0 +1,1443 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跨域 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

跨域

+ +

跨域(CORS)

+
    +
  • 允许指定策略
  • +
+
appsetting.json
1
+2
+3
+4
"App": {
+    // 逗号分隔
+    "CorsOrigins": "http://*.com,http://localhost:4200"
+  },
+
+
    +
  • 配置跨域
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
private void ConfigureCors(ServiceConfigurationContext context)
+{
+    var configuration = context.Services.GetConfiguration();
+    context.Services.AddCors(options =>
+    {
+        options.AddPolicy(DefaultCorsPolicyName, builder =>
+        {
+            builder
+                .WithOrigins(
+                    configuration["App:CorsOrigins"]
+                        .Split(",", StringSplitOptions.RemoveEmptyEntries)
+                        .Select(o => o.RemovePostFix("/"))
+                        .ToArray()
+                )
+                .WithAbpExposedHeaders()
+                .SetIsOriginAllowedToAllowWildcardSubdomains()
+                .AllowAnyHeader()
+                .AllowAnyMethod()
+                .AllowCredentials();
+        });
+    });
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/CurrentUser/index.html b/user-guide/zh/infrastructure/CurrentUser/index.html new file mode 100644 index 000000000..a9ad118c9 --- /dev/null +++ b/user-guide/zh/infrastructure/CurrentUser/index.html @@ -0,0 +1,1725 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 当前用户 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

当前用户

+

在 Web 应用程序中检索有关已登录用户的信息是很常见的. 当前用户是与 Web 应用程序中的当前请求相关的活动用户.

+

ICurrentUser

+

ICurrentUser 是主要的服务,用于获取有关当前活动的用户信息.

+

示例: [注入] ICurrentUser 到服务中:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
using System;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Users;
+
+namespace AbpDemo
+{
+    public class MyService : ITransientDependency
+    {
+        private readonly ICurrentUser _currentUser;
+
+        public MyService(ICurrentUser currentUser)
+        {
+            _currentUser = currentUser;
+        }
+
+        public void Foo()
+        {
+            Guid? userId = _currentUser.Id;
+        }
+    }
+}
+
+

公共基类已经将此服务作为基本属性注入. 例如你可以直接在应用服务中使用 CurrentUser 属性:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
using System;
+using Volo.Abp.Application.Services;
+
+namespace AbpDemo
+{
+    public class MyAppService : ApplicationService
+    {
+        public void Foo()
+        {
+            Guid? userId = CurrentUser.Id;
+        }
+    }
+}
+
+

属性

+

以下是 ICurrentUser 接口的基本属性:

+
    +
  • IsAuthenticated 如果当前用户已登录(已认证),则返回 true. 如果用户尚未登录,则 IdUserName 将返回 null.
  • +
  • Id (Guid?): 当前用户的 Id,如果用户未登录,返回 null.
  • +
  • UserName (string): 当前用户的用户名称. 如果用户未登录,返回 null.
  • +
  • TenantId (Guid?): 当前用户的租户 Id. 对于多租户 应用程序很有用. 如果当前用户未分配给租户,返回 null.
  • +
  • Email (string): 当前用户的电子邮件地址. 如果当前用户尚未登录或未设置电子邮件地址,返回 null.
  • +
  • EmailVerified (bool): 如果当前用户的电子邮件地址已经过验证,返回 true.
  • +
  • PhoneNumber (string): 当前用户的电话号码. 如果当前用户尚未登录或未设置电话号码,返回 null.
  • +
  • PhoneNumberVerified (bool): 如果当前用户的电话号码已经过验证,返回 true.
  • +
  • Roles (string[]): 当前用户的角色. 返回当前用户角色名称的字符串数组.
  • +
+

Methods

+

ICurrentUser 是在 ICurrentPrincipalAccessor 上实现的(请参阅以下部分),并可以处理声明. 实际上所有上述属性都是从当前经过身份验证的用户的声明中检索的.

+

如果你有自定义声明或获取其他非常见声明类型, ICurrentUser 有一些直接使用声明的方法.

+
    +
  • FindClaim: 获取给定名称的声明,如果未找到返回 null.
  • +
  • FindClaims: 获取具有给定名称的所有声明(允许具有相同名称的多个声明值).
  • +
  • GetAllClaims: 获取所有声明.
  • +
  • IsInRole: 一种检查当前用户是否在指定角色中的简化方法.
  • +
+

除了这些标准方法,还有一些扩展方法:

+
    +
  • FindClaimValue: 获取具有给定名称的声明的值,如果未找到返回 null. 它有一个泛型重载将值强制转换为特定类型.
  • +
  • GetId: 返回当前用户的 Id. 如果当前用户没有登录它会抛出一个异常(而不是返回null). 仅在你确定用户已经在你的代码上下文中进行了身份验证时才使用此选项.
  • +
+

验证和授权

+

ICurrentUser 的工作方式与用户的身份验证或授权方式无关. 它可以与使用当前主体的任何身份验证系统无缝地配合使用(请参阅下面的部分).

+

ICurrentPrincipalAccessor

+

ICurrentPrincipalAccessor 是当需要当前用户的 Principal 时使用的服务(由 ABP 框架和你的应用程序代码使用).

+

对于 Web 应用程序, 它获取当前 HttpContextUser 属性,对于非 Web 应用程序它将返回 Thread.CurrentPrincipal.

+
+

通常你不需要这种低级别的 ICurrentPrincipalAccessor 服务,直接使用上述的 ICurrentUser 即可.

+
+

基本用法

+

你可以注入 ICurrentPrincipalAccessor 并且使用 Principal 属性获取当前 principal:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
public class MyService : ITransientDependency
+{
+    private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
+
+    public MyService(ICurrentPrincipalAccessor currentPrincipalAccessor)
+    {
+        _currentPrincipalAccessor = currentPrincipalAccessor;
+    }
+
+    public void Foo()
+    {
+        var allClaims = _currentPrincipalAccessor.Principal.Claims.ToList();
+        //...
+    }
+}
+
+

更改当前 Principal

+

除了某些高级场景外,你不需要设置或更改当前 Principal. 如果需要可以使用 ICurrentPrincipalAccessorChange 方法. 它接受一个 ClaimsPrincipal 对象并使其成为作用域的"当前"对象.

+

示例:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
public class MyAppService : ApplicationService
+{
+    private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor;
+
+    public MyAppService(ICurrentPrincipalAccessor currentPrincipalAccessor)
+    {
+        _currentPrincipalAccessor = currentPrincipalAccessor;
+    }
+
+    public void Foo()
+    {
+        var newPrincipal = new ClaimsPrincipal(
+            new ClaimsIdentity(
+                new Claim[]
+                {
+                    new Claim(AbpClaimTypes.UserId, Guid.NewGuid().ToString()),
+                    new Claim(AbpClaimTypes.UserName, "john"),
+                    new Claim("MyCustomCliam", "42")
+                }
+            )
+        );
+
+        using (_currentPrincipalAccessor.Change(newPrincipal))
+        {
+            var userName = CurrentUser.UserName; //returns "john"
+            //...
+        }
+    }
+}
+
+

始终在 using 语句中使用 Change 方法,在 using 范围结束后它将恢复为原始值.

+

这可以是一种模拟用户登录的应用程序代码范围的方法,但是请尝试谨慎使用它.

+

AbpClaimTypes

+

AbpClaimTypes 是一个静态类它定义了标准声明的名称被 ABP 框架使用.

+
    +
  • +

    UserName, UserId, RoleEmail 属性的默认值是通常System.Security.Claims.ClaimTypes类设置的, 但你可以改变它们.

    +
  • +
  • +

    其他属性,如 EmailVerified, PhoneNumber, TenantId ...是由 ABP 框架通过尽可能遵循标准名称来定义的.

    +
  • +
+

建议使用这个类的属性来代替声明名称的魔术字符串.

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/DataFiltering/index.html b/user-guide/zh/infrastructure/DataFiltering/index.html new file mode 100644 index 000000000..c550f042c --- /dev/null +++ b/user-guide/zh/infrastructure/DataFiltering/index.html @@ -0,0 +1,1696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 数据过滤 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

数据过滤

+

Volo.Abp.Data 包定义了在查询数据库时自动过滤数据的服务.

+

预定义的过滤

+

ISoftDelete

+

将实体标记为已删除,并不是物理删除. 实现 ISoftDelete 接口将你的实体"软删除".

+

示例:

+
C#
1
+2
+3
+4
+5
+6
+7
+8
+9
namespace Acme.BookStore
+{
+    public class Book : AggregateRoot<Guid>, ISoftDelete
+    {
+        public string Name { get; set; }
+
+        public bool IsDeleted { get; set; } //Defined by ISoftDelete
+    }
+}
+
+

ISoftDelete 定义了 IsDeleted 属性. 当你使用仓储删除一条记录时, ABP 会自动将 IsDeleted 设置为 true,并将删除操作替换为修改操作(如果需要,也可以手动将 IsDeleted 设置为 true). 在查询数据库时会自动过滤软删除的实体.

+
+

ISoftDelete 过滤默认启用, 想要真正的从数据库删除实体需要显示的禁用过滤. 参见下面提到的 IDataFilter 服务.

+
+

IMultiTenant

+

[多租户]是创建 SaaS 应用程序的有效方法. 多租户应用程序通常需要在租户间隔离数据. 实现 IMultiTenant 接口使你的实体支持 "多租户".

+

示例:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
namespace Acme.BookStore
+{
+    public class Book : AggregateRoot<Guid>, ISoftDelete, IMultiTenant
+    {
+        public string Name { get; set; }
+
+        public bool IsDeleted { get; set; } //Defined by ISoftDelete
+
+        public Guid? TenantId { get; set; } //Defined by IMultiTenant
+    }
+}
+
+

IMultiTenant 接口定义了 TenantId 属性用于自动过滤当前租户实体. 更多信息参见[多租户]文档.

+

IDataFilter 服务: 启用/禁用 数据过滤

+

你可以使用 IDataFilter 服务控制数据过滤.

+

示例:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
namespace Acme.BookStore
+{
+    public class MyBookService : ITransientDependency
+    {
+        private readonly IDataFilter _dataFilter;
+        private readonly IRepository<Book, Guid> _bookRepository;
+
+        public MyBookService(
+            IDataFilter dataFilter,
+            IRepository<Book, Guid> bookRepository)
+        {
+            _dataFilter = dataFilter;
+            _bookRepository = bookRepository;
+        }
+
+        public async Task<List<Book>> GetAllBooksIncludingDeletedAsync()
+        {
+            //Temporary disable the ISoftDelete filter
+            using (_dataFilter.Disable<ISoftDelete>())
+            {
+                return await _bookRepository.GetListAsync();
+            }
+        }
+    }
+}
+
+
    +
  • [注入] IDataFilter 服务到你的类中.
  • +
  • using 语句中使用 Disable 方法创建一个代码块,其中禁用了 ISoftDelete 过滤器(始终与 using 搭配使用,确保代码块执行后将过滤重置为之前的状态).
  • +
+

IDataFilter.Enable 方法可以启用过滤. 可以嵌套使用 EnableDisable 方法定义内部作用域.

+

AbpDataFilterOptions

+

AbpDataFilterOptions 用于设置数据过滤系统.

+

下面的示例代码在默认情况下禁用了 ISoftDelete 过滤,除非显示启用,在查询数据库时会包含标记为已删除的实体:

+
C#
1
+2
+3
+4
Configure<AbpDataFilterOptions>(options =>
+{
+    options.DefaultStates[typeof(ISoftDelete)] = new DataFilterState(isEnabled: false);
+});
+
+
+

更改全局过滤的默认值需要小心,特别是在你使用预构建的模块时该模块可能是在默认启用软删除过滤的情况下开发的. 但你可以安全的为自己定义的数据过滤执行此操作.

+
+

自定义数据过滤

+

定义和实现新的过滤很大程序上取决与数据库提供者. ABP 为所有的数据库提供者实现了预构建的过滤.

+

首先为过滤定义一个接口 (如 ISoftDeleteIMultiTenant) 然后用实体实现它.

+

示例:

+
C#
1
+2
+3
+4
public interface IIsActive
+{
+    bool IsActive { get; }
+}
+
+

IIsActive 接口可以过滤活跃/消极数据,任何[实体]都可以实现它:

+
C#
1
+2
+3
+4
+5
+6
public class Book : AggregateRoot<Guid>, IIsActive
+{
+    public string Name { get; set; }
+
+    public bool IsActive { get; set; } //Defined by IIsActive
+}
+
+

EntityFramework Core

+

ABP 使用EF Core 的全局过滤系统用于[EF Core 集成]. 所以它很好的集成到 EF Core 中,即使你直接使用 DbContext 它也可以正常工作.

+

实现自定义过滤的最佳方法是为重写你的 DbContextShouldFilterEntityCreateFilterExpression 方法. 示例:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
+
+protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
+{
+    if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
+    {
+        return true;
+    }
+
+    return base.ShouldFilterEntity<TEntity>(entityType);
+}
+
+protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
+{
+    var expression = base.CreateFilterExpression<TEntity>();
+
+    if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
+    {
+        Expression<Func<TEntity, bool>> isActiveFilter =
+            e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
+        expression = expression == null
+            ? isActiveFilter
+            : CombineExpressions(expression, isActiveFilter);
+    }
+
+    return expression;
+}
+
+
    +
  • 添加 IsActiveFilterEnabled 属性用于检查是否启用了 IIsActive . 内部使用了之前介绍到的 IDataFilter 服务.
  • +
  • 重写 ShouldFilterEntityCreateFilterExpression 方法检查给定实体是否实现 IIsActive 接口,在必要时组合表达式.
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/DataSeeding/index.html b/user-guide/zh/infrastructure/DataSeeding/index.html new file mode 100644 index 000000000..2b6b522fa --- /dev/null +++ b/user-guide/zh/infrastructure/DataSeeding/index.html @@ -0,0 +1,1515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 种子数据 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

种子数据

+

介绍

+
    +
  • 使用数据库的某些应用程序(或模块),可能需要有一些初始数据才能够正常启动和运行. 例如管理员用户和角色必须在一开始就可用. 否则你就无法登录到应用程序创建新用户和角色.
  • +
  • 数据种子也可用于测试的目的,你的自动测试可以假定数据库中有一些可用的初始数据.
  • +
+

IDataSeedContributor

+

将数据种子化到数据库需要实现IDataSeedContributor接口.

+

示例: 如果没有图书,则向数据库播种一个初始图书

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
namespace Acme.BookStore
+{
+    public class BookStoreDataSeedContributor : IDataSeedContributor, ITransientDependency
+    {
+        private readonly IRepository<Book, Guid> _bookRepository;
+        private readonly IGuidGenerator _guidGenerator;
+        private readonly ICurrentTenant _currentTenant;
+
+        public BookStoreDataSeedContributor(
+            IRepository<Book, Guid> bookRepository,
+            IGuidGenerator guidGenerator,
+            ICurrentTenant currentTenant)
+        {
+            _bookRepository = bookRepository;
+            _guidGenerator = guidGenerator;
+            _currentTenant = currentTenant;
+        }
+
+        public async Task SeedAsync(DataSeedContext context)
+        {
+            using (_currentTenant.Change(context?.TenantId))
+            {
+                if (await _bookRepository.GetCountAsync() > 0)
+                {
+                    return;
+                }
+
+                var book = new Book(
+                    id: _guidGenerator.Create(),
+                    name: "The Hitchhiker's Guide to the Galaxy",
+                    type: BookType.ScienceFiction,
+                    publishDate: new DateTime(1979, 10, 12),
+                    price: 42
+                );
+
+                await _bookRepository.InsertAsync(book);
+            }
+        }
+    }
+}
+
+
    +
  • IDataSeedContributor 定义了 SeedAsync 方法用于执行 数据种子逻辑.
  • +
  • 通常检查数据库是否已经存在种子数据.
  • +
  • 你可以注入服务,检查数据播种所需的任何逻辑.
  • +
+
+

数据种子贡献者由 ABP 框架自动发现,并作为数据播种过程的一部分执行.

+
+

模块化

+

一个应用程序可以具有多个种子数据贡献者(IDataSeedContributor)类. 任何可重用模块也可以实现此接口播种其自己的初始数据.

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/MultiTenancy/index.html b/user-guide/zh/infrastructure/MultiTenancy/index.html new file mode 100644 index 000000000..83efaca7b --- /dev/null +++ b/user-guide/zh/infrastructure/MultiTenancy/index.html @@ -0,0 +1,1684 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 多租户 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

多租户

+

定义实体

+

你可以在你的实体中实现 IMultiTenant 接口来实现多租户,例如:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
namespace MyCompany.MyProject
+{
+    public class Product : AggregateRoot, IMultiTenant
+    {
+        public Guid? TenantId { get; set; } //IMultiTenant 定义了 TenantId 属性
+
+        public string Name { get; set; }
+
+        public float Price { get; set; }
+    }
+}
+
+

实现 IMultiTenant 接口,需要在实体中定义一个 TenantId 的属性

+

获取当前租户

+

你的代码中可能需要获取当前租户(先不管它具体是怎么取得的).对于这种情况你可以注入并使用 ICurrentTenant 接口.例如:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
namespace MyCompany.MyProject
+{
+    public class MyService : ITransientDependency
+    {
+        private readonly ICurrentTenant _currentTenant;
+
+        public MyService(ICurrentTenant currentTenant)
+        {
+            _currentTenant = currentTenant;
+        }
+
+        public void DoIt()
+        {
+            var tenantId = _currentTenant.Id;
+            //在你的代码中使用tenantId
+        }
+    }
+}
+
+

改变当前租户

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
namespace MultiTenancyDemo.Products
+{
+    public class ProductManager : DomainService
+    {
+        private readonly IRepository<Product, Guid> _productRepository;
+
+        public ProductManager(IRepository<Product, Guid> productRepository)
+        {
+            _productRepository = productRepository;
+        }
+
+        public async Task<long> GetProductCountAsync(Guid? tenantId)
+        {
+            using (CurrentTenant.Change(tenantId))
+            {
+                return await _productRepository.GetCountAsync();
+            }
+        }
+    }
+}
+
+

确定当前租户

+

多租户的应用程序运行的时候首先要做的就是确定当前租户. +Volo.Abp.MultiTenancy 只提供了用于确定当前租户的抽象(称为租户解析器),但是并没有现成的实现. +Volo.Abp.AspNetCore.MultiTenancy 已经实现了从当前 Web 请求(从子域名,请求头,cookie,路由...等)中确定当前租户.本文后面会介绍 Volo.Abp.AspNetCore.MultiTenancy.

+

自定义租户解析器

+

你可以像下面这样,在你模块的 ConfigureServices 方法中将自定义解析器并添加到 AbpTenantResolveOptions 中:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
namespace MyCompany.MyProject
+{
+    [DependsOn(typeof(AbpMultiTenancyModule))]
+    public class MyModule : AbpModule
+    {
+        public override void ConfigureServices(ServiceConfigurationContext context)
+        {
+            Configure<AbpTenantResolveOptions>(options =>
+            {
+                options.TenantResolvers.Add(new MyCustomTenantResolveContributor());
+            });
+
+            //...
+        }
+    }
+}
+
+

MyCustomTenantResolveContributor必须像下面这样实现 ITenantResolveContributor 接口:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
namespace MyCompany.MyProject
+{
+    public class MyCustomTenantResolveContributor : ITenantResolveContributor
+    {
+        public override Task ResolveAsync(ITenantResolveContext context)
+        {
+            context.TenantIdOrName = ... //从其他地方获取租户id或租户名字...
+        }
+    }
+}
+
+

如果能确定租户 id 或租户名字可以在租户解析器中设置 TenantIdOrName.如果不能确定,那就空着让下一个解析器来确定它.

+

租户信息

+

ITenantStore 跟 TenantConfiguration 类一起工作,并且包含了几个租户属性:

+
    +
  • Id:租户的唯一 Id.
  • +
  • Name: 租户的唯一名称.
  • +
  • ConnectionStrings:如果这个租户有专门的数据库来存储数据.它可以提供数据库的字符串(它可以具有默认的连接字符串和每个模块的连接字符串).
  • +
+

多租户中间件

+

Volo.Abp.AspNetCore.MultiTenancy 包含了多租户中间件...

+
C#
app.UseMultiTenancy();
+
+

从 Web 请求中确定当前租户

+

Volo.Abp.AspNetCore.MultiTenancy 添加了下面这些租户解析器,从当前 Web 请求(按优先级排序)中确定当前租户.

+
    +
  • CurrentUserTenantResolveContributor: 如果当前用户已登录,从当前用户的声明中获取租户 Id. 出于安全考虑,应该始终将其做为第一个 Contributor.
  • +
  • QueryStringTenantResolveContributor: 尝试从 query string 参数中获取当前租户,默认参数名为"__tenant".
  • +
  • RouteTenantResolveContributor:尝试从当前路由中获取(URL 路径),默认是变量名是"__tenant".所以,如果你的路由中定义了这个变量,就可以从路由中确定当前租户.
  • +
  • HeaderTenantResolveContributor: 尝试从 HTTP header 中获取当前租户,默认的 header 名称是"__tenant".
  • +
  • CookieTenantResolveContributor: 尝试从当前 cookie 中获取当前租户.默认的 Cookie 名称是"__tenant".
  • +
+
+

如果你使用 nginx 作为反向代理服务器,请注意如果TenantKey包含下划线或其他特殊字符可能存在问题, 请参考: +http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers > http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers

+
+

可以使用 AbpAspNetCoreMultiTenancyOptions 修改默认的参数名"__tenant".例如:

+
C#
1
+2
+3
+4
services.Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
+{
+    options.TenantKey = "MyTenantKey";
+});
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/authorization/index.html b/user-guide/zh/infrastructure/authorization/index.html new file mode 100644 index 000000000..654530267 --- /dev/null +++ b/user-guide/zh/infrastructure/authorization/index.html @@ -0,0 +1,1737 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 授权 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

授权

+

授权用于在应用程序中判断是否允许用户执行某些特定的操作.

+

ABP 扩展了ASP.NET Core 授权, 将权限添加为自动策略并且使授权系统在 应用服务 同样可用.

+

Authorize Attribute

+

ASP.NET Core 定义了 Authorize特性用于在控制器,控制器方法以及页面上授权. 现在 ABP 将它带到了应用服务.

+

示例:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Volo.Abp.Application.Services;
+
+namespace Acme.BookStore
+{
+    [Authorize]
+    public class AuthorAppService : ApplicationService, IAuthorAppService
+    {
+        public Task<List<AuthorDto>> GetListAsync()
+        {
+            ...
+        }
+
+        [AllowAnonymous]
+        public Task<AuthorDto> GetAsync(Guid id)
+        {
+            ...
+        }
+
+        [Authorize("BookStore_Author_Create")]
+        public Task CreateAsync(CreateAuthorDto input)
+        {
+            ...
+        }
+    }
+}
+
+
    +
  • Authorize用户必须登陆到应用程序才可以访问 AuthorAppService 中的方法. 所以GetListAsync 方法仅可用于通过身份验证的用户.
  • +
  • AllowAnonymous 禁用身份验证. 所以 GetAsync 方法任何人都可以访问,包括未授权的用户.
  • +
  • [Authorize("BookStore_Author_Create")] 定义了一个策略 (参阅 基于策略的授权),它用于检查当前用户的权限."BookStore_Author_Create" 是一个策略名称. 如果你想要使用策略的授权方式,需要在 ASP.NET Core 授权系统中预先定义它.
  • +
+

定义权限

+

创建一个继承自 PermissionDefinitionProvider 的类,如下所示:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
using Volo.Abp.Authorization.Permissions;
+
+namespace Acme.BookStore.Permissions
+{
+    public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
+    {
+        public override void Define(IPermissionDefinitionContext context)
+        {
+            var myGroup = context.AddGroup("BookStore");
+
+            myGroup.AddPermission("BookStore_Author_Create");
+        }
+    }
+}
+
+
+

ABP 会自动发现这个类,不需要进行配置!

+
+

Define 方法中添加权限组或者获取已存在的权限组,并向权限组中添加权限. +在定义权限后就可以在 ASP.NET Core 权限系统中当做策略名称使用. 在角色的权限管理模态框中同样可以看到: +

+

多租户

+

在定义新权限时可以设置多租户选项. 有下面三个值:

+
    +
  • Host: 权限仅适用于宿主.
  • +
  • Tenant: 权限仅适用于租户.
  • +
  • Both (默认): 权限适用与宿主和租户.
  • +
+
+

如果你的应用程序不是多租户的,可以忽略这个选项.

+
+

AddPermission 方法的第三个参数用于设置多租户选项:

+
C#
1
+2
+3
+4
+5
myGroup.AddPermission(
+    "BookStore_Author_Create",
+    LocalizableString.Create<BookStoreResource>("Permission:BookStore_Author_Create"),
+    multiTenancySide: MultiTenancySides.Tenant //set multi-tenancy side!
+);
+
+

前端权限

+

菜单权限

+
TypeScript
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
import type { AppRouteModule } from "/@/router/types";
+import { LAYOUT } from "/@/router/constant";
+import { t } from "/@/hooks/web/useI18n";
+const tenant: AppRouteModule = {
+  path: "/tenant",
+  name: "Tenant",
+  component: LAYOUT,
+  meta: {
+    orderNo: 30,
+    icon: "ant-design:contacts-outlined",
+    title: t("routes.tenant.tenantManagement"),
+  },
+  children: [
+    {
+      path: "Tenant",
+      name: "Tenant",
+      component: () => import("/@/views/tenants/Tenant.vue"),
+      meta: {
+        title: t("routes.tenant.tenantList"),
+        icon: "ant-design:switcher-filled",
+        policy: "AbpTenantManagement.Tenants", //菜单权限
+      },
+    },
+  ],
+};
+
+export default tenant;
+
+

按钮权限

+
TypeScript
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
<template>
+  <div>
+    <BasicTable @register="registerTable" size="small">
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              icon: 'ant-design:edit-outlined',
+              auth: 'AbpIdentity.Users.Update', // 按钮权限
+              label: t('common.editText'),
+              onClick: handleEdit.bind(null, record),
+            },
+          ]"
+          :dropDownActions="[
+            {
+              auth: 'AbpIdentity.Users.Delete', // 按钮权限
+              label: t('common.delText'),
+              onClick: handleDelete.bind(null, record),
+            },
+            {
+              auth: 'System.Users.Enable', // 按钮权限
+              label: !record.isActive
+                ? t('common.enabled')
+                : t('common.disEnabled'),
+              onClick: handleLock.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <CreateAbpUser
+      @register="registerCreateAbpUserModal"
+      @reload="reload"
+      :bodyStyle="{ 'padding-top': '0' }"
+    />
+    <EditAbpUser
+      @register="registerEditAbpUserModal"
+      @reload="reload"
+      :bodyStyle="{ 'padding-top': '0' }"
+    />
+  </div>
+</template>
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/batch/index.html b/user-guide/zh/infrastructure/batch/index.html new file mode 100644 index 000000000..f5ba429c7 --- /dev/null +++ b/user-guide/zh/infrastructure/batch/index.html @@ -0,0 +1,1737 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 批量操作 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

批量操作

+

EFCore7.0 之后,提供了批量更新和批量删除,但是不提供批量新增, +微软 EFCore 批量操作文档

+

安装

+
    +
  • 添加以下 NuGet 包到你的项目
  • +
  • Lion.AbpPro.EntityFrameworkCore.Mysql
  • +
  • 添加 [DependsOn(typeof(AbpProEntityFrameworkCoreMysqlModule))] 到你的项目模块类.
  • +
+

原理

+
    +
  • 通过 MySqlBulkCopy 来实现批量新增官方文档
  • +
  • 实现 Abp 批量操作接口 IEfCoreBulkOperationProvider
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
public class EfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
+{
+    /// <summary>
+    /// 批量新增
+    /// </summary>
+    ///<remarks>
+    /// <para>
+    /// - mysql启用:SET GLOBAL local_infile = true;
+    /// </para>
+    /// <para>
+    /// - 数据库连接字符串需要加上:AllowLoadLocalInfile=true
+    /// </para>
+    /// - abp的审计字段需要手动赋值,比如创建人,创建时间,或者使用AuditPropertySetter
+    /// <para>
+    /// - 只支持单表,比如有一个Blog表和Post表一对多关系,需要调用两次 InsertManyAsync
+    /// </para>
+    /// </remarks>
+    public virtual async Task InsertManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository, IEnumerable<TEntity> entities, bool autoSave, CancellationToken cancellationToken)
+        where TDbContext : IEfCoreDbContext where TEntity : class, IEntity
+    {
+        var dbContext = await repository.GetDbContextAsync();
+        var dbTransaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();
+        await dbContext.BulkInsertAsync(entities, dbTransaction as MySqlTransaction, cancellationToken);
+        if (autoSave)
+        {
+            await dbContext.SaveChangesAsync(cancellationToken);
+        }
+    }
+}
+
+

示例

+
C#
  1
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
namespace Lion.AbpPro.EntityFrameworkCore.Tests.Services;
+
+public class BlogAppService : ApplicationService
+{
+    private readonly IBlogRepository _blogRepository;
+    private readonly IRepository<Post, Guid> _postRepository;
+    private readonly IRepository<Comment, Guid> _commentRepository;
+    private readonly IdentityRoleManager _identityRoleManager;
+
+    public BlogAppService(IBlogRepository blogRepository, IdentityRoleManager identityRoleManager, IRepository<Post, Guid> postRepository, IRepository<Comment, Guid> commentRepository)
+    {
+        _blogRepository = blogRepository;
+        _identityRoleManager = identityRoleManager;
+        _postRepository = postRepository;
+        _commentRepository = commentRepository;
+    }
+
+
+    /// <summary>
+    /// 批量插入10000条数据
+    /// </summary>
+    public async Task CreateAsync(int qty = 10000)
+    {
+        // mock 数据
+        var list = GenFu.GenFu.ListOf<Blog>(qty);
+        var stopwatch = new Stopwatch();
+        stopwatch.Start();
+        await _blogRepository.InsertManyAsync(list);
+        stopwatch.Stop();
+        Logger.LogInformation($"批量插入{list.Count}条,耗时(单位:毫秒):{stopwatch.ElapsedMilliseconds}");
+    }
+
+    /// <summary>
+    /// 批量插入10000条数据
+    /// </summary>
+    public async Task CreateAllAsync(int qty = 10000)
+    {
+        // mock 数据
+        var blogs = GenFu.GenFu.ListOf<Blog>(qty);
+        var posts = new List<Post>();
+        var comments = new List<Comment>();
+        // blog和post一对多,post和comment一对多
+        // 有主外键关系,所以循环mock数据
+        foreach (var blog in blogs)
+        {
+            posts.Add(new Post(GuidGenerator.Create(), blog.Id, "name"));
+        }
+
+
+        foreach (var post in posts)
+        {
+            comments.Add(new Comment(GuidGenerator.Create(), 1, post.Id, "content"));
+        }
+
+        var stopwatch = new Stopwatch();
+        stopwatch.Start();
+        // 需要执行三次,不会因为ef有定义关系而一次性插入posts和comments
+        await _blogRepository.InsertManyAsync(blogs);
+        await _postRepository.InsertManyAsync(posts);
+        await _commentRepository.InsertManyAsync(comments);
+        stopwatch.Stop();
+        Logger.LogInformation($"批量插入blogs:{blogs.Count},posts:{posts.Count},comments:{comments.Count}条,耗时(单位:毫秒):{stopwatch.ElapsedMilliseconds}");
+    }
+
+    /// <summary>
+    /// 批量插入10000条数据,并且测试事务是否和其它业务逻辑保持一致
+    /// 测试结果:在一个事务内
+    /// </summary>
+    public async Task CreateTransactionAsync(int qty = 10)
+    {
+        var list = GenFu.GenFu.ListOf<Blog>(qty);
+        var stopwatch = new Stopwatch();
+        stopwatch.Start();
+        await _blogRepository.InsertManyAsync(list);
+        stopwatch.Stop();
+        Logger.LogInformation($"批量插入{list.Count}条,耗时(单位:毫秒):{stopwatch.ElapsedMilliseconds}");
+        await _identityRoleManager.CreateAsync(new IdentityRole(GuidGenerator.Create(), GuidGenerator.Create().ToString()));
+        throw new UserFriendlyException("test");
+    }
+
+    /// <summary>
+    /// 批量更新
+    /// <see cref="https://learn.microsoft.com/zh-cn/ef/core/saving/execute-insert-update-delete"/>
+    /// </summary>
+    public async Task BatchUpdateAsync(int qty = 10000)
+    {
+        using (var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions(true), true))
+        {
+            var list = GenFu.GenFu.ListOf<Blog>(qty);
+            await _blogRepository.InsertManyAsync(list);
+            await uow.CompleteAsync();
+        }
+
+        var stopwatch = new Stopwatch();
+        stopwatch.Start();
+        var dbSet = await _blogRepository.GetDbSetAsync();
+        await dbSet.ExecuteUpdateAsync(setters => setters
+            .SetProperty(x => x.IsDeleted, x => true)
+            .SetProperty(x => x.Name, x => "test"));
+        stopwatch.Stop();
+        Logger.LogInformation($"批量更新{qty}条,耗时(单位:毫秒):{stopwatch.ElapsedMilliseconds}");
+    }
+
+    /// <summary>
+    /// 批量删除
+    /// <see cref="https://learn.microsoft.com/zh-cn/ef/core/saving/execute-insert-update-delete"/>
+    /// </summary>
+    public async Task BatchDeleteAsync(int qty = 10000)
+    {
+        using (var uow = UnitOfWorkManager.Begin(new AbpUnitOfWorkOptions(true), true))
+        {
+            var list = GenFu.GenFu.ListOf<Blog>(qty);
+            await _blogRepository.InsertManyAsync(list);
+            await uow.CompleteAsync();
+        }
+
+        var stopwatch = new Stopwatch();
+        stopwatch.Start();
+        var dbSet = await _blogRepository.GetDbSetAsync();
+        await dbSet.ExecuteDeleteAsync();
+        stopwatch.Stop();
+        Logger.LogInformation($"批量删除{qty}条,耗时(单位:毫秒):{stopwatch.ElapsedMilliseconds}");
+    }
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/cache/index.html b/user-guide/zh/infrastructure/cache/index.html new file mode 100644 index 000000000..0acdf19c0 --- /dev/null +++ b/user-guide/zh/infrastructure/cache/index.html @@ -0,0 +1,1592 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 缓存 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

缓存

+

ABP 框架扩展了 ASP.NET Core 的分布式缓存系统. +ABP vNext Pro 已集成 Redis 做为缓存。

+

配置

+
appsetting.json
1
+2
+3
+4
"Redis":
+  {
+     "Configuration": "localhost,password=1q2w3E*,defaultdatabase=1"
+  }
+
+

AbpDistributedCacheOptions

+

示例:为应用程序设置缓存键前缀

+
AbpProHttpApiHostModule.cs
1
+2
+3
+4
Configure<AbpDistributedCacheOptions>(options =>
+{
+    options.KeyPrefix = "MyApp1";
+});
+
+

可用选项

+
    +
  • HideErrors (bool, 默认: true): 启用/禁用隐藏从缓存服务器写入/读取值时的错误.
  • +
  • KeyPrefix (string, 默认: null): 如果你的缓存服务器由多个应用程序共同使用, 则可以为应用程序的缓存键设置一个前缀. 在这种情况下, 不同的应用程序不能覆盖彼此的缓存内容.
  • +
  • GlobalCacheEntryOptions (DistributedCacheEntryOptions): 用于设置保存缓内容却没有指定选项时, 默认的分布式缓存选项 (例如 AbsoluteExpirationSlidingExpiration). SlidingExpiration的默认值设置为 20 分钟.
  • +
+

使用方式

+

示例: 在缓存中存储图书名称和价格

+
C#
1
+2
+3
+4
+5
+6
+7
+8
+9
namespace MyProject
+{
+    public class BookCacheItem
+    {
+        public string Name { get; set; }
+
+        public float Price { get; set; }
+    }
+}
+
+

你可以注入 IDistributedCache<BookCacheItem> 服务用于 get/set BookCacheItem 对象.

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Distributed;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
+
+namespace MyProject
+{
+    public class BookService : ITransientDependency
+    {
+        private readonly IDistributedCache<BookCacheItem> _cache;
+
+        public BookService(IDistributedCache<BookCacheItem> cache)
+        {
+            _cache = cache;
+        }
+
+        public async Task<BookCacheItem> GetAsync(Guid bookId)
+        {
+            return await _cache.GetOrAddAsync(
+                bookId.ToString(), //缓存键
+                async () => await GetBookFromDatabaseAsync(bookId),
+                () => new DistributedCacheEntryOptions
+                {
+                    AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
+                }
+            );
+        }
+
+        private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId)
+        {
+            //TODO: 从数据库获取数据
+        }
+    }
+}
+
+
    +
  • 示例服务代码中的 GetOrAddAsync() 方法从缓存中获取图书项. GetOrAddAsync是 ABP 框架在 ASP.NET Core 分布式缓存方法中添增的附加方法.
  • +
  • 如果没有在缓存中找到图书,它会调用工厂方法 (本示例中是 GetBookFromDatabaseAsync)从原始数据源中获取图书项.
  • +
  • GetOrAddAsync 有一个可选参数 DistributedCacheEntryOptions , 可用于设置缓存的生命周期.
  • +
+

批量操作

+

ABP 的分布式缓存接口定义了以下批量操作方法,当你需要在一个方法中调用多次缓存操作时,这些方法可以提高性能

+
    +
  • SetManyAsyncSetMany 方法可以用来向缓存中设置多个值.
  • +
  • GetManyAsyncGetMany 方法可以用来从缓存中获取多个值.
  • +
  • GetOrAddManyAsyncGetOrAddMany 方法可以用来从缓存中获取并添加缺少的值.
  • +
  • RefreshManyAsyncRefreshMany 方法可以来用重置多个值的滚动过期时间.
  • +
  • RemoveManyAsyncRemoveMany 方法可以用来从缓存中删除多个值.
  • +
+
+

这些不是标准的 ASP.NET Core 缓存方法, 所以某些提供程序可能不支持. [ABP Redis 集成包]实现了它们. 如果提供程序不支持,会回退到 SetAsyncGetAsync ... 方法(循环调用).

+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/cap/index.html b/user-guide/zh/infrastructure/cap/index.html new file mode 100644 index 000000000..681024ba2 --- /dev/null +++ b/user-guide/zh/infrastructure/cap/index.html @@ -0,0 +1,1468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分布式事件 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

dotnetcore.cap

+

分布式事件总线系统允许发布和订阅跨应用/服务边界传输的事件. 你可以使用分布式事件总线在微服务或应用程序之间异步发送和接收消息.

+

安装

+
    +
  • 添加以下 NuGet 包到你的项目
  • +
  • Lion.AbpPro.CAP
  • +
  • Lion.AbpPro.CAP.EntityFrameworkCore
  • +
  • DotNetCore.CAP.MySql (如果是其它数据库,请安装对应类型)
  • +
  • DotNetCore.CAP.RabbitMQ (如果是其它中间件,请安装对应类型)
  • +
  • 添加 [DependsOn(typeof(AbpProCapEntityFrameworkCoreModule))] 到你的项目模块类.
  • +
+

配置

+

Mysql,和 RabbitMq 为例

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
public override void ConfigureServices(ServiceConfigurationContext context)
+{
+        context.AddAbpCap(capOptions =>
+        {
+            // 指定数据数据库连接字符串
+            capOptions.SetCapDbConnectionString(configuration["ConnectionStrings:Default"]);
+            capOptions.UseEntityFramework<AbpProDbContext>();
+            // 使用rabbitmq,配置host,username,password
+            capOptions.UseRabbitMQ(option =>
+            {
+                option.HostName = configuration.GetValue<string>("Cap:RabbitMq:HostName");
+                option.UserName = configuration.GetValue<string>("Cap:RabbitMq:UserName");
+                option.Password = configuration.GetValue<string>("Cap:RabbitMq:Password");
+            });
+
+            var hostingEnvironment = context.Services.GetHostingEnvironment();
+            bool auth = !hostingEnvironment.IsDevelopment();
+            // 启用面板
+            capOptions.UseDashboard(options =>
+            {
+                options.UseAuth = auth;
+                options.AuthorizationPolicy = LionAbpProCapPermissions.CapManagement.Cap;
+            });
+        });
+}
+
+
    +
  • 即可使用 Abp vNext 标准写法,发送分布式事件和订阅事件
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/config/index.html b/user-guide/zh/infrastructure/config/index.html new file mode 100644 index 000000000..34c90d597 --- /dev/null +++ b/user-guide/zh/infrastructure/config/index.html @@ -0,0 +1,1578 @@ + + + + + + + + + + + + + + + + + + + + + + + + 配置 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

配置

+

日志

+

日志级别

+
+

Debug → Information → Warning → Error → Fatal

+
+
JSON
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
"Serilog": {
+  "Using": [
+    "Serilog.Sinks.Console",
+    "Serilog.Sinks.File"
+  ],
+  "MinimumLevel": {
+    // 默认全局日志级别
+    "Default": "Information",
+    "Override": {
+      //名称空间为 Microsoft 日志级别
+      "Microsoft": "Information",
+      //名称空间为 Volo.Abp 日志级别
+      "Volo.Abp": "Information",
+      //名称空间为 Hangfire 日志级别
+      "Hangfire": "Information",
+      //名称空间为 DotNetCore.CAP 日志级别
+      "DotNetCore.CAP": "Information",
+      //名称空间为 Serilog.AspNetCore 日志级别
+      "Serilog.AspNetCore": "Information",
+      //名称空间为 Microsoft.EntityFrameworkCore 日志级别
+      "Microsoft.EntityFrameworkCore": "Warning",
+      //名称空间为 Microsoft.AspNetCore 日志级别
+      "Microsoft.AspNetCore": "Information"
+    }
+  },
+  "WriteTo": [
+    {
+      // 输出到控制台日志
+      "Name": "Console"
+    },
+    {
+      // 输出到文件
+      "Name": "File",
+      "Args": {
+        "path": "logs/logs-.txt",
+         // 按天输出
+        "rollingInterval": "Day"
+      }
+    }
+  ]
+}
+
+

写入 ES

+
+

先决条件:搭建好 ES 环境

+
+
    +
  • Enabled:是否启用
  • +
  • Url:es 地址
  • +
  • IndexFormat:es 索引
  • +
  • UserName:用户名
  • +
  • Password:密码
  • +
  • SearchIndexFormat:es 日志查询索引模式
  • +
+
JSON
1
+2
+3
+4
+5
+6
+7
+8
"ElasticSearch": {
+  "Enabled": "false",
+  "Url": "http://es.cn",
+  "IndexFormat": "Lion.AbpPro.development.{0:yyyy.MM.dd}",
+  "UserName": "elastic",
+  "Password": "aVVhjQ95RP7nbwNy",
+  "SearchIndexFormat": "Lion.AbpPro.development*"
+},
+
+
    +
  • 查看 Lion.AbpPro.HttpApi.Host.Program.cs
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
public class Program
+{
+    public static void Main(string[] args)
+    {
+        CreateHostBuilder(args).Build().Run();
+
+    }
+    private static IHostBuilder CreateHostBuilder(string[] args) =>
+        Host.CreateDefaultBuilder(args)
+            .ConfigureWebHostDefaults(webBuilder =>
+            {
+                webBuilder.ConfigureKestrel((context, options) => { options.Limits.MaxRequestBodySize = 1024 * 50; });
+                webBuilder.UseStartup<Startup>();
+            })
+            .UseSerilog((context, loggerConfiguration) =>
+            {
+                // 配置ES日志
+                SerilogToEsExtensions.SetSerilogConfiguration(
+                    loggerConfiguration,
+                    context.Configuration);
+            }).UseAutofac();
+}
+
+

跨域(CORS)

+
    +
  • 允许指定策略
  • +
+
JSON
1
+2
+3
+4
"App": {
+    // 逗号分隔
+    "CorsOrigins": "http://*.com,http://localhost:4200"
+  },
+
+
    +
  • 配置跨域
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
private void ConfigureCors(ServiceConfigurationContext context)
+{
+    var configuration = context.Services.GetConfiguration();
+    context.Services.AddCors(options =>
+    {
+        options.AddPolicy(DefaultCorsPolicyName, builder =>
+        {
+            builder
+                .WithOrigins(
+                    configuration["App:CorsOrigins"]
+                        .Split(",", StringSplitOptions.RemoveEmptyEntries)
+                        .Select(o => o.RemovePostFix("/"))
+                        .ToArray()
+                )
+                .WithAbpExposedHeaders()
+                .SetIsOriginAllowedToAllowWildcardSubdomains()
+                .AllowAnyHeader()
+                .AllowAnyMethod()
+                .AllowCredentials();
+        });
+    });
+}
+
+

AccessToken

+
    +
  • Audience:接收对象
  • +
  • Issuer:签发主体
  • +
  • SecurityKey:密钥
  • +
  • ExpirationTime:过期时间(单位小时)
  • +
+
JSON
1
+2
+3
+4
+5
+6
  "Jwt": {
+    "Audience": "Lion.AbpPro",
+    "SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=",
+    "Issuer": "Lion.AbpPro",
+    "ExpirationTime": 30
+  }
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/elastic/index.html b/user-guide/zh/infrastructure/elastic/index.html new file mode 100644 index 000000000..d2b3fc437 --- /dev/null +++ b/user-guide/zh/infrastructure/elastic/index.html @@ -0,0 +1,1654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ElasticSearch - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

ElasticSearch

+

安装

+
    +
  • 添加以下 NuGet 包到你的项目
  • +
  • Lion.AbpPro.ElasticSearch
  • +
  • 添加 [DependsOn(typeof(AbpProElasticSearchModule))] 到你的项目模块类.
  • +
+

配置

+
JSON
1
+2
+3
+4
+5
+6
+7
{
+  "ElasticSearch": {
+    "Host": "http://localhost:9200",
+    "UserName": "admin",
+    "Password": "1q2w3E*"
+  }
+}
+
+

示例

+
    +
  • 实现 Student 的增删查改
  • +
+

定义学生类

+
    +
  • 实现 IElasticSearchEntity 接口,此接口有主键 Id,创建时间字段,也是泛型约束。
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
public class Student : IElasticSearchEntity
+{
+    public Guid Id { get; set; }
+
+    public DateTime CreationTime { get; set; }
+
+    public double Price { get; set; }
+
+    public string Name { get; set; }
+
+    public int Age { get; set; }
+}
+
+

定义接口

+
C#
1
+2
+3
public interface IStudentElasticSearchRepository : IBasicElasticSearchRepository<Student>
+{
+}
+
+

实现接口

+
C#
1
+2
+3
+4
+5
+6
+7
+8
+9
public class StudentElasticSearchRepository : ElasticSearchRepository<Student>, IStudentElasticSearchRepository, ITransientDependency
+{
+    public StudentElasticSearchRepository(IElasticsearchProvider elasticsearchProvider) : base(elasticsearchProvider)
+    {
+    }
+
+    // index 只能是小写
+    protected override string IndexName => "Students".ToLower();
+}
+
+

CURD

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
// 注入
+private readonly IStudentElasticSearchRepository _studentElasticSearchRepository;
+public StudentElasticSearchRepositoryTests(IStudentElasticSearchRepository studentElasticSearchRepository)
+{
+    _studentElasticSearchRepository = studentElasticSearchRepository;
+}
+
+// var student = new Student
+//          {
+//              Id = Guid.NewGuid(),
+//              Name = "韩立",
+//              Age = 10,
+//              CreationTime = DateTime.Now,
+//              Price = 100.3,
+//          };
+
+// 根据主键Id查询
+var result = await _studentElasticSearchRepository.FindAsync(student.Id);
+
+// 新增
+await _studentElasticSearchRepository.InsertAsync(student);
+
+// 批量新增
+await _studentElasticSearchRepository.InsertManyAsync(students);
+
+// 更新
+await _studentElasticSearchRepository.UpdateAsync(student);
+
+// 删除
+await _studentElasticSearchRepository.DeleteAsync(student.Id);
+
+// 分页查询
+var mustFilters = new List<Func<QueryContainerDescriptor<Student>, QueryContainer>>();
+mustFilters.Add(e => e.Term(f => f.Field(b => b.Name.Suffix("keyword")).Value("韩立")));
+var result = await _studentElasticSearchRepository.PageAsync(mustFilters);
+
+

其它操作

+
    +
  • 在 StudentElasticSearchRepository 中使用 Client 即可获取到 es 原生 api 对象。
  • +
  • 更多示例请查看单元测试(StudentElasticSearchRepositoryTests.cs)
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/exception-handler/index.html b/user-guide/zh/infrastructure/exception-handler/index.html new file mode 100644 index 000000000..d8c01aef1 --- /dev/null +++ b/user-guide/zh/infrastructure/exception-handler/index.html @@ -0,0 +1,1723 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 异常 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

异常处理

+

ABP 提供了用于处理 Web 应用程序异常的标准模型.

+
    +
  • 自动处理所有异常.如果是 API/AJAX 请求,会向客户端返回一个标准格式化后的错误消息 .
  • +
  • 自动隐藏内部详细错误并返回标准错误消息.
  • +
  • 为异常消息的本地化提供一种可配置的方式.
  • +
  • 自动为标准异常设置*HTTP 状态代码,并提供可配置选项,以映射自定义异常.
  • +
+

自动处理异常

+

当满足下面任意一个条件时,AbpExceptionFilter 会处理此异常:

+
    +
  • 当 controller action 方法返回类型是 object result(而不是 view result)并有异常抛出时.
  • +
  • 当一个请求为 AJAX(Http 请求头中X-Requested-WithXMLHttpRequest)时.
  • +
  • 当客户端接受的返回类型为application/json(Http 请求头中acceptapplication/json)时.
  • +
+

错误消息格式

+
JSON
1
+2
+3
+4
+5
+6
+7
{
+  "error": {
+    "code": "App:010042",
+    "message": "This topic is locked and can not add a new message",
+    "details": "A more detailed info about the error..."
+  }
+}
+
+
    +
  • 错误代码(code)是异常信息中一个有唯一值并可选的字符串值.抛出的异常应实现IHasErrorCode 接口来填充该字段.
  • +
  • 错误的详细信息(Details) 是可选属性.抛出的异常应实现IHasErrorDetails 接口来填充该字段.
  • +
+

验证错误

+

当抛出的异常实现IHasValidationErrors 接口时,validationErrors 是一个可被填充的标准字段.示例 JSON 如下:

+
JSON
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
{
+  "error": {
+    "code": "App:010046",
+    "message": "Your request is not valid, please correct and try again!",
+    "validationErrors": [
+      {
+        "message": "Username should be minimum length of 3.",
+        "members": ["userName"]
+      },
+      {
+        "message": "Password is required",
+        "members": ["password"]
+      }
+    ]
+  }
+}
+
+

AbpValidationException已经实现了IHasValidationErrors接口,当请求输入无效时,框架会自动抛出此错误. 因此,除非你有自定义的验证逻辑,否则不需要处理验证错误.

+

业务异常

+

大多数异常都是业务异常.可以通过使用IBusinessException 接口来标记异常为业务异常.

+

BusinessException 除了实现IHasErrorCode,IHasErrorDetails ,IHasLogLevel 接口外,还实现了IBusinessException 接口.其默认日志级别为Warning.

+

通常你会将一个错误代码关联至特定的业务异常.例如:

+
C#
throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer);
+
+

QaErrorCodes.CanNotVoteYourOwnAnswer 是一个字符串常量. 建议使用下面的错误代码格式:

+
Text Only
<code-namespace>:<error-code>
+
+

code-namespace,应在指定的模块/应用层中保证其唯一.例如:

+
Text Only
Volo.Qa:010002
+
+

Volo.Qa在这是作为code-namespace. code-namespace 同样可以在 本地化 异常信息时使用.

+
    +
  • 你可以直接抛出一个 BusinessException 异常,或者需要时可以从该类派生你自己的 Exception 类型.
  • +
  • 对于BusinessException 类型,其所有属性都是可选的.但是通常会设置ErrorCodeMessage属性.
  • +
+

使用错误代码

+

通过使用错误代码的方式来处理本地化,而不是在抛出异常的时候.

+

首先,在模块配置代码中将 code-namespace 映射至 本地化资源:

+
C#
1
+2
+3
+4
services.Configure<AbpExceptionLocalizationOptions>(options =>
+{
+    options.MapCodeNamespace("Volo.Qa", typeof(QaResource));
+});
+
+

然后Volo.Qa命名空间下的所有异常都将被对应的本地化资源进行本地化处理. 本地化资源中应包含对应错误代码的文本. 例如:

+
JSON
1
+2
+3
+4
+5
+6
{
+  "culture": "en",
+  "texts": {
+    "Volo.Qa:010002": "You can not vote your own answer!"
+  }
+}
+
+

最后就可以抛出一个包含错误代码的业务异常了:

+
C#
throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);
+
+
    +
  • 抛出所有实现IHasErrorCode 接口的异常都具有相同的行为.因此,对错误代码的本地化,并不是BusinessException类所特有的.
  • +
  • 为错误消息定义本地化文本并不是必须的. 如果未定义,ABP 会将默认的错误消息发送给客户端. 而不使用异常的Message属性. 如果你想要发送异常的Message,使用UserFriendlyException(或使用实现IUserFriendlyException接口的异常类型)
  • +
+

使用消息的格式化参数

+

如果有参数化的错误消息,则可以使用异常的Data属性进行设置.例如:

+
C#
1
+2
+3
+4
+5
+6
+7
throw new BusinessException("App:010046")
+{
+    Data =
+    {
+        {"UserName", "john"}
+    }
+};
+
+

另外有一种更为快捷的方式:

+
C#
1
+2
throw new BusinessException("App:010046")
+    .WithData("UserName", "john");
+
+

下面就是一个包含UserName 参数的错误消息:

+
JSON
1
+2
+3
+4
+5
+6
{
+  "culture": "en",
+  "texts": {
+    "App:010046": "Username should be unique. '{UserName}' is already taken!"
+  }
+}
+
+
    +
  • WithData 支持有多个参数的链式调用 (如.WithData(...).WithData(...)).
  • +
+

HTTP 状态代码映射

+

ABP 尝试按照以下规则,自动映射常见的异常类型的 HTTP 状态代码:

+
    +
  • 对于 AbpAuthorizationException:
  • +
  • 用户没有登录,返回 401 (未认证).
  • +
  • 用户已登录,但是当前访问未授权,返回 403 (未授权).
  • +
  • 对于 AbpValidationException 返回 400 (错误的请求) .
  • +
  • 对于 EntityNotFoundException返回 404 (未找到).
  • +
  • 对于 IBusinessExceptionIUserFriendlyException (它是IBusinessException的扩展) 返回403 (未授权) .
  • +
  • 对于 NotImplementedException 返回 501 (未实现) .
  • +
  • 对于其他异常 (基础架构中未定义的) 返回 500 (服务器内部错误) .
  • +
+

IHttpExceptionStatusCodeFinder 是用来自动判断 HTTP 状态代码.默认的实现是DefaultHttpExceptionStatusCodeFinder.可以根据需要对其进行更换或扩展.

+

自定义映射

+

可以重写 HTTP 状态代码的自动映射,示例如下:

+
C#
1
+2
+3
+4
services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
+{
+    options.Map("Volo.Qa:010002", HttpStatusCode.Conflict);
+});
+
+

发送异常详情到客户端

+

你可以通过 AbpExceptionHandlingOptions 类的 SendExceptionsDetailsToClients 属性异常发送到客户端:

+
C#
1
+2
+3
+4
services.Configure<AbpExceptionHandlingOptions>(options =>
+{
+    options.SendExceptionsDetailsToClients = true;
+});
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/freesql/index.html b/user-guide/zh/infrastructure/freesql/index.html new file mode 100644 index 000000000..f7d0fcb31 --- /dev/null +++ b/user-guide/zh/infrastructure/freesql/index.html @@ -0,0 +1,1470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + FreeSql - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

CQRS

+

CQRS:命令查询职责隔离,命令是指 插入、修改、删除,就是更改数据的动作.通过 Freesql 解决单一数据模型带来的查询尴尬场面。 +当前架构下,Freesql 和 ef 不在一个事务,最好实现就是用来做查询,比如分页查询。 +

+

配置

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
public class AbpProFreeSqlModule : AbpModule
+{
+    public override void ConfigureServices(ServiceConfigurationContext context)
+    {
+        var configuration = context.Services.GetConfiguration();
+        var connectionString = configuration.GetConnectionString("Default");
+        var freeSql = new FreeSql.FreeSqlBuilder()
+            .UseConnectionString(FreeSql.DataType.MySql, connectionString)
+            .Build();
+
+        context.Services.AddSingleton<IFreeSql>(freeSql);
+    }
+}
+
+

使用

+
    +
  • 在 Domain 层添加接口
  • +
+
C#
1
+2
+3
+4
public interface IUserFreeSqlBasicRepository
+{
+    Task<List<UserOutput>> GetListAsync();
+}
+
+
    +
  • 在 Freesql 层添加实现
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
public class UserFreeSqlBasicRepository : FreeSqlBasicRepository, IUserFreeSqlBasicRepository
+{
+    public async Task<List<UserOutput>> GetListAsync()
+    {
+        var sql = "select id from AbpUsers";
+        var result = await FreeSql.Select<UserOutput>()
+        .WithSql(sql)
+        .ToListAsync();
+        return result;
+    }
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/frontend/index.html b/user-guide/zh/infrastructure/frontend/index.html new file mode 100644 index 000000000..549eadfb8 --- /dev/null +++ b/user-guide/zh/infrastructure/frontend/index.html @@ -0,0 +1,1688 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 前端 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

前端

+

Vben Admin 文档

+

代码生成

+
+

前端接口,参数,自动生成,全部采用 Post 方式

+
+
    +
  • 所有访问后端接口代码自动生成 NSwag
  • +
+

配置代理的地址

+
    +
  • nswag->nswag.json
  • +
+
JSON
1
+2
+3
+4
+5
+6
  "documentGenerator": {
+    "fromDocument": {
+      // 代理地址,只有生成的时候用,不区分环境
+      "url": "http://localhost:44315/swagger/v1/swagger.json",
+    }
+  }
+
+
    +
  • 如果接口参数或者返回值有改变,需要重新生成代理,执行:
  • +
+
Bash
npm run nswag
+
+

后端 Api 格式

+
C#
1
+2
+3
+4
+5
+6
+7
// 一定要打Tags,因为前端会根据这个生成代理类
+// 建议参数都封装为一个Input
+[SwaggerOperation(summary: "登录", Tags = new[] {"Account"})]
+public Task<LoginOutput> LoginAsync(LoginInput input)
+{
+    return _loginAppService.LoginAsync(input);
+}
+
+

前端多环境

+
    +
  • .env.development 和.env.production
  • +
  • VITE_API_URL:后端接口地址
  • +
  • VITE_WEBSOCKE_URL: WEBSOCKE 地址
  • +
+

权限配置

+

菜单权限

+
TypeScript
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
import type { AppRouteModule } from "/@/router/types";
+import { LAYOUT } from "/@/router/constant";
+import { t } from "/@/hooks/web/useI18n";
+const tenant: AppRouteModule = {
+  path: "/tenant",
+  name: "Tenant",
+  component: LAYOUT,
+  meta: {
+    orderNo: 30,
+    icon: "ant-design:contacts-outlined",
+    title: t("routes.tenant.tenantManagement"),
+  },
+  children: [
+    {
+      path: "Tenant",
+      name: "Tenant",
+      component: () => import("/@/views/tenants/Tenant.vue"),
+      meta: {
+        title: t("routes.tenant.tenantList"),
+        icon: "ant-design:switcher-filled",
+        policy: "AbpTenantManagement.Tenants", //菜单权限
+      },
+    },
+  ],
+};
+
+export default tenant;
+
+

按钮权限

+
Text Only
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
<template>
+  <div>
+    <BasicTable @register="registerTable" size="small">
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              icon: 'ant-design:edit-outlined',
+              auth: 'AbpIdentity.Users.Update', // 按钮权限
+              label: t('common.editText'),
+              onClick: handleEdit.bind(null, record),
+            },
+          ]"
+          :dropDownActions="[
+            {
+              auth: 'AbpIdentity.Users.Delete', // 按钮权限
+              label: t('common.delText'),
+              onClick: handleDelete.bind(null, record),
+            },
+            {
+              auth: 'System.Users.Enable', // 按钮权限
+              label: !record.isActive
+                ? t('common.enabled')
+                : t('common.disEnabled'),
+              onClick: handleLock.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <CreateAbpUser
+      @register="registerCreateAbpUserModal"
+      @reload="reload"
+      :bodyStyle="{ 'padding-top': '0' }"
+    />
+    <EditAbpUser
+      @register="registerEditAbpUserModal"
+      @reload="reload"
+      :bodyStyle="{ 'padding-top': '0' }"
+    />
+  </div>
+</template>
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/log/index.html b/user-guide/zh/infrastructure/log/index.html new file mode 100644 index 000000000..7fa05ac19 --- /dev/null +++ b/user-guide/zh/infrastructure/log/index.html @@ -0,0 +1,1646 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 日志 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

Serilog

+
+

ABP 框架没有实现任何日志基础设施. 它使用 ASP.NET Core 日志系统.

+
+

日志等级

+
+

Debug → Information → Warning → Error → Fatal

+
+

如何集成

+
Program.cs
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
public class Program
+{
+    private static IHostBuilder CreateHostBuilder(string[] args) =>
+        Host.CreateDefaultBuilder(args)
+            .ConfigureWebHostDefaults()
+            // 使用Serilog
+            .UseSerilog((context, loggerConfiguration) =>
+            {
+                SerilogToEsExtensions.SetSerilogConfiguration(
+                    loggerConfiguration,
+                    context.Configuration);
+            }).UseAutofac();
+}
+
+
SerilogToEsExtensions.cs
1
+2
+3
+4
+5
+6
+7
+8
+9
public static void SetSerilogConfiguration(LoggerConfiguration loggerConfiguration, IConfiguration configuration)
+{
+    // 默认读取 configuration 中 "Serilog" 节点下的配置
+    loggerConfiguration
+        .ReadFrom.Configuration(configuration)
+        .Enrich.FromLogContext();
+    // 如果要再日志加上自定义字段
+    loggerConfiguration.Enrich.WithProperty("Application", applicationName);
+}
+
+

配置

+
appsetting.json
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
"Serilog": {
+  "Using": [
+    "Serilog.Sinks.Console",
+    "Serilog.Sinks.File"
+  ],
+  "MinimumLevel": {
+    // 默认全局日志级别
+    "Default": "Information",
+    "Override": {
+      //名称空间为 Microsoft 日志级别
+      "Microsoft": "Information",
+      //名称空间为 Volo.Abp 日志级别
+      "Volo.Abp": "Information",
+      //名称空间为 Hangfire 日志级别
+      "Hangfire": "Information",
+      //名称空间为 DotNetCore.CAP 日志级别
+      "DotNetCore.CAP": "Information",
+      //名称空间为 Serilog.AspNetCore 日志级别
+      "Serilog.AspNetCore": "Information",
+      //名称空间为 Microsoft.EntityFrameworkCore 日志级别
+      "Microsoft.EntityFrameworkCore": "Warning",
+      //名称空间为 Microsoft.AspNetCore 日志级别
+      "Microsoft.AspNetCore": "Information"
+    }
+  },
+  "WriteTo": [
+    {
+      // 输出到控制台日志
+      "Name": "Console"
+    },
+    {
+      // 输出到文件
+      "Name": "File",
+      "Args": {
+        "path": "logs/logs-.txt",
+         // 按天输出
+        "rollingInterval": "Day"
+      }
+    }
+  ]
+}
+
+

写入 ElasticSearch

+
+

AbpPro 已经集成 ElasticSearch 只需要通过配置文件启用即可。

+
+
    +
  • Enabled:是否启用
  • +
  • Url:es 地址
  • +
  • IndexFormat:es 索引
  • +
  • UserName:用户名
  • +
  • Password:密码
  • +
+
appsetting.json
1
+2
+3
+4
+5
+6
+7
+8
"ElasticSearch": {
+  "Enabled": "false",
+  "Url": "http://es.cn",
+  // 索引名必须小写
+  "IndexFormat": "lion.abppro.development.{0:yyyy.MM.dd}",
+  "UserName": "elastic",
+  "Password": "aVVhjQ95RP7nbwNy"
+},
+
+

使用

+
SampleAppService.cs
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
public class SampleAppService : AbpProAppService,ISampleAppService
+{
+    private readonly ILogger<SampleAppService> _logger;
+
+    public SampleAppService(ILogger<SampleAppService> logger)
+    {
+        _logger = logger;
+    }
+
+    public async Task TestAsync()
+    {
+        _logger.LogDebug("LogDebug");
+        _logger.LogInformation("LogInformation");
+        _logger.LogWarning("LogWarning");
+        _logger.LogError("LogError");
+        _logger.LogTrace("LogTrace");
+        await Task.CompletedTask;
+    }
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/infrastructure/login/index.html b/user-guide/zh/infrastructure/login/index.html new file mode 100644 index 000000000..552e3350c --- /dev/null +++ b/user-guide/zh/infrastructure/login/index.html @@ -0,0 +1,1456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 登录 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

登录

+

ABP vNext Pro 没有集成 IdentityServer4 或者 OpenIddict,而是直接使用默认的 Asp Net Core Identity。

+
    +
  • 减少系统复杂度
  • +
  • 大部分(IdentityServer4|OpenIddict)功能用不上
  • +
+

登录接口

+
AccountAppService.cs
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
public virtual async Task<LoginOutput> LoginAsync(LoginInput input)
+{
+    var result = await _signInManager.PasswordSignInAsync(input.Name, input.Password, false, true);
+    if (result.IsNotAllowed)
+    {
+        throw new BusinessException(BasicManagementErrorCodes.UserLockedOut);
+    }
+    if (!result.Succeeded)
+    {
+        throw new BusinessException(BasicManagementErrorCodes.UserOrPasswordMismatch);
+    }
+    var user = await _userManager.FindByNameAsync(input.Name);
+    return await BuildResult(user);
+}
+
+

配置 AccessToken

+
appsetting.json
1
+2
+3
+4
+5
+6
 "Jwt": {
+    "Audience": "Lion.AbpPro",
+    "SecurityKey": "dzehzRz9a8asdfasfdadfasdfasdfafsdadfasbasdf=",
+    "Issuer": "Lion.AbpPro",
+    "ExpirationTime": 30
+  }
+
+
    +
  • Audience:接收对象
  • +
  • Issuer:签发主体
  • +
  • SecurityKey:密钥
  • +
  • ExpirationTime:过期时间(单位小时)
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/modules/basic/index.html b/user-guide/zh/modules/basic/index.html new file mode 100644 index 000000000..15009151d --- /dev/null +++ b/user-guide/zh/modules/basic/index.html @@ -0,0 +1,1421 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 基础模块 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

基础模块

+
    +
  • 把abp自带 账户模块,权限模块,identity模块,setting模块,feature模块,后台任务模块,租户模块封装到BasicManagement
  • +
+

安装

+
    +
  • Lion.Abp.BasicManagement.Application
  • +
  • Lion.Abp.BasicManagement.Application.Contracts
  • +
  • Lion.Abp.BasicManagement.Domain
  • +
  • Lion.Abp.BasicManagement.Domain.Shared
  • +
  • Lion.Abp.BasicManagement.EntityFrameworkCore
  • +
  • Lion.Abp.BasicManagement.HttpApi
  • +
  • Lion.Abp.BasicManagement.HttpApi.Client
  • +
+

模块依赖

+
    +
  • 添加 DependsOn(typeof(BasicManagementXxxModule)) 特性到对应模块。
  • +
  • 在EntityFrameworkCore层添加数据库配置在AbpProDbContext.cs的OnModelCreating()方法中添加builder.ConfigureBasicManagement();
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/modules/dic/index.html b/user-guide/zh/modules/dic/index.html new file mode 100644 index 000000000..20bff63c3 --- /dev/null +++ b/user-guide/zh/modules/dic/index.html @@ -0,0 +1,1598 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 数据字典 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

数据字典模块

+

Abp自带的Setting模块可能满足不了需求,特意提供数据字典模块。 +

+

安装

+
    +
  • Lion.Abp.DataDictionaryManagement.Application
  • +
  • Lion.Abp.DataDictionaryManagement.Application.Contracts
  • +
  • Lion.Abp.DataDictionaryManagement.Domain
  • +
  • Lion.Abp.DataDictionaryManagement.Domain.Shared
  • +
  • Lion.Abp.DataDictionaryManagement.EntityFrameworkCore
  • +
  • Lion.Abp.DataDictionaryManagement.HttpApi
  • +
  • Lion.Abp.DataDictionaryManagement.HttpApi.Client
  • +
+

模块依赖

+
    +
  • +

    添加 DependsOn(typeof(DataDictionaryManagementXxxModule)) 特性到对应模块。

    +
  • +
  • +

    在EntityFrameworkCore层添加数据库配置在AbpProDbContext.cs的OnModelCreating()方法中添加builder.ConfigureDataDictionaryManagement();

    +
  • +
+

实体

+

DataDictionary 表结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字段名描述类型
IdIdGuid
TenantId租户idGuid?
Code字典编码string
DisplayText显示名string
Description描述DateTime
Details字典明细List
IsDeleted是否删除bool
DeleterId删除人Guid?
DeletionTime删除时间DateTime
LastModifierId最后修改人Guid?
LastModificationTime最后修改时间DateTime
CreatorId创建人Guid?
CreationTime创建时间DateTime
+

DataDictionaryDetail 表结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字段名描述类型
IdIdGuid
DataDictionaryId所属字典IdGuid
Order排序Int
Code字典编码string
IsEnabled启/停用(默认启用)bool
DisplayText显示名string
Description描述DateTime
IsDeleted是否删除bool
DeleterId删除人Guid?
DeletionTime删除时间DateTime
LastModifierId最后修改人Guid?
LastModificationTime最后修改时间DateTime
CreatorId创建人Guid?
CreationTime创建时间DateTime
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/modules/file/index.html b/user-guide/zh/modules/file/index.html new file mode 100644 index 000000000..20b99f958 --- /dev/null +++ b/user-guide/zh/modules/file/index.html @@ -0,0 +1,1577 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 文件管理 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

文件模块

+
    +
  • 与 abp 自带的文件模块不一样,此模块接入阿里云 oss 作为云存储。
  • +
  • 前端上传文件到 OSS,文件模块保存相对路径。
  • +
+

+

安装

+
    +
  • Lion.Abp.FileManagement.Application
  • +
  • Lion.Abp.FileManagement.Application.Contracts
  • +
  • Lion.Abp.FileManagement.Domain
  • +
  • Lion.Abp.FileManagement.Domain.Shared
  • +
  • Lion.Abp.FileManagement.EntityFrameworkCore
  • +
  • Lion.Abp.FileManagement.HttpApi
  • +
  • Lion.Abp.FileManagement.HttpApi.Client
  • +
+

模块依赖

+
    +
  • 添加 DependsOn(typeof(FileManagementXxxModule)) 特性到对应模块。
  • +
  • 在 EntityFrameworkCore 层添加数据库配置在 AbpProDbContext.cs 的 OnModelCreating()方法中添加 builder.ConfigureFileManagement();
  • +
+

实体

+

File 表结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字段名描述类型
IdIdGuid
TenantId租户 idGuid?
FileName文件名称string
FilePath文件路径string
IsDeleted是否删除bool
DeleterId删除人Guid?
DeletionTime删除时间DateTime
LastModifierId最后修改人Guid?
LastModificationTime最后修改时间DateTime
CreatorId创建人Guid?
CreationTime创建时间DateTime
+

OSS 配置

+

阿里云 OSS 配置

+
    +
  • 将 OSS 配置添加到 AppSetting
  • +
+

AppSetting 配置

+
JSON
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
  "AliYun": {
+    "OSS": {
+      "AccessKeyId": "LTAI5tLkt3vvScGPVZ5qKJDc1S",
+      "AccessKeySecret": "BixV8vP5uPrbsdwjYzzsEXOPjkxPST12S",
+      "Endpoint": "oss-cn-shenzhen.aliyuncs.com",
+      "ContainerName": "lion-abp-pro",
+      "RegionId": "oss-cn-shenzhen",
+      "RoleArn": "acs:ram::1846393972471789:role/ramosst1t"
+    }
+  }
+
+

上传组件

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/modules/setting/index.html b/user-guide/zh/modules/setting/index.html new file mode 100644 index 000000000..11f558c56 --- /dev/null +++ b/user-guide/zh/modules/setting/index.html @@ -0,0 +1,1542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 设置(Setting) - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

设置管理

+

官方 Setting 模块参考文档

+

配置系统是在启动时配置应用程序很好的方式. 除了配置之外, ABP 提供了另外一种设置和获取应用程序设置的方式. +设置存储在动态数据源(通常是数据库)中的键值对. 设置系统预构建了用户,租户,全局和默认设置方法并且可以进行扩展.

+

定义设置

+

使用设置之前需要定义它. ABP 是 模块化的, 不同的模块可以拥有不同的设置. 只需要实现 SettingDefinitionProvider 类既可. 示例如下:

+
+

和官方 Setting 模块区别,值添加了 2 个属性,一个分组,一个组件类型

+
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
public class CustomSettingProvider : SettingDefinitionProvider
+{
+    public override void Define(ISettingDefinitionContext context)
+    {
+     context.Add(
+        new SettingDefinition(
+                AbpProSettings.Other.Github,
+                "https://github.com/WangJunZzz/abp-vnext-pro",
+                L("DisplayName:" + AbpProSettings.Other.Github),
+                L("Description:" + AbpProSettings.Other.Github)
+            )
+            // 分组
+            .WithProperty(AbpProSettings.Group.Default,AbpProSettings.Group.OtherManagement)
+            // 前端组件类型
+            .WithProperty(AbpProSettings.ControlType.Default,AbpProSettings.ControlType.TypeText));
+    }
+}
+
+
    +
  • +

    SettingDefinition 类具有以下属性:

    +
  • +
  • +

    Name: 应用程序中设置的唯一名称. 是具有约束的唯一属性, 在应用程序获取/设置此设置的值 (设置名称定义为常量而不是魔法字符串是个好主意).

    +
  • +
  • DefaultValue: 设置的默认值.
  • +
  • DisplayName: 本地化的字符串,用于在 UI 上显示名称.
  • +
  • +

    Description: 本地化的字符串,用于在 UI 上显示描述.

    +
  • +
  • +

    上面添加了 2 个属性,为了适配 vue 前端,一个设置 Setting 属于哪个分组,一个是根据 Setting 的类型指定对应的前端组件,比如字符串就是,Input 组件。

    +
  • +
  • 支持以下组件:Text,CheckBox,Number
  • +
+

读取设置值

+

ISettingProvider 用于获取指定设置的值或所有设置的值. 示例用法:

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
public class MyService
+{
+    private readonly ISettingProvider _settingProvider;
+
+    //Inject ISettingProvider in the constructor
+    public MyService(ISettingProvider settingProvider)
+    {
+        _settingProvider = settingProvider;
+    }
+
+    public async Task FooAsync()
+    {
+        //Get a value as string.
+        string userName = await _settingProvider.GetOrNullAsync("Smtp.UserName");
+
+        //Get a bool value and fallback to the default value (false) if not set.
+        bool enableSsl = await _settingProvider.GetAsync<bool>("Smtp.EnableSsl");
+
+        //Get a bool value and fallback to the provided default value (true) if not set.
+        bool enableSsl = await _settingProvider.GetAsync<bool>(
+            "Smtp.EnableSsl", defaultValue: true);
+
+        //Get a bool value with the IsTrueAsync shortcut extension method
+        bool enableSsl = await _settingProvider.IsTrueAsync("Smtp.EnableSsl");
+
+        //Get an int value or the default value (0) if not set
+        int port = (await _settingProvider.GetAsync<int>("Smtp.Port"));
+
+        //Get an int value or null if not provided
+        int? port = (await _settingProvider.GetOrNullAsync("Smtp.Port"))?.To<int>();
+    }
+}
+
+
+

ISettingProvider 是非常常用的服务,一些基类中(如 IApplicationService)已经将其属性注入. 这种情况下可以直接使用 SettingProvider.

+
+
    +
  • ISettingProvider 使用设置值提供程序来获取设置值. 如果值提供程序无法获取设置值,则会回退到下一个值提供程序.
  • +
  • DefaultValueSettingValueProvider: 从设置定义的默认值中获取值.
  • +
  • ConfigurationSettingValueProvider: 从 IConfiguration 服务中获取值.
  • +
  • GlobalSettingValueProvider: 获取设置的全局值.
  • +
  • TenantSettingValueProvider: 获取当前租户的设置值.
  • +
  • UserSettingValueProvider: 获取当前用户的设置值.
  • +
+
+

设置回退系统从底部 (用户) 到 顶部(默认) 方向起作用.

+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/modules/signalr/index.html b/user-guide/zh/modules/signalr/index.html new file mode 100644 index 000000000..9080165be --- /dev/null +++ b/user-guide/zh/modules/signalr/index.html @@ -0,0 +1,1708 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 实时通信 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

实时通信

+

集成 Abp SignalR,实现类似站内信模块。

+
    +
  • 发送消息会在前端右上角,根据不同消息等级有不同的窗体提示。
  • +
+

+
    +
  • 在右上角灯泡按钮可以看到接受的消息。
  • +
+

+

安装

+
    +
  • Lion.Abp.NotificationManagement.Application
  • +
  • Lion.Abp.NotificationManagement.Application.Contracts
  • +
  • Lion.Abp.NotificationManagement.Domain
  • +
  • Lion.Abp.NotificationManagement.Domain.Shared
  • +
  • Lion.Abp.NotificationManagement.EntityFrameworkCore
  • +
  • Lion.Abp.NotificationManagement.HttpApi
  • +
  • Lion.Abp.NotificationManagement.HttpApi.Client
  • +
+

模块依赖

+
    +
  • +

    添加 DependsOn(typeof(NotificationManagementXxxModule)) 特性到对应模块。

    +
  • +
  • +

    在 EntityFrameworkCore 层添加数据库配置在 AbpProDbContext.cs 的 OnModelCreating()方法中添加 builder.ConfigureNotificationManagement();

    +
  • +
+

实体

+

Notification 表结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字段名描述类型
IdIdGuid
Title消息标题string
Content消息内容string
MessageType消息类型MessageType
MessageLevel消息等级MessageLevel
SenderId创建人发送人
NotificationSubscriptions消息订阅者集合List
IsDeleted是否删除bool
DeleterId删除人Guid?
DeletionTime删除时间DateTime
LastModifierId最后修改人Guid?
LastModificationTime最后修改时间DateTime
CreatorId创建人Guid?
CreationTime创建时间DateTime
+

NotificationSubscription 表结构:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字段名描述类型
IdIdGuid
ReceiveId接收人Guid
Read是否已读bool
ReadTime已读时间DateTime?
IsDeleted是否删除bool
DeleterId删除人Guid?
DeletionTime删除时间DateTime
LastModifierId最后修改人Guid?
LastModificationTime最后修改时间DateTime
CreatorId创建人Guid?
CreationTime创建时间DateTime
+

发送消息

+
    +
  • 注入 NotificationManager,NotificationAppService 即可发送不同级别,不同等级的消息。
  • +
  • 发送给指定人
  • +
+
C#
1
+2
+3
+4
+5
+6
+7
/// <summary>
+/// 发送警告文本消息
+/// </summary>
+/// <param name="title">标题</param>
+/// <param name="content">消息内容</param>
+/// <param name="receiveIds">接受人,发送给谁。</param>
+await _notificationManager.SendCommonWarningMessageAsync(title,content,receiveIds);
+
+
    +
  • 发送给所有人
  • +
+
C#
1
+2
+3
+4
+5
+6
/// <summary>
+/// 发送警告文本消息
+/// </summary>
+/// <param name="title">标题</param>
+/// <param name="content">消息内容</param>
+await _notificationManager.SendBroadCastInformationMessageAsync(title,content);
+
+

依赖

+
    +
  • 如果服务分布式部署,需要使用 Redis(默认依赖),解决 SignalR 消息重复问题。
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
private void ConfigurationSignalR(ServiceConfigurationContext context)
+{
+    var redisConnection = context.Services.GetConfiguration()["Redis:Configuration"];
+    if (redisConnection.IsNullOrWhiteSpace())
+    {
+        throw new UserFriendlyException(message: "Redis连接字符串未配置.");
+    }
+    context.Services.AddSignalR()
+    .AddStackExchangeRedis(redisConnection, options => { options.Configuration.ChannelPrefix = "Lion.AbpPro"; });
+}
+
+

Vue 客户端连接

+
    +
  • 在用户登陆成功之后,连接 SignalR,并且带自动重连机制。源码
  • +
  • 示例如下:
  • +
+
TypeScript
1
+2
+3
+4
const { startConnect } = useSignalR();
+onMounted(() => {
+  startConnect();
+});
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/problem/ef/index.html b/user-guide/zh/problem/ef/index.html new file mode 100644 index 000000000..2c6c2de28 --- /dev/null +++ b/user-guide/zh/problem/ef/index.html @@ -0,0 +1,1702 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + EFCore - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

EFCore

+

创建时间,更新时间,删除时间

+
    +
  • +

    当实体继承了这三个属性得时候,只有在新增才会又创建时间,如果想要也有更新时间如何处理呢?

    +
  • +
  • +

    解决方式:重写 DbContext 一下方法

    +
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
namespace Lion.AbpPro.EntityFrameworkCore
+{
+    [ConnectionStringName("Default")]
+    public class AbpProDbContext :
+        AbpDbContext<AbpProDbContext>,
+        IAbpProDbContext
+    {
+        protected override void SetCreationAuditProperties(EntityEntry entry)
+        {
+            SetModificationAuditProperties(entry);
+            base.SetCreationAuditProperties(entry);
+        }
+
+        protected override void SetDeletionAuditProperties(EntityEntry entry)
+        {
+            SetModificationAuditProperties(entry);
+            base.SetDeletionAuditProperties(entry);
+        }
+    }
+}
+
+

设置数据库字符集格式

+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
namespace Lion.AbpPro.EntityFrameworkCore
+{
+    [ConnectionStringName("Default")]
+    public class AbpProDbContext :
+        AbpDbContext<AbpProDbContext>,
+        IAbpProDbContext
+    {
+        protected override void OnModelCreating(ModelBuilder builder)
+        {
+            builder.UseCollation("utf8mb4_unicode_ci");
+            builder.UseGuidCollation("utf8mb4_unicode_ci");
+            base.OnModelCreating(builder);
+        }
+    }
+}
+
+

全局设置字符串长度

+
    +
  • 当数据类型是 string 时,需要给每个字段指定长度很麻烦,以下提供统一处理方式。
  • +
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
namespace Lion.Pro.EntityFrameworkCore;
+
+/// <summary>
+/// ef迁移全局设置
+/// </summary>
+public static class LionDbContextGlobalSettingExtensions
+{
+    private const string Remark = "Remark";
+    private const string Description = "Description";
+    private const string CreationTime = "CreationTime";
+    private const string IndexPrefix = "IX_Default_";
+
+    public static void ConfigureGlobalSetting(this ModelBuilder builder)
+    {
+        ConfigureDefaultMaxLength(builder);
+        ConfigureIndexForCreationTime(builder);
+    }
+
+    /// <summary>
+    /// 配置默认字符串长度
+    /// </summary>
+    private static void ConfigureDefaultMaxLength(ModelBuilder builder)
+    {
+        var rules = ConfigureEntityMaxLengthOptions.Configure();
+        // 如果是abp表不全局修改长度
+        foreach (var property in builder.Model
+                     .GetEntityTypes()
+                     .Where(e => !e.GetTableName().StartsWith(AbpCommonDbProperties.DbTablePrefix))
+                     .SelectMany(t => t.GetProperties())
+                     .Where(e => e.ClrType == typeof(string)))
+        {
+            // 默认设置128长度
+           if (property.GetMaxLength() == null)
+            {
+                property.SetMaxLength(128);
+            }
+
+        }
+    }
+
+    /// <summary>
+    /// 创建时间添加索引
+    /// </summary>
+    private static void ConfigureIndexForCreationTime(ModelBuilder builder)
+    {
+        foreach (var property in builder.Model
+                     .GetEntityTypes()
+                     .Where(e => !e.GetTableName().StartsWith(AbpCommonDbProperties.DbTablePrefix))
+                     .SelectMany(t => t.GetProperties())
+                     .Where(e => e.Name == CreationTime))
+        {
+            var entityType = builder.Model.GetEntityTypes().Where(e => e.ClrType == property.DeclaringEntityType.ClrType).ToList().FirstOrDefault();
+            var indexName = IndexPrefix + entityType.GetTableName() + "_" + property.Name;
+            if (entityType.FindIndex(indexName) == null)
+            {
+                entityType?.AddIndex(property, indexName);
+            }
+        }
+    }
+}
+
+
C#
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
namespace Lion.AbpPro.EntityFrameworkCore
+{
+    [ConnectionStringName("Default")]
+    public class AbpProDbContext :
+        AbpDbContext<AbpProDbContext>,
+        IAbpProDbContext
+    {
+        protected override void OnModelCreating(ModelBuilder builder)
+        {
+            builder.ConfigureGlobalSetting();
+            base.OnModelCreating(builder);
+        }
+    }
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/user-guide/zh/problem/problem/index.html b/user-guide/zh/problem/problem/index.html new file mode 100644 index 000000000..8fe05694a --- /dev/null +++ b/user-guide/zh/problem/problem/index.html @@ -0,0 +1,1432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编译 - Lion.AbpPro + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +

编译

+

VS 编译项目字符串超过 256 个字符

+
    +
  • 把项目拷贝到磁盘根目录 OR 使用 Rider 开发
  • +
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + + + \ No newline at end of file