diff --git a/.docs/README.md b/.docs/README.md new file mode 100644 index 00000000..c7aaa452 --- /dev/null +++ b/.docs/README.md @@ -0,0 +1,2 @@ +BlogCore官方文档仓库地址已经迁移到: +https://gitee.com/laozhangIsPhi/Blog.Core.E-Book \ No newline at end of file diff --git a/.docs/contents/.vuepress/config.js b/.docs/contents/.vuepress/config.js deleted file mode 100644 index 6cb23754..00000000 --- a/.docs/contents/.vuepress/config.js +++ /dev/null @@ -1,46 +0,0 @@ -module.exports = { - title: 'Blog.Core', - description: 'Hello, 欢迎使用前后端分离之 ASP.NET Core 后端全家桶框架!', - base : '/.doc/', - head: [ - ['link', { - rel: 'icon', - href: `/favicon.ico` - }] - ], - dest: './contents/.vuepress/dist', - ga: '', - evergreen: true, - themeConfig: { - nav: [ - { text: '首页', link: '/' }, - { text: '指南', link: '/guide/' }, - { text: '更新日志', link: '/Update/' }, - { text: '压测', link: '/PressureTest/' }, - { text: '参与贡献', link: '/Contribution/' }, - { text: 'BCVP社区', link: '/QQ/' }, - { text: '接口API', link: 'http://apk.neters.club' }, - { text: '管理后台', link: 'http://vueadmin.neters.club' }, - { text: 'Github', link: 'https://github.com/anjoy8/Blog.Core' }, - ], - sidebarDepth: 2, - sidebar: { - '/guide/': getGuideSidebar('Guide'), - } - } -} - -function getGuideSidebar (groupA) { - return [ - { - title: groupA, - collapsable: false, - children: [ - '', - 'getting-started', - 'function-sheet', - 'cheat-sheet' - ] - } - ] - } \ No newline at end of file diff --git a/.docs/contents/.vuepress/public/bcvphomelogo.png b/.docs/contents/.vuepress/public/bcvphomelogo.png deleted file mode 100644 index e1bf0f79..00000000 Binary files a/.docs/contents/.vuepress/public/bcvphomelogo.png and /dev/null differ diff --git a/.docs/contents/.vuepress/public/favicon.ico b/.docs/contents/.vuepress/public/favicon.ico deleted file mode 100644 index 68062fe0..00000000 Binary files a/.docs/contents/.vuepress/public/favicon.ico and /dev/null differ diff --git a/.docs/contents/Contribution/README.md b/.docs/contents/Contribution/README.md deleted file mode 100644 index a29822b4..00000000 --- a/.docs/contents/Contribution/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# 贡献 - - -欢迎一起完善文档, -参与打赏的小可爱名单如下(单位:元),你们的贡献是我继续的动力: -(2021年9月9日 13点53分) - - -|序号|微信昵称|助力值|备注| -|-|-|-|-| -|01|排 * * 瓜|100|| -|02|船 * * 长|100|| -|03|二 * * 生|1|| -|04||1|未留微信号| -|05|旭 * * 光|10|| -|06||12.66|未留微信号| -|07|Ro * * st|10|| -|08|陈 * * 朝|10|| -|09|勇 * * 勇|10|| -|10|袁 * * 嘉|10|| -|11|En * * us|20|| -|12|风 * * 在|18|| -|13|林 * * 杰|10|| -|14|枫 * * 叶|10|| -|15|火 * * 鸟 |50|| -|16|阿 * * 福|10|| -|17||20|未留微信号| -|18|Er * * or|100|未留微信号| -|19|陶 * * ve|20|| -|20|熊 * * 育|50|| -|21|点 * * 痕|20|| -|22|夏 * * 目|20|| -|23|CL * * L|50|| -|24|rm * * rf|100|| -|25|Je * * ca|30|搜不到微信号| -|26|W * * 生|50|| -|27|鹏 * * 郎|20|| -|28|ws * * ai|10|| -|29|逐 * * 梦|20|| -|30|Jo * * aH|10|| -|31|Do * * n|10|| -|32|灰 * * 白|50|| -|33|Ne * * er|100|| -|34|Ar * * as|10|| -|35|吉 * * 祥|36|| -|36|ma * * y|10|| -|37|Yu * * ic|30|| -|38|亡 * * 死|30|| -|39|板 * * 根|20|| -|40|-- * * -|100|未留微信号| -|41|t * * |20|| -|42|王 * * 聪 |10|未留微信号| -|43|哈 * * 方|50|| -|44|le * * on|30|| -|45|李 * * |10|| -|46|不 * * 染|10|未留微信号| -|47|林 * * LIN|10|| -|48|阿 * * 奇|30|| -|49|哒 * * 哒|10|| -|50|王 * * 龙|100|| -|51|Ja * * Tu|100|| -|52|it * * hi|100|| -|53|沙 * * 锋|50|未留微信号| -|54|Ba * * ai|10|| -|55|古 * * 桐|10|| -|56|小 * * 柜|20|| -|57|rm * * rf|100|| -|58|高 * * 源|10|| -|59|Qq * * 80|10|未留微信号| -|60|如 * * 寄|20|| -|61|大 * * 灰|10|未留微信号| -|62|包 * * 华|10|微信号错误| -|63|西 * * 5°|30|| -|64|王 * * 粮|20|| -|65|Q1 * * ..|30|| -|66|Le * * ry|100|| -|67|bo * * t|30|| -|68|早 * * 蛋|10|| -|69|罗 * * 啊|10|| -|70|40 * * er|30|| -|71|胡 * * 运|30|| -|72|昌 * * 进|5|| -|73|甲 * * 学|15|| -|74|小 * * 走|100|| -|75|啊 * * 辉|50|| -|76|D * * W|100|| -|77|简 * * 简|99|| -|78|空 * * 空|101|| -|79|朱 * * 傻|20|| -|80|Mr * * hi|50|| -|81|王 * * 飞|110|| -|82|Mr * * nn|20|微信号未搜到| -|83|蓝 * * 兴|30|| -|84|Et * * ng|100|| -|85|Ke * * on|10|| -|86|几 * * 己|20|| -|87|张 * * 群|20|| -|88|睡 * * 鱼|30|| -|89|Mr * * 宋|50|| -|90|布 * * 布|10|| -|91|s * * s|6.6|| -|92|V * * V|50|| -|93|o**o|50|| -|94|书**6|6.6|| -|95|精**E|50|| -|96|有**幸|10|| -|97|r**y|10|| -|98|v**c|5|| -|99|j**n|20|| -|100|贾**琪|100|| -|101|a**s|20|| -|102|狂**牛|10|| -|103|风**宜|10|| -|104|鸠**夜|5|| -|105|大**灰|15|| -|106|不**懂|100|| -|107|圣**旨|50|| -|108|A**n|50|| -|109|薛**猫|100|| -|110|岁**叁|10|| -|111|K**g|100|| -|112|琛**田|30|| -|113|L**s|20|| -|114|小**哥|10|| -|115|G**e|10|| -|116|s**o|30|| -|117|大**白|50|| -|118|星**星|20|| -|119|沙**锋|200|| -|120|词**意|11|| -|121|A**N|20|| -|122|像**恋|10|| -|123|M**o|10|| -|124|风**虹|50|| -|125|健**诚|10|| -|126|**|5|| -|127|何**玖|6.66|| -|128|A**g|10|| -|129|高**广|20|| -|130|吴**杰|0.01|| -|131|刘**标|0.01|| - - diff --git a/.docs/contents/PressureTest/README.md b/.docs/contents/PressureTest/README.md deleted file mode 100644 index af2a2e93..00000000 --- a/.docs/contents/PressureTest/README.md +++ /dev/null @@ -1,80 +0,0 @@ -# 框架压测报告 - - -## 1、测试工具 -使用 `JMeter` 进行压力测试。 -测试时间:2021年1月21日 09点38分。 -服务器报告: - - - - -## 2、测试准备 -因为 `JMeter` 是使用 `JAVA` 写的,所以使用 `JMeter` 之前,先安装 `JAVA` 环境。 -安装好后,在 `bin` 文件夹下,点击 `jmeter.bat` 启动程序。 -启动之后会有两个窗口,一个`cmd`窗口,一个`JMeter`的 `GUI`。前面不要忽略`CMD`窗口的提示信息,不要关闭它。 -注意:使用`API`模式,不要使用`GUI`模式。 - - -## 3、测试配置 -本地发布后的 `windows` 环境,直接用 `kestrel` 启动。 -线程数:100 -循环数:1000 -HTTP默认值:协议:`http`;服务器或IP:`localhost`;端口号:`9291`; -HTTP请求:方法:GET;路径:`/api/blog/ApacheTestUpdate` -HTTP信息请求管理器:无 -响应断言:无 - - - -## 4、项目初始化 -目前采用 `Blog.Core` 默认的配置, -只开启了内存 `AOP` , -其他的都是默认的,然后也把任务调度也关闭了, -最后注意要把 `IP限流`给关闭,不然压测没效果,因为限流了: - - - -## 5、压测过程 - -##### 第一阶段 - - - - -##### 第二阶段 - - - - -##### 第三阶段 - - - - -##### 第四阶段(压测后,检测内存是否降低,20m后) - - - -##### 第五阶段(停止压测1h后) - - - -## 6、测试结果 -内存方面,`100*1000` 的 **压测过程中** (写操作),项目保证所占内存在 `350m~500m` 之间 -然后停止一个小时后,内存将为`150m~200m`: - - - - - - -## 7、压测配置文件下载 - [配置文件](https://img.neters.club/doc/blogcore_blog_ApacheTestUpdate.jmx) - 下载后,导入到工具里,可以直接测试,察看结果树。 - -## 8、Docker 镜像 - 已经提交到 `docker hub` 自行拉取操作即可: - ``` - docker pull laozhangisphi/apkimg:latest - ``` diff --git a/.docs/contents/QQ/README.md b/.docs/contents/QQ/README.md deleted file mode 100644 index 31b7f909..00000000 --- a/.docs/contents/QQ/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## 开源社区 - -bcvp - -[https://github.com/BaseCoreVueProject/Home](https://github.com/BaseCoreVueProject/Home) - -Base netCore (Vue) Project Team, -基于Net/Core 和Vue(react/ng),快速搭建 MVC & SPA 及微服务应用 -如果你有关于dotNet/core 的,不错的,可以正常运行,且一年内维护的,均可以加入。 -唯一宗旨:我们来自社区,服务社区,反哺社区。 - - - -## 微信公众号 - -公众号 - - diff --git a/.docs/contents/README.md b/.docs/contents/README.md deleted file mode 100644 index 597f16d9..00000000 --- a/.docs/contents/README.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -home: true -heroImage: /bcvphomelogo.png -actionText: 快速上手 → -actionLink: /guide/ -features: -- title: 详尽的文档 - details: 通过详细的文章和视频讲解,将知识点各个击破,入门ASP.Net Core不再难 -- title: 强大的社区 - details: 通过 QQ 群,和数千位同业大佬一起切磋交流。 -- title: 丰富的内容 - details: 框架涵盖ASP.Net Core开发中常见的基本知识点,不仅适合初学者入门,同时也适用于企业级别的开发。 -footer: MIT Licensed | Copyright © 2018-2020-老张的哲学 Powered by VUEPRESS on CentOS 7.6 ---- \ No newline at end of file diff --git a/.docs/contents/Update/README.md b/.docs/contents/Update/README.md deleted file mode 100644 index 870f98ec..00000000 --- a/.docs/contents/Update/README.md +++ /dev/null @@ -1,198 +0,0 @@ - -## 更新日志 - - -### 2021-08-21 - -重要功能增加:项目增加 `Apollo` 配置中心; - -### 2021-08-03 - -重要功能增加:项目增加 `ES` 搜索,增加 `Serilog` 使用 `tcp` 的方式自定义格式化,写入 `elk` 的实现; - -### 2021-06-28 - -功能增加:项目增加 `nacos` 配置,支持将项目注册到 `nacos` 服务中心,搭建微服务之子服务; - -### 2021-06-04 - -小功能更新:执行的时候,将 `Sql` 日志输出到控制台,方便查看,支持配置文件关闭; - -### 2021-05-01 - -组件更新:多项日志中间件,由自写组件,转为使用`serilog`组件记录日志; - -### 2021-03-03 - -项目目录调整:新增测试文件夹和模板文件夹; - -### 2021-02-09 - -重大项目更新:新增建行聚合支付; - -### 2021-01-11 - -更新:优化任务调度功能,新增暂停和停止; - -### 2020-12-02 - -更新:新增调用`MongoDB`功能,功能可用待完善中; - - -### 2020-11-19 -> 重大内容更新:更新项目模板 `Update Blog.Core.Webapi.Template.2.5.2.nupkg` -> 主要内容:1、泛型主键;2、通过测试中间件;3、`RabbitMQ`消息队列 - - -### 2020-11-18 - -项目更新:新增`RabbitMQ`消息队列和`EventBus`事件总线,功能可用待完善。 - - -### 2020-11-11 - -项目重大更新:更新至`.NET 5.0`。 - - -### 2020-11-05 - -项目更新:增加`测试用户`中间件,通过一键操作可以跳过权限限制,方便调试,文章[使用测试用户中间件](http://apk.neters.club/api/Blog/GoUrl?id=156)。 - - - -### 2020-10-11 - -项目更新:设计泛型主键功能,可以在项目初始化的时候设计主键类型。 - - - -### 2020-09-18 - -项目更新:更新项目模板 `Update Blog.Core.Webapi.Template.2.2.3.nupkg` 。 -> 1、增加 `Redis` 消息队列功能; - - - -### 2020-09-04 - -项目更新:增加 `Redis` 消息队列功能; - - -### 2020-08-06 - -项目更新:更新项目模板 `Update Blog.Core.Webapi.Template.2.2.0.nupkg` 。 -> 1、根据解决方案名,来自动导入model; -> 2、单独封装服务扩展层 `Blog.Core.Extensions` ; -> 3、代码生成器,支持控制器文件的生成; -> 4、弱化仓储层,用泛型仓储基类注入服务; - - - - -### 2020-08-01 - -> 重大结构更新:弱化仓储层,通过泛型仓储基类,来实现仓储服务注入,并去掉`Blog.Core.IRepository` 接口层; - -### 2020-07-03 - -> 更新:`DbFirstController` 生成四层文件,目前新增支持 `控制器Controller` 文件的输出; - - -### 2020-06-22 - -> 项目更新:将服务扩展和自定义中间件,单独封装一层 `Blog.Core.Extensions` ,更解耦。 - - - -### 2020-06-08 - -> 简单项目更新:生成数据库表结构的时候,利用反射机制,自动生成固定命名空间 `Blog.Core.Model.Models` 下的全部实体. -> 同时判断表是否存在,如果存在下次不再重复生成。 - - -### 2020-06-06 - -项目更新:更新项目模板 `Update Blog.Core.Webapi.Template.2.1.0.nupkg` [1a726f8](https://github.com/anjoy8/Blog.Core/commit/1a726f890e527c978982071462e82db4478632f0),更新项目即可 。 -> 1、配置内容展示到控制台; -> 2、简化封装 `Startup.cs` 类文件; -> 3、`DbFirst` 模式支持多库模式; -> 4、`Log4net` 讲异常和 `Info` 分开; -> 5、修复 `BlogLogAop` 偶尔卡顿问题; -> 6、将生成种子数据和任务调度功能,封装到中间件; -> 7、获取当前项目在服务器中的运行信息; -> 8、删除所有的不需要的 `using` 指令; - - - - -### 2020-05-29 -项目启动开启 `QuzrtzNet` 调度任务,并且在 `Admin` 后台管理中配置操作界面; -> 内容更新:封装生成种子数据的入口方法; - - - -### 2020-05-12 -修复:支持多库模式下,生成项目模板代码 `DbFirstController` [102c6d6](https://github.com/anjoy8/Blog.Core/commit/102c6d6bfcafd06bf5241844759dea5e7a6815da) -> 注意:`T4` 模板不能此功能,一次只能一个数据库,且只能 `SqlServer` - - -### 2020-05-07 -> 重大内容更新:更新项目模板 `Update Blog.Core.Webapi.Template.2.1.0.nupkg` [7f64fde](https://github.com/anjoy8/Blog.Core/commit/7f64fde5507f7a8572372dcadb6af5110bd37d68) - - -### 2020-05-06 -> 重大内容更新:优化Log4Net使用方案,完美配合 `NetCore` 官方的 `ILogger`, [ecaffb6](https://github.com/anjoy8/Blog.Core/commit/ecaffb66bdf10a90c087d01e6e817e54f23a97d4) - - -### 2020-05-01 - -> 重要内容更新:配合Admin全部完成按钮级别权限,更新初始化种子数据 - -### 2020-04-27 - -增加功能:配合前端Admin,增加页面 `KeepAlive` 功能; -增加功能:增加 `Sql` 语句查询Demo,支持返回 `DataTable`; - - -### 2020-04-25 - -增加功能:`Http api` 接口调用,满足微服务需求 -> 重要内容更新:优化 `Appsettings.app()` 方法,通过官方 `IConfiguration` 接口来获取DBS连接字符串; -> 优化 `BlogLogAOP.cs` - - -### 2020-04-15 - -> 重大内容更新:更新项目模板 `Update Blog.Core.Webapi.Template.1.11.30.nupkg` - - -### 2020-04-14 -> 重大内容更新:主分支,可以通过配置,一键切换JWT和Ids4认证授权模式 - - -### 2020-03-30 -> 重大内容更新:统一所有接口返回格式 - - -### 2020-03-25 -增加功能:支持读写分离(目前是三种模式:单库、多库、读写分离) -> 重大BUG更新:系统登录接口,未对用户软删除进行判断,现已修复 -> API: /api/login/GetJwtToken3 -> Code: await _sysUserInfoServices.Query(d => d.uLoginName == name && d.uLoginPWD == pass && d.tdIsDelete == false); - - - -### 2020-03-18 -增加功能:创建 Quartz.net 任务调度服务 - - -### 2020-01-09 -增加功能:项目迁移到IdentityServer4,统一授权认证中心 - - -### 2020-01-05 -增加功能:设计一个简单的中间件,可以查看所有已经注入的服务 - - -### 2020-01-04 -增加功能:Ip限流,防止过多刷数据 diff --git a/.docs/contents/guide/README.md b/.docs/contents/guide/README.md deleted file mode 100644 index 30950e0c..00000000 --- a/.docs/contents/guide/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# W 文档指南 -## 亮点与优势 - -Blog.Core 是一个开箱即用的企业级权限管理应用框架。 -采用最新的前后端完全分离技术【 ASP.NET Core Api 5.0 + Vue 2.x 】。 -并结合 `IdentityServer4` ,可快速解决多客户端和多资源服务的统一认证与鉴权的问题。 - -## 其他资料 - -博客园,早期架构搭建:[博客园](https://www.cnblogs.com/laozhang-is-phi/p/9495618.html) -公众号,后期调整:[文章](https://mvp.neters.club/MVP_aspnetcore_2020/2020) -视频:[B站](https://www.bilibili.com/video/BV1vC4y1p7Za) - - -## 配套站点 - -本资源服务器,配合多个项目,构建前后端权限一体化平台,前端用 `VUE` 框架。 -前端-客户端:[预览](https://vueadmin.neters.club/)、[源码](https://github.com/anjoy8/Blog.Admin) -前端-管理后台:[预览](http://vueblog.neters.club/)、[源码](https://github.com/anjoy8/Blog.Vue) -认证平台:[预览](https://ids.neters.club/)、[源码](https://github.com/anjoy8/Blog.IdentityServer) - - -### 为什么选择 ASPNET.Core -1、【开源】`ASPNET.NET Core` 是由 `Microsoft` 和 `.NET` 社区在 `GitHub` 上开源并维护的一个跨平台(支持 Windows、macOS 和 Linux)的新一代高性能框架, -拥有十分广泛的社区与支持者,可用于构建web应用、物联网IOT应用和移动端应用。 -2、【高效】Asp.net core(.net core)来源于.net,很容易迁移,而且也很容易上手, -但是又是不同的一个框架,除了上述对.net开发者十分友好以外,相对于之前的.net项目,速度上有巨大的改进, -相比与原来的`Web(.net framework 4.6)`程序性能提升了`2300%`。跟`python`、`java`等相同环境比较,性能都要优越, -参考[www.techempower.com](https://www.techempower.com/benchmarks/)。 -3、【跨平台】可以在`Windows`、`Mac`和`Linux`构建和运行跨平台的`Asp.Net Core`应用。 -4、【云原生】在云原生领域拥有天然的优势,搭配Azure云服务,配合K8s,更好的实现分布式应用,以及微服务应用。 -5、【微服务】`ASP.NET Core`尤其适用于微服务架构,也就是说ASP.NET Core不仅适合于中小型项目而且还特别适合于大型,超大型项目。 -6、【大公司】目前国内采用`ASP.NET Core`的大公司比如腾讯、网易,国际的有Bing,GoDaddy,Stackoverflow,Adobe,Microsoft -7、【总结来说】,`java`支持的,`ASPNET.Core`都支持,而且更轻量级、更高效跨,并且对.net开发者十分友好,微服务案例成熟。 - - - -### 框架功能点 -1、丰富完整的接口文档,在查看的基础上,可以模拟前端调用,更方便。 -2、采用多层开发,隔离性更好,封装更完善。 -3、基于项目模板,可以一键创建自己的项目。 -4、搭配代码生成器,实现快速开发,节省成本。 -5、项目集成多库模式以及读写分离模式,可以同时处理多个数据库的不同模块,更快更安全。 -6、集成统一认证平台 `IdentityServer4` ,实现多个项目的统一认证管理,解决了之前一个项目, -一套用户的弊端,更适用微服务的开发。 -7、丰富的审计日志处理,方便线上项目快速定位异常点。 -8、支持自由切换多种数据库,Sqlite/SqlServer/MySql/PostgreSQL/Oracle; -9、支持 `Docker` 容器化开发,可以搭配 k8s 更好的实现微服务。 - - -### 应用领域 -1、【对接第三方api】项目通过`webapi`,可以快速对接第三方`api`服务,实现业务逻辑。 -2、【前后端分离】 采用的是`API`+前端的完全分离的开发模式,满足平时开发的所有需求, -你可以对接任何的自定义前端项目:无论是微信小程序,还是授权APP,无论是PC网页, -还是手机H5。 -3、【多项目】同时框架还集成了一套鉴权平台,采用IdentityServer4,可以快速的实现多个客户端的认证与授权服务, -从而大大的减少了平时的工作量,可以快速的进行产品迭代。 -4、【微服务】当然,因为采用的是API模式,所以同样适用于微服务项目,实现高并发的产品需求。 - - - -### 市场前景 -1、前后端分离模式已经是目前的主流开发模式,框架已经是一套可行的方案,开箱即用。 -2、拥有几十篇技术文档和3000人的技术社区,方便快捷的解决问题。 -3、目前已经有超过20多家公司在生产环境中使用,当然实际中更多,具体查看 [点击查看使用的情况](https://github.com/anjoy8/Blog.Core/issues/75)。 -4、同时可以搭配自己的业务,实现微服务的开发,在大数据高并发中,占有更好的优势。 -5、本项目直接作者由微软MVP“老张的哲学”出品,并长久维护,不会断更,有保障。 - - - -## 功能与进度 - -框架模块: -- [√] 采用`仓储+服务+接口`的形式封装框架; -- [√] 异步 async/await 开发; -- [√] 接入国产数据库ORM组件 —— SqlSugar,封装数据库操作; -- [√] 支持自由切换多种数据库,MySql/SqlServer/Sqlite/Oracle/Postgresql/达梦/人大金仓; -- [√] 实现项目启动,自动生成种子数据 ✨; -- [√] 五种日志记录,审计/异常/请求响应/服务操作/Sql记录等; -- [√] 支持项目事务处理(若要分布式,用cap即可)✨; -- [√] 设计4种 AOP 切面编程,功能涵盖:日志、缓存、审计、事务 ✨; -- [√] 支持 T4 代码模板,自动生成每层代码; -- [√] 或使用 DbFirst 一键创建自己项目的四层文件(支持多库); -- [√] 封装`Blog.Core.Webapi.Template`项目模板,一键重建自己的项目 ✨; -- [√] 搭配多个前端案例供参考和借鉴:Blog.Vue、Blog.Admin、Nuxt.tbug、Blog.Mvp.Blazor ✨; -- [√] 统一集成 IdentityServer4 认证 ✨; - -组件模块: -- [√] 提供 Redis 做缓存处理; -- [√] 使用 Swagger 做api文档; -- [√] 使用 MiniProfiler 做接口性能分析 ✨; -- [√] 使用 Automapper 处理对象映射; -- [√] 使用 AutoFac 做依赖注入容器,并提供批量服务注入 ✨; -- [√] 支持 CORS 跨域; -- [√] 封装 JWT 自定义策略授权; -- [√] 使用 Log4Net 日志框架,集成原生 ILogger 接口做日志记录; -- [√] 使用 SignalR 双工通讯 ✨; -- [√] 添加 IpRateLimiting 做 API 限流处理; -- [√] 使用 Quartz.net 做任务调度(目前单机多任务,集群调度暂不支持); -- [√] 支持 数据库`读写分离`和多库操作 ✨; -- [√] 新增 Redis 消息队列 ✨; -- [√] 新增 RabbitMQ 消息队列 ✨; -- [√] 新增 EventBus 事件总线 ✨; -- [√] 新增 实现聚合支付; -- [ ] 计划 - 数据部门权限; -- [ ] 计划 - ES 搜索; - -微服务模块: -- [√] 可配合 Docker 实现容器化; -- [√] 可配合 Jenkins 实现CI / CD; -- [√] 可配合 Consul 实现服务发现; -- [√] 可配合 Ocelot 实现网关处理; -- [√] 可配合 Nginx 实现负载均衡; -- [√] 可配合 Ids4 实现认证中心; - - -  - - - - diff --git a/.docs/contents/guide/cheat-sheet.md b/.docs/contents/guide/cheat-sheet.md deleted file mode 100644 index 36f95e36..00000000 --- a/.docs/contents/guide/cheat-sheet.md +++ /dev/null @@ -1,580 +0,0 @@ -# Z 主要知识点 - - - -## AOP - -本项目多处采用面向切面编程思想——AOP,除了广义上的过滤器和中间件以外,主要通过动态代理的形式来实现AOP编程思想,主要的案例共有四个,分别是: -1、服务日志AOP; -2、服务InMemory缓存AOP; -3、服务Redis缓存AOP; -4、服务事务AOP; - - -具体的代码可以在 `Blog.Core\Blog.Core\AOP` 文件夹下查看。 - -与此同时,多个AOP也设置了阀门来控制是否开启,具体的可以查看 `appsettings.json` 中的: - -``` - "AppSettings": { - "RedisCachingAOP": { - "Enabled": false, - "ConnectionString": "127.0.0.1:6319" - }, - "MemoryCachingAOP": { - "Enabled": true - }, - "LogAOP": { - "Enabled": false - }, - "TranAOP": { - "Enabled": false - }, - "SqlAOP": { - "Enabled": false - } - }, - -``` - -## Appsettings - -整个系统通过一个封装的操作类 `Appsettings.cs` 来控制配置文件 `appsettings.json` 文件, -操作类地址在:`\Blog.Core.Common\Helper` 文件夹下。 -具体的使用方法是: - -``` -Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }) - -// 里边的参数,按照 appsettings.json 中设置的层级顺序来写,可以获取到指定的任意内容。 - -``` - - - -## AspNetCoreRateLimit - -系统使用 `AspNetCoreRateLimit` 组件来实现ip限流: -1、添加 `nuget` 包: -``` - -``` - -2、注入服务 `IpPolicyRateLimitSetup.cs` -``` -services.AddIpPolicyRateLimitSetup(Configuration); -``` - -3、配置中间件 -``` - // Ip限流,尽量放管道外层 - app.UseIpRateLimiting(); -``` - -4、配置数据 - -具体的内容,自行百度即可 -``` - "IpRateLimiting": { - "EnableEndpointRateLimiting": true, - "StackBlockedRequests": false, - "RealIpHeader": "X-Real-IP", - "ClientIdHeader": "X-ClientId", - "HttpStatusCode": 429,//返回状态码 - "GeneralRules": [//规则,结尾一定要带* - { - "Endpoint": "*", - "Period": "1m", - "Limit": 120 - }, - { - "Endpoint": "*:/api/blog*", - "Period": "1m", - "Limit": 30 - } - ] - - } -``` - - - -## Async-Await - -整个系统采用 async/await 异步编程,符合主流的开发模式, -特别是对多线程开发很友好。 - - - -## Authorization-Ids4 - -本系统 v2.0 版本(目前的系统已经集成 `ids4` 和 `jwt`,并且可以自由切换),已经支持了统一授权认证,和 `blog` 项目、`Admin` 项目、`DDD` 项目等一起,使用一个统一的认证中心。 - -具体的代码参考:`.\Blog.Core\Extensions` 文件夹下的 `Authorization_Ids4Setup.cs` ,注意需要引用指定的 `nuget` 包,核心代码如下: - -``` - //【认证】 - services.AddAuthentication(o => - { - o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - o.DefaultChallengeScheme = nameof(ApiResponseHandler); - o.DefaultForbidScheme = nameof(ApiResponseHandler); - }) - // 2.添加Identityserver4认证 - .AddIdentityServerAuthentication(options => - { - options.Authority = Appsettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" }); - options.RequireHttpsMetadata = false; - options.ApiName = Appsettings.app(new string[] { "Startup", "IdentityServer4", "ApiName" }); - options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt; - options.ApiSecret = "api_secret"; - - }) - - -``` - -### 如何在Swagger中配置Ids4? -很简单,直接在 `SwaggerSetup.cs` 中直接接入 `oauth、Implicit` 即可: - -``` - //接入identityserver4 - c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows - { - Implicit = new OpenApiOAuthFlow - { - AuthorizationUrl = new Uri($"{Appsettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" })}/connect/authorize"), - Scopes = new Dictionary { - { - "blog.core.api","ApiResource id" - } - } - } - } - }); - -``` - -然后在 `IdentityServer4` 项目中,做指定的修改,配置 `9291` 的回调地址: - -``` - new Client { - ClientId = "blogadminjs", - ClientName = "Blog.Admin JavaScript Client", - AllowedGrantTypes = GrantTypes.Implicit, - AllowAccessTokensViaBrowser = true, - - RedirectUris = - { - "http://vueadmin.neters.club/callback", - // 这里要配置回调地址 - "http://localhost:9291/oauth2-redirect.html" - }, - PostLogoutRedirectUris = { "http://vueadmin.neters.club" }, - AllowedCorsOrigins = { "http://vueadmin.neters.club" }, - - AllowedScopes = { - IdentityServerConstants.StandardScopes.OpenId, - IdentityServerConstants.StandardScopes.Profile, - "roles", - "blog.core.api" - } - }, - -``` - -然后再 `Swagger` 中,配置登录授权: - -swagger - - -## Authorization-JWT - -如果你不想使用 `IdentityServer4` 的话,也可以使用 `JWT` 认证,同样是是`Blog.Core\Blog.Core\Extensions` 文件夹下的 `AuthorizationSetup.cs` 中有关认证的部分: - -``` - 1.添加JwtBearer认证服务 -.AddJwtBearer(o => -{ - o.TokenValidationParameters = tokenValidationParameters; - o.Events = new JwtBearerEvents - { - OnAuthenticationFailed = context => - { - // 如果过期,则把<是否过期>添加到,返回头信息中 - if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) - { - context.Response.Headers.Add("Token-Expired", "true"); - } - return Task.CompletedTask; - } - }; -}) - -``` - - -## AutoMapper - -使用 `AutoMapper` 组件来实现 `Dto` 模型的传输转换,具体的用法,可以查看: -`Blog.Core\Blog.Core\Extensions` 文件夹下的 `AutoMapperSetup.cs` 扩展类, -通过引用 `AutoMapper` 和 `AutoMapper.Extensions.Microsoft.DependencyInjection` 两个 `nuget` 包,并设置指定的 `profile` 文件,来实现模型转换控制。 - -``` -// 比如如何定义: - public class CustomProfile : Profile - { - /// - /// 配置构造函数,用来创建关系映射 - /// - public CustomProfile() - { - CreateMap(); - CreateMap(); - } - } - - -// 比如如何使用 -models = _mapper.Map(blogArticle); - -``` - -具体的查看项目中代码即可。 - - - - -## CORS - -在线项目使用的是 `nginx` 跨域代理,但是同时也是支持 `CORS` 代理: -1、注入服务 `services.AddCorsSetup();` 具体代码 `Blog.Core\Blog.Core\Extensions` 文件夹下的 `CorsSetup.cs` 扩展类; -2、配置中间件 `app.UseCors("LimitRequests");` ,要注意中间件顺序; -3、配置自己项目的前端端口,通过在 `appsettings.json` 文件中配置自己的前端项目 `ip:端口` ,来实现跨域: - -``` - "Startup": { - "Cors": { - "IPs": "http://127.0.0.1:2364,http://localhost:2364,http://localhost:8080,http://localhost:8021,http://localhost:1818" - } - }, - -``` - - -## DI-AutoFac - -项目使用了依赖注入,除了原生的依赖注入以外,更多的使用的是第三方组件 `Autofac` : -1、引用依赖包: -``` - - - -``` -主要是第一个 `nuget` 包,下边的是为了实现动态代理 `AOP` 操作; - -2、项目之间采用引用解耦的方式,通过反射来注入服务层和仓储层的程序集 `dll` 来实现批量注入,更方便,以后每次新增和修改 `Service` 层和 `Repository` 层,只需要 `F6` 编译一下即可,具体代码查看 `Startup.cs`: - -``` - - - // 注意在CreateDefaultBuilder中,添加Autofac服务工厂 - public void ConfigureContainer(ContainerBuilder builder) - { - var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; - //builder.RegisterType().As(); - - - #region 带有接口层的服务注入 - - - var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); - var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); - - if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile))) - { - throw new Exception("Repository.dll和service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。"); - } - - - - // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 - var cacheType = new List(); - if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool()) - { - builder.RegisterType(); - cacheType.Add(typeof(BlogRedisCacheAOP)); - } - if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) - { - builder.RegisterType(); - cacheType.Add(typeof(BlogCacheAOP)); - } - if (Appsettings.app(new string[] { "AppSettings", "TranAOP", "Enabled" }).ObjToBool()) - { - builder.RegisterType(); - cacheType.Add(typeof(BlogTranAOP)); - } - if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool()) - { - builder.RegisterType(); - cacheType.Add(typeof(BlogLogAOP)); - } - - // 获取 Service.dll 程序集服务,并注册 - var assemblysServices = Assembly.LoadFrom(servicesDllFile); - builder.RegisterAssemblyTypes(assemblysServices) - .AsImplementedInterfaces() - .InstancePerDependency() - .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; - .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。 - - // 获取 Repository.dll 程序集服务,并注册 - var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); - builder.RegisterAssemblyTypes(assemblysRepository) - .AsImplementedInterfaces() - .InstancePerDependency(); - - #endregion - - #region 没有接口层的服务层注入 - - //因为没有接口层,所以不能实现解耦,只能用 Load 方法。 - //注意如果使用没有接口的服务,并想对其使用 AOP 拦截,就必须设置为虚方法 - //var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); - //builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); - - #endregion - - #region 没有接口的单独类 class 注入 - - //只能注入该类中的虚方法 - builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) - .EnableClassInterceptors() - .InterceptedBy(cacheType.ToArray()); - - #endregion - - - // 这里和注入没关系,只是获取注册列表,请忽略 - tsDIAutofac.AddRange(assemblysServices.GetTypes().ToList()); - tsDIAutofac.AddRange(assemblysRepository.GetTypes().ToList()); - } - -``` - -3、然后 `Program.cs` 中也要加一句话:` .UseServiceProviderFactory(new AutofacServiceProviderFactory()) //<--NOTE THIS ` - - - -## DI-NetCore - -除了主要的 `Autofac` 依赖注入以外,也减少的使用了原生的依赖注入方式,很简单,比如这样的: -``` - - services.AddSingleton(); - // 注入权限处理器 - services.AddScoped(); - services.AddSingleton(permissionRequirement); -``` - - -## Filter - -项目中一共有四个过滤器 -``` -1、GlobalAuthorizeFilter.cs —— 全局授权配置,添加后,就可以不用在每一个控制器上添加 [Authorize] 特性,但是3.1版本好像有些问题,【暂时放弃使用】; -2、GlobalExceptionFilter.cs —— 全局异常处理,实现 actionContext 级别的异常日志收集; -3、GlobalRoutePrefixFilter.cs —— 全局路由前缀公约,统计在路由上加上前缀; -4、UseServiceDIAttribute.cs —— 测试注入,【暂时无用】; -``` -文件地址在 `.\Blog.Core\Filter` 文件夹下,其中核心的是 `2` 个,重点使用的是 `1` 个 —— 全局异常错误日志 `GlobalExceptionsFilter`: -通过注册在 `MVC` 服务 `services.AddControllers()` 中,实现全局异常过滤: -``` - services.AddControllers(o => - { - // 全局异常过滤 - o.Filters.Add(typeof(GlobalExceptionsFilter)); - // 全局路由权限公约 - //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); - // 全局路由前缀,统一修改路由 - o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); - }) -``` - - - -## Framework - -项目采用 `服务+仓储+接口` 的多层结构,使用依赖注入,并且通过解耦项目,较完整的实现了 `DIP` 原则: -高层模块不应该依赖于底层模块,二者都应该依赖于抽象。 -抽象不应该依赖于细节,细节应该依赖于抽象。 - -同时项目也封装了: -`CodeFirst` 初始化数据库以及数据; -`DbFirst` 根据数据库(支持多库),生成多层代码,算是简单代码生成器; -其他功能,[核心功能与进度](http://apk.neters.club/.doc/guide/#%E5%8A%9F%E8%83%BD%E4%B8%8E%E8%BF%9B%E5%BA%A6) - - - - -## Log - -通过集成 `Log4Net` 组件,完美配合 `NetCore` 官方的 `ILogger` 接口,实现对日志的管控,引用 `nuget` 包 `Microsoft.Extensions.Logging.Log4Net.AspNetCore`: -Program.cs -``` - webBuilder - .UseStartup() - .ConfigureLogging((hostingContext, builder) => - { - //该方法需要引入Microsoft.Extensions.Logging名称空间 - builder.AddFilter("System", LogLevel.Error); //过滤掉系统默认的一些日志 - builder.AddFilter("Microsoft", LogLevel.Error);//过滤掉系统默认的一些日志 - - //添加Log4Net - //var path = Directory.GetCurrentDirectory() + "\\log4net.config"; - //不带参数:表示log4net.config的配置文件就在应用程序根目录下,也可以指定配置文件的路径 - //需要添加nuget包:Microsoft.Extensions.Logging.Log4Net.AspNetCore - builder.AddLog4Net(); - }); - -``` - -然后直接在需要的地方注入使用,比如在控制器中 -` public UserController(ILogger logger)` - -然后就可以使用了。 - -> 注意:日志 其实是分为两部分的: -> netcore输出(控制台、输出窗口等) 和 `ILogger` 持久化 -> 两者对应配置也不一样,就比如上边的过滤,是针对日志持久化的,如果想要对控制台进行控制,需要配置 `appsettings.json` 中的 `Logging` 节点 - - -## MemoryCache - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 - -## Middleware - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## MiniProfiler - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 - -## publish -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 - - -## Redis - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## Repository -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## SeedData - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## SignalR - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## SqlSugar - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## SqlSugar-Codefirst&DataSeed - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## SqlSugar-SqlAOP - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## Swagger - -精力有限,还是更新中... -如果你愿意帮忙,可以直接在GitHub中,提交pull request, -我会在后边的贡献者页面里,列出你的名字和项目地址做推广 -## T4 - -项目集成 `T4` 模板 `.\Blog.Core.FrameWork` 层,目的是可以一键生成项目模板代码。 -1、需要在 `DbHelper.ttinclude` 中配置连接数据库连接字符串; -2、针对每一层的代码,就去指定的 `.tt` 模板,直接 `CTRL+S` 保存即可; - -> 注意,目前的代码是 `SqlServer` 版本的,其他数据库版本的,可以去群文件查看。 - - -## Test-xUnit - -项目简单使用了单元测试,通过 `xUnit` 组件,具体的可以查看 `Blog.Core.Tests` 层相关代码。 -目前单元测试用例还比较少,大家可以自行添加。 - - -## Temple-Nuget - -本项目封装了 `Nuget` 自定义模板,你可以根据这个模板,一键创建自己的项目名,具体的操作,可以双击项目根目录下的 `CreateYourProject.bat` ,可以参考 [#如何项目重命名](http://apk.neters.club/.doc/guide/getting-started.html#%E5%A6%82%E4%BD%95%E9%A1%B9%E7%9B%AE%E9%87%8D%E5%91%BD%E5%90%8D) - -同时,你也可以再 `Nuget` 管理器中,搜索到: -nuget - - - -## UserInfo - - -项目中封装了获取用户信息的代码: -在 `.\Blog.Core.Common\HttpContextUser` 文件夹下 `AspNetUser.cs` 实现类和 `IUser.cs` 接口。 - -如果使用,首先需要注册相应的服务,参见:`.\Blog.Core\Extensions` 文件夹下的 `HttpContextSetup.cs`; -然后,就直接在控制器构造函数中,注入接口 `IUser` 即可; - -> `注意`: -> 1、如果要想获取指定的服务,必须登录,也就是必须要在 `Header` 中传递有效 `Token` ,这是肯定的。 -> 2、如果要获取用户信息,一定要在中间件 `app.UseAuthentication()` 之后(不要问为什么),控制器肯定在它之后,所以能获取到; -> 3、`【并不是】`一定需要添加 `[Authorize]` 特性,如果你加了这个特性,可以直接获取,但是如果不加,可以从我的 `AspNetUser.cs` 方法中,有一个直接从 `Header` 中解析的方法 `List GetUserInfoFromToken(string ClaimType);`: - -``` - public string GetToken() - { - return _accessor.HttpContext.Request.Headers["Authorization"].ObjToString().Replace("Bearer ", ""); - } - - public List GetUserInfoFromToken(string ClaimType) - { - - var jwtHandler = new JwtSecurityTokenHandler(); - if (!string.IsNullOrEmpty(GetToken())) - { - JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(GetToken()); - - return (from item in jwtToken.Claims - where item.Type == ClaimType - select item.Value).ToList(); - } - else - { - return new List() { }; - } - } - -``` diff --git a/.docs/contents/guide/function-sheet.md b/.docs/contents/guide/function-sheet.md deleted file mode 100644 index d81ad13f..00000000 --- a/.docs/contents/guide/function-sheet.md +++ /dev/null @@ -1,471 +0,0 @@ -# H 核心功能一览表 - -## 一、表结构解析 - -`Blog.Core` 项目共包含四部分的数据库表结构,分别是:用户角色管理部分、接口菜单权限管理部分、博客文章管理部分、以及其他不重要部分。 -> 注意:目前不提供与维护数据库数据,直接通过 `SeedData` 生成种子数据; - -### 1、用户角色管理部分[必须] -主要是三个表:分别对应用户表(sysUserInfo)、角色表(Role)、用户角色关系表(UserRole)。 - -usermanager - - - -### 2、接口菜单权限管理部分[必须] - -主要是四个表:分别对应接口表(Module)、菜单表(Permission)、接口菜单关系表(ModulePermission)暂时没用到、角色接口菜单关系表(RoleModulePermission)。 - -permissionmanager - - - - -### 3、博客文章管理部分[可选] -主要是三个表:分别对应博客表(BlogArticle)、Bug专题表(Topic)、Bug内容表(TopicDetail)。 - -blogmanager - - - - -### 4、其他不重要部分 - -主要是三个表:分别对应Job调度表(TasksQz)、密码库表(PasswordLib)、操作日志表(OperateLog)、广告表(Advertisement)、公告表(Guestbook)。 - -othersmanager - - - - - - -## 二、日志记录 - -本框架涵盖了不同领域的日志记录,共五个,分别是: - -1、全局异常日志 - - 开启方式:无需操作。 - 文件路径:web目录下,Log/GlobalExcepLogs_{日期}.log。 - 功能描述:记录项目启动后出现的所有异常日志,不包括中间件中异常。 - - -2、IP 请求日志 - - 开启方式:无需操作。 - 文件路径:web目录下,Log/RequestIpInfoLog.log。 - 功能描述:记录项目启动后客户端请求的ip和接口信息。 - 举例来说: - {"Ip":"xxx.xx.xx.x","Url":"/api/values","Datetime":"2020-01-06 18:02:19","Date":"2020-01-06","Week":"周一"} - - -3、用户API访问日志 - - 开启方式:appsettings.json -> Middlewar -> RecordAccessLogs 节点为true。 - 文件路径:web目录下,Log/RecordAccessLogs_{日期}.log。 - 功能描述:记录项目启动后客户端所有的API访问日志,包括参数、body以及用户信息。 - - -4、服务层请求响应AOP日志 - - 开启方式:appsettings.json -> AppSettings -> LogAOP 节点为true。 - 文件路径:web目录下,Log/AOPLog.log。 - 功能描述:记录项目启动请求api后,所有的service层日志,包括方法名、参数、响应结果或用户(非必须)。 - - -5、数据库操作日志 - - 开启方式:appsettings.json -> AppSettings -> SqlAOP 节点为true。 - 文件路径:web目录下,Log/SqlLog.log。 - 功能描述:记录项目启动请求api并访问service后,所有的db操作日志,包括Sql参数与Sql语句。 - 举例来说: - -------------------------------- - 1/6/2020 6:13:04 PM| - 【SQL参数】:@bID0:1 - 【SQL语句】:SELECT `bID`,`bsubmitter`,`btitle`,`bcategory`,`bcontent`,`btraffic`,`bcommentNum`,`bUpdateTime`,`bCreateTime`,`bRemark`,`IsDeleted` FROM `BlogArticle` WHERE ( `bID` = @bID0 ) - - - ## 三、控制台信息展示 - - 配置 - - - - ## 四、Nginx一览表 - - - -``` -#user nobody; -worker_processes 1; - -#error_log logs/error.log; -#error_log logs/error.log notice; -#error_log logs/error.log info; - -#pid logs/nginx.pid; -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - server_names_hash_bucket_size 64; - - #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - # '$status $body_bytes_sent "$http_referer" ' - # '"$http_user_agent" "$http_x_forwarded_for"'; - - #access_log logs/access.log main; - sendfile on; - #tcp_nopush on; - - #keepalive_timeout 0; - keepalive_timeout 600; - proxy_read_timeout 600; - proxy_send_timeout 600; - - proxy_buffer_size 128k; - proxy_buffers 32 32k; - proxy_busy_buffers_size 128k; - - #gzip on; - - ###################################################################### - server { - listen 80; - server_name www.neters.club; - - #charset koi8-r; - - #access_log logs/host.access.log main; - location / { - root C:\code\Code\Neters\home; - index index.html index.htm; - } - } - - server { - listen 80; - server_name neters.club; - - #charset koi8-r; - - #access_log logs/host.access.log main; - location / { - root C:\code\Code\Neters\home; - - index index.html index.htm; - } - } - - server { - listen 80; - server_name ids.neters.club; - rewrite ^(.*)$ https://$host$1 permanent;#把http的域名请求转成https,第二种写法在此节的末端 - - #charset koi8-r; - - #access_log logs/host.access.log main; - location / { - #proxy_pass http://localhost:5004; - root html; - index index.html index.htm; - } - } - - server { - listen 443 ssl; - server_name ids.neters.club; #网站域名,和80端口保持一致 - ssl on; - ssl_certificate 1_ids.neters.club_bundle.crt; #证书公钥 - ssl_certificate_key 2_ids.neters.club.key; #证书私钥 - ssl_session_cache shared:SSL:1m; - ssl_session_timeout 5m; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers ECDH:AESGCM:HIGH:!RC4:!DH:!MD5:!3DES:!aNULL:!eNULL; - ssl_prefer_server_ciphers on; - - error_page 497 https://$host$uri?$args; - - location / { - proxy_pass http://localhost:5004; - proxy_redirect off; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_set_header Cookie $http_cookie; - #proxy_cookie_path - chunked_transfer_encoding off; - } - } - - server { - listen 80; - server_name apk.neters.club; - - #charset koi8-r; - - #access_log logs/host.access.log main; - location / { - root html; - proxy_pass http://localhost:9291; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection keep-alive; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - index index.html index.htm; - } - - location /.doc/ { - proxy_pass http://docs.neters.club/; - } - } - - server { - listen 80; - server_name docs.neters.club; - - location / { - root C:\code\Code\Blog.Core\.docs\contents\.vuepress\dist; - index index.html index.htm; - } - } - - server { - listen 80; - server_name vueadmin.neters.club; - - location / { - try_files $uri $uri/ /index.html; - root C:\code\Code\Blog.Admin\distis; - #proxy_pass http://localhost:2364; - index index.html index.htm; - } - - location /api/ { - rewrite ^.+apb/?(.*)$ /$1 break; - include uwsgi_params; - proxy_pass http://localhost:9291; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - #proxy_set_header Connection "upgrade"; - #proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - location /api2/ { - rewrite ^.+apb/?(.*)$ /$1 break; - include uwsgi_params; - proxy_pass http://localhost:9291; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - location /images/ { - include uwsgi_params; - proxy_pass http://localhost:9291; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - #proxy_set_header Connection "upgrade"; - #proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - location /.doc/ { - proxy_pass http://docsadmin.neters.club/; - } - - error_page 404 /404.html; - - # redirect server error pages to the static page /50x.html - # - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } - - server { - listen 80; - server_name docsadmin.neters.club; - - location / { - root C:\code\Code\Blog.Admin\.doc\contents\.vuepress\dist; - index index.html index.htm; - } - } - - - server { - listen 80; - server_name ddd.neters.club; - location / { - proxy_pass http://localhost:4773; - index index.php index.html index.htm; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection keep-alive; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - } - } - - - server { - listen 80; - server_name ask.neters.club; - - #charset koi8-r; - - #access_log logs/host.access.log main; - location / { - root html; - proxy_pass http://localhost:5020; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - #proxy_set_header Connection "upgrade"; - #proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - index index.html index.htm; - } - } - - - server { - listen 80; - server_name vueblog.neters.club; - - location / { - try_files $uri $uri/ /index.html; - root C:\code\Code\Blog.Vue\dist; - index index.html index.htm; - } - - - location /api { - rewrite ^.+apb/?(.*)$ /$1 break; - include uwsgi_params; - proxy_pass http://localhost:9291; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - - location /images { - include uwsgi_params; - proxy_pass http://localhost:9291; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - error_page 404 /404.html; - - # redirect server error pages to the static page /50x.html - # - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } - - upstream nodenuxt { - server 127.0.0.1:3089; # nuxt 项目监听PC端端口 - keepalive 64; - } - server { - listen 80; - server_name tibug.neters.club; - - location / { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Nginx-Proxy true; - proxy_cache_bypass $http_upgrade; - proxy_pass http://nodenuxt; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } - - server { - listen 80; - server_name jwt.neters.club; - - location / { - root C:\code\Code\jwttoken; - index index.html index.htm; - } - - error_page 404 /404.html; - - # redirect server error pages to the static page /50x.html - # - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } -} - -``` -> 这里说明下,我的 `Nginx` 文件中,`Ids4` 项目强制使用 `Https` ,采用的是直接跳转,这也是一个办法,当然还有第二种办法(感谢 `tibos`): -``` -server { - listen 80; - server_name admin.wmowm.com; - location / { - proxy_pass http://localhost:9002; - index index.php index.html index.htm; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection keep-alive; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - } -} - -server { - listen 443 ssl;#监听443端口(https默认端口) - server_name admin.wmowm.com; #填写绑定证书的域名 - ssl_certificate /etc/nginx/conf.d/key/admin.wm.crt;#填写你的证书所在的位置 - ssl_certificate_key /etc/nginx/conf.d/key/admin.wm.key;#填写你的key所在的位置 - ssl_session_timeout 5m; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置 - ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置 - ssl_prefer_server_ciphers on; - location / { - proxy_pass http://localhost:9002; - index index.php index.html index.htm; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection keep-alive; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - -} -``` \ No newline at end of file diff --git a/.docs/contents/guide/getting-started.md b/.docs/contents/guide/getting-started.md deleted file mode 100644 index 9c4f6b0d..00000000 --- a/.docs/contents/guide/getting-started.md +++ /dev/null @@ -1,132 +0,0 @@ -# K 快速上手 -注意 - -请确保你的 `Visual Studio 2019` 版本 >= `16.8.2`。 -并安装 `.NET 5.0 SDK` - - -## 下载 -Github(国际) 下载 [https://github.com/anjoy8/Blog.Core](https://github.com/anjoy8/Blog.Core) - -Gitee(国内) 下载 [https://gitee.com/laozhangIsPhi/Blog.Core](https://gitee.com/laozhangIsPhi/Blog.Core) - - -## 编译与运行 -1、拿到项目后,双击 `Blog.Core.sln` 解决方案; -2、首先 `F6` 编译,看是否有错误; -3、然后 `F5` 运行,调起 `9291` 端口,浏览器查看效果; -4、因为系统默认的是 `sqlite` 数据库,如果你想换其他数据库,请看下边; -5、注意:本系统是直接自动生成数据库和数据的,不用手动创建数据库; - - - - -## CodeFirst 与 DbFirst -1、项目同时支持两个常见开发模式:`CodeFirst` 和 `DbFirst`; -2、首先 如果你是第一次下载我的项目,肯定是想要浏览效果和直接使用对应的权限相关的内容,这个时候肯定需要用到数据库表结构,那就肯定需要 `CodeFirst` ,只需要在`appsettings.json` 里配置好数据库连接字符串(下文会说到如何配置),就能正确运行; -3、浏览器查看效果,或者配合 `Admin` 项目查看效果后,如果感觉项目可行,并打算在此基础上二次开发,那肯定会在你刚刚创建的数据库种去创建新的表结构,这个时候就需要使用 `DbFirst` 模式,来生成四层项目问题:Model+Service+Repository等; -4、你可以使用T4模板,但是我更建议使用 `/api/DbFirst/GetFrameFiles` 接口来生成,不仅支持多种类型的数据库,还支持同时多库模式的输出; -5、如果你不想用我的表结构和实体类,在项目启动的时候,把配置文件的 `SeedDBEnabled`节点设置成False即可,然后配置对应的你自己的数据库连接字符串,比如是商城的,然后使用 `/api/DbFirst/GetFrameFiles` 接口来生成你的数据库四层类文件; - - - -## 如何配置数据库连接字符串 - -1、打开 `Blog.Core` 项目下的 `appsettings.json` 文件; -2、修改 `DBS` 字节内容,配置对应的连接字符串,注意`DBType`对应不同的数据库类型; -3、把你想要运行的数据库 `Enabled` 为 `true` 即可,其他都要设置 `false`; -4、然后 `MainDB` 设置为下边你使用的指定 `ConnId`: - -``` - "MainDB": "WMBLOG_MSSQL", //当前项目的主库,所对应的连接字符串的Enabled必须为true - "MutiDBEnabled": false, //是否开启多库 - "DBS": [ - { - "ConnId": "WMBLOG_SQLITE", - "DBType": 2,// sqlite数据库 - "Enabled": true,// 设置为true,启用1 - "Connection": "WMBlog.db" //只写数据库名就行 - }, - { - "ConnId": "WMBLOG_MSSQL", - "DBType": 1,// sqlserver数据库 - "Enabled": true,// 设置为true,启用2 - "Connection": "Server=.;Database=WMBlogDB;User ID=sa;Password=123;", - "ProviderName": "System.Data.SqlClient" - }, - { - "ConnId": "WMBLOG_MYSQL", - "DBType": 0,// mysql - "Enabled": false,// false 不启用 - "Connection": "Server=localhost; Port=3306;Stmt=; Database=wmblogdb; Uid=root; Pwd=456;" - }, - { - "ConnId": "WMBLOG_ORACLE", - "DBType": 3,// Oracle - "Enabled": false,// 不启用 - "Connection": "Provider=OraOLEDB.Oracle; Data Source=WMBlogDB; User Id=sss; Password=789;" - } - ], -``` - - -5、如果你想多库操作,需要配置 -``` - a:MainDB 设置为主库的 ConnId; - b:MutiDBEnabled设置为true, - c:把下边想要连接的多个连接字符串都设置为true -``` - -## 如何配置项目端口号 -1、在 `Blog.Core` 层下的 `program.cs` 文件中,将 `9291`端口,修改为自己想要的端口号; -2、或者在 `launchSettings.json` 中设置(`注意,如果仅仅修改这里,publish后,端口访问无效`); - -## 如何项目重命名 -1、双击项目根目录下的 `CreateYourProject.bat` 批处理文件; -2、根据提示,`在Dos窗口内`输入自己想要的项目名称即可; -3、在根目录会有一个 `.1YourProject` 文件夹,里边即你的项目; - - -## 新增实体模块后如何迁移到数据库 -1、在 `Blog.Core.Model` 项目目录下的 `Seed` 文件夹下,找到 `DBSeed` 类; -2、根据提示,找到生成table的地方 `myContext.CreateTableByEntity`; -3、添加进去你新增的实体类,当然也可以用下边的单独写法; -4、编译项目,没错后,运行,则数据库更新完毕; - - -## 新增实体,如何进行增删改查CURD操作 -1、随便找一个含有业务逻辑的 `controller` 参考一下即可; -2、主要 `api` 是通过 `Service` 服务层提供业务逻辑; -3、然后服务层通过 `Repository` 仓储层封装持久化操作; -4、每一个表基本上对应一个仓储类,基本的操作都封装到了 `BaseRepository.cs` 基类仓储中; -5、添加完业务逻辑,记得要 `F6` 重新编译一下,因为项目间引用解耦了; -6、项目已经自动注入了,直接在控制器使用对应的服务层接口就行: `IxxxxService` ; - - -## 新增数据库表,如何反向生成四层文件 -1、可以通过 `T4` 模板来生成,在 `Blog.Core.FrameWork` 层,使用方法: [9757999.html](https://www.cnblogs.com/laozhang-is-phi/p/9757999.html#autoid-4-3-0) ; -> 注意:这种方案,目前默认的只能是 `SqlServer` ,其他类型的数据库,可以看上边文章中的代码,或者群文件里对应的代码。 - -> 1、修改`DbHelper.ttinclude`文件中的连接字符串,注意是`SqlServer`的: public static readonly string ConnectionString; -> 2、然后去各个层模板文件,点击`Ctrl+S`; -> 3、就会在对应的层内,看到新文件,比如:Blog.Core.Model/Model_NEW - - - -2、也可以通过 `Sqlsugar` 所带的方法来实现 `DbFirst`,具体查看 `Controller` 层下的 `DbFirstController.cs`; - -3、总体操作过程,可以参考我的视频:[av77612407](https://www.bilibili.com/video/av77612407?p=2) ; - - -## 发布与部署 -1、双击项目根目录下的 `Blog.Core.Publish.bat`批处理文件; -2、执行完成后,根目录会有一个`.PublishFiles` 文件夹,就是发布后的项目; - - -## 如何更新项目模板 -1、着急的话自己打包,不着急就提 `issue`,等我更新; -2、我的开源项目中,有个模板项目 `BlogCoreTempl` [地址](https://github.com/anjoy8/BlogCoreTempl),下载下来; -3、下载最新的 `Blog.Core` 源代码; -4、将源代码拷贝到模板项目的 `content` 文件夹下; -5、双击 `Package.bat` 文件,就生成了最新的模板了; - diff --git a/.docs/package.json b/.docs/package.json deleted file mode 100644 index 3f0483bf..00000000 --- a/.docs/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "BCVP", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC" -} diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index faa57fc4..f11fee1f 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -12,7 +12,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Build with dotnet run: dotnet build --configuration Release - name: Build image diff --git a/.gitignore b/.gitignore index b98046c6..99804f89 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ bld/ # Visual Studio 2017 auto generated files Generated\ Files/ +# Visual Studio Code +.vscode + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* @@ -352,3 +355,8 @@ Blog.Core/WMBlog.db Blog.Core/Blog.Core*.xml Blog.Core.Api/WMBlog.db Blog.Core.Api/wwwroot/ui/ +Blog.Core.Api/Logs +*.db +/Blog.Core.Api/WMBlog.db-journal +.docs/.vuepress/dist/ +Blog.Core.Api/wwwroot/Temp/Sessions diff --git a/Blog.Core.Api/Blog.Core.Api.csproj b/Blog.Core.Api/Blog.Core.Api.csproj index 38575a55..8bc327ab 100644 --- a/Blog.Core.Api/Blog.Core.Api.csproj +++ b/Blog.Core.Api/Blog.Core.Api.csproj @@ -1,14 +1,12 @@  - Exe - - net6.0 enable Linux true + default @@ -26,36 +24,51 @@ + + + + + + + + + + + + + + + + + - - - - - - - + + + + + @@ -82,6 +95,9 @@ Always + + PreserveNewest + @@ -94,7 +110,6 @@ - @@ -104,4 +119,4 @@ - + \ No newline at end of file diff --git a/Blog.Core.Api/Blog.Core.Model.xml b/Blog.Core.Api/Blog.Core.Model.xml index 36975d11..00b4c5be 100644 --- a/Blog.Core.Api/Blog.Core.Model.xml +++ b/Blog.Core.Api/Blog.Core.Model.xml @@ -200,7 +200,7 @@ 用户访问趋势日志 - + 用户 @@ -301,6 +301,16 @@ 逻辑删除 + + + 评论 + + + + + 博客文章 评论 + + 部门表 @@ -666,6 +676,11 @@ 菜单图标 + + + 菜单图标新 + + 菜单描述 @@ -822,6 +837,122 @@ 修改时间 + + + 状态
+ 中立字段,某些表可使用某些表不使用 +
+
+ + + 中立字段,某些表可使用某些表不使用
+ 逻辑上的删除,非物理删除
+ 例如:单据删除并非直接删除 +
+
+ + + 中立字段
+ 是否内置数据 +
+
+ + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 更新者 + + + + + 修改日期 + + + + + 数据版本 + + + + + 软删除 过滤器 + + + + + 系统租户表
+ 根据TenantType 分为两种方案:
+ 1.按租户字段区分
+ 2.按租户分库
+ +
+ + 注意:
+ 使用租户Id方案,无需配置分库的连接 +
+
+ + + 名称 + + + + + 租户类型 + + + + + 数据库/租户标识 不可重复
+ 使用Id方案,可无需配置 +
+
+ + + 主机
+ 使用Id方案,可无需配置 +
+
+ + + 数据库类型
+ 使用Id方案,可无需配置 +
+
+ + + 数据库连接
+ 使用Id方案,可无需配置 +
+
+ + + 状态 + + + + + 备注 + + 用户信息表 @@ -887,6 +1018,96 @@ 登录账号 + + + 租户Id + + + + + 任务日志表 + + + + + 任务ID + + + + + 任务耗时 + + + + + 执行结果(0-失败 1-成功) + + + + + 运行时间 + + + + + 结束时间 + + + + + 执行参数 + + + + + 异常信息 + + + + + 异常堆栈 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + 任务名称 + + + + + 任务分组 + + 任务计划表 @@ -952,6 +1173,11 @@ 循环执行次数 + + + 已循环次数 + + 是否启动 @@ -972,6 +1198,63 @@ 任务内存中的状态 + + + 业务数据
+ 多租户 (Id 隔离) +
+
+ + + 无需手动赋值 + + + + + 名称 + + + + + 金额 + + + + + 多租户-多表方案 业务表 子表
+
+
+ + + 多租户-多表方案 业务表
+
+
+ + + 名称 + + + + + 金额 + + + + + 多租户-多库方案 业务表
+ 公共库无需标记[MultiTenant]特性 +
+
+ + + 名称 + + + + + 金额 + + Tibug 类别 @@ -1512,11 +1795,6 @@ 修改时间 - - - ID - - 部门表 @@ -1684,6 +1962,11 @@ 找不到指定资源 + + + 数据库读取类型 + + 表格数据,支持分页 @@ -1709,6 +1992,43 @@ 返回数据集 + + + 租户模型接口 + + + + + 租户Id + + + + + 标识 多租户 的业务表
+ 默认设置是多库
+ 公共表无需区分 直接使用主库 各自业务在各自库中
+
+
+ + + 租户隔离方案 + + + + + Id隔离 + + + + + 库隔离 + + + + + 表隔离 + + 广告类 diff --git a/Blog.Core.Api/Blog.Core.xml b/Blog.Core.Api/Blog.Core.xml index b48d95ee..0ed61c91 100644 --- a/Blog.Core.Api/Blog.Core.xml +++ b/Blog.Core.Api/Blog.Core.xml @@ -26,14 +26,14 @@ - + 获取博客详情 - + 获取详情【无权限】 @@ -67,7 +67,7 @@ - + 删除博客 @@ -177,7 +177,7 @@ 登录管理【无权限】 - + 构造函数注入 @@ -186,6 +186,7 @@ + @@ -254,7 +255,7 @@ 接口管理 - + 获取全部接口api @@ -276,7 +277,7 @@ - + 删除一条接口 @@ -361,7 +362,7 @@ 菜单管理 - + 构造函数 @@ -369,20 +370,22 @@ + - + 获取菜单 + - + 查询树形 Table @@ -404,7 +407,7 @@ - + 获取菜单树 @@ -412,21 +415,21 @@ - + 获取路由树 - + 获取路由树 - + 通过角色获取菜单 @@ -440,7 +443,7 @@ - + 删除菜单 @@ -454,13 +457,10 @@ - + - 菜单同步 + 系统接口菜单同步接口 - 接口module的控制器名称 - 菜单permission的父id - 是否执行迁移到数据 @@ -490,7 +490,7 @@ - + 删除角色 @@ -519,42 +519,42 @@ - + 删除一个任务 - + 启动计划任务 - + 停止一个计划任务 - + 暂停一个计划任务 - + 恢复一个计划任务 - + 重启一个计划任务 @@ -567,13 +567,25 @@ - + 立即执行任务 + + + 获取任务运行日志 + + + + + + 任务概况 + + + 类别管理【无权限】 @@ -614,7 +626,7 @@ - + 获取详情【无权限】 @@ -635,14 +647,14 @@ - + 删除 bug - + 测试事务在AOP中的使用 @@ -697,7 +709,7 @@ - + 删除用户 @@ -733,7 +745,7 @@ - + 新建用户角色关系 @@ -746,20 +758,15 @@ Values控制器 - + - ValuesController + 测试Rabbit消息队列发送 + + + + + 测试Rabbit消息队列订阅 - - - - - - - - - - @@ -842,12 +849,6 @@ - - - 测试http请求 WebApiClient Get - - - 测试Fulent做参数校验 @@ -878,6 +879,25 @@ 通过此处的key格式为 xx:xx:x + + + 获取雪花Id + + + + + + 测试缓存 + + + + + + 雪花Id To DateTime + + + + WeChatCompanyController @@ -979,8 +999,6 @@ 微信公众号管理 - 作者:胡丁文 - 时间:2020-3-29 21:24:12 @@ -1061,6 +1079,13 @@ 卡片消息对象 + + + 推送卡片消息接口 + + 卡片消息对象 + + 推送文本消息 @@ -1188,7 +1213,7 @@ 关键字 - + 获取部门树 @@ -1235,28 +1260,339 @@ - + - 自定义路由 /api/{version}/[controler]/[action] + SignalR测试 - + - 分组名称,是来实现接口 IApiDescriptionGroupNameProvider + 向指定用户发送消息 + + + - + - 自定义路由构造函数,继承基类路由 + 向指定角色发送消息 - + + + - + - 自定义版本+路由构造函数,继承基类路由 + 分表demo + + + + + 分页获取数据 + + + + + + + + + + + 根据ID获取信息 + + + + + + + 添加一条测试数据 + + + + + + + 修改一条测试数据 + + + + + + + 根据id删除数据 + + + + + + + SqlSugar 相关测试 + + + + + SqlSugar 相关测试 + + + + + 测试建表后,SqlSugar缓存 + + + + + + 缓存管理 + + + + + 缓存管理 + + + + + 获取全部缓存 + + + + + + 获取缓存 + + + + + + 新增 + + + + + + 删除全部缓存 + + + + + + 删除缓存 + + + + + + 数据库管理 + + + + + 获取库配置 + + + + + + 获取表信息 + + 配置Id + 读取类型 + + + + + 获取表字段 + + 表名 + ConfigId + 读取类型 + + + + + 编辑表备注 + + + + + + 编辑列备注 + + + + + + 动态建表 CURD + + + + + 动态type + + + + + + 动态type 继承BaseEntity + + + + + + 测试建表 + + + + + + 测试查询 + + + + + + 测试写入 + + + + + + 多租户-多库方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增数据 + + + + + + 多租户-Id方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增业务数据 + + + + + + 多租户-多表方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增数据 + + + + + + 租户管理 + + + + + 获取全部租户 + + + + + + 获取租户信息 + + + + + + 新增租户信息
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 修改租户信息
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 删除租户
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 枚举测试 + + + + + 获取学生信息 + + 学生类型 + + + 学生信息 + + + + 学生类型 + + + + + 小学生 + + + + + 中学生 + + + + + 大学生 + + + + + 学生姓名 + + + + + 学生年龄 + + + + + 学生类型 - - @@ -1299,5 +1635,28 @@ 全局路由前缀公约 + + + 自定义路由 /api/{version}/[controler]/[action] + + + + + 分组名称,是来实现接口 IApiDescriptionGroupNameProvider + + + + + 自定义路由构造函数,继承基类路由 + + + + + + 自定义版本+路由构造函数,继承基类路由 + + + + diff --git a/Blog.Core.Api/Controllers/BaseApiController.cs b/Blog.Core.Api/Controllers/BaseApiController.cs index 5a374ded..97a938ea 100644 --- a/Blog.Core.Api/Controllers/BaseApiController.cs +++ b/Blog.Core.Api/Controllers/BaseApiController.cs @@ -4,82 +4,85 @@ namespace Blog.Core.Controllers { - public class BaseApiController : Controller - { - [NonAction] - public MessageModel Success(T data, string msg = "成功") - { - return new MessageModel() - { - success = true, - msg = msg, - response = data, - }; - } - // [NonAction] - //public MessageModel Success(T data, string msg = "成功",bool success = true) - //{ - // return new MessageModel() - // { - // success = success, - // msg = msg, - // response = data, - // }; - //} - [NonAction] - public MessageModel Success(string msg = "成功") - { - return new MessageModel() - { - success = true, - msg = msg, - response = null, - }; - } - [NonAction] - public MessageModel Failed(string msg = "失败", int status = 500) - { - return new MessageModel() - { - success = false, - status = status, - msg = msg, - response = null, - }; - } - [NonAction] - public MessageModel Failed(string msg = "失败", int status = 500) - { - return new MessageModel() - { - success = false, - status = status, - msg = msg, - response = default, - }; - } - [NonAction] - public MessageModel> SuccessPage(int page, int dataCount, int pageSize, List data, int pageCount, string msg = "获取成功") - { + public class BaseApiController : Controller + { + [NonAction] + public MessageModel Success(T data, string msg = "成功") + { + return new MessageModel() + { + success = true, + msg = msg, + response = data, + }; + } - return new MessageModel>() - { - success = true, - msg = msg, - response = new PageModel(page, dataCount, pageSize, data) + // [NonAction] + //public MessageModel Success(T data, string msg = "成功",bool success = true) + //{ + // return new MessageModel() + // { + // success = success, + // msg = msg, + // response = data, + // }; + //} + [NonAction] + public MessageModel Success(string msg = "成功") + { + return new MessageModel() + { + success = true, + msg = msg, + response = null, + }; + } + + [NonAction] + public MessageModel Failed(string msg = "失败", int status = 500) + { + return new MessageModel() + { + success = false, + status = status, + msg = msg, + response = null, + }; + } - }; - } - [NonAction] - public MessageModel> SuccessPage(PageModel pageModel, string msg = "获取成功") - { + [NonAction] + public MessageModel Failed(string msg = "失败", int status = 500) + { + return new MessageModel() + { + success = false, + status = status, + msg = msg, + response = default, + }; + } - return new MessageModel>() - { - success = true, - msg = msg, - response = pageModel - }; - } - } -} + [NonAction] + public MessageModel> SuccessPage(int page, int dataCount, int pageSize, List data, + int pageCount, string msg = "获取成功") + { + return new MessageModel>() + { + success = true, + msg = msg, + response = new PageModel(page, dataCount, pageSize, data) + }; + } + + [NonAction] + public MessageModel> SuccessPage(PageModel pageModel, string msg = "获取成功") + { + return new MessageModel>() + { + success = true, + msg = msg, + response = pageModel + }; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/BlogController.cs b/Blog.Core.Api/Controllers/BlogController.cs index 6525448e..d0e9a235 100644 --- a/Blog.Core.Api/Controllers/BlogController.cs +++ b/Blog.Core.Api/Controllers/BlogController.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; +using System.Linq.Expressions; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Blog.Core.Common.Helper; using Blog.Core.IServices; using Blog.Core.Model; @@ -11,7 +8,7 @@ using Blog.Core.SwaggerHelper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; +using Serilog; using StackExchange.Profiling; using static Blog.Core.Extensions.CustomApiVersion; @@ -87,7 +84,7 @@ public async Task>> Get(int id, int page = 1 [HttpGet("{id}")] //[Authorize(Policy = "Scope_BlogModule_Policy")] [Authorize] - public async Task> Get(int id) + public async Task> Get(long id) { return Success(await _blogArticleServices.GetBlogDetails(id)); } @@ -100,15 +97,16 @@ public async Task> Get(int id) /// [HttpGet] [Route("DetailNuxtNoPer")] - public async Task> DetailNuxtNoPer(int id) + public async Task> DetailNuxtNoPer(long id) { _logger.LogInformation("xxxxxxxxxxxxxxxxxxx"); + Log.Information("yyyyyyyyyyyyyyyyy"); return Success(await _blogArticleServices.GetBlogDetails(id)); } [HttpGet] [Route("GoUrl")] - public async Task GoUrl(int id = 0) + public async Task GoUrl(long id = 0) { var response = await _blogArticleServices.QueryById(id); if (response != null && response.bsubmitter.IsNotEmptyOrNull()) @@ -140,7 +138,7 @@ public async Task>> GetBlogsByTypesForMVP(string [HttpGet] [Route("GetBlogByIdForMVP")] - public async Task> GetBlogByIdForMVP(int id = 0) + public async Task> GetBlogByIdForMVP(long id = 0) { if (id > 0) { @@ -251,7 +249,7 @@ public async Task> Put([FromBody] BlogArticle BlogArticle) [HttpDelete] [Authorize(Permissions.Name)] [Route("Delete")] - public async Task> Delete(int id) + public async Task> Delete(long id) { if (id > 0) { diff --git a/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs index 4f51856d..553dee75 100644 --- a/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs +++ b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs @@ -36,19 +36,19 @@ public MessageModel GetFrameFiles() { var data = new MessageModel() { success = true, msg = "" }; data.response += @"file path is:C:\my-file\}"; - var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool(); + var isMuti = BaseDBConfig.IsMulti; if (Env.IsDevelopment()) { data.response += $"Controller层生成:{FrameSeed.CreateControllers(_sqlSugarClient)} || "; - BaseDBConfig.MutiConnectionString.allDbs.ToList().ForEach(m => + BaseDBConfig.ValidConfig.ForEach(m => { - _sqlSugarClient.ChangeDatabase(m.ConnId.ToLower()); - data.response += $"库{m.ConnId}-Model层生成:{FrameSeed.CreateModels(_sqlSugarClient, m.ConnId, isMuti)} || "; - data.response += $"库{m.ConnId}-IRepositorys层生成:{FrameSeed.CreateIRepositorys(_sqlSugarClient, m.ConnId, isMuti)} || "; - data.response += $"库{m.ConnId}-IServices层生成:{FrameSeed.CreateIServices(_sqlSugarClient, m.ConnId, isMuti)} || "; - data.response += $"库{m.ConnId}-Repository层生成:{FrameSeed.CreateRepository(_sqlSugarClient, m.ConnId, isMuti)} || "; - data.response += $"库{m.ConnId}-Services层生成:{FrameSeed.CreateServices(_sqlSugarClient, m.ConnId, isMuti)} || "; + _sqlSugarClient.ChangeDatabase(m.ConfigId.ToString().ToLower()); + data.response += $"库{m.ConfigId}-Model层生成:{FrameSeed.CreateModels(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-IRepositorys层生成:{FrameSeed.CreateIRepositorys(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-IServices层生成:{FrameSeed.CreateIServices(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-Repository层生成:{FrameSeed.CreateRepository(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; + data.response += $"库{m.ConfigId}-Services层生成:{FrameSeed.CreateServices(_sqlSugarClient, m.ConfigId.ToString(), isMuti)} || "; }); // 切回主库 @@ -74,7 +74,7 @@ public MessageModel GetFrameFilesByTableNames([FromBody]string[] tableNa { ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; - var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool(); + var isMuti = BaseDBConfig.IsMulti; var data = new MessageModel() { success = true, msg = "" }; if (Env.IsDevelopment()) { @@ -102,7 +102,7 @@ public MessageModel GetFrameFilesByTableNamesForEntity([FromBody] string { ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; - var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool(); + var isMuti = BaseDBConfig.IsMulti; var data = new MessageModel() { success = true, msg = "" }; if (Env.IsDevelopment()) { @@ -112,7 +112,7 @@ public MessageModel GetFrameFilesByTableNamesForEntity([FromBody] string { data.success = false; data.msg = "当前不处于开发模式,代码生成不可用!"; - } + } return data; } /// @@ -126,7 +126,7 @@ public MessageModel GetFrameFilesByTableNamesForController([FromBody] st { ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; - var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool(); + var isMuti = BaseDBConfig.IsMulti; var data = new MessageModel() { success = true, msg = "" }; if (Env.IsDevelopment()) { @@ -151,7 +151,7 @@ public MessageModel GetAllFrameFilesByTableNames([FromBody]string[] tabl { ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; - var isMuti = AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool(); + var isMuti = BaseDBConfig.IsMulti; var data = new MessageModel() { success = true, msg = "" }; if (Env.IsDevelopment()) { diff --git a/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs b/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs index 8006d1fb..7865cc69 100644 --- a/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs +++ b/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs @@ -81,7 +81,7 @@ public async Task> DataMigrateFromOld2New() InitPermissionTree(permissions, permissionsAllList, apiList); var actionPermissionIds = permissionsAllList.Where(d => d.Id >= filterPermissionId).Select(d => d.Id).ToList(); - List filterPermissionIds = new(); + List filterPermissionIds = new(); FilterPermissionTree(permissionsAllList, actionPermissionIds, filterPermissionIds); permissions = permissions.Where(d => filterPermissionIds.Contains(d.Id)).ToList(); @@ -93,10 +93,10 @@ public async Task> DataMigrateFromOld2New() // 1、保持菜单和接口 await SavePermissionTreeAsync(permissions, pms); - var rid = 0; - var pid = 0; - var mid = 0; - var rpmid = 0; + long rid = 0; + long pid = 0; + long mid = 0; + long rpmid = 0; // 2、保存关系表 foreach (var item in rmps) @@ -116,8 +116,8 @@ public async Task> DataMigrateFromOld2New() } } - pid = (pms.FirstOrDefault(d => d.PidOld == item.PermissionId)?.PidNew).ObjToInt(); - mid = (pms.FirstOrDefault(d => d.MidOld == item.ModuleId)?.MidNew).ObjToInt(); + pid = (pms.FirstOrDefault(d => d.PidOld == item.PermissionId)?.PidNew).ObjToLong(); + mid = (pms.FirstOrDefault(d => d.MidOld == item.ModuleId)?.MidNew).ObjToLong(); // 关系 if (rid > 0 && pid > 0) { @@ -282,7 +282,7 @@ private void InitPermissionTree(List permissionsTree, List permissionsAll, List actionPermissionId, List filterPermissionIds) + private void FilterPermissionTree(List permissionsAll, List actionPermissionId, List filterPermissionIds) { actionPermissionId = actionPermissionId.Distinct().ToList(); var doneIds = permissionsAll.Where(d => actionPermissionId.Contains(d.Id) && d.Pid == 0).Select(d => d.Id).ToList(); @@ -295,7 +295,7 @@ private void FilterPermissionTree(List permissionsAll, List act } } - private async Task SavePermissionTreeAsync(List permissionsTree, List pms, int permissionId = 0) + private async Task SavePermissionTreeAsync(List permissionsTree, List pms, long permissionId = 0) { var parendId = permissionId; @@ -304,9 +304,9 @@ private async Task SavePermissionTreeAsync(List permissionsTree, Lis PM pm = new PM(); // 保留原始主键id pm.PidOld = item.Id; - pm.MidOld = (item.Module?.Id).ObjToInt(); + pm.MidOld = (item.Module?.Id).ObjToLong(); - var mid = 0; + long mid = 0; // 接口 if (item.Module != null) { @@ -351,9 +351,9 @@ private async Task SavePermissionTreeAsync(List permissionsTree, Lis public class PM { - public int PidOld { get; set; } - public int MidOld { get; set; } - public int PidNew { get; set; } - public int MidNew { get; set; } + public long PidOld { get; set; } + public long MidOld { get; set; } + public long PidNew { get; set; } + public long MidNew { get; set; } } } diff --git a/Blog.Core.Api/Controllers/DepartmentController.cs b/Blog.Core.Api/Controllers/DepartmentController.cs index 1674883f..faf1f850 100644 --- a/Blog.Core.Api/Controllers/DepartmentController.cs +++ b/Blog.Core.Api/Controllers/DepartmentController.cs @@ -4,17 +4,10 @@ using Blog.Core.Model; using Blog.Core.Model.Models; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Hosting; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Linq.Expressions; using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Api.Controllers { @@ -90,7 +83,7 @@ public async Task>> GetTreeTable(long f = 0, strin foreach (var item in departments) { - List pidarr = new() { }; + List pidarr = new() { }; var parent = departmentList.FirstOrDefault(d => d.Id == item.Pid); while (parent != null) @@ -116,7 +109,7 @@ public async Task>> GetTreeTable(long f = 0, strin /// /// [HttpGet] - public async Task> GetDepartmentTree(int pid = 0) + public async Task> GetDepartmentTree(long pid = 0) { var departments = await _departmentServices.Query(d => d.IsDeleted == false); var departmentTrees = (from child in departments @@ -175,7 +168,7 @@ public async Task> Put([FromBody] Department request) } [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { var data = new MessageModel(); var model = await _departmentServices.QueryById(id); diff --git a/Blog.Core.Api/Controllers/ImgController.cs b/Blog.Core.Api/Controllers/ImgController.cs index 11d9d209..5ba85388 100644 --- a/Blog.Core.Api/Controllers/ImgController.cs +++ b/Blog.Core.Api/Controllers/ImgController.cs @@ -54,11 +54,11 @@ public FileStreamResult DownImg() public async Task> InsertPicture([FromForm]UploadFileDto dto) { - if (dto.Files == null || !dto.Files.Any()) return Failed("请选择上传的文件。"); + if (dto.file == null || !dto.file.Any()) return Failed("请选择上传的文件。"); //格式限制 var allowType = new string[] { "image/jpg", "image/png", "image/jpeg" }; - var allowedFile = dto.Files.Where(c => allowType.Contains(c.ContentType)); + var allowedFile = dto.file.Where(c => allowType.Contains(c.ContentType)); if (!allowedFile.Any()) return Failed("图片格式错误"); if (allowedFile.Sum(c => c.Length) > 1024 * 1024 * 4) return Failed("图片过大"); @@ -79,7 +79,7 @@ public async Task> InsertPicture([FromForm]UploadFileDto dt } } - var excludeFiles = dto.Files.Except(allowedFile); + var excludeFiles = dto.file.Except(allowedFile); if (excludeFiles.Any()) { diff --git a/Blog.Core.Api/Controllers/LoginController.cs b/Blog.Core.Api/Controllers/LoginController.cs index 25818c13..5b40773d 100644 --- a/Blog.Core.Api/Controllers/LoginController.cs +++ b/Blog.Core.Api/Controllers/LoginController.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using Blog.Core.AuthHelper; +using Blog.Core.AuthHelper; using Blog.Core.AuthHelper.OverWrite; using Blog.Core.Common.Helper; using Blog.Core.IServices; @@ -12,8 +6,10 @@ using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Blog.Core.Common.Swagger; namespace Blog.Core.Controllers @@ -31,7 +27,7 @@ public class LoginController : BaseApiController readonly IRoleServices _roleServices; readonly PermissionRequirement _requirement; private readonly IRoleModulePermissionServices _roleModulePermissionServices; - + private readonly ILogger _logger; /// /// 构造函数注入 @@ -41,17 +37,22 @@ public class LoginController : BaseApiController /// /// /// - public LoginController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IRoleServices roleServices, PermissionRequirement requirement, IRoleModulePermissionServices roleModulePermissionServices) + /// + public LoginController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, + IRoleServices roleServices, PermissionRequirement requirement, + IRoleModulePermissionServices roleModulePermissionServices, ILogger logger) { this._sysUserInfoServices = sysUserInfoServices; this._userRoleServices = userRoleServices; this._roleServices = roleServices; _requirement = requirement; _roleModulePermissionServices = roleModulePermissionServices; + _logger = logger; } #region 获取token的第1种方法 + /// /// 获取JWT的方法1 /// @@ -62,16 +63,14 @@ public LoginController(ISysUserInfoServices sysUserInfoServices, IUserRoleServic [Route("Token")] public async Task> GetJwtStr(string name, string pass) { - string jwtStr = string.Empty; bool suc = false; - //这里就是用户登陆以后,通过数据库去调取数据,分配权限的操作 + //这里就是用户登录以后,通过数据库去调取数据,分配权限的操作 var user = await _sysUserInfoServices.GetUserRoleNameStr(name, MD5Helper.MD5Encrypt32(pass)); if (user != null) { - - TokenModelJwt tokenModel = new TokenModelJwt { Uid = 1, Role = user }; + TokenModelJwt tokenModel = new TokenModelJwt {Uid = 1, Role = user}; jwtStr = JwtHelper.IssueJwt(tokenModel); suc = true; @@ -102,7 +101,7 @@ public MessageModel GetJwtStrForNuxt(string name, string pass) { string jwtStr = string.Empty; bool suc = false; - //这里就是用户登陆以后,通过数据库去调取数据,分配权限的操作 + //这里就是用户登录以后,通过数据库去调取数据,分配权限的操作 //这里直接写死了 if (name == "admins" && pass == "admins") { @@ -119,9 +118,10 @@ public MessageModel GetJwtStrForNuxt(string name, string pass) { jwtStr = "login fail!!!"; } + var result = new { - data = new { success = suc, token = jwtStr } + data = new {success = suc, token = jwtStr} }; return new MessageModel() @@ -131,8 +131,8 @@ public MessageModel GetJwtStrForNuxt(string name, string pass) response = jwtStr }; } - #endregion + #endregion /// @@ -144,6 +144,7 @@ public MessageModel GetJwtStrForNuxt(string name, string pass) [HttpGet] [Route("JWTToken3.0")] public async Task> GetJwtToken3(string name = "", string pass = "") + { string jwtStr = string.Empty; @@ -152,16 +153,21 @@ public async Task> GetJwtToken3(string name = " pass = MD5Helper.MD5Encrypt32(pass); - var user = await _sysUserInfoServices.Query(d => d.LoginName == name && d.LoginPWD == pass && d.IsDeleted == false); + var user = await _sysUserInfoServices.Query(d => + d.LoginName == name && d.LoginPWD == pass && d.IsDeleted == false); if (user.Count > 0) { var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(name, pass); //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色 - var claims = new List { + var claims = new List + { new Claim(ClaimTypes.Name, name), new Claim(JwtRegisteredClaimNames.Jti, user.FirstOrDefault().Id.ToString()), - new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()), - new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString()) }; + new Claim("TenantId", user.FirstOrDefault().TenantId.ToString()), + new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.DateToTimeStamp()), + new Claim(ClaimTypes.Expiration, + DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString()) + }; claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s))); @@ -191,6 +197,14 @@ orderby item.Id } } + [HttpGet] + [Route("GetJwtTokenSecret")] + public async Task> GetJwtTokenSecret(string name = "", string pass = "") + { + var rlt = await GetJwtToken3(name, pass); + return rlt; + } + /// /// 请求刷新Token(以旧换新) /// @@ -218,11 +232,14 @@ public async Task> RefreshToken(string token = { var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(user.LoginName, user.LoginPWD); //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色 - var claims = new List { - new Claim(ClaimTypes.Name, user.LoginName), - new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ObjToString()), - new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()), - new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString()) }; + var claims = new List + { + new Claim(ClaimTypes.Name, user.LoginName), + new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ObjToString()), + new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.DateToTimeStamp()), + new Claim(ClaimTypes.Expiration, + DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString()) + }; claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s))); //用户标识 @@ -233,6 +250,7 @@ public async Task> RefreshToken(string token = return Success(refreshToken, "获取成功"); } } + return Failed("认证失败!"); } @@ -247,7 +265,8 @@ public async Task> RefreshToken(string token = /// [HttpGet] [Route("jsonp")] - public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, int expiresAbsoulute = 30) + public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, + int expiresAbsoulute = 30) { TokenModelJwt tokenModel = new TokenModelJwt { @@ -282,16 +301,29 @@ public string Md5Password(string password = "") /// [HttpPost] [Route("/api/Login/swgLogin")] - public dynamic SwgLogin([FromBody] SwaggerLoginRequest loginRequest) + public async Task SwgLogin([FromBody] SwaggerLoginRequest loginRequest) { - // 这里可以查询数据库等各种校验 - if (loginRequest?.name == "admin" && loginRequest?.pwd == "admin") + if (loginRequest is null) + { + return new {result = false}; + } + + try + { + var result = await GetJwtToken3(loginRequest.name, loginRequest.pwd); + if (result.success) + { + HttpContext.SuccessSwagger(); + HttpContext.SuccessSwaggerJwt(result.response.token); + return new {result = true}; + } + } + catch (Exception ex) { - HttpContext.Session.SetString("swagger-code", "success"); - return new { result = true }; + _logger.LogWarning(ex, "Swagger登录异常"); } - return new { result = false }; + return new {result = false}; } /// @@ -302,7 +334,7 @@ public dynamic SwgLogin([FromBody] SwaggerLoginRequest loginRequest) [Route("wxLogin")] public dynamic WxLogin(string g = "", string token = "") { - return new { g, token }; + return new {g, token}; } } diff --git a/Blog.Core.Api/Controllers/ModuleController.cs b/Blog.Core.Api/Controllers/ModuleController.cs index 334a8ea4..3b286808 100644 --- a/Blog.Core.Api/Controllers/ModuleController.cs +++ b/Blog.Core.Api/Controllers/ModuleController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Threading.Tasks; +using System.Linq.Expressions; using Blog.Core.Common.HttpContextUser; using Blog.Core.IServices; using Blog.Core.Model; @@ -37,15 +34,14 @@ public ModuleController(IModuleServices moduleServices, IUser user) /// // GET: api/User [HttpGet] - public async Task>> Get(int page = 1, string key = "") + public async Task>> Get(int page = 1, string key = "", int pageSize = 50) { if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) { key = ""; } - int intPageSize = 50; - Expression> whereExpression = a => a.IsDeleted != true && (a.Name != null && a.Name.Contains(key)); + Expression> whereExpression = a => a.IsDeleted != true && ((a.Name != null && a.Name.Contains(key) || (a.LinkUrl != null && a.LinkUrl.Contains(key)))); PageModel data = new PageModel(); @@ -56,7 +52,7 @@ public async Task>> Get(int page = 1, string key } else { - data = await _moduleServices.QueryPage(whereExpression, page, intPageSize, " Id desc "); + data = await _moduleServices.QueryPage(whereExpression, page, pageSize, " Id desc "); } @@ -122,7 +118,7 @@ public async Task> Put([FromBody] Modules module) /// // DELETE: api/ApiWithActions/5 [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { if (id <= 0) return Failed("缺少参数"); diff --git a/Blog.Core.Api/Controllers/MonitorController.cs b/Blog.Core.Api/Controllers/MonitorController.cs index c0dbed52..d707d497 100644 --- a/Blog.Core.Api/Controllers/MonitorController.cs +++ b/Blog.Core.Api/Controllers/MonitorController.cs @@ -6,19 +6,12 @@ using Blog.Core.Model; using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; using Blog.Core.Extensions.Middlewares; namespace Blog.Core.Controllers @@ -33,12 +26,13 @@ public class MonitorController : BaseApiController private readonly IApplicationUserServices _applicationUserServices; private readonly ILogger _logger; - public MonitorController(IHubContext hubContext, IWebHostEnvironment env, IApplicationUserServices applicationUserServices, ILogger logger) + public MonitorController(IHubContext hubContext, IWebHostEnvironment env, + IApplicationUserServices applicationUserServices, ILogger logger) { - _hubContext = hubContext; - _env = env; + _hubContext = hubContext; + _env = env; _applicationUserServices = applicationUserServices; - _logger = logger; + _logger = logger; } /// @@ -50,13 +44,13 @@ public MessageModel Server() { return Success(new ServerViewModel() { - EnvironmentName = _env.EnvironmentName, - OSArchitecture = RuntimeInformation.OSArchitecture.ObjToString(), - ContentRootPath = _env.ContentRootPath, - WebRootPath = _env.WebRootPath, + EnvironmentName = _env.EnvironmentName, + OSArchitecture = RuntimeInformation.OSArchitecture.ObjToString(), + ContentRootPath = _env.ContentRootPath, + WebRootPath = _env.WebRootPath, FrameworkDescription = RuntimeInformation.FrameworkDescription, - MemoryFootprint = (Process.GetCurrentProcess().WorkingSet64 / 1048576).ToString("N2") + " MB", - WorkingTime = DateHelper.TimeSubTract(DateTime.Now, Process.GetCurrentProcess().StartTime) + MemoryFootprint = (Process.GetCurrentProcess().WorkingSet64 / 1048576).ToString("N2") + " MB", + WorkingTime = DateHelper.TimeSubTract(DateTime.Now, Process.GetCurrentProcess().StartTime) }, "获取服务器配置信息成功"); } @@ -71,17 +65,18 @@ public MessageModel> Get() { if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) { - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); + _hubContext.Clients.All.SendAsync("ReceiveUpdate", "执行成功").Wait(); } + return Success>(null, "执行成功"); } - [HttpGet] public MessageModel GetRequestApiinfoByWeek() { - return Success(LogLock.RequestApiinfoByWeek(), "成功"); + //后续补充扩展Log + return Success(new RequestApiWeekView(), "成功"); } [HttpGet] @@ -94,7 +89,8 @@ public MessageModel GetAccessApiByDate() // response = LogLock.AccessApiByDate() //}; - return Success(LogLock.AccessApiByDate(), "获取成功"); + //后续补充扩展Log + return Success(new AccessApiDateView(), "获取成功"); } [HttpGet] @@ -107,70 +103,74 @@ public MessageModel GetAccessApiByHour() // response = LogLock.AccessApiByHour() //}; - return Success(LogLock.AccessApiByHour(), "获取成功"); + //后续补充扩展Log + return Success(new AccessApiDateView(), "获取成功"); } private List GetAccessLogsToday(IWebHostEnvironment environment) { List userAccessModels = new(); - var accessLogs = LogLock.ReadLog( - Path.Combine(environment.ContentRootPath, "Log"), "RecordAccessLogs_", Encoding.UTF8, ReadType.PrefixLatest - ).ObjToString(); - try - { - return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); - userAccessModels.Add(accItem); - } - catch (Exception) - { - } - } - } - - } + //后续补充扩展Log + // var accessLogs = LogLock.ReadLog( + // Path.Combine(environment.ContentRootPath, "Log"), "RecordAccessLogs_", Encoding.UTF8, + // ReadType.PrefixLatest + // ).ObjToString(); + // try + // { + // return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); + // } + // catch (Exception) + // { + // var accLogArr = accessLogs.Split("\n"); + // foreach (var item in accLogArr) + // { + // if (item.ObjToString() != "") + // { + // try + // { + // var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); + // userAccessModels.Add(accItem); + // } + // catch (Exception) + // { + // } + // } + // } + // } return userAccessModels; } + private List GetAccessLogsTrend(IWebHostEnvironment environment) { List userAccessModels = new(); - var accessLogs = LogLock.ReadLog( - Path.Combine(environment.ContentRootPath, "Log"), "ACCESSTRENDLOG_", Encoding.UTF8, ReadType.PrefixLatest - ).ObjToString(); - try - { - return JsonConvert.DeserializeObject>(accessLogs); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimStart('[').TrimEnd(']')); - userAccessModels.Add(accItem); - } - catch (Exception) - { - } - } - } - - } + //后续补充扩展Log + // var accessLogs = LogLock.ReadLog( + // Path.Combine(environment.ContentRootPath, "Log"), "ACCESSTRENDLOG_", Encoding.UTF8, + // ReadType.PrefixLatest + // ).ObjToString(); + // try + // { + // return JsonConvert.DeserializeObject>(accessLogs); + // } + // catch (Exception) + // { + // var accLogArr = accessLogs.Split("\n"); + // foreach (var item in accLogArr) + // { + // if (item.ObjToString() != "") + // { + // try + // { + // var accItem = JsonConvert.DeserializeObject(item.TrimStart('[').TrimEnd(']')); + // userAccessModels.Add(accItem); + // } + // catch (Exception) + // { + // } + // } + // } + // } return userAccessModels; } @@ -182,17 +182,16 @@ public MessageModel GetActiveUsers([FromServices] IWebHostEnvir var Logs = accessLogsToday.OrderByDescending(d => d.BeginTime).Take(50).ToList(); - var errorCountToday = LogLock.GetLogData().Where(d => d.Import == 9).Count(); - accessLogsToday = accessLogsToday.Where(d => d.User != "").ToList(); var activeUsers = (from n in accessLogsToday - group n by new { n.User } into g - select new ActiveUserVM - { - user = g.Key.User, - count = g.Count(), - }).ToList(); + group n by new { n.User } + into g + select new ActiveUserVM + { + user = g.Key.User, + count = g.Count(), + }).ToList(); int activeUsersCount = activeUsers.Count; activeUsers = activeUsers.OrderByDescending(d => d.count).Take(10).ToList(); @@ -213,11 +212,11 @@ public MessageModel GetActiveUsers([FromServices] IWebHostEnvir return Success(new WelcomeInitData() { - activeUsers = activeUsers, + activeUsers = activeUsers, activeUserCount = activeUsersCount, - errorCount = errorCountToday, - logs = Logs, - activeCount = GetAccessLogsTrend(environment) + errorCount = default, + logs = Logs, + activeCount = GetAccessLogsTrend(environment) }, "获取成功"); } @@ -226,17 +225,18 @@ public async Task> GetIds4Users() { List apiDates = new List(); - if (AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()) + if (_applicationUserServices.IsEnable()) { var users = await _applicationUserServices.Query(d => d.tdIsDelete == false); apiDates = (from n in users - group n by new { n.birth.Date } into g - select new ApiDate - { - date = g.Key?.Date.ToString("yyyy-MM-dd"), - count = g.Count(), - }).ToList(); + group n by new { n.birth.Date } + into g + select new ApiDate + { + date = g.Key?.Date.ToString("yyyy-MM-dd"), + count = g.Count(), + }).ToList(); apiDates = apiDates.OrderByDescending(d => d.date).Take(30).ToList(); } @@ -246,7 +246,7 @@ public async Task> GetIds4Users() { apiDates.Add(new ApiDate() { - date = "没数据,或未开启相应接口服务", + date = "没数据,或未开启相应接口服务", count = 0 }); } @@ -264,10 +264,9 @@ public async Task> GetIds4Users() return Success(new AccessApiDateView { columns = new string[] { "date", "count" }, - rows = apiDates.OrderBy(d => d.date).ToList(), + rows = apiDates.OrderBy(d => d.date).ToList(), }, "获取成功"); } - } public class WelcomeInitData @@ -278,5 +277,4 @@ public class WelcomeInitData public int errorCount { get; set; } public List activeCount { get; set; } } - -} +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/NacosController.cs b/Blog.Core.Api/Controllers/NacosController.cs index a8701b39..e5223851 100644 --- a/Blog.Core.Api/Controllers/NacosController.cs +++ b/Blog.Core.Api/Controllers/NacosController.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Nacos.V2; -using System.Threading.Tasks; namespace Blog.Core.Api.Controllers { diff --git a/Blog.Core.Api/Controllers/PayController.cs b/Blog.Core.Api/Controllers/PayController.cs index 0cbe0541..6c05c249 100644 --- a/Blog.Core.Api/Controllers/PayController.cs +++ b/Blog.Core.Api/Controllers/PayController.cs @@ -1,10 +1,8 @@ -using System.Threading.Tasks; -using Blog.Core.IServices; +using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Blog.Core.Controllers { diff --git a/Blog.Core.Api/Controllers/PermissionController.cs b/Blog.Core.Api/Controllers/PermissionController.cs index edb9dd8c..ccdb3dc4 100644 --- a/Blog.Core.Api/Controllers/PermissionController.cs +++ b/Blog.Core.Api/Controllers/PermissionController.cs @@ -6,10 +6,12 @@ using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; +using Blog.Core.Repository.UnitOfWorks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.Security.Claims; namespace Blog.Core.Controllers { @@ -21,6 +23,7 @@ namespace Blog.Core.Controllers [Authorize(Permissions.Name)] public class PermissionController : BaseApiController { + readonly IUnitOfWorkManage _unitOfWorkManage; readonly IPermissionServices _permissionServices; readonly IModuleServices _moduleServices; readonly IRoleModulePermissionServices _roleModulePermissionServices; @@ -37,16 +40,19 @@ public class PermissionController : BaseApiController /// /// /// + /// /// /// /// /// public PermissionController(IPermissionServices permissionServices, IModuleServices moduleServices, IRoleModulePermissionServices roleModulePermissionServices, IUserRoleServices userRoleServices, + IUnitOfWorkManage unitOfWorkManage, IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContext, IUser user, PermissionRequirement requirement) { _permissionServices = permissionServices; + _unitOfWorkManage = unitOfWorkManage; _moduleServices = moduleServices; _roleModulePermissionServices = roleModulePermissionServices; _userRoleServices = userRoleServices; @@ -61,34 +67,19 @@ public PermissionController(IPermissionServices permissionServices, IModuleServi /// /// /// + /// /// // GET: api/User [HttpGet] - public async Task>> Get(int page = 1, string key = "") + public async Task>> Get(int page = 1, string key = "", int pageSize = 50) { PageModel permissions = new PageModel(); - int intPageSize = 50; if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) { key = ""; } - #region 舍弃 - //var permissions = await _permissionServices.Query(a => a.IsDeleted != true); - //if (!string.IsNullOrEmpty(key)) - //{ - // permissions = permissions.Where(t => (t.Name != null && t.Name.Contains(key))).ToList(); - //} - ////筛选后的数据总数 - //totalCount = permissions.Count; - ////筛选后的总页数 - //pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intTotalCount.ObjToDecimal())).ObjToInt(); - //permissions = permissions.OrderByDescending(d => d.Id).Skip((page - 1) * intTotalCount).Take(intTotalCount).ToList(); - #endregion - - - - permissions = await _permissionServices.QueryPage(a => a.IsDeleted != true && (a.Name != null && a.Name.Contains(key)), page, intPageSize, " Id desc "); + permissions = await _permissionServices.QueryPage(a => a.IsDeleted != true && (a.Name != null && a.Name.Contains(key)), page, pageSize, " Id desc "); #region 单独处理 @@ -99,7 +90,7 @@ public async Task>> Get(int page = 1, string var permissionAll = await _permissionServices.Query(d => d.IsDeleted != true); foreach (var item in permissionsView) { - List pidarr = new List + List pidarr = new() { item.Pid }; @@ -156,7 +147,7 @@ public async Task>> Get(int page = 1, string /// [HttpGet] [AllowAnonymous] - public async Task>> GetTreeTable(int f = 0, string key = "") + public async Task>> GetTreeTable(long f = 0, string key = "") { List permissions = new List(); var apiList = await _moduleServices.Query(d => d.IsDeleted == false); @@ -177,7 +168,7 @@ public async Task>> GetTreeTable(int f = 0, string foreach (var item in permissions) { - List pidarr = new List { }; + List pidarr = new() { }; var parent = permissionsList.FirstOrDefault(d => d.Id == item.Pid); while (parent != null) @@ -240,64 +231,55 @@ public async Task> Post([FromBody] Permission permission) [HttpPost] public async Task> Assign([FromBody] AssignView assignView) { - var data = new MessageModel(); - - if (assignView.rid > 0) { - data.success = true; - - var roleModulePermissions = await _roleModulePermissionServices.Query(d => d.RoleId == assignView.rid); + //开启事务 + try + { + var old_rmps = await _roleModulePermissionServices.Query(d => d.RoleId == assignView.rid); - var remove = roleModulePermissions.Where(d => !assignView.pids.Contains(d.PermissionId.ObjToInt())).Select(c => (object)c.Id); - data.success &= remove.Any() ? await _roleModulePermissionServices.DeleteByIds(remove.ToArray()) : true; + _unitOfWorkManage.BeginTran(); + await _permissionServices.Db.Deleteable(t => t.RoleId == assignView.rid).ExecuteCommandAsync(); + var permissions = await _permissionServices.Query(d => d.IsDeleted == false); - foreach (var item in assignView.pids) - { - var rmpitem = roleModulePermissions.Where(d => d.PermissionId == item); - var moduleid = (await _permissionServices.Query(p => p.Id == item)).FirstOrDefault()?.Mid; - if (!rmpitem.Any()) + List new_rmps = new List(); + var nowTime = _permissionServices.Db.GetDate(); + foreach (var item in assignView.pids) { + var moduleid = permissions.Find(p => p.Id == item)?.Mid; + var find_old_rmps = old_rmps.Find(p => p.PermissionId == item); RoleModulePermission roleModulePermission = new RoleModulePermission() { IsDeleted = false, RoleId = assignView.rid, - ModuleId = moduleid.ObjToInt(), + ModuleId = moduleid.ObjToLong(), PermissionId = item, - }; - - - roleModulePermission.CreateId = _user.ID; - roleModulePermission.CreateBy = _user.Name; + CreateId = find_old_rmps == null ? _user.ID : find_old_rmps.CreateId, + CreateBy = find_old_rmps == null ? _user.Name : find_old_rmps.CreateBy, + CreateTime = find_old_rmps == null ? nowTime : find_old_rmps.CreateTime, + ModifyId = _user.ID, + ModifyBy = _user.Name, + ModifyTime = nowTime - data.success &= (await _roleModulePermissionServices.Add(roleModulePermission)) > 0; - - } - else - { - foreach (var role in rmpitem) - { - if (!role.ModuleId.Equals(moduleid)) - { - role.ModuleId = moduleid.Value; - await _roleModulePermissionServices.Update(role, new List { "ModuleId" }); - } - } + }; + new_rmps.Add(roleModulePermission); } + if (new_rmps.Count > 0) await _roleModulePermissionServices.Add(new_rmps); + _unitOfWorkManage.CommitTran(); } - - if (data.success) + catch (Exception) { - _requirement.Permissions.Clear(); - data.response = ""; - data.msg = "保存成功"; + _unitOfWorkManage.RollbackTran(); + throw; } - + _requirement.Permissions.Clear(); + return Success("保存成功"); + } + else + { + return Failed("请选择要操作的角色"); } - - - return data; } @@ -308,7 +290,7 @@ public async Task> Assign([FromBody] AssignView assignView) /// /// [HttpGet] - public async Task> GetPermissionTree(int pid = 0, bool needbtn = false) + public async Task> GetPermissionTree(long pid = 0, bool needbtn = false) { //var data = new MessageModel(); @@ -353,29 +335,41 @@ orderby child.Id /// /// [HttpGet] - public async Task> GetNavigationBar(int uid) + public async Task> GetNavigationBar(long uid) { var data = new MessageModel(); - var uidInHttpcontext1 = 0; - var roleIds = new List(); + long uidInHttpcontext1 = 0; + var roleIds = new List(); // ids4和jwt切换 if (Permissions.IsUseIds4) { // ids4 uidInHttpcontext1 = (from item in _httpContext.HttpContext.User.Claims - where item.Type == "sub" - select item.Value).FirstOrDefault().ObjToInt(); + where item.Type == ClaimTypes.NameIdentifier + select item.Value).FirstOrDefault().ObjToLong(); + if (!(uidInHttpcontext1 > 0)) + { + uidInHttpcontext1 = (from item in _httpContext.HttpContext.User.Claims + where item.Type == "sub" + select item.Value).FirstOrDefault().ObjToLong(); + } roleIds = (from item in _httpContext.HttpContext.User.Claims - where item.Type == "role" - select item.Value.ObjToInt()).ToList(); + where item.Type == ClaimTypes.Role + select item.Value.ObjToLong()).ToList(); + if (!roleIds.Any()) + { + roleIds = (from item in _httpContext.HttpContext.User.Claims + where item.Type == "role" + select item.Value.ObjToLong()).ToList(); + } } else { // jwt - uidInHttpcontext1 = ((JwtHelper.SerializeJwt(_httpContext.HttpContext.Request.Headers["Authorization"].ObjToString().Replace("Bearer ", "")))?.Uid).ObjToInt(); - roleIds = (await _userRoleServices.Query(d => d.IsDeleted == false && d.UserId == uid)).Select(d => d.RoleId.ObjToInt()).Distinct().ToList(); + uidInHttpcontext1 = ((JwtHelper.SerializeJwt(_httpContext.HttpContext.Request.Headers["Authorization"].ObjToString().Replace("Bearer ", "")))?.Uid).ObjToLong(); + roleIds = (await _userRoleServices.Query(d => d.IsDeleted == false && d.UserId == uid)).Select(d => d.RoleId.ObjToLong()).Distinct().ToList(); } @@ -383,7 +377,7 @@ public async Task> GetNavigationBar(int uid) { if (roleIds.Any()) { - var pids = (await _roleModulePermissionServices.Query(d => d.IsDeleted == false && roleIds.Contains(d.RoleId))).Select(d => d.PermissionId.ObjToInt()).Distinct(); + var pids = (await _roleModulePermissionServices.Query(d => d.IsDeleted == false && roleIds.Contains(d.RoleId))).Select(d => d.PermissionId.ObjToLong()).Distinct(); if (pids.Any()) { var rolePermissionMoudles = (await _permissionServices.Query(d => pids.Contains(d.Id))).OrderBy(c => c.OrderSort); @@ -404,6 +398,7 @@ orderby child.Id IsButton = child.IsButton.ObjToBool(), meta = new NavigationBarMeta { + icon = child.IconNew, requireAuth = true, title = child.Name, NoTabPage = child.IsHide.ObjToBool(), @@ -445,28 +440,40 @@ orderby child.Id /// /// [HttpGet] - public async Task>> GetNavigationBarPro(int uid) + public async Task>> GetNavigationBarPro(long uid) { var data = new MessageModel>(); - var uidInHttpcontext1 = 0; - var roleIds = new List(); + long uidInHttpcontext1 = 0; + var roleIds = new List(); // ids4和jwt切换 if (Permissions.IsUseIds4) { // ids4 uidInHttpcontext1 = (from item in _httpContext.HttpContext.User.Claims - where item.Type == "sub" - select item.Value).FirstOrDefault().ObjToInt(); + where item.Type == ClaimTypes.NameIdentifier + select item.Value).FirstOrDefault().ObjToLong(); + if (!(uidInHttpcontext1 > 0)) + { + uidInHttpcontext1 = (from item in _httpContext.HttpContext.User.Claims + where item.Type == "sub" + select item.Value).FirstOrDefault().ObjToLong(); + } roleIds = (from item in _httpContext.HttpContext.User.Claims - where item.Type == "role" - select item.Value.ObjToInt()).ToList(); + where item.Type == ClaimTypes.Role + select item.Value.ObjToLong()).ToList(); + if (!roleIds.Any()) + { + roleIds = (from item in _httpContext.HttpContext.User.Claims + where item.Type == "role" + select item.Value.ObjToLong()).ToList(); + } } else { // jwt - uidInHttpcontext1 = ((JwtHelper.SerializeJwt(_httpContext.HttpContext.Request.Headers["Authorization"].ObjToString().Replace("Bearer ", "")))?.Uid).ObjToInt(); - roleIds = (await _userRoleServices.Query(d => d.IsDeleted == false && d.UserId == uid)).Select(d => d.RoleId.ObjToInt()).Distinct().ToList(); + uidInHttpcontext1 = ((JwtHelper.SerializeJwt(_httpContext.HttpContext.Request.Headers["Authorization"].ObjToString().Replace("Bearer ", "")))?.Uid).ObjToLong(); + roleIds = (await _userRoleServices.Query(d => d.IsDeleted == false && d.UserId == uid)).Select(d => d.RoleId.ObjToLong()).Distinct().ToList(); } if (uid > 0 && uid == uidInHttpcontext1) @@ -474,7 +481,7 @@ public async Task>> GetNavigationBarPro(int if (roleIds.Any()) { var pids = (await _roleModulePermissionServices.Query(d => d.IsDeleted == false && roleIds.Contains(d.RoleId))) - .Select(d => d.PermissionId.ObjToInt()).Distinct(); + .Select(d => d.PermissionId.ObjToLong()).Distinct(); if (pids.Any()) { var rolePermissionMoudles = (await _permissionServices.Query(d => pids.Contains(d.Id) && d.IsButton == false)).OrderBy(c => c.OrderSort); @@ -522,14 +529,14 @@ orderby item.Id /// [HttpGet] [AllowAnonymous] - public async Task> GetPermissionIdByRoleId(int rid = 0) + public async Task> GetPermissionIdByRoleId(long rid = 0) { //var data = new MessageModel(); var rmps = await _roleModulePermissionServices.Query(d => d.IsDeleted == false && d.RoleId == rid); var permissionTrees = (from child in rmps orderby child.Id - select child.PermissionId.ObjToInt()).ToList(); + select child.PermissionId.ObjToLong()).ToList(); var permissions = await _permissionServices.Query(d => d.IsDeleted == false); List assignbtns = new List(); @@ -595,7 +602,7 @@ public async Task> Put([FromBody] Permission permission) /// // DELETE: api/ApiWithActions/5 [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { var data = new MessageModel(); if (id > 0) @@ -649,14 +656,11 @@ public async Task> BatchPost([FromBody] List pe } /// - /// 菜单同步 + /// 系统接口菜单同步接口 /// - /// 接口module的控制器名称 - /// 菜单permission的父id - /// 是否执行迁移到数据 /// [HttpGet] - public async Task>> MigratePermission(string controllerName = "", int pid = 0, bool isAction = false) + public async Task>> MigratePermission(string action = "", string token = "", string gatewayPrefix = "", string swaggerDomain = "", string controllerName = "", long pid = 0, bool isAction = false) { var data = new MessageModel>(); if (controllerName.IsNullOrEmpty()) @@ -665,18 +669,24 @@ public async Task>> MigratePermission(string contr return data; } - controllerName = controllerName.ToLower(); + controllerName = controllerName.TrimEnd('/').ToLower(); - using var client = _httpClientFactory.CreateClient(); - var jsonFileDomain = AppSettings.GetValue("Startup:Domain"); + gatewayPrefix = gatewayPrefix.Trim(); + swaggerDomain = swaggerDomain.Trim(); + controllerName = controllerName.Trim(); - if (jsonFileDomain.IsNullOrEmpty()) + using var client = _httpClientFactory.CreateClient(); + var Configuration = swaggerDomain.IsNotEmptyOrNull() ? swaggerDomain : AppSettings.GetValue("SystemCfg:Domain"); + var url = $"{Configuration}/swagger/V2/swagger.json"; + if (Configuration.IsNullOrEmpty()) { data.msg = "Swagger.json在线文件域名不能为空"; return data; } + if (token.IsNullOrEmpty()) token = Request.Headers.Authorization; + token = token.Trim(); + client.DefaultRequestHeaders.Add("Authorization", $"{token}"); - var url = $"{jsonFileDomain}/swagger/V2/swagger.json"; var response = await client.GetAsync(url); var body = await response.Content.ReadAsStringAsync(); @@ -687,7 +697,15 @@ public async Task>> MigratePermission(string contr List permissions = new List(); foreach (JProperty jProperty in pathsJObj.Properties()) { - var apiPath = jProperty.Name.ToLower(); + var apiPath = gatewayPrefix + jProperty.Name.ToLower(); + if (action.IsNotEmptyOrNull()) + { + action = action.Trim(); + if (!apiPath.Contains(action.ToLower())) + { + continue; + } + } string httpmethod = ""; if (jProperty.Value.ToString().ToLower().Contains("get")) { @@ -706,7 +724,13 @@ public async Task>> MigratePermission(string contr httpmethod = "delete"; } - var summary = jProperty.Value.SelectToken($"{httpmethod}.summary").ObjToString(); + var summary = jProperty.Value?.SelectToken($"{httpmethod}.summary")?.ObjToString() ?? ""; + + var subIx = summary.IndexOf("(Auth"); + if (subIx >= 0) + { + summary = summary.Substring(0, subIx); + } permissions.Add(new Permission() { @@ -718,7 +742,6 @@ public async Task>> MigratePermission(string contr CreateTime = DateTime.Now, IsDeleted = false, Pid = pid, - MName = apiPath ?? "", Module = new Modules() { LinkUrl = apiPath ?? "", @@ -742,15 +765,16 @@ public async Task>> MigratePermission(string contr List modules = await _moduleServices.Query(d => d.LinkUrl != null && d.LinkUrl.ToLower() == item.Module.LinkUrl); if (!modules.Any()) { - int mid = await _moduleServices.Add(item.Module); + var mid = await _moduleServices.Add(item.Module); if (mid > 0) { item.Mid = mid; - int permissionid = await _permissionServices.Add(item); + var permissionid = await _permissionServices.Add(item); } } } + data.msg = "同步完成"; } data.response = permissions; @@ -759,17 +783,16 @@ public async Task>> MigratePermission(string contr return data; } - } public class AssignView { - public List pids { get; set; } - public int rid { get; set; } + public List pids { get; set; } + public long rid { get; set; } } public class AssignShow { - public List permissionids { get; set; } + public List permissionids { get; set; } public List assignbtns { get; set; } } diff --git a/Blog.Core.Api/Controllers/RoleController.cs b/Blog.Core.Api/Controllers/RoleController.cs index 36df73a4..0b93e943 100644 --- a/Blog.Core.Api/Controllers/RoleController.cs +++ b/Blog.Core.Api/Controllers/RoleController.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Blog.Core.Common.HttpContextUser; +using Blog.Core.Common.HttpContextUser; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; @@ -113,7 +112,7 @@ public async Task> Put([FromBody] Role role) /// // DELETE: api/ApiWithActions/5 [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { var data = new MessageModel(); diff --git a/Blog.Core.Api/Controllers/SignalRTestController.cs b/Blog.Core.Api/Controllers/SignalRTestController.cs new file mode 100644 index 00000000..16ba47f0 --- /dev/null +++ b/Blog.Core.Api/Controllers/SignalRTestController.cs @@ -0,0 +1,49 @@ +using Blog.Core.Controllers; +using Blog.Core.Hubs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; + +namespace Blog.Core.Api.Controllers; + +/// +/// SignalR测试 +/// +[Route("api/[controller]/[action]")] +[ApiController] +[Authorize] +public class SignalRTestController : BaseApiController +{ + private readonly IHubContext _hubContext; + + public SignalRTestController(IHubContext hubContext) + { + _hubContext = hubContext; + } + + /// + /// 向指定用户发送消息 + /// + /// + /// + /// + [HttpPost] + public async Task SendMessageToUser(string user, string message) + { + await _hubContext.Clients.Group(user).ReceiveMessage(user, message); + return Ok(); + } + + /// + /// 向指定角色发送消息 + /// + /// + /// + /// + [HttpPost] + public async Task SendMessageToRole(string role, string message) + { + await _hubContext.Clients.Group(role).ReceiveMessage(role, message); + return Ok(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/SplitDemoController.cs b/Blog.Core.Api/Controllers/SplitDemoController.cs new file mode 100644 index 00000000..f625b202 --- /dev/null +++ b/Blog.Core.Api/Controllers/SplitDemoController.cs @@ -0,0 +1,199 @@ +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Repository.UnitOfWorks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Linq.Expressions; + +namespace Blog.Core.Api.Controllers +{ + /// + /// 分表demo + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public class SplitDemoController : ControllerBase + { + readonly ISplitDemoServices splitDemoServices; + readonly IUnitOfWorkManage unitOfWorkManage; + public SplitDemoController(ISplitDemoServices _splitDemoServices, IUnitOfWorkManage _unitOfWorkManage) + { + splitDemoServices = _splitDemoServices; + unitOfWorkManage = _unitOfWorkManage; + } + + /// + /// 分页获取数据 + /// + /// + /// + /// + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task>> Get(DateTime beginTime, DateTime endTime, int page = 1, string key = "", int pageSize = 10) + { + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + Expression> whereExpression = a => (a.Name != null && a.Name.Contains(key)); + var data = await splitDemoServices.QueryPageSplit(whereExpression, beginTime, endTime, page, pageSize, " Id desc "); + return MessageModel>.Message(data.dataCount >= 0, "获取成功", data); + } + + /// + /// 根据ID获取信息 + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task> GetById(long id) + { + var data = new MessageModel(); + var model = await splitDemoServices.QueryByIdSplit(id); + if (model != null) + { + return MessageModel.Success("获取成功", model); + } + else + { + return MessageModel.Fail("获取失败"); + } + } + + /// + /// 添加一条测试数据 + /// + /// + /// + [HttpPost] + [AllowAnonymous] + public async Task> Post([FromBody] SplitDemo splitDemo) + { + var data = new MessageModel(); + //unitOfWorkManage.BeginTran(); + var id = (await splitDemoServices.AddSplit(splitDemo)); + data.success = (id == null ? false : true); + try + { + if (data.success) + { + data.response = id.FirstOrDefault().ToString(); + data.msg = "添加成功"; + } + else + { + data.msg = "添加失败"; + } + } + catch (Exception) + { + throw; + } + finally + { + //if (data.success) + // unitOfWorkManage.CommitTran(); + //else + // unitOfWorkManage.RollbackTran(); + } + return data; + } + + /// + /// 修改一条测试数据 + /// + /// + /// + [HttpPut] + [AllowAnonymous] + public async Task> Put([FromBody] SplitDemo splitDemo) + { + var data = new MessageModel(); + if (splitDemo != null && splitDemo.Id > 0) + { + unitOfWorkManage.BeginTran(); + data.success = await splitDemoServices.UpdateSplit(splitDemo, splitDemo.CreateTime); + try + { + if (data.success) + { + data.msg = "修改成功"; + data.response = splitDemo?.Id.ObjToString(); + } + else + { + data.msg = "修改失败"; + } + } + catch (Exception) + { + throw; + } + finally + { + if (data.success) + unitOfWorkManage.CommitTran(); + else + unitOfWorkManage.RollbackTran(); + } + } + return data; + } + + /// + /// 根据id删除数据 + /// + /// + /// + [HttpDelete] + [AllowAnonymous] + public async Task> Delete(long id) + { + var data = new MessageModel(); + + var model = await splitDemoServices.QueryByIdSplit(id); + if (model != null) + { + unitOfWorkManage.BeginTran(); + data.success = await splitDemoServices.DeleteSplit(model,model.CreateTime); + try + { + data.response = id.ObjToString(); + if (data.success) + { + data.msg = "删除成功"; + } + else + { + data.msg = "删除失败"; + } + + } + catch (Exception) + { + throw; + } + finally + { + if (data.success) + unitOfWorkManage.CommitTran(); + else + unitOfWorkManage.RollbackTran(); + } + } + else + { + data.msg = "不存在"; + } + return data; + + } + } +} diff --git a/Blog.Core.Api/Controllers/SqlSugarTestController.cs b/Blog.Core.Api/Controllers/SqlSugarTestController.cs new file mode 100644 index 00000000..f43e32bf --- /dev/null +++ b/Blog.Core.Api/Controllers/SqlSugarTestController.cs @@ -0,0 +1,61 @@ +using System.Text; +using Blog.Core.Common.DB.Extension; +using Blog.Core.Controllers; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; + +namespace Blog.Core.Api.Controllers; + +/// +/// SqlSugar 相关测试 +/// +[Route("api/[controller]/[action]")] +[ApiController] +[AllowAnonymous] +public class SqlSugarTestController(ISqlSugarClient db) : BaseApiController +{ + /// + /// 测试建表后,SqlSugar缓存 + /// + /// + [HttpGet] + public MessageModel ClearDbTableCache() + { + var tableName = "BlogArticle_Test"; + + //先删除表 + try + { + db.DbMaintenance.DropTable(tableName); + db.ClearDbTableCache(); + } + catch + { + //Ignore + } + + StringBuilder sb = new StringBuilder(); + + //提前检查表是否存在,测试缓存 + sb.AppendLine($"表{tableName} 是否存在:{db.DbMaintenance.IsAnyTable(tableName)}"); + + //创建表 + db.CodeFirst.As(tableName).InitTables(); + sb.AppendLine($"表{tableName} 创建成功"); + + //检查表是否存在 + sb.AppendLine($"表{tableName} 是否存在:{db.DbMaintenance.IsAnyTable(tableName)}"); + + //清除缓存 + db.ClearDbTableCache(); + sb.AppendLine($"清除缓存后"); + + //检查表是否存在 + sb.AppendLine($"表{tableName} 是否存在:{db.DbMaintenance.IsAnyTable(tableName)}"); + + return Success(sb.ToString()); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Systems/CacheManageController.cs b/Blog.Core.Api/Controllers/Systems/CacheManageController.cs new file mode 100644 index 00000000..ef276c17 --- /dev/null +++ b/Blog.Core.Api/Controllers/Systems/CacheManageController.cs @@ -0,0 +1,75 @@ +using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Controllers; +using Blog.Core.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Api.Controllers.Systems; + +/// +/// 缓存管理 +/// +[Route("api/Systems/[controller]")] +[ApiController] +[Authorize(Permissions.Name)] +public class CacheManageController(ICaching caching) : BaseApiController +{ + /// + /// 获取全部缓存 + /// + /// + [HttpGet] + public MessageModel> Get() + { + return Success(caching.GetAllCacheKeys()); + } + + /// + /// 获取缓存 + /// + /// + [HttpGet("{key}")] + public async Task> Get(string key) + { + return Success(await caching.GetStringAsync(key)); + } + + /// + /// 新增 + /// + /// + [HttpPost] + public async Task Post([FromQuery] string key, [FromQuery] string value, [FromQuery] int? expire) + { + if (expire.HasValue) + await caching.SetStringAsync(key, value, TimeSpan.FromMilliseconds(expire.Value)); + else + await caching.SetStringAsync(key, value); + + return Success(); + } + + /// + /// 删除全部缓存 + /// + /// + [HttpDelete] + public MessageModel Delete() + { + caching.RemoveAll(); + return Success(); + } + + /// + /// 删除缓存 + /// + /// + [Route("{key}")] + [HttpDelete] + public async Task Delete(string key) + { + await caching.RemoveAsync(key); + return Success(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Systems/DataBaseController.cs b/Blog.Core.Api/Controllers/Systems/DataBaseController.cs new file mode 100644 index 00000000..1f7b3089 --- /dev/null +++ b/Blog.Core.Api/Controllers/Systems/DataBaseController.cs @@ -0,0 +1,192 @@ +using System.Diagnostics.CodeAnalysis; +using Blog.Core.Common; +using Blog.Core.Common.DB; +using Blog.Core.Controllers; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.Systems.DataBase; +using Blog.Core.Model.Tenants; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; + +namespace Blog.Core.Api.Controllers.Systems; + +/// +/// 数据库管理 +/// +[Route("api/Systems/[controller]/[action]")] +[ApiController] +[Authorize(Permissions.Name)] +public class DataBaseController : BaseApiController +{ + private readonly ISqlSugarClient _db; + + public DataBaseController(ISqlSugarClient db) + { + _db = db; + } + + [return: NotNull] + private ISqlSugarClient GetTenantDb(string configId) + { + if (!_db.AsTenant().IsAnyConnection(configId)) + { + var tenant = _db.Queryable().WithCache() + .Where(s => s.TenantType == TenantTypeEnum.Db) + .Where(s => s.ConfigId == configId) + .First(); + if (tenant != null) + { + _db.AsTenant().AddConnection(tenant.GetConnectionConfig()); + } + } + + var db = _db.AsTenant().GetConnectionScope(configId); + if (db is null) + { + throw new ApplicationException("无效的数据库配置"); + } + + return db; + } + + /// + /// 获取库配置 + /// + /// + [HttpGet] + public async Task>> GetAllConfig() + { + //增加多租户的连接 + var allConfigs = new List(BaseDBConfig.AllConfigs); + var tenants = await _db.Queryable().WithCache() + .Where(s => s.TenantType == TenantTypeEnum.Db) + .ToListAsync(); + if (tenants.Any()) + { + allConfigs.AddRange(tenants.Select(tenant => tenant.GetConnectionConfig())); + } + + var configs = await Task.FromResult(allConfigs); + return Success(configs.Adapt>()); + } + + /// + /// 获取表信息 + /// + /// 配置Id + /// 读取类型 + /// + [HttpGet] + public MessageModel> GetTableInfoList(string configId, + DataBaseReadType readType = DataBaseReadType.Db) + { + if (configId.IsNullOrEmpty()) + { + configId = MainDb.CurrentDbConnId; + } + + configId = configId.ToLower(); + + var provider = GetTenantDb(configId); + List data = null; + switch (readType) + { + case DataBaseReadType.Db: + data = provider.DbMaintenance.GetTableInfoList(false); + break; + case DataBaseReadType.Entity: + if (EntityUtility.TenantEntitys.TryGetValue(configId, out var types)) + { + data = types.Select(s => provider.EntityMaintenance.GetEntityInfo(s)) + .Select(s => new {Name = s.DbTableName, Description = s.TableDescription}) + .Adapt>(); + } + + break; + } + + + return Success(data); + } + + /// + /// 获取表字段 + /// + /// 表名 + /// ConfigId + /// 读取类型 + /// + [HttpGet] + public MessageModel> GetColumnInfosByTableName(string tableName, string configId = null, + DataBaseReadType readType = DataBaseReadType.Db) + { + if (string.IsNullOrWhiteSpace(tableName)) + return Failed>("表名不能为空"); + + if (configId.IsNullOrEmpty()) + { + configId = MainDb.CurrentDbConnId; + } + + configId = configId.ToLower(); + + List data = null; + var provider = GetTenantDb(configId); + switch (readType) + { + case DataBaseReadType.Db: + data = provider.DbMaintenance.GetColumnInfosByTableName(tableName, false) + .Adapt>(); + break; + case DataBaseReadType.Entity: + if (EntityUtility.TenantEntitys.TryGetValue(configId, out var types)) + { + var type = types.FirstOrDefault(s => s.Name == tableName); + data = provider.EntityMaintenance.GetEntityInfo(type).Columns.Adapt>(); + } + + break; + } + + + return Success(data); + } + + /// + /// 编辑表备注 + /// + /// + [HttpPut] + public MessageModel PutTableEditRemark([FromBody] EditTableInput input) + { + var provider = GetTenantDb(input.ConfigId); + if (provider.DbMaintenance.IsAnyTableRemark(input.TableName)) + { + provider.DbMaintenance.DeleteTableRemark(input.TableName); + } + + provider.DbMaintenance.AddTableRemark(input.TableName, input.Description); + return Success(); + } + + /// + /// 编辑列备注 + /// + /// + [HttpPut] + public MessageModel PutColumnEditRemark([FromBody] EditColumnInput input) + { + var provider = GetTenantDb(input.ConfigId); + if (provider.DbMaintenance.IsAnyColumnRemark(input.DbColumnName, input.TableName)) + { + provider.DbMaintenance.DeleteColumnRemark(input.DbColumnName, input.TableName); + } + + provider.DbMaintenance.AddColumnRemark(input.DbColumnName, input.TableName, input.ColumnDescription); + + return Success(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Systems/DynamicCodeFirstController.cs b/Blog.Core.Api/Controllers/Systems/DynamicCodeFirstController.cs new file mode 100644 index 00000000..37c84791 --- /dev/null +++ b/Blog.Core.Api/Controllers/Systems/DynamicCodeFirstController.cs @@ -0,0 +1,96 @@ +using Blog.Core.Common.DB.Extension; +using Blog.Core.Controllers; +using Blog.Core.Model; +using Blog.Core.Model.Models.RootTkey; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NetTaste; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Math; +using SqlSugar; + +namespace Blog.Core.Api.Controllers.Systems; + +/// +/// 动态建表 CURD +/// +[Route("api/Systems/[controller]/[action]")] +[ApiController] +[Authorize(Permissions.Name)] +public class DynamicCodeFirstController : BaseApiController +{ + private readonly ISqlSugarClient _db; + + public DynamicCodeFirstController(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 动态type + /// + /// + private Type GetDynamicType() + { + return _db.DynamicBuilder().CreateClass("DynamicTestTable") + //{table} 占位符会自动替换成表名 + .CreateIndex(new SugarIndexAttribute("idx_{table}_Code", "Code", OrderByType.Desc)) + .CreateProperty("Id", typeof(int), new SugarColumn() {IsPrimaryKey = true, IsIdentity = true}) + .CreateProperty("Code", typeof(string), new SugarColumn() {Length = 50}) + .CreateProperty("Name", typeof(string), new SugarColumn() {Length = 50}) + .WithCache() + .BuilderType(); + } + + /// + /// 动态type 继承BaseEntity + /// + /// + private Type GetDynamicType2() + { + return _db.DynamicBuilder().CreateClass("DynamicTestTable2", null, typeof(BaseEntity)) + //{table} 占位符会自动替换成表名 + .CreateIndex(new SugarIndexAttribute("idx_{table}_Code", "Code", OrderByType.Desc)) + .CreateProperty("Code", typeof(string), new SugarColumn() {Length = 50}) + .CreateProperty("Name", typeof(string), new SugarColumn() {Length = 50}) + .WithCache() + .BuilderType(); + } + + /// + /// 测试建表 + /// + /// + [HttpPost] + public MessageModel TestCreateTable() + { + var type = GetDynamicType(); + _db.CodeFirst.InitTables(type); + return Success(); + } + + /// + /// 测试查询 + /// + /// + [HttpGet] + public MessageModel TestQuery() + { + var type = GetDynamicType(); + return Success(_db.QueryableByObject(type).ToList()); + } + + /// + /// 测试写入 + /// + /// + [HttpPost] + public MessageModel TestInsert(string code, string name) + { + var type = GetDynamicType(); + var entity = Activator.CreateInstance(type); + type.GetProperty("Code")!.SetValue(entity, code); + type.GetProperty("Name")!.SetValue(entity, name); + _db.InsertableByObject(entity).ExecuteCommand(); + return Success(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/TasksQzController.cs b/Blog.Core.Api/Controllers/TasksQzController.cs index 264fa195..887cfcfc 100644 --- a/Blog.Core.Api/Controllers/TasksQzController.cs +++ b/Blog.Core.Api/Controllers/TasksQzController.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; +using System.Linq.Expressions; using System.Reflection; -using System.Threading.Tasks; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; @@ -22,14 +18,16 @@ namespace Blog.Core.Controllers public class TasksQzController : ControllerBase { private readonly ITasksQzServices _tasksQzServices; + private readonly ITasksLogServices _tasksLogServices; private readonly ISchedulerCenter _schedulerCenter; private readonly IUnitOfWorkManage _unitOfWorkManage; - public TasksQzController(ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, IUnitOfWorkManage unitOfWorkManage) + public TasksQzController(ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, IUnitOfWorkManage unitOfWorkManage, ITasksLogServices tasksLogServices) { _unitOfWorkManage = unitOfWorkManage; _tasksQzServices = tasksQzServices; _schedulerCenter = schedulerCenter; + _tasksLogServices = tasksLogServices; } /// @@ -58,7 +56,7 @@ public async Task>> Get(int page = 1, string key item.Triggers = await _schedulerCenter.GetTaskStaus(item); } } - return MessageModel>.Message(data.dataCount >= 0, "获取成功", data); + return MessageModel>.Message(data.dataCount >= 0, "获取成功", data); } /// @@ -86,32 +84,33 @@ public async Task> Post([FromBody] TasksQz tasksQz) var ResuleModel = await _schedulerCenter.AddScheduleJobAsync(tasksQz); data.success = ResuleModel.success; if (ResuleModel.success) - { + { data.msg = $"{data.msg}=>启动成功=>{ResuleModel.msg}"; } else - { + { data.msg = $"{data.msg}=>启动失败=>{ResuleModel.msg}"; } } } else - { + { data.msg = "添加失败"; - } + } } catch (Exception) { throw; } finally - { if(data.success) + { + if (data.success) _unitOfWorkManage.CommitTran(); else _unitOfWorkManage.RollbackTran(); } - return data; + return data; } @@ -135,7 +134,7 @@ public async Task> Put([FromBody] TasksQz tasksQz) data.msg = "修改成功"; data.response = tasksQz?.Id.ObjToString(); if (tasksQz.IsStart) - { + { var ResuleModelStop = await _schedulerCenter.StopScheduleJobAsync(tasksQz); data.msg = $"{data.msg}=>停止:{ResuleModelStop.msg}"; var ResuleModelStar = await _schedulerCenter.AddScheduleJobAsync(tasksQz); @@ -163,7 +162,7 @@ public async Task> Put([FromBody] TasksQz tasksQz) _unitOfWorkManage.CommitTran(); else _unitOfWorkManage.RollbackTran(); - } + } } return data; } @@ -173,7 +172,7 @@ public async Task> Put([FromBody] TasksQz tasksQz) /// /// [HttpDelete] - public async Task> Delete(int jobId) + public async Task> Delete(long jobId) { var data = new MessageModel(); @@ -207,7 +206,7 @@ public async Task> Delete(int jobId) _unitOfWorkManage.CommitTran(); else _unitOfWorkManage.RollbackTran(); - } + } } else { @@ -222,14 +221,14 @@ public async Task> Delete(int jobId) /// /// [HttpGet] - public async Task> StartJob(int jobId) + public async Task> StartJob(long jobId) { var data = new MessageModel(); var model = await _tasksQzServices.QueryById(jobId); if (model != null) { - _unitOfWorkManage.BeginTran(); + _unitOfWorkManage.BeginTran(); try { model.IsStart = true; @@ -265,7 +264,7 @@ public async Task> StartJob(int jobId) _unitOfWorkManage.CommitTran(); else _unitOfWorkManage.RollbackTran(); - } + } } else { @@ -279,7 +278,7 @@ public async Task> StartJob(int jobId) /// /// [HttpGet] - public async Task> StopJob(int jobId) + public async Task> StopJob(long jobId) { var data = new MessageModel(); @@ -319,12 +318,12 @@ public async Task> StopJob(int jobId) /// /// [HttpGet] - public async Task> PauseJob(int jobId) + public async Task> PauseJob(long jobId) { - var data = new MessageModel(); + var data = new MessageModel(); var model = await _tasksQzServices.QueryById(jobId); if (model != null) - { + { _unitOfWorkManage.BeginTran(); try { @@ -359,7 +358,7 @@ public async Task> PauseJob(int jobId) _unitOfWorkManage.CommitTran(); else _unitOfWorkManage.RollbackTran(); - } + } } else { @@ -373,13 +372,13 @@ public async Task> PauseJob(int jobId) /// /// [HttpGet] - public async Task> ResumeJob(int jobId) + public async Task> ResumeJob(long jobId) { var data = new MessageModel(); var model = await _tasksQzServices.QueryById(jobId); if (model != null) - { + { _unitOfWorkManage.BeginTran(); try { @@ -415,7 +414,7 @@ public async Task> ResumeJob(int jobId) _unitOfWorkManage.CommitTran(); else _unitOfWorkManage.RollbackTran(); - } + } } else { @@ -429,7 +428,7 @@ public async Task> ResumeJob(int jobId) /// /// [HttpGet] - public async Task> ReCovery(int jobId) + public async Task> ReCovery(long jobId) { var data = new MessageModel(); var model = await _tasksQzServices.QueryById(jobId); @@ -475,7 +474,7 @@ public async Task> ReCovery(int jobId) _unitOfWorkManage.CommitTran(); else _unitOfWorkManage.RollbackTran(); - } + } } else { @@ -488,7 +487,7 @@ public async Task> ReCovery(int jobId) /// 获取任务命名空间 /// /// - [HttpGet] + [HttpGet] public MessageModel> GetTaskNameSpace() { var baseType = typeof(IJob); @@ -501,14 +500,14 @@ public MessageModel> GetTaskNameSpace() var implementTypes = types.Where(x => x.IsClass).Select(item => new QuartzReflectionViewModel { nameSpace = item.Namespace, nameClass = item.Name, remark = "" }).ToList(); return MessageModel>.Success("获取成功", implementTypes); } - + /// /// 立即执行任务 /// /// /// [HttpGet] - public async Task> ExecuteJob(int jobId) + public async Task> ExecuteJob(long jobId) { var data = new MessageModel(); @@ -523,6 +522,26 @@ public async Task> ExecuteJob(int jobId) } return data; } + /// + /// 获取任务运行日志 + /// + /// + [HttpGet] + public async Task>> GetTaskLogs(long jobId, int page = 1, int pageSize = 10, DateTime? runTimeStart = null, DateTime? runTimeEnd = null) + { + var model = await _tasksLogServices.GetTaskLogs(jobId, page, pageSize, runTimeStart, runTimeEnd); + return MessageModel>.Message(model.dataCount >= 0, "获取成功", model); + } + /// + /// 任务概况 + /// + /// + [HttpGet] + public async Task> GetTaskOverview(long jobId, int page = 1, int pageSize = 10, DateTime? runTimeStart = null, DateTime? runTimeEnd = null, string type = "month") + { + var model = await _tasksLogServices.GetTaskOverview(jobId, runTimeStart, runTimeEnd, type); + return MessageModel.Message(true, "获取成功", model); + } } } diff --git a/Blog.Core.Api/Controllers/Tenant/TenantByDbController.cs b/Blog.Core.Api/Controllers/Tenant/TenantByDbController.cs new file mode 100644 index 00000000..046f7f7b --- /dev/null +++ b/Blog.Core.Api/Controllers/Tenant/TenantByDbController.cs @@ -0,0 +1,50 @@ +using Blog.Core.Common.HttpContextUser; +using Blog.Core.Controllers; +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Api.Controllers.Tenant; + +/// +/// 多租户-多库方案 测试 +/// +[Produces("application/json")] +[Route("api/Tenant/ByDb")] +[Authorize] +public class TenantByDbController : BaseApiController +{ + private readonly IBaseServices _services; + private readonly IUser _user; + + public TenantByDbController(IUser user, IBaseServices services) + { + _user = user; + _services = services; + } + + /// + /// 获取租户下全部业务数据
+ ///
+ /// + [HttpGet] + public async Task>> GetAll() + { + var data = await _services.Query(); + return Success(data); + } + + /// + /// 新增数据 + /// + /// + [HttpPost] + public async Task Post(SubLibraryBusinessTable data) + { + await _services.Db.Insertable(data).ExecuteReturnSnowflakeIdAsync(); + + return Success(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Tenant/TenantByIdController.cs b/Blog.Core.Api/Controllers/Tenant/TenantByIdController.cs new file mode 100644 index 00000000..b015bc6d --- /dev/null +++ b/Blog.Core.Api/Controllers/Tenant/TenantByIdController.cs @@ -0,0 +1,49 @@ +using Blog.Core.Common.HttpContextUser; +using Blog.Core.Controllers; +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Api.Controllers.Tenant; + +/// +/// 多租户-Id方案 测试 +/// +[Produces("application/json")] +[Route("api/Tenant/ById")] +[Authorize] +public class TenantByIdController : BaseApiController +{ + private readonly IBaseServices _services; + private readonly IUser _user; + + public TenantByIdController(IUser user, IBaseServices services) + { + _user = user; + _services = services; + } + + /// + /// 获取租户下全部业务数据
+ ///
+ /// + [HttpGet] + public async Task>> GetAll() + { + var data = await _services.Query(); + return Success(data); + } + + /// + /// 新增业务数据 + /// + /// + [HttpPost] + public async Task Post([FromBody] BusinessTable data) + { + await _services.Db.Insertable(data).ExecuteReturnSnowflakeIdAsync(); + return Success(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Tenant/TenantByTableController.cs b/Blog.Core.Api/Controllers/Tenant/TenantByTableController.cs new file mode 100644 index 00000000..6c0b110e --- /dev/null +++ b/Blog.Core.Api/Controllers/Tenant/TenantByTableController.cs @@ -0,0 +1,57 @@ +using Blog.Core.Common.HttpContextUser; +using Blog.Core.Controllers; +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Api.Controllers.Tenant; + +/// +/// 多租户-多表方案 测试 +/// +[Produces("application/json")] +[Route("api/Tenant/ByTable")] +[Authorize] +public class TenantByTableController : BaseApiController +{ + private readonly IBaseServices _services; + private readonly IUser _user; + + public TenantByTableController(IUser user, IBaseServices services) + { + _user = user; + _services = services; + } + + /// + /// 获取租户下全部业务数据
+ ///
+ /// + [HttpGet] + public async Task>> GetAll() + { + //查询 + // var data = await _services.Query(); + + //关联查询 + var data = await _services.Db + .Queryable() + .Includes(s => s.Child) + .ToListAsync(); + return Success(data); + } + + /// + /// 新增数据 + /// + /// + [HttpPost] + public async Task Post(MultiBusinessTable data) + { + await _services.Db.Insertable(data).ExecuteReturnSnowflakeIdAsync(); + + return Success(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Tenant/TenantManagerController.cs b/Blog.Core.Api/Controllers/Tenant/TenantManagerController.cs new file mode 100644 index 00000000..90133fdb --- /dev/null +++ b/Blog.Core.Api/Controllers/Tenant/TenantManagerController.cs @@ -0,0 +1,87 @@ +using Blog.Core.Controllers; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Api.Controllers.Tenant; + +/// +/// 租户管理 +/// +[Produces("application/json")] +[Route("api/TenantManager")] +[Authorize] +public class TenantManagerController : BaseApiController +{ + private readonly ITenantService _services; + + public TenantManagerController(ITenantService services) + { + _services = services; + } + + + /// + /// 获取全部租户 + /// + /// + [HttpGet] + public async Task>> GetAll() + { + var data = await _services.Query(); + return Success(data); + } + + + /// + /// 获取租户信息 + /// + /// + [HttpGet("{id}")] + public async Task> GetInfo(long id) + { + var data = await _services.QueryById(id); + return Success(data); + } + + /// + /// 新增租户信息
+ /// 此处只做演示,具体要以实际业务为准 + ///
+ /// + [HttpPost] + public async Task Post(SysTenant tenant) + { + await _services.SaveTenant(tenant); + return Success(); + } + + /// + /// 修改租户信息
+ /// 此处只做演示,具体要以实际业务为准 + ///
+ /// + [HttpPut] + public async Task Put(SysTenant tenant) + { + await _services.SaveTenant(tenant); + return Success(); + } + + /// + /// 删除租户
+ /// 此处只做演示,具体要以实际业务为准 + ///
+ /// + [HttpDelete] + public async Task Delete(long id) + { + //是否删除租户库? + //要根据实际情况而定 + //例如直接删除租户库、备份租户库到xx + await _services.DeleteById(id); + return Success(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Test/EnumTestController.cs b/Blog.Core.Api/Controllers/Test/EnumTestController.cs new file mode 100644 index 00000000..29a85ac6 --- /dev/null +++ b/Blog.Core.Api/Controllers/Test/EnumTestController.cs @@ -0,0 +1,75 @@ +using System.ComponentModel; +using Blog.Core.Controllers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Api.Controllers.Test; + +/// +/// 枚举测试 +/// +[Route("api/[Controller]/[Action]")] +[AllowAnonymous] +public class EnumTestController : BaseApiController +{ + /// + /// 获取学生信息 + /// + /// 学生类型 + /// + /// + /// 学生信息 + [HttpGet] + public Student GetStudent( StudentType studentType, StudentType? studentType2, List studentTypes) + { + return new Student + { + Name = "张三", + Age = 20, + Type = studentType + }; + } +} + +/// +/// 学生类型 +/// +[Description("学生类型")] +public enum StudentType +{ + /// + /// 小学生 + /// + [Description("小学生")] + PrimarySchool = 1, + + /// + /// 中学生 + /// + [Description("中学生")] + MiddleSchool = 2, + + /// + /// 大学生 + /// + [Description("大学生")] + University = 3 +} + +public class Student +{ + /// + /// 学生姓名 + /// + public string Name { get; set; } + + /// + /// 学生年龄 + /// + public int Age { get; set; } + + /// + /// 学生类型 + /// + public StudentType Type { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/Test/SqlsugarTestController.cs b/Blog.Core.Api/Controllers/Test/SqlsugarTestController.cs new file mode 100644 index 00000000..774a9b12 --- /dev/null +++ b/Blog.Core.Api/Controllers/Test/SqlsugarTestController.cs @@ -0,0 +1,29 @@ +using Blog.Core.Common; +using Blog.Core.Controllers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; + +namespace Blog.Core.Api.Controllers.Test; + +[Route("api/[Controller]/[Action]")] +[AllowAnonymous] +public class SqlsugarTestController : BaseApiController +{ + private readonly SqlSugarScope _db; + + public SqlsugarTestController(SqlSugarScope db) + { + _db = db; + } + + [HttpGet] + public async Task Get() + { + Console.WriteLine(App.HttpContext.Request.Path); + Console.WriteLine(App.HttpContext.RequestServices.ToString()); + Console.WriteLine(App.User?.ID); + await Task.CompletedTask; + return Ok(); + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/TopicController.cs b/Blog.Core.Api/Controllers/TopicController.cs index 1fe2d4a3..253f54ff 100644 --- a/Blog.Core.Api/Controllers/TopicController.cs +++ b/Blog.Core.Api/Controllers/TopicController.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Blog.Core.IServices; +using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; using Microsoft.AspNetCore.Authorization; @@ -46,7 +44,7 @@ public async Task>> Get() // GET: api/Topic/5 [HttpGet("{id}")] - public string Get(int id) + public string Get(long id) { return "value"; } @@ -59,13 +57,13 @@ public void Post([FromBody] string value) // PUT: api/Topic/5 [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) + public void Put(long id, [FromBody] string value) { } // DELETE: api/ApiWithActions/5 [HttpDelete("{id}")] - public void Delete(int id) + public void Delete(long id) { } } diff --git a/Blog.Core.Api/Controllers/TopicDetailController.cs b/Blog.Core.Api/Controllers/TopicDetailController.cs index 264fe2df..374aca24 100644 --- a/Blog.Core.Api/Controllers/TopicDetailController.cs +++ b/Blog.Core.Api/Controllers/TopicDetailController.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.Common.Helper; +using Blog.Core.Common.Helper; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; @@ -45,7 +42,7 @@ public TopicDetailController(ITopicServices topicServices, ITopicDetailServices [AllowAnonymous] public async Task>> Get(int page = 1, string tname = "", string key = "", int intPageSize = 12) { - int tid = 0; + long tid = 0; if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) { @@ -59,7 +56,7 @@ public async Task>> Get(int page = 1, string if (!string.IsNullOrEmpty(tname)) { - tid = ((await _topicServices.Query(ts => ts.tName == tname)).FirstOrDefault()?.Id).ObjToInt(); + tid = ((await _topicServices.Query(ts => ts.tName == tname)).FirstOrDefault()?.Id).ObjToLong(); } @@ -84,7 +81,7 @@ public async Task>> Get(int page = 1, string // GET: api/TopicDetail/5 [HttpGet("{id}")] [AllowAnonymous] - public async Task> Get(int id) + public async Task> Get(long id) { var data = new MessageModel(); var response = id > 0 ? await _topicDetailServices.QueryById(id) : new TopicDetail(); @@ -157,7 +154,7 @@ public async Task> Update([FromBody] TopicDetail topicDetai /// // DELETE: api/ApiWithActions/5 [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { var data = new MessageModel(); if (id > 0) diff --git a/Blog.Core.Api/Controllers/TransactionController.cs b/Blog.Core.Api/Controllers/TransactionController.cs index dd6b0384..9853d985 100644 --- a/Blog.Core.Api/Controllers/TransactionController.cs +++ b/Blog.Core.Api/Controllers/TransactionController.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Blog.Core.IServices; +using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; using Blog.Core.Repository.UnitOfWorks; @@ -98,7 +95,7 @@ public async Task>> Get() // GET: api/Transaction/5 [HttpGet("{id}")] - public async Task> Get(int id) + public async Task> Get(long id) { return await _guestbookServices.TestTranInRepository(); } @@ -129,7 +126,7 @@ public void Post([FromBody] string value) // PUT: api/Transaction/5 [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) + public void Put(long id, [FromBody] string value) { } @@ -139,7 +136,7 @@ public void Put(int id, [FromBody] string value) /// /// [HttpDelete("{id}")] - public async Task Delete(int id) + public async Task Delete(long id) { return await _guestbookServices.TestTranInRepositoryAOP(); } diff --git a/Blog.Core.Api/Controllers/UserController.cs b/Blog.Core.Api/Controllers/UserController.cs index 6a2a18fa..95137c8e 100644 --- a/Blog.Core.Api/Controllers/UserController.cs +++ b/Blog.Core.Api/Controllers/UserController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AutoMapper; +using AutoMapper; using Blog.Core.AuthHelper.OverWrite; using Blog.Core.Common.Helper; using Blog.Core.Common.HttpContextUser; @@ -13,7 +9,6 @@ using Blog.Core.Repository.UnitOfWorks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Blog.Core.Controllers { @@ -108,15 +103,15 @@ public async Task>> Get(int page = 1, str return Success(data.ConvertTo(_mapper)); } - private (string, List) GetFullDepartmentName(List departments, int departmentId) + private (string, List) GetFullDepartmentName(List departments, long departmentId) { var departmentModel = departments.FirstOrDefault(d => d.Id == departmentId); if (departmentModel == null) { - return ("", new List()); + return ("", new List()); } - var pids = departmentModel.CodeRelationship?.TrimEnd(',').Split(',').Select(d => d.ObjToInt()).ToList(); + var pids = departmentModel.CodeRelationship?.TrimEnd(',').Split(',').Select(d => d.ObjToLong()).ToList(); pids.Add(departmentModel.Id); var pnams = departments.Where(d => pids.Contains(d.Id)).ToList().Select(d => d.Name).ToArray(); var fullName = string.Join("/", pnams); @@ -270,7 +265,7 @@ public async Task> Put([FromBody] SysUserInfoDto sysUserInf /// // DELETE: api/ApiWithActions/5 [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { var data = new MessageModel(); if (id > 0) diff --git a/Blog.Core.Api/Controllers/UserRoleController.cs b/Blog.Core.Api/Controllers/UserRoleController.cs index d14d6a73..693a68b8 100644 --- a/Blog.Core.Api/Controllers/UserRoleController.cs +++ b/Blog.Core.Api/Controllers/UserRoleController.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using AutoMapper; +using AutoMapper; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; @@ -81,7 +80,7 @@ public async Task> AddRole(string roleName) /// /// [HttpGet] - public async Task> AddUserRole(int uid, int rid) + public async Task> AddUserRole(long uid, long rid) { return new MessageModel() { diff --git a/Blog.Core.Api/Controllers/ValuesController.cs b/Blog.Core.Api/Controllers/ValuesController.cs index 52eb0ea1..22d663e9 100644 --- a/Blog.Core.Api/Controllers/ValuesController.cs +++ b/Blog.Core.Api/Controllers/ValuesController.cs @@ -1,8 +1,8 @@ using AutoMapper; using Blog.Core.Common; using Blog.Core.Common.HttpContextUser; -using Blog.Core.Common.HttpPolly; -using Blog.Core.Common.WebApiClients.HttpApis; +using Blog.Core.Common.Https.HttpPolly; +using Blog.Core.Common.Option; using Blog.Core.EventBus; using Blog.Core.EventBus.EventHandling; using Blog.Core.Extensions; @@ -13,8 +13,14 @@ using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; +using System.Text; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Utility; namespace Blog.Core.Controllers { @@ -28,7 +34,7 @@ namespace Blog.Core.Controllers //[Authorize(Policy = "SystemOrAdmin")] //[Authorize(PermissionNames.Permission)] [Authorize] - public class ValuesController : ControllerBase + public class ValuesController : BaseApiController { private IMapper _mapper; private readonly IAdvertisementServices _advertisementServices; @@ -36,52 +42,78 @@ public class ValuesController : ControllerBase private readonly IRoleModulePermissionServices _roleModulePermissionServices; private readonly IUser _user; private readonly IPasswordLibServices _passwordLibServices; - private readonly IBlogApi _blogApi; - private readonly IDoubanApi _doubanApi; readonly IBlogArticleServices _blogArticleServices; private readonly IHttpPollyHelper _httpPollyHelper; - - /// - /// ValuesController - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ValuesController(IBlogArticleServices blogArticleServices - , IMapper mapper - , IAdvertisementServices advertisementServices - , Love love - , IRoleModulePermissionServices roleModulePermissionServices - , IUser user, IPasswordLibServices passwordLibServices - , IBlogApi blogApi - , IDoubanApi doubanApi - , IHttpPollyHelper httpPollyHelper) + private readonly IRabbitMQPersistentConnection _persistentConnection; + private readonly SeqOptions _seqOptions; + private readonly ICaching _cache; + + public ValuesController(IBlogArticleServices blogArticleServices, IMapper mapper, + IAdvertisementServices advertisementServices, Love love, + IRoleModulePermissionServices roleModulePermissionServices, IUser user, + IPasswordLibServices passwordLibServices, + IHttpPollyHelper httpPollyHelper, IRabbitMQPersistentConnection persistentConnection, + IOptions seqOptions, ICaching caching) { // 测试 Authorize 和 mapper - _mapper = mapper; - _advertisementServices = advertisementServices; - _love = love; + _mapper = mapper; + _advertisementServices = advertisementServices; + _love = love; _roleModulePermissionServices = roleModulePermissionServices; // 测试 Httpcontext _user = user; // 测试多库 _passwordLibServices = passwordLibServices; - // 测试http请求 - _blogApi = blogApi; - _doubanApi = doubanApi; // 测试AOP加载顺序,配合 return _blogArticleServices = blogArticleServices; // 测试redis消息队列 _blogArticleServices = blogArticleServices; // httpPolly - _httpPollyHelper = httpPollyHelper; + _httpPollyHelper = httpPollyHelper; + _persistentConnection = persistentConnection; + _cache = caching; + _seqOptions = seqOptions.Value; + } + + /// + /// 测试Rabbit消息队列发送 + /// + [HttpGet] + [AllowAnonymous] + public IActionResult TestRabbitMqPublish() + { + if (!_persistentConnection.IsConnected) + { + _persistentConnection.TryConnect(); + } + + _persistentConnection.PublishMessage("Hello, RabbitMQ!", exchangeName: "blogcore", + routingKey: "myRoutingKey"); + return Ok(); + } + + /// + /// 测试Rabbit消息队列订阅 + /// + [HttpGet] + [AllowAnonymous] + public IActionResult TestRabbitMqSubscribe() + { + if (!_persistentConnection.IsConnected) + { + _persistentConnection.TryConnect(); + } + + _persistentConnection.StartConsuming("myQueue"); + return Ok(); + } + + private async Task Dealer(string exchange, string routingKey, byte[] msgBody, + IDictionary headers) + { + await Task.CompletedTask; + Console.WriteLine("我是消费者,这里消费了一条信息是:" + Encoding.UTF8.GetString(msgBody)); + return true; } [HttpGet] @@ -93,7 +125,7 @@ public MessageModel> MyClaims() response = (_user.GetClaimsIdentity().ToList()).Select(d => new ClaimDto { - Type = d.Type, + Type = d.Type, Value = d.Value } ).ToList() @@ -127,36 +159,34 @@ public async Task> Get() /* * 测试 sql 查询 */ - var queryBySql = await _blogArticleServices.QuerySql("SELECT bsubmitter,btitle,bcontent,bCreateTime FROM BlogArticle WHERE bID>5"); + var queryBySql = + await _blogArticleServices.QuerySql( + "SELECT bsubmitter,btitle,bcontent,bCreateTime FROM BlogArticle WHERE bID>5"); /* * 测试按照指定列查询 */ var queryByColums = await _blogArticleServices - .Query(it => new BlogViewModels() { btitle = it.btitle }); + .Query(it => new BlogViewModels() { btitle = it.btitle }); /* - * 测试按照指定列查询带多条件和排序方法 - */ + * 测试按照指定列查询带多条件和排序方法 + */ Expression> registerInfoWhere = a => a.btitle == "xxx" && a.bRemark == "XXX"; var queryByColumsByMultiTerms = await _blogArticleServices - .Query(it => new BlogArticle() { btitle = it.btitle }, registerInfoWhere, "bID Desc"); + .Query(it => new BlogArticle() { btitle = it.btitle }, registerInfoWhere, "bID Desc"); /* * 测试 sql 更新 - * + * * 【SQL参数】:@bID:5 * @bsubmitter:laozhang619 * @IsDeleted:False * 【SQL语句】:UPDATE `BlogArticle` SET * `bsubmitter`=@bsubmitter,`IsDeleted`=@IsDeleted WHERE `bID`=@bID */ - var updateSql = await _blogArticleServices.Update(new { bsubmitter = $"laozhang{DateTime.Now.Millisecond}", IsDeleted = false, bID = 5 }); - - - // 测试模拟异常,全局异常过滤器拦截 - var i = 0; - var d = 3 / i; + var updateSql = await _blogArticleServices.Update(new + { bsubmitter = $"laozhang{DateTime.Now.Millisecond}", IsDeleted = false, bID = 5 }); // 测试 AOP 缓存 @@ -169,9 +199,9 @@ public async Task> Get() // 测试多个异步执行时间 var roleModuleTask = _roleModulePermissionServices.Query(); - var listTask = _advertisementServices.Query(); - var ad = await roleModuleTask; - var list = await listTask; + var listTask = _advertisementServices.Query(); + var ad = await roleModuleTask; + var list = await listTask; // 测试service层返回异常 @@ -180,6 +210,22 @@ public async Task> Get() return data; } + + [HttpGet] + [AllowAnonymous] + public async Task>> Test_Aop_Cache() + { + // 测试 AOP 缓存 + var blogArticles = await _blogArticleServices.GetBlogs(); + + if (blogArticles.Any()) + { + return Success(blogArticles); + } + + return Failed>(); + } + /// /// 测试Redis消息队列 /// @@ -216,7 +262,6 @@ public void EventBusTry([FromServices] IEventBus _eventBus, string blogId = "1") // GET api/values/5 [HttpGet("{id}")] [AllowAnonymous] - //[TypeFilter(typeof(DeleteSubscriptionCache),Arguments =new object[] { "1"})] [TypeFilter(typeof(UseServiceDIAttribute), Arguments = new object[] { "laozhang" })] public ActionResult Get(int id) { @@ -250,8 +295,8 @@ public MessageModel> GetUserInfo(string ClaimType = "jti") var getUserInfoByToken = _user.GetUserInfoFromToken(ClaimType); return new MessageModel>() { - success = _user.IsAuthenticated(), - msg = _user.IsAuthenticated() ? _user.Name.ObjToString() : "未登录", + success = _user.IsAuthenticated(), + msg = _user.IsAuthenticated() ? _user.Name.ObjToString() : "未登录", response = _user.GetClaimValueByType(ClaimType) }; } @@ -298,7 +343,6 @@ public object Post([FromBody] BlogArticle blogArticle, int id) /// /// [HttpPost] - [Route("TestPostPara")] [AllowAnonymous] public object TestPostPara(string name) { @@ -314,11 +358,11 @@ public object TestPostPara(string name) public async Task TestMutiDBAPI() { // 从主库中,操作blogs - var blogs = await _blogArticleServices.Query(d => d.bID == 1); + var blogs = await _blogArticleServices.Query(d => d.bID == 1); var addBlog = await _blogArticleServices.Add(new BlogArticle() { }); // 从从库中,操作pwds - var pwds = await _passwordLibServices.Query(d => d.PLID > 0); + var pwds = await _passwordLibServices.Query(d => d.PLID > 0); var addPwd = await _passwordLibServices.Add(new PasswordLib() { }); return new @@ -328,20 +372,6 @@ public async Task TestMutiDBAPI() }; } - /// - /// 测试http请求 WebApiClient Get - /// - /// - [HttpGet("WebApiClientGetAsync")] - [AllowAnonymous] - public async Task WebApiClientGetAsync() - { - int id = 1; - string isbn = "9787544270878"; - var doubanVideoDetail = await _doubanApi.VideoDetailAsync(isbn); - return await _blogApi.DetailNuxtNoPerAsync(id); - } - /// /// 测试Fulent做参数校验 /// @@ -365,6 +395,7 @@ public async Task FluentVaTest([FromBody] UserRegisterVo param) public void Put(int id, [FromBody] string value) { } + /// /// Delete方法 /// @@ -376,15 +407,18 @@ public void Delete(int id) } #region Apollo 配置 + /// /// 测试接入Apollo获取配置信息 /// [HttpGet("/apollo")] [AllowAnonymous] - public async Task>> GetAllConfigByAppllo([FromServices] IConfiguration configuration) + public async Task>> GetAllConfigByAppllo( + [FromServices] IConfiguration configuration) { return await Task.FromResult(configuration.AsEnumerable()); } + /// /// 通过此处的key格式为 xx:xx:x /// @@ -394,14 +428,17 @@ public async Task GetConfigByAppllo(string key) { return await Task.FromResult(AppSettings.app(key)); } + #endregion #region HttpPolly + [HttpPost] [AllowAnonymous] public async Task HttpPollyPost() { - var response = await _httpPollyHelper.PostAsync(HttpEnum.LocalHost, "/api/ElasticDemo/EsSearchTest", "{\"from\": 0,\"size\": 10,\"word\": \"非那雄安\"}"); + var response = await _httpPollyHelper.PostAsync(HttpEnum.LocalHost, "/api/ElasticDemo/EsSearchTest", + "{\"from\": 0,\"size\": 10,\"word\": \"非那雄安\"}"); return response; } @@ -410,17 +447,75 @@ public async Task HttpPollyPost() [AllowAnonymous] public async Task HttpPollyGet() { - return await _httpPollyHelper.GetAsync(HttpEnum.LocalHost, "/api/ElasticDemo/GetDetailInfo?esid=3130&esindex=chinacodex"); + return await _httpPollyHelper.GetAsync(HttpEnum.LocalHost, + "/api/ElasticDemo/GetDetailInfo?esid=3130&esindex=chinacodex"); } + #endregion [HttpPost] [AllowAnonymous] public string TestEnum(EnumDemoDto dto) => dto.Type.ToString(); + + [HttpGet] + [AllowAnonymous] + public string TestOption() + { + return _seqOptions.ToJson(); + } + + /// + /// 获取雪花Id + /// + /// + [HttpGet] + [AllowAnonymous] + public long GetSnowflakeId() + { + return IdGeneratorUtility.NextId(); + } + + /// + /// 测试缓存 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task> TestCacheAsync() + { + await _cache.SetAsync("test", "test", new TimeSpan(0, 10, 0)); + + var result = await _cache.GetAsync("test"); + if (!"test".Equals(result)) + { + return Failed("缓存失败,值不一样"); + } + + var count = _cache.GetAllCacheKeys().Count; + if (count <= 0) + { + return Failed("缓存失败,数量不对"); + } + + return Success(""); + } + + /// + /// 雪花Id To DateTime + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public DateTime SnowflakeIdToDateTime(long id) + { + return YitterSnowflakeHelper.GetDateTime(IdGeneratorUtility.GetOptions(), id); + } } + public class ClaimDto { public string Type { get; set; } public string Value { get; set; } } -} +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/WeChatCompanyController.cs b/Blog.Core.Api/Controllers/WeChatCompanyController.cs index 4fa6eea5..dc12930b 100644 --- a/Blog.Core.Api/Controllers/WeChatCompanyController.cs +++ b/Blog.Core.Api/Controllers/WeChatCompanyController.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; diff --git a/Blog.Core.Api/Controllers/WeChatConfigController.cs b/Blog.Core.Api/Controllers/WeChatConfigController.cs index c597cb3f..1f3b705d 100644 --- a/Blog.Core.Api/Controllers/WeChatConfigController.cs +++ b/Blog.Core.Api/Controllers/WeChatConfigController.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; diff --git a/Blog.Core.Api/Controllers/WeChatController.cs b/Blog.Core.Api/Controllers/WeChatController.cs index 4c4877f6..5c7e5c6f 100644 --- a/Blog.Core.Api/Controllers/WeChatController.cs +++ b/Blog.Core.Api/Controllers/WeChatController.cs @@ -1,18 +1,13 @@ -using System.IO; -using System.Threading.Tasks; -using Blog.Core.IServices; +using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Blog.Core.Controllers { /// /// 微信公众号管理 - /// 作者:胡丁文 - /// 时间:2020-3-29 21:24:12 /// [Route("api/[controller]/[action]")] [ApiController] @@ -146,6 +141,18 @@ public async Task> PushCardMsg(WeChatCardMs return await _weChatConfigServices.PushCardMsg(msg, pushUserIP); } /// + /// 推送卡片消息接口 + /// + /// 卡片消息对象 + /// + [HttpGet] + [AllowAnonymous] + public async Task> PushCardMsgGet([FromQuery] WeChatCardMsgDataDto msg) + { + string pushUserIP = $"{Request.HttpContext.Connection.RemoteIpAddress}:{Request.HttpContext.Connection.RemotePort}"; + return await _weChatConfigServices.PushCardMsg(msg, pushUserIP); + } + /// /// 推送文本消息 /// /// 消息对象 diff --git a/Blog.Core.Api/Controllers/WeChatPushLogController.cs b/Blog.Core.Api/Controllers/WeChatPushLogController.cs index 1fe1603d..af168091 100644 --- a/Blog.Core.Api/Controllers/WeChatPushLogController.cs +++ b/Blog.Core.Api/Controllers/WeChatPushLogController.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; diff --git a/Blog.Core.Api/Controllers/WeChatSubController.cs b/Blog.Core.Api/Controllers/WeChatSubController.cs index bd8d1759..94f982d2 100644 --- a/Blog.Core.Api/Controllers/WeChatSubController.cs +++ b/Blog.Core.Api/Controllers/WeChatSubController.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; diff --git a/Blog.Core.Api/Dockerfile b/Blog.Core.Api/Dockerfile index 43618077..afb398c1 100644 --- a/Blog.Core.Api/Dockerfile +++ b/Blog.Core.Api/Dockerfile @@ -3,7 +3,7 @@ #FROM swr.cn-south-1.myhuaweicloud.com/mcr/aspnet:5.0-alpine #FROM mcr.microsoft.com/dotnet/core/aspnet:5.0-buster-slim -FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone diff --git a/Blog.Core.Api/Filter/AutofacPropertityModuleReg.cs b/Blog.Core.Api/Filter/AutofacPropertityModuleReg.cs index 9691178d..605c4f5a 100644 --- a/Blog.Core.Api/Filter/AutofacPropertityModuleReg.cs +++ b/Blog.Core.Api/Filter/AutofacPropertityModuleReg.cs @@ -1,12 +1,14 @@ using Autofac; using Microsoft.AspNetCore.Mvc; -namespace Blog.Core.Extensions +namespace Blog.Core.Filter { public class AutofacPropertityModuleReg : Autofac.Module { protected override void Load(ContainerBuilder builder) { + // 记得要启动服务注册 + // builder.Services.Replace(ServiceDescriptor.Transient()); var controllerBaseType = typeof(ControllerBase); builder.RegisterAssemblyTypes(typeof(Program).Assembly) .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType) diff --git a/Blog.Core.Api/Filter/GlobalExceptionFilter.cs b/Blog.Core.Api/Filter/GlobalExceptionFilter.cs index da119a6e..e11d8dbd 100644 --- a/Blog.Core.Api/Filter/GlobalExceptionFilter.cs +++ b/Blog.Core.Api/Filter/GlobalExceptionFilter.cs @@ -3,14 +3,10 @@ using Blog.Core.Common.LogHelper; using Blog.Core.Hubs; using Blog.Core.Model; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Logging; using StackExchange.Profiling; -using System; namespace Blog.Core.Filter { @@ -23,19 +19,20 @@ public class GlobalExceptionsFilter : IExceptionFilter private readonly IHubContext _hubContext; private readonly ILogger _loggerHelper; - public GlobalExceptionsFilter(IWebHostEnvironment env, ILogger loggerHelper, IHubContext hubContext) + public GlobalExceptionsFilter(IWebHostEnvironment env, ILogger loggerHelper, + IHubContext hubContext) { - _env = env; + _env = env; _loggerHelper = loggerHelper; - _hubContext = hubContext; + _hubContext = hubContext; } public void OnException(ExceptionContext context) { var json = new MessageModel(); - json.msg = context.Exception.Message;//错误信息 - json.status = 500;//500异常 + json.msg = context.Exception.Message; //错误信息 + json.status = 500; //500异常 var errorAudit = "Unable to resolve service for"; if (!string.IsNullOrEmpty(json.msg) && json.msg.Contains(errorAudit)) { @@ -44,8 +41,9 @@ public void OnException(ExceptionContext context) if (_env.EnvironmentName.ObjToString().Equals("Development")) { - json.msgDev = context.Exception.StackTrace;//堆栈信息 + json.msgDev = context.Exception.StackTrace; //堆栈信息 } + var res = new ContentResult(); res.Content = JsonHelper.GetJSON>(json); @@ -54,14 +52,12 @@ public void OnException(ExceptionContext context) MiniProfiler.Current.CustomTiming("Errors:", json.msg); - //采用log4net 进行错误日志记录 + //进行错误日志记录 _loggerHelper.LogError(json.msg + WriteLog(json.msg, context.Exception)); if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) { - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); + _hubContext.Clients.All.SendAsync("ReceiveUpdate", json.msg).Wait(); } - - } /// @@ -72,11 +68,14 @@ public void OnException(ExceptionContext context) /// public string WriteLog(string throwMsg, Exception ex) { - return string.Format("\r\n【自定义错误】:{0} \r\n【异常类型】:{1} \r\n【异常信息】:{2} \r\n【堆栈调用】:{3}", new object[] { throwMsg, - ex.GetType().Name, ex.Message, ex.StackTrace }); + return string.Format("\r\n【自定义错误】:{0} \r\n【异常类型】:{1} \r\n【异常信息】:{2} \r\n【堆栈调用】:{3}", new object[] + { + throwMsg, + ex.GetType().Name, ex.Message, ex.StackTrace + }); } - } + public class InternalServerErrorObjectResult : ObjectResult { public InternalServerErrorObjectResult(object value) : base(value) @@ -84,6 +83,7 @@ public InternalServerErrorObjectResult(object value) : base(value) StatusCode = StatusCodes.Status500InternalServerError; } } + //返回错误信息 public class JsonErrorResponse { @@ -91,10 +91,10 @@ public class JsonErrorResponse /// 生产环境的消息 /// public string Message { get; set; } + /// /// 开发环境的消息 /// public string DevelopmentMessage { get; set; } } - -} +} \ No newline at end of file diff --git a/Blog.Core.Api/Filter/UseServiceDIAttribute.cs b/Blog.Core.Api/Filter/UseServiceDIAttribute.cs index 867ca0cd..2c487872 100644 --- a/Blog.Core.Api/Filter/UseServiceDIAttribute.cs +++ b/Blog.Core.Api/Filter/UseServiceDIAttribute.cs @@ -1,6 +1,5 @@ using Blog.Core.IServices; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Logging; namespace Blog.Core.Filter { @@ -11,7 +10,7 @@ public class UseServiceDIAttribute : ActionFilterAttribute private readonly IBlogArticleServices _blogArticleServices; private readonly string _name; - public UseServiceDIAttribute(ILogger logger, IBlogArticleServices blogArticleServices,string Name="") + public UseServiceDIAttribute(ILogger logger, IBlogArticleServices blogArticleServices, string Name = "") { _logger = logger; _blogArticleServices = blogArticleServices; @@ -21,14 +20,16 @@ public UseServiceDIAttribute(ILogger logger, IBlogArticle public override void OnActionExecuted(ActionExecutedContext context) { - //var dd =await _blogArticleServices.Query(); + var dd = _blogArticleServices.Query().Result; + _logger.LogInformation("测试自定义服务特性"); + Console.WriteLine(_name); base.OnActionExecuted(context); DeleteSubscriptionFiles(); } private void DeleteSubscriptionFiles() { - + } } } diff --git a/Blog.Core.Api/Log4net.config b/Blog.Core.Api/Log4net.config deleted file mode 100644 index 3ce11b6a..00000000 --- a/Blog.Core.Api/Log4net.config +++ /dev/null @@ -1,274 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Blog.Core.Api/Program.cs b/Blog.Core.Api/Program.cs index b3bf2387..bc2b51cc 100644 --- a/Blog.Core.Api/Program.cs +++ b/Blog.Core.Api/Program.cs @@ -1,134 +1,137 @@ - -// 以下为asp.net 6.0的写法,如果用5.0,请看Program.five.cs文件 +// 以下为asp.net 6.0的写法,如果用5.0,请看Program.five.cs文件, +// 或者参考github上的.net6.0分支相关代码 + using Autofac; using Autofac.Extensions.DependencyInjection; using Blog.Core; using Blog.Core.Common; -using Blog.Core.Common.LogHelper; -using Blog.Core.Common.Seed; +using Blog.Core.Common.Core; +using Blog.Core.Common.Helper; using Blog.Core.Extensions; using Blog.Core.Extensions.Apollo; using Blog.Core.Extensions.Middlewares; +using Blog.Core.Extensions.ServiceExtensions; using Blog.Core.Filter; using Blog.Core.Hubs; -using Blog.Core.IServices; -using Blog.Core.Tasks; -using FluentValidation.AspNetCore; +using Blog.Core.Serilog.Utility; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.IdentityModel.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; +using Serilog; using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Text; var builder = WebApplication.CreateBuilder(args); + // 1、配置host与容器 builder.Host -.UseServiceProviderFactory(new AutofacServiceProviderFactory()) -.ConfigureContainer(builder => -{ - builder.RegisterModule(new AutofacModuleRegister()); - builder.RegisterModule(); -}) -.ConfigureLogging((hostingContext, builder) => -{ - builder.AddFilter("System", LogLevel.Error); - builder.AddFilter("Microsoft", LogLevel.Error); - builder.SetMinimumLevel(LogLevel.Error); - builder.AddLog4Net(Path.Combine(Directory.GetCurrentDirectory(), "Log4net.config")); -}) -.ConfigureAppConfiguration((hostingContext, config) => -{ - config.Sources.Clear(); - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false); - config.AddConfigurationApollo("appsettings.apollo.json"); -}); - + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureContainer(builder => + { + builder.RegisterModule(new AutofacModuleRegister()); + builder.RegisterModule(); + }) + .ConfigureAppConfiguration((hostingContext, config) => + { + hostingContext.Configuration.ConfigureApplication(); + config.Sources.Clear(); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false); + config.AddConfigurationApollo("appsettings.apollo.json"); + }); +builder.ConfigureApplication(); // 2、配置服务 builder.Services.AddSingleton(new AppSettings(builder.Configuration)); -builder.Services.AddSingleton(new LogLock(builder.Environment.ContentRootPath)); +builder.Services.AddAllOptionRegister(); + builder.Services.AddUiFilesZipSetup(builder.Environment); Permissions.IsUseIds4 = AppSettings.app(new string[] { "Startup", "IdentityServer4", "Enabled" }).ObjToBool(); +Permissions.IsUseAuthing = AppSettings.app(new string[] { "Startup", "Authing", "Enabled" }).ObjToBool(); RoutePrefix.Name = AppSettings.app(new string[] { "AppSettings", "SvcName" }).ObjToString(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); -builder.Services.AddMemoryCacheSetup(); -builder.Services.AddRedisCacheSetup(); +builder.Services.AddCacheSetup(); builder.Services.AddSqlsugarSetup(); builder.Services.AddDbSetup(); +builder.Services.AddInitializationHostServiceSetup(); + +builder.Host.AddSerilogSetup(); + builder.Services.AddAutoMapperSetup(); builder.Services.AddCorsSetup(); builder.Services.AddMiniProfilerSetup(); builder.Services.AddSwaggerSetup(); builder.Services.AddJobSetup(); + builder.Services.AddHttpContextSetup(); builder.Services.AddAppTableConfigSetup(builder.Environment); -builder.Services.AddHttpApi(); -builder.Services.AddRedisInitMqSetup(); -builder.Services.AddRabbitMQSetup(); -builder.Services.AddKafkaSetup(builder.Configuration); -builder.Services.AddEventBusSetup(); +builder.Services.AddHttpPollySetup(); builder.Services.AddNacosSetup(builder.Configuration); +builder.Services.AddRedisInitMqSetup(); + +builder.Services.AddIpPolicyRateLimitSetup(builder.Configuration); +builder.Services.AddSignalR().AddNewtonsoftJsonProtocol(); builder.Services.AddAuthorizationSetup(); -if (Permissions.IsUseIds4) +if (Permissions.IsUseIds4 || Permissions.IsUseAuthing) { - builder.Services.AddAuthentication_Ids4Setup(); + if (Permissions.IsUseIds4) builder.Services.AddAuthentication_Ids4Setup(); + else if (Permissions.IsUseAuthing) builder.Services.AddAuthentication_AuthingSetup(); } else { builder.Services.AddAuthentication_JWTSetup(); } -builder.Services.AddIpPolicyRateLimitSetup(builder.Configuration); -builder.Services.AddSignalR().AddNewtonsoftJsonProtocol(); builder.Services.AddScoped(); builder.Services.Configure(x => x.AllowSynchronousIO = true) - .Configure(x => x.AllowSynchronousIO = true); + .Configure(x => x.AllowSynchronousIO = true); -builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(); -builder.Services.AddHttpPollySetup(); +builder.Services.AddDataProtectionSetup(); builder.Services.AddControllers(o => -{ - o.Filters.Add(typeof(GlobalExceptionsFilter)); - //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); - o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); -}) -.AddNewtonsoftJson(options => -{ - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - options.SerializerSettings.ContractResolver = new DefaultContractResolver(); - options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; - //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; - options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; - options.SerializerSettings.Converters.Add(new StringEnumConverter()); -}) -//.AddFluentValidation(config => -//{ -// //程序集方式添加验证 -// config.RegisterValidatorsFromAssemblyContaining(typeof(UserRegisterVoValidator)); -// //是否与MvcValidation共存 -// config.DisableDataAnnotationsValidation = true; -//}) -; + { + o.Filters.Add(typeof(GlobalExceptionsFilter)); + //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); + o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); + }) + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + options.SerializerSettings.ContractResolver = new DefaultContractResolver(); + options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; + //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; + options.SerializerSettings.Converters.Add(new StringEnumConverter()); + //将long类型转为string + options.SerializerSettings.Converters.Add(new NumberConverter(NumberConverterShip.Int64)); + }); + +builder.Services.AddRabbitMQSetup(); +builder.Services.AddKafkaSetup(builder.Configuration); +builder.Services.AddEventBusSetup(); builder.Services.AddEndpointsApiExplorer(); builder.Services.Replace(ServiceDescriptor.Transient()); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - // 3、配置中间件 var app = builder.Build(); +IdentityModelEventSource.ShowPII = true; + +app.ConfigureApplication(); +app.UseApplicationSetup(); +app.UseResponseBodyRead(); if (app.Environment.IsDevelopment()) { @@ -140,6 +143,10 @@ //app.UseHsts(); } +app.UseEncryptionRequest(); +app.UseEncryptionResponse(); + +app.UseExceptionHandlerMiddle(); app.UseIpLimitMiddle(); app.UseRequestResponseLogMiddle(); app.UseRecordAccessLogsMiddle(); @@ -159,36 +166,25 @@ app.UseStaticFiles(); app.UseCookiePolicy(); app.UseStatusCodePages(); +app.UseSerilogRequestLogging(options => +{ + options.MessageTemplate = SerilogRequestUtility.HttpMessageTemplate; + options.GetLevel = SerilogRequestUtility.GetRequestLevel; + options.EnrichDiagnosticContext = SerilogRequestUtility.EnrichFromRequest; +}); app.UseRouting(); if (builder.Configuration.GetValue("AppSettings:UseLoadTest")) { app.UseMiddleware(); } + app.UseAuthentication(); app.UseAuthorization(); app.UseMiniProfilerMiddleware(); -//app.UseExceptionHandlerMidd(); - -app.UseEndpoints(endpoints => -{ - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - - endpoints.MapHub("/api2/chatHub"); -}); - -var scope = app.Services.GetRequiredService().CreateScope(); -var myContext = scope.ServiceProvider.GetRequiredService(); -var tasksQzServices = scope.ServiceProvider.GetRequiredService(); -var schedulerCenter = scope.ServiceProvider.GetRequiredService(); -var lifetime = scope.ServiceProvider.GetRequiredService(); -app.UseSeedDataMiddle(myContext, builder.Environment.WebRootPath); -app.UseQuartzJobMiddleware(tasksQzServices, schedulerCenter); -app.UseConsulMiddle(builder.Configuration, lifetime); -app.ConfigureEventBus(); +app.MapControllers(); +app.MapHub("/api2/chatHub"); // 4、运行 -app.Run(); +app.Run(); \ No newline at end of file diff --git a/Blog.Core.Api/Properties/launchSettings.json b/Blog.Core.Api/Properties/launchSettings.json index 425ec45f..e3113d39 100644 --- a/Blog.Core.Api/Properties/launchSettings.json +++ b/Blog.Core.Api/Properties/launchSettings.json @@ -13,8 +13,8 @@ "commandName": "Project", "launchBrowser": true, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore" + "ASPNETCORE_ENVIRONMENT": "Development" + //"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore"// 如果要开始skywalking,请取消此行注释 }, "applicationUrl": "http://localhost:9291" }, diff --git a/Blog.Core.Api/Startup.cs b/Blog.Core.Api/Startup.cs deleted file mode 100644 index b341819e..00000000 --- a/Blog.Core.Api/Startup.cs +++ /dev/null @@ -1,239 +0,0 @@ -using Autofac; -using Blog.Core.Common; -using Blog.Core.Common.LogHelper; -using Blog.Core.Common.Seed; -using Blog.Core.Extensions; -using Blog.Core.Filter; -using Blog.Core.Hubs; -using Blog.Core.IServices; -using Blog.Core.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; -using System.IdentityModel.Tokens.Jwt; -using System.Reflection; -using System.Text; -using Blog.Core.Extensions.Middlewares; - -namespace Blog.Core -{ - public class Startup - { - - private IServiceCollection _services; - - public Startup(IConfiguration configuration, IWebHostEnvironment env) - { - Configuration = configuration; - Env = env; - } - - public IConfiguration Configuration { get; } - public IWebHostEnvironment Env { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - // 以下code可能与文章中不一样,对代码做了封装,具体查看右侧 Extensions 文件夹. - services.AddSingleton(new AppSettings(Configuration)); - services.AddSingleton(new LogLock(Env.ContentRootPath)); - services.AddUiFilesZipSetup(Env); - - Permissions.IsUseIds4 = AppSettings.app(new string[] { "Startup", "IdentityServer4", "Enabled" }).ObjToBool(); - RoutePrefix.Name = AppSettings.app(new string[] { "AppSettings", "SvcName" }).ObjToString(); - - // 确保从认证中心返回的ClaimType不被更改,不使用Map映射 - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - services.AddMemoryCacheSetup(); - services.AddRedisCacheSetup(); - - services.AddSqlsugarSetup(); - services.AddDbSetup(); - services.AddAutoMapperSetup(); - services.AddCorsSetup(); - services.AddMiniProfilerSetup(); - services.AddSwaggerSetup(); - services.AddJobSetup(); - services.AddHttpContextSetup(); - //services.AddAppConfigSetup(Env); - services.AddAppTableConfigSetup(Env);//表格打印配置 - services.AddHttpApi(); - services.AddRedisInitMqSetup(); - - services.AddRabbitMQSetup(); - services.AddKafkaSetup(Configuration); - services.AddEventBusSetup(); - - services.AddNacosSetup(Configuration); - - // 授权+认证 (jwt or ids4) - services.AddAuthorizationSetup(); - if (Permissions.IsUseIds4) - { - services.AddAuthentication_Ids4Setup(); - } - else - { - services.AddAuthentication_JWTSetup(); - } - - services.AddIpPolicyRateLimitSetup(Configuration); - - services.AddSignalR().AddNewtonsoftJsonProtocol(); - - services.AddScoped(); - - services.Configure(x => x.AllowSynchronousIO = true) - .Configure(x => x.AllowSynchronousIO = true); - - services.AddDistributedMemoryCache(); - services.AddSession(); - services.AddHttpPollySetup(); - - services.AddControllers(o => - { - // 全局异常过滤 - o.Filters.Add(typeof(GlobalExceptionsFilter)); - // 全局路由权限公约 - //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); - // 全局路由前缀,统一修改路由 - o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); - }) - // 这种写法也可以 - //.AddJsonOptions(options => - //{ - // options.JsonSerializerOptions.PropertyNamingPolicy = null; - //}) - //MVC全局配置Json序列化处理 - .AddNewtonsoftJson(options => - { - //忽略循环引用 - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - //不使用驼峰样式的key - options.SerializerSettings.ContractResolver = new DefaultContractResolver(); - //设置时间格式 - options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; - //忽略Model中为null的属性 - //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; - //设置本地时间而非UTC时间 - options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; - //添加Enum转string - options.SerializerSettings.Converters.Add(new StringEnumConverter()); - }); - - services.Replace(ServiceDescriptor.Transient()); - - _services = services; - //支持编码大全 例如:支持 System.Text.Encoding.GetEncoding("GB2312") System.Text.Encoding.GetEncoding("GB18030") - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - } - - // 注意在Program.CreateHostBuilder,添加Autofac服务工厂 - public void ConfigureContainer(ContainerBuilder builder) - { - builder.RegisterModule(new AutofacModuleRegister()); - builder.RegisterModule(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyContext myContext, ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, IHostApplicationLifetime lifetime) - { - // Ip限流,尽量放管道外层 - app.UseIpLimitMiddle(); - // 记录请求与返回数据 - app.UseRequestResponseLogMiddle(); - // 用户访问记录(必须放到外层,不然如果遇到异常,会报错,因为不能返回流) - app.UseRecordAccessLogsMiddle(); - // signalr - app.UseSignalRSendMiddle(); - // 记录ip请求 - app.UseIpLogMiddle(); - // 查看注入的所有服务 - app.UseAllServicesMiddle(_services); - - if (env.IsDevelopment()) - { - // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 - // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection - //app.UseHsts(); - } - - app.UseSession(); - app.UseSwaggerAuthorized(); - // 封装Swagger展示 - app.UseSwaggerMiddle(() => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.Api.index.html")); - - // ↓↓↓↓↓↓ 注意下边这些中间件的顺序,很重要 ↓↓↓↓↓↓ - - // CORS跨域 - app.UseCors(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" })); - // 跳转https - //app.UseHttpsRedirection(); - // 使用静态文件 - DefaultFilesOptions defaultFilesOptions = new DefaultFilesOptions(); - defaultFilesOptions.DefaultFileNames.Clear(); - defaultFilesOptions.DefaultFileNames.Add("index.html"); - app.UseDefaultFiles(defaultFilesOptions); - app.UseStaticFiles(); - // 使用cookie - app.UseCookiePolicy(); - // 返回错误码 - app.UseStatusCodePages(); - // Routing - app.UseRouting(); - // 这种自定义授权中间件,可以尝试,但不推荐 - // app.UseJwtTokenAuth(); - - // 测试用户,用来通过鉴权 - if (Configuration.GetValue("AppSettings:UseLoadTest")) - { - app.UseMiddleware(); - } - // 先开启认证 - app.UseAuthentication(); - // 然后是授权中间件 - app.UseAuthorization(); - //开启性能分析 - app.UseMiniProfilerMiddleware(); - // 开启异常中间件,要放到最后 - //app.UseExceptionHandlerMidd(); - - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - - endpoints.MapHub("/api2/chatHub"); - }); - - // 生成种子数据 - app.UseSeedDataMiddle(myContext, Env.WebRootPath); - // 开启QuartzNetJob调度服务 - app.UseQuartzJobMiddleware(tasksQzServices, schedulerCenter); - // 服务注册 - app.UseConsulMiddle(Configuration, lifetime); - // 事件总线,订阅服务 - app.ConfigureEventBus(); - - } - - } -} diff --git a/Blog.Core.Api/appsettings.json b/Blog.Core.Api/appsettings.json index 2adeb137..d62aae9e 100644 --- a/Blog.Core.Api/appsettings.json +++ b/Blog.Core.Api/appsettings.json @@ -1,37 +1,34 @@ { "urls": "http://*:9291", //web服务端口,如果用IIS部署,把这个去掉 - "Logging": { - "LogLevel": { - "Default": "Information", //加入Default否则log4net本地写入不了日志 - "Blog.Core.AuthHelper.ApiResponseHandler": "Error" - }, - "Debug": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning", - "Microsoft.Hosting.Lifetime": "Debug" + "Serilog": { + "MinimumLevel": { + "Default": "Information", //关闭日志1:修改Serilog的最低日志级别,比如Warning + "Override": { + "Microsoft": "Information", + "Microsoft.AspNetCore": "Warning", + "System": "Warning", + "System.Net.Http.HttpClient": "Warning", + "Hangfire": "Information", + "Magicodes": "Warning", + "DotNetCore.CAP": "Information", + "Savorboard.CAP": "Information", + "Quartz": "Information" } - }, - "Log4Net": { - "Name": "Blog.Core" } }, "AllowedHosts": "*", "Redis": { - "ConnectionString": "127.0.0.1:6319,password=admin" + "Enable": false, + "ConnectionString": "127.0.0.1:6379,allowAdmin=true", + "InstanceName": "" //前缀 }, "RabbitMQ": { - "Enabled": false, - "Connection": "118.25.251.13", - "UserName": "", - "Password": "!", - "RetryCount": 3 + "Enabled": true, + "Connection": "101xxxx57", + "UserName": "xxxx", + "Password": "xxxxx", + "Port": "5672", + "RetryCount": 2 }, "Kafka": { "Enabled": false, @@ -45,16 +42,14 @@ "SubscriptionClientName": "Blog.Core" }, "AppSettings": { - "RedisCachingAOP": { - "Enabled": false - }, - "MemoryCachingAOP": { + "CachingAOP": { "Enabled": true }, + "LogToDb": true, "LogAOP": { - "Enabled": true, + "Enabled": false, "LogToFile": { - "Enabled": false + "Enabled": true }, "LogToDB": { "Enabled": true @@ -63,13 +58,16 @@ "TranAOP": { "Enabled": true }, + "UserAuditAOP": { + "Enabled": false + }, "SqlAOP": { - "Enabled": true, + "Enabled": true, //关闭日志2:修改Sql日志是否显示(也可以精准配置,是否生成到文件、数据库、控制台) "LogToFile": { - "Enabled": false + "Enabled": true }, "LogToDB": { - "Enabled": false + "Enabled": true }, "LogToConsole": { "Enabled": true @@ -80,17 +78,21 @@ "SeedDBDataEnabled": true, //生成表,并初始化数据 "Author": "Blog.Core", "SvcName": "", // /svc/blog - "UseLoadTest": false + "UseLoadTest": false, + "CacheDbEnabled": false }, - // 请配置MainDB为你想要的主库的ConnId值,并设置对应的Enabled为true; - // *** 单库操作,把 MutiDBEnabled 设为false ***; - // *** 多库操作,把 MutiDBEnabled 设为true,其他的从库Enabled也为true **; - // 具体配置看视频:https://www.bilibili.com/video/BV1BJ411B7mn?p=6 - - "MainDB": "WMBLOG_SQLITE", //当前项目的主库,所对应的连接字符串的Enabled必须为true - "MutiDBEnabled": false, //是否开启多库模式 - "CQRSEnabled": false, //是否开启读写分离模式,必须是单库模式,且数据库类型一致,比如都是SqlServer + //优化DB配置、不会再区分单库多库 + //MainDb:标识当前项目的主库,所对应的连接字符串的Enabled必须为true + //Log:标识日志库,所对应的连接字符串的Enabled必须为true + //从库只需配置Slaves数组,要求数据库类型一致!,比如都是SqlServer + // + //新增,故障转移方案 + //如果主库挂了,会自动切换到备用连接(比如说主库+备用库) + //备用连接的ConnId配置为主库的ConnId+数字即可,比如主库的ConnId为Main,那么备用连接的ConnId为Mian1 + //主库、备用库无需数据库类型一致! + //备用库不会有程序维护,需要手动维护 + "MainDB": "Main", //当前项目的主库,所对应的连接字符串的Enabled必须为true "DBS": [ /* 对应下边的 DBType @@ -103,17 +105,40 @@ Kdbndp = 6,//人大金仓 */ { - "ConnId": "WMBLOG_SQLITE", + "ConnId": "Main", "DBType": 2, "Enabled": true, - "HitRate": 50, // 值越大,优先级越高 - "Connection": "WMBlog.db" //sqlite只写数据库名就行 + "Connection": "WMBlog.db", //sqlite只写数据库名就行 + "Slaves": [ + { + "HitRate": 0,// 值越大,优先级越高 0不使用 + "Connection": "WMBlog2.db" + } + ] + }, + { + "ConnId": "Main2", + "DBType": 2, + "Enabled": false, + "Connection": "WMBlog3.db", //sqlite只写数据库名就行 + "Slaves": [ + { + "HitRate": 0,// 值越大,优先级越高 0不使用 + "Connection": "WMBlog4.db" + } + ] + }, + { + "ConnId": "Log", //日志库连接固定名称,不要改,其他的可以改 + "DBType": 2, + "Enabled": true, + "HitRate": 50, + "Connection": "WMBlogLog.db" //sqlite只写数据库名就行 }, { "ConnId": "WMBLOG_MSSQL_1", "DBType": 1, "Enabled": false, - "HitRate": 40, "Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_1;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", "ProviderName": "System.Data.SqlClient" }, @@ -121,7 +146,6 @@ "ConnId": "WMBLOG_MSSQL_2", "DBType": 1, "Enabled": false, - "HitRate": 30, "Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", "ProviderName": "System.Data.SqlClient" }, @@ -129,43 +153,38 @@ "ConnId": "WMBLOG_MYSQL", "DBType": 0, "Enabled": false, - "HitRate": 20, - "Connection": "server=.;Database=ddd;Uid=root;Pwd=123456;Port=10060;Allow User Variables=True;" + "Connection": "server=localhost;Database=blog;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" }, { "ConnId": "WMBLOG_MYSQL_2", "DBType": 0, - "Enabled": true, - "HitRate": 20, + "Enabled": false, "Connection": "server=localhost;Database=blogcore001;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" }, { "ConnId": "WMBLOG_ORACLE", "DBType": 3, "Enabled": false, - "HitRate": 10, "Connection": "Data Source=127.0.0.1/ops;User ID=OPS;Password=123456;Persist Security Info=True;Connection Timeout=60;" }, { "ConnId": "WMBLOG_DM", "DBType": 5, "Enabled": false, - "HitRate": 10, - "Connection": "PORT=5236;DATABASE=DAMENG;HOST=localhost;PASSWORD=SYSDBA;USER ID=SYSDBA;" + "Connection": "Server=xxxxx:5236;User Id=xxxxx;PWD=xxxxx;SCHEMA=TESTDBA;" }, { "ConnId": "WMBLOG_KDBNDP", "DBType": 6, - "Enabled": true, - "HitRate": 10, + "Enabled": false, "Connection": "Server=127.0.0.1;Port=54321;UID=SYSTEM;PWD=system;database=SQLSUGAR4XTEST1;" } ], "Audience": { "Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", //不要太短,16位+ "SecretFile": "C:\\my-file\\blog.core.audience.secret.txt", //安全。内容就是Secret - "Issuer": "Blog.Core", - "Audience": "wr" + "Issuer": "Blog.Core", //这个值一定要在自己的项目里修改!! + "Audience": "wr" //这个值一定要在自己的项目里修改!! }, "Mongo": { "ConnectionString": "mongodb://nosql.data", @@ -189,11 +208,17 @@ "AuthorizationUrl": "http://localhost:5004", // 认证中心域名 "ApiName": "blog.core.api" // 资源服务器 }, + "Authing": { + "Enabled": false, + "Issuer": "https://uldr24esx31h-demo.authing.cn/oidc", + "Audience": "63d51c4205c2849803be5178", + "JwksUri": "https://uldr24esx31h-demo.authing.cn/oidc/.well-known/jwks.json" + }, "RedisMq": { "Enabled": false //redis 消息队列 }, "MiniProfiler": { - "Enabled": false //性能分析开启 + "Enabled": true //性能分析开启 }, "Nacos": { "Enabled": false //Nacos注册中心 @@ -203,7 +228,7 @@ "RequestResponseLog": { "Enabled": true, "LogToFile": { - "Enabled": false + "Enabled": true }, "LogToDB": { "Enabled": true @@ -212,7 +237,7 @@ "IPLog": { "Enabled": true, "LogToFile": { - "Enabled": false + "Enabled": true }, "LogToDB": { "Enabled": true @@ -221,7 +246,7 @@ "RecordAccessLogs": { "Enabled": true, "LogToFile": { - "Enabled": false + "Enabled": true }, "LogToDB": { "Enabled": true @@ -229,10 +254,10 @@ "IgnoreApis": "/api/permission/getnavigationbar,/api/monitor/getids4users,/api/monitor/getaccesslogs,/api/monitor/server,/api/monitor/getactiveusers,/api/monitor/server," }, "SignalR": { - "Enabled": false + "Enabled": true }, "SignalRSendLog": { - "Enabled": false + "Enabled": true }, "QuartzNetJob": { "Enabled": true @@ -242,6 +267,20 @@ }, "IpRateLimit": { "Enabled": true + }, + "EncryptionResponse": { + "Enabled": true, + "AllApis": false, + "LimitApis": [ + "/api/Login/GetJwtTokenSecret" + ] + }, + "EncryptionRequest": { + "Enabled": true, + "AllApis": false, + "LimitApis": [ + "/api/Login/GetJwtTokenSecret" + ] } }, "IpRateLimiting": { @@ -316,5 +355,10 @@ "FiedValue": "Blog.Core.Api" } ] + }, + "Seq": { + "Enabled": false, + "Address": "http://localhost:5341/", + "ApiKey": "" } -} +} \ No newline at end of file diff --git a/Blog.Core.Api/index.html b/Blog.Core.Api/index.html index b0092a62..99b4caa4 100644 --- a/Blog.Core.Api/index.html +++ b/Blog.Core.Api/index.html @@ -1,8 +1,6 @@  - + @@ -10,14 +8,28 @@ - + %(DocumentTitle) + %(HeadContent) - + @@ -80,9 +111,9 @@
diff --git a/Blog.Core.Api/skyapm.json b/Blog.Core.Api/skyapm.json index cd5ed0ee..cdb0e606 100644 --- a/Blog.Core.Api/skyapm.json +++ b/Blog.Core.Api/skyapm.json @@ -11,7 +11,7 @@ }, "Logging": { "Level": "Information", - "FilePath": "Log/skyapm-{Date}.log" + "FilePath": "Logs/Skyapm/skyapm-{Date}.log" }, "Transport": { "Interval": 3000, diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/SysUserInfo.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/SysUserInfo.xlsx index 51c0e125..6a1e4ca6 100644 Binary files a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/SysUserInfo.xlsx and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/SysUserInfo.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv index 2bb130a1..76c55a22 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv @@ -1535,5 +1535,49 @@ "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", "Id": 72 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "任务日志查询", + "LinkUrl": "\/api\/TasksQz\/GetTaskLogs", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 73 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "任务概况", + "LinkUrl": "\/api\/TasksQz\/GetTaskOverview", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 74 } ] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv index 52493a12..66b4313e 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv @@ -7,6 +7,7 @@ "Mid": 0, "OrderSort": -90, "Icon": "fa-home", + "IconNew": "HomeFilled", "Description": "33", "Enabled": 1, "CreateId": 18, @@ -27,6 +28,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-users", + "IconNew": "UserFilled", "Description": "11", "Enabled": 1, "CreateId": 18, @@ -47,6 +49,7 @@ "Mid": 22, "OrderSort": 0, "Icon": null, + "IconNew": "Menu", "Description": null, "Enabled": 1, "CreateId": 18, @@ -67,6 +70,7 @@ "Mid": 7, "OrderSort": 0, "Icon": null, + "IconNew": "Menu", "Description": null, "Enabled": 1, "CreateId": 18, @@ -87,6 +91,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-sitemap", + "IconNew": "Lock", "Description": null, "Enabled": 1, "CreateId": 18, @@ -169,7 +174,7 @@ "Icon": null, "Description": "这个用户页的查询按钮", "Enabled": 1, - "Func": "getUsers", + "Func": "handleQuery", "CreateId": 18, "CreateBy": "提bug账号", "CreateTime": "\/Date(1546272000000+0800)\/", @@ -188,6 +193,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-line-chart", + "IconNew": "Histogram", "Description": null, "Enabled": 1, "CreateId": 18, @@ -319,7 +325,7 @@ "ModifyId": null, "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", - "Func": "getRoles", + "Func": "handleQuery", "IsDeleted": 0, "Id": 16, "IsHide": 0 @@ -396,7 +402,7 @@ "OrderSort": 0, "Icon": null, "Description": "查询 接口", - "Func": "getModules", + "Func": "handleQuery", "Enabled": 1, "CreateId": 18, "CreateBy": "提bug账号", @@ -480,7 +486,7 @@ "OrderSort": 0, "Icon": null, "Description": "查询 菜单", - "Func": "getPermissions", + "Func": "handleQuery", "Enabled": 1, "CreateId": 18, "CreateBy": "提bug账号", @@ -583,6 +589,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-file-word-o", + "IconNew": "List", "Description": null, "Enabled": 1, "CreateId": 18, @@ -651,7 +658,7 @@ "CreateBy": "后台总管理员", "CreateTime": "\/Date(1546272000000+0800)\/", "ModifyId": null, - "Func": "getBugs", + "Func": "handleQuery", "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", "IsDeleted": 0, @@ -746,6 +753,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-flask", + "IconNew": "WarningFilled", "Description": null, "Enabled": 1, "CreateId": 23, @@ -826,6 +834,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-language", + "IconNew": "HelpFilled", "Description": null, "Enabled": 1, "CreateId": 23, @@ -846,6 +855,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-bug", + "IconNew": "Flag", "Description": null, "Enabled": 1, "CreateId": 23, @@ -886,6 +896,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-sort-amount-asc", + "IconNew": "ChromeFilled", "Description": null, "Enabled": 1, "CreateId": 23, @@ -1307,6 +1318,7 @@ "Mid": 0, "OrderSort": 0, "Icon": "fa-diamond", + "IconNew": "Stamp", "Description": null, "Enabled": 1, "CreateId": 12, @@ -1367,6 +1379,7 @@ "Mid": 0, "OrderSort": 1, "Icon": "el-icon-s-operation", + "IconNew": "Tools", "Description": null, "Enabled": 1, "CreateId": 12, @@ -1471,7 +1484,7 @@ "Enabled": 1, "CreateId": 12, "CreateBy": "后台总管理员", - "Func": "getBlogs", + "Func": "handleQuery", "CreateTime": "\/Date(1546272000000+0800)\/", "ModifyId": null, "ModifyBy": null, @@ -1509,6 +1522,7 @@ "Mid": 0, "OrderSort": 1, "Icon": "fa-history", + "IconNew": "Ticket", "Description": null, "Enabled": 1, "CreateId": 12, @@ -1553,7 +1567,7 @@ "Enabled": 1, "CreateId": 12, "CreateBy": "后台总管理员", - "Func": "getTasks", + "Func": "handleQuery", "CreateTime": "\/Date(1546272000000+0800)\/", "ModifyId": null, "ModifyBy": null, @@ -1760,6 +1774,7 @@ "IskeepAlive": 0, "Func": null, "OrderSort": 3, + "IconNew": "WalletFilled", "Icon": "fa-weixin", "Description": null, "Enabled": 1, @@ -2323,28 +2338,28 @@ "Pid": 94, "Mid": 63 }, - { - "Id": 114, - "Code": " ", - "Name": "推送文字消息", - "IsButton": 1, - "IsHide": 0, - "IskeepAlive": 0, - "Func": null, - "OrderSort": 0, - "Icon": null, - "Description": null, - "Enabled": 1, - "CreateId": 8, - "CreateBy": "test", - "CreateTime": "2020-04-23 16:22:11", - "ModifyId": null, - "ModifyBy": null, - "ModifyTime": "2021-09-29 00:00:00", - "IsDeleted": 0, - "Pid": 95, - "Mid": 0 - }, + //{ + // "Id": 114, + // "Code": " ", + // "Name": "推送文字消息", + // "IsButton": 1, + // "IsHide": 0, + // "IskeepAlive": 0, + // "Func": null, + // "OrderSort": 0, + // "Icon": null, + // "Description": null, + // "Enabled": 1, + // "CreateId": 8, + // "CreateBy": "test", + // "CreateTime": "2020-04-23 16:22:11", + // "ModifyId": null, + // "ModifyBy": null, + // "ModifyTime": "2021-09-29 00:00:00", + // "IsDeleted": 0, + // "Pid": 95, + // "Mid": 0 + //}, { "Code": "-", "Name": "部门权限管理", @@ -2353,6 +2368,7 @@ "IskeepAlive": false, "OrderSort": -10, "Icon": "fa-address-book", + "IconNew": "Briefcase", "Description": "", "Enabled": true, "CreateId": 12, @@ -2379,6 +2395,7 @@ "IskeepAlive": false, "OrderSort": 0, "Icon": "", + "IconNew": "Collection", "Description": "", "Enabled": true, "CreateId": 12, @@ -2571,5 +2588,47 @@ "IsDeleted": 0, "Id": 122, "IsHide": 0 + }, + { + "Code": " ", + "Name": "日志查询", + "IsButton": 1, + "Pid": 76, + "Mid": 73, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 23, + "Func": "handleLog", + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 123, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "任务概况", + "IsButton": 1, + "Pid": 76, + "Mid": 74, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 23, + "Func": "handleOverview", + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 124, + "IsHide": 0 } ] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv index b7923ab4..5626b81f 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv @@ -1647,7 +1647,7 @@ "ModifyId": null, "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", - "Id": 128 + "Id": 228 }, { "IsDeleted": false, @@ -1658,7 +1658,7 @@ "RoleId": 6, "ModuleId": 0, "PermissionId": 114, - "Id": 129 + "Id": 229 }, { "IsDeleted": false, @@ -1669,7 +1669,7 @@ "RoleId": 6, "ModuleId": 66, "PermissionId": 115, - "Id": 130 + "Id": 230 }, { "IsDeleted": false, @@ -1680,7 +1680,7 @@ "RoleId": 6, "ModuleId": 70, "PermissionId": 120, - "Id": 131 + "Id": 231 }, { "IsDeleted": false, @@ -1691,7 +1691,7 @@ "RoleId": 6, "ModuleId": 66, "PermissionId": 116, - "Id": 132 + "Id": 232 }, { "IsDeleted": false, @@ -1702,6 +1702,1397 @@ "RoleId": 4, "ModuleId": 72, "PermissionId": 122, - "Id": 133 + "Id": 233 + }, + { + "Id": 1658115520798527489, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 1 + }, + { + "Id": 1658115520798527490, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 114 + }, + { + "Id": 1658115520798527491, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 66, + "PermissionId": 115 + }, + { + "Id": 1658115520798527492, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 2 + }, + { + "Id": 1658115520798527493, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 22, + "PermissionId": 3 + }, + { + "Id": 1658115520798527494, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 7, + "PermissionId": 4 + }, + { + "Id": 1658115520798527495, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 5 + }, + { + "Id": 1658115520798527496, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 13, + "PermissionId": 6 + }, + { + "Id": 1658115520798527497, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 17, + "PermissionId": 7 + }, + { + "Id": 1658115520798527498, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 34 + }, + { + "Id": 1658115520798527499, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 10 + }, + { + "Id": 1658115520798527500, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 11 + }, + { + "Id": 1658115520798527501, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 12 + }, + { + "Id": 1658115520798527502, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 29 + }, + { + "Id": 1658115520798527503, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 27, + "PermissionId": 43 + }, + { + "Id": 1658115520798527504, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 67 + }, + { + "Id": 1658115520798527505, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 37 + }, + { + "Id": 1658115520798527506, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 38 + }, + { + "Id": 1658115520798527507, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 39 + }, + { + "Id": 1658115520798527508, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 41 + }, + { + "Id": 1658115520798527509, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 40 + }, + { + "Id": 1658115520798527510, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 42 + }, + { + "Id": 1658115520798527511, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 26, + "PermissionId": 28 + }, + { + "Id": 1658115520798527512, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 44 + }, + { + "Id": 1658115520798527513, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 45 + }, + { + "Id": 1658115520798527514, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 46 + }, + { + "Id": 1658115520798527515, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 47 + }, + { + "Id": 1658115520798527516, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 48 + }, + { + "Id": 1658115520798527517, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 49 + }, + { + "Id": 1658115520798527518, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 65 + }, + { + "Id": 1658115520798527519, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 33, + "PermissionId": 66 + }, + { + "Id": 1658115520798527520, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 68 + }, + { + "Id": 1658115520798527521, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 69 + }, + { + "Id": 1658115520798527522, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 8 + }, + { + "Id": 1658115520798527523, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 75 + }, + { + "Id": 1658115520798527524, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 37, + "PermissionId": 76 + }, + { + "Id": 1658115520798527525, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 87 + }, + { + "Id": 1658115520798527526, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 88 + }, + { + "Id": 1658115520798527527, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 89 + }, + { + "Id": 1658115520798527528, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 90 + }, + { + "Id": 1658115520798527529, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 91 + }, + { + "Id": 1658115520798527530, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 95 + }, + { + "Id": 1658115520798527531, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 93 + }, + { + "Id": 1658115520798527532, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 94 + }, + { + "Id": 1658115520798527533, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 92 + }, + { + "Id": 1658115520798527534, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 7, + "PermissionId": 9 + }, + { + "Id": 1658115520798527535, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 10, + "PermissionId": 13 + }, + { + "Id": 1658115520798527536, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 12, + "PermissionId": 14 + }, + { + "Id": 1658115520798527537, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 11, + "PermissionId": 15 + }, + { + "Id": 1658115520798527538, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 22, + "PermissionId": 16 + }, + { + "Id": 1658115520798527539, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 25, + "PermissionId": 17 + }, + { + "Id": 1658115520798527540, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 24, + "PermissionId": 18 + }, + { + "Id": 1658115520798527541, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 23, + "PermissionId": 19 + }, + { + "Id": 1658115520798527542, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 13, + "PermissionId": 20 + }, + { + "Id": 1658115520798527543, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 16, + "PermissionId": 21 + }, + { + "Id": 1658115520798527544, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 15, + "PermissionId": 22 + }, + { + "Id": 1658115520798527545, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 14, + "PermissionId": 23 + }, + { + "Id": 1658115520798527546, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 17, + "PermissionId": 24 + }, + { + "Id": 1658115520798527547, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 20, + "PermissionId": 25 + }, + { + "Id": 1658115520798527548, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 19, + "PermissionId": 26 + }, + { + "Id": 1658115520798527549, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 18, + "PermissionId": 27 + }, + { + "Id": 1658115520798527550, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 5, + "PermissionId": 30 + }, + { + "Id": 1658115520798527551, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 6, + "PermissionId": 31 + }, + { + "Id": 1658115520798527552, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 26, + "PermissionId": 32 + }, + { + "Id": 1658115520798527553, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 21, + "PermissionId": 33 + }, + { + "Id": 1658115520798527554, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 28, + "PermissionId": 35 + }, + { + "Id": 1658115520798527555, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 29, + "PermissionId": 36 + }, + { + "Id": 1658115520798527556, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 66, + "PermissionId": 116 + }, + { + "Id": 1658115520798527557, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 69, + "PermissionId": 117 + }, + { + "Id": 1658115520798527558, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 68, + "PermissionId": 118 + }, + { + "Id": 1658115520798527559, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 67, + "PermissionId": 119 + }, + { + "Id": 1658115520798527560, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 70, + "PermissionId": 120 + }, + { + "Id": 1658115520798527561, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 32, + "PermissionId": 64 + }, + { + "Id": 1658115520798527562, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 36, + "PermissionId": 72 + }, + { + "Id": 1658115520798527563, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 27, + "PermissionId": 73 + }, + { + "Id": 1658115520798527564, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 27, + "PermissionId": 74 + }, + { + "Id": 1658115520798527565, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2019-01-01 00:00:00", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 71, + "PermissionId": 121 + }, + { + "Id": 1658115520798527566, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-04-11 16:08:49", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 72, + "PermissionId": 122 + }, + { + "Id": 1658115520798527567, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 37, + "PermissionId": 77 + }, + { + "Id": 1658115520798527568, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 38, + "PermissionId": 78 + }, + { + "Id": 1658115520798527569, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 39, + "PermissionId": 79 + }, + { + "Id": 1658115520798527570, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 40, + "PermissionId": 80 + }, + { + "Id": 1658115520798527571, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 41, + "PermissionId": 81 + }, + { + "Id": 1658115520798527572, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 42, + "PermissionId": 82 + }, + { + "Id": 1658115520798527573, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 43, + "PermissionId": 83 + }, + { + "Id": 1658115520798527574, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 44, + "PermissionId": 84 + }, + { + "Id": 1658115520798527575, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 45, + "PermissionId": 85 + }, + { + "Id": 1658115520798527576, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 46, + "PermissionId": 86 + }, + { + "Id": 1658115520798527577, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 73, + "PermissionId": 123 + }, + { + "Id": 1658115520798527578, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 74, + "PermissionId": 124 + }, + { + "Id": 1658115520798527579, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 55, + "PermissionId": 108 + }, + { + "Id": 1658115520798527580, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 61, + "PermissionId": 109 + }, + { + "Id": 1658115520798527581, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 50, + "PermissionId": 103 + }, + { + "Id": 1658115520798527582, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 49, + "PermissionId": 104 + }, + { + "Id": 1658115520798527583, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 48, + "PermissionId": 105 + }, + { + "Id": 1658115520798527584, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 51, + "PermissionId": 106 + }, + { + "Id": 1658115520798527585, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 52, + "PermissionId": 107 + }, + { + "Id": 1658115520798527586, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 54, + "PermissionId": 96 + }, + { + "Id": 1658115520798527587, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 64, + "PermissionId": 98 + }, + { + "Id": 1658115520798527588, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 59, + "PermissionId": 99 + }, + { + "Id": 1658115520798527589, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 58, + "PermissionId": 100 + }, + { + "Id": 1658115520798527590, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 53, + "PermissionId": 101 + }, + { + "Id": 1658115520798527591, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 60, + "PermissionId": 102 + }, + { + "Id": 1658115520798527592, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 57, + "PermissionId": 110 + }, + { + "Id": 1658115520798527593, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 56, + "PermissionId": 112 + }, + { + "Id": 1658115520798527594, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 62, + "PermissionId": 111 + }, + { + "Id": 1658115520798527595, + "IsDeleted": 0, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2023-05-15 14:20:12", + "ModifyId": 12, + "ModifyBy": "blogadmin", + "ModifyTime": "2023-05-15 14:20:12", + "RoleId": 4, + "ModuleId": 63, + "PermissionId": 113 } ] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv index c9f80890..5aaef106 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv @@ -2,16 +2,16 @@ { "Name": "博客管理", "JobGroup": "博客测试组", - "TriggerType": 1, - "Cron": "0 */10 * * * ?", + "TriggerType": 1, + "Cron": "0 */1 * * * ?", "AssemblyName": "Blog.Core.Tasks", "ClassName": "Job_Blogs_Quartz", "Remark": "", "RunTimes": 0, "BeginTime": "\/Date(1546272000000+0800)\/", - "EndTime": "\/Date(1640966400000+0800)\/", + "EndTime": "\/Date(8888888800000+0800)\/", "IntervalSecond": 0, - "CycleRunTimes": 0, + "CycleRunTimes": 0, "IsStart": true, "JobParams": 1, "IsDeleted": false, diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/TopicDetail.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TopicDetail.tsv index 2cee8073..ff5f8d68 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/TopicDetail.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TopicDetail.tsv @@ -3,7 +3,7 @@ "TopicId": 1, "tdLogo": null, "tdName": "第一章 罗马的诞生 第一节 传说的年代", - "tdContent": "

第一节 传说的年代<\/p>

每个民族都有自己的神话传说。大概希望知道本民族的来源是个很自然的愿望吧。但这是一个难题,因为这几乎不可能用科学的方法来解释清楚。不过所有的民族都没有这样的奢求。他们只要有一个具有一定的条理性,而又能振奋其民族精神的浪漫故事就行,别抬杠,象柏杨那样将中国的三皇五帝都来个科学分析,来评论他们的执政之优劣是大可不必的。<\/p>

对於罗马人,他们有一个和特洛伊城的陷落相关的传说。<\/p>

位於小亚细亚西岸的繁荣的城市特洛伊,在遭受了阿加美农统帅的希腊联军的十年围攻之後,仍未陷落。希腊联军於是留下一个巨大的木马後假装撤兵。特洛伊人以为那是希腊联军留给自己的礼物,就将它拉入城内。<\/p>

当庆祝胜利的狂欢结束,特洛伊人满怀对明日的和平生活的希望熟睡後,藏在木马内的希腊士兵一个又一个地爬了出来。就在这天夜里,特洛伊城便在火光和叫喊中陷落了。全城遭到大屠杀 ,幸免於死的人全都沦为奴隶。混乱之中只有特洛伊国王的驸马阿伊尼阿斯带着老父,儿子等数人在女神维娜斯的帮助下成功地逃了出来。这驸马爷乃是女神维娜斯与凡人男子之间的儿子,女神维娜斯不忍心看着自己的儿子被希腊士兵屠杀 。<\/p>

这阿驸马一行人分乘几条船,离开了火光冲天的特洛伊城。在女神维娜斯的指引下,浪迹地中海,最後在意大利西岸登陆。当地的国王看上了阿伊尼阿斯并把自己的女儿嫁给了他。他又是驸马了,与他的新妻过起了幸福的生活。难民们也安定了下来。<\/p>

阿伊尼阿斯死後,跟随他逃难来的儿子继承了王位。新王在位三十年後,离开了这块地方,到台伯河(Tiber)下游建了一个新城亚尔巴龙迦城。这便是罗马城的前身了。<\/p>

罗马人自古相信罗马城是公元前731年4月21日由罗莫路和勒莫(Romulus and Remus)建设的。而这两个孪生兄弟是从特洛伊逃出的阿伊尼阿斯的子孙。後来,罗马人接触了希腊文化後才知道特洛伊的陷落是在公元前十三世纪,老早的事了。罗马人好象并没有对这段空白有任何烦恼,随手编出一串传说,把那空白给填补了。反正传说这事荒唐一点的更受欢迎。经过了一堆搞不清谁是谁的王的统治,出现了一个什麽王的公主。<\/p>

公主的叔父在篡夺了王位後,为了防止公主结婚生子威胁自己的王位,便任命未婚的公主为巫女。这是主管祭神的职位,象修女一样不得结婚。<\/p>

不巧一日这美丽的公主在祭事的空余,来到小河边午睡。也是合当有事,被过往的战神玛尔斯(Mars)一见钟情。这玛尔斯本是靠挑起战争混饭吃的,但也常勾引 良家妇女。这天战神也没错过机会,立刻由天而降,与公主一试云雨。据说战神的技术特神,公主还没来得及醒便完事升天去了。後来公主生了一双胞胎,起名罗莫路和勒莫。<\/p>

叔父闻知此事大怒,将公主投入大牢,又把那双胞胎放在篮子里抛入台伯河,指望那篮子漂入大海将那双胞胎淹死。类似的故事在旧约圣经里也有,那是关於摩西的事,好象这类传说在当地十分流行。<\/p>

再说那兄弟俩的篮子被河口附近茂密的灌木丛钩住而停了下来,俩人哭声引来的一只过路的母狼。意大利的狼都带点慈悲心,不但没吃了俩人当点心,还用自己的奶去喂他们,这才救了俩小命。<\/p>

不过,总是由狼养活也没法交 待,於是又一日一放羊的在这地盘上溜哒,发现了兄弟俩,将他们抱了回去扶养成人 。据说现在这一带仍有许多放羊的。<\/p>

兄弟俩长大後成了这一带放羊人的头,在与别的放羊人的圈子的打斗中不断地扩展自己的势力范围。圈子大了,情报也就多了,终于有一天,罗莫路和勒莫知道了自己身事。<\/p>

兄弟俩就带着手下的放羊人呼啸着去打破了亚尔巴龙迦城,杀了那国王,将王位又交 还给了自己祖父。他们的母亲似乎已经死在了大牢里。但兄弟俩也没在亚尔巴龙迦城多住,他们认为亚尔巴龙迦城位於山地,虽然易守难攻,却不利发展。加上兄弟俩是在台伯河的下游长大的,所以便回到原地,建了个新城。除了手下的放羊人又加上了附近的放羊人和农民。<\/p>

消灭了共同的敌人後,兄弟俩的关系开始恶化。有人说是为了新城的命名,有人说是为了新城的城址,也有人说是为了争夺王位。兄弟俩於是分割统治,各占一小山包。但纷争又开始了,勒莫跳过了罗莫路为表示势力范围而挖的沟。对於这种侵犯他人权力的行为,罗莫路大义灭亲地在自己兄弟的後脑上重重地来了一锄头,勒莫便被灭了。<\/p>

<\/p>

於是这城便以罗莫路的名字命名为罗马,这就是公元前731年4月21日的事了,到现在这天仍是意大利的节日,罗马人会欢天喜地的庆祝罗莫路杀了自己的…不,是庆祝罗马建城。王位当然也得由罗莫路来坐,一切问题都没了。这时四年一度的奥林匹克运动会在希腊已经开了六回,罗马也从传说的时代走出,近入了历史时代。<\/p>


<\/p>", + "tdContent": "

第一节 传说的年代<\/p>

每个民族都有自己的神话传说。大概希望知道本民族的来源是个很自然的愿望吧。但这是一个难题,因为这几乎不可能用科学的方法来解释清楚。", "tdDetail": "标题", "tdSectendDetail": null, "tdIsDelete": 0, diff --git a/Blog.Core.Api/wwwroot/swg-login.html b/Blog.Core.Api/wwwroot/swg-login.html index 6e7c6b09..d361cdef 100644 --- a/Blog.Core.Api/wwwroot/swg-login.html +++ b/Blog.Core.Api/wwwroot/swg-login.html @@ -1,128 +1,141 @@  - + 登录 - 接口文档 - - + + -

-
-
-
+
+
+
+
- -
欢迎使用!
-
用户名:admin,密码:admin
-
-
- - - - - - - - - -
- - - - - -
+ +
欢迎使用!
+
使用真实用户账号登录,测试账号: blogadmin/blogadmin
+
+
+ + + + + + + + + +
+ + + + +
+
- + } + }); + } + \ No newline at end of file diff --git a/Blog.Core.Build.bat b/Blog.Core.Build.bat index 632e841c..7d614aa4 100644 --- a/Blog.Core.Build.bat +++ b/Blog.Core.Build.bat @@ -1,4 +1,3 @@ -git pull @echo off diff --git a/Blog.Core.Common/App.cs b/Blog.Core.Common/App.cs new file mode 100644 index 00000000..95481ecf --- /dev/null +++ b/Blog.Core.Common/App.cs @@ -0,0 +1,192 @@ +using Blog.Core.Common.Core; +using Blog.Core.Common.Extensions; +using Blog.Core.Common.HttpContextUser; +using Blog.Core.Common.Option.Core; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Blog.Core.Common; + +public class App +{ + static App() + { + EffectiveTypes = Assemblies.SelectMany(GetTypes); + } + + private static bool _isRun; + + /// 是否正在运行 + public static bool IsBuild { get; set; } + + public static bool IsRun + { + get => _isRun; + set => _isRun = IsBuild = value; + } + + /// 应用有效程序集 + public static readonly IEnumerable Assemblies = RuntimeExtension.GetAllAssemblies(); + + /// 有效程序集类型 + public static readonly IEnumerable EffectiveTypes; + + /// 优先使用App.GetService()手动获取服务 + public static IServiceProvider RootServices => IsRun || IsBuild ? InternalApp.RootServices : null; + + /// 获取Web主机环境,如,是否是开发环境,生产环境等 + public static IWebHostEnvironment WebHostEnvironment => InternalApp.WebHostEnvironment; + + /// 获取泛型主机环境,如,是否是开发环境,生产环境等 + public static IHostEnvironment HostEnvironment => InternalApp.HostEnvironment; + + /// 全局配置选项 + public static IConfiguration Configuration => InternalApp.Configuration; + + /// + /// 获取请求上下文 + /// + public static HttpContext HttpContext => RootServices?.GetService()?.HttpContext; + + public static IUser User => GetService(); + + #region Service + + /// 解析服务提供器 + /// + /// + /// + /// + public static IServiceProvider GetServiceProvider(Type serviceType, bool mustBuild = false, bool throwException = true) + { + //获取请求生存周期的服务 + if (HttpContext?.RequestServices != null) + return HttpContext.RequestServices; + + if (App.RootServices != null) + { + return RootServices; + } + + //单例 + if (InternalApp.InternalServices + .Where(u => u.ServiceType == (serviceType.IsGenericType ? serviceType.GetGenericTypeDefinition() : serviceType)) + .Any(u => u.Lifetime == ServiceLifetime.Singleton)) + { + return RootServices ?? InternalApp.InternalServices.BuildServiceProvider(); + } + + if (mustBuild) + { + if (throwException) + { + throw new ApplicationException("当前不可用,必须要等到 WebApplication Build后"); + } + + return default; + } + + ServiceProvider serviceProvider = InternalApp.InternalServices.BuildServiceProvider(); + return serviceProvider; + } + + public static TService GetService(bool mustBuild = true) where TService : class => + App.GetService(typeof(TService), null, mustBuild) as TService; + + /// 获取请求生存周期的服务 + /// + /// + /// + /// + public static TService GetService(IServiceProvider serviceProvider, bool mustBuild = true) + where TService : class => (serviceProvider ?? App.GetServiceProvider(typeof(TService), mustBuild, false))?.GetService(); + + /// 获取请求生存周期的服务 + /// + /// + /// + /// + public static object GetService(Type type, IServiceProvider serviceProvider = null, bool mustBuild = true) => + (serviceProvider ?? App.GetServiceProvider(type, mustBuild, false))?.GetService(type); + + #endregion + + #region private + + /// 加载程序集中的所有类型 + /// + /// + private static IEnumerable GetTypes(Assembly ass) + { + Type[] source = Array.Empty(); + try + { + source = ass.GetTypes(); + } + catch + { + $@"Error load `{ass.FullName}` assembly.".WriteErrorLine(); + } + + return source.Where(u => u.IsPublic); + } + + #endregion + + #region Options + + /// 获取配置 + /// 强类型选项类 + /// TOptions + public static TOptions GetConfig() + where TOptions : class, IConfigurableOptions + { + TOptions instance = App.Configuration + .GetSection(ConfigurableOptions.GetConfigurationPath(typeof(TOptions))) + .Get(); + return instance; + } + + /// 获取选项 + /// 强类型选项类 + /// + /// TOptions + public static TOptions GetOptions(IServiceProvider serviceProvider = null) where TOptions : class, new() + { + IOptions service = App.GetService>(serviceProvider ?? App.RootServices, false); + return service?.Value; + } + + /// 获取选项 + /// 强类型选项类 + /// + /// TOptions + public static TOptions GetOptionsMonitor(IServiceProvider serviceProvider = null) + where TOptions : class, new() + { + IOptionsMonitor service = + App.GetService>(serviceProvider ?? App.RootServices, false); + return service?.CurrentValue; + } + + /// 获取选项 + /// 强类型选项类 + /// + /// TOptions + public static TOptions GetOptionsSnapshot(IServiceProvider serviceProvider = null) + where TOptions : class, new() + { + IOptionsSnapshot service = App.GetService>(serviceProvider, false); + return service?.Value; + } + + #endregion +} \ No newline at end of file diff --git a/Blog.Core.Common/Blog.Core.Common.csproj b/Blog.Core.Common/Blog.Core.Common.csproj index 43f65ab9..6e1624a3 100644 --- a/Blog.Core.Common/Blog.Core.Common.csproj +++ b/Blog.Core.Common/Blog.Core.Common.csproj @@ -1,8 +1,5 @@  - - net6.0 - @@ -16,24 +13,34 @@ - - + + + + - - + + - - - - - - - - + + + + + + + + + + + + + + - - + + + + @@ -43,4 +50,8 @@ + + + + diff --git a/Blog.Core.Common/Caches/Caching.cs b/Blog.Core.Common/Caches/Caching.cs new file mode 100644 index 00000000..67f8ac18 --- /dev/null +++ b/Blog.Core.Common/Caches/Caching.cs @@ -0,0 +1,260 @@ +using System.Collections.Concurrent; +using System.Text; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Extensions; +using Blog.Core.Common.Option; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace Blog.Core.Common.Caches; + +public class Caching( + ILogger logger, + IDistributedCache cache, + IOptions redisOptions) + : ICaching +{ + private static readonly ConcurrentDictionary _loggedWarnings = new(); + private readonly RedisOptions _redisOptions = redisOptions.Value; + private const string WarningMessage = "注入的缓存服务不是MemoryCacheManager,请检查注册配置,无法获取所有KEY"; + public IDistributedCache Cache => cache; + + public void DelByPattern(string key) + { + var allkeys = GetAllCacheKeys(key); + if (allkeys == null) return; + + foreach (var u in allkeys) + { + cache.Remove(u); + } + } + + /// + /// 删除某特征关键字缓存 + /// + /// + /// + public async Task DelByPatternAsync(string key) + { + var allkeys = GetAllCacheKeys(key); + if (allkeys == null) return; + + foreach (var s in allkeys) await cache.RemoveAsync(s); + } + + public bool Exists(string cacheKey) + { + var res = cache.Get(cacheKey); + return res != null; + } + + /// + /// 检查给定 key 是否存在 + /// + /// 键 + /// + public async Task ExistsAsync(string cacheKey) + { + var res = await cache.GetAsync(cacheKey); + return res != null; + } + + public List GetAllCacheKeys(string pattern = default) + { + if (_redisOptions.Enable) + { + var redis = App.GetService(false); + var endpoints = redis.GetEndPoints(); + var server = redis.GetServer(endpoints[0]); + + // 使用 SCAN 命令来增量获取符合条件的键,避免 KEYS 的性能问题 + return server.Keys(pattern: pattern, pageSize: 100).Select(key => key.ToString()).ToList(); + } + + var memoryCache = App.GetService(); + if (memoryCache is not MemoryCacheManager memoryCacheManager) + { + if (_loggedWarnings.TryAdd(WarningMessage, true)) + { + logger.LogWarning(WarningMessage); + } + + return []; + } + + return memoryCacheManager.GetAllKeys().WhereIf(!pattern.IsNullOrEmpty(), s => s.StartsWith(pattern!)).ToList(); + } + + public T Get(string cacheKey) + { + var res = cache.Get(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + } + + /// + /// 获取缓存 + /// + /// + /// + /// + public async Task GetAsync(string cacheKey) + { + var res = await cache.GetAsync(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + } + + public object Get(Type type, string cacheKey) + { + var res = cache.Get(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res), type); + } + + public async Task GetAsync(Type type, string cacheKey) + { + var res = await cache.GetAsync(cacheKey); + return res == null ? default : JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res), type); + } + + public string GetString(string cacheKey) + { + return cache.GetString(cacheKey); + } + + /// + /// 获取缓存 + /// + /// + /// + public async Task GetStringAsync(string cacheKey) + { + return await cache.GetStringAsync(cacheKey); + } + + public void Remove(string key) + { + cache.Remove(key); + } + + /// + /// 删除缓存 + /// + /// + /// + public async Task RemoveAsync(string key) + { + await cache.RemoveAsync(key); + } + + public void RemoveAll() + { + if (_redisOptions.Enable) + { + var redis = App.GetService(false); + var endpoints = redis.GetEndPoints(); + var server = redis.GetServer(endpoints[0]); + server.FlushDatabase(); + } + else + { + var manage = App.GetService(false); + manage.Reset(); + } + } + + public void Set(string cacheKey, T value, TimeSpan? expire = null) + { + cache.Set(cacheKey, GetBytes(value), + expire == null + ? new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) } + : new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + public void SetPermanent(string cacheKey, T value) + { + cache.Set(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value))); + } + + /// + /// 增加对象缓存 + /// + /// + /// + /// + public async Task SetAsync(string cacheKey, T value) + { + await cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), + new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) }); + } + + /// + /// 增加对象缓存,并设置过期时间 + /// + /// + /// + /// + /// + public async Task SetAsync(string cacheKey, T value, TimeSpan expire) + { + await cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), + new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + public async Task SetPermanentAsync(string cacheKey, T value) + { + await cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value))); + } + + public void SetString(string cacheKey, string value, TimeSpan? expire = null) + { + cache.SetString(cacheKey, value, + expire == null + ? new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) } + : new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + /// + /// 增加字符串缓存 + /// + /// + /// + /// + public async Task SetStringAsync(string cacheKey, string value) + { + await cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6) }); + } + + /// + /// 增加字符串缓存,并设置过期时间 + /// + /// + /// + /// + /// + public async Task SetStringAsync(string cacheKey, string value, TimeSpan expire) + { + await cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = expire }); + } + + /// + /// 根据父键清空 + /// + /// + /// + public async Task DelByParentKeyAsync(string key) + { + var allkeys = GetAllCacheKeys(key); + if (allkeys == null) return; + + foreach (var s in allkeys) await cache.RemoveAsync(s); + } + + private byte[] GetBytes(T source) + { + return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(source)); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/Distributed/CommonMemoryDistributedCache.cs b/Blog.Core.Common/Caches/Distributed/CommonMemoryDistributedCache.cs new file mode 100644 index 00000000..65ff4503 --- /dev/null +++ b/Blog.Core.Common/Caches/Distributed/CommonMemoryDistributedCache.cs @@ -0,0 +1,103 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Blog.Core.Common.Caches.Distributed; + +/// +/// A common memory distributed cache.
+/// 因为微软的MemoryDistributedCache内部自己实例化MemoryCache,而不是使用IMemoryCache接口 +///
+public class CommonMemoryDistributedCache : IDistributedCache +{ + private readonly IMemoryCache _memCache; + + public CommonMemoryDistributedCache(IOptions optionsAccessor, IMemoryCache memoryCache) + : this(optionsAccessor, NullLoggerFactory.Instance, memoryCache) + { + } + + public CommonMemoryDistributedCache(IOptions optionsAccessor, ILoggerFactory loggerFactory, + IMemoryCache memoryCache) + { + ArgumentNullException.ThrowIfNull(optionsAccessor); + ArgumentNullException.ThrowIfNull(loggerFactory); + + _memCache = memoryCache ?? new MemoryCache(optionsAccessor.Value, loggerFactory); + } + + public byte[] Get(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return (byte[])_memCache.Get(key); + } + + public Task GetAsync(string key, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + return Task.FromResult(Get(key)); + } + + public void Set(string key, byte[] value, DistributedCacheEntryOptions options) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(options); + + var memoryCacheEntryOptions = new MemoryCacheEntryOptions + { + AbsoluteExpiration = options.AbsoluteExpiration, + AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow, + SlidingExpiration = options.SlidingExpiration, + Size = value.Length + }; + + _memCache.Set(key, value, memoryCacheEntryOptions); + } + + public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(options); + + Set(key, value, options); + return Task.CompletedTask; + } + + public void Refresh(string key) + { + ArgumentNullException.ThrowIfNull(key); + + _memCache.TryGetValue(key, out object value); + } + + public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + + Refresh(key); + return Task.CompletedTask; + } + + public void Remove(string key) + { + ArgumentNullException.ThrowIfNull(key); + + _memCache.Remove(key); + } + + public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken)) + { + ArgumentNullException.ThrowIfNull(key); + + Remove(key); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/Extensions/MemoryCacheExtensions.cs b/Blog.Core.Common/Caches/Extensions/MemoryCacheExtensions.cs new file mode 100644 index 00000000..380fbc84 --- /dev/null +++ b/Blog.Core.Common/Caches/Extensions/MemoryCacheExtensions.cs @@ -0,0 +1,80 @@ +using System.Collections; +using System.Reflection; +using System.Reflection.Emit; +using Microsoft.Extensions.Caching.Memory; + +namespace Blog.Core.Common.Caches.Extensions; + +public static class MemoryCacheExtensions +{ + #region Microsoft.Extensions.Caching.Memory_6_OR_OLDER + + /// + /// 6.x
+ /// 6.0.2 调整了字段名,使用 StringKeyEntriesCollection + ///
+ private static readonly Lazy> GetEntries6 = new(() => + (Func)Delegate.CreateDelegate(typeof(Func), + typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance)?.GetGetMethod(true) + ?? typeof(MemoryCache).GetProperty("StringKeyEntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance)?.GetGetMethod(true) + ?? throw new InvalidOperationException("Cannot find property 'EntriesCollection' or 'StringKeyEntriesCollection' on MemoryCache."), + true)); + + #endregion + + #region Microsoft.Extensions.Caching.Memory_7_OR_NEWER + + private static readonly Lazy> GetCoherentState7 = new(() => + CreateGetter(typeof(MemoryCache) + .GetField("_coherentState", BindingFlags.NonPublic | BindingFlags.Instance))); + + private static readonly Lazy> GetEntries7 = new(() => + CreateGetter(typeof(MemoryCache) + .GetNestedType("CoherentState", BindingFlags.NonPublic)? + .GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance))); + + #endregion + + #region Microsoft.Extensions.Caching.Memory_8_OR_NEWER + + private static readonly Lazy> GetCoherentState8 = new(() => + CreateGetter(typeof(MemoryCache) + .GetField("_coherentState", BindingFlags.NonPublic | BindingFlags.Instance))); + + private static readonly Lazy> GetEntries8 = new(() => + CreateGetter(typeof(MemoryCache) + .GetNestedType("CoherentState", BindingFlags.NonPublic)? + .GetField("_stringEntries", BindingFlags.NonPublic | BindingFlags.Instance))); + + #endregion + + private static Func CreateGetter(FieldInfo field) + { + if (field == null) + { + throw new ArgumentNullException(nameof(field), "Field cannot be null."); + } + + var methodName = $"{field.ReflectedType!.FullName}.get_{field.Name}"; + var method = new DynamicMethod(methodName, typeof(TReturn), new[] { typeof(TParam) }, typeof(TParam), true); + var ilGen = method.GetILGenerator(); + ilGen.Emit(OpCodes.Ldarg_0); + ilGen.Emit(OpCodes.Ldfld, field); + ilGen.Emit(OpCodes.Ret); + return (Func)method.CreateDelegate(typeof(Func)); + } + + private static readonly Func GetEntries = + Assembly.GetAssembly(typeof(MemoryCache))?.GetName().Version?.Major switch + { + < 7 => cache => (IDictionary)GetEntries6.Value(cache), + 7 => cache => GetEntries7.Value(GetCoherentState7.Value(cache)), + _ => cache => GetEntries8.Value(GetCoherentState8.Value(cache)), + }; + + public static ICollection GetKeys(this IMemoryCache memoryCache) => + GetEntries((MemoryCache)memoryCache).Keys; + + public static IEnumerable GetKeys(this IMemoryCache memoryCache) => + memoryCache.GetKeys().OfType(); +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/Interface/ICaching.cs b/Blog.Core.Common/Caches/Interface/ICaching.cs new file mode 100644 index 00000000..836e11c0 --- /dev/null +++ b/Blog.Core.Common/Caches/Interface/ICaching.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Caching.Distributed; + +namespace Blog.Core.Common.Caches.Interface; + +/// +/// 缓存抽象接口,基于IDistributedCache封装 +/// +public interface ICaching +{ + public IDistributedCache Cache { get; } + + void DelByPattern(string key); + Task DelByPatternAsync(string key); + + bool Exists(string cacheKey); + Task ExistsAsync(string cacheKey); + + List GetAllCacheKeys(string pattern = default); + + T Get(string cacheKey); + Task GetAsync(string cacheKey); + + object Get(Type type, string cacheKey); + Task GetAsync(Type type, string cacheKey); + + string GetString(string cacheKey); + Task GetStringAsync(string cacheKey); + + void Remove(string key); + Task RemoveAsync(string key); + + void RemoveAll(); + + void Set(string cacheKey, T value, TimeSpan? expire = null); + Task SetAsync(string cacheKey, T value); + Task SetAsync(string cacheKey, T value, TimeSpan expire); + + void SetPermanent(string cacheKey, T value); + Task SetPermanentAsync(string cacheKey, T value); + + void SetString(string cacheKey, string value, TimeSpan? expire = null); + Task SetStringAsync(string cacheKey, string value); + Task SetStringAsync(string cacheKey, string value, TimeSpan expire); + + Task DelByParentKeyAsync(string key); +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/MemoryCacheManager.cs b/Blog.Core.Common/Caches/MemoryCacheManager.cs new file mode 100644 index 00000000..5f0a8861 --- /dev/null +++ b/Blog.Core.Common/Caches/MemoryCacheManager.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Reflection; +using Blog.Core.Common.Caches.Extensions; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace Blog.Core.Common.Caches; + +public class MemoryCacheManager : IMemoryCache +{ + private readonly IOptions _optionsAccessor; + + private MemoryCache _inner; + + public MemoryCacheManager(IOptions optionsAccessor) + { + _optionsAccessor = optionsAccessor; + _inner = new MemoryCache(_optionsAccessor); + } + + public void Dispose() => _inner.Dispose(); + + public ICacheEntry CreateEntry(object key) => _inner.CreateEntry(key); + + public void Remove(object key) => _inner.Remove(key); + + public bool TryGetValue(object key, out object value) => _inner.TryGetValue(key, out value); + + public void Reset() + { + lock (_optionsAccessor) + { + var old = _inner; + _inner = new MemoryCache(_optionsAccessor); + old.Dispose(); + } + } + + public IEnumerable GetAllKeys() + { + return _inner.GetKeys(); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Caches/SqlSugarCacheService.cs b/Blog.Core.Common/Caches/SqlSugarCacheService.cs new file mode 100644 index 00000000..d94eec54 --- /dev/null +++ b/Blog.Core.Common/Caches/SqlSugarCacheService.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Option; +using SqlSugar; + +namespace Blog.Core.Common.Caches; + +/// +/// 实现SqlSugar的ICacheService接口
+///
+/// 建议自己实现业务缓存,注入ICaching直接用即可
+///
+/// 不建议使用SqlSugar缓存,性能有很大问题,会导致redis堆积
+/// 核心问题在于SqlSugar,每次query(注意:不管你有没有启用,所有表的查询)都会查缓存, insert\update\delete,又会频繁GetAllKey,导致性能特别低
+///
+public class SqlSugarCacheService : ICacheService +{ + private readonly Lazy _caching = new(() => App.GetService(false)); + private ICaching Caching => _caching.Value; + private readonly AppSettingsOption _options = App.GetOptions(); + + public void Add(string key, V value) + { + if (!_options.CacheDbEnabled) + { + return; + } + + Caching.Set(key, value); + } + + public void Add(string key, V value, int cacheDurationInSeconds) + { + if (!_options.CacheDbEnabled) + { + return; + } + + Caching.Set(key, value, TimeSpan.FromSeconds(cacheDurationInSeconds)); + } + + public bool ContainsKey(string key) + { + if (!_options.CacheDbEnabled) + { + return default; + } + + return Caching.Exists(key); + } + + public V Get(string key) + { + if (!_options.CacheDbEnabled) + { + return default; + } + + return Caching.Get(key); + } + + public IEnumerable GetAllKey() + { + if (!_options.CacheDbEnabled) + { + return default; + } + + return Caching.GetAllCacheKeys(); + } + + public V GetOrCreate(string cacheKey, Func create, int cacheDurationInSeconds = int.MaxValue) + { + if (!_options.CacheDbEnabled) + { + return create(); + } + + if (!ContainsKey(cacheKey)) + { + var value = create(); + Caching.Set(cacheKey, value, TimeSpan.FromSeconds(cacheDurationInSeconds)); + return value; + } + + return Caching.Get(cacheKey); + } + + public void Remove(string key) + { + if (!_options.CacheDbEnabled) + { + return; + } + + Caching.Remove(key); + } + + public bool RemoveAll() + { + if (!_options.CacheDbEnabled) + { + return true; + } + + Caching.RemoveAll(); + return true; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Const/CacheConst.cs b/Blog.Core.Common/Const/CacheConst.cs new file mode 100644 index 00000000..73e0d048 --- /dev/null +++ b/Blog.Core.Common/Const/CacheConst.cs @@ -0,0 +1,82 @@ +namespace Blog.Core.Common.Const; + +/// +/// 缓存相关常量 +/// +public class CacheConst +{ + /// + /// 用户缓存 + /// + public const string KeyUser = "user:"; + + /// + /// 用户部门缓存 + /// + public const string KeyUserDepart = "userDepart:"; + + /// + /// 菜单缓存 + /// + public const string KeyMenu = "menu:"; + + /// + /// 菜单 + /// + public const string KeyPermissions = "permissions"; + + /// + /// 权限缓存 + /// + public const string KeyPermission = "permission:"; + + /// + /// 接口路由 + /// + public const string KeyModules = "modules"; + + /// + /// 系统配置 + /// + public const string KeySystemConfig = "sysConfig"; + + /// + /// 查询过滤器缓存 + /// + public const string KeyQueryFilter = "queryFilter:"; + + /// + /// 机构Id集合缓存 + /// + public const string KeyOrgIdList = "org:"; + + /// + /// 最大角色数据范围缓存 + /// + public const string KeyMaxDataScopeType = "maxDataScopeType:"; + + /// + /// 验证码缓存 + /// + public const string KeyVerCode = "verCode:"; + + /// + /// 定时任务缓存 + /// + public const string KeyTimer = "timer:"; + + /// + /// 在线用户缓存 + /// + public const string KeyOnlineUser = "onlineuser:"; + + /// + /// 常量下拉框 + /// + public const string KeyConstSelector = "selector:"; + + /// + /// swagger登录缓存 + /// + public const string SwaggerLogin = "swaggerLogin:"; +} \ No newline at end of file diff --git a/Blog.Core.Common/Const/SqlSugarConst.cs b/Blog.Core.Common/Const/SqlSugarConst.cs new file mode 100644 index 00000000..f5efd7e2 --- /dev/null +++ b/Blog.Core.Common/Const/SqlSugarConst.cs @@ -0,0 +1,9 @@ +namespace Blog.Core.Common.Const; + +public class SqlSugarConst +{ + /// + /// 默认Log数据库标识 + /// + public const string LogConfigId = "Log"; +} \ No newline at end of file diff --git a/Blog.Core.Common/Core/InternalApp.cs b/Blog.Core.Common/Core/InternalApp.cs new file mode 100644 index 00000000..5cf3a97b --- /dev/null +++ b/Blog.Core.Common/Core/InternalApp.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; + +namespace Blog.Core.Common.Core; + +/// +/// 内部只用于初始化使用 +/// +public static class InternalApp +{ + internal static IServiceCollection InternalServices; + + /// 根服务 + internal static IServiceProvider RootServices; + + /// 获取Web主机环境 + internal static IWebHostEnvironment WebHostEnvironment; + + /// 获取泛型主机环境 + internal static IHostEnvironment HostEnvironment; + + /// 配置对象 + internal static IConfiguration Configuration; + + public static void ConfigureApplication(this WebApplicationBuilder wab) + { + HostEnvironment = wab.Environment; + WebHostEnvironment = wab.Environment; + InternalServices = wab.Services; + } + + public static void ConfigureApplication(this IConfiguration configuration) + { + Configuration = configuration; + } + + public static void ConfigureApplication(this IHost app) + { + RootServices = app.Services; + } + + public static void ConfigureApplication(this IServiceCollection services) + { + InternalServices = services; + } + + public static void ConfigureApplication(this IServiceProvider services) + { + RootServices = services; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/Aop/SqlSugarReuse.cs b/Blog.Core.Common/DB/Aop/SqlSugarReuse.cs new file mode 100644 index 00000000..949af640 --- /dev/null +++ b/Blog.Core.Common/DB/Aop/SqlSugarReuse.cs @@ -0,0 +1,23 @@ +using System.Linq; +using SqlSugar; + +namespace Blog.Core.Common.DB.Aop; + +public class SqlSugarReuse +{ + public static void AutoChangeAvailableConnect(SqlSugarClient db) + { + if (db == null) return; + if (db.Ado.IsValidConnection()) return; + if (!BaseDBConfig.ReuseConfigs.Any()) return; + + foreach (var connectionConfig in BaseDBConfig.ReuseConfigs) + { + var config = db.CurrentConnectionConfig.ConfigId; + db.ChangeDatabase(connectionConfig.ConfigId); + //移除旧的连接,只会在本次上下文移除,因为主库已经故障会导致多库事务无法使用 + db.RemoveConnection(config); + if (db.Ado.IsValidConnection()) return; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/Aop/SqlsugarAop.cs b/Blog.Core.Common/DB/Aop/SqlsugarAop.cs new file mode 100644 index 00000000..7d2031ad --- /dev/null +++ b/Blog.Core.Common/DB/Aop/SqlsugarAop.cs @@ -0,0 +1,162 @@ +using Blog.Core.Model.Models.RootTkey; +using Blog.Core.Model.Tenants; +using SqlSugar; +using StackExchange.Profiling; +using System; +using Serilog; +using Blog.Core.Common.LogHelper; +using Blog.Core.Common.Utility; +using Blog.Core.Model; + +namespace Blog.Core.Common.DB.Aop; + +public static class SqlSugarAop +{ + public static void OnLogExecuting(ISqlSugarClient sqlSugarScopeProvider, string user, string table, string operate, string sql, + SugarParameter[] p, ConnectionConfig config) + { + try + { + MiniProfiler.Current.CustomTiming($"ConnId:[{config.ConfigId}] SQL:", GetParas(p) + "【SQL语句】:" + sql); + + if (!AppSettings.app(new string[] { "AppSettings", "SqlAOP", "Enabled" }).ObjToBool()) return; + + if (AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToConsole", "Enabled" }).ObjToBool() || + AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToFile", "Enabled" }).ObjToBool() || + AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToDB", "Enabled" }).ObjToBool()) + { + using (LogContextExtension.Create.SqlAopPushProperty(sqlSugarScopeProvider)) + { + Log.Information( + "------------------ \r\n User:[{User}] Table:[{Table}] Operate:[{Operate}] ConnId:[{ConnId}]【SQL语句】: \r\n {Sql}", + user, table, operate, config.ConfigId, UtilMethods.GetNativeSql(sql, p)); + } + } + } + catch (Exception e) + { + Log.Error("Error occured OnLogExcuting:" + e); + } + } + + public static void DataExecuting(object oldValue, DataFilterModel entityInfo) + { + if (entityInfo.EntityValue is RootEntityTkey rootEntity) + { + if (rootEntity.Id == 0) + { + rootEntity.Id = IdGeneratorUtility.NextId(); + } + } + + if (entityInfo.EntityValue is BaseEntity baseEntity) + { + // 新增操作 + if (entityInfo.OperationType == DataFilterType.InsertByObject) + { + if (baseEntity.CreateTime == DateTime.MinValue) + { + baseEntity.CreateTime = DateTime.Now; + } + } + + if (entityInfo.OperationType == DataFilterType.UpdateByObject) + { + baseEntity.ModifyTime = DateTime.Now; + } + + + if (App.User?.ID > 0) + { + if (baseEntity is ITenantEntity tenant && App.User.TenantId > 0) + { + if (tenant.TenantId == 0) + { + tenant.TenantId = App.User.TenantId; + } + } + + switch (entityInfo.OperationType) + { + case DataFilterType.UpdateByObject: + baseEntity.ModifyId = App.User.ID; + baseEntity.ModifyBy = App.User.Name; + break; + case DataFilterType.InsertByObject: + if (baseEntity.CreateBy.IsNullOrEmpty() || baseEntity.CreateId is null or <= 0) + { + baseEntity.CreateId = App.User.ID; + baseEntity.CreateBy = App.User.Name; + } + + break; + } + } + } + else + { + //兼容以前的表 + //这里要小心 在AOP里用反射 数据量多性能就会有问题 + //要么都统一使用基类 + //要么考虑老的表没必要兼容老的表 + // + + var getType = entityInfo.EntityValue.GetType(); + + switch (entityInfo.OperationType) + { + case DataFilterType.InsertByObject: + var dyCreateBy = getType.GetProperty("CreateBy"); + var dyCreateId = getType.GetProperty("CreateId"); + var dyCreateTime = getType.GetProperty("CreateTime"); + + if (App.User?.ID > 0 && dyCreateBy != null && dyCreateBy.GetValue(entityInfo.EntityValue) == null) + dyCreateBy.SetValue(entityInfo.EntityValue, App.User.Name); + + if (App.User?.ID > 0 && dyCreateId != null && dyCreateId.GetValue(entityInfo.EntityValue) == null) + dyCreateId.SetValue(entityInfo.EntityValue, App.User.ID); + + if (dyCreateTime != null && dyCreateTime.GetValue(entityInfo.EntityValue) != null && + (DateTime)dyCreateTime.GetValue(entityInfo.EntityValue) == DateTime.MinValue) + dyCreateTime.SetValue(entityInfo.EntityValue, DateTime.Now); + + break; + case DataFilterType.UpdateByObject: + var dyModifyBy = getType.GetProperty("ModifyBy"); + var dyModifyId = getType.GetProperty("ModifyId"); + var dyModifyTime = getType.GetProperty("ModifyTime"); + + if (App.User?.ID > 0 && dyModifyBy != null) + dyModifyBy.SetValue(entityInfo.EntityValue, App.User.Name); + + if (App.User?.ID > 0 && dyModifyId != null) + dyModifyId.SetValue(entityInfo.EntityValue, App.User.ID); + + if (dyModifyTime != null) + dyModifyTime.SetValue(entityInfo.EntityValue, DateTime.Now); + break; + } + } + } + + private static string GetWholeSql(SugarParameter[] paramArr, string sql) + { + foreach (var param in paramArr) + { + sql = sql.Replace(param.ParameterName, $@"'{param.Value.ObjToString()}'"); + } + + return sql; + } + + private static string GetParas(SugarParameter[] pars) + { + string key = "【SQL参数】:"; + foreach (var param in pars) + { + key += $"{param.ParameterName}:{param.Value}\n"; + } + + return key; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/BaseDBConfig.cs b/Blog.Core.Common/DB/BaseDBConfig.cs index dc7fee4e..7eb74fb8 100644 --- a/Blog.Core.Common/DB/BaseDBConfig.cs +++ b/Blog.Core.Common/DB/BaseDBConfig.cs @@ -2,15 +2,46 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using SqlSugar; namespace Blog.Core.Common.DB { public class BaseDBConfig { + /// + /// 所有库配置 + /// + public static readonly List AllConfigs = new(); + + /// + /// 主库的备用连接配置 + /// + public static readonly List ReuseConfigs = new(); + + /// + /// 有效的库连接(除去Log库) + /// + public static List ValidConfig = new(); + + public static ConnectionConfig MainConfig; + public static ConnectionConfig LogConfig; //日志库 + + public static bool IsMulti => ValidConfig.Count > 1; + /* 之前的单库操作已经删除,如果想要之前的代码,可以查看我的GitHub的历史记录 * 目前是多库操作,默认加载的是appsettings.json设置为true的第一个db连接。 + * + * 优化配置连接 + * 老的配置方式,再多库和从库中有些冲突 + * 直接在单个配置中可以配置从库 + * + * 新增故障转移方案 + * 增加主库备用连接,配置方式为ConfigId为主库的ConfigId+随便数字 只要不重复就好 + * + * 主库在无法连接后会自动切换到备用链接 */ public static (List allDbs, List slaveDbs) MutiConnectionString => MutiInitConn(); + private static string DifDBConnOfSecurity(params string[] conn) { foreach (var item in conn) @@ -22,7 +53,9 @@ private static string DifDBConnOfSecurity(params string[] conn) return File.ReadAllText(item).Trim(); } } - catch (System.Exception) { } + catch (System.Exception) + { + } } return conn[conn.Length - 1]; @@ -33,46 +66,13 @@ public static (List, List) MutiInitConn() { List listdatabase = AppSettings.app("DBS") .Where(i => i.Enabled).ToList(); - foreach (var i in listdatabase) - { - SpecialDbString(i); - } - List listdatabaseSimpleDB = new List();//单库 - List listdatabaseSlaveDB = new List();//从库 + var mainDbId = AppSettings.app(new string[] {"MainDB"}).ObjToString(); + var mainDbModel = listdatabase.Single(d => d.ConnId == mainDbId); + listdatabase.Remove(mainDbModel); + listdatabase.Insert(0, mainDbModel); - // 单库,且不开启读写分离,只保留一个 - if (!AppSettings.app(new string[] { "CQRSEnabled" }).ObjToBool() && !AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()) - { - if (listdatabase.Count == 1) - { - return (listdatabase, listdatabaseSlaveDB); - } - else - { - var dbFirst = listdatabase.FirstOrDefault(d => d.ConnId == AppSettings.app(new string[] { "MainDB" }).ObjToString()); - if (dbFirst == null) - { - dbFirst = listdatabase.FirstOrDefault(); - } - listdatabaseSimpleDB.Add(dbFirst); - return (listdatabaseSimpleDB, listdatabaseSlaveDB); - } - } - - - // 读写分离,且必须是单库模式,获取从库 - if (AppSettings.app(new string[] { "CQRSEnabled" }).ObjToBool() && !AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()) - { - if (listdatabase.Count > 1) - { - listdatabaseSlaveDB = listdatabase.Where(d => d.ConnId != AppSettings.app(new string[] { "MainDB" }).ObjToString()).ToList(); - } - } - - - - return (listdatabase, listdatabaseSlaveDB); - //} + foreach (var i in listdatabase) SpecialDbString(i); + return (listdatabase, mainDbModel.Slaves); } /// @@ -85,19 +85,23 @@ private static MutiDBOperate SpecialDbString(MutiDBOperate mutiDBOperate) { if (mutiDBOperate.DbType == DataBaseType.Sqlite) { - mutiDBOperate.Connection = $"DataSource=" + Path.Combine(Environment.CurrentDirectory, mutiDBOperate.Connection); + mutiDBOperate.Connection = + $"DataSource=" + Path.Combine(Environment.CurrentDirectory, mutiDBOperate.Connection); } else if (mutiDBOperate.DbType == DataBaseType.SqlServer) { - mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_SqlserverConn.txt", mutiDBOperate.Connection); + mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_SqlserverConn.txt", + mutiDBOperate.Connection); } else if (mutiDBOperate.DbType == DataBaseType.MySql) { - mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_MySqlConn.txt", mutiDBOperate.Connection); + mutiDBOperate.Connection = + DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_MySqlConn.txt", mutiDBOperate.Connection); } else if (mutiDBOperate.DbType == DataBaseType.Oracle) { - mutiDBOperate.Connection = DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_OracleConn.txt", mutiDBOperate.Connection); + mutiDBOperate.Connection = + DifDBConnOfSecurity(@"D:\my-file\dbCountPsw1_OracleConn.txt", mutiDBOperate.Connection); } return mutiDBOperate; @@ -115,27 +119,37 @@ public enum DataBaseType Dm = 5, Kdbndp = 6, } + public class MutiDBOperate { /// /// 连接启用开关 /// public bool Enabled { get; set; } + /// /// 连接ID /// public string ConnId { get; set; } + /// /// 从库执行级别,越大越先执行 /// public int HitRate { get; set; } + /// /// 连接字符串 /// public string Connection { get; set; } + /// /// 数据库类型 /// public DataBaseType DbType { get; set; } + + /// + /// 从库 + /// + public List Slaves { get; set; } } } \ No newline at end of file diff --git a/Blog.Core.Common/DB/EntityUtility.cs b/Blog.Core.Common/DB/EntityUtility.cs new file mode 100644 index 00000000..f997a1eb --- /dev/null +++ b/Blog.Core.Common/DB/EntityUtility.cs @@ -0,0 +1,53 @@ +using Blog.Core.Common.Extensions; +using Blog.Core.Model; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace Blog.Core.Common.DB; + +public class EntityUtility +{ + private static readonly Lazy>> _tenantEntitys = new(() => + { + Dictionary> dic = new Dictionary>(); + var assembly = Assembly.Load("Blog.Core.Model"); + //扫描 实体 + foreach (var type in assembly.GetTypes().Where(s => s.IsClass && !s.IsAbstract)) + { + var tenant = type.GetCustomAttribute(); + if (tenant != null) + { + dic.TryAdd(tenant.configId.ToString(), type); + continue; + } + + if (type.IsSubclassOf(typeof(RootEntityTkey<>))) + { + dic.TryAdd(MainDb.CurrentDbConnId, type); + continue; + } + + var table = type.GetCustomAttribute(); + if (table != null) + { + dic.TryAdd(MainDb.CurrentDbConnId, type); + continue; + } + + Debug.Assert(type.Namespace != null, "type.Namespace != null"); + if (type.Namespace.StartsWith("Blog.Core.Model.Models")) + { + dic.TryAdd(MainDb.CurrentDbConnId, type); + continue; + } + } + + return dic; + }); + + public static Dictionary> TenantEntitys => _tenantEntitys.Value; +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/Extension/DbEntityException.cs b/Blog.Core.Common/DB/Extension/DbEntityException.cs new file mode 100644 index 00000000..6d06f291 --- /dev/null +++ b/Blog.Core.Common/DB/Extension/DbEntityException.cs @@ -0,0 +1,14 @@ +using System; +using System.Reflection; +using SqlSugar; + +namespace Blog.Core.Common.DB.Extension; + +public static class DbEntityException +{ + public static object GetEntityTenant(this Type type) + { + var tenant = type.GetCustomAttribute(); + return tenant?.configId; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/Extension/DynamicBuildException.cs b/Blog.Core.Common/DB/Extension/DynamicBuildException.cs new file mode 100644 index 00000000..15e638a4 --- /dev/null +++ b/Blog.Core.Common/DB/Extension/DynamicBuildException.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using Blog.Core.Common.Extensions; +using SqlSugar; + +namespace Blog.Core.Common.DB.Extension; + +public static class DynamicBuildException +{ + private static List GetEntityAttr(this DynamicBuilder builder) + { + FieldInfo fieldInfo = builder.GetType().GetField("entityAttr", BindingFlags.Instance | BindingFlags.NonPublic); + List entityAttr = (List) fieldInfo.GetValue(builder); + return entityAttr; + } + + private static CustomAttributeBuilder CreateIndex(SugarIndexAttribute indexAttribute) + { + Type type = typeof(SugarIndexAttribute); + var constructorTypes = new List() {typeof(string)}; + for (int i = 0; i < indexAttribute.IndexFields.Count; i++) + { + constructorTypes.AddRange(new[] {typeof(string), typeof(OrderByType)}); + } + + constructorTypes.Add(typeof(bool)); + + var values = new List() {indexAttribute.IndexName}; + foreach (var indexField in indexAttribute.IndexFields) + { + values.AddRange(new object[] {indexField.Key, indexField.Value}); + } + + values.Add(indexAttribute.IsUnique); + return new CustomAttributeBuilder(type.GetConstructor(constructorTypes.ToArray())!, values.ToArray()); + } + + public static DynamicProperyBuilder CreateIndex(this DynamicProperyBuilder builder, SugarIndexAttribute indexAttribute) + { + var classBuilder = builder.baseBuilder; + var entityAttr = classBuilder.GetEntityAttr(); + entityAttr.Add(CreateIndex(indexAttribute)); + return builder; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/Extension/SqlSugarDbMaintenanceExtension.cs b/Blog.Core.Common/DB/Extension/SqlSugarDbMaintenanceExtension.cs new file mode 100644 index 00000000..a316a2e7 --- /dev/null +++ b/Blog.Core.Common/DB/Extension/SqlSugarDbMaintenanceExtension.cs @@ -0,0 +1,38 @@ +using SqlSugar; + +namespace Blog.Core.Common.DB.Extension; + +/// +/// SqlSugar DbMaintenance 扩展 +/// +public static class SqlSugarDbMaintenanceExtension +{ + /// + /// 清除 SqlSugar 中表信息的缓存。 + /// 使用场景: + /// + /// + /// + /// 调用 时会缓存表的元数据信息, + /// SqlSugar 未提供显式清理该缓存的方法。 + /// + /// + /// + /// + /// 如果通过 DbMaintenance.IsAnyTable(tableName, false) 绕过缓存, + /// 则每次调用都会重新查询所有表信息,影响性能。 + /// + /// + /// + /// 建议在InitTable后进行清理缓存. + /// + /// SqlSugar的数据库上下文实例。 + public static void ClearDbTableCache(this ISqlSugarClient context) + { + var fullCacheKey = GetDbTableCacheKey(context, "DbMaintenanceProvider.GetTableInfoList" + context.CurrentConnectionConfig.ConfigId); + context.CurrentConnectionConfig.ConfigureExternalServices.ReflectionInoCacheService.Remove>(fullCacheKey); + } + + private static string GetDbTableCacheKey(ISqlSugarClient context, string cacheKey) => + $"{context.CurrentConnectionConfig.DbType}.{context.Ado.Connection.Database}.{cacheKey}"; +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/MainDb.cs b/Blog.Core.Common/DB/MainDb.cs index f132b710..3de863f3 100644 --- a/Blog.Core.Common/DB/MainDb.cs +++ b/Blog.Core.Common/DB/MainDb.cs @@ -2,6 +2,6 @@ { public static class MainDb { - public static string CurrentDbConnId = "1"; + public static string CurrentDbConnId = "Main"; } } diff --git a/Blog.Core.Common/DB/RepositorySetting.cs b/Blog.Core.Common/DB/RepositorySetting.cs new file mode 100644 index 00000000..bfca7174 --- /dev/null +++ b/Blog.Core.Common/DB/RepositorySetting.cs @@ -0,0 +1,48 @@ +using Blog.Core.Model.Models.RootTkey; +using Blog.Core.Model.Models.RootTkey.Interface; +using Blog.Core.Model.Tenants; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Blog.Core.Common.DB; + +public class RepositorySetting +{ + private static readonly Lazy> AllEntitys = new(() => + { + return typeof(BaseEntity).Assembly + .GetTypes() + .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(BaseEntity))) + .Where(it => it.FullName != null && it.FullName.StartsWith("Blog.Core.Model.Models")); + }); + + public static IEnumerable Entitys => AllEntitys.Value; + + /// + /// 配置实体软删除过滤器
+ /// 统一过滤 软删除 无需自己写条件 + ///
+ public static void SetDeletedEntityFilter(SqlSugarScopeProvider db) + { + db.QueryFilter.AddTableFilter(it => it.IsDeleted == false); + } + + /// + /// 配置租户 + /// + public static void SetTenantEntityFilter(SqlSugarScopeProvider db) + { + if (App.User is not { ID: > 0, TenantId: > 0 }) + { + return; + } + + //多租户 单表 + db.QueryFilter.AddTableFilter(it => it.TenantId == App.User.TenantId || it.TenantId == 0); + + //多租户 多表 + db.SetTenantTable(App.User.TenantId.ToString()); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/DB/TenantUtil.cs b/Blog.Core.Common/DB/TenantUtil.cs new file mode 100644 index 00000000..8395c271 --- /dev/null +++ b/Blog.Core.Common/DB/TenantUtil.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Blog.Core.Model.Models; +using Blog.Core.Model.Tenants; +using SqlSugar; + +namespace Blog.Core.Common.DB; + +public static class TenantUtil +{ + public static SysTenant DefaultTenantConfig(this SysTenant tenant) + { + tenant.DbType ??= DbType.Sqlite; + + //如果没有配置连接 + if (tenant.Connection.IsNullOrEmpty()) + { + //此处默认配置 Sqlite 地址 + //实际业务中 也会有运维、系统管理员等来维护 + switch (tenant.DbType.Value) + { + case DbType.Sqlite: + tenant.Connection = $"DataSource={Path.Combine(Environment.CurrentDirectory, tenant.ConfigId)}.db"; + break; + } + } + + return tenant; + } + + public static ConnectionConfig GetConnectionConfig(this SysTenant tenant) + { + if (tenant.DbType is null) + { + throw new ArgumentException("Tenant DbType Must"); + } + + + return new ConnectionConfig() + { + ConfigId = tenant.ConfigId, + DbType = tenant.DbType.Value, + ConnectionString = tenant.Connection, + IsAutoCloseConnection = true, + MoreSettings = new ConnMoreSettings() + { + IsAutoRemoveDataCache = true, + SqlServerCodeFirstNvarchar = true, + }, + }; + } + + public static List GetTenantEntityTypes(TenantTypeEnum? tenantType = null) + { + return RepositorySetting.Entitys + .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass) + .Where(s => IsTenantEntity(s, tenantType)) + .ToList(); + } + + public static bool IsTenantEntity(this Type u, TenantTypeEnum? tenantType = null) + { + var mta = u.GetCustomAttribute(); + if (mta is null) + { + return false; + } + + if (tenantType != null) + { + if (mta.TenantType != tenantType) + { + return false; + } + } + + return true; + } + + public static string GetTenantTableName(this Type type, ISqlSugarClient db, string id) + { + var entityInfo = db.EntityMaintenance.GetEntityInfo(type); + return $@"{entityInfo.DbTableName}_{id}"; + } + + public static string GetTenantTableName(this Type type, ISqlSugarClient db, SysTenant tenant) + { + return GetTenantTableName(type, db, tenant.Id.ToString()); + } + + public static void SetTenantTable(this ISqlSugarClient db, string id) + { + var types = GetTenantEntityTypes(TenantTypeEnum.Tables); + + foreach (var type in types) + { + db.MappingTables.Add(type.Name, type.GetTenantTableName(db, id)); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/AssemblysExtensions.cs b/Blog.Core.Common/Extensions/AssemblysExtensions.cs new file mode 100644 index 00000000..5e5d4349 --- /dev/null +++ b/Blog.Core.Common/Extensions/AssemblysExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.DependencyModel; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; + +namespace Blog.Core.Common.Extensions; + +public static class AssemblysExtensions +{ + public static List GetAllAssemblies() + { + var list = new List(); + var deps = DependencyContext.Default; + var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package" ); + foreach (var lib in libs) + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name)); + list.Add(assembly); + } + + return list; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/DictionaryExtensions.cs b/Blog.Core.Common/Extensions/DictionaryExtensions.cs new file mode 100644 index 00000000..ffcf910c --- /dev/null +++ b/Blog.Core.Common/Extensions/DictionaryExtensions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Blog.Core.Common.Extensions; + +public static class DictionaryExtensions +{ + public static void TryAdd(this IDictionary> dic, TKey key, TValue value) + { + if (dic.TryGetValue(key, out var old)) + { + old.Add(value); + } + else + { + dic.Add(key, new List {value}); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/ExpressionExtensions.cs b/Blog.Core.Common/Extensions/ExpressionExtensions.cs new file mode 100644 index 00000000..76179d5d --- /dev/null +++ b/Blog.Core.Common/Extensions/ExpressionExtensions.cs @@ -0,0 +1,217 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; + +namespace Blog.Core.Common.Helper +{ + /// + /// Linq扩展 + /// + public static class ExpressionExtensions + { + #region HttpContext + + /// + /// 返回请求上下文 + /// + /// + /// + /// + /// + /// + public static async Task Cof_SendResponse(this HttpContext context, System.Net.HttpStatusCode code, string message, + string ContentType = "text/html;charset=utf-8") + { + context.Response.StatusCode = (int) code; + context.Response.ContentType = ContentType; + await context.Response.WriteAsync(message); + } + + #endregion + + #region ICaching + + /// + /// 从缓存里取数据,如果不存在则执行查询方法, + /// + /// 类型 + /// ICaching + /// 键值 + /// 查询方法 + /// 有效期 单位分钟/param> + /// + public static T Cof_GetICaching(this ICaching cache, string key, Func GetFun, int timeSpanMin) where T : class + { + var obj = cache.Get(key); + if (obj == null) + { + obj = GetFun(); + cache.Set(key, obj, TimeSpan.FromMinutes(timeSpanMin)); + } + + return obj; + } + + /// + /// 异步从缓存里取数据,如果不存在则执行查询方法 + /// + /// 类型 + /// ICaching + /// 键值 + /// 查询方法 + /// 有效期 单位分钟/param> + /// + public static async Task Cof_AsyncGetICaching(this ICaching cache, string key, Func> GetFun, int timeSpanMin) where T : class + { + var obj = await cache.GetAsync(key); + if (obj == null) + { + obj = await GetFun(); + cache.Set(key, obj, TimeSpan.FromMinutes(timeSpanMin)); + } + + return obj; + } + + #endregion + + #region 常用扩展方法 + + public static bool Cof_CheckAvailable(this IEnumerable Tlist) + { + return Tlist != null && Tlist.Count() > 0; + } + + /// + /// 调用内部方法 + /// + public static Expression Call(this Expression instance, string methodName, params Expression[] arguments) + { + if (instance.Type == typeof(string)) + return Expression.Call(instance, instance.Type.GetMethod(methodName, new Type[] {typeof(string)}), + arguments); //修复string contains 出现的问题 Ambiguous match found. + else + return Expression.Call(instance, instance.Type.GetMethod(methodName), arguments); + } + + /// + /// 获取内部成员 + /// + public static Expression Property(this Expression expression, string propertyName) + { + // Todo:左边条件如果是dynamic, + // 则Expression.Property无法获取子内容 + // 报错在这里,由于expression内的对象为Object,所以无法解析到 + // var x = (expression as IQueryable).ElementType; + var exp = Expression.Property(expression, propertyName); + if (exp.Type.IsGenericType && exp.Type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return Expression.Convert(exp, exp.Type.GetGenericArguments()[0]); + } + + return exp; + } + + /// + /// 转Lambda + /// + public static Expression ToLambda(this Expression body, + params ParameterExpression[] parameters) + { + return Expression.Lambda(body, parameters); + } + + #endregion + + #region 常用运算符 [ > , >= , == , < , <= , != , || , && ] + + /// + /// && + /// + public static Expression AndAlso(this Expression left, Expression right) + { + return Expression.AndAlso(left, right); + } + + /// + /// || + /// + public static Expression OrElse(this Expression left, Expression right) + { + return Expression.OrElse(left, right); + } + + /// + /// Contains + /// + public static Expression Contains(this Expression left, Expression right) + { + return left.Call("Contains", right); + } + + public static Expression StartContains(this Expression left, Expression right) + { + return left.Call("StartsWith", right); + } + + public static Expression EndContains(this Expression left, Expression right) + { + return left.Call("EndsWith", right); + } + + /// + /// > + /// + public static Expression GreaterThan(this Expression left, Expression right) + { + return Expression.GreaterThan(left, right); + } + + /// + /// >= + /// + public static Expression GreaterThanOrEqual(this Expression left, Expression right) + { + return Expression.GreaterThanOrEqual(left, right); + } + + /// + /// < + /// + public static Expression LessThan(this Expression left, Expression right) + { + return Expression.LessThan(left, right); + } + + /// + /// <= + /// + public static Expression LessThanOrEqual(this Expression left, Expression right) + { + return Expression.LessThanOrEqual(left, right); + } + + /// + /// == + /// + public static Expression Equal(this Expression left, Expression right) + { + return Expression.Equal(left, right); + } + + /// + /// != + /// + public static Expression NotEqual(this Expression left, Expression right) + { + return Expression.NotEqual(left, right); + } + + #endregion + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/ExpressionExtensions_Nacos.cs b/Blog.Core.Common/Extensions/ExpressionExtensions_Nacos.cs new file mode 100644 index 00000000..705d4c15 --- /dev/null +++ b/Blog.Core.Common/Extensions/ExpressionExtensions_Nacos.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Helper +{ + /// + /// Linq扩展 + /// + public static class ExpressionExtensions_Nacos + { + #region Nacos NamingService + + private static string GetServiceUrl(Nacos.V2.INacosNamingService serv, string ServiceName, string Group, + string apiurl) + { + try + { + var instance = serv.SelectOneHealthyInstance(ServiceName, Group).GetAwaiter().GetResult(); + var host = $"{instance.Ip}:{instance.Port}"; + if (instance.Metadata.ContainsKey("endpoint")) host = instance.Metadata["endpoint"]; + + + var baseUrl = instance.Metadata.TryGetValue("secure", out _) + ? $"https://{host}" + : $"http://{host}"; + + if (string.IsNullOrWhiteSpace(baseUrl)) + { + return ""; + } + + return $"{baseUrl}{apiurl}"; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + return ""; + } + + public static async Task Cof_NaoceGet(this Nacos.V2.INacosNamingService serv, string ServiceName, + string Group, string apiurl, Dictionary Parameters = null) + { + try + { + var url = GetServiceUrl(serv, ServiceName, Group, apiurl); + if (string.IsNullOrEmpty(url)) return ""; + if (Parameters != null && Parameters.Any()) + { + StringBuilder sb = new StringBuilder(); + foreach (var pitem in Parameters) + { + sb.Append($"{pitem.Key}={pitem.Value}&"); + } + + url = $"{url}?{sb.ToString().Trim('&')}"; + } + + HttpHelper.Httpclient.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json")); + var result = await HttpHelper.Httpclient.GetAsync(url); + return await result.Content.ReadAsStringAsync(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + return ""; + } + + public static async Task Cof_NaocePostForm(this Nacos.V2.INacosNamingService serv, string ServiceName, + string Group, string apiurl, Dictionary Parameters) + { + try + { + var url = GetServiceUrl(serv, ServiceName, Group, apiurl); + if (string.IsNullOrEmpty(url)) return ""; + + var content = (Parameters != null && Parameters.Any()) ? new FormUrlEncodedContent(Parameters) : null; + HttpHelper.Httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var result = await HttpHelper.Httpclient.PostAsync(url, content); + return await result.Content.ReadAsStringAsync(); //.GetAwaiter().GetResult(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + return ""; + } + + public static async Task Cof_NaocePostJson(this Nacos.V2.INacosNamingService serv, string ServiceName, + string Group, string apiurl, string jSonData) + { + try + { + var url = GetServiceUrl(serv, ServiceName, Group, apiurl); + if (string.IsNullOrEmpty(url)) return ""; + HttpHelper.Httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var result = + await HttpHelper.Httpclient.PostAsync(url, new StringContent(jSonData, Encoding.UTF8, "application/json")); + return await result.Content.ReadAsStringAsync(); //.GetAwaiter().GetResult(); + + //httpClient.BaseAddress = new Uri("https://www.testapi.com"); + //httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + //httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + return ""; + } + + public static async Task Cof_NaocePostFile(this Nacos.V2.INacosNamingService serv, string ServiceName, + string Group, string apiurl, Dictionary Parameters) + { + try + { + var url = GetServiceUrl(serv, ServiceName, Group, apiurl); + if (string.IsNullOrEmpty(url)) return ""; + + var content = new MultipartFormDataContent(); + foreach (var pitem in Parameters) + { + content.Add(new ByteArrayContent(pitem.Value), "files", pitem.Key); + } + + HttpHelper.Httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + var result = await HttpHelper.Httpclient.PostAsync(url, content); + return await result.Content.ReadAsStringAsync(); //.GetAwaiter().GetResult(); + } + catch (Exception e) + { + //InfluxdbHelper.GetInstance().AddLog("Cof_NaocePostFile.Err", ee); + Console.WriteLine(e.Message); + } + + return ""; + } + + #endregion + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/Extention.Expression.cs b/Blog.Core.Common/Extensions/Extention.Expression.cs index 9b746bd2..84047ef1 100644 --- a/Blog.Core.Common/Extensions/Extention.Expression.cs +++ b/Blog.Core.Common/Extensions/Extention.Expression.cs @@ -204,7 +204,7 @@ public static Expression> And(this Expression> on var parameterReplacer = new ParameterReplaceVisitor(newParameter); var left = parameterReplacer.Visit(one.Body); var right = parameterReplacer.Visit(another.Body); - var body = Expression.And(left, right); + var body = Expression.AndAlso(left, right); return Expression.Lambda>(body, newParameter); } diff --git a/Blog.Core.Common/Extensions/GenericTypeExtensions.cs b/Blog.Core.Common/Extensions/GenericTypeExtensions.cs index 368b6676..6c067773 100644 --- a/Blog.Core.Common/Extensions/GenericTypeExtensions.cs +++ b/Blog.Core.Common/Extensions/GenericTypeExtensions.cs @@ -26,5 +26,31 @@ public static string GetGenericTypeName(this object @object) { return @object.GetType().GetGenericTypeName(); } + + /// + /// 判断类型是否实现某个泛型 + /// + /// 类型 + /// 泛型类型 + /// bool + // public static bool HasImplementedRawGeneric(this Type type, Type generic) + // { + // // 检查接口类型 + // var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType); + // if (isTheRawGenericType) return true; + + // // 检查类型 + // while (type != null && type != typeof(object)) + // { + // isTheRawGenericType = IsTheRawGenericType(type); + // if (isTheRawGenericType) return true; + // type = type.BaseType; + // } + + // return false; + + // // 判断逻辑 + // bool IsTheRawGenericType(Type type) => generic == (type.IsGenericType ? type.GetGenericTypeDefinition() : type); + // } } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/HttpContextExtension.cs b/Blog.Core.Common/Extensions/HttpContextExtension.cs new file mode 100644 index 00000000..0de018a0 --- /dev/null +++ b/Blog.Core.Common/Extensions/HttpContextExtension.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace Blog.Core.Common.Extensions; + +public static class HttpContextExtension +{ + public static ISession GetSession(this HttpContext context) + { + try + { + return context.Session; + } + catch (Exception) + { + return default; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/HttpRequestExtension.cs b/Blog.Core.Common/Extensions/HttpRequestExtension.cs new file mode 100644 index 00000000..285a4a9e --- /dev/null +++ b/Blog.Core.Common/Extensions/HttpRequestExtension.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Http; +using System.IO; +using System.Text; + +namespace Blog.Core.Common.Extensions; + +public static class HttpRequestExtension +{ + public static string GetRequestBody(this HttpRequest request) + { + if (!request.Body.CanRead) + { + return default; + } + + if (!request.Body.CanSeek) + { + return default; + } + + if (request.Body.Length < 1) + { + return default; + } + + var bodyStr = ""; + // 启用倒带功能,就可以让 Request.Body 可以再次读取 + request.Body.Seek(0, SeekOrigin.Begin); + using (StreamReader reader + = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true)) + { + bodyStr = reader.ReadToEnd(); + } + + request.Body.Position = 0; + return bodyStr; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/HttpResponseExceptions.cs b/Blog.Core.Common/Extensions/HttpResponseExceptions.cs new file mode 100644 index 00000000..34c3baae --- /dev/null +++ b/Blog.Core.Common/Extensions/HttpResponseExceptions.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using Blog.Core.Common.Https; +using Microsoft.AspNetCore.Http; + +namespace Blog.Core.Common.Extensions; + +public static class HttpResponseExceptions +{ + public static string GetResponseBody(this HttpResponse response) + { + if (response is null) + { + return string.Empty; + } + + //原始HttpResponseStream 无法读取 + //实际上只是个包装类,内部使用了HttpResponsePipeWriter write + switch (response.Body) + { + case FluentHttpResponseStream: + case MemoryStream: + { + response.Body.Position = 0; + using var stream = new StreamReader(response.Body, leaveOpen: true); + var body = stream.ReadToEnd(); + response.Body.Position = 0; + return body; + } + default: + // throw new ApplicationException("The response body is not a FluentHttpResponseStream"); + return string.Empty; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/RuntimeExtension.cs b/Blog.Core.Common/Extensions/RuntimeExtension.cs new file mode 100644 index 00000000..7d58f0a8 --- /dev/null +++ b/Blog.Core.Common/Extensions/RuntimeExtension.cs @@ -0,0 +1,92 @@ +using Microsoft.Extensions.DependencyModel; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; + +namespace Blog.Core.Common.Extensions; + +public static class RuntimeExtension +{ + private static readonly List ProjectAssemblies = + [ + "Blog.Core" + ]; + + /// + /// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包 + /// + /// + public static IList GetAllAssemblies() + { + var assemblies = new List(); + var dllFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"); + + foreach (var dllFile in dllFiles) + { + var fileName = Path.GetFileNameWithoutExtension(dllFile); + if (!ProjectAssemblies.Any(s => fileName.StartsWith(s))) continue; + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(dllFile); + assemblies.Add(assembly); + } + catch (Exception e) + { + Console.WriteLine($"Failed to load assembly: {fileName}. Error: {e.Message}"); + } + } + + return assemblies; + } + + public static Assembly GetAssembly(string assemblyName) + { + return GetAllAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName)); + } + + public static IList GetAllTypes() + { + var list = new List(); + foreach (var assembly in GetAllAssemblies()) + { + var typeInfos = assembly.DefinedTypes; + foreach (var typeInfo in typeInfos) + { + list.Add(typeInfo.AsType()); + } + } + + return list; + } + + public static IList GetTypesByAssembly(string assemblyName) + { + var list = new List(); + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName)); + var typeInfos = assembly.DefinedTypes; + foreach (var typeInfo in typeInfos) + { + list.Add(typeInfo.AsType()); + } + + return list; + } + + public static Type GetImplementType(string typeName, Type baseInterfaceType) + { + return GetAllTypes().FirstOrDefault(t => + { + if (t.Name == typeName && + t.GetTypeInfo().GetInterfaces().Any(b => b.Name == baseInterfaceType.Name)) + { + var typeInfo = t.GetTypeInfo(); + return typeInfo.IsClass && !typeInfo.IsAbstract && !typeInfo.IsGenericType; + } + + return false; + }); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Extensions/UntilExtensions.cs b/Blog.Core.Common/Extensions/UntilExtensions.cs new file mode 100644 index 00000000..efb17dc8 --- /dev/null +++ b/Blog.Core.Common/Extensions/UntilExtensions.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Blog.Core.Common.Extensions; + +public static class UntilExtensions +{ + public static void AddOrModify(this IDictionary dic, TKey key, TValue value) + { + if (dic.TryGetValue(key, out _)) + { + dic[key] = value; + } + else + { + dic.Add(key, value); + } + } + + public static IEnumerable WhereIf(this IEnumerable source, bool condition, Func predicate) + { + return condition ? source.Where(predicate) : source; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/GlobalVar/GlobalVars.cs b/Blog.Core.Common/GlobalVar/GlobalVars.cs index cb281e26..84dd9e19 100644 --- a/Blog.Core.Common/GlobalVar/GlobalVars.cs +++ b/Blog.Core.Common/GlobalVar/GlobalVars.cs @@ -20,6 +20,12 @@ public static class Permissions /// true:表示启动IDS4 /// false:表示使用JWT public static bool IsUseIds4 = false; + + /// + /// 当前项目是否启用Authing权限方案 + /// true:表示启动 + /// false:表示使用JWT + public static bool IsUseAuthing = false; } /// diff --git a/Blog.Core.Common/Helper/Appsettings.cs b/Blog.Core.Common/Helper/Appsettings.cs index d6d06651..83e3d7e7 100644 --- a/Blog.Core.Common/Helper/Appsettings.cs +++ b/Blog.Core.Common/Helper/Appsettings.cs @@ -22,9 +22,12 @@ public AppSettings(string contentPath) //Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"; Configuration = new ConfigurationBuilder() - .SetBasePath(contentPath) - .Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true })//这样的话,可以直接读目录里的json文件,而不是 bin 文件夹下的,所以不用修改复制属性 - .Build(); + .SetBasePath(contentPath) + .Add(new JsonConfigurationSource + { + Path = Path, Optional = false, ReloadOnChange = true + }) //这样的话,可以直接读目录里的json文件,而不是 bin 文件夹下的,所以不用修改复制属性 + .Build(); } public AppSettings(IConfiguration configuration) @@ -41,13 +44,14 @@ public static string app(params string[] sections) { try { - if (sections.Any()) { return Configuration[string.Join(":", sections)]; } } - catch (Exception) { } + catch (Exception) + { + } return ""; } @@ -78,10 +82,11 @@ public static string GetValue(string sectionsPath) { return Configuration[sectionsPath]; } - catch (Exception) { } + catch (Exception) + { + } return ""; - } } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/DynamicLinqFactory.cs b/Blog.Core.Common/Helper/DynamicLinqFactory.cs index a12c6bbb..adb57074 100644 --- a/Blog.Core.Common/Helper/DynamicLinqFactory.cs +++ b/Blog.Core.Common/Helper/DynamicLinqFactory.cs @@ -1,15 +1,12 @@ -using Microsoft.AspNetCore.Http; -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Linq.Expressions; -using System.Net.Http; -using System.Net.Http.Headers; using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; +using Mapster; namespace Blog.Core.Common.Helper { @@ -20,10 +17,10 @@ namespace Blog.Core.Common.Helper /// public static class DynamicLinqFactory { - private static readonly Dictionary _operatingSystems = new(); + private static readonly Dictionary _operatingSystems = new Dictionary(); public static Dictionary OperatingSystems => GetOperationSymbol(); - private static readonly Dictionary _linkSymbols = new(); + private static readonly Dictionary _linkSymbols = new Dictionary(); public static Dictionary LinkSymbols => GetLinkSymbol(); /// @@ -44,10 +41,9 @@ public static Expression> CreateLambda(string prope // 第一个判断条件,固定一个判断条件作为最左边 - Expression mainExpressin = ExpressionStudio(null, strArr.FirstOrDefault(x => x.LinkSymbol == LinkSymbol.Empty), parameter); - + Expression mainExpressin = ExpressionStudio(null, strArr[0], parameter); // 将需要放置在最左边的判断条件从列表中去除,因为已经合成到表达式最左边了 - strArr.Remove(strArr.FirstOrDefault(x => x.LinkSymbol == LinkSymbol.Empty)); + strArr.RemoveAt(0); foreach (var x in strArr) { @@ -61,177 +57,185 @@ public static Expression> CreateLambda(string prope /// 组合条件判断表达式 /// /// 左边的表达式 - /// + /// /// /// - public static Expression ExpressionStudio(Expression left, DynamicLinqHelper DynamicLinq, ParameterExpression key) + public static Expression ExpressionStudio(Expression left, DynamicLinqHelper dynamicLinq, ParameterExpression key) { Expression mainExpression = key; - var properties = DynamicLinq.Left.Split('.'); + if (!dynamicLinq.Left.IsNullOrEmpty()) + { + var properties = dynamicLinq.Left.Split('.'); + + int index = 0; + foreach (var t in properties) + { + if (mainExpression.Type.HasImplementedRawGeneric(typeof(IEnumerable<>))) + { + return ExpressionStudioEnumerable(left, mainExpression, dynamicLinq.Adapt(), + properties.Skip(index).ToArray()); + } + + mainExpression = mainExpression.Property(t); + index++; + } + } - // 从1开始,是不想用自定义种子,外层种子已经定义好了 - // 暂时也不会有多个自定义种子,先这样 - for (var i = 0; i < properties.Length; i++) + Expression right = null; + if (dynamicLinq.IsMerge && dynamicLinq.Child.Any()) { - mainExpression = mainExpression.Property(properties[i]); + right = ExpressionStudio(null, dynamicLinq.Child[0], key); + for (var i = 1; i < dynamicLinq.Child.Count; i++) + { + right = ChangeLinkSymbol(dynamicLinq.Child[i].LinkSymbol, right, ExpressionStudio(null, dynamicLinq.Child[i], key)); + } + } + else + { + right = ChangeOperationSymbol(dynamicLinq.OperationSymbol, mainExpression, dynamicLinq.Right); } left = left == null // 如果左边表达式为空,则当前的表达式就为最左边 - ? ChangeOperationSymbol(DynamicLinq.OperationSymbol, mainExpression, DynamicLinq.Right) + ? right // 如果不为空,则将当前的表达式连接到左边 - : ChangeLinkSymbol(DynamicLinq.LinkSymbol, left, ChangeOperationSymbol(DynamicLinq.OperationSymbol, mainExpression, DynamicLinq.Right)); + : ChangeLinkSymbol(dynamicLinq.LinkSymbol, left, right); return left; } - /// - /// 将字符串装换成动态帮助类(内含递归) - /// - public static List SpiltStrings(string propertyStr) + public static Expression ExpressionStudioEnumerable(Expression left, Expression property, DynamicLinqHelper dynamicLinq, string[] properties) { - // 定义返回用List - var outList = new List(); + var realType = property.Type.GenericTypeArguments[0]; - // 当最后已经没有连接运算符的时候,进入该条件 - if (!propertyStr.Contains("&") & !propertyStr.Contains("|")) + var parameter = Expression.Parameter(realType, "z"); + Expression mainExpression = property; + if (!properties.Any()) { - // 当前的条件是不具备连接符号的 - var lastStr = propertyStr.Trim().Split(' '); - outList.Add(new DynamicLinqHelper - { - LinkSymbol = LinkSymbol.Empty, - Left = lastStr[0], - Right = lastStr[2], - OperationSymbol = ChangeOperationSymbol(lastStr[1]) - }); - return outList; + throw new ApplicationException("条件表达式错误,属性为集合时,需要明确具体属性"); } - // 判断当前 & | 哪个符号在最后一个判断逻辑内 - var key = propertyStr.LastIndexOf('&') > propertyStr.LastIndexOf('|') ? '&' : '|'; + dynamicLinq.Left = string.Join(".", properties); + mainExpression = ExpressionStudio(null, dynamicLinq, parameter); - var nowStrArr = propertyStr.Substring(propertyStr.LastIndexOf(key)).Trim().Split(' '); + var lambda = Expression.Lambda(mainExpression, parameter); - outList.Add(new DynamicLinqHelper - { - LinkSymbol = ChangeLinkSymbol(nowStrArr[0]), - Left = nowStrArr[1], - OperationSymbol = ChangeOperationSymbol(nowStrArr[2]), - Right = nowStrArr[3] - }); - // 将剩余部分继续切割 - propertyStr = propertyStr.Substring(0, propertyStr.LastIndexOf(key)).Trim(); - // 递归 由后彺前 - outList.AddRange(SpiltStrings(propertyStr)); + mainExpression = Expression.Call(typeof(Enumerable), "Any", new[] {realType}, property, lambda); - return outList; + left = left == null + ? mainExpression + : ChangeLinkSymbol(dynamicLinq.LinkSymbol, left, mainExpression); + + return left; } + public static List SplitOperationSymbol(string str) { var outList = new List(); var tokens = Regex.Matches(FormatString(str), _pattern, RegexOptions.Compiled) .Select(m => m.Groups[1].Value.Trim()) .ToList(); + SplitOperationSymbol(tokens, outList); + return outList; + } - int lastIndex = tokens.Count - 1; - int lastOperatingSymbolIndex = -1; - for (int i = tokens.Count - 1; i >= 0; i--) + private static void SplitOperationSymbol(List tokens, List outList, int start = 0, int end = 0) + { + var dys = new Stack(); + var dynamicLinqHelper = new DynamicLinqHelper(); + if (end == 0) + { + end = tokens.Count - 1; + } + + for (int i = start; i <= end; i++) { var token = tokens[i]; - if (OperatingSystems.ContainsKey(token)) + if (LinkSymbols.TryGetValue(token, out var symbol)) { - //比较运算符 - lastOperatingSymbolIndex = i; - } - else if (LinkSymbols.ContainsKey(token)) - { - var left = ""; - for (int j = i + 1; j < lastOperatingSymbolIndex; j++) + if (dys.Count > 0) { - left += tokens[j]; + var linqHelper = dys.Peek(); + linqHelper.Child.Add(dynamicLinqHelper); } - - var right = ""; - for (int j = lastOperatingSymbolIndex + 1; j <= lastIndex; j++) + else { - right += tokens[j]; + outList.Add(dynamicLinqHelper); } - outList.Add(GetDynamicLinqHelper(LinkSymbols[token], - OperatingSystems[tokens[lastOperatingSymbolIndex]], - left, - right)); - lastIndex = i - 1; - lastOperatingSymbolIndex = -1; + dynamicLinqHelper = new DynamicLinqHelper() + { + LinkSymbol = symbol, + }; + continue; } - else if (i == 0 && lastOperatingSymbolIndex != -1) + + if (OperatingSystems.TryGetValue(token.ToLower(), out var system)) { - var left = ""; - for (int j = i; j < lastOperatingSymbolIndex; j++) - { - left += tokens[j]; - } + dynamicLinqHelper!.OperationSymbol = system; + continue; + } - var right = ""; - for (int j = lastOperatingSymbolIndex + 1; j <= lastIndex; j++) + + if (dynamicLinqHelper!.OperationSymbol != OperationSymbol.In) + { + if (string.Equals(token.Trim(), "(")) { - right += tokens[j]; + dynamicLinqHelper!.IsMerge = true; + dynamicLinqHelper.Child = new List(); + dys.Push(dynamicLinqHelper); + dynamicLinqHelper = new DynamicLinqHelper(); + continue; } + if (string.Equals(token.Trim(), ")")) + { + if (dys.Count > 1) + { + var dya = dys.Pop(); + dya.Child.Add(dynamicLinqHelper); - outList.Add(GetDynamicLinqHelper(LinkSymbol.Empty, - OperatingSystems[tokens[lastOperatingSymbolIndex]], - left, - right)); + dynamicLinqHelper = dya; + continue; + } + else + { + var dya = dys.Pop(); + dya.Child.Add(dynamicLinqHelper); + outList.Add(dya); + dynamicLinqHelper = null; + continue; + } + } } - } - return outList; - } - public static DynamicLinqHelper GetDynamicLinqHelper(LinkSymbol linkSymbol, OperationSymbol operationSymbol, string left, string right) - { - var dynamic = new DynamicLinqHelper - { - LinkSymbol = linkSymbol, - OperationSymbol = operationSymbol, - Left = left, - Right = right - }; + if (dynamicLinqHelper!.OperationSymbol is null) + { + dynamicLinqHelper.Left += token; + } + else + { + dynamicLinqHelper.Right += FormatValue(token); + } - if (dynamic.Right.StartsWith("\"") && dynamic.Right.EndsWith("\"")) - { - dynamic.Right = dynamic.Right.Remove(0, 1) - .Remove(dynamic.Right.Length - 2, 1) - .Replace(@"\""", @""""); + if (i == end) + { + outList.Add(dynamicLinqHelper); + dynamicLinqHelper = null; + } } - - return dynamic; } - - /// - /// 将字符串符号转成运算枚举符号 - /// - public static LinkSymbol ChangeLinkSymbol(string str) + public static string FormatValue(string str) { - // 这里判断链接符号 - // 当链接符号为Empty,则说明当前对象为表达式的最左边 - // 如果一个表达式出现两次链接符号为空,则说明输入的字符串格式有问题 - switch (str) - { - case "|": - return LinkSymbol.OrElse; - case "&": - return LinkSymbol.AndAlso; - default: - return LinkSymbol.Empty; - } + return str.TrimStart('"').TrimEnd('"'); + // return str.TrimStart('"').TrimEnd('"').Replace(@"\""", @""""); } + /// /// 将运算枚举符号转成具体使用方法 /// @@ -259,7 +263,7 @@ public static Dictionary GetOperationSymbol() { foreach (var name in attr.Name.Split(';')) { - _operatingSystems.Add(name, (OperationSymbol)item.GetValue(null)); + _operatingSystems.Add(name.ToLower(), (OperationSymbol) item.GetValue(null)); } } } @@ -278,7 +282,7 @@ public static Dictionary GetLinkSymbol() { foreach (var name in attr.Name.Split(';')) { - _linkSymbols.Add(name, (LinkSymbol)item.GetValue(null)); + _linkSymbols.Add(name, (LinkSymbol) item.GetValue(null)); } } } @@ -289,22 +293,18 @@ public static Dictionary GetLinkSymbol() public static string FormatString(string str) { - StringBuilder sb = new StringBuilder(); - int firstIndex = -1; - int lastIndex = -1; - for (int i = 0; i < str.Length; i++) + var sb = new StringBuilder(); + var firstIndex = -1; + var lastIndex = -1; + for (var i = 0; i < str.Length; i++) { var character = str[i]; if (firstIndex == -1) { if (character.IsNullOrEmpty() && i < str.Length - 2) - { if ('"'.Equals(str[i + 1])) - { firstIndex = i + 1; - } - } } else { @@ -312,17 +312,17 @@ public static string FormatString(string str) { var andIndex = str.IndexOf("\" &", firstIndex); var orIndex = str.IndexOf("\" |", firstIndex); - var andOrIndex = andIndex > 0 ? andIndex : orIndex; + var andOrIndex = Math.Min(andIndex, orIndex); + andOrIndex = andOrIndex == -1 ? Math.Max(andOrIndex, orIndex) : andOrIndex; + if (andOrIndex != -1) { lastIndex = andOrIndex; } else { - if (i == str.Length - 1 || str[i + 1].IsNullOrEmpty()) - { - lastIndex = i; - } + if (i == firstIndex) continue; + if (i == str.Length - 1 || str[i + 1].IsNullOrEmpty()) lastIndex = i; } } @@ -338,10 +338,7 @@ public static string FormatString(string str) } } - if (firstIndex != -1) - { - continue; - } + if (firstIndex != -1) continue; sb.Append(character); } @@ -353,45 +350,25 @@ public static string FormatString(string str) public static readonly string _pattern = @"\s*(" + string.Join("|", new string[] { // operators and punctuation that are longer than one char: longest first - string.Join("|", new[] { "||", "&&", "==", "!=", "<=", ">=", "like", "contains" }.Select(Regex.Escape)), + string.Join("|", new[] + { + "||", "&&", "==", "!=", "<=", ">=", + "in", + "like", "contains", "%=", + "startslike", "StartsLike", "startscontains", "StartsContains", "%>", + "endlike", "EndLike", "endcontains", "EndContains", "%<", + }.Select(Regex.Escape)), @"""(?:\\.|[^""])*""", // string @"\d+(?:\.\d+)?", // number with optional decimal part @"\w+", // word @"\S", // other 1-char tokens (or eat up one character in case of an error) }) + @")\s*"; - /// - /// 将字符串符号转成运算枚举符号 - /// - public static OperationSymbol ChangeOperationSymbol(string str) - { - switch (str) - { - case "<": - return OperationSymbol.LessThan; - case "<=": - return OperationSymbol.LessThanOrEqual; - case ">": - return OperationSymbol.GreaterThan; - case ">=": - return OperationSymbol.GreaterThanOrEqual; - case "==": - case "=": - return OperationSymbol.Equal; - case "!=": - return OperationSymbol.NotEqual; - case "contains": - case "like": - return OperationSymbol.Contains; - } - - throw new Exception("OperationSymbol IS NULL"); - } /// /// 将运算枚举符号转成具体使用方法 /// - public static Expression ChangeOperationSymbol(OperationSymbol symbol, Expression key, object right) + public static Expression ChangeOperationSymbol(OperationSymbol? symbol, Expression key, object right) { // 将右边数据类型强行转换成左边一样的类型 // 两者如果Type不匹配则无法接下去的运算操作,抛出异常 @@ -422,45 +399,40 @@ public static Expression ChangeOperationSymbol(OperationSymbol symbol, Expressio { if (key.Type == typeof(string)) return key.Contains(Expression.Constant(newTypeRight)); //对string 特殊处理 由于string - else - return key.GreaterThan(Expression.Constant((newTypeRight))); + return key.GreaterThan(Expression.Constant((newTypeRight))); } case OperationSymbol.GreaterThanOrEqual: { if (key.Type == typeof(string)) return key.Contains(Expression.Constant(newTypeRight, typeof(string))); - else - return key.GreaterThanOrEqual(Expression.Constant(newTypeRight)); + return key.GreaterThanOrEqual(Expression.Constant(newTypeRight)); } case OperationSymbol.LessThan: { if (key.Type == typeof(string)) return key.Contains(Expression.Constant(newTypeRight, typeof(string))); - else - return key.LessThan(Expression.Constant((newTypeRight))); + return key.LessThan(Expression.Constant((newTypeRight))); } case OperationSymbol.LessThanOrEqual: { if (key.Type == typeof(string)) return key.Contains(Expression.Constant(newTypeRight, typeof(string))); - else - return key.LessThanOrEqual(Expression.Constant((newTypeRight))); + return key.LessThanOrEqual(Expression.Constant((newTypeRight))); } case OperationSymbol.NotEqual: return key.NotEqual(Expression.Constant(newTypeRight)); case OperationSymbol.Contains: return key.Contains(Expression.Constant(newTypeRight)); + case OperationSymbol.StartsContains: + return key.StartContains(Expression.Constant(newTypeRight)); + case OperationSymbol.EndContains: + return key.EndContains(Expression.Constant(newTypeRight)); case OperationSymbol.In: - var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public) - .Single(x => x.Name == "Contains" && x.GetParameters().Length == 2) - .MakeGenericMethod(key.Type); return Expression.Constant(newTypeRight).Contains(key); - - //return Expression.Call(contains, , key); + default: + throw new ArgumentException("OperationSymbol IS NULL"); } - - throw new NotImplementedException("OperationSymbol IS NULL"); } } @@ -476,10 +448,20 @@ public class DynamicLinqHelper public string Right { get; set; } [Display(Name = "运算符")] - public OperationSymbol OperationSymbol { get; set; } + public OperationSymbol? OperationSymbol { get; set; } [Display(Name = "连接符")] public LinkSymbol LinkSymbol { get; set; } + + /// + /// 是否是合并 用于括号 + /// + public bool IsMerge { get; set; } = false; + + /// + /// 再有括号时候使用 + /// + public List Child { get; set; } } /// @@ -504,9 +486,15 @@ public enum OperationSymbol [Display(Name = "in")] In, - [Display(Name = "like;contains")] + [Display(Name = "like;contains;%=")] Contains, + [Display(Name = "StartsLike;StartsContains;%>")] + StartsContains, + + [Display(Name = "EndLike;EndContains;%<")] + EndContains, + [Display(Name = ">")] GreaterThan, @@ -528,331 +516,6 @@ public enum OperationSymbol #endregion - /// - /// Linq扩展 - /// - public static class ExpressionExtensions - { - #region Nacos NamingService - - private static readonly HttpClient httpclient = new HttpClient(); - - private static string GetServiceUrl(Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl) - { - try - { - var instance = serv.SelectOneHealthyInstance(ServiceName, Group).GetAwaiter().GetResult(); - var host = $"{instance.Ip}:{instance.Port}"; - if (instance.Metadata.ContainsKey("endpoint")) host = instance.Metadata["endpoint"]; - - - var baseUrl = instance.Metadata.TryGetValue("secure", out _) - ? $"https://{host}" - : $"http://{host}"; - - if (string.IsNullOrWhiteSpace(baseUrl)) - { - return ""; - } - - return $"{baseUrl}{apiurl}"; - } - catch (Exception e) - { - Console.WriteLine(e.Message); - } - - return ""; - } - - public static async Task Cof_NaoceGet(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, Dictionary Parameters = null) - { - try - { - var url = GetServiceUrl(serv, ServiceName, Group, apiurl); - if (string.IsNullOrEmpty(url)) return ""; - if (Parameters != null && Parameters.Any()) - { - StringBuilder sb = new StringBuilder(); - foreach (var pitem in Parameters) - { - sb.Append($"{pitem.Key}={pitem.Value}&"); - } - - url = $"{url}?{sb.ToString().Trim('&')}"; - } - - httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var result = await httpclient.GetAsync(url); - return await result.Content.ReadAsStringAsync(); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - } - - return ""; - } - - public static async Task Cof_NaocePostForm(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, Dictionary Parameters) - { - try - { - var url = GetServiceUrl(serv, ServiceName, Group, apiurl); - if (string.IsNullOrEmpty(url)) return ""; - - var content = (Parameters != null && Parameters.Any()) ? new FormUrlEncodedContent(Parameters) : null; - httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var result = await httpclient.PostAsync(url, content); - return await result.Content.ReadAsStringAsync(); //.GetAwaiter().GetResult(); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - } - - return ""; - } - - public static async Task Cof_NaocePostJson(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, string jSonData) - { - try - { - var url = GetServiceUrl(serv, ServiceName, Group, apiurl); - if (string.IsNullOrEmpty(url)) return ""; - httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var result = await httpclient.PostAsync(url, new StringContent(jSonData, Encoding.UTF8, "application/json")); - return await result.Content.ReadAsStringAsync(); //.GetAwaiter().GetResult(); - - //httpClient.BaseAddress = new Uri("https://www.testapi.com"); - //httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - //httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - } - - return ""; - } - - public static async Task Cof_NaocePostFile(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, Dictionary Parameters) - { - try - { - var url = GetServiceUrl(serv, ServiceName, Group, apiurl); - if (string.IsNullOrEmpty(url)) return ""; - - var content = new MultipartFormDataContent(); - foreach (var pitem in Parameters) - { - content.Add(new ByteArrayContent(pitem.Value), "files", pitem.Key); - } - - httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var result = await httpclient.PostAsync(url, content); - return await result.Content.ReadAsStringAsync(); //.GetAwaiter().GetResult(); - } - catch (Exception e) - { - //InfluxdbHelper.GetInstance().AddLog("Cof_NaocePostFile.Err", ee); - Console.WriteLine(e.Message); - } - - return ""; - } - - #endregion - - #region HttpContext - - /// - /// 返回请求上下文 - /// - /// - /// - /// - /// - /// - public static async Task Cof_SendResponse(this HttpContext context, System.Net.HttpStatusCode code, string message, string ContentType = "text/html;charset=utf-8") - { - context.Response.StatusCode = (int)code; - context.Response.ContentType = ContentType; - await context.Response.WriteAsync(message); - } - - #endregion - - #region ICaching - - /// - /// 从缓存里取数据,如果不存在则执行查询方法, - /// - /// 类型 - /// ICaching - /// 键值 - /// 查询方法 - /// 有效期 单位分钟/param> - /// - public static T Cof_GetICaching(this ICaching cache, string key, Func GetFun, int timeSpanMin) where T : class - { - var obj = cache.Get(key); - obj = GetFun(); - if (obj == null) - { - obj = GetFun(); - cache.Set(key, obj, timeSpanMin); - } - - return obj as T; - } - - /// - /// 异步从缓存里取数据,如果不存在则执行查询方法 - /// - /// 类型 - /// ICaching - /// 键值 - /// 查询方法 - /// 有效期 单位分钟/param> - /// - public static async Task Cof_AsyncGetICaching(this ICaching cache, string key, Func> GetFun, int timeSpanMin) where T : class - { - var obj = cache.Get(key); - if (obj == null) - { - obj = await GetFun(); - cache.Set(key, obj, timeSpanMin); - } - - return obj as T; - } - - #endregion - - #region 常用扩展方法 - - public static bool Cof_CheckAvailable(this IEnumerable Tlist) - { - return Tlist != null && Tlist.Count() > 0; - } - - /// - /// 调用内部方法 - /// - public static Expression Call(this Expression instance, string methodName, params Expression[] arguments) - { - if (instance.Type == typeof(string)) - return Expression.Call(instance, instance.Type.GetMethod(methodName, new Type[] { typeof(string) }), arguments); //修复string contains 出现的问题 Ambiguous match found. - else - return Expression.Call(instance, instance.Type.GetMethod(methodName), arguments); - } - - /// - /// 获取内部成员 - /// - public static Expression Property(this Expression expression, string propertyName) - { - // Todo:左边条件如果是dynamic, - // 则Expression.Property无法获取子内容 - // 报错在这里,由于expression内的对象为Object,所以无法解析到 - // var x = (expression as IQueryable).ElementType; - var exp = Expression.Property(expression, propertyName); - if (exp.Type.IsGenericType && exp.Type.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - return Expression.Convert(exp, exp.Type.GetGenericArguments()[0]); - } - - return exp; - } - - /// - /// 转Lambda - /// - public static Expression ToLambda(this Expression body, - params ParameterExpression[] parameters) - { - return Expression.Lambda(body, parameters); - } - - #endregion - - #region 常用运算符 [ > , >= , == , < , <= , != , || , && ] - - /// - /// && - /// - public static Expression AndAlso(this Expression left, Expression right) - { - return Expression.AndAlso(left, right); - } - - /// - /// || - /// - public static Expression OrElse(this Expression left, Expression right) - { - return Expression.OrElse(left, right); - } - - /// - /// Contains - /// - public static Expression Contains(this Expression left, Expression right) - { - return left.Call("Contains", right); - } - - /// - /// > - /// - public static Expression GreaterThan(this Expression left, Expression right) - { - return Expression.GreaterThan(left, right); - } - - /// - /// >= - /// - public static Expression GreaterThanOrEqual(this Expression left, Expression right) - { - return Expression.GreaterThanOrEqual(left, right); - } - - /// - /// < - /// - public static Expression LessThan(this Expression left, Expression right) - { - return Expression.LessThan(left, right); - } - - /// - /// <= - /// - public static Expression LessThanOrEqual(this Expression left, Expression right) - { - return Expression.LessThanOrEqual(left, right); - } - - /// - /// == - /// - public static Expression Equal(this Expression left, Expression right) - { - return Expression.Equal(left, right); - } - - /// - /// != - /// - public static Expression NotEqual(this Expression left, Expression right) - { - return Expression.NotEqual(left, right); - } - - #endregion - } /// /// Queryable扩展 @@ -896,8 +559,9 @@ public static IOrderedQueryable ISort(this IQueryable var parameter = Expression.Parameter(type, "p"); var propertyAccess = Expression.MakeMemberAccess(parameter, property); var orderByExpression = Expression.Lambda(propertyAccess, parameter); - var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExpression)); - return (IOrderedQueryable)source.Provider.CreateQuery(resultExpression); + var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] {type, property.PropertyType}, source.Expression, + Expression.Quote(orderByExpression)); + return (IOrderedQueryable) source.Provider.CreateQuery(resultExpression); } /// @@ -924,7 +588,7 @@ public static IQueryable ISkip(this IQueryable source // 调用的方法 "Skip", // 元素类别 - new Type[] { source.ElementType }, + new Type[] {source.ElementType}, // 调用的表达树 source.Expression, // 参数 @@ -942,7 +606,7 @@ public static IQueryable ITake(this IQueryable source // 调用的方法 "Take", // 元素类别 - new Type[] { source.ElementType }, + new Type[] {source.ElementType}, // 调用的表达树 source.Expression, // 参数 @@ -974,7 +638,8 @@ public static IEnumerable IDistinctBy(this IEnumerable + /// 判断类型是否实现某个泛型 + /// + /// 类型 + /// 泛型类型 + /// bool + public static bool HasImplementedRawGeneric(this Type type, Type generic) + { + // 检查接口类型 + var isTheRawGenericType = type.GetInterfaces().Any(IsTheRawGenericType); + if (isTheRawGenericType) return true; + + // 检查类型 + while (type != null && type != typeof(object)) + { + isTheRawGenericType = IsTheRawGenericType(type); + if (isTheRawGenericType) return true; + type = type.BaseType; + } + + return false; + + // 判断逻辑 + bool IsTheRawGenericType(Type t) => generic == (t.IsGenericType ? t.GetGenericTypeDefinition() : t); + } + + public static string GetGenericTypeName(this Type type) + { + var typeName = string.Empty; + + if (type.IsGenericType) + { + var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray()); + typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>"; + } + else + { + typeName = type.Name; + } + + return typeName; + } + + public static string GetGenericTypeName(this object @object) + { + return @object.GetType().GetGenericTypeName(); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/HttpHelper.cs b/Blog.Core.Common/Helper/HttpHelper.cs index 4bd14883..82f9c32f 100644 --- a/Blog.Core.Common/Helper/HttpHelper.cs +++ b/Blog.Core.Common/Helper/HttpHelper.cs @@ -10,21 +10,23 @@ namespace Blog.Core.Common.Helper /// public class HttpHelper { + public static readonly HttpClient Httpclient = new HttpClient(); + public static async Task GetAsync(string serviceAddress) { try { string result = string.Empty; Uri getUrl = new Uri(serviceAddress); - using var httpClient = new HttpClient(); - httpClient.Timeout = new TimeSpan(0, 0, 60); - result = await httpClient.GetAsync(serviceAddress).Result.Content.ReadAsStringAsync(); + Httpclient.Timeout = new TimeSpan(0, 0, 60); + result = await Httpclient.GetAsync(serviceAddress).Result.Content.ReadAsStringAsync(); return result; } catch (Exception e) { Console.WriteLine(e.Message); } + return null; } @@ -38,19 +40,19 @@ public static async Task PostAsync(string serviceAddress, string request using (HttpContent httpContent = new StringContent(requestJson)) { httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - using var httpClient = new HttpClient(); - httpClient.Timeout = new TimeSpan(0, 0, 60); - result = await httpClient.PostAsync(serviceAddress, httpContent).Result.Content.ReadAsStringAsync(); + + Httpclient.Timeout = new TimeSpan(0, 0, 60); + result = await Httpclient.PostAsync(serviceAddress, httpContent).Result.Content.ReadAsStringAsync(); } + return result; } catch (Exception e) { Console.WriteLine(e.Message); } + return null; } } - - -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/NumberConverter.cs b/Blog.Core.Common/Helper/NumberConverter.cs new file mode 100644 index 00000000..4232a75f --- /dev/null +++ b/Blog.Core.Common/Helper/NumberConverter.cs @@ -0,0 +1,174 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Helper +{ + /// + /// + /// 大数据json序列化重写 + /// + public sealed class NumberConverter : JsonConverter + { + /// + /// 转换成字符串的类型 + /// + private readonly NumberConverterShip _ship; + + /// + /// 大数据json序列化重写实例化 + /// + public NumberConverter() + { + _ship = (NumberConverterShip)0xFF; + } + + /// + /// 大数据json序列化重写实例化 + /// + /// 转换成字符串的类型 + public NumberConverter(NumberConverterShip ship) + { + _ship = ship; + } + + /// + /// + /// 确定此实例是否可以转换指定的对象类型。 + /// + /// 对象的类型。 + /// 如果此实例可以转换指定的对象类型,则为:true,否则为:false + public override bool CanConvert(Type objectType) + { + var typecode = Type.GetTypeCode(objectType.Name.Equals("Nullable`1") ? objectType.GetGenericArguments().First() : objectType); + switch (typecode) + { + case TypeCode.Decimal: + return (_ship & NumberConverterShip.Decimal) == NumberConverterShip.Decimal; + case TypeCode.Double: + return (_ship & NumberConverterShip.Double) == NumberConverterShip.Double; + case TypeCode.Int64: + return (_ship & NumberConverterShip.Int64) == NumberConverterShip.Int64; + case TypeCode.UInt64: + return (_ship & NumberConverterShip.UInt64) == NumberConverterShip.UInt64; + case TypeCode.Single: + return (_ship & NumberConverterShip.Single) == NumberConverterShip.Single; + default: return false; + } + } + + /// + /// + /// 读取对象的JSON表示。 + /// + /// 中读取。 + /// 对象的类型。 + /// 正在读取的对象的现有值。 + /// 调用的序列化器实例。 + /// 对象值。 + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return AsType(reader.Value.ObjToString(), objectType); + } + + /// + /// 字符串格式数据转其他类型数据 + /// + /// 输入的字符串 + /// 目标格式 + /// 转换结果 + public static object AsType(string input, Type destinationType) + { + try + { + var converter = TypeDescriptor.GetConverter(destinationType); + if (converter.CanConvertFrom(typeof(string))) + { + return converter.ConvertFrom(null, null, input); + } + + converter = TypeDescriptor.GetConverter(typeof(string)); + if (converter.CanConvertTo(destinationType)) + { + return converter.ConvertTo(null, null, input, destinationType); + } + } + catch + { + return null; + } + return null; + } + + /// + /// + /// 写入对象的JSON表示形式。 + /// + /// 要写入的 。 + /// 要写入对象值 + /// 调用的序列化器实例。 + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + } + else + { + var objectType = value.GetType(); + var typeCode = Type.GetTypeCode(objectType.Name.Equals("Nullable`1") ? objectType.GetGenericArguments().First() : objectType); + switch (typeCode) + { + case TypeCode.Decimal: + writer.WriteValue(((decimal)value).ToString("f6")); + break; + case TypeCode.Double: + writer.WriteValue(((double)value).ToString("f4")); + break; + case TypeCode.Single: + writer.WriteValue(((float)value).ToString("f2")); + break; + default: + writer.WriteValue(value.ToString()); + break; + } + } + } + } + + /// + /// 转换成字符串的类型 + /// + [Flags] + public enum NumberConverterShip + { + /// + /// 长整数 + /// + Int64 = 1, + + /// + /// 无符号长整数 + /// + UInt64 = 2, + + /// + /// 浮点数 + /// + Single = 4, + + /// + /// 双精度浮点数 + /// + Double = 8, + + /// + /// 大数字 + /// + Decimal =16 + } +} diff --git a/Blog.Core.Common/Helper/RecursionHelper.cs b/Blog.Core.Common/Helper/RecursionHelper.cs index 9b27a37d..8b9874db 100644 --- a/Blog.Core.Common/Helper/RecursionHelper.cs +++ b/Blog.Core.Common/Helper/RecursionHelper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; namespace Blog.Core.Common.Helper @@ -8,9 +9,8 @@ namespace Blog.Core.Common.Helper /// public static class RecursionHelper { - public static void LoopToAppendChildren(List all, PermissionTree curItem, int pid, bool needbtn) + public static void LoopToAppendChildren(List all, PermissionTree curItem, long pid, bool needbtn) { - var subItems = all.Where(ee => ee.Pid == curItem.value).ToList(); var btnItems = subItems.Where(ss => ss.isbtn == true).ToList(); @@ -28,6 +28,7 @@ public static void LoopToAppendChildren(List all, PermissionTree { subItems = subItems.Where(ss => ss.isbtn == false).ToList(); } + if (subItems.Count > 0) { curItem.children = new List(); @@ -49,14 +50,14 @@ public static void LoopToAppendChildren(List all, PermissionTree { //subItem.disabled = true;//禁用当前节点 } + LoopToAppendChildren(all, subItem, pid, needbtn); } } - public static void LoopToAppendChildren(List all, DepartmentTree curItem, int pid) + public static void LoopToAppendChildren(List all, DepartmentTree curItem, long pid) { - var subItems = all.Where(ee => ee.Pid == curItem.value).ToList(); - + if (subItems.Count > 0) { curItem.children = new List(); @@ -73,15 +74,14 @@ public static void LoopToAppendChildren(List all, DepartmentTree { //subItem.disabled = true;//禁用当前节点 } + LoopToAppendChildren(all, subItem, pid); } } - public static void LoopNaviBarAppendChildren(List all, NavigationBar curItem) { - var subItems = all.Where(ee => ee.pid == curItem.id).ToList(); if (subItems.Count > 0) @@ -102,7 +102,6 @@ public static void LoopNaviBarAppendChildren(List all, Navigation } - public static void LoopToAppendChildrenT(List all, T curItem, string parentIdName = "Pid", string idName = "value", string childrenName = "children") { var subItems = all.Where(ee => ee.GetType().GetProperty(parentIdName).GetValue(ee, null).ToString() == curItem.GetType().GetProperty(idName).GetValue(curItem, null).ToString()).ToList(); @@ -113,12 +112,47 @@ public static void LoopToAppendChildrenT(List all, T curItem, string paren LoopToAppendChildrenT(all, subItem); } } + + /// + /// 将父子级数据结构转换为普通list + /// + /// + /// + public static List TreeToList(List list, Action> action = null) + { + List results = new List(); + foreach (var item in list) + { + results.Add(item); + OperationChildData(results, item, action); + } + + return results; + } + + /// + /// 递归子级数据 + /// + /// 树形列表数据 + /// Item + public static void OperationChildData(List allList, T item, Action> action) + { + dynamic dynItem = item; + if (dynItem.Children == null) return; + if (dynItem.Children.Count <= 0) return; + allList.AddRange(dynItem.Children); + foreach (var subItem in dynItem.Children) + { + action?.Invoke(item, subItem, allList); + OperationChildData(allList, subItem, action); + } + } } public class PermissionTree { - public int value { get; set; } - public int Pid { get; set; } + public long value { get; set; } + public long Pid { get; set; } public string label { get; set; } public int order { get; set; } public bool isbtn { get; set; } @@ -139,8 +173,8 @@ public class DepartmentTree public class NavigationBar { - public int id { get; set; } - public int pid { get; set; } + public long id { get; set; } + public long pid { get; set; } public int order { get; set; } public string name { get; set; } public bool IsHide { get; set; } = false; @@ -158,15 +192,14 @@ public class NavigationBarMeta public bool requireAuth { get; set; } = true; public bool NoTabPage { get; set; } = false; public bool keepAlive { get; set; } = false; - - + public string icon { get; set; } } public class NavigationBarPro { - public int id { get; set; } - public int parentId { get; set; } + public long id { get; set; } + public long parentId { get; set; } public int order { get; set; } public string name { get; set; } public bool IsHide { get; set; } = false; @@ -184,4 +217,4 @@ public class NavigationBarMetaPro public string icon { get; set; } public bool show { get; set; } = false; } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/SM/SM4.cs b/Blog.Core.Common/Helper/SM/SM4.cs index 4b1f1996..b4aa3ebf 100644 --- a/Blog.Core.Common/Helper/SM/SM4.cs +++ b/Blog.Core.Common/Helper/SM/SM4.cs @@ -10,7 +10,9 @@ public class SM4 private long GET_ULONG_BE(SByte[] b, int i) { +#pragma warning disable CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符 long n2 = (b[i] & 0xFF) << 24 | (b[(i + 1)] & 0xFF) << 16 | (b[(i + 2)] & 0xFF) << 8 | b[(i + 3)] & 0xFF & 0xFFFFFFFF; +#pragma warning restore CS0675 // 对进行了带符号扩展的操作数使用了按位或运算符 return n2; } @@ -207,7 +209,6 @@ public SByte[] sm4_crypt_ecb(SM4_Context ctx, SByte[] input) int length = input.Length; SByte[] bins = new SByte[length]; SByte[] bous = new SByte[length]; - SByte[] output = null; Array.Copy(input, 0, bins, 0, length); diff --git a/Blog.Core.Common/Helper/UtilConvert.cs b/Blog.Core.Common/Helper/UtilConvert.cs index 282989e3..a75005ac 100644 --- a/Blog.Core.Common/Helper/UtilConvert.cs +++ b/Blog.Core.Common/Helper/UtilConvert.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; namespace Blog.Core @@ -43,6 +44,18 @@ public static int ObjToInt(this object thisValue, int errorValue) return errorValue; } + public static long ObjToLong(this object thisValue) + { + long reval = 0; + if (thisValue == null) return 0; + if (thisValue != DBNull.Value && long.TryParse(thisValue.ToString(), out reval)) + { + return reval; + } + + return reval; + } + /// /// /// @@ -94,7 +107,8 @@ public static string ObjToString(this object thisValue) /// public static bool IsNotEmptyOrNull(this object thisValue) { - return ObjToString(thisValue) != "" && ObjToString(thisValue) != "undefined" && ObjToString(thisValue) != "null"; + return ObjToString(thisValue) != "" && ObjToString(thisValue) != "undefined" && + ObjToString(thisValue) != "null"; } /// @@ -109,7 +123,8 @@ public static string ObjToString(this object thisValue, string errorValue) return errorValue; } - public static bool IsNullOrEmpty(this object thisValue) => thisValue == null || thisValue == DBNull.Value || string.IsNullOrWhiteSpace(thisValue.ToString()); + public static bool IsNullOrEmpty(this object thisValue) => thisValue == null || thisValue == DBNull.Value || + string.IsNullOrWhiteSpace(thisValue.ToString()); /// /// @@ -156,6 +171,16 @@ public static DateTime ObjToDate(this object thisValue) { reval = Convert.ToDateTime(thisValue); } + else + { + //时间戳转为时间 + var seconds = ObjToLong(thisValue); + if (seconds > 0) + { + var startTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1), TimeZoneInfo.Local); + reval = startTime.AddSeconds(Convert.ToDouble(thisValue)); + } + } return reval; } @@ -222,7 +247,7 @@ public static object ChangeType(this object value, Type type) { Type innerType = type.GetGenericArguments()[0]; object innerValue = ChangeType(value, innerType); - return Activator.CreateInstance(type, new object[] { innerValue }); + return Activator.CreateInstance(type, new object[] {innerValue}); } if (value is string && type == typeof(Guid)) return new Guid(value as string); @@ -265,7 +290,7 @@ public static object ChangeTypeList(this object value, Type type) .Remove(split.Length - 2, 1); } - addMethod.Invoke(lis, new object[] { ChangeType(str, type) }); + addMethod.Invoke(lis, new object[] {ChangeType(str, type)}); } } @@ -276,5 +301,12 @@ public static string ToJson(this object value) { return JsonConvert.SerializeObject(value); } + + public static bool AnyNoException(this ICollection source) + { + if (source == null) return false; + + return source.Any() && source.All(s => s != null); + } } } \ No newline at end of file diff --git a/Blog.Core.Common/Helper/XmlHelper.cs b/Blog.Core.Common/Helper/XmlHelper.cs index 510c84a1..f81d6817 100644 --- a/Blog.Core.Common/Helper/XmlHelper.cs +++ b/Blog.Core.Common/Helper/XmlHelper.cs @@ -1,33 +1,40 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Xml.Serialization; namespace Blog.Core.Common.Helper { + /// + /// xml序列化帮助类 + /// public class XmlHelper { + /// + /// 存储序列类型,防止内存泄漏 + /// + private static ConcurrentDictionary hasTypes = new ConcurrentDictionary(); /// /// 转换对象为JSON格式数据 /// /// /// 对象 /// 字符格式的JSON数据 - public static string GetXML(object obj) + public static string GetXML(object obj, string rootName = "root") { - try + XmlSerializer xs; + var xsType = typeof(T); + hasTypes.TryGetValue(xsType, out xs); + if(xs == null) { - XmlSerializer xs = new XmlSerializer(typeof(T)); - - using (TextWriter tw = new StringWriter()) - { - xs.Serialize(tw, obj); - return tw.ToString(); - } + xs = new XmlSerializer(typeof(T)); + hasTypes.TryAdd(xsType, xs); } - catch (Exception) + using (TextWriter tw = new StringWriter()) { - return string.Empty; + xs.Serialize(tw, obj); + return tw.ObjToString(); } } @@ -37,15 +44,20 @@ public static string GetXML(object obj) /// /// /// - public static T ParseFormByXml(string xml,string rootName="root") + public static T ParseFormByXml(string xml, string rootName = "root") { - XmlSerializer serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(rootName)); - StringReader reader = new StringReader(xml); - - T res = (T)serializer.Deserialize(reader); - reader.Close(); - reader.Dispose(); - return res; - } + XmlSerializer xs; + var xsType = typeof(T); + hasTypes.TryGetValue(xsType, out xs); + if (xs == null) + { + xs = new XmlSerializer(xsType, new XmlRootAttribute(rootName)); + hasTypes.TryAdd(xsType, xs); + } + using (StringReader reader = new StringReader(xml)) + { + return (T)xs.Deserialize(reader); + } + } } } diff --git a/Blog.Core.Common/HttpContextUser/AspNetUser.cs b/Blog.Core.Common/HttpContextUser/AspNetUser.cs index e4e27d87..dfa87949 100644 --- a/Blog.Core.Common/HttpContextUser/AspNetUser.cs +++ b/Blog.Core.Common/HttpContextUser/AspNetUser.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Security.Claims; +using Blog.Core.Common.Swagger; using Blog.Core.Model; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; namespace Blog.Core.Common.HttpContextUser { @@ -31,7 +33,9 @@ private string GetName() { if (!string.IsNullOrEmpty(GetToken())) { - var getNameType = Permissions.IsUseIds4 ? "name" : "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"; + var getNameType = Permissions.IsUseIds4 + ? "name" + : "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"; return GetUserInfoFromToken(getNameType).FirstOrDefault().ObjToString(); } } @@ -39,17 +43,40 @@ private string GetName() return ""; } - public int ID => GetClaimValueByType("jti").FirstOrDefault().ObjToInt(); + public long ID => GetClaimValueByType("jti").FirstOrDefault().ObjToLong(); + public long TenantId => GetClaimValueByType("TenantId").FirstOrDefault().ObjToLong(); public bool IsAuthenticated() { - return _accessor.HttpContext.User.Identity.IsAuthenticated; + return _accessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false; } public string GetToken() { - return _accessor.HttpContext?.Request?.Headers["Authorization"].ObjToString().Replace("Bearer ", ""); + var token = _accessor.HttpContext?.Request?.Headers["Authorization"].ObjToString().Replace("Bearer ", ""); + if (!token.IsNullOrEmpty()) + { + return token; + } + + if (_accessor.HttpContext?.IsSuccessSwagger() == true) + { + token = _accessor.HttpContext.GetSuccessSwaggerJwt(); + if (token.IsNotEmptyOrNull()) + { + if (_accessor.HttpContext.User.Claims.Any(s => s.Type == JwtRegisteredClaimNames.Jti)) + { + return token; + } + + var claims = new ClaimsIdentity(GetClaimsIdentity(token)); + _accessor.HttpContext.User.AddIdentity(claims); + return token; + } + } + + return token; } public List GetUserInfoFromToken(string ClaimType) @@ -64,8 +91,8 @@ public List GetUserInfoFromToken(string ClaimType) JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(token); return (from item in jwtToken.Claims - where item.Type == ClaimType - select item.Value).ToList(); + where item.Type == ClaimType + select item.Value).ToList(); } return new List() { }; @@ -75,6 +102,10 @@ public List GetUserInfoFromToken(string ClaimType) public IEnumerable GetClaimsIdentity() { + if (_accessor.HttpContext == null) return ArraySegment.Empty; + + if (!IsAuthenticated()) return GetClaimsIdentity(GetToken()); + var claims = _accessor.HttpContext.User.Claims.ToList(); var headers = _accessor.HttpContext.Request.Headers; foreach (var header in headers) @@ -85,13 +116,25 @@ public IEnumerable GetClaimsIdentity() return claims; } - public List GetClaimValueByType(string ClaimType) + public IEnumerable GetClaimsIdentity(string token) { + var jwtHandler = new JwtSecurityTokenHandler(); + // token校验 + if (token.IsNotEmptyOrNull() && jwtHandler.CanReadToken(token)) + { + var jwtToken = jwtHandler.ReadJwtToken(token); - return (from item in GetClaimsIdentity() - where item.Type == ClaimType - select item.Value).ToList(); + return jwtToken.Claims; + } + + return new List(); + } + public List GetClaimValueByType(string ClaimType) + { + return (from item in GetClaimsIdentity() + where item.Type == ClaimType + select item.Value).ToList(); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/HttpContextUser/IUser.cs b/Blog.Core.Common/HttpContextUser/IUser.cs index beef3b7d..aa6094b1 100644 --- a/Blog.Core.Common/HttpContextUser/IUser.cs +++ b/Blog.Core.Common/HttpContextUser/IUser.cs @@ -7,7 +7,8 @@ namespace Blog.Core.Common.HttpContextUser public interface IUser { string Name { get; } - int ID { get; } + long ID { get; } + long TenantId { get; } bool IsAuthenticated(); IEnumerable GetClaimsIdentity(); List GetClaimValueByType(string ClaimType); @@ -17,4 +18,4 @@ public interface IUser MessageModel MessageModel { get; set; } } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Https/FluentHttpResponseStream.cs b/Blog.Core.Common/Https/FluentHttpResponseStream.cs new file mode 100644 index 00000000..c668f47d --- /dev/null +++ b/Blog.Core.Common/Https/FluentHttpResponseStream.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Blog.Core.Common.Https; + +/// +/// 扩展 HttpResponseStream
+/// 原始[HttpResponseStream]实际上只是个包装类,内部包装了[HttpResponsePipeWriter]来进行写入响应数据 +///
+public class FluentHttpResponseStream : Stream +{ + private readonly IHttpBodyControlFeature _bodyControl; + private readonly IHttpResponseBodyFeature _pipeWriter; + private readonly MemoryStream _stream = new(); + + public FluentHttpResponseStream(IHttpResponseBodyFeature pipeWriter, IHttpBodyControlFeature bodyControl) + { + _pipeWriter = pipeWriter; + _bodyControl = bodyControl; + } + + public override bool CanRead => _stream.CanRead; + + public override bool CanSeek => _stream.CanSeek; + + public override bool CanWrite => _stream.CanWrite; + + public override long Length => _stream.Length; + + public override long Position { get => _stream.Position; set => _stream.Position = value; } + + public override void Flush() + { + if (!_bodyControl.AllowSynchronousIO) + { + throw new InvalidOperationException("SynchronousWritesDisallowed "); + } + _stream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _stream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _stream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _stream.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + WriteAsync(buffer, offset, count, default).GetAwaiter().GetResult(); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + _stream.Write(buffer, offset, count); + return _pipeWriter.Writer.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); + } + + protected override void Dispose(bool disposing) + { + _stream.Dispose(); + base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/HttpPolly/HttpPollyHelper.cs b/Blog.Core.Common/Https/HttpPolly/HttpPollyHelper.cs similarity index 98% rename from Blog.Core.Common/HttpPolly/HttpPollyHelper.cs rename to Blog.Core.Common/Https/HttpPolly/HttpPollyHelper.cs index f1a1e84c..1187d711 100644 --- a/Blog.Core.Common/HttpPolly/HttpPollyHelper.cs +++ b/Blog.Core.Common/Https/HttpPolly/HttpPollyHelper.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading.Tasks; -namespace Blog.Core.Common.HttpPolly +namespace Blog.Core.Common.Https.HttpPolly { public class HttpPollyHelper : IHttpPollyHelper { @@ -35,7 +35,7 @@ public async Task PostAsync(HttpEnum httpEnum, string url, R request, D var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); var response = await client.PostAsync(url, stringContent); - + if (response.StatusCode == System.Net.HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); @@ -72,7 +72,7 @@ public async Task PostAsync(HttpEnum httpEnum, string url, string request, var stringContent = new StringContent(request, Encoding.UTF8, "application/json"); var response = await client.PostAsync(url, stringContent); - + if (response.StatusCode == System.Net.HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); @@ -110,7 +110,7 @@ public async Task PostAsync(HttpEnum httpEnum, string url, R request, var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); var response = await client.PostAsync(url, stringContent); - + if (response.StatusCode == System.Net.HttpStatusCode.OK) { return await response.Content.ReadAsStringAsync(); @@ -146,7 +146,7 @@ public async Task PostAsync(HttpEnum httpEnum, string url, string reques var stringContent = new StringContent(request, Encoding.UTF8, "application/json"); var response = await client.PostAsync(url, stringContent); - + if (response.StatusCode == System.Net.HttpStatusCode.OK) { return await response.Content.ReadAsStringAsync(); @@ -182,7 +182,7 @@ public async Task GetAsync(HttpEnum httpEnum, string url, Dictionary GetAsync(HttpEnum httpEnum, string url, Dictionary PutAsync(HttpEnum httpEnum, string url, R request, Di var stringContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); var response = await client.PutAsync(url, stringContent); - + if (response.StatusCode == System.Net.HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); @@ -294,7 +294,7 @@ public async Task PutAsync(HttpEnum httpEnum, string url, string request, var stringContent = new StringContent(request, Encoding.UTF8, "application/json"); var response = await client.PutAsync(url, stringContent); - + if (response.StatusCode == System.Net.HttpStatusCode.OK) { string result = await response.Content.ReadAsStringAsync(); @@ -331,7 +331,7 @@ public async Task DeleteAsync(HttpEnum httpEnum, string url, Dictionary(context, "X-Forwarded-For")).FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(ip)) + ip = SplitCsv(GetHeaderValueAs(context, "X-Real-IP")).FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(ip) && context.Connection?.RemoteIpAddress != null) + ip = context.Connection.RemoteIpAddress.ToString(); + + if (string.IsNullOrWhiteSpace(ip)) + ip = GetHeaderValueAs(context, "REMOTE_ADDR"); + + return ip; + } + + public static bool IsLocal(this HttpContext context) + { + return GetRequestIp(context) is "127.0.0.1" or "::1" || context.Request?.IsLocal() == true; + } + + + public static bool IsLocal(this HttpRequest req) + { + var connection = req.HttpContext.Connection; + if (connection.RemoteIpAddress != null) + { + if (connection.LocalIpAddress != null) + { + return connection.RemoteIpAddress.Equals(connection.LocalIpAddress); + } + else + { + return IPAddress.IsLoopback(connection.RemoteIpAddress); + } + } + + // for in memory TestServer or when dealing with default connection info + if (connection.RemoteIpAddress == null && connection.LocalIpAddress == null) + { + return true; + } + + return false; + } + + + private static T GetHeaderValueAs(HttpContext context, string headerName) + { + if (context.Request?.Headers?.TryGetValue(headerName, out var values) ?? false) + { + string rawValues = values.ToString(); + + if (!string.IsNullOrWhiteSpace(rawValues)) + return (T) Convert.ChangeType(values.ToString(), typeof(T)); + } + + return default; + } + + private static List SplitCsv(string csvList) + { + if (string.IsNullOrWhiteSpace(csvList)) + return new List(); + + return csvList + .TrimEnd(',') + .Split(',') + .AsEnumerable() + .Select(s => s.Trim()) + .ToList(); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Hubs/ChatHub.cs b/Blog.Core.Common/Hubs/ChatHub.cs index ff15c97f..63803d2f 100644 --- a/Blog.Core.Common/Hubs/ChatHub.cs +++ b/Blog.Core.Common/Hubs/ChatHub.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; using Blog.Core.Common; using Blog.Core.Common.LogHelper; using Microsoft.AspNetCore.SignalR; @@ -53,10 +55,23 @@ public async Task SendPrivateMessage(string user, string message) /// 当连接建立时运行 ///
/// - public override Task OnConnectedAsync() + public override async Task OnConnectedAsync() { - //TODO.. - return base.OnConnectedAsync(); + await base.OnConnectedAsync(); + if (Context.User?.Identity?.IsAuthenticated == true) + { + //按用户分组 + //是有必要的 例如多个浏览器、多个标签页使用同个用户登录 应当归属于一组 + await AddToGroup(Context.User.Identity.Name); + + //加入角色组 + //根据角色分组 例如管理员分组发送管理员的消息 + var roles = Context.User.Claims.Where(s => s.Type == ClaimTypes.Role).ToList(); + foreach (var role in roles) + { + await AddToGroup(role.Value); + } + } } /// @@ -83,13 +98,12 @@ public async Task GetLatestCount(string random) //2、服务端主动向客户端发送数据,名字千万不能错 if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) { - await Clients.All.ReceiveUpdate(LogLock.GetLogData()); + //TODO 主动发送错误消息 + await Clients.All.ReceiveUpdate("这是一个LOG"); } - //3、客户端再通过 ReceiveUpdate ,来接收 - } } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/LogHelper/LogContextExtension.cs b/Blog.Core.Common/LogHelper/LogContextExtension.cs new file mode 100644 index 00000000..573ba057 --- /dev/null +++ b/Blog.Core.Common/LogHelper/LogContextExtension.cs @@ -0,0 +1,42 @@ +using Serilog.Context; +using SqlSugar; +using System; +using System.Collections.Generic; + +namespace Blog.Core.Common.LogHelper; + +public class LogContextExtension : IDisposable +{ + private readonly Stack _disposableStack = new Stack(); + + public static LogContextExtension Create => new(); + + public void AddStock(IDisposable disposable) + { + _disposableStack.Push(disposable); + } + + public IDisposable SqlAopPushProperty(ISqlSugarClient db) + { + AddStock(LogContext.PushProperty(LogContextStatic.LogSource, LogContextStatic.AopSql)); + AddStock(LogContext.PushProperty(LogContextStatic.SqlOutToConsole, + AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToConsole", "Enabled" }).ObjToBool())); + AddStock(LogContext.PushProperty(LogContextStatic.SqlOutToFile, + AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToFile", "Enabled" }).ObjToBool())); + AddStock(LogContext.PushProperty(LogContextStatic.OutToDb, + AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToDB", "Enabled" }).ObjToBool())); + + AddStock(LogContext.PushProperty(LogContextStatic.SugarActionType, db.SugarActionType)); + + return this; + } + + + public void Dispose() + { + while (_disposableStack.Count > 0) + { + _disposableStack.Pop().Dispose(); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/LogHelper/LogContextStatic.cs b/Blog.Core.Common/LogHelper/LogContextStatic.cs new file mode 100644 index 00000000..52a8167d --- /dev/null +++ b/Blog.Core.Common/LogHelper/LogContextStatic.cs @@ -0,0 +1,42 @@ +using System.IO; + +namespace Blog.Core.Common.LogHelper; + +public class LogContextStatic +{ + static LogContextStatic() + { + if (!Directory.Exists(BaseLogs)) + { + Directory.CreateDirectory(BaseLogs); + } + } + + public static readonly string BaseLogs = "Logs"; + public static readonly string BasePathLogs = @"Logs"; + + public static readonly string LogSource = "LogSource"; + public static readonly string AopSql = "AopSql"; + public static readonly string SqlOutToConsole = "OutToConsole"; + public static readonly string SqlOutToFile = "SqlOutToFile"; + public static readonly string OutToDb = "OutToDb"; + public static readonly string SugarActionType = "SugarActionType"; + + public static readonly string FileMessageTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100); + + + public static string Combine(string path1) + { + return Path.Combine(BaseLogs, path1); + } + + public static string Combine(string path1, string path2) + { + return Path.Combine(BaseLogs, path1, path2); + } + + public static string Combine(string path1, string path2, string path3) + { + return Path.Combine(BaseLogs, path1, path2, path3); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/LogHelper/LogLock.cs b/Blog.Core.Common/LogHelper/LogLock.cs deleted file mode 100644 index 70c5f7f2..00000000 --- a/Blog.Core.Common/LogHelper/LogLock.cs +++ /dev/null @@ -1,593 +0,0 @@ -using Blog.Core.Common.Helper; -using log4net; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Blog.Core.Common.LogHelper -{ - public class LogLock - { - private static readonly ILog log = LogManager.GetLogger(typeof(LogLock)); - static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); - static int WritedCount = 0; - static int FailedCount = 0; - static string _contentRoot = string.Empty; - - public LogLock(string contentPath) - { - _contentRoot = contentPath; - } - - public static void OutLogAOP(string prefix, string traceId, string[] dataParas, bool IsHeader = true) - { - string AppSetingNodeName = "AppSettings"; - string AppSetingName = "LogAOP"; - switch (prefix) - { - case "AOPLog": - AppSetingName = "LogAOP"; - break; - case "AOPLogEx": - AppSetingName = "LogAOP"; - break; - case "RequestIpInfoLog": - AppSetingNodeName = "Middleware"; - AppSetingName = "IPLog"; - break; - case "RecordAccessLogs": - AppSetingNodeName = "Middleware"; - AppSetingName = "RecordAccessLogs"; - break; - case "SqlLog": - AppSetingName = "SqlAOP"; - break; - case "RequestResponseLog": - AppSetingNodeName = "Middleware"; - AppSetingName = "RequestResponseLog"; - break; - default: - break; - } - if (AppSettings.app(new string[] { AppSetingNodeName, AppSetingName, "Enabled" }).ObjToBool()) - { - if (AppSettings.app(new string[] { AppSetingNodeName, AppSetingName, "LogToDB", "Enabled" }).ObjToBool()) - { - OutSql2LogToDB(prefix, traceId, dataParas, IsHeader); - } - if (AppSettings.app(new string[] { AppSetingNodeName, AppSetingName, "LogToFile", "Enabled" }).ObjToBool()) - { - OutSql2LogToFile(prefix, traceId, dataParas, IsHeader); - } - } - - //if (AppSettings.app(new string[] { "AppSettings", "LogFile", "Enabled" }).ObjToBool()) - //{ - // OutSql2LogFile(prefix, dataParas, IsHeader); - //} - //else - //{ - // OutSql2Log(prefix, dataParas, IsHeader); - //} - } - - public static void OutSql2LogToFile(string prefix, string traceId, string[] dataParas, bool IsHeader = true, bool isWrt = false) - { - try - { - //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入 - //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。 - // 从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度 - // 因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常 - LogWriteLock.EnterWriteLock(); - - var folderPath = Path.Combine(_contentRoot, "Log"); - if (!Directory.Exists(folderPath)) - { - Directory.CreateDirectory(folderPath); - } - //string logFilePath = Path.Combine(path, $@"{filename}.log"); - var logFilePath = FileHelper.GetAvailableFileWithPrefixOrderSize(folderPath, prefix); - switch (prefix) - { - case "AOPLog": - AOPLogInfo apiLogAopInfo = JsonConvert.DeserializeObject(dataParas[1]); - //记录被拦截方法信息的日志信息 - var dataIntercept = "" + - $"【操作时间】:{apiLogAopInfo.RequestTime}\r\n" + - $"【当前操作用户】:{ apiLogAopInfo.OpUserName} \r\n" + - $"【当前执行方法】:{ apiLogAopInfo.RequestMethodName} \r\n" + - $"【携带的参数有】: {apiLogAopInfo.RequestParamsName} \r\n" + - $"【携带的参数JSON】: {apiLogAopInfo.RequestParamsData} \r\n" + - $"【响应时间】:{apiLogAopInfo.ResponseIntervalTime}\r\n" + - $"【执行完成时间】:{apiLogAopInfo.ResponseTime}\r\n" + - $"【执行完成结果】:{apiLogAopInfo.ResponseJsonData}\r\n"; - dataParas = new string[] { dataIntercept }; - break; - case "AOPLogEx": - AOPLogExInfo apiLogAopExInfo = JsonConvert.DeserializeObject(dataParas[1]); - var dataInterceptEx = "" + - $"【操作时间】:{apiLogAopExInfo.ApiLogAopInfo.RequestTime}\r\n" + - $"【当前操作用户】:{ apiLogAopExInfo.ApiLogAopInfo.OpUserName} \r\n" + - $"【当前执行方法】:{ apiLogAopExInfo.ApiLogAopInfo.RequestMethodName} \r\n" + - $"【携带的参数有】: {apiLogAopExInfo.ApiLogAopInfo.RequestParamsName} \r\n" + - $"【携带的参数JSON】: {apiLogAopExInfo.ApiLogAopInfo.RequestParamsData} \r\n" + - $"【响应时间】:{apiLogAopExInfo.ApiLogAopInfo.ResponseIntervalTime}\r\n" + - $"【执行完成时间】:{apiLogAopExInfo.ApiLogAopInfo.ResponseTime}\r\n" + - $"【执行完成结果】:{apiLogAopExInfo.ApiLogAopInfo.ResponseJsonData}\r\n" + - $"【执行完成异常信息】:方法中出现异常:{apiLogAopExInfo.ExMessage}\r\n" + - $"【执行完成异常】:方法中出现异常:{apiLogAopExInfo.InnerException}\r\n"; - dataParas = new string[] { dataInterceptEx }; - break; - } - var now = DateTime.Now; - string logContent = String.Join("\r\n", dataParas); - if (IsHeader) - { - logContent = ( - "--------------------------------\r\n" + - DateTime.Now + "|\r\n" + - String.Join("\r\n", dataParas) + "\r\n" - ); - } - else - { - logContent = ( - dataParas[1] + ",\r\n" - ); - } - - //if (logContent.IsNotEmptyOrNull() && logContent.Length > 500) - //{ - // logContent = logContent.Substring(0, 500) + "\r\n"; - //} - if (isWrt) - { - File.WriteAllText(logFilePath, logContent); - - } - else - { - File.AppendAllText(logFilePath, logContent); - } - WritedCount++; - } - catch (Exception e) - { - Console.Write(e.Message); - FailedCount++; - } - finally - { - //退出写入模式,释放资源占用 - //注意:一次请求对应一次释放 - // 若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放] - // 若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定] - LogWriteLock.ExitWriteLock(); - } - } - public static void OutSql2LogToDB(string prefix, string traceId, string[] dataParas, bool IsHeader = true) - { - log4net.LogicalThreadContext.Properties["LogType"] = prefix; - log4net.LogicalThreadContext.Properties["TraceId"] = traceId; - if (dataParas.Length >= 2) - { - log4net.LogicalThreadContext.Properties["DataType"] = dataParas[0]; - } - - dataParas = dataParas.Skip(1).ToArray(); - - string logContent = String.Join("", dataParas); - if (IsHeader) - { - logContent = (String.Join("", dataParas)); - } - switch (prefix) - { - //DEBUG | INFO | WARN | ERROR | FATAL - case "AOPLog": - log.Info(logContent); - break; - case "AOPLogEx": - log.Error(logContent); - break; - case "RequestIpInfoLog": - log.Debug(logContent); - break; - case "RecordAccessLogs": - log.Debug(logContent); - break; - case "SqlLog": - log.Info(logContent); - break; - case "RequestResponseLog": - log.Debug(logContent); - break; - default: - break; - } - - } - /// - /// 读取文件内容 - /// - /// 文件夹路径 - /// 文件名 - /// 编码 - /// 读取类型(0:精准,1:前缀模糊) - /// - public static string ReadLog(string folderPath, string fileName, Encoding encode, ReadType readType = ReadType.Accurate, int takeOnlyTop = -1) - { - string s = ""; - try - { - LogWriteLock.EnterReadLock(); - - // 根据文件名读取当前文件内容 - if (readType == ReadType.Accurate) - { - var filePath = Path.Combine(folderPath, fileName); - if (!File.Exists(filePath)) - { - s = null; - } - else - { - StreamReader f2 = new StreamReader(filePath, encode); - s = f2.ReadToEnd(); - f2.Close(); - f2.Dispose(); - } - } - - // 根据前缀读取所有文件内容 - if (readType == ReadType.Prefix) - { - var allFiles = new DirectoryInfo(folderPath); - var selectFiles = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(fileName.ToLower())).ToList(); - - selectFiles = takeOnlyTop > 0 ? selectFiles.OrderByDescending(d => d.Name).Take(takeOnlyTop).ToList() : selectFiles; - - foreach (var item in selectFiles) - { - if (File.Exists(item.FullName)) - { - StreamReader f2 = new StreamReader(item.FullName, encode); - s += f2.ReadToEnd(); - f2.Close(); - f2.Dispose(); - } - } - } - - // 根据前缀读取 最新文件 时间倒叙 - if (readType == ReadType.PrefixLatest) - { - var allFiles = new DirectoryInfo(folderPath); - var selectLastestFile = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(fileName.ToLower())).OrderByDescending(d => d.Name).FirstOrDefault(); - - if (selectLastestFile != null && File.Exists(selectLastestFile.FullName)) - { - StreamReader f2 = new StreamReader(selectLastestFile.FullName, encode); - s = f2.ReadToEnd(); - f2.Close(); - f2.Dispose(); - } - } - } - catch (Exception) - { - FailedCount++; - } - finally - { - LogWriteLock.ExitReadLock(); - } - return s; - } - - private static List GetRequestInfo(ReadType readType) - { - List requestInfos = new(); - var accessLogs = ReadLog(Path.Combine(_contentRoot, "Log"), "RequestIpInfoLog_", Encoding.UTF8, readType).ObjToString(); - try - { - return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\r\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); - requestInfos.Add(accItem); - } - catch (Exception) - { - } - } - } - - } - - return requestInfos; - } - - - public static List GetLogData() - { - List aopLogs = new List(); - List excLogs = new List(); - List sqlLogs = new List(); - List reqresLogs = new List(); - - try - { - var aoplogContent = ReadLog(Path.Combine(_contentRoot, "Log"), "AOPLog_", Encoding.UTF8, ReadType.Prefix); - - if (!string.IsNullOrEmpty(aoplogContent)) - { - aopLogs = aoplogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = d.Split("|")[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
"), - LogColor = "AOP", - }).ToList(); - } - } - catch (Exception) { } - - try - { - var exclogContent = ReadLog(Path.Combine(_contentRoot, "Log"), $"GlobalExceptionLogs_{DateTime.Now.ToString("yyyMMdd")}.log", Encoding.UTF8); - - if (!string.IsNullOrEmpty(exclogContent)) - { - excLogs = exclogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = (d.Split("|")[0]).Split(',')[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
"), - LogColor = "EXC", - Import = 9, - }).ToList(); - } - } - catch (Exception) { } - - - try - { - var sqllogContent = ReadLog(Path.Combine(_contentRoot, "Log"), "SqlLog_", Encoding.UTF8, ReadType.PrefixLatest); - - if (!string.IsNullOrEmpty(sqllogContent)) - { - sqlLogs = sqllogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = d.Split("|")[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
"), - LogColor = "SQL", - }).ToList(); - } - } - catch (Exception) { } - - //try - //{ - // reqresLogs = ReadLog(Path.Combine(_contentRoot, "Log", "RequestResponseLog.log"), Encoding.UTF8)? - // .Split("--------------------------------") - // .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - // .Select(d => new LogInfo - // { - // Datetime = d.Split("|")[0].ObjToDate(), - // Content = d.Split("|")[1]?.Replace("\r\n", "
"), - // LogColor = "ReqRes", - // }).ToList(); - //} - //catch (Exception) - //{ - //} - - try - { - var Logs = GetRequestInfo(ReadType.PrefixLatest); - - Logs = Logs.Where(d => d.Datetime.ObjToDate() >= DateTime.Today).ToList(); - - reqresLogs = Logs.Select(d => new LogInfo - { - Datetime = d.Datetime.ObjToDate(), - Content = $"IP:{d.Ip}
{d.Url}", - LogColor = "ReqRes", - }).ToList(); - } - catch (Exception) - { - } - - if (excLogs != null) - { - aopLogs.AddRange(excLogs); - } - if (sqlLogs != null) - { - aopLogs.AddRange(sqlLogs); - } - if (reqresLogs != null) - { - aopLogs.AddRange(reqresLogs); - } - aopLogs = aopLogs.OrderByDescending(d => d.Import).ThenByDescending(d => d.Datetime).Take(100).ToList(); - - return aopLogs; - } - - - public static RequestApiWeekView RequestApiinfoByWeek() - { - List Logs = new List(); - List apiWeeks = new List(); - string apiWeeksJson = string.Empty; - List columns = new List(); - columns.Add("日期"); - - - try - { - Logs = GetRequestInfo(ReadType.Prefix); - - apiWeeks = (from n in Logs - group n by new { n.Week, n.Url } into g - select new ApiWeek - { - week = g.Key.Week, - url = g.Key.Url, - count = g.Count(), - }).ToList(); - - //apiWeeks = apiWeeks.OrderByDescending(d => d.count).Take(8).ToList(); - - } - catch (Exception) - { - } - - StringBuilder jsonBuilder = new StringBuilder(); - jsonBuilder.Append("["); - - var weeks = apiWeeks.GroupBy(x => new { x.week }).Select(s => s.First()).ToList(); - foreach (var week in weeks) - { - var apiweeksCurrentWeek = apiWeeks.Where(d => d.week == week.week).OrderByDescending(d => d.count).Take(5).ToList(); - jsonBuilder.Append("{"); - - jsonBuilder.Append("\""); - jsonBuilder.Append("日期"); - jsonBuilder.Append("\":\""); - jsonBuilder.Append(week.week); - jsonBuilder.Append("\","); - - foreach (var item in apiweeksCurrentWeek) - { - columns.Add(item.url); - jsonBuilder.Append("\""); - jsonBuilder.Append(item.url); - jsonBuilder.Append("\":\""); - jsonBuilder.Append(item.count); - jsonBuilder.Append("\","); - } - if (apiweeksCurrentWeek.Count > 0) - { - jsonBuilder.Remove(jsonBuilder.Length - 1, 1); - } - jsonBuilder.Append("},"); - } - - if (weeks.Count > 0) - { - jsonBuilder.Remove(jsonBuilder.Length - 1, 1); - } - jsonBuilder.Append("]"); - - //columns.AddRange(apiWeeks.OrderByDescending(d => d.count).Take(8).Select(d => d.url).ToList()); - columns = columns.Distinct().ToList(); - - return new RequestApiWeekView() - { - columns = columns, - rows = jsonBuilder.ToString(), - }; - } - - public static AccessApiDateView AccessApiByDate() - { - List Logs = new List(); - List apiDates = new List(); - try - { - Logs = GetRequestInfo(ReadType.Prefix); - - apiDates = (from n in Logs - group n by new { n.Date } into g - select new ApiDate - { - date = g.Key.Date, - count = g.Count(), - }).ToList(); - - apiDates = apiDates.OrderByDescending(d => d.date).Take(7).ToList(); - - } - catch (Exception) - { - } - - return new AccessApiDateView() - { - columns = new string[] { "date", "count" }, - rows = apiDates.OrderBy(d => d.date).ToList(), - }; - } - - public static AccessApiDateView AccessApiByHour() - { - List Logs = new List(); - List apiDates = new List(); - try - { - Logs = GetRequestInfo(ReadType.Prefix); - - apiDates = (from n in Logs - where n.Datetime.ObjToDate() >= DateTime.Today - group n by new { hour = n.Datetime.ObjToDate().Hour } into g - select new ApiDate - { - date = g.Key.hour.ToString("00"), - count = g.Count(), - }).ToList(); - - apiDates = apiDates.OrderBy(d => d.date).Take(24).ToList(); - - } - catch (Exception) - { - } - - return new AccessApiDateView() - { - columns = new string[] { "date", "count" }, - rows = apiDates, - }; - } - } - - public enum ReadType - { - /// - /// 精确查找一个 - /// - Accurate, - /// - /// 指定前缀,模糊查找全部 - /// - Prefix, - /// - /// 指定前缀,最新一个文件 - /// - PrefixLatest - } - -} diff --git a/Blog.Core.Common/LogHelper/Seri/SerilogServer.cs b/Blog.Core.Common/LogHelper/Seri/SerilogServer.cs deleted file mode 100644 index 98affb9e..00000000 --- a/Blog.Core.Common/LogHelper/Seri/SerilogServer.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Blog.Core.Common.Helper; -using Blog.Core.Serilog.Es; -using Blog.Core.Serilog.Es.Formatters; -using Serilog; -using Serilog.Events; -using System; -using System.IO; - -namespace Blog.Core.Common.LogHelper -{ - public class SerilogServer - { - /// - /// 记录日常日志 - /// - /// - /// - /// - public static void WriteLog(string filename, string[] dataParas, bool IsHeader = true, string defaultFolder = "", bool isJudgeJsonFormat = false) - { - Log.Logger = new LoggerConfiguration() - // TCPSink 集成Serilog 使用tcp的方式向elk 输出log日志 LogstashJsonFormatter 这个是按照自定义格式化输出内容 - .WriteTo.TCPSink(new LogstashJsonFormatter()) - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Error) - //.WriteTo.File(Path.Combine($"log/Serilog/{filename}/", ".log"), rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}") - .WriteTo.File(Path.Combine("Log", defaultFolder, $"{filename}.log"), - rollingInterval: RollingInterval.Infinite, - outputTemplate: "{Message}{NewLine}{Exception}") - - // 将日志托送到远程ES - // docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d --name ES01 elasticsearch:7.2.0 - //.Enrich.FromLogContext() - //.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://x.xxx.xx.xx:9200/")) - //{ - // AutoRegisterTemplate = true, - //}) - - .CreateLogger(); - - var now = DateTime.Now; - string logContent = String.Join("\r\n", dataParas); - var isJsonFormat = true; - if (isJudgeJsonFormat) - { - var judCont = logContent.Substring(0, logContent.LastIndexOf(",")); - isJsonFormat = JsonHelper.IsJson(judCont); - } - - if (isJsonFormat) - { - if (IsHeader) - { - logContent = ( - "--------------------------------\r\n" + - DateTime.Now + "|\r\n" + - String.Join("\r\n", dataParas) + "\r\n" - ); - } - // 展示elk支持输出4种日志级别 - Log.Information(logContent); - //Log.Warning(logContent); - //Log.Error(logContent); - //Log.Debug(logContent); - } - else - { - Console.WriteLine("【JSON格式异常:】"+logContent + now.ObjToString()); - } - Log.CloseAndFlush(); - } - /// - /// 记录异常日志 - /// - /// - /// - /// - public static void WriteErrorLog(string filename, string message, Exception ex) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .MinimumLevel.Override("Microsoft", LogEventLevel.Error) - .WriteTo.File(Path.Combine($"log/Error/{filename}/", ".txt"), rollingInterval: RollingInterval.Day) - .CreateLogger(); - Log.Error(ex, message); - Log.CloseAndFlush(); - } - } -} diff --git a/Blog.Core.Common/MemoryCache/ICachingProvider.cs b/Blog.Core.Common/MemoryCache/ICachingProvider.cs deleted file mode 100644 index e6a06529..00000000 --- a/Blog.Core.Common/MemoryCache/ICachingProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Blog.Core.Common -{ - /// - /// 简单的缓存接口,只有查询和添加,以后会进行扩展 - /// - public interface ICaching - { - object Get(string cacheKey); - - void Set(string cacheKey, object cacheValue, int timeSpan); - } -} diff --git a/Blog.Core.Common/MemoryCache/MemoryCaching.cs b/Blog.Core.Common/MemoryCache/MemoryCaching.cs deleted file mode 100644 index fc9a60c9..00000000 --- a/Blog.Core.Common/MemoryCache/MemoryCaching.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using System; - -namespace Blog.Core.Common -{ - /// - /// 实例化缓存接口ICaching - /// - public class MemoryCaching : ICaching - { - //引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了 - private readonly IMemoryCache _cache; - //还是通过构造函数的方法,获取 - public MemoryCaching(IMemoryCache cache) - { - _cache = cache; - } - - public object Get(string cacheKey) - { - return _cache.Get(cacheKey); - } - - public void Set(string cacheKey, object cacheValue,int timeSpan) - { - _cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(timeSpan * 60)); - } - } - -} diff --git a/Blog.Core.Common/Option/AppSettingsOption.cs b/Blog.Core.Common/Option/AppSettingsOption.cs new file mode 100644 index 00000000..bd8770e2 --- /dev/null +++ b/Blog.Core.Common/Option/AppSettingsOption.cs @@ -0,0 +1,28 @@ +using Blog.Core.Common.Option.Core; + +namespace Blog.Core.Common.Option; + +public class AppSettingsOption : IConfigurableOptions +{ + /// + /// 迁移表结构 + /// + public bool MigrateDBEnabled { get; set; } + + /// + /// 初始化数据 + /// + public bool SeedDBEnabled { get; set; } + + /// + /// 生成测试数据 + /// + public bool TestSeedDbEnabled { get; set; } + + public string Author { get; set; } + + /// + /// 是否启用数据库二级缓存 + /// + public bool CacheDbEnabled { get; set; } = false; +} \ No newline at end of file diff --git a/Blog.Core.Common/Option/Core/ConfigurableOptions.cs b/Blog.Core.Common/Option/Core/ConfigurableOptions.cs new file mode 100644 index 00000000..adeea98a --- /dev/null +++ b/Blog.Core.Common/Option/Core/ConfigurableOptions.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; + +namespace Blog.Core.Common.Option.Core; + +public static class ConfigurableOptions +{ + /// 添加选项配置 + /// 选项类型 + /// 服务集合 + /// 服务集合 + public static IServiceCollection AddConfigurableOptions(this IServiceCollection services) + where TOptions : class, IConfigurableOptions + { + Type optionsType = typeof(TOptions); + string path = GetConfigurationPath(optionsType); + services.Configure(App.Configuration.GetSection(path)); + + return services; + } + + public static IServiceCollection AddConfigurableOptions(this IServiceCollection services, Type type) + { + string path = GetConfigurationPath(type); + var config = App.Configuration.GetSection(path); + + Type iOptionsChangeTokenSource = typeof(IOptionsChangeTokenSource<>); + Type iConfigureOptions = typeof(IConfigureOptions<>); + Type configurationChangeTokenSource = typeof(ConfigurationChangeTokenSource<>); + Type namedConfigureFromConfigurationOptions = typeof(NamedConfigureFromConfigurationOptions<>); + iOptionsChangeTokenSource = iOptionsChangeTokenSource.MakeGenericType(type); + iConfigureOptions = iConfigureOptions.MakeGenericType(type); + configurationChangeTokenSource = configurationChangeTokenSource.MakeGenericType(type); + namedConfigureFromConfigurationOptions = namedConfigureFromConfigurationOptions.MakeGenericType(type); + + services.AddOptions(); + services.AddSingleton(iOptionsChangeTokenSource, + Activator.CreateInstance(configurationChangeTokenSource, Options.DefaultName, config) ?? throw new InvalidOperationException()); + return services.AddSingleton(iConfigureOptions, + Activator.CreateInstance(namedConfigureFromConfigurationOptions, Options.DefaultName, config) ?? throw new InvalidOperationException()); + } + + /// 获取配置路径 + /// 选项类型 + /// + public static string GetConfigurationPath(Type optionsType) + { + var endPath = new[] {"Option", "Options"}; + var configurationPath = optionsType.Name; + foreach (var s in endPath) + { + if (configurationPath.EndsWith(s)) + { + return configurationPath[..^s.Length]; + } + } + + return configurationPath; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Option/Core/IConfigurableOptions.cs b/Blog.Core.Common/Option/Core/IConfigurableOptions.cs new file mode 100644 index 00000000..71568268 --- /dev/null +++ b/Blog.Core.Common/Option/Core/IConfigurableOptions.cs @@ -0,0 +1,11 @@ +namespace Blog.Core.Common.Option.Core; + +/// +/// 应用选项依赖接口
+/// 自动注入配置文件
+/// 文件名为Option或Options结尾 +///
+public interface IConfigurableOptions +{ + +} \ No newline at end of file diff --git a/Blog.Core.Common/Option/RedisOptions.cs b/Blog.Core.Common/Option/RedisOptions.cs new file mode 100644 index 00000000..27326595 --- /dev/null +++ b/Blog.Core.Common/Option/RedisOptions.cs @@ -0,0 +1,24 @@ +using Blog.Core.Common.Option.Core; + +namespace Blog.Core.Common.Option; + +/// +/// 缓存配置选项 +/// +public sealed class RedisOptions : IConfigurableOptions +{ + /// + /// 是否启用 + /// + public bool Enable { get; set; } + + /// + /// Redis连接 + /// + public string ConnectionString { get; set; } + + /// + /// 键值前缀 + /// + public string InstanceName { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Common/Option/SeqOptions.cs b/Blog.Core.Common/Option/SeqOptions.cs new file mode 100644 index 00000000..944aa7bc --- /dev/null +++ b/Blog.Core.Common/Option/SeqOptions.cs @@ -0,0 +1,18 @@ +using Blog.Core.Common.Option.Core; + +namespace Blog.Core.Common.Option; + +public class SeqOptions : IConfigurableOptions +{ + /// + /// 是否启用 + /// + public bool Enabled { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } + + public string ApiKey { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/DBSeed.cs b/Blog.Core.Common/Seed/DBSeed.cs index 9afdc30d..29222802 100644 --- a/Blog.Core.Common/Seed/DBSeed.cs +++ b/Blog.Core.Common/Seed/DBSeed.cs @@ -1,15 +1,16 @@ using Blog.Core.Common.DB; +using Blog.Core.Common.Extensions; using Blog.Core.Common.Helper; using Blog.Core.Model.Models; +using Blog.Core.Model.Tenants; using Magicodes.ExporterAndImporter.Excel; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using SqlSugar; +using System.Diagnostics; using System.Reflection; using System.Text; -using System.Threading.Tasks; +using Blog.Core.Common.Const; +using Microsoft.Data.SqlClient; namespace Blog.Core.Common.Seed { @@ -36,55 +37,49 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) SeedDataFolder = Path.Combine(WebRootPath, SeedDataFolder); Console.WriteLine("************ Blog.Core DataBase Set *****************"); - Console.WriteLine($"Is multi-DataBase: {AppSettings.app(new string[] { "MutiDBEnabled" })}"); - Console.WriteLine($"Is CQRS: {AppSettings.app(new string[] { "CQRSEnabled" })}"); + Console.WriteLine($"Master DB ConId: {myContext.Db.CurrentConnectionConfig.ConfigId}"); + Console.WriteLine($"Master DB Type: {myContext.Db.CurrentConnectionConfig.DbType}"); + Console.WriteLine($"Master DB ConnectString: {myContext.Db.CurrentConnectionConfig.ConnectionString}"); Console.WriteLine(); - Console.WriteLine($"Master DB ConId: {MyContext.ConnId}"); - Console.WriteLine($"Master DB Type: {MyContext.DbType}"); - Console.WriteLine($"Master DB ConnectString: {MyContext.ConnectionString}"); - Console.WriteLine(); - if (AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()) + if (BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException()) { - var slaveIndex = 0; - BaseDBConfig.MutiConnectionString.allDbs.Where(x => x.ConnId != MainDb.CurrentDbConnId).ToList().ForEach(m => + var index = 0; + BaseDBConfig.MainConfig.SlaveConnectionConfigs.ForEach(m => { - slaveIndex++; - Console.WriteLine($"Slave{slaveIndex} DB ID: {m.ConnId}"); - Console.WriteLine($"Slave{slaveIndex} DB Type: {m.DbType}"); - Console.WriteLine($"Slave{slaveIndex} DB ConnectString: {m.Connection}"); + index++; + Console.WriteLine($"Slave{index} DB HitRate: {m.HitRate}"); + Console.WriteLine($"Slave{index} DB ConnectString: {m.ConnectionString}"); Console.WriteLine($"--------------------------------------"); }); } - else if (AppSettings.app(new string[] { "CQRSEnabled" }).ObjToBool()) + else if (BaseDBConfig.ReuseConfigs.AnyNoException()) { - var slaveIndex = 0; - BaseDBConfig.MutiConnectionString.slaveDbs.Where(x => x.ConnId != MainDb.CurrentDbConnId).ToList().ForEach(m => + var index = 0; + BaseDBConfig.ReuseConfigs.ForEach(m => { - slaveIndex++; - Console.WriteLine($"Slave{slaveIndex} DB ID: {m.ConnId}"); - Console.WriteLine($"Slave{slaveIndex} DB Type: {m.DbType}"); - Console.WriteLine($"Slave{slaveIndex} DB ConnectString: {m.Connection}"); + index++; + Console.WriteLine($"Reuse{index} DB ID: {m.ConfigId}"); + Console.WriteLine($"Reuse{index} DB Type: {m.DbType}"); + Console.WriteLine($"Reuse{index} DB ConnectString: {m.ConnectionString}"); Console.WriteLine($"--------------------------------------"); }); } - else - { - } Console.WriteLine(); // 创建数据库 Console.WriteLine($"Create Database(The Db Id:{MyContext.ConnId})..."); - if (MyContext.DbType != SqlSugar.DbType.Oracle) + if (MyContext.DbType != SqlSugar.DbType.Oracle && MyContext.DbType != SqlSugar.DbType.Dm) { myContext.Db.DbMaintenance.CreateDatabase(); + SqlConnection.ClearAllPools(); ConsoleHelper.WriteSuccessLine($"Database created successfully!"); } else { //Oracle 数据库不支持该操作 - ConsoleHelper.WriteSuccessLine($"Oracle 数据库不支持该操作,可手动创建Oracle数据库!"); + ConsoleHelper.WriteSuccessLine($"Oracle 数据库不支持该操作,可手动创建Oracle/Dm数据库!"); } // 创建数据库表,遍历指定命名空间下的class, @@ -92,11 +87,14 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) Console.WriteLine("Create Tables..."); var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; - var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll").Select(Assembly.LoadFrom).ToArray(); + var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll") + .Select(Assembly.LoadFrom).ToArray(); var modelTypes = referencedAssemblies - .SelectMany(a => a.DefinedTypes) - .Select(type => type.AsType()) - .Where(x => x.IsClass && x.Namespace != null && x.Namespace.Equals("Blog.Core.Model.Models")).ToList(); + .SelectMany(a => a.DefinedTypes) + .Select(type => type.AsType()) + .Where(x => x.IsClass && x.Namespace is "Blog.Core.Model.Models") + .Where(s => !s.IsDefined(typeof(MultiTenantAttribute), false)) + .ToList(); modelTypes.ForEach(t => { // 这里只支持添加表,不支持删除 @@ -104,14 +102,12 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) if (!myContext.Db.DbMaintenance.IsAnyTable(t.Name)) { Console.WriteLine(t.Name); - myContext.Db.CodeFirst.InitTables(t); + myContext.Db.CodeFirst.SplitTables().InitTables(t); } }); ConsoleHelper.WriteSuccessLine($"Tables created successfully!"); Console.WriteLine(); - - if (AppSettings.app(new string[] { "AppSettings", "SeedDBDataEnabled" }).ObjToBool()) { JsonSerializerSettings setting = new JsonSerializerSettings(); @@ -135,25 +131,28 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) var importer = new ExcelImporter(); #region BlogArticle + if (!await myContext.Db.Queryable().AnyAsync()) { - myContext.GetEntityDB().InsertRange(JsonHelper.ParseFormByJson>(FileHelper.ReadFile(string.Format(SeedDataFolder, "BlogArticle"), Encoding.UTF8))); + myContext.GetEntityDB().InsertRange( + JsonHelper.ParseFormByJson>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "BlogArticle"), Encoding.UTF8))); Console.WriteLine("Table:BlogArticle created success!"); } else { Console.WriteLine("Table:BlogArticle already exists..."); } + #endregion #region Modules + if (!await myContext.Db.Queryable().AnyAsync()) { - - - - var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Modules"), Encoding.UTF8), setting); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "Modules"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:Modules created success!"); @@ -162,13 +161,16 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:Modules already exists..."); } + #endregion #region Permission + if (!await myContext.Db.Queryable().AnyAsync()) { - var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Permission"), Encoding.UTF8), setting); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "Permission"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:Permission created success!"); @@ -177,16 +179,19 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:Permission already exists..."); } + #endregion #region Role + if (!await myContext.Db.Queryable().AnyAsync()) { - //var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Role"), Encoding.UTF8), setting); - using var stream = new FileStream(Path.Combine(WebRootPath, "BlogCore.Data.excel", "Role.xlsx"), FileMode.Open); - var result = await importer.Import(stream); - var data = result.Data.ToList(); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "Role"), Encoding.UTF8), setting); + //using var stream = new FileStream(Path.Combine(WebRootPath, "BlogCore.Data.excel", "Role.xlsx"), FileMode.Open); + //var result = await importer.Import(stream); + //var data = result.Data.ToList(); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:Role created success!"); @@ -195,13 +200,17 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:Role already exists..."); } + #endregion #region RoleModulePermission + if (!await myContext.Db.Queryable().AnyAsync()) { - var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "RoleModulePermission"), Encoding.UTF8), setting); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "RoleModulePermission"), Encoding.UTF8), + setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:RoleModulePermission created success!"); @@ -210,13 +219,16 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:RoleModulePermission already exists..."); } + #endregion #region Topic + if (!await myContext.Db.Queryable().AnyAsync()) { - var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Topic"), Encoding.UTF8), setting); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "Topic"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:Topic created success!"); @@ -225,13 +237,16 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:Topic already exists..."); } + #endregion #region TopicDetail + if (!await myContext.Db.Queryable().AnyAsync()) { - var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "TopicDetail"), Encoding.UTF8), setting); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "TopicDetail"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:TopicDetail created success!"); @@ -240,16 +255,16 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:TopicDetail already exists..."); } + #endregion #region UserRole + if (!await myContext.Db.Queryable().AnyAsync()) { - //var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "UserRole"), Encoding.UTF8), setting); - using var stream = new FileStream(Path.Combine(WebRootPath, "BlogCore.Data.excel", "UserRole.xlsx"), FileMode.Open); - var result = await importer.Import(stream); - var data = result.Data.ToList(); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "UserRole"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:UserRole created success!"); @@ -258,16 +273,16 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:UserRole already exists..."); } + #endregion #region sysUserInfo + if (!await myContext.Db.Queryable().AnyAsync()) { - //var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "sysUserInfo"), Encoding.UTF8), setting); - using var stream = new FileStream(Path.Combine(WebRootPath, "BlogCore.Data.excel", "SysUserInfo.xlsx"), FileMode.Open); - var result = await importer.Import(stream); - var data = result.Data.ToList(); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "sysUserInfo"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:sysUserInfo created success!"); @@ -276,13 +291,16 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:sysUserInfo already exists..."); } + #endregion #region TasksQz + if (!await myContext.Db.Queryable().AnyAsync()) { - var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "TasksQz"), Encoding.UTF8), setting); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "TasksQz"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:TasksQz created success!"); @@ -291,12 +309,28 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:TasksQz already exists..."); } + + #endregion + + #region TasksLog + + if (!await myContext.Db.Queryable().AnyAsync()) + { + Console.WriteLine("Table:TasksLog created success!"); + } + else + { + Console.WriteLine("Table:TasksLog already exists..."); + } + #endregion #region Department + if (!await myContext.Db.Queryable().AnyAsync()) { - var data = JsonConvert.DeserializeObject>(FileHelper.ReadFile(string.Format(SeedDataFolder, "Department"), Encoding.UTF8), setting); + var data = JsonConvert.DeserializeObject>( + FileHelper.ReadFile(string.Format(SeedDataFolder, "Department"), Encoding.UTF8), setting); myContext.GetEntityDB().InsertRange(data); Console.WriteLine("Table:Department created success!"); @@ -305,13 +339,16 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) { Console.WriteLine("Table:Department already exists..."); } + #endregion + //种子初始化 + await SeedDataAsync(myContext.Db); + ConsoleHelper.WriteSuccessLine($"Done seeding database!"); } Console.WriteLine(); - } catch (Exception ex) { @@ -321,5 +358,288 @@ public static async Task SeedAsync(MyContext myContext, string WebRootPath) "3、其他错误:" + ex.Message); } } + + /// + /// 种子初始化数据 + /// + /// + /// + private static async Task SeedDataAsync(ISqlSugarClient db) + { + // 获取所有种子配置-初始化数据 + var seedDataTypes = AssemblysExtensions.GetAllAssemblies().SelectMany(s => s.DefinedTypes) + .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass) + .Where(u => + { + var esd = u.GetInterfaces() + .FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>))); + if (esd is null) + { + return false; + } + + var eType = esd.GenericTypeArguments[0]; + if (eType.GetCustomAttribute() is null) + { + return true; + } + + return false; + }); + + if (!seedDataTypes.Any()) return; + foreach (var seedType in seedDataTypes) + { + dynamic instance = Activator.CreateInstance(seedType); + //初始化数据 + { + var seedData = instance.InitSeedData(); + if (seedData != null && Enumerable.Any(seedData)) + { + var entityType = seedType.GetInterfaces().First().GetGenericArguments().First(); + var entity = db.EntityMaintenance.GetEntityInfo(entityType); + + if (!await db.Queryable(entity.DbTableName, "").AnyAsync()) + { + await db.Insertable(Enumerable.ToList(seedData)).ExecuteCommandAsync(); + Console.WriteLine($"Table:{entity.DbTableName} init success!"); + } + } + } + + //种子数据 + { + var seedData = instance.SeedData(); + if (seedData != null && Enumerable.Any(seedData)) + { + var entityType = seedType.GetInterfaces().First().GetGenericArguments().First(); + var entity = db.EntityMaintenance.GetEntityInfo(entityType); + + await db.Storageable(Enumerable.ToList(seedData)).ExecuteCommandAsync(); + Console.WriteLine($"Table:{entity.DbTableName} seedData success!"); + } + } + + //自定义处理 + { + await instance.CustomizeSeedData(db); + } + } + } + + /// + /// 迁移日志数据库 + /// + /// + public static void MigrationLogs(MyContext myContext) + { + // 创建数据库表,遍历指定命名空间下的class, + // 注意不要把其他命名空间下的也添加进来。 + Console.WriteLine("Create Log Tables..."); + if (!myContext.Db.IsAnyConnection(SqlSugarConst.LogConfigId.ToLower())) + { + throw new ApplicationException("未配置日志数据库,请在appsettings.json中DBS节点中配置"); + } + + var logDb = myContext.Db.GetConnection(SqlSugarConst.LogConfigId.ToLower()); + Console.WriteLine($"Create log Database(The Db Id:{SqlSugarConst.LogConfigId.ToLower()})..."); + logDb.DbMaintenance.CreateDatabase(); + ConsoleHelper.WriteSuccessLine($"Log Database created successfully!"); + var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; + var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Model.dll") + .Select(Assembly.LoadFrom).ToArray(); + var modelTypes = referencedAssemblies + .SelectMany(a => a.DefinedTypes) + .Select(type => type.AsType()) + .Where(x => x.IsClass && x.Namespace != null && x.Namespace.StartsWith("Blog.Core.Model.Logs")) + .ToList(); + Stopwatch sw = Stopwatch.StartNew(); + + var tables = logDb.DbMaintenance.GetTableInfoList(); + + modelTypes.ForEach(t => + { + // 这里只支持添加修改表,不支持删除 + // 如果想要删除,数据库直接右键删除,或者联系SqlSugar作者; + if (!tables.Any(s => s.Name.Contains(t.Name))) + { + Console.WriteLine(t.Name); + if (t.GetCustomAttribute() != null) + { + logDb.CodeFirst.SplitTables().InitTables(t); + } + else + { + logDb.CodeFirst.InitTables(t); + } + } + }); + + sw.Stop(); + + $"Log Tables created successfully! {sw.ElapsedMilliseconds}ms".WriteSuccessLine(); + Console.WriteLine(); + } + + + /// + /// 初始化 多租户 + /// + /// + /// + public static async Task TenantSeedAsync(MyContext myContext) + { + var tenants = await myContext.Db.Queryable().Where(s => s.TenantType == TenantTypeEnum.Db) + .ToListAsync(); + if (tenants.Any()) + { + Console.WriteLine($@"Init Multi Tenant Db"); + foreach (var tenant in tenants) + { + Console.WriteLine($@"Init Multi Tenant Db : {tenant.ConfigId}/{tenant.Name}"); + await InitTenantSeedAsync(myContext.Db.AsTenant(), tenant.GetConnectionConfig()); + } + } + + tenants = await myContext.Db.Queryable().Where(s => s.TenantType == TenantTypeEnum.Tables) + .ToListAsync(); + if (tenants.Any()) + { + await InitTenantSeedAsync(myContext, tenants); + } + } + + #region 多租户 多表 初始化 + + private static async Task InitTenantSeedAsync(MyContext myContext, List tenants) + { + ConsoleHelper.WriteInfoLine($"Init Multi Tenant Tables : {myContext.Db.CurrentConnectionConfig.ConfigId}"); + + // 获取所有实体表-初始化租户业务表 + var entityTypes = TenantUtil.GetTenantEntityTypes(TenantTypeEnum.Tables); + if (!entityTypes.Any()) return; + + foreach (var sysTenant in tenants) + { + foreach (var entityType in entityTypes) + { + myContext.Db.CodeFirst + .As(entityType, entityType.GetTenantTableName(myContext.Db, sysTenant)) + .InitTables(entityType); + + Console.WriteLine($@"Init Tables:{entityType.GetTenantTableName(myContext.Db, sysTenant)}"); + } + + myContext.Db.SetTenantTable(sysTenant.Id.ToString()); + //多租户初始化种子数据 + await TenantSeedDataAsync(myContext.Db, TenantTypeEnum.Tables); + } + + ConsoleHelper.WriteSuccessLine( + $"Init Multi Tenant Tables : {myContext.Db.CurrentConnectionConfig.ConfigId} created successfully!"); + } + + #endregion + + #region 多租户 多库 初始化 + + /// + /// 初始化多库 + /// + /// + /// + /// + public static async Task InitTenantSeedAsync(ITenant itenant, ConnectionConfig config) + { + itenant.RemoveConnection(config.ConfigId); + itenant.AddConnection(config); + + var db = itenant.GetConnectionScope(config.ConfigId); + + db.DbMaintenance.CreateDatabase(); + ConsoleHelper.WriteSuccessLine($"Init Multi Tenant Db : {config.ConfigId} Database created successfully!"); + + Console.WriteLine($@"Init Multi Tenant Db : {config.ConfigId} Create Tables"); + + // 获取所有实体表-初始化租户业务表 + var entityTypes = TenantUtil.GetTenantEntityTypes(TenantTypeEnum.Db); + if (!entityTypes.Any()) return; + foreach (var entityType in entityTypes) + { + var splitTable = entityType.GetCustomAttribute(); + if (splitTable == null) + db.CodeFirst.InitTables(entityType); + else + db.CodeFirst.SplitTables().InitTables(entityType); + + Console.WriteLine(entityType.Name); + } + + //多租户初始化种子数据 + await TenantSeedDataAsync(db, TenantTypeEnum.Db); + } + + #endregion + + #region 多租户 种子数据 初始化 + + private static async Task TenantSeedDataAsync(ISqlSugarClient db, TenantTypeEnum tenantType) + { + // 获取所有种子配置-初始化数据 + var seedDataTypes = AssemblysExtensions.GetAllAssemblies().SelectMany(s => s.DefinedTypes) + .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass) + .Where(u => + { + var esd = u.GetInterfaces() + .FirstOrDefault(i => i.HasImplementedRawGeneric(typeof(IEntitySeedData<>))); + if (esd is null) + { + return false; + } + + var eType = esd.GenericTypeArguments[0]; + return eType.IsTenantEntity(tenantType); + }); + if (!seedDataTypes.Any()) return; + foreach (var seedType in seedDataTypes) + { + dynamic instance = Activator.CreateInstance(seedType); + //初始化数据 + { + var seedData = instance.InitSeedData(); + if (seedData != null && Enumerable.Any(seedData)) + { + var entityType = seedType.GetInterfaces().First().GetGenericArguments().First(); + var entity = db.EntityMaintenance.GetEntityInfo(entityType); + + if (!await db.Queryable(entity.DbTableName, "").AnyAsync()) + { + await db.Insertable(Enumerable.ToList(seedData)).ExecuteCommandAsync(); + Console.WriteLine($"Table:{entity.DbTableName} init success!"); + } + } + } + + //种子数据 + { + var seedData = instance.SeedData(); + if (seedData != null && Enumerable.Any(seedData)) + { + var entityType = seedType.GetInterfaces().First().GetGenericArguments().First(); + var entity = db.EntityMaintenance.GetEntityInfo(entityType); + + await db.Storageable(Enumerable.ToList(seedData)).ExecuteCommandAsync(); + Console.WriteLine($"Table:{entity.DbTableName} seedData success!"); + } + } + + //自定义处理 + { + await instance.CustomizeSeedData(db); + } + } + } + + #endregion } } \ No newline at end of file diff --git a/Blog.Core.Common/Seed/IEntitySeedData.cs b/Blog.Core.Common/Seed/IEntitySeedData.cs new file mode 100644 index 00000000..3e2f4859 --- /dev/null +++ b/Blog.Core.Common/Seed/IEntitySeedData.cs @@ -0,0 +1,36 @@ +using SqlSugar; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Seed; + +/// +/// 种子数据 接口 +/// +/// +public interface IEntitySeedData + where T : class, new() +{ + /// + /// 初始化种子数据
+ /// 只要表不存在数据,程序启动就会自动初始化 + ///
+ /// + IEnumerable InitSeedData(); + + /// + /// 种子数据
+ /// 存在不操作、不存在Insert
+ /// 适合系统内置数据,项目开发后续增加内置数据 + ///
+ /// + IEnumerable SeedData(); + + /// + /// 自定义操作
+ /// 以上满不足了,可以自己编写 + ///
+ /// + /// + Task CustomizeSeedData(ISqlSugarClient db); +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/SeedData/BusinessDataSeedData.cs b/Blog.Core.Common/Seed/SeedData/BusinessDataSeedData.cs new file mode 100644 index 00000000..361cd725 --- /dev/null +++ b/Blog.Core.Common/Seed/SeedData/BusinessDataSeedData.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Blog.Core.Model.Models; +using SqlSugar; + +namespace Blog.Core.Common.Seed.SeedData; + +/// +/// 初始化 业务数据 +/// +public class BusinessDataSeedData : IEntitySeedData +{ + public IEnumerable InitSeedData() + { + return new[] + { + new BusinessTable() + { + Id = 1, + TenantId = 1000001, + Name = "张三的数据01", + Amount = 150, + IsDeleted = true, + }, + new BusinessTable() + { + Id = 2, + TenantId = 1000001, + Name = "张三的数据02", + Amount = 200, + }, + new BusinessTable() + { + Id = 3, + TenantId = 1000001, + Name = "张三的数据03", + Amount = 250, + }, + new BusinessTable() + { + Id = 4, + TenantId = 1000002, + Name = "李四的数据01", + Amount = 300, + }, + new BusinessTable() + { + Id = 5, + TenantId = 1000002, + Name = "李四的数据02", + Amount = 500, + }, + new BusinessTable() + { + Id = 6, + TenantId = 0, + Name = "公共数据01", + Amount = 16600, + }, + new BusinessTable() + { + Id = 7, + TenantId = 0, + Name = "公共数据02", + Amount = 19800, + }, + }; + } + + public IEnumerable SeedData() + { + return default; + } + + public Task CustomizeSeedData(ISqlSugarClient db) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/SeedData/MultiBusinessDataSeedData.cs b/Blog.Core.Common/Seed/SeedData/MultiBusinessDataSeedData.cs new file mode 100644 index 00000000..4ca1a7dd --- /dev/null +++ b/Blog.Core.Common/Seed/SeedData/MultiBusinessDataSeedData.cs @@ -0,0 +1,38 @@ +using Blog.Core.Model.Models; +using SqlSugar; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Seed.SeedData; + +public class MultiBusinessDataSeedData : IEntitySeedData +{ + public IEnumerable InitSeedData() + { + return new List() + { + new() + { + Id = 1001, + Name = "业务数据1", + Amount = 100, + }, + new() + { + Id = 1002, + Name = "业务数据2", + Amount = 1000, + }, + }; + } + + public IEnumerable SeedData() + { + return default; + } + + public Task CustomizeSeedData(ISqlSugarClient db) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/SeedData/MultiBusinessSubDataSeedData.cs b/Blog.Core.Common/Seed/SeedData/MultiBusinessSubDataSeedData.cs new file mode 100644 index 00000000..e73d4603 --- /dev/null +++ b/Blog.Core.Common/Seed/SeedData/MultiBusinessSubDataSeedData.cs @@ -0,0 +1,38 @@ +using Blog.Core.Model.Models; +using SqlSugar; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Seed.SeedData; + +public class MultiBusinessSubDataSeedData : IEntitySeedData +{ + public IEnumerable InitSeedData() + { + return new List() + { + new() + { + Id = 100, + MainId = 1001, + Memo = "子数据", + }, + new() + { + Id = 1001, + MainId = 1001, + Memo = "子数据2", + }, + }; + } + + public IEnumerable SeedData() + { + return default; + } + + public Task CustomizeSeedData(ISqlSugarClient db) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/SeedData/SubBusinessDataSeedData.cs b/Blog.Core.Common/Seed/SeedData/SubBusinessDataSeedData.cs new file mode 100644 index 00000000..3d7b8937 --- /dev/null +++ b/Blog.Core.Common/Seed/SeedData/SubBusinessDataSeedData.cs @@ -0,0 +1,71 @@ +using Blog.Core.Model.Models; +using SqlSugar; +using System.Collections.Generic; +using System.Threading.Tasks; +using Blog.Core.Common.Utility; + +namespace Blog.Core.Common.Seed.SeedData; + +public class SubBusinessDataSeedData : IEntitySeedData +{ + public IEnumerable InitSeedData() + { + return default; + } + + public IEnumerable SeedData() + { + return default; + } + + public async Task CustomizeSeedData(ISqlSugarClient db) + { + //初始化分库数据 + //只是用于测试 + if (db.CurrentConnectionConfig.ConfigId == "Tenant_3") + { + if (!await db.Queryable().AnyAsync()) + { + await db.Insertable(new List() + { + new() + { + Id = IdGeneratorUtility.NextId(), + Name = "王五业务数据1", + Amount = 100, + }, + new() + { + Id = IdGeneratorUtility.NextId(), + Name = "王五业务数据2", + Amount = 1000, + }, + }).ExecuteReturnSnowflakeIdListAsync(); + } + } + else if (db.CurrentConnectionConfig.ConfigId == "Tenant_4") + { + if (!await db.Queryable().AnyAsync()) + { + await db.Insertable(new List() + { + new() + { + Id = IdGeneratorUtility.NextId(), + Name = "赵六业务数据1", + Amount = 50, + }, + new() + { + Id = IdGeneratorUtility.NextId(), + Name = "赵六业务数据2", + Amount = 60, + }, + }).ExecuteReturnSnowflakeIdListAsync(); + } + } + + + await Task.Delay(1); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/SeedData/TenantSeedData.cs b/Blog.Core.Common/Seed/SeedData/TenantSeedData.cs new file mode 100644 index 00000000..f33f83b2 --- /dev/null +++ b/Blog.Core.Common/Seed/SeedData/TenantSeedData.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Blog.Core.Common.DB; +using Blog.Core.Model.Models; +using Blog.Core.Model.Tenants; +using SqlSugar; + +namespace Blog.Core.Common.Seed.SeedData; + +/// +/// 租户 种子数据 +/// +public class TenantSeedData : IEntitySeedData +{ + public IEnumerable InitSeedData() + { + return new[] + { + new SysTenant() + { + Id = 1000001, + ConfigId = "Tenant_1", + Name = "张三", + TenantType = TenantTypeEnum.Id + }, + new SysTenant() + { + Id = 1000002, + ConfigId = "Tenant_2", + Name = "李四", + TenantType = TenantTypeEnum.Id + }, + new SysTenant() + { + Id = 1000003, + ConfigId = "Tenant_3", + Name = "王五", + TenantType = TenantTypeEnum.Db, + DbType = DbType.Sqlite, + Connection = $"DataSource=" + Path.Combine(Environment.CurrentDirectory, "WangWu.db"), + }, + new SysTenant() + { + Id = 1000004, + ConfigId = "Tenant_4", + Name = "赵六", + TenantType = TenantTypeEnum.Db, + DbType = DbType.Sqlite, + Connection = $"DataSource=" + Path.Combine(Environment.CurrentDirectory, "ZhaoLiu.db"), + }, + new SysTenant() + { + Id = 1000005, + ConfigId = "Tenant_5", + Name = "孙七", + TenantType = TenantTypeEnum.Tables, + }, + }; + } + + public IEnumerable SeedData() + { + return default; + } + + public Task CustomizeSeedData(ISqlSugarClient db) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/SeedData/UserInfoSeedData.cs b/Blog.Core.Common/Seed/SeedData/UserInfoSeedData.cs new file mode 100644 index 00000000..900ca79e --- /dev/null +++ b/Blog.Core.Common/Seed/SeedData/UserInfoSeedData.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blog.Core.Model.Models; +using SqlSugar; + +namespace Blog.Core.Common.Seed.SeedData; + +public class UserInfoSeedData : IEntitySeedData +{ + public IEnumerable InitSeedData() + { + return default; + } + + public IEnumerable SeedData() + { + return default; + } + + public async Task CustomizeSeedData(ISqlSugarClient db) + { + var data = new List() + { + new SysUserInfo() + { + Id = 10001, + LoginName = "zhangsan", + LoginPWD = "E10ADC3949BA59ABBE56E057F20F883E", + Name = "张三", + TenantId = 1000001, //租户Id + }, + new SysUserInfo() + { + Id = 10002, + LoginName = "lisi", + LoginPWD = "E10ADC3949BA59ABBE56E057F20F883E", + Name = "李四", + TenantId = 1000002, //租户Id + }, + new SysUserInfo() + { + Id = 10003, + LoginName = "wangwu", + LoginPWD = "E10ADC3949BA59ABBE56E057F20F883E", + Name = "王五", + TenantId = 1000003, //租户Id + }, + new SysUserInfo() + { + Id = 10004, + LoginName = "zhaoliu", + LoginPWD = "E10ADC3949BA59ABBE56E057F20F883E", + Name = "赵六", + TenantId = 1000004, //租户Id + }, + new SysUserInfo() + { + Id = 10005, + LoginName = "sunqi", + LoginPWD = "E10ADC3949BA59ABBE56E057F20F883E", + Name = "孙七", + TenantId = 1000005, //租户Id + }, + }; + + var names = data.Select(s => s.LoginName).ToList(); + names = await db.Queryable() + .Where(s => names.Contains(s.LoginName)) + .Select(s => s.LoginName).ToListAsync(); + + var sysUserInfos = data.Where(s => !names.Contains(s.LoginName)).ToList(); + if (sysUserInfos.Any()) + { + //await db.Insertable(sysUserInfos).ExecuteReturnIdentityAsync();//postgresql这句会报错 + await db.Insertable(sysUserInfos).ExecuteCommandAsync(); + } + + await Task.CompletedTask; + } +} diff --git a/Blog.Core.Common/Swagger/Filter/EnumSchemaFilter.cs b/Blog.Core.Common/Swagger/Filter/EnumSchemaFilter.cs new file mode 100644 index 00000000..a20f8544 --- /dev/null +++ b/Blog.Core.Common/Swagger/Filter/EnumSchemaFilter.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; +using System.Reflection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Blog.Core.Common.Swagger.Filter; + +/// +/// Enum 转换 +/// +public class EnumSchemaFilter : ISchemaFilter +{ + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (schema.Enum == null || schema.Enum.Count == 0 || + context.Type == null || !context.Type.IsEnum) + return; + + schema.Description += "

枚举说明:

    "; + + var enumMembers = context.Type.GetFields(BindingFlags.Public | BindingFlags.Static); + + foreach (var enumMember in enumMembers) + { + var enumValue = Convert.ToInt64(Enum.Parse(context.Type, enumMember.Name)); + + var descriptionAttribute = enumMember.GetCustomAttribute(); + var description = descriptionAttribute != null ? descriptionAttribute.Description : enumMember.Name; + + schema.Description += $"
  • {enumValue}:{enumMember.Name} - {description}
  • "; + } + + schema.Description += "
"; + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Swagger/Filter/EnumTypesDocumentFilter.cs b/Blog.Core.Common/Swagger/Filter/EnumTypesDocumentFilter.cs new file mode 100644 index 00000000..b0e73b5f --- /dev/null +++ b/Blog.Core.Common/Swagger/Filter/EnumTypesDocumentFilter.cs @@ -0,0 +1,107 @@ +using System.Reflection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Blog.Core.Common.Swagger.Filter; + +public class EnumTypesDocumentFilter : IDocumentFilter +{ + private static Type UnwrapEnumType(Type type) + { + if (type == null) + return null; + + // Nullable + if (Nullable.GetUnderlyingType(type)?.IsEnum == true) + return Nullable.GetUnderlyingType(type); + + // 直接是 Enum + if (type.IsEnum) + return type; + + // List / IEnumerable + if (type.IsGenericType && + (type.GetGenericTypeDefinition() == typeof(IEnumerable<>) || + type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))) + { + var itemType = type.GetGenericArguments().FirstOrDefault(); + return UnwrapEnumType(itemType); + } + + // Enum[] + if (type.IsArray) + return UnwrapEnumType(type.GetElementType()); + + return null; + } + + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + foreach (var path in swaggerDoc.Paths.Values) + { + foreach (var operation in path.Operations.Values) + { + foreach (var parameter in operation.Parameters) + { + OpenApiSchema schema = null; + if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Description) && parameter.Description.Contains("
    ")) + { + schema = parameter.Schema; + } + else if (parameter.Schema is { Reference: not null }) + { + // 引用类型Enum + schema = context.SchemaRepository.Schemas[parameter.Schema.Reference.Id]; + } + else if (parameter.Schema.Type == "array" && parameter.Schema.Items.Reference != null) + { + // 数组类型Enum + schema = context.SchemaRepository.Schemas[parameter.Schema.Items.Reference.Id]; + } + else if (parameter.Schema is { Items: { Enum: { Count: > 0 } } }) + { + schema = parameter.Schema.Items; + } + else if (parameter.Schema.Enum is { Count: > 0 }) + { + // 基础类型Enum (integer) + var apiParam = context.ApiDescriptions + .SelectMany(desc => desc.ParameterDescriptions) + .FirstOrDefault(desc => desc.Name == parameter.Name); + + Type paramType = apiParam?.ParameterDescriptor?.ParameterType; + Type enumType = null; + + if (paramType != null) + { + // 如果是 DTO 类型,从其属性中反查当前参数名 + if (!paramType.IsEnum && !paramType.IsValueType && !paramType.IsPrimitive && paramType != typeof(string)) + { + var dtoProp = paramType.GetProperty(apiParam.Name, + BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + if (dtoProp != null) + paramType = dtoProp.PropertyType; + } + + enumType = UnwrapEnumType(paramType); + + if (enumType?.IsEnum == true) + { + schema = context.SchemaGenerator.GenerateSchema(enumType, context.SchemaRepository); + } + } + } + + if (schema == null || schema.Description == null) continue; + + var cutStart = schema.Description.IndexOf("
      "); + var cutEnd = schema.Description.IndexOf("
    ") + 5; + + if (cutStart < 0 || cutEnd <= cutStart) continue; + + parameter.Description += "

    说明:

    " + schema.Description.Substring(cutStart, cutEnd - cutStart); + } + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Swagger/SwaggerContextExtension.cs b/Blog.Core.Common/Swagger/SwaggerContextExtension.cs new file mode 100644 index 00000000..ad89344d --- /dev/null +++ b/Blog.Core.Common/Swagger/SwaggerContextExtension.cs @@ -0,0 +1,48 @@ +using Blog.Core.Common.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; + +namespace Blog.Core.Common.Swagger; + +public static class SwaggerContextExtension +{ + public const string SwaggerCodeKey = "swagger-code"; + public const string SwaggerJwt = "swagger-jwt"; + + public static bool IsSuccessSwagger() + { + return App.HttpContext?.GetSession()?.GetString(SwaggerCodeKey) == "success"; + } + + public static bool IsSuccessSwagger(this HttpContext context) + { + return context.GetSession()?.GetString(SwaggerCodeKey) == "success"; + } + + public static void SuccessSwagger() + { + App.HttpContext?.GetSession()?.SetString(SwaggerCodeKey, "success"); + } + + public static void SuccessSwagger(this HttpContext context) + { + context.GetSession()?.SetString(SwaggerCodeKey, "success"); + } + + public static void SuccessSwaggerJwt(this HttpContext context, string token) + { + context.GetSession()?.SetString(SwaggerJwt, token); + } + + public static string GetSuccessSwaggerJwt(this HttpContext context) + { + return context.GetSession()?.GetString(SwaggerJwt); + } + + + public static void RedirectSwaggerLogin(this HttpContext context) + { + var returnUrl = context.Request.GetDisplayUrl(); //获取当前url地址 + context.Response.Redirect("/swg-login.html?returnUrl=" + returnUrl); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Utility/IdGeneratorUtility.cs b/Blog.Core.Common/Utility/IdGeneratorUtility.cs new file mode 100644 index 00000000..dcc308b1 --- /dev/null +++ b/Blog.Core.Common/Utility/IdGeneratorUtility.cs @@ -0,0 +1,78 @@ +using Blog.Core.Common.DB; +using Blog.Core.Common.Option; +using Microsoft.Extensions.Hosting; +using Serilog; +using SnowflakeId.AutoRegister.Builder; +using SnowflakeId.AutoRegister.Interfaces; +using SqlSugar; +using Yitter.IdGenerator; + +namespace Blog.Core.Common.Utility; + +public class IdGeneratorUtility +{ + private static IdGeneratorOptions _options; + + private static readonly Lazy AutoRegister = new(() => + { + var builder = new AutoRegisterBuilder() + // Register Option + // Use the following line to set the identifier. + // Recommended setting to distinguish multiple applications on a single machine + .SetExtraIdentifier(App.Configuration["urls"] ?? string.Empty) + // Use the following line to set the WorkerId scope. + .SetWorkerIdScope(1, 30) + // Use the following line to set the register option. + // .SetRegisterOption(option => {}) + ; + var redisOptions = App.GetOptions(); + if (redisOptions.Enable) + // Use the following line to use the Redis store. + builder.UseRedisStore(redisOptions.ConnectionString); + else if (BaseDBConfig.LogConfig != null && BaseDBConfig.LogConfig.DbType == DbType.SqlServer) + // Use the following line to use the SQL Server store. + builder.UseSqlServerStore(BaseDBConfig.LogConfig.ConnectionString); + else + // Use the following line to use the default store. + // Only suitable for standalone use, local testing, etc. + builder.UseDefaultStore(); + + App.GetService(false).ApplicationStopping.Register(UnRegister); + return builder.Build(); + }); + + private static readonly Lazy _idGenInstance = new(() => + { + var config = AutoRegister.Value.Register(); + + //WorkerId DataCenterId 取值 1-31 + var options = GetOptions(); + options.WorkerId = (ushort)config.WorkerId; + IIdGenerator idGenInstance = new DefaultIdGenerator(options); + return idGenInstance; + }); + + private static IIdGenerator IdGenInstance => _idGenInstance.Value; + + public static IdGeneratorOptions GetOptions() + { + _options ??= new IdGeneratorOptions + { + }; + + return _options; + } + + public static long NextId() + { + return IdGenInstance.NewLong(); + } + + public static void UnRegister() + { + if (!AutoRegister.IsValueCreated) return; + + AutoRegister.Value.UnRegister(); + Log.Information("Snowflake Id Unregistered"); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Utility/SqlSugarSnowflakeHelper.cs b/Blog.Core.Common/Utility/SqlSugarSnowflakeHelper.cs new file mode 100644 index 00000000..4fc7a9ca --- /dev/null +++ b/Blog.Core.Common/Utility/SqlSugarSnowflakeHelper.cs @@ -0,0 +1,31 @@ +namespace Blog.Core.Common.Utility; + +/// +/// SqlSugar 雪花算法 工具类 +/// +public class SqlSugarSnowflakeHelper +{ + private const long Twepoch = 1288834974657L; + private const int TimestampLeftShift = 22; + private const int DatacenterIdShift = 17; + private const int WorkerIdShift = 12; + private const long SequenceMask = 0xFFF; // 4095 + private const long WorkerMask = 0x1F; // 31 + private const long DatacenterMask = 0x1F; // 31 + + public static DateTime GetDateTime(long id) + { + long timestamp = (id >> TimestampLeftShift) + Twepoch; + return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToLocalTime().DateTime; + } + + public static (DateTime time, long datacenterId, long workerId, long sequence) Decode(long id) + { + long timestamp = (id >> TimestampLeftShift) + Twepoch; + long datacenterId = (id >> DatacenterIdShift) & DatacenterMask; + long workerId = (id >> WorkerIdShift) & WorkerMask; + long sequence = id & SequenceMask; + var time = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToLocalTime().DateTime; + return (time, datacenterId, workerId, sequence); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Utility/YitterSnowflakeHelper.cs b/Blog.Core.Common/Utility/YitterSnowflakeHelper.cs new file mode 100644 index 00000000..122e8927 --- /dev/null +++ b/Blog.Core.Common/Utility/YitterSnowflakeHelper.cs @@ -0,0 +1,48 @@ +using Yitter.IdGenerator; + +namespace Blog.Core.Common.Utility; + +/// +/// Yitter 雪花算法 工具类 +/// +public class YitterSnowflakeHelper +{ + /// + /// 从ID中解析时间 + /// + public static DateTime GetDateTime(IdGeneratorOptions options, long id) + { + int shift = options.SeqBitLength + options.WorkerIdBitLength + options.DataCenterIdBitLength; + long timeDiff = id >> shift; + + DateTime utcTime = options.TimestampType == 1 + ? options.BaseTime.AddSeconds(timeDiff) + : options.BaseTime.AddMilliseconds(timeDiff); + + return DateTime.SpecifyKind(utcTime, DateTimeKind.Utc).ToLocalTime(); + } + + /// + /// 从ID中解析时间、WorkerId、序列号 + /// + public static (DateTime time, long workerId, long sequence, long datacenterId) Decode(IdGeneratorOptions options, + long id) + { + int seqBits = options.SeqBitLength; + int workerBits = options.WorkerIdBitLength; + int datacenterBits = options.DataCenterIdBitLength; + + long sequence = id & ((1L << seqBits) - 1); + long workerId = (id >> seqBits) & ((1L << workerBits) - 1); + long datacenterId = datacenterBits == 0 ? 0 : (id >> (seqBits + workerBits)) & ((1L << datacenterBits) - 1); + long timeDiff = id >> (seqBits + workerBits + datacenterBits); + + DateTime utcTime = options.TimestampType == 1 + ? options.BaseTime.AddSeconds(timeDiff) + : options.BaseTime.AddMilliseconds(timeDiff); + + DateTime localTime = DateTime.SpecifyKind(utcTime, DateTimeKind.Utc).ToLocalTime(); + + return (localTime, workerId, sequence, datacenterId); + } +} \ No newline at end of file diff --git a/Blog.Core.EventBus/Blog.Core.EventBus.csproj b/Blog.Core.EventBus/Blog.Core.EventBus.csproj index 070c0a48..622856f5 100644 --- a/Blog.Core.EventBus/Blog.Core.EventBus.csproj +++ b/Blog.Core.EventBus/Blog.Core.EventBus.csproj @@ -1,21 +1,18 @@ - - net6.0 - - - + + - + - - - - + + + + diff --git a/Blog.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs b/Blog.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs index c90b0d4a..bad482ae 100644 --- a/Blog.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs +++ b/Blog.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs @@ -10,10 +10,35 @@ namespace Blog.Core.EventBus public interface IRabbitMQPersistentConnection : IDisposable { + /// + /// 是否已经连接 + /// bool IsConnected { get; } + /// + /// 尝试重连 + /// + /// bool TryConnect(); + /// + /// 创建Model + /// + /// IModel CreateModel(); + + /// + /// 发布消息 + /// + /// + /// + /// + void PublishMessage(string message, string exchangeName, string routingKey); + + /// + /// 订阅消息 + /// + /// + void StartConsuming(string queueName); } } diff --git a/Blog.Core.EventBus/RabbitMQPersistent/RabbitMQPersistentConnection.cs b/Blog.Core.EventBus/RabbitMQPersistent/RabbitMQPersistentConnection.cs index be2d8de3..1fa3bd08 100644 --- a/Blog.Core.EventBus/RabbitMQPersistent/RabbitMQPersistentConnection.cs +++ b/Blog.Core.EventBus/RabbitMQPersistent/RabbitMQPersistentConnection.cs @@ -7,6 +7,7 @@ using System; using System.IO; using System.Net.Sockets; +using System.Text; namespace Blog.Core.EventBus { @@ -162,5 +163,49 @@ void OnConnectionShutdown(object sender, ShutdownEventArgs reason) TryConnect(); } + + /// + /// 发布消息 + /// + /// + /// + /// + public void PublishMessage(string message, string exchangeName, string routingKey) + { + using var channel = CreateModel(); + channel.ExchangeDeclare(exchange: exchangeName, type: ExchangeType.Direct, true); + var body = Encoding.UTF8.GetBytes(message); + channel.BasicPublish(exchange: exchangeName, routingKey: routingKey, basicProperties: null, body: body); + } + + /// + /// 订阅消息 + /// + /// + public void StartConsuming(string queueName) + { + using var channel = CreateModel(); + channel.QueueDeclare(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null); + + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.Received += new AsyncEventHandler( + async (a, b) => + { + var Headers = b.BasicProperties.Headers; + var msgBody = b.Body.ToArray(); + var message = Encoding.UTF8.GetString(msgBody); + await Task.CompletedTask; + Console.WriteLine("Received message: {0}", message); + + //bool Dealresult = await Dealer(b.Exchange, b.RoutingKey, msgBody, Headers); + //if (Dealresult) channel.BasicAck(b.DeliveryTag, false); + //else channel.BasicNack(b.DeliveryTag, false, true); + } + ); + + channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); + + Console.WriteLine("Consuming messages..."); + } } } diff --git a/Blog.Core.Extensions/AOP/BlogCacheAOP.cs b/Blog.Core.Extensions/AOP/BlogCacheAOP.cs index a3a52f58..2fb8f7bd 100644 --- a/Blog.Core.Extensions/AOP/BlogCacheAOP.cs +++ b/Blog.Core.Extensions/AOP/BlogCacheAOP.cs @@ -1,53 +1,84 @@ -using Blog.Core.Common; +using System; +using Blog.Core.Common; using Castle.DynamicProxy; using System.Linq; +using System.Threading.Tasks; +using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; namespace Blog.Core.AOP { - /// - /// 面向切面的缓存使用 - /// - public class BlogCacheAOP : CacheAOPbase - { - //通过注入的方式,把缓存操作接口通过构造函数注入 - private readonly ICaching _cache; - public BlogCacheAOP(ICaching cache) - { - _cache = cache; - } - - //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义 - public override void Intercept(IInvocation invocation) - { - var method = invocation.MethodInvocationTarget ?? invocation.Method; - //对当前方法的特性验证 - //如果需要验证 - var CachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)); - if (CachingAttribute is CachingAttribute qCachingAttribute) - { - //获取自定义缓存键 - var cacheKey = CustomCacheKey(invocation); - //根据key获取相应的缓存值 - var cacheValue = _cache.Get(cacheKey); - if (cacheValue != null) - { - //将当前获取到的缓存值,赋值给当前执行方法 - invocation.ReturnValue = cacheValue; - return; - } - //去执行当前的方法 - invocation.Proceed(); - //存入缓存 - if (!string.IsNullOrWhiteSpace(cacheKey)) - { - _cache.Set(cacheKey, invocation.ReturnValue, qCachingAttribute.AbsoluteExpiration); - } - } - else - { - invocation.Proceed();//直接执行被拦截方法 - } - } - } - -} + /// + /// 面向切面的缓存使用 + /// + public class BlogCacheAOP : CacheAOPbase + { + //通过注入的方式,把缓存操作接口通过构造函数注入 + private readonly ICaching _cache; + + public BlogCacheAOP(ICaching cache) + { + _cache = cache; + } + + //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义 + public override void Intercept(IInvocation invocation) + { + var method = invocation.MethodInvocationTarget ?? invocation.Method; + //对当前方法的特性验证 + //如果需要验证 + var CachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)); + if (CachingAttribute is CachingAttribute qCachingAttribute) + { + //获取自定义缓存键 + var cacheKey = CustomCacheKey(invocation); + if (_cache.Exists(cacheKey)) + { + //将当前获取到的缓存值,赋值给当前执行方法 + Type returnType; + if (typeof(Task).IsAssignableFrom(method.ReturnType)) + { + returnType = method.ReturnType.GenericTypeArguments.FirstOrDefault(); + } + else + { + returnType = method.ReturnType; + } + + //根据key获取相应的缓存值 + dynamic cacheValue = _cache.Get(returnType, cacheKey); + invocation.ReturnValue = (typeof(Task).IsAssignableFrom(method.ReturnType)) ? Task.FromResult(cacheValue) : cacheValue; + return; + } + + //去执行当前的方法 + invocation.Proceed(); + //存入缓存 + if (!string.IsNullOrWhiteSpace(cacheKey)) + { + object response; + + //Type type = invocation.ReturnValue?.GetType(); + var type = invocation.Method.ReturnType; + if (typeof(Task).IsAssignableFrom(type)) + { + dynamic result = invocation.ReturnValue; + response = result.Result; + } + else + { + response = invocation.ReturnValue; + } + + if (response == null) response = string.Empty; + + _cache.Set(cacheKey, response, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration)); + } + } + else + { + invocation.Proceed(); //直接执行被拦截方法 + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/AOP/BlogLogAOP.cs b/Blog.Core.Extensions/AOP/BlogLogAOP.cs index 29861578..a52d3ad8 100644 --- a/Blog.Core.Extensions/AOP/BlogLogAOP.cs +++ b/Blog.Core.Extensions/AOP/BlogLogAOP.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace Blog.Core.AOP { @@ -20,11 +21,13 @@ public class BlogLogAOP : IInterceptor { private readonly IHubContext _hubContext; private readonly IHttpContextAccessor _accessor; + private readonly ILogger _logger; - public BlogLogAOP(IHubContext hubContext, IHttpContextAccessor accessor) + public BlogLogAOP(IHubContext hubContext, IHttpContextAccessor accessor, ILogger logger) { _hubContext = hubContext; - _accessor = accessor; + _accessor = accessor; + _logger = logger; } @@ -44,14 +47,15 @@ public void Intercept(IInvocation invocation) { json = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString(); } + DateTime startTime = DateTime.Now; AOPLogInfo apiLogAopInfo = new AOPLogInfo { - RequestTime = startTime.ToString("yyyy-MM-dd hh:mm:ss fff"), - OpUserName = UserName, + RequestTime = startTime.ToString("yyyy-MM-dd hh:mm:ss fff"), + OpUserName = UserName, RequestMethodName = invocation.Method.Name, RequestParamsName = string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()), - ResponseJsonData = json + ResponseJsonData = json }; //测试异常记录 @@ -73,32 +77,27 @@ public void Intercept(IInvocation invocation) // 异步获取异常,先执行 if (IsAsyncMethod(invocation.Method)) { - #region 方案一 + //Wait task execution and modify return value if (invocation.Method.ReturnType == typeof(Task)) { invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( (Task)invocation.ReturnValue, - async () => await SuccessAction(invocation, apiLogAopInfo, startTime),/*成功时执行*/ - ex => - { - LogEx(ex, apiLogAopInfo); - }); + async () => await SuccessAction(invocation, apiLogAopInfo, startTime), /*成功时执行*/ + ex => { LogEx(ex, apiLogAopInfo); }); } //Task else { invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( - invocation.Method.ReturnType.GenericTypeArguments[0], - invocation.ReturnValue, - //async () => await SuccessAction(invocation, dataIntercept),/*成功时执行*/ - async (o) => await SuccessAction(invocation, apiLogAopInfo, startTime, o),/*成功时执行*/ - ex => - { - LogEx(ex, apiLogAopInfo); - }); + invocation.Method.ReturnType.GenericTypeArguments[0], + invocation.ReturnValue, + //async () => await SuccessAction(invocation, dataIntercept),/*成功时执行*/ + async (o) => await SuccessAction(invocation, apiLogAopInfo, startTime, o), /*成功时执行*/ + ex => { LogEx(ex, apiLogAopInfo); }); } + #endregion @@ -137,33 +136,36 @@ public void Intercept(IInvocation invocation) { jsonResult = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString(); } - var type = invocation.Method.ReturnType; - var resultProperty = type.GetProperty("Result"); - DateTime endTime = DateTime.Now; - string ResponseTime = (endTime - startTime).Milliseconds.ToString(); - apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); + + var type = invocation.Method.ReturnType; + var resultProperty = type.GetProperty("Result"); + DateTime endTime = DateTime.Now; + string ResponseTime = (endTime - startTime).Milliseconds.ToString(); + apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; //apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue)); apiLogAopInfo.ResponseJsonData = jsonResult; //dataIntercept += ($"【执行完成结果】:{jsonResult}"); - Parallel.For(0, 1, e => - { - //LogLock.OutLogAOP("AOPLog", new string[] { dataIntercept }); - LogLock.OutLogAOP("AOPLog", _accessor.HttpContext?.TraceIdentifier, new string[] { apiLogAopInfo.GetType().ToString(), JsonConvert.SerializeObject(apiLogAopInfo) }); - }); + + _logger.LogInformation("AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopInfo)); } } - catch (Exception ex)// 同步2 + catch (Exception ex) // 同步2 { LogEx(ex, apiLogAopInfo); + throw; } + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) { - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); + //发送日志 + _hubContext.Clients.All.SendAsync("ReceiveUpdate", JsonConvert.SerializeObject(apiLogAopInfo)).Wait(); } } - private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInfo, DateTime startTime, object o = null) + private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInfo, DateTime startTime, + object o = null) { //invocation.ReturnValue = o; //var type = invocation.Method.ReturnType; @@ -177,21 +179,14 @@ private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInf //{ // dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}"); //} - DateTime endTime = DateTime.Now; - string ResponseTime = (endTime - startTime).Milliseconds.ToString(); - apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); + DateTime endTime = DateTime.Now; + string ResponseTime = (endTime - startTime).Milliseconds.ToString(); + apiLogAopInfo.ResponseTime = endTime.ToString("yyyy-MM-dd hh:mm:ss fff"); apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; - apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(o); - + apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(o); - await Task.Run(() => - { - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("AOPLog", new string[] { JsonConvert.SerializeObject(apiLogAopInfo) }); - LogLock.OutLogAOP("AOPLog", _accessor.HttpContext?.TraceIdentifier, new string[] { apiLogAopInfo.GetType().ToString(), JsonConvert.SerializeObject(apiLogAopInfo) }); - }); - }); + _logger.LogInformation("AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopInfo)); } private void LogEx(Exception ex, AOPLogInfo dataIntercept) @@ -205,16 +200,14 @@ private void LogEx(Exception ex, AOPLogInfo dataIntercept) AOPLogExInfo apiLogAopExInfo = new AOPLogExInfo { ExMessage = ex.Message, - InnerException = "InnerException-内部异常:\r\n" + (ex.InnerException == null ? "" : ex.InnerException.InnerException.ToString()) + ("\r\nStackTrace-堆栈跟踪:\r\n") + (ex.StackTrace == null ? "" : ex.StackTrace.ToString()), + InnerException = "InnerException-内部异常:\r\n" + + (ex.InnerException == null ? "" : ex.InnerException.InnerException.ToString()) + + ("\r\nStackTrace-堆栈跟踪:\r\n") + (ex.StackTrace == null ? "" : ex.StackTrace.ToString()), ApiLogAopInfo = dataIntercept }; // 异常日志里有详细的堆栈信息 - Parallel.For(0, 1, e => - { - //LogLock.OutLogAOP("AOPLogEx", new string[] { dataIntercept }); - LogLock.OutLogAOP("AOPLogEx", _accessor.HttpContext?.TraceIdentifier, new string[] { apiLogAopExInfo.GetType().ToString(), JsonConvert.SerializeObject(apiLogAopExInfo) }); - - }); + _logger.LogError(ex, "AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopExInfo)); } } @@ -224,15 +217,15 @@ public static bool IsAsyncMethod(MethodInfo method) return ( method.ReturnType == typeof(Task) || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) - ); + ); } - } internal static class InternalAsyncHelper { - public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func postAction, Action finalAction) + public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func postAction, + Action finalAction) { Exception exception = null; @@ -251,7 +244,9 @@ public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValu } } - public static async Task AwaitTaskWithPostActionAndFinallyAndGetResult(Task actualReturnValue, Func postAction, Action finalAction) + public static async Task AwaitTaskWithPostActionAndFinallyAndGetResult(Task actualReturnValue, + Func postAction, + Action finalAction) { Exception exception = null; try @@ -271,13 +266,14 @@ public static async Task AwaitTaskWithPostActionAndFinallyAndGetResult(Tas } } - public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func action, Action finalAction) + public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, + object actualReturnValue, + Func action, Action finalAction) { return typeof(InternalAsyncHelper) - .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) - .MakeGenericMethod(taskReturnType) - .Invoke(null, new object[] { actualReturnValue, action, finalAction }); + .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) + .MakeGenericMethod(taskReturnType) + .Invoke(null, new object[] { actualReturnValue, action, finalAction }); } } - -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/AOP/BlogRedisCacheAOP.cs b/Blog.Core.Extensions/AOP/BlogRedisCacheAOP.cs deleted file mode 100644 index 9bc7f7f7..00000000 --- a/Blog.Core.Extensions/AOP/BlogRedisCacheAOP.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Blog.Core.Common; -using Blog.Core.Extensions; -using Castle.DynamicProxy; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Blog.Core.AOP -{ - /// - /// 面向切面的缓存使用 - /// - public class BlogRedisCacheAOP : CacheAOPbase - { - //通过注入的方式,把缓存操作接口通过构造函数注入 - private readonly IRedisBasketRepository _cache; - public BlogRedisCacheAOP(IRedisBasketRepository cache) - { - _cache = cache; - } - - //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义 - //代码已经合并 ,学习pr流程 - public override void Intercept(IInvocation invocation) - { - var method = invocation.MethodInvocationTarget ?? invocation.Method; - if (method.ReturnType == typeof(void) || method.ReturnType == typeof(Task)) - { - invocation.Proceed(); - return; - } - //对当前方法的特性验证 - var qCachingAttribute = method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingAttribute)) as CachingAttribute; - - if (qCachingAttribute != null) - { - //获取自定义缓存键 - var cacheKey = CustomCacheKey(invocation); - //注意是 string 类型,方法GetValue - var cacheValue = _cache.GetValue(cacheKey).Result; - if (cacheValue != null) - { - //将当前获取到的缓存值,赋值给当前执行方法 - Type returnType; - if (typeof(Task).IsAssignableFrom(method.ReturnType)) - { - returnType = method.ReturnType.GenericTypeArguments.FirstOrDefault(); - } - else - { - returnType = method.ReturnType; - } - - dynamic _result = Newtonsoft.Json.JsonConvert.DeserializeObject(cacheValue, returnType); - invocation.ReturnValue = (typeof(Task).IsAssignableFrom(method.ReturnType)) ? Task.FromResult(_result) : _result; - return; - } - //去执行当前的方法 - invocation.Proceed(); - - //存入缓存 - if (!string.IsNullOrWhiteSpace(cacheKey)) - { - object response; - - //Type type = invocation.ReturnValue?.GetType(); - var type = invocation.Method.ReturnType; - if (typeof(Task).IsAssignableFrom(type)) - { - var resultProperty = type.GetProperty("Result"); - response = resultProperty.GetValue(invocation.ReturnValue); - } - else - { - response = invocation.ReturnValue; - } - if (response == null) response = string.Empty; - - _cache.Set(cacheKey, response, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration)).Wait(); - } - } - else - { - invocation.Proceed();//直接执行被拦截方法 - } - } - } - -} diff --git a/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs b/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs index 018f4c39..6fa9c9b6 100644 --- a/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs +++ b/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs @@ -36,8 +36,8 @@ public static string IssueJwt(TokenModelJwt tokenModel) new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ToString()), - new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"), - new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") , + new Claim(JwtRegisteredClaimNames.Iat, $"{DateTime.Now.DateToTimeStamp()}"), + new Claim(JwtRegisteredClaimNames.Nbf,$"{DateTime.Now.DateToTimeStamp()}") , //这个就是过期时间,目前是过期1000秒,可自定义,注意JWT有自己的缓冲过期时间 new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"), new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(1000).ToString()), @@ -90,7 +90,7 @@ public static TokenModelJwt SerializeJwt(string jwtStr) tokenModelJwt = new TokenModelJwt { - Uid = (jwtToken.Id).ObjToInt(), + Uid = (jwtToken.Id).ObjToLong(), Role = role != null ? role.ObjToString() : "", }; } diff --git a/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs b/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs index 9b8a7389..2b9435a5 100644 --- a/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs +++ b/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs @@ -13,7 +13,7 @@ public class JwtToken /// /// 获取基于JWT的Token /// - /// 需要在登陆的时候配置 + /// 需要在登录的时候配置 /// 在startup中定义的参数 /// public static TokenInfoViewModel BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) diff --git a/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs b/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs index 4ed55525..8d09be4f 100644 --- a/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs +++ b/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs @@ -1,6 +1,8 @@ using Blog.Core.Common; using Blog.Core.Common.Helper; +using Blog.Core.Common.HttpContextUser; using Blog.Core.IServices; +using Blog.Core.Model; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -12,8 +14,8 @@ using System.Security.Claims; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Blog.Core.Common.HttpContextUser; -using Blog.Core.Model; +using Blog.Core.Common.Swagger; +using Blog.Core.Model.Models; namespace Blog.Core.AuthHelper { @@ -40,7 +42,9 @@ public class PermissionHandler : AuthorizationHandler /// /// /// - public PermissionHandler(IAuthenticationSchemeProvider schemes, IRoleModulePermissionServices roleModulePermissionServices, IHttpContextAccessor accessor, ISysUserInfoServices userServices, IUser user) + public PermissionHandler(IAuthenticationSchemeProvider schemes, + IRoleModulePermissionServices roleModulePermissionServices, IHttpContextAccessor accessor, + ISysUserInfoServices userServices, IUser user) { _accessor = accessor; _userServices = userServices; @@ -50,7 +54,8 @@ public PermissionHandler(IAuthenticationSchemeProvider schemes, IRoleModulePermi } // 重写异步处理程序 - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + PermissionRequirement requirement) { var httpContext = _accessor.HttpContext; @@ -105,14 +110,14 @@ orderby item.Id var handlers = httpContext.RequestServices.GetRequiredService(); foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()) { - if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler handler && await handler.HandleRequestAsync()) + if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler + handler && await handler.HandleRequestAsync()) { context.Fail(); return; } } - //判断请求是否拥有凭据,即有没有登录 var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) @@ -123,51 +128,38 @@ orderby item.Id var isTestCurrent = AppSettings.app(new string[] { "AppSettings", "UseLoadTest" }).ObjToBool(); //result?.Principal不为空即登录成功 - if (result?.Principal != null || isTestCurrent) + if (result?.Principal != null || isTestCurrent || httpContext.IsSuccessSwagger()) { if (!isTestCurrent) httpContext.User = result.Principal; - // 获取当前用户的角色信息 - var currentUserRoles = new List(); - // ids4和jwt切换 - // ids4 - if (Permissions.IsUseIds4) - { - currentUserRoles = (from item in httpContext.User.Claims - where item.Type == "role" - select item.Value).ToList(); - } - else - { - // jwt - currentUserRoles = (from item in httpContext.User.Claims - where item.Type == requirement.ClaimType - select item.Value).ToList(); - } - - var isMatchRole = false; - var permisssionRoles = requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role)); - foreach (var item in permisssionRoles) + //应该要先校验用户的信息 再校验菜单权限相关的 + // JWT模式下校验当前用户状态 + // IDS4也可以校验,可以通过服务或者接口形式 + SysUserInfo user = new(); + if (!Permissions.IsUseIds4) { - try + //校验用户 + user = await _userServices.QueryById(_user.ID, true); + if (user == null) { - if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl) - { - isMatchRole = true; - break; - } + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户不存在或已被删除").MessageModel; + context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); + return; } - catch (Exception) + + if (user.IsDeleted) { - // ignored + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被删除,禁止登录!").MessageModel; + context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); + return; } - } - //验证权限 - if (currentUserRoles.Count <= 0 || !isMatchRole) - { - context.Fail(); - return; + if (!user.Enable) + { + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被禁用!禁止登录!").MessageModel; + context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); + return; + } } // 判断token是否过期,过期则重新登录 @@ -176,12 +168,18 @@ orderby item.Id // ids4 if (Permissions.IsUseIds4) { - isExp = (httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) != null && DateHelper.StampToDateTime(httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) >= DateTime.Now; + isExp = (httpContext.User.Claims.FirstOrDefault(s => s.Type == "exp")?.Value) != null && + DateHelper.StampToDateTime(httpContext.User.Claims + .FirstOrDefault(s => s.Type == "exp")?.Value) >= DateTime.Now; } else { // jwt - isExp = (httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) != null && DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now; + isExp = + (httpContext.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.Expiration) + ?.Value) != null && + DateTime.Parse(httpContext.User.Claims + .FirstOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now; } if (!isExp) @@ -190,29 +188,75 @@ orderby item.Id return; } + //校验签发时间 if (!Permissions.IsUseIds4) { - var value = httpContext.User.Claims.SingleOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value; + var value = httpContext.User.Claims + .FirstOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value; if (value != null) { - var user = await _userServices.QueryById(_user.ID, true); if (user.CriticalModifyTime > value.ObjToDate()) { - _user.MessageModel = new ApiResponse(StatusCode.CODE401, "很抱歉,授权已失效,请重新授权").MessageModel; + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "很抱歉,授权已失效,请重新授权") + .MessageModel; context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); return; } - } + } + } + + // 获取当前用户的角色信息 + var currentUserRoles = new List(); + currentUserRoles = (from item in httpContext.User.Claims + where item.Type == ClaimTypes.Role + select item.Value).ToList(); + if (!currentUserRoles.Any()) + { + currentUserRoles = (from item in httpContext.User.Claims + where item.Type == "role" + select item.Value).ToList(); } + //超级管理员 默认拥有所有权限 + if (currentUserRoles.All(s => s != "SuperAdmin")) + { + var isMatchRole = false; + var permisssionRoles = + requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role)); + foreach (var item in permisssionRoles) + { + try + { + if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl) + { + isMatchRole = true; + break; + } + } + catch (Exception) + { + // ignored + } + } + + //验证权限 + if (currentUserRoles.Count <= 0 || !isMatchRole) + { + context.Fail(); + return; + } + } + + context.Succeed(requirement); return; } } //判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败 - if (!(questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))) + if (!(questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && + (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))) { context.Fail(); return; diff --git a/Blog.Core.Extensions/Blog.Core.Extensions.csproj b/Blog.Core.Extensions/Blog.Core.Extensions.csproj index aca15b55..3ea16203 100644 --- a/Blog.Core.Extensions/Blog.Core.Extensions.csproj +++ b/Blog.Core.Extensions/Blog.Core.Extensions.csproj @@ -1,39 +1,43 @@  - - net6.0 - - + - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + - - + + + diff --git a/Blog.Core.Extensions/HostedService/ConsulHostedService.cs b/Blog.Core.Extensions/HostedService/ConsulHostedService.cs new file mode 100644 index 00000000..df866e6a --- /dev/null +++ b/Blog.Core.Extensions/HostedService/ConsulHostedService.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Consul; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Blog.Core.Extensions.HostedService; + +public class ConsulHostedService : IHostedService +{ + private readonly IConfiguration _configuration; + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly ILogger _logger; + + public ConsulHostedService(IConfiguration configuration, IHostApplicationLifetime hostApplicationLifetime, ILogger logger) + { + _configuration = configuration; + _hostApplicationLifetime = hostApplicationLifetime; + _logger = logger; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Start Consul Service!"); + await DoWork(); + } + + public async Task DoWork() + { + if (_configuration["Middleware:Consul:Enabled"].ObjToBool()) + { + var consulClient = new ConsulClient(c => + { + //consul地址 + c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]); + }); + + var registration = new AgentServiceRegistration() + { + ID = Guid.NewGuid().ToString(),//服务实例唯一标识 + Name = _configuration["ConsulSetting:ServiceName"],//服务名 + Address = _configuration["ConsulSetting:ServiceIP"], //服务IP + Port = int.Parse(_configuration["ConsulSetting:ServicePort"]),//服务端口 + Check = new AgentServiceCheck() + { + DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册 + Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔 + HTTP = $"http://{_configuration["ConsulSetting:ServiceIP"]}:{_configuration["ConsulSetting:ServicePort"]}{_configuration["ConsulSetting:ServiceHealthCheck"]}",//健康检查地址 + Timeout = TimeSpan.FromSeconds(5)//超时时间 + } + }; + + //服务注册 + await consulClient.Agent.ServiceRegister(registration); + + //应用程序终止时,取消注册 + _hostApplicationLifetime.ApplicationStopping.Register(async () => + { + await consulClient.Agent.ServiceDeregister(registration.ID); + }); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Stop Consul Service!"); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/HostedService/EventBusHostedService.cs b/Blog.Core.Extensions/HostedService/EventBusHostedService.cs new file mode 100644 index 00000000..7f18ed19 --- /dev/null +++ b/Blog.Core.Extensions/HostedService/EventBusHostedService.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.EventBus; +using Blog.Core.EventBus.EventHandling; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Blog.Core.Extensions.HostedService; + +public class EventBusHostedService : IHostedService +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + public EventBusHostedService(IServiceProvider serviceProvider, ILogger logger) + { + _serviceProvider = serviceProvider; + _logger = logger; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Start EventBus Service!"); + await DoWork(); + } + + private Task DoWork() + { + if (AppSettings.app(new string[] { "EventBus", "Enabled" }).ObjToBool()) + { + var eventBus = _serviceProvider.GetRequiredService(); + eventBus.Subscribe(); + } + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Stop EventBus Service!"); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/HostedService/Job1TimedService.cs b/Blog.Core.Extensions/HostedService/Job1TimedService.cs new file mode 100644 index 00000000..bd515194 --- /dev/null +++ b/Blog.Core.Extensions/HostedService/Job1TimedService.cs @@ -0,0 +1,60 @@ +using Blog.Core.Common; +using Blog.Core.IServices; +using Microsoft.Extensions.Hosting; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions +{ + public class Job1TimedService : IHostedService, IDisposable + { + private Timer _timer; + private readonly IBlogArticleServices _blogArticleServices; + + // 这里可以注入 + public Job1TimedService(IBlogArticleServices blogArticleServices) + { + _blogArticleServices = blogArticleServices; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + Console.WriteLine("Job 1 is starting."); + + _timer = new Timer(DoWork, null, TimeSpan.Zero, + TimeSpan.FromSeconds(60 * 60));//一个小时 + + return Task.CompletedTask; + } + + private void DoWork(object state) + { + try + { + var model = _blogArticleServices.GetBlogDetails(1).Result; + Console.WriteLine($"Job 1 启动成功,获取id=1的博客title为:{model?.btitle}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error:{ex.Message}"); + } + + ConsoleHelper.WriteSuccessLine($"Job 1: {DateTime.Now}"); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + Console.WriteLine("Job 1 is stopping."); + + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} diff --git a/Blog.Core.Extensions/HostedService/Job2TimedService.cs b/Blog.Core.Extensions/HostedService/Job2TimedService.cs new file mode 100644 index 00000000..a1d0d88a --- /dev/null +++ b/Blog.Core.Extensions/HostedService/Job2TimedService.cs @@ -0,0 +1,47 @@ +using Blog.Core.Common; +using Microsoft.Extensions.Hosting; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions +{ + public class Job2TimedService : IHostedService, IDisposable + { + private Timer _timer; + + // 这里可以注入 + public Job2TimedService() + { + } + + public Task StartAsync(CancellationToken cancellationToken) + { + Console.WriteLine("Job 2 is starting."); + + _timer = new Timer(DoWork, null, TimeSpan.Zero, + TimeSpan.FromSeconds(60 * 60 * 2));//两个小时 + + return Task.CompletedTask; + } + + private void DoWork(object state) + { + ConsoleHelper.WriteWarningLine($"Job 2: {DateTime.Now}"); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + Console.WriteLine("Job 2 is stopping."); + + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} diff --git a/Blog.Core.Extensions/HostedService/QuartzJobHostedService.cs b/Blog.Core.Extensions/HostedService/QuartzJobHostedService.cs new file mode 100644 index 00000000..d8f4d602 --- /dev/null +++ b/Blog.Core.Extensions/HostedService/QuartzJobHostedService.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.IServices; +using Blog.Core.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Blog.Core.Extensions.HostedService; + +public class QuartzJobHostedService : IHostedService +{ + private readonly ITasksQzServices _tasksQzServices; + private readonly ISchedulerCenter _schedulerCenter; + private readonly ILogger _logger; + + public QuartzJobHostedService(ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, ILogger logger) + { + _tasksQzServices = tasksQzServices; + _schedulerCenter = schedulerCenter; + _logger = logger; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Start QuartzJob Service!"); + await DoWork(); + } + + private async Task DoWork() + { + try + { + if (AppSettings.app("Middleware", "QuartzNetJob", "Enabled").ObjToBool()) + { + var allQzServices = await _tasksQzServices.Query(); + foreach (var item in allQzServices) + { + if (item.IsStart) + { + var result = await _schedulerCenter.AddScheduleJobAsync(item); + if (result.success) + { + Console.WriteLine($"QuartzNetJob{item.Name}启动成功!"); + } + else + { + Console.WriteLine($"QuartzNetJob{item.Name}启动失败!错误信息:{result.msg}"); + } + } + } + } + } + catch (Exception e) + { + _logger.LogError(e, "An error was reported when starting the job service."); + throw; + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Stop QuartzJob Service!"); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/HostedService/SeedDataHostedService.cs b/Blog.Core.Extensions/HostedService/SeedDataHostedService.cs new file mode 100644 index 00000000..e1da3d69 --- /dev/null +++ b/Blog.Core.Extensions/HostedService/SeedDataHostedService.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.Common.Seed; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Blog.Core.Extensions; + +public sealed class SeedDataHostedService : IHostedService +{ + private readonly MyContext _myContext; + private readonly ILogger _logger; + private readonly string _webRootPath; + + public SeedDataHostedService( + MyContext myContext, + IWebHostEnvironment webHostEnvironment, + ILogger logger) + { + _myContext = myContext; + _logger = logger; + _webRootPath = webHostEnvironment.WebRootPath; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Start Initialization Db Seed Service!"); + await DoWork(); + } + + private async Task DoWork() + { + try + { + if (AppSettings.app("AppSettings", "SeedDBEnabled").ObjToBool() || AppSettings.app("AppSettings", "SeedDBDataEnabled").ObjToBool()) + { + await DBSeed.SeedAsync(_myContext, _webRootPath); + + //日志 + DBSeed.MigrationLogs(_myContext); + + //多租户 同步 + await DBSeed.TenantSeedAsync(_myContext); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occured seeding the Database."); + throw; + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Stop Initialization Db Seed Service!"); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/ConsulMiddleware.cs b/Blog.Core.Extensions/Middlewares/ConsulMiddleware.cs deleted file mode 100644 index c1aae8cc..00000000 --- a/Blog.Core.Extensions/Middlewares/ConsulMiddleware.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; - -namespace Blog.Core.Extensions.Middlewares -{ - /// - /// Consul 注册服务 - /// - public static class ConsulMiddleware - { - public static IApplicationBuilder UseConsulMiddle(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime) - { - if (configuration["Middleware:Consul:Enabled"].ObjToBool()) - { - var consulClient = new ConsulClient(c => - { - //consul地址 - c.Address = new Uri(configuration["ConsulSetting:ConsulAddress"]); - }); - - var registration = new AgentServiceRegistration() - { - ID = Guid.NewGuid().ToString(),//服务实例唯一标识 - Name = configuration["ConsulSetting:ServiceName"],//服务名 - Address = configuration["ConsulSetting:ServiceIP"], //服务IP - Port = int.Parse(configuration["ConsulSetting:ServicePort"]),//服务端口 - Check = new AgentServiceCheck() - { - DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册 - Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔 - HTTP = $"http://{configuration["ConsulSetting:ServiceIP"]}:{configuration["ConsulSetting:ServicePort"]}{configuration["ConsulSetting:ServiceHealthCheck"]}",//健康检查地址 - Timeout = TimeSpan.FromSeconds(5)//超时时间 - } - }; - - //服务注册 - consulClient.Agent.ServiceRegister(registration).Wait(); - - //应用程序终止时,取消注册 - lifetime.ApplicationStopping.Register(() => - { - consulClient.Agent.ServiceDeregister(registration.ID).Wait(); - }); - - } - return app; - } - } -} diff --git a/Blog.Core.Extensions/Middlewares/EncryptionRequestMiddleware.cs b/Blog.Core.Extensions/Middlewares/EncryptionRequestMiddleware.cs new file mode 100644 index 00000000..9cb78a30 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/EncryptionRequestMiddleware.cs @@ -0,0 +1,116 @@ +using Blog.Core.Common; +using Blog.Core.Common.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions +{ + /// + /// 自定义中间件 + /// 通过配置,对指定接口返回数据进行加密返回 + /// 可过滤文件流 + /// + public class EncryptionRequestMiddleware + { + private readonly RequestDelegate _next; + + public EncryptionRequestMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // 配置开关,过滤接口 + if (AppSettings.app("Middleware", "EncryptionRequest", "Enabled").ObjToBool()) + { + var isAllApis = AppSettings.app("Middleware", "EncryptionRequest", "AllApis").ObjToBool(); + var needEnApis = AppSettings.app("Middleware", "EncryptionRequest", "LimitApis"); + var path = context.Request.Path.Value.ToLower(); + if (isAllApis || (path.Length > 5 && needEnApis.Any(d => d.ToLower().Contains(path)))) + { + Console.WriteLine($"{isAllApis} -- {path}"); + + if (context.Request.Method.ToLower() == "post") + { + // 读取请求主体 + using StreamReader reader = new(context.Request.Body, Encoding.UTF8); + string requestBody = await reader.ReadToEndAsync(); + + // 检查是否有要解密的数据 + if (!string.IsNullOrEmpty(requestBody) && context.Request.Headers.ContainsKey("Content-Type") && + context.Request.Headers["Content-Type"].ToString().ToLower().Contains("application/json")) + { + // 解密数据 + string decryptedString = DecryptData(requestBody); + + // 更新请求主体中的数据 + context.Request.Body = GenerateStreamFromString(decryptedString); + } + } + else if (context.Request.Method.ToLower() == "get") + { + // 获取url参数 + string param = context.Request.Query["param"]; + + // 检查是否有要解密的数据 + if (!string.IsNullOrEmpty(param)) + { + // 解密数据 + string decryptedString = DecryptData(param); + + // 更新url参数值 + context.Request.QueryString = new QueryString($"?{decryptedString}"); + } + } + + await _next(context); + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + private string DecryptData(string encryptedData) + { + // 解密逻辑实现,可以根据你使用的加密算法和密钥进行自定义 + byte[] bytes = Convert.FromBase64String(encryptedData); + string originalString = Encoding.UTF8.GetString(bytes); + Console.WriteLine(originalString); + return originalString; + } + private static Stream GenerateStreamFromString(string s) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(s); + writer.Flush(); + stream.Position = 0; + return stream; + } + } + + public static class EncryptionRequestExtensions + { + /// + /// 自定义中间件 + /// 通过配置,对指定接口入参进行解密操作 + /// 注意:放到管道最外层 + /// + public static IApplicationBuilder UseEncryptionRequest(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/EncryptionResponseMiddleware.cs b/Blog.Core.Extensions/Middlewares/EncryptionResponseMiddleware.cs new file mode 100644 index 00000000..188c6f8d --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/EncryptionResponseMiddleware.cs @@ -0,0 +1,118 @@ +using Blog.Core.Common; +using Blog.Core.Common.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions +{ + /// + /// 自定义中间件 + /// 通过配置,对指定接口返回数据进行加密返回 + /// 可过滤文件流 + /// + public class EncryptionResponseMiddleware + { + private readonly RequestDelegate _next; + + public EncryptionResponseMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // 配置开关,过滤接口 + if (AppSettings.app("Middleware", "EncryptionResponse", "Enabled").ObjToBool()) + { + var isAllApis = AppSettings.app("Middleware", "EncryptionResponse", "AllApis").ObjToBool(); + var needEnApis = AppSettings.app("Middleware", "EncryptionResponse", "LimitApis"); + var path = context.Request.Path.Value.ToLower(); + if (isAllApis || (path.Length > 5 && needEnApis.Any(d => d.ToLower().Contains(path)))) + { + Console.WriteLine($"{isAllApis} -- {path}"); + var responseCxt = context.Response; + var originalBodyStream = responseCxt.Body; + + // 创建一个新的内存流用于存储加密后的数据 + using var encryptedBodyStream = new MemoryStream(); + // 用新的内存流替换 responseCxt.Body + responseCxt.Body = encryptedBodyStream; + + // 执行下一个中间件请求管道 + await _next(context); + + //encryptedBodyStream.Seek(0, SeekOrigin.Begin); + //encryptedBodyStream.Position = 0; + + // 可以去掉某些流接口 + if (!context.Response.ContentType.ToLower().Contains("application/json")) + { + Console.WriteLine($"非json返回格式 {context.Response.ContentType}"); + //await encryptedBodyStream.CopyToAsync(originalBodyStream); + context.Response.Body = originalBodyStream; + return; + } + + // 读取加密后的数据 + //var encryptedBody = await new StreamReader(encryptedBodyStream).ReadToEndAsync(); + var encryptedBody = responseCxt.GetResponseBody(); + + if (encryptedBody.IsNotEmptyOrNull()) + { + dynamic jsonObject = JsonConvert.DeserializeObject(encryptedBody); + string statusCont = jsonObject.status; + var status = statusCont.ObjToInt(); + string msg = jsonObject.msg; + string successCont = jsonObject.success; + var success = successCont.ObjToBool(); + dynamic responseCnt = success ? jsonObject.response : ""; + string s = "1"; + // 这里换成自己的任意加密方式 + var response = responseCnt.ToString() != "" ? Convert.ToBase64String(Encoding.UTF8.GetBytes(responseCnt.ToString())) : ""; + string resJson = JsonConvert.SerializeObject(new { response, msg, status, s, success }); + + context.Response.Clear(); + responseCxt.ContentType = "application/json"; + + //await using var streamlriter = new StreamWriter(originalBodyStream, leaveOpen: true); + //await streamlriter.WriteAsync(resJson); + + var encryptedData = Encoding.UTF8.GetBytes(resJson); + responseCxt.ContentLength = encryptedData.Length; + await originalBodyStream.WriteAsync(encryptedData, 0, encryptedData.Length); + + responseCxt.Body = originalBodyStream; + } + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + } + + public static class EncryptionResponseExtensions + { + /// + /// 自定义中间件 + /// 通过配置,对指定接口返回数据进行加密返回 + /// 可过滤文件流 + /// 注意:放到管道最外层 + /// + public static IApplicationBuilder UseEncryptionResponse(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs b/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs index 85d96e9b..dc2cd17d 100644 --- a/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs @@ -10,8 +10,6 @@ namespace Blog.Core.Extensions.Middlewares public class ExceptionHandlerMiddleware { private readonly RequestDelegate _next; - private static readonly log4net.ILog Log = - log4net.LogManager.GetLogger(typeof(ExceptionHandlerMiddleware)); public ExceptionHandlerMiddleware(RequestDelegate next) { @@ -34,21 +32,27 @@ private async Task HandleExceptionAsync(HttpContext context, Exception e) { if (e == null) return; - Log.Error(e.GetBaseException().ToString()); - await WriteExceptionAsync(context, e).ConfigureAwait(false); } private static async Task WriteExceptionAsync(HttpContext context, Exception e) { - if (e is UnauthorizedAccessException) - context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - else if (e is Exception) - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + var message = e.Message; + switch (e) + { + case UnauthorizedAccessException: + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + break; + default: + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + break; + } context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonConvert.SerializeObject((new ApiResponse(StatusCode.CODE500, e.Message)).MessageModel)).ConfigureAwait(false); + await context.Response + .WriteAsync(JsonConvert.SerializeObject(new ApiResponse(StatusCode.CODE500, message).MessageModel)) + .ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/FluentResponseBodyMiddleware.cs b/Blog.Core.Extensions/Middlewares/FluentResponseBodyMiddleware.cs new file mode 100644 index 00000000..dfb5d19e --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/FluentResponseBodyMiddleware.cs @@ -0,0 +1,21 @@ +using System.IO; +using Blog.Core.Common.Https; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Features; + +namespace Blog.Core.Extensions.Middlewares; + +public static class FluentResponseBodyMiddleware +{ + public static IApplicationBuilder UseResponseBodyRead(this IApplicationBuilder app) + { + return app.Use(async (context, next) => + { + await using var swapStream = new FluentHttpResponseStream(context!.Features!.Get()!, + context!.Features!.Get()!); + context.Response.Body = swapStream; + await next(context); + context.Response.Body.Seek(0, SeekOrigin.Begin); + }); + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs b/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs index 958d6ff3..7fe68fc4 100644 --- a/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs @@ -1,8 +1,8 @@ -using System; -using AspNetCoreRateLimit; +using AspNetCoreRateLimit; using Blog.Core.Common; -using log4net; using Microsoft.AspNetCore.Builder; +using System; +using Serilog; namespace Blog.Core.Extensions.Middlewares { @@ -11,7 +11,6 @@ namespace Blog.Core.Extensions.Middlewares ///
public static class IpLimitMiddleware { - private static readonly ILog Log = LogManager.GetLogger(typeof(IpLimitMiddleware)); public static void UseIpLimitMiddle(this IApplicationBuilder app) { if (app == null) throw new ArgumentNullException(nameof(app)); diff --git a/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs b/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs index b6b91bd5..ee74d6fc 100644 --- a/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs @@ -1,10 +1,11 @@ -using System; -using System.Threading.Tasks; -using Blog.Core.Common; +using Blog.Core.Common; using Blog.Core.Common.LogHelper; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace Blog.Core.Extensions.Middlewares { @@ -18,17 +19,15 @@ public class IpLogMiddleware /// ///
private readonly RequestDelegate _next; + private readonly IWebHostEnvironment _environment; - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(IpLogMiddleware)); + private readonly ILogger _logger; - /// - /// - /// - /// - public IpLogMiddleware(RequestDelegate next, IWebHostEnvironment environment) + public IpLogMiddleware(RequestDelegate next, IWebHostEnvironment environment, ILogger logger) { - _next = next; + _next = next; _environment = environment; + _logger = logger; } public async Task InvokeAsync(HttpContext context) @@ -40,53 +39,44 @@ public async Task InvokeAsync(HttpContext context) { context.Request.EnableBuffering(); - try - { - // 存储请求数据 - var request = context.Request; - - var requestInfo = JsonConvert.SerializeObject(new RequestInfo() - { - Ip = GetClientIP(context), - Url = request.Path.ObjToString().TrimEnd('/').ToLower(), - Datetime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), - Date = DateTime.Now.ToString("yyyy-MM-dd"), - Week = GetWeek(), - }); - - if (!string.IsNullOrEmpty(requestInfo)) - { - // 自定义log输出 - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RequestIpInfoLog", new string[] { requestInfo + "," }, false); - LogLock.OutLogAOP("RequestIpInfoLog", context.TraceIdentifier, new string[] { requestInfo.GetType().ToString(), requestInfo }, false); - }); - - //try - //{ - // var testLogMatchRequestInfo = JsonConvert.DeserializeObject(requestInfo); - // if (testLogMatchRequestInfo != null) - // { - // var logFileName = FileHelper.GetAvailableFileNameWithPrefixOrderSize(_environment.ContentRootPath, "RequestIpInfoLog"); - // SerilogServer.WriteLog(logFileName, new string[] { requestInfo + "," }, false, "", true); - // } - //} - //catch (Exception e) - //{ - // log.Error(requestInfo + "\r\n" + e.GetBaseException().ToString()); - //} + // 存储请求数据 + var request = context.Request; - request.Body.Position = 0; - } + var requestInfo = JsonConvert.SerializeObject(new RequestInfo() + { + Ip = GetClientIP(context), + Url = request.Path.ObjToString().TrimEnd('/').ToLower(), + Datetime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), + Date = DateTime.Now.ToString("yyyy-MM-dd"), + Week = GetWeek(), + }); - await _next(context); - } - catch (Exception) + if (!string.IsNullOrEmpty(requestInfo)) { - // ignored + // 自定义log输出 + _logger.LogInformation("RequestIpInfoLog:{TraceIdentifier}: {RequestInfo}", + context.TraceIdentifier, + requestInfo); + + //try + //{ + // var testLogMatchRequestInfo = JsonConvert.DeserializeObject(requestInfo); + // if (testLogMatchRequestInfo != null) + // { + // var logFileName = FileHelper.GetAvailableFileNameWithPrefixOrderSize(_environment.ContentRootPath, "RequestIpInfoLog"); + // SerilogServer.WriteLog(logFileName, new string[] { requestInfo + "," }, false, "", true); + // } + //} + //catch (Exception e) + //{ + // log.Error(requestInfo + "\r\n" + e.GetBaseException().ToString()); + //} + + request.Body.Position = 0; } + + await _next(context); } else { @@ -129,6 +119,7 @@ private string GetWeek() week = "N/A"; break; } + return week; } @@ -139,9 +130,8 @@ public static string GetClientIP(HttpContext context) { ip = context.Connection.RemoteIpAddress.ObjToString(); } + return ip; } - } -} - +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs b/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs index 068c4a0d..49e3b70d 100644 --- a/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs @@ -1,7 +1,7 @@ -using System; -using Blog.Core.Common; -using log4net; +using Blog.Core.Common; using Microsoft.AspNetCore.Builder; +using System; +using Serilog; namespace Blog.Core.Extensions.Middlewares { @@ -10,7 +10,6 @@ namespace Blog.Core.Extensions.Middlewares ///
public static class MiniProfilerMiddleware { - private static readonly ILog Log = LogManager.GetLogger(typeof(MiniProfilerMiddleware)); public static void UseMiniProfilerMiddleware(this IApplicationBuilder app) { if (app == null) throw new ArgumentNullException(nameof(app)); diff --git a/Blog.Core.Extensions/Middlewares/QuartzJobMiddleware.cs b/Blog.Core.Extensions/Middlewares/QuartzJobMiddleware.cs deleted file mode 100644 index 69c52de3..00000000 --- a/Blog.Core.Extensions/Middlewares/QuartzJobMiddleware.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Blog.Core.Common; -using Blog.Core.IServices; -using Blog.Core.Tasks; -using log4net; -using Microsoft.AspNetCore.Builder; - -namespace Blog.Core.Extensions.Middlewares -{ - /// - /// Quartz 启动服务 - /// - public static class QuartzJobMiddleware - { - private static readonly ILog Log = LogManager.GetLogger(typeof(QuartzJobMiddleware)); - public static void UseQuartzJobMiddleware(this IApplicationBuilder app, ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter) - { - if (app == null) throw new ArgumentNullException(nameof(app)); - - try - { - if (AppSettings.app("Middleware", "QuartzNetJob", "Enabled").ObjToBool()) - { - - var allQzServices = tasksQzServices.Query().Result; - foreach (var item in allQzServices) - { - if (item.IsStart) - { - var result = schedulerCenter.AddScheduleJobAsync(item).Result; - if (result.success) - { - Console.WriteLine($"QuartzNetJob{item.Name}启动成功!"); - } - else - { - Console.WriteLine($"QuartzNetJob{item.Name}启动失败!错误信息:{result.msg}"); - } - } - } - - } - } - catch (Exception e) - { - Log.Error($"An error was reported when starting the job service.\n{e.Message}"); - throw; - } - } - } -} diff --git a/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs index 4e12dd93..cee13581 100644 --- a/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Web; using Blog.Core.Common; +using Blog.Core.Common.Extensions; using Blog.Core.Common.Helper; using Blog.Core.Common.HttpContextUser; using Blog.Core.Common.LogHelper; @@ -25,6 +26,7 @@ public class RecordAccessLogsMiddleware /// /// private readonly RequestDelegate _next; + private readonly IUser _user; private readonly ILogger _logger; private readonly IWebHostEnvironment _environment; @@ -34,20 +36,21 @@ public class RecordAccessLogsMiddleware /// /// /// - public RecordAccessLogsMiddleware(RequestDelegate next, IUser user, ILogger logger, IWebHostEnvironment environment) + public RecordAccessLogsMiddleware(RequestDelegate next, IUser user, ILogger logger, + IWebHostEnvironment environment) { - _next = next; - _user = user; - _logger = logger; + _next = next; + _user = user; + _logger = logger; _environment = environment; - _stopwatch = new Stopwatch(); + _stopwatch = new Stopwatch(); } public async Task InvokeAsync(HttpContext context) { if (AppSettings.app("Middleware", "RecordAccessLogs", "Enabled").ObjToBool()) { - var api = context.Request.Path.ObjToString().TrimEnd('/').ToLower(); + var api = context.Request.Path.ObjToString().TrimEnd('/').ToLower(); var ignoreApis = AppSettings.app("Middleware", "RecordAccessLogs", "IgnoreApis"); // 过滤,只有接口 @@ -58,12 +61,12 @@ public async Task InvokeAsync(HttpContext context) HttpRequest request = context.Request; - userAccessModel.API = api; - userAccessModel.User = _user.Name; - userAccessModel.IP = IpLogMiddleware.GetClientIP(context); - userAccessModel.BeginTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + userAccessModel.API = api; + userAccessModel.User = _user.Name; + userAccessModel.IP = IpLogMiddleware.GetClientIP(context); + userAccessModel.BeginTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); userAccessModel.RequestMethod = request.Method; - userAccessModel.Agent = request.Headers["User-Agent"].ObjToString(); + userAccessModel.Agent = request.Headers["User-Agent"].ObjToString(); // 获取请求body内容 @@ -81,21 +84,11 @@ public async Task InvokeAsync(HttpContext context) } else if (request.Method.ToLower().Equals("get") || request.Method.ToLower().Equals("delete")) { - userAccessModel.RequestData = HttpUtility.UrlDecode(request.QueryString.ObjToString(), Encoding.UTF8); + userAccessModel.RequestData = + HttpUtility.UrlDecode(request.QueryString.ObjToString(), Encoding.UTF8); } - // 获取Response.Body内容 - var originalBodyStream = context.Response.Body; - using (var responseBody = new MemoryStream()) - { - context.Response.Body = responseBody; - - await _next(context); - - var responseBodyData = await GetResponse(context.Response); - - await responseBody.CopyToAsync(originalBodyStream); - } + await _next(context); // 响应完成记录时间和存入日志 context.Response.OnCompleted(() => @@ -106,16 +99,11 @@ public async Task InvokeAsync(HttpContext context) // 自定义log输出 var requestInfo = JsonConvert.SerializeObject(userAccessModel); - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RecordAccessLogs", new string[] { requestInfo + "," }, false); - LogLock.OutLogAOP("RecordAccessLogs", context.TraceIdentifier, new string[] { userAccessModel.GetType().ToString(), requestInfo }, false); - }); - //var logFileName = FileHelper.GetAvailableFileNameWithPrefixOrderSize(_environment.ContentRootPath, "RecordAccessLogs"); - //SerilogServer.WriteLog(logFileName, new string[] { requestInfo + "," }, false); + _logger.LogInformation("RecordAccessLogs:{TraceIdentifier}: {RequestInfo}", + context.TraceIdentifier, + requestInfo); return Task.CompletedTask; }); - } else { @@ -127,20 +115,6 @@ public async Task InvokeAsync(HttpContext context) await _next(context); } } - - - /// - /// 获取响应内容 - /// - /// - /// - public async Task GetResponse(HttpResponse response) - { - response.Body.Seek(0, SeekOrigin.Begin); - var text = await new StreamReader(response.Body).ReadToEndAsync(); - response.Body.Seek(0, SeekOrigin.Begin); - return text; - } } public class UserAccessModel @@ -153,8 +127,5 @@ public class UserAccessModel public string RequestMethod { get; set; } public string RequestData { get; set; } public string Agent { get; set; } - } - -} - +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs b/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs index 6ff8d044..9917d83d 100644 --- a/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs @@ -3,11 +3,11 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Blog.Core.Common; +using Blog.Core.Common.Extensions; using Blog.Core.Common.LogHelper; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using Ubiety.Dns.Core.Common; namespace Blog.Core.Extensions.Middlewares { @@ -21,6 +21,7 @@ public class RequRespLogMiddleware /// /// private readonly RequestDelegate _next; + private readonly ILogger _logger; /// @@ -29,12 +30,11 @@ public class RequRespLogMiddleware /// public RequRespLogMiddleware(RequestDelegate next, ILogger logger) { - _next = next; + _next = next; _logger = logger; } - public async Task InvokeAsync(HttpContext context) { if (AppSettings.app("Middleware", "RequestResponseLog", "Enabled").ObjToBool()) @@ -43,35 +43,14 @@ public async Task InvokeAsync(HttpContext context) if (context.Request.Path.Value.Contains("api")) { context.Request.EnableBuffering(); - Stream originalBody = context.Response.Body; - - try - { - // 存储请求数据 - await RequestDataLog(context); - - using (var ms = new MemoryStream()) - { - context.Response.Body = ms; - - await _next(context); - - // 存储响应数据 - ResponseDataLog(context.Response, ms); - - ms.Position = 0; - await ms.CopyToAsync(originalBody); - } - } - catch (Exception ex) - { - // 记录异常 - _logger.LogError(ex.Message + "" + ex.InnerException); - } - finally - { - context.Response.Body = originalBody; - } + + // 存储请求数据 + await RequestDataLog(context); + + await _next(context); + + // 存储响应数据 + ResponseDataLog(context.Response); } else { @@ -87,27 +66,38 @@ public async Task InvokeAsync(HttpContext context) private async Task RequestDataLog(HttpContext context) { var request = context.Request; - var sr = new StreamReader(request.Body); + var sr = new StreamReader(request.Body); RequestLogInfo requestResponse = new RequestLogInfo() { - Path = request.Path, + Path = request.Path, QueryString = request.QueryString.ToString(), - BodyData = await sr.ReadToEndAsync() + BodyData = await sr.ReadToEndAsync() }; var content = JsonConvert.SerializeObject(requestResponse); //var content = $" QueryData:{request.Path + request.QueryString}\r\n BodyData:{await sr.ReadToEndAsync()}"; if (!string.IsNullOrEmpty(content)) { - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RequestResponseLog", new string[] { "Request Data:", content }); - LogLock.OutLogAOP("RequestResponseLog", context.TraceIdentifier, new string[] { "Request Data - RequestJsonDataType:" + requestResponse.GetType().ToString(), content }); + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {RequestData}", + context.TraceIdentifier, + content); + request.Body.Position = 0; + } + } - }); - //SerilogServer.WriteLog("RequestResponseLog", new string[] { "Request Data:", content }); + private void ResponseDataLog(HttpResponse response) + { + var responseBody = response.GetResponseBody(); - request.Body.Position = 0; + // 去除 Html + var reg = "<[^>]+>"; + + if (!string.IsNullOrEmpty(responseBody)) + { + var isHtml = Regex.IsMatch(responseBody, reg); + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {ResponseData}", + response.HttpContext.TraceIdentifier, + responseBody); } } @@ -117,20 +107,15 @@ private void ResponseDataLog(HttpResponse response, MemoryStream ms) var responseBody = new StreamReader(ms).ReadToEnd(); // 去除 Html - var reg = "<[^>]+>"; + var reg = "<[^>]+>"; var isHtml = Regex.IsMatch(responseBody, reg); if (!string.IsNullOrEmpty(responseBody)) { - Parallel.For(0, 1, e => - { - //LogLock.OutSql2Log("RequestResponseLog", new string[] { "Response Data:", ResponseBody }); - LogLock.OutLogAOP("RequestResponseLog", response.HttpContext.TraceIdentifier, new string[] { "Response Data - ResponseJsonDataType:" + responseBody.GetType().ToString(), responseBody }); - - }); - //SerilogServer.WriteLog("RequestResponseLog", new string[] { "Response Data:", responseBody }); + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {ResponseData}", + response.HttpContext.TraceIdentifier, + responseBody); } } } -} - +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/SeedDataMiddleware.cs b/Blog.Core.Extensions/Middlewares/SeedDataMiddleware.cs deleted file mode 100644 index ad944bfd..00000000 --- a/Blog.Core.Extensions/Middlewares/SeedDataMiddleware.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Blog.Core.Common; -using Blog.Core.Common.Seed; -using log4net; -using Microsoft.AspNetCore.Builder; - -namespace Blog.Core.Extensions.Middlewares -{ - /// - /// 生成种子数据中间件服务 - /// - public static class SeedDataMiddleware - { - private static readonly ILog Log = LogManager.GetLogger(typeof(SeedDataMiddleware)); - public static void UseSeedDataMiddle(this IApplicationBuilder app, MyContext myContext, string webRootPath) - { - if (app == null) throw new ArgumentNullException(nameof(app)); - - try - { - if (AppSettings.app("AppSettings", "SeedDBEnabled").ObjToBool() || AppSettings.app("AppSettings", "SeedDBDataEnabled").ObjToBool()) - { - DBSeed.SeedAsync(myContext, webRootPath).Wait(); - } - } - catch (Exception e) - { - Log.Error($"Error occured seeding the Database.\n{e.Message}"); - throw; - } - } - } -} diff --git a/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs index 1909c08f..f1e4ee51 100644 --- a/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs @@ -17,6 +17,7 @@ public class SignalRSendMiddleware /// /// private readonly RequestDelegate _next; + private readonly IHubContext _hubContext; /// @@ -26,21 +27,20 @@ public class SignalRSendMiddleware /// public SignalRSendMiddleware(RequestDelegate next, IHubContext hubContext) { - _next = next; + _next = next; _hubContext = hubContext; } - public async Task InvokeAsync(HttpContext context) { if (AppSettings.app("Middleware", "SignalR", "Enabled").ObjToBool()) { - await _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()); + //TODO 主动发送错误消息 + await _hubContext.Clients.All.SendAsync("ReceiveUpdate", "这是一个Log"); } + await _next(context); } - } -} - +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/SwaggerAuthMiddleware.cs b/Blog.Core.Extensions/Middlewares/SwaggerAuthMiddleware.cs index 1438588c..3cc284a1 100644 --- a/Blog.Core.Extensions/Middlewares/SwaggerAuthMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/SwaggerAuthMiddleware.cs @@ -1,5 +1,6 @@ using System.Net; using System.Threading.Tasks; +using Blog.Core.Common.Swagger; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -28,7 +29,7 @@ public async Task InvokeAsync(HttpContext context) } // 无权限,跳转swagger登录页 - context.Response.Redirect("/swg-login.html"); + context.RedirectSwaggerLogin(); } else { @@ -40,7 +41,7 @@ public bool IsAuthorized(HttpContext context) { // 使用session模式 // 可以使用其他的 - return context.Session.GetString("swagger-code") == "success"; + return context.IsSuccessSwagger(); } /// diff --git a/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs b/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs index 099b2576..141ffe40 100644 --- a/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs +++ b/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs @@ -1,10 +1,10 @@ -using System; -using System.IO; -using System.Linq; -using Blog.Core.Common; -using log4net; +using Blog.Core.Common; using Microsoft.AspNetCore.Builder; using Swashbuckle.AspNetCore.SwaggerUI; +using System; +using System.IO; +using System.Linq; +using Serilog; using static Blog.Core.Extensions.CustomApiVersion; namespace Blog.Core.Extensions.Middlewares @@ -14,7 +14,6 @@ namespace Blog.Core.Extensions.Middlewares /// public static class SwaggerMiddleware { - private static readonly ILog Log = LogManager.GetLogger(typeof(SwaggerMiddleware)); public static void UseSwaggerMiddle(this IApplicationBuilder app, Func streamHtml) { if (app == null) throw new ArgumentNullException(nameof(app)); @@ -24,10 +23,7 @@ public static void UseSwaggerMiddle(this IApplicationBuilder app, Func s { //根据版本名称倒序 遍历展示 var apiName = AppSettings.app(new string[] { "Startup", "ApiName" }); - typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => - { - c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{apiName} {version}"); - }); + typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => { c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{apiName} {version}"); }); c.SwaggerEndpoint($"https://petstore.swagger.io/v2/swagger.json", $"{apiName} pet"); @@ -38,18 +34,21 @@ public static void UseSwaggerMiddle(this IApplicationBuilder app, Func s Log.Error(msg); throw new Exception(msg); } + c.IndexStream = streamHtml; c.DocExpansion(DocExpansion.None); //->修改界面打开时自动折叠 if (Permissions.IsUseIds4) { - c.OAuthClientId("blogadminjs"); + c.OAuthClientId("blogadminjs"); } + //增加令牌本地缓存 reload不会丢失 + c.ConfigObject.AdditionalItems.Add("persistAuthorization","true"); // 路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.RoutePrefix = "doc"; c.RoutePrefix = ""; }); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Redis/IRedisBasketRepository.cs b/Blog.Core.Extensions/Redis/IRedisBasketRepository.cs index ca1827fd..85408160 100644 --- a/Blog.Core.Extensions/Redis/IRedisBasketRepository.cs +++ b/Blog.Core.Extensions/Redis/IRedisBasketRepository.cs @@ -1,6 +1,7 @@ using StackExchange.Redis; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; namespace Blog.Core.Extensions @@ -8,6 +9,7 @@ namespace Blog.Core.Extensions /// /// Redis缓存接口 /// + [Description("普通缓存考虑直接使用ICaching,如果要使用Redis队列等还是使用此类")] public interface IRedisBasketRepository { diff --git a/Blog.Core.Extensions/Redis/IRedisCacheManager.cs b/Blog.Core.Extensions/Redis/IRedisCacheManager.cs deleted file mode 100644 index 56cd4f84..00000000 --- a/Blog.Core.Extensions/Redis/IRedisCacheManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace Blog.Core.Extensions -{ - /// - /// Redis缓存接口 - /// - public interface IRedisCacheManager - { - - //获取 Reids 缓存值 - string GetValue(string key); - - //获取值,并序列化 - TEntity Get(string key); - - //保存 - void Set(string key, object value, TimeSpan cacheTime); - - //判断是否存在 - bool Get(string key); - - //移除某一个缓存值 - void Remove(string key); - - //全部清除 - void Clear(); - } -} diff --git a/Blog.Core.Extensions/Redis/RedisBasketRepository.cs b/Blog.Core.Extensions/Redis/RedisBasketRepository.cs index 31c7a030..822f9253 100644 --- a/Blog.Core.Extensions/Redis/RedisBasketRepository.cs +++ b/Blog.Core.Extensions/Redis/RedisBasketRepository.cs @@ -4,11 +4,13 @@ using StackExchange.Redis; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Threading.Tasks; namespace Blog.Core.Extensions { + [Description("普通缓存考虑直接使用ICaching,如果要使用Redis队列等还是使用此类")] public class RedisBasketRepository : IRedisBasketRepository { private readonly ILogger _logger; diff --git a/Blog.Core.Extensions/Redis/RedisCacheManager.cs b/Blog.Core.Extensions/Redis/RedisCacheManager.cs deleted file mode 100644 index 0e5d7819..00000000 --- a/Blog.Core.Extensions/Redis/RedisCacheManager.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Blog.Core.Common; -using StackExchange.Redis; -using System; - -namespace Blog.Core.Extensions -{ - public class RedisCacheManager : IRedisCacheManager - { - - private readonly string redisConnenctionString; - - public volatile ConnectionMultiplexer redisConnection; - - private readonly object redisConnectionLock = new object(); - - public RedisCacheManager() - { - string redisConfiguration = AppSettings.app(new string[] { "AppSettings", "RedisCachingAOP", "ConnectionString" });//获取连接字符串 - - if (string.IsNullOrWhiteSpace(redisConfiguration)) - { - throw new ArgumentException("redis config is empty", nameof(redisConfiguration)); - } - this.redisConnenctionString = redisConfiguration; - this.redisConnection = GetRedisConnection(); - } - - /// - /// 核心代码,获取连接实例 - /// 通过双if 夹lock的方式,实现单例模式 - /// - /// - private ConnectionMultiplexer GetRedisConnection() - { - //如果已经连接实例,直接返回 - if (this.redisConnection != null && this.redisConnection.IsConnected) - { - return this.redisConnection; - } - //加锁,防止异步编程中,出现单例无效的问题 - lock (redisConnectionLock) - { - if (this.redisConnection != null) - { - //释放redis连接 - this.redisConnection.Dispose(); - } - try - { - var config = new ConfigurationOptions - { - AbortOnConnectFail = false, - AllowAdmin = true, - ConnectTimeout = 15000,//改成15s - SyncTimeout = 5000, - //Password = "Pwd",//Redis数据库密码 - EndPoints = { redisConnenctionString }// connectionString 为IP:Port 如”192.168.2.110:6379” - }; - this.redisConnection = ConnectionMultiplexer.Connect(config); - } - catch (Exception) - { - throw new Exception("Redis服务未启用,请开启该服务,并且请注意端口号,本项目使用的的6319,而且我的是没有设置密码。"); - } - } - return this.redisConnection; - } - /// - /// 清除 - /// - public void Clear() - { - foreach (var endPoint in this.GetRedisConnection().GetEndPoints()) - { - var server = this.GetRedisConnection().GetServer(endPoint); - foreach (var key in server.Keys()) - { - redisConnection.GetDatabase().KeyDelete(key); - } - } - } - /// - /// 判断是否存在 - /// - /// - /// - public bool Get(string key) - { - return redisConnection.GetDatabase().KeyExists(key); - } - - /// - /// 查询 - /// - /// - /// - public string GetValue(string key) - { - return redisConnection.GetDatabase().StringGet(key); - } - - /// - /// 获取 - /// - /// - /// - /// - public TEntity Get(string key) - { - var value = redisConnection.GetDatabase().StringGet(key); - if (value.HasValue) - { - //需要用的反序列化,将Redis存储的Byte[],进行反序列化 - return SerializeHelper.Deserialize(value); - } - else - { - return default(TEntity); - } - } - - /// - /// 移除 - /// - /// - public void Remove(string key) - { - redisConnection.GetDatabase().KeyDelete(key); - } - /// - /// 设置 - /// - /// - /// - /// - public void Set(string key, object value, TimeSpan cacheTime) - { - if (value != null) - { - if (value is string cacheValue) - { - // 字符串无需序列化 - redisConnection.GetDatabase().StringSet(key, cacheValue, cacheTime); - } - else - { - //序列化,将object值生成RedisValue - redisConnection.GetDatabase().StringSet(key, SerializeHelper.Serialize(value), cacheTime); - } - } - } - - /// - /// 增加/修改 - /// - /// - /// - /// - public bool SetValue(string key, byte[] value) - { - return redisConnection.GetDatabase().StringSet(key, value, TimeSpan.FromSeconds(120)); - } - - } -} diff --git a/Blog.Core.Extensions/ServiceExtensions/AllOptionRegister.cs b/Blog.Core.Extensions/ServiceExtensions/AllOptionRegister.cs new file mode 100644 index 00000000..28e1739b --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/AllOptionRegister.cs @@ -0,0 +1,21 @@ +using Blog.Core.Common; +using Blog.Core.Common.Option.Core; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; + +namespace Blog.Core.Extensions.ServiceExtensions; + +public static class AllOptionRegister +{ + public static void AddAllOptionRegister(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + foreach (var optionType in App.EffectiveTypes.Where(s => + !s.IsInterface && typeof(IConfigurableOptions).IsAssignableFrom(s))) + { + services.AddConfigurableOptions(optionType); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/AppConfigSetup.cs b/Blog.Core.Extensions/ServiceExtensions/AppConfigSetup.cs index 0622e766..7cb098ff 100644 --- a/Blog.Core.Extensions/ServiceExtensions/AppConfigSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/AppConfigSetup.cs @@ -1,12 +1,9 @@ using Blog.Core.Common; -using Blog.Core.Common.Helper; -using Blog.Core.Common.LogHelper; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using System; -using System.Collections.Generic; using System.Text; +using Blog.Core.Common.DB; namespace Blog.Core.Extensions { @@ -15,214 +12,12 @@ namespace Blog.Core.Extensions /// public static class AppConfigSetup { - public static void AddAppConfigSetup(this IServiceCollection services, IHostEnvironment env) - { - if (services == null) throw new ArgumentNullException(nameof(services)); - - if (AppSettings.app(new string[] { "Startup", "AppConfigAlert", "Enabled" }).ObjToBool()) - { - if (env.IsDevelopment()) - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - Console.OutputEncoding = Encoding.GetEncoding("GB2312"); - } - - Console.WriteLine("************ Blog.Core Config Set *****************"); - - ConsoleHelper.WriteSuccessLine("Current environment: " + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); - - // 授权策略方案 - if (Permissions.IsUseIds4) - { - ConsoleHelper.WriteSuccessLine($"Current authorization scheme: " + (Permissions.IsUseIds4 ? "Ids4" : "JWT")); - } - else - { - Console.WriteLine($"Current authorization scheme: " + (Permissions.IsUseIds4 ? "Ids4" : "JWT")); - } - - // Redis缓存AOP - if (!AppSettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool()) - { - Console.WriteLine($"Redis Caching AOP: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Redis Caching AOP: True"); - } - - // 内存缓存AOP - if (!AppSettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) - { - Console.WriteLine($"Memory Caching AOP: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Memory Caching AOP: True"); - } - - // 服务日志AOP - if (!AppSettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool()) - { - Console.WriteLine($"Service Log AOP: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Service Log AOP: True"); - } - - // 开启的中间件日志 - var requestResponseLogOpen = AppSettings.app(new string[] { "Middleware", "RequestResponseLog", "Enabled" }).ObjToBool(); - var ipLogOpen = AppSettings.app(new string[] { "Middleware", "IPLog", "Enabled" }).ObjToBool(); - var recordAccessLogsOpen = AppSettings.app(new string[] { "Middleware", "RecordAccessLogs", "Enabled" }).ObjToBool(); - ConsoleHelper.WriteSuccessLine($"OPEN Log: " + - (requestResponseLogOpen ? "RequestResponseLog √," : "") + - (ipLogOpen ? "IPLog √," : "") + - (recordAccessLogsOpen ? "RecordAccessLogs √," : "") - ); - - // 事务AOP - if (!AppSettings.app(new string[] { "AppSettings", "TranAOP", "Enabled" }).ObjToBool()) - { - Console.WriteLine($"Transaction AOP: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Transaction AOP: True"); - } - - // 数据库Sql执行AOP - if (!AppSettings.app(new string[] { "AppSettings", "SqlAOP", "OutToLogFile", "Enabled" }).ObjToBool()) - { - Console.WriteLine($"DB Sql AOP To LogFile: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"DB Sql AOP To LogFile: True"); - } - - // Sql执行日志输出到控制台 - if (!AppSettings.app(new string[] { "AppSettings", "SqlAOP", "OutToConsole", "Enabled" }).ObjToBool()) - { - Console.WriteLine($"DB Sql AOP To Console: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"DB Sql AOP To Console: True"); - } - - // SingnalR发送数据 - if (!AppSettings.app(new string[] { "Middleware", "SignalR", "Enabled" }).ObjToBool()) - { - Console.WriteLine($"SignalR send data: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"SignalR send data: True"); - } - - // IP限流 - if (!AppSettings.app("Middleware", "IpRateLimit", "Enabled").ObjToBool()) - { - Console.WriteLine($"IpRateLimiting: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"IpRateLimiting: True"); - } - - // 性能分析 - if (!AppSettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool()) - { - Console.WriteLine($"MiniProfiler: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"MiniProfiler: True"); - } - - // CORS跨域 - if (!AppSettings.app("Startup", "Cors", "EnableAllIPs").ObjToBool()) - { - Console.WriteLine($"EnableAllIPs For CORS: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"EnableAllIPs For CORS: True"); - } - - // redis消息队列 - if (!AppSettings.app("Startup", "RedisMq", "Enabled").ObjToBool()) - { - Console.WriteLine($"Redis MQ: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Redis MQ: True"); - } - - // RabbitMQ 消息队列 - if (!AppSettings.app("RabbitMQ", "Enabled").ObjToBool()) - { - Console.WriteLine($"RabbitMQ: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"RabbitMQ: True"); - } - - // Consul 注册服务 - if (!AppSettings.app("Middleware", "Consul", "Enabled").ObjToBool()) - { - Console.WriteLine($"Consul service: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Consul service: True"); - } - - // EventBus 事件总线 - if (!AppSettings.app("EventBus", "Enabled").ObjToBool()) - { - Console.WriteLine($"EventBus: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"EventBus: True"); - } - - // 多库 - if (!AppSettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()) - { - Console.WriteLine($"Is multi-DataBase: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Is multi-DataBase: True"); - } - - // 读写分离 - if (!AppSettings.app(new string[] { "CQRSEnabled" }).ObjToBool()) - { - Console.WriteLine($"Is CQRS: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Is CQRS: True"); - } - - Console.WriteLine(); - } - - } - public static void AddAppTableConfigSetup(this IServiceCollection services, IHostEnvironment env) { if (services == null) throw new ArgumentNullException(nameof(services)); if (AppSettings.app(new string[] { "Startup", "AppConfigAlert", "Enabled" }).ObjToBool()) { - if (env.IsDevelopment()) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); @@ -230,6 +25,7 @@ public static void AddAppTableConfigSetup(this IServiceCollection services, IHos } #region 程序配置 + List configInfos = new() { new string[] { "当前环境", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") }, @@ -238,8 +34,7 @@ public static void AddAppTableConfigSetup(this IServiceCollection services, IHos new string[] { "RabbitMQ消息列队", AppSettings.app("RabbitMQ", "Enabled") }, new string[] { "事件总线(必须开启消息列队)", AppSettings.app("EventBus", "Enabled") }, new string[] { "redis消息队列", AppSettings.app("Startup", "RedisMq", "Enabled") }, - new string[] { "是否多库", AppSettings.app("MutiDBEnabled" ) }, - new string[] { "读写分离", AppSettings.app("CQRSEnabled") }, + new string[] { "读写分离", BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException()? "True" : "False" }, }; new ConsoleTable() @@ -253,17 +48,19 @@ public static void AddAppTableConfigSetup(this IServiceCollection services, IHos TableStyle = TableStyle.Alternative }.Writer(ConsoleColor.Blue); Console.WriteLine(); + #endregion 程序配置 #region AOP + List aopInfos = new() -{ - new string[] { "Redis缓存AOP", AppSettings.app("AppSettings", "RedisCachingAOP", "Enabled") }, - new string[] { "内存缓存AOP", AppSettings.app("AppSettings", "MemoryCachingAOP", "Enabled") }, - new string[] { "服务日志AOP", AppSettings.app("AppSettings", "LogAOP", "Enabled" ) }, - new string[] { "事务AOP", AppSettings.app("AppSettings", "TranAOP", "Enabled" ) }, - new string[] { "Sql执行AOP", AppSettings.app("AppSettings", "SqlAOP", "OutToLogFile", "Enabled" ) }, - new string[] { "Sql执行AOP控制台输出", AppSettings.app("AppSettings", "SqlAOP", "OutToConsole", "Enabled" ) }, + { + new string[] { "缓存AOP", AppSettings.app("AppSettings", "CachingAOP", "Enabled") }, + new string[] { "服务日志AOP", AppSettings.app("AppSettings", "LogAOP", "Enabled") }, + new string[] { "事务AOP", AppSettings.app("AppSettings", "TranAOP", "Enabled") }, + new string[] { "服务审计AOP", AppSettings.app("AppSettings", "UserAuditAOP", "Enabled") }, + new string[] { "Sql执行AOP", AppSettings.app("AppSettings", "SqlAOP", "Enabled") }, + new string[] { "Sql执行AOP控制台输出", AppSettings.app("AppSettings", "SqlAOP", "LogToConsole", "Enabled") }, }; new ConsoleTable @@ -277,15 +74,17 @@ public static void AddAppTableConfigSetup(this IServiceCollection services, IHos TableStyle = TableStyle.Alternative }.Writer(ConsoleColor.Blue); Console.WriteLine(); + #endregion AOP #region 中间件 + List MiddlewareInfos = new() { new string[] { "请求纪录中间件", AppSettings.app("Middleware", "RecordAccessLogs", "Enabled") }, - new string[] { "IP记录中间件", AppSettings.app("Middleware", "IPLog", "Enabled" ) }, - new string[] { "请求响应日志中间件", AppSettings.app("Middleware", "RequestResponseLog", "Enabled" ) }, - new string[] { "SingnalR实时发送请求数据中间件", AppSettings.app("Middleware", "SignalR", "Enabled" ) }, + new string[] { "IP记录中间件", AppSettings.app("Middleware", "IPLog", "Enabled") }, + new string[] { "请求响应日志中间件", AppSettings.app("Middleware", "RequestResponseLog", "Enabled") }, + new string[] { "SingnalR实时发送请求数据中间件", AppSettings.app("Middleware", "SignalR", "Enabled") }, new string[] { "IP限流中间件", AppSettings.app("Middleware", "IpRateLimit", "Enabled") }, new string[] { "性能分析中间件", AppSettings.app("Startup", "MiniProfiler", "Enabled") }, new string[] { "Consul注册服务", AppSettings.app("Middleware", "Consul", "Enabled") }, @@ -302,10 +101,9 @@ public static void AddAppTableConfigSetup(this IServiceCollection services, IHos TableStyle = TableStyle.Alternative }.Writer(ConsoleColor.Blue); Console.WriteLine(); - #endregion 中间件 + #endregion 中间件 } - } } -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/ApplicationSetup.cs b/Blog.Core.Extensions/ServiceExtensions/ApplicationSetup.cs new file mode 100644 index 00000000..793df2c8 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/ApplicationSetup.cs @@ -0,0 +1,24 @@ +using Blog.Core.Common; +using Microsoft.AspNetCore.Builder; +using Serilog; + +namespace Blog.Core.Extensions.ServiceExtensions; + +public static class ApplicationSetup +{ + public static void UseApplicationSetup(this WebApplication app) + { + app.Lifetime.ApplicationStarted.Register(() => + { + App.IsRun = true; + }); + + app.Lifetime.ApplicationStopped.Register(() => + { + App.IsRun = false; + + //清除日志 + Log.CloseAndFlush(); + }); + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/Authentication_AuthingSetup.cs b/Blog.Core.Extensions/ServiceExtensions/Authentication_AuthingSetup.cs new file mode 100644 index 00000000..4c2c68c8 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/Authentication_AuthingSetup.cs @@ -0,0 +1,53 @@ +using Blog.Core.AuthHelper; +using Blog.Core.Common; +using Blog.Core.Common.HttpContextUser; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using NetDevPack.Security.JwtExtensions; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// Authing权限 认证服务 + /// + public static class Authentication_AuthingSetup + { + public static void AddAuthentication_AuthingSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + var tokenValidationParameters = new TokenValidationParameters + { + ValidIssuer = AppSettings.app(new string[] { "Startup", "Authing", "Issuer" }), + ValidAudience = AppSettings.app(new string[] { "Startup", "Authing", "Audience" }), + ValidAlgorithms = new string[] { "RS256" }, + //ValidateLifetime = true, + //ClockSkew = TimeSpan.FromSeconds(30), + //RequireExpirationTime = true, + }; + + services.AddAuthentication(o => + { + //认证middleware配置 + o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + o.DefaultChallengeScheme = nameof(ApiResponseHandler); + o.DefaultForbidScheme = nameof(ApiResponseHandler); + }) + .AddJwtBearer(o => + { + //主要是jwt token参数设置 + o.TokenValidationParameters = tokenValidationParameters; + o.RequireHttpsMetadata = false; + o.SaveToken = false; + o.IncludeErrorDetails = true; + o.SetJwksOptions(new JwkOptions(AppSettings.app(new string[] { "Startup", "Authing", "JwksUri" }), AppSettings.app(new string[] { "Startup", "Authing", "Issuer" }), new TimeSpan(TimeSpan.TicksPerDay))); + }) + .AddScheme(nameof(ApiResponseHandler), o => { }); + + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/Authentication_JWTSetup.cs b/Blog.Core.Extensions/ServiceExtensions/Authentication_JWTSetup.cs index 930bfb6e..d9048c6e 100644 --- a/Blog.Core.Extensions/ServiceExtensions/Authentication_JWTSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/Authentication_JWTSetup.cs @@ -5,11 +5,8 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; -using System; using System.IdentityModel.Tokens.Jwt; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Extensions { @@ -58,9 +55,23 @@ public static void AddAuthentication_JWTSetup(this IServiceCollection services) o.TokenValidationParameters = tokenValidationParameters; o.Events = new JwtBearerEvents { + OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + + // If the request is for our hub... + var path = context.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken) && + (path.StartsWithSegments("/api2/chathub"))) + { + // Read the token out of the query string + context.Token = accessToken; + } + return Task.CompletedTask; + }, OnChallenge = context => { - context.Response.Headers.Add("Token-Error", context.ErrorDescription); + context.Response.Headers["Token-Error"] = context.ErrorDescription; return Task.CompletedTask; }, OnAuthenticationFailed = context => @@ -74,12 +85,12 @@ public static void AddAuthentication_JWTSetup(this IServiceCollection services) if (jwtToken.Issuer != Issuer) { - context.Response.Headers.Add("Token-Error-Iss", "issuer is wrong!"); + context.Response.Headers["Token-Error-Iss"] = "issuer is wrong!"; } if (jwtToken.Audiences.FirstOrDefault() != Audience) { - context.Response.Headers.Add("Token-Error-Aud", "Audience is wrong!"); + context.Response.Headers["Token-Error-Aud"] = "Audience is wrong!"; } } @@ -87,7 +98,7 @@ public static void AddAuthentication_JWTSetup(this IServiceCollection services) // 如果过期,则把<是否过期>添加到,返回头信息中 if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { - context.Response.Headers.Add("Token-Expired", "true"); + context.Response.Headers["Token-Expired"] = "true"; } return Task.CompletedTask; } diff --git a/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs b/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs index 7f2997bd..4236bfb7 100644 --- a/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs +++ b/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs @@ -6,20 +6,18 @@ using Blog.Core.IServices.BASE; using Blog.Core.Model; using Blog.Core.Repository.Base; +using Blog.Core.Repository.UnitOfWorks; using Blog.Core.Services.BASE; -using log4net; using System; using System.Collections.Generic; using System.IO; using System.Reflection; -using Blog.Core.Repository.UnitOfWorks; +using Serilog; namespace Blog.Core.Extensions { public class AutofacModuleRegister : Autofac.Module { - private static readonly ILog log = LogManager.GetLogger(typeof(AutofacModuleRegister)); - protected override void Load(ContainerBuilder builder) { var basePath = AppContext.BaseDirectory; @@ -34,39 +32,39 @@ protected override void Load(ContainerBuilder builder) if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile))) { var msg = "Repository.dll和service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。"; - log.Error(msg); + Log.Error(msg); throw new Exception(msg); } // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 var cacheType = new List(); - if (AppSettings.app(new string[] {"AppSettings", "RedisCachingAOP", "Enabled"}).ObjToBool()) - { - builder.RegisterType(); - cacheType.Add(typeof(BlogRedisCacheAOP)); - } - - if (AppSettings.app(new string[] {"AppSettings", "MemoryCachingAOP", "Enabled"}).ObjToBool()) + if (AppSettings.app(new string[] { "AppSettings", "CachingAOP", "Enabled" }).ObjToBool()) { builder.RegisterType(); cacheType.Add(typeof(BlogCacheAOP)); } - if (AppSettings.app(new string[] {"AppSettings", "TranAOP", "Enabled"}).ObjToBool()) + if (AppSettings.app(new string[] { "AppSettings", "TranAOP", "Enabled" }).ObjToBool()) { builder.RegisterType(); cacheType.Add(typeof(BlogTranAOP)); } - if (AppSettings.app(new string[] {"AppSettings", "LogAOP", "Enabled"}).ObjToBool()) + if (AppSettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool()) { builder.RegisterType(); cacheType.Add(typeof(BlogLogAOP)); } - builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency();//注册仓储 - builder.RegisterGeneric(typeof(BaseServices<>)).As(typeof(IBaseServices<>)).InstancePerDependency();//注册服务 + if (AppSettings.app(new string[] { "AppSettings", "UserAuditAOP", "Enabled" }).ObjToBool()) + { + builder.RegisterType(); + cacheType.Add(typeof(BlogUserAuditAOP)); + } + + builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储 + builder.RegisterGeneric(typeof(BaseServices<>)).As(typeof(IBaseServices<>)).InstancePerDependency(); //注册服务 // 获取 Service.dll 程序集服务,并注册 var assemblysServices = Assembly.LoadFrom(servicesDllFile); diff --git a/Blog.Core.Extensions/ServiceExtensions/CacheSetup.cs b/Blog.Core.Extensions/ServiceExtensions/CacheSetup.cs new file mode 100644 index 00000000..b1c30a35 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/CacheSetup.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Distributed; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Option; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Serilog; +using StackExchange.Redis; + +namespace Blog.Core.Extensions.ServiceExtensions; + +public static class CacheSetup +{ + /// + /// 统一注册缓存 + /// + /// + public static void AddCacheSetup(this IServiceCollection services) + { + var cacheOptions = App.GetOptions(); + if (cacheOptions.Enable) + { + // 配置启动Redis服务,虽然可能影响项目启动速度,但是不能在运行的时候报错,所以是合理的 + services.AddSingleton(sp => + { + //获取连接字符串 + var configuration = ConfigurationOptions.Parse(cacheOptions.ConnectionString, true); + configuration.ResolveDns = true; + return ConnectionMultiplexer.Connect(configuration); + }); + services.AddSingleton(p => p.GetService() as ConnectionMultiplexer); + //使用Redis + services.AddStackExchangeRedisCache(options => + { + options.ConnectionMultiplexerFactory = + () => Task.FromResult(App.GetService(false)); + if (!cacheOptions.InstanceName.IsNullOrEmpty()) options.InstanceName = cacheOptions.InstanceName; + }); + + services.AddTransient(); + } + else + { + //使用内存 + services.Remove(services.FirstOrDefault(x => x.ServiceType == typeof(IMemoryCache))); + services.AddSingleton(); + services.AddSingleton(provider => provider.GetService()); + services.AddOptions(); + services.AddSingleton(); + } + + services.AddSingleton(); + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/DataProtectionSetup.cs b/Blog.Core.Extensions/ServiceExtensions/DataProtectionSetup.cs new file mode 100644 index 00000000..5c94074c --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/DataProtectionSetup.cs @@ -0,0 +1,25 @@ +using Blog.Core.Common; +using Blog.Core.Common.Option; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.DependencyInjection; +using StackExchange.Redis; + +namespace Blog.Core.Extensions.ServiceExtensions; + +public static class DataProtectionSetup +{ + public static void AddDataProtectionSetup(this IServiceCollection services) + { + var builder = services.AddDataProtection(); + + var redisOption = App.GetOptions(); + if (redisOption.Enable) + { + builder.PersistKeysToStackExchangeRedis(App.GetService()); + return; + } + + //默认写到 webroot/temp/ + builder.PersistKeysToFileSystem(new DirectoryInfo(App.WebHostEnvironment.WebRootPath + "/Temp/Sessions/")); + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/DbSetup.cs b/Blog.Core.Extensions/ServiceExtensions/DbSetup.cs index b00470ab..1f377bcc 100644 --- a/Blog.Core.Extensions/ServiceExtensions/DbSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/DbSetup.cs @@ -1,6 +1,5 @@ using Blog.Core.Common.Seed; using Microsoft.Extensions.DependencyInjection; -using System; namespace Blog.Core.Extensions { diff --git a/Blog.Core.Extensions/ServiceExtensions/EventBusSetup.cs b/Blog.Core.Extensions/ServiceExtensions/EventBusSetup.cs index 1aae5ed5..4ae98830 100644 --- a/Blog.Core.Extensions/ServiceExtensions/EventBusSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/EventBusSetup.cs @@ -1,11 +1,9 @@ -using Autofac; +using System; +using Autofac; using Blog.Core.Common; using Blog.Core.EventBus; -using Blog.Core.EventBus.EventHandling; -using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System; namespace Blog.Core.Extensions { @@ -43,23 +41,12 @@ public static void AddEventBusSetup(this IServiceCollection services) return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); }); } - if(AppSettings.app(new string[] { "Kafka", "Enabled" }).ObjToBool()) + if (AppSettings.app(new string[] { "Kafka", "Enabled" }).ObjToBool()) { services.AddHostedService(); services.AddSingleton(); } } } - - - public static void ConfigureEventBus(this IApplicationBuilder app) - { - if (AppSettings.app(new string[] { "EventBus", "Enabled" }).ObjToBool()) - { - var eventBus = app.ApplicationServices.GetRequiredService(); - - eventBus.Subscribe(); - } - } } -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/HttpPollySetup.cs b/Blog.Core.Extensions/ServiceExtensions/HttpPollySetup.cs index e8e3929f..8c1d637b 100644 --- a/Blog.Core.Extensions/ServiceExtensions/HttpPollySetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/HttpPollySetup.cs @@ -1,11 +1,9 @@ -using Blog.Core.Common.HttpPolly; +using Blog.Core.Common.Https.HttpPolly; using Blog.Core.Model; using Microsoft.Extensions.DependencyInjection; using Polly; using Polly.Extensions.Http; using Polly.Timeout; -using System; -using System.Net.Http; namespace Blog.Core.Extensions { diff --git a/Blog.Core.Extensions/ServiceExtensions/HttpRuntimeCache.cs b/Blog.Core.Extensions/ServiceExtensions/HttpRuntimeCache.cs deleted file mode 100644 index b9b4d7b3..00000000 --- a/Blog.Core.Extensions/ServiceExtensions/HttpRuntimeCache.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using SqlSugar; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; - -namespace Blog.Core.Extensions -{ - /// - /// 实现SqlSugar的ICacheService接口 - /// - public class SqlSugarMemoryCacheService : ICacheService - { - protected IMemoryCache _memoryCache; - public SqlSugarMemoryCacheService(IMemoryCache memoryCache) - { - _memoryCache = memoryCache; - } - public void Add(string key, V value) - { - _memoryCache.Set(key, value); - } - public void Add(string key, V value, int cacheDurationInSeconds) - { - _memoryCache.Set(key, value, DateTimeOffset.Now.AddSeconds(cacheDurationInSeconds)); - } - public bool ContainsKey(string key) - { - return _memoryCache.TryGetValue(key, out _); - } - - public V Get(string key) - { - return _memoryCache.Get(key); - } - - public IEnumerable GetAllKey() - { - const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; - var entries = _memoryCache.GetType().GetField("_entries", flags).GetValue(_memoryCache); - var cacheItems = entries as IDictionary; - var keys = new List(); - if (cacheItems == null) return keys; - foreach (DictionaryEntry cacheItem in cacheItems) - { - keys.Add(cacheItem.Key.ToString()); - } - return keys; - } - - public V GetOrCreate(string cacheKey, Func create, int cacheDurationInSeconds = int.MaxValue) - { - if (!_memoryCache.TryGetValue(cacheKey, out V value)) - { - value = create(); - _memoryCache.Set(cacheKey, value, DateTime.Now.AddSeconds(cacheDurationInSeconds)); - } - return value; - } - - public void Remove(string key) - { - _memoryCache.Remove(key); - } - } -} diff --git a/Blog.Core.Extensions/ServiceExtensions/InitializationHostServiceSetup.cs b/Blog.Core.Extensions/ServiceExtensions/InitializationHostServiceSetup.cs new file mode 100644 index 00000000..fea6a5ad --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/InitializationHostServiceSetup.cs @@ -0,0 +1,24 @@ +using System; +using Blog.Core.Extensions.HostedService; +using Microsoft.Extensions.DependencyInjection; + +namespace Blog.Core.Extensions; + +public static class InitializationHostServiceSetup +{ + /// + /// 应用初始化服务注入 + /// + /// + public static void AddInitializationHostServiceSetup(this IServiceCollection services) + { + if (services is null) + { + ArgumentNullException.ThrowIfNull(nameof(services)); + } + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + services.AddHostedService(); + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs b/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs index 61dabc3e..c92c077e 100644 --- a/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs @@ -1,8 +1,8 @@ using AspNetCoreRateLimit; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using System; namespace Blog.Core.Extensions { @@ -14,18 +14,15 @@ public static class IpPolicyRateLimitSetup public static void AddIpPolicyRateLimitSetup(this IServiceCollection services, IConfiguration Configuration) { if (services == null) throw new ArgumentNullException(nameof(services)); - - // needed to store rate limit counters and ip rules - services.AddMemoryCache(); + //load general configuration from appsettings.json services.Configure(Configuration.GetSection("IpRateLimiting")); - // inject counter and rules stores - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + // inject counter and rules distributed cache stores - //services.AddSingleton(); - //services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); // the clientId/clientIp resolvers use it. services.AddSingleton(); diff --git a/Blog.Core.Extensions/ServiceExtensions/JobSetup.cs b/Blog.Core.Extensions/ServiceExtensions/JobSetup.cs index 318168d2..da881cb3 100644 --- a/Blog.Core.Extensions/ServiceExtensions/JobSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/JobSetup.cs @@ -2,8 +2,6 @@ using Microsoft.Extensions.DependencyInjection; using Quartz; using Quartz.Spi; -using System; -using System.Linq; using System.Reflection; namespace Blog.Core.Extensions @@ -17,12 +15,7 @@ public static void AddJobSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - //services.AddHostedService(); - //services.AddHostedService(); - services.AddSingleton(); - //services.AddTransient();//Job使用瞬时依赖注入 - //services.AddTransient();//Job使用瞬时依赖注入 services.AddSingleton(); //任务注入 var baseType = typeof(IJob); diff --git a/Blog.Core.Extensions/ServiceExtensions/JobSetup_HostedService.cs b/Blog.Core.Extensions/ServiceExtensions/JobSetup_HostedService.cs new file mode 100644 index 00000000..ccf5eb4d --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/JobSetup_HostedService.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// 任务调度 启动服务 + /// + public static class JobSetup_HostedService + { + public static void AddJobSetup_HostedService(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddHostedService(); + services.AddHostedService(); + + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/MemoryCacheSetup.cs b/Blog.Core.Extensions/ServiceExtensions/MemoryCacheSetup.cs deleted file mode 100644 index 0e489681..00000000 --- a/Blog.Core.Extensions/ServiceExtensions/MemoryCacheSetup.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Blog.Core.Common; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using System; - -namespace Blog.Core.Extensions -{ - /// - /// Memory缓存 启动服务 - /// - public static class MemoryCacheSetup - { - public static void AddMemoryCacheSetup(this IServiceCollection services) - { - if (services == null) throw new ArgumentNullException(nameof(services)); - - services.AddScoped(); - services.AddSingleton(factory => - { - var value = factory.GetRequiredService>(); - var cache = new MemoryCache(value); - return cache; - }); - } - } -} diff --git a/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs b/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs index a40d684d..1bd601f4 100644 --- a/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs @@ -1,6 +1,15 @@ using Blog.Core.Common; using Microsoft.Extensions.DependencyInjection; using System; +using Blog.Core.Common.Https; +using Blog.Core.Common.Option; +using Blog.Core.Common.Swagger; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; +using StackExchange.Profiling; +using StackExchange.Profiling.SqlFormatters; +using StackExchange.Profiling.Storage; +using StackExchange.Redis; namespace Blog.Core.Extensions { @@ -12,23 +21,68 @@ public static class MiniProfilerSetup public static void AddMiniProfilerSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - if(AppSettings.app(new string[] { "Startup", "MiniProfiler", "Enabled" }).ObjToBool()) - { - services.AddMiniProfiler(); - } - // 3.x使用MiniProfiler,必须要注册MemoryCache服务 - // services.AddMiniProfiler(options => - // { - // options.RouteBasePath = "/profiler"; - // //(options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10); - // options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.Left; - // options.PopupShowTimeWithChildren = true; - - // // 可以增加权限 - // //options.ResultsAuthorize = request => request.HttpContext.User.IsInRole("Admin"); - // //options.UserIdProvider = request => request.HttpContext.User.Identity.Name; - // } - //); + if (!AppSettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool()) return; + + //使用MiniProfiler + services.AddMiniProfiler(options => + { + //访问地址路由根目录;默认为:/mini-profiler-resources + //options.RouteBasePath = "/profiler"; + //数据缓存时间 + //获取redis配置 + var redisOptions = App.GetOptions(); + if (redisOptions.Enable) + options.Storage = + new RedisStorage((ConnectionMultiplexer)App.GetService()); + else + options.Storage = new MemoryCacheStorage(App.GetService(), TimeSpan.FromMinutes(60)); + + //sql格式化设置 + options.SqlFormatter = new InlineFormatter(); + //跟踪连接打开关闭 + options.TrackConnectionOpenClose = true; + //界面主题颜色方案;默认浅色 + options.ColorScheme = ColorScheme.Dark; + //.net core 3.0以上:对MVC过滤器进行分析 + options.EnableMvcFilterProfiling = true; + //对视图进行分析 + options.EnableMvcViewProfiling = true; + + //控制访问页面授权,默认所有人都能访问 + //options.ResultsAuthorize; + //要控制分析哪些请求,默认说有请求都分析 + //options.ShouldProfile + + //内部异常处理 + //options.OnInternalError = e => MyExceptionLogger(e); + //options.RouteBasePath = "/profiler"; + + //(options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10); + options.PopupRenderPosition = RenderPosition.Left; + options.PopupShowTimeWithChildren = true; + + //只监控api 接口 + options.ShouldProfile = ShouldProfile; + + // 可以增加权限 + // options.ResultsAuthorize = request => + // { + // if (request.IsLocal()) return true; + // + // var path = request.HttpContext.Request.Path.Value; + // if (path == null || !path.StartsWith(options.RouteBasePath)) return true; + // + // var flag = request.HttpContext.IsSuccessSwagger(); + // if (!flag) request.HttpContext.RedirectSwaggerLogin(); + // return flag; + // }; + } + ); + } + + private static bool ShouldProfile(HttpRequest request) + { + return request.Path.StartsWithSegments("/api"); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/RedisCacheSetup.cs b/Blog.Core.Extensions/ServiceExtensions/RedisCacheSetup.cs deleted file mode 100644 index e070cbd6..00000000 --- a/Blog.Core.Extensions/ServiceExtensions/RedisCacheSetup.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Blog.Core.Common; -using Microsoft.Extensions.DependencyInjection; -using StackExchange.Redis; -using System; - -namespace Blog.Core.Extensions -{ - /// - /// Redis缓存 启动服务 - /// - public static class RedisCacheSetup - { - public static void AddRedisCacheSetup(this IServiceCollection services) - { - if (services == null) throw new ArgumentNullException(nameof(services)); - - services.AddTransient(); - - // 配置启动Redis服务,虽然可能影响项目启动速度,但是不能在运行的时候报错,所以是合理的 - services.AddSingleton(sp => - { - //获取连接字符串 - string redisConfiguration = AppSettings.app(new string[] { "Redis", "ConnectionString" }); - - var configuration = ConfigurationOptions.Parse(redisConfiguration, true); - - configuration.ResolveDns = true; - - return ConnectionMultiplexer.Connect(configuration); - }); - - } - } -} diff --git a/Blog.Core.Extensions/ServiceExtensions/SerilogSetup.cs b/Blog.Core.Extensions/ServiceExtensions/SerilogSetup.cs new file mode 100644 index 00000000..89cbedd6 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/SerilogSetup.cs @@ -0,0 +1,52 @@ +using Blog.Core.Common; +using Blog.Core.Common.LogHelper; +using Blog.Core.Serilog.Configuration; +using Blog.Core.Serilog.Extensions; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Debugging; +using Serilog.Events; +using Blog.Core.Common.Option; + +namespace Blog.Core.Extensions.ServiceExtensions; + +public static class SerilogSetup +{ + public static IHostBuilder AddSerilogSetup(this IHostBuilder host) + { + if (host == null) throw new ArgumentNullException(nameof(host)); + + var loggerConfiguration = new LoggerConfiguration() + .ReadFrom.Configuration(AppSettings.Configuration) + .Enrich.FromLogContext() + //输出到控制台 + .WriteToConsole() + //将日志保存到文件中 + .WriteToFile() + //配置日志库 + .WriteToLogBatching(); + + var option = App.GetOptions(); + //配置Seq日志中心 + if (option.Enabled) + { + var address = option.Address; + var apiKey = option.ApiKey; + if (!address.IsNullOrEmpty()) + { + loggerConfiguration = + loggerConfiguration.WriteTo.Seq(address, restrictedToMinimumLevel: LogEventLevel.Verbose, + apiKey: apiKey, eventBodyLimitBytes: 10485760); + } + } + + Log.Logger = loggerConfiguration.CreateLogger(); + + //Serilog 内部日志 + var file = File.CreateText(LogContextStatic.Combine($"SerilogDebug{DateTime.Now:yyyyMMdd}.txt")); + SelfLog.Enable(TextWriter.Synchronized(file)); + + host.UseSerilog(); + return host; + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs index b3eb4bdc..71e1f7fd 100644 --- a/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs @@ -1,14 +1,14 @@ using Blog.Core.Common; +using Blog.Core.Common.Const; using Blog.Core.Common.DB; -using Blog.Core.Common.Helper; -using Blog.Core.Common.LogHelper; +using Blog.Core.Common.DB.Aop; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using SqlSugar; -using StackExchange.Profiling; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using Blog.Core.Common.Caches; +using System.Text.RegularExpressions; +using Blog.Core.Common.Option; +using Blog.Core.Common.Utility; namespace Blog.Core.Extensions { @@ -23,86 +23,112 @@ public static void AddSqlsugarSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - // 默认添加主数据库连接 - MainDb.CurrentDbConnId = AppSettings.app(new string[] { "MainDB" }); + StaticConfig.CustomSnowFlakeFunc = IdGeneratorUtility.NextId; - // SqlSugarScope是线程安全,可使用单例注入 - // 参考:https://www.donet5.com/Home/Doc?typeId=1181 - services.AddSingleton(o => + // 默认添加主数据库连接 + if (!AppSettings.app("MainDB").IsNullOrEmpty()) { - var memoryCache = o.GetRequiredService(); + MainDb.CurrentDbConnId = AppSettings.app("MainDB"); + } - // 连接字符串 - var listConfig = new List(); - // 从库 - var listConfig_Slave = new List(); - BaseDBConfig.MutiConnectionString.slaveDbs.ForEach(s => + BaseDBConfig.MutiConnectionString.allDbs.ForEach(m => + { + var config = new ConnectionConfig() { - listConfig_Slave.Add(new SlaveConnectionConfig() + ConfigId = m.ConnId.ObjToString().ToLower(), + ConnectionString = m.Connection, + DbType = (DbType)m.DbType, + IsAutoCloseConnection = true, + // Check out more information: https://github.com/anjoy8/Blog.Core/issues/122 + //IsShardSameThread = false, + MoreSettings = new ConnMoreSettings() { - HitRate = s.HitRate, - ConnectionString = s.Connection - }); - }); - - BaseDBConfig.MutiConnectionString.allDbs.ForEach(m => - { - listConfig.Add(new ConnectionConfig() + //IsWithNoLockQuery = true, + IsAutoRemoveDataCache = true, + SqlServerCodeFirstNvarchar = true, + }, + // 从库 + SlaveConnectionConfigs = m.Slaves?.Where(s => s.HitRate > 0).Select(s => new SlaveConnectionConfig { - ConfigId = m.ConnId.ObjToString().ToLower(), - ConnectionString = m.Connection, - DbType = (DbType)m.DbType, - IsAutoCloseConnection = true, - // Check out more information: https://github.com/anjoy8/Blog.Core/issues/122 - //IsShardSameThread = false, - AopEvents = new AopEvents - { - OnLogExecuting = (sql, p) => - { - if (AppSettings.app(new string[] { "AppSettings", "SqlAOP", "Enabled" }).ObjToBool()) - { - if (AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToFile", "Enabled" }).ObjToBool()) - { - Parallel.For(0, 1, e => - { - MiniProfiler.Current.CustomTiming("SQL:", GetParas(p) + "【SQL语句】:" + sql); - //LogLock.OutSql2Log("SqlLog", new string[] { GetParas(p), "【SQL语句】:" + sql }); - LogLock.OutLogAOP("SqlLog", "", new string[] { sql.GetType().ToString(), GetParas(p), "【SQL语句】:" + sql }); - - }); - } - if (AppSettings.app(new string[] { "AppSettings", "SqlAOP", "LogToConsole", "Enabled" }).ObjToBool()) - { - ConsoleHelper.WriteColorLine(string.Join("\r\n", new string[] { "--------", $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} :" + GetWholeSql(p, sql) }), ConsoleColor.DarkCyan); - } - } - }, - }, - MoreSettings = new ConnMoreSettings() - { - //IsWithNoLockQuery = true, - IsAutoRemoveDataCache = true - }, - // 从库 - SlaveConnectionConfigs = listConfig_Slave, - // 自定义特性 - ConfigureExternalServices = new ConfigureExternalServices() + ConnectionString = s.Connection, + HitRate = s.HitRate + }).ToList(), + // 自定义特性 + ConfigureExternalServices = new ConfigureExternalServices() + { + //不建议使用,性能有很大问题,会导致redis堆积 + //核心问题在于SqlSugar,每次query都会查缓存, insert\update\delete,又会频繁GetAllKey,导致性能特别低 + DataInfoCacheService = new SqlSugarCacheService(), + EntityService = (property, column) => { - DataInfoCacheService = new SqlSugarMemoryCacheService(memoryCache), - EntityService = (property, column) => + if (column.IsPrimarykey && property.PropertyType == typeof(int)) { - if (column.IsPrimarykey && property.PropertyType == typeof(int)) - { - column.IsIdentity = true; - } + column.IsIdentity = true; } - }, - InitKeyType = InitKeyType.Attribute + } + }, + InitKeyType = InitKeyType.Attribute + }; + if (SqlSugarConst.LogConfigId.ToLower().Equals(m.ConnId.ToLower())) + { + BaseDBConfig.LogConfig = config; + } + else + { + if (string.Equals(config.ConfigId.ToString(), MainDb.CurrentDbConnId, + StringComparison.CurrentCultureIgnoreCase)) + { + BaseDBConfig.MainConfig = config; } - ); + else if (m.ConnId.ToLower().StartsWith(MainDb.CurrentDbConnId.ToLower())) + { + //复用连接 + BaseDBConfig.ReuseConfigs.Add(config); + } + + + BaseDBConfig.ValidConfig.Add(config); + } + + BaseDBConfig.AllConfigs.Add(config); + }); + + if (BaseDBConfig.LogConfig is null) + { + throw new ApplicationException("未配置Log库连接"); + } + + // SqlSugarScope是线程安全,可使用单例注入 + // 参考:https://www.donet5.com/Home/Doc?typeId=1181 + services.AddSingleton(o => + { + return new SqlSugarScope(BaseDBConfig.AllConfigs, db => + { + BaseDBConfig.ValidConfig.ForEach(config => + { + var dbProvider = db.GetConnectionScope((string)config.ConfigId); + + // 打印SQL语句 + dbProvider.Aop.OnLogExecuting = (s, parameters) => + { + SqlSugarAop.OnLogExecuting(dbProvider, App.User?.Name.ObjToString(), ExtractTableName(s), + Enum.GetName(typeof(SugarActionType), dbProvider.SugarActionType), s, parameters, + config); + }; + + // 数据审计 + dbProvider.Aop.DataExecuting = SqlSugarAop.DataExecuting; + + // 配置实体假删除过滤器 + RepositorySetting.SetDeletedEntityFilter(dbProvider); + // 配置实体数据权限 + RepositorySetting.SetTenantEntityFilter(dbProvider); + }); + //故障转移,检查主库链接自动切换备用连接 + SqlSugarReuse.AutoChangeAvailableConnect(db); }); - return new SqlSugarScope(listConfig); }); + services.AddTransient(s => s.GetService() as SqlSugarScope); } private static string GetWholeSql(SugarParameter[] paramArr, string sql) @@ -125,5 +151,25 @@ private static string GetParas(SugarParameter[] pars) return key; } + + private static string ExtractTableName(string sql) + { + // 匹配 SQL 语句中的表名的正则表达式 + //string regexPattern = @"\s*(?:UPDATE|DELETE\s+FROM|SELECT\s+\*\s+FROM)\s+(\w+)"; + string regexPattern = @"(?i)(?:FROM|UPDATE|DELETE\s+FROM)\s+`(.+?)`"; + Regex regex = new Regex(regexPattern, RegexOptions.IgnoreCase); + Match match = regex.Match(sql); + + if (match.Success) + { + // 提取匹配到的表名 + return match.Groups[1].Value; + } + else + { + // 如果没有匹配到表名,则返回空字符串或者抛出异常等处理 + return string.Empty; + } + } } } \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs b/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs index 449b6a4d..ade9f993 100644 --- a/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs @@ -1,13 +1,14 @@ using Blog.Core.Common; -using log4net; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; +using Serilog; using Swashbuckle.AspNetCore.Filters; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using Blog.Core.Common.Swagger.Filter; using static Blog.Core.Extensions.CustomApiVersion; namespace Blog.Core.Extensions @@ -17,10 +18,6 @@ namespace Blog.Core.Extensions /// public static class SwaggerSetup { - - private static readonly ILog log = - LogManager.GetLogger(typeof(SwaggerSetup)); - public static void AddSwaggerSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); @@ -59,7 +56,7 @@ public static void AddSwaggerSetup(this IServiceCollection services) } catch (Exception ex) { - log.Error("Blog.Core.xml和Blog.Core.Model.xml 丢失,请检查并拷贝。\n" + ex.Message); + Log.Error("Blog.Core.xml和Blog.Core.Model.xml 丢失,请检查并拷贝。\n" + ex.Message); } // 开启加权小锁 @@ -69,7 +66,10 @@ public static void AddSwaggerSetup(this IServiceCollection services) // 在header中添加token,传递到后台 c.OperationFilter(); - + //自定义过滤器 + c.SchemaFilter(); + c.DocumentFilter(); + // ids4和jwt切换 if (Permissions.IsUseIds4) { @@ -82,12 +82,13 @@ public static void AddSwaggerSetup(this IServiceCollection services) Implicit = new OpenApiOAuthFlow { AuthorizationUrl = new Uri($"{AppSettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" })}/connect/authorize"), - Scopes = new Dictionary { + Scopes = new Dictionary { - "blog.core.api","ApiResource id" + { + "blog.core.api", "ApiResource id" + } } } - } } }); } @@ -97,14 +98,11 @@ public static void AddSwaggerSetup(this IServiceCollection services) c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", - Name = "Authorization",//jwt默认的参数名称 - In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中) + Name = "Authorization", //jwt默认的参数名称 + In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中) Type = SecuritySchemeType.ApiKey }); } - - - }); services.AddSwaggerGenNewtonsoftSupport(); } @@ -124,11 +122,11 @@ public enum ApiVersions /// V1 版本 /// V1 = 1, + /// /// V2 版本 /// V2 = 2, } } - -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/WebApiClientSetup.cs b/Blog.Core.Extensions/ServiceExtensions/WebApiClientSetup.cs deleted file mode 100644 index 7cee91a4..00000000 --- a/Blog.Core.Extensions/ServiceExtensions/WebApiClientSetup.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using System; -using WebApiClient.Extensions.DependencyInjection; -using Blog.Core.Common.WebApiClients.HttpApis; - -namespace Blog.Core.Extensions -{ - /// - /// WebApiClientSetup 启动服务 - /// - public static class WebApiClientSetup - { - /// - /// 注册WebApiClient接口 - /// - /// - public static void AddHttpApi(this IServiceCollection services) - { - if (services == null) throw new ArgumentNullException(nameof(services)); - - services.AddHttpApi().ConfigureHttpApiConfig(c => - { - c.HttpHost = new Uri("http://apk.neters.club/"); - c.FormatOptions.DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fff"; - }); - services.AddHttpApi().ConfigureHttpApiConfig(c => - { - c.HttpHost = new Uri("http://api.xiaomafeixiang.com/"); - c.FormatOptions.DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fff"; - }); - } - } -} diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj b/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj index b167521c..76d6f7e6 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj @@ -1,8 +1,5 @@ - - net6.0 - diff --git a/Blog.Core.Gateway/Blog.Core.Gateway.csproj b/Blog.Core.Gateway/Blog.Core.Gateway.csproj index 47398520..d45e9ba5 100644 --- a/Blog.Core.Gateway/Blog.Core.Gateway.csproj +++ b/Blog.Core.Gateway/Blog.Core.Gateway.csproj @@ -1,8 +1,5 @@  - - net6.0 - ..\Blog.Core.Gateway\Blog.Core.Gateway.xml @@ -15,13 +12,21 @@ - - - - - - - + + + + + + + + + + + + + + + diff --git a/Blog.Core.Gateway/Blog.Core.Gateway.xml b/Blog.Core.Gateway/Blog.Core.Gateway.xml index 41507d7a..34543342 100644 --- a/Blog.Core.Gateway/Blog.Core.Gateway.xml +++ b/Blog.Core.Gateway/Blog.Core.Gateway.xml @@ -51,50 +51,5 @@ │ 作 者:anson zhang └──────────────────────────────────────────────────────────────┘ - - - Nacos配置文件变更事件 - - - - - Nacos 配置文件监听事件 - - - - - - - - - - - - - - - 执行 - - - - - - - 停止 - - - - - - - 配置监听事件 - - - - - 收到配置文件变更 - - - diff --git a/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs b/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs index f04df2a5..b197c824 100644 --- a/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs +++ b/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs @@ -16,11 +16,12 @@ public static void AddCustomOcelotSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - var basePath = AppContext.BaseDirectory; - services.AddAuthentication_JWTSetup(); - services.AddOcelot().AddDelegatingHandler().AddNacosDiscovery().AddPolly(); - //.AddConsul().AddPolly(); + services.AddOcelot() + .AddDelegatingHandler() + //.AddNacosDiscovery() + //.AddConsul() + .AddPolly(); } public static async Task UseCustomOcelotMildd(this IApplicationBuilder app) diff --git a/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs b/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs index 92956ff1..033feeb5 100644 --- a/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs +++ b/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs @@ -1,11 +1,16 @@ -using Microsoft.AspNetCore.Builder; +using Blog.Core.Common; +using Blog.Core.Extensions.Middlewares; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.Filters; +using Swashbuckle.AspNetCore.SwaggerUI; using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using static Blog.Core.Extensions.CustomApiVersion; namespace Blog.Core.Gateway.Extensions { public static class CustomSwaggerSetup @@ -44,23 +49,30 @@ public static void AddCustomSwaggerSetup(this IServiceCollection services) }); } - public static void UseCustomSwaggerMildd(this IApplicationBuilder app) + public static void UseCustomSwaggerMildd(this IApplicationBuilder app, Func streamHtml) { if (app == null) throw new ArgumentNullException(nameof(app)); var apis = new List { "blog-svc" }; - app.UseMvc().UseSwagger(); - app.UseSwaggerUI(options => + app.UseSwagger(); + app.UseSwaggerUI(c => { - options.SwaggerEndpoint($"/swagger/v1/swagger.json", $"Blog.Core.Gateway-v1"); - + c.SwaggerEndpoint($"/swagger/v1/swagger.json", "gateway"); apis.ForEach(m => { - options.SwaggerEndpoint($"/swagger/apiswg/{m}/swagger.json", m); - options.IndexStream = () => app.GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.ApiGateway.index.html"); + c.SwaggerEndpoint($"/swagger/apiswg/{m}/swagger.json", m); }); - options.RoutePrefix = ""; + + if (streamHtml.Invoke() == null) + { + var msg = "index.html的属性,必须设置为嵌入的资源"; + throw new Exception(msg); + } + + c.IndexStream = streamHtml; + + c.RoutePrefix = ""; }); } diff --git a/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs b/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs index 88181bc3..9441e00c 100644 --- a/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs +++ b/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs @@ -1,15 +1,10 @@ -using System; -using System.Net; -using System.Linq; -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Net; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; using Blog.Core.Common; +using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; using Blog.Core.Common.Helper; -using Nacos.V2; -using Newtonsoft.Json.Linq; namespace Blog.Core.AuthHelper { @@ -22,7 +17,6 @@ public class CustomJwtTokenAuthMiddleware { private readonly ICaching _cache; - private readonly INacosNamingService NacosServClient; /// /// 验证方案提供对象 @@ -35,13 +29,11 @@ public class CustomJwtTokenAuthMiddleware private readonly RequestDelegate _next; - public CustomJwtTokenAuthMiddleware(INacosNamingService serv, RequestDelegate next, IAuthenticationSchemeProvider schemes, AppSettings appset,ICaching cache) + public CustomJwtTokenAuthMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes, AppSettings appset,ICaching cache) { - NacosServClient = serv; _cache = cache; _next = next; Schemes = schemes; - List Permissions = _cache.Cof_AsyncGetICaching>("Permissions", GetPermitionData, 10).GetAwaiter().GetResult(); } /// @@ -65,7 +57,7 @@ public async Task Invoke(HttpContext httpContext) return; } - List Permissions= await _cache.Cof_AsyncGetICaching>("Permissions", GetPermitionData, 10); + List Permissions= new(); httpContext.Features.Set(new AuthenticationFeature { @@ -125,28 +117,6 @@ public async Task Invoke(HttpContext httpContext) await _next.Invoke(httpContext); } - private async Task> GetPermitionData() - { - try - { - string PermissionServName = AppSettings.GetValue("ApiGateWay:PermissionServName"); - string PermissionServGroup = AppSettings.GetValue("ApiGateWay:PermissionServGroup"); - string PermissionServUrl = AppSettings.GetValue("ApiGateWay:PermissionServUrl"); - - string requestdata = await NacosServClient.Cof_NaoceGet(PermissionServName, PermissionServGroup, PermissionServUrl); - if (string.IsNullOrEmpty(requestdata)) return null; - JToken perJt = JToken.Parse(requestdata); - if(perJt["response"]!=null) return perJt["response"].ToObject>(); - return perJt["data"].ToObject>(); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - } - - return null; - } - /// /// 返回相应 /// diff --git a/Blog.Core.Gateway/Helper/OcelotConfigurationTask.cs b/Blog.Core.Gateway/Helper/OcelotConfigurationTask.cs deleted file mode 100644 index a95df86e..00000000 --- a/Blog.Core.Gateway/Helper/OcelotConfigurationTask.cs +++ /dev/null @@ -1,147 +0,0 @@ - -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Nacos.V2; -using System; -using System.Threading; -using System.Threading.Tasks; -using Blog.Core.Common.Helper; -using Ocelot.Configuration.Repository; -using Ocelot.Configuration.Creator; -using Newtonsoft.Json.Linq; -using Ocelot.Configuration.File; -using Blog.Core.Common; - -namespace ApiGateway.Helper -{ - /// - /// Nacos配置文件变更事件 - /// - public class OcelotConfigurationTask : BackgroundService - { - private readonly INacosConfigService _configClient; - private readonly INacosNamingService _servClient; - /// - /// Nacos 配置文件监听事件 - /// - private OcelotConfigListener nacosConfigListener = new OcelotConfigListener(); - private AppConfigListener AppConfigListener = new AppConfigListener(); - private string OcelotConfig = ""; - private string OcelotConfigGroup = ""; - private string AppConfig = ""; - private string AppConfigGroup = ""; - - - /// - /// - /// - /// - /// - /// - /// - /// - public OcelotConfigurationTask(INacosNamingService serv, INacosConfigService configClient, IServiceProvider serviceProvider, IInternalConfigurationRepository _internalConfigurationRepo, IInternalConfigurationCreator _internalConfigurationCreator) - { - _configClient = configClient; - _servClient = serv; - nacosConfigListener.internalConfigurationRepo = _internalConfigurationRepo; - nacosConfigListener.internalConfigurationCreator = _internalConfigurationCreator; - OcelotConfig = AppSettings.GetValue("ApiGateWay:OcelotConfig"); - OcelotConfigGroup = AppSettings.GetValue("ApiGateWay:OcelotConfigGroup"); - AppConfig = AppSettings.GetValue("ApiGateWay:AppConfig"); - AppConfigGroup = AppSettings.GetValue("ApiGateWay:AppConfigGroup"); - - - - - string OcelotCfg = configClient.GetConfig(OcelotConfig, OcelotConfigGroup, 10000).GetAwaiter().GetResult(); - nacosConfigListener.ReceiveConfigInfo(OcelotCfg); - string AppCfg= configClient.GetConfig(AppConfig, AppConfigGroup, 10000).GetAwaiter().GetResult(); - AppConfigListener.ReceiveConfigInfo(AppCfg); - //string sss = serv.Cof_NaoceGet("fld-cloud-datax", "DEFAULT_GROUP", "/api/base/deviceList?limit=10&page=1").GetAwaiter().GetResult(); - } - - - - /// - /// 执行 - /// - /// - /// - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - try - { - // Add listener OcelotConfig.json" - await _configClient.AddListener(OcelotConfig, OcelotConfigGroup, nacosConfigListener); - await _configClient.AddListener(AppConfig, AppConfigGroup, AppConfigListener); - } - catch (Exception) - { - } - } - - /// - /// 停止 - /// - /// - /// - public override async Task StopAsync(CancellationToken cancellationToken) - { - // Remove listener - await _configClient.RemoveListener(OcelotConfig, OcelotConfigGroup, nacosConfigListener); - await _configClient.RemoveListener(AppConfig, AppConfigGroup, AppConfigListener); - await base.StopAsync(cancellationToken); - } - } - - /// - /// 配置监听事件 - /// - public class OcelotConfigListener : IListener - { - public IInternalConfigurationRepository internalConfigurationRepo { get; set; } - public IInternalConfigurationCreator internalConfigurationCreator { get; set; } - /// - /// 收到配置文件变更 - /// - /// - public void ReceiveConfigInfo(string configInfo) - { - Task.Run(async () => - { - FileConfiguration filecfg = JToken.Parse(configInfo).ToObject(); - var internalConfiguration = await internalConfigurationCreator.Create(filecfg); - if (!internalConfiguration.IsError) - { - - internalConfigurationRepo.AddOrReplace(internalConfiguration.Data); - } - }); - - - } - } - - public class AppConfigListener : IListener - { - public void ReceiveConfigInfo(string configInfo) - { - var _configurationBuilder = new ConfigurationBuilder(); - _configurationBuilder.Sources.Clear(); - var buffer = System.Text.Encoding.Default.GetBytes(configInfo); - System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer); - _configurationBuilder.AddJsonStream(ms); - var configuration = _configurationBuilder.Build(); - ms.Dispose(); - - - - // 读取配置 将nacos配置中心读取到的配置 替换掉.net core 内存中的 configuration - // 当前监听到配置配置 应该重新断开 重连 刷新等一些中间件操作 - // 比如 mq redis 等其他跟配置相关的中间件 - JsonConfigSettings.Configuration = configuration; - AppSettings.Configuration = configuration; - } - } -} diff --git a/Blog.Core.Gateway/Program.cs b/Blog.Core.Gateway/Program.cs index 1ba46816..9c1ba1ce 100644 --- a/Blog.Core.Gateway/Program.cs +++ b/Blog.Core.Gateway/Program.cs @@ -15,7 +15,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { - config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: true) + config.AddJsonFile("appsettings.gw.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.gw.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: false) + .AddJsonFile("ocelot.json", optional: true, reloadOnChange: true) .AddJsonFile($"ocelot.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true); }) .ConfigureWebHostDefaults(webBuilder => diff --git a/Blog.Core.Gateway/Startup.cs b/Blog.Core.Gateway/Startup.cs index 552b4ef1..d8f81253 100644 --- a/Blog.Core.Gateway/Startup.cs +++ b/Blog.Core.Gateway/Startup.cs @@ -1,14 +1,11 @@ using Blog.Core.AuthHelper; using Blog.Core.Common; +using Blog.Core.Common.Caches; using Blog.Core.Extensions; using Blog.Core.Gateway.Extensions; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Nacos.V2.DependencyInjection; +using System.Reflection; +using Blog.Core.Common.Caches.Interface; namespace Blog.Core.AdminMvc { @@ -34,17 +31,10 @@ public void ConfigureServices(IServiceCollection services) { services.AddSingleton(new AppSettings(Configuration)); - services.AddAuthentication_JWTSetup(); - services.AddAuthentication() .AddScheme(Permissions.GWName, _ => { }); - services.AddNacosV2Config(Configuration, null, "nacosConfig"); - services.AddNacosV2Naming(Configuration, null, "nacos"); - services.AddHostedService(); - - services.AddCustomSwaggerSetup(); services.AddControllers(); @@ -53,6 +43,10 @@ public void ConfigureServices(IServiceCollection services) services.AddCorsSetup(); + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + services.AddSingleton(); + services.AddCustomOcelotSetup(); } @@ -69,7 +63,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseAuthentication(); app.UseAuthorization(); - app.UseCustomSwaggerMildd(); + app.UseCustomSwaggerMildd(() => Assembly.GetExecutingAssembly().GetManifestResourceStream("Blog.Core.Gateway.index.html")); app.UseCors(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" })); @@ -79,7 +73,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) }); app.UseMiddleware(); - + app.UseCustomOcelotMildd().Wait(); } } diff --git a/Blog.Core.Gateway/appsettings.Development.json b/Blog.Core.Gateway/appsettings.gw.Development.json similarity index 100% rename from Blog.Core.Gateway/appsettings.Development.json rename to Blog.Core.Gateway/appsettings.gw.Development.json diff --git a/Blog.Core.Gateway/appsettings.gw.json b/Blog.Core.Gateway/appsettings.gw.json new file mode 100644 index 00000000..93e64636 --- /dev/null +++ b/Blog.Core.Gateway/appsettings.gw.json @@ -0,0 +1,51 @@ +{ + "Logging": { + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning", + "Microsoft.Hosting.Lifetime": "Debug" + } + } + }, + "AllowedHosts": "*", + "Startup": { + "Cors": { + "PolicyName": "CorsIpAccess", + "EnableAllIPs": false, + "IPs": "http://127.0.0.1:2364,http://localhost:2364" + } + }, + "Redis": { + "Enable": false, + "ConnectionString": "127.0.0.1:6379", + "InstanceName": "" //前缀 + }, + "Audience": { + "Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", + "SecretFile": "C:\\my-file\\blog.core.audience.secret.txt", + "Issuer": "Blog.Core", + "Audience": "wr" + }, + "WhiteList": [ + { "url": "/" }, + { "url": "/illagal/****" }, + { "url": "/api3/****" }, + { "url": "/baseapi/swagger.json" }, + { "url": "/swagger/v1/swagger.json" }, + { "url": "/swagger/apiswg/blog-svc/swagger.json" } + ], + "BlackList": [ + { "url": "/favicon.ico" } + ], + "Influxdb": { + "Endpoint": "http://*******:9328", + "uid": "root", + "pwd": "*****", + "dbname": "mndata" + } +} diff --git a/Blog.Core.Gateway/appsettings.json b/Blog.Core.Gateway/appsettings.json deleted file mode 100644 index 33b99ee8..00000000 --- a/Blog.Core.Gateway/appsettings.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "LogLevel": { - "Default": "Warning", - "Microsoft.Hosting.Lifetime": "Debug" - } - } - }, - "AllowedHosts": "*", - "Startup": { - "Cors": { - "PolicyName": "CorsIpAccess", - "EnableAllIPs": false, - "IPs": "http://127.0.0.1:2364,http://localhost:2364" - } - }, - "Audience": { - "Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", - "SecretFile": "C:\\my-file\\blog.core.audience.secret.txt", - "Issuer": "Blog.Core", - "Audience": "wr" - }, - "WhiteList": [ - { "url": "/" }, - { "url": "/illagal/****" }, - { "url": "/api3/****" }, - { "url": "/baseapi/swagger.json" } - ], - "BlackList": [ - { "url": "/favicon.ico" } - ], - "ApiGateWay": { - "OcelotConfig": "OcelotConfig.json", - "OcelotConfigGroup": "DEFAULT_GROUP", - "AppConfig": "****.****.Gateway.json", - "AppConfigGroup": "DEFAULT_GROUP", - "PermissionServName": "****.****.Api", - "PermissionServGroup": "DEFAULT_GROUP", - "PermissionServUrl": "/api/Permission/GetPermissionlist" - }, - "Influxdb": { - "Endpoint": "http://*******:9328", - "uid": "root", - "pwd": "*****", - "dbname": "mndata" - }, - "nacos": { - "ServerAddresses": [ "http://******:8848/" ], - "ServiceName": "*****.****.Gateway", - "DefaultTimeOut": 15000, - "Namespace": "****", - "ListenInterval": 1000, - "GroupName": "DEFAULT_GROUP", - "ClusterName": "DEFAULT", - "Ip": "", - "PreferredNetworks": "", - "Port": 8090, - "Weight": 100, - "RegisterEnabled": true, - "InstanceEnabled": true, - "Ephemeral": true, - "Secure": false, - "AccessKey": "", - "SecretKey": "", - "UserName": "****", - "Password": "*****", - "NamingUseRpc": true, - "NamingLoadCacheAtStart": "", - "LBStrategy": "WeightRandom", - "Metadata": { - "aa": "bb", - "cc": "dd", - "endpoint33": "******:8090" - } - }, - "nacosConfig": { - "ServiceName": "*****.*****.Gateway", - "Optional": false, - "DataId": "options1", - "Tenant": "******", - "Group": "DEFAULT_GROUP", - "Namespace": "*****", - "ServerAddresses": [ "http://******:8848/" ], - "UserName": "****", - "Password": "*****", - "AccessKey": "", - "SecretKey": "", - "EndPoint": "", - "ConfigUseRpc": true, - "ConfigFilterAssemblies": [ "apigateway" ], - "ConfigFilterExtInfo": "{\"JsonPaths\":[\"ConnectionStrings.Default\"],\"Other\":\"xxxxxx\"}" - } - - - -} diff --git a/Blog.Core.Gateway/ocelot.Development.json b/Blog.Core.Gateway/ocelot.Development.json index 6af5171b..589a5a6e 100644 --- a/Blog.Core.Gateway/ocelot.Development.json +++ b/Blog.Core.Gateway/ocelot.Development.json @@ -49,11 +49,6 @@ ], "GlobalConfiguration": { - "BaseUrl": "http://localhost:9000", - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Type": "Consul" - } + "BaseUrl": "http://localhost:9000" } } \ No newline at end of file diff --git a/Blog.Core.IServices/BASE/IBaseServices.cs b/Blog.Core.IServices/BASE/IBaseServices.cs index 27daae98..a496b59f 100644 --- a/Blog.Core.IServices/BASE/IBaseServices.cs +++ b/Blog.Core.IServices/BASE/IBaseServices.cs @@ -10,22 +10,24 @@ namespace Blog.Core.IServices.BASE { public interface IBaseServices where TEntity : class { + ISqlSugarClient Db { get; } Task QueryById(object objId); Task QueryById(object objId, bool blnUseCache = false); Task> QueryByIDs(object[] lstIds); - Task Add(TEntity model); + Task Add(TEntity model); - Task Add(List listEntity); + Task> Add(List listEntity); Task DeleteById(object id); Task Delete(TEntity model); - + Task DeleteByIds(object[] ids); Task Update(TEntity model); + Task Update(List model); Task Update(TEntity entity, string where); Task Update(object operateAnonymousObjects); @@ -58,6 +60,14 @@ Task> QueryMuch( Expression> selectExpression, Expression> whereLambda = null) where T : class, new(); Task> QueryPage(PaginationModel pagination); + + #region 分表 + Task QueryByIdSplit(object objId); + Task> AddSplit(TEntity entity); + Task DeleteSplit(TEntity entity, DateTime dateTime); + Task UpdateSplit(TEntity entity, DateTime dateTime); + Task> QueryPageSplit(Expression> whereExpression, DateTime beginTime, DateTime endTime, int pageIndex = 1, int pageSize = 20, string orderByFields = null); + #endregion } } diff --git a/Blog.Core.IServices/Blog.Core.IServices.csproj b/Blog.Core.IServices/Blog.Core.IServices.csproj index 820e1511..b5cc2f2c 100644 --- a/Blog.Core.IServices/Blog.Core.IServices.csproj +++ b/Blog.Core.IServices/Blog.Core.IServices.csproj @@ -1,8 +1,5 @@  - - net6.0 - diff --git a/Blog.Core.IServices/IBlogArticleServices.cs b/Blog.Core.IServices/IBlogArticleServices.cs index 23e6081b..a38826fb 100644 --- a/Blog.Core.IServices/IBlogArticleServices.cs +++ b/Blog.Core.IServices/IBlogArticleServices.cs @@ -9,7 +9,7 @@ namespace Blog.Core.IServices public interface IBlogArticleServices :IBaseServices { Task> GetBlogs(); - Task GetBlogDetails(int id); + Task GetBlogDetails(long id); } diff --git a/Blog.Core.IServices/IDS4Db/IApplicationUserServices.cs b/Blog.Core.IServices/IDS4Db/IApplicationUserServices.cs index c6d784ff..4be6a876 100644 --- a/Blog.Core.IServices/IDS4Db/IApplicationUserServices.cs +++ b/Blog.Core.IServices/IDS4Db/IApplicationUserServices.cs @@ -1,9 +1,11 @@ -using Blog.Core.IServices.BASE; +using System.Threading.Tasks; +using Blog.Core.IServices.BASE; using Blog.Core.Model.IDS4DbModels; namespace Blog.Core.IServices { public partial interface IApplicationUserServices : IBaseServices { + bool IsEnable(); } } \ No newline at end of file diff --git a/Blog.Core.IServices/IGuestbookServices.cs b/Blog.Core.IServices/IGuestbookServices.cs index 7fc72f62..cd8b2287 100644 --- a/Blog.Core.IServices/IGuestbookServices.cs +++ b/Blog.Core.IServices/IGuestbookServices.cs @@ -15,5 +15,7 @@ public partial interface IGuestbookServices : IBaseServices Task TestTranPropagationNoTran(); Task TestTranPropagationTran(); + Task TestTranPropagationTran2(); + Task TestTranPropagationTran3(); } } \ No newline at end of file diff --git a/Blog.Core.IServices/IPasswordLibServices.cs b/Blog.Core.IServices/IPasswordLibServices.cs index d7a0b310..67532fd6 100644 --- a/Blog.Core.IServices/IPasswordLibServices.cs +++ b/Blog.Core.IServices/IPasswordLibServices.cs @@ -9,5 +9,6 @@ public partial interface IPasswordLibServices :IBaseServices Task TestTranPropagation2(); Task TestTranPropagationNoTranError(); Task TestTranPropagationTran2(); + Task TestTranPropagationTran3(); } } diff --git a/Blog.Core.IServices/IRoleModulePermissionServices.cs b/Blog.Core.IServices/IRoleModulePermissionServices.cs index 22532479..2a5c7345 100644 --- a/Blog.Core.IServices/IRoleModulePermissionServices.cs +++ b/Blog.Core.IServices/IRoleModulePermissionServices.cs @@ -21,6 +21,6 @@ public interface IRoleModulePermissionServices :IBaseServices˵ /// ӿ /// - Task UpdateModuleId(int permissionId, int moduleId); + Task UpdateModuleId(long permissionId, long moduleId); } } diff --git a/Blog.Core.IServices/ISplitDemoServices.cs b/Blog.Core.IServices/ISplitDemoServices.cs new file mode 100644 index 00000000..55215761 --- /dev/null +++ b/Blog.Core.IServices/ISplitDemoServices.cs @@ -0,0 +1,15 @@ + + +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; +using System.Threading.Tasks; + +namespace Blog.Core.IServices +{ + /// + /// sysUserInfoServices + /// + public interface ISplitDemoServices : IBaseServices + { + } +} diff --git a/Blog.Core.IServices/ITasksLogServices.cs b/Blog.Core.IServices/ITasksLogServices.cs new file mode 100644 index 00000000..fb6ad8a7 --- /dev/null +++ b/Blog.Core.IServices/ITasksLogServices.cs @@ -0,0 +1,19 @@ + +using System; +using System.Threading.Tasks; +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices +{ + /// + /// ITasksLogServices + /// + public interface ITasksLogServices :IBaseServices + { + public Task> GetTaskLogs(long jobId, int page, int intPageSize,DateTime? runTime,DateTime? endTime); + public Task GetTaskOverview(long jobId, DateTime? runTime, DateTime? endTime, string type); + } +} + \ No newline at end of file diff --git a/Blog.Core.IServices/ITenantService.cs b/Blog.Core.IServices/ITenantService.cs new file mode 100644 index 00000000..0927b793 --- /dev/null +++ b/Blog.Core.IServices/ITenantService.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices; + +public interface ITenantService : IBaseServices +{ + public Task SaveTenant(SysTenant tenant); + + public Task InitTenantDb(SysTenant tenant); +} \ No newline at end of file diff --git a/Blog.Core.IServices/IUserRoleServices.cs b/Blog.Core.IServices/IUserRoleServices.cs index 9e7d3d29..91272a09 100644 --- a/Blog.Core.IServices/IUserRoleServices.cs +++ b/Blog.Core.IServices/IUserRoleServices.cs @@ -10,8 +10,8 @@ namespace Blog.Core.IServices public interface IUserRoleServices :IBaseServices { - Task SaveUserRole(int uid, int rid); - Task GetRoleIdByUid(int uid); + Task SaveUserRole(long uid, long rid); + Task GetRoleIdByUid(long uid); } } diff --git a/Blog.Core.IServices/WebApiClients/DoubanApis/DoubanViewModel.cs b/Blog.Core.IServices/WebApiClients/DoubanApis/DoubanViewModel.cs deleted file mode 100644 index 0ddb402e..00000000 --- a/Blog.Core.IServices/WebApiClients/DoubanApis/DoubanViewModel.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace Blog.Core.Common.WebApiClients.HttpApis -{ - public class Data - { - /// - /// - /// - public string isbn { get; set; } - /// - /// 解忧杂货店 - /// - public string title { get; set; } - /// - /// ナミヤ雑貨店の奇蹟 - /// - public string origintitle { get; set; } - /// - /// - /// - public string subtitle { get; set; } - /// - /// - /// - public string image { get; set; } - /// - /// [日]东野圭吾 - /// - public string author { get; set; } - /// - /// 李盈春 - /// - public string translator { get; set; } - /// - /// 南海出版公司 - /// - public string publisher { get; set; } - /// - /// - /// - public string pubdate { get; set; } - /// - /// <东野圭吾><治愈><温暖><小说><日本><日本文学><東野圭吾><推理> - /// - public string tags { get; set; } - /// - /// - /// - public string kaiben { get; set; } - /// - /// - /// - public string zhizhang { get; set; } - /// - /// 精装 - /// - public string binding { get; set; } - /// - /// - /// - public string taozhuang { get; set; } - /// - /// 新经典文库·东野圭吾作品 - /// - public string series { get; set; } - /// - /// - /// - public string pages { get; set; } - /// - /// 39.50元 - /// - public string price { get; set; } - - public string author_intro { get; set; } - - public string summary { get; set; } - - public string catalog { get; set; } - } - - public class DoubanViewModel - { - /// - /// - /// - public string status { get; set; } - /// - /// - /// - public Data data { get; set; } - /// - /// 获取图书数据成功 - /// - public string msg { get; set; } - } -} diff --git a/Blog.Core.IServices/WebApiClients/DoubanApis/IDoubanApi.cs b/Blog.Core.IServices/WebApiClients/DoubanApis/IDoubanApi.cs deleted file mode 100644 index c2f1d42d..00000000 --- a/Blog.Core.IServices/WebApiClients/DoubanApis/IDoubanApi.cs +++ /dev/null @@ -1,22 +0,0 @@ -using WebApiClient; -using WebApiClient.Attributes; - -namespace Blog.Core.Common.WebApiClients.HttpApis -{ - /// - /// 豆瓣视频管理 - /// - [TraceFilter] - public interface IDoubanApi : IHttpApi - { - /// - /// 获取电影详情 - /// - /// - [HttpGet("api/bookinfo")] - ITask VideoDetailAsync(string isbn); - - } - - -} diff --git a/Blog.Core.IServices/WebApiClients/HttpApis/IBlogApi.cs b/Blog.Core.IServices/WebApiClients/HttpApis/IBlogApi.cs deleted file mode 100644 index 52ff8aa4..00000000 --- a/Blog.Core.IServices/WebApiClients/HttpApis/IBlogApi.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Blog.Core.Model; -using Blog.Core.Model.Models; -using Blog.Core.Model.ViewModels; -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using WebApiClient; -using WebApiClient.Attributes; - -namespace Blog.Core.Common.WebApiClients.HttpApis -{ - /// - /// 博客管理 - /// - [TraceFilter] - public interface IBlogApi : IHttpApi - { - /// - /// 获取博客列表【无权限】 - /// - /// - /// - /// - /// - /// Success - [HttpGet("api/Blog")] - Task>> BlogAsync(int? id, int page, string bcategory, string key); - - /// - /// 添加博客【无权限】 - /// - /// - /// Success - [HttpPost("api/Blog")] - Task> Blog2Async([JsonContent] BlogArticle body); - - /// - /// 获取博客详情 (Auth) - /// - /// - /// Success - [HttpGet("api/Blog/{id}")] - Task> Blog3Async([Required] int id); - - /// - /// apache jemeter 压力测试 - /// 更新接口 - /// - /// Success - [HttpGet("api/Blog/ApacheTestUpdate")] - Task> ApacheTestUpdateAsync(); - - /// - /// 删除博客 (Auth policies: Permission) - /// - /// - /// Success - [HttpDelete("api/Blog/Delete")] - Task> DeleteAsync(int? id); - - /// - /// 获取详情【无权限】 - /// - /// - /// Success - [HttpGet("api/Blog/DetailNuxtNoPer")] - Task> DetailNuxtNoPerAsync(int? id); - - /// - /// 更新博客信息 (Auth) - /// - /// - /// Success - [HttpPut("api/Blog/Update")] - Task> UpdateAsync([JsonContent] BlogArticle body); - - /// - /// 获取博客测试信息 v2版本 - /// - /// Success - [HttpGet("api/V2/Blog/Blogtest")] - Task> BlogtestAsync(); - - } -} diff --git a/Blog.Core.Model/Base/BaseLog.cs b/Blog.Core.Model/Base/BaseLog.cs new file mode 100644 index 00000000..829ff09e --- /dev/null +++ b/Blog.Core.Model/Base/BaseLog.cs @@ -0,0 +1,22 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model.Base; + +public abstract class BaseLog : RootEntityTkey +{ + [SplitField] + public DateTime? DateTime { get; set; } + + [SugarColumn(IsNullable = true)] + public string Level { get; set; } + + [SugarColumn(IsNullable = true, ColumnDataType = "longtext,text,clob")] + public string Message { get; set; } + + [SugarColumn(IsNullable = true, ColumnDataType = "longtext,text,clob")] + public string MessageTemplate { get; set; } + + [SugarColumn(IsNullable = true, ColumnDataType = "longtext,text,clob")] + public string Properties { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Blog.Core.Model.csproj b/Blog.Core.Model/Blog.Core.Model.csproj index de7e6467..fb04c45c 100644 --- a/Blog.Core.Model/Blog.Core.Model.csproj +++ b/Blog.Core.Model/Blog.Core.Model.csproj @@ -1,8 +1,5 @@  - - net6.0 - ..\Blog.Core.Api\Blog.Core.Model.xml @@ -16,9 +13,13 @@ - - - + + + + + + + diff --git a/Blog.Core.Model/Logs/AuditSqlLog.cs b/Blog.Core.Model/Logs/AuditSqlLog.cs new file mode 100644 index 00000000..f4b28195 --- /dev/null +++ b/Blog.Core.Model/Logs/AuditSqlLog.cs @@ -0,0 +1,12 @@ +using Blog.Core.Model.Base; +using SqlSugar; + +namespace Blog.Core.Model.Logs; + +[Tenant("log")] +[SplitTable(SplitType.Month)] //按月分表 (自带分表支持 年、季、月、周、日) +[SugarTable($@"{nameof(AuditSqlLog)}_{{year}}{{month}}{{day}}")] +public class AuditSqlLog: BaseLog +{ + +} \ No newline at end of file diff --git a/Blog.Core.Model/Logs/GlobalErrorLog.cs b/Blog.Core.Model/Logs/GlobalErrorLog.cs new file mode 100644 index 00000000..cea55642 --- /dev/null +++ b/Blog.Core.Model/Logs/GlobalErrorLog.cs @@ -0,0 +1,13 @@ +using Blog.Core.Model.Base; +using SqlSugar; + +namespace Blog.Core.Model.Logs; + +[Tenant("log")] +[SplitTable(SplitType.Month)] //按月分表 (自带分表支持 年、季、月、周、日) +[SugarTable($@"{nameof(GlobalErrorLog)}_{{year}}{{month}}{{day}}")] +public class GlobalErrorLog : BaseLog +{ + [SugarColumn(IsNullable = true, ColumnDataType = "longtext,text,clob")] + public string Exception { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Logs/GlobalInformationLog.cs b/Blog.Core.Model/Logs/GlobalInformationLog.cs new file mode 100644 index 00000000..9e627acb --- /dev/null +++ b/Blog.Core.Model/Logs/GlobalInformationLog.cs @@ -0,0 +1,12 @@ +using Blog.Core.Model.Base; +using SqlSugar; + +namespace Blog.Core.Model.Logs; + +[Tenant("log")] +[SplitTable(SplitType.Month)] //按月分表 (自带分表支持 年、季、月、周、日) +[SugarTable($@"{nameof(GlobalInformationLog)}_{{year}}{{month}}{{day}}")] +public class GlobalInformationLog : BaseLog +{ + +} \ No newline at end of file diff --git a/Blog.Core.Model/Logs/GlobalWarningLog.cs b/Blog.Core.Model/Logs/GlobalWarningLog.cs new file mode 100644 index 00000000..36d8545e --- /dev/null +++ b/Blog.Core.Model/Logs/GlobalWarningLog.cs @@ -0,0 +1,12 @@ +using Blog.Core.Model.Base; +using SqlSugar; + +namespace Blog.Core.Model.Logs; + +[Tenant("log")] +[SplitTable(SplitType.Month)] //按月分表 (自带分表支持 年、季、月、周、日) +[SugarTable($@"{nameof(GlobalWarningLog)}_{{year}}{{month}}{{day}}")] +public class GlobalWarningLog: BaseLog +{ + +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/AccessTrendLog.cs b/Blog.Core.Model/Models/AccessTrendLog.cs index 0bad6836..bc4848cf 100644 --- a/Blog.Core.Model/Models/AccessTrendLog.cs +++ b/Blog.Core.Model/Models/AccessTrendLog.cs @@ -6,13 +6,13 @@ namespace Blog.Core.Model.Models /// /// 用户访问趋势日志 /// - public class AccessTrendLog : RootEntityTkey + public class AccessTrendLog : RootEntityTkey { /// /// 用户 /// - [SugarColumn(Length = 128, IsNullable = true, ColumnDataType = "nvarchar")] - public string User { get; set; } + [SugarColumn(Length = 128, IsNullable = true)] + public string UserInfo { get; set; } /// /// 次数 diff --git a/Blog.Core.Model/Models/Advertisement.cs b/Blog.Core.Model/Models/Advertisement.cs index c2babd74..3b11b21f 100644 --- a/Blog.Core.Model/Models/Advertisement.cs +++ b/Blog.Core.Model/Models/Advertisement.cs @@ -3,7 +3,7 @@ namespace Blog.Core.Model.Models { - public class Advertisement : RootEntityTkey + public class Advertisement : RootEntityTkey { /// diff --git a/Blog.Core.Model/Models/BlogArticle.cs b/Blog.Core.Model/Models/BlogArticle.cs index 52176190..8b75c8df 100644 --- a/Blog.Core.Model/Models/BlogArticle.cs +++ b/Blog.Core.Model/Models/BlogArticle.cs @@ -1,5 +1,6 @@ using SqlSugar; using System; +using System.Collections.Generic; namespace Blog.Core.Model.Models { @@ -12,14 +13,18 @@ public class BlogArticle /// 主键 /// /// 这里之所以没用RootEntity,是想保持和之前的数据库一致,主键是bID,不是Id - [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] - public int bID { get; set; } + [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = false)] + public long bID { get; set; } + /// /// 创建人 /// [SugarColumn(Length = 600, IsNullable = true)] public string bsubmitter { get; set; } + [Navigate(NavigateType.OneToOne, nameof(bsubmitter))] + public SysUserInfo User { get; set; } + /// /// 标题blog /// @@ -57,6 +62,7 @@ public class BlogArticle /// 创建时间 /// public System.DateTime bCreateTime { get; set; } + /// /// 备注 /// @@ -69,5 +75,11 @@ public class BlogArticle [SugarColumn(IsNullable = true)] public bool? IsDeleted { get; set; } + + /// + /// 评论 + /// + [Navigate(NavigateType.OneToMany, nameof(BlogArticleComment.bID))] + public List Comments { get; set; } } -} +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/BlogArticleComment.cs b/Blog.Core.Model/Models/BlogArticleComment.cs new file mode 100644 index 00000000..519fb003 --- /dev/null +++ b/Blog.Core.Model/Models/BlogArticleComment.cs @@ -0,0 +1,19 @@ +using SqlSugar; + +namespace Blog.Core.Model.Models; + +/// +/// 博客文章 评论 +/// +public class BlogArticleComment : RootEntityTkey +{ + public long bID { get; set; } + + public string Comment { get; set; } + + + public string UserId { get; set; } + + [Navigate(NavigateType.OneToOne, nameof(UserId))] + public SysUserInfo User { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/Department.cs b/Blog.Core.Model/Models/Department.cs index 3583bcff..424bcf44 100644 --- a/Blog.Core.Model/Models/Department.cs +++ b/Blog.Core.Model/Models/Department.cs @@ -7,7 +7,7 @@ namespace Blog.Core.Model.Models /// /// 部门表 /// - public class Department : DepartmentRoot + public class Department : DepartmentRoot { /// /// Desc:部门关系编码 diff --git a/Blog.Core.Model/Models/GblLogAudit.cs b/Blog.Core.Model/Models/GblLogAudit.cs index 58e04dd6..d4a85411 100644 --- a/Blog.Core.Model/Models/GblLogAudit.cs +++ b/Blog.Core.Model/Models/GblLogAudit.cs @@ -12,58 +12,58 @@ public class GblLogAudit /// ///ID /// - [SugarColumn(ColumnDescription = "ID", IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + [SugarColumn(ColumnDescription = "ID", IsNullable = false, IsPrimaryKey = true, IsIdentity = false)] + public long Id { get; set; } /// ///HttpContext.TraceIdentifier 事件链路ID(获取或设置一个唯一标识符,用于在跟踪日志中表示此请求。) /// - [SugarColumn(ColumnDescription = "事件链路ID", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "varchar", Length = 255)] + [SugarColumn(ColumnDescription = "事件链路ID", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] public string TraceId { get; set; } /// ///时间 /// - [SugarColumn(ColumnDescription = "时间", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "datetime")] + [SugarColumn(ColumnDescription = "时间", IsNullable = false, IsPrimaryKey = false, IsIdentity = false)] public DateTime Date { get; set; } = DateTime.Now; /// ///线程 /// - [SugarColumn(ColumnDescription = "线程", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "varchar", Length = 255)] + [SugarColumn(ColumnDescription = "线程", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] public string Thread { get; set; } /// ///等级 /// - [SugarColumn(ColumnDescription = "等级", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "varchar", Length = 255)] + [SugarColumn(ColumnDescription = "等级", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] public string Level { get; set; } /// ///记录器 /// - [SugarColumn(ColumnDescription = "记录器", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "varchar", Length = 255)] + [SugarColumn(ColumnDescription = "记录器", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] public string Logger { get; set; } /// ///日志类型 /// - [SugarColumn(ColumnDescription = "日志类型", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "varchar", Length = 255)] + [SugarColumn(ColumnDescription = "日志类型", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] public string LogType { get; set; } /// ///数据类型 /// - [SugarColumn(ColumnDescription = "数据类型", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "varchar", Length = 255)] + [SugarColumn(ColumnDescription = "数据类型", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] public string DataType { get; set; } /// ///错误信息 /// - [SugarColumn(ColumnDescription = "错误信息", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "text")] + [SugarColumn(ColumnDescription = "错误信息", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 2000)] public string Message { get; set; } /// ///异常 /// - [SugarColumn(ColumnDescription = "异常", IsNullable = true, IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "text")] + [SugarColumn(ColumnDescription = "异常", IsNullable = true, IsPrimaryKey = false, IsIdentity = false, Length = 2000)] public string Exception { get; set; } } diff --git a/Blog.Core.Model/Models/Guestbook.cs b/Blog.Core.Model/Models/Guestbook.cs index d1f671c0..0cd5dcef 100644 --- a/Blog.Core.Model/Models/Guestbook.cs +++ b/Blog.Core.Model/Models/Guestbook.cs @@ -3,13 +3,13 @@ namespace Blog.Core.Model.Models { - public class Guestbook:RootEntityTkey + public class Guestbook : RootEntityTkey { - + /// 博客ID /// /// - public int? blogId { get; set; } + public long? blogId { get; set; } /// 创建时间 /// /// diff --git a/Blog.Core.Model/Models/Modules.cs b/Blog.Core.Model/Models/Modules.cs index b62c0a47..684cfcd0 100644 --- a/Blog.Core.Model/Models/Modules.cs +++ b/Blog.Core.Model/Models/Modules.cs @@ -6,7 +6,7 @@ namespace Blog.Core.Model.Models /// /// 接口API地址信息表 /// - public class Modules : ModulesRoot + public class Modules : ModulesRoot { public Modules() { @@ -78,7 +78,7 @@ public Modules() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// @@ -93,7 +93,7 @@ public Modules() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// diff --git a/Blog.Core.Model/Models/OperateLog.cs b/Blog.Core.Model/Models/OperateLog.cs index 4086781c..3c2fb54c 100644 --- a/Blog.Core.Model/Models/OperateLog.cs +++ b/Blog.Core.Model/Models/OperateLog.cs @@ -6,7 +6,7 @@ namespace Blog.Core.Model.Models /// /// 日志记录 /// - public class OperateLog : RootEntityTkey + public class OperateLog : RootEntityTkey { /// diff --git a/Blog.Core.Model/Models/PasswordLib.cs b/Blog.Core.Model/Models/PasswordLib.cs index 2b43c265..2037df4d 100644 --- a/Blog.Core.Model/Models/PasswordLib.cs +++ b/Blog.Core.Model/Models/PasswordLib.cs @@ -7,11 +7,11 @@ namespace Blog.Core.Model.Models /// 密码库表 /// [SugarTable("PasswordLib", "密码库表")]//('数据库表名','数据库表备注') - [TenantAttribute("WMBLOG_MYSQL_2")] //('代表是哪个数据库,名字是appsettings.json 的 ConnId') + //[TenantAttribute("WMBLOG_MYSQL_2")] //('代表是哪个数据库,名字是appsettings.json 的 ConnId') public class PasswordLib { - [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] - public int PLID { get; set; } + [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = false)] + public long PLID { get; set; } /// ///获取或设置是否禁用,逻辑上的删除,非物理删除 diff --git a/Blog.Core.Model/Models/Permission.cs b/Blog.Core.Model/Models/Permission.cs index c650cb5e..95a46b85 100644 --- a/Blog.Core.Model/Models/Permission.cs +++ b/Blog.Core.Model/Models/Permission.cs @@ -7,7 +7,7 @@ namespace Blog.Core.Model.Models /// /// 路由菜单表 /// - public class Permission : PermissionRoot + public class Permission : PermissionRoot { public Permission() { @@ -57,6 +57,11 @@ public Permission() [SugarColumn(Length = 100, IsNullable = true)] public string Icon { get; set; } /// + /// 菜单图标新 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string IconNew { get; set; } + /// /// 菜单描述 /// [SugarColumn(Length = 100, IsNullable = true)] @@ -69,7 +74,7 @@ public Permission() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// @@ -84,7 +89,7 @@ public Permission() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// diff --git a/Blog.Core.Model/Models/Role.cs b/Blog.Core.Model/Models/Role.cs index e34ccdd9..0e65bcaf 100644 --- a/Blog.Core.Model/Models/Role.cs +++ b/Blog.Core.Model/Models/Role.cs @@ -6,7 +6,7 @@ namespace Blog.Core.Model.Models /// /// 角色表 /// - public class Role : RootEntityTkey + public class Role : RootEntityTkey { public Role() { @@ -64,7 +64,7 @@ public Role(string name) /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// @@ -79,7 +79,7 @@ public Role(string name) /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// diff --git a/Blog.Core.Model/Models/RoleModulePermission.cs b/Blog.Core.Model/Models/RoleModulePermission.cs index 13d82a80..f33c1080 100644 --- a/Blog.Core.Model/Models/RoleModulePermission.cs +++ b/Blog.Core.Model/Models/RoleModulePermission.cs @@ -6,7 +6,7 @@ namespace Blog.Core.Model.Models /// /// 按钮跟权限关联表 /// - public class RoleModulePermission : RoleModulePermissionRoot + public class RoleModulePermission : RoleModulePermissionRoot { public RoleModulePermission() { @@ -26,7 +26,7 @@ public RoleModulePermission() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// @@ -41,7 +41,7 @@ public RoleModulePermission() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// diff --git a/Blog.Core.Model/Models/RootEntity.cs b/Blog.Core.Model/Models/RootEntity.cs deleted file mode 100644 index d3874bb4..00000000 --- a/Blog.Core.Model/Models/RootEntity.cs +++ /dev/null @@ -1,15 +0,0 @@ -using SqlSugar; - -namespace Blog.Core.Model -{ - public class RootEntity - { - /// - /// ID - /// - [SugarColumn(IsNullable = false, IsPrimaryKey = true)] - public int Id { get; set; } - - - } -} \ No newline at end of file diff --git a/Blog.Core.Model/Models/RootTkey/BaseEntity.cs b/Blog.Core.Model/Models/RootTkey/BaseEntity.cs new file mode 100644 index 00000000..5d5d4414 --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/BaseEntity.cs @@ -0,0 +1,82 @@ +using Blog.Core.Model.Models.RootTkey.Interface; +using SqlSugar; +using System; + +namespace Blog.Core.Model.Models.RootTkey; + +[SugarIndex("index_{table}_Enabled", nameof(Enabled), OrderByType.Asc)] +[SugarIndex("index_{table}_IsDeleted", nameof(IsDeleted), OrderByType.Asc)] +public class BaseEntity : RootEntityTkey, IDeleteFilter +{ + #region 数据状态管理 + + /// + /// 状态
+ /// 中立字段,某些表可使用某些表不使用 + ///
+ public bool Enabled { get; set; } = true; + + /// + /// 中立字段,某些表可使用某些表不使用
+ /// 逻辑上的删除,非物理删除
+ /// 例如:单据删除并非直接删除 + ///
+ public bool IsDeleted { get; set; } + + /// + /// 中立字段
+ /// 是否内置数据 + ///
+ public bool IsInternal { get; set; } + + #endregion + + #region 创建 + + /// + /// 创建ID + /// + [SugarColumn(IsNullable = true, IsOnlyIgnoreUpdate = true)] + public long? CreateId { get; set; } + + /// + /// 创建者 + /// + [SugarColumn(IsNullable = true, IsOnlyIgnoreUpdate = true)] + public string CreateBy { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(IsOnlyIgnoreUpdate = true)] + public DateTime CreateTime { get; set; } = DateTime.Now; + + #endregion + + #region 修改 + + /// + /// 修改ID + /// + [SugarColumn(IsNullable = true)] + public long? ModifyId { get; set; } + + /// + /// 更新者 + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + + /// + /// 修改日期 + /// + public DateTime? ModifyTime { get; set; } = DateTime.Now; + + /// + /// 数据版本 + /// + [SugarColumn(DefaultValue = "0", IsEnableUpdateVersionValidation = true)] //标识版本字段 + public long Version { get; set; } + + #endregion +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/RootTkey/Interface/IDeleteFilter.cs b/Blog.Core.Model/Models/RootTkey/Interface/IDeleteFilter.cs new file mode 100644 index 00000000..57d421e9 --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/Interface/IDeleteFilter.cs @@ -0,0 +1,9 @@ +namespace Blog.Core.Model.Models.RootTkey.Interface; + +/// +/// 软删除 过滤器 +/// +public interface IDeleteFilter +{ + public bool IsDeleted { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/RootTkey/RootEntityTkey.cs b/Blog.Core.Model/Models/RootTkey/RootEntityTkey.cs index afecdec9..20bdda0d 100644 --- a/Blog.Core.Model/Models/RootTkey/RootEntityTkey.cs +++ b/Blog.Core.Model/Models/RootTkey/RootEntityTkey.cs @@ -11,7 +11,5 @@ public class RootEntityTkey where Tkey : IEquatable /// [SugarColumn(IsNullable = false, IsPrimaryKey = true)] public Tkey Id { get; set; } - - } } \ No newline at end of file diff --git a/Blog.Core.Model/Models/SplitDemo.cs b/Blog.Core.Model/Models/SplitDemo.cs new file mode 100644 index 00000000..75154038 --- /dev/null +++ b/Blog.Core.Model/Models/SplitDemo.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Blog.Core.Model.Models +{ + [SplitTable(SplitType.Day)]//按天分表 (自带分表支持 年、季、月、周、日) + [SugarTable("SplitDemo_{year}{month}{day}")]//3个变量必须要有,这么设计为了兼容开始按年,后面改成按月、按日 + public class SplitDemo + { + [SugarColumn(IsPrimaryKey = true)] + public long Id { get; set; } + + public string Name { get; set; } + + [SugarColumn(IsNullable = true)]//设置为可空字段 (更多用法看文档 迁移) + public DateTime UpdateTime { get; set; } + + [SplitField] //分表字段 在插入的时候会根据这个字段插入哪个表,在更新删除的时候用这个字段找出相关表 + public DateTime CreateTime { get; set; } + } +} diff --git a/Blog.Core.Model/Models/SysTenant.cs b/Blog.Core.Model/Models/SysTenant.cs new file mode 100644 index 00000000..61d03866 --- /dev/null +++ b/Blog.Core.Model/Models/SysTenant.cs @@ -0,0 +1,67 @@ +using Blog.Core.Model.Tenants; +using SqlSugar; + +namespace Blog.Core.Model.Models; + +/// +/// 系统租户表
+/// 根据TenantType 分为两种方案:
+/// 1.按租户字段区分
+/// 2.按租户分库
+/// +///
+/// +/// 注意:
+/// 使用租户Id方案,无需配置分库的连接 +///
+public class SysTenant : RootEntityTkey +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 租户类型 + /// + public TenantTypeEnum TenantType { get; set; } + + /// + /// 数据库/租户标识 不可重复
+ /// 使用Id方案,可无需配置 + ///
+ [SugarColumn(Length = 64)] + public string ConfigId { get; set; } + + /// + /// 主机
+ /// 使用Id方案,可无需配置 + ///
+ [SugarColumn(IsNullable = true)] + public string Host { get; set; } + + /// + /// 数据库类型
+ /// 使用Id方案,可无需配置 + ///
+ [SugarColumn(IsNullable = true)] + public SqlSugar.DbType? DbType { get; set; } + + /// + /// 数据库连接
+ /// 使用Id方案,可无需配置 + ///
+ [SugarColumn(IsNullable = true)] + public string Connection { get; set; } + + /// + /// 状态 + /// + public bool Status { get; set; } = true; + + /// + /// 备注 + /// + [SugarColumn(IsNullable = true)] + public string Remark { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/TasksLog.cs b/Blog.Core.Model/Models/TasksLog.cs new file mode 100644 index 00000000..c79e8077 --- /dev/null +++ b/Blog.Core.Model/Models/TasksLog.cs @@ -0,0 +1,87 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model.Models +{ + /// + /// 任务日志表 + /// + public class TasksLog : RootEntityTkey + { + /// + /// 任务ID + /// + public long JobId { get; set; } + /// + /// 任务耗时 + /// + public double TotalTime { get; set; } + /// + /// 执行结果(0-失败 1-成功) + /// + public bool RunResult { get; set; } + /// + /// 运行时间 + /// + public DateTime RunTime { get; set; } + /// + /// 结束时间 + /// + public DateTime EndTime { get; set; } + /// + /// 执行参数 + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string RunPars { get; set; } + /// + /// 异常信息 + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string ErrMessage { get; set; } + /// + /// 异常堆栈 + /// + [SugarColumn(Length = 2000, IsNullable = true)] + public string ErrStackTrace { get; set; } + /// + /// 创建ID + /// + [SugarColumn(IsNullable = true)] + public int? CreateId { get; set; } + /// + /// 创建者 + /// + [SugarColumn(Length = 50, IsNullable = true)] + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime CreateTime { get; set; } = DateTime.Now; + /// + /// 修改ID + /// + [SugarColumn(IsNullable = true)] + public int? ModifyId { get; set; } + /// + /// 修改者 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } = DateTime.Now; + /// + /// 任务名称 + /// + [SugarColumn(IsIgnore = true)] + public string Name { get; set; } + /// + /// 任务分组 + /// + [SugarColumn(IsIgnore = true)] + public string JobGroup { get; set; } + } +} diff --git a/Blog.Core.Model/Models/TasksQz.cs b/Blog.Core.Model/Models/TasksQz.cs index 5c812f79..b029a995 100644 --- a/Blog.Core.Model/Models/TasksQz.cs +++ b/Blog.Core.Model/Models/TasksQz.cs @@ -8,7 +8,7 @@ namespace Blog.Core.Model.Models /// /// 任务计划表 /// - public class TasksQz : RootEntityTkey + public class TasksQz : RootEntityTkey { /// /// 任务名称 @@ -65,6 +65,10 @@ public class TasksQz : RootEntityTkey /// public int CycleRunTimes { get; set; } /// + /// 已循环次数 + /// + public int CycleHasRunTimes { get; set; } + /// /// 是否启动 /// public bool IsStart { get; set; } = false; diff --git a/Blog.Core.Model/Models/Tenant/BusinessTable.cs b/Blog.Core.Model/Models/Tenant/BusinessTable.cs new file mode 100644 index 00000000..b3b0140a --- /dev/null +++ b/Blog.Core.Model/Models/Tenant/BusinessTable.cs @@ -0,0 +1,27 @@ +using Blog.Core.Model.Models.RootTkey; +using Blog.Core.Model.Tenants; + +namespace Blog.Core.Model.Models; + +/// +/// 业务数据
+/// 多租户 (Id 隔离) +///
+public class BusinessTable : BaseEntity, ITenantEntity +{ + /// + /// 无需手动赋值 + /// + public long TenantId { get; set; } + + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 金额 + /// + public decimal Amount { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/Tenant/MultiBusinessSubTable.cs b/Blog.Core.Model/Models/Tenant/MultiBusinessSubTable.cs new file mode 100644 index 00000000..1ada394d --- /dev/null +++ b/Blog.Core.Model/Models/Tenant/MultiBusinessSubTable.cs @@ -0,0 +1,14 @@ +using Blog.Core.Model.Models.RootTkey; +using Blog.Core.Model.Tenants; + +namespace Blog.Core.Model.Models; + +/// +/// 多租户-多表方案 业务表 子表
+///
+[MultiTenant(TenantTypeEnum.Tables)] +public class MultiBusinessSubTable : BaseEntity +{ + public long MainId { get; set; } + public string Memo { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/Tenant/MultiBusinessTable.cs b/Blog.Core.Model/Models/Tenant/MultiBusinessTable.cs new file mode 100644 index 00000000..619bdaaf --- /dev/null +++ b/Blog.Core.Model/Models/Tenant/MultiBusinessTable.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Blog.Core.Model.Models.RootTkey; +using Blog.Core.Model.Tenants; +using SqlSugar; + +namespace Blog.Core.Model.Models; + +/// +/// 多租户-多表方案 业务表
+///
+[MultiTenant(TenantTypeEnum.Tables)] +public class MultiBusinessTable : BaseEntity +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 金额 + /// + public decimal Amount { get; set; } + + [Navigate(NavigateType.OneToMany, nameof(MultiBusinessSubTable.MainId))] + public List Child { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/Tenant/SubLibraryBusinessTable.cs b/Blog.Core.Model/Models/Tenant/SubLibraryBusinessTable.cs new file mode 100644 index 00000000..446fb436 --- /dev/null +++ b/Blog.Core.Model/Models/Tenant/SubLibraryBusinessTable.cs @@ -0,0 +1,22 @@ +using Blog.Core.Model.Models.RootTkey; +using Blog.Core.Model.Tenants; + +namespace Blog.Core.Model.Models; + +/// +/// 多租户-多库方案 业务表
+/// 公共库无需标记[MultiTenant]特性 +///
+[MultiTenant] +public class SubLibraryBusinessTable : BaseEntity +{ + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 金额 + /// + public decimal Amount { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/TestModels.cs b/Blog.Core.Model/Models/TestModels.cs index f5fa7dc7..8a8d123c 100644 --- a/Blog.Core.Model/Models/TestModels.cs +++ b/Blog.Core.Model/Models/TestModels.cs @@ -5,9 +5,9 @@ public class TestMuchTableResult { public string moduleName { get; set; } public string permName { get; set; } - public int rid { get; set; } - public int mid { get; set; } - public int? pid { get; set; } + public long rid { get; set; } + public long mid { get; set; } + public long? pid { get; set; } } } diff --git a/Blog.Core.Model/Models/Topic.cs b/Blog.Core.Model/Models/Topic.cs index 16bf7dad..e57bd561 100644 --- a/Blog.Core.Model/Models/Topic.cs +++ b/Blog.Core.Model/Models/Topic.cs @@ -7,7 +7,7 @@ namespace Blog.Core.Model.Models /// /// Tibug 类别 /// - public class Topic : RootEntityTkey + public class Topic : RootEntityTkey { public Topic() { diff --git a/Blog.Core.Model/Models/TopicDetail.cs b/Blog.Core.Model/Models/TopicDetail.cs index 87e16ebf..6cb69c67 100644 --- a/Blog.Core.Model/Models/TopicDetail.cs +++ b/Blog.Core.Model/Models/TopicDetail.cs @@ -6,7 +6,7 @@ namespace Blog.Core.Model.Models /// /// Tibug 博文 /// - public class TopicDetail : TopicDetailRoot + public class TopicDetail : TopicDetailRoot { public TopicDetail() { @@ -19,7 +19,7 @@ public TopicDetail() [SugarColumn(Length = 200, IsNullable = true)] public string tdName { get; set; } - [SugarColumn(Length = 6000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string tdContent { get; set; } [SugarColumn(Length = 2000, IsNullable = true)] diff --git a/Blog.Core.Model/Models/UserRole.cs b/Blog.Core.Model/Models/UserRole.cs index 996eea2c..7ed9c6be 100644 --- a/Blog.Core.Model/Models/UserRole.cs +++ b/Blog.Core.Model/Models/UserRole.cs @@ -6,11 +6,11 @@ namespace Blog.Core.Model.Models /// /// 用户跟角色关联表 /// - public class UserRole : UserRoleRoot + public class UserRole : UserRoleRoot { public UserRole() { } - public UserRole(int uid, int rid) + public UserRole(long uid, long rid) { UserId = uid; RoleId = rid; @@ -31,7 +31,7 @@ public UserRole(int uid, int rid) /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// diff --git a/Blog.Core.Model/Models/WeChatConfig.cs b/Blog.Core.Model/Models/WeChatConfig.cs index f0aa97c4..16910487 100644 --- a/Blog.Core.Model/Models/WeChatConfig.cs +++ b/Blog.Core.Model/Models/WeChatConfig.cs @@ -51,13 +51,13 @@ public class WeChatConfig /// /// 公众号推送token /// - [SugarColumn(Length = 100, IsNullable = true)] + [SugarColumn(Length = 500, IsNullable = true)] public string token { get; set; } /// /// 验证秘钥(验证消息是否真实) /// - [SugarColumn(Length = 100, IsNullable = false)] + [SugarColumn(Length = 500, IsNullable = false)] public string interactiveToken { get; set; } /// diff --git a/Blog.Core.Model/Models/WeChatPushLog.cs b/Blog.Core.Model/Models/WeChatPushLog.cs index b2b6752e..5368c806 100644 --- a/Blog.Core.Model/Models/WeChatPushLog.cs +++ b/Blog.Core.Model/Models/WeChatPushLog.cs @@ -50,7 +50,7 @@ public partial class WeChatPushLog /// /// 推送内容 /// - [SugarColumn(IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string PushLogContent { get; set; } /// diff --git a/Blog.Core.Model/Models/sysUserInfo.cs b/Blog.Core.Model/Models/sysUserInfo.cs index 8aafb164..1137d91c 100644 --- a/Blog.Core.Model/Models/sysUserInfo.cs +++ b/Blog.Core.Model/Models/sysUserInfo.cs @@ -4,130 +4,141 @@ namespace Blog.Core.Model.Models { - /// - /// 用户信息表 - /// - //[SugarTable("SysUserInfo")] - [SugarTable("SysUserInfo", "用户表")]//('数据库表名','数据库表备注') - public class SysUserInfo : SysUserInfoRoot - { - public SysUserInfo() - { - } - - public SysUserInfo(string loginName, string loginPWD) - { - LoginName = loginName; - LoginPWD = loginPWD; - RealName = LoginName; - Status = 0; - CreateTime = DateTime.Now; - UpdateTime = DateTime.Now; - LastErrorTime = DateTime.Now; - ErrorCount = 0; - Name = ""; - } - - /// - /// 登录账号 - /// - [SugarColumn(Length = 200, IsNullable = true, ColumnDescription = "登录账号")] - //:eg model 根据sqlsugar的完整定义可以如下定义,ColumnDescription可定义表字段备注 - //[SugarColumn(IsNullable = false, ColumnDescription = "登录账号", IsPrimaryKey = false, IsIdentity = false, ColumnDataType = "nvarchar", Length = 50)] - //ColumnDescription 表字段备注, 已在MSSQL测试,配合 [SugarTable("SysUserInfo", "用户表")]//('数据库表名','数据库表备注') - //可以完整生成 表备注和各个字段的中文备注 - //2022/10/11 - //测试mssql 发现 不写ColumnDescription,写好注释在mssql下也能生成表字段备注 - public string LoginName { get; set; } - - /// - /// 登录密码 - /// - [SugarColumn(Length = 200, IsNullable = true)] - public string LoginPWD { get; set; } - - /// - /// 真实姓名 - /// - [SugarColumn(Length = 200, IsNullable = true)] - public string RealName { get; set; } - - /// - /// 状态 - /// - public int Status { get; set; } - - /// - /// 部门 - /// - [SugarColumn(IsNullable = true)] - public int DepartmentId { get; set; } = -1; - - /// - /// 备注 - /// - [SugarColumn(Length = 2000, IsNullable = true)] - public string Remark { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreateTime { get; set; } = DateTime.Now; - - /// - /// 更新时间 - /// - public DateTime UpdateTime { get; set; } = DateTime.Now; - - /// - /// 关键业务修改时间 - /// - public DateTime CriticalModifyTime { get; set; } = DateTime.Now; - - /// - ///最后异常时间 - /// - public DateTime LastErrorTime { get; set; } = DateTime.Now; - - /// - ///错误次数 - /// - public int ErrorCount { get; set; } - - - /// - /// 登录账号 - /// - [SugarColumn(Length = 200, IsNullable = true)] - public string Name { get; set; } - - // 性别 - [SugarColumn(IsNullable = true)] - public int Sex { get; set; } = 0; - - // 年龄 - [SugarColumn(IsNullable = true)] - public int Age { get; set; } - - // 生日 - [SugarColumn(IsNullable = true)] - public DateTime Birth { get; set; } = DateTime.Now; - - // 地址 - [SugarColumn(Length = 200, IsNullable = true)] - public string Address { get; set; } - - [SugarColumn(IsNullable = true)] - public bool IsDeleted { get; set; } - - - [SugarColumn(IsIgnore = true)] - public List RoleNames { get; set; } - - [SugarColumn(IsIgnore = true)] - public List Dids { get; set; } - - [SugarColumn(IsIgnore = true)] - public string DepartmentName { get; set; } - } -} \ No newline at end of file + /// + /// 用户信息表 + /// + //[SugarTable("SysUserInfo")] + [SugarTable("SysUserInfo", "用户表")] //('数据库表名','数据库表备注') + public class SysUserInfo : SysUserInfoRoot + { + public SysUserInfo() + { + } + + public SysUserInfo(string loginName, string loginPWD) + { + LoginName = loginName; + LoginPWD = loginPWD; + RealName = LoginName; + Status = 0; + CreateTime = DateTime.Now; + UpdateTime = DateTime.Now; + LastErrorTime = DateTime.Now; + ErrorCount = 0; + Name = ""; + } + + /// + /// 登录账号 + /// + [SugarColumn(Length = 200, IsNullable = true, ColumnDescription = "登录账号")] + //:eg model 根据sqlsugar的完整定义可以如下定义,ColumnDescription可定义表字段备注 + //[SugarColumn(IsNullable = false, ColumnDescription = "登录账号", IsPrimaryKey = false, IsIdentity = false, Length = 50)] + //ColumnDescription 表字段备注, 已在MSSQL测试,配合 [SugarTable("SysUserInfo", "用户表")]//('数据库表名','数据库表备注') + //可以完整生成 表备注和各个字段的中文备注 + //2022/10/11 + //测试mssql 发现 不写ColumnDescription,写好注释在mssql下也能生成表字段备注 + public string LoginName { get; set; } + + /// + /// 登录密码 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string LoginPWD { get; set; } + + /// + /// 真实姓名 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string RealName { get; set; } + + /// + /// 状态 + /// + public int Status { get; set; } + + /// + /// 部门 + /// + [SugarColumn(IsNullable = true)] + public long DepartmentId { get; set; } = -1; + + /// + /// 备注 + /// + [SugarColumn(Length = 2000, IsNullable = true)] + public string Remark { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } = DateTime.Now; + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } = DateTime.Now; + + /// + /// 关键业务修改时间 + /// + public DateTime CriticalModifyTime { get; set; } = DateTime.Now; + + /// + ///最后异常时间 + /// + public DateTime LastErrorTime { get; set; } = DateTime.Now; + + /// + ///错误次数 + /// + public int ErrorCount { get; set; } + + + /// + /// 登录账号 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string Name { get; set; } + + // 性别 + [SugarColumn(IsNullable = true)] + public int Sex { get; set; } = 0; + + // 年龄 + [SugarColumn(IsNullable = true)] + public int Age { get; set; } + + // 生日 + [SugarColumn(IsNullable = true)] + public DateTime Birth { get; set; } = DateTime.Now; + + // 地址 + [SugarColumn(Length = 200, IsNullable = true)] + public string Address { get; set; } + + [SugarColumn(DefaultValue = "1")] + public bool Enable { get; set; } = true; + + [SugarColumn(IsNullable = true)] + public bool IsDeleted { get; set; } + + /// + /// 租户Id + /// + [SugarColumn(IsNullable = false, DefaultValue = "0")] + public long TenantId { get; set; } + + [Navigate(NavigateType.OneToOne, nameof(TenantId))] + public SysTenant Tenant { get; set; } + + [SugarColumn(IsIgnore = true)] + public List RoleNames { get; set; } + + [SugarColumn(IsIgnore = true)] + public List Dids { get; set; } + + [SugarColumn(IsIgnore = true)] + public string DepartmentName { get; set; } + } +} diff --git a/Blog.Core.Model/Systems/DataBase/DataBaseReadType.cs b/Blog.Core.Model/Systems/DataBase/DataBaseReadType.cs new file mode 100644 index 00000000..f2ae1ed0 --- /dev/null +++ b/Blog.Core.Model/Systems/DataBase/DataBaseReadType.cs @@ -0,0 +1,10 @@ +namespace Blog.Core.Model.Systems.DataBase; + +/// +/// 数据库读取类型 +/// +public enum DataBaseReadType +{ + Db, + Entity +} \ No newline at end of file diff --git a/Blog.Core.Model/Systems/DataBase/DatabaseOutput.cs b/Blog.Core.Model/Systems/DataBase/DatabaseOutput.cs new file mode 100644 index 00000000..8cefaeb6 --- /dev/null +++ b/Blog.Core.Model/Systems/DataBase/DatabaseOutput.cs @@ -0,0 +1,10 @@ +using SqlSugar; + +namespace Blog.Core.Model.Systems.DataBase; + +public class DatabaseOutput +{ + public string ConfigId { get; set; } + + public DbType DbType { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Systems/DataBase/DbColumnInfoOutput.cs b/Blog.Core.Model/Systems/DataBase/DbColumnInfoOutput.cs new file mode 100644 index 00000000..6cada2ff --- /dev/null +++ b/Blog.Core.Model/Systems/DataBase/DbColumnInfoOutput.cs @@ -0,0 +1,36 @@ +namespace Blog.Core.Model.Systems.DataBase; + +public class DbColumnInfoOutput +{ + public string TableName { get; set; } + + public int TableId { get; set; } + + public string DbColumnName { get; set; } + + public string PropertyName { get; set; } + + public string DataType { get; set; } + + public int Length { get; set; } + + public string ColumnDescription { get; set; } + + public string DefaultValue { get; set; } + + public bool IsNullable { get; set; } + + public bool IsIdentity { get; set; } + + public bool IsPrimarykey { get; set; } + + public object Value { get; set; } + + public int DecimalDigits { get; set; } + + public int Scale { get; set; } + + public bool IsArray { get; set; } + + internal bool IsJson { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Systems/DataBase/EditColumnInput.cs b/Blog.Core.Model/Systems/DataBase/EditColumnInput.cs new file mode 100644 index 00000000..88c66973 --- /dev/null +++ b/Blog.Core.Model/Systems/DataBase/EditColumnInput.cs @@ -0,0 +1,9 @@ +namespace Blog.Core.Model.Systems.DataBase; + +public class EditColumnInput +{ + public string ConfigId { get; set; } + public string TableName { get; set; } + public string DbColumnName { get; set; } + public string ColumnDescription { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Systems/DataBase/EditTableInput.cs b/Blog.Core.Model/Systems/DataBase/EditTableInput.cs new file mode 100644 index 00000000..496a6dd2 --- /dev/null +++ b/Blog.Core.Model/Systems/DataBase/EditTableInput.cs @@ -0,0 +1,10 @@ +namespace Blog.Core.Model.Systems.DataBase; + +public class EditTableInput +{ + public string ConfigId { get; set; } + + public string TableName { get; set; } + + public string Description { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Tenants/ITenantEntity.cs b/Blog.Core.Model/Tenants/ITenantEntity.cs new file mode 100644 index 00000000..2d0c5dc9 --- /dev/null +++ b/Blog.Core.Model/Tenants/ITenantEntity.cs @@ -0,0 +1,15 @@ +using SqlSugar; + +namespace Blog.Core.Model.Tenants; + +/// +/// 租户模型接口 +/// +public interface ITenantEntity +{ + /// + /// 租户Id + /// + [SugarColumn(DefaultValue = "0")] + public long TenantId { get; set; } +} \ No newline at end of file diff --git a/Blog.Core.Model/Tenants/MultiTenantAttribute.cs b/Blog.Core.Model/Tenants/MultiTenantAttribute.cs new file mode 100644 index 00000000..443745bf --- /dev/null +++ b/Blog.Core.Model/Tenants/MultiTenantAttribute.cs @@ -0,0 +1,24 @@ +using System; + +namespace Blog.Core.Model.Tenants; + +/// +/// 标识 多租户 的业务表
+/// 默认设置是多库
+/// 公共表无需区分 直接使用主库 各自业务在各自库中
+///
+[AttributeUsage(AttributeTargets.Class)] +public class MultiTenantAttribute : Attribute +{ + public MultiTenantAttribute() + { + } + + public MultiTenantAttribute(TenantTypeEnum tenantType) + { + TenantType = tenantType; + } + + + public TenantTypeEnum TenantType { get; set; } = TenantTypeEnum.Db; +} \ No newline at end of file diff --git a/Blog.Core.Model/Tenants/TenantTypeEnum.cs b/Blog.Core.Model/Tenants/TenantTypeEnum.cs new file mode 100644 index 00000000..f4af3bda --- /dev/null +++ b/Blog.Core.Model/Tenants/TenantTypeEnum.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; + +namespace Blog.Core.Model.Tenants; + +/// +/// 租户隔离方案 +/// +public enum TenantTypeEnum +{ + None = 0, + + /// + /// Id隔离 + /// + [Description("Id隔离")] + Id = 1, + + /// + /// 库隔离 + /// + [Description("库隔离")] + Db = 2, + + /// + /// 表隔离 + /// + [Description("表隔离")] + Tables = 3, +} \ No newline at end of file diff --git a/Blog.Core.Model/ViewModels/BlogViewModels.cs b/Blog.Core.Model/ViewModels/BlogViewModels.cs index f959270c..86c16618 100644 --- a/Blog.Core.Model/ViewModels/BlogViewModels.cs +++ b/Blog.Core.Model/ViewModels/BlogViewModels.cs @@ -10,7 +10,7 @@ public class BlogViewModels /// /// /// - public int bID { get; set; } + public long bID { get; set; } /// 创建人 /// /// @@ -34,7 +34,7 @@ public class BlogViewModels /// /// 上一篇id /// - public int previousID { get; set; } + public long previousID { get; set; } /// /// 下一篇 @@ -44,7 +44,7 @@ public class BlogViewModels /// /// 下一篇id /// - public int nextID { get; set; } + public long nextID { get; set; } /// 类别 /// diff --git a/Blog.Core.Model/ViewModels/SysUserInfoDto.cs b/Blog.Core.Model/ViewModels/SysUserInfoDto.cs index 33d84161..3b5451f0 100644 --- a/Blog.Core.Model/ViewModels/SysUserInfoDto.cs +++ b/Blog.Core.Model/ViewModels/SysUserInfoDto.cs @@ -3,13 +3,13 @@ namespace Blog.Core.Model.ViewModels { - public class SysUserInfoDto : SysUserInfoDtoRoot + public class SysUserInfoDto : SysUserInfoDtoRoot { public string uLoginName { get; set; } public string uLoginPWD { get; set; } public string uRealName { get; set; } public int uStatus { get; set; } - public int DepartmentId { get; set; } + public long DepartmentId { get; set; } public string uRemark { get; set; } public System.DateTime uCreateTime { get; set; } = DateTime.Now; public System.DateTime uUpdateTime { get; set; } = DateTime.Now; @@ -22,7 +22,7 @@ public class SysUserInfoDto : SysUserInfoDtoRoot public string addr { get; set; } public bool tdIsDelete { get; set; } public List RoleNames { get; set; } - public List Dids { get; set; } + public List Dids { get; set; } public string DepartmentName { get; set; } } } diff --git a/Blog.Core.Model/ViewModels/UploadFileDto.cs b/Blog.Core.Model/ViewModels/UploadFileDto.cs index fc2b3cf7..dd0f51bb 100644 --- a/Blog.Core.Model/ViewModels/UploadFileDto.cs +++ b/Blog.Core.Model/ViewModels/UploadFileDto.cs @@ -1,10 +1,5 @@ using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.ViewModels { @@ -12,7 +7,7 @@ public class UploadFileDto { //多文件 [Required] - public IFormFileCollection Files { get; set; } + public IFormFileCollection file { get; set; } //单文件 //public IFormFile File { get; set; } diff --git a/Blog.Core.Publish.Docker.Jenkins.sh b/Blog.Core.Publish.Docker.Jenkins.sh index 3c561c67..d82c2d2f 100644 --- a/Blog.Core.Publish.Docker.Jenkins.sh +++ b/Blog.Core.Publish.Docker.Jenkins.sh @@ -4,7 +4,7 @@ cd Blog.Core.Api dotnet publish echo "Successfully!!!! ^ please see the file ." -cd bin/Debug/net6.0/publish/ +cd bin/Debug/net7.0/publish/ #rm -f appsettings.json #\cp -rf /var/jenkins_home/workspace/SecurityConfig/Blog.Core/appsettings.json appsettings.json diff --git a/Blog.Core.Publish.Linux.sh b/Blog.Core.Publish.Linux.sh index 05eb8103..7599f204 100644 --- a/Blog.Core.Publish.Linux.sh +++ b/Blog.Core.Publish.Linux.sh @@ -1,8 +1,9 @@ -git pull; + find .PublishFiles/ -type f -and ! -path '*/wwwroot/images/*' ! -name 'appsettings.*' |xargs rm -rf dotnet build; rm -rf /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles; dotnet publish -o /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles; +rm -rf /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles/WMBlog.db; # cp -r /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles ./; awk 'BEGIN { cmd="cp -ri /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles ./"; print "n" |cmd; }' echo "Successfully!!!! ^ please see the file .PublishFiles"; \ No newline at end of file diff --git a/Blog.Core.Publish.bat b/Blog.Core.Publish.bat index f022508d..cebbbf58 100644 --- a/Blog.Core.Publish.bat +++ b/Blog.Core.Publish.bat @@ -8,11 +8,11 @@ dotnet build cd Blog.Core.Api -dotnet publish -o ..\Blog.Core.Api\bin\Debug\net6.0\ +dotnet publish -o ..\Blog.Core.Api\bin\Debug\net7.0\ md ..\.PublishFiles -xcopy ..\Blog.Core.Api\bin\Debug\net6.0\*.* ..\.PublishFiles\ /s /e +xcopy ..\Blog.Core.Api\bin\Debug\net7.0\*.* ..\.PublishFiles\ /s /e echo "Successfully!!!! ^ please see the file .PublishFiles" diff --git a/Blog.Core.Repository/BASE/BaseRepository.cs b/Blog.Core.Repository/BASE/BaseRepository.cs index 82900689..3a1c1ee8 100644 --- a/Blog.Core.Repository/BASE/BaseRepository.cs +++ b/Blog.Core.Repository/BASE/BaseRepository.cs @@ -1,6 +1,11 @@ using Blog.Core.Common; +using Blog.Core.Common.DB; using Blog.Core.IRepository.Base; using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.Tenants; +using Blog.Core.Repository.UnitOfWorks; +using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime; using SqlSugar; using System; using System.Collections.Generic; @@ -8,7 +13,6 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; -using Blog.Core.Repository.UnitOfWorks; namespace Blog.Core.Repository.Base { @@ -22,19 +26,35 @@ private ISqlSugarClient _db get { ISqlSugarClient db = _dbBase; - /* 如果要开启多库支持, - * 1、在appsettings.json 中开启MutiDBEnabled节点为true,必填 - * 2、设置一个主连接的数据库ID,节点MainDB,对应的连接字符串的Enabled也必须true,必填 - */ - if (AppSettings.app(new[] {"MutiDBEnabled"}).ObjToBool()) + + //修改使用 model备注字段作为切换数据库条件,使用sqlsugar TenantAttribute存放数据库ConnId + //参考 https://www.donet5.com/Home/Doc?typeId=2246 + var tenantAttr = typeof(TEntity).GetCustomAttribute(); + if (tenantAttr != null) + { + //统一处理 configId 小写 + db = _dbBase.GetConnectionScope(tenantAttr.configId.ToString().ToLower()); + return db; + } + + //多租户 + var mta = typeof(TEntity).GetCustomAttribute(); + if (mta is { TenantType: TenantTypeEnum.Db }) { - //修改使用 model备注字段作为切换数据库条件,使用sqlsugar TenantAttribute存放数据库ConnId - //参考 https://www.donet5.com/Home/Doc?typeId=2246 - var tenantAttr = typeof(TEntity).GetCustomAttribute(); - if (tenantAttr != null) + //获取租户信息 租户信息可以提前缓存下来 + if (App.User is { TenantId: > 0 }) { - //统一处理 configId 小写 - db = _dbBase.GetConnectionScope(tenantAttr.configId.ToString().ToLower()); + var tenant = db.Queryable().WithCache().Where(s => s.Id == App.User.TenantId).First(); + if (tenant != null) + { + var iTenant = db.AsTenant(); + if (!iTenant.IsAnyConnection(tenant.ConfigId)) + { + iTenant.AddConnection(tenant.GetConnectionConfig()); + } + + return iTenant.GetConnectionScope(tenant.ConfigId); + } } } @@ -51,12 +71,12 @@ public BaseRepository(IUnitOfWorkManage unitOfWorkManage) } - public async Task QueryById(object objId) { //return await Task.Run(() => _db.Queryable().InSingle(objId)); return await _db.Queryable().In(objId).SingleAsync(); } + /// /// 功能描述:根据ID查询一条数据 /// 作  者:Blog.Core @@ -87,7 +107,7 @@ public async Task> QueryByIDs(object[] lstIds) /// /// 博文实体类 /// - public async Task Add(TEntity entity) + public async Task Add(TEntity entity) { //var i = await Task.Run(() => _db.Insertable(entity).ExecuteReturnBigIdentity()); ////返回的i是long类型,这里你可以根据你的业务需要进行处理 @@ -98,26 +118,25 @@ public async Task Add(TEntity entity) //这里你可以返回TEntity,这样的话就可以获取id值,无论主键是什么类型 //var return3 = await insert.ExecuteReturnEntityAsync(); - return await insert.ExecuteReturnIdentityAsync(); + return await insert.ExecuteReturnSnowflakeIdAsync(); } - /// /// 写入实体数据 /// /// 实体类 /// 指定只插入列 /// 返回自增量列 - public async Task Add(TEntity entity, Expression> insertColumns = null) + public async Task Add(TEntity entity, Expression> insertColumns = null) { var insert = _db.Insertable(entity); if (insertColumns == null) { - return await insert.ExecuteReturnIdentityAsync(); + return await insert.ExecuteReturnSnowflakeIdAsync(); } else { - return await insert.InsertColumns(insertColumns).ExecuteReturnIdentityAsync(); + return await insert.InsertColumns(insertColumns).ExecuteReturnSnowflakeIdAsync(); } } @@ -126,9 +145,9 @@ public async Task Add(TEntity entity, Expression> ins /// /// 实体集合 /// 影响行数 - public async Task Add(List listEntity) + public async Task> Add(List listEntity) { - return await _db.Insertable(listEntity.ToArray()).ExecuteCommandAsync(); + return await _db.Insertable(listEntity.ToArray()).ExecuteReturnSnowflakeIdListAsync(); } /// @@ -144,6 +163,15 @@ public async Task Update(TEntity entity) //这种方式会以主键为条件 return await _db.Updateable(entity).ExecuteCommandHasChangeAsync(); } + /// + /// 更新实体数据 + /// + /// 博文实体类 + /// + public async Task Update(List entity) + { + return await _db.Updateable(entity).ExecuteCommandHasChangeAsync(); + } public async Task Update(TEntity entity, string where) { @@ -161,25 +189,28 @@ public async Task Update(object operateAnonymousObjects) } public async Task Update( - TEntity entity, - List lstColumns = null, - List lstIgnoreColumns = null, - string where = "" - ) + TEntity entity, + List lstColumns = null, + List lstIgnoreColumns = null, + string where = "" + ) { IUpdateable up = _db.Updateable(entity); if (lstIgnoreColumns != null && lstIgnoreColumns.Count > 0) { up = up.IgnoreColumns(lstIgnoreColumns.ToArray()); } + if (lstColumns != null && lstColumns.Count > 0) { up = up.UpdateColumns(lstColumns.ToArray()); } + if (!string.IsNullOrEmpty(where)) { up = up.Where(where); } + return await up.ExecuteCommandHasChangeAsync(); } @@ -214,7 +245,6 @@ public async Task DeleteByIds(object[] ids) } - /// /// 功能描述:查询所有数据 /// 作  者:Blog.Core @@ -284,6 +314,7 @@ public async Task> Query(Expression> whereExpr { return await _db.Queryable().WhereIF(whereExpression != null, whereExpression).OrderByIF(orderByFields != null, orderByFields).ToListAsync(); } + /// /// 功能描述:查询一个列表 /// @@ -393,18 +424,16 @@ public async Task> Query( /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( - string where, - int pageIndex, - int pageSize, - - string orderByFields) + string where, + int pageIndex, + int pageSize, + string orderByFields) { return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) .WhereIF(!string.IsNullOrEmpty(where), where).ToPageListAsync(pageIndex, pageSize); } - /// /// 分页查询[使用版本,其他分页未测试] /// @@ -415,12 +444,11 @@ public async Task> Query( /// public async Task> QueryPage(Expression> whereExpression, int pageIndex = 1, int pageSize = 20, string orderByFields = null) { - RefAsync totalCount = 0; var list = await _db.Queryable() - .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) - .WhereIF(whereExpression != null, whereExpression) - .ToPageListAsync(pageIndex, pageSize, totalCount); + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); return new PageModel(pageIndex, totalCount, pageSize, list); } @@ -446,6 +474,7 @@ public async Task> QueryMuch( { return await _db.Queryable(joinExpression).Select(selectExpression).ToListAsync(); } + return await _db.Queryable(joinExpression).Where(whereLambda).Select(selectExpression).ToListAsync(); } @@ -471,13 +500,12 @@ public async Task> QueryTabsPage( int pageSize = 20, string orderByFields = null) { - RefAsync totalCount = 0; var list = await _db.Queryable(joinExpression) - .Select(selectExpression) - .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) - .WhereIF(whereExpression != null, whereExpression) - .ToPageListAsync(pageIndex, pageSize, totalCount); + .Select(selectExpression) + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); return new PageModel(pageIndex, totalCount, pageSize, list); } @@ -506,10 +534,10 @@ public async Task> QueryTabsPage( { RefAsync totalCount = 0; var list = await _db.Queryable(joinExpression).GroupBy(groupExpression) - .Select(selectExpression) - .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) - .WhereIF(whereExpression != null, whereExpression) - .ToPageListAsync(pageIndex, pageSize, totalCount); + .Select(selectExpression) + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); return new PageModel(pageIndex, totalCount, pageSize, list); } @@ -532,6 +560,80 @@ public async Task> QueryTabsPage( // jobName = s.jobName // }, exp, s => new { s.uID, s.uRealName, s.groupName, s.jobName }, model.currentPage, model.pageSize, model.orderField + " " + model.orderType); - } + #region Split分表基础接口 (基础CRUD) -} + /// + /// 分页查询[使用版本,其他分页未测试] + /// + /// 条件表达式 + /// 页码(下标0) + /// 页大小 + /// 排序字段,如name asc,age desc + /// + public async Task> QueryPageSplit(Expression> whereExpression, DateTime beginTime, DateTime endTime, int pageIndex = 1, int pageSize = 20, string orderByFields = null) + { + RefAsync totalCount = 0; + var list = await _db.Queryable().SplitTable(beginTime, endTime) + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); + var data = new PageModel(pageIndex, totalCount, pageSize, list); + return data; + } + + /// + /// 写入实体数据 + /// + /// 数据实体 + /// + public async Task> AddSplit(TEntity entity) + { + var insert = _db.Insertable(entity).SplitTable(); + //插入并返回雪花ID并且自动赋值ID  + return await insert.ExecuteReturnSnowflakeIdListAsync(); + } + + /// + /// 更新实体数据 + /// + /// 数据实体 + /// + public async Task UpdateSplit(TEntity entity, DateTime dateTime) + { + //直接根据实体集合更新 (全自动 找表更新) + //return await _db.Updateable(entity).SplitTable().ExecuteCommandAsync();//,SplitTable不能少 + + //精准找单个表 + var tableName = _db.SplitHelper().GetTableName(dateTime); //根据时间获取表名 + return await _db.Updateable(entity).AS(tableName).ExecuteCommandHasChangeAsync(); + } + + /// + /// 删除数据 + /// + /// + /// + /// + public async Task DeleteSplit(TEntity entity, DateTime dateTime) + { + ////直接根据实体集合删除 (全自动 找表插入),返回受影响数 + //return await _db.Deleteable(entity).SplitTable().ExecuteCommandAsync();//,SplitTable不能少 + + //精准找单个表 + var tableName = _db.SplitHelper().GetTableName(dateTime); //根据时间获取表名 + return await _db.Deleteable().AS(tableName).Where(entity).ExecuteCommandHasChangeAsync(); + } + + /// + /// 根据ID查找数据 + /// + /// + /// + public async Task QueryByIdSplit(object objId) + { + return await _db.Queryable().In(objId).SplitTable(tabs => tabs).SingleAsync(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Blog.Core.Repository/BASE/IBaseRepository.cs b/Blog.Core.Repository/BASE/IBaseRepository.cs index 29783505..77e9a808 100644 --- a/Blog.Core.Repository/BASE/IBaseRepository.cs +++ b/Blog.Core.Repository/BASE/IBaseRepository.cs @@ -33,14 +33,14 @@ public interface IBaseRepository where TEntity : class /// /// /// - Task Add(TEntity model); + Task Add(TEntity model); /// /// 批量添加 /// /// /// - Task Add(List listEntity); + Task> Add(List listEntity); /// /// 根据id 删除某一实体 @@ -69,6 +69,12 @@ public interface IBaseRepository where TEntity : class /// /// Task Update(TEntity model); + /// + /// 更新model + /// + /// + /// + Task Update(List model); /// /// 根据model,更新,带where条件 @@ -208,5 +214,45 @@ Task> QueryTabsPage( int pageIndex = 1, int pageSize = 20, string orderByFields = null); + + #region 分表 + /// + /// 通过ID查询 + /// + /// + /// + Task QueryByIdSplit(object objId); + /// + /// 自动分表插入 + /// + /// + /// + Task> AddSplit(TEntity entity); + /// + /// 删除 + /// + /// + /// + /// + Task DeleteSplit(TEntity entity, DateTime dateTime); + /// + /// 更新 + /// + /// + /// + /// + Task UpdateSplit(TEntity entity, DateTime dateTime); + /// + /// 分页查询 + /// + /// + /// + /// + /// + /// + /// + /// + Task> QueryPageSplit(Expression> whereExpression, DateTime beginTime, DateTime endTime, int pageIndex = 1, int pageSize = 20, string orderByFields = null); + #endregion } } diff --git a/Blog.Core.Repository/Blog.Core.Repository.csproj b/Blog.Core.Repository/Blog.Core.Repository.csproj index 5c9764b0..fff34572 100644 --- a/Blog.Core.Repository/Blog.Core.Repository.csproj +++ b/Blog.Core.Repository/Blog.Core.Repository.csproj @@ -1,14 +1,11 @@  - - net6.0 - - - - + + + @@ -21,7 +18,7 @@ - + diff --git a/Blog.Core.Repository/IRoleModulePermissionRepository.cs b/Blog.Core.Repository/IRoleModulePermissionRepository.cs index c66448f0..9ba3d4ed 100644 --- a/Blog.Core.Repository/IRoleModulePermissionRepository.cs +++ b/Blog.Core.Repository/IRoleModulePermissionRepository.cs @@ -19,6 +19,6 @@ public interface IRoleModulePermissionRepository : IBaseRepository菜单主键 /// 接口主键 /// - Task UpdateModuleId(int permissionId, int moduleId); + Task UpdateModuleId(long permissionId, long moduleId); } } diff --git a/Blog.Core.Repository/RoleModulePermissionRepository.cs b/Blog.Core.Repository/RoleModulePermissionRepository.cs index 1cb21ebf..9438ff50 100644 --- a/Blog.Core.Repository/RoleModulePermissionRepository.cs +++ b/Blog.Core.Repository/RoleModulePermissionRepository.cs @@ -99,7 +99,7 @@ public async Task> GetRMPMapsPage() /// 菜单主键 /// 接口主键 /// - public async Task UpdateModuleId(int permissionId, int moduleId) + public async Task UpdateModuleId(long permissionId, long moduleId) { await Db.Updateable(it => it.ModuleId == moduleId).Where( it => it.PermissionId == permissionId).ExecuteCommandAsync(); diff --git a/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs b/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs index fe190b3b..cfaffd9b 100644 --- a/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs +++ b/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs @@ -51,21 +51,25 @@ public UnitOfWork CreateUnitOfWork() public void BeginTran() { + Console.WriteLine("Begin Transaction Before:" + GetDbClient().ContextID); lock (this) { _tranCount++; GetDbClient().BeginTran(); } + Console.WriteLine("Begin Transaction After:" + GetDbClient().ContextID); } public void BeginTran(MethodInfo method) { + Console.WriteLine("Begin Transaction Before:" + GetDbClient().ContextID); lock (this) { GetDbClient().BeginTran(); TranStack.Push(method.GetFullName()); _tranCount = TranStack.Count; } + Console.WriteLine("Begin Transaction After:" + GetDbClient().ContextID); } public void CommitTran() diff --git a/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj b/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj index 5cf82020..2024571e 100644 --- a/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj +++ b/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj @@ -1,17 +1,14 @@ - - net6.0 - - - - - - - + + + + + + diff --git a/Blog.Core.Serilog/Blog.Core.Serilog.csproj b/Blog.Core.Serilog/Blog.Core.Serilog.csproj new file mode 100644 index 00000000..c1c161b1 --- /dev/null +++ b/Blog.Core.Serilog/Blog.Core.Serilog.csproj @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/Blog.Core.Serilog/Configuration/LogBatchingSinkConfiguration.cs b/Blog.Core.Serilog/Configuration/LogBatchingSinkConfiguration.cs new file mode 100644 index 00000000..65aacde7 --- /dev/null +++ b/Blog.Core.Serilog/Configuration/LogBatchingSinkConfiguration.cs @@ -0,0 +1,31 @@ +using Blog.Core.Common; +using Blog.Core.Serilog.Sink; +using Serilog; +using Serilog.Sinks.PeriodicBatching; + +namespace Blog.Core.Serilog.Configuration; + +public static class LogBatchingSinkConfiguration +{ + public static LoggerConfiguration WriteToLogBatching(this LoggerConfiguration loggerConfiguration) + { + if (!AppSettings.app("AppSettings", "LogToDb").ObjToBool()) + { + return loggerConfiguration; + } + + var exampleSink = new LogBatchingSink(); + + var batchingOptions = new PeriodicBatchingSinkOptions + { + BatchSizeLimit = 500, + Period = TimeSpan.FromSeconds(1), + EagerlyEmitFirstEvent = true, + QueueLimit = 10000 + }; + + var batchingSink = new PeriodicBatchingSink(exampleSink, batchingOptions); + + return loggerConfiguration.WriteTo.Sink(batchingSink); + } +} \ No newline at end of file diff --git a/Blog.Core.Serilog/Extensions/LoggerConfigurationExtensions.cs b/Blog.Core.Serilog/Extensions/LoggerConfigurationExtensions.cs new file mode 100644 index 00000000..d70616b4 --- /dev/null +++ b/Blog.Core.Serilog/Extensions/LoggerConfigurationExtensions.cs @@ -0,0 +1,83 @@ +using Blog.Core.Common; +using Blog.Core.Common.LogHelper; +using Serilog; +using Serilog.Events; +using Serilog.Filters; +using SqlSugar; + +namespace Blog.Core.Serilog.Extensions; + +public static class LoggerConfigurationExtensions +{ + public static LoggerConfiguration WriteToConsole(this LoggerConfiguration loggerConfiguration) + { + //输出普通日志 + loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg => + lg.FilterRemoveSqlLog().WriteTo.Console()); + + //输出SQL + loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg => + lg.FilterSqlLog().Filter.ByIncludingOnly(Matching.WithProperty(LogContextStatic.SqlOutToConsole, s => s)) + .WriteTo.Console()); + + return loggerConfiguration; + } + + public static LoggerConfiguration WriteToFile(this LoggerConfiguration loggerConfiguration) + { + //输出SQL + loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg => + lg.FilterSqlLog().Filter.ByIncludingOnly(Matching.WithProperty(LogContextStatic.SqlOutToFile, s => s)) + .WriteTo.Async(s => s.File(LogContextStatic.Combine(LogContextStatic.AopSql, @"AopSql.txt"), rollingInterval: RollingInterval.Day, + outputTemplate: LogContextStatic.FileMessageTemplate, retainedFileCountLimit: 31))); + //输出普通日志 + loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg => + lg.FilterRemoveSqlLog().WriteTo.Async(s => s.File(LogContextStatic.Combine(LogContextStatic.BasePathLogs, @"Log.txt"), rollingInterval: RollingInterval.Day, + outputTemplate: LogContextStatic.FileMessageTemplate, retainedFileCountLimit: 31))); + return loggerConfiguration; + } + + public static LoggerConfiguration FilterSqlLog(this LoggerConfiguration lc) + { + lc = lc.Filter.ByIncludingOnly(Matching.WithProperty(LogContextStatic.LogSource, s => LogContextStatic.AopSql.Equals(s))); + return lc; + } + + public static IEnumerable FilterSqlLog(this IEnumerable batch) + { + //只记录 Insert、Update、Delete语句 + return batch.Where(s => s.WithProperty(LogContextStatic.LogSource, q => LogContextStatic.AopSql.Equals(q))) + .Where(s => s.WithProperty(LogContextStatic.SugarActionType, + q => !new[] { SugarActionType.UnKnown, SugarActionType.Query }.Contains(q))); + } + + public static LoggerConfiguration FilterRemoveSqlLog(this LoggerConfiguration lc) + { + lc = lc.Filter.ByIncludingOnly(WithProperty(LogContextStatic.LogSource, s => !LogContextStatic.AopSql.Equals(s))); + return lc; + } + + public static IEnumerable FilterRemoveOtherLog(this IEnumerable batch) + { + return batch.Where(s => WithProperty(LogContextStatic.LogSource, + q => !LogContextStatic.AopSql.Equals(q))(s)); + } + + public static Func WithProperty(string propertyName, Func predicate) + { + //如果不包含属性 也认为是true + return e => + { + if (!e.Properties.TryGetValue(propertyName, out var propertyValue)) return true; + + return propertyValue is ScalarValue { Value: T value } && predicate(value); + }; + } + + public static bool WithProperty(this LogEvent e, string key, Func predicate) + { + if (!e.Properties.TryGetValue(key, out var propertyValue)) return false; + + return propertyValue is ScalarValue { Value: T value } && predicate(value); + } +} \ No newline at end of file diff --git a/Blog.Core.Serilog/Sink/LogBatchingSink.cs b/Blog.Core.Serilog/Sink/LogBatchingSink.cs new file mode 100644 index 00000000..0a6e1223 --- /dev/null +++ b/Blog.Core.Serilog/Sink/LogBatchingSink.cs @@ -0,0 +1,135 @@ +using Blog.Core.Common; +using Blog.Core.Model.Logs; +using Blog.Core.Serilog.Extensions; +using Mapster; +using Serilog.Events; +using Serilog.Sinks.PeriodicBatching; +using SqlSugar; + +namespace Blog.Core.Serilog.Sink; + +public class LogBatchingSink : IBatchedLogEventSink +{ + public async Task EmitBatchAsync(IEnumerable batch) + { + var sugar = App.GetService(false); + + await WriteSqlLog(sugar, batch.FilterSqlLog()); + await WriteLogs(sugar, batch.FilterRemoveOtherLog()); + } + + public Task OnEmptyBatchAsync() + { + return Task.CompletedTask; + } + + #region Write Log + + private async Task WriteLogs(ISqlSugarClient db, IEnumerable batch) + { + if (!batch.Any()) + { + return; + } + + var group = batch.GroupBy(s => s.Level); + foreach (var v in group) + { + switch (v.Key) + { + case LogEventLevel.Information: + await WriteInformationLog(db, v); + break; + case LogEventLevel.Warning: + await WriteWarningLog(db, v); + break; + case LogEventLevel.Error: + case LogEventLevel.Fatal: + await WriteErrorLog(db, v); + break; + } + } + } + + private async Task WriteInformationLog(ISqlSugarClient db, IEnumerable batch) + { + if (!batch.Any()) + { + return; + } + + var logs = new List(); + foreach (var logEvent in batch) + { + var log = logEvent.Adapt(); + log.Message = logEvent.RenderMessage(); + log.Properties = logEvent.Properties.ToJson(); + log.DateTime = logEvent.Timestamp.DateTime; + logs.Add(log); + } + + await db.AsTenant().InsertableWithAttr(logs).SplitTable().ExecuteReturnSnowflakeIdAsync(); + } + + private async Task WriteWarningLog(ISqlSugarClient db, IEnumerable batch) + { + if (!batch.Any()) + { + return; + } + + var logs = new List(); + foreach (var logEvent in batch) + { + var log = logEvent.Adapt(); + log.Message = logEvent.RenderMessage(); + log.Properties = logEvent.Properties.ToJson(); + log.DateTime = logEvent.Timestamp.DateTime; + logs.Add(log); + } + + await db.AsTenant().InsertableWithAttr(logs).SplitTable().ExecuteReturnSnowflakeIdAsync(); + } + + private async Task WriteErrorLog(ISqlSugarClient db, IEnumerable batch) + { + if (!batch.Any()) + { + return; + } + + var logs = new List(); + foreach (var logEvent in batch) + { + var log = logEvent.Adapt(); + log.Message = logEvent.RenderMessage(); + log.Properties = logEvent.Properties.ToJson(); + log.DateTime = logEvent.Timestamp.DateTime; + logs.Add(log); + } + + await db.AsTenant().InsertableWithAttr(logs).SplitTable().ExecuteReturnSnowflakeIdAsync(); + } + + private async Task WriteSqlLog(ISqlSugarClient db, IEnumerable batch) + { + if (!batch.Any()) + { + return; + } + + var logs = new List(); + foreach (var logEvent in batch) + { + var log = logEvent.Adapt(); + log.Message = logEvent.RenderMessage(); + log.Properties = logEvent.Properties.ToJson(); + log.DateTime = logEvent.Timestamp.DateTime; + logs.Add(log); + } + + await db.AsTenant().InsertableWithAttr(logs).SplitTable().ExecuteReturnSnowflakeIdAsync(); + } + + #endregion +} \ No newline at end of file diff --git a/Blog.Core.Serilog/Utility/SerilogRequestUtility.cs b/Blog.Core.Serilog/Utility/SerilogRequestUtility.cs new file mode 100644 index 00000000..888d2dc7 --- /dev/null +++ b/Blog.Core.Serilog/Utility/SerilogRequestUtility.cs @@ -0,0 +1,73 @@ +using Blog.Core.Common.Extensions; +using Blog.Core.Common.Https; +using Microsoft.AspNetCore.Http; +using Serilog; +using Serilog.Events; + +namespace Blog.Core.Serilog.Utility; + +public class SerilogRequestUtility +{ + public const string HttpMessageTemplate = + "HTTP {RequestMethod} {RequestPath} QueryString:{QueryString} Body:{Body} responded {StatusCode} in {Elapsed:0.0000} ms"; + + private static readonly List _ignoreUrl = new() + { + "/job", + }; + + private static LogEventLevel DefaultGetLevel(HttpContext ctx, + double _, + Exception? ex) + { + return ex is null && ctx.Response.StatusCode <= 499 ? LogEventLevel.Information : LogEventLevel.Error; + } + + public static LogEventLevel GetRequestLevel(HttpContext ctx, double _, Exception? ex) => + ex is null && ctx.Response.StatusCode <= 499 ? IgnoreRequest(ctx) : LogEventLevel.Error; + + private static LogEventLevel IgnoreRequest(HttpContext ctx) + { + var path = ctx.Request.Path.Value; + if (path.IsNullOrEmpty()) + { + return LogEventLevel.Information; + } + + return _ignoreUrl.Any(s => path.StartsWith(s)) ? LogEventLevel.Verbose : LogEventLevel.Information; + } + + /// + /// 从Request中增加附属属性 + /// + /// + /// + public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext) + { + var request = httpContext.Request; + + diagnosticContext.Set("RequestHost", request.Host); + diagnosticContext.Set("RequestScheme", request.Scheme); + diagnosticContext.Set("Protocol", request.Protocol); + diagnosticContext.Set("RequestIp", httpContext.GetRequestIp()); + + if (request.Method == HttpMethods.Get) + { + diagnosticContext.Set("QueryString", request.QueryString.HasValue ? request.QueryString.Value : string.Empty); + diagnosticContext.Set("Body", string.Empty); + } + else + { + diagnosticContext.Set("QueryString", request.QueryString.HasValue ? request.QueryString.Value : string.Empty); + diagnosticContext.Set("Body", request.ContentLength > 0 ? request.GetRequestBody() : string.Empty); + } + + diagnosticContext.Set("ContentType", httpContext.Response.ContentType); + + var endpoint = httpContext.GetEndpoint(); + if (endpoint != null) + { + diagnosticContext.Set("EndpointName", endpoint.DisplayName); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Services/AdvertisementServices.cs b/Blog.Core.Services/AdvertisementServices.cs index c95f4142..348048e8 100644 --- a/Blog.Core.Services/AdvertisementServices.cs +++ b/Blog.Core.Services/AdvertisementServices.cs @@ -13,7 +13,7 @@ public void ReturnExp() int a = 1; int b = 0; - int c = a / b; + // int c = a / b; } //public IAdvertisementRepository dal = new AdvertisementRepository(); diff --git a/Blog.Core.Services/BASE/BaseServices.cs b/Blog.Core.Services/BASE/BaseServices.cs index ec685be2..44042382 100644 --- a/Blog.Core.Services/BASE/BaseServices.cs +++ b/Blog.Core.Services/BASE/BaseServices.cs @@ -17,13 +17,17 @@ public BaseServices(IBaseRepository BaseDal = null) { this.BaseDal = BaseDal; } + //public IBaseRepository baseDal = new BaseRepository(); - public IBaseRepository BaseDal { get; set; }//通过在子类的构造函数中注入,这里是基类,不用构造函数 + public IBaseRepository BaseDal { get; set; } //通过在子类的构造函数中注入,这里是基类,不用构造函数 + + public ISqlSugarClient Db => BaseDal.Db; public async Task QueryById(object objId) { return await BaseDal.QueryById(objId); } + /// /// 功能描述:根据ID查询一条数据 /// 作  者:AZLinli.Blog.Core @@ -52,7 +56,7 @@ public async Task> QueryByIDs(object[] lstIds) /// /// 博文实体类 /// - public async Task Add(TEntity entity) + public async Task Add(TEntity entity) { return await BaseDal.Add(entity); } @@ -62,7 +66,7 @@ public async Task Add(TEntity entity) /// /// 实体集合 /// 影响行数 - public async Task Add(List listEntity) + public async Task> Add(List listEntity) { return await BaseDal.Add(listEntity); } @@ -76,21 +80,32 @@ public async Task Update(TEntity entity) { return await BaseDal.Update(entity); } + /// + /// 更新实体数据 + /// + /// 博文实体类 + /// + public async Task Update(List entity) + { + return await BaseDal.Update(entity); + } + public async Task Update(TEntity entity, string where) { return await BaseDal.Update(entity, where); } + public async Task Update(object operateAnonymousObjects) { return await BaseDal.Update(operateAnonymousObjects); } public async Task Update( - TEntity entity, - List lstColumns = null, - List lstIgnoreColumns = null, - string where = "" - ) + TEntity entity, + List lstColumns = null, + List lstIgnoreColumns = null, + string where = "" + ) { return await BaseDal.Update(entity, lstColumns, lstIgnoreColumns, where); } @@ -127,7 +142,6 @@ public async Task DeleteByIds(object[] ids) } - /// /// 功能描述:查询所有数据 /// 作  者:AZLinli.Blog.Core @@ -181,7 +195,7 @@ public async Task> Query(Expression查询实体条件 /// 排序条件 /// - public async Task> Query(Expression> expression, Expression> whereExpression,string orderByFileds) + public async Task> Query(Expression> expression, Expression> whereExpression, string orderByFileds) { return await BaseDal.Query(expression, whereExpression, orderByFileds); } @@ -224,7 +238,6 @@ public async Task> Query(string where, string orderByFileds) public async Task> QuerySql(string sql, SugarParameter[] parameters = null) { return await BaseDal.QuerySql(sql, parameters); - } /// @@ -236,8 +249,8 @@ public async Task> QuerySql(string sql, SugarParameter[] parameter public async Task QueryTable(string sql, SugarParameter[] parameters = null) { return await BaseDal.QueryTable(sql, parameters); - } + /// /// 功能描述:查询前N条数据 /// 作  者:AZLinli.Blog.Core @@ -283,10 +296,10 @@ public async Task> Query( string orderByFileds) { return await BaseDal.Query( - whereExpression, - pageIndex, - pageSize, - orderByFileds); + whereExpression, + pageIndex, + pageSize, + orderByFileds); } /// @@ -299,34 +312,70 @@ public async Task> Query( /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( - string where, - int pageIndex, - int pageSize, - string orderByFileds) + string where, + int pageIndex, + int pageSize, + string orderByFileds) { return await BaseDal.Query( - where, - pageIndex, - pageSize, - orderByFileds); + where, + pageIndex, + pageSize, + orderByFileds); } public async Task> QueryPage(Expression> whereExpression, - int pageIndex = 1, int pageSize = 20, string orderByFileds = null) + int pageIndex = 1, int pageSize = 20, string orderByFileds = null) { return await BaseDal.QueryPage(whereExpression, - pageIndex, pageSize, orderByFileds); + pageIndex, pageSize, orderByFileds); } public async Task> QueryMuch(Expression> joinExpression, Expression> selectExpression, Expression> whereLambda = null) where T : class, new() { return await BaseDal.QueryMuch(joinExpression, selectExpression, whereLambda); } + public async Task> QueryPage(PaginationModel pagination) { var express = DynamicLinqFactory.CreateLambda(pagination.Conditions); return await QueryPage(express, pagination.PageIndex, pagination.PageSize, pagination.OrderByFileds); } - } -} + #region 分表 + + public async Task> AddSplit(TEntity entity) + { + return await BaseDal.AddSplit(entity); + } + + public async Task UpdateSplit(TEntity entity, DateTime dateTime) + { + return await BaseDal.UpdateSplit(entity, dateTime); + } + + /// + /// 根据实体删除一条数据 + /// + /// 博文实体类 + /// + public async Task DeleteSplit(TEntity entity, DateTime dateTime) + { + return await BaseDal.DeleteSplit(entity, dateTime); + } + + public async Task QueryByIdSplit(object objId) + { + return await BaseDal.QueryByIdSplit(objId); + } + + public async Task> QueryPageSplit(Expression> whereExpression, DateTime beginTime, DateTime endTime, + int pageIndex = 1, int pageSize = 20, string orderByFields = null) + { + return await BaseDal.QueryPageSplit(whereExpression, beginTime, endTime, + pageIndex, pageSize, orderByFields); + } + + #endregion + } +} \ No newline at end of file diff --git a/Blog.Core.Services/Blog.Core.Services.csproj b/Blog.Core.Services/Blog.Core.Services.csproj index ae5e969a..215dde29 100644 --- a/Blog.Core.Services/Blog.Core.Services.csproj +++ b/Blog.Core.Services/Blog.Core.Services.csproj @@ -1,8 +1,5 @@  - - net6.0 - ..\Blog.Core.Api\bin\Debug\ diff --git a/Blog.Core.Services/BlogArticleServices.cs b/Blog.Core.Services/BlogArticleServices.cs index eeff04e3..67ea2b9e 100644 --- a/Blog.Core.Services/BlogArticleServices.cs +++ b/Blog.Core.Services/BlogArticleServices.cs @@ -23,7 +23,7 @@ public BlogArticleServices(IMapper mapper) /// /// /// - public async Task GetBlogDetails(int id) + public async Task GetBlogDetails(long id) { // 此处想获取上一条下一条数据,因此将全部数据list出来,有好的想法请提出 //var bloglist = await base.Query(a => a.IsDeleted==false, a => a.bID); diff --git a/Blog.Core.Services/GuestbookServices.cs b/Blog.Core.Services/GuestbookServices.cs index daff1cee..33364a51 100644 --- a/Blog.Core.Services/GuestbookServices.cs +++ b/Blog.Core.Services/GuestbookServices.cs @@ -7,7 +7,9 @@ using System; using System.Threading.Tasks; using Blog.Core.Common.DB; +using Blog.Core.Common.Utility; using Blog.Core.Repository.UnitOfWorks; +using SqlSugar; namespace Blog.Core.Services { @@ -15,14 +17,17 @@ public class GuestbookServices : BaseServices, IGuestbookServices { private readonly IUnitOfWorkManage _unitOfWorkManage; private readonly IBaseRepository _passwordLibRepository; - private readonly IPasswordLibServices _passwordLibServices; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; - public GuestbookServices(IUnitOfWorkManage unitOfWorkManage, IBaseRepository dal, IBaseRepository passwordLibRepository, IPasswordLibServices passwordLibServices) + public GuestbookServices(IUnitOfWorkManage unitOfWorkManage, IBaseRepository dal, + IBaseRepository passwordLibRepository, IPasswordLibServices passwordLibServices, ISqlSugarClient db) { _unitOfWorkManage = unitOfWorkManage; _passwordLibRepository = passwordLibRepository; _passwordLibServices = passwordLibServices; + _db = db; } public async Task> TestTranInRepository() @@ -193,8 +198,9 @@ public async Task TestTranPropagationNoTran() public async Task TestTranPropagationTran() { var guestbooks = await base.Query(); + guestbooks = await base.Query(); Console.WriteLine($"first time : the count of guestbooks is :{guestbooks.Count}"); - + Console.WriteLine(base.Db.ContextID); var insertGuestbook = await base.Add(new Guestbook() { username = "bbb", @@ -207,5 +213,68 @@ public async Task TestTranPropagationTran() return true; } + + [UseTran(Propagation = Propagation.Required)] + public async Task TestTranPropagationTran2() + { + await Db.Insertable(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }).ExecuteReturnSnowflakeIdAsync(); + + + await Db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + await _passwordLibServices.TestTranPropagationTran2(); + + Console.WriteLine("完成"); + } + + public async Task TestTranPropagationTran3() + { + try + { + Console.WriteLine("Begin Transaction Before:" + db.ContextID); + db.BeginTran(); + Console.WriteLine("Begin Transaction After:" + db.ContextID); + + await db.Insertable(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }).ExecuteReturnSnowflakeIdAsync(); + + + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + await _passwordLibServices.TestTranPropagationTran3(); + + db.CommitTran(); + Console.WriteLine("完成"); + } + catch (Exception e) + { + db.RollbackTran(); + throw; + } + + } } } \ No newline at end of file diff --git a/Blog.Core.Services/IDS4Db/ApplicationUserServices.cs b/Blog.Core.Services/IDS4Db/ApplicationUserServices.cs index 1cda7d36..bfaf5dfc 100644 --- a/Blog.Core.Services/IDS4Db/ApplicationUserServices.cs +++ b/Blog.Core.Services/IDS4Db/ApplicationUserServices.cs @@ -1,4 +1,7 @@ -using Blog.Core.IRepository.Base; +using System.Threading.Tasks; +using Blog.Core.Common.DB; +using Blog.Core.Common.DB.Extension; +using Blog.Core.IRepository.Base; using Blog.Core.Model.IDS4DbModels; using Blog.Core.Services.BASE; @@ -6,6 +9,10 @@ namespace Blog.Core.IServices { public class ApplicationUserServices : BaseServices, IApplicationUserServices { - + public bool IsEnable() + { + var configId = typeof(ApplicationUser).GetEntityTenant(); + return Db.AsTenant().IsAnyConnection(configId); + } } } \ No newline at end of file diff --git a/Blog.Core.Services/PasswordLibServices.cs b/Blog.Core.Services/PasswordLibServices.cs index de58a6b6..c2f39391 100644 --- a/Blog.Core.Services/PasswordLibServices.cs +++ b/Blog.Core.Services/PasswordLibServices.cs @@ -2,20 +2,28 @@ using System.Threading.Tasks; using Blog.Core.Common; using Blog.Core.Common.DB; +using Blog.Core.Common.Utility; using Blog.Core.IRepository.Base; using Blog.Core.IServices; using Blog.Core.Model.Models; +using Blog.Core.Repository.UnitOfWorks; using Blog.Core.Services.BASE; +using SqlSugar; namespace Blog.Core.Services { public partial class PasswordLibServices : BaseServices, IPasswordLibServices { IBaseRepository _dal; + private readonly IUnitOfWorkManage _unitOfWorkManage; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; - public PasswordLibServices(IBaseRepository dal) + public PasswordLibServices(IBaseRepository dal, IUnitOfWorkManage unitOfWorkManage, ISqlSugarClient db) { this._dal = dal; + _unitOfWorkManage = unitOfWorkManage; + _db = db; base.BaseDal = dal; } @@ -24,6 +32,7 @@ public async Task TestTranPropagation2() { await _dal.Add(new PasswordLib() { + PLID = IdGeneratorUtility.NextId(), IsDeleted = false, plAccountName = "aaa", plCreateTime = DateTime.Now @@ -48,13 +57,33 @@ await _dal.Add(new PasswordLib() [UseTran(Propagation = Propagation.Nested)] public async Task TestTranPropagationTran2() { - await _dal.Add(new PasswordLib() + await db.Insertable(new PasswordLib() { + PLID = IdGeneratorUtility.NextId(), IsDeleted = false, plAccountName = "aaa", plCreateTime = DateTime.Now - }); + }).ExecuteReturnSnowflakeIdAsync(); + + //throw new Exception("测试嵌套事务异常回滚"); + return true; + } + + public async Task TestTranPropagationTran3() + { + Console.WriteLine("Begin Transaction Before:" + db.ContextID); + db.BeginTran(); + Console.WriteLine("Begin Transaction After:" + db.ContextID); + Console.WriteLine(""); + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + //throw new Exception("测试嵌套事务异常回滚"); return true; } } diff --git a/Blog.Core.Services/RoleModulePermissionServices.cs b/Blog.Core.Services/RoleModulePermissionServices.cs index c0248e17..d3834f89 100644 --- a/Blog.Core.Services/RoleModulePermissionServices.cs +++ b/Blog.Core.Services/RoleModulePermissionServices.cs @@ -83,7 +83,7 @@ public async Task> GetRMPMaps() /// 菜单主键 /// 接口主键 /// - public async Task UpdateModuleId(int permissionId, int moduleId) + public async Task UpdateModuleId(long permissionId, long moduleId) { await _dal.UpdateModuleId(permissionId, moduleId); } diff --git a/Blog.Core.Services/SplitDemoServices.cs b/Blog.Core.Services/SplitDemoServices.cs new file mode 100644 index 00000000..cf8e2cc1 --- /dev/null +++ b/Blog.Core.Services/SplitDemoServices.cs @@ -0,0 +1,23 @@ +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; +using System.Linq; +using System.Threading.Tasks; + +namespace Blog.Core.FrameWork.Services +{ + /// + /// sysUserInfoServices + /// + public class SplitDemoServices : BaseServices, ISplitDemoServices + { + private readonly IBaseRepository _splitDemoRepository; + public SplitDemoServices(IBaseRepository splitDemoRepository) + { + _splitDemoRepository = splitDemoRepository; + } + + + } +} diff --git a/Blog.Core.Services/TasksLogServices.cs b/Blog.Core.Services/TasksLogServices.cs new file mode 100644 index 00000000..07d95f1f --- /dev/null +++ b/Blog.Core.Services/TasksLogServices.cs @@ -0,0 +1,137 @@ +using System.Linq.Expressions; +using System; +using System.Threading.Tasks; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; +using Blog.Core.Common.Extensions; +using SqlSugar; +using Blog.Core.Model; +using System.Collections.Generic; +using System.Linq; + +namespace Blog.Core.Services +{ + public partial class TasksLogServices : BaseServices, ITasksLogServices + { + public async Task> GetTaskLogs(long jobId, int page, int intPageSize, DateTime? runTime, DateTime? endTime) + { + RefAsync totalCount = 0; + Expression> whereExpression = log => true; + if (jobId > 0) whereExpression = whereExpression.And(log => log.JobId == jobId); + var data = await this.Db.Queryable() + .LeftJoin((log, qz) => log.JobId == qz.Id) + .OrderByDescending((log) => log.RunTime) + .WhereIF(jobId > 0, (log) => log.JobId == jobId) + .WhereIF(runTime != null, (log) => log.RunTime >= runTime.Value) + .WhereIF(endTime != null, (log) => log.RunTime <= endTime.Value) + .Select((log, qz) => new TasksLog + { + RunPars = log.RunPars, + RunResult = log.RunResult, + RunTime = log.RunTime, + EndTime = log.EndTime, + ErrMessage = log.ErrMessage, + ErrStackTrace = log.ErrStackTrace, + TotalTime = log.TotalTime, + Name = qz.Name, + JobGroup = qz.JobGroup + + }) + .ToPageListAsync(page, intPageSize, totalCount); + return new PageModel(page, totalCount, intPageSize, data); + } + public async Task GetTaskOverview(long jobId, DateTime? runTime, DateTime? endTime, string type) + { + //按年 + if ("year".Equals(type)) + { + + var days = endTime.Value.Year - runTime.Value.Year; + var dayArray = new List(); + while (days >= 0) + { + dayArray.Add(new DateTime(runTime.Value.Year + days, 1, 1)); + days--; + } + var queryableLeft = this.Db.Reportable(dayArray).ToQueryable(); + var queryableRight = this.Db.Queryable().Where((x) => x.RunTime.Year >= runTime.Value.Year && x.RunTime.Year <= endTime.Value.Year); ; ; //声名表 + + var list = this.Db.Queryable(queryableLeft, queryableRight, JoinType.Left, + (x1, x2) => x1.ColumnName.Year == x2.RunTime.Year) + .GroupBy((x1, x2) => x1.ColumnName) + .Select((x1, x2) => new + { + 执行次数 = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Id > 0, 1, 0)), + date = x1.ColumnName.Year.ToString() + "年" + }).ToList().OrderBy(t => t.date); + return list; + } + else if ("month".Equals(type)) + { + //按月 + var queryableLeft = this.Db.Reportable(ReportableDateType.MonthsInLast1years).ToQueryable(); //生成月份 //ReportableDateType.MonthsInLast1yea 表式近一年月份 并且queryable之后还能在where过滤 + var queryableRight = this.Db.Queryable().Where((x) => x.RunTime.Year == runTime.Value.Year); //声名表 + + //月份和表JOIN + var list = queryableLeft + .LeftJoin(queryableRight, (x1, x2) => x2.RunTime.ToString("MM月") == x1.ColumnName.ToString("MM月")) + + .GroupBy((x1, x2) => x1.ColumnName) + .Select((x1, x2) => new + { + //null的数据要为0所以不能用count + 执行次数 = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Id > 0, 1, 0)), + date = x1.ColumnName.ToString("MM月") + } + ).ToList().OrderBy(t => t.date); + await Task.CompletedTask; + return list; + } + else if ("day".Equals(type)) + { + //按日 + var time = runTime.Value; + var days = DateTime.DaysInMonth(time.Year, time.Month); + var dayArray = Enumerable.Range(1, days).Select(it => Convert.ToDateTime(time.ToString("yyyy-MM-" + it))).ToList();//转成时间数组 + var queryableLeft = this.Db.Reportable(dayArray).ToQueryable(); + var star = Convert.ToDateTime(runTime.Value.ToString("yyyy-MM-01 00:00:00")); + var end = Convert.ToDateTime(runTime.Value.ToString($"yyyy-MM-{days} 23:59:59")); + var queryableRight = this.Db.Queryable().Where((x) => x.RunTime >= star && x.RunTime <= end); ; ; //声名表 + + var list = this.Db.Queryable(queryableLeft, queryableRight, JoinType.Left, + (x1, x2) => x1.ColumnName.Date == x2.RunTime.Date) + .GroupBy((x1, x2) => x1.ColumnName) + .Select((x1, x2) => new + { + 执行次数 = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Id > 0, 1, 0)), + date = x1.ColumnName.Day + }).ToList().OrderBy(t => t.date); + await Task.CompletedTask; + return list; + } + else if ("hour".Equals(type)) + { + //按小时 + var time = runTime.Value; + var days = 24; + var dayArray = Enumerable.Range(0, days).Select(it => Convert.ToDateTime(time.ToString($"yyyy-MM-dd {it.ToString().PadLeft(2, '0')}:00:00"))).ToList();//转成时间数组 + var queryableLeft = this.Db.Reportable(dayArray).ToQueryable(); + var queryableRight = this.Db.Queryable().Where((x) => x.RunTime >= runTime.Value.Date && x.RunTime <= runTime.Value.Date.AddDays(1).AddMilliseconds(-1)); //声名表 + + var list = this.Db.Queryable(queryableLeft, queryableRight, JoinType.Left, + (x1, x2) => x1.ColumnName.Hour == x2.RunTime.Hour) + .GroupBy((x1, x2) => x1.ColumnName) + .Select((x1, x2) => new + { + 执行次数 = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Id > 0, 1, 0)), + date = x1.ColumnName.Hour + }).ToList().OrderBy(t => t.date); + await Task.CompletedTask; + return list; + } + await Task.CompletedTask; + return null; + } + } +} diff --git a/Blog.Core.Services/TenantService.cs b/Blog.Core.Services/TenantService.cs new file mode 100644 index 00000000..a552442b --- /dev/null +++ b/Blog.Core.Services/TenantService.cs @@ -0,0 +1,57 @@ +using Blog.Core.Common.DB; +using Blog.Core.Common.Seed; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Repository.UnitOfWorks; +using Blog.Core.Services.BASE; +using System.Threading.Tasks; + +namespace Blog.Core.Services; + +public class TenantService : BaseServices, ITenantService +{ + private readonly IUnitOfWorkManage _uowManager; + + public TenantService(IUnitOfWorkManage uowManage) + { + this._uowManager = uowManage; + } + + + public async Task SaveTenant(SysTenant tenant) + { + bool initDb = tenant.Id == 0; + using (var uow = _uowManager.CreateUnitOfWork()) + { + + tenant.DefaultTenantConfig(); + + if (tenant.Id == 0) + { + await Db.Insertable(tenant).ExecuteReturnSnowflakeIdAsync(); + } + else + { + var oldTenant = await QueryById(tenant.Id); + if (oldTenant.Connection != tenant.Connection) + { + initDb = true; + } + + await Db.Updateable(tenant).ExecuteCommandAsync(); + } + + uow.Commit(); + } + + if (initDb) + { + await InitTenantDb(tenant); + } + } + + public async Task InitTenantDb(SysTenant tenant) + { + await DBSeed.InitTenantSeedAsync(Db.AsTenant(), tenant.GetConnectionConfig()); + } +} \ No newline at end of file diff --git a/Blog.Core.Services/UserRoleServices.cs b/Blog.Core.Services/UserRoleServices.cs index 38f524da..26194837 100644 --- a/Blog.Core.Services/UserRoleServices.cs +++ b/Blog.Core.Services/UserRoleServices.cs @@ -19,7 +19,7 @@ public class UserRoleServices : BaseServices, IUserRoleServices /// /// /// - public async Task SaveUserRole(int uid, int rid) + public async Task SaveUserRole(long uid, long rid) { UserRole userRole = new UserRole(uid, rid); @@ -42,7 +42,7 @@ public async Task SaveUserRole(int uid, int rid) [Caching(AbsoluteExpiration = 30)] - public async Task GetRoleIdByUid(int uid) + public async Task GetRoleIdByUid(long uid) { return ((await base.Query(d => d.UserId == uid)).OrderByDescending(d => d.Id).LastOrDefault()?.RoleId).ObjToInt(); } diff --git a/Blog.Core.Tasks/Blog.Core.Tasks.csproj b/Blog.Core.Tasks/Blog.Core.Tasks.csproj index 5e173559..d93eb8bd 100644 --- a/Blog.Core.Tasks/Blog.Core.Tasks.csproj +++ b/Blog.Core.Tasks/Blog.Core.Tasks.csproj @@ -1,16 +1,14 @@  - - net6.0 - - + + diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs b/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs index 4a71fbf5..8da86165 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs @@ -1,5 +1,6 @@ using Blog.Core.Common.Helper; using Blog.Core.IServices; +using Blog.Core.Model.Models; using Quartz; using System; using System.Diagnostics; @@ -10,6 +11,12 @@ namespace Blog.Core.Tasks public class JobBase { public ITasksQzServices _tasksQzServices; + public ITasksLogServices _tasksLogServices; + public JobBase(ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + { + _tasksQzServices = tasksQzServices; + _tasksLogServices = tasksLogServices; + } /// /// 执行指定任务 /// @@ -17,40 +24,50 @@ public class JobBase /// public async Task ExecuteJob(IJobExecutionContext context, Func func) { - //记录Job时间 - Stopwatch stopwatch = new Stopwatch(); + //记录Job + TasksLog tasksLog = new TasksLog(); //JOBID - int jobid = context.JobDetail.Key.Name.ObjToInt(); + long jobid = context.JobDetail.Key.Name.ObjToLong(); //JOB组名 string groupName = context.JobDetail.Key.Group; //日志 - string jobHistory = $"【{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}】【执行开始】【Id:{jobid},组别:{groupName}】"; - //耗时 - double taskSeconds = 0; + tasksLog.JobId = jobid; + tasksLog.RunTime = DateTime.Now; + string jobHistory = $"【{tasksLog.RunTime.ToString("yyyy-MM-dd HH:mm:ss")}】【执行开始】【Id:{jobid},组别:{groupName}】"; try { - stopwatch.Start(); await func();//执行任务 - stopwatch.Stop(); - jobHistory += $",【{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}】【执行成功】"; + tasksLog.EndTime = DateTime.Now; + tasksLog.RunResult = true; + jobHistory += $",【{tasksLog.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}】【执行成功】"; + + JobDataMap jobPars = context.JobDetail.JobDataMap; + tasksLog.RunPars = jobPars.GetString("JobParam"); } catch (Exception ex) { - JobExecutionException e2 = new JobExecutionException(ex); + tasksLog.EndTime = DateTime.Now; + tasksLog.RunResult = false; + //JobExecutionException e2 = new JobExecutionException(ex); //true 是立即重新执行任务 - e2.RefireImmediately = true; - jobHistory += $",【{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}】【执行失败:{ex.Message}】"; + //e2.RefireImmediately = true; + tasksLog.ErrMessage = ex.Message; + tasksLog.ErrStackTrace = ex.StackTrace; + jobHistory += $",【{tasksLog.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}】【执行失败:{ex.Message}】"; } finally { - taskSeconds = Math.Round(stopwatch.Elapsed.TotalSeconds, 3); - jobHistory += $",【{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}】【执行结束】(耗时:{taskSeconds}秒)"; + tasksLog.TotalTime = Math.Round((tasksLog.EndTime - tasksLog.RunTime).TotalSeconds,3); + jobHistory += $"(耗时:{tasksLog.TotalTime}秒)"; if (_tasksQzServices != null) { var model = await _tasksQzServices.QueryById(jobid); if (model != null) { + if(_tasksLogServices != null) await _tasksLogServices.Add(tasksLog); model.RunTimes += 1; + if (model.TriggerType == 0) model.CycleHasRunTimes += 1; + if (model.TriggerType == 0 && model.CycleRunTimes != 0 && model.CycleHasRunTimes >= model.CycleRunTimes) model.IsStart = false;//循环完善,当循环任务完成后,停止该任务,防止下次启动再次执行 var separator = "
"; // 这里注意数据库字段的长度问题,超过限制,会造成数据库remark不更新问题。 model.Remark = @@ -60,7 +77,7 @@ public async Task ExecuteJob(IJobExecutionContext context, Func fu } } - Console.Out.WriteLine(jobHistory); + //Console.Out.WriteLine(jobHistory); return jobHistory; } } diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs b/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs index 743180c3..88b79e8b 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; /// /// 这里要注意下,命名空间和程序集是一样的,不然反射不到 @@ -20,65 +21,41 @@ public class Job_AccessTrendLog_Quartz : JobBase, IJob { private readonly IAccessTrendLogServices _accessTrendLogServices; private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; - public Job_AccessTrendLog_Quartz(IAccessTrendLogServices accessTrendLogServices, ITasksQzServices tasksQzServices, IWebHostEnvironment environment) + public Job_AccessTrendLog_Quartz(IAccessTrendLogServices accessTrendLogServices, + IWebHostEnvironment environment, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices, + ILogger logger) + : base(tasksQzServices, tasksLogServices) { _accessTrendLogServices = accessTrendLogServices; - _environment = environment; - _tasksQzServices = tasksQzServices; + _environment = environment; + _logger = logger; + _tasksQzServices = tasksQzServices; } + public async Task Execute(IJobExecutionContext context) { var executeLog = await ExecuteJob(context, async () => await Run(context)); } + public async Task Run(IJobExecutionContext context) { - // 可以直接获取 JobDetail 的值 var jobKey = context.JobDetail.Key; - var jobId = jobKey.Name; + var jobId = jobKey.Name; // 也可以通过数据库配置,获取传递过来的参数 JobDataMap data = context.JobDetail.JobDataMap; - var lastestLogDatetime = (await _accessTrendLogServices.Query(null, d => d.UpdateTime, false)).FirstOrDefault()?.UpdateTime; + var lastestLogDatetime = (await _accessTrendLogServices.Query(null, d => d.UpdateTime, false)) + .FirstOrDefault()?.UpdateTime; if (lastestLogDatetime == null) { lastestLogDatetime = Convert.ToDateTime("2021-09-01"); } - var accLogs = GetAccessLogs().Where(d => d.User != "" && d.BeginTime.ObjToDate() >= lastestLogDatetime).ToList(); - var logUpdate = DateTime.Now; - - var activeUsers = (from n in accLogs - group n by new { n.User } into g - select new ActiveUserVM - { - user = g.Key.User, - count = g.Count(), - }).ToList(); - - foreach (var item in activeUsers) - { - var user = (await _accessTrendLogServices.Query(d => d.User != "" && d.User == item.user)).FirstOrDefault(); - if (user != null) - { - user.Count += item.count; - user.UpdateTime = logUpdate; - await _accessTrendLogServices.Update(user); - } - else - { - await _accessTrendLogServices.Add(new AccessTrendLog() - { - Count = item.count, - UpdateTime = logUpdate, - User = item.user - }); - } - } - // 重新拉取 - var actUsers = await _accessTrendLogServices.Query(d => d.User != "", d => d.Count, false); + var actUsers = await _accessTrendLogServices.Query(d => d.UserInfo != "", d => d.Count, false); actUsers = actUsers.Take(15).ToList(); List activeUserVMs = new(); @@ -86,61 +63,13 @@ await _accessTrendLogServices.Add(new AccessTrendLog() { activeUserVMs.Add(new ActiveUserVM() { - user = item.User, + user = item.UserInfo, count = item.Count }); } - Parallel.For(0, 1, e => - { - LogLock.OutLogAOP("ACCESSTRENDLOG","",new string[] { activeUserVMs.GetType().ToString(), JsonConvert.SerializeObject(activeUserVMs) }, false); - }); - } - - private List GetAccessLogs() - { - List userAccessModels = new(); - var accessLogs = LogLock.ReadLog( - Path.Combine(_environment.ContentRootPath, "Log"), "RecordAccessLogs_", Encoding.UTF8, ReadType.Prefix, 2 - ).ObjToString().TrimEnd(','); - - try - { - return JsonConvert.DeserializeObject>("[" + accessLogs + "]"); - } - catch (Exception) - { - var accLogArr = accessLogs.Split("\n"); - foreach (var item in accLogArr) - { - if (item.ObjToString() != "") - { - try - { - var accItem = JsonConvert.DeserializeObject(item.TrimEnd(',')); - userAccessModels.Add(accItem); - } - catch (Exception) - { - } - } - } - - } - - return userAccessModels; + _logger.LogInformation("Job_AccessTrendLog_Quartz: {ActiveUserVMs}", + JsonConvert.SerializeObject(activeUserVMs)); } - } - public class UserAccessFromFIles - { - public string User { get; set; } - public string IP { get; set; } - public string API { get; set; } - public string BeginTime { get; set; } - public string OPTime { get; set; } - public string RequestMethod { get; set; } = ""; - public string Agent { get; set; } - } - -} +} \ No newline at end of file diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs b/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs index 173e996e..6d1276c8 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs @@ -1,31 +1,101 @@ using Blog.Core.IServices; using Quartz; +using System; using System.Threading.Tasks; +using Blog.Core.Common.Utility; +using Blog.Core.Model.Models; +using Blog.Core.Repository.UnitOfWorks; +using SqlSugar; /// /// 这里要注意下,命名空间和程序集是一样的,不然反射不到 /// namespace Blog.Core.Tasks { + [DisallowConcurrentExecution] public class Job_Blogs_Quartz : JobBase, IJob { private readonly IBlogArticleServices _blogArticleServices; + private readonly IGuestbookServices _guestbookServices; + private readonly IUnitOfWorkManage _uowm; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; - public Job_Blogs_Quartz(IBlogArticleServices blogArticleServices, ITasksQzServices tasksQzServices) + public Job_Blogs_Quartz(IBlogArticleServices blogArticleServices, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices, + IGuestbookServices guestbookServices, IUnitOfWorkManage uowm, ISqlSugarClient db) + : base(tasksQzServices, tasksLogServices) { _blogArticleServices = blogArticleServices; - _tasksQzServices = tasksQzServices; + _guestbookServices = guestbookServices; + _uowm = uowm; + this._db = db; } + + /// + /// 直接写就没有锁库 上下文ContextID一样 + /// + /// + public async Task Execute2(IJobExecutionContext context) + { + try + { + db.BeginTran(); + Console.WriteLine(_uowm.GetDbClient().ContextID); + await db.Insertable(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }).ExecuteReturnSnowflakeIdAsync(); + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + db.BeginTran(); + Console.WriteLine(db.ContextID); + await db.Insertable(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }).ExecuteReturnSnowflakeIdAsync(); + + db.CommitTran(); + + Console.WriteLine(db.ContextID); + db.CommitTran(); + Console.WriteLine("完成"); + } + catch (Exception e) + { + db.RollbackTran(); + } + } + + /// + /// 但是调用其他类方法 上下文ContextID就不一样 + /// + /// public async Task Execute(IJobExecutionContext context) { var executeLog = await ExecuteJob(context, async () => await Run(context)); } + public async Task Run(IJobExecutionContext context) { + System.Console.WriteLine($"Job_Blogs_Quartz 执行 {DateTime.Now.ToShortTimeString()}"); var list = await _blogArticleServices.Query(); // 也可以通过数据库配置,获取传递过来的参数 JobDataMap data = context.JobDetail.JobDataMap; //int jobId = data.GetInt("JobParam"); + + await _guestbookServices.TestTranPropagationTran2(); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs b/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs index 8e9b3847..49e95847 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs @@ -17,68 +17,69 @@ namespace Blog.Core.Tasks { public class Job_OperateLog_Quartz : JobBase, IJob { - private readonly IOperateLogServices _operateLogServices; + private readonly IOperateLogServices _operateLogServices; private readonly IWebHostEnvironment _environment; - public Job_OperateLog_Quartz(IOperateLogServices operateLogServices, ITasksQzServices tasksQzServices, IWebHostEnvironment environment) + public Job_OperateLog_Quartz(IOperateLogServices operateLogServices, IWebHostEnvironment environment, + ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + : base(tasksQzServices, tasksLogServices) { - _operateLogServices = operateLogServices; - _environment = environment; - _tasksQzServices = tasksQzServices; + _operateLogServices = operateLogServices; + _environment = environment; } + public async Task Execute(IJobExecutionContext context) { var executeLog = await ExecuteJob(context, async () => await Run(context)); } + public async Task Run(IJobExecutionContext context) { - // 可以直接获取 JobDetail 的值 var jobKey = context.JobDetail.Key; - var jobId = jobKey.Name; + var jobId = jobKey.Name; // 也可以通过数据库配置,获取传递过来的参数 JobDataMap data = context.JobDetail.JobDataMap; - List excLogs = new List(); - var exclogContent = LogLock.ReadLog(Path.Combine(_environment.ContentRootPath, "Log"), $"GlobalExceptionLogs_{DateTime.Now.ToString("yyyMMdd")}.log", Encoding.UTF8); - - if (!string.IsNullOrEmpty(exclogContent)) - { - excLogs = exclogContent.Split("--------------------------------") - .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") - .Select(d => new LogInfo - { - Datetime = (d.Split("|")[0]).Split(',')[0].ObjToDate(), - Content = d.Split("|")[1]?.Replace("\r\n", "
"), - LogColor = "EXC", - Import = 9, - }).ToList(); - } - - var filterDatetime = DateTime.Now.AddHours(-1); - excLogs = excLogs.Where(d => d.Datetime >= filterDatetime).ToList(); - - var operateLogs = new List() { }; - excLogs.ForEach(m => - { - operateLogs.Add(new OperateLog() - { - LogTime = m.Datetime, - Description = m.Content, - IPAddress = m.IP, - UserId = 0, - IsDeleted = false, - }); - }); - - - if (operateLogs.Count > 0) - { - var logsIds = await _operateLogServices.Add(operateLogs); - } + //[INFO] 注释无用代码,后续如果需要再调整 + // List excLogs = new List(); + // var exclogContent = LogLock.ReadLog(Path.Combine(_environment.ContentRootPath, "Log"), + // $"GlobalExceptionLogs_{DateTime.Now.ToString("yyyMMdd")}.log", Encoding.UTF8); + // + // if (!string.IsNullOrEmpty(exclogContent)) + // { + // excLogs = exclogContent.Split("--------------------------------") + // .Where(d => !string.IsNullOrEmpty(d) && d != "\n" && d != "\r\n") + // .Select(d => new LogInfo + // { + // Datetime = (d.Split("|")[0]).Split(',')[0].ObjToDate(), + // Content = d.Split("|")[1]?.Replace("\r\n", "
"), + // LogColor = "EXC", + // Import = 9, + // }).ToList(); + // } + // + // var filterDatetime = DateTime.Now.AddHours(-1); + // excLogs = excLogs.Where(d => d.Datetime >= filterDatetime).ToList(); + // + // var operateLogs = new List() { }; + // excLogs.ForEach(m => + // { + // operateLogs.Add(new OperateLog() + // { + // LogTime = m.Datetime, + // Description = m.Content, + // IPAddress = m.IP, + // UserId = 0, + // IsDeleted = false, + // }); + // }); + // + // + // if (operateLogs.Count > 0) + // { + // var logsIds = await _operateLogServices.Add(operateLogs); + // } } } - - - -} +} \ No newline at end of file diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/Job_URL_Quartz.cs b/Blog.Core.Tasks/QuartzNet/Jobs/Job_URL_Quartz.cs new file mode 100644 index 00000000..1ec225c3 --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_URL_Quartz.cs @@ -0,0 +1,47 @@ +using Blog.Core.Common.Helper; +using Blog.Core.IServices; +using Microsoft.Extensions.Logging; +using Quartz; +using System.Threading.Tasks; + +/// +/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入) +/// +namespace Blog.Core.Tasks +{ + public class Job_URL_Quartz : JobBase, IJob + { + private readonly ILogger _logger; + + public Job_URL_Quartz(ILogger logger, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + : base(tasksQzServices, tasksLogServices) + { + _tasksQzServices = tasksQzServices; + _logger = logger; + } + public async Task Execute(IJobExecutionContext context) + { + // 可以直接获取 JobDetail 的值 + var jobKey = context.JobDetail.Key; + var jobId = jobKey.Name; + var executeLog = await ExecuteJob(context, async () => await Run(context, jobId.ObjToInt())); + + } + public async Task Run(IJobExecutionContext context, int jobid) + { + if (jobid > 0) + { + JobDataMap data = context.JobDetail.JobDataMap; + string pars = data.GetString("JobParam"); + if (!string.IsNullOrWhiteSpace(pars)) + { + var log = await HttpHelper.GetAsync(pars); + _logger.LogInformation(log); + } + } + } + } + + + +} diff --git a/Blog.Core.Tasks/QuartzNet/SchedulerCenterServer.cs b/Blog.Core.Tasks/QuartzNet/SchedulerCenterServer.cs index 8c21115c..474a2a9a 100644 --- a/Blog.Core.Tasks/QuartzNet/SchedulerCenterServer.cs +++ b/Blog.Core.Tasks/QuartzNet/SchedulerCenterServer.cs @@ -125,6 +125,12 @@ public async Task> AddScheduleJobAsync(TasksQz tasksQz) result.msg = $"该任务计划已经在执行:【{tasksQz.Name}】,请勿重复启动!"; return result; } + if(tasksQz.TriggerType == 0 && tasksQz.CycleRunTimes != 0 && tasksQz.CycleHasRunTimes>=tasksQz.CycleRunTimes) + { + result.success = false; + result.msg = $"该任务计划已完成:【{tasksQz.Name}】,无需重复启动,如需启动请修改已循环次数再提交"; + return result; + } #region 设置开始时间和结束时间 if (tasksQz.BeginTime == null) diff --git a/Blog.Core.Tests/Blog.Core.Tests.csproj b/Blog.Core.Tests/Blog.Core.Tests.csproj index 2ceef163..50a63431 100644 --- a/Blog.Core.Tests/Blog.Core.Tests.csproj +++ b/Blog.Core.Tests/Blog.Core.Tests.csproj @@ -1,8 +1,6 @@  - net6.0 - false false @@ -19,10 +17,11 @@ - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,6 +29,7 @@ + @@ -38,6 +38,14 @@ + + + true + Always + PreserveNewest + + + diff --git a/Blog.Core.Tests/Common_Test/CacheTest.cs b/Blog.Core.Tests/Common_Test/CacheTest.cs new file mode 100644 index 00000000..f3a78396 --- /dev/null +++ b/Blog.Core.Tests/Common_Test/CacheTest.cs @@ -0,0 +1,37 @@ +using Autofac; +using Blog.Core.Common; +using Blog.Core.Common.Caches.Interface; +using Xunit; +using Xunit.Abstractions; + +namespace Blog.Core.Tests.Common_Test; + +public class CacheTest +{ + private readonly ITestOutputHelper _testOutputHelper; + DI_Test dI_Test = new DI_Test(); + private readonly ICaching _cache; + + public CacheTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + dI_Test.Build(); + _cache = App.GetService(); + } + + [Fact] + public void TestCaching() + { + _cache.Set("test", "test", new TimeSpan(0, 10, 0)); + + var result = _cache.Get("test"); + Assert.Equal("test", result); + + var caches = _cache.GetAllCacheKeys(); + _testOutputHelper.WriteLine(caches.ToJson()); + Assert.NotNull(caches); + + var count = _cache.GetAllCacheKeys().Count; + Assert.Equal(1, count); + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/Common_Test/DynamicLambdaTest.cs b/Blog.Core.Tests/Common_Test/DynamicLambdaTest.cs index 99757e4f..513d283d 100644 --- a/Blog.Core.Tests/Common_Test/DynamicLambdaTest.cs +++ b/Blog.Core.Tests/Common_Test/DynamicLambdaTest.cs @@ -1,8 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Autofac; using Blog.Core.Common.Helper; using Blog.Core.IRepository.Base; using Blog.Core.Model.Models; +using SqlSugar; using Xunit; using Xunit.Abstractions; @@ -21,13 +23,31 @@ public DynamicLambdaTest(ITestOutputHelper testOutputHelper) var container = dI_Test.DICollections(); _baseRepository = container.Resolve>(); + _baseRepository.Db.Aop.OnLogExecuting = (sql, p) => + { + _testOutputHelper.WriteLine(UtilMethods.GetNativeSql(sql, p)); + }; - //DbContext.Init(BaseDBConfig.ConnectionString,(DbType)BaseDBConfig.DbType); + Init(); + } + + private void Init() + { + _baseRepository.Db.CodeFirst.InitTables(); + _baseRepository.Db.CodeFirst.InitTables(); + _baseRepository.Db.CodeFirst.InitTables(); } + /// + /// 普通查询 例子
+ /// 没有复杂链表 主要使用导航属性
+ /// 推荐将条件拼接交给前端 后端只定义个接口就很方便 维护也很简单
+ ///
[Fact] public async void Get_Blogs_DynamicTest() { + //方便前端自定义条件查询 + //语法更舒服 var data = await _baseRepository.Query(); _testOutputHelper.WriteLine(data.ToJson()); @@ -36,22 +56,103 @@ public async void Get_Blogs_DynamicTest() await TestConditions("bId=2"); await TestConditions("bId in (1,2,3,4,5)"); await TestConditions("bId in (1,2,3,4,5)|| bUpdateTime>=\"2019-01-01 01:01:01\""); + await TestConditions("btitle like \" 测试数据\""); await TestConditions("btitle like \"测试数据\" && bId>0"); await TestConditions("btitle like \"测试!@#$%^&*()_+|}{\":<>?LP\"数据\" && bId>0"); - await TestConditions("btitle like \"测试!@+)(*()_&%^&^$^%$IUYWIQOJVLXKZM>?Z<>??LP\"数据\" && bId>0"); - - //比如文章下 过滤创建人 - //await TestConditions("btitle.user.name like \"老张\""); - + await TestConditions( + "btitle like \"测试!@+)(*()_&%^&^$^%$IUYWIQOJVLXKZM>?Z<>??LP\"数据\" && bId>0"); await TestConditions("IsDeleted == false"); await TestConditions("IsDeleted == true"); + await TestConditions("IsDeleted == true && ( btitle like \"张三\" || btitle like \"李四\" )"); + await TestConditions( + "IsDeleted == true && ( btitle like \"张三\" || btitle like \"李四\" || ( btitle StartsLike \"王五\" && btitle EndLike \"赵六\" ) )"); + + //导航属性 + + //一对一 + + //查询 老张的文章 + await TestConditions("User.RealName like \"老张\""); + //查询 2019年后的老张文章 + await TestConditions("User.RealName like \"老张\" && bUpdateTime>=\"2019-01-01 01:01:01\""); + + //一对多 + + //查询 评论中有"写的不错"的文章 + await TestConditions("Comments.Comment like \"写的不错\""); + //查询 2019后的 评论中有"写的不错"的文章 + await TestConditions("Comments.Comment like \"写的不错\" && bUpdateTime>=\"2019-01-01 01:01:01\""); + //查询 有老张评论的文章 + await TestConditions("Comments.User.LoginName like \"老张\""); + } + + /// + /// 复杂链表 也能使用动态条件
+ /// 存在复杂的链表 left join等 + ///
+ [Fact] + public async void Get_Blogs_DynamicJoinTest() + { + //方便前端自定义条件查询 + //语法更舒服 + var data = await _baseRepository.Query(); + _testOutputHelper.WriteLine(data.ToJson()); + + await TestJoinConditions(""); + await TestJoinConditions("bId=1"); + await TestJoinConditions("bId=2"); + await TestJoinConditions("bId in (1,2,3,4,5)"); + await TestJoinConditions("bId in (1,2,3,4,5)|| bUpdateTime>=\"2019-01-01 01:01:01\""); + await TestJoinConditions("btitle like \" 测试数据\""); + await TestJoinConditions("btitle like \"测试数据\" && bId>0"); + await TestJoinConditions("btitle like \"测试!@#$%^&*()_+|}{\":<>?LP\"数据\" && bId>0"); + await TestJoinConditions( + "btitle like \"测试!@+)(*()_&%^&^$^%$IUYWIQOJVLXKZM>?Z<>??LP\"数据\" && bId>0"); + await TestJoinConditions("IsDeleted == false"); + await TestJoinConditions("IsDeleted == true"); + await TestJoinConditions("IsDeleted == true && ( btitle like \"张三\" || btitle like \"李四\" )"); + await TestJoinConditions( + "IsDeleted == true && ( btitle like \"张三\" || btitle like \"李四\" || ( btitle StartsLike \"王五\" && btitle EndLike \"赵六\" ) )"); + + //导航属性 + + //一对一 + + //查询 老张的文章 + await TestJoinConditions("User.RealName like \"老张\""); + //查询 2019年后的老张文章 + await TestJoinConditions("User.RealName like \"老张\" && bUpdateTime>=\"2019-01-01 01:01:01\""); + + //一对多 + + //查询 评论中有"写的不错"的文章 + await TestJoinConditions("Comments.Comment like \"写的不错\""); + //查询 2019后的 评论中有"写的不错"的文章 + await TestJoinConditions("Comments.Comment like \"写的不错\" && bUpdateTime>=\"2019-01-01 01:01:01\""); + //查询 有老张评论的文章 + await TestJoinConditions("Comments.User.LoginName like \"老张\""); } + private async Task TestConditions(string conditions) { var express = DynamicLinqFactory.CreateLambda(conditions); + _testOutputHelper.WriteLine(new string('=', 100)); var product = await _baseRepository.Query(express); + _testOutputHelper.WriteLine($"条件:{DynamicLinqFactory.FormatString(conditions)}\r\nLambda:{express}\r\n结果:{product.Count}"); + _testOutputHelper.WriteLine(new string('=', 100)); + } + + private async Task TestJoinConditions(string conditions) + { + var express = DynamicLinqFactory.CreateLambda(conditions); + _testOutputHelper.WriteLine(new string('=', 100)); + var product = await _baseRepository.Db.Queryable() + .LeftJoin((b, u) => Convert.ToInt64(b.bsubmitter) == u.Id) + .MergeTable() + .Where(express) + .ToListAsync(); + _testOutputHelper.WriteLine($"条件:{DynamicLinqFactory.FormatString(conditions)}\r\nLambda:{express}\r\n结果:{product.Count}"); _testOutputHelper.WriteLine(new string('=', 100)); - _testOutputHelper.WriteLine($"原条件:{conditions}\r\nLambda:{express}\r\n结果:{product.Count}"); } } \ No newline at end of file diff --git a/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs b/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs index b7bfa83d..16053f43 100644 --- a/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs +++ b/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs @@ -17,9 +17,12 @@ public void Get_Async_Test() [Fact] public void Post_Async_Test() { - var responseString = HttpHelper.PostAsync("http://apk.neters.club/api/Login/swgLogin", "{\"name\":\"admin\",\"pwd\":\"admin\"}").Result; - - Assert.NotNull(responseString); + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; + + using var client = new HttpClient(handler); } } diff --git a/Blog.Core.Tests/Controller_Test/BlogController_Should.cs b/Blog.Core.Tests/Controller_Test/BlogController_Should.cs index 3d1e3178..59d42ae0 100644 --- a/Blog.Core.Tests/Controller_Test/BlogController_Should.cs +++ b/Blog.Core.Tests/Controller_Test/BlogController_Should.cs @@ -53,7 +53,7 @@ public async void Get_Blog_Page_Test() [Fact] public async void Get_Blog_Test() { - MessageModel blogVo = await blogController.Get(1); + MessageModel blogVo = await blogController.Get(1.ObjToLong()); Assert.NotNull(blogVo); } diff --git a/Blog.Core.Tests/Controller_Test/LoginController_Should.cs b/Blog.Core.Tests/Controller_Test/LoginController_Should.cs index 7c0cb1bb..c9387f72 100644 --- a/Blog.Core.Tests/Controller_Test/LoginController_Should.cs +++ b/Blog.Core.Tests/Controller_Test/LoginController_Should.cs @@ -3,72 +3,76 @@ using Xunit; using Autofac; using Blog.Core.AuthHelper; +using Microsoft.Extensions.Logging; namespace Blog.Core.Tests { - public class LoginController_Should - { - LoginController loginController; - - private readonly ISysUserInfoServices _sysUserInfoServices; - private readonly IUserRoleServices _userRoleServices; - private readonly IRoleServices _roleServices; - private readonly PermissionRequirement _requirement; - private readonly IRoleModulePermissionServices _roleModulePermissionServices; - - DI_Test dI_Test = new DI_Test(); - - - - public LoginController_Should() - { - var container = dI_Test.DICollections(); - _sysUserInfoServices = container.Resolve(); - _userRoleServices = container.Resolve(); - _roleServices = container.Resolve(); - _requirement = container.Resolve(); - _roleModulePermissionServices = container.Resolve(); - loginController = new LoginController(_sysUserInfoServices,_userRoleServices,_roleServices,_requirement, _roleModulePermissionServices); - } - - [Fact] - public void GetJwtStrTest() - { - var data = loginController.GetJwtStr("test", "test"); - - Assert.NotNull(data); - } - [Fact] - public void GetJwtStrForNuxtTest() - { - object blogs = loginController.GetJwtStrForNuxt("test", "test"); - - Assert.NotNull(blogs); - } - - [Fact] - public async void GetJwtToken3Test() - { - - var res = await loginController.GetJwtToken3("test", "test"); - - Assert.NotNull(res); - } - - [Fact] - public async void RefreshTokenTest() - { - var res = await loginController.RefreshToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImp0aSI6IjgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2V4cGlyYXRpb24iOiIyMDE5LzEwLzE4IDIzOjI2OjQ5IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5UZXN0IiwibmJmIjoxNTcxNDA4ODA5LCJleHAiOjE1NzE0MTI0MDksImlzcyI6IkJsb2cuQ29yZSIsImF1ZCI6IndyIn0.oz-SPz6UCL78fM09bUecw5rmjcNYEY9dWGtuPs2gdBg"); - - Assert.NotNull(res); - } - - [Fact] - public void Md5PasswordTest() - { - var res = loginController.Md5Password("test"); - - Assert.NotNull(res); - } - } -} + public class LoginController_Should + { + LoginController loginController; + + private readonly ISysUserInfoServices _sysUserInfoServices; + private readonly IUserRoleServices _userRoleServices; + private readonly IRoleServices _roleServices; + private readonly PermissionRequirement _requirement; + private readonly IRoleModulePermissionServices _roleModulePermissionServices; + private readonly ILogger _logger; + + DI_Test dI_Test = new DI_Test(); + + + public LoginController_Should() + { + var container = dI_Test.DICollections(); + _sysUserInfoServices = container.Resolve(); + _userRoleServices = container.Resolve(); + _roleServices = container.Resolve(); + _requirement = container.Resolve(); + _roleModulePermissionServices = container.Resolve(); + _logger = container.Resolve>(); + loginController = new LoginController(_sysUserInfoServices, _userRoleServices, _roleServices, _requirement, + _roleModulePermissionServices, _logger); + } + + [Fact] + public void GetJwtStrTest() + { + var data = loginController.GetJwtStr("test", "test"); + + Assert.NotNull(data); + } + + [Fact] + public void GetJwtStrForNuxtTest() + { + object blogs = loginController.GetJwtStrForNuxt("test", "test"); + + Assert.NotNull(blogs); + } + + [Fact] + public async void GetJwtToken3Test() + { + var res = await loginController.GetJwtToken3("test", "test"); + + Assert.NotNull(res); + } + + [Fact] + public async void RefreshTokenTest() + { + var res = await loginController.RefreshToken( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImp0aSI6IjgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2V4cGlyYXRpb24iOiIyMDE5LzEwLzE4IDIzOjI2OjQ5IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5UZXN0IiwibmJmIjoxNTcxNDA4ODA5LCJleHAiOjE1NzE0MTI0MDksImlzcyI6IkJsb2cuQ29yZSIsImF1ZCI6IndyIn0.oz-SPz6UCL78fM09bUecw5rmjcNYEY9dWGtuPs2gdBg"); + + Assert.NotNull(res); + } + + [Fact] + public void Md5PasswordTest() + { + var res = loginController.Md5Password("test"); + + Assert.NotNull(res); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/DependencyInjection/DI_Test.cs b/Blog.Core.Tests/DependencyInjection/DI_Test.cs index 6fad1f45..bb037efd 100644 --- a/Blog.Core.Tests/DependencyInjection/DI_Test.cs +++ b/Blog.Core.Tests/DependencyInjection/DI_Test.cs @@ -1,12 +1,10 @@ using Autofac; using Autofac.Extensions.DependencyInjection; using Autofac.Extras.DynamicProxy; -using AutoMapper; using Blog.Core.AuthHelper; using Blog.Core.Common; using Blog.Core.Common.AppConfig; using Blog.Core.Common.DB; -using Blog.Core.Common.LogHelper; using Blog.Core.Common.Seed; using Blog.Core.Extensions; using Blog.Core.IRepository.Base; @@ -17,12 +15,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IO; using System.Reflection; using System.Security.Claims; using System.Text; +using Blog.Core.Common.Core; +using Blog.Core.Extensions.ServiceExtensions; using Microsoft.Extensions.Logging; namespace Blog.Core.Tests @@ -56,10 +53,13 @@ public IContainer DICollections() var basePath = AppContext.BaseDirectory; IServiceCollection services = new ServiceCollection(); - services.AddAutoMapper(typeof(Startup)); + services.ConfigureApplication(); + + services.AddLogging(); + services.AddAutoMapperSetup(); + services.AddCacheSetup(); services.AddSingleton(new AppSettings(basePath)); - services.AddSingleton(new LogLock(basePath)); services.AddScoped(); services.AddScoped(); @@ -74,13 +74,13 @@ public IContainer DICollections() var permission = new List(); var permissionRequirement = new PermissionRequirement( - "/api/denied", - permission, - ClaimTypes.Role, - AppSettings.app(new string[] { "Audience", "Issuer" }), - AppSettings.app(new string[] { "Audience", "Audience" }), - signingCredentials,//签名凭据 - expiration: TimeSpan.FromSeconds(60 * 60)//接口的过期时间 + "/api/denied", + permission, + ClaimTypes.Role, + AppSettings.app(new string[] { "Audience", "Issuer" }), + AppSettings.app(new string[] { "Audience", "Audience" }), + signingCredentials, //签名凭据 + expiration: TimeSpan.FromSeconds(60 * 60) //接口的过期时间 ); services.AddSingleton(permissionRequirement); @@ -88,17 +88,16 @@ public IContainer DICollections() services.AddAuthorization(options => { options.AddPolicy(Permissions.Name, - policy => policy.Requirements.Add(permissionRequirement)); + policy => policy.Requirements.Add(permissionRequirement)); }); services.AddScoped(o => { return new SqlSugar.SqlSugarScope(new SqlSugar.ConnectionConfig() { - ConnectionString = GetMainConnectionDb().Connection,//必填, 数据库连接字符串 - DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType,//必填, 数据库类型 - IsAutoCloseConnection = true,//默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作 - InitKeyType = SqlSugar.InitKeyType.SystemTable//默认SystemTable, 字段信息读取, 如:该属性是不是主键,标识列等等信息 + ConnectionString = GetMainConnectionDb().Connection, //必填, 数据库连接字符串 + DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType, //必填, 数据库类型 + IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作 }); }); @@ -106,11 +105,11 @@ public IContainer DICollections() var builder = new ContainerBuilder(); //builder.RegisterType().As(); builder.RegisterInstance(new LoggerFactory()) - .As(); + .As(); builder.RegisterGeneric(typeof(Logger<>)) - .As(typeof(ILogger<>)) - .SingleInstance(); + .As(typeof(ILogger<>)) + .SingleInstance(); //指定已扫描程序集中的类型注册为提供所有其实现的接口。 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储 @@ -118,22 +117,22 @@ public IContainer DICollections() // 属性注入 var controllerBaseType = typeof(ControllerBase); - builder.RegisterAssemblyTypes(typeof(Startup).Assembly) - .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType) - .PropertiesAutowired(); - + //builder.RegisterAssemblyTypes(typeof(Program).Assembly) + // .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType) + // .PropertiesAutowired(); + var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFrom(servicesDllFile); builder.RegisterAssemblyTypes(assemblysServices) - .AsImplementedInterfaces() - .InstancePerLifetimeScope() - .PropertiesAutowired() - .EnableInterfaceInterceptors(); + .AsImplementedInterfaces() + .InstancePerLifetimeScope() + .PropertiesAutowired() + .EnableInterfaceInterceptors(); var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository) - .PropertiesAutowired().AsImplementedInterfaces(); + .PropertiesAutowired().AsImplementedInterfaces(); services.Replace(ServiceDescriptor.Transient()); @@ -143,9 +142,18 @@ public IContainer DICollections() builder.Populate(services); //使用已进行的组件登记创建新容器 - var ApplicationContainer = builder.Build(); + var applicationContainer = builder.Build(); + return applicationContainer; + } - return ApplicationContainer; + public IServiceProvider Build() + { + var container = DICollections(); + var serviceProvider = new AutofacServiceProvider(container); + serviceProvider.ConfigureApplication(); + App.IsBuild = true; + App.IsRun = true; + return serviceProvider; } } } \ No newline at end of file diff --git a/Blog.Core.Tests/Repository_Test/OrmTest.cs b/Blog.Core.Tests/Repository_Test/OrmTest.cs new file mode 100644 index 00000000..fa4629f7 --- /dev/null +++ b/Blog.Core.Tests/Repository_Test/OrmTest.cs @@ -0,0 +1,73 @@ +using System; +using Autofac; +using Blog.Core.Common.Extensions; +using Blog.Core.IRepository.Base; +using Blog.Core.Model.Models; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Math; +using SqlSugar; +using Xunit; +using Xunit.Abstractions; + +namespace Blog.Core.Tests; + +public class OrmTest +{ + private readonly ITestOutputHelper _testOutputHelper; + private readonly IBaseRepository _baseRepository; + DI_Test dI_Test = new DI_Test(); + + public OrmTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + + var container = dI_Test.DICollections(); + + _baseRepository = container.Resolve>(); + _baseRepository.Db.Aop.OnLogExecuting = (sql, p) => + { + _testOutputHelper.WriteLine(""); + _testOutputHelper.WriteLine("==================FullSql=====================", "", new string[] { sql.GetType().ToString(), GetParas(p), "【SQL语句】:" + sql }); + _testOutputHelper.WriteLine("【SQL语句】:" + sql); + _testOutputHelper.WriteLine(GetParas(p)); + _testOutputHelper.WriteLine("=============================================="); + _testOutputHelper.WriteLine(""); + }; + } + + private static string GetParas(SugarParameter[] pars) + { + string key = "【SQL参数】:"; + foreach (var param in pars) + { + key += $"{param.ParameterName}:{param.Value}\n"; + } + + return key; + } + + [Fact] + public void MultiTables() + { + var sql = _baseRepository.Db.Queryable() + .AS($@"{nameof(BlogArticle)}_TenantA") + .ToSqlString(); + //_testOutputHelper.WriteLine(sql); + + _baseRepository.Db.MappingTables.Add(nameof(BlogArticle), $@"{nameof(BlogArticle)}_TenantA"); + + var query = _baseRepository.Db.Queryable() + .LeftJoin((a, c) => a.bID == c.bID); + // query.QueryBuilder.AsTables.AddOrModify(nameof(BlogArticle), $@"{nameof(BlogArticle)}_TenantA"); + //query.QueryBuilder.AsTables.AddOrModify(nameof(BlogArticleComment), $@"{nameof(BlogArticleComment)}_TenantA"); + // query.QueryBuilder.AsTables.AddOrModify(nameof(BlogArticleComment), $@"{nameof(BlogArticleComment)}_TenantA"); + // query.QueryBuilder.AsTables.AddOrModify(nameof(SysUserInfo), $@"{nameof(SysUserInfo)}_TenantA"); + + + sql = query.ToSqlString(); + + _testOutputHelper.WriteLine(sql); + + sql = _baseRepository.Db.Deleteable().ToSqlString(); + _testOutputHelper.WriteLine(sql); + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/Service_Test/DbTest.cs b/Blog.Core.Tests/Service_Test/DbTest.cs new file mode 100644 index 00000000..377859fe --- /dev/null +++ b/Blog.Core.Tests/Service_Test/DbTest.cs @@ -0,0 +1,39 @@ +using Microsoft.Data.SqlClient; +using Xunit; +using Xunit.Abstractions; + +namespace Blog.Core.Tests; + +public class DbTest(ITestOutputHelper testOutputHelper) +{ + [Fact] + public void Test_CreateDataBase() + { + string connectionString = "Database=master;TrustServerCertificate=true;Persist Security Info=False;Trusted_Connection=True;server=(local)"; + string connectionString2 = "Database=Blog.Core;TrustServerCertificate=true;Persist Security Info=False;Trusted_Connection=True;server=(local)"; + + // 创建数据库 + using (SqlConnection masterConnection = new SqlConnection(connectionString)) + { + masterConnection.Open(); + string createDbQuery = $"CREATE DATABASE [Blog.Core];"; + using (SqlCommand command = new SqlCommand(createDbQuery, masterConnection)) + { + command.ExecuteNonQuery(); + testOutputHelper.WriteLine("Database created successfully."); + } + } + + // 连接到新创建的数据库 + using (SqlConnection newDbConnection = new SqlConnection(connectionString2)) + { + newDbConnection.Open(); + string testQuery = "SELECT 1;"; + using (SqlCommand command = new SqlCommand(testQuery, newDbConnection)) + { + int result = (int)command.ExecuteScalar()!; + testOutputHelper.WriteLine("Connection to new database successful, test query result: " + result); + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/Utility/SqlSugarSnowflakeHelperTest.cs b/Blog.Core.Tests/Utility/SqlSugarSnowflakeHelperTest.cs new file mode 100644 index 00000000..0ce29b44 --- /dev/null +++ b/Blog.Core.Tests/Utility/SqlSugarSnowflakeHelperTest.cs @@ -0,0 +1,29 @@ +using System.Globalization; +using Blog.Core.Common.Utility; +using JetBrains.Annotations; +using SqlSugar; +using SqlSugar.DistributedSystem.Snowflake; +using Xunit; +using Xunit.Abstractions; + +namespace Blog.Core.Tests.Utility; + +[TestSubject(typeof(SqlSugarSnowflakeHelper))] +public class SqlSugarSnowflakeHelperTest(ITestOutputHelper testOutputHelper) +{ + [Fact] + public void Test_Id_To_Datetime() + { + var id = SnowFlakeSingle.Instance.NextId(); + + testOutputHelper.WriteLine(SqlSugarSnowflakeHelper.GetDateTime(id).ToString(CultureInfo.InvariantCulture)); + } + + [Fact] + public void Test_Id() + { + var id = SnowFlakeSingle.Instance.NextId(); + + testOutputHelper.WriteLine(SqlSugarSnowflakeHelper.Decode(id).ToJson()); + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/Utility/YitterSnowflakeHelperTest.cs b/Blog.Core.Tests/Utility/YitterSnowflakeHelperTest.cs new file mode 100644 index 00000000..f55ef405 --- /dev/null +++ b/Blog.Core.Tests/Utility/YitterSnowflakeHelperTest.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using Blog.Core.Common.Utility; +using JetBrains.Annotations; +using Xunit; +using Xunit.Abstractions; +using Yitter.IdGenerator; + +namespace Blog.Core.Tests.Utility; + +[TestSubject(typeof(YitterSnowflakeHelper))] +public class YitterSnowflakeHelperTest(ITestOutputHelper testOutputHelper) +{ + private static readonly IdGeneratorOptions _options = new IdGeneratorOptions { WorkerId = 1 }; + private readonly IIdGenerator _idGenInstance = new DefaultIdGenerator(_options); + + [Fact] + public void Test_Id_To_Datetime() + { + var id = _idGenInstance.NewLong(); + + var dateTime = YitterSnowflakeHelper.GetDateTime(_options, id); + testOutputHelper.WriteLine(dateTime.ToString(CultureInfo.InvariantCulture)); + } + + [Fact] + public void Test_Id() + { + var id = _idGenInstance.NewLong(); + + var decoded = YitterSnowflakeHelper.Decode(_options, id); + testOutputHelper.WriteLine(decoded.ToJson()); + } +} \ No newline at end of file diff --git a/Blog.Core.Tests/WMBlog.db b/Blog.Core.Tests/WMBlog.db deleted file mode 100644 index 4931b967..00000000 Binary files a/Blog.Core.Tests/WMBlog.db and /dev/null differ diff --git a/Blog.Core.Tests/appsettings.json b/Blog.Core.Tests/appsettings.json index 23ab9c65..f91f6df8 100644 --- a/Blog.Core.Tests/appsettings.json +++ b/Blog.Core.Tests/appsettings.json @@ -1,30 +1,26 @@ { "urls": "http://*:9291", //web服务端口,如果用IIS部署,把这个去掉 - "Logging": { - "LogLevel": { - "Default": "Information", //加入Default否则log4net本地写入不了日志 - "Blog.Core.AuthHelper.ApiResponseHandler": "Error" - }, - "Debug": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning", - "Microsoft.Hosting.Lifetime": "Debug" + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "Microsoft.AspNetCore": "Warning", + "System": "Warning", + "System.Net.Http.HttpClient": "Warning", + "Hangfire": "Information", + "Magicodes": "Warning", + "DotNetCore.CAP": "Information", + "Savorboard.CAP": "Information", + "Quartz": "Information" } - }, - "Log4Net": { - "Name": "Blog.Core" } }, "AllowedHosts": "*", "Redis": { - "ConnectionString": "127.0.0.1:6319,password=admin" + "Enable": false, + "ConnectionString": "127.0.0.1:6379", + "InstanceName": "" //前缀 }, "RabbitMQ": { "Enabled": false, @@ -45,30 +41,37 @@ "SubscriptionClientName": "Blog.Core" }, "AppSettings": { - "RedisCachingAOP": { - "Enabled": false - }, - "MemoryCachingAOP": { + "CachingAOP": { "Enabled": true }, + "LogToDb": true, "LogAOP": { - "Enabled": false + "Enabled": false, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + } }, "TranAOP": { + "Enabled": true + }, + "UserAuditAOP": { "Enabled": false }, "SqlAOP": { "Enabled": true, - "OutToLogFile": { - "Enabled": false + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true }, - "OutToConsole": { + "LogToConsole": { "Enabled": true } }, - "LogToDb": { - "Enabled": true - }, "Date": "2018-08-28", "SeedDBEnabled": true, //只生成表结构 "SeedDBDataEnabled": true, //生成表,并初始化数据 @@ -77,14 +80,17 @@ "UseLoadTest": false }, - // 请配置MainDB为你想要的主库的ConnId值,并设置对应的Enabled为true; - // *** 单库操作,把 MutiDBEnabled 设为false ***; - // *** 多库操作,把 MutiDBEnabled 设为true,其他的从库Enabled也为true **; - // 具体配置看视频:https://www.bilibili.com/video/BV1BJ411B7mn?p=6 - - "MainDB": "WMBLOG_SQLITE", //当前项目的主库,所对应的连接字符串的Enabled必须为true - "MutiDBEnabled": false, //是否开启多库模式 - "CQRSEnabled": false, //是否开启读写分离模式,必须是单库模式,且数据库类型一致,比如都是SqlServer + //优化DB配置、不会再区分单库多库 + //MainDb:标识当前项目的主库,所对应的连接字符串的Enabled必须为true + //Log:标识日志库,所对应的连接字符串的Enabled必须为true + //从库只需配置Slaves数组,要求数据库类型一致!,比如都是SqlServer + // + //新增,故障转移方案 + //如果主库挂了,会自动切换到备用连接(比如说主库+备用库) + //备用连接的ConnId配置为主库的ConnId+数字即可,比如主库的ConnId为Main,那么备用连接的ConnId为Mian1 + //主库、备用库无需数据库类型一致! + //备用库不会有程序维护,需要手动维护 + "MainDB": "Main", //当前项目的主库,所对应的连接字符串的Enabled必须为true "DBS": [ /* 对应下边的 DBType @@ -97,17 +103,40 @@ Kdbndp = 6,//人大金仓 */ { - "ConnId": "WMBLOG_SQLITE", + "ConnId": "Main", + "DBType": 2, + "Enabled": true, + "Connection": "WMBlog.db", //sqlite只写数据库名就行 + "Slaves": [ + { + "HitRate": 0,// 值越大,优先级越高 0不使用 + "Connection": "WMBlog2.db" + } + ] + }, + { + "ConnId": "Main2", + "DBType": 2, + "Enabled": true, + "Connection": "WMBlog3.db", //sqlite只写数据库名就行 + "Slaves": [ + { + "HitRate": 0,// 值越大,优先级越高 0不使用 + "Connection": "WMBlog4.db" + } + ] + }, + { + "ConnId": "Log", //日志库连接固定名称,不要改,其他的可以改 "DBType": 2, "Enabled": true, - "HitRate": 50, // 值越大,优先级越高 - "Connection": "WMBlog.db" //sqlite只写数据库名就行 + "HitRate": 50, + "Connection": "WMBlogLog.db" //sqlite只写数据库名就行 }, { "ConnId": "WMBLOG_MSSQL_1", "DBType": 1, "Enabled": false, - "HitRate": 40, "Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_1;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", "ProviderName": "System.Data.SqlClient" }, @@ -115,7 +144,6 @@ "ConnId": "WMBLOG_MSSQL_2", "DBType": 1, "Enabled": false, - "HitRate": 30, "Connection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", "ProviderName": "System.Data.SqlClient" }, @@ -123,35 +151,30 @@ "ConnId": "WMBLOG_MYSQL", "DBType": 0, "Enabled": false, - "HitRate": 20, - "Connection": "server=.;Database=ddd;Uid=root;Pwd=123456;Port=10060;Allow User Variables=True;" + "Connection": "server=localhost;Database=blog;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" }, { "ConnId": "WMBLOG_MYSQL_2", "DBType": 0, - "Enabled": true, - "HitRate": 20, - "Connection": "server=.;Database=blogcore001;Uid=root;Pwd=123456;Port=3096;Allow User Variables=True;" + "Enabled": false, + "Connection": "server=localhost;Database=blogcore001;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" }, { "ConnId": "WMBLOG_ORACLE", "DBType": 3, "Enabled": false, - "HitRate": 10, "Connection": "Data Source=127.0.0.1/ops;User ID=OPS;Password=123456;Persist Security Info=True;Connection Timeout=60;" }, { "ConnId": "WMBLOG_DM", "DBType": 5, "Enabled": false, - "HitRate": 10, - "Connection": "PORT=5236;DATABASE=DAMENG;HOST=localhost;PASSWORD=SYSDBA;USER ID=SYSDBA;" + "Connection": "Server=xxxxx:5236;User Id=xxxxx;PWD=xxxxx;SCHEMA=TESTDBA;" }, { "ConnId": "WMBLOG_KDBNDP", "DBType": 6, - "Enabled": true, - "HitRate": 10, + "Enabled": false, "Connection": "Server=127.0.0.1;Port=54321;UID=SYSTEM;PWD=system;database=SQLSUGAR4XTEST1;" } ], @@ -166,12 +189,13 @@ "Database": "BlogCoreDb" }, "Startup": { + "Domain": "http://localhost:9291", "Cors": { "PolicyName": "CorsIpAccess", //策略名称 "EnableAllIPs": false, //当为true时,开放所有IP均可访问。 // 支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的 // 注意,http://127.0.0.1:1818 和 http://localhost:1818 是不一样的 - "IPs": "http://127.0.0.1:2364,http://localhost:2364" + "IPs": "http://127.0.0.1:2364,http://localhost:2364,http://127.0.0.1:6688,http://localhost:6688" }, "AppConfigAlert": { "Enabled": true @@ -182,6 +206,12 @@ "AuthorizationUrl": "http://localhost:5004", // 认证中心域名 "ApiName": "blog.core.api" // 资源服务器 }, + "Authing": { + "Enabled": false, + "Issuer": "https://uldr24esx31h-demo.authing.cn/oidc", + "Audience": "63d51c4205c2849803be5178", + "JwksUri": "https://uldr24esx31h-demo.authing.cn/oidc/.well-known/jwks.json" + }, "RedisMq": { "Enabled": false //redis 消息队列 }, @@ -194,17 +224,38 @@ }, "Middleware": { "RequestResponseLog": { - "Enabled": false + "Enabled": true, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + } }, "IPLog": { - "Enabled": true + "Enabled": true, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + } }, "RecordAccessLogs": { "Enabled": true, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + }, "IgnoreApis": "/api/permission/getnavigationbar,/api/monitor/getids4users,/api/monitor/getaccesslogs,/api/monitor/server,/api/monitor/getactiveusers,/api/monitor/server," }, "SignalR": { - "Enabled": false + "Enabled": true + }, + "SignalRSendLog": { + "Enabled": true }, "QuartzNetJob": { "Enabled": true @@ -288,5 +339,10 @@ "FiedValue": "Blog.Core.Api" } ] + }, + "Seq": { + "Enabled": true, + "Address": "http://localhost:5341/", + "ApiKey": "" } -} +} \ No newline at end of file diff --git a/Blog.Core.sln b/Blog.Core.sln index c8f61505..c04e775b 100644 --- a/Blog.Core.sln +++ b/Blog.Core.sln @@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt Dockerfile = Dockerfile nuget.config = nuget.config README.md = README.md + Directory.Build.props = Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDA8901E-541E-4ADC-B71E-59697D5F9549}" @@ -57,6 +58,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Serilog.Es", "Blo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Nacos", "Ocelot.Provider.Nacos\Ocelot.Provider.Nacos.csproj", "{6463FB13-5F01-4A1D-8B62-A454FB3812EB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blog.Core.Serilog", "Blog.Core.Serilog\Blog.Core.Serilog.csproj", "{7F9057F0-ED8D-4694-B590-7D75C012DF00}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Service", "Service", "{4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Expand", "Expand", "{9C90FB30-ADB8-4C74-9A2F-43109B58C87E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -119,6 +128,10 @@ Global {6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Release|Any CPU.Build.0 = Release|Any CPU + {7F9057F0-ED8D-4694-B590-7D75C012DF00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F9057F0-ED8D-4694-B590-7D75C012DF00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F9057F0-ED8D-4694-B590-7D75C012DF00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F9057F0-ED8D-4694-B590-7D75C012DF00}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -129,6 +142,15 @@ Global {17C9E9DC-E926-4C90-9025-3DAC55D7EDA3} = {A592C96A-4E44-4F2A-AC21-30683AF6C493} {A11C0DF2-1E13-4EED-BA49-44A57136B189} = {E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB} {6463FB13-5F01-4A1D-8B62-A454FB3812EB} = {E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB} + {97D32A49-994C-44C5-A167-51E71D173B6F} = {D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C} + {558F1B39-07E4-4FAB-BE7E-5B6104607064} = {D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C} + {52AFAB53-D1CA-4014-8B63-3550FDCDA6E1} = {9C90FB30-ADB8-4C74-9A2F-43109B58C87E} + {7F9057F0-ED8D-4694-B590-7D75C012DF00} = {9C90FB30-ADB8-4C74-9A2F-43109B58C87E} + {F8E9FA1F-4079-4F62-B717-E389BC0014E8} = {D6240AF3-765A-41BE-8EAD-5D13F9D3DC3C} + {A2EFEFFC-39AD-48D2-8337-E6840B26023B} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} + {8D651E7F-49D3-4D27-8486-ADCF000BB24D} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} + {E725F0A1-0B03-406F-B84B-0F486C6137FC} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} + {37BB8600-94DA-4A2C-9230-DE93EA1EB0BD} = {4ED6E3A6-13BE-4E9B-A01F-5CABE01A1B5F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AB40D0C5-E3EA-4A9B-86C2-38F0BB33FC04} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..2934ef7e --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,6 @@ + + + net8.0 + enable + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 95bdaf92..575d3583 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,28 +5,44 @@ #如果你想先手动dotnet build成可执行的二进制文件,然后再构建镜像,请看.Api层下的dockerfile。 -FROM mcr.microsoft.com/dotnet/aspnet:6.0-bullseye-slim AS base +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src + +# 复制 Directory.Build.props 文件 +COPY ["Directory.Build.props", "."] + +# 复制所有项目文件 COPY ["Blog.Core.Api/Blog.Core.Api.csproj", "Blog.Core.Api/"] COPY ["Blog.Core.Extensions/Blog.Core.Extensions.csproj", "Blog.Core.Extensions/"] -COPY ["Blog.Core.Tasks/Blog.Core.Tasks.csproj", "Blog.Core.Tasks/"] -COPY ["Blog.Core.IServices/Blog.Core.IServices.csproj", "Blog.Core.IServices/"] -COPY ["Blog.Core.Model/Blog.Core.Model.csproj", "Blog.Core.Model/"] +COPY ["Blog.Core.EventBus/Blog.Core.EventBus.csproj", "Blog.Core.EventBus/"] COPY ["Blog.Core.Common/Blog.Core.Common.csproj", "Blog.Core.Common/"] +COPY ["Blog.Core.Model/Blog.Core.Model.csproj", "Blog.Core.Model/"] +COPY ["Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj", "Blog.Core.Serilog.Es/"] +COPY ["Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj", "Ocelot.Provider.Nacos/"] COPY ["Blog.Core.Services/Blog.Core.Services.csproj", "Blog.Core.Services/"] +COPY ["Blog.Core.IServices/Blog.Core.IServices.csproj", "Blog.Core.IServices/"] COPY ["Blog.Core.Repository/Blog.Core.Repository.csproj", "Blog.Core.Repository/"] -COPY ["Blog.Core.EventBus/Blog.Core.EventBus.csproj", "Blog.Core.EventBus/"] +COPY ["Blog.Core.Tasks/Blog.Core.Tasks.csproj", "Blog.Core.Tasks/"] +COPY ["build", "build/"] + +# 恢复依赖项 RUN dotnet restore "Blog.Core.Api/Blog.Core.Api.csproj" + +# 复制其余的源代码 COPY . . + +# 构建项目 WORKDIR "/src/Blog.Core.Api" RUN dotnet build "Blog.Core.Api.csproj" -c Release -o /app/build FROM build AS publish -RUN dotnet publish "Blog.Core.Api.csproj" -c Release -o /app/publish +RUN dotnet publish "Blog.Core.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app diff --git a/Ocelot.Provider.Nacos/Nacos.cs b/Ocelot.Provider.Nacos/Nacos.cs index d8357920..b1d4cd4f 100644 --- a/Ocelot.Provider.Nacos/Nacos.cs +++ b/Ocelot.Provider.Nacos/Nacos.cs @@ -1,8 +1,4 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.ServiceDiscovery.Providers; +using Ocelot.ServiceDiscovery.Providers; using Ocelot.Values; using Nacos.V2; using Microsoft.Extensions.Options; @@ -52,5 +48,10 @@ public async Task> Get() return await Task.FromResult(services); } + + public Task> GetAsync() + { + throw new NotImplementedException(); + } } } diff --git a/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj b/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj index c713416a..a4c4225f 100644 --- a/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj +++ b/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj @@ -1,7 +1,6 @@  - + - net6.0 softlgl softlgl https://github.com/softlgl/Ocelot.Provider.Nacos @@ -11,10 +10,10 @@ - - - - - + + + + + diff --git a/README.md b/README.md index 7f37ce66..20b0dabf 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ [![sdk](https://img.shields.io/badge/sdk-6.0.1-d.svg)](#) [![Build status](https://github.com/anjoy8/blog.core/workflows/.NET%20Core/badge.svg)](https://github.com/anjoy8/Blog.Core/actions) [![Build Status](https://dev.azure.com/laozhangisphi/anjoy8/_apis/build/status/anjoy8.Blog.Core?branchName=master)](https://dev.azure.com/laozhangisphi/anjoy8/_build?definitionId=1) [![codecov](https://codecov.io/gh/anjoy8/Blog.Core/branch/master/graph/badge.svg)](https://codecov.io/gh/anjoy8/Blog.Core) [![License MIT](https://img.shields.io/badge/license-Apache-blue.svg?style=flat-square)](https://github.com/anjoy8/Blog.Core/blob/master/LICENSE) [![star this repo](http://githubbadges.com/star.svg?user=anjoy8&repo=blog.core&style=flat)](https://github.com/boennemann/badges) [![fork this repo](http://githubbadges.com/fork.svg?user=anjoy8&repo=blog.core&style=flat)](https://github.com/boennemann/badges/fork) [![博客园](https://img.shields.io/badge/博客园-老张的哲学-brightgreen.svg)](https://www.cnblogs.com/laozhang-is-phi/) +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=anjoy8/blog.core&type=Date)](https://star-history.com/#anjoy8/blog.core&Date) +     @@ -28,9 +32,13 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x --------------------- **已被近100家公司所使用(🐱‍🚀):[点击查看列表](https://github.com/anjoy8/Blog.Core/issues/75)** 欢迎盖楼,留下公司真实名字的,可得定制化指导服务。 -同时商业授权付费版也悄悄准备上线,欢迎联系老张(QQ:3143422472)。 +同时如果企业有付费咨询,欢迎联系老张(QQ:3143422472)。 +### 核心项目组成员(排名不分先后) + +[hudingwen](https://github.com/hudingwen)、[LemonNoCry](https://github.com/LemonNoCry)、、[Jamnine何拾玖](https://github.com/Jamnine)、 + #### ❤ 真实用户反馈 ❤ ``` @@ -56,9 +64,9 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x ### 功能与进度 -#### 商业授权付费版下🎁🎁🎁 +#### 企业使用高级版本 -- [x] 包含下边框架模块中的所有功能; +- [x] 包含开源版 `框架模块/组件模块` 中的所有功能; - [x] 全部表结构主键底层架构改成`string`类型(默认雪花,支持guid),更方便迁移; - [x] 完善部门数据权限,可以基于策略配置查看数据范围; - [x] 优化权限处理器,解决多实例分布式下,权限不同步问题(必须配置Redis); @@ -73,20 +81,26 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x #### 框架模块: - [x] 采用`仓储+服务+接口`的形式封装框架; -- [x] 异步 async/await 开发; -- [x] 接入国产数据库ORM组件 —— SqlSugar,封装数据库操作; +- [x] 自定义项目模板 `CreateYourProject.bat` ,可以一键生成自己的项目;🎶 +- [x] 异步 async/await 开发; +- [x] 接入国产数据库ORM组件 —— SqlSugar,封装数据库操作,支持级联操作; - [x] 支持自由切换多种数据库,MySql/SqlServer/Sqlite/Oracle/Postgresql/达梦/人大金仓; - [x] 实现项目启动,自动生成种子数据 ✨; - [x] 实现数据库主键类型配置化,什么类型都可以自定义 ✨; - [x] 五种日志记录,审计/异常/请求响应/服务操作/Sql记录等,并自动持久化到数据库表🎶; - [x] 支持项目事务处理(若要分布式,用cap即可)✨; - [x] 设计4种 AOP 切面编程,功能涵盖:日志、缓存、审计、事务 ✨; +- [x] 全局统一封装 Serilog 生成多种日志,并自动生成到数据库中,目前支持MySql/SqlServer/Sqlite/Oracle/Postgresql🎉; - [x] 设计并支持按钮级别的RBAC权限控制,同时支持一键同步接口和菜单 🎶; - [x] 支持 T4 代码模板,自动生成每层代码; - [x] 或使用 DbFirst 一键创建自己项目的四层文件(支持多库); - [x] 封装`Blog.Core.Webapi.Template`项目模板,一键重建自己的项目 ✨; - [x] 搭配多个前端案例供参考和借鉴:Blog.Vue、Blog.Admin、Nuxt.tbug、Blog.Mvp.Blazor ✨; - [x] 统一集成 IdentityServer4 认证 ✨; +- [x] 统一实现多租户; +- [x] 实现分表案例,支持分表的增删改查哈分页查询,具体查看SplitDemoController.cs; +- [x] 支持signalR对指定用户通讯; + 组件模块: - [x] 提供 Redis 做缓存处理; @@ -96,7 +110,7 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x - [x] 使用 AutoFac 做依赖注入容器,并提供批量服务注入 ✨; - [x] 支持 CORS 跨域; - [x] 封装 JWT 自定义策略授权; -- [x] 使用 Log4Net 日志框架,集成原生 ILogger 接口做日志记录; +- [x] 使用 Serilog 日志框架,集成原生 ILogger 接口做日志记录; - [x] 使用 SignalR 双工通讯 ✨; - [x] 添加 IpRateLimiting 做 API 限流处理; - [x] 使用 Quartz.net 做任务调度(目前单机多任务,集群调度暂不支持); @@ -111,16 +125,30 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x - [x] 新增 Kafka 消息队列,并配合实现EventBus ✨; - [x] 新增 微信公众号管理,并集成到Blog.Admin后台 ✨; - [x] 新增 - 数据部门权限; +- [x] 新增 - Serilog 集成日志数据持久化到数据库; +- [x] 新增 - 多租户模式(单表,多表,多库三种模式); +- [x] 新增 - 使用 [SnowflakeId.AutoRegister](https://github.com/LemonNoCry/SnowflakeId.AutoRegister) 自动注册雪花Id WorkerId,支持分布式部署 ✨; + 微服务模块: - [x] 可配合 Docker 实现容器化; - [x] 可配合 Jenkins 实现CI / CD; - [x] 可配合 Consul 实现服务发现; - [x] 可配合 Nacos 实现服务发现; -- [x] 可配合 Ocelot 实现网关处理; +- [x] 可配合 apisix/Ocelot 实现网关处理; - [x] 可配合 Nginx 实现负载均衡; - [x] 可配合 Ids4 实现认证中心; +### 核心业务模块 +#### 框架采用泛型仓储模式,以下几层为核心层,不可删除 +`Blog.Core.Api`、`Blog.Core.Common`、`Blog.Core.IServices`、`Blog.Core.Model`、`Blog.Core.Repository`、`Blog.Core.Services`、`Blog.Core.Tasks`、`Blog.Core.Serilog` +其他代码分层是支撑层,如果自己业务涉及不到,可以删除。 + +#### API接口层主要是基于RBAC的基于按钮级别的角色授权逻辑,以下几个Controller,不可删除 +`BaseApiController.cs`(接口基类)、`DepartmentController`(部门)、`ImgController`(图片)、`LoginController`(登录)、`ModuleController`(接口)、`PermissionController`(菜单)、`RoleController`(角色)、`TasksQzController`(任务调度)、`UserController`(用户)、`UserRoleController`(用户角色关系) +其他代码部分是扩展业务,如果自己业务涉及不到,可以删除。 + + ### 自定义全部日志记录图 ![系统架构图](https://img.neters.club/github/log5.png) @@ -229,11 +257,8 @@ Contributions of any kind are welcome! ## 售后服务与支持 -鼓励作者,简单打赏,入微信群,随时随地解答我框架中(NetCore、Vue、DDD、IdentityServer4等)的疑难杂症。 -注意主要是帮忙解决bug和思路,不会远程授课,但是可以适当发我代码,我帮忙调试, -打赏的时候,备注自己的微信号,我拉你进群,两天内没回应,QQ私聊我(3143422472); - -[赞赏列表](http://apk.neters.club/.doc/Contribution/) +鼓励作者,简单打赏~~ +如果你喜欢,就给作者加个鸡腿吧 赞赏码 diff --git a/build/README.md b/build/README.md new file mode 100644 index 00000000..e69de29b