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 027e74b3..00000000 --- a/.docs/contents/Contribution/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# 贡献 - - -欢迎一起完善文档, -参与打赏的小可爱名单如下(单位:元),你们的贡献是我继续的动力: -(2021年1月14日 10点25分) - - -|序号|微信昵称|助力值|备注| -|-|-|-|-| -|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|| - - 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/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..3df0ef21 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '32 13 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp', 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 80289a98..f11fee1f 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -12,6 +12,12 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.100 + dotnet-version: 8.0.x - name: Build with dotnet run: dotnet build --configuration Release + - name: Build image + run: docker build -f "Dockerfile" --force-rm -t laozhangisphi/apkimg . + - name: Log into registry + run: echo "${{ secrets.ACCESS_TOKEN }}" | docker login -u laozhangisphi --password-stdin + - name: Push image + run: docker push laozhangisphi/apkimg diff --git a/.gitignore b/.gitignore index 9a7ee7a9..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.* @@ -351,3 +354,9 @@ Blog.Core/WMBlog.db .docs/contents/.vuepress/dist/* 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/.config/dotnet-tools.json b/Blog.Core.Api/.config/dotnet-tools.json new file mode 100644 index 00000000..98091c92 --- /dev/null +++ b/Blog.Core.Api/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "6.0.8", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Blog.Core.Api.csproj b/Blog.Core.Api/Blog.Core.Api.csproj index da5c4e05..8bc327ab 100644 --- a/Blog.Core.Api/Blog.Core.Api.csproj +++ b/Blog.Core.Api/Blog.Core.Api.csproj @@ -1,14 +1,12 @@  - Exe - - net5.0 - - OutOfProcess + enable + Linux true + default @@ -26,25 +24,51 @@ + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + @@ -57,7 +81,7 @@ - + @@ -71,6 +95,9 @@ Always + + PreserveNewest + @@ -82,10 +109,14 @@ + + + + - + \ 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 575f738b..00b4c5be 100644 --- a/Blog.Core.Api/Blog.Core.Model.xml +++ b/Blog.Core.Api/Blog.Core.Model.xml @@ -4,6 +4,36 @@ Blog.Core.Model + + + 无任何权限 + + + + + 自定义权限 + + + + + 本部门 + + + + + 本部门及以下 + + + + + 仅自己 + + + + + 所有 + + 以下model 来自ids4项目,多库模式,为了调取ids4数据 @@ -165,6 +195,26 @@ 返回数据集合 + + + 用户访问趋势日志 + + + + + 用户 + + + + + 次数 + + + + + 更新时间 + + 广告图片 @@ -251,6 +301,146 @@ 逻辑删除 + + + 评论 + + + + + 博客文章 评论 + + + + + 部门表 + + + + + Desc:部门关系编码 + Default: + Nullable:True + + + + + Desc:部门名称 + Default: + Nullable:True + + + + + Desc:负责人 + Default: + Nullable:True + + + + + Desc:排序 + Default: + Nullable:True + + + + + Desc:部门状态(0正常 1停用) + Default:0 + Nullable:True + + + + + Desc:删除标志(0代表存在 2代表删除) + Default:0 + Nullable:True + + + + + Desc:创建者 + Default: + Nullable:True + + + + + Desc:创建时间 + Default: + Nullable:True + + + + + Desc:更新者 + Default: + Nullable:True + + + + + Desc:更新时间 + Default: + Nullable:True + + + + + 用户团队表 + + + + + ID + + + + + HttpContext.TraceIdentifier 事件链路ID(获取或设置一个唯一标识符,用于在跟踪日志中表示此请求。) + + + + + 时间 + + + + + 线程 + + + + + 等级 + + + + + 记录器 + + + + + 日志类型 + + + + + 数据类型 + + + + + 错误信息 + + + + + 异常 + + 博客ID @@ -286,46 +476,6 @@ - - - 菜单与按钮关系表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - 接口API地址信息表 @@ -526,6 +676,11 @@ 菜单图标 + + + 菜单图标新 + + 菜单描述 @@ -596,6 +751,17 @@ 排序 + + + 自定义权限的部门ids + + + + + 权限范围 + -1 无任何权限;1 自定义权限;2 本部门;3 本部门及以下;4 仅自己;9 全部; + + 是否激活 @@ -671,1321 +837,2570 @@ 修改时间 - + - 用户信息表 + 状态
+ 中立字段,某些表可使用某些表不使用
- + - 登录账号 + 中立字段,某些表可使用某些表不使用
+ 逻辑上的删除,非物理删除
+ 例如:单据删除并非直接删除
- + - 登录密码 + 中立字段
+ 是否内置数据
- + - 真实姓名 + 创建ID - + - 状态 + 创建者 - + - 备注 + 创建时间 - + - 创建时间 + 修改ID - + - 更新时间 + 更新者 - - - 最后登录时间 - - - - - 错误次数 - + + + 修改日期 + - + - 登录账号 + 数据版本 - + - 任务计划表 + 软删除 过滤器 - + - 任务名称 + 系统租户表
+ 根据TenantType 分为两种方案:
+ 1.按租户字段区分
+ 2.按租户分库
+ +
+ + 注意:
+ 使用租户Id方案,无需配置分库的连接
- + - 任务分组 + 名称 - + - 任务运行时间表达式 + 租户类型 - + - 任务所在DLL对应的程序集名称 + 数据库/租户标识 不可重复
+ 使用Id方案,可无需配置
- + - 任务所在类 + 主机
+ 使用Id方案,可无需配置
- + - 任务描述 + 数据库类型
+ 使用Id方案,可无需配置
- + - 执行次数 + 数据库连接
+ 使用Id方案,可无需配置
- + - 开始时间 + 状态 - + - 结束时间 + 备注 - + - 触发器类型(0、simple 1、cron) + 用户信息表 - + - 执行间隔时间, 秒为单位 + 登录账号 - + - 循环执行次数 + 登录密码 - + - 是否启动 + 真实姓名 - + - 执行传参 + 状态 - + - 创建时间 + 部门 - + - 任务内存中的状态 + 备注 - + - Tibug 类别 + 创建时间 - + - Tibug 博文 + 更新时间 - + - 用户跟角色关联表 + 关键业务修改时间 - + - 获取或设置是否禁用,逻辑上的删除,非物理删除 + 最后异常时间 - + + + 错误次数 + + + - 创建ID + 登录账号 - + - 创建者 + 租户Id - + - 创建时间 + 任务日志表 - + - 修改ID + 任务ID - + - 修改者 + 任务耗时 - + - 修改时间 + 执行结果(0-失败 1-成功) - + - ID + 运行时间 - + - 菜单与按钮关系表 - 父类 + 结束时间 - + - 菜单ID + 执行参数 - + - 按钮ID + 异常信息 - + - 接口API地址信息表 - 父类 + 异常堆栈 - + - 父ID + 创建ID - + - 路由菜单表 + 创建者 - + - 上一级菜单(0表示上一级无菜单) + 创建时间 - + - 接口api + 修改ID - + - 按钮跟权限关联表 - 父类 + 修改者 - + - 角色ID + 修改时间 - + - 菜单ID + 任务名称 - + - api ID + 任务分组 - + - ID - 泛型主键Tkey + 任务计划表 - + - 用户信息表 + 任务名称 - + - uID - 泛型主键Tkey + 任务分组 - + - Tibug 博文 + 任务运行时间表达式 - + - 用户跟角色关联表 - 父类 + 任务所在DLL对应的程序集名称 - + - 用户ID + 任务所在类 - + - 角色ID + 任务描述 - + - 通用分页信息类 + 执行次数 - + - 当前页标 + 开始时间 - + - 总页数 + 结束时间 - + - 数据总数 + 触发器类型(0、simple 1、cron) - + - 每页大小 + 执行间隔时间, 秒为单位 - + - 返回数据 + 循环执行次数 - + - 无权限 + 已循环次数 - + - 找不到指定资源 + 是否启动 - + - 找不到指定资源 + 执行传参 - + - 异步添加种子数据 + 创建时间 - - - - + - 生成Controller层 + 任务内存中的状态 - sqlsugar实例 - 数据库链接ID - 数据库表名数组,默认空,生成所有表 - - - + - 生成Model层 + 业务数据
+ 多租户 (Id 隔离)
- sqlsugar实例 - 数据库链接ID - 数据库表名数组,默认空,生成所有表 - -
- + - 生成IRepository层 + 无需手动赋值 - sqlsugar实例 - 数据库链接ID - - 数据库表名数组,默认空,生成所有表 - - + - 生成 IService 层 + 名称 - sqlsugar实例 - 数据库链接ID - - 数据库表名数组,默认空,生成所有表 - - + - 生成 Repository 层 + 金额 - sqlsugar实例 - 数据库链接ID - - 数据库表名数组,默认空,生成所有表 - - + - 生成 Service 层 + 多租户-多表方案 业务表 子表
- sqlsugar实例 - 数据库链接ID - - 数据库表名数组,默认空,生成所有表 -
- + - 功能描述:根据数据库表生产Controller层 - 作  者:Blog.Core + 多租户-多表方案 业务表
- - 数据库链接ID - 实体类存放路径 - 命名空间 - 生产指定的表 - 实现接口 - - 是否序列化
- + - 功能描述:根据数据库表生产Model层 - 作  者:Blog.Core + 名称 - - 数据库链接ID - 实体类存放路径 - 命名空间 - 生产指定的表 - 实现接口 - - 是否序列化 - + - 功能描述:根据数据库表生产IRepository层 - 作  者:Blog.Core + 金额 - - 数据库链接ID - 实体类存放路径 - 命名空间 - 生产指定的表 - 实现接口 - - + - 功能描述:根据数据库表生产IServices层 - 作  者:Blog.Core + 多租户-多库方案 业务表
+ 公共库无需标记[MultiTenant]特性
- - 数据库链接ID - 实体类存放路径 - 命名空间 - 生产指定的表 - 实现接口 -
- + - 功能描述:根据数据库表生产 Repository 层 - 作  者:Blog.Core + 名称 - - 数据库链接ID - 实体类存放路径 - 命名空间 - 生产指定的表 - 实现接口 - - + - 功能描述:根据数据库表生产 Services 层 - 作  者:Blog.Core + 金额 - - 数据库链接ID - 实体类存放路径 - 命名空间 - 生产指定的表 - 实现接口 - - + - 根据模板内容批量生成文件 + Tibug 类别 - 类文件字符串list - 生成路径 - 文件名格式模板 - + - 连接字符串 - Blog.Core + Tibug 博文 - + - 连接字符串 - Blog.Core + 用户跟角色关联表 - + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + - 数据库类型 - Blog.Core + 创建ID - + - 数据连接对象 - Blog.Core + 创建者 - + - 功能描述:构造函数 - 作  者:Blog.Core + 创建时间 - + - 功能描述:获取数据库处理对象 - 作  者:Blog.Core + 修改ID - 返回值 - + - 功能描述:获取数据库处理对象 - 作  者:Blog.Core + 修改者 - db - 返回值 - + - 功能描述:根据实体类生成数据库表 - 作  者:Blog.Core + 修改时间 - 是否备份表 - 指定的实体 - + - 功能描述:根据实体类生成数据库表 - 作  者:Blog.Core + - 是否备份表 - 指定的实体 - + - 功能描述:设置初始化参数 - 作  者:Blog.Core - - 连接字符串 - 数据库类型 + 公司ID + - + - 功能描述:创建一个链接配置 - 作  者:Blog.Core + 公司名称 - 是否自动关闭连接 - 是否夸类事务 - ConnectionConfig - + - 功能描述:获取一个自定义的DB - 作  者:Blog.Core + 公司IP - config - 返回值 - + - 功能描述:获取一个自定义的数据库处理对象 - 作  者:Blog.Core + 公司备注 - sugarClient - 返回值 - + - 功能描述:获取一个自定义的数据库处理对象 - 作  者:Blog.Core + api地址 - config - 返回值 - + - 表格数据,支持分页 + 是否激活 - + - 返回编码 + 创建者id - + - 返回信息 + 创建人 - + - 记录总数 + 创建时间 - + - 返回数据集 + 修改者id - + - 广告类 + 修改人 - + - 分类ID + 修改时间 - + - 创建时间 + - + - 广告图片 + 微信公众号唯一标识 - + - 广告标题 + 微信公众号名称 - + - 广告链接 + 微信账号 - + - 备注 + 微信名称 - + - 博客信息展示类 + 应用ID - + - + 应用秘钥 - - 创建人 - + + + 公众号推送token - - 博客标题 - + + + 验证秘钥(验证消息是否真实) - - 摘要 - + + + 微信公众号token过期时间 - + - 上一篇 + 备注 - + - 上一篇id + 是否激活 - + - 下一篇 + 创建者id - + - 下一篇id + 创建人 - - 类别 - + + + 创建时间 - - 内容 - + + + 修改者id - + - 访问量 + 修改人 - + - 评论数量 + 修改时间 - - 修改时间 + + - + - 创建时间 + 推送ID - - 备注 - + + + 来自谁 - + - 留言信息展示类 + 推送IP - - 留言表 - + + + 推送客户 - - 博客ID - + + + 推送用户 - - 创建时间 - + + + 推送模板ID - - 手机 - + + + 推送内容 - - qq - + + + 推送时间 - - 留言内容 - + + + 推送状态(Y/N) - - ip地址 - + + + 备注 - - 是否显示在前台,0否1是 - + + + 推送OpenID - + - 商户号 - + 推送微信公众号 + - + - 柜台号 - + 创建者id + - + - 分行号 - + 创建人 + - + - 集团商户信息 - + 创建时间 + - + - 交易码 - + 修改者id + - + - 商户类型 - + 修改人 + + + + + 修改时间 + + + + + + + + + + 主键id,ticket + + + + + 需要绑定的公司 + + + + + 需要绑定的员工id + + + + + 需要绑定的员工昵称 + + + + + 创建时间 + + + + + 关联的公众号 + + + + + 是否已使用 + + + + + 使用时间 + + + + + 关联的微信用户id + + + + + 创建者id + + + + + 创建人 + + + + + 创建时间 + + + + + 修改者id + + + + + 修改人 + + + + + 修改时间 + + + + + + + + + + 来自哪个公众号 + + + + + 绑定公司id + + + + + 绑定员工id + + + + + 绑定微信id + + + + + 绑定微信联合id + + + + + 绑定时间 + + + + + 更新时间 + + + + + 备注 + + + + + 是否已解绑 + + + + + 上次绑定微信id + + + + + 创建者id + + + + + 创建人 + + + + + 创建时间 + + + + + 修改者id + + + + + 修改人 + + + + + 修改时间 + + + + + + + + + + 文件ID + + + + + 文件名称 + + + + + 文件大小 + + + + + 文件类型 + + + + + 文件拓展名 + + + + + 文件位置 + + + + + 文件上传时间 + + + + + 文件备注 + + + + + 创建者id + + + + + 创建人 + + + + + 创建时间 + + + + + 修改者id + + + + + 修改人 + + + + + 修改时间 + + + + + 部门表 + + + + + 上一级(0表示无上一级) + + + + + 接口API地址信息表 + 父类 + + + + + 父ID + + + + + 路由菜单表 + + + + + 上一级菜单(0表示上一级无菜单) + + + + + 接口api + + + + + 按钮跟权限关联表 + 父类 + + + + + 角色ID + + + + + 菜单ID + + + + + api ID + + + + + ID + 泛型主键Tkey + + + + + 用户信息表 + + + + + Id + 泛型主键Tkey + + + + + Tibug 博文 + + + + + 用户跟角色关联表 + 父类 + + + + + 用户ID + + + + + 角色ID + + + + + 通用分页信息类 + + + + + 当前页标 + + + + + 总页数 + + + + + 数据总数 + + + + + 每页大小 + + + + + 返回数据 + + + + + 所需分页参数 + 作者:胡丁文 + 时间:2020-4-3 20:31:26 + + + + + 当前页 + + + + + 每页大小 + + + + + 排序字段(例如:id desc,time asc) + + + + + 查询条件( 例如:id = 1 and name = 小明) + + + + + 无权限 + + + + + 找不到指定资源 + + + + + 找不到指定资源 + + + + + 数据库读取类型 + + + + + 表格数据,支持分页 + + + + + 返回编码 + + + + + 返回信息 + + + + + 记录总数 + + + + + 返回数据集 + + + + + 租户模型接口 + + + + + 租户Id + + + + + 标识 多租户 的业务表
+ 默认设置是多库
+ 公共表无需区分 直接使用主库 各自业务在各自库中
+
+
+ + + 租户隔离方案 + + + + + Id隔离 + + + + + 库隔离 + + + + + 表隔离 + + + + + 广告类 + + + + + 分类ID + + + + + 创建时间 + + + + + 广告图片 + + + + + 广告标题 + + + + + 广告链接 + + + + + 备注 + + + + + 博客信息展示类 + + + + + + + + + 创建人 + + + + + 博客标题 + + + + + 摘要 + + + + + + 上一篇 + + + + + 上一篇id + + + + + 下一篇 + + + + + 下一篇id + + + + 类别 + + + + + 内容 + + + + + + 访问量 + + + + + 评论数量 + + + + 修改时间 + + + + + + 创建时间 + + + + 备注 + + + + + + Type Description balabala + + + + + 留言信息展示类 + + + + 留言表 + + + + + 博客ID + + + + + 创建时间 + + + + + 手机 + + + + + qq + + + + + 留言内容 + + + + + ip地址 + + + + + 是否显示在前台,0否1是 + + + + + + 商户号 + + + + + 柜台号 + + + + + 分行号 + + + + + 集团商户信息 + + + + + 交易码 + + + + + 商户类型 + - 终端编号 1 - + 终端编号 1 +
+
+ + + 终端编号 2 + + + + + 订单号 + + + + + 码信息(一维码、二维码) + + + + + 订单金额,单位:元 + + + + + 商品名称 + + + + + 备注 1 + + + + + 备注 2 + + + + + 分账信息一 + + + + + 分账信息二 + + + + + 子商户公众账号 ID + + + + + 返回信息位图 + + + + + 实名支付 + + + + + 商品详情 + + + + + 订单优惠标记 + + + + + 公钥 + + + + + 请求地址 + + + + + 是否删除空值 + + + + + 退款参数 + + + + + 订单ID + + + + + 商品名称 + + + + + 支付金额(小数点最多两位) + + + + + 二维码/条码信息 + + + + + 备注信息1 + + + + + 备注信息2 + + + + + 订单参数 + + + + + 订单号 + + + + + 退款金额 + + + + + 退款流水号(可选) + + + + + 退款返回消息 + + + + + 序列号 + + + + + 商户号 + + + + + 交易码 + + + + + 返回码 + + + + + 返回码说明 + + + + + 语言 + + + + + 订单信息 + + + + + 订单信息 + + + + + 订单号 + + + + + 支付金额 + + + + + 退款金额 + + + + + 备注1 + + + + + 备注2 + + + + + 退款返回结果消息 + + + + + 订单号 + + + + + 支付金额 + + + + + 退款金额 + + + + + 序列号 + + + + + 商户号 + + + + + 交易码 + + + + + 返回码 + + + + + 返回码说明 + + + + + 语言 + + + + + 支付结果dto + + + + + 支付结果 + Y:成功 + N:失败 + U:不确定 + Q:待轮询 + + + + + 订单ID + + + + + 支付金额 + + + + + 二维码类型 + 1:龙支付 + 2:微信 + 3:支付宝 + 4:银联 + + + + + 等待时间-轮询等待时间 + + + + + 全局事件跟踪号-建行交易流水号 + + + + + 错误码 + + + + + 错误信息 + + + + + 验证签名-防止伪造攻击 + + + + + 返回支付结果 + + + + + 发起的订单ID + + + + + 返回支付的金额 + + + + + 返回支付的类型 1:龙支付 2:微信 3:支付宝 4:银联 + + + + + 返回建行的流水号 + + + + + 错误代码 + + + + + 错误信息 + + + + + 实现IJob的类 + + + + + 命名空间 + + + + + 类名 + + + + + 备注 + + + + + 服务器VM + + + + + 环境变量 + + + + + 系统架构 + + + + + ContentRootPath + + + + + WebRootPath + + + + + .NET Core版本 + + + + + 内存占用 + + + + + 启动时间 + + + + + 菜单展示model + + + + + 调度任务触发器信息实体 + + + + + 任务ID + + + + + 任务名称 + + + + + 任务分组 + + + + + 触发器ID + + + + + 触发器名称 + + + + + 触发器分组 + + + + + 触发器状态 + + + + + 用来测试 RestSharp Get 请求 + + + + + + + + + + + + + + + 用来测试 RestSharp Post 请求 + + + + + 留言排名展示类 + + + + 博客ID + + + + + + 评论数量 + + + + 博客标题 + + + + + + 微信接口消息DTO + 作者:胡丁文 + 时间:2020-03-25 + + + + + 微信公众号ID(数据库查询) + + + + + 错误代码 + + + + + 错误信息 + + + + + token + + + + + 过期时间(秒) + + + + + 用户关注数 + + + + + 获取用户数量 + + + + + 获取用户OpenIDs + + + + + 下一个关注用户 + + + + + 微信消息模板列表 + + + + + 微信菜单 + + + + + 二维码票据 + + + + + 二维码过期时间 + + + + + 二维码地址 + + + + + 关注状态 + + + + + 用户微信ID + + + + + 昵称 + + + + + 性别 + + + + + 语言 + + + + + 城市 + + + + + 省份 + + + + + 城市 + + + + + 头像地址 + - + - 终端编号 2 - + 微信推送消息Dto + 作者:胡丁文 + 时间:2020-4-8 09:16:16 +
- + - 订单号 - + 推送关键信息 +
- + - 码信息(一维码、二维码) - + 推送卡片消息Dto +
- + - 订单金额,单位:元 - + 微信推送消息Dto + 作者:胡丁文 + 时间:2020-11-23 16:29:05 +
- + - 商品名称 - + 推送关键信息 +
- + - 备注 1 - + 推送卡片消息Dto +
- + - 备注 2 - + 消息模板dto(如何填写数据,请参考微信模板即可) + 作者:胡丁文 + 时间:2020-4-1 09:32:16 +
- + - 分账信息一 - + 消息模板 +
- + - 分账信息二 - + 标题 + - + - 子商户公众账号 ID - + 标题颜色(颜色代码都必须为#开头的16进制代码) + - + - 返回信息位图 - + 内容1 + - + - 实名支付 - + 内容1颜色 + - + - 商品详情 - + 内容2 + - + - 订单优惠标记 - + 内容2颜色 + - + - 公钥 + 内容3 - + - 请求地址 + 内容3颜色 - + - 是否删除空值 + 内容4 - + - 退款参数 + 内容4颜色 - + - 订单ID + 内容5 - + - 商品名称 + 内容5颜色 - + - 支付金额(小数点最多两位) + 备注信息 - + - 二维码/条码信息 + 备注信息颜色 - + - 备注信息1 + 跳转连接 - + - 备注信息2 + 获取微信菜单DTO,用于存放具体菜单内容 - + - 订单参数 + 获取微信菜单DTO - + - 订单号 + 按钮列表(最多三个) - + - 退款金额 + 微信OpenID列表Dto - + - 退款流水号(可选) + 推送详细数据 + 作者:胡丁文 + 时间:2020-4-8 09:16:16 - + - 退款返回消息 + 推送给微信所需Dto + 作者:胡丁文 + 时间:2020-4-8 09:16:16 - + - 序列号 + 推送微信用户ID - + - 商户号 + 推送的模板ID - + - 交易码 + 推送URL地址 - + - 返回码 + 推送的数据 - + - 返回码说明 + 微信keyword所需Dto + 作者:胡丁文 + 时间:2020-4-8 09:18:08 - + - 语言 + 内容 - + - 订单信息 + 文字颜色 - + - 订单信息 + 图文链接标题 - + - 订单号 + 图文描述 - + - 支付金额 + 访问URL - + - 退款金额 + 图片URL - + - 备注1 + 图片mediaID - + - 备注2 + 推送模拟消息Dto + 作者:胡丁文 + 时间:2020-4-24 14:52:44 - + - 退款返回结果消息 + 当前选中的微信公众号 - + - 订单号 + 当前选中的操作集合 - + - 支付金额 + 当前选中的绑定还是订阅 - + - 退款金额 + 当前选中的微信客户 - + - 序列号 + 当前选中的消息类型 - + - 商户号 + 当前选中要发送的用户 - + - 交易码 + 文本消息 - + - 返回码 + 图片消息 - + - 返回码说明 + 语音消息 - + - 语言 + 视频消息 - + - 支付结果dto + 链接消息 - + - 支付结果 - Y:成功 - N:失败 - U:不确定 - Q:待轮询 + 文字消息 - + - 订单ID + 视频标题 - + - 支付金额 + 视频封面mediaID - + - 二维码类型 - 1:龙支付 - 2:微信 - 3:支付宝 - 4:银联 + 视频mediaID - + - 等待时间-轮询等待时间 + 语音mediaID - + - 全局事件跟踪号-建行交易流水号 + 微信二维码预装发送信息dto - + - 错误码 + 微信二维码预装具体消息 - + - 错误信息 + 微信二维码预装信息DTO - + - 验证签名-防止伪造攻击 + 返回给调用者的Dto + 作者:胡丁文 + 时间:2020-4-8 09:52:06 - + - 返回支付结果 + 微信公众号ID - + - 发起的订单ID + 公司代码 - + - 返回支付的金额 + 数据 - + - 返回支付的类型 1:龙支付 2:微信 3:支付宝 4:银联 + 微信消息模板Dto - + - 返回建行的流水号 + 微信推送所需信息(公司版本) + 作者:胡丁文 + 时间:2020-4-8 09:04:36 - + - 错误代码 + 微信公众号ID - + - 错误信息 + 公司代码 - + - 实现IJob的类 + 用户id - + - 命名空间 + 用户昵称 - + - 类名 + 微信推送所需信息(OpenID版本) + 作者:胡丁文 + 时间:2020-11-23 16:27:29 - + - 备注 + 微信公众号ID - + - 服务器VM + 微信OpenID - + - 环境变量 + 微信验证Dto + 作者:胡丁文 + 时间:2020-4-1 21:34:07 + + + + + 微信公众号唯一标识 - + - 系统架构 + 验证成功后返回给微信的字符串 - + - ContentRootPath + 签名 - + - WebRootPath + 时间戳 - + - .NET Core版本 + 随机数 - + - 内存占用 + 微信XmlDto + 作者:胡丁文 + 时间:2020-4-3 20:31:26 + + + + + 微信公众号唯一表示 - + - 启动时间 + 微信开发者 + + + + + 来自谁 + + + + + 创建时间 - + - 菜单展示model + 消息类型 + + + + + 文字内容 + + + + + 消息ID - + - 调度任务触发器信息实体 + 消息事件 + + + + + 事件key值 + + + + + 图片地址 - + - 任务ID + 多媒体ID - + - 任务名称 + 格式 - + - 任务分组 + 语音失败 - + - 触发器ID + 缩略媒体ID - + - 触发器名称 + 地理位置维度 - + - 触发器分组 + 地理位置经度 - + - 触发器状态 + 地图缩放大小 - + - 用来测试 RestSharp Get 请求 + 地理位置信息 - + - + 消息标题 - + - + 消息描述 - + - 用来测试 RestSharp Post 请求 + 消息链接 - + - 留言排名展示类 + 二维码的ticket,可用来换取二维码图片 - - 博客ID - + + + 地理位置纬度 - + - 评论数量 + 地理位置经度 - - 博客标题 - + + + 地理位置精度 diff --git a/Blog.Core.Api/Blog.Core.xml b/Blog.Core.Api/Blog.Core.xml index 87df176e..0ed61c91 100644 --- a/Blog.Core.Api/Blog.Core.xml +++ b/Blog.Core.Api/Blog.Core.xml @@ -26,14 +26,14 @@ - + 获取博客详情 - + 获取详情【无权限】 @@ -67,7 +67,7 @@ - + 删除博客 @@ -137,6 +137,12 @@ + + + 权限数据库导出excel + + + 健康检查 @@ -153,19 +159,17 @@ 图片管理 - + 下载图片(支持中文字符) - - + - 上传图片,多文件,可以使用 postman 测试, - 如果是单文件,可以 参数写 IFormFile file1 + 上传图片,多文件 - + @@ -173,7 +177,7 @@ 登录管理【无权限】 - + 构造函数注入 @@ -182,6 +186,7 @@ + @@ -232,12 +237,25 @@ + + + swagger登录 + + + + + + + weixin登录 + + + 接口管理 - + 获取全部接口api @@ -259,13 +277,20 @@ - + 删除一条接口 + + + 导入多条接口信息 + + + + 服务器配置信息 @@ -337,7 +362,7 @@ 菜单管理 - + 构造函数 @@ -345,19 +370,22 @@ + + - + 获取菜单 + - + 查询树形 Table @@ -379,7 +407,7 @@ - + 获取菜单树 @@ -387,16 +415,23 @@ - + + + 获取路由树 + + + + + 获取路由树 - + - 通过角色获取菜单【无权限】 + 通过角色获取菜单 @@ -408,13 +443,26 @@ - + 删除菜单 + + + 导入多条菜单信息 + + + + + + + 系统接口菜单同步接口 + + + 角色管理 @@ -442,7 +490,7 @@ - + 删除角色 @@ -471,42 +519,42 @@ - + 删除一个任务 - + 启动计划任务 - + 停止一个计划任务 - + 暂停一个计划任务 - + 恢复一个计划任务 - + 重启一个计划任务 @@ -519,13 +567,25 @@ - + 立即执行任务 + + + 获取任务运行日志 + + + + + + 任务概况 + + + 类别管理【无权限】 @@ -566,7 +626,7 @@ - + 获取详情【无权限】 @@ -587,14 +647,14 @@ - + 删除 bug - + 测试事务在AOP中的使用 @@ -606,15 +666,17 @@ 用户管理 - + 构造函数 - + + + @@ -633,21 +695,21 @@ 令牌 - + 添加一个用户 - + 更新用户与角色 - + 删除用户 @@ -659,12 +721,13 @@ 用户角色关系 - + 构造函数 + @@ -682,7 +745,7 @@ - + 新建用户角色关系 @@ -695,19 +758,23 @@ Values控制器 - + - ValuesController + 测试Rabbit消息队列发送 + + + + + 测试Rabbit消息队列订阅 - - - - - - - - - + + + + 测试SqlSugar二级缓存 + 可设置过期时间 + 或通过接口方式更新该数据,也会离开清除缓存 + + @@ -776,28 +843,17 @@ - - - 测试http请求 RestSharp Get - - - - - - 测试http请求 RestSharp Post - - - 测试多库连接 - + - 测试http请求 WebApiClient Get + 测试Fulent做参数校验 + @@ -823,6 +879,347 @@ 通过此处的key格式为 xx:xx:x + + + 获取雪花Id + + + + + + 测试缓存 + + + + + + 雪花Id To DateTime + + + + + + + WeChatCompanyController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + WeChatConfigController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + 微信公众号管理 + + + + + 构造函数 + + + + + + + 更新Token + + + + + + + 刷新Token + + + + + + + 获取模板 + + + + + + + 获取菜单 + + + + + + + 更新菜单 + + + + + + + 获取订阅用户(所有) + + + + + + + 入口 + + + + + + + 获取订阅用户 + + + + + + + + 获取一个绑定员工公众号二维码 + + 消息 + + + + + 推送卡片消息接口 + + 卡片消息对象 + + + + + 推送卡片消息接口 + + 卡片消息对象 + + + + + 推送文本消息 + + 消息对象 + + + + + 通过绑定用户获取微信用户信息(一般用于初次绑定检测) + + 信息 + + + + + 用户解绑 + + 消息 + + + + + WeChatPushLogController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + WeChatSubController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + 查询树形 Table + + 父节点 + 关键字 + + + + + 获取部门树 + + + + 服务管理 @@ -863,28 +1260,339 @@ - + - 自定义路由 /api/{version}/[controler]/[action] + SignalR测试 - + - 分组名称,是来实现接口 IApiDescriptionGroupNameProvider + 向指定用户发送消息 + + + - + - 自定义路由构造函数,继承基类路由 + 向指定角色发送消息 - + + + - + - 自定义版本+路由构造函数,继承基类路由 + 分表demo + + + + + 分页获取数据 + + + + + + + + + + + 根据ID获取信息 + + + + + + + 添加一条测试数据 + + + + + + + 修改一条测试数据 + + + + + + + 根据id删除数据 + + + + + + + SqlSugar 相关测试 + + + + + SqlSugar 相关测试 + + + + + 测试建表后,SqlSugar缓存 + + + + + + 缓存管理 + + + + + 缓存管理 + + + + + 获取全部缓存 + + + + + + 获取缓存 + + + + + + 新增 + + + + + + 删除全部缓存 + + + + + + 删除缓存 + + + + + + 数据库管理 + + + + + 获取库配置 + + + + + + 获取表信息 + + 配置Id + 读取类型 + + + + + 获取表字段 + + 表名 + ConfigId + 读取类型 + + + + + 编辑表备注 + + + + + + 编辑列备注 + + + + + + 动态建表 CURD + + + + + 动态type + + + + + + 动态type 继承BaseEntity + + + + + + 测试建表 + + + + + + 测试查询 + + + + + + 测试写入 + + + + + + 多租户-多库方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增数据 + + + + + + 多租户-Id方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增业务数据 + + + + + + 多租户-多表方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增数据 + + + + + + 租户管理 + + + + + 获取全部租户 + + + + + + 获取租户信息 + + + + + + 新增租户信息
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 修改租户信息
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 删除租户
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 枚举测试 + + + + + 获取学生信息 + + 学生类型 + + + 学生信息 + + + + 学生类型 + + + + + 小学生 + + + + + 中学生 + + + + + 大学生 + + + + + 学生姓名 + + + + + 学生年龄 + + + + + 学生类型 - - @@ -927,11 +1635,28 @@ 全局路由前缀公约 - + - 根据环境变量定向配置文件名称 + 自定义路由 /api/{version}/[controler]/[action] - + + + + 分组名称,是来实现接口 IApiDescriptionGroupNameProvider + + + + + 自定义路由构造函数,继承基类路由 + + + + + + 自定义版本+路由构造函数,继承基类路由 + + +
diff --git a/Blog.Core.Api/Controllers/BaseApiController.cs b/Blog.Core.Api/Controllers/BaseApiController.cs new file mode 100644 index 00000000..97a938ea --- /dev/null +++ b/Blog.Core.Api/Controllers/BaseApiController.cs @@ -0,0 +1,88 @@ +using Blog.Core.Model; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; + +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 = "获取成功") + { + 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/BaseApiCpntroller.cs b/Blog.Core.Api/Controllers/BaseApiCpntroller.cs deleted file mode 100644 index 92f9db16..00000000 --- a/Blog.Core.Api/Controllers/BaseApiCpntroller.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Blog.Core.Model; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; - -namespace Blog.Core.Controllers -{ - public class BaseApiCpntroller : Controller - { - [NonAction] - public MessageModel Success(T data, string msg = "成功") - { - return new MessageModel() - { - success = true, - 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, List data, int pageCount, string msg = "获取成功") - { - - return new MessageModel>() - { - success = true, - msg = msg, - response = new PageModel() - { - page = page, - dataCount = dataCount, - data = data, - pageCount = pageCount, - } - }; - } - [NonAction] - public MessageModel> SuccessPage(PageModel pageModel, string msg = "获取成功") - { - - return new MessageModel>() - { - success = true, - msg = msg, - response = new PageModel() - { - page = pageModel.page, - dataCount = pageModel.dataCount, - data = pageModel.data, - pageCount = pageModel.pageCount, - } - }; - } - } -} diff --git a/Blog.Core.Api/Controllers/BlogController.cs b/Blog.Core.Api/Controllers/BlogController.cs index 24860971..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; @@ -22,7 +19,7 @@ namespace Blog.Core.Controllers /// [Produces("application/json")] [Route("api/Blog")] - public class BlogController : BaseApiCpntroller + public class BlogController : BaseApiController { public IBlogArticleServices _blogArticleServices { get; set; } private readonly ILogger _logger; @@ -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()) @@ -132,7 +130,7 @@ public async Task>> GetBlogsByTypesForMVP(string { if (types.IsNotEmptyOrNull()) { - var blogs = await _blogArticleServices.Query(d => d.bcategory != null && types.Contains(d.bcategory) && d.IsDeleted == false); + var blogs = await _blogArticleServices.Query(d => d.bcategory != null && types.Contains(d.bcategory) && d.IsDeleted == false, d => d.bID, false); return Success(blogs); } return Success(new List() { }); @@ -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) { @@ -233,7 +231,9 @@ public async Task> Put([FromBody] BlogArticle BlogArticle) model.btraffic = BlogArticle.btraffic; if (await _blogArticleServices.Update(model)) - Success(BlogArticle?.bID.ObjToString()); + { + return Success(BlogArticle?.bID.ObjToString()); + } } } return Failed("更新失败"); @@ -249,11 +249,15 @@ 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) { var blogArticle = await _blogArticleServices.QueryById(id); + if (blogArticle == null) + { + return Failed("查询无数据"); + } blogArticle.IsDeleted = true; return await _blogArticleServices.Update(blogArticle) ? Success(blogArticle?.bID.ObjToString(), "删除成功") : Failed("删除失败"); } diff --git a/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs index cad64362..553dee75 100644 --- a/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs +++ b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs @@ -1,7 +1,7 @@ using Blog.Core.Common; using Blog.Core.Common.DB; +using Blog.Core.Common.Seed; using Blog.Core.Model; -using Blog.Core.Model.Seed; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; @@ -15,7 +15,7 @@ namespace Blog.Core.Controllers //[Authorize(Permissions.Name)] public class DbFirstController : ControllerBase { - private readonly SqlSugarClient _sqlSugarClient; + private readonly SqlSugarScope _sqlSugarClient; private readonly IWebHostEnvironment Env; /// @@ -23,7 +23,7 @@ public class DbFirstController : ControllerBase /// public DbFirstController(ISqlSugarClient sqlSugarClient, IWebHostEnvironment env) { - _sqlSugarClient = sqlSugarClient as SqlSugarClient; + _sqlSugarClient = sqlSugarClient as SqlSugarScope; Env = env; } @@ -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 730a9e8d..7865cc69 100644 --- a/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs +++ b/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs @@ -1,17 +1,20 @@ using Blog.Core.Common.Helper; -using Blog.Core.IRepository.UnitOfWork; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; +using Magicodes.ExporterAndImporter.Core; +using Magicodes.ExporterAndImporter.Excel; 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.Text; using System.Threading.Tasks; +using Blog.Core.Repository.UnitOfWorks; namespace Blog.Core.Controllers { @@ -20,31 +23,38 @@ namespace Blog.Core.Controllers //[Authorize(Permissions.Name)] public class MigrateController : ControllerBase { - private readonly IUnitOfWork _unitOfWork; + private readonly IUnitOfWorkManage _unitOfWorkManage; private readonly IRoleModulePermissionServices _roleModulePermissionServices; private readonly IUserRoleServices _userRoleServices; private readonly IRoleServices _roleServices; private readonly IPermissionServices _permissionServices; private readonly IModuleServices _moduleServices; + private readonly IDepartmentServices _departmentServices; + private readonly ISysUserInfoServices _sysUserInfoServices; private readonly IWebHostEnvironment _env; - public MigrateController(IUnitOfWork unitOfWork, + public MigrateController(IUnitOfWorkManage unitOfWorkManage, IRoleModulePermissionServices roleModulePermissionServices, IUserRoleServices userRoleServices, IRoleServices roleServices, IPermissionServices permissionServices, IModuleServices moduleServices, + IDepartmentServices departmentServices, + ISysUserInfoServices sysUserInfoServices, IWebHostEnvironment env) { - _unitOfWork = unitOfWork; + _unitOfWorkManage = unitOfWorkManage; _roleModulePermissionServices = roleModulePermissionServices; _userRoleServices = userRoleServices; _roleServices = roleServices; _permissionServices = permissionServices; _moduleServices = moduleServices; + _departmentServices = departmentServices; + _sysUserInfoServices = sysUserInfoServices; _env = env; } + /// /// 获取权限部分Map数据(从库) /// 迁移到新库(主库) @@ -54,24 +64,41 @@ public MigrateController(IUnitOfWork unitOfWork, public async Task> DataMigrateFromOld2New() { var data = new MessageModel() { success = true, msg = "" }; + var filterPermissionId = 122; if (_env.IsDevelopment()) { try { - // 获取权限集合数据 + var apiList = await _moduleServices.Query(d => d.IsDeleted == false); + var permissionsAllList = await _permissionServices.Query(d => d.IsDeleted == false); + var permissions = permissionsAllList.Where(d => d.Pid == 0).ToList(); var rmps = await _roleModulePermissionServices.GetRMPMaps(); + List pms = new(); + // 当然,你可以做个where查询 - //rmps = rmps.Where(d => d.ModuleId > 88).ToList(); + rmps = rmps.Where(d => d.PermissionId >= filterPermissionId).ToList(); - // 开启事务,保证数据一致性 - _unitOfWork.BeginTran(); + InitPermissionTree(permissions, permissionsAllList, apiList); - var rid = 0; - var pid = 0; - var mid = 0; - var rpmid = 0; + var actionPermissionIds = permissionsAllList.Where(d => d.Id >= filterPermissionId).Select(d => d.Id).ToList(); + List filterPermissionIds = new(); + FilterPermissionTree(permissionsAllList, actionPermissionIds, filterPermissionIds); + permissions = permissions.Where(d => filterPermissionIds.Contains(d.Id)).ToList(); + + // 开启事务,保证数据一致性 + _unitOfWorkManage.BeginTran(); // 注意信息的完整性,不要重复添加,确保主库没有要添加的数据 + + // 1、保持菜单和接口 + await SavePermissionTreeAsync(permissions, pms); + + long rid = 0; + long pid = 0; + long mid = 0; + long rpmid = 0; + + // 2、保存关系表 foreach (var item in rmps) { // 角色信息,防止重复添加,做了判断 @@ -89,22 +116,10 @@ public async Task> DataMigrateFromOld2New() } } - // 菜单 - if (item.Permission != null) - { - pid = await _permissionServices.Add(item.Permission); - Console.WriteLine($"Permission Added:{item.Permission.Name}"); - } - - // 接口 - if (item.Module != null) - { - mid = await _moduleServices.Add(item.Module); - Console.WriteLine($"Module Added:{item.Module.LinkUrl}"); - } - + 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 && mid > 0) + if (rid > 0 && pid > 0) { rpmid = await _roleModulePermissionServices.Add(new RoleModulePermission() { @@ -121,14 +136,14 @@ public async Task> DataMigrateFromOld2New() } - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); data.success = true; data.msg = "导入成功!"; } catch (Exception) { - _unitOfWork.RollbackTran(); + _unitOfWorkManage.RollbackTran(); } } @@ -152,28 +167,97 @@ public async Task> SaveData2TsvAsync() var data = new MessageModel() { success = true, msg = "" }; if (_env.IsDevelopment()) { - + JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat }; // 取出数据,序列化,自己可以处理判空 + var SysUserInfoJson = JsonConvert.SerializeObject(await _sysUserInfoServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "SysUserInfo.tsv"), SysUserInfoJson, Encoding.UTF8); + + var DepartmentJson = JsonConvert.SerializeObject(await _departmentServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Department.tsv"), DepartmentJson, Encoding.UTF8); + var rolesJson = JsonConvert.SerializeObject(await _roleServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); - FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Role_New.tsv"), rolesJson, Encoding.UTF8); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Role.tsv"), rolesJson, Encoding.UTF8); + + var UserRoleJson = JsonConvert.SerializeObject(await _userRoleServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "UserRole.tsv"), UserRoleJson, Encoding.UTF8); var permissionsJson = JsonConvert.SerializeObject(await _permissionServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); - FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Permission_New.tsv"), permissionsJson, Encoding.UTF8); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Permission.tsv"), permissionsJson, Encoding.UTF8); var modulesJson = JsonConvert.SerializeObject(await _moduleServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); - FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Modules_New.tsv"), modulesJson, Encoding.UTF8); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Modules.tsv"), modulesJson, Encoding.UTF8); var rmpsJson = JsonConvert.SerializeObject(await _roleModulePermissionServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); - FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "RoleModulePermission_New.tsv"), rmpsJson, Encoding.UTF8); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "RoleModulePermission.tsv"), rmpsJson, Encoding.UTF8); + + + + data.success = true; + data.msg = "生成成功!"; + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + + return data; + } + + + /// + /// 权限数据库导出excel + /// + /// + [HttpGet] + public async Task> SaveData2ExcelAsync() + { + var data = new MessageModel() { success = true, msg = "" }; + if (_env.IsDevelopment()) + { + + JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat + }; + + // 取出数据,序列化,自己可以处理判空 + IExporter exporter = new ExcelExporter(); + var SysUserInfoList = await _sysUserInfoServices.Query(d => d.IsDeleted == false); + var result = await exporter.ExportAsByteArray(SysUserInfoList); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.excel", "SysUserInfo.xlsx"), result); + + var DepartmentList = await _departmentServices.Query(d => d.IsDeleted == false); + var DepartmentResult = await exporter.ExportAsByteArray(DepartmentList); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.excel", "Department.xlsx"), DepartmentResult); + + var RoleList = await _roleServices.Query(d => d.IsDeleted == false); + var RoleResult = await exporter.ExportAsByteArray(RoleList); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.excel", "Role.xlsx"), RoleResult); + var UserRoleList = await _userRoleServices.Query(d => d.IsDeleted == false); + var UserRoleResult = await exporter.ExportAsByteArray(UserRoleList); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.excel", "UserRole.xlsx"), UserRoleResult); + + var PermissionList = await _permissionServices.Query(d => d.IsDeleted == false); + var PermissionResult = await exporter.ExportAsByteArray(PermissionList); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.excel", "Permission.xlsx"), PermissionResult); + + var ModulesList = await _moduleServices.Query(d => d.IsDeleted == false); + var ModulesResult = await exporter.ExportAsByteArray(ModulesList); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.excel", "Modules.xlsx"), ModulesResult); + + var RoleModulePermissionList = await _roleModulePermissionServices.Query(d => d.IsDeleted == false); + var RoleModulePermissionResult = await exporter.ExportAsByteArray(RoleModulePermissionList); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.excel", "RoleModulePermission.xlsx"), RoleModulePermissionResult); data.success = true; @@ -188,5 +272,88 @@ public async Task> SaveData2TsvAsync() return data; } + private void InitPermissionTree(List permissionsTree, List all, List apis) + { + foreach (var item in permissionsTree) + { + item.Children = all.Where(d => d.Pid == item.Id).ToList(); + item.Module = apis.FirstOrDefault(d => d.Id == item.Mid); + InitPermissionTree(item.Children, all, apis); + } + } + + 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(); + filterPermissionIds.AddRange(doneIds); + + var hasDoIds = permissionsAll.Where(d => actionPermissionId.Contains(d.Id) && d.Pid != 0).Select(d => d.Pid).ToList(); + if (hasDoIds.Any()) + { + FilterPermissionTree(permissionsAll, hasDoIds, filterPermissionIds); + } + } + + private async Task SavePermissionTreeAsync(List permissionsTree, List pms, long permissionId = 0) + { + var parendId = permissionId; + + foreach (var item in permissionsTree) + { + PM pm = new PM(); + // 保留原始主键id + pm.PidOld = item.Id; + pm.MidOld = (item.Module?.Id).ObjToLong(); + + long mid = 0; + // 接口 + if (item.Module != null) + { + var moduleModel = (await _moduleServices.Query(d => d.LinkUrl == item.Module.LinkUrl)).FirstOrDefault(); + if (moduleModel != null) + { + mid = moduleModel.Id; + } + else + { + mid = await _moduleServices.Add(item.Module); + } + pm.MidNew = mid; + Console.WriteLine($"Moudle Added:{item.Module.Name}"); + } + // 菜单 + if (item != null) + { + var permissionModel = (await _permissionServices.Query(d => d.Name == item.Name && d.Pid == item.Pid && d.Mid == item.Mid)).FirstOrDefault(); + item.Pid = parendId; + item.Mid = mid; + if (permissionModel != null) + { + permissionId = permissionModel.Id; + } + else + { + permissionId = await _permissionServices.Add(item); + } + + pm.PidNew = permissionId; + Console.WriteLine($"Permission Added:{item.Name}"); + } + pms.Add(pm); + + await SavePermissionTreeAsync(item.Children, pms, permissionId); + } + } + + + } + + public class PM + { + 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 new file mode 100644 index 00000000..faf1f850 --- /dev/null +++ b/Blog.Core.Api/Controllers/DepartmentController.cs @@ -0,0 +1,215 @@ +using Blog.Core.Common.Helper; +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; +using Newtonsoft.Json; +using System.Linq.Expressions; +using System.Text; + +namespace Blog.Core.Api.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public class DepartmentController : BaseApiController + { + private readonly IDepartmentServices _departmentServices; + private readonly IWebHostEnvironment _env; + + public DepartmentController(IDepartmentServices departmentServices, IWebHostEnvironment env) + { + _departmentServices = departmentServices; + _env = env; + } + + [HttpGet] + public async Task>> Get(int page = 1, string key = "", int intPageSize = 50) + { + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + + Expression> whereExpression = a => true; + + return new MessageModel>() + { + msg = "获取成功", + success = true, + response = await _departmentServices.QueryPage(whereExpression, page, intPageSize) + }; + + } + + [HttpGet("{id}")] + public async Task> Get(string id) + { + return new MessageModel() + { + msg = "获取成功", + success = true, + response = await _departmentServices.QueryById(id) + }; + } + + /// + /// 查询树形 Table + /// + /// 父节点 + /// 关键字 + /// + [HttpGet] + [AllowAnonymous] + public async Task>> GetTreeTable(long f = 0, string key = "") + { + List departments = new List(); + var departmentList = await _departmentServices.Query(d => d.IsDeleted == false); + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + + if (key != "") + { + departments = departmentList.Where(a => a.Name.Contains(key)).OrderBy(a => a.OrderSort).ToList(); + } + else + { + departments = departmentList.Where(a => a.Pid == f).OrderBy(a => a.OrderSort).ToList(); + } + + foreach (var item in departments) + { + List pidarr = new() { }; + var parent = departmentList.FirstOrDefault(d => d.Id == item.Pid); + + while (parent != null) + { + pidarr.Add(parent.Id); + parent = departmentList.FirstOrDefault(d => d.Id == parent.Pid); + } + + pidarr.Reverse(); + pidarr.Insert(0, 0); + item.PidArr = pidarr; + + item.hasChildren = departmentList.Where(d => d.Pid == item.Id).Any(); + } + + + return Success(departments, "获取成功"); + } + + /// + /// 获取部门树 + /// + /// + /// + [HttpGet] + public async Task> GetDepartmentTree(long pid = 0) + { + var departments = await _departmentServices.Query(d => d.IsDeleted == false); + var departmentTrees = (from child in departments + where child.IsDeleted == false + orderby child.Id + select new DepartmentTree + { + value = child.Id, + label = child.Name, + Pid = child.Pid, + order = child.OrderSort, + }).ToList(); + DepartmentTree rootRoot = new DepartmentTree + { + value = 0, + Pid = 0, + label = "根节点" + }; + + departmentTrees = departmentTrees.OrderBy(d => d.order).ToList(); + + + RecursionHelper.LoopToAppendChildren(departmentTrees, rootRoot, pid); + + return Success(rootRoot, "获取成功"); + } + + [HttpPost] + public async Task> Post([FromBody] Department request) + { + var data = new MessageModel(); + + var id = await _departmentServices.Add(request); + data.success = id > 0; + if (data.success) + { + data.response = id.ObjToString(); + data.msg = "添加成功"; + } + + return data; + } + + [HttpPut] + public async Task> Put([FromBody] Department request) + { + var data = new MessageModel(); + data.success = await _departmentServices.Update(request); + if (data.success) + { + data.msg = "更新成功"; + data.response = request?.Id.ObjToString(); + } + + return data; + } + + [HttpDelete] + public async Task> Delete(long id) + { + var data = new MessageModel(); + var model = await _departmentServices.QueryById(id); + model.IsDeleted = true; + data.success = await _departmentServices.Update(model); + if (data.success) + { + data.msg = "删除成功"; + data.response = model?.Id.ObjToString(); + } + + + return data; + } + + [HttpGet] + [AllowAnonymous] + public async Task> SaveData2Tsv() + { + var data = new MessageModel() { success = true, msg = "" }; + if (_env.IsDevelopment()) + { + + JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat + }; + + var rolesJson = JsonConvert.SerializeObject(await _departmentServices.Query(d => d.IsDeleted == false), microsoftDateFormatSettings); + FileHelper.WriteFile(Path.Combine(_env.WebRootPath, "BlogCore.Data.json", "Department_New.tsv"), rolesJson, Encoding.UTF8); + + data.success = true; + data.msg = "生成成功!"; + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + + return data; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/ImgController.cs b/Blog.Core.Api/Controllers/ImgController.cs index 716db28e..5ba85388 100644 --- a/Blog.Core.Api/Controllers/ImgController.cs +++ b/Blog.Core.Api/Controllers/ImgController.cs @@ -1,11 +1,6 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.Model; +using Blog.Core.Model; +using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Blog.Core.Controllers @@ -16,20 +11,28 @@ namespace Blog.Core.Controllers [Route("api/[controller]")] [ApiController] [Authorize] - public class ImgController : Controller + public class ImgController : BaseApiController { + + private readonly IWebHostEnvironment _env; + + public ImgController(IWebHostEnvironment webHostEnvironment) + { + _env = webHostEnvironment; + } + + // GET: api/Download /// /// 下载图片(支持中文字符) /// - /// /// [HttpGet] [Route("/images/Down/Pic")] - public FileStreamResult DownImg([FromServices] IWebHostEnvironment environment) + public FileStreamResult DownImg() { string foldername = ""; - string filepath = Path.Combine(environment.WebRootPath, foldername, "测试下载中文名称的图片.png"); + string filepath = Path.Combine(_env.WebRootPath, foldername, "测试下载中文名称的图片.png"); var stream = System.IO.File.OpenRead(filepath); string fileExt = ".jpg"; // 这里可以写一个获取文件扩展名的方法,获取扩展名 //获取文件的ContentType @@ -42,71 +45,50 @@ public FileStreamResult DownImg([FromServices] IWebHostEnvironment environment) } /// - /// 上传图片,多文件,可以使用 postman 测试, - /// 如果是单文件,可以 参数写 IFormFile file1 + /// 上传图片,多文件 /// - /// + /// /// [HttpPost] [Route("/images/Upload/Pic")] - public async Task> InsertPicture([FromServices] IWebHostEnvironment environment) + public async Task> InsertPicture([FromForm]UploadFileDto dto) { - var data = new MessageModel(); - string path = string.Empty; - string foldername = "images"; - IFormFileCollection files = null; - - - // 获取提交的文件 - files = Request.Form.Files; - // 获取附带的数据 - var max_ver = Request.Form["max_ver"].ObjToString(); - - - if (files == null || !files.Any()) { data.msg = "请选择上传的文件。"; return data; } + + if (dto.file == null || !dto.file.Any()) return Failed("请选择上传的文件。"); //格式限制 var allowType = new string[] { "image/jpg", "image/png", "image/jpeg" }; + + 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("图片过大"); - string folderpath = Path.Combine(environment.WebRootPath, foldername); + string foldername = "images"; + string folderpath = Path.Combine(_env.WebRootPath, foldername); if (!Directory.Exists(folderpath)) { Directory.CreateDirectory(folderpath); } - - if (files.Any(c => allowType.Contains(c.ContentType))) + foreach (var file in allowedFile) { - if (files.Sum(c => c.Length) <= 1024 * 1024 * 4) - { - //foreach (var file in files) - var file = files.FirstOrDefault(); - string strpath = Path.Combine(foldername, DateTime.Now.ToString("MMddHHmmss") + Path.GetFileName(file.FileName)); - path = Path.Combine(environment.WebRootPath, strpath); - - using (var stream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite)) - { - await file.CopyToAsync(stream); - } - - data = new MessageModel() - { - response = strpath, - msg = "上传成功", - success = true, - }; - return data; - } - else + string strpath = Path.Combine(foldername, DateTime.Now.ToString("MMddHHmmss") + Path.GetFileName(file.FileName)); + var path = Path.Combine(_env.WebRootPath, strpath); + + using (var stream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { - data.msg = "图片过大"; - return data; + await file.CopyToAsync(stream); } } - else + var excludeFiles = dto.file.Except(allowedFile); + + if (excludeFiles.Any()) { - data.msg = "图片格式错误"; - return data; + var infoMsg = $"{string.Join('、', excludeFiles.Select(c => c.FileName))} 图片格式错误"; + return Success(null, infoMsg); } + + return Success(null, "上传成功"); + } @@ -114,14 +96,14 @@ public async Task> InsertPicture([FromServices] IWebHostEnv [HttpGet] [Route("/images/Down/Bmd")] [AllowAnonymous] - public FileStreamResult DownBmd([FromServices] IWebHostEnvironment environment, string filename) + public FileStreamResult DownBmd(string filename) { if (string.IsNullOrEmpty(filename)) { return null; } // 前端 blob 接收,具体查看前端admin代码 - string filepath = Path.Combine(environment.WebRootPath, Path.GetFileName(filename)); + string filepath = Path.Combine(_env.WebRootPath, Path.GetFileName(filename)); if (System.IO.File.Exists(filepath)) { var stream = System.IO.File.OpenRead(filepath); diff --git a/Blog.Core.Api/Controllers/LoginController.cs b/Blog.Core.Api/Controllers/LoginController.cs index e007cc6d..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,9 +6,11 @@ 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 { @@ -24,14 +20,14 @@ namespace Blog.Core.Controllers [Produces("application/json")] [Route("api/Login")] [AllowAnonymous] - public class LoginController : BaseApiCpntroller + public class LoginController : BaseApiController { readonly ISysUserInfoServices _sysUserInfoServices; readonly IUserRoleServices _userRoleServices; readonly IRoleServices _roleServices; readonly PermissionRequirement _requirement; private readonly IRoleModulePermissionServices _roleModulePermissionServices; - + private readonly ILogger _logger; /// /// 构造函数注入 @@ -41,17 +37,22 @@ public class LoginController : BaseApiCpntroller /// /// /// - 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,15 +153,21 @@ public async Task> GetJwtToken3(string name = " pass = MD5Helper.MD5Encrypt32(pass); - var user = await _sysUserInfoServices.Query(d => d.uLoginName == name && d.uLoginPWD == pass && d.tdIsDelete == 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().uID.ToString()), - new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString()) }; + new Claim(JwtRegisteredClaimNames.Jti, user.FirstOrDefault().Id.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))); @@ -190,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(以旧换新) /// @@ -204,17 +219,27 @@ public async Task> RefreshToken(string token = if (string.IsNullOrEmpty(token)) return Failed("token无效,请重新登录!"); var tokenModel = JwtHelper.SerializeJwt(token); - if (tokenModel != null && tokenModel.Uid > 0) + if (tokenModel != null && JwtHelper.customSafeVerify(token) && tokenModel.Uid > 0) { var user = await _sysUserInfoServices.QueryById(tokenModel.Uid); - if (user != null) + var value = User.Claims.SingleOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value; + if (value != null && user.CriticalModifyTime > value.ObjToDate()) { - var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(user.uLoginName, user.uLoginPWD); + return Failed("很抱歉,授权已失效,请重新授权!"); + } + + if (user != null && !(value != null && user.CriticalModifyTime > value.ObjToDate())) + { + var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(user.LoginName, user.LoginPWD); //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色 - var claims = new List { - new Claim(ClaimTypes.Name, user.uLoginName), - new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ObjToString()), - 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))); //用户标识 @@ -225,6 +250,7 @@ public async Task> RefreshToken(string token = return Success(refreshToken, "获取成功"); } } + return Failed("认证失败!"); } @@ -239,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 { @@ -266,5 +293,54 @@ public string Md5Password(string password = "") { return MD5Helper.MD5Encrypt32(password); } + + /// + /// swagger登录 + /// + /// + /// + [HttpPost] + [Route("/api/Login/swgLogin")] + public async Task SwgLogin([FromBody] SwaggerLoginRequest loginRequest) + { + 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) + { + _logger.LogWarning(ex, "Swagger登录异常"); + } + + return new {result = false}; + } + + /// + /// weixin登录 + /// + /// + [HttpGet] + [Route("wxLogin")] + public dynamic WxLogin(string g = "", string token = "") + { + return new {g, token}; + } + } + + public class SwaggerLoginRequest + { + public string name { get; set; } + public string pwd { get; set; } } } \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/ModuleController.cs b/Blog.Core.Api/Controllers/ModuleController.cs index a7ad5e58..3b286808 100644 --- a/Blog.Core.Api/Controllers/ModuleController.cs +++ b/Blog.Core.Api/Controllers/ModuleController.cs @@ -1,6 +1,4 @@ -using System; -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; @@ -16,12 +14,12 @@ namespace Blog.Core.Controllers [Route("api/[controller]/[action]")] [ApiController] [Authorize(Permissions.Name)] - public class ModuleController : ControllerBase + public class ModuleController : BaseApiController { readonly IModuleServices _moduleServices; readonly IUser _user; - + public ModuleController(IModuleServices moduleServices, IUser user) { _moduleServices = moduleServices; @@ -36,24 +34,30 @@ 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)))); - var data = await _moduleServices.QueryPage(whereExpression, page, intPageSize, " Id desc "); + PageModel data = new PageModel(); - return new MessageModel>() + if (page == -1) + { + var modules = await _moduleServices.Query(whereExpression, " Id desc "); + data.data = modules; + } + else { - msg = "获取成功", - success = data.dataCount >= 0, - response = data - }; + data = await _moduleServices.QueryPage(whereExpression, page, pageSize, " Id desc "); + } + + + return Success(data, "获取成功"); + } @@ -73,20 +77,11 @@ public string Get(string id) [HttpPost] public async Task> Post([FromBody] Modules module) { - var data = new MessageModel(); - module.CreateId = _user.ID; module.CreateBy = _user.Name; - var id = (await _moduleServices.Add(module)); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } + return id > 0 ? Success(id.ObjToString(), "添加成功") : Failed(); - return data; } /// @@ -98,18 +93,22 @@ public async Task> Post([FromBody] Modules module) [HttpPut] public async Task> Put([FromBody] Modules module) { - var data = new MessageModel(); - if (module != null && module.Id > 0) - { - data.success = await _moduleServices.Update(module); - if (data.success) - { - data.msg = "更新成功"; - data.response = module?.Id.ObjToString(); - } - } - - return data; + //var data = new MessageModel(); + //if (module != null && module.Id > 0) + //{ + //data.success = await _moduleServices.Update(module); + //if (data.success) + //{ + // data.msg = "更新成功"; + // data.response = module?.Id.ObjToString(); + //} + + // } + + //return data; + if (module == null || module.Id <= 0) + return Failed("缺少参数"); + return await _moduleServices.Update(module) ? Success(module?.Id.ObjToString(), "更新成功") : Failed(); } /// @@ -119,22 +118,56 @@ public async Task> Put([FromBody] Modules module) /// // DELETE: api/ApiWithActions/5 [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { - var data = new MessageModel(); - if (id > 0) + if (id <= 0) + return Failed("缺少参数"); + var userDetail = await _moduleServices.QueryById(id); + if (userDetail == null) + return Failed("信息不存在"); + + userDetail.IsDeleted = true; + return await _moduleServices.Update(userDetail) ? Success(userDetail?.Id.ObjToString(), "删除成功") : Failed("删除失败"); + + //var data = new MessageModel(); + //if (id > 0) + //{ + // var userDetail = await _moduleServices.QueryById(id); + // userDetail.IsDeleted = true; + // data.success = await _moduleServices.Update(userDetail); + // if (data.success) + // { + // data.msg = "删除成功"; + // data.response = userDetail?.Id.ObjToString(); + // } + //} + //return data; + } + + /// + /// 导入多条接口信息 + /// + /// + /// + // POST: api/User + [HttpPost] + public async Task> BatchPost([FromBody] List modules) + { + string ids = string.Empty; + int sucCount = 0; + + for (int i = 0; i < modules.Count; i++) { - var userDetail = await _moduleServices.QueryById(id); - userDetail.IsDeleted = true; - data.success = await _moduleServices.Update(userDetail); - if (data.success) + var module = modules[i]; + if (module != null) { - data.msg = "删除成功"; - data.response = userDetail?.Id.ObjToString(); + module.CreateId = _user.ID; + module.CreateBy = _user.Name; + ids += (await _moduleServices.Add(module)); + sucCount++; } } - - return data; + return ids.IsNotEmptyOrNull() ? Success(ids, $"{sucCount}条数据添加成功") : Failed(); } } } diff --git a/Blog.Core.Api/Controllers/MonitorController.cs b/Blog.Core.Api/Controllers/MonitorController.cs index 28ff3265..d707d497 100644 --- a/Blog.Core.Api/Controllers/MonitorController.cs +++ b/Blog.Core.Api/Controllers/MonitorController.cs @@ -3,42 +3,36 @@ using Blog.Core.Common.LogHelper; using Blog.Core.Hubs; using Blog.Core.IServices; -using Blog.Core.Middlewares; 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 { [Route("api/[Controller]/[action]")] [ApiController] [AllowAnonymous] - public class MonitorController : BaseApiCpntroller + public class MonitorController : BaseApiController { private readonly IHubContext _hubContext; private readonly IWebHostEnvironment _env; 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,14 +44,14 @@ 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) + }, "获取服务器配置信息成功"); } @@ -69,71 +63,114 @@ public MessageModel Server() [HttpGet] public MessageModel> Get() { + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + _hubContext.Clients.All.SendAsync("ReceiveUpdate", "执行成功").Wait(); + } - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); - - return Success>(null); + return Success>(null, "执行成功"); } - [HttpGet] public MessageModel GetRequestApiinfoByWeek() { - return Success(LogLock.RequestApiinfoByWeek()); + //后续补充扩展Log + return Success(new RequestApiWeekView(), "成功"); } [HttpGet] public MessageModel GetAccessApiByDate() { - return new MessageModel() - { - msg = "获取成功", - success = true, - response = LogLock.AccessApiByDate() - }; + //return new MessageModel() + //{ + // msg = "获取成功", + // success = true, + // response = LogLock.AccessApiByDate() + //}; + + //后续补充扩展Log + return Success(new AccessApiDateView(), "获取成功"); } [HttpGet] public MessageModel GetAccessApiByHour() { - return new MessageModel() - { - msg = "获取成功", - success = true, - response = LogLock.AccessApiByHour() - }; + //return new MessageModel() + //{ + // msg = "获取成功", + // success = true, + // response = 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(); + //后续补充扩展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; } @@ -145,33 +182,42 @@ 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(); - return new MessageModel() + //return new MessageModel() + //{ + // msg = "获取成功", + // success = true, + // response = new WelcomeInitData() + // { + // activeUsers = activeUsers, + // activeUserCount = activeUsersCount, + // errorCount = errorCountToday, + // logs = Logs, + // activeCount = GetAccessLogsTrend(environment) + // } + //}; + + return Success(new WelcomeInitData() { - msg = "获取成功", - success = true, - response = new WelcomeInitData() - { - activeUsers = activeUsers, - activeUserCount = activeUsersCount, - errorCount = errorCountToday, - logs = Logs - } - }; + activeUsers = activeUsers, + activeUserCount = activeUsersCount, + errorCount = default, + logs = Logs, + activeCount = GetAccessLogsTrend(environment) + }, "获取成功"); } [HttpGet] @@ -179,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(); } @@ -199,22 +246,27 @@ public async Task> GetIds4Users() { apiDates.Add(new ApiDate() { - date = "没数据,或未开启相应接口服务", + date = "没数据,或未开启相应接口服务", count = 0 }); } - return new MessageModel() + //return new MessageModel() + //{ + // msg = "获取成功", + // success = true, + // response = new AccessApiDateView + // { + // columns = new string[] { "date", "count" }, + // rows = apiDates.OrderBy(d => d.date).ToList(), + // } + //}; + + return Success(new AccessApiDateView { - msg = "获取成功", - success = true, - response = new AccessApiDateView - { - columns = new string[] { "date", "count" }, - rows = apiDates.OrderBy(d => d.date).ToList(), - } - }; + columns = new string[] { "date", "count" }, + rows = apiDates.OrderBy(d => d.date).ToList(), + }, "获取成功"); } - } public class WelcomeInitData @@ -223,6 +275,6 @@ public class WelcomeInitData public int activeUserCount { get; set; } public List logs { get; set; } 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 6aac936a..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 { @@ -14,7 +13,7 @@ namespace Blog.Core.Api.Controllers [Produces("application/json")] [Route("api/[Controller]/[action]")] [Authorize(Permissions.Name)] - public class NacosController : BaseApiCpntroller + public class NacosController : BaseApiController { #region 变量 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 10f3c20e..ccdb3dc4 100644 --- a/Blog.Core.Api/Controllers/PermissionController.cs +++ b/Blog.Core.Api/Controllers/PermissionController.cs @@ -1,17 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.AuthHelper; +using Blog.Core.AuthHelper; using Blog.Core.AuthHelper.OverWrite; +using Blog.Core.Common; using Blog.Core.Common.Helper; using Blog.Core.Common.HttpContextUser; 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.Http; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Security.Claims; namespace Blog.Core.Controllers { @@ -21,12 +21,14 @@ namespace Blog.Core.Controllers [Route("api/[controller]/[action]")] [ApiController] [Authorize(Permissions.Name)] - public class PermissionController : ControllerBase + public class PermissionController : BaseApiController { + readonly IUnitOfWorkManage _unitOfWorkManage; readonly IPermissionServices _permissionServices; readonly IModuleServices _moduleServices; readonly IRoleModulePermissionServices _roleModulePermissionServices; readonly IUserRoleServices _userRoleServices; + private readonly IHttpClientFactory _httpClientFactory; readonly IHttpContextAccessor _httpContext; readonly IUser _user; private readonly PermissionRequirement _requirement; @@ -38,15 +40,23 @@ public class PermissionController : ControllerBase /// /// /// + /// + /// /// /// /// - public PermissionController(IPermissionServices permissionServices, IModuleServices moduleServices, IRoleModulePermissionServices roleModulePermissionServices, IUserRoleServices userRoleServices, IHttpContextAccessor httpContext, IUser user, PermissionRequirement requirement) + 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; + this._httpClientFactory = httpClientFactory; _httpContext = httpContext; _user = user; _requirement = requirement; @@ -57,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 单独处理 @@ -95,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 }; @@ -133,12 +128,14 @@ public async Task>> Get(int page = 1, string #endregion - return new MessageModel>() - { - msg = "获取成功", - success = permissions.dataCount >= 0, - response = permissions - }; + //return new MessageModel>() + //{ + // msg = "获取成功", + // success = permissions.dataCount >= 0, + // response = permissions + //}; + + return permissions.dataCount >= 0 ? Success(permissions, "获取成功") : Failed>("获取失败"); } @@ -150,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); @@ -171,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) @@ -191,19 +188,13 @@ public async Task>> GetTreeTable(int f = 0, string } - return new MessageModel>() - { - msg = "获取成功", - success = true, - response = permissions - }; - } - - // GET: api/User/5 - [HttpGet("{id}")] - public string Get(string id) - { - return "value"; + //return new MessageModel>() + //{ + // msg = "获取成功", + // success = true, + // response = permissions + //}; + return Success(permissions, "获取成功"); } /// @@ -215,20 +206,21 @@ public string Get(string id) [HttpPost] public async Task> Post([FromBody] Permission permission) { - var data = new MessageModel(); + //var data = new MessageModel(); permission.CreateId = _user.ID; permission.CreateBy = _user.Name; var id = (await _permissionServices.Add(permission)); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } + //data.success = id > 0; + //if (data.success) + //{ + // data.response = id.ObjToString(); + // data.msg = "添加成功"; + //} - return data; + + return id > 0 ? Success(id.ObjToString(), "添加成功") : Failed("添加失败"); } /// @@ -239,52 +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); - if (!rmpitem.Any()) + List new_rmps = new List(); + var nowTime = _permissionServices.Db.GetDate(); + foreach (var item in assignView.pids) { - var moduleid = (await _permissionServices.Query(p => p.Id == item)).FirstOrDefault()?.Mid; + 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; - - data.success &= (await _roleModulePermissionServices.Add(roleModulePermission)) > 0; + 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 + }; + 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; } @@ -295,9 +290,9 @@ 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(); + //var data = new MessageModel(); var permissions = await _permissionServices.Query(d => d.IsDeleted == false); var permissionTrees = (from child in permissions @@ -323,14 +318,15 @@ orderby child.Id RecursionHelper.LoopToAppendChildren(permissionTrees, rootRoot, pid, needbtn); - data.success = true; - if (data.success) - { - data.response = rootRoot; - data.msg = "获取成功"; - } + //data.success = true; + //if (data.success) + //{ + // data.response = rootRoot; + // data.msg = "获取成功"; + //} - return data; + return Success(rootRoot, "获取成功"); + //return data; } /// @@ -339,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(); } @@ -369,10 +377,11 @@ 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); + var temp = rolePermissionMoudles.ToList().Find(t => t.Id == 87); var permissionTrees = (from child in rolePermissionMoudles where child.IsDeleted == false orderby child.Id @@ -389,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(), @@ -410,7 +420,6 @@ orderby child.Id }; permissionTrees = permissionTrees.OrderBy(d => d.order).ToList(); - RecursionHelper.LoopNaviBarAppendChildren(permissionTrees, rootRoot); data.success = true; @@ -426,20 +435,108 @@ orderby child.Id } /// - /// 通过角色获取菜单【无权限】 + /// 获取路由树 + /// + /// + /// + [HttpGet] + public async Task>> GetNavigationBarPro(long uid) + { + var data = new MessageModel>(); + + long uidInHttpcontext1 = 0; + var roleIds = new List(); + // ids4和jwt切换 + if (Permissions.IsUseIds4) + { + // ids4 + uidInHttpcontext1 = (from item in _httpContext.HttpContext.User.Claims + 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 == 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).ObjToLong(); + roleIds = (await _userRoleServices.Query(d => d.IsDeleted == false && d.UserId == uid)).Select(d => d.RoleId.ObjToLong()).Distinct().ToList(); + } + + if (uid > 0 && uid == uidInHttpcontext1) + { + if (roleIds.Any()) + { + 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) && d.IsButton == false)).OrderBy(c => c.OrderSort); + var permissionTrees = (from item in rolePermissionMoudles + where item.IsDeleted == false + orderby item.Id + select new NavigationBarPro + { + id = item.Id, + name = item.Name, + parentId = item.Pid, + order = item.OrderSort, + path = item.Code == "-" ? item.Name.GetTotalPingYin().FirstOrDefault() : (item.Code == "/" ? "/dashboard/workplace" : item.Code), + component = item.Pid == 0 ? (item.Code == "/" ? "dashboard/Workplace" : "RouteView") : item.Code?.TrimStart('/'), + iconCls = item.Icon, + Func = item.Func, + IsHide = item.IsHide.ObjToBool(), + IsButton = item.IsButton.ObjToBool(), + meta = new NavigationBarMetaPro + { + show = true, + title = item.Name, + icon = "user"//item.Icon + } + }).ToList(); + + permissionTrees = permissionTrees.OrderBy(d => d.order).ToList(); + + data.success = true; + if (data.success) + { + data.response = permissionTrees; + data.msg = "获取成功"; + } + } + } + } + return data; + } + + /// + /// 通过角色获取菜单 /// /// /// [HttpGet] [AllowAnonymous] - public async Task> GetPermissionIdByRoleId(int rid = 0) + public async Task> GetPermissionIdByRoleId(long rid = 0) { - var data = new MessageModel(); + //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(); @@ -454,18 +551,24 @@ orderby child.Id } } - data.success = true; - if (data.success) + //data.success = true; + //if (data.success) + //{ + // data.response = new AssignShow() + // { + // permissionids = permissionTrees, + // assignbtns = assignbtns, + // }; + // data.msg = "获取成功"; + //} + + return Success(new AssignShow() { - data.response = new AssignShow() - { - permissionids = permissionTrees, - assignbtns = assignbtns, - }; - data.msg = "获取成功"; - } + permissionids = permissionTrees, + assignbtns = assignbtns, + }, "获取成功"); - return data; + //return data; } /// @@ -499,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) @@ -516,16 +619,180 @@ public async Task> Delete(int id) return data; } + + /// + /// 导入多条菜单信息 + /// + /// + /// + // POST: api/User + [HttpPost] + public async Task> BatchPost([FromBody] List permissions) + { + var data = new MessageModel(); + string ids = string.Empty; + int sucCount = 0; + + for (int i = 0; i < permissions.Count; i++) + { + var permission = permissions[i]; + if (permission != null) + { + permission.CreateId = _user.ID; + permission.CreateBy = _user.Name; + ids += (await _permissionServices.Add(permission)); + sucCount++; + } + } + + data.success = ids.IsNotEmptyOrNull(); + if (data.success) + { + data.response = ids; + data.msg = $"{sucCount}条数据添加成功"; + } + + return data; + } + + /// + /// 系统接口菜单同步接口 + /// + /// + [HttpGet] + 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()) + { + data.msg = "必须填写要迁移的所属接口的控制器名称"; + return data; + } + + controllerName = controllerName.TrimEnd('/').ToLower(); + + gatewayPrefix = gatewayPrefix.Trim(); + swaggerDomain = swaggerDomain.Trim(); + controllerName = controllerName.Trim(); + + 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 response = await client.GetAsync(url); + var body = await response.Content.ReadAsStringAsync(); + + var resultJObj = (JObject)JsonConvert.DeserializeObject(body); + var paths = resultJObj["paths"].ObjToString(); + var pathsJObj = (JObject)JsonConvert.DeserializeObject(paths); + + List permissions = new List(); + foreach (JProperty jProperty in pathsJObj.Properties()) + { + 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")) + { + httpmethod = "get"; + } + else if (jProperty.Value.ToString().ToLower().Contains("post")) + { + httpmethod = "post"; + } + else if (jProperty.Value.ToString().ToLower().Contains("put")) + { + httpmethod = "put"; + } + else if (jProperty.Value.ToString().ToLower().Contains("delete")) + { + httpmethod = "delete"; + } + + 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() + { + Code = " ", + Name = summary, + IsButton = true, + IsHide = false, + Enabled = true, + CreateTime = DateTime.Now, + IsDeleted = false, + Pid = pid, + Module = new Modules() + { + LinkUrl = apiPath ?? "", + Name = summary, + Enabled = true, + CreateTime = DateTime.Now, + ModifyTime = DateTime.Now, + IsDeleted = false, + } + }); + } + + var modulesList = (await _moduleServices.Query(d => d.IsDeleted == false && d.LinkUrl != null)).Select(d => d.LinkUrl.ToLower()).ToList(); + permissions = permissions.Where(d => !modulesList.Contains(d.Module.LinkUrl.ToLower()) && d.Module.LinkUrl.Contains($"/{controllerName}/")).ToList(); + + + if (isAction) + { + foreach (var item in permissions) + { + List modules = await _moduleServices.Query(d => d.LinkUrl != null && d.LinkUrl.ToLower() == item.Module.LinkUrl); + if (!modules.Any()) + { + var mid = await _moduleServices.Add(item.Module); + if (mid > 0) + { + item.Mid = mid; + var permissionid = await _permissionServices.Add(item); + } + + } + } + data.msg = "同步完成"; + } + + data.response = permissions; + data.status = 200; + data.success = isAction; + + 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 ce6a9e6b..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; @@ -14,7 +13,7 @@ namespace Blog.Core.Controllers [Route("api/[controller]/[action]")] [ApiController] [Authorize(Permissions.Name)] - public class RoleController : ControllerBase + public class RoleController : BaseApiController { readonly IRoleServices _roleServices; readonly IUser _user; @@ -45,12 +44,14 @@ public async Task>> Get(int page = 1, string key = var data = await _roleServices.QueryPage(a => a.IsDeleted != true && (a.Name != null && a.Name.Contains(key)), page, intPageSize, " Id desc "); - return new MessageModel>() - { - msg = "获取成功", - success = data.dataCount >= 0, - response = data - }; + //return new MessageModel>() + //{ + // msg = "获取成功", + // success = data.dataCount >= 0, + // response = data + //}; + + return Success(data, "获取成功"); } @@ -70,20 +71,11 @@ public string Get(string id) [HttpPost] public async Task> Post([FromBody] Role role) { - var data = new MessageModel(); - role.CreateId = _user.ID; role.CreateBy = _user.Name; - var id = (await _roleServices.Add(role)); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } + return id > 0 ? Success(id.ObjToString(), "添加成功") : Failed("添加失败"); - return data; } /// @@ -95,18 +87,22 @@ public async Task> Post([FromBody] Role role) [HttpPut] public async Task> Put([FromBody] Role role) { - var data = new MessageModel(); - if (role != null && role.Id > 0) - { - data.success = await _roleServices.Update(role); - if (data.success) - { - data.msg = "更新成功"; - data.response = role?.Id.ObjToString(); - } - } - - return data; + if (role == null || role.Id <= 0) + return Failed("缺少参数"); + + return await _roleServices.Update(role) ? Success(role?.Id.ObjToString(),"更新成功") : Failed("更新失败"); + + //var data = new MessageModel(); + //if (role != null && role.Id > 0) + //{ + // data.success = await _roleServices.Update(role); + // if (data.success) + // { + // data.msg = "更新成功"; + // data.response = role?.Id.ObjToString(); + // } + //} + //return data; } /// @@ -116,22 +112,28 @@ 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(); - if (id > 0) - { - var userDetail = await _roleServices.QueryById(id); - userDetail.IsDeleted = true; - data.success = await _roleServices.Update(userDetail); - if (data.success) - { - data.msg = "删除成功"; - data.response = userDetail?.Id.ObjToString(); - } - } - - return data; + //if (id > 0) + //{ + // var userDetail = await _roleServices.QueryById(id); + // userDetail.IsDeleted = true; + // data.success = await _roleServices.Update(userDetail); + // if (data.success) + // { + // data.msg = "删除成功"; + // data.response = userDetail?.Id.ObjToString(); + // } + //} + //return data; + + if (id <= 0) return Failed(); + var userDetail = await _roleServices.QueryById(id); + if (userDetail == null) return Success(null,"角色不存在"); + userDetail.IsDeleted = true; + return await _roleServices.Update(userDetail) ? Success(userDetail?.Id.ObjToString(), "删除成功") : Failed(); } } } 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 ba9b7355..887cfcfc 100644 --- a/Blog.Core.Api/Controllers/TasksQzController.cs +++ b/Blog.Core.Api/Controllers/TasksQzController.cs @@ -1,14 +1,10 @@ -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.IRepository.UnitOfWork; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; using Blog.Core.Model.ViewModels; +using Blog.Core.Repository.UnitOfWorks; using Blog.Core.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -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 IUnitOfWork _unitOfWork; + private readonly IUnitOfWorkManage _unitOfWorkManage; - public TasksQzController(ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, IUnitOfWork unitOfWork) + public TasksQzController(ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, IUnitOfWorkManage unitOfWorkManage, ITasksLogServices tasksLogServices) { - _unitOfWork = unitOfWork; + _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); } /// @@ -70,7 +68,7 @@ public async Task>> Get(int page = 1, string key public async Task> Post([FromBody] TasksQz tasksQz) { var data = new MessageModel(); - _unitOfWork.BeginTran(); + _unitOfWorkManage.BeginTran(); var id = (await _tasksQzServices.Add(tasksQz)); data.success = id > 0; try @@ -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) - _unitOfWork.CommitTran(); + { + if (data.success) + _unitOfWorkManage.CommitTran(); else - _unitOfWork.RollbackTran(); + _unitOfWorkManage.RollbackTran(); } - return data; + return data; } @@ -126,7 +125,7 @@ public async Task> Put([FromBody] TasksQz tasksQz) var data = new MessageModel(); if (tasksQz != null && tasksQz.Id > 0) { - _unitOfWork.BeginTran(); + _unitOfWorkManage.BeginTran(); data.success = await _tasksQzServices.Update(tasksQz); try { @@ -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); @@ -160,10 +159,10 @@ public async Task> Put([FromBody] TasksQz tasksQz) finally { if (data.success) - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); else - _unitOfWork.RollbackTran(); - } + _unitOfWorkManage.RollbackTran(); + } } return data; } @@ -173,14 +172,14 @@ public async Task> Put([FromBody] TasksQz tasksQz) /// /// [HttpDelete] - public async Task> Delete(int jobId) + public async Task> Delete(long jobId) { var data = new MessageModel(); var model = await _tasksQzServices.QueryById(jobId); if (model != null) { - _unitOfWork.BeginTran(); + _unitOfWorkManage.BeginTran(); data.success = await _tasksQzServices.Delete(model); try { @@ -204,10 +203,10 @@ public async Task> Delete(int jobId) finally { if (data.success) - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); else - _unitOfWork.RollbackTran(); - } + _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) { - _unitOfWork.BeginTran(); + _unitOfWorkManage.BeginTran(); try { model.IsStart = true; @@ -262,10 +261,10 @@ public async Task> StartJob(int jobId) finally { if (data.success) - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); else - _unitOfWork.RollbackTran(); - } + _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,13 +318,13 @@ 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) - { - _unitOfWork.BeginTran(); + { + _unitOfWorkManage.BeginTran(); try { data.success = await _tasksQzServices.Update(model); @@ -356,10 +355,10 @@ public async Task> PauseJob(int jobId) finally { if (data.success) - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); else - _unitOfWork.RollbackTran(); - } + _unitOfWorkManage.RollbackTran(); + } } else { @@ -373,14 +372,14 @@ 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) - { - _unitOfWork.BeginTran(); + { + _unitOfWorkManage.BeginTran(); try { model.IsStart = true; @@ -412,10 +411,10 @@ public async Task> ResumeJob(int jobId) finally { if (data.success) - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); else - _unitOfWork.RollbackTran(); - } + _unitOfWorkManage.RollbackTran(); + } } else { @@ -429,14 +428,14 @@ 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); if (model != null) { - _unitOfWork.BeginTran(); + _unitOfWorkManage.BeginTran(); try { model.IsStart = true; @@ -472,10 +471,10 @@ public async Task> ReCovery(int jobId) finally { if (data.success) - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); else - _unitOfWork.RollbackTran(); - } + _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 4129a27e..9853d985 100644 --- a/Blog.Core.Api/Controllers/TransactionController.cs +++ b/Blog.Core.Api/Controllers/TransactionController.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Blog.Core.IRepository.UnitOfWork; -using Blog.Core.IServices; +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; @@ -17,12 +14,12 @@ public class TransactionController : ControllerBase { private readonly IPasswordLibServices _passwordLibServices; private readonly IGuestbookServices _guestbookServices; - private readonly IUnitOfWork _unitOfWork; + private readonly IUnitOfWorkManage _unitOfWorkManage; - public TransactionController(IUnitOfWork unitOfWork, IPasswordLibServices passwordLibServices, IGuestbookServices guestbookServices) + public TransactionController(IUnitOfWorkManage unitOfWorkManage, IPasswordLibServices passwordLibServices, IGuestbookServices guestbookServices) { - _unitOfWork = unitOfWork; + _unitOfWorkManage = unitOfWorkManage; _passwordLibServices = passwordLibServices; _guestbookServices = guestbookServices; } @@ -36,7 +33,7 @@ public async Task>> Get() { returnMsg.Add($"Begin Transaction"); - _unitOfWork.BeginTran(); + _unitOfWorkManage.BeginTran(); var passwords = await _passwordLibServices.Query(d => d.IsDeleted == false); returnMsg.Add($"first time : the count of passwords is :{passwords.Count}"); @@ -76,11 +73,11 @@ public async Task>> Get() returnMsg.Add($"first time : the count of guestbooks is :{guestbooks.Count}"); returnMsg.Add($" "); - _unitOfWork.CommitTran(); + _unitOfWorkManage.CommitTran(); } catch (Exception) { - _unitOfWork.RollbackTran(); + _unitOfWorkManage.RollbackTran(); var passwords = await _passwordLibServices.Query(); returnMsg.Add($"third time : the count of passwords is :{passwords.Count}"); @@ -98,11 +95,29 @@ 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(); } + [HttpGet] + public async Task GetTestTranPropagation() + { + return await _guestbookServices.TestTranPropagation(); + } + + [HttpGet] + public async Task GetTestTranPropagationNoTran() + { + return await _guestbookServices.TestTranPropagationNoTran(); + } + + [HttpGet] + public async Task GetTestTranPropagationTran() + { + return await _guestbookServices.TestTranPropagationTran(); + } + // POST: api/Transaction [HttpPost] public void Post([FromBody] string value) @@ -111,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) { } @@ -121,9 +136,9 @@ 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(); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/UserController.cs b/Blog.Core.Api/Controllers/UserController.cs index fa37fd1a..95137c8e 100644 --- a/Blog.Core.Api/Controllers/UserController.cs +++ b/Blog.Core.Api/Controllers/UserController.cs @@ -1,17 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using AutoMapper; using Blog.Core.AuthHelper.OverWrite; using Blog.Core.Common.Helper; using Blog.Core.Common.HttpContextUser; -using Blog.Core.IRepository.UnitOfWork; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using Blog.Core.Repository.UnitOfWorks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Blog.Core.Controllers { @@ -21,31 +18,41 @@ namespace Blog.Core.Controllers [Route("api/[controller]/[action]")] [ApiController] [Authorize(Permissions.Name)] - public class UserController : ControllerBase + public class UserController : BaseApiController { - private readonly IUnitOfWork _unitOfWork; + private readonly IUnitOfWorkManage _unitOfWorkManage; readonly ISysUserInfoServices _sysUserInfoServices; readonly IUserRoleServices _userRoleServices; readonly IRoleServices _roleServices; + private readonly IDepartmentServices _departmentServices; private readonly IUser _user; + private readonly IMapper _mapper; private readonly ILogger _logger; /// /// 构造函数 /// - /// + /// /// /// /// + /// /// + /// /// - public UserController(IUnitOfWork unitOfWork, ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IRoleServices roleServices, IUser user, ILogger logger) + public UserController(IUnitOfWorkManage unitOfWorkManage, ISysUserInfoServices sysUserInfoServices, + IUserRoleServices userRoleServices, + IRoleServices roleServices, + IDepartmentServices departmentServices, + IUser user, IMapper mapper, ILogger logger) { - _unitOfWork = unitOfWork; + _unitOfWorkManage = unitOfWorkManage; _sysUserInfoServices = sysUserInfoServices; _userRoleServices = userRoleServices; _roleServices = roleServices; + _departmentServices = departmentServices; _user = user; + _mapper = mapper; _logger = logger; } @@ -57,16 +64,17 @@ public UserController(IUnitOfWork unitOfWork, ISysUserInfoServices sysUserInfoSe /// // GET: api/User [HttpGet] - public async Task>> Get(int page = 1, string key = "") + public async Task>> Get(int page = 1, string key = "") { if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) { key = ""; } + int intPageSize = 50; - var data = await _sysUserInfoServices.QueryPage(a => a.tdIsDelete != true && a.uStatus >= 0 && ((a.uLoginName != null && a.uLoginName.Contains(key)) || (a.uRealName != null && a.uRealName.Contains(key))), page, intPageSize, " uID desc "); + var data = await _sysUserInfoServices.QueryPage(a => a.IsDeleted != true && a.Status >= 0 && ((a.LoginName != null && a.LoginName.Contains(key)) || (a.RealName != null && a.RealName.Contains(key))), page, intPageSize, " Id desc "); #region MyRegion @@ -74,26 +82,41 @@ public async Task>> Get(int page = 1, string // 这里可以封装到多表查询,此处简单处理 var allUserRoles = await _userRoleServices.Query(d => d.IsDeleted == false); var allRoles = await _roleServices.Query(d => d.IsDeleted == false); + var allDepartments = await _departmentServices.Query(d => d.IsDeleted == false); var sysUserInfos = data.data; foreach (var item in sysUserInfos) { - var currentUserRoles = allUserRoles.Where(d => d.UserId == item.uID).Select(d => d.RoleId).ToList(); + var currentUserRoles = allUserRoles.Where(d => d.UserId == item.Id).Select(d => d.RoleId).ToList(); item.RIDs = currentUserRoles; item.RoleNames = allRoles.Where(d => currentUserRoles.Contains(d.Id)).Select(d => d.Name).ToList(); + var departmentNameAndIds = GetFullDepartmentName(allDepartments, item.DepartmentId); + item.DepartmentName = departmentNameAndIds.Item1; + item.Dids = departmentNameAndIds.Item2; } data.data = sysUserInfos; + #endregion - return new MessageModel>() + return Success(data.ConvertTo(_mapper)); + } + + private (string, List) GetFullDepartmentName(List departments, long departmentId) + { + var departmentModel = departments.FirstOrDefault(d => d.Id == departmentId); + if (departmentModel == null) { - msg = "获取成功", - success = data.dataCount >= 0, - response = data - }; + return ("", new List()); + } + + 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); + return (fullName, pids); } // GET: api/User/5 @@ -114,9 +137,9 @@ public string Get(string id) /// [HttpGet] [AllowAnonymous] - public async Task> GetInfoByToken(string token) + public async Task> GetInfoByToken(string token) { - var data = new MessageModel(); + var data = new MessageModel(); if (!string.IsNullOrEmpty(token)) { var tokenModel = JwtHelper.SerializeJwt(token); @@ -125,13 +148,13 @@ public async Task> GetInfoByToken(string token) var userinfo = await _sysUserInfoServices.QueryById(tokenModel.Uid); if (userinfo != null) { - data.response = userinfo; + data.response = _mapper.Map(userinfo); data.success = true; data.msg = "获取成功"; } } - } + return data; } @@ -142,14 +165,14 @@ public async Task> GetInfoByToken(string token) /// // POST: api/User [HttpPost] - public async Task> Post([FromBody] sysUserInfo sysUserInfo) + public async Task> Post([FromBody] SysUserInfoDto sysUserInfo) { var data = new MessageModel(); sysUserInfo.uLoginPWD = MD5Helper.MD5Encrypt32(sysUserInfo.uLoginPWD); sysUserInfo.uRemark = _user.Name; - var id = await _sysUserInfoServices.Add(sysUserInfo); + var id = await _sysUserInfoServices.Add(_mapper.Map(sysUserInfo)); data.success = id > 0; if (data.success) { @@ -167,51 +190,68 @@ public async Task> Post([FromBody] sysUserInfo sysUserInfo) /// // PUT: api/User/5 [HttpPut] - public async Task> Put([FromBody] sysUserInfo sysUserInfo) + public async Task> Put([FromBody] SysUserInfoDto sysUserInfo) { // 这里使用事务处理 - var data = new MessageModel(); + + var oldUser = await _sysUserInfoServices.QueryById(sysUserInfo.uID); + if (oldUser is not { Id: > 0 }) + { + return Failed("用户不存在或已被删除"); + } + try { - _unitOfWork.BeginTran(); + if (sysUserInfo.uLoginPWD != oldUser.LoginPWD) + { + oldUser.CriticalModifyTime = DateTime.Now; + } - if (sysUserInfo != null && sysUserInfo.uID > 0) + _mapper.Map(sysUserInfo, oldUser); + + _unitOfWorkManage.BeginTran(); + // 无论 Update Or Add , 先删除当前用户的全部 U_R 关系 + var usreroles = (await _userRoleServices.Query(d => d.UserId == oldUser.Id)); + if (usreroles.Any()) { - if (sysUserInfo.RIDs.Count > 0) + var ids = usreroles.Select(d => d.Id.ToString()).ToArray(); + var isAllDeleted = await _userRoleServices.DeleteByIds(ids); + if (!isAllDeleted) { - // 无论 Update Or Add , 先删除当前用户的全部 U_R 关系 - var usreroles = (await _userRoleServices.Query(d => d.UserId == sysUserInfo.uID)).Select(d => d.Id.ToString()).ToArray(); - if (usreroles.Count() > 0) - { - var isAllDeleted = await _userRoleServices.DeleteByIds(usreroles); - } - - // 然后再执行添加操作 - var userRolsAdd = new List(); - sysUserInfo.RIDs.ForEach(rid => - { - userRolsAdd.Add(new UserRole(sysUserInfo.uID, rid)); - }); - - await _userRoleServices.Add(userRolsAdd); - + return Failed("服务器更新异常"); } + } - data.success = await _sysUserInfoServices.Update(sysUserInfo); - - _unitOfWork.CommitTran(); + // 然后再执行添加操作 + if (sysUserInfo.RIDs.Count > 0) + { + var userRolsAdd = new List(); + sysUserInfo.RIDs.ForEach(rid => { userRolsAdd.Add(new UserRole(oldUser.Id, rid)); }); - if (data.success) + var oldRole = usreroles.Select(s => s.RoleId).OrderBy(i => i).ToArray(); + var newRole = userRolsAdd.Select(s => s.RoleId).OrderBy(i => i).ToArray(); + if (!oldRole.SequenceEqual(newRole)) { - data.msg = "更新成功"; - data.response = sysUserInfo?.uID.ObjToString(); + oldUser.CriticalModifyTime = DateTime.Now; } + + await _userRoleServices.Add(userRolsAdd); + } + + data.success = await _sysUserInfoServices.Update(oldUser); + + _unitOfWorkManage.CommitTran(); + + if (data.success) + { + data.msg = "更新成功"; + data.response = oldUser.Id.ObjToString(); } } catch (Exception e) { - _unitOfWork.RollbackTran(); + _unitOfWorkManage.RollbackTran(); _logger.LogError(e, e.Message); } @@ -225,22 +265,22 @@ public async Task> Put([FromBody] sysUserInfo sysUserInfo) /// // DELETE: api/ApiWithActions/5 [HttpDelete] - public async Task> Delete(int id) + public async Task> Delete(long id) { var data = new MessageModel(); if (id > 0) { var userDetail = await _sysUserInfoServices.QueryById(id); - userDetail.tdIsDelete = true; + userDetail.IsDeleted = true; data.success = await _sysUserInfoServices.Update(userDetail); if (data.success) { data.msg = "删除成功"; - data.response = userDetail?.uID.ObjToString(); + data.response = userDetail?.Id.ObjToString(); } } return data; } } -} +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/UserRoleController.cs b/Blog.Core.Api/Controllers/UserRoleController.cs index 5225fbdc..693a68b8 100644 --- a/Blog.Core.Api/Controllers/UserRoleController.cs +++ b/Blog.Core.Api/Controllers/UserRoleController.cs @@ -1,7 +1,8 @@ -using System.Threading.Tasks; +using AutoMapper; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -16,21 +17,24 @@ namespace Blog.Core.Controllers [Authorize(Permissions.Name)] public class UserRoleController : Controller { - readonly ISysUserInfoServices _sysUserInfoServices; - readonly IUserRoleServices _userRoleServices; - readonly IRoleServices _roleServices; + private readonly ISysUserInfoServices _sysUserInfoServices; + private readonly IUserRoleServices _userRoleServices; + private readonly IRoleServices _roleServices; + private readonly IMapper _mapper; /// /// 构造函数 /// /// /// + /// /// - public UserRoleController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IRoleServices roleServices) + public UserRoleController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IMapper mapper, IRoleServices roleServices) { - this._sysUserInfoServices = sysUserInfoServices; - this._userRoleServices = userRoleServices; - this._roleServices = roleServices; + _sysUserInfoServices = sysUserInfoServices; + _userRoleServices = userRoleServices; + _roleServices = roleServices; + _mapper = mapper; } @@ -42,13 +46,14 @@ public UserRoleController(ISysUserInfoServices sysUserInfoServices, IUserRoleSer /// /// [HttpGet] - public async Task> AddUser(string loginName, string loginPwd) + public async Task> AddUser(string loginName, string loginPwd) { - return new MessageModel() + var userInfo = await _sysUserInfoServices.SaveUserInfo(loginName, loginPwd); + return new MessageModel() { success = true, msg = "添加成功", - response = await _sysUserInfoServices.SaveUserInfo(loginName, loginPwd) + response = _mapper.Map(userInfo) }; } @@ -75,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 002f1e62..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.HttpRestSharp; -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,12 +13,14 @@ using Blog.Core.Model.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using System; -using System.Collections.Generic; +using Microsoft.Extensions.Options; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; -using System.Threading.Tasks; +using System.Text; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Utility; namespace Blog.Core.Controllers { @@ -32,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; @@ -40,48 +42,109 @@ 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; - - /// - /// ValuesController - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ValuesController(IBlogArticleServices blogArticleServices - , IMapper mapper - , IAdvertisementServices advertisementServices - , Love love - , IRoleModulePermissionServices roleModulePermissionServices - , IUser user, IPasswordLibServices passwordLibServices - , IBlogApi blogApi - , IDoubanApi doubanApi) + private readonly 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; + _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] + public MessageModel> MyClaims() + { + return new MessageModel>() + { + success = true, + response = (_user.GetClaimsIdentity().ToList()).Select(d => + new ClaimDto + { + Type = d.Type, + Value = d.Value + } + ).ToList() + }; + } + + /// + /// 测试SqlSugar二级缓存 + /// 可设置过期时间 + /// 或通过接口方式更新该数据,也会离开清除缓存 + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task TestSqlsugarWithCache() + { + return await _blogArticleServices.QueryById("1", true); + } + /// /// Get方法 /// @@ -96,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"; + * 测试按照指定列查询带多条件和排序方法 + */ + 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 缓存 @@ -138,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层返回异常 @@ -149,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消息队列 /// @@ -172,7 +249,7 @@ public async Task RedisMq([FromServices] IRedisBasketRepository _redisBasketRepo [AllowAnonymous] public void EventBusTry([FromServices] IEventBus _eventBus, string blogId = "1") { - var blogDeletedEvent = new BlogDeletedIntegrationEvent(blogId); + var blogDeletedEvent = new BlogQueryIntegrationEvent(blogId); _eventBus.Publish(blogDeletedEvent); } @@ -185,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) { @@ -219,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) }; } @@ -267,34 +343,12 @@ public object Post([FromBody] BlogArticle blogArticle, int id) /// /// [HttpPost] - [Route("TestPostPara")] [AllowAnonymous] public object TestPostPara(string name) { return Ok(new { success = true, name = name }); } - /// - /// 测试http请求 RestSharp Get - /// - /// - [HttpGet("RestsharpGet")] - [AllowAnonymous] - public MessageModel RestsharpGet() - { - return HttpHelper.GetApi>("http://apk.neters.club/", "api/Blog/DetailNuxtNoPer", "id=1"); - } - /// - /// 测试http请求 RestSharp Post - /// - /// - [HttpGet("RestsharpPost")] - [AllowAnonymous] - public TestRestSharpPostDto RestsharpPost() - { - return HttpHelper.PostApi("http://apk.neters.club/api/Values/TestPostPara?name=老张", new { age = 18 }); - } - /// /// 测试多库连接 /// @@ -304,11 +358,11 @@ public TestRestSharpPostDto RestsharpPost() 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 @@ -319,17 +373,16 @@ public async Task TestMutiDBAPI() } /// - /// 测试http请求 WebApiClient Get + /// 测试Fulent做参数校验 /// + /// /// - [HttpGet("WebApiClientGetAsync")] + [HttpPost] [AllowAnonymous] - public async Task WebApiClientGetAsync() + public async Task FluentVaTest([FromBody] UserRegisterVo param) { - int id = 1; - string isbn = "9787544270878"; - var doubanVideoDetail = await _doubanApi.VideoDetailAsync(isbn); - return await _blogApi.DetailNuxtNoPerAsync(id); + await Task.CompletedTask; + return "Okay"; } /// @@ -342,6 +395,7 @@ public async Task WebApiClientGetAsync() public void Put(int id, [FromBody] string value) { } + /// /// Delete方法 /// @@ -353,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 /// @@ -369,8 +426,96 @@ public async Task>> GetAllConfigByAppllo [AllowAnonymous] public async Task GetConfigByAppllo(string key) { - return await Task.FromResult(Appsettings.app(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\": \"非那雄安\"}"); + + return response; + } + + [HttpGet] + [AllowAnonymous] + public async Task HttpPollyGet() + { + 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 new file mode 100644 index 00000000..dc12930b --- /dev/null +++ b/Blog.Core.Api/Controllers/WeChatCompanyController.cs @@ -0,0 +1,91 @@ +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// WeChatCompanyController + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public partial class WeChatCompanyController : Controller + { + readonly IWeChatCompanyServices _WeChatCompanyServices; + /// + /// 构造函数 + /// + /// + public WeChatCompanyController(IWeChatCompanyServices iWeChatCompanyServices) + { + _WeChatCompanyServices = iWeChatCompanyServices; + } + /// + /// 获取 + /// + /// 分页条件 + /// + [HttpGet] + public async Task>> Get([FromQuery] PaginationModel pagination) + { + var data = await _WeChatCompanyServices.QueryPage(pagination); + return new MessageModel> { success = true, response = data}; + } + /// + /// 获取(id) + /// + /// 主键ID + /// + [HttpGet("{id}")] + public async Task> Get(string id) + { + var data = await _WeChatCompanyServices.QueryById(id); + return new MessageModel { success = true, response = data }; + } + /// + /// 添加 + /// + /// + [HttpPost] + public async Task> Post([FromBody] WeChatCompany obj) + { + await _WeChatCompanyServices.Add(obj); + return new MessageModel { success = true}; + } + /// + /// 更新 + /// + /// + [HttpPut] + public async Task> Put([FromBody] WeChatCompany obj) + { + await _WeChatCompanyServices.Update(obj); + return new MessageModel { success = true}; + } + /// + /// 删除 + /// + /// + [HttpDelete] + public async Task> Delete(string id) + { + await _WeChatCompanyServices.DeleteById(id); + return new MessageModel { success = true}; + } + /// + /// 批量删除 + /// + /// + [HttpDelete] + public async Task> BatchDelete(string ids) + { + var i = ids.Split(","); + await _WeChatCompanyServices.DeleteByIds(i); + return new MessageModel { success = true }; + } + + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/WeChatConfigController.cs b/Blog.Core.Api/Controllers/WeChatConfigController.cs new file mode 100644 index 00000000..1f3b705d --- /dev/null +++ b/Blog.Core.Api/Controllers/WeChatConfigController.cs @@ -0,0 +1,91 @@ +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// WeChatConfigController + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public partial class WeChatConfigController : Controller + { + readonly IWeChatConfigServices _WeChatConfigServices; + /// + /// 构造函数 + /// + /// + public WeChatConfigController(IWeChatConfigServices iWeChatConfigServices) + { + _WeChatConfigServices = iWeChatConfigServices; + } + /// + /// 获取 + /// + /// 分页条件 + /// + [HttpGet] + public async Task>> Get([FromQuery] PaginationModel pagination) + { + var data = await _WeChatConfigServices.QueryPage(pagination); + return new MessageModel> { success = true, response = data}; + } + /// + /// 获取(id) + /// + /// 主键ID + /// + [HttpGet("{id}")] + public async Task> Get(string id) + { + var data = await _WeChatConfigServices.QueryById(id); + return new MessageModel { success = true, response = data }; + } + /// + /// 添加 + /// + /// + [HttpPost] + public async Task> Post([FromBody] WeChatConfig obj) + { + await _WeChatConfigServices.Add(obj); + return new MessageModel { success = true}; + } + /// + /// 更新 + /// + /// + [HttpPut] + public async Task> Put([FromBody] WeChatConfig obj) + { + await _WeChatConfigServices.Update(obj); + return new MessageModel { success = true}; + } + /// + /// 删除 + /// + /// + [HttpDelete] + public async Task> Delete(string id) + { + await _WeChatConfigServices.DeleteById(id); + return new MessageModel { success = true}; + } + /// + /// 批量删除 + /// + /// + [HttpDelete] + public async Task> BatchDelete(string ids) + { + var i = ids.Split(","); + await _WeChatConfigServices.DeleteByIds(i); + return new MessageModel { success = true }; + } + + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/WeChatController.cs b/Blog.Core.Api/Controllers/WeChatController.cs new file mode 100644 index 00000000..5c7e5c6f --- /dev/null +++ b/Blog.Core.Api/Controllers/WeChatController.cs @@ -0,0 +1,189 @@ +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// 微信公众号管理 + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public partial class WeChatController : Controller + { + readonly IWeChatConfigServices _weChatConfigServices; + readonly ILogger _logger; + /// + /// 构造函数 + /// + /// + /// + public WeChatController(IWeChatConfigServices weChatConfigServices, ILogger logger) + { + _weChatConfigServices = weChatConfigServices; + _logger = logger; + } + /// + /// 更新Token + /// + /// + /// + [HttpGet] + public async Task> GetToken(string id) + { + return await _weChatConfigServices.GetToken(id); + + } + /// + /// 刷新Token + /// + /// + /// + [HttpGet] + public async Task> RefreshToken(string id) + { + return await _weChatConfigServices.RefreshToken(id); + + } + /// + /// 获取模板 + /// + /// + /// + [HttpGet] + public async Task> GetTemplate(string id) + { + return await _weChatConfigServices.GetTemplate(id); + } + /// + /// 获取菜单 + /// + /// + /// + [HttpGet] + public async Task> GetMenu(string id) + { + return await _weChatConfigServices.GetMenu(id); + } + + /// + /// 更新菜单 + /// + /// + /// + [HttpPut] + public async Task> UpdateMenu(WeChatApiDto menu) + { + return await _weChatConfigServices.UpdateMenu(menu); + } + /// + /// 获取订阅用户(所有) + /// + /// + /// + [HttpGet] + public async Task> GetSubUsers(string id) + { + return await _weChatConfigServices.GetSubUsers(id); + } + /// + /// 入口 + /// + /// + /// + [AllowAnonymous] + [HttpPost] + [HttpGet] + public async Task Valid([FromQuery] WeChatValidDto validDto) + { + using (var reader = new StreamReader(Request.Body)) + { + var body = await reader.ReadToEndAsync(); + return await _weChatConfigServices.Valid(validDto, body); + } + } + /// + /// 获取订阅用户 + /// + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task> GetSubUser(string id,string openid) + { + return await _weChatConfigServices.GetSubUser(id,openid); + } + /// + /// 获取一个绑定员工公众号二维码 + /// + /// 消息 + /// + [HttpGet] + [AllowAnonymous] + public async Task> GetQRBind([FromQuery]WeChatUserInfo info) + { + return await _weChatConfigServices.GetQRBind(info); + } + /// + /// 推送卡片消息接口 + /// + /// 卡片消息对象 + /// + [HttpPost] + [AllowAnonymous] + public async Task> PushCardMsg(WeChatCardMsgDataDto msg) + { + string pushUserIP = $"{Request.HttpContext.Connection.RemoteIpAddress}:{Request.HttpContext.Connection.RemotePort}"; + 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); + } + /// + /// 推送文本消息 + /// + /// 消息对象 + /// + [HttpPost] + [AllowAnonymous] + public async Task> PushTxtMsg([FromBody] WeChatPushTestDto msg) + { + return await _weChatConfigServices.PushTxtMsg(msg); + } + /// + /// 通过绑定用户获取微信用户信息(一般用于初次绑定检测) + /// + /// 信息 + /// + [HttpGet] + [AllowAnonymous] + public async Task> GetBindUserInfo([FromQuery]WeChatUserInfo info) + { + return await _weChatConfigServices.GetBindUserInfo(info); + } + /// + /// 用户解绑 + /// + /// 消息 + /// + [HttpGet] + [AllowAnonymous] + public async Task> UnBind([FromQuery]WeChatUserInfo info) + { + return await _weChatConfigServices.UnBind(info); + } + } +} diff --git a/Blog.Core.Api/Controllers/WeChatPushLogController.cs b/Blog.Core.Api/Controllers/WeChatPushLogController.cs new file mode 100644 index 00000000..af168091 --- /dev/null +++ b/Blog.Core.Api/Controllers/WeChatPushLogController.cs @@ -0,0 +1,91 @@ +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// WeChatPushLogController + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public partial class WeChatPushLogController : Controller + { + readonly IWeChatPushLogServices _WeChatPushLogServices; + /// + /// 构造函数 + /// + /// + public WeChatPushLogController(IWeChatPushLogServices iWeChatPushLogServices) + { + _WeChatPushLogServices = iWeChatPushLogServices; + } + /// + /// 获取 + /// + /// 分页条件 + /// + [HttpGet] + public async Task>> Get([FromQuery] PaginationModel pagination) + { + var data = await _WeChatPushLogServices.QueryPage(pagination); + return new MessageModel> { success = true, response = data}; + } + /// + /// 获取(id) + /// + /// 主键ID + /// + [HttpGet("{id}")] + public async Task> Get(string id) + { + var data = await _WeChatPushLogServices.QueryById(id); + return new MessageModel { success = true, response = data }; + } + /// + /// 添加 + /// + /// + [HttpPost] + public async Task> Post([FromBody] WeChatPushLog obj) + { + await _WeChatPushLogServices.Add(obj); + return new MessageModel { success = true}; + } + /// + /// 更新 + /// + /// + [HttpPut] + public async Task> Put([FromBody] WeChatPushLog obj) + { + await _WeChatPushLogServices.Update(obj); + return new MessageModel { success = true}; + } + /// + /// 删除 + /// + /// + [HttpDelete] + public async Task> Delete(string id) + { + await _WeChatPushLogServices.DeleteById(id); + return new MessageModel { success = true}; + } + /// + /// 批量删除 + /// + /// + [HttpDelete] + public async Task> BatchDelete(string ids) + { + var i = ids.Split(","); + await _WeChatPushLogServices.DeleteByIds(i); + return new MessageModel { success = true }; + } + + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/WeChatSubController.cs b/Blog.Core.Api/Controllers/WeChatSubController.cs new file mode 100644 index 00000000..94f982d2 --- /dev/null +++ b/Blog.Core.Api/Controllers/WeChatSubController.cs @@ -0,0 +1,91 @@ +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// WeChatSubController + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public partial class WeChatSubController : Controller + { + readonly IWeChatSubServices _WeChatSubServices; + /// + /// 构造函数 + /// + /// + public WeChatSubController(IWeChatSubServices iWeChatSubServices) + { + _WeChatSubServices = iWeChatSubServices; + } + /// + /// 获取 + /// + /// 分页条件 + /// + [HttpGet] + public async Task>> Get([FromQuery] PaginationModel pagination) + { + var data = await _WeChatSubServices.QueryPage(pagination); + return new MessageModel> { success = true, response = data}; + } + /// + /// 获取(id) + /// + /// 主键ID + /// + [HttpGet("{id}")] + public async Task> Get(string id) + { + var data = await _WeChatSubServices.QueryById(id); + return new MessageModel { success = true, response = data }; + } + /// + /// 添加 + /// + /// + [HttpPost] + public async Task> Post([FromBody] WeChatSub obj) + { + await _WeChatSubServices.Add(obj); + return new MessageModel { success = true}; + } + /// + /// 更新 + /// + /// + [HttpPut] + public async Task> Put([FromBody] WeChatSub obj) + { + await _WeChatSubServices.Update(obj); + return new MessageModel { success = true}; + } + /// + /// 删除 + /// + /// + [HttpDelete] + public async Task> Delete(string id) + { + await _WeChatSubServices.DeleteById(id); + return new MessageModel { success = true}; + } + /// + /// 批量删除 + /// + /// + [HttpDelete] + public async Task> BatchDelete(string ids) + { + var i = ids.Split(","); + await _WeChatSubServices.DeleteByIds(i); + return new MessageModel { success = true }; + } + + } +} \ No newline at end of file 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 4a39efc2..e11d8dbd 100644 --- a/Blog.Core.Api/Filter/GlobalExceptionFilter.cs +++ b/Blog.Core.Api/Filter/GlobalExceptionFilter.cs @@ -1,15 +1,12 @@ -using Blog.Core.Common.Helper; +using Blog.Core.Common; +using Blog.Core.Common.Helper; 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 { @@ -22,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)) { @@ -43,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); @@ -53,11 +52,12 @@ public void OnException(ExceptionContext context) MiniProfiler.Current.CustomTiming("Errors:", json.msg); - //采用log4net 进行错误日志记录 + //进行错误日志记录 _loggerHelper.LogError(json.msg + WriteLog(json.msg, context.Exception)); - - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); - + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + _hubContext.Clients.All.SendAsync("ReceiveUpdate", json.msg).Wait(); + } } /// @@ -68,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) @@ -80,6 +83,7 @@ public InternalServerErrorObjectResult(object value) : base(value) StatusCode = StatusCodes.Status500InternalServerError; } } + //返回错误信息 public class JsonErrorResponse { @@ -87,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/Filter/UserRegisterVo.cs b/Blog.Core.Api/Filter/UserRegisterVo.cs new file mode 100644 index 00000000..163a39e3 --- /dev/null +++ b/Blog.Core.Api/Filter/UserRegisterVo.cs @@ -0,0 +1,67 @@ +using FluentValidation; +using System.Text.RegularExpressions; + +namespace Blog.Core.Filter +{ + public class UserRegisterVo + { + public string WxUid { get; set; } + + public string Telphone { get; set; } + + public string NickName { get; set; } + + public string SourceType { get; set; } + public IEnumerable Cars { get; set; } + + } + + public class CarInfo + { + public int CarCount { get; set; } + public int CarSize { get; set; } + } + + public class UserRegisterVoValidator : AbstractValidator + { + public UserRegisterVoValidator() + { + When(x => !string.IsNullOrEmpty(x.NickName) || !string.IsNullOrEmpty(x.Telphone), () => + { + RuleFor(x => x.NickName) + .Must(e => IsLegalName(e)).WithMessage("请填写合法的姓名,必须是汉字和字母"); + RuleFor(x => x.Telphone) + .Must(e => IsLegalPhone(e)).WithMessage("请填写正确的手机号码"); + RuleFor(x => x.Cars) + .NotNull().NotEmpty().WithMessage("车辆信息不正确"); + RuleForEach(x => x.Cars).SetValidator(new CarInfoValidator()); + }); + + } + + public static bool IsLegalName(string username) + { + //判断用户名是否合法 + const string pattern = "(^([A-Za-z]|[\u4E00-\u9FA5]){1,10}$)"; + return (!string.IsNullOrEmpty(username) && Regex.IsMatch(username, pattern)); + } + public static bool IsLegalPhone(string phone) + { + //判断手机号 + const string pattern = "(^1\\d{10}$)"; + return (!string.IsNullOrEmpty(phone) && Regex.IsMatch(phone, pattern)); + } + } + public class CarInfoValidator : AbstractValidator + { + public CarInfoValidator() + { + RuleFor(x => x.CarCount) + .GreaterThanOrEqualTo(0).WithMessage("车辆数量必须大于等于0") + .LessThanOrEqualTo(500).WithMessage($"存在车型数量已达上限"); + RuleFor(x => x.CarSize) + .IsInEnum().WithMessage("车型不正确"); + } + } + +} \ No newline at end of file diff --git a/Blog.Core.Api/Log4net.config b/Blog.Core.Api/Log4net.config deleted file mode 100644 index ba9b0f0a..00000000 --- a/Blog.Core.Api/Log4net.config +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Blog.Core.Api/Program.cs b/Blog.Core.Api/Program.cs index 9e416297..bc2b51cc 100644 --- a/Blog.Core.Api/Program.cs +++ b/Blog.Core.Api/Program.cs @@ -1,74 +1,190 @@ -using Autofac.Extensions.DependencyInjection; +// 以下为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.Core; +using Blog.Core.Common.Helper; +using Blog.Core.Extensions; using Blog.Core.Extensions.Apollo; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.IO; - -namespace Blog.Core +using Blog.Core.Extensions.Middlewares; +using Blog.Core.Extensions.ServiceExtensions; +using Blog.Core.Filter; +using Blog.Core.Hubs; +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(); + }) + .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.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.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.AddHttpPollySetup(); +builder.Services.AddNacosSetup(builder.Configuration); +builder.Services.AddRedisInitMqSetup(); + +builder.Services.AddIpPolicyRateLimitSetup(builder.Configuration); +builder.Services.AddSignalR().AddNewtonsoftJsonProtocol(); + +builder.Services.AddAuthorizationSetup(); +if (Permissions.IsUseIds4 || Permissions.IsUseAuthing) { - public class Program + if (Permissions.IsUseIds4) builder.Services.AddAuthentication_Ids4Setup(); + else if (Permissions.IsUseAuthing) builder.Services.AddAuthentication_AuthingSetup(); +} +else +{ + builder.Services.AddAuthentication_JWTSetup(); +} + +builder.Services.AddScoped(); +builder.Services.Configure(x => x.AllowSynchronousIO = true) + .Configure(x => x.AllowSynchronousIO = true); + +builder.Services.AddSession(); +builder.Services.AddDataProtectionSetup(); +builder.Services.AddControllers(o => { - public static void Main(string[] args) - { - //初始化默认主机Builder - Host.CreateDefaultBuilder(args) - .UseServiceProviderFactory(new AutofacServiceProviderFactory()) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder - .UseStartup() - .ConfigureAppConfiguration((hostingContext, config) => - { - config.Sources.Clear(); - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - //.AddJsonFile($"appsettings{ GetAppSettingsConfigName() }json", optional: true, reloadOnChange: false) - ; - //接入Apollo配置中心 - config.AddConfigurationApollo("appsettings.apollo.json"); - }) - .UseUrls("http://*:9291") - .ConfigureLogging((hostingContext, builder) => - { - // 1.过滤掉系统默认的一些日志 - builder.AddFilter("System", LogLevel.Error); - builder.AddFilter("Microsoft", LogLevel.Error); - - // 2.也可以在appsettings.json中配置,LogLevel节点 - - // 3.统一设置 - builder.SetMinimumLevel(LogLevel.Error); - - // 默认log4net.confg - builder.AddLog4Net(Path.Combine(Directory.GetCurrentDirectory(), "Log4net.config")); - }) - ; - }) - // 生成承载 web 应用程序的 Microsoft.AspNetCore.Hosting.IWebHost。Build是WebHostBuilder最终的目的,将返回一个构造的WebHost,最终生成宿主。 - .Build() - // 运行 web 应用程序并阻止调用线程, 直到主机关闭。 - // ※※※※ 有异常,查看 Log 文件夹下的异常日志 ※※※※ - .Run(); - } - - - /// - /// 根据环境变量定向配置文件名称 - /// - /// - private static string GetAppSettingsConfigName() - { - if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != null - && Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "") - { - return $".{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}."; - } - else - { - return "."; - } - } - } + 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()) +{ + app.UseDeveloperExceptionPage(); } +else +{ + app.UseExceptionHandler("/Error"); + //app.UseHsts(); +} + +app.UseEncryptionRequest(); +app.UseEncryptionResponse(); + +app.UseExceptionHandlerMiddle(); +app.UseIpLimitMiddle(); +app.UseRequestResponseLogMiddle(); +app.UseRecordAccessLogsMiddle(); +app.UseSignalRSendMiddle(); +app.UseIpLogMiddle(); +app.UseAllServicesMiddle(builder.Services); + +app.UseSession(); +app.UseSwaggerAuthorized(); +app.UseSwaggerMiddle(() => Assembly.GetExecutingAssembly().GetManifestResourceStream("Blog.Core.Api.index.html")); + +app.UseCors(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" })); +DefaultFilesOptions defaultFilesOptions = new DefaultFilesOptions(); +defaultFilesOptions.DefaultFileNames.Clear(); +defaultFilesOptions.DefaultFileNames.Add("index.html"); +app.UseDefaultFiles(defaultFilesOptions); +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.MapControllers(); +app.MapHub("/api2/chatHub"); + +// 4、运行 +app.Run(); \ No newline at end of file diff --git a/Blog.Core.Api/Program.five.cs b/Blog.Core.Api/Program.five.cs new file mode 100644 index 00000000..900c9495 --- /dev/null +++ b/Blog.Core.Api/Program.five.cs @@ -0,0 +1,75 @@ +//using Autofac.Extensions.DependencyInjection; +//using Blog.Core.Extensions.Apollo; +//using Microsoft.AspNetCore.Hosting; +//using Microsoft.Extensions.Configuration; +//using Microsoft.Extensions.Hosting; +//using Microsoft.Extensions.Logging; +//using System; +//using System.IO; + +// 这是asp.net5.0的写法,如果用5.0,请用本文件代码替换Program.cs代码 +//namespace Blog.Core +//{ +// public class Program +// { +// public static void Main(string[] args) +// { +// //初始化默认主机Builder +// Host.CreateDefaultBuilder(args) +// .UseServiceProviderFactory(new AutofacServiceProviderFactory()) +// .ConfigureWebHostDefaults(webBuilder => +// { +// webBuilder +// .UseStartup() +// .ConfigureAppConfiguration((hostingContext, config) => +// { +// config.Sources.Clear(); +// config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) +// //.AddJsonFile($"appsettings{ GetAppSettingsConfigName() }json", optional: true, reloadOnChange: false) +// ; +// //接入Apollo配置中心 +// config.AddConfigurationApollo("appsettings.apollo.json"); +// }) +// .UseUrls("http://*:9291") +// .ConfigureLogging((hostingContext, builder) => +// { +// // 1.过滤掉系统默认的一些日志 +// builder.AddFilter("System", LogLevel.Error); +// builder.AddFilter("Microsoft", LogLevel.Error); + +// // 2.也可以在appsettings.json中配置,LogLevel节点 + +// // 3.统一设置 +// builder.SetMinimumLevel(LogLevel.Error); + +// // 默认log4net.confg +// builder.AddLog4Net(Path.Combine(Directory.GetCurrentDirectory(), "Log4net.config")); +// }) +// ; +// }) +// // 生成承载 web 应用程序的 Microsoft.AspNetCore.Hosting.IWebHost。Build是WebHostBuilder最终的目的,将返回一个构造的WebHost,最终生成宿主。 +// .Build() +// // 运行 web 应用程序并阻止调用线程, 直到主机关闭。 +// // ※※※※ 有异常,查看 Log 文件夹下的异常日志 ※※※※ +// .Run(); +// } + + +// /// +// /// 根据环境变量定向配置文件名称 +// /// +// /// +// private static string GetAppSettingsConfigName() +// { +// if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != null +// && Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "") +// { +// return $".{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}."; +// } +// else +// { +// return "."; +// } +// } +// } +//} diff --git a/Blog.Core.Api/Properties/launchSettings.json b/Blog.Core.Api/Properties/launchSettings.json index 9b02dcc0..e3113d39 100644 --- a/Blog.Core.Api/Properties/launchSettings.json +++ b/Blog.Core.Api/Properties/launchSettings.json @@ -13,7 +13,8 @@ "commandName": "Project", "launchBrowser": true, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "" + "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 b2aaf7ab..00000000 --- a/Blog.Core.Api/Startup.cs +++ /dev/null @@ -1,224 +0,0 @@ -using Autofac; -using Blog.Core.Common; -using Blog.Core.Common.LogHelper; -using Blog.Core.Extensions; -using Blog.Core.Filter; -using Blog.Core.Hubs; -using Blog.Core.IServices; -using Blog.Core.Middlewares; -using Blog.Core.Model.Seed; -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.Serialization; -using System.IdentityModel.Tokens.Jwt; -using System.Reflection; -using System.Text; - -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)); - - - Permissions.IsUseIds4 = Appsettings.app(new string[] { "Startup", "IdentityServer4", "Enabled" }).ObjToBool(); - - // 确保从认证中心返回的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.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.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; - //}) - //全局配置Json序列化处理 - .AddNewtonsoftJson(options => - { - //忽略循环引用 - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - //不使用驼峰样式的key - options.SerializerSettings.ContractResolver = new DefaultContractResolver(); - //设置时间格式 - //options.SerializerSettings.DateFormatString = "yyyy-MM-dd"; - //忽略Model中为null的属性 - //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; - //设置本地时间而非UTC时间 - options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; - }); - - 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.UseIpLimitMildd(); - // 记录请求与返回数据 - app.UseReuestResponseLog(); - // 用户访问记录(必须放到外层,不然如果遇到异常,会报错,因为不能返回流) - app.UseRecordAccessLogsMildd(); - // signalr - app.UseSignalRSendMildd(); - // 记录ip请求 - app.UseIPLogMildd(); - // 查看注入的所有服务 - app.UseAllServicesMildd(_services); - - if (env.IsDevelopment()) - { - // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 - // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection - //app.UseHsts(); - } - - // 封装Swagger展示 - app.UseSwaggerMildd(() => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.Api.index.html")); - - // ↓↓↓↓↓↓ 注意下边这些中间件的顺序,很重要 ↓↓↓↓↓↓ - - // CORS跨域 - app.UseCors(Appsettings.app(new string[] { "Startup", "Cors", "PolicyName" })); - // 跳转https - //app.UseHttpsRedirection(); - // 使用静态文件 - app.UseStaticFiles(); - // 使用cookie - app.UseCookiePolicy(); - // 返回错误码 - app.UseStatusCodePages(); - // Routing - app.UseRouting(); - // 这种自定义授权中间件,可以尝试,但不推荐 - // app.UseJwtTokenAuth(); - - // 测试用户,用来通过鉴权 - if (Configuration.GetValue("AppSettings:UseLoadTest")) - { - app.UseMiddleware(); - } - // 先开启认证 - app.UseAuthentication(); - // 然后是授权中间件 - app.UseAuthorization(); - //开启性能分析 - app.UseMiniProfilerMildd(); - // 开启异常中间件,要放到最后 - //app.UseExceptionHandlerMidd(); - - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - - endpoints.MapHub("/api2/chatHub"); - }); - - // 生成种子数据 - app.UseSeedDataMildd(myContext, Env.WebRootPath); - // 开启QuartzNetJob调度服务 - app.UseQuartzJobMildd(tasksQzServices, schedulerCenter); - // 服务注册 - app.UseConsulMildd(Configuration, lifetime); - // 事件总线,订阅服务 - app.ConfigureEventBus(); - - } - - } -} diff --git a/Blog.Core.Api/appsettings.Development.json b/Blog.Core.Api/appsettings.Development.json new file mode 100644 index 00000000..9016c7ce --- /dev/null +++ b/Blog.Core.Api/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "nacos": { + "ServerAddresses": [ "http://localhost:8848" ], // nacos 连接地址 + "DefaultTimeOut": 15000, // 默认超时时间 + "Namespace": "public", // 命名空间 + "ListenInterval": 10000, // 监听的频率 + "ServiceName": "blog.Core.Api", // 服务名 + "Port": "9291", // 服务端口号 + "RegisterEnabled": true // 是否直接注册nacos + } +} \ No newline at end of file diff --git a/Blog.Core.Api/appsettings.json b/Blog.Core.Api/appsettings.json index c31755df..d62aae9e 100644 --- a/Blog.Core.Api/appsettings.json +++ b/Blog.Core.Api/appsettings.json @@ -1,38 +1,36 @@ { "urls": "http://*:9291", //web服务端口,如果用IIS部署,把这个去掉 - "Logging": { - "LogLevel": { - "Default": "Information", //加入Default否则log4net本地写入不了日志 - "Blog.Core.AuthHelper.ApiResponseHandler": "Error" - }, - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "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": { + "Kafka": { "Enabled": false, "Servers": "localhost:9092", "Topic": "blog", @@ -44,24 +42,34 @@ "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 + "Enabled": true, //关闭日志2:修改Sql日志是否显示(也可以精准配置,是否生成到文件、数据库、控制台) + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true }, - "OutToConsole": { + "LogToConsole": { "Enabled": true } }, @@ -69,17 +77,22 @@ "SeedDBEnabled": true, //只生成表结构 "SeedDBDataEnabled": true, //生成表,并初始化数据 "Author": "Blog.Core", - "UseLoadTest": false + "SvcName": "", // /svc/blog + "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 @@ -92,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" }, @@ -110,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" }, @@ -118,55 +153,51 @@ "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;" } ], "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", "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 @@ -177,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注册中心 @@ -189,17 +226,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 @@ -209,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": { @@ -274,14 +346,19 @@ "Port": "9291", // 服务端口号 "RegisterEnabled": true // 是否直接注册nacos }, - "LogFiedOutPutConfigs": { - "tcpAddressHost": "", // 输出elk的tcp连接地址 - "tcpAddressPort": 0, // 输出elk的tcp端口号 + "LogFiedOutPutConfigs": { + "tcpAddressHost": "", // 输出elk的tcp连接地址 + "tcpAddressPort": 0, // 输出elk的tcp端口号 "ConfigsInfo": [ // 配置的输出elk节点内容 常用语动态标识 { "FiedName": "applicationName", "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 98482a0e..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 new file mode 100644 index 00000000..cdb0e606 --- /dev/null +++ b/Blog.Core.Api/skyapm.json @@ -0,0 +1,29 @@ +{ + "SkyWalking": { + "ServiceName": "blog-core-api", + "Namespace": "", + "HeaderVersions": [ + "sw8" + ], + "Sampling": { + "SamplePer3Secs": -1, + "Percentage": -1.0 + }, + "Logging": { + "Level": "Information", + "FilePath": "Logs/Skyapm/skyapm-{Date}.log" + }, + "Transport": { + "Interval": 3000, + "ProtocolVersion": "v8", + "QueueSize": 30000, + "BatchSize": 3000, + "gRPC": { + "Servers": "elasticsearch:11800", + "Timeout": 10000, + "ConnectTimeout": 10000, + "ReportTimeout": 600000 + } + } + } +} diff --git a/Blog.Core.Api/web.config b/Blog.Core.Api/web.config new file mode 100644 index 00000000..f9910150 --- /dev/null +++ b/Blog.Core.Api/web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Department.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Department.xlsx new file mode 100644 index 00000000..c0362d77 Binary files /dev/null and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Department.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Modules.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Modules.xlsx new file mode 100644 index 00000000..e2de3e21 Binary files /dev/null and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Modules.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Permission.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Permission.xlsx new file mode 100644 index 00000000..ec65657e Binary files /dev/null and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Permission.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Role.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Role.xlsx new file mode 100644 index 00000000..89184c6c Binary files /dev/null and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/Role.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/RoleModulePermission.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/RoleModulePermission.xlsx new file mode 100644 index 00000000..99a771d2 Binary files /dev/null and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/RoleModulePermission.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/SysUserInfo.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/SysUserInfo.xlsx new file mode 100644 index 00000000..6a1e4ca6 Binary files /dev/null and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/SysUserInfo.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.excel/UserRole.xlsx b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/UserRole.xlsx new file mode 100644 index 00000000..4e7bb12d Binary files /dev/null and b/Blog.Core.Api/wwwroot/BlogCore.Data.excel/UserRole.xlsx differ diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Department.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Department.tsv new file mode 100644 index 00000000..dfb887b1 --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Department.tsv @@ -0,0 +1 @@ +[{"CodeRelationship":"0,","Name":"BCVP开发社区","Leader":"老张的哲学","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 15:47:25","ModifyTime":"2022-04-01 15:47:25","hasChildren":true,"Pid":0,"Id":1},{"CodeRelationship":"0,","Name":"DDD思想社区组织","Leader":"DDD","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 15:48:08","ModifyTime":"2022-04-01 15:48:08","hasChildren":true,"Pid":0,"Id":2},{"CodeRelationship":"0,1,","Name":"BCVP-北京分部","Leader":"北京","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 15:48:41","ModifyTime":"2022-04-01 15:48:41","hasChildren":true,"Pid":1,"Id":3},{"CodeRelationship":"0,1,","Name":"BCVP-上海分部","Leader":"上海","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 15:49:27","ModifyTime":"2022-04-01 15:49:27","hasChildren":true,"Pid":1,"Id":4},{"CodeRelationship":"0,1,","Name":"BCVP-广州分部","Leader":"广州","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 15:50:23","ModifyTime":"2022-04-01 15:50:44","hasChildren":true,"Pid":1,"Id":5},{"CodeRelationship":"0,1,3,","Name":"前端小组(1群)","Leader":"--","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 15:51:43","ModifyTime":"2022-04-01 15:51:43","hasChildren":true,"Pid":3,"Id":6},{"CodeRelationship":"0,1,4,","Name":"后端小组(2群)","Leader":"--","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 15:58:13","ModifyTime":"2022-04-01 15:58:13","hasChildren":true,"Pid":4,"Id":7},{"CodeRelationship":"0,","Name":"VUE学习联盟","Leader":"vue","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 16:14:21","ModifyTime":"2022-04-01 16:14:21","hasChildren":true,"Pid":0,"Id":8},{"CodeRelationship":"0,8,","Name":"ES指导(1组)","Leader":"es","OrderSort":0,"Status":true,"IsDeleted":false,"CreateBy":"老张的哲学","CreateTime":"2022-04-01 16:14:47","ModifyTime":"2022-04-01 16:15:00","hasChildren":true,"Pid":8,"Id":9}] \ No newline at end of file diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv index a0f2eaef..76c55a22 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv @@ -1010,5 +1010,574 @@ "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", "Id": 46 + }, + + { + "Id": "47", + "IsDeleted": 0, + "Name": "微信获取", + "LinkUrl": "\/api\/WeChatConfig\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-22 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "48", + "IsDeleted": 0, + "Name": "微信客户批量删除", + "LinkUrl": "\/api\/WeChatCompany\/BatchDelete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "49", + "IsDeleted": 0, + "Name": "微信客户删除", + "LinkUrl": "\/api\/WeChatCompany\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "50", + "IsDeleted": 0, + "Name": "微信客户获取", + "LinkUrl": "\/api\/WeChatCompany\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "51", + "IsDeleted": 0, + "Name": "微信客户添加", + "LinkUrl": "\/api\/WeChatCompany\/post", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "52", + "IsDeleted": 0, + "Name": "微信客户更新", + "LinkUrl": "\/api\/WeChatCompany\/put", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "53", + "IsDeleted": 0, + "Name": "微信公众号批量删除", + "LinkUrl": "\/api\/WeChatConfig\/BatchDelete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-25 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "54", + "IsDeleted": 0, + "Name": "微信公众号获取", + "LinkUrl": "\/api\/WeChatConfig\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-22 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "55", + "IsDeleted": 0, + "Name": "获取公众号菜单设置", + "LinkUrl": "\/api\/WeChat\/GetMenu", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "ParentId": 0 + }, + { + "Id": "56", + "IsDeleted": 0, + "Name": "获取订阅用户", + "LinkUrl": "\/api\/WeChat\/GetSubUsers", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-23 16:20:30", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "ParentId": 0 + }, + { + "Id": "57", + "IsDeleted": 0, + "Name": "获取消息模板列表", + "LinkUrl": "\/api\/WeChat\/GetTemplate", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-08 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "ParentId": 0 + }, + { + "Id": "58", + "IsDeleted": 0, + "Name": "微信公众号更新", + "LinkUrl": "\/api\/WeChatConfig\/post", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-24 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "59", + "IsDeleted": 0, + "Name": "微信公众号添加", + "LinkUrl": "\/api\/WeChatConfig\/put", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-24 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "Id": "60", + "IsDeleted": 0, + "Name": "刷新Token", + "LinkUrl": "\/api\/WeChat\/RefreshToken", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-30 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "ParentId": 0 + }, + { + "Id": "61", + "IsDeleted": 0, + "Name": "更新微信菜单设置", + "LinkUrl": "\/api\/WeChat\/UpdateMenu", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "ParentId": 0 + }, + { + "Id": "62", + "IsDeleted": 0, + "Name": "获取推送记录", + "LinkUrl": "\/api\/WeChatPushLog\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-08 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "ParentId": 0 + }, + { + "Id": "63", + "IsDeleted": 0, + "Name": "获取绑定用户", + "LinkUrl": "\/api\/WeChatSub\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-23 16:20:47", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-23 00:00:00", + "ParentId": 0 + }, + { + "Id": "64", + "IsDeleted": 0, + "Name": "微信公众号删除", + "LinkUrl": "\/api\/WeChatConfig\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-24 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "ParentId": 0 + }, + { + "IsDeleted": false, + "Name": "获取部门数据", + "LinkUrl": "/api/department/get", + "OrderSort": 0, + "IsMenu": false, + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "ParentId": 0, + "Id": 65 + }, + { + "IsDeleted": false, + "Name": "获取部门数据树表格", + "LinkUrl": "/api/permission/GetTreeTable", + "OrderSort": 0, + "IsMenu": false, + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "ParentId": 0, + "Id": 66 + }, + { + "IsDeleted": false, + "Name": "删除部门", + "LinkUrl": "/api/department/delete", + "OrderSort": 0, + "IsMenu": false, + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "ParentId": 0, + "Id": 67 + }, + { + "IsDeleted": false, + "Name": "更新部门", + "LinkUrl": "/api/department/put", + "OrderSort": 0, + "IsMenu": false, + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "ParentId": 0, + "Id": 68 + }, + { + "IsDeleted": false, + "Name": "添加部门", + "LinkUrl": "/api/department/post", + "OrderSort": 0, + "IsMenu": false, + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "ParentId": 0, + "Id": 69 + }, + { + "IsDeleted": false, + "Name": "获取部门树", + "LinkUrl": "/api/department/getDepartmentTree", + "OrderSort": 0, + "IsMenu": false, + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "ParentId": 0, + "Id": 70 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "Get导航条Pro", + "LinkUrl": "\/api\/permission\/GetNavigationBarPro", + "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": 71 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "菜单同步", + "LinkUrl": "\/api\/permission\/migratepermission", + "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": 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 9054658f..66b4313e 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv @@ -5,8 +5,9 @@ "IsButton": 0, "Pid": 0, "Mid": 0, - "OrderSort": 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, @@ -140,10 +145,10 @@ "IsHide": 0 }, { - "Code": "\/Thanks", - "Name": "致谢页", + "Code": "\/System\/BasicSetting", + "Name": "个人设置", "IsButton": 0, - "Pid": 0, + "Pid": 68, "Mid": 0, "OrderSort": 5, "Icon": "fa-star ", @@ -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, @@ -1666,8 +1680,7 @@ "IsDeleted": 0, "Id": 82, "IsHide": 0 - } - , + }, { "Code": " ", "Name": "删除", @@ -1751,5 +1764,871 @@ "IsDeleted": 0, "Id": 86, "IsHide": 1 + }, + { + "Id": 87, + "Code": "-", + "Name": "微信公众号管理", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 3, + "IconNew": "WalletFilled", + "Icon": "fa-weixin", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-21 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-13 00:00:00", + "IsDeleted": 0, + "Pid": 0, + "Mid": 0 + }, + { + "Id": 88, + "Code": "\/WeChat\/Manager", + "Name": "微信列表", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 1, + "Icon": "fa-list", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-21 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 89, + "Code": "\/WeChat\/Company", + "Name": "微信客户", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": "2", + "Icon": "fa-address-book", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-26 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 90, + "Code": "\/WeChat\/Menu", + "Name": "微信菜单", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 3, + "Icon": "fa-sliders", + "Description": "微信菜单设置", + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 91, + "Code": "\/WeChat\/Template", + "Name": "模板消息", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 4, + "Icon": "fa-comments-o", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-08 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 92, + "Code": "\/WeChat\/PushLog", + "Name": "推送记录", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 8, + "Icon": "fa-history", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-08 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 93, + "Code": "\/WeChat\/SubUser", + "Name": "订阅用户", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 6, + "Icon": "fa fa-user", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-23 14:09:08", + "ModifyId": 8, + "ModifyBy": "test", + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 94, + "Code": "\/WeChat\/BindUser", + "Name": "绑定用户", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 7, + "Icon": "fa fa-user-circle-o", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-23 16:12:52", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 95, + "Code": "\/WeChat\/SendMessage", + "Name": "文本消息", + "IsButton": 0, + "IsHide": 0, + "IskeepAlive": 0, + "Func": null, + "OrderSort": 5, + "Icon": "fa fa-paper-plane", + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-24 09:05:50", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-30 00:00:00", + "IsDeleted": 0, + "Pid": 87, + "Mid": 0 + }, + { + "Id": 96, + "Code": " ", + "Name": "查询", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": 0, + "Func": "getWeChatAccount", + "OrderSort": "0", + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-22 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-17 00:00:00", + "IsDeleted": 0, + "Pid": 88, + "Mid": 54 + }, + { + "Id": 98, + "Code": " ", + "Name": "删除", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": 0, + "Func": "handleDel", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-22 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-17 00:00:00", + "IsDeleted": 0, + "Pid": 88, + "Mid": 64 + }, + { + "Id": 99, + "Code": " ", + "Name": "新增", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": 0, + "Func": "handleAdd", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-24 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-17 00:00:00", + "IsDeleted": 0, + "Pid": 88, + "Mid": 59 + }, + { + "Id": 100, + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": 0, + "Func": "handleEdit", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-24 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-17 00:00:00", + "IsDeleted": 0, + "Pid": 88, + "Mid": 58 + }, + { + "Id": 101, + "Code": " ", + "Name": "批量删除", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": 0, + "Func": "batchRemove", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-25 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-17 00:00:00", + "IsDeleted": 0, + "Pid": 88, + "Mid": 53 + }, + { + "Id": 102, + "Code": " ", + "Name": "刷新Token", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": 0, + "Func": "handleRefreshWeChatToken", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-03-30 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-17 00:00:00", + "IsDeleted": 0, + "Pid": 88, + "Mid": 60 + }, + { + "Id": 103, + "Code": " ", + "Name": "查询", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": null, + "Func": "getWeChatCompany", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "IsDeleted": 0, + "Pid": 89, + "Mid": 50 + }, + { + "Id": 104, + "Code": " ", + "Name": "删除", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": null, + "Func": "handleDel", + "OrderSort": "0", + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "IsDeleted": 0, + "Pid": 89, + "Mid": 49 + }, + { + "Id": 105, + "Code": " ", + "Name": "批量删除", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": null, + "Func": "batchRemove", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "IsDeleted": 0, + "Pid": 89, + "Mid": 48 + }, + { + "Id": 106, + "Code": " ", + "Name": "添加", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": null, + "Func": "handleAdd", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "IsDeleted": 0, + "Pid": 89, + "Mid": 51 + }, + { + "Id": 107, + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": null, + "Func": "handleEdit", + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-06 00:00:00", + "IsDeleted": 0, + "Pid": 89, + "Mid": 52 + }, + { + "Id": 108, + "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-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "IsDeleted": 0, + "Pid": 90, + "Mid": 55 + }, + { + "Id": 109, + "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-06 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "IsDeleted": 0, + "Pid": 90, + "Mid": 61 + }, + { + "Id": 110, + "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-08 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "IsDeleted": 0, + "Pid": 91, + "Mid": 57 + }, + { + "Id": 111, + "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-08 00:00:00", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2021-09-29 00:00:00", + "IsDeleted": 0, + "Pid": 92, + "Mid": 62 + }, + { + "Id": 112, + "Code": " ", + "Name": "获取订阅用户", + "IsButton": 1, + "IsHide": 0, + "IskeepAlive": null, + "Func": null, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 8, + "CreateBy": "test", + "CreateTime": "2020-04-23 16:21:53", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "2020-04-23 00:00:00", + "IsDeleted": 0, + "Pid": 93, + "Mid": 56 + }, + { + "Id": 113, + "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": 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 + //}, + { + "Code": "-", + "Name": "部门权限管理", + "IsButton": false, + "IsHide": false, + "IskeepAlive": false, + "OrderSort": -10, + "Icon": "fa-address-book", + "IconNew": "Briefcase", + "Description": "", + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "IsDeleted": false, + "PnameArr": [ + + ], + "PCodeArr": [ + + ], + "hasChildren": true, + "Pid": 0, + "Mid": 0, + "Id": 114 + }, + { + "Code": "/Department/Department", + "Name": "部门管理", + "IsButton": false, + "IsHide": false, + "IskeepAlive": false, + "OrderSort": 0, + "Icon": "", + "IconNew": "Collection", + "Description": "", + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "IsDeleted": false, + "PnameArr": [ + + ], + "PCodeArr": [ + + ], + "hasChildren": true, + "Pid": 114, + "Mid": 66, + "Id": 115 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": true, + "IsHide": false, + "IskeepAlive": false, + "Func": "handleQuery", + "OrderSort": 0, + "Icon": "", + "Description": "", + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "IsDeleted": false, + "PnameArr": [ + + ], + "PCodeArr": [ + + ], + "hasChildren": true, + "Pid": 115, + "Mid": 66, + "Id": 116 + }, + { + "Code": " ", + "Name": "新增", + "IsButton": true, + "IsHide": false, + "IskeepAlive": false, + "Func": "handleAdd", + "OrderSort": 0, + "Icon": "", + "Description": "", + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "IsDeleted": false, + "PnameArr": [ + + ], + "PCodeArr": [ + + ], + "hasChildren": true, + "Pid": 115, + "Mid": 69, + "Id": 117 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": true, + "IsHide": false, + "IskeepAlive": false, + "Func": "handleEdit", + "OrderSort": 0, + "Icon": "", + "Description": "", + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "IsDeleted": false, + "PnameArr": [ + + ], + "PCodeArr": [ + + ], + "hasChildren": true, + "Pid": 115, + "Mid": 68, + "Id": 118 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": true, + "IsHide": false, + "IskeepAlive": false, + "Func": "handleDel", + "OrderSort": 0, + "Icon": "", + "Description": "", + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "IsDeleted": false, + "PnameArr": [ + + ], + "PCodeArr": [ + + ], + "hasChildren": true, + "Pid": 115, + "Mid": 67, + "Id": 119 + }, + { + "Code": " ", + "Name": "部门树", + "IsButton": true, + "IsHide": true, + "IskeepAlive": false, + "OrderSort": 0, + "Icon": "", + "Description": "", + "Enabled": true, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 00:00:00", + "ModifyTime": "2022-03-23 00:00:00", + "IsDeleted": false, + "PnameArr": [ + + ], + "PCodeArr": [ + + ], + "hasChildren": true, + "Pid": 115, + "Mid": 70, + "Id": 120 + }, + { + "Code": " ", + "Name": "左侧导航Pro", + "IsButton": 1, + "Pid": 7, + "Mid": 71, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 121, + "IsHide": 1 + }, + { + "Code": " ", + "Name": "菜单同步", + "IsButton": 1, + "Pid": 7, + "Mid": 72, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 23, + "Func": "handleSync", + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "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 4307f6ee..5626b81f 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv @@ -1558,5 +1558,1541 @@ "ModifyBy": null, "ModifyTime": "\/Date(1546272000000+0800)\/", "Id": 157 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyTime": "2022-03-23 19:21:58", + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 114, + "Id": 121 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyTime": "2022-03-23 19:21:58", + "RoleId": 4, + "ModuleId": 66, + "PermissionId": 115, + "Id": 122 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyTime": "2022-03-23 19:21:58", + "RoleId": 4, + "ModuleId": 66, + "PermissionId": 116, + "Id": 123 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyTime": "2022-03-23 19:21:58", + "RoleId": 4, + "ModuleId": 69, + "PermissionId": 117, + "Id": 124 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyTime": "2022-03-23 19:21:58", + "RoleId": 4, + "ModuleId": 68, + "PermissionId": 118, + "Id": 125 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyTime": "2022-03-23 19:21:58", + "RoleId": 4, + "ModuleId": 67, + "PermissionId": 119, + "Id": 126 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-03-23 19:21:58", + "ModifyTime": "2022-03-23 19:21:58", + "RoleId": 4, + "ModuleId": 70, + "PermissionId": 120, + "Id": 127 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 71, + "PermissionId": 121, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 228 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-04-11 16:08:48", + "ModifyTime": "2022-04-11 00:00:00", + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 114, + "Id": 229 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-04-11 16:08:48", + "ModifyTime": "2022-04-11 00:00:00", + "RoleId": 6, + "ModuleId": 66, + "PermissionId": 115, + "Id": 230 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-04-11 16:08:49", + "ModifyTime": "2022-04-11 00:00:00", + "RoleId": 6, + "ModuleId": 70, + "PermissionId": 120, + "Id": 231 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-04-11 16:08:49", + "ModifyTime": "2022-04-11 00:00:00", + "RoleId": 6, + "ModuleId": 66, + "PermissionId": 116, + "Id": 232 + }, + { + "IsDeleted": false, + "CreateId": 12, + "CreateBy": "blogadmin", + "CreateTime": "2022-04-11 16:08:49", + "ModifyTime": "2022-04-11 00:00:00", + "RoleId": 4, + "ModuleId": 72, + "PermissionId": 122, + "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/BlogCore.Data.json/UserRole.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/UserRole.tsv index 54b28249..9da3befe 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/UserRole.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/UserRole.tsv @@ -26,7 +26,7 @@ { "IsDeleted": 0, "UserId": 7, - "RoleId": 3, + "RoleId": 4, "CreateId": null, "CreateBy": null, "CreateTime": "\/Date(1546272000000+0800)\/", diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/sysUserInfo.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/sysUserInfo.tsv index cdd73aee..4eb742bd 100644 --- a/Blog.Core.Api/wwwroot/BlogCore.Data.json/sysUserInfo.tsv +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/sysUserInfo.tsv @@ -1,704 +1,704 @@ [ { "Id": 1, - "uLoginName": "laozhang", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "老张", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": "老张的哲学", - "sex": 1, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "laozhang", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "老张", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": "老张的哲学", + "Sex": 1, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 }, { "Id": 2, - "uLoginName": "laoli", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "laoli", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 1, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "laoli", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "laoli", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 1, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 }, { "Id": 3, - "uLoginName": "user", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "userli", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": "广告", - "sex": 1, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "user", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "userli", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": "广告", + "Sex": 1, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 }, { "Id": 4, - "uLoginName": "admins", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "admins", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 }, { "Id": 5, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 6, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 7, - "uLoginName": "tibug", - "uLoginPWD": "BB1C0516F0F4469549CD4A95833A78E5", - "uRealName": "提bug账号", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 1, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "tibug", + "LoginPWD": "BB1C0516F0F4469549CD4A95833A78E5", + "RealName": "提bug账号", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 1, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 }, { "Id": 8, - "uLoginName": "test", - "uLoginPWD": "098F6BCD4621D373CADE4E832627B4F6", - "uRealName": "后台测试1号", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": "测试是", - "sex": 1, - "age": 3, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "test", + "LoginPWD": "098F6BCD4621D373CADE4E832627B4F6", + "RealName": "后台测试1号", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": "测试是", + "Sex": 1, + "Age": 3, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 }, { "Id": 9, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 10, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 11, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 12, - "uLoginName": "blogadmin", - "uLoginPWD": "3FACF26687DAB7254848976256EDB56F", - "uRealName": "后台总管理员", - "uStatus": 0, - "uRemark": "t15", - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 1, - "age": 10, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "blogadmin", + "LoginPWD": "3FACF26687DAB7254848976256EDB56F", + "RealName": "后台总管理员", + "Status": 0, + "Remark": "t15", + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 1, + "Age": 10, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 }, { "Id": 13, - "uLoginName": "test2", - "uLoginPWD": "AD0234829205B9033196BA818F7A872B", - "uRealName": "后台测试2号", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 12, - "birth": "\/Date(1546272000000+0800)\/", - "addr": "北京市", - "tdIsDelete": 0 + "LoginName": "test2", + "LoginPWD": "AD0234829205B9033196BA818F7A872B", + "RealName": "后台测试2号", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 12, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": "北京市", + "IsDeleted": 0 }, { "Id": 14, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 15, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 16, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 17, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 18, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 19, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 20, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 21, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 22, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 23, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 24, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 25, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 26, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 27, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 28, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 29, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 30, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 31, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 32, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 33, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 34, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 35, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 36, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 37, - "uLoginName": "xx", - "uLoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", - "uRealName": "admins", - "uStatus": 0, - "uRemark": null, - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 0, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "xx", + "LoginPWD": "2AEFC34200A294A3CC7DB81B43A81873", + "RealName": "admins", + "Status": 0, + "Remark": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 0, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 38, - "uLoginName": "99", - "uLoginPWD": "AC627AB1CCBDB62EC96E702F7F6425B", - "uRealName": "99", - "uStatus": 0, - "uRemark": "blogadmin", - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": -1, - "age": 0, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 1 + "LoginName": "99", + "LoginPWD": "AC627AB1CCBDB62EC96E702F7F6425B", + "RealName": "99", + "Status": 0, + "Remark": "blogadmin", + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": -1, + "Age": 0, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 1 }, { "Id": 39, - "uLoginName": "Kawhi", - "uLoginPWD": "96FEE3FD714358658BFB881A4E1642BE", - "uRealName": "Kawhi 测试员", - "uStatus": 0, - "uRemark": "blogadmin", - "uCreateTime": "\/Date(1546272000000+0800)\/", - "uUpdateTime": "\/Date(1546272000000+0800)\/", - "uLastErrTime": "\/Date(1546272000000+0800)\/", - "uErrorCount": 0, - "name": null, - "sex": 1, - "age": 18, - "birth": "\/Date(1546272000000+0800)\/", - "addr": null, - "tdIsDelete": 0 + "LoginName": "Kawhi", + "LoginPWD": "96FEE3FD714358658BFB881A4E1642BE", + "RealName": "Kawhi 测试员", + "Status": 0, + "Remark": "blogadmin", + "CreateTime": "\/Date(1546272000000+0800)\/", + "UpdateTime": "\/Date(1546272000000+0800)\/", + "LastErrorTime": "\/Date(1546272000000+0800)\/", + "ErrorCount": 0, + "Name": null, + "Sex": 1, + "Age": 18, + "Birth": "\/Date(1546272000000+0800)\/", + "Address": null, + "IsDeleted": 0 } ] diff --git a/Blog.Core.Api/wwwroot/css/site.css b/Blog.Core.Api/wwwroot/css/site.css new file mode 100644 index 00000000..e679a8ea --- /dev/null +++ b/Blog.Core.Api/wwwroot/css/site.css @@ -0,0 +1,71 @@ +/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification +for details on configuring this project to bundle and minify static web assets. */ + +a.navbar-brand { + white-space: normal; + text-align: center; + word-break: break-all; +} + +/* Provide sufficient contrast against white background */ +a { + color: #0366d6; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.nav-pills .nav-link.active, .nav-pills .show > .nav-link { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +/* Sticky footer styles +-------------------------------------------------- */ +html { + font-size: 14px; +} +@media (min-width: 768px) { + html { + font-size: 16px; + } +} + +.border-top { + border-top: 1px solid #e5e5e5; +} +.border-bottom { + border-bottom: 1px solid #e5e5e5; +} + +.box-shadow { + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); +} + +button.accept-policy { + font-size: 1rem; + line-height: inherit; +} + +/* Sticky footer styles +-------------------------------------------------- */ +html { + position: relative; + min-height: 100%; +} + +body { + /* Margin bottom by footer height */ + margin-bottom: 60px; +} +.footer { + position: absolute; + bottom: 0; + width: 100%; + white-space: nowrap; + line-height: 60px; /* Vertically center the text there */ +} diff --git a/Blog.Core.Api/wwwroot/css/style.css b/Blog.Core.Api/wwwroot/css/style.css new file mode 100644 index 00000000..f8fe18dd --- /dev/null +++ b/Blog.Core.Api/wwwroot/css/style.css @@ -0,0 +1,132 @@ +@charset "utf-8"; +::selection { + background: #2D2F36; +} +::-webkit-selection { + background: #2D2F36; +} +::-moz-selection { + background: #2D2F36; +} +body { + background: white; + font-family: 'Inter UI', sans-serif; + margin: 0; + padding: 20px; +} +.page { + background: #e2e2e5; + display: flex; + flex-direction: column; + height: calc(100% - 40px); + position: absolute; + place-content: center; + width: calc(100% - 40px); +} +@media (max-width: 767px) { + .page { + height: auto; + margin-bottom: 20px; + padding-bottom: 20px; + } +} +.container { + display: flex; + height: 320px; + margin: 0 auto; + width: 640px; +} +@media (max-width: 767px) { + .container { + flex-direction: column; + height: 630px; + width: 320px; + } +} +.left { + background: white; + height: calc(100% - 40px); + top: 20px; + position: relative; + width: 50%; +} +@media (max-width: 767px) { + .left { + height: 100%; + left: 20px; + width: calc(100% - 40px); + max-height: 270px; + } +} +.login { + font-size: 33px; + font-weight: 900; + margin: 50px 40px 40px; +} +.eula { + color: #999; + font-size: 14px; + line-height: 1.5; + margin: 40px; +} +.right { + background: #474A59; + box-shadow: 0px 0px 40px 16px rgba(0,0,0,0.22); + color: #F1F1F2; + position: relative; + width: 50%; +} +@media (max-width: 767px) { + .right { + flex-shrink: 0; + height: 100%; + width: 100%; + max-height: 350px; + } +} +svg { + position: absolute; + width: 320px; +} +path { + fill: none; + stroke: url(#linearGradient);; + stroke-width: 4; + stroke-dasharray: 240 1386; +} +.form { + margin: 40px; + position: absolute; +} +label { + color: #c2c2c5; + display: block; + font-size: 14px; + height: 16px; + margin-top: 20px; + margin-bottom: 5px; +} +input { + background: transparent; + border: 0; + color: #f2f2f2; + font-size: 20px; + height: 30px; + line-height: 30px; + outline: none !important; + width: 100%; +} +input::-moz-focus-inner { + border: 0; +} +#submit { + color: #707075; + margin-top: 40px; + transition: color 300ms; +} +#submit:focus { + color: #f2f2f2; +} +#submit:active { + color: #d0d0d2; +} \ No newline at end of file diff --git a/Blog.Core.Api/wwwroot/js/anime.min.js b/Blog.Core.Api/wwwroot/js/anime.min.js new file mode 100644 index 00000000..c3993246 --- /dev/null +++ b/Blog.Core.Api/wwwroot/js/anime.min.js @@ -0,0 +1,33 @@ +/* + 2017 Julian Garnier + Released under the MIT license +*/ +var $jscomp={scope:{}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(e,r,p){if(p.get||p.set)throw new TypeError("ES3 does not support getters and setters.");e!=Array.prototype&&e!=Object.prototype&&(e[r]=p.value)};$jscomp.getGlobal=function(e){return"undefined"!=typeof window&&window===e?e:"undefined"!=typeof global&&null!=global?global:e};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_"; +$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(e){return $jscomp.SYMBOL_PREFIX+(e||"")+$jscomp.symbolCounter_++}; +$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var e=$jscomp.global.Symbol.iterator;e||(e=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[e]&&$jscomp.defineProperty(Array.prototype,e,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(e){var r=0;return $jscomp.iteratorPrototype(function(){return rb&&(b+=1);1b?c:b<2/3?a+(c-a)*(2/3-b)*6:a}var d=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(a)||/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(a);a=parseInt(d[1])/360;var b=parseInt(d[2])/100,f=parseInt(d[3])/100,d=d[4]||1;if(0==b)f=b=a=f;else{var n=.5>f?f*(1+b):f+b-f*b,k=2*f-n,f=c(k,n,a+1/3),b=c(k,n,a);a=c(k,n,a-1/3)}return"rgba("+ +255*f+","+255*b+","+255*a+","+d+")"}function y(a){if(a=/([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(a))return a[2]}function V(a){if(-1=g.currentTime)for(var G=0;G=w||!k)g.began||(g.began=!0,f("begin")),f("run");if(q>n&&q=k&&r!==k||!k)b(k),x||e();f("update");a>=k&&(g.remaining?(t=h,"alternate"===g.direction&&(g.reversed=!g.reversed)):(g.pause(),g.completed||(g.completed=!0,f("complete"),"Promise"in window&&(p(),m=c()))),l=0)}a=void 0===a?{}:a;var h,t,l=0,p=null,m=c(),g=fa(a);g.reset=function(){var a=g.direction,c=g.loop;g.currentTime= +0;g.progress=0;g.paused=!0;g.began=!1;g.completed=!1;g.reversed="reverse"===a;g.remaining="alternate"===a&&1===c?2:c;b(0);for(a=g.children.length;a--;)g.children[a].reset()};g.tick=function(a){h=a;t||(t=h);k((l+h-t)*q.speed)};g.seek=function(a){k(d(a))};g.pause=function(){var a=v.indexOf(g);-1=c&&0<=b&&1>=b){var e=new Float32Array(11);if(c!==d||b!==f)for(var k=0;11>k;++k)e[k]=a(.1*k,c,b);return function(k){if(c===d&&b===f)return k;if(0===k)return 0;if(1===k)return 1;for(var h=0,l=1;10!==l&&e[l]<=k;++l)h+=.1;--l;var l=h+(k-e[l])/(e[l+1]-e[l])*.1,n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(.001<=n){for(h=0;4>h;++h){n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(0===n)break;var m=a(l,c,b)-k,l=l-m/n}k=l}else if(0=== +n)k=l;else{var l=h,h=h+.1,g=0;do m=l+(h-l)/2,n=a(m,c,b)-k,0++g);k=m}return a(k,d,f)}}}}(),Q=function(){function a(a,b){return 0===a||1===a?a:-Math.pow(2,10*(a-1))*Math.sin(2*(a-1-b/(2*Math.PI)*Math.asin(1))*Math.PI/b)}var c="Quad Cubic Quart Quint Sine Expo Circ Back Elastic".split(" "),d={In:[[.55,.085,.68,.53],[.55,.055,.675,.19],[.895,.03,.685,.22],[.755,.05,.855,.06],[.47,0,.745,.715],[.95,.05,.795,.035],[.6,.04,.98,.335],[.6,-.28,.735,.045],a],Out:[[.25, +.46,.45,.94],[.215,.61,.355,1],[.165,.84,.44,1],[.23,1,.32,1],[.39,.575,.565,1],[.19,1,.22,1],[.075,.82,.165,1],[.175,.885,.32,1.275],function(b,c){return 1-a(1-b,c)}],InOut:[[.455,.03,.515,.955],[.645,.045,.355,1],[.77,0,.175,1],[.86,0,.07,1],[.445,.05,.55,.95],[1,0,0,1],[.785,.135,.15,.86],[.68,-.55,.265,1.55],function(b,c){return.5>b?a(2*b,c)/2:1-a(-2*b+2,c)/2}]},b={linear:A(.25,.25,.75,.75)},f={},e;for(e in d)f.type=e,d[f.type].forEach(function(a){return function(d,f){b["ease"+a.type+c[f]]=h.fnc(d)? +d:A.apply($jscomp$this,d)}}(f)),f={type:f.type};return b}(),ha={css:function(a,c,d){return a.style[c]=d},attribute:function(a,c,d){return a.setAttribute(c,d)},object:function(a,c,d){return a[c]=d},transform:function(a,c,d,b,f){b[f]||(b[f]=[]);b[f].push(c+"("+d+")")}},v=[],B=0,ia=function(){function a(){B=requestAnimationFrame(c)}function c(c){var b=v.length;if(b){for(var d=0;db&&(c.duration=d.duration);c.children.push(d)});c.seek(0);c.reset();c.autoplay&&c.restart();return c};return c};q.random=function(a,c){return Math.floor(Math.random()*(c-a+1))+a};return q}); \ No newline at end of file diff --git a/Blog.Core.Api/wwwroot/js/site.js b/Blog.Core.Api/wwwroot/js/site.js new file mode 100644 index 00000000..ac49c186 --- /dev/null +++ b/Blog.Core.Api/wwwroot/js/site.js @@ -0,0 +1,4 @@ +// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification +// for details on configuring this project to bundle and minify static web assets. + +// Write your JavaScript code. diff --git a/Blog.Core.Api/wwwroot/operateFlow.gif b/Blog.Core.Api/wwwroot/operateFlow.gif deleted file mode 100644 index d7ae6882..00000000 Binary files a/Blog.Core.Api/wwwroot/operateFlow.gif and /dev/null differ diff --git a/Blog.Core.Api/wwwroot/swg-login.html b/Blog.Core.Api/wwwroot/swg-login.html new file mode 100644 index 00000000..d361cdef --- /dev/null +++ b/Blog.Core.Api/wwwroot/swg-login.html @@ -0,0 +1,141 @@ + + + + + 登录 - 接口文档 + + + + + +

+
+
+
+ + +
欢迎使用!
+
使用真实用户账号登录,测试账号: blogadmin/blogadmin
+
+
+ + + + + + + + + +
+ + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/Blog.Core.Api/wwwroot/ui.zip b/Blog.Core.Api/wwwroot/ui.zip new file mode 100644 index 00000000..c6bc7dbd Binary files /dev/null and b/Blog.Core.Api/wwwroot/ui.zip differ 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.Clean.bat b/Blog.Core.Clean.bat new file mode 100644 index 00000000..c9683c66 --- /dev/null +++ b/Blog.Core.Clean.bat @@ -0,0 +1,31 @@ +del .\Blog.Core.Api\bin\*.* /s /q +del .\Blog.Core.Api\obj\*.* /s /q +del .\Blog.Core.Api\wwwroot\ui\*.* /s /q +del .\Blog.Core.Api\WMBlog.db /s /q +del .\Blog.Core.Common\bin\*.* /s /q +del .\Blog.Core.Common\obj\*.* /s /q +del .\Blog.Core.EventBus\bin\*.* /s /q +del .\Blog.Core.EventBus\obj\*.* /s /q +del .\Blog.Core.Extensions\bin\*.* /s /q +del .\Blog.Core.FrameWork\bin\*.* /s /q +del .\Blog.Core.Gateway\bin\*.* /s /q +del .\Blog.Core.IServices\bin\*.* /s /q +del .\Blog.Core.Model\bin\*.* /s /q +del .\Blog.Core.Repository\bin\*.* /s /q +del .\Blog.Core.Serilog.Es\bin\*.* /s /q +del .\Blog.Core.Services\bin\*.* /s /q +del .\Blog.Core.Tasks\bin\*.* /s /q +del .\Blog.Core.Tests\bin\*.* /s /q +del .\Ocelot.Provider.Nacos\bin\*.* /s /q + +del .\Blog.Core.Extensions\obj\*.* /s /q +del .\Blog.Core.FrameWork\obj\*.* /s /q +del .\Blog.Core.Gateway\obj\*.* /s /q +del .\Blog.Core.IServices\obj\*.* /s /q +del .\Blog.Core.Model\obj\*.* /s /q +del .\Blog.Core.Repository\obj\*.* /s /q +del .\Blog.Core.Serilog.Es\obj\*.* /s /q +del .\Blog.Core.Services\obj\*.* /s /q +del .\Blog.Core.Tasks\obj\*.* /s /q +del .\Blog.Core.Tests\obj\*.* /s /q +del .\Ocelot.Provider.Nacos\obj\*.* /s /q \ No newline at end of file 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/Attribute/EnumAttachedAttribute.cs b/Blog.Core.Common/Attribute/EnumAttachedAttribute.cs new file mode 100644 index 00000000..8d1ea85e --- /dev/null +++ b/Blog.Core.Common/Attribute/EnumAttachedAttribute.cs @@ -0,0 +1,28 @@ +using System; + +namespace Blog.Core.Common +{ + + [AttributeUsage(AttributeTargets.Field, Inherited = true)] + public class EnumAttachedAttribute : Attribute + { + /// + /// 标签类型 样式 + /// + public string TagType { get; set; } + /// + /// 中文描述 + /// + public string Description { get; set; } + + /// + /// 图标 + /// + public string Icon { get; set; } + + /// + /// 图标颜色 + /// + public string IconColor { get; set; } + } +} diff --git a/Blog.Core.Common/Attribute/UseTranAttribute.cs b/Blog.Core.Common/Attribute/UseTranAttribute.cs index 31accda3..5a2b0215 100644 --- a/Blog.Core.Common/Attribute/UseTranAttribute.cs +++ b/Blog.Core.Common/Attribute/UseTranAttribute.cs @@ -1,4 +1,5 @@ using System; +using Blog.Core.Common.DB; namespace Blog.Core.Common { @@ -8,6 +9,9 @@ namespace Blog.Core.Common [AttributeUsage(AttributeTargets.Method, Inherited = true)] public class UseTranAttribute : Attribute { - + /// + /// 事务传播方式 + /// + public Propagation Propagation { get; set; } = Propagation.Required; } -} +} \ 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 5bc856c1..6e1624a3 100644 --- a/Blog.Core.Common/Blog.Core.Common.csproj +++ b/Blog.Core.Common/Blog.Core.Common.csproj @@ -1,8 +1,11 @@  - - net5.0 - + + + + + + @@ -10,30 +13,45 @@ - + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + 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/AppSecretConfig.cs b/Blog.Core.Common/DB/AppSecretConfig.cs index 8bac10ca..befbc8ae 100644 --- a/Blog.Core.Common/DB/AppSecretConfig.cs +++ b/Blog.Core.Common/DB/AppSecretConfig.cs @@ -4,8 +4,8 @@ namespace Blog.Core.Common.AppConfig { public class AppSecretConfig { - private static string Audience_Secret = Appsettings.app(new string[] { "Audience", "Secret" }); - private static string Audience_Secret_File = Appsettings.app(new string[] { "Audience", "SecretFile" }); + private static string Audience_Secret = AppSettings.app(new string[] { "Audience", "Secret" }); + private static string Audience_Secret_File = AppSettings.app(new string[] { "Audience", "SecretFile" }); public static string Audience_Secret_String => InitAudience_Secret(); diff --git a/Blog.Core.Common/DB/BaseDBConfig.cs b/Blog.Core.Common/DB/BaseDBConfig.cs index 59724094..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]; @@ -31,48 +64,15 @@ private static string DifDBConnOfSecurity(params string[] conn) public static (List, List) MutiInitConn() { - List listdatabase = Appsettings.app("DBS") + 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/Propagation.cs b/Blog.Core.Common/DB/Propagation.cs new file mode 100644 index 00000000..34dfe64c --- /dev/null +++ b/Blog.Core.Common/DB/Propagation.cs @@ -0,0 +1,19 @@ +namespace Blog.Core.Common.DB; + +public enum Propagation +{ + /// + /// 默认:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中。 + /// + Required = 0, + + /// + /// 使用当前事务,如果没有当前事务,就抛出异常 + /// + Mandatory = 1, + + /// + /// 以嵌套事务方式执行 + /// + Nested = 2, +} \ No newline at end of file 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/EnumExtensions.cs b/Blog.Core.Common/Extensions/EnumExtensions.cs new file mode 100644 index 00000000..6c714880 --- /dev/null +++ b/Blog.Core.Common/Extensions/EnumExtensions.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; + +namespace Blog.Core.Common.Extensions +{ + /// + /// 枚举的扩展方法 + /// + public static class EnumExtensions + { + /// + /// 获取到对应枚举的描述-没有描述信息,返回枚举值 + /// + /// + /// + public static string EnumDescription(this Enum @enum) + { + Type type = @enum.GetType(); + string name = Enum.GetName(type, @enum); + if (name == null) + { + return null; + } + FieldInfo field = type.GetField(name); + if (!(Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute)) + { + return name; + } + return attribute?.Description; + } + public static int ToEnumInt(this Enum e) + { + try + { + return e.GetHashCode(); + } + catch (Exception) + { + return 0; + } + } + + public static List EnumToList() + { + return setEnumToList(typeof(T)); + } + + public static List EnumToList(Type enumType) + { + return setEnumToList(enumType); + } + + private static List setEnumToList(Type enumType) + { + List list = new List(); + foreach (var e in Enum.GetValues(enumType)) + { + EnumDto m = new(); + object[] attacheds = e.GetType().GetField(e.ToString()).GetCustomAttributes(typeof(EnumAttachedAttribute), true); + if (attacheds != null && attacheds.Length > 0) + { + EnumAttachedAttribute aa = attacheds[0] as EnumAttachedAttribute; + //m.Attached = aa; + m.TagType = aa.TagType; + m.Description = aa.Description; + m.Icon = aa.Icon; + m.IconColor = aa.IconColor; + } + + m.Value = Convert.ToInt32(e); + m.Name = e.ToString(); + list.Add(m); + } + return list; + } + } + + /// + /// 枚举对象 + /// + public class EnumDto + { + /// + /// 附加属性 + /// + public EnumAttachedAttribute Attached { get; set; } + + /// + /// 标签类型 + /// + public string TagType { get; set; } + /// + /// 枚举描述 + /// + public string Description { get; set; } + /// + /// 枚举名称 + /// + public string Name { get; set; } + /// + /// 枚举值 + /// + public int Value { get; set; } + + /// + /// 图标 + /// + public string Icon { get; set; } + + /// + /// 图标颜色 + /// + public string IconColor { get; set; } + } +} 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 new file mode 100644 index 00000000..84047ef1 --- /dev/null +++ b/Blog.Core.Common/Extensions/Extention.Expression.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Blog.Core.Common.Extensions +{ + public static partial class Extention + { + #region 拓展BuildExtendSelectExpre方法 + + /// + /// 组合继承属性选择表达式树,无拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,1个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,2个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,3个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 拓展类型3 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,4个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 拓展类型3 + /// 拓展类型4 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,5个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 拓展类型3 + /// 拓展类型4 + /// 拓展类型5 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,6个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 拓展类型3 + /// 拓展类型4 + /// 拓展类型5 + /// 拓展类型6 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,7个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 拓展类型3 + /// 拓展类型4 + /// 拓展类型5 + /// 拓展类型6 + /// 拓展类型7 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,8个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 拓展类型3 + /// 拓展类型4 + /// 拓展类型5 + /// 拓展类型6 + /// 拓展类型7 + /// 拓展类型8 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + /// + /// 组合继承属性选择表达式树,9个拓展参数 + /// TResult将继承TBase的所有属性 + /// + /// 原数据类型 + /// 拓展类型1 + /// 拓展类型2 + /// 拓展类型3 + /// 拓展类型4 + /// 拓展类型5 + /// 拓展类型6 + /// 拓展类型7 + /// 拓展类型8 + /// 拓展类型9 + /// 返回类型 + /// 拓展表达式 + /// + public static Expression> BuildExtendSelectExpre(this Expression> expression) where TResult : TBase + { + return GetExtendSelectExpre>(expression); + } + + #endregion + + #region 拓展And和Or方法 + + /// + /// 连接表达式与运算 + /// + /// 参数 + /// 原表达式 + /// 新的表达式 + /// + public static Expression> And(this Expression> one, Expression> another) + { + //创建新参数 + var newParameter = Expression.Parameter(typeof(T), "parameter"); + + var parameterReplacer = new ParameterReplaceVisitor(newParameter); + var left = parameterReplacer.Visit(one.Body); + var right = parameterReplacer.Visit(another.Body); + var body = Expression.AndAlso(left, right); + + return Expression.Lambda>(body, newParameter); + } + + /// + /// 连接表达式或运算 + /// + /// 参数 + /// 原表达式 + /// 新表达式 + /// + public static Expression> Or(this Expression> one, Expression> another) + { + //创建新参数 + var newParameter = Expression.Parameter(typeof(T), "parameter"); + + var parameterReplacer = new ParameterReplaceVisitor(newParameter); + var left = parameterReplacer.Visit(one.Body); + var right = parameterReplacer.Visit(another.Body); + var body = Expression.Or(left, right); + + return Expression.Lambda>(body, newParameter); + } + + #endregion + + #region 拓展Expression的Invoke方法 + + public static TResult Invoke(this Expression> expression) + { + return expression.Compile().Invoke(); + } + + public static TResult Invoke(this Expression> expression, T1 arg1) + { + return expression.Compile().Invoke(arg1); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2) + { + return expression.Compile().Invoke(arg1, arg2); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3) + { + return expression.Compile().Invoke(arg1, arg2, arg3); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + return expression.Compile().Invoke(arg1, arg2, arg3, arg4); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + return expression.Compile().Invoke(arg1, arg2, arg3, arg4, arg5); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + return expression.Compile().Invoke(arg1, arg2, arg3, arg4, arg5, arg6); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + return expression.Compile().Invoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + return expression.Compile().Invoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) + { + return expression.Compile().Invoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + } + + public static TResult Invoke(this Expression> expression, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) + { + return expression.Compile().Invoke(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); + } + + #endregion + + /// + /// 获取表达式中的固定值 + /// + /// 表达式 + /// + public static object GetConstantValue(this Expression expression) + { + var visitor = new GetConstantValueVisitor(); + visitor.Visit(expression); + return visitor.ConstantValue; + } + + public static object GetMemberValue(this Expression expression) + { + var visitor = new GetMemberValueVisitor(); + visitor.Visit(expression); + return visitor.Value; + } + + #region 私有成员 + + private static Expression GetExtendSelectExpre(Expression expression) + { + NewExpression newBody = Expression.New(typeof(TResult)); + MemberInitExpression oldExpression = (MemberInitExpression)expression.Body; + + ParameterExpression[] oldParamters = expression.Parameters.ToArray(); + List existsProperties = new List(); + foreach (var aBinding in oldExpression.Bindings) + { + existsProperties.Add(aBinding.Member.Name); + } + + List newBindings = new List(); + + var ls = typeof(TResult).GetProperties().Where(x => !existsProperties.Contains(x.Name)); + foreach (var aProperty in ls) + { + if (typeof(TBase).GetMembers().Any(x => x.Name == aProperty.Name)) + { + MemberInfo newMember = typeof(TBase).GetMember(aProperty.Name)[0]; + MemberBinding newMemberBinding = Expression.Bind(newMember, Expression.Property(oldParamters[0], aProperty.Name)); + newBindings.Add(newMemberBinding); + } + } + newBindings.AddRange(oldExpression.Bindings); + + var body = Expression.MemberInit(newBody, newBindings.ToArray()); + var resExpression = Expression.Lambda(body, oldParamters); + + return resExpression; + } + + #endregion + } + + /// + /// 继承ExpressionVisitor类,实现参数替换统一 + /// + class ParameterReplaceVisitor : ExpressionVisitor + { + public ParameterReplaceVisitor(ParameterExpression paramExpr) + { + _parameter = paramExpr; + } + + //新的表达式参数 + private ParameterExpression _parameter { get; set; } + + protected override Expression VisitParameter(ParameterExpression p) + { + if (p.Type == _parameter.Type) + return _parameter; + else + return p; + } + } + + class GetConstantValueVisitor : ExpressionVisitor + { + public object ConstantValue { get; set; } + protected override Expression VisitConstant(ConstantExpression node) + { + ConstantValue = node.Value; + + return base.VisitConstant(node); + } + } + + class GetMemberValueVisitor : ExpressionVisitor + { + public object Value { get; set; } + + } +} 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/MethodInfoExtensions.cs b/Blog.Core.Common/Extensions/MethodInfoExtensions.cs new file mode 100644 index 00000000..a19bb30a --- /dev/null +++ b/Blog.Core.Common/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,16 @@ +using System.Reflection; + +namespace Blog.Core.Common.Extensions; + +public static class MethodInfoExtensions +{ + public static string GetFullName(this MethodInfo method) + { + if (method.DeclaringType == null) + { + return $@"{method.Name}"; + } + + return $"{method.DeclaringType.FullName}.{method.Name}"; + } +} \ 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 68134874..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; } /// @@ -31,8 +37,9 @@ public static class RoutePrefix /// 前缀名 /// 如果不需要,尽量留空,不要修改 /// 除非一定要在所有的 api 前统一加上特定前缀 + /// 前缀在appsettings.json中配置 /// - public const string Name = ""; + public static string Name = ""; } /// diff --git a/Blog.Core.Common/Helper/Appsettings.cs b/Blog.Core.Common/Helper/Appsettings.cs index 229b6ef3..83e3d7e7 100644 --- a/Blog.Core.Common/Helper/Appsettings.cs +++ b/Blog.Core.Common/Helper/Appsettings.cs @@ -9,12 +9,12 @@ namespace Blog.Core.Common /// /// appsettings.json操作类 /// - public class Appsettings + public class AppSettings { - static IConfiguration Configuration { get; set; } + public static IConfiguration Configuration { get; set; } static string contentPath { get; set; } - public Appsettings(string contentPath) + public AppSettings(string contentPath) { string Path = "appsettings.json"; @@ -22,12 +22,15 @@ 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) + public AppSettings(IConfiguration configuration) { Configuration = 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 ""; } @@ -65,5 +69,24 @@ public static List app(params string[] sections) Configuration.Bind(string.Join(":", sections), list); return list; } + + + /// + /// 根据路径 configuration["App:Name"]; + /// + /// + /// + public static string GetValue(string sectionsPath) + { + try + { + return Configuration[sectionsPath]; + } + catch (Exception) + { + } + + return ""; + } } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/Base64Encoder.cs b/Blog.Core.Common/Helper/Base64Encoder.cs new file mode 100644 index 00000000..6869ffec --- /dev/null +++ b/Blog.Core.Common/Helper/Base64Encoder.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Helper +{ + /// + /// Base64编码类。 + /// 将byte[]类型转换成Base64编码的string类型。 + /// + public class Base64Encoder + { + byte[] source; + int length, length2; + int blockCount; + int paddingCount; + public static Base64Encoder Encoder = new Base64Encoder(); + + public Base64Encoder() + { + } + + private void init(byte[] input) + { + source = input; + length = input.Length; + if ((length % 3) == 0) + { + paddingCount = 0; + blockCount = length / 3; + } + else + { + paddingCount = 3 - (length % 3); + blockCount = (length + paddingCount) / 3; + } + length2 = length + paddingCount; + } + + public string GetEncoded(byte[] input) + { + //初始化 + init(input); + + byte[] source2; + source2 = new byte[length2]; + + for (int x = 0; x < length2; x++) + { + if (x < length) + { + source2[x] = source[x]; + } + else + { + source2[x] = 0; + } + } + + byte b1, b2, b3; + byte temp, temp1, temp2, temp3, temp4; + byte[] buffer = new byte[blockCount * 4]; + char[] result = new char[blockCount * 4]; + for (int x = 0; x < blockCount; x++) + { + b1 = source2[x * 3]; + b2 = source2[x * 3 + 1]; + b3 = source2[x * 3 + 2]; + + temp1 = (byte)((b1 & 252) >> 2); + + temp = (byte)((b1 & 3) << 4); + temp2 = (byte)((b2 & 240) >> 4); + temp2 += temp; + + temp = (byte)((b2 & 15) << 2); + temp3 = (byte)((b3 & 192) >> 6); + temp3 += temp; + + temp4 = (byte)(b3 & 63); + + buffer[x * 4] = temp1; + buffer[x * 4 + 1] = temp2; + buffer[x * 4 + 2] = temp3; + buffer[x * 4 + 3] = temp4; + + } + + for (int x = 0; x < blockCount * 4; x++) + { + result[x] = sixbit2char(buffer[x]); + } + + + switch (paddingCount) + { + case 0: break; + case 1: result[blockCount * 4 - 1] = '='; break; + case 2: + result[blockCount * 4 - 1] = '='; + result[blockCount * 4 - 2] = '='; + break; + default: break; + } + return new string(result); + } + private char sixbit2char(byte b) + { + char[] lookupTable = new char[64]{ + 'A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9','+','/'}; + + if ((b >= 0) && (b <= 63)) + { + return lookupTable[(int)b]; + } + else + { + + return ' '; + } + } + + } + + + /// + /// Base64解码类 + /// 将Base64编码的string类型转换成byte[]类型 + /// + public class Base64Decoder + { + char[] source; + int length, length2, length3; + int blockCount; + int paddingCount; + public static Base64Decoder Decoder = new Base64Decoder(); + + public Base64Decoder() + { + } + + private void init(char[] input) + { + int temp = 0; + source = input; + length = input.Length; + + for (int x = 0; x < 2; x++) + { + if (input[length - x - 1] == '=') + temp++; + } + paddingCount = temp; + + blockCount = length / 4; + length2 = blockCount * 3; + } + + public byte[] GetDecoded(string strInput) + { + //初始化 + init(strInput.ToCharArray()); + + byte[] buffer = new byte[length]; + byte[] buffer2 = new byte[length2]; + + for (int x = 0; x < length; x++) + { + buffer[x] = char2sixbit(source[x]); + } + + byte b, b1, b2, b3; + byte temp1, temp2, temp3, temp4; + + for (int x = 0; x < blockCount; x++) + { + temp1 = buffer[x * 4]; + temp2 = buffer[x * 4 + 1]; + temp3 = buffer[x * 4 + 2]; + temp4 = buffer[x * 4 + 3]; + + b = (byte)(temp1 << 2); + b1 = (byte)((temp2 & 48) >> 4); + b1 += b; + + b = (byte)((temp2 & 15) << 4); + b2 = (byte)((temp3 & 60) >> 2); + b2 += b; + + b = (byte)((temp3 & 3) << 6); + b3 = temp4; + b3 += b; + + buffer2[x * 3] = b1; + buffer2[x * 3 + 1] = b2; + buffer2[x * 3 + 2] = b3; + } + + length3 = length2 - paddingCount; + byte[] result = new byte[length3]; + + for (int x = 0; x < length3; x++) + { + result[x] = buffer2[x]; + } + + return result; + } + + private byte char2sixbit(char c) + { + char[] lookupTable = new char[64]{ + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N', + 'O','P','Q','R','S','T','U','V','W','X','Y', 'Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9','+','/'}; + if (c == '=') + return 0; + else + { + for (int x = 0; x < 64; x++) + { + if (lookupTable[x] == c) + return (byte)x; + } + + return 0; + } + + } + } +} diff --git a/Blog.Core.Common/Helper/CCBPayUtil.cs b/Blog.Core.Common/Helper/CCBPayUtil.cs index 6d43c39d..59ac0dde 100644 --- a/Blog.Core.Common/Helper/CCBPayUtil.cs +++ b/Blog.Core.Common/Helper/CCBPayUtil.cs @@ -96,7 +96,7 @@ private string GetSignContent(IDictionary parameters) } // Token: 0x04000001 RID: 1 - private string VERSION = "1.0.0"; + //private string VERSION = "1.0.0"; // Token: 0x04000002 RID: 2 private string MD5KEY = "20120315201809041004"; @@ -179,7 +179,7 @@ private byte[] DESEncrypt(string dataCharset, string data, string keyCharset, st byte[] bytes = Encoding.GetEncoding(keyCharset).GetBytes(key); byte[] rgbIV = bytes; byte[] bytes2 = Encoding.GetEncoding(dataCharset).GetBytes(data); - DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider(); + var descryptoServiceProvider = DES.Create(); descryptoServiceProvider.Mode = CipherMode.ECB; descryptoServiceProvider.Padding = PaddingMode.PKCS7; MemoryStream memoryStream = new MemoryStream(); @@ -204,7 +204,7 @@ private string DESDecrypt(string dataCharset, string data, string keyCoding, str byte[] bytes = Encoding.GetEncoding(keyCoding).GetBytes(key); byte[] rgbIV = bytes; byte[] array = this.Base64Decode(data); - DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider(); + var descryptoServiceProvider = DES.Create(); descryptoServiceProvider.Mode = CipherMode.ECB; descryptoServiceProvider.Padding = PaddingMode.PKCS7; MemoryStream memoryStream = new MemoryStream(); @@ -262,7 +262,7 @@ internal class MessageDigest_MD5 // Token: 0x06000020 RID: 32 RVA: 0x000028A0 File Offset: 0x00000AA0 protected internal string Md5_32(string src) { - MD5 md = new MD5CryptoServiceProvider(); + var md = MD5.Create(); byte[] bytes = Encoding.UTF8.GetBytes(src); byte[] array = md.ComputeHash(bytes); string text = ""; @@ -481,46 +481,46 @@ internal enum CRYPT_STRING_FLAGS : uint } // Token: 0x0200000B RID: 11 - internal struct CRYPT_OBJID_BLOB + internal class CRYPT_OBJID_BLOB { // Token: 0x04000028 RID: 40 - internal uint cbData; + internal uint cbData = default; // Token: 0x04000029 RID: 41 - internal IntPtr pbData; + internal IntPtr pbData = default; } // Token: 0x0200000C RID: 12 - internal struct CRYPT_ALGORITHM_IDENTIFIER + internal class CRYPT_ALGORITHM_IDENTIFIER { // Token: 0x0400002A RID: 42 - internal IntPtr pszObjId; + internal IntPtr pszObjId = default; // Token: 0x0400002B RID: 43 - internal RSACryptoServiceProviderExtension.CRYPT_OBJID_BLOB Parameters; + internal RSACryptoServiceProviderExtension.CRYPT_OBJID_BLOB Parameters = default; } // Token: 0x0200000D RID: 13 - private struct CRYPT_BIT_BLOB + private class CRYPT_BIT_BLOB { // Token: 0x0400002C RID: 44 - internal uint cbData; + internal uint cbData = default; // Token: 0x0400002D RID: 45 - internal IntPtr pbData; + internal IntPtr pbData = default; // Token: 0x0400002E RID: 46 - internal uint cUnusedBits; + internal uint cUnusedBits = default; } // Token: 0x0200000E RID: 14 - private struct CERT_PUBLIC_KEY_INFO + private class CERT_PUBLIC_KEY_INFO { // Token: 0x0400002F RID: 47 - internal RSACryptoServiceProviderExtension.CRYPT_ALGORITHM_IDENTIFIER Algorithm; + internal RSACryptoServiceProviderExtension.CRYPT_ALGORITHM_IDENTIFIER Algorithm = default; // Token: 0x04000030 RID: 48 - internal RSACryptoServiceProviderExtension.CRYPT_BIT_BLOB PublicKey; + internal RSACryptoServiceProviderExtension.CRYPT_BIT_BLOB PublicKey = default; } } } diff --git a/Blog.Core.Common/Helper/ConsoleHelper.cs b/Blog.Core.Common/Helper/Console/ConsoleHelper.cs similarity index 60% rename from Blog.Core.Common/Helper/ConsoleHelper.cs rename to Blog.Core.Common/Helper/Console/ConsoleHelper.cs index f0faef52..036c4769 100644 --- a/Blog.Core.Common/Helper/ConsoleHelper.cs +++ b/Blog.Core.Common/Helper/Console/ConsoleHelper.cs @@ -1,15 +1,25 @@ using System; -namespace Blog.Core.Common.Helper +namespace Blog.Core.Common { public static class ConsoleHelper { + private static readonly object _objLock = new(); + + /// + /// 在控制台输出 + /// + /// 文本 + /// 前颜色 public static void WriteColorLine(string str, ConsoleColor color) { - ConsoleColor currentForeColor = Console.ForegroundColor; - Console.ForegroundColor = color; - Console.WriteLine(str); - Console.ForegroundColor = currentForeColor; + lock (_objLock) + { + ConsoleColor currentForeColor = Console.ForegroundColor; + Console.ForegroundColor = color; + Console.WriteLine(str); + Console.ForegroundColor = currentForeColor; + } } /// @@ -17,38 +27,27 @@ public static void WriteColorLine(string str, ConsoleColor color) /// /// 待打印的字符串 /// 想要打印的颜色 - public static void WriteErrorLine(this string str, ConsoleColor color = ConsoleColor.Red) - { - WriteColorLine(str, color); - } + public static void WriteErrorLine(this string str, ConsoleColor color = ConsoleColor.Red)=> WriteColorLine(str, color); /// /// 打印警告信息 /// /// 待打印的字符串 /// 想要打印的颜色 - public static void WriteWarningLine(this string str, ConsoleColor color = ConsoleColor.Yellow) - { - WriteColorLine(str, color); - } + public static void WriteWarningLine(this string str, ConsoleColor color = ConsoleColor.Yellow)=> WriteColorLine(str, color); + /// /// 打印正常信息 /// /// 待打印的字符串 /// 想要打印的颜色 - public static void WriteInfoLine(this string str, ConsoleColor color = ConsoleColor.White) - { - WriteColorLine(str, color); - } + public static void WriteInfoLine(this string str, ConsoleColor color = ConsoleColor.White)=> WriteColorLine(str, color); + /// /// 打印成功的信息 /// /// 待打印的字符串 /// 想要打印的颜色 - public static void WriteSuccessLine(this string str, ConsoleColor color = ConsoleColor.Green) - { - WriteColorLine(str, color); - } - + public static void WriteSuccessLine(this string str, ConsoleColor color = ConsoleColor.Green)=> WriteColorLine(str, color); } } diff --git a/Blog.Core.Common/Helper/Console/Table/ColumnShowFormat.cs b/Blog.Core.Common/Helper/Console/Table/ColumnShowFormat.cs new file mode 100644 index 00000000..04070253 --- /dev/null +++ b/Blog.Core.Common/Helper/Console/Table/ColumnShowFormat.cs @@ -0,0 +1,39 @@ +namespace System +{ + /// + /// 列显示格式信息 + /// + public class ColumnShowFormat + { + public ColumnShowFormat(int index, int strLength, Alignment alignment) + { + Index = index; + StrLength = strLength; + Alignment = alignment; + } + + /// + /// 索引,第几列数据 + /// + public int Index { get; set; } + + /// + /// 对其方式 + /// + public Alignment Alignment { get; set; } + + /// + /// 一列字符串长度 + /// + public int StrLength { get; set; } + } + + /// + /// 对其方式 + /// + public enum Alignment + { + Left, + Right + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/Console/Table/ConsoleTable.cs b/Blog.Core.Common/Helper/Console/Table/ConsoleTable.cs new file mode 100644 index 00000000..b99a6683 --- /dev/null +++ b/Blog.Core.Common/Helper/Console/Table/ConsoleTable.cs @@ -0,0 +1,330 @@ +using Blog.Core.Common; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace System +{ + public class ConsoleTable + { + #region 属性 + + /// + /// 表格头部字符串 + /// + public string TitleString { get; set; } + + /// + /// 表格的列 + /// + public IList Columns + { + get + { + if (_columns == null) _columns = new List(); + return _columns; + } + set + { + _columns = value; + _finalColumnWides = new List(); + } + } + + /// + /// 行 + /// + public List Rows { get; set; } = new List(); + + /// + /// 列宽 + /// + public List ColumnWides { get; set; } = new List(); + + /// + /// 空白字符数量 + /// + public int ColumnBlankNum { get; set; } = 4; + + /// + /// 对其方式 + /// + public Alignment Alignment { get; set; } = Alignment.Left; + + /// + /// 是否显示行数 + /// + public bool EnableCount { get; set; } = false; + + /// + /// 表格显示样式 + /// 每次设置样子后就会重置 StyleInfo + /// + public TableStyle TableStyle + { + get + { + return _tableStyle; + } + set + { + if (_tableStyle == value) return; + _tableStyle = value; + _formatInfo = null; + } + } + + #endregion 属性 + + #region 私有信息 + private IList _columns; + private TableStyle _tableStyle; + private StyleInfo _formatInfo; + private List _columnShowFormats = new List(); + private List _finalColumnWides = new List(); + + /// + /// 通过 Format 获得到表格显示样式 + /// + private StyleInfo FormatInfo + { + get + { + if (_formatInfo == null) + _formatInfo = _tableStyle.GetFormatInfo();//得到样式信息 + return _formatInfo; + } + set + { + _formatInfo = value; + } + } + + /// + /// 每一列的宽度 + /// + private List FinalColumnWides + { + get + { + if (_finalColumnWides is null || _finalColumnWides.Count < 1) + { + // 得到每一列最大的宽度 + List _columnWides = Columns.GetColumnWides(Rows); + // 替换用户输入长度 + ColumnWides ??= new List(); + for (int i = 0; i < ColumnWides.Count; i++) _columnWides[i] = ColumnWides[i]; + _finalColumnWides = _columnWides; + } + return _finalColumnWides; + } + } + + /// + /// 每一列显示的基本信息 + /// + private List ColumnShowFormats + { + get + { + if (_columnShowFormats.Count == 0) + { + for (int i = 0; i < Columns.Count; i++) _columnShowFormats.Add(new ColumnShowFormat(i, FinalColumnWides[i], Alignment)); + } + return _columnShowFormats; + } + } + + #endregion 私有信息 + + #region 配置数据 + + /// + /// 添加列 + /// + /// 列明 + /// 列的宽 + /// + public ConsoleTable AddColumn(string columnName, int columnWide = 0) + { + Columns.Add(columnName); + columnWide = columnWide == 0 ? columnName.Length : columnWide; + _finalColumnWides.Add(columnWide); + return this; + } + + /// + /// 添加行 + /// + /// 该行数据 + /// + public ConsoleTable AddRow(params string[] values) + { + _ = values ?? throw new ArgumentNullException(nameof(values)); + + Rows.Add(values); + return this; + } + + /// + /// 加载 List 对象的数据 + /// + /// + /// + /// + public static ConsoleTable From(IEnumerable values) + { + ConsoleTable table = new(); + + List columns = GetColumns().Where(c => !string.IsNullOrWhiteSpace(c)).ToList(); + columns.ForEach(c => + { + table.AddColumn(c); + }); + + values.ToList().ForEach(value => + { + table.AddRow(columns.Select(c => GetColumnValue(value, c)).ToArray()); + }); + + return table; + } + + #endregion 配置数据 + + /// + /// 获取表格字符串 + /// + /// + public override string ToString() + { + StringBuilder builder = new(); + + builder.AppendLine(GetHeader()); + builder.AppendLine(GetExistData()); + builder.AppendLine(GetEnd()); + + return builder.ToString(); + } + + /// + /// 绘制表格 + /// + /// 样式 + /// title颜色 + public void Writer(ConsoleColor color = ConsoleColor.White) + { + ConsoleHelper.WriteColorLine(GetHeader(), color); + ConsoleHelper.WriteInfoLine(GetExistData()); + ConsoleHelper.WriteColorLine(GetEnd(), color); + } + + #region 帮助方法 + + /// + /// 获取完成头 + /// + /// + public string GetHeader() + { + // 创建顶部和底部分隔线 + string top_DownDividerdivider = FinalColumnWides.GetTopAndDwon(FormatInfo.AngleStr, ColumnBlankNum); + // 创建分隔线 + string divider = FinalColumnWides.GetDivider(FormatInfo.AngleStr, ColumnBlankNum); + // 获取标题字符串 + string tilte = FinalColumnWides.GetTitleStr(TitleString, ColumnBlankNum, FormatInfo.DelimiterStr); + // 得到头部字符串 + string headers = ColumnShowFormats.FillFormatTostring(Columns.ToArray(), FormatInfo.DelimiterStr, ColumnBlankNum); + + //绘制表格头 + StringBuilder top = new(); + if (FormatInfo.IsShowTop_Down_DataBorder) top.AppendLine(top_DownDividerdivider); + if (!string.IsNullOrWhiteSpace(tilte)) + { + top.AppendLine(tilte); + top.AppendLine(divider); + } + top.AppendLine(headers); + top.AppendLine(divider); + return top.ToString().Trim(); + } + + /// + /// 获取现有数据 + /// + /// + public string GetExistData() + { + // 创建分隔线 + string divider = FinalColumnWides.GetDivider(FormatInfo.AngleStr, ColumnBlankNum); + // 得到每行数据的字符串 + List rowStrs = Rows.Select(row => ColumnShowFormats.FillFormatTostring(row, FormatInfo.DelimiterStr, ColumnBlankNum)).ToList(); + StringBuilder data = new(); + for (int i = 0; i < rowStrs.Count; i++) + { + if (FormatInfo.IsShowTop_Down_DataBorder && i != 0) data.AppendLine(divider); + data.AppendLine(rowStrs[i]); + } + return data.ToString().Trim(); + } + + /// + /// 获取新行数据 + /// + /// + /// + public string GetNewRow(string[] row) + { + if (row is null) return ""; + + Rows.Add(row); + //内容 + StringBuilder data = new(); + if (Rows.Count > 1) data.AppendLine(FinalColumnWides.GetDivider(FormatInfo.AngleStr, ColumnBlankNum)); + data.AppendLine(ColumnShowFormats.FillFormatTostring(row, FormatInfo.DelimiterStr, ColumnBlankNum)); + return data.ToString().Trim(); + } + + /// + /// 获取底 + /// + /// + public string GetEnd() + { + StringBuilder down = new(); + if (FormatInfo.IsShowTop_Down_DataBorder) down.AppendLine(FinalColumnWides.GetTopAndDwon(FormatInfo.AngleStr, ColumnBlankNum)); + if (EnableCount) down.AppendLine($" Count: {Rows.Count}"); + return down.ToString().Trim(); + } + + /// + /// 获取列名 + /// + /// + /// + private static IEnumerable GetColumns() + { + return typeof(T).GetProperties().Select(x => x.Name).ToArray(); + } + + /// + /// 获取列值 + /// + /// 类型 + /// 数据 + /// 列名 + /// + private static string GetColumnValue(T obj, string column) + { + if (obj == null) return null; + + JObject o = obj as JObject ?? (JObject)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + + return o.GetValue(column).ToString(); + } + + #endregion 帮助方法 + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/Console/Table/DrawTableInfo.cs b/Blog.Core.Common/Helper/Console/Table/DrawTableInfo.cs new file mode 100644 index 00000000..850f4ea8 --- /dev/null +++ b/Blog.Core.Common/Helper/Console/Table/DrawTableInfo.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace System +{ + /// + /// 绘制表格需要的信息 + /// + public class DrawTableInfo + { + /// + /// 顶部和底部字符串分隔线 + /// + public string Top_DownDivider { get; set; } + + /// + /// 分隔线 + /// + public string Divider { get; set; } + + /// + /// 标题 + /// + public string Title { get; set; } + + /// + /// 头部 + /// + public string Header { get; set; } + + /// + /// 数据 + /// + public List Data { get; set; } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/Console/Table/TableExtension.cs b/Blog.Core.Common/Helper/Console/Table/TableExtension.cs new file mode 100644 index 00000000..6e26a718 --- /dev/null +++ b/Blog.Core.Common/Helper/Console/Table/TableExtension.cs @@ -0,0 +1,151 @@ +using Blog.Core; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace System +{ + public static class TableExtension + { + /// + /// 按照现有数据计算每列最大宽度 + /// + /// 列信息 + /// 现有行数据 + /// 每一列显示宽度 + public static List GetColumnWides(this IList columns, IList rows) + { + List columnLengths = columns.Select((t, i) => + rows.Select(x => x[i])//得到所有行当前列的数据 + .Union(new[] { columns[i] })//连接当前列标题 + .Where(x => x != null) + .Select(x => x.ObjToString().FullHalfLength())//得到该列每一行的字符串长度(计算中文占用两格) + .Max())//到该列中长度最大的以列 + .ToList(); + return columnLengths; + } + + /// + /// 将填充格式转成字符串 + /// 表头和数据行会用到 + /// + /// 一行的显示格式信息 + /// 一行要显示的数据 + /// 间隔符 + /// 每列留白数 + /// + public static string FillFormatTostring(this List format, string[] objs, string delimiterStr, int columnBlankNum) + { + string formatStr = string.Empty; + format.ForEach(f => + { + string ali = f.Alignment == Alignment.Right ? "" : "-"; + string val = objs[f.Index].ObjToString(); + if (val.Length > f.StrLength) + { + //val = val[0..f.StrLength]; + //val = val[0..(val.Length - val.GetChineseText().Length)]; + objs[f.Index] = "...";//标记超出长度 + } + + if (!string.IsNullOrWhiteSpace(formatStr)) formatStr += $"{"".PadLeft(columnBlankNum, ' ')}"; + int alignmentStrLength = Math.Max(f.StrLength - objs[f.Index].ObjToString().GetChineseText().Length, 0);//对其填充空格数量 + formatStr += $"{delimiterStr}{"".PadLeft(columnBlankNum, ' ')}{{{f.Index},{ali}{alignmentStrLength}}}"; + }); + formatStr += $"{"".PadLeft(columnBlankNum, ' ')}{delimiterStr}"; + return string.Format(formatStr, objs); + } + + /// + /// 获取title 字符串 + /// + /// > + /// 标题字符串信息 + /// 列两端留白数 + /// 每列之间分割字符串 + /// + public static string GetTitleStr(this List columnWides, string titleStr, int columnBlankNum, string delimiterStr) + { + if (string.IsNullOrWhiteSpace(titleStr)) return ""; + //一行的宽度 + int rowWide = columnWides.Sum() + columnWides.Count * 2 * columnBlankNum + columnWides.Count + 1; + int blankNum = (rowWide - titleStr.FullHalfLength()) / 2 - 1; + string tilte = $"{delimiterStr}{"".PadLeft(blankNum, ' ')}{titleStr}{"".PadLeft(blankNum, ' ')}{delimiterStr}"; + if (tilte.FullHalfLength() != rowWide) tilte = tilte.Replace($" {delimiterStr}", $" {delimiterStr}"); + return tilte; + } + + /// + /// 获取每行之间的分割行字符串 + /// + /// 列宽信息 + /// 每列之间分割字符串 + /// 列两端留白数 + /// + public static string GetDivider(this List columnWides, string angleStr, int columnBlankNum) + { + string divider = ""; + columnWides.ForEach(i => + { + divider += $"{angleStr}{"".PadRight(i + columnBlankNum * 2, '-')}"; + }); + divider += angleStr; + return divider; + } + + /// + /// 获取头部和底部字符串 + /// + /// 列宽信息 + /// 每列之间分割字符串 + /// 列两端留白数 + /// + public static string GetTopAndDwon(this List columnWides, string angleStr, int columnBlankNum) + { + string top_DownDividerdivider = ""; + columnWides.ForEach(i => + { + if (string.IsNullOrWhiteSpace(top_DownDividerdivider)) top_DownDividerdivider += $"{angleStr}{"".PadRight(i + columnBlankNum * 2, '-')}"; + else top_DownDividerdivider += $"{"".PadRight(i + columnBlankNum * 2 + 1, '-')}"; + }); + top_DownDividerdivider += angleStr; + return top_DownDividerdivider; + } + + /// + /// 获取表格显示样式 + /// + /// + /// + public static StyleInfo GetFormatInfo(this TableStyle format) + { + return format switch + { + TableStyle.Default => new StyleInfo("|", true, "-"), + TableStyle.MarkDown => new StyleInfo("|", false, "|"), + TableStyle.Alternative => new StyleInfo("|", true, "+"), + TableStyle.Minimal => new StyleInfo("", false, "-"), + _ => new StyleInfo(), + }; + } + + /// + /// 获取文本长度,区分全角半角 + /// 全角算两个字符 + /// + /// + public static int FullHalfLength(this string text) + { + return Regex.Replace(text, "[^\x00-\xff]", "**").Length; + //可使用以下方法,不过要看在不同编码中字节数 + //return Encoding.Default.GetByteCount(text); + } + + /// + /// 获取中文文本 + /// + /// + /// + public static string GetChineseText(this string text) => Regex.Replace(text, "[\x00-\xff]", ""); + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/Console/Table/TableStyle.cs b/Blog.Core.Common/Helper/Console/Table/TableStyle.cs new file mode 100644 index 00000000..20a662d4 --- /dev/null +++ b/Blog.Core.Common/Helper/Console/Table/TableStyle.cs @@ -0,0 +1,57 @@ +namespace System +{ + /// + /// 表格显示样式 + /// + public enum TableStyle + { + /// + /// 默认格式的表格 + /// + Default = 0, + + /// + /// Markdwon格式的表格 + /// + MarkDown = 1, + + /// + /// 交替格式的表格 + /// + Alternative = 2, + + /// + /// 最简格式的表格 + /// + Minimal = 3 + } + + /// + /// 表格显示样式信息 + /// 通过 Format 获取到的 + /// + public class StyleInfo + { + public StyleInfo(string delimiterStr = "|", bool isShowTop_Down_DataBorder = true, string angleStr = "-") + { + DelimiterStr = delimiterStr; + IsShowTop_Down_DataBorder = isShowTop_Down_DataBorder; + AngleStr = angleStr; + } + + /// + /// 每一列数据之间的间隔字符串 + /// + public string DelimiterStr { get; set; } + + /// + /// 是否显示顶部,底部,和每一行数据之间的横向边框 + /// + public bool IsShowTop_Down_DataBorder { get; set; } + + /// + /// 边角字符串 + /// + public string AngleStr { get; set; } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/DynamicLinqFactory.cs b/Blog.Core.Common/Helper/DynamicLinqFactory.cs new file mode 100644 index 00000000..adb57074 --- /dev/null +++ b/Blog.Core.Common/Helper/DynamicLinqFactory.cs @@ -0,0 +1,663 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using Mapster; + +namespace Blog.Core.Common.Helper +{ + #region 动态linq帮助类,连接符号,运算符号 + + /// + /// 动态linq工厂 + /// + public static class DynamicLinqFactory + { + private static readonly Dictionary _operatingSystems = new Dictionary(); + public static Dictionary OperatingSystems => GetOperationSymbol(); + + private static readonly Dictionary _linkSymbols = new Dictionary(); + public static Dictionary LinkSymbols => GetLinkSymbol(); + + /// + /// 生成lambd表达式(如:CompanyID != 1 & CompanyID == 1) + /// + /// + /// + /// + public static Expression> CreateLambda(string propertyStr) + { + // 设置自定义lanbd + // 定义 lanbd 种子(p=> xxxxxx)中的 p + if (string.IsNullOrWhiteSpace(propertyStr)) + return LinqHelper.True(); //为空就返回空的表达式 + + var parameter = Expression.Parameter(typeof(TSource), "p"); + var strArr = SplitOperationSymbol(propertyStr); + + + // 第一个判断条件,固定一个判断条件作为最左边 + Expression mainExpressin = ExpressionStudio(null, strArr[0], parameter); + // 将需要放置在最左边的判断条件从列表中去除,因为已经合成到表达式最左边了 + strArr.RemoveAt(0); + + foreach (var x in strArr) + { + mainExpressin = ExpressionStudio(mainExpressin, x, parameter); + } + + return mainExpressin.ToLambda>(parameter); + } + + /// + /// 组合条件判断表达式 + /// + /// 左边的表达式 + /// + /// + /// + public static Expression ExpressionStudio(Expression left, DynamicLinqHelper dynamicLinq, ParameterExpression key) + { + Expression mainExpression = key; + + 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++; + } + } + + Expression right = null; + if (dynamicLinq.IsMerge && dynamicLinq.Child.Any()) + { + 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 + // 如果左边表达式为空,则当前的表达式就为最左边 + ? right + // 如果不为空,则将当前的表达式连接到左边 + : ChangeLinkSymbol(dynamicLinq.LinkSymbol, left, right); + return left; + } + + public static Expression ExpressionStudioEnumerable(Expression left, Expression property, DynamicLinqHelper dynamicLinq, string[] properties) + { + var realType = property.Type.GenericTypeArguments[0]; + + var parameter = Expression.Parameter(realType, "z"); + Expression mainExpression = property; + if (!properties.Any()) + { + throw new ApplicationException("条件表达式错误,属性为集合时,需要明确具体属性"); + } + + dynamicLinq.Left = string.Join(".", properties); + mainExpression = ExpressionStudio(null, dynamicLinq, parameter); + + var lambda = Expression.Lambda(mainExpression, parameter); + + mainExpression = Expression.Call(typeof(Enumerable), "Any", new[] {realType}, property, lambda); + + 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; + } + + 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 (LinkSymbols.TryGetValue(token, out var symbol)) + { + if (dys.Count > 0) + { + var linqHelper = dys.Peek(); + linqHelper.Child.Add(dynamicLinqHelper); + } + else + { + outList.Add(dynamicLinqHelper); + } + + dynamicLinqHelper = new DynamicLinqHelper() + { + LinkSymbol = symbol, + }; + continue; + } + + if (OperatingSystems.TryGetValue(token.ToLower(), out var system)) + { + dynamicLinqHelper!.OperationSymbol = system; + continue; + } + + + if (dynamicLinqHelper!.OperationSymbol != OperationSymbol.In) + { + if (string.Equals(token.Trim(), "(")) + { + 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); + + dynamicLinqHelper = dya; + continue; + } + else + { + var dya = dys.Pop(); + dya.Child.Add(dynamicLinqHelper); + outList.Add(dya); + dynamicLinqHelper = null; + continue; + } + } + } + + + if (dynamicLinqHelper!.OperationSymbol is null) + { + dynamicLinqHelper.Left += token; + } + else + { + dynamicLinqHelper.Right += FormatValue(token); + } + + if (i == end) + { + outList.Add(dynamicLinqHelper); + dynamicLinqHelper = null; + } + } + } + + public static string FormatValue(string str) + { + return str.TrimStart('"').TrimEnd('"'); + // return str.TrimStart('"').TrimEnd('"').Replace(@"\""", @""""); + } + + + /// + /// 将运算枚举符号转成具体使用方法 + /// + public static Expression ChangeLinkSymbol(LinkSymbol symbol, Expression left, Expression right) + { + switch (symbol) + { + case LinkSymbol.OrElse: + return left.OrElse(right); + case LinkSymbol.AndAlso: + return left.AndAlso(right); + default: + return left; + } + } + + public static Dictionary GetOperationSymbol() + { + if (_operatingSystems.Any()) return _operatingSystems; + + var fielding = typeof(OperationSymbol).GetFields(); + foreach (var item in fielding) + { + if (item.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute attr && !attr.Name.IsNullOrEmpty()) + { + foreach (var name in attr.Name.Split(';')) + { + _operatingSystems.Add(name.ToLower(), (OperationSymbol) item.GetValue(null)); + } + } + } + + return _operatingSystems; + } + + public static Dictionary GetLinkSymbol() + { + if (_linkSymbols.Any()) return _linkSymbols; + + var fielding = typeof(LinkSymbol).GetFields(); + foreach (var item in fielding) + { + if (item.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute attr && !attr.Name.IsNullOrEmpty()) + { + foreach (var name in attr.Name.Split(';')) + { + _linkSymbols.Add(name, (LinkSymbol) item.GetValue(null)); + } + } + } + + return _linkSymbols; + } + + + public static string FormatString(string str) + { + 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 + { + if ('\"'.Equals(character)) + { + var andIndex = str.IndexOf("\" &", firstIndex); + var orIndex = str.IndexOf("\" |", firstIndex); + var andOrIndex = Math.Min(andIndex, orIndex); + andOrIndex = andOrIndex == -1 ? Math.Max(andOrIndex, orIndex) : andOrIndex; + + if (andOrIndex != -1) + { + lastIndex = andOrIndex; + } + else + { + if (i == firstIndex) continue; + if (i == str.Length - 1 || str[i + 1].IsNullOrEmpty()) lastIndex = i; + } + } + + if (lastIndex != -1) + { + var temp = str.Substring(firstIndex + 1, lastIndex - firstIndex - 1).Replace(@"""", @"\"""); + sb.Append($" \"{temp}\" "); + + i = lastIndex; + firstIndex = -1; + lastIndex = -1; + continue; + } + } + + if (firstIndex != -1) continue; + + sb.Append(character); + } + + return sb.ToString(); + } + + /// tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces + public static readonly string _pattern = @"\s*(" + string.Join("|", new string[] + { + // operators and punctuation that are longer than one char: longest first + 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 Expression ChangeOperationSymbol(OperationSymbol? symbol, Expression key, object right) + { + // 将右边数据类型强行转换成左边一样的类型 + // 两者如果Type不匹配则无法接下去的运算操作,抛出异常 + object newTypeRight; + if (right == null || string.IsNullOrEmpty(right.ToString()) || right.ToString() == "null") + { + newTypeRight = null; + } + else + { + if (symbol == OperationSymbol.In) + { + newTypeRight = right.ChangeTypeList(key.Type); + } + else + { + newTypeRight = right.ChangeType(key.Type); + } + } + + + // 根据当前枚举类别判断使用那种比较方法 + switch (symbol) + { + case OperationSymbol.Equal: + return key.Equal(Expression.Constant(newTypeRight)); + case OperationSymbol.GreaterThan: + { + if (key.Type == typeof(string)) + return key.Contains(Expression.Constant(newTypeRight)); //对string 特殊处理 由于string + return key.GreaterThan(Expression.Constant((newTypeRight))); + } + case OperationSymbol.GreaterThanOrEqual: + { + if (key.Type == typeof(string)) + return key.Contains(Expression.Constant(newTypeRight, typeof(string))); + return key.GreaterThanOrEqual(Expression.Constant(newTypeRight)); + } + + case OperationSymbol.LessThan: + { + if (key.Type == typeof(string)) + return key.Contains(Expression.Constant(newTypeRight, typeof(string))); + return key.LessThan(Expression.Constant((newTypeRight))); + } + case OperationSymbol.LessThanOrEqual: + { + if (key.Type == typeof(string)) + return key.Contains(Expression.Constant(newTypeRight, typeof(string))); + 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: + return Expression.Constant(newTypeRight).Contains(key); + default: + throw new ArgumentException("OperationSymbol IS NULL"); + } + } + } + + /// + /// 动态linq帮助类 + /// + public class DynamicLinqHelper + { + [Display(Name = "左")] + public string Left { get; set; } + + [Display(Name = "右")] + public string Right { get; set; } + + [Display(Name = "运算符")] + public OperationSymbol? OperationSymbol { get; set; } + + [Display(Name = "连接符")] + public LinkSymbol LinkSymbol { get; set; } + + /// + /// 是否是合并 用于括号 + /// + public bool IsMerge { get; set; } = false; + + /// + /// 再有括号时候使用 + /// + public List Child { get; set; } + } + + /// + /// 连接符枚举(将来可能会包含 括号 ) + /// + public enum LinkSymbol + { + [Display(Name = "&&;&")] + AndAlso, + + [Display(Name = "||;|")] + OrElse, + + Empty + } + + /// + /// 常用比较运算符 > , >= , == , < , <= , != ,Contains + /// + public enum OperationSymbol + { + [Display(Name = "in")] + In, + + [Display(Name = "like;contains;%=")] + Contains, + + [Display(Name = "StartsLike;StartsContains;%>")] + StartsContains, + + [Display(Name = "EndLike;EndContains;%<")] + EndContains, + + [Display(Name = ">")] + GreaterThan, + + [Display(Name = ">=")] + GreaterThanOrEqual, + + [Display(Name = "<")] + LessThan, + + [Display(Name = "<=")] + LessThanOrEqual, + + [Display(Name = "==;=")] + Equal, + + [Display(Name = "!=")] + NotEqual + } + + #endregion + + + /// + /// Queryable扩展 + /// + public static class QueryableExtensions + { + #region 自定义扩展Queryable + + /// + /// Where扩展 + /// + public static IEnumerable IWhere(this IEnumerable source, string linqStr) + { + return source.Where(DynamicLinqFactory.CreateLambda(linqStr).Compile()); + } + + /// + /// FirstOrDefault扩展 + /// + public static TSource IFirstOrDefault(this IEnumerable source, string linqStr) + { + return source.FirstOrDefault(DynamicLinqFactory.CreateLambda(linqStr).Compile()); + } + + /// + /// Count扩展 + /// + public static Int32 ICount(this IEnumerable source, string linqStr) + { + return source.Count(DynamicLinqFactory.CreateLambda(linqStr).Compile()); + } + + /// + /// 自定义排序 + /// + public static IOrderedQueryable ISort(this IQueryable source, string orderByProperty, bool asc) + { + string command = asc ? "OrderBy" : "OrderByDescending"; + var type = typeof(TSource); + var property = type.GetProperty(orderByProperty); + 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); + } + + /// + /// 自定义分页 + /// + /// + /// + /// + /// + /// + public static IQueryable IPaging(this IQueryable source, int nowPage, int pageSize) + { + return source.ISkip((nowPage - 1) * pageSize).ITake(pageSize); + } + + /// + /// 自定义Skip + /// + public static IQueryable ISkip(this IQueryable source, int count) + { + return source.Provider.CreateQuery(Expression.Call( + // 类别 + typeof(Queryable), + // 调用的方法 + "Skip", + // 元素类别 + new Type[] {source.ElementType}, + // 调用的表达树 + source.Expression, + // 参数 + Expression.Constant(count))); + } + + /// + /// 自定义Take + /// + public static IQueryable ITake(this IQueryable source, int count) + { + return source.Provider.CreateQuery(Expression.Call( + // 类别 + typeof(Queryable), + // 调用的方法 + "Take", + // 元素类别 + new Type[] {source.ElementType}, + // 调用的表达树 + source.Expression, + // 参数 + Expression.Constant(count))); + } + + /// + /// 自定义去重复 + /// + public static IEnumerable IDistinctBy(this IEnumerable source, Func keySelector) + { + var seenKeys = new HashSet(); + return source.Where(element => seenKeys.Add(keySelector(element))); + } + + /// + /// 动态赋值 + /// + public static void CopyTo(this object source, T target) where T : class, new() + { + if (source == null) + return; + + if (target == null) + { + target = new T(); + } + + foreach (var property in target.GetType().GetProperties()) + { + // 这里可以判断一下当前属性值是否为空的 source.GetType().GetProperty(property.Name).GetValue(source, null) + target.GetType().InvokeMember(property.Name, BindingFlags.SetProperty, null, target, + new object[] {source.GetType().GetProperty(property.Name).GetValue(source, null)}); + } + } + + /// + /// 移除特殊字段数据 + /// + public static void RemoveSpecialPropertyValue(this object source) + { + var properties = source.GetType().GetProperties(); + foreach (var x in properties) + { + if (x.GetAccessors().Any(y => y.IsVirtual)) + { + source.GetType().GetProperty(x.Name).SetValue(source, null, null); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/FileHelper.cs b/Blog.Core.Common/Helper/FileHelper.cs index c07c86e7..01c65a7c 100644 --- a/Blog.Core.Common/Helper/FileHelper.cs +++ b/Blog.Core.Common/Helper/FileHelper.cs @@ -74,7 +74,7 @@ public static string GetPostfixStr(string filename) public static string GetAvailableFileWithPrefixOrderSize(string folderPath, string prefix, int size = 1 * 1024 * 1024, string ext = ".log") { var allFiles = new DirectoryInfo(folderPath); - var selectFiles = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(prefix.ToLower()) && fi.Extension.ToLower() == ext.ToLower() && fi.Length < size).OrderByDescending(d=>d.Name).ToList(); + var selectFiles = allFiles.GetFiles().Where(fi => fi.Name.ToLower().Contains(prefix.ToLower()) && fi.Extension.ToLower() == ext.ToLower() && fi.Length < size).OrderByDescending(d => d.Name).ToList(); if (selectFiles.Count > 0) { @@ -96,7 +96,7 @@ public static string GetAvailableFileNameWithPrefixOrderSize(string _contentRoot if (selectFiles.Count > 0) { - return selectFiles.FirstOrDefault().Name.Replace(".log",""); + return selectFiles.FirstOrDefault().Name.Replace(".log", ""); } return $@"{prefix}_{DateTime.Now.DateToTimeStamp()}"; @@ -130,6 +130,23 @@ public static void WriteFile(string Path, string Strings) f2.Close(); f2.Dispose(); } + /// + /// 写文件 + /// + /// 文件路径 + /// 文件内容 + public static void WriteFile(string Path, byte[] buf) + { + if (!File.Exists(Path)) + { + FileStream f = File.Create(Path); + f.Close(); + } + FileStream f2 = new FileStream(Path, FileMode.Create, FileAccess.Write); + f2.Write(buf, 0, buf.Length); + f2.Close(); + f2.Dispose(); + } /// /// 写文件 diff --git a/Blog.Core.Common/Helper/GenericTypeExtensions.cs b/Blog.Core.Common/Helper/GenericTypeExtensions.cs new file mode 100644 index 00000000..aa095e2a --- /dev/null +++ b/Blog.Core.Common/Helper/GenericTypeExtensions.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; + +namespace Blog.Core.Common.Helper +{ + public static class GenericTypeExtensions + { + /// + /// 判断类型是否实现某个泛型 + /// + /// 类型 + /// 泛型类型 + /// 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 54d56613..82f9c32f 100644 --- a/Blog.Core.Common/Helper/HttpHelper.cs +++ b/Blog.Core.Common/Helper/HttpHelper.cs @@ -1,89 +1,58 @@ -using System.IO; -using System.Net; -using System.Text; +using System; +using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; namespace Blog.Core.Common.Helper { + /// + /// httpclinet请求方式,请尽量使用IHttpClientFactory方式 + /// public class HttpHelper { - public static string Get(string serviceAddress) - { - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceAddress); - request.Method = "GET"; - request.ContentType = "text/html;charset=UTF-8"; - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - Stream myResponseStream = response.GetResponseStream(); - StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8); - string retString = myStreamReader.ReadToEnd(); - myStreamReader.Close(); - myResponseStream.Close(); - return retString; - } - public static async Task GetAsync(string serviceAddress) - { - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceAddress); - request.Method = "GET"; - request.ContentType = "text/html;charset=UTF-8"; - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - Stream myResponseStream = response.GetResponseStream(); - StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8); - string retString = await myStreamReader.ReadToEndAsync(); - myStreamReader.Close(); - myResponseStream.Close(); - return retString; - } + public static readonly HttpClient Httpclient = new HttpClient(); - public static string Post(string serviceAddress, string strContent = null) + public static async Task GetAsync(string serviceAddress) { - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceAddress); - request.Method = "POST"; - request.ContentType = "application/json"; - //判断有无POST内容 - if (!string.IsNullOrWhiteSpace(strContent)) + try { - using (StreamWriter dataStream = new StreamWriter(request.GetRequestStream())) - { - dataStream.Write(strContent); - dataStream.Close(); - } + string result = string.Empty; + Uri getUrl = new Uri(serviceAddress); + Httpclient.Timeout = new TimeSpan(0, 0, 60); + result = await Httpclient.GetAsync(serviceAddress).Result.Content.ReadAsStringAsync(); + return result; } - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - string encoding = response.ContentEncoding; - if (encoding.Length < 1) + catch (Exception e) { - encoding = "UTF-8"; //默认编码 + Console.WriteLine(e.Message); } - StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(encoding)); - string retString = reader.ReadToEnd(); - return retString; + + return null; } - public static async Task PostAsync(string serviceAddress, string strContent = null) + public static async Task PostAsync(string serviceAddress, string requestJson = null) { - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceAddress); - request.Method = "POST"; - request.ContentType = "application/json"; - //判断有无POST内容 - if (!string.IsNullOrWhiteSpace(strContent)) + try { - using (StreamWriter dataStream = new StreamWriter(request.GetRequestStream())) + string result = string.Empty; + Uri postUrl = new Uri(serviceAddress); + + using (HttpContent httpContent = new StringContent(requestJson)) { - dataStream.Write(strContent); - dataStream.Close(); + httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + Httpclient.Timeout = new TimeSpan(0, 0, 60); + result = await Httpclient.PostAsync(serviceAddress, httpContent).Result.Content.ReadAsStringAsync(); } + + return result; } - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - string encoding = response.ContentEncoding; - if (encoding.Length < 1) + catch (Exception e) { - encoding = "UTF-8"; //默认编码 + Console.WriteLine(e.Message); } - StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(encoding)); - string retString = await reader.ReadToEndAsync(); - return retString; + + return null; } } - - -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/JsonConfigUtils.cs b/Blog.Core.Common/Helper/JsonConfigUtils.cs index b49024b3..3986a698 100644 --- a/Blog.Core.Common/Helper/JsonConfigUtils.cs +++ b/Blog.Core.Common/Helper/JsonConfigUtils.cs @@ -63,52 +63,6 @@ public static string GetJson(string jsonPath, string key) } } - - /// - /// 配置文件管理器 - /// - public interface IConfigurationManager - { - T GetAppConfig(string key, T defaultValue = default(T)); - } - - /// - /// 配置读取 根据环境变量 - /// - public class ConfigurationManager : IConfigurationManager - { - private readonly IConfigurationRoot config; - - public ConfigurationManager(IConfigurationRoot _config) - { - config = _config; - } - - public T GetAppConfig(string key, T defaultValue = default(T)) - { - T value = default(T); - try - { - var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); - var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json", true, false) - .AddJsonFile($"appsettings.{env}.json", true, false) - .AddEnvironmentVariables(); - - var configuration = builder.Build(); - value = (T)Convert.ChangeType(configuration[key], typeof(T)); - if (value == null) - value = defaultValue; - } - catch (Exception ex) - { - value = defaultValue; - } - - return value; - } - } - - #region Nacos 配置清单 public class JsonConfigSettings { diff --git a/Blog.Core.Common/Helper/JsonHelper.cs b/Blog.Core.Common/Helper/JsonHelper.cs index 352cb8e1..a615abf0 100644 --- a/Blog.Core.Common/Helper/JsonHelper.cs +++ b/Blog.Core.Common/Helper/JsonHelper.cs @@ -1,11 +1,46 @@ using System; using System.Collections.Generic; -using System.Text.Json; + namespace Blog.Core.Common.Helper { public class JsonHelper { + /// + /// 对象序列化 + /// + /// 对象 + /// 是否使用textjson + /// 返回json字符串 + public static string ObjToJson(object obj, bool isUseTextJson = false) + { + if (isUseTextJson) + { + return System.Text.Json.JsonSerializer.Serialize(obj); + } + else + { + return Newtonsoft.Json.JsonConvert.SerializeObject(obj); + } + } + /// + /// json反序列化obj + /// + /// 反序列类型 + /// json + /// 是否使用textjson + /// 返回对象 + public static T JsonToObj(string strJson, bool isUseTextJson = false) + { + if (isUseTextJson) + { + return System.Text.Json.JsonSerializer.Deserialize(strJson); + } + else + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(strJson); + } + } /// /// 转换对象为JSON格式数据 /// @@ -17,7 +52,6 @@ public static string GetJSON(object obj) string result = String.Empty; try { - JsonSerializer.Serialize(""); System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(T)); using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) diff --git a/Blog.Core.Common/Helper/LinqHelper.cs b/Blog.Core.Common/Helper/LinqHelper.cs new file mode 100644 index 00000000..de472150 --- /dev/null +++ b/Blog.Core.Common/Helper/LinqHelper.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq.Expressions; + +namespace Blog.Core.Common.Helper +{ + /// + /// Linq操作帮助类 + /// + public static class LinqHelper + { + /// + /// 创建初始条件为True的表达式 + /// + /// + /// + public static Expression> True() + { + return x => true; + } + + /// + /// 创建初始条件为False的表达式 + /// + /// + /// + public static Expression> False() + { + return x => false; + } + } +} diff --git a/Blog.Core.Common/Helper/MD5Hepler.cs b/Blog.Core.Common/Helper/MD5Hepler.cs index 984771bd..2f46f0b5 100644 --- a/Blog.Core.Common/Helper/MD5Hepler.cs +++ b/Blog.Core.Common/Helper/MD5Hepler.cs @@ -13,7 +13,7 @@ public class MD5Helper /// public static string MD5Encrypt16(string password) { - var md5 = new MD5CryptoServiceProvider(); + var md5 = MD5.Create(); string t2 = BitConverter.ToString(md5.ComputeHash(Encoding.Default.GetBytes(password)), 4, 8); t2 = t2.Replace("-", string.Empty); return t2; @@ -62,6 +62,37 @@ public static string MD5Encrypt64(string password) byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password)); return Convert.ToBase64String(s); } - + /// + /// Sha1加密 + /// + /// 要加密的字符串 + /// 加密后的十六进制的哈希散列(字符串) + public static string Sha1(string str, string format = "x2") + { + var buffer = Encoding.UTF8.GetBytes(str); + var data = SHA1.Create().ComputeHash(buffer); + var sb = new StringBuilder(); + foreach (var t in data) + { + sb.Append(t.ToString(format)); + } + return sb.ToString(); + } + /// + /// Sha256加密 + /// + /// 要加密的字符串 + /// 加密后的十六进制的哈希散列(字符串) + public static string Sha256(string str, string format = "x2") + { + var buffer = Encoding.UTF8.GetBytes(str); + var data = SHA256.Create().ComputeHash(buffer); + var sb = new StringBuilder(); + foreach (var t in data) + { + sb.Append(t.ToString(format)); + } + return sb.ToString(); + } } } 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/PingYinHelper.cs b/Blog.Core.Common/Helper/PingYinHelper.cs new file mode 100644 index 00000000..5bf1c1e4 --- /dev/null +++ b/Blog.Core.Common/Helper/PingYinHelper.cs @@ -0,0 +1,108 @@ +using Microsoft.International.Converters.PinYinConverter; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Blog.Core.Common.Helper +{ + /// + /// 汉字转换拼音 + /// + public static class PingYinUtil + { + private static Dictionary> GetTotalPingYinDictionary(string text) + { + var chs = text.ToCharArray(); + + //记录每个汉字的全拼 + Dictionary> totalPingYinList = new Dictionary>(); + + for (int i = 0; i < chs.Length; i++) + { + var pinyinList = new List(); + + //是否是有效的汉字 + if (ChineseChar.IsValidChar(chs[i])) + { + ChineseChar cc = new ChineseChar(chs[i]); + pinyinList = cc.Pinyins.Where(p => !string.IsNullOrWhiteSpace(p)).ToList(); + } + else + { + pinyinList.Add(chs[i].ToString()); + } + + //去除声调,转小写 + pinyinList = pinyinList.ConvertAll(p => Regex.Replace(p, @"\d", "").ToLower()); + + //去重 + pinyinList = pinyinList.Where(p => !string.IsNullOrWhiteSpace(p)).Distinct().ToList(); + if (pinyinList.Any()) + { + totalPingYinList[i] = pinyinList; + } + } + + return totalPingYinList; + } + /// + /// 获取汉语拼音全拼 + /// + /// The string. + /// + public static List GetTotalPingYin(this string text) + { + var result = new List(); + foreach (var pys in GetTotalPingYinDictionary(text)) + { + var items = pys.Value; + if (result.Count <= 0) + { + result = items; + } + else + { + //全拼循环匹配 + var newTotalPingYinList = new List(); + foreach (var totalPingYin in result) + { + newTotalPingYinList.AddRange(items.Select(item => totalPingYin + item)); + } + newTotalPingYinList = newTotalPingYinList.Distinct().ToList(); + result = newTotalPingYinList; + } + } + return result; + } + + /// + /// 获取汉语拼音首字母 + /// + /// + /// + public static List GetFirstPingYin(this string text) + { + var result = new List(); + foreach (var pys in GetTotalPingYinDictionary(text)) + { + var items = pys.Value; + if (result.Count <= 0) + { + result = items.ConvertAll(p => p.Substring(0, 1)).Distinct().ToList(); + } + else + { + //首字母循环匹配 + var newFirstPingYinList = new List(); + foreach (var firstPingYin in result) + { + newFirstPingYinList.AddRange(items.Select(item => firstPingYin + item.Substring(0, 1))); + } + newFirstPingYinList = newFirstPingYinList.Distinct().ToList(); + result = newFirstPingYinList; + } + } + return result; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/RSAHelperExtend.cs b/Blog.Core.Common/Helper/RSAHelperExtend.cs index 6e2025bb..87d6c2d4 100644 --- a/Blog.Core.Common/Helper/RSAHelperExtend.cs +++ b/Blog.Core.Common/Helper/RSAHelperExtend.cs @@ -265,7 +265,7 @@ public static string PublicKeyDecrypt(string xmlPublicKey, string strDecryptStri string strDec = Encoding.UTF8.GetString(resData); return strDec; - } + } } /// @@ -284,19 +284,19 @@ public static string Sign(string str, string privateKey, SignAlgType signAlgType { case SignAlgType.SHA1: { - var csp = new SHA1CryptoServiceProvider(); + var csp = SHA1.Create(); rgbHash = csp.ComputeHash(bt); } break; case SignAlgType.SHA256: { - var csp = new SHA256CryptoServiceProvider(); + var csp = SHA256.Create(); rgbHash = csp.ComputeHash(bt); } break; case SignAlgType.MD5: { - var csp = new MD5CryptoServiceProvider(); + var csp = MD5.Create(); rgbHash = csp.ComputeHash(bt); } break; @@ -331,19 +331,19 @@ public static bool Verify(string str, string sign, string publicKey, SignAlgType { case SignAlgType.SHA1: { - var csp = new SHA1CryptoServiceProvider(); + var csp = SHA1.Create(); rgbHash = csp.ComputeHash(bt); } break; case SignAlgType.SHA256: { - var csp = new SHA256CryptoServiceProvider(); + var csp = SHA256.Create(); rgbHash = csp.ComputeHash(bt); } break; case SignAlgType.MD5: { - var csp = new MD5CryptoServiceProvider(); + var csp = MD5.Create(); rgbHash = csp.ComputeHash(bt); } break; diff --git a/Blog.Core.Common/Helper/RecursionHelper.cs b/Blog.Core.Common/Helper/RecursionHelper.cs index 508b03df..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,15 +50,38 @@ public static void LoopToAppendChildren(List all, PermissionTree { //subItem.disabled = true;//禁用当前节点 } + LoopToAppendChildren(all, subItem, pid, needbtn); } } + 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(); + curItem.children.AddRange(subItems); + } + else + { + curItem.children = null; + } + foreach (var subItem in subItems) + { + if (subItem.value == pid && pid > 0) + { + //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) @@ -78,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(); @@ -89,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; } @@ -102,10 +160,21 @@ public class PermissionTree public List children { get; set; } public List btns { get; set; } } + + public class DepartmentTree + { + public long value { get; set; } + public long Pid { get; set; } + public string label { get; set; } + public int order { get; set; } + public bool disabled { get; set; } + public List children { get; set; } + } + 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; @@ -123,7 +192,29 @@ 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 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; + public bool IsButton { get; set; } = false; + public string path { get; set; } + public string component { get; set; } + public string Func { get; set; } + public string iconCls { get; set; } + public NavigationBarMetaPro meta { get; set; } + } + + public class NavigationBarMetaPro + { + public string title { get; set; } + 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 new file mode 100644 index 00000000..b4aa3ebf --- /dev/null +++ b/Blog.Core.Common/Helper/SM/SM4.cs @@ -0,0 +1,301 @@ +using System; +using System.Collections.Generic; + +namespace Blog.Core.Common.Helper.SM +{ + public class SM4 + { + public const int SM4_ENCRYPT = 1; + public const int SM4_DECRYPT = 0; + + 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; + } + + private void PUT_ULONG_BE(long n, SByte[] b, int i) + { + b[i] = (SByte)(int)(0xFF & n >> 24); + b[i + 1] = (SByte)(int)(0xFF & n >> 16); + b[i + 2] = (SByte)(int)(0xFF & n >> 8); + b[i + 3] = (SByte)(int)(0xFF & n); + } + + private long SHL(long x, int n) + { + return (x & 0xFFFFFFFF) << n; + } + + private long ROTL(long x, int n) + { + return SHL(x, n) | x >> (32 - n); + } + + private void SWAP(long[] sk, int i) + { + long t = sk[i]; + sk[i] = sk[(31 - i)]; + sk[(31 - i)] = t; + } + + /// + /// S盒 + /// + //public SByte[] SboxTable = new SByte[] { + // 0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05, + // 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99, + // 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62, + // 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6, + // 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8, + // 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35, + // 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87, + // 0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, + // 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, + // 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, + // 0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f, + // 0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, + // 0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, + // 0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, + // 0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84, + // 0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48 + //}; + + public SByte[] SboxTable = { -42, -112, -23, -2, -52, -31, 61, -73, 22, -74, 20, -62, 40, -5, 44, 5, 43, 103, -102, 118, 42, -66, 4, -61, -86, 68, 19, 38, 73, -122, 6, -103, -100, 66, 80, -12, -111, -17, -104, 122, 51, 84, 11, 67, -19, -49, -84, 98, -28, -77, 28, -87, -55, 8, -24, -107, -128, -33, -108, -6, 117, -113, 63, -90, 71, 7, -89, -4, -13, 115, 23, -70, -125, 89, 60, 25, -26, -123, 79, -88, 104, 107, -127, -78, 113, 100, -38, -117, -8, -21, 15, 75, 112, 86, -99, 53, 30, 36, 14, 94, 99, 88, -47, -94, 37, 34, 124, 59, 1, 33, 120, -121, -44, 0, 70, 87, -97, -45, 39, 82, 76, 54, 2, -25, -96, -60, -56, -98, -22, -65, -118, -46, 64, -57, 56, -75, -93, -9, -14, -50, -7, 97, 21, -95, -32, -82, 93, -92, -101, 52, 26, 85, -83, -109, 50, 48, -11, -116, -79, -29, 29, -10, -30, 46, -126, 102, -54, 96, -64, 41, 35, -85, 13, 83, 78, 111, -43, -37, 55, 69, -34, -3, -114, 47, 3, -1, 106, 114, 109, 108, 91, 81, -115, 27, -81, -110, -69, -35, -68, 127, 17, -39, 92, 65, 31, 16, 90, -40, 10, -63, 49, -120, -91, -51, 123, -67, 45, 116, -48, 18, -72, -27, -76, -80, -119, 105, -105, 74, 12, -106, 119, 126, 101, -71, -15, 9, -59, 110, -58, -124, 24, -16, 125, -20, 58, -36, 77, 32, 121, -18, 95, 62, -41, -53, 57, 72 }; + public int[] FK = { -1548633402, 1453994832, 1736282519, -1301273892 }; + public int[] CK = { 462357, 472066609, 943670861, 1415275113, 1886879365, -1936483679, -1464879427, -993275175, -521670923, -66909679, 404694573, 876298825, 1347903077, 1819507329, -2003855715, -1532251463, -1060647211, -589042959, -117504499, 337322537, 808926789, 1280531041, 1752135293, -2071227751, -1599623499, -1128019247, -656414995, -184876535, 269950501, 741554753, 1213159005, 1684763257 }; + + private SByte sm4Sbox(SByte inch) + { + int i = inch & 0xFF; + SByte retVal = SboxTable[i]; + return retVal; + } + + private long sm4Lt(long ka) + { + long bb = 0L; + long c = 0L; + SByte[] a = new SByte[4]; + SByte[] b = new SByte[4]; + PUT_ULONG_BE(ka, a, 0); + b[0] = sm4Sbox(a[0]); + b[1] = sm4Sbox(a[1]); + b[2] = sm4Sbox(a[2]); + b[3] = sm4Sbox(a[3]); + bb = GET_ULONG_BE(b, 0); + c = bb ^ ROTL(bb, 2) ^ ROTL(bb, 10) ^ ROTL(bb, 18) ^ ROTL(bb, 24); + return c; + } + + private long sm4F(long x0, long x1, long x2, long x3, long rk) + { + return x0 ^ sm4Lt(x1 ^ x2 ^ x3 ^ rk); + } + + private long sm4CalciRK(long ka) + { + long bb = 0L; + long rk = 0L; + SByte[] a = new SByte[4]; + SByte[] b = new SByte[4]; + PUT_ULONG_BE(ka, a, 0); + b[0] = sm4Sbox(a[0]); + b[1] = sm4Sbox(a[1]); + b[2] = sm4Sbox(a[2]); + b[3] = sm4Sbox(a[3]); + bb = GET_ULONG_BE(b, 0); + rk = bb ^ ROTL(bb, 13) ^ ROTL(bb, 23); + return rk; + } + + private void sm4_setkey(long[] SK, SByte[] key) + { + long[] MK = new long[4]; + long[] k = new long[36]; + int i = 0; + MK[0] = GET_ULONG_BE(key, 0); + MK[1] = GET_ULONG_BE(key, 4); + MK[2] = GET_ULONG_BE(key, 8); + MK[3] = GET_ULONG_BE(key, 12); + MK[0] ^= FK[0]; + MK[1] ^= FK[1]; + MK[2] ^= FK[2]; + MK[3] ^= FK[3]; + for (; i < 32; i++) + { + k[(i + 4)] = (k[i] ^ sm4CalciRK(k[(i + 1)] ^ k[(i + 2)] ^ k[(i + 3)] ^ CK[i])); + SK[i] = k[(i + 4)]; + } + } + + private void sm4_one_round(long[] sk, SByte[] input, SByte[] output) + { + int i = 0; + long[] ulbuf = new long[36]; + ulbuf[0] = GET_ULONG_BE(input, 0); + ulbuf[1] = GET_ULONG_BE(input, 4); + ulbuf[2] = GET_ULONG_BE(input, 8); + ulbuf[3] = GET_ULONG_BE(input, 12); + while (i < 32) + { + ulbuf[(i + 4)] = sm4F(ulbuf[i], ulbuf[(i + 1)], ulbuf[(i + 2)], ulbuf[(i + 3)], sk[i]); + i++; + } + PUT_ULONG_BE(ulbuf[35], output, 0); + PUT_ULONG_BE(ulbuf[34], output, 4); + PUT_ULONG_BE(ulbuf[33], output, 8); + PUT_ULONG_BE(ulbuf[32], output, 12); + } + + private SByte[] padding(SByte[] input, int mode) + { + if (input == null) + { + return null; + } + + SByte[] ret = (SByte[])null; + if (mode == SM4_ENCRYPT) + { + int p = 16 - input.Length % 16; + ret = new SByte[input.Length + p]; + Array.Copy(input, 0, ret, 0, input.Length); + for (int i = 0; i < p; i++) + { + ret[input.Length + i] = (SByte)p; + } + } + else + { + int p = input[input.Length - 1]; + ret = new SByte[input.Length - p]; + Array.Copy(input, 0, ret, 0, input.Length - p); + } + return ret; + } + + public void sm4_setkey_enc(SM4_Context ctx, SByte[] key) + { + ctx.mode = SM4_ENCRYPT; + sm4_setkey(ctx.sk, key); + } + + public void sm4_setkey_dec(SM4_Context ctx, SByte[] key) + { + int i = 0; + ctx.mode = SM4_DECRYPT; + sm4_setkey(ctx.sk, key); + for (i = 0; i < 16; i++) + { + SWAP(ctx.sk, i); + } + } + + public SByte[] sm4_crypt_ecb(SM4_Context ctx, SByte[] input) + { + if (input == null) + { + throw new Exception("input is null!"); + } + + if ((ctx.isPadding) && (ctx.mode == SM4_ENCRYPT)) + { + input = padding(input, SM4_ENCRYPT); + } + + int length = input.Length; + SByte[] bins = new SByte[length]; + SByte[] bous = new SByte[length]; + + Array.Copy(input, 0, bins, 0, length); + + for (int i = 0; length > 0; length -= 16, i++) + { + SByte[] inBytes = new SByte[16]; + SByte[] outBytes = new SByte[16]; + Array.Copy(bins, i * 16, inBytes, 0, length > 16 ? 16 : length); + sm4_one_round(ctx.sk, inBytes, outBytes); + Array.Copy(outBytes, 0, bous, i * 16, length > 16 ? 16 : length); + } + + if (ctx.isPadding && ctx.mode == SM4_DECRYPT) + { + bous = padding(bous, SM4_DECRYPT); + } + return bous; + } + + public SByte[] sm4_crypt_cbc(SM4_Context ctx, SByte[] iv, SByte[] input) + { + if (ctx.isPadding && ctx.mode == SM4_ENCRYPT) + { + input = padding(input, SM4_ENCRYPT); + } + + int i = 0; + int length = input.Length; + SByte[] bins = new SByte[length]; + Array.Copy(input, 0, bins, 0, length); + SByte[] bous = null; + List bousList = new List(); + if (ctx.mode == SM4_ENCRYPT) + { + for (int j = 0; length > 0; length -= 16, j++) + { + SByte[] inBytes = new SByte[16]; + SByte[] outBytes = new SByte[16]; + SByte[] out1 = new SByte[16]; + + Array.Copy(bins, i * 16, inBytes, 0, length > 16 ? 16 : length); + for (i = 0; i < 16; i++) + { + outBytes[i] = ((SByte)(inBytes[i] ^ iv[i])); + } + sm4_one_round(ctx.sk, outBytes, out1); + Array.Copy(out1, 0, iv, 0, 16); + for (int k = 0; k < 16; k++) + { + bousList.Add(out1[k]); + } + } + } + else + { + SByte[] temp = new SByte[16]; + for (int j = 0; length > 0; length -= 16, j++) + { + SByte[] inBytes = new SByte[16]; + SByte[] outBytes = new SByte[16]; + SByte[] out1 = new SByte[16]; + + Array.Copy(bins, i * 16, inBytes, 0, length > 16 ? 16 : length); + Array.Copy(inBytes, 0, temp, 0, 16); + sm4_one_round(ctx.sk, inBytes, outBytes); + for (i = 0; i < 16; i++) + { + out1[i] = ((SByte)(outBytes[i] ^ iv[i])); + } + Array.Copy(temp, 0, iv, 0, 16); + for (int k = 0; k < 16; k++) + { + bousList.Add(out1[k]); + } + } + + } + + if (ctx.isPadding && ctx.mode == SM4_DECRYPT) + { + bous = padding(bousList.ToArray(), SM4_DECRYPT); + return bous; + } + else + { + return bousList.ToArray(); + } + } + } +} diff --git a/Blog.Core.Common/Helper/SM/SM4Helper.cs b/Blog.Core.Common/Helper/SM/SM4Helper.cs new file mode 100644 index 00000000..e1793bb3 --- /dev/null +++ b/Blog.Core.Common/Helper/SM/SM4Helper.cs @@ -0,0 +1,96 @@ +using Org.BouncyCastle.Utilities.Encoders; +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace Blog.Core.Common.Helper.SM +{ + public class SM4Helper + { + public String secretKey = "1234567890123456";// 16位 + public String iv = ""; + public bool hexString = false; + + private SByte[] Byte2SByte(byte[] myByte) + { + sbyte[] mySByte = new sbyte[myByte.Length]; + + for (int i = 0; i < myByte.Length; i++) + { + if (myByte[i] > 127) + mySByte[i] = (sbyte)(myByte[i] - 256); + else + mySByte[i] = (sbyte)myByte[i]; + } + + return mySByte; + } + private byte[] SByte2Byte(sbyte[] orig) + { + byte[] arr = new byte[orig.Length]; + Buffer.BlockCopy(orig, 0, arr, 0, orig.Length); + + return arr; + } + + public String Encrypt_ECB(String plainText) + { + SM4_Context ctx = new SM4_Context(); + ctx.isPadding = true; + ctx.mode = SM4.SM4_ENCRYPT; + + SByte[] keyBytes; + if (hexString) + { + keyBytes = null;// Hex.Decode(secretKey); + } + else + { + + keyBytes = Byte2SByte(Encoding.UTF8.GetBytes(secretKey)); + } + + SM4 sm4 = new SM4(); + sm4.sm4_setkey_enc(ctx, keyBytes); + SByte[] bytes = Byte2SByte(Encoding.UTF8.GetBytes(plainText)); + SByte[] encrypted = sm4.sm4_crypt_ecb(ctx, bytes); + + //String cipherText = Encoding.UTF8.GetString(Hex.Encode(SByte2Byte(encrypted))); + String cipherText = new Base64Encoder().GetEncoded(SByte2Byte(encrypted)); + + if ((cipherText != null) && (cipherText.Trim().Length > 0)) + { + var matchCol = Regex.Matches(cipherText, "\\s*|\t|\r|\n", RegexOptions.Multiline); + for (int i = matchCol.Count - 1; i >= 0; i--) + { + Match item = matchCol[i]; + cipherText.Remove(item.Index, item.Length); + } + } + return cipherText; + } + + public String Decrypt_ECB(String cipherText) + { + SM4_Context ctx = new SM4_Context(); + ctx.isPadding = true; + ctx.mode = SM4.SM4_DECRYPT; + + SByte[] keyBytes; + if (hexString) + { + keyBytes = null;// Hex.Decode(secretKey); + } + else + { + keyBytes = Byte2SByte(Encoding.UTF8.GetBytes(secretKey)); + } + + SM4 sm4 = new SM4(); + sm4.sm4_setkey_dec(ctx, keyBytes); + SByte[] decrypted = sm4.sm4_crypt_ecb(ctx, Byte2SByte(new Base64Decoder().GetDecoded(cipherText))); + return Encoding.UTF8.GetString(SByte2Byte(decrypted)); + } + + } +} diff --git a/Blog.Core.Common/Helper/SM/SM4_Context.cs b/Blog.Core.Common/Helper/SM/SM4_Context.cs new file mode 100644 index 00000000..a12ee579 --- /dev/null +++ b/Blog.Core.Common/Helper/SM/SM4_Context.cs @@ -0,0 +1,18 @@ +namespace Blog.Core.Common.Helper.SM +{ + public class SM4_Context + { + public int mode; + + public long[] sk; + + public bool isPadding; + + public SM4_Context() + { + this.mode = 1; + this.isPadding = true; + this.sk = new long[32]; + } + } +} diff --git a/Blog.Core.Common/Helper/ShaHelper.cs b/Blog.Core.Common/Helper/ShaHelper.cs new file mode 100644 index 00000000..7a91257a --- /dev/null +++ b/Blog.Core.Common/Helper/ShaHelper.cs @@ -0,0 +1,1096 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +// This is an implementation of +// http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf + + + + + +namespace Blog.Core.Common.Helper +{ + using Word32 = System.UInt32; + using Word64 = System.UInt64; + + + public static class ShaHelper + { + // Constants K + static Word32[] K1; + static Word32[] K256; + static Word64[] K512; + + // Initial hash values H0 + static Word32[] H0Sha1; + static Word32[] H0Sha224; + static Word32[] H0Sha256; + static Word64[] H0Sha384; + static Word64[] H0Sha512; + static Word64[] H0Sha512_224; + static Word64[] H0Sha512_256; + + + static ShaHelper() + { + DefineK1(); + DefineK256(); + DefineK512(); + + DefineH0Sha1(); + DefineH0Sha224(); + DefineH0Sha256(); + DefineH0Sha384(); + DefineH0Sha512(); + DefineH0Sha512_224(); + DefineH0Sha512_256(); + } + + + #region Public Functions + + public static byte[] Sha1(byte[] plaintext) + { + DefineH0Sha1(); + return Sha1Algorithm(plaintext); + } + + public static string Sha1(string plaintext) + { + return ShaUtilities.ByteArrayToHexString(Sha1(ShaUtilities.StringToByteArray(plaintext))); + } + + public static byte[] Sha224(byte[] plaintext) + { + DefineH0Sha224(); + return Sha256Algorithm(plaintext, H0Sha224, 224); + } + + public static string Sha224(string plaintext) + { + return ShaUtilities.ByteArrayToHexString(Sha224(ShaUtilities.StringToByteArray(plaintext))); + } + + public static byte[] Sha256(byte[] plaintext) + { + DefineH0Sha256(); + return Sha256Algorithm(plaintext, H0Sha256, 256); + } + + public static string Sha256(string plaintext) + { + return ShaUtilities.ByteArrayToHexString(Sha256(ShaUtilities.StringToByteArray(plaintext))); + } + + public static byte[] Sha512(byte[] plaintext) + { + DefineH0Sha512(); + return Sha512Algorithm(plaintext, H0Sha512, 512); + } + + public static string Sha512(string plaintext) + { + return ShaUtilities.ByteArrayToHexString(Sha512(ShaUtilities.StringToByteArray(plaintext))); + } + + public static byte[] Sha384(byte[] plaintext) + { + DefineH0Sha384(); + return Sha512Algorithm(plaintext, H0Sha384, 384); + } + + public static string Sha384(string plaintext) + { + return ShaUtilities.ByteArrayToHexString(Sha384(ShaUtilities.StringToByteArray(plaintext))); + } + + public static byte[] Sha512_224(byte[] plaintext) + { + DefineH0Sha512_224(); + return Sha512Algorithm(plaintext, H0Sha512_224, 224); + } + + public static string Sha512_224(string plaintext) + { + return ShaUtilities.ByteArrayToHexString(Sha512_224(ShaUtilities.StringToByteArray(plaintext))); + } + + public static byte[] Sha512_256(byte[] plaintext) + { + DefineH0Sha512_256(); + return Sha512Algorithm(plaintext, H0Sha512_256, 256); + } + + public static string Sha512_256(string plaintext) + { + return ShaUtilities.ByteArrayToHexString(Sha512_256(ShaUtilities.StringToByteArray(plaintext))); + } + + #endregion + + + + + + #region Hash Algorithms + + static Word32[] CreateMessageScheduleSha1(Block512 block) + { + // The message schedule. + Word32[] W = new Word32[80]; + + // Prepare the message schedule W. + // The first 16 words in W are the same as the words of the block. + // The remaining 80-16 = 64 words in W are functions of the previously defined words. + for (int t = 0; t < 80; t++) + { + if (t < 16) + { + W[t] = block.words[t]; + } + else + { + W[t] = RotL(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); + } + } + + return W; + } + + static Word32[] CreateMessageScheduleSha256(Block512 block) + { + // The message schedule. + Word32[] W = new Word32[64]; + + // Prepare the message schedule W. + // The first 16 words in W are the same as the words of the block. + // The remaining 64-16 = 48 words in W are functions of the previously defined words. + for (int t = 0; t < 64; t++) + { + if (t < 16) + { + W[t] = block.words[t]; + } + else + { + W[t] = sigma1_256(W[t - 2]) + W[t - 7] + sigma0_256(W[t - 15]) + W[t - 16]; + } + } + + return W; + } + + static Word64[] CreateMessageScheduleSha512(Block1024 block) + { + // The message schedule. + Word64[] W = new Word64[80]; + + // Prepare the message schedule W. + // The first 16 words in W are the same as the words of the block. + // The remaining 80-16 =64 words in W are functions of the previously defined words. + for (int t = 0; t < 80; t++) + { + if (t < 16) + { + W[t] = block.words[t]; + } + else + { + W[t] = sigma1_512(W[t - 2]) + W[t - 7] + sigma0_512(W[t - 15]) + W[t - 16]; + } + } + + return W; + } + + static byte[] Sha1Algorithm(byte[] plaintext) + { + Block512[] blocks = ConvertPaddedTextToBlock512Array(PadPlainText512(plaintext)); + + // Define the hash variable and set its initial values. + Word32[] H = new Word32[5]; + H0Sha1.CopyTo(H, 0); + + for (int i = 0; i < blocks.Length; i++) + { + Word32[] W = CreateMessageScheduleSha1(blocks[i]); + + // Set the working variables a,...,e to the current hash values. + Word32 a = H[0]; + Word32 b = H[1]; + Word32 c = H[2]; + Word32 d = H[3]; + Word32 e = H[4]; + + for (int t = 0; t < 80; t++) + { + Word32 T = RotL(5, a) + f(t, b, c, d) + e + K1[t] + W[t]; + e = d; + d = c; + c = RotL(30, b); + b = a; + a = T; + } + + // Update the current value of the hash H after processing block i. + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + } + + // Concatenating the final 5 hash words H[0],...,H[4] gives the digest. + // Since each H[i] is 4 bytes, the digest is 5 * 4 = 20 bytes = 160 bits. + return ShaUtilities.Word32ArrayToByteArray(H); + } + + static byte[] Sha256Algorithm(byte[] plaintext, Word32[] H0, int numberBits) + { + Block512[] blocks = ConvertPaddedTextToBlock512Array(PadPlainText512(plaintext)); + + // Define the hash variables and set their initial values. + Word32[] H = H0; + + for (int i = 0; i < blocks.Length; i++) + { + Word32[] W = CreateMessageScheduleSha256(blocks[i]); + + // Set the working variables a,...,h to the current hash values. + Word32 a = H[0]; + Word32 b = H[1]; + Word32 c = H[2]; + Word32 d = H[3]; + Word32 e = H[4]; + Word32 f = H[5]; + Word32 g = H[6]; + Word32 h = H[7]; + + for (int t = 0; t < 64; t++) + { + Word32 T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[t] + W[t]; + Word32 T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // Update the current value of the hash H after processing block i. + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; + } + + // Concatenate all the Word32 Hash Values + byte[] hash = ShaUtilities.Word32ArrayToByteArray(H); + + // The number of bytes in the final output hash + int numberBytes = numberBits / 8; + byte[] truncatedHash = new byte[numberBytes]; + Array.Copy(hash, truncatedHash, numberBytes); + + return truncatedHash; + } + + static byte[] Sha512Algorithm(byte[] plaintext, Word64[] H0, int numberBits) + { + Block1024[] blocks = ConvertPaddedMessageToBlock1024Array(PadPlainText1024(plaintext)); + + // Define the hash variable and set its initial values. + Word64[] H = H0; + + for (int i = 0; i < blocks.Length; i++) + { + Word64[] W = CreateMessageScheduleSha512(blocks[i]); + + // Set the working variables a,...,h to the current hash values. + Word64 a = H[0]; + Word64 b = H[1]; + Word64 c = H[2]; + Word64 d = H[3]; + Word64 e = H[4]; + Word64 f = H[5]; + Word64 g = H[6]; + Word64 h = H[7]; + + for (int t = 0; t < 80; t++) + { + Word64 T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[t] + W[t]; + Word64 T2 = Sigma0_512(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // Update the current value of the hash H after processing block i. + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; + } + + // Concatenate all the Word64 Hash Values + byte[] hash = ShaUtilities.Word64ArrayToByteArray(H); + + // The number of bytes in the final output hash + int numberBytes = numberBits / 8; + byte[] truncatedHash = new byte[numberBytes]; + Array.Copy(hash, truncatedHash, numberBytes); + + return truncatedHash; + } + + #endregion + + + #region Plaintext preprocessing functions + + static byte[] PadPlainText512(byte[] plaintext) + { + // After padding the total bits of the output will be divisible by 512. + int numberBits = plaintext.Length * 8; + int t = (numberBits + 8 + 64) / 512; + + // Note that 512 * (t + 1) is the least multiple of 512 greater than (numberBits + 8 + 64) + // Therefore the number of zero bits we need to add is + int k = 512 * (t + 1) - (numberBits + 8 + 64); + + // Since numberBits % 8 = 0, we know k % 8 = 0. So n = k / 8 is the number of zero bytes to add. + int n = k / 8; + + List paddedtext = plaintext.ToList(); + + // Start the padding by concatenating 1000_0000 = 0x80 = 128 + paddedtext.Add(0x80); + + // Next add n zero bytes + for (int i = 0; i < n; i++) + { + paddedtext.Add(0); + } + + // Now add 8 bytes (64 bits) to represent the length of the message in bits + byte[] B = BitConverter.GetBytes((ulong)numberBits); + Array.Reverse(B); + + for (int i = 0; i < B.Length; i++) + { + paddedtext.Add(B[i]); + } + + return paddedtext.ToArray(); + } + + static byte[] PadPlainText1024(byte[] plaintext) + { + // After padding the total bits of the output will be divisible by 1024. + int numberBits = plaintext.Length * 8; + int t = (numberBits + 8 + 128) / 1024; + + // Note that 1024 * (t + 1) is the least multiple of 1024 greater than (numberBits + 8 + 128) + // Therefore the number of zero bits we need to add is + int k = 1024 * (t + 1) - (numberBits + 8 + 128); + + // Since numberBits % 8 = 0, we know k % 8 = 0. So n = k / 8 is the number of zero bytes to add. + int n = k / 8; + + List paddedtext = plaintext.ToList(); + + // Start the padding by concatenating 1000_0000 = 0x80 = 128 + paddedtext.Add(0x80); + + // Next add n zero bytes + for (int i = 0; i < n; i++) + { + paddedtext.Add(0); + } + + // Now add 16 bytes (128 bits) to represent the length of the message in bits. + // C# does not have 128 bit integer. + // For now just add 8 zero bytes and then 8 bytes to represent the int + for (int i = 0; i < 8; i++) + { + paddedtext.Add(0); + } + + byte[] B = BitConverter.GetBytes((ulong)numberBits); + Array.Reverse(B); + + for (int i = 0; i < B.Length; i++) + { + paddedtext.Add(B[i]); + } + + return paddedtext.ToArray(); + } + + static Block512[] ConvertPaddedTextToBlock512Array(byte[] paddedtext) + { + // We are assuming M has been padded, so the number of bits in M is divisible by 512 + int numberBlocks = (paddedtext.Length * 8) / 512; // same as: paddedtext.Length / 64 + Block512[] blocks = new Block512[numberBlocks]; + + for (int i = 0; i < numberBlocks; i++) + { + // First extract the relavant subarray from paddedtext + byte[] B = new byte[64]; // 64 * 8 = 512 + + for (int j = 0; j < 64; j++) + { + B[j] = paddedtext[i * 64 + j]; + } + + Word32[] words = ShaUtilities.ByteArrayToWord32Array(B); + blocks[i] = new Block512(words); + } + + return blocks; + } + + static Block1024[] ConvertPaddedMessageToBlock1024Array(byte[] M) + { + // We are assuming M is padded, so the number of bits in M is divisible by 1024 + int numberBlocks = (M.Length * 8) / 1024; // same as: M.Length / 128 + Block1024[] blocks = new Block1024[numberBlocks]; + + for (int i = 0; i < numberBlocks; i++) + { + // First extract the relavant subarray from M + byte[] B = new byte[128]; // 128 * 8 = 1024 + + for (int j = 0; j < 128; j++) + { + B[j] = M[i * 128 + j]; + } + + Word64[] words = ShaUtilities.ByteArrayToWord64Array(B); + blocks[i] = new Block1024(words); + } + + return blocks; + } + + + #endregion + + + + #region Functions used in the hashing process. + + // Most of these functions have a Word32 version and a Word64 version. + // Sometimes they are the same (Ch, Maj,..) but sometimes different (Sigma0_256, Sigma0_512). + // We do not need a RotL or Parity function for Word64 since they are only used in Sha-1. + + static Word32 ShR(int n, Word32 x) + { + // should have 0 <= n < 32 + return (x >> n); + } + + static Word64 ShR(int n, Word64 x) + { + // should have 0 <= n < 64 + return (x >> n); + } + + static Word32 RotR(int n, Word32 x) + { + // should have 0 <= n < 32 + return (x >> n) | (x << 32 - n); + } + + static Word64 RotR(int n, Word64 x) + { + // should have 0 <= n < 64 + return (x >> n) | (x << 64 - n); + } + + static Word32 RotL(int n, Word32 x) + { + // should have 0 <= n < 32 + return (x << n) | (x >> 32 - n); + } + + static Word32 Ch(Word32 x, Word32 y, Word32 z) + { + return (x & y) ^ (~x & z); + } + + static Word64 Ch(Word64 x, Word64 y, Word64 z) + { + return (x & y) ^ (~x & z); + } + + static Word32 Maj(Word32 x, Word32 y, Word32 z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + + static Word64 Maj(Word64 x, Word64 y, Word64 z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + + static Word32 Parity(Word32 x, Word32 y, Word32 z) + { + return x ^ y ^ z; + } + + static Word32 f(int t, Word32 x, Word32 y, Word32 z) + { + // This function is used in Sha-1 + // should have 0 <= t <= 79 + + if (t >= 0 && t <= 19) + { + return Ch(x, y, z); + } + else if (t >= 20 && t <= 39) + { + return Parity(x, y, z); + } + else if (t >= 40 && t <= 59) + { + return Maj(x, y, z); + } + else if (t >= 60 && t <= 79) + { + return Parity(x, y, z); + } + else + { + throw new ArgumentException("ERROR: t is out of bounds"); + } + } + + static Word32 Sigma0_256(Word32 x) + { + return RotR(2, x) ^ RotR(13, x) ^ RotR(22, x); + } + + static Word32 Sigma1_256(Word32 x) + { + return RotR(6, x) ^ RotR(11, x) ^ RotR(25, x); + } + + static Word32 sigma0_256(Word32 x) + { + return RotR(7, x) ^ RotR(18, x) ^ ShR(3, x); + } + + static Word32 sigma1_256(Word32 x) + { + return RotR(17, x) ^ RotR(19, x) ^ ShR(10, x); + } + + static Word64 Sigma0_512(Word64 x) + { + return RotR(28, x) ^ RotR(34, x) ^ RotR(39, x); + } + + static Word64 Sigma1_512(Word64 x) + { + return RotR(14, x) ^ RotR(18, x) ^ RotR(41, x); + } + + static Word64 sigma0_512(Word64 x) + { + return RotR(1, x) ^ RotR(8, x) ^ ShR(7, x); + } + + static Word64 sigma1_512(Word64 x) + { + return RotR(19, x) ^ RotR(61, x) ^ ShR(6, x); + } + + #endregion + + + + #region Functions to define the constants K and the initial hashes H0. + + static void DefineK1() + { + // The eighty 32-bit words in the array K1 are used in Sha-1. + + K1 = new Word32[80]; + + for (int i = 0; i < 80; i++) + { + if (i <= 19) + { + K1[i] = 0x5a827999; + } + else if (i <= 39) + { + K1[i] = 0x6ed9eba1; + } + else if (i <= 59) + { + K1[i] = 0x8f1bbcdc; + } + else + { + K1[i] = 0xca62c1d6; + } + } + } + + static void DefineK256() + { + // The sixty four 32-bit words in the array K256 are used in Sha-224 and Sha-256. + // They are obtained by taking the first 32 bits of the fractional + // parts of the cube roots of the first sixty four primes. + // ------------------------------------------------------- + // NOTE: To find the first 32 bits of the fractional part of the cube root of an integer n: + // double x = Math.Pow(n, 1d / 3); + // x = x - Math.Floor(x); + // x = x * Math.Pow(2, 32); + // return (uint)x; + + K256 = new Word32[] + { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + } + + static void DefineK512() + { + // The eighty 64-bit words in the array K512 are used in Sha-384, Sha-512, Sha-512/224, Sha-512/256. + // They are obtained by taking the first 64 bits of the fractional + // parts of the cube roots of the first eighty primes. + + K512 = new Word64[] + { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 + }; + } + + static void DefineH0Sha1() + { + H0Sha1 = new Word32[] + { + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 + }; + } + + static void DefineH0Sha224() + { + H0Sha224 = new Word32[] + { + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 + }; + + } + + static void DefineH0Sha256() + { + // These eight 32-bit words are obtained by taking the first 32 bits of the + // fractional parts of the square roots of the first 8 prime numbers. + + H0Sha256 = new Word32[] + { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + }; + } + + static void DefineH0Sha384() + { + // These eight 64-bit words are obtained by taking the first 64 bits of the + // fractional parts of the square roots of the ninth through sixteenth prime numbers. + + H0Sha384 = new Word64[] + { + 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, + 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4 + }; + } + + static void DefineH0Sha512() + { + // These eight 64-bit words are obtained by taking the first 64 bits of the + // fractional parts of the square roots of the first eight prime numbers. + + H0Sha512 = new Word64[] + { + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 + }; + } + + static void DefineH0Sha512_224() + { + // These eight 64-bit words are obtained from GenerateInitialHashSha512t(224) + + H0Sha512_224 = new Word64[] + { + 0x8c3d37c819544da2, 0x73e1996689dcd4d6, 0x1dfab7ae32ff9c82, 0x679dd514582f9fcf, + 0x0f6d2b697bd44da8, 0x77e36f7304c48942, 0x3f9d85a86a1d36c8, 0x1112e6ad91d692a1 + }; + } + + static void DefineH0Sha512_256() + { + // These eight 64-bit words are obtained from GenerateInitialHashSha512t(256) + + H0Sha512_256 = new Word64[] + { + 0x22312194fc2bf72c, 0x9f555fa3c84c64c2, 0x2393b86b6f53b151, 0x963877195940eabd, + 0x96283ee2a88effe3, 0xbe5e1e2553863992, 0x2b0199fc2c85b8aa, 0x0eb72ddc81c52ca2 + }; + } + + /* + static Word64[] GenerateInitialHashSha512t(int t) + { + // t = number of bits. + // We assume t is postive, divisible by 8 and is strictly less than 512. + // Also assume numberBits != 384 (WHY does 384 get its own initial hash?) + + Word64[] H0 = new Word64[8]; + + for (int i = 0; i < 8; i++) + { + H0[i] = H0Sha512[i] ^ 0xa5a5a5a5a5a5a5a5; + } + + byte[] B = ShaUtil.StringToByteArray("SHA-512/" + t.ToString()); // so arbitary! + + return ShaUtil.ByteArrayToWord64Array(Sha512(B)); ; + } + */ + + #endregion + + } + + + // Helper Classes + + class Block512 + { + // A Block512 consists of an array of 16 elements of type Word32. + public Word32[] words; + + public Block512(Word32[] words) + { + if (words.Length == 16) + { + this.words = words; + } + else + { + Console.WriteLine("ERROR: A block must be 16 words"); + this.words = null; + } + } + } + + + class Block1024 + { + // A Block1024 consists of an array of 16 elements of type Word64. + public Word64[] words; + + public Block1024(Word64[] words) + { + if (words.Length == 16) + { + this.words = words; + } + else + { + Console.WriteLine("ERROR: A block must be 16 words"); + this.words = null; + } + } + } + + + static class ShaUtilities + { + #region Functions to convert between byte arrays and Word32 arrays, and Word64 arrays. + + public static bool ByteArraysEqual(byte[] B1, byte[] B2) + { + if ((B1 == null) && (B2 == null)) + return true; + + if ((B1 == null) || (B2 == null)) + return false; + + if (B1.Length != B2.Length) + return false; + + for (int i = 0; i < B1.Length; i++) + { + if (B1[i] != B2[i]) + return false; + } + + return true; + } + + public static byte[] StringToByteArray(string plaintext) + { + char[] c = plaintext.ToCharArray(); + int numberBytes = plaintext.Length; + byte[] b = new byte[numberBytes]; + + for (int i = 0; i < numberBytes; i++) + { + b[i] = Convert.ToByte(c[i]); + } + + return b; + } + + // Returns an array of 4 bytes. + public static byte[] Word32ToByteArray(Word32 x) + { + byte[] b = BitConverter.GetBytes(x); + Array.Reverse(b); + return b; + } + + // Returns an array of 8 bytes. + public static byte[] Word64ToByteArray(Word64 x) + { + byte[] b = BitConverter.GetBytes(x); + Array.Reverse(b); + return b; + } + + public static byte[] Word32ArrayToByteArray(Word32[] words) + { + List b = new List(); + + for (int i = 0; i < words.Length; i++) + { + b.AddRange(Word32ToByteArray(words[i])); + } + + return b.ToArray(); + } + + public static byte[] Word32ArrayToByteArray(Word32[] words, int startIndex, int numberWords) + { + // This overload is useful in Sha224 + // assume 0 <= startIndex < words.Length and startIndex + numberWords <= words.Length + + List b = new List(); + + for (int i = startIndex; i < startIndex + numberWords; i++) + { + b.AddRange(Word32ToByteArray(words[i])); + } + + return b.ToArray(); + } + + public static byte[] Word64ArrayToByteArray(Word64[] words) + { + List b = new List(); + + for (int i = 0; i < words.Length; i++) + { + b.AddRange(Word64ToByteArray(words[i])); + } + + return b.ToArray(); + } + + public static Word32 ByteArrayToWord32(byte[] B, int startIndex) + { + // We assume: 0 <= startIndex < B. Length, and startIndex + 4 <= B.Length + + Word32 c = 256; + Word32 output = 0; + + for (int i = startIndex; i < startIndex + 4; i++) + { + output = output * c + (Word32)B[i]; + } + + return output; + } + + public static Word64 ByteArrayToWord64(byte[] B, int startIndex) + { + // We assume: 0 <= startIndex < B. Length, and startIndex + 8 <= B.Length + Word64 c = 256; + Word64 output = 0; + + for (int i = startIndex; i < startIndex + 8; i++) + { + output = output * c + B[i]; + } + + return output; + } + + public static Word32[] ByteArrayToWord32Array(byte[] B) + { + // We assume B is not null, is not empty and number elements is divisible by 4 + int numberBytes = B.Length; + int n = numberBytes / 4; // 4 bytes for each Word32 + Word32[] word32Array = new Word32[n]; + + for (int i = 0; i < n; i++) + { + word32Array[i] = ByteArrayToWord32(B, 4 * i); + } + + return word32Array; + } + + + public static Word64[] ByteArrayToWord64Array(byte[] B) + { + // We assume B is not null, is not empty and number elements is divisible by 8 + int numberWords = B.Length / 8; // 8 bytes for each Word32 + Word64[] word64Array = new Word64[numberWords]; + + for (int i = 0; i < numberWords; i++) + { + word64Array[i] = ByteArrayToWord64(B, 8 * i); + } + + return word64Array; + } + + #endregion + + + #region To string methods + + public static string ByteToBinaryString(byte b) + { + string binaryString = Convert.ToString(b, 2).PadLeft(8, '0'); + return binaryString.Substring(0, 4) + "_" + binaryString.Substring(4, 4); + } + + public static string ByteArrayToBinaryString(byte[] x) + { + string binaryString = ""; + + for (int i = 0; i < x.Length; i++) + { + binaryString += ByteToBinaryString(x[i]); + + if (i < x.Length - 1) + { + binaryString += " "; + } + } + + return binaryString; + } + + public static string ByteToHexString(byte b) + { + return Convert.ToString(b, 16).PadLeft(2, '0'); + } + + public static string ByteArrayToHexString(byte[] a) + { + string hexString = ""; + + for (int i = 0; i < a.Length; i++) + { + hexString += ByteToHexString(a[i]); + } + + return hexString; + } + + public static string Word32ToBinaryString(Word32 x) + { + return ByteArrayToBinaryString(Word32ToByteArray(x)); + } + + public static string Word32ToHexString(Word32 x) + { + return ByteArrayToHexString(Word32ToByteArray(x)); + } + + public static string Word64ToHexString(Word64 x) + { + return ByteArrayToHexString(Word64ToByteArray(x)); + } + + public static string ByteArrayToString(byte[] X) + { + if (X == null) + { + Console.WriteLine("ERROR: The byte array is null"); + return null; + } + + string s = ""; + + for (int i = 0; i < X.Length; i++) + { + s += (char)X[i]; + } + + return s; + } + + #endregion + } + +} + + + + + diff --git a/Blog.Core.Common/Helper/UtilConvert.cs b/Blog.Core.Common/Helper/UtilConvert.cs index 636e7099..a75005ac 100644 --- a/Blog.Core.Common/Helper/UtilConvert.cs +++ b/Blog.Core.Common/Helper/UtilConvert.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + namespace Blog.Core { /// @@ -19,8 +23,10 @@ public static int ObjToInt(this object thisValue) { return reval; } + return reval; } + /// /// /// @@ -34,8 +40,22 @@ public static int ObjToInt(this object thisValue, int errorValue) { return reval; } + 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; + } + /// /// /// @@ -48,8 +68,10 @@ public static double ObjToMoney(this object thisValue) { return reval; } + return 0; } + /// /// /// @@ -63,8 +85,10 @@ public static double ObjToMoney(this object thisValue, double errorValue) { return reval; } + return errorValue; } + /// /// /// @@ -75,6 +99,7 @@ public static string ObjToString(this object thisValue) if (thisValue != null) return thisValue.ToString().Trim(); return ""; } + /// /// /// @@ -82,8 +107,10 @@ 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"; } + /// /// /// @@ -95,6 +122,10 @@ public static string ObjToString(this object thisValue, string errorValue) if (thisValue != null) return thisValue.ToString().Trim(); return errorValue; } + + public static bool IsNullOrEmpty(this object thisValue) => thisValue == null || thisValue == DBNull.Value || + string.IsNullOrWhiteSpace(thisValue.ToString()); + /// /// /// @@ -107,8 +138,10 @@ public static Decimal ObjToDecimal(this object thisValue) { return reval; } + return 0; } + /// /// /// @@ -122,8 +155,10 @@ public static Decimal ObjToDecimal(this object thisValue, decimal errorValue) { return reval; } + return errorValue; } + /// /// /// @@ -136,8 +171,20 @@ 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; } + /// /// /// @@ -151,8 +198,10 @@ public static DateTime ObjToDate(this object thisValue, DateTime errorValue) { return reval; } + return errorValue; } + /// /// /// @@ -165,6 +214,7 @@ public static bool ObjToBool(this object thisValue) { return reval; } + return reval; } @@ -179,5 +229,84 @@ public static string DateToTimeStamp(this DateTime thisValue) TimeSpan ts = thisValue - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalSeconds).ToString(); } + + public static object ChangeType(this object value, Type type) + { + if (value == null && type.IsGenericType) return Activator.CreateInstance(type); + if (value == null) return null; + if (type == value.GetType()) return value; + if (type.IsEnum) + { + if (value is string) + return Enum.Parse(type, value as string); + else + return Enum.ToObject(type, value); + } + + if (!type.IsInterface && type.IsGenericType) + { + Type innerType = type.GetGenericArguments()[0]; + object innerValue = ChangeType(value, innerType); + return Activator.CreateInstance(type, new object[] {innerValue}); + } + + if (value is string && type == typeof(Guid)) return new Guid(value as string); + if (value is string && type == typeof(Version)) return new Version(value as string); + if (!(value is IConvertible)) return value; + return Convert.ChangeType(value, type); + } + + public static object ChangeTypeList(this object value, Type type) + { + if (value == null) return default; + + var gt = typeof(List<>).MakeGenericType(type); + dynamic lis = Activator.CreateInstance(gt); + + var addMethod = gt.GetMethod("Add"); + string values = value.ToString(); + if (values != null && values.StartsWith("(") && values.EndsWith(")")) + { + string[] splits; + if (values.Contains("\",\"")) + { + splits = values.Remove(values.Length - 2, 2) + .Remove(0, 2) + .Split("\",\""); + } + else + { + splits = values.Remove(0, 1) + .Remove(values.Length - 2, 1) + .Split(","); + } + + foreach (var split in splits) + { + var str = split; + if (split.StartsWith("\"") && split.EndsWith("\"")) + { + str = split.Remove(0, 1) + .Remove(split.Length - 2, 1); + } + + addMethod.Invoke(lis, new object[] {ChangeType(str, type)}); + } + } + + return lis; + } + + 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/WeChatHelper.cs b/Blog.Core.Common/Helper/WeChatHelper.cs new file mode 100644 index 00000000..75ece17a --- /dev/null +++ b/Blog.Core.Common/Helper/WeChatHelper.cs @@ -0,0 +1,310 @@ +using Blog.Core.Model.ViewModels; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Helper +{ + /// + /// 微信公众号帮助类 + /// + public static class WeChatHelper + { + /// + /// 新增素材/上传多媒体文件(临时) + /// http://mp.weixin.qq.com/wiki/5/963fc70b80dc75483a271298a76a8d59.html + /// 1.上传的媒体文件限制: + ///图片(image) : 1MB,支持JPG格式 + ///语音(voice):1MB,播放长度不超过60s,支持MP4格式 + ///视频(video):10MB,支持MP4格式 + ///缩略图(thumb):64KB,支持JPG格式 + ///2.媒体文件在后台保存时间为3天,即3天后media_id失效 + /// + /// + /// 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) + /// 文件名 + /// 文件输入流 + /// media_id + public async static Task UploadMediaTemp(string token, string type, string fileName, Stream inputStream) + { + var url = $"http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token={token}&type={type}"; + using var client = new HttpClient(); + using HttpContent content = new StreamContent(inputStream); + var httpResponse = await client.PostAsync(url, content); + var txt = await httpResponse.Content.ReadAsStringAsync(); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 新增素材/上传多媒体文件(永久) + /// http://mp.weixin.qq.com/wiki/5/963fc70b80dc75483a271298a76a8d59.html + /// 1.上传的媒体文件限制: + ///图片(image) : 1MB,支持JPG格式 + ///语音(voice):1MB,播放长度不超过60s,支持MP4格式 + ///视频(video):10MB,支持MP4格式 + ///缩略图(thumb):64KB,支持JPG格式 + /// + /// + /// 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb) + /// 文件名 + /// 文件输入流 + /// media_id + public async static Task UploadMedia(string token, string type, string fileName, Stream inputStream) + { + var url = $"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={token}&type={type}"; + using var client = new HttpClient(); + using HttpContent content = new StreamContent(inputStream); + var httpResponse = await client.PostAsync(url, content); + var txt = await httpResponse.Content.ReadAsStringAsync(); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 通过绑定票据获取公众号关注二维码 + /// + /// + /// + public async static Task GetQRCodePicture(string ticket) + { + string url = $"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={ticket}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 获取临时关注二维码 + /// + /// The token. + /// The post data. + public async static Task GetQRCode(string token, string jsonData) + { + string url = $"https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={token}"; + var txt = await HttpHelper.PostAsync(url, jsonData); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 获取关注的公众号用户openid(获取所有OpenID) + /// + /// The token. + /// 是否递归获取所有用户的 + /// List<System.String>. + public async static Task GetUsers(string token,bool isGetAll=false) + { + string url = $"https://api.weixin.qq.com/cgi-bin/user/get?access_token={token}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + if (data.data == null) data.data = new WeChatOpenIDsDto(); + if(!string.IsNullOrEmpty(data.next_openid)) + await GetUsers(token, data.next_openid, data.data.openid); + return data; + } + /// + /// 获取关注的公众号用户openid(递归) + /// + /// The token. + /// The next user. + /// The users. + public async static Task GetUsers(string token, string nextUser, List users) + { + string url = $"https://api.weixin.qq.com/cgi-bin/user/get?access_token={token}&next_openid={nextUser}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + if (data.data != null && data.data.openid != null) + users.AddRange(data.data.openid); + if (!string.IsNullOrEmpty(data.next_openid)) + await GetUsers(token, data.next_openid, data.data.openid); + } + /// + /// 获取菜单内容(获取菜单有menu外层,提交菜单不需要menu外层) + /// + /// The token. + /// System.String. + public async static Task GetMenu(string token) + { + string url = $"https://api.weixin.qq.com/cgi-bin/menu/get?access_token={token}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 转换微信菜单按钮为事件的按钮 + /// + public static void ConverMenuButtonForEvent(WeChatApiDto weChatApiDto) + { + foreach (var item in weChatApiDto?.menu?.button) + { + if (item.key.ObjToString().Equals("event") || item.type.ObjToString().Equals("event")) + { + var temp = item.type; + item.type = item.key; + item.key = temp; + } + if (item.sub_button != null) + { + ConverMenuButtonForEvent(item.sub_button); + } + } + } + /// + /// 转换微信菜单按钮为事件的按钮 + /// + public static void ConverMenuButtonForEvent(WeChatMenuButtonDto[] weChatMenuButtonDto) + { + foreach (var item in weChatMenuButtonDto) + { + if (item.key.ObjToString().Equals("event") || item.type.ObjToString().Equals("event")) + { + var temp = item.type; + item.type = item.key; + item.key = temp; + } + if (item.sub_button != null) + { + ConverMenuButtonForEvent(item.sub_button); + } + } + } + /// + /// 设置菜单内容(设置菜单不需要menu外层) + /// + /// The token. + /// The json menu. + /// System.String. + public async static Task SetMenu(string token, string jsonMenu) + { + string url = $"https://api.weixin.qq.com/cgi-bin/menu/create?access_token={token}"; + var txt = await HttpHelper.PostAsync(url, jsonMenu); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 删除菜单内容 + /// + /// The token. + /// true if XXXX, false otherwise. + public async static Task DeleteMenu(string token) + { + string url = $"https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={token}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 发送普通消息(群发所有人,单人发送也可以) + /// + /// The token. + /// The json data. + /// System.String. + public async static Task SendMsgToAll(string token, string jsonData) + { + string url = $"https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token={token}"; + var txt = await HttpHelper.PostAsync(url, jsonData); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 发送普通消息(单个人-24小时内用户跟微信公众号有互动才会推送成功) + /// + /// The token. + /// The json data. + /// System.String. + public async static Task SendMsg(string token, string jsonData) + { + string url = $"https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={token}"; + var txt = await HttpHelper.PostAsync(url, jsonData); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 发送卡片消息模板 + /// + /// The token. + /// The json data. + /// true if XXXX, false otherwise. + public async static Task SendCardMsg(string token, string jsonData) + { + string url = $"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={token}"; + var txt = await HttpHelper.PostAsync(url, jsonData); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 拉取普通access_token + /// + /// appid + /// appsecret + /// 返回token + public async static Task GetToken(string appid, string appsecret) + { + string url = $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={appsecret}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 获取微信服务器IP列表 + /// + /// The token. + /// System.String. + public async static Task GetWechatIP(string token) + { + string url = $"https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token={token}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// openid获取微信用户信息 + /// + /// The token. + /// The openid. + /// Dictionary<System.String, System.Object>. + public async static Task GetUserInfo(string token,string openid) + { + string url = $"https://api.weixin.qq.com/cgi-bin/user/info?access_token={token}&openid={openid}&lang=zh_CN"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// openid获取微信用户信息 + /// + /// The openid. + /// The access token. + public async static Task GetUserInfoTwo(string token,string openid) + { + string url = $"https://api.weixin.qq.com/sns/userinfo?access_token={token}&openid={openid}&lang=zh_CN"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// code换取用户openID + /// + /// The appid. + /// The appsecret. + /// The code. + /// Dictionary<System.String, System.Object>. + public async static Task GetOpenidByCode(string appid, string appsecret, string code) + { + string url = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={appid}&secret={appsecret}&code={code}&grant_type=authorization_code"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + /// + /// 获取模板消息 + /// + /// The token. + /// Dictionary<System.String, System.Object>. + public async static Task GetTemplate(string token) + { + string url = $"https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token={token}"; + var txt = await HttpHelper.GetAsync(url); + var data = JsonHelper.ParseFormByJson(txt); + return data; + } + } +} 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 d7fbc5f0..dfa87949 100644 --- a/Blog.Core.Common/HttpContextUser/AspNetUser.cs +++ b/Blog.Core.Common/HttpContextUser/AspNetUser.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +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; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; namespace Blog.Core.Common.HttpContextUser { @@ -30,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(); } } @@ -38,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) @@ -63,25 +91,50 @@ 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() { }; } + public MessageModel MessageModel { get; set; } + public IEnumerable GetClaimsIdentity() { - return _accessor.HttpContext.User.Claims; + 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) + { + claims.Add(new Claim(header.Key, header.Value)); + } + + return claims; + } + + public IEnumerable GetClaimsIdentity(string token) + { + var jwtHandler = new JwtSecurityTokenHandler(); + // token校验 + if (token.IsNotEmptyOrNull() && jwtHandler.CanReadToken(token)) + { + var jwtToken = jwtHandler.ReadJwtToken(token); + + return jwtToken.Claims; + } + + return new List(); } public List GetClaimValueByType(string ClaimType) { - return (from item in GetClaimsIdentity() - where item.Type == ClaimType - select item.Value).ToList(); - + 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 3ab11bdc..aa6094b1 100644 --- a/Blog.Core.Common/HttpContextUser/IUser.cs +++ b/Blog.Core.Common/HttpContextUser/IUser.cs @@ -1,17 +1,21 @@ using System.Collections.Generic; using System.Security.Claims; +using Blog.Core.Model; 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); string GetToken(); List GetUserInfoFromToken(string ClaimType); + + 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/Https/HttpPolly/HttpPollyHelper.cs b/Blog.Core.Common/Https/HttpPolly/HttpPollyHelper.cs new file mode 100644 index 00000000..1187d711 --- /dev/null +++ b/Blog.Core.Common/Https/HttpPolly/HttpPollyHelper.cs @@ -0,0 +1,354 @@ +using Blog.Core.Model; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Https.HttpPolly +{ + public class HttpPollyHelper : IHttpPollyHelper + { + private readonly IHttpClientFactory _clientFactory; + + public HttpPollyHelper(IHttpClientFactory httpClientFactory) + { + _clientFactory = httpClientFactory; + } + + public async Task PostAsync(HttpEnum httpEnum, string url, R request, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + 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(); + return JsonConvert.DeserializeObject(result); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + } + catch (Exception) + { + + throw; + } + + } + + public async Task PostAsync(HttpEnum httpEnum, string url, string request, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + 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(); + return JsonConvert.DeserializeObject(result); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + + } + catch (Exception) + { + + throw; + } + + } + + public async Task PostAsync(HttpEnum httpEnum, string url, R request, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + 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(); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + } + catch (Exception) + { + + throw; + } + + } + + public async Task PostAsync(HttpEnum httpEnum, string url, string request, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + 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(); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + + } + catch (Exception) + { + + throw; + } + + } + + public async Task GetAsync(HttpEnum httpEnum, string url, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + var response = await client.GetAsync(url); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + string result = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(result); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + + } + catch (Exception) + { + + throw; + } + + } + + public async Task GetAsync(HttpEnum httpEnum, string url, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + var response = await client.GetAsync(url); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + return await response.Content.ReadAsStringAsync(); ; + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + + } + catch (Exception) + { + + throw; + } + + } + + public async Task PutAsync(HttpEnum httpEnum, string url, R request, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + 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(); + return JsonConvert.DeserializeObject(result); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + + } + catch (Exception) + { + + throw; + } + + } + + public async Task PutAsync(HttpEnum httpEnum, string url, string request, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + 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(); + return JsonConvert.DeserializeObject(result); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + + } + catch (Exception) + { + + throw; + } + + } + + public async Task DeleteAsync(HttpEnum httpEnum, string url, Dictionary headers = null) + { + try + { + var client = _clientFactory.CreateClient(httpEnum.ToString()); + if (headers != null) + { + foreach (var header in headers) + { + if (!client.DefaultRequestHeaders.Contains(header.Key)) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + } + + var response = await client.DeleteAsync(url); + + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + string result = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(result); + } + else + { + throw new Exception($"Http Error StatusCode:{response.StatusCode}"); + } + + } + catch (Exception) + { + + throw; + } + + } + } +} diff --git a/Blog.Core.Common/Https/HttpPolly/IHttpPollyHelper.cs b/Blog.Core.Common/Https/HttpPolly/IHttpPollyHelper.cs new file mode 100644 index 00000000..da8bd5ab --- /dev/null +++ b/Blog.Core.Common/Https/HttpPolly/IHttpPollyHelper.cs @@ -0,0 +1,19 @@ +using Blog.Core.Model; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Https.HttpPolly +{ + public interface IHttpPollyHelper + { + Task PostAsync(HttpEnum httpEnum, string url, R request, Dictionary headers = null); + Task PostAsync(HttpEnum httpEnum, string url, string request, Dictionary headers = null); + Task PostAsync(HttpEnum httpEnum, string url, string request, Dictionary headers = null); + Task PostAsync(HttpEnum httpEnum, string url, R request, Dictionary headers = null); + Task GetAsync(HttpEnum httpEnum, string url, Dictionary headers = null); + Task GetAsync(HttpEnum httpEnum, string url, Dictionary headers = null); + Task PutAsync(HttpEnum httpEnum, string url, R request, Dictionary headers = null); + Task PutAsync(HttpEnum httpEnum, string url, string request, Dictionary headers = null); + Task DeleteAsync(HttpEnum httpEnum, string url, Dictionary headers = null); + } +} diff --git a/Blog.Core.Common/Https/RequestIpUtility.cs b/Blog.Core.Common/Https/RequestIpUtility.cs new file mode 100644 index 00000000..ac7e28f3 --- /dev/null +++ b/Blog.Core.Common/Https/RequestIpUtility.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Microsoft.AspNetCore.Http; + +namespace Blog.Core.Common.Https; + +public static class RequestIpUtility +{ + public static string GetRequestIp(this HttpContext context) + { + string ip = SplitCsv(GetHeaderValueAs(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 1033ccb6..63803d2f 100644 --- a/Blog.Core.Common/Hubs/ChatHub.cs +++ b/Blog.Core.Common/Hubs/ChatHub.cs @@ -1,4 +1,7 @@ -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; @@ -52,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); + } + } } /// @@ -72,7 +88,7 @@ public override Task OnDisconnectedAsync(System.Exception ex) public async Task SendMessage(string user, string message) { - await Clients.All.ReceiveMessage( user, message); + await Clients.All.ReceiveMessage(user, message); } //定于一个通讯管道,用来管理我们和客户端的连接 @@ -80,10 +96,14 @@ public async Task SendMessage(string user, string message) public async Task GetLatestCount(string random) { //2、服务端主动向客户端发送数据,名字千万不能错 - await Clients.All.ReceiveUpdate(LogLock.GetLogData()); + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + //TODO 主动发送错误消息 + await Clients.All.ReceiveUpdate("这是一个LOG"); + } - //3、客户端再通过 ReceiveUpdate ,来接收 + //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 dc2d158a..00000000 --- a/Blog.Core.Common/LogHelper/LogLock.cs +++ /dev/null @@ -1,453 +0,0 @@ -using Blog.Core.Common.Helper; -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 - { - - 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 OutSql2Log(string prefix, string[] dataParas, bool IsHeader = true) - { - 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); - - 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" - ); - } - - //if (logContent.IsNotEmptyOrNull() && logContent.Length > 500) - //{ - // logContent = logContent.Substring(0, 500) + "\r\n"; - //} - - File.AppendAllText(logFilePath, logContent); - WritedCount++; - } - catch (Exception e) - { - Console.Write(e.Message); - FailedCount++; - } - finally - { - //退出写入模式,释放资源占用 - //注意:一次请求对应一次释放 - // 若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放] - // 若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定] - LogWriteLock.ExitWriteLock(); - } - } - - /// - /// 读取文件内容 - /// - /// 文件夹路径 - /// 文件名 - /// 编码 - /// 读取类型(0:精准,1:前缀模糊) - /// - public static string ReadLog(string folderPath, string fileName, Encoding encode, ReadType readType = ReadType.Accurate) - { - 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(); - - 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/RequestInfo.cs b/Blog.Core.Common/LogHelper/RequestInfo.cs index 9e4aac27..02160475 100644 --- a/Blog.Core.Common/LogHelper/RequestInfo.cs +++ b/Blog.Core.Common/LogHelper/RequestInfo.cs @@ -39,4 +39,71 @@ public class RequestInfo public string Week { get; set; } } + + public class AOPLogInfo + { + /// + /// 请求时间 + /// + public string RequestTime { get; set; } = string.Empty; + /// + /// 操作人员 + /// + public string OpUserName { get; set; } = string.Empty; + /// + /// 请求方法名 + /// + public string RequestMethodName { get; set; } = string.Empty; + /// + /// 请求参数名 + /// + public string RequestParamsName { get; set; } = string.Empty; + /// + /// 请求参数数据JSON + /// + public string RequestParamsData { get; set; } = string.Empty; + /// + /// 请求响应间隔时间 + /// + public string ResponseIntervalTime { get; set; } = string.Empty; + /// + /// 响应时间 + /// + public string ResponseTime { get; set; } = string.Empty; + /// + /// 响应结果 + /// + public string ResponseJsonData { get; set; } = string.Empty; + } + + public class AOPLogExInfo + { + public AOPLogInfo ApiLogAopInfo { get; set; } + /// + /// 异常 + /// + public string InnerException { get; set; } = string.Empty; + /// + /// 异常信息 + /// + public string ExMessage { get; set; } = string.Empty; + } + + public class RequestLogInfo + { + /// + /// 请求地址 + /// + public string Path { get; set; } + + /// + /// 请求参数 + /// + public string QueryString { get; set; } + + /// + /// Body参数 + /// + public string BodyData { get; set; } + } } diff --git a/Blog.Core.Common/LogHelper/Seri/SerilogServer.cs b/Blog.Core.Common/LogHelper/Seri/SerilogServer.cs deleted file mode 100644 index 2c70532d..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 new file mode 100644 index 00000000..29222802 --- /dev/null +++ b/Blog.Core.Common/Seed/DBSeed.cs @@ -0,0 +1,645 @@ +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 SqlSugar; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Blog.Core.Common.Const; +using Microsoft.Data.SqlClient; + +namespace Blog.Core.Common.Seed +{ + public class DBSeed + { + private static string SeedDataFolder = "BlogCore.Data.json/{0}.tsv"; + + + /// + /// 异步添加种子数据 + /// + /// + /// + /// + public static async Task SeedAsync(MyContext myContext, string WebRootPath) + { + try + { + if (string.IsNullOrEmpty(WebRootPath)) + { + throw new Exception("获取wwwroot路径时,异常!"); + } + + SeedDataFolder = Path.Combine(WebRootPath, SeedDataFolder); + + Console.WriteLine("************ Blog.Core DataBase Set *****************"); + 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(); + if (BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException()) + { + var index = 0; + BaseDBConfig.MainConfig.SlaveConnectionConfigs.ForEach(m => + { + index++; + Console.WriteLine($"Slave{index} DB HitRate: {m.HitRate}"); + Console.WriteLine($"Slave{index} DB ConnectString: {m.ConnectionString}"); + Console.WriteLine($"--------------------------------------"); + }); + } + else if (BaseDBConfig.ReuseConfigs.AnyNoException()) + { + var index = 0; + BaseDBConfig.ReuseConfigs.ForEach(m => + { + 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($"--------------------------------------"); + }); + } + + Console.WriteLine(); + + // 创建数据库 + Console.WriteLine($"Create Database(The Db Id:{MyContext.ConnId})..."); + + 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/Dm数据库!"); + } + + // 创建数据库表,遍历指定命名空间下的class, + // 注意不要把其他命名空间下的也添加进来。 + 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 modelTypes = referencedAssemblies + .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 => + { + // 这里只支持添加表,不支持删除 + // 如果想要删除,数据库直接右键删除,或者联系SqlSugar作者; + if (!myContext.Db.DbMaintenance.IsAnyTable(t.Name)) + { + Console.WriteLine(t.Name); + 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(); + JsonConvert.DefaultSettings = new Func(() => + { + //日期类型默认格式化处理 + setting.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; + setting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; + + //空值处理 + setting.NullValueHandling = NullValueHandling.Ignore; + + //高级用法九中的Bool类型转换 设置 + //setting.Converters.Add(new BoolConvert("是,否")); + + return setting; + }); + + Console.WriteLine($"Seeding database data (The Db Id:{MyContext.ConnId})..."); + + 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))); + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:Modules created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:Permission created success!"); + } + else + { + 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(); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:Role created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:RoleModulePermission created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:Topic created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:TopicDetail created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:UserRole created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:sysUserInfo created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:TasksQz created success!"); + } + else + { + 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); + + myContext.GetEntityDB().InsertRange(data); + Console.WriteLine("Table:Department created success!"); + } + else + { + Console.WriteLine("Table:Department already exists..."); + } + + #endregion + + //种子初始化 + await SeedDataAsync(myContext.Db); + + ConsoleHelper.WriteSuccessLine($"Done seeding database!"); + } + + Console.WriteLine(); + } + catch (Exception ex) + { + throw new Exception( + $"1、若是Mysql,查看常见问题:https://github.com/anjoy8/Blog.Core/issues/148#issue-776281770 \n" + + $"2、若是Oracle,查看常见问题:https://github.com/anjoy8/Blog.Core/issues/148#issuecomment-752340231 \n" + + "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.Model/Seed/FrameSeed.cs b/Blog.Core.Common/Seed/FrameSeed.cs similarity index 93% rename from Blog.Core.Model/Seed/FrameSeed.cs rename to Blog.Core.Common/Seed/FrameSeed.cs index 2ac7d916..9e1fd145 100644 --- a/Blog.Core.Model/Seed/FrameSeed.cs +++ b/Blog.Core.Common/Seed/FrameSeed.cs @@ -1,11 +1,10 @@ using SqlSugar; -using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -namespace Blog.Core.Model.Seed +namespace Blog.Core.Common.Seed { public class FrameSeed { @@ -18,7 +17,7 @@ public class FrameSeed /// 数据库表名数组,默认空,生成所有表 /// /// - public static bool CreateControllers(SqlSugarClient sqlSugarClient, string ConnId = null, bool isMuti = false, string[] tableNames = null) + public static bool CreateControllers(SqlSugarScope sqlSugarClient, string ConnId = null, bool isMuti = false, string[] tableNames = null) { Create_Controller_ClassFileByDBTalbe(sqlSugarClient, ConnId, $@"C:\my-file\Blog.Core.Api.Controllers", "Blog.Core.Api.Controllers", tableNames, "", isMuti); return true; @@ -32,7 +31,7 @@ public static bool CreateControllers(SqlSugarClient sqlSugarClient, string ConnI /// 数据库表名数组,默认空,生成所有表 /// /// - public static bool CreateModels(SqlSugarClient sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) + public static bool CreateModels(SqlSugarScope sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) { Create_Model_ClassFileByDBTalbe(sqlSugarClient, ConnId, $@"C:\my-file\Blog.Core.Model", "Blog.Core.Model.Models", tableNames, "", isMuti); return true; @@ -46,7 +45,7 @@ public static bool CreateModels(SqlSugarClient sqlSugarClient, string ConnId, bo /// /// 数据库表名数组,默认空,生成所有表 /// - public static bool CreateIRepositorys(SqlSugarClient sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) + public static bool CreateIRepositorys(SqlSugarScope sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) { Create_IRepository_ClassFileByDBTalbe(sqlSugarClient, ConnId, $@"C:\my-file\Blog.Core.IRepository", "Blog.Core.IRepository", tableNames, "", isMuti); return true; @@ -62,7 +61,7 @@ public static bool CreateIRepositorys(SqlSugarClient sqlSugarClient, string Conn /// /// 数据库表名数组,默认空,生成所有表 /// - public static bool CreateIServices(SqlSugarClient sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) + public static bool CreateIServices(SqlSugarScope sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) { Create_IServices_ClassFileByDBTalbe(sqlSugarClient, ConnId, $@"C:\my-file\Blog.Core.IServices", "Blog.Core.IServices", tableNames, "", isMuti); return true; @@ -78,7 +77,7 @@ public static bool CreateIServices(SqlSugarClient sqlSugarClient, string ConnId, /// /// 数据库表名数组,默认空,生成所有表 /// - public static bool CreateRepository(SqlSugarClient sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) + public static bool CreateRepository(SqlSugarScope sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) { Create_Repository_ClassFileByDBTalbe(sqlSugarClient, ConnId, $@"C:\my-file\Blog.Core.Repository", "Blog.Core.Repository", tableNames, "", isMuti); return true; @@ -94,7 +93,7 @@ public static bool CreateRepository(SqlSugarClient sqlSugarClient, string ConnId /// /// 数据库表名数组,默认空,生成所有表 /// - public static bool CreateServices(SqlSugarClient sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) + public static bool CreateServices(SqlSugarScope sqlSugarClient, string ConnId, bool isMuti = false, string[] tableNames = null) { Create_Services_ClassFileByDBTalbe(sqlSugarClient, ConnId, $@"C:\my-file\Blog.Core.Services", "Blog.Core.Services", tableNames, "", isMuti); return true; @@ -116,7 +115,7 @@ public static bool CreateServices(SqlSugarClient sqlSugarClient, string ConnId, /// /// 是否序列化 private static void Create_Controller_ClassFileByDBTalbe( - SqlSugarClient sqlSugarClient, + SqlSugarScope sqlSugarClient, string ConnId, string strPath, string strNameSpace, @@ -195,6 +194,7 @@ public async Task> Post([FromBody] {ClassName} request) var data = new MessageModel(); var id = await _{ClassName}Services.Add(request); + data.success = id > 0; if (data.success) { data.response = id.ObjToString(); @@ -218,15 +218,17 @@ public async Task> Put([FromBody] {ClassName} request) return data; } - [HttpDelete(""{id}"")] - public async Task> Delete(string id) + [HttpDelete] + public async Task> Delete(int id) { var data = new MessageModel(); - data.success = await _{ClassName}Services.DeleteById(id); + var model = await _{ClassName}Services.QueryById(id); + model.IsDeleted = true; + data.success = await _departmentServices.Update(model); if (data.success) { data.msg = ""删除成功""; - data.response = id; + data.response = model?.Id.ObjToString(); } return data; @@ -264,7 +266,7 @@ public async Task> Delete(string id) /// /// 是否序列化 private static void Create_Model_ClassFileByDBTalbe( - SqlSugarClient sqlSugarClient, + SqlSugarScope sqlSugarClient, string ConnId, string strPath, string strNameSpace, @@ -329,7 +331,7 @@ public class {ClassName}" + (string.IsNullOrEmpty(strInterface) ? "" : (" : " + /// 实现接口 /// private static void Create_IRepository_ClassFileByDBTalbe( - SqlSugarClient sqlSugarClient, + SqlSugarScope sqlSugarClient, string ConnId, string strPath, string strNameSpace, @@ -386,7 +388,7 @@ public interface I{ClassName}Repository : IBaseRepository<{ClassName}>" + (strin /// 实现接口 /// private static void Create_IServices_ClassFileByDBTalbe( - SqlSugarClient sqlSugarClient, + SqlSugarScope sqlSugarClient, string ConnId, string strPath, string strNameSpace, @@ -443,7 +445,7 @@ public interface I{ClassName}Services :IBaseServices<{ClassName}>" + (string.IsN /// 实现接口 /// private static void Create_Repository_ClassFileByDBTalbe( - SqlSugarClient sqlSugarClient, + SqlSugarScope sqlSugarClient, string ConnId, string strPath, string strNameSpace, @@ -505,7 +507,7 @@ public class {ClassName}Repository : BaseRepository<{ClassName}>, I{ClassName}Re /// 实现接口 /// private static void Create_Services_ClassFileByDBTalbe( - SqlSugarClient sqlSugarClient, + SqlSugarScope sqlSugarClient, string ConnId, string strPath, string strNameSpace, 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.Model/Seed/MyContext.cs b/Blog.Core.Common/Seed/MyContext.cs similarity index 92% rename from Blog.Core.Model/Seed/MyContext.cs rename to Blog.Core.Common/Seed/MyContext.cs index e4d71328..1251f825 100644 --- a/Blog.Core.Model/Seed/MyContext.cs +++ b/Blog.Core.Common/Seed/MyContext.cs @@ -2,7 +2,7 @@ using SqlSugar; using System; -namespace Blog.Core.Model.Seed +namespace Blog.Core.Common.Seed { public class MyContext { @@ -11,7 +11,7 @@ public class MyContext private static string _connectionString = connectObject.Connection; private static DbType _dbType = (DbType)connectObject.DbType; public static string ConnId = connectObject.ConnId; - private SqlSugarClient _db; + private SqlSugarScope _db; /// /// 连接字符串 @@ -56,7 +56,7 @@ public static DbType DbType /// 数据连接对象 /// Blog.Core /// - public SqlSugarClient Db + public SqlSugarScope Db { get { return _db; } private set { _db = value; } @@ -71,7 +71,7 @@ public MyContext(ISqlSugarClient sqlSugarClient) if (string.IsNullOrEmpty(_connectionString)) throw new ArgumentNullException("数据库连接字符串为空"); - _db = sqlSugarClient as SqlSugarClient; + _db = sqlSugarClient as SqlSugarScope; } @@ -92,10 +92,10 @@ public MyContext(ISqlSugarClient sqlSugarClient) ///
/// db /// 返回值 - public SimpleClient GetEntityDB(SqlSugarClient db) where T : class, new() - { - return new SimpleClient(db); - } + //public SimpleClient GetEntityDB(SqlSugarClient db) where T : class, new() + //{ + // return new SimpleClient(db); + //} @@ -186,7 +186,7 @@ public static ConnectionConfig GetConnectionConfig(bool blnIsAutoCloseConnection { //DataInfoCacheService = new HttpRuntimeCache() }, - IsShardSameThread = blnIsShardSameThread + //IsShardSameThread = blnIsShardSameThread }; return config; } @@ -197,9 +197,9 @@ public static ConnectionConfig GetConnectionConfig(bool blnIsAutoCloseConnection ///
/// config /// 返回值 - public static SqlSugarClient GetCustomDB(ConnectionConfig config) + public static SqlSugarScope GetCustomDB(ConnectionConfig config) { - return new SqlSugarClient(config); + return new SqlSugarScope(config); } /// /// 功能描述:获取一个自定义的数据库处理对象 @@ -207,7 +207,7 @@ public static SqlSugarClient GetCustomDB(ConnectionConfig config) /// /// sugarClient /// 返回值 - public static SimpleClient GetCustomEntityDB(SqlSugarClient sugarClient) where T : class, new() + public static SimpleClient GetCustomEntityDB(SqlSugarScope sugarClient) where T : class, new() { return new SimpleClient(sugarClient); } @@ -219,7 +219,7 @@ public static SqlSugarClient GetCustomDB(ConnectionConfig config) /// 返回值 public static SimpleClient GetCustomEntityDB(ConnectionConfig config) where T : class, new() { - SqlSugarClient sugarClient = GetCustomDB(config); + SqlSugarScope sugarClient = GetCustomDB(config); return GetCustomEntityDB(sugarClient); } #endregion 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/StaticHelper/StaticPayInfo.cs b/Blog.Core.Common/Static/StaticPayInfo.cs similarity index 65% rename from Blog.Core.Common/StaticHelper/StaticPayInfo.cs rename to Blog.Core.Common/Static/StaticPayInfo.cs index 36a62d4b..a4242bfd 100644 --- a/Blog.Core.Common/StaticHelper/StaticPayInfo.cs +++ b/Blog.Core.Common/Static/StaticPayInfo.cs @@ -1,37 +1,37 @@  -namespace Blog.Core.Common.StaticHelper +namespace Blog.Core.Common.Static { public static class StaticPayInfo { /// /// 商户号 /// - public readonly static string MERCHANTID = Appsettings.app(new string[] { "PayInfo", "MERCHANTID" }).ObjToString(); + public readonly static string MERCHANTID = AppSettings.app(new string[] { "PayInfo", "MERCHANTID" }).ObjToString(); /// /// 柜台号 /// - public readonly static string POSID = Appsettings.app(new string[] { "PayInfo", "POSID" }).ObjToString(); + public readonly static string POSID = AppSettings.app(new string[] { "PayInfo", "POSID" }).ObjToString(); /// /// 分行号 /// - public readonly static string BRANCHID = Appsettings.app(new string[] { "PayInfo", "BRANCHID" }).ObjToString(); + public readonly static string BRANCHID = AppSettings.app(new string[] { "PayInfo", "BRANCHID" }).ObjToString(); /// /// 公钥 /// - public readonly static string pubKey = Appsettings.app(new string[] { "PayInfo", "pubKey" }).ObjToString(); + public readonly static string pubKey = AppSettings.app(new string[] { "PayInfo", "pubKey" }).ObjToString(); /// /// 操作员号 /// - public readonly static string USER_ID = Appsettings.app(new string[] { "PayInfo", "USER_ID" }).ObjToString(); + public readonly static string USER_ID = AppSettings.app(new string[] { "PayInfo", "USER_ID" }).ObjToString(); /// /// 密码 /// - public readonly static string PASSWORD = Appsettings.app(new string[] { "PayInfo", "PASSWORD" }).ObjToString(); + public readonly static string PASSWORD = AppSettings.app(new string[] { "PayInfo", "PASSWORD" }).ObjToString(); /// /// 外联平台通讯地址 /// - public readonly static string OutAddress = Appsettings.app(new string[] { "PayInfo", "OutAddress" }).ObjToString(); + public readonly static string OutAddress = AppSettings.app(new string[] { "PayInfo", "OutAddress" }).ObjToString(); } } 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.ConsoleApp/Blog.Core.ConsoleApp.csproj b/Blog.Core.ConsoleApp/Blog.Core.ConsoleApp.csproj deleted file mode 100644 index 20827042..00000000 --- a/Blog.Core.ConsoleApp/Blog.Core.ConsoleApp.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - Exe - net5.0 - - - diff --git a/Blog.Core.ConsoleApp/Program.cs b/Blog.Core.ConsoleApp/Program.cs deleted file mode 100644 index 87e5d801..00000000 --- a/Blog.Core.ConsoleApp/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Blog.Core.ConsoleApp -{ - class Program - { - static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} diff --git a/Blog.Core.EventBus/Blog.Core.EventBus.csproj b/Blog.Core.EventBus/Blog.Core.EventBus.csproj index b71254af..622856f5 100644 --- a/Blog.Core.EventBus/Blog.Core.EventBus.csproj +++ b/Blog.Core.EventBus/Blog.Core.EventBus.csproj @@ -1,21 +1,18 @@ - - netcoreapp5.0 - - - - + + + - + - - - - + + + + diff --git a/Blog.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs b/Blog.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs index 5eddad7d..7438ddd4 100644 --- a/Blog.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs +++ b/Blog.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs @@ -1,6 +1,6 @@ using Autofac; using Blog.Core.Common.Extensions; -using Blog.Core.Common.Helper; +using Blog.Core.Common; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; 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 e7153c78..a52d3ad8 100644 --- a/Blog.Core.Extensions/AOP/BlogLogAOP.cs +++ b/Blog.Core.Extensions/AOP/BlogLogAOP.cs @@ -1,4 +1,5 @@ -using Blog.Core.Common.LogHelper; +using Blog.Core.Common; +using Blog.Core.Common.LogHelper; using Blog.Core.Hubs; using Castle.DynamicProxy; using Microsoft.AspNetCore.Http; @@ -9,6 +10,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace Blog.Core.AOP { @@ -19,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; } @@ -34,12 +38,34 @@ public BlogLogAOP(IHubContext hubContext, IHttpContextAccessor accessor public void Intercept(IInvocation invocation) { string UserName = _accessor.HttpContext?.User?.Identity?.Name; + string json; + try + { + json = JsonConvert.SerializeObject(invocation.Arguments); + } + catch (Exception ex) + { + json = "无法序列化,可能是兰姆达表达式等原因造成,按照框架优化代码" + ex.ToString(); + } + + DateTime startTime = DateTime.Now; + AOPLogInfo apiLogAopInfo = new AOPLogInfo + { + 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 + }; + + //测试异常记录 + //Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss fff")); //记录被拦截方法信息的日志信息 - var dataIntercept = "" + - $"【当前操作用户】:{ UserName} \r\n" + - $"【当前执行方法】:{ invocation.Method.Name} \r\n" + - $"【携带的参数有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; + //var dataIntercept = "" + + // $"【当前操作用户】:{ UserName} \r\n" + + // $"【当前执行方法】:{ invocation.Method.Name} \r\n" + + // $"【携带的参数有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; try { @@ -51,69 +77,95 @@ 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, dataIntercept),/*成功时执行*/ - ex => - { - LogEx(ex, dataIntercept); - }); + 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, dataIntercept, o),/*成功时执行*/ - ex => - { - LogEx(ex, dataIntercept); - }); + 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 // 如果方案一不行,试试这个方案 - #region 方案二 + //#region 方案二 //var type = invocation.Method.ReturnType; //var resultProperty = type.GetProperty("Result"); - //dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue))}"); + //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)); + + ////dataIntercept += ($"【响应时间】:{ResponseTime}ms\r\n"); + ////dataIntercept += ($"【执行完成时间】:{endTime.ToString("yyyy-MM-dd hh:mm:ss fff")}\r\n"); + ////dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue))}\r\n"); //Parallel.For(0, 1, e => //{ - // LogLock.OutSql2Log("AOPLog", new string[] { dataIntercept }); + // //LogLock.OutLogAOP("AOPLog", new string[] { dataIntercept }); + // LogLock.OutLogAOP("AOPLog", new string[] { apiLogAopInfo.GetType().ToString() + " - ResponseJsonDataType:" + type, JsonConvert.SerializeObject(apiLogAopInfo) }); //}); - #endregion + //#endregion } else - {// 同步1 - - dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}"); - Parallel.For(0, 1, e => + { + // 同步1 + string jsonResult; + try + { + jsonResult = JsonConvert.SerializeObject(invocation.ReturnValue); + } + catch (Exception ex) { - LogLock.OutSql2Log("AOPLog", new string[] { dataIntercept }); - }); + 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"); + apiLogAopInfo.ResponseIntervalTime = ResponseTime + "ms"; + //apiLogAopInfo.ResponseJsonData = JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue)); + apiLogAopInfo.ResponseJsonData = jsonResult; + //dataIntercept += ($"【执行完成结果】:{jsonResult}"); + + _logger.LogInformation("AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopInfo)); } } - catch (Exception ex)// 同步2 + catch (Exception ex) // 同步2 { - LogEx(ex, dataIntercept); - + LogEx(ex, apiLogAopInfo); + throw; } - _hubContext.Clients.All.SendAsync("ReceiveUpdate", LogLock.GetLogData()).Wait(); + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + //发送日志 + _hubContext.Clients.All.SendAsync("ReceiveUpdate", JsonConvert.SerializeObject(apiLogAopInfo)).Wait(); + } } - private async Task SuccessAction(IInvocation invocation, string dataIntercept, object o = null) + private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInfo, DateTime startTime, + object o = null) { //invocation.ReturnValue = o; //var type = invocation.Method.ReturnType; @@ -127,33 +179,35 @@ private async Task SuccessAction(IInvocation invocation, string dataIntercept, o //{ // dataIntercept += ($"【执行完成结果】:{invocation.ReturnValue}"); //} - - dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(o)}"); - - - await Task.Run(() => - { - Parallel.For(0, 1, e => - { - LogLock.OutSql2Log("AOPLog", new string[] { dataIntercept }); - }); - }); + 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); + + _logger.LogInformation("AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopInfo)); } - private void LogEx(Exception ex, string dataIntercept) + private void LogEx(Exception ex, AOPLogInfo dataIntercept) { if (ex != null) { //执行的 service 中,收录异常 MiniProfiler.Current.CustomTiming("Errors:", ex.Message); //执行的 service 中,捕获异常 - dataIntercept += ($"【执行完成结果】:方法中出现异常:{ex.Message + ex.InnerException}\r\n"); - - // 异常日志里有详细的堆栈信息 - Parallel.For(0, 1, e => + //dataIntercept += ($"【执行完成结果】:方法中出现异常:{ex.Message + ex.InnerException}\r\n"); + AOPLogExInfo apiLogAopExInfo = new AOPLogExInfo { - LogLock.OutSql2Log("AOPLog", new string[] { dataIntercept }); - }); + ExMessage = ex.Message, + InnerException = "InnerException-内部异常:\r\n" + + (ex.InnerException == null ? "" : ex.InnerException.InnerException.ToString()) + + ("\r\nStackTrace-堆栈跟踪:\r\n") + (ex.StackTrace == null ? "" : ex.StackTrace.ToString()), + ApiLogAopInfo = dataIntercept + }; + // 异常日志里有详细的堆栈信息 + _logger.LogError(ex, "AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopExInfo)); } } @@ -163,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; @@ -190,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 @@ -210,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/AOP/BlogTranAOP.cs b/Blog.Core.Extensions/AOP/BlogTranAOP.cs index 6d54b383..2c252ef6 100644 --- a/Blog.Core.Extensions/AOP/BlogTranAOP.cs +++ b/Blog.Core.Extensions/AOP/BlogTranAOP.cs @@ -1,10 +1,11 @@ using Blog.Core.Common; -using Blog.Core.IRepository.UnitOfWork; using Castle.DynamicProxy; +using Microsoft.Extensions.Logging; using System; -using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Blog.Core.Common.DB; +using Blog.Core.Repository.UnitOfWorks; namespace Blog.Core.AOP { @@ -13,10 +14,13 @@ namespace Blog.Core.AOP ///
public class BlogTranAOP : IInterceptor { - private readonly IUnitOfWork _unitOfWork; - public BlogTranAOP(IUnitOfWork unitOfWork) + private readonly ILogger _logger; + private readonly IUnitOfWorkManage _unitOfWorkManage; + + public BlogTranAOP(IUnitOfWorkManage unitOfWorkManage, ILogger logger) { - _unitOfWork = unitOfWork; + _unitOfWorkManage = unitOfWorkManage; + _logger = logger; } /// @@ -28,17 +32,14 @@ public void Intercept(IInvocation invocation) var method = invocation.MethodInvocationTarget ?? invocation.Method; //对当前方法的特性验证 //如果需要验证 - if (method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(UseTranAttribute)) is UseTranAttribute) + if (method.GetCustomAttribute(true) is { } uta) { try { - Console.WriteLine($"Begin Transaction"); - - _unitOfWork.BeginTran(); + Before(method, uta.Propagation); invocation.Proceed(); - // 异步获取异常,先执行 if (IsAsyncMethod(invocation.Method)) { @@ -48,20 +49,70 @@ public void Intercept(IInvocation invocation) Task.WaitAll(result as Task); } } - _unitOfWork.CommitTran(); + After(method); } - catch (Exception) + catch (Exception ex) { - Console.WriteLine($"Rollback Transaction"); - _unitOfWork.RollbackTran(); + _logger.LogError(ex.ToString()); + AfterException(method); + throw; } } else { - invocation.Proceed();//直接执行被拦截方法 + invocation.Proceed(); //直接执行被拦截方法 } + } + + private void Before(MethodInfo method, Propagation propagation) + { + switch (propagation) + { + case Propagation.Required: + if (_unitOfWorkManage.TranCount <= 0) + { + _logger.LogDebug($"Begin Transaction"); + Console.WriteLine($"Begin Transaction"); + _unitOfWorkManage.BeginTran(method); + } + + break; + case Propagation.Mandatory: + if (_unitOfWorkManage.TranCount <= 0) + { + throw new Exception("事务传播机制为:[Mandatory],当前不存在事务"); + } + break; + case Propagation.Nested: + _logger.LogDebug($"Begin Transaction"); + Console.WriteLine($"Begin Transaction"); + _unitOfWorkManage.BeginTran(method); + break; + default: + throw new ArgumentOutOfRangeException(nameof(propagation), propagation, null); + } + } + + private void After(MethodInfo method) + { + _unitOfWorkManage.CommitTran(method); + } + + private void AfterException(MethodInfo method) + { + _unitOfWorkManage.RollbackTran(method); + } + + /// + /// 获取变量的默认值 + /// + /// + /// + public object GetDefaultValue(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; } private async Task SuccessAction(IInvocation invocation) @@ -77,15 +128,12 @@ public static bool IsAsyncMethod(MethodInfo method) return ( method.ReturnType == typeof(Task) || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) - ); + ); } + private async Task TestActionAsync(IInvocation invocation) { await Task.Run(null); } - } - - - -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs b/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs index 8efc16a9..6fa9c9b6 100644 --- a/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs +++ b/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs @@ -20,8 +20,8 @@ public class JwtHelper /// public static string IssueJwt(TokenModelJwt tokenModel) { - string iss = Appsettings.app(new string[] { "Audience", "Issuer" }); - string aud = Appsettings.app(new string[] { "Audience", "Audience" }); + string iss = AppSettings.app(new string[] { "Audience", "Issuer" }); + string aud = AppSettings.app(new string[] { "Audience", "Audience" }); string secret = AppSecretConfig.Audience_Secret_String; //var claims = new Claim[] //old @@ -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,12 +90,24 @@ public static TokenModelJwt SerializeJwt(string jwtStr) tokenModelJwt = new TokenModelJwt { - Uid = (jwtToken.Id).ObjToInt(), + Uid = (jwtToken.Id).ObjToLong(), Role = role != null ? role.ObjToString() : "", }; } return tokenModelJwt; } + + public static bool customSafeVerify(string token) + { + var jwtHandler = new JwtSecurityTokenHandler(); + var symmetricKeyAsBase64 = AppSecretConfig.Audience_Secret_String; + var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); + var signingKey = new SymmetricSecurityKey(keyByteArray); + var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); + + var jwt = jwtHandler.ReadJwtToken(token); + return jwt.RawSignature == Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateEncodedSignature(jwt.RawHeader + "." + jwt.RawPayload, signingCredentials); + } } /// diff --git a/Blog.Core.Extensions/Authorizations/Policys/ApiResponseHandler.cs b/Blog.Core.Extensions/Authorizations/Policys/ApiResponseHandler.cs index 6aedc54c..9c8f4e5c 100644 --- a/Blog.Core.Extensions/Authorizations/Policys/ApiResponseHandler.cs +++ b/Blog.Core.Extensions/Authorizations/Policys/ApiResponseHandler.cs @@ -7,19 +7,24 @@ using System; using System.Text.Encodings.Web; using System.Threading.Tasks; +using Blog.Core.Common.HttpContextUser; namespace Blog.Core.AuthHelper { public class ApiResponseHandler : AuthenticationHandler { - public ApiResponseHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) + private readonly IUser _user; + + public ApiResponseHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IUser user) : base(options, logger, encoder, clock) { + _user = user; } protected override Task HandleAuthenticateAsync() { throw new NotImplementedException(); } + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) { Response.ContentType = "application/json"; @@ -30,9 +35,16 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) { Response.ContentType = "application/json"; - Response.StatusCode = StatusCodes.Status403Forbidden; - await Response.WriteAsync(JsonConvert.SerializeObject((new ApiResponse(StatusCode.CODE403)).MessageModel)); + if (_user.MessageModel != null) + { + Response.StatusCode = _user.MessageModel.status; + await Response.WriteAsync(JsonConvert.SerializeObject(_user.MessageModel)); + } + else + { + Response.StatusCode = StatusCodes.Status403Forbidden; + await Response.WriteAsync(JsonConvert.SerializeObject((new ApiResponse(StatusCode.CODE403)).MessageModel)); + } } - } -} +} \ No newline at end of file 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 8bb41e42..8d09be4f 100644 --- a/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs +++ b/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs @@ -1,16 +1,21 @@ 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; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Blog.Core.Common.Swagger; +using Blog.Core.Model.Models; namespace Blog.Core.AuthHelper { @@ -23,8 +28,11 @@ public class PermissionHandler : AuthorizationHandler /// 验证方案提供对象 /// public IAuthenticationSchemeProvider Schemes { get; set; } + private readonly IRoleModulePermissionServices _roleModulePermissionServices; private readonly IHttpContextAccessor _accessor; + private readonly ISysUserInfoServices _userServices; + private readonly IUser _user; /// /// 构造函数注入 @@ -32,15 +40,22 @@ public class PermissionHandler : AuthorizationHandler /// /// /// - public PermissionHandler(IAuthenticationSchemeProvider schemes, IRoleModulePermissionServices roleModulePermissionServices, IHttpContextAccessor accessor) + /// + /// + public PermissionHandler(IAuthenticationSchemeProvider schemes, + IRoleModulePermissionServices roleModulePermissionServices, IHttpContextAccessor accessor, + ISysUserInfoServices userServices, IUser user) { _accessor = accessor; + _userServices = userServices; + _user = user; Schemes = schemes; _roleModulePermissionServices = roleModulePermissionServices; } // 重写异步处理程序 - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + PermissionRequirement requirement) { var httpContext = _accessor.HttpContext; @@ -74,6 +89,7 @@ orderby item.Id Role = item.Role?.Name.ObjToString(), }).ToList(); } + requirement.Permissions = list; } @@ -94,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) @@ -109,83 +125,138 @@ orderby item.Id var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); // 是否开启测试环境 - var isTestCurrent = Appsettings.app(new string[] { "AppSettings", "UseLoadTest" }).ObjToBool(); + 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模式下校验当前用户状态 + // IDS4也可以校验,可以通过服务或者接口形式 + SysUserInfo user = new(); + if (!Permissions.IsUseIds4) { - // 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) - { - 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是否过期,过期则重新登录 var isExp = false; // ids4和jwt切换 // 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) + { + context.Fail(new AuthorizationFailureReason(this, "授权已过期,请重新授权")); + return; } - if (isExp) + + + //校验签发时间 + if (!Permissions.IsUseIds4) { - context.Succeed(requirement); + var value = httpContext.User.Claims + .FirstOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value; + if (value != null) + { + if (user.CriticalModifyTime > value.ObjToDate()) + { + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "很抱歉,授权已失效,请重新授权") + .MessageModel; + context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); + return; + } + } } - else + + // 获取当前用户的角色信息 + var currentUserRoles = new List(); + currentUserRoles = (from item in httpContext.User.Claims + where item.Type == ClaimTypes.Role + select item.Value).ToList(); + if (!currentUserRoles.Any()) { - context.Fail(); - return; + 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; @@ -195,4 +266,4 @@ orderby item.Id //context.Succeed(requirement); } } -} +} \ No newline at end of file diff --git a/Blog.Core.Extensions/AutoMapper/CustomProfile.cs b/Blog.Core.Extensions/AutoMapper/CustomProfile.cs index 9e1957ff..7b05f079 100644 --- a/Blog.Core.Extensions/AutoMapper/CustomProfile.cs +++ b/Blog.Core.Extensions/AutoMapper/CustomProfile.cs @@ -13,6 +13,43 @@ public CustomProfile() { CreateMap(); CreateMap(); + + CreateMap() + .ForMember(a => a.uID, o => o.MapFrom(d => d.Id)) + .ForMember(a => a.RIDs, o => o.MapFrom(d => d.RIDs)) + .ForMember(a => a.addr, o => o.MapFrom(d => d.Address)) + .ForMember(a => a.age, o => o.MapFrom(d => d.Age)) + .ForMember(a => a.birth, o => o.MapFrom(d => d.Birth)) + .ForMember(a => a.uStatus, o => o.MapFrom(d => d.Status)) + .ForMember(a => a.uUpdateTime, o => o.MapFrom(d => d.UpdateTime)) + .ForMember(a => a.uCreateTime, o => o.MapFrom(d => d.CreateTime)) + .ForMember(a => a.uErrorCount, o => o.MapFrom(d => d.ErrorCount)) + .ForMember(a => a.uLastErrTime, o => o.MapFrom(d => d.LastErrorTime)) + .ForMember(a => a.uLoginName, o => o.MapFrom(d => d.LoginName)) + .ForMember(a => a.uLoginPWD, o => o.MapFrom(d => d.LoginPWD)) + .ForMember(a => a.uRemark, o => o.MapFrom(d => d.Remark)) + .ForMember(a => a.uRealName, o => o.MapFrom(d => d.RealName)) + .ForMember(a => a.name, o => o.MapFrom(d => d.Name)) + .ForMember(a => a.tdIsDelete, o => o.MapFrom(d => d.IsDeleted)) + .ForMember(a => a.RoleNames, o => o.MapFrom(d => d.RoleNames)); + CreateMap() + .ForMember(a => a.Id, o => o.MapFrom(d => d.uID)) + .ForMember(a => a.Address, o => o.MapFrom(d => d.addr)) + .ForMember(a => a.RIDs, o => o.MapFrom(d => d.RIDs)) + .ForMember(a => a.Age, o => o.MapFrom(d => d.age)) + .ForMember(a => a.Birth, o => o.MapFrom(d => d.birth)) + .ForMember(a => a.Status, o => o.MapFrom(d => d.uStatus)) + .ForMember(a => a.UpdateTime, o => o.MapFrom(d => d.uUpdateTime)) + .ForMember(a => a.CreateTime, o => o.MapFrom(d => d.uCreateTime)) + .ForMember(a => a.ErrorCount, o => o.MapFrom(d => d.uErrorCount)) + .ForMember(a => a.LastErrorTime, o => o.MapFrom(d => d.uLastErrTime)) + .ForMember(a => a.LoginName, o => o.MapFrom(d => d.uLoginName)) + .ForMember(a => a.LoginPWD, o => o.MapFrom(d => d.uLoginPWD)) + .ForMember(a => a.Remark, o => o.MapFrom(d => d.uRemark)) + .ForMember(a => a.RealName, o => o.MapFrom(d => d.uRealName)) + .ForMember(a => a.Name, o => o.MapFrom(d => d.name)) + .ForMember(a => a.IsDeleted, o => o.MapFrom(d => d.tdIsDelete)) + .ForMember(a => a.RoleNames, o => o.MapFrom(d => d.RoleNames)); } } } diff --git a/Blog.Core.Extensions/Blog.Core.Extensions.csproj b/Blog.Core.Extensions/Blog.Core.Extensions.csproj index 90d5ed97..3ea16203 100644 --- a/Blog.Core.Extensions/Blog.Core.Extensions.csproj +++ b/Blog.Core.Extensions/Blog.Core.Extensions.csproj @@ -1,36 +1,43 @@  - - net5.0 - - - - - - - - - + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + - + + + diff --git a/Blog.Core.Extensions/EventHandling/BlogDeletedIntegrationEvent.cs b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEvent.cs similarity index 54% rename from Blog.Core.Extensions/EventHandling/BlogDeletedIntegrationEvent.cs rename to Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEvent.cs index 69c74911..f84842d6 100644 --- a/Blog.Core.Extensions/EventHandling/BlogDeletedIntegrationEvent.cs +++ b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEvent.cs @@ -1,10 +1,10 @@ namespace Blog.Core.EventBus.EventHandling { - public class BlogDeletedIntegrationEvent : IntegrationEvent + public class BlogQueryIntegrationEvent : IntegrationEvent { public string BlogId { get; private set; } - public BlogDeletedIntegrationEvent(string blogid) + public BlogQueryIntegrationEvent(string blogid) => BlogId = blogid; } } diff --git a/Blog.Core.Extensions/EventHandling/BlogDeletedIntegrationEventHandler.cs b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEventHandler.cs similarity index 61% rename from Blog.Core.Extensions/EventHandling/BlogDeletedIntegrationEventHandler.cs rename to Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEventHandler.cs index 6b475872..4e6384c2 100644 --- a/Blog.Core.Extensions/EventHandling/BlogDeletedIntegrationEventHandler.cs +++ b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEventHandler.cs @@ -1,4 +1,4 @@ -using Blog.Core.Common.Helper; +using Blog.Core.Common; using Blog.Core.EventBus.EventHandling; using Blog.Core.IServices; using Microsoft.Extensions.Logging; @@ -7,26 +7,26 @@ namespace Blog.Core.EventBus { - public class BlogDeletedIntegrationEventHandler : IIntegrationEventHandler + public class BlogQueryIntegrationEventHandler : IIntegrationEventHandler { private readonly IBlogArticleServices _blogArticleServices; - private readonly ILogger _logger; + private readonly ILogger _logger; - public BlogDeletedIntegrationEventHandler( + public BlogQueryIntegrationEventHandler( IBlogArticleServices blogArticleServices, - ILogger logger) + ILogger logger) { _blogArticleServices = blogArticleServices; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task Handle(BlogDeletedIntegrationEvent @event) + public async Task Handle(BlogQueryIntegrationEvent @event) { _logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, "Blog.Core", @event); ConsoleHelper.WriteSuccessLine($"----- Handling integration event: {@event.Id} at Blog.Core - ({@event})"); - await _blogArticleServices.DeleteById(@event.BlogId.ToString()); + await _blogArticleServices.QueryById(@event.BlogId.ToString()); } } 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/AllServicesMildd.cs b/Blog.Core.Extensions/Middlewares/AllServicesMiddleware.cs similarity index 87% rename from Blog.Core.Extensions/Middlewares/AllServicesMildd.cs rename to Blog.Core.Extensions/Middlewares/AllServicesMiddleware.cs index e92211e5..2346967c 100644 --- a/Blog.Core.Extensions/Middlewares/AllServicesMildd.cs +++ b/Blog.Core.Extensions/Middlewares/AllServicesMiddleware.cs @@ -1,19 +1,18 @@ -using Autofac.Extensions.DependencyInjection; +using System; +using System.Linq; +using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using System; -using System.IO; -using System.Linq; -namespace Blog.Core.Extensions +namespace Blog.Core.Extensions.Middlewares { /// /// 查看所有注入的服务 /// - public static class AllServicesMildd + public static class AllServicesMiddleware { - public static void UseAllServicesMildd(this IApplicationBuilder app, IServiceCollection _services) + public static void UseAllServicesMiddle(this IApplicationBuilder app, IServiceCollection _services) { if (app == null) throw new ArgumentNullException(nameof(app)); @@ -21,7 +20,7 @@ public static void UseAllServicesMildd(this IApplicationBuilder app, IServiceCol //tsDIAutofac.AddRange(Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "Blog.Core.Services.dll")).GetTypes().ToList()); //tsDIAutofac.AddRange(Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "Blog.Core.Repository.dll")).GetTypes().ToList()); - var autofacContaniers = (app.ApplicationServices.GetAutofacRoot())?.ComponentRegistry?.Registrations; + var autofacContainers = (app.ApplicationServices.GetAutofacRoot())?.ComponentRegistry?.Registrations; app.Map("/allservices", builder => builder.Run(async context => @@ -39,7 +38,7 @@ public static void UseAllServicesMildd(this IApplicationBuilder app, IServiceCol await context.Response.WriteAsync($"{svc.ImplementationType?.Name}"); await context.Response.WriteAsync(""); } - foreach (var item in autofacContaniers.ToList()) + foreach (var item in autofacContainers.ToList()) { var interfaceType = item.Services; foreach (var typeArray in interfaceType) diff --git a/Blog.Core.Extensions/Middlewares/ByPassAuthMidd.cs b/Blog.Core.Extensions/Middlewares/ByPassAuthMiddleware.cs similarity index 95% rename from Blog.Core.Extensions/Middlewares/ByPassAuthMidd.cs rename to Blog.Core.Extensions/Middlewares/ByPassAuthMiddleware.cs index d9a36089..c1f1f4a5 100644 --- a/Blog.Core.Extensions/Middlewares/ByPassAuthMidd.cs +++ b/Blog.Core.Extensions/Middlewares/ByPassAuthMiddleware.cs @@ -1,22 +1,22 @@ -using Microsoft.AspNetCore.Http; -using System; +using System; using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; -namespace Blog.Core.Middlewares +namespace Blog.Core.Extensions.Middlewares { /// /// 测试用户,用来通过鉴权 /// JWT:?userid=8&rolename=AdminTest /// - public class ByPassAuthMidd + public class ByPassAuthMiddleware { private readonly RequestDelegate _next; // 定义变量:当前用户Id,会常驻内存。 private string _currentUserId; // 同理定义:当前角色名 private string _currentRoleName; - public ByPassAuthMidd(RequestDelegate next) + public ByPassAuthMiddleware(RequestDelegate next) { _next = next; _currentUserId = null; diff --git a/Blog.Core.Extensions/Middlewares/ConsulMildd.cs b/Blog.Core.Extensions/Middlewares/ConsulMildd.cs deleted file mode 100644 index 428c778b..00000000 --- a/Blog.Core.Extensions/Middlewares/ConsulMildd.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using System; - -namespace Blog.Core.Extensions -{ - /// - /// Consul 注册服务 - /// - public static class ConsulMildd - { - public static IApplicationBuilder UseConsulMildd(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/ExceptionHandlerMidd.cs b/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs similarity index 53% rename from Blog.Core.Extensions/Middlewares/ExceptionHandlerMidd.cs rename to Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs index 056bc6c2..dc2cd17d 100644 --- a/Blog.Core.Extensions/Middlewares/ExceptionHandlerMidd.cs +++ b/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs @@ -1,19 +1,17 @@ -using Blog.Core.Model; -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using System; +using System; using System.Net; using System.Threading.Tasks; +using Blog.Core.Model; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; -namespace Blog.Core.Middlewares +namespace Blog.Core.Extensions.Middlewares { - public class ExceptionHandlerMidd + public class ExceptionHandlerMiddleware { private readonly RequestDelegate _next; - private static readonly log4net.ILog log = - log4net.LogManager.GetLogger(typeof(ExceptionHandlerMidd)); - public ExceptionHandlerMidd(RequestDelegate next) + public ExceptionHandlerMiddleware(RequestDelegate next) { _next = 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/IPLogMildd.cs b/Blog.Core.Extensions/Middlewares/IPLogMildd.cs deleted file mode 100644 index eda2e369..00000000 --- a/Blog.Core.Extensions/Middlewares/IPLogMildd.cs +++ /dev/null @@ -1,144 +0,0 @@ -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; - -namespace Blog.Core.Middlewares -{ - /// - /// 中间件 - /// 记录IP请求数据 - /// - public class IPLogMildd - { - /// - /// - /// - private readonly RequestDelegate _next; - private readonly IWebHostEnvironment _environment; - private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(IPLogMildd)); - - /// - /// - /// - /// - public IPLogMildd(RequestDelegate next, IWebHostEnvironment environment) - { - _next = next; - _environment = environment; - } - - public async Task InvokeAsync(HttpContext context) - { - if (Appsettings.app("Middleware", "IPLog", "Enabled").ObjToBool()) - { - // 过滤,只有接口 - if (context.Request.Path.Value.Contains("api")) - { - 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); - }); - - //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); - } - catch (Exception) - { - } - } - else - { - await _next(context); - } - } - else - { - await _next(context); - } - } - - private string GetWeek() - { - string week = string.Empty; - switch (DateTime.Now.DayOfWeek) - { - case DayOfWeek.Monday: - week = "周一"; - break; - case DayOfWeek.Tuesday: - week = "周二"; - break; - case DayOfWeek.Wednesday: - week = "周三"; - break; - case DayOfWeek.Thursday: - week = "周四"; - break; - case DayOfWeek.Friday: - week = "周五"; - break; - case DayOfWeek.Saturday: - week = "周六"; - break; - case DayOfWeek.Sunday: - week = "周日"; - break; - default: - week = "N/A"; - break; - } - return week; - } - - public static string GetClientIP(HttpContext context) - { - var ip = context.Request.Headers["X-Forwarded-For"].ObjToString(); - if (string.IsNullOrEmpty(ip)) - { - ip = context.Connection.RemoteIpAddress.ObjToString(); - } - return ip; - } - - } -} - diff --git a/Blog.Core.Extensions/Middlewares/IpLimitMildd.cs b/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs similarity index 58% rename from Blog.Core.Extensions/Middlewares/IpLimitMildd.cs rename to Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs index 591bfb16..7fe68fc4 100644 --- a/Blog.Core.Extensions/Middlewares/IpLimitMildd.cs +++ b/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs @@ -1,31 +1,30 @@ using AspNetCoreRateLimit; using Blog.Core.Common; -using log4net; using Microsoft.AspNetCore.Builder; using System; +using Serilog; -namespace Blog.Core.Extensions +namespace Blog.Core.Extensions.Middlewares { /// /// ip 限流 /// - public static class IpLimitMildd + public static class IpLimitMiddleware { - private static readonly ILog log = LogManager.GetLogger(typeof(IpLimitMildd)); - public static void UseIpLimitMildd(this IApplicationBuilder app) + public static void UseIpLimitMiddle(this IApplicationBuilder app) { if (app == null) throw new ArgumentNullException(nameof(app)); try { - if (Appsettings.app("Middleware", "IpRateLimit", "Enabled").ObjToBool()) + if (AppSettings.app("Middleware", "IpRateLimit", "Enabled").ObjToBool()) { app.UseIpRateLimiting(); } } catch (Exception e) { - log.Error($"Error occured limiting ip rate.\n{e.Message}"); + Log.Error($"Error occured limiting ip rate.\n{e.Message}"); throw; } } diff --git a/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs b/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs new file mode 100644 index 00000000..ee74d6fc --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/IpLogMiddleware.cs @@ -0,0 +1,137 @@ +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 +{ + /// + /// 中间件 + /// 记录IP请求数据 + /// + public class IpLogMiddleware + { + /// + /// + /// + private readonly RequestDelegate _next; + + private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; + + public IpLogMiddleware(RequestDelegate next, IWebHostEnvironment environment, ILogger logger) + { + _next = next; + _environment = environment; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + if (AppSettings.app("Middleware", "IPLog", "Enabled").ObjToBool()) + { + // 过滤,只有接口 + if (context.Request.Path.Value.Contains("api")) + { + context.Request.EnableBuffering(); + + + // 存储请求数据 + 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输出 + _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 + { + await _next(context); + } + } + else + { + await _next(context); + } + } + + private string GetWeek() + { + string week = string.Empty; + switch (DateTime.Now.DayOfWeek) + { + case DayOfWeek.Monday: + week = "周一"; + break; + case DayOfWeek.Tuesday: + week = "周二"; + break; + case DayOfWeek.Wednesday: + week = "周三"; + break; + case DayOfWeek.Thursday: + week = "周四"; + break; + case DayOfWeek.Friday: + week = "周五"; + break; + case DayOfWeek.Saturday: + week = "周六"; + break; + case DayOfWeek.Sunday: + week = "周日"; + break; + default: + week = "N/A"; + break; + } + + return week; + } + + public static string GetClientIP(HttpContext context) + { + var ip = context.Request.Headers["X-Forwarded-For"].ObjToString(); + if (string.IsNullOrEmpty(ip)) + { + ip = context.Connection.RemoteIpAddress.ObjToString(); + } + + return ip; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/JwtTokenAuth.cs b/Blog.Core.Extensions/Middlewares/JwtTokenAuthMiddleware.cs similarity index 92% rename from Blog.Core.Extensions/Middlewares/JwtTokenAuth.cs rename to Blog.Core.Extensions/Middlewares/JwtTokenAuthMiddleware.cs index 931ad8cb..76786f87 100644 --- a/Blog.Core.Extensions/Middlewares/JwtTokenAuth.cs +++ b/Blog.Core.Extensions/Middlewares/JwtTokenAuthMiddleware.cs @@ -1,16 +1,16 @@ -using Microsoft.AspNetCore.Http; -using System; +using System; using System.Threading.Tasks; using Blog.Core.AuthHelper.OverWrite; +using Microsoft.AspNetCore.Http; -namespace Blog.Core.AuthHelper +namespace Blog.Core.Extensions.Middlewares { /// /// 中间件 /// 原做为自定义授权中间件 /// 先做检查 header token的使用 /// - public class JwtTokenAuth + public class JwtTokenAuthMiddleware { /// /// @@ -20,7 +20,7 @@ public class JwtTokenAuth /// /// /// - public JwtTokenAuth(RequestDelegate next) + public JwtTokenAuthMiddleware(RequestDelegate next) { _next = next; } diff --git a/Blog.Core.Extensions/Middlewares/MiddlewareHelpers.cs b/Blog.Core.Extensions/Middlewares/MiddlewareHelpers.cs index 07069a4b..fa5c3835 100644 --- a/Blog.Core.Extensions/Middlewares/MiddlewareHelpers.cs +++ b/Blog.Core.Extensions/Middlewares/MiddlewareHelpers.cs @@ -1,7 +1,6 @@ -using Blog.Core.AuthHelper; -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; -namespace Blog.Core.Middlewares +namespace Blog.Core.Extensions.Middlewares { public static class MiddlewareHelpers { @@ -12,7 +11,7 @@ public static class MiddlewareHelpers /// public static IApplicationBuilder UseJwtTokenAuth(this IApplicationBuilder app) { - return app.UseMiddleware(); + return app.UseMiddleware(); } /// @@ -20,9 +19,9 @@ public static IApplicationBuilder UseJwtTokenAuth(this IApplicationBuilder app) /// /// /// - public static IApplicationBuilder UseReuestResponseLog(this IApplicationBuilder app) + public static IApplicationBuilder UseRequestResponseLogMiddle(this IApplicationBuilder app) { - return app.UseMiddleware(); + return app.UseMiddleware(); } /// @@ -30,9 +29,9 @@ public static IApplicationBuilder UseReuestResponseLog(this IApplicationBuilder /// /// /// - public static IApplicationBuilder UseSignalRSendMildd(this IApplicationBuilder app) + public static IApplicationBuilder UseSignalRSendMiddle(this IApplicationBuilder app) { - return app.UseMiddleware(); + return app.UseMiddleware(); } /// @@ -40,9 +39,9 @@ public static IApplicationBuilder UseSignalRSendMildd(this IApplicationBuilder a /// /// /// - public static IApplicationBuilder UseExceptionHandlerMidd(this IApplicationBuilder app) + public static IApplicationBuilder UseExceptionHandlerMiddle(this IApplicationBuilder app) { - return app.UseMiddleware(); + return app.UseMiddleware(); } /// @@ -50,9 +49,9 @@ public static IApplicationBuilder UseExceptionHandlerMidd(this IApplicationBuild /// /// /// - public static IApplicationBuilder UseIPLogMildd(this IApplicationBuilder app) + public static IApplicationBuilder UseIpLogMiddle(this IApplicationBuilder app) { - return app.UseMiddleware(); + return app.UseMiddleware(); } /// @@ -60,9 +59,9 @@ public static IApplicationBuilder UseIPLogMildd(this IApplicationBuilder app) /// /// /// - public static IApplicationBuilder UseRecordAccessLogsMildd(this IApplicationBuilder app) + public static IApplicationBuilder UseRecordAccessLogsMiddle(this IApplicationBuilder app) { - return app.UseMiddleware(); + return app.UseMiddleware(); } } } diff --git a/Blog.Core.Extensions/Middlewares/MiniProfilerMildd.cs b/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs similarity index 59% rename from Blog.Core.Extensions/Middlewares/MiniProfilerMildd.cs rename to Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs index 74030daf..49e3b70d 100644 --- a/Blog.Core.Extensions/Middlewares/MiniProfilerMildd.cs +++ b/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs @@ -1,23 +1,22 @@ using Blog.Core.Common; -using log4net; using Microsoft.AspNetCore.Builder; using System; +using Serilog; -namespace Blog.Core.Extensions +namespace Blog.Core.Extensions.Middlewares { /// /// MiniProfiler性能分析 /// - public static class MiniProfilerMildd + public static class MiniProfilerMiddleware { - private static readonly ILog log = LogManager.GetLogger(typeof(MiniProfilerMildd)); - public static void UseMiniProfilerMildd(this IApplicationBuilder app) + public static void UseMiniProfilerMiddleware(this IApplicationBuilder app) { if (app == null) throw new ArgumentNullException(nameof(app)); try { - if (Appsettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool()) + if (AppSettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool()) { // 性能分析 app.UseMiniProfiler(); @@ -26,7 +25,7 @@ public static void UseMiniProfilerMildd(this IApplicationBuilder app) } catch (Exception e) { - log.Error($"An error was reported when starting the MiniProfilerMildd.\n{e.Message}"); + Log.Error($"An error was reported when starting the MiniProfilerMildd.\n{e.Message}"); throw; } } diff --git a/Blog.Core.Extensions/Middlewares/QuartzJobMildd.cs b/Blog.Core.Extensions/Middlewares/QuartzJobMildd.cs deleted file mode 100644 index a64fdb2b..00000000 --- a/Blog.Core.Extensions/Middlewares/QuartzJobMildd.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Blog.Core.Common; -using Blog.Core.IServices; -using Blog.Core.Tasks; -using log4net; -using Microsoft.AspNetCore.Builder; -using System; - -namespace Blog.Core.Extensions -{ - /// - /// Quartz 启动服务 - /// - public static class QuartzJobMildd - { - private static readonly ILog log = LogManager.GetLogger(typeof(QuartzJobMildd)); - public static void UseQuartzJobMildd(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 ResuleModel = schedulerCenter.AddScheduleJobAsync(item).Result; - if (ResuleModel.success) - { - Console.WriteLine($"QuartzNetJob{item.Name}启动成功!"); - } - else - { - Console.WriteLine($"QuartzNetJob{item.Name}启动失败!错误信息:{ResuleModel.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/RecordAccessLogsMildd.cs b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs similarity index 55% rename from Blog.Core.Extensions/Middlewares/RecordAccessLogsMildd.cs rename to Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs index 4174a459..cee13581 100644 --- a/Blog.Core.Extensions/Middlewares/RecordAccessLogsMildd.cs +++ b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs @@ -1,4 +1,11 @@ -using Blog.Core.Common; +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +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; @@ -6,27 +13,22 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using System.Web; -namespace Blog.Core.Middlewares +namespace Blog.Core.Extensions.Middlewares { /// /// 中间件 /// 记录用户方访问数据 /// - public class RecordAccessLogsMildd + public class RecordAccessLogsMiddleware { /// /// /// private readonly RequestDelegate _next; + private readonly IUser _user; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IWebHostEnvironment _environment; private Stopwatch _stopwatch; @@ -34,21 +36,22 @@ public class RecordAccessLogsMildd /// /// /// - public RecordAccessLogsMildd(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()) + if (AppSettings.app("Middleware", "RecordAccessLogs", "Enabled").ObjToBool()) { - var api = context.Request.Path.ObjToString().TrimEnd('/').ToLower(); - var ignoreApis = Appsettings.app("Middleware", "RecordAccessLogs", "IgnoreApis"); + var api = context.Request.Path.ObjToString().TrimEnd('/').ToLower(); + var ignoreApis = AppSettings.app("Middleware", "RecordAccessLogs", "IgnoreApis"); // 过滤,只有接口 if (api.Contains("api") && !ignoreApis.Contains(api)) @@ -58,12 +61,12 @@ public async Task InvokeAsync(HttpContext context) HttpRequest request = context.Request; - userAccessModel.API = api; - userAccessModel.User = _user.Name; - userAccessModel.IP = IPLogMildd.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,18 +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); - //}); - - 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 { @@ -129,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 @@ -155,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 new file mode 100644 index 00000000..9917d83d --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/RequRespLogMiddleware.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +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; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// 中间件 + /// 记录请求和响应数据 + /// + public class RequRespLogMiddleware + { + /// + /// + /// + private readonly RequestDelegate _next; + + private readonly ILogger _logger; + + /// + /// + /// + /// + public RequRespLogMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + + public async Task InvokeAsync(HttpContext context) + { + if (AppSettings.app("Middleware", "RequestResponseLog", "Enabled").ObjToBool()) + { + // 过滤,只有接口 + if (context.Request.Path.Value.Contains("api")) + { + context.Request.EnableBuffering(); + + // 存储请求数据 + await RequestDataLog(context); + + await _next(context); + + // 存储响应数据 + ResponseDataLog(context.Response); + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + + private async Task RequestDataLog(HttpContext context) + { + var request = context.Request; + var sr = new StreamReader(request.Body); + RequestLogInfo requestResponse = new RequestLogInfo() + { + Path = request.Path, + QueryString = request.QueryString.ToString(), + 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)) + { + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {RequestData}", + context.TraceIdentifier, + content); + request.Body.Position = 0; + } + } + + private void ResponseDataLog(HttpResponse response) + { + var responseBody = response.GetResponseBody(); + + // 去除 Html + var reg = "<[^>]+>"; + + if (!string.IsNullOrEmpty(responseBody)) + { + var isHtml = Regex.IsMatch(responseBody, reg); + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {ResponseData}", + response.HttpContext.TraceIdentifier, + responseBody); + } + } + + private void ResponseDataLog(HttpResponse response, MemoryStream ms) + { + ms.Position = 0; + var responseBody = new StreamReader(ms).ReadToEnd(); + + // 去除 Html + var reg = "<[^>]+>"; + var isHtml = Regex.IsMatch(responseBody, reg); + + if (!string.IsNullOrEmpty(responseBody)) + { + _logger.LogInformation("RequestResponseLog:{TraceIdentifier}: {ResponseData}", + response.HttpContext.TraceIdentifier, + responseBody); + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/RequRespLogMildd.cs b/Blog.Core.Extensions/Middlewares/RequRespLogMildd.cs deleted file mode 100644 index fa5a5e31..00000000 --- a/Blog.Core.Extensions/Middlewares/RequRespLogMildd.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Blog.Core.Common; -using Blog.Core.Common.LogHelper; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace Blog.Core.Middlewares -{ - /// - /// 中间件 - /// 记录请求和响应数据 - /// - public class RequRespLogMildd - { - /// - /// - /// - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - /// - /// - /// - /// - public RequRespLogMildd(RequestDelegate next, ILogger logger) - { - _next = next; - _logger = logger; - } - - - - public async Task InvokeAsync(HttpContext context) - { - if (Appsettings.app("Middleware", "RequestResponseLog", "Enabled").ObjToBool()) - { - // 过滤,只有接口 - 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; - } - } - else - { - await _next(context); - } - } - else - { - await _next(context); - } - } - - private async Task RequestDataLog(HttpContext context) - { - var request = context.Request; - var sr = new StreamReader(request.Body); - - 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 }); - - //}); - SerilogServer.WriteLog("RequestResponseLog", new string[] { "Request Data:", content }); - - request.Body.Position = 0; - } - } - - private void ResponseDataLog(HttpResponse response, MemoryStream ms) - { - ms.Position = 0; - var ResponseBody = new StreamReader(ms).ReadToEnd(); - - // 去除 Html - 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 }); - - //}); - SerilogServer.WriteLog("RequestResponseLog", new string[] { "Response Data:", ResponseBody }); - } - } - } -} - diff --git a/Blog.Core.Extensions/Middlewares/SeedDataMildd.cs b/Blog.Core.Extensions/Middlewares/SeedDataMildd.cs deleted file mode 100644 index 5fb621e7..00000000 --- a/Blog.Core.Extensions/Middlewares/SeedDataMildd.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Blog.Core.Common; -using Blog.Core.Model.Seed; -using log4net; -using Microsoft.AspNetCore.Builder; -using System; - -namespace Blog.Core.Extensions -{ - /// - /// 生成种子数据中间件服务 - /// - public static class SeedDataMildd - { - private static readonly ILog log = LogManager.GetLogger(typeof(SeedDataMildd)); - public static void UseSeedDataMildd(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/SignalRSendMildd.cs b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs similarity index 66% rename from Blog.Core.Extensions/Middlewares/SignalRSendMildd.cs rename to Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs index 9a02404d..f1e4ee51 100644 --- a/Blog.Core.Extensions/Middlewares/SignalRSendMildd.cs +++ b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs @@ -1,22 +1,23 @@ -using Microsoft.AspNetCore.Http; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Blog.Core.Common; using Blog.Core.Common.LogHelper; using Blog.Core.Hubs; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SignalR; -using Blog.Core.Common; -namespace Blog.Core.Middlewares +namespace Blog.Core.Extensions.Middlewares { /// /// 中间件 /// SignalR发送数据 /// - public class SignalRSendMildd + public class SignalRSendMiddleware { /// /// /// private readonly RequestDelegate _next; + private readonly IHubContext _hubContext; /// @@ -24,23 +25,22 @@ public class SignalRSendMildd /// /// /// - public SignalRSendMildd(RequestDelegate next, IHubContext hubContext) + 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()) + 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 new file mode 100644 index 00000000..3cc284a1 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/SwaggerAuthMiddleware.cs @@ -0,0 +1,77 @@ +using System.Net; +using System.Threading.Tasks; +using Blog.Core.Common.Swagger; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace Blog.Core.Extensions.Middlewares +{ + public class SwaggerAuthMiddleware + { + + private readonly RequestDelegate next; + + public SwaggerAuthMiddleware(RequestDelegate next) + { + this.next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // 也可以根据是否是本地做判断 IsLocalRequest + if (context.Request.Path.Value.ToLower().Contains("index.html")) + { + // 判断权限是否正确 + if (IsAuthorized(context)) + { + await next.Invoke(context); + return; + } + + // 无权限,跳转swagger登录页 + context.RedirectSwaggerLogin(); + } + else + { + await next.Invoke(context); + } + } + + public bool IsAuthorized(HttpContext context) + { + // 使用session模式 + // 可以使用其他的 + return context.IsSuccessSwagger(); + } + + /// + /// 判断是不是本地访问 + /// 本地不用swagger拦截 + /// + /// + /// + public bool IsLocalRequest(HttpContext context) + { + if (context.Connection.RemoteIpAddress == null && context.Connection.LocalIpAddress == null) + { + return true; + } + if (context.Connection.RemoteIpAddress.Equals(context.Connection.LocalIpAddress)) + { + return true; + } + if (IPAddress.IsLoopback(context.Connection.RemoteIpAddress)) + { + return true; + } + return false; + } + } + public static class SwaggerAuthorizeExtensions + { + public static IApplicationBuilder UseSwaggerAuthorized(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/SwaggerMildd.cs b/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs similarity index 64% rename from Blog.Core.Extensions/Middlewares/SwaggerMildd.cs rename to Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs index a7409458..141ffe40 100644 --- a/Blog.Core.Extensions/Middlewares/SwaggerMildd.cs +++ b/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs @@ -1,20 +1,20 @@ using Blog.Core.Common; -using log4net; 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 +namespace Blog.Core.Extensions.Middlewares { /// /// Swagger中间件 /// - public static class SwaggerMildd + public static class SwaggerMiddleware { - private static readonly ILog log = LogManager.GetLogger(typeof(SwaggerMildd)); - public static void UseSwaggerMildd(this IApplicationBuilder app, Func streamHtml) + public static void UseSwaggerMiddle(this IApplicationBuilder app, Func streamHtml) { if (app == null) throw new ArgumentNullException(nameof(app)); @@ -22,32 +22,33 @@ public static void UseSwaggerMildd(this IApplicationBuilder app, Func st app.UseSwaggerUI(c => { //根据版本名称倒序 遍历展示 - 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}"); - }); + 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}"); }); - c.SwaggerEndpoint($"https://petstore.swagger.io/v2/swagger.json", $"{ApiName} pet"); + c.SwaggerEndpoint($"https://petstore.swagger.io/v2/swagger.json", $"{apiName} pet"); // 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:{项目名.index.html} if (streamHtml.Invoke() == null) { var msg = "index.html的属性,必须设置为嵌入的资源"; - log.Error(msg); + 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/NacosConfig/NacosListenNamingTask.cs b/Blog.Core.Extensions/NacosConfig/NacosListenNamingTask.cs index 60657bb5..3215703d 100644 --- a/Blog.Core.Extensions/NacosConfig/NacosListenNamingTask.cs +++ b/Blog.Core.Extensions/NacosConfig/NacosListenNamingTask.cs @@ -1,4 +1,5 @@ -using Blog.Core.Common.Helper; +using Blog.Core.Common; +using Blog.Core.Common.Helper; using Microsoft.Extensions.Hosting; using Nacos.V2; using System; 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 ab4704df..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 83788535..7cb098ff 100644 --- a/Blog.Core.Extensions/ServiceExtensions/AppConfigSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/AppConfigSetup.cs @@ -1,10 +1,9 @@ using Blog.Core.Common; -using Blog.Core.Common.Helper; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using System; using System.Text; +using Blog.Core.Common.DB; namespace Blog.Core.Extensions { @@ -13,11 +12,11 @@ namespace Blog.Core.Extensions /// public static class AppConfigSetup { - public static void AddAppConfigSetup(this IServiceCollection services, IWebHostEnvironment env) + 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 (AppSettings.app(new string[] { "Startup", "AppConfigAlert", "Enabled" }).ObjToBool()) { if (env.IsDevelopment()) { @@ -25,193 +24,86 @@ public static void AddAppConfigSetup(this IServiceCollection services, IWebHostE Console.OutputEncoding = Encoding.GetEncoding("GB2312"); } - Console.WriteLine("************ Blog.Core Config Set *****************"); + #region 程序配置 - 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 + List configInfos = new() { - ConsoleHelper.WriteSuccessLine($"SignalR send data: True"); - } + new string[] { "当前环境", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") }, + new string[] { "当前的授权方案", Permissions.IsUseIds4 ? "Ids4" : "JWT" }, + new string[] { "CORS跨域", AppSettings.app("Startup", "Cors", "EnableAllIPs") }, + new string[] { "RabbitMQ消息列队", AppSettings.app("RabbitMQ", "Enabled") }, + new string[] { "事件总线(必须开启消息列队)", AppSettings.app("EventBus", "Enabled") }, + new string[] { "redis消息队列", AppSettings.app("Startup", "RedisMq", "Enabled") }, + new string[] { "读写分离", BaseDBConfig.MainConfig.SlaveConnectionConfigs.AnyNoException()? "True" : "False" }, + }; - // IP限流 - if (!Appsettings.app("Middleware", "IpRateLimit", "Enabled").ObjToBool()) + new ConsoleTable() { - Console.WriteLine($"IpRateLimiting: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"IpRateLimiting: True"); - } + TitleString = "Blog.Core 配置集", + Columns = new string[] { "配置名称", "配置信息/是否启动" }, + Rows = configInfos, + EnableCount = false, + Alignment = Alignment.Left, + ColumnBlankNum = 4, + TableStyle = TableStyle.Alternative + }.Writer(ConsoleColor.Blue); + Console.WriteLine(); - // 性能分析 - if (!Appsettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool()) - { - Console.WriteLine($"MiniProfiler: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"MiniProfiler: True"); - } + #endregion 程序配置 - // CORS跨域 - if (!Appsettings.app("Startup", "Cors", "EnableAllIPs").ObjToBool()) - { - Console.WriteLine($"EnableAllIPs For CORS: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"EnableAllIPs For CORS: True"); - } + #region AOP - // redis消息队列 - if (!Appsettings.app("Startup", "RedisMq", "Enabled").ObjToBool()) + List aopInfos = new() { - Console.WriteLine($"Redis MQ: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Redis MQ: True"); - } + 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") }, + }; - // RabbitMQ 消息队列 - if (!Appsettings.app("RabbitMQ", "Enabled").ObjToBool()) - { - Console.WriteLine($"RabbitMQ: False"); - } - else + new ConsoleTable { - ConsoleHelper.WriteSuccessLine($"RabbitMQ: True"); - } + TitleString = "AOP", + Columns = new string[] { "配置名称", "配置信息/是否启动" }, + Rows = aopInfos, + EnableCount = false, + Alignment = Alignment.Left, + ColumnBlankNum = 7, + TableStyle = TableStyle.Alternative + }.Writer(ConsoleColor.Blue); + Console.WriteLine(); - // Consul 注册服务 - if (!Appsettings.app("Middleware", "Consul", "Enabled").ObjToBool()) - { - Console.WriteLine($"Consul service: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Consul service: True"); - } + #endregion AOP - // EventBus 事件总线 - if (!Appsettings.app("EventBus", "Enabled").ObjToBool()) - { - Console.WriteLine($"EventBus: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"EventBus: True"); - } + #region 中间件 - // 多库 - if (!Appsettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()) + List MiddlewareInfos = new() { - Console.WriteLine($"Is multi-DataBase: False"); - } - else - { - ConsoleHelper.WriteSuccessLine($"Is multi-DataBase: True"); - } + 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", "IpRateLimit", "Enabled") }, + new string[] { "性能分析中间件", AppSettings.app("Startup", "MiniProfiler", "Enabled") }, + new string[] { "Consul注册服务", AppSettings.app("Middleware", "Consul", "Enabled") }, + }; - // 读写分离 - if (!Appsettings.app(new string[] { "CQRSEnabled" }).ObjToBool()) - { - Console.WriteLine($"Is CQRS: False"); - } - else + new ConsoleTable { - ConsoleHelper.WriteSuccessLine($"Is CQRS: True"); - } - + TitleString = "中间件", + Columns = new string[] { "配置名称", "配置信息/是否启动" }, + Rows = MiddlewareInfos, + EnableCount = false, + Alignment = Alignment.Left, + ColumnBlankNum = 3, + TableStyle = TableStyle.Alternative + }.Writer(ConsoleColor.Blue); Console.WriteLine(); - } + #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_Ids4Setup.cs b/Blog.Core.Extensions/ServiceExtensions/Authentication_Ids4Setup.cs index c91691dd..aa8c8bd0 100644 --- a/Blog.Core.Extensions/ServiceExtensions/Authentication_Ids4Setup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/Authentication_Ids4Setup.cs @@ -26,9 +26,9 @@ public static void AddAuthentication_Ids4Setup(this IServiceCollection services) }) .AddJwtBearer(options => { - options.Authority = Appsettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" }); + options.Authority = AppSettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" }); options.RequireHttpsMetadata = false; - options.Audience = Appsettings.app(new string[] { "Startup", "IdentityServer4", "ApiName" }); + options.Audience = AppSettings.app(new string[] { "Startup", "IdentityServer4", "ApiName" }); }) .AddScheme(nameof(ApiResponseHandler), o => { }); } diff --git a/Blog.Core.Extensions/ServiceExtensions/Authentication_JWTSetup.cs b/Blog.Core.Extensions/ServiceExtensions/Authentication_JWTSetup.cs index 223197a2..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 { @@ -26,8 +23,8 @@ public static void AddAuthentication_JWTSetup(this IServiceCollection services) var symmetricKeyAsBase64 = AppSecretConfig.Audience_Secret_String; var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); - var Issuer = Appsettings.app(new string[] { "Audience", "Issuer" }); - var Audience = Appsettings.app(new string[] { "Audience", "Audience" }); + var Issuer = AppSettings.app(new string[] { "Audience", "Issuer" }); + var Audience = AppSettings.app(new string[] { "Audience", "Audience" }); var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); @@ -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/AuthorizationSetup.cs b/Blog.Core.Extensions/ServiceExtensions/AuthorizationSetup.cs index ae64a5ec..ae0a6383 100644 --- a/Blog.Core.Extensions/ServiceExtensions/AuthorizationSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/AuthorizationSetup.cs @@ -45,8 +45,8 @@ public static void AddAuthorizationSetup(this IServiceCollection services) var symmetricKeyAsBase64 = AppSecretConfig.Audience_Secret_String; var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); - var Issuer = Appsettings.app(new string[] { "Audience", "Issuer" }); - var Audience = Appsettings.app(new string[] { "Audience", "Audience" }); + var Issuer = AppSettings.app(new string[] { "Audience", "Issuer" }); + var Audience = AppSettings.app(new string[] { "Audience", "Audience" }); var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); diff --git a/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs b/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs index 5cd4ba6a..4236bfb7 100644 --- a/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs +++ b/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs @@ -3,19 +3,21 @@ using Blog.Core.AOP; using Blog.Core.Common; using Blog.Core.IRepository.Base; +using Blog.Core.IServices.BASE; using Blog.Core.Model; using Blog.Core.Repository.Base; -using log4net; +using Blog.Core.Repository.UnitOfWorks; +using Blog.Core.Services.BASE; using System; using System.Collections.Generic; using System.IO; using System.Reflection; +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; @@ -30,52 +32,60 @@ 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();//注册仓储 + 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); builder.RegisterAssemblyTypes(assemblysServices) - .AsImplementedInterfaces() - .InstancePerDependency() - .PropertiesAutowired() - .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; - .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。 + .AsImplementedInterfaces() + .InstancePerDependency() + .PropertiesAutowired() + .EnableInterfaceInterceptors() //引用Autofac.Extras.DynamicProxy; + .InterceptedBy(cacheType.ToArray()); //允许将拦截器服务的列表分配给注册。 // 获取 Repository.dll 程序集服务,并注册 var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository) - .AsImplementedInterfaces() - .PropertiesAutowired() - .InstancePerDependency(); + .AsImplementedInterfaces() + .PropertiesAutowired() + .InstancePerDependency(); + + builder.RegisterType().As() + .AsImplementedInterfaces() + .InstancePerLifetimeScope() + .PropertiesAutowired(); #endregion @@ -95,6 +105,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(cacheType.ToArray()); + #endregion #region 单独注册一个含有接口的类,启用interface代理拦截 @@ -104,8 +115,8 @@ protected override void Load(ContainerBuilder builder) // .AsImplementedInterfaces() // .EnableInterfaceInterceptors() // .InterceptedBy(typeof(BlogCacheAOP)); - #endregion + #endregion } } -} +} \ No newline at end of file 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/CorsSetup.cs b/Blog.Core.Extensions/ServiceExtensions/CorsSetup.cs index 3f1c14b3..d9961ff9 100644 --- a/Blog.Core.Extensions/ServiceExtensions/CorsSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/CorsSetup.cs @@ -15,15 +15,15 @@ public static void AddCorsSetup(this IServiceCollection services) services.AddCors(c => { - if (!Appsettings.app(new string[] { "Startup", "Cors", "EnableAllIPs" }).ObjToBool()) + if (!AppSettings.app(new string[] { "Startup", "Cors", "EnableAllIPs" }).ObjToBool()) { - c.AddPolicy(Appsettings.app(new string[] { "Startup", "Cors", "PolicyName" }), + c.AddPolicy(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" }), policy => { policy - .WithOrigins(Appsettings.app(new string[] { "Startup", "Cors", "IPs" }).Split(',')) + .WithOrigins(AppSettings.app(new string[] { "Startup", "Cors", "IPs" }).Split(',')) .AllowAnyHeader()//Ensures that the policy allows any header. .AllowAnyMethod(); }); @@ -31,7 +31,7 @@ public static void AddCorsSetup(this IServiceCollection services) else { //允许任意跨域请求 - c.AddPolicy(Appsettings.app(new string[] { "Startup", "Cors", "PolicyName" }), + c.AddPolicy(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" }), policy => { policy 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 ac6668d1..1f377bcc 100644 --- a/Blog.Core.Extensions/ServiceExtensions/DbSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/DbSetup.cs @@ -1,6 +1,5 @@ -using Blog.Core.Model.Seed; +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 197803c1..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 { @@ -18,14 +16,14 @@ public static void AddEventBusSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - if (Appsettings.app(new string[] { "EventBus", "Enabled" }).ObjToBool()) + if (AppSettings.app(new string[] { "EventBus", "Enabled" }).ObjToBool()) { - var subscriptionClientName = Appsettings.app(new string[] { "EventBus", "SubscriptionClientName" }); + var subscriptionClientName = AppSettings.app(new string[] { "EventBus", "SubscriptionClientName" }); services.AddSingleton(); - services.AddTransient(); + services.AddTransient(); - if (Appsettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool()) + if (AppSettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool()) { services.AddSingleton(sp => { @@ -35,31 +33,20 @@ public static void AddEventBusSetup(this IServiceCollection services) var eventBusSubcriptionsManager = sp.GetRequiredService(); var retryCount = 5; - if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" }))) + if (!string.IsNullOrEmpty(AppSettings.app(new string[] { "RabbitMQ", "RetryCount" }))) { - retryCount = int.Parse(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" })); + retryCount = int.Parse(AppSettings.app(new string[] { "RabbitMQ", "RetryCount" })); } 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 new file mode 100644 index 00000000..8c1d637b --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/HttpPollySetup.cs @@ -0,0 +1,54 @@ +using Blog.Core.Common.Https.HttpPolly; +using Blog.Core.Model; +using Microsoft.Extensions.DependencyInjection; +using Polly; +using Polly.Extensions.Http; +using Polly.Timeout; + +namespace Blog.Core.Extensions +{ + /// + /// Cors 启动服务 + /// + public static class HttpPollySetup + { + public static void AddHttpPollySetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + #region Polly策略 + var retryPolicy = HttpPolicyExtensions + .HandleTransientHttpError() + .Or() // 若超时则抛出此异常 + .WaitAndRetryAsync(new[] + { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + // 为每个重试定义超时策略 + var timeoutPolicy = Policy.TimeoutAsync(10); + #endregion + + services.AddHttpClient(HttpEnum.Common.ToString(), c => + { + c.DefaultRequestHeaders.Add("Accept", "application/json"); + }) + .AddPolicyHandler(retryPolicy) + // 将超时策略放在重试策略之内,每次重试会应用此超时策略 + .AddPolicyHandler(timeoutPolicy); + + services.AddHttpClient(HttpEnum.LocalHost.ToString(), c => + { + c.BaseAddress = new Uri("http://www.localhost.com"); + c.DefaultRequestHeaders.Add("Accept", "application/json"); + }) + .AddPolicyHandler(retryPolicy) + // 将超时策略放在重试策略之内,每次重试会应用此超时策略 + .AddPolicyHandler(timeoutPolicy); + + services.AddSingleton(); + } + } +} 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 7db5f0d2..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(); // 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/KafkaSetup.cs b/Blog.Core.Extensions/ServiceExtensions/KafkaSetup.cs index 79e60b94..0dbc3d07 100644 --- a/Blog.Core.Extensions/ServiceExtensions/KafkaSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/KafkaSetup.cs @@ -16,7 +16,7 @@ public static void AddKafkaSetup(this IServiceCollection services,IConfiguration { if (services == null) throw new ArgumentNullException(nameof(services)); - if (Appsettings.app(new string[] { "Kafka", "Enabled" }).ObjToBool()) + if (AppSettings.app(new string[] { "Kafka", "Enabled" }).ObjToBool()) { services.Configure(configuration.GetSection("kafka")); services.AddSingleton(); diff --git a/Blog.Core.Extensions/ServiceExtensions/MemoryCacheSetup.cs b/Blog.Core.Extensions/ServiceExtensions/MemoryCacheSetup.cs deleted file mode 100644 index 20ced266..00000000 --- a/Blog.Core.Extensions/ServiceExtensions/MemoryCacheSetup.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Blog.Core.Common; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -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 cache = new MemoryCache(new MemoryCacheOptions()); - return cache; - }); - } - } -} diff --git a/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs b/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs index 811cac6f..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/NacosSetup.cs b/Blog.Core.Extensions/ServiceExtensions/NacosSetup.cs index d314f6ea..1854f3ed 100644 --- a/Blog.Core.Extensions/ServiceExtensions/NacosSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/NacosSetup.cs @@ -1,4 +1,4 @@ -using Blog.Core.Common; +using Blog.Core.Common; using Blog.Core.Common.Helper; using Blog.Core.Extensions.NacosConfig; using Microsoft.Extensions.Configuration; @@ -19,7 +19,7 @@ public static void AddNacosSetup(this IServiceCollection services, IConfiguratio // 在实际生产工作中 本地开发是不需要注册nacos的 所以根据环境变量去判断 // 比如 开发环境 dev 测试环境 test 生产 prod 只有这几种环境变量的时候才需要去注册nacos - if (Appsettings.app(new string[] { "Startup", "Nacos", "Enabled" }).ObjToBool()) + if (AppSettings.app(new string[] { "Startup", "Nacos", "Enabled" }).ObjToBool()) { // 从当前配置取文件去注册naocs services.AddNacosV2Config(x => @@ -47,9 +47,7 @@ public static void AddNacosSetup(this IServiceCollection services, IConfiguratio services.AddHostedService();//增加配置文件监听事件 } - services.AddSingleton(new ConfigurationManager((ConfigurationRoot)Configuration)); services.AddSingleton(Configuration); - } } } diff --git a/Blog.Core.Extensions/ServiceExtensions/RabbitMQSetup.cs b/Blog.Core.Extensions/ServiceExtensions/RabbitMQSetup.cs index 125431f2..72b72f43 100644 --- a/Blog.Core.Extensions/ServiceExtensions/RabbitMQSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/RabbitMQSetup.cs @@ -16,7 +16,7 @@ public static void AddRabbitMQSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - if (Appsettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool()) + if (AppSettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool()) { services.AddSingleton(sp => { @@ -24,24 +24,29 @@ public static void AddRabbitMQSetup(this IServiceCollection services) var factory = new ConnectionFactory() { - HostName = Appsettings.app(new string[] { "RabbitMQ", "Connection" }), + HostName = AppSettings.app(new string[] { "RabbitMQ", "Connection" }), DispatchConsumersAsync = true }; - if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "UserName" }))) + if (!string.IsNullOrEmpty(AppSettings.app(new string[] { "RabbitMQ", "UserName" }))) { - factory.UserName = Appsettings.app(new string[] { "RabbitMQ", "UserName" }); + factory.UserName = AppSettings.app(new string[] { "RabbitMQ", "UserName" }); } - if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "Password" }))) + if (!string.IsNullOrEmpty(AppSettings.app(new string[] { "RabbitMQ", "Password" }))) { - factory.Password = Appsettings.app(new string[] { "RabbitMQ", "Password" }); + factory.Password = AppSettings.app(new string[] { "RabbitMQ", "Password" }); + } + + if (!string.IsNullOrEmpty(AppSettings.app(new string[] { "RabbitMQ", "Port" }))) + { + factory.Port = AppSettings.app(new string[] { "RabbitMQ", "Port" }).ObjToInt(); } var retryCount = 5; - if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" }))) + if (!string.IsNullOrEmpty(AppSettings.app(new string[] { "RabbitMQ", "RetryCount" }))) { - retryCount = int.Parse(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" })); + retryCount = AppSettings.app(new string[] { "RabbitMQ", "RetryCount" }).ObjToInt(); } return new RabbitMQPersistentConnection(factory, logger, retryCount); diff --git a/Blog.Core.Extensions/ServiceExtensions/RedisCacheSetup.cs b/Blog.Core.Extensions/ServiceExtensions/RedisCacheSetup.cs deleted file mode 100644 index 568742ef..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/RedisInitMqSetup.cs b/Blog.Core.Extensions/ServiceExtensions/RedisInitMqSetup.cs index 9b6f281e..b96b81b3 100644 --- a/Blog.Core.Extensions/ServiceExtensions/RedisInitMqSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/RedisInitMqSetup.cs @@ -16,7 +16,7 @@ public static void AddRedisInitMqSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - if (Appsettings.app(new string[] { "Startup", "RedisMq", "Enabled" }).ObjToBool()) + if (AppSettings.app(new string[] { "Startup", "RedisMq", "Enabled" }).ObjToBool()) { // services.AddInitQ(m => @@ -24,7 +24,7 @@ public static void AddRedisInitMqSetup(this IServiceCollection services) //时间间隔 m.SuspendTime = 2000; //redis服务器地址 - m.ConnectionString = Appsettings.app(new string[] { "Redis", "ConnectionString" }); + m.ConnectionString = AppSettings.app(new string[] { "Redis", "ConnectionString" }); //对应的订阅者类,需要new一个实例对象,当然你也可以传参,比如日志对象 m.ListSubscribe = new List() { typeof(RedisSubscribe), 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 799a80d2..71e1f7fd 100644 --- a/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs +++ b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs @@ -1,13 +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 { @@ -16,85 +17,118 @@ namespace Blog.Core.Extensions ///
public static class SqlsugarSetup { + private static readonly MemoryCache Cache = new MemoryCache(new MemoryCacheOptions()); + public static void AddSqlsugarSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); + StaticConfig.CustomSnowFlakeFunc = IdGeneratorUtility.NextId; + // 默认添加主数据库连接 - MainDb.CurrentDbConnId = Appsettings.app(new string[] { "MainDB" }); + if (!AppSettings.app("MainDB").IsNullOrEmpty()) + { + MainDb.CurrentDbConnId = AppSettings.app("MainDB"); + } - // 把多个连接对象注入服务,这里必须采用Scope,因为有事务操作 - services.AddScoped(o => + BaseDBConfig.MutiConnectionString.allDbs.ForEach(m => { - // 连接字符串 - var listConfig = new List(); - // 从库 - var listConfig_Slave = new List(); - BaseDBConfig.MutiConnectionString.slaveDbs.ForEach(s => + 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", "OutToLogFile", "Enabled" }).ObjToBool()) - { - Parallel.For(0, 1, e => - { - MiniProfiler.Current.CustomTiming("SQL:", GetParas(p) + "【SQL语句】:" + sql); - LogLock.OutSql2Log("SqlLog", new string[] { GetParas(p), "【SQL语句】:" + sql }); - - }); - } - if (Appsettings.app(new string[] { "AppSettings", "SqlAOP", "OutToConsole", "Enabled" }).ObjToBool()) - { - ConsoleHelper.WriteColorLine(string.Join("\r\n", new string[] { "--------", "【SQL语句】:" + 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) => { - 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 SqlSugarClient(listConfig); }); + services.AddTransient(s => s.GetService() as SqlSugarScope); } private static string GetWholeSql(SugarParameter[] paramArr, string sql) @@ -117,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 e563b335..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,17 +18,13 @@ 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)); var basePath = AppContext.BaseDirectory; //var basePath2 = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; - var ApiName = Appsettings.app(new string[] { "Startup", "ApiName" }); + var ApiName = AppSettings.app(new string[] { "Startup", "ApiName" }); services.AddSwaggerGen(c => { @@ -45,7 +42,7 @@ public static void AddSwaggerSetup(this IServiceCollection services) c.OrderActionsBy(o => o.RelativePath); }); - + c.UseInlineDefinitionsForEnums(); try { //这个就是刚刚配置的xml文件名 @@ -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) { @@ -81,13 +81,14 @@ 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 { + AuthorizationUrl = new Uri($"{AppSettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" })}/connect/authorize"), + Scopes = new Dictionary { - "blog.core.api","ApiResource id" + { + "blog.core.api", "ApiResource id" + } } } - } } }); } @@ -97,15 +98,13 @@ 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(); } } @@ -123,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/UiFilesZipSetup.cs b/Blog.Core.Extensions/ServiceExtensions/UiFilesZipSetup.cs new file mode 100644 index 00000000..24a74f7f --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/UiFilesZipSetup.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.IO; +using System.IO.Compression; + +namespace Blog.Core.Extensions +{ + /// + /// 将前端UI压缩文件进行解压 + /// + public static class UiFilesZipSetup + { + public static void AddUiFilesZipSetup(this IServiceCollection services, IWebHostEnvironment _env) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + string wwwrootFolderPath = Path.Combine(_env.ContentRootPath, "wwwroot"); + string zipUiItemFiles = Path.Combine(wwwrootFolderPath, "ui.zip"); + if (!File.Exists(Path.Combine(wwwrootFolderPath, "ui", "index.html"))) + { + ZipFile.ExtractToDirectory(zipUiItemFiles, wwwrootFolderPath); + } + } + } +} 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 b11deec8..76d6f7e6 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj @@ -1,8 +1,5 @@ - - net5.0 - diff --git a/Blog.Core.Gateway/Blog.Core.Gateway.csproj b/Blog.Core.Gateway/Blog.Core.Gateway.csproj index 84713355..d45e9ba5 100644 --- a/Blog.Core.Gateway/Blog.Core.Gateway.csproj +++ b/Blog.Core.Gateway/Blog.Core.Gateway.csproj @@ -1,8 +1,5 @@  - - net5.0 - ..\Blog.Core.Gateway\Blog.Core.Gateway.xml @@ -11,16 +8,25 @@ + + + + + + + + + - - - - - - - + + + + + + + diff --git a/Blog.Core.Gateway/Blog.Core.Gateway.xml b/Blog.Core.Gateway/Blog.Core.Gateway.xml index 33e74e70..34543342 100644 --- a/Blog.Core.Gateway/Blog.Core.Gateway.xml +++ b/Blog.Core.Gateway/Blog.Core.Gateway.xml @@ -4,12 +4,50 @@ Blog.Core.Gateway
+ + + 中间件 + 原做为自定义授权中间件 + 先做检查 header token的使用 + + + + + 验证方案提供对象 + + + + + 请求上下文 + + + + + 网关授权 + + + + + + + 返回相应 + + + + + + + + + 判断是否在白名单内,支持通配符 **** + + + + ┌──────────────────────────────────────────────────────────────┐ │ 描 述:模拟一个网关项目 - │ 测 试:http://localhost:9000/gateway/user/MyClaims - │ 测 试:http://localhost:9000/gateway/api/blog - │ 测 试:http://localhost:9000/gateway/is4api/GetAchieveUsers + │ 测 试:在网关swagger中查看具体的服务 │ 作 者:anson zhang └──────────────────────────────────────────────────────────────┘ diff --git a/Blog.Core.Gateway/Controllers/UserController.cs b/Blog.Core.Gateway/Controllers/UserController.cs index 8a2d7fa3..36532021 100644 --- a/Blog.Core.Gateway/Controllers/UserController.cs +++ b/Blog.Core.Gateway/Controllers/UserController.cs @@ -7,7 +7,7 @@ namespace Blog.Core.Gateway.Controllers { - [Authorize(Permissions.GWName)] + [Authorize(AuthenticationSchemes = Permissions.GWName)] [Route("/gateway/[controller]/[action]")] public class UserController : ControllerBase { diff --git a/Blog.Core.Gateway/Extensions/CustomAuthenticationHandler.cs b/Blog.Core.Gateway/Extensions/CustomAuthenticationHandler.cs new file mode 100644 index 00000000..938840cc --- /dev/null +++ b/Blog.Core.Gateway/Extensions/CustomAuthenticationHandler.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; + +namespace Blog.Core.Gateway.Extensions +{ + public class CustomAuthenticationHandler : AuthenticationHandler + { + public CustomAuthenticationHandler(IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) : base(options, logger, encoder, clock) + { + } + + protected override async Task HandleAuthenticateAsync() + { + // 可以查询数据库等操作 + // 获取当前用户不能放到token中的私密信息 + var userPhone = "15010000000"; + + var claims = new List() + { + new Claim("user-phone", userPhone), + new Claim("gw-sign", "gw") + }; + + var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name)); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + await Task.CompletedTask; + return AuthenticateResult.Success(ticket); + } + + protected virtual string GetTokenStringFromHeader() + { + var token = string.Empty; + string authorization = Request.Headers[HeaderNames.Authorization]; + + if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith($"Bearer ", StringComparison.OrdinalIgnoreCase)) + { + token = authorization["Bearer ".Length..].Trim(); + } + + return token; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs b/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs index 831d3e6d..b197c824 100644 --- a/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs +++ b/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs @@ -1,8 +1,9 @@ -using Microsoft.AspNetCore.Builder; +using Blog.Core.Extensions; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; using Ocelot.Middleware; -using Ocelot.Provider.Consul; +using Ocelot.Provider.Nacos; using Ocelot.Provider.Polly; using System; using System.Threading.Tasks; @@ -15,9 +16,12 @@ public static void AddCustomOcelotSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); - var basePath = AppContext.BaseDirectory; - - services.AddOcelot().AddConsul().AddPolly(); + services.AddAuthentication_JWTSetup(); + services.AddOcelot() + .AddDelegatingHandler() + //.AddNacosDiscovery() + //.AddConsul() + .AddPolly(); } public static async Task UseCustomOcelotMildd(this IApplicationBuilder app) diff --git a/Blog.Core.Gateway/Extensions/CustomResultHandler.cs b/Blog.Core.Gateway/Extensions/CustomResultHandler.cs new file mode 100644 index 00000000..3c4a314b --- /dev/null +++ b/Blog.Core.Gateway/Extensions/CustomResultHandler.cs @@ -0,0 +1,62 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.Gateway.Extensions +{ + public class CustomResultHandler : DelegatingHandler + { + JsonSerializerSettings _camelSettings = new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() }; + + protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = await base.SendAsync(request, cancellationToken); + var contentType = response.Content.Headers.ContentType?.MediaType ?? ""; + if (!contentType.Equals("application/json")) return response; + + dynamic result = null; + var resultStr = await response.Content.ReadAsStringAsync(); + try + { + Console.WriteLine(resultStr); + result = JsonConvert.DeserializeObject(resultStr); + } + catch (Exception) + { + return response; + } + + if (result != null && result.errorCode == 500) resultStr = result.message.ToString(); + + var exception = new Exception(resultStr); + + if (response.StatusCode == HttpStatusCode.InternalServerError || result.errorCode == (int)HttpStatusCode.InternalServerError) + { + var apiResult = new + { + Result = false, + Message = "服务器内部错误", + ErrorCode = (int)HttpStatusCode.InternalServerError, + Data = new + { + exception.Message, + exception.StackTrace + } + }; + response.Content = new StringContent(JsonConvert.SerializeObject(apiResult, _camelSettings), Encoding.UTF8, "application/json"); + } + else + { + + } + + + return response; + } + } +} diff --git a/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs b/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs index f392ef6a..033feeb5 100644 --- a/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs +++ b/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs @@ -1,10 +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 @@ -15,6 +21,8 @@ public static void AddCustomSwaggerSetup(this IServiceCollection services) var basePath = AppContext.BaseDirectory; + services.AddMvc(option => option.EnableEndpointRouting = false); + services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo @@ -41,14 +49,29 @@ 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.UseSwagger(); app.UseSwaggerUI(c => { - c.SwaggerEndpoint($"/swagger/v1/swagger.json", $"Blog.Core.Gateway-v1"); + c.SwaggerEndpoint($"/swagger/v1/swagger.json", "gateway"); + apis.ForEach(m => + { + c.SwaggerEndpoint($"/swagger/apiswg/{m}/swagger.json", m); + }); + + + 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 new file mode 100644 index 00000000..9441e00c --- /dev/null +++ b/Blog.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs @@ -0,0 +1,187 @@ +using System.Net; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Authentication; +using Blog.Core.Common; +using Blog.Core.Common.Caches; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Helper; + +namespace Blog.Core.AuthHelper +{ + /// + /// 中间件 + /// 原做为自定义授权中间件 + /// 先做检查 header token的使用 + /// + public class CustomJwtTokenAuthMiddleware + { + private readonly ICaching _cache; + + + /// + /// 验证方案提供对象 + /// + public IAuthenticationSchemeProvider Schemes { get; set; } + + /// + /// 请求上下文 + /// + private readonly RequestDelegate _next; + + + public CustomJwtTokenAuthMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes, AppSettings appset,ICaching cache) + { + _cache = cache; + _next = next; + Schemes = schemes; + } + + /// + /// 网关授权 + /// + /// + /// + public async Task Invoke(HttpContext httpContext) + { + var questUrl = httpContext?.Request.Path.Value.ToLower(); + if (string.IsNullOrEmpty(questUrl)) return; + //白名单验证 + if (CheckWhiteList(questUrl)) + { + await _next.Invoke(httpContext); + return; + } + //黑名单验证 + if(CheckBlackList(questUrl)) + { + return; + } + + List Permissions= new(); + + httpContext.Features.Set(new AuthenticationFeature + { + OriginalPath = httpContext.Request.Path, + OriginalPathBase = httpContext.Request.PathBase + }); + + //判断请求是否拥有凭据,即有没有登录 + var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); + if (defaultAuthenticate != null) + { + var Authresult = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); + if (Authresult?.Principal != null) + { + httpContext.User = Authresult.Principal; + // 获取当前用户的角色信息 + var currentUserRoles = (from item in httpContext.User.Claims + where item.Type == "CofRole" + select item.Value).ToList(); + var isMatchRole = false; + var permisssionRoles = Permissions.Where(w => currentUserRoles.Contains(w.Role)); + foreach (var item in permisssionRoles) + { + try + { + if (Regex.IsMatch(questUrl, item.Url, RegexOptions.IgnoreCase)) + { + isMatchRole = true; + break; + } + } + catch (Exception) + { + // ignored + } + } + + //验证权限 + if (currentUserRoles.Count <= 0 || !isMatchRole) + { + await httpContext.Cof_SendResponse(HttpStatusCode.ServiceUnavailable, "未授权此资源"); + return ; + } + } + else + { + await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "请重新登录"); + return ; + } + + } + else + { + await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "系统鉴权出错"); + return ; + } + await _next.Invoke(httpContext); + } + + /// + /// 返回相应 + /// + /// + /// + /// + /// + private async Task SendResponse(HttpContext context, string message, HttpStatusCode code) + { + context.Response.StatusCode = (int)code; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync(message); + } + + /// + /// 判断是否在白名单内,支持通配符 **** + /// + /// + /// + public bool CheckWhiteList(string url) + { + List WhiteList = _cache.Cof_GetICaching>("WhiteList", () => AppSettings.app("WhiteList"), 10); + + if (!WhiteList.Cof_CheckAvailable()) return false; + foreach (var Urlitem in WhiteList) + { + if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) return true; + + if (Urlitem.url.IndexOf("****") > 0) + { + string UrlitemP = Urlitem.url.Replace("****", ""); + if (Regex.IsMatch(url, UrlitemP, RegexOptions.IgnoreCase)) return true; + if (url.Length >= UrlitemP.Length && UrlitemP.ToLower() == url.Substring(0, UrlitemP.Length).ToLower()) return true; + + } + } + return false; + + } + + public bool CheckBlackList(string url) + { + List BlackList = _cache.Cof_GetICaching>("BlackList", () => AppSettings.app("BlackList"), 10); + + if (!BlackList.Cof_CheckAvailable()) return false; + foreach (var Urlitem in BlackList) + { + if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) return true; + + if (Urlitem.url.IndexOf("****") > 0) + { + string UrlitemP = Urlitem.url.Replace("****", ""); + if (Regex.IsMatch(url, UrlitemP, RegexOptions.IgnoreCase)) return true; + if (url.Length >= UrlitemP.Length && UrlitemP.ToLower() == url.Substring(0, UrlitemP.Length).ToLower()) return true; + + } + } + return false; + + } + } + + public class Urlobj + { + public string url { get; set; } + } +} + diff --git a/Blog.Core.Gateway/Helper/HeaderDelegatingHandler.cs b/Blog.Core.Gateway/Helper/HeaderDelegatingHandler.cs new file mode 100644 index 00000000..b9293007 --- /dev/null +++ b/Blog.Core.Gateway/Helper/HeaderDelegatingHandler.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ApiGateway.Helper +{ + public class HeaderDelegatingHandler : DelegatingHandler + { + private readonly IHttpContextAccessor _httpContextAccessor; + + public HeaderDelegatingHandler(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + IEnumerable headerValues; + if (request.Headers.TryGetValues("AccessToken", out headerValues)) + { + string accessToken = headerValues.First(); + + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + request.Headers.Remove("AccessToken"); + } + return await base.SendAsync(request, cancellationToken); + } + } +} 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 eddcdeb5..d8f81253 100644 --- a/Blog.Core.Gateway/Startup.cs +++ b/Blog.Core.Gateway/Startup.cs @@ -1,11 +1,11 @@ -using Blog.Core.Common; +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.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Authentication; +using System.Reflection; +using Blog.Core.Common.Caches.Interface; namespace Blog.Core.AdminMvc { @@ -14,9 +14,7 @@ public class Startup /** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述:模拟一个网关项目 - *│ 测 试:http://localhost:9000/gateway/user/MyClaims - *│ 测 试:http://localhost:9000/gateway/api/blog - *│ 测 试:http://localhost:9000/gateway/is4api/GetAchieveUsers + *│ 测 试:在网关swagger中查看具体的服务 *│ 作 者:anson zhang *└──────────────────────────────────────────────────────────────┘ */ @@ -31,14 +29,11 @@ public Startup(IConfiguration configuration, IWebHostEnvironment env) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(new Appsettings(Configuration)); + services.AddSingleton(new AppSettings(Configuration)); - services.AddAuthentication_JWTSetup(); + services.AddAuthentication() + .AddScheme(Permissions.GWName, _ => { }); - services.AddAuthorization(options => - { - options.AddPolicy("GW", policy => policy.RequireRole("AdminTest").Build()); - }); services.AddCustomSwaggerSetup(); @@ -48,6 +43,10 @@ public void ConfigureServices(IServiceCollection services) services.AddCorsSetup(); + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + services.AddSingleton(); + services.AddCustomOcelotSetup(); } @@ -64,15 +63,17 @@ 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" })); + app.UseCors(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" })); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + 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 59f40d82..00000000 --- a/Blog.Core.Gateway/appsettings.json +++ /dev/null @@ -1,31 +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" - } - -} diff --git a/Blog.Core.Gateway/index.html b/Blog.Core.Gateway/index.html new file mode 100644 index 00000000..9d9cbcfa --- /dev/null +++ b/Blog.Core.Gateway/index.html @@ -0,0 +1,124 @@ + + + + + + + + + + + + + %(DocumentTitle) + + + + + + %(HeadContent) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + diff --git a/Blog.Core.Gateway/ocelot.Development.json b/Blog.Core.Gateway/ocelot.Development.json index 88475e25..589a5a6e 100644 --- a/Blog.Core.Gateway/ocelot.Development.json +++ b/Blog.Core.Gateway/ocelot.Development.json @@ -1,52 +1,54 @@ { "Routes": [ + // blog-svc { - "UpstreamPathTemplate": "/gateway/api/{url}", - "UpstreamHttpMethod": [ - "Get", - "Post", - "Put", - "Delete" - ], + "UpstreamPathTemplate": "/svc/blog/{url}", + "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], "LoadBalancerOptions": { "Type": "RoundRobin" }, - "DownstreamPathTemplate": "/api/{url}", + "DownstreamPathTemplate": "/svc/blog/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 9291 } + ], + "AddHeadersToRequest": { + "user-phone": "Claims[user-phone] > value", + "gw-sign": "Claims[gw-sign] > value" + }, + "UpstreamHeaderTransform": { + "custom-key": "blog.gateway" + }, + "DownstreamHeaderTransform": { + "trace-id": "Trace-Id" + }, + "AuthenticationOptions": { + "AuthenticationProviderKey": "GW" + }, + "DelegatingHandlers": [ + "CustomResultHandler" ] }, + // blog-svc-swagger { - "UpstreamPathTemplate": "/gateway/is4api/{url}", - "UpstreamHttpMethod": [ - "Get", - "Post", - "Put", - "Delete" - ], - "LoadBalancerOptions": { - "Type": "RoundRobin" - }, - "DownstreamPathTemplate": "/is4api/{url}", + "UpstreamPathTemplate": "/swagger/apiswg/blog-svc/swagger.json", + "UpstreamHttpMethod": [ "GET" ], + "DownstreamPathTemplate": "/swagger/V2/swagger.json", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", - "Port": 5004 + "Port": 9291 } - ] + ], + "LoadBalancer": "RoundRobin" } + ], "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.Gateway/ocelot.json b/Blog.Core.Gateway/ocelot.json index c9612a60..29091fa5 100644 --- a/Blog.Core.Gateway/ocelot.json +++ b/Blog.Core.Gateway/ocelot.json @@ -1,52 +1,3 @@ { - "Routes": [ - { - "UpstreamPathTemplate": "/gateway/api/{url}", - "UpstreamHttpMethod": [ - "Get", - "Post", - "Put", - "Delete" - ], - "LoadBalancerOptions": { - "Type": "RoundRobin" - }, - "DownstreamPathTemplate": "/api/{url}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 9291 - } - ] - }, - { - "UpstreamPathTemplate": "/gateway/is4api/{url}", - "UpstreamHttpMethod": [ - "Get", - "Post", - "Put", - "Delete" - ], - "LoadBalancerOptions": { - "Type": "RoundRobin" - }, - "DownstreamPathTemplate": "/is4api/{url}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 5004 - } - ] - } - ], - "GlobalConfiguration": { - "BaseUrl": "http://localhost:9000", - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Type": "Consul" - } - } + } \ No newline at end of file diff --git a/Blog.Core.IServices/BASE/IBaseServices.cs b/Blog.Core.IServices/BASE/IBaseServices.cs index 1797786a..a496b59f 100644 --- a/Blog.Core.IServices/BASE/IBaseServices.cs +++ b/Blog.Core.IServices/BASE/IBaseServices.cs @@ -10,53 +10,64 @@ 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(TEntity entity, string strWhere); + Task Update(List model); + Task Update(TEntity entity, string where); Task Update(object operateAnonymousObjects); - Task Update(TEntity entity, List lstColumns = null, List lstIgnoreColumns = null, string strWhere = ""); + Task Update(TEntity entity, List lstColumns = null, List lstIgnoreColumns = null, string where = ""); Task> Query(); - Task> Query(string strWhere); + Task> Query(string where); Task> Query(Expression> whereExpression); - Task> Query(Expression> whereExpression, string strOrderByFileds); + Task> Query(Expression> whereExpression, string orderByFields); Task> Query(Expression> expression); - Task> Query(Expression> expression, Expression> whereExpression,string strOrderByFileds); + Task> Query(Expression> expression, Expression> whereExpression, string orderByFields); Task> Query(Expression> whereExpression, Expression> orderByExpression, bool isAsc = true); - Task> Query(string strWhere, string strOrderByFileds); - Task> QuerySql(string strSql, SugarParameter[] parameters = null); - Task QueryTable(string strSql, SugarParameter[] parameters = null); + Task> Query(string where, string orderByFields); + Task> QuerySql(string sql, SugarParameter[] parameters = null); + Task QueryTable(string sql, SugarParameter[] parameters = null); - Task> Query(Expression> whereExpression, int intTop, string strOrderByFileds); - Task> Query(string strWhere, int intTop, string strOrderByFileds); + Task> Query(Expression> whereExpression, int top, string orderByFields); + Task> Query(string where, int top, string orderByFields); Task> Query( - Expression> whereExpression, int intPageIndex, int intPageSize, string strOrderByFileds); - Task> Query(string strWhere, int intPageIndex, int intPageSize, string strOrderByFileds); + Expression> whereExpression, int pageIndex, int pageSize, string orderByFields); + Task> Query(string where, int pageIndex, int pageSize, string orderByFields); - Task> QueryPage(Expression> whereExpression, int intPageIndex = 1, int intPageSize = 20, string strOrderByFileds = null); + Task> QueryPage(Expression> whereExpression, int pageIndex = 1, int pageSize = 20, string orderByFields = null); Task> QueryMuch( Expression> joinExpression, 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 937d8104..b5cc2f2c 100644 --- a/Blog.Core.IServices/Blog.Core.IServices.csproj +++ b/Blog.Core.IServices/Blog.Core.IServices.csproj @@ -1,10 +1,8 @@  - - net5.0 - + diff --git a/Blog.Core.IServices/IAccessTrendLogServices.cs b/Blog.Core.IServices/IAccessTrendLogServices.cs new file mode 100644 index 00000000..e902f399 --- /dev/null +++ b/Blog.Core.IServices/IAccessTrendLogServices.cs @@ -0,0 +1,14 @@ +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices +{ + /// + /// IAccessTrendLogServices + /// + public interface IAccessTrendLogServices : IBaseServices + { + + } +} + \ No newline at end of file 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/IDepartmentServices.cs b/Blog.Core.IServices/IDepartmentServices.cs new file mode 100644 index 00000000..fdd650d7 --- /dev/null +++ b/Blog.Core.IServices/IDepartmentServices.cs @@ -0,0 +1,12 @@ +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices +{ + /// + /// IDepartmentServices + /// + public interface IDepartmentServices : IBaseServices + { + } +} \ No newline at end of file diff --git a/Blog.Core.IServices/IGuestbookServices.cs b/Blog.Core.IServices/IGuestbookServices.cs index 2b9eeca5..cd8b2287 100644 --- a/Blog.Core.IServices/IGuestbookServices.cs +++ b/Blog.Core.IServices/IGuestbookServices.cs @@ -9,5 +9,13 @@ public partial interface IGuestbookServices : IBaseServices { Task> TestTranInRepository(); Task TestTranInRepositoryAOP(); + + Task TestTranPropagation(); + + Task TestTranPropagationNoTran(); + + Task TestTranPropagationTran(); + Task TestTranPropagationTran2(); + Task TestTranPropagationTran3(); } -} +} \ No newline at end of file diff --git a/Blog.Core.IServices/IModulePermissionServices.cs b/Blog.Core.IServices/IModulePermissionServices.cs deleted file mode 100644 index a0adcb07..00000000 --- a/Blog.Core.IServices/IModulePermissionServices.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Blog.Core.IServices.BASE; -using Blog.Core.Model.Models; - -namespace Blog.Core.IServices -{ - public partial interface IModulePermissionServices : IBaseServices - { - } -} \ No newline at end of file diff --git a/Blog.Core.IServices/IPasswordLibServices.cs b/Blog.Core.IServices/IPasswordLibServices.cs index 2678a6a7..67532fd6 100644 --- a/Blog.Core.IServices/IPasswordLibServices.cs +++ b/Blog.Core.IServices/IPasswordLibServices.cs @@ -1,9 +1,14 @@ -using Blog.Core.IServices.BASE; +using System.Threading.Tasks; +using Blog.Core.IServices.BASE; using Blog.Core.Model.Models; namespace Blog.Core.IServices { 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/IWeChatCompanyServices.cs b/Blog.Core.IServices/IWeChatCompanyServices.cs new file mode 100644 index 00000000..b47ac453 --- /dev/null +++ b/Blog.Core.IServices/IWeChatCompanyServices.cs @@ -0,0 +1,16 @@ +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using System.Threading.Tasks; + +namespace Blog.Core.IServices +{ + /// + /// IWeChatCompanyServices + /// + public interface IWeChatCompanyServices : IBaseServices + { + + } +} \ No newline at end of file diff --git a/Blog.Core.IServices/IWeChatConfigServices.cs b/Blog.Core.IServices/IWeChatConfigServices.cs new file mode 100644 index 00000000..68f9f61c --- /dev/null +++ b/Blog.Core.IServices/IWeChatConfigServices.cs @@ -0,0 +1,102 @@ +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using System.Threading.Tasks; + +namespace Blog.Core.IServices +{ + /// + /// IWeChatConfigServices + /// + public interface IWeChatConfigServices :IBaseServices + { + /// + /// 获取可用的微信token + /// + /// + /// + Task> GetToken(string id); + /// + /// 刷新微信token + /// + /// + /// + Task> RefreshToken(string id); + /// + /// 获取模板信息 + /// + /// + /// + Task> GetTemplate(string id); + /// + /// 获取菜单 + /// + /// + /// + Task> GetMenu(string id); + /// + /// 获取订阅用户 + /// + /// + /// + /// + Task> GetSubUser(string id,string openid); + /// + /// 获取订阅用户列表 + /// + /// + Task> GetSubUsers(string id); + /// + /// 处理微信事件 + /// + /// + /// + Task HandleWeChat(WeChatXMLDto weChat); + /// + /// 微信验证入库 + /// + /// + /// + /// + + Task Valid(WeChatValidDto validDto,string body); + /// + /// 获取绑定二维码 + /// + /// + /// + Task> GetQRBind(WeChatUserInfo info); + /// + /// 推送卡片消息(绑定用户) + /// + /// + /// + /// + Task> PushCardMsg(WeChatCardMsgDataDto msg,string ip); + /// + /// 推送文本消息(绑定或订阅) + /// + /// + /// + Task> PushTxtMsg(WeChatPushTestDto msg); + /// + /// 更新菜单 + /// + /// + /// + Task> UpdateMenu(WeChatApiDto menu); + /// + /// 通过绑定用户获取微信用户信息 + /// + /// + /// + Task> GetBindUserInfo(WeChatUserInfo info); + /// + /// 解除绑定用户 + /// + /// + /// + Task> UnBind(WeChatUserInfo info); + } +} \ No newline at end of file diff --git a/Blog.Core.IServices/IWeChatPushLogServices.cs b/Blog.Core.IServices/IWeChatPushLogServices.cs new file mode 100644 index 00000000..00632097 --- /dev/null +++ b/Blog.Core.IServices/IWeChatPushLogServices.cs @@ -0,0 +1,16 @@ +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using System.Threading.Tasks; + +namespace Blog.Core.IServices +{ + /// + /// IWeChatPushLogServices + /// + public interface IWeChatPushLogServices : IBaseServices + { + + } +} \ No newline at end of file diff --git a/Blog.Core.IServices/IWeChatSubServices.cs b/Blog.Core.IServices/IWeChatSubServices.cs new file mode 100644 index 00000000..3ad954d2 --- /dev/null +++ b/Blog.Core.IServices/IWeChatSubServices.cs @@ -0,0 +1,16 @@ +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using System.Threading.Tasks; + +namespace Blog.Core.IServices +{ + /// + /// IWeChatSubServices + /// + public interface IWeChatSubServices : IBaseServices + { + + } +} \ No newline at end of file diff --git a/Blog.Core.IServices/IsysUserInfoServices.cs b/Blog.Core.IServices/IsysUserInfoServices.cs index f46cba0b..a2db873f 100644 --- a/Blog.Core.IServices/IsysUserInfoServices.cs +++ b/Blog.Core.IServices/IsysUserInfoServices.cs @@ -9,9 +9,9 @@ namespace Blog.Core.IServices /// /// sysUserInfoServices /// - public interface ISysUserInfoServices :IBaseServices + public interface ISysUserInfoServices :IBaseServices { - Task SaveUserInfo(string loginName, string loginPwd); + Task SaveUserInfo(string loginName, string loginPwd); Task GetUserRoleNameStr(string loginName, string loginPwd); } } 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/ApiResponse.cs b/Blog.Core.Model/ApiResponse.cs index 0927aca2..74944aea 100644 --- a/Blog.Core.Model/ApiResponse.cs +++ b/Blog.Core.Model/ApiResponse.cs @@ -1,5 +1,4 @@ - -namespace Blog.Core.Model +namespace Blog.Core.Model { public class ApiResponse { @@ -12,28 +11,28 @@ public ApiResponse(StatusCode apiCode, string msg = null) switch (apiCode) { case StatusCode.CODE401: - { - Status = 401; - Value = "很抱歉,您无权访问该接口,请确保已经登录!"; - } + { + Status = 401; + Value = msg ?? "很抱歉,您无权访问该接口,请确保已经登录!"; + } break; case StatusCode.CODE403: - { - Status = 403; - Value = "很抱歉,您的访问权限等级不够,联系管理员!"; - } + { + Status = 403; + Value = msg ?? "很抱歉,您的访问权限等级不够,联系管理员!"; + } break; case StatusCode.CODE404: - { - Status = 404; - Value = "资源不存在!"; - } + { + Status = 404; + Value = "资源不存在!"; + } break; case StatusCode.CODE500: - { - Status = 500; - Value = msg; - } + { + Status = 500; + Value = msg; + } break; } @@ -41,7 +40,7 @@ public ApiResponse(StatusCode apiCode, string msg = null) { status = Status, msg = Value, - success = apiCode != StatusCode.CODE200 + success = apiCode == StatusCode.CODE200 }; } } @@ -54,5 +53,4 @@ public enum StatusCode CODE404, CODE500 } - -} +} \ No newline at end of file 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 2111ab86..fb04c45c 100644 --- a/Blog.Core.Model/Blog.Core.Model.csproj +++ b/Blog.Core.Model/Blog.Core.Model.csproj @@ -1,8 +1,5 @@  - - net5.0 - ..\Blog.Core.Api\Blog.Core.Model.xml @@ -15,11 +12,14 @@ - + + + + - + diff --git a/Blog.Core.Model/CustomEnums/AuthorityScopeEnum.cs b/Blog.Core.Model/CustomEnums/AuthorityScopeEnum.cs new file mode 100644 index 00000000..422b2c56 --- /dev/null +++ b/Blog.Core.Model/CustomEnums/AuthorityScopeEnum.cs @@ -0,0 +1,30 @@ +namespace Blog.Core.Model +{ + public enum AuthorityScopeEnum + { + /// + /// 无任何权限 + /// + NONE = -1, + /// + /// 自定义权限 + /// + Custom = 1, + /// + /// 本部门 + /// + MyDepart = 2, + /// + /// 本部门及以下 + /// + MyDepartAndDown = 3, + /// + /// 仅自己 + /// + OnlySelf = 4, + /// + /// 所有 + /// + ALL = 9 + } +} diff --git a/Blog.Core.Model/HttpEnum.cs b/Blog.Core.Model/HttpEnum.cs new file mode 100644 index 00000000..05e25582 --- /dev/null +++ b/Blog.Core.Model/HttpEnum.cs @@ -0,0 +1,10 @@ +using System; + +namespace Blog.Core.Model +{ + public enum HttpEnum + { + Common, + LocalHost + } +} diff --git a/Blog.Core.Model/IDS4DbModels/ApplicationRole.cs b/Blog.Core.Model/IDS4DbModels/ApplicationRole.cs index fd2c737f..8baf76ea 100644 --- a/Blog.Core.Model/IDS4DbModels/ApplicationRole.cs +++ b/Blog.Core.Model/IDS4DbModels/ApplicationRole.cs @@ -7,7 +7,8 @@ namespace Blog.Core.Model.IDS4DbModels /// 以下model 来自ids4项目,多库模式,为了调取ids4数据 /// 角色表 /// - [SugarTable("ApplicationRole", "WMBLOG_MYSQL_2")] + [SugarTable("ApplicationRole", "角色表")] //('数据库表名','数据库表备注') + [TenantAttribute("WMBLOG_MYSQL_2")] //('代表是哪个数据库,名字是appsettings.json 的 ConnId') public class ApplicationRole { public bool IsDeleted { get; set; } diff --git a/Blog.Core.Model/IDS4DbModels/ApplicationUser.cs b/Blog.Core.Model/IDS4DbModels/ApplicationUser.cs index f3d02ae5..50529166 100644 --- a/Blog.Core.Model/IDS4DbModels/ApplicationUser.cs +++ b/Blog.Core.Model/IDS4DbModels/ApplicationUser.cs @@ -7,7 +7,8 @@ namespace Blog.Core.Model.IDS4DbModels /// 以下model 来自ids4项目,多库模式,为了调取ids4数据 /// 用户表 /// - [SugarTable("AspNetUsers", "WMBLOG_MYSQL_2")] + [SugarTable("AspNetUsers", "用户表")]//('数据库表名','数据库表备注') + [TenantAttribute("WMBLOG_MYSQL_2")] //('代表是哪个数据库,名字是appsettings.json 的 ConnId') public class ApplicationUser { public string LoginName { get; set; } 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 new file mode 100644 index 00000000..bc4848cf --- /dev/null +++ b/Blog.Core.Model/Models/AccessTrendLog.cs @@ -0,0 +1,27 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model.Models +{ + /// + /// 用户访问趋势日志 + /// + public class AccessTrendLog : RootEntityTkey + { + /// + /// 用户 + /// + [SugarColumn(Length = 128, IsNullable = true)] + public string UserInfo { get; set; } + + /// + /// 次数 + /// + public int Count { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } = DateTime.Now; + } +} diff --git a/Blog.Core.Model/Models/Advertisement.cs b/Blog.Core.Model/Models/Advertisement.cs index 568a0c13..3b11b21f 100644 --- a/Blog.Core.Model/Models/Advertisement.cs +++ b/Blog.Core.Model/Models/Advertisement.cs @@ -3,31 +3,31 @@ namespace Blog.Core.Model.Models { - public class Advertisement : RootEntityTkey + public class Advertisement : RootEntityTkey { /// /// 广告图片 /// - [SugarColumn(Length = 512, IsNullable = true, ColumnDataType = "nvarchar")] + [SugarColumn(Length = 512, IsNullable = true)] public string ImgUrl { get; set; } /// /// 广告标题 /// - [SugarColumn(Length = 64, IsNullable = true, ColumnDataType = "nvarchar")] + [SugarColumn(Length = 64, IsNullable = true)] public string Title { get; set; } /// /// 广告链接 /// - [SugarColumn(Length = 256, IsNullable = true, ColumnDataType = "nvarchar")] + [SugarColumn(Length = 256, IsNullable = true)] public string Url { get; set; } /// /// 备注 /// - [SugarColumn(Length = 2000, IsNullable = true, ColumnDataType = "nvarchar")] + [SugarColumn(Length = 2000, IsNullable = true)] public string Remark { get; set; } /// diff --git a/Blog.Core.Model/Models/BlogArticle.cs b/Blog.Core.Model/Models/BlogArticle.cs index 19235dae..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,30 +13,34 @@ 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(ColumnDataType = "nvarchar", Length = 600, IsNullable = true)] + [SugarColumn(Length = 600, IsNullable = true)] public string bsubmitter { get; set; } + [Navigate(NavigateType.OneToOne, nameof(bsubmitter))] + public SysUserInfo User { get; set; } + /// /// 标题blog /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 256, IsNullable = true)] + [SugarColumn(Length = 256, IsNullable = true)] public string btitle { get; set; } /// /// 类别 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string bcategory { get; set; } /// /// 内容 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string bcontent { get; set; } /// @@ -57,10 +62,11 @@ public class BlogArticle /// 创建时间 /// public System.DateTime bCreateTime { get; set; } + /// /// 备注 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string bRemark { 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 new file mode 100644 index 00000000..424bcf44 --- /dev/null +++ b/Blog.Core.Model/Models/Department.cs @@ -0,0 +1,83 @@ +using SqlSugar; +using System; + + +namespace Blog.Core.Model.Models +{ + /// + /// 部门表 + /// + public class Department : DepartmentRoot + { + /// + /// Desc:部门关系编码 + /// Default: + /// Nullable:True + /// + public string CodeRelationship { get; set; } + /// + /// Desc:部门名称 + /// Default: + /// Nullable:True + /// + public string Name { get; set; } + /// + /// Desc:负责人 + /// Default: + /// Nullable:True + /// + [SugarColumn(IsNullable = true)] + public string Leader { get; set; } + /// + /// Desc:排序 + /// Default: + /// Nullable:True + /// + public int OrderSort { get; set; } = 0; + /// + /// Desc:部门状态(0正常 1停用) + /// Default:0 + /// Nullable:True + /// + public bool Status { get; set; } = false; + /// + /// Desc:删除标志(0代表存在 2代表删除) + /// Default:0 + /// Nullable:True + /// + public bool IsDeleted { get; set; } = false; + /// + /// Desc:创建者 + /// Default: + /// Nullable:True + /// + [SugarColumn(IsNullable = true)] + public string CreateBy { get; set; } + /// + /// Desc:创建时间 + /// Default: + /// Nullable:True + /// + [SugarColumn(IsNullable = true)] + public DateTime? CreateTime { get; set; } + /// + /// Desc:更新者 + /// Default: + /// Nullable:True + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// Desc:更新时间 + /// Default: + /// Nullable:True + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } + + + + [SugarColumn(IsIgnore = true)] + public bool hasChildren { get; set; } = true; + } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/GblLogAudit.cs b/Blog.Core.Model/Models/GblLogAudit.cs new file mode 100644 index 00000000..d4a85411 --- /dev/null +++ b/Blog.Core.Model/Models/GblLogAudit.cs @@ -0,0 +1,70 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model.Models +{ + /// + /// 用户团队表 + /// + [SugarTable("GblLogAudit", TableDescription = "日志审计")] + public class GblLogAudit + { + /// + ///ID + /// + [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, Length = 255)] + public string TraceId { get; set; } + + /// + ///时间 + /// + [SugarColumn(ColumnDescription = "时间", IsNullable = false, IsPrimaryKey = false, IsIdentity = false)] + public DateTime Date { get; set; } = DateTime.Now; + + /// + ///线程 + /// + [SugarColumn(ColumnDescription = "线程", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] + public string Thread { get; set; } + + /// + ///等级 + /// + [SugarColumn(ColumnDescription = "等级", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] + public string Level { get; set; } + /// + ///记录器 + /// + [SugarColumn(ColumnDescription = "记录器", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] + public string Logger { get; set; } + /// + ///日志类型 + /// + [SugarColumn(ColumnDescription = "日志类型", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] + public string LogType { get; set; } + /// + ///数据类型 + /// + [SugarColumn(ColumnDescription = "数据类型", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 255)] + public string DataType { get; set; } + + /// + ///错误信息 + /// + [SugarColumn(ColumnDescription = "错误信息", IsNullable = false, IsPrimaryKey = false, IsIdentity = false, Length = 2000)] + public string Message { get; set; } + + /// + ///异常 + /// + [SugarColumn(ColumnDescription = "异常", IsNullable = true, IsPrimaryKey = false, IsIdentity = false, Length = 2000)] + public string Exception { get; set; } + + } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/Guestbook.cs b/Blog.Core.Model/Models/Guestbook.cs index ee315721..0cd5dcef 100644 --- a/Blog.Core.Model/Models/Guestbook.cs +++ b/Blog.Core.Model/Models/Guestbook.cs @@ -3,41 +3,41 @@ namespace Blog.Core.Model.Models { - public class Guestbook:RootEntityTkey + public class Guestbook : RootEntityTkey { - + /// 博客ID /// /// - public int? blogId { get; set; } + public long? blogId { get; set; } /// 创建时间 /// /// public DateTime createdate { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string username { get; set; } /// 手机 /// /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string phone { get; set; } /// qq /// /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string QQ { get; set; } /// 留言内容 /// /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string body { get; set; } /// ip地址 /// /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string ip { get; set; } /// 是否显示在前台,0否1是 diff --git a/Blog.Core.Model/Models/ModulePermission.cs b/Blog.Core.Model/Models/ModulePermission.cs deleted file mode 100644 index 6d1a9ea8..00000000 --- a/Blog.Core.Model/Models/ModulePermission.cs +++ /dev/null @@ -1,52 +0,0 @@ -using SqlSugar; -using System; - -namespace Blog.Core.Model.Models -{ - /// - /// 菜单与按钮关系表 - /// - public class ModulePermission : ModulePermissionRoot - { - - /// - ///获取或设置是否禁用,逻辑上的删除,非物理删除 - /// - [SugarColumn(IsNullable = true)] - public bool? IsDeleted { get; set; } - - /// - /// 创建ID - /// - [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } - /// - /// 创建者 - /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] - public string CreateBy { get; set; } - /// - /// 创建时间 - /// - [SugarColumn(IsNullable = true)] - public DateTime? CreateTime { get; set; } - /// - /// 修改ID - /// - [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } - /// - /// 修改者 - /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] - public string ModifyBy { get; set; } - /// - ///修改时间 - /// - [SugarColumn(IsNullable = true)] - public DateTime? ModifyTime { get; set; } - - //public virtual Module Module { get; set; } - //public virtual Permission Permission { get; set; } - } -} diff --git a/Blog.Core.Model/Models/Modules.cs b/Blog.Core.Model/Models/Modules.cs index 2ecb21f5..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() { @@ -25,37 +25,37 @@ public Modules() /// /// 名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string Name { get; set; } /// /// 菜单链接地址 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string LinkUrl { get; set; } /// /// 区域名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Area { get; set; } /// /// 控制器名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Controller { get; set; } /// /// Action名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Action { get; set; } /// /// 图标 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string Icon { get; set; } /// /// 菜单编号 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 10, IsNullable = true)] + [SugarColumn(Length = 10, IsNullable = true)] public string Code { get; set; } /// /// 排序 @@ -64,7 +64,7 @@ public Modules() /// /// /描述 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string Description { get; set; } /// /// 是否是右侧菜单 @@ -78,11 +78,11 @@ public Modules() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string CreateBy { get; set; } /// /// 创建时间 @@ -93,11 +93,11 @@ public Modules() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string ModifyBy { get; set; } /// /// 修改时间 diff --git a/Blog.Core.Model/Models/OperateLog.cs b/Blog.Core.Model/Models/OperateLog.cs index 7f60dc3d..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 { /// @@ -17,27 +17,27 @@ public class OperateLog : RootEntityTkey /// /// 区域名 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Area { get; set; } /// /// 区域控制器名 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Controller { get; set; } /// /// Action名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Action { get; set; } /// /// IP地址 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string IPAddress { get; set; } /// /// 描述 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Description { get; set; } /// /// 登录时间 @@ -47,7 +47,7 @@ public class OperateLog : RootEntityTkey /// /// 登录名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string LoginName { get; set; } /// /// 用户ID @@ -55,6 +55,6 @@ public class OperateLog : RootEntityTkey public int UserId { get; set; } [SugarColumn(IsIgnore = true)] - public virtual sysUserInfo User { get; set; } + public virtual SysUserInfo User { get; set; } } } diff --git a/Blog.Core.Model/Models/PasswordLib.cs b/Blog.Core.Model/Models/PasswordLib.cs index 5164085e..2037df4d 100644 --- a/Blog.Core.Model/Models/PasswordLib.cs +++ b/Blog.Core.Model/Models/PasswordLib.cs @@ -6,11 +6,12 @@ namespace Blog.Core.Model.Models /// /// 密码库表 /// - [SugarTable("PasswordLib", "WMBLOG_MSSQL_2")] + [SugarTable("PasswordLib", "密码库表")]//('数据库表名','数据库表备注') + //[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; } /// ///获取或设置是否禁用,逻辑上的删除,非物理删除 @@ -18,13 +19,13 @@ public class PasswordLib [SugarColumn(IsNullable = true)] public bool? IsDeleted { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string plURL { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string plPWD { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string plAccountName { get; set; } [SugarColumn(IsNullable = true)] @@ -33,10 +34,10 @@ public class PasswordLib [SugarColumn(IsNullable = true)] public int? plErrorCount { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string plHintPwd { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string plHintquestion { get; set; } [SugarColumn(IsNullable = true)] @@ -48,7 +49,7 @@ public class PasswordLib [SugarColumn(IsNullable = true)] public DateTime? plLastErrTime { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string test { get; set; } diff --git a/Blog.Core.Model/Models/Permission.cs b/Blog.Core.Model/Models/Permission.cs index f20e5033..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() { @@ -18,12 +18,12 @@ public Permission() /// /// 菜单执行Action名 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string Code { get; set; } /// /// 菜单显示名(如用户页、编辑(按钮)、删除(按钮)) /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string Name { get; set; } /// /// 是否是按钮 @@ -44,7 +44,7 @@ public Permission() /// /// 按钮事件 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string Func { get; set; } /// @@ -54,12 +54,17 @@ public Permission() /// /// 菜单图标 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string Icon { get; set; } /// + /// 菜单图标新 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string IconNew { get; set; } + /// /// 菜单描述 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string Description { get; set; } /// /// 激活状态 @@ -69,11 +74,11 @@ public Permission() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string CreateBy { get; set; } /// /// 创建时间 @@ -84,11 +89,11 @@ public Permission() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string ModifyBy { get; set; } /// /// 修改时间 @@ -113,6 +118,12 @@ public Permission() [SugarColumn(IsIgnore = true)] public bool hasChildren { get; set; } = true; + [SugarColumn(IsIgnore = true)] + public List Children { get; set; } = new List(); + + [SugarColumn(IsIgnore = true)] + public Modules Module { get; set; } + //public virtual ICollection ModulePermission { get; set; } //public virtual ICollection RoleModulePermission { get; set; } } diff --git a/Blog.Core.Model/Models/Role.cs b/Blog.Core.Model/Models/Role.cs index 0f08cc1e..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() { @@ -34,18 +34,29 @@ public Role(string name) /// /// 角色名 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string Name { get; set; } /// ///描述 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = true)] + [SugarColumn(Length = 100, IsNullable = true)] public string Description { get; set; } /// ///排序 /// public int OrderSort { get; set; } /// + /// 自定义权限的部门ids + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string Dids { get; set; } + /// + /// 权限范围 + /// -1 无任何权限;1 自定义权限;2 本部门;3 本部门及以下;4 仅自己;9 全部; + /// + [SugarColumn(IsNullable = true)] + public int AuthorityScope { get; set; } = -1; + /// /// 是否激活 /// public bool Enabled { get; set; } @@ -53,11 +64,11 @@ public Role(string name) /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string CreateBy { get; set; } /// /// 创建时间 @@ -68,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 66705596..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,11 +26,11 @@ public RoleModulePermission() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string CreateBy { get; set; } /// /// 创建时间 @@ -41,11 +41,11 @@ public RoleModulePermission() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string ModifyBy { 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/DepartmentRoot.cs b/Blog.Core.Model/Models/RootTkey/DepartmentRoot.cs new file mode 100644 index 00000000..ab10c9f8 --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/DepartmentRoot.cs @@ -0,0 +1,20 @@ +using SqlSugar; +using System; +using System.Collections.Generic; + +namespace Blog.Core.Model +{ + /// + /// 部门表 + /// + public class DepartmentRoot : RootEntityTkey where Tkey : IEquatable + { + /// + /// 上一级(0表示无上一级) + /// + public Tkey Pid { get; set; } + + [SugarColumn(IsIgnore = true)] + public List PidArr { get; set; } + } +} 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/ModulePermissionRoot.cs b/Blog.Core.Model/Models/RootTkey/ModulePermissionRoot.cs deleted file mode 100644 index 86873f69..00000000 --- a/Blog.Core.Model/Models/RootTkey/ModulePermissionRoot.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Blog.Core.Model -{ - /// - /// 菜单与按钮关系表 - /// 父类 - /// - public class ModulePermissionRoot : RootEntityTkey where Tkey : IEquatable - { - /// - /// 菜单ID - /// - public Tkey ModuleId { get; set; } - /// - /// 按钮ID - /// - public Tkey PermissionId { get; set; } - - } -} 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/RootTkey/sysUserInfoRoot.cs b/Blog.Core.Model/Models/RootTkey/sysUserInfoRoot.cs index 5605374d..60531b1c 100644 --- a/Blog.Core.Model/Models/RootTkey/sysUserInfoRoot.cs +++ b/Blog.Core.Model/Models/RootTkey/sysUserInfoRoot.cs @@ -7,14 +7,14 @@ namespace Blog.Core.Model /// /// 用户信息表 /// - public class sysUserInfoRoot where Tkey : IEquatable + public class SysUserInfoRoot where Tkey : IEquatable { /// - /// uID + /// Id /// 泛型主键Tkey /// [SugarColumn(IsNullable = false, IsPrimaryKey = true)] - public Tkey uID { get; set; } + public Tkey Id { get; set; } [SugarColumn(IsIgnore = true)] public List RIDs { get; set; } 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 8cc53d26..b029a995 100644 --- a/Blog.Core.Model/Models/TasksQz.cs +++ b/Blog.Core.Model/Models/TasksQz.cs @@ -8,37 +8,37 @@ namespace Blog.Core.Model.Models /// /// 任务计划表 /// - public class TasksQz : RootEntityTkey + public class TasksQz : RootEntityTkey { /// /// 任务名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string Name { get; set; } /// /// 任务分组 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string JobGroup { get; set; } /// /// 任务运行时间表达式 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string Cron { get; set; } /// /// 任务所在DLL对应的程序集名称 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string AssemblyName { get; set; } /// /// 任务所在类 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string ClassName { get; set; } /// /// 任务描述 /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 1000, IsNullable = true)] + [SugarColumn(Length = 1000, IsNullable = true)] public string Remark { get; set; } /// /// 执行次数 @@ -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 258d5457..e57bd561 100644 --- a/Blog.Core.Model/Models/Topic.cs +++ b/Blog.Core.Model/Models/Topic.cs @@ -7,26 +7,26 @@ namespace Blog.Core.Model.Models /// /// Tibug 类别 /// - public class Topic : RootEntityTkey + public class Topic : RootEntityTkey { public Topic() { this.TopicDetail = new List(); this.tUpdatetime = DateTime.Now; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tLogo { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tName { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 400, IsNullable = true)] + [SugarColumn(Length = 400, IsNullable = true)] public string tDetail { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tAuthor { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tSectendDetail { get; set; } public bool tIsDelete { get; set; } diff --git a/Blog.Core.Model/Models/TopicDetail.cs b/Blog.Core.Model/Models/TopicDetail.cs index 1f352780..6cb69c67 100644 --- a/Blog.Core.Model/Models/TopicDetail.cs +++ b/Blog.Core.Model/Models/TopicDetail.cs @@ -6,26 +6,26 @@ namespace Blog.Core.Model.Models /// /// Tibug 博文 /// - public class TopicDetail : TopicDetailRoot + public class TopicDetail : TopicDetailRoot { public TopicDetail() { this.tdUpdatetime = DateTime.Now; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tdLogo { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tdName { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string tdContent { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string tdDetail { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tdSectendDetail { get; set; } public bool tdIsDelete { get; set; } = false; @@ -36,7 +36,7 @@ public TopicDetail() public DateTime tdUpdatetime { get; set; } public int tdTop { get; set; } - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] + [SugarColumn(Length = 200, IsNullable = true)] public string tdAuthor { get; set; } diff --git a/Blog.Core.Model/Models/UserRole.cs b/Blog.Core.Model/Models/UserRole.cs index a3bb1687..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,11 +31,11 @@ public UserRole(int uid, int rid) /// 创建ID ///
[SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// - [SugarColumn(ColumnDataType ="nvarchar",Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string CreateBy { get; set; } /// /// 创建时间 @@ -50,7 +50,7 @@ public UserRole(int uid, int rid) /// /// 修改者 /// - [SugarColumn(ColumnDataType ="nvarchar",Length = 50, IsNullable = true)] + [SugarColumn(Length = 50, IsNullable = true)] public string ModifyBy { get; set; } /// /// 修改时间 diff --git a/Blog.Core.Model/Models/WeChatCompany.cs b/Blog.Core.Model/Models/WeChatCompany.cs new file mode 100644 index 00000000..d07d4208 --- /dev/null +++ b/Blog.Core.Model/Models/WeChatCompany.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using System.Text; +using SqlSugar; + +namespace Blog.Core.Model.Models +{ + /// + /// + /// + [SugarTable("WeChatCompany")] + public partial class WeChatCompany + { + + /// + /// 公司ID + /// + [SugarColumn(IsPrimaryKey = true, Length = 100, IsNullable = false)] + public string CompanyID { get; set; } + /// + /// 公司名称 + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string CompanyName { get; set; } + /// + /// 公司IP + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string CompanyIP { get; set; } + /// + /// 公司备注 + /// + [SugarColumn(Length = 200, IsNullable = false)] + public string CompanyRemark { get; set; } + /// + /// api地址 + /// + [SugarColumn(Length = 200, IsNullable = false)] + public string CompanyAPI { get; set; } + /// + /// 是否激活 + /// + public bool Enabled { get; set; } + /// + /// 创建者id + /// + [SugarColumn(IsNullable = true)] + public int? CreateId { get; set; } + /// + /// 创建人 + /// + [SugarColumn(IsNullable = true)] + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? CreateTime { get; set; } + /// + /// 修改者id + /// + [SugarColumn(IsNullable = true)] + public int? ModifyId { get; set; } + /// + /// 修改人 + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } + } +} diff --git a/Blog.Core.Model/Models/WeChatConfig.cs b/Blog.Core.Model/Models/WeChatConfig.cs new file mode 100644 index 00000000..16910487 --- /dev/null +++ b/Blog.Core.Model/Models/WeChatConfig.cs @@ -0,0 +1,110 @@ +using System; +using System.Linq; +using System.Text; +using SqlSugar; + +namespace Blog.Core.Model.Models +{ + /// + /// + /// + [SugarTable("WeChatConfig")] + public class WeChatConfig + { + + /// + /// 微信公众号唯一标识 + /// + [SugarColumn(IsPrimaryKey = true, Length = 100, IsNullable = false)] + public string publicAccount { get; set; } + + /// + /// 微信公众号名称 + /// + [SugarColumn(Length = 200, IsNullable = false)] + public string publicNick { get; set; } + + /// + /// 微信账号 + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string weChatAccount { get; set; } + + /// + /// 微信名称 + /// + [SugarColumn(Length = 200)] + public string weChatNick { get; set; } + + /// + /// 应用ID + /// + [SugarColumn(Length = 100)] + public string appid { get; set; } + + /// + /// 应用秘钥 + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string appsecret { get; set; } + + /// + /// 公众号推送token + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string token { get; set; } + + /// + /// 验证秘钥(验证消息是否真实) + /// + [SugarColumn(Length = 500, IsNullable = false)] + public string interactiveToken { get; set; } + + /// + /// 微信公众号token过期时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? tokenExpiration { get; set; } + + /// + /// 备注 + /// + [SugarColumn(Length = 200,IsNullable = true)] + public string remark { get; set; } + /// + /// 是否激活 + /// + public bool Enabled { get; set; } + + /// + /// 创建者id + /// + [SugarColumn(IsNullable = true)] + public int? CreateId { get; set; } + /// + /// 创建人 + /// + [SugarColumn(IsNullable = true)] + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? CreateTime { get; set; } + /// + /// 修改者id + /// + [SugarColumn(IsNullable = true)] + public int? ModifyId { get; set; } + /// + /// 修改人 + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } + } +} diff --git a/Blog.Core.Model/Models/WeChatPushLog.cs b/Blog.Core.Model/Models/WeChatPushLog.cs new file mode 100644 index 00000000..5368c806 --- /dev/null +++ b/Blog.Core.Model/Models/WeChatPushLog.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using System.Text; +using SqlSugar; + +namespace Blog.Core.Model.Models +{ + /// + /// + /// + [SugarTable("WeChatPushLog")] + public partial class WeChatPushLog + { + + /// + /// 推送ID + /// + [SugarColumn(IsPrimaryKey = true,IsNullable = false)] + public string id { get; set; } + /// + /// 来自谁 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string PushLogFrom { get; set; } + + /// + /// 推送IP + /// + [SugarColumn(Length = 50, IsNullable = true)] + public string PushLogIP { get; set; } + + /// + /// 推送客户 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string PushLogCompanyID { get; set; } + + /// + /// 推送用户 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string PushLogToUserID { get; set; } + + /// + /// 推送模板ID + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string PushLogTemplateID { get; set; } + + /// + /// 推送内容 + /// + [SugarColumn(Length = 2000, IsNullable = true)] + public string PushLogContent { get; set; } + + /// + /// 推送时间 + /// + [SugarColumn(IsNullable = false)] + public DateTime? PushLogTime { get; set; } + + /// + /// 推送状态(Y/N) + /// + [SugarColumn(Length =1,IsNullable = false)] + public string PushLogStatus { get; set; } + + /// + /// 备注 + /// + [SugarColumn(Length = 200, IsNullable = false)] + public string PushLogRemark { get; set; } + + /// + /// 推送OpenID + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string PushLogOpenid { get; set; } + + /// + /// 推送微信公众号 + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string PushLogPublicAccount { get; set; } + /// + /// 创建者id + /// + [SugarColumn(IsNullable = true)] + public int? CreateId { get; set; } + /// + /// 创建人 + /// + [SugarColumn(IsNullable = true)] + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? CreateTime { get; set; } + /// + /// 修改者id + /// + [SugarColumn(IsNullable = true)] + public int? ModifyId { get; set; } + /// + /// 修改人 + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } + } +} diff --git a/Blog.Core.Model/Models/WeChatQR.cs b/Blog.Core.Model/Models/WeChatQR.cs new file mode 100644 index 00000000..06ea8684 --- /dev/null +++ b/Blog.Core.Model/Models/WeChatQR.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; +using System.Text; +using SqlSugar; + +namespace Blog.Core.Model.Models +{ + /// + /// + /// + [SugarTable("WeChatQR")] + public partial class WeChatQR + { + + /// + /// 主键id,ticket + /// + [SugarColumn(Length = 200, IsPrimaryKey = true, IsNullable = false)] + public string QRticket { get; set; } + + /// + /// 需要绑定的公司 + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string QRbindCompanyID { get; set; } + + /// + /// 需要绑定的员工id + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string QRbindJobID { get; set; } + /// + /// 需要绑定的员工昵称 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string QRbindJobNick { get; set; } + + /// + /// 创建时间 + /// + public DateTime QRcrateTime { get; set; } + + /// + /// 关联的公众号 + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string QRpublicAccount { get; set; } + + /// + /// 是否已使用 + /// + public bool QRisUsed { get; set; } + + /// + /// 使用时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? QRuseTime { get; set; } + + /// + /// 关联的微信用户id + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string QRuseOpenid { get; set; } + /// + /// 创建者id + /// + [SugarColumn(IsNullable = true)] + public int? CreateId { get; set; } + /// + /// 创建人 + /// + [SugarColumn(IsNullable = true)] + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? CreateTime { get; set; } + /// + /// 修改者id + /// + [SugarColumn(IsNullable = true)] + public int? ModifyId { get; set; } + /// + /// 修改人 + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } + } +} diff --git a/Blog.Core.Model/Models/WeChatSub.cs b/Blog.Core.Model/Models/WeChatSub.cs new file mode 100644 index 00000000..48787a5f --- /dev/null +++ b/Blog.Core.Model/Models/WeChatSub.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; +using System.Text; +using SqlSugar; + +namespace Blog.Core.Model.Models +{ + /// + /// + /// + [SugarTable("WeChatSub")] + public partial class WeChatSub + { + [SugarColumn(IsNullable = false,IsPrimaryKey = true)] + public string id { get; set; } + /// + /// 来自哪个公众号 + /// + [SugarColumn(Length = 100 , IsNullable = false, IndexGroupNameList = new string[] { "index" })] + public string SubFromPublicAccount { get; set; } + + /// + /// 绑定公司id + /// + [SugarColumn(Length = 100 , IsNullable = false, IndexGroupNameList = new string[] { "index" })] + public string CompanyID { get; set; } + + /// + /// 绑定员工id + /// + [SugarColumn(Length = 100 , IsNullable = false, IndexGroupNameList = new string[] { "index" })] + public string SubJobID { get; set; } + + /// + /// 绑定微信id + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string SubUserOpenID { get; set; } + + /// + /// 绑定微信联合id + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string SubUserUnionID { get; set; } + + /// + /// 绑定时间 + /// + public DateTime SubUserRegTime { get; set; } + + /// + /// 更新时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? SubUserRefTime { get; set; } + + /// + /// 备注 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string SubUserRemark { get; set; } + + /// + /// 是否已解绑 + /// + public bool IsUnBind { get; set; } + + /// + /// 上次绑定微信id + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string LastSubUserOpenID { get; set; } + /// + /// 创建者id + /// + [SugarColumn(IsNullable = true)] + public int? CreateId { get; set; } + /// + /// 创建人 + /// + [SugarColumn(IsNullable = true)] + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? CreateTime { get; set; } + /// + /// 修改者id + /// + [SugarColumn(IsNullable = true)] + public int? ModifyId { get; set; } + /// + /// 修改人 + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } + } +} diff --git a/Blog.Core.Model/Models/WeChatUploadFile.cs b/Blog.Core.Model/Models/WeChatUploadFile.cs new file mode 100644 index 00000000..f7b979de --- /dev/null +++ b/Blog.Core.Model/Models/WeChatUploadFile.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using System.Text; +using SqlSugar; + +namespace Blog.Core.Model.Models +{ + /// + /// + /// + [SugarTable("WeChatUploadFile")] + public partial class WeChatUploadFile + { + + /// + /// 文件ID + /// + [SugarColumn(Length = 100,IsPrimaryKey = true,IsNullable =false)] + public string UploadFileID { get; set; } + + /// + /// 文件名称 + /// + [SugarColumn(Length = 200, IsNullable = false)] + public string UploadFileName { get; set; } + + /// + /// 文件大小 + /// + [SugarColumn(IsNullable = false)] + public int? UploadFileSize { get; set; } + + /// + /// 文件类型 + /// + [SugarColumn(Length = 50, IsNullable = true)] + public string UploadFileContentType { get; set; } + + /// + /// 文件拓展名 + /// + [SugarColumn(Length = 50, IsNullable = true)] + public string UploadFileExtension { get; set; } + + /// + /// 文件位置 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string UploadFilePosition { get; set; } + + /// + /// 文件上传时间 + /// + public DateTime? UploadFileTime { get; set; } + + /// + /// 文件备注 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string UploadFileRemark { get; set; } + /// + /// 创建者id + /// + [SugarColumn(IsNullable = true)] + public int? CreateId { get; set; } + /// + /// 创建人 + /// + [SugarColumn(IsNullable = true)] + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? CreateTime { get; set; } + /// + /// 修改者id + /// + [SugarColumn(IsNullable = true)] + public int? ModifyId { get; set; } + /// + /// 修改人 + /// + [SugarColumn(IsNullable = true)] + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime? ModifyTime { get; set; } + } +} diff --git a/Blog.Core.Model/Models/sysUserInfo.cs b/Blog.Core.Model/Models/sysUserInfo.cs index 65b37161..1137d91c 100644 --- a/Blog.Core.Model/Models/sysUserInfo.cs +++ b/Blog.Core.Model/Models/sysUserInfo.cs @@ -4,97 +4,141 @@ namespace Blog.Core.Model.Models { - /// - /// 用户信息表 - /// - public class sysUserInfo : sysUserInfoRoot - { - public sysUserInfo() { } - - public sysUserInfo(string loginName, string loginPWD) - { - uLoginName = loginName; - uLoginPWD = loginPWD; - uRealName = uLoginName; - uStatus = 0; - uCreateTime = DateTime.Now; - uUpdateTime = DateTime.Now; - uLastErrTime = DateTime.Now; - uErrorCount = 0; - name = ""; - - } - - /// - /// 登录账号 - /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] - public string uLoginName { get; set; } - /// - /// 登录密码 - /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] - public string uLoginPWD { get; set; } - /// - /// 真实姓名 - /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] - public string uRealName { get; set; } - /// - /// 状态 - /// - public int uStatus { get; set; } - /// - /// 备注 - /// - [SugarColumn(ColumnDataType = "nvarchar", Length = 2000, IsNullable = true)] - public string uRemark { get; set; } - /// - /// 创建时间 - /// - public System.DateTime uCreateTime { get; set; } = DateTime.Now; - /// - /// 更新时间 - /// - public System.DateTime uUpdateTime { get; set; } = DateTime.Now; - - /// - ///最后登录时间 - /// - public DateTime uLastErrTime { get; set; } = DateTime.Now; - - /// - ///错误次数 - /// - public int uErrorCount { get; set; } - - - - /// - /// 登录账号 - /// - [SugarColumn(ColumnDataType = "nvarchar", 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(ColumnDataType = "nvarchar", Length = 200, IsNullable = true)] - public string addr { get; set; } - - [SugarColumn(IsNullable = true)] - public bool tdIsDelete { get; set; } - - - [SugarColumn(IsIgnore = true)] - public List RoleNames { get; set; } - - } + /// + /// 用户信息表 + /// + //[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/PageModel.cs b/Blog.Core.Model/PageModel.cs index 45a3798e..f6872b66 100644 --- a/Blog.Core.Model/PageModel.cs +++ b/Blog.Core.Model/PageModel.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using AutoMapper; +using System; +using System.Collections.Generic; namespace Blog.Core.Model { @@ -14,7 +16,7 @@ public class PageModel /// /// 总页数 /// - public int pageCount { get; set; } = 6; + public int pageCount => (int)Math.Ceiling((decimal)dataCount / PageSize); /// /// 数据总数 /// @@ -22,12 +24,53 @@ public class PageModel /// /// 每页大小 /// - public int PageSize { set; get; } + public int PageSize { set; get; } = 20; /// /// 返回数据 /// public List data { get; set; } + public PageModel() { } + + public PageModel(int page, int dataCount, int pageSize, List data) + { + this.page = page; + this.dataCount = dataCount; + PageSize = pageSize; + this.data = data; + } + + public PageModel ConvertTo() + { + return new PageModel(page, dataCount, PageSize, default); + } + + + public PageModel ConvertTo(IMapper mapper) + { + var model = ConvertTo(); + + if (data != null) + { + model.data = mapper.Map>(data); + } + + return model; + } + + + public PageModel ConvertTo(IMapper mapper, Action options) + { + var model = ConvertTo(); + if (data != null) + { + model.data = mapper.Map>(data, options); + } + + return model; + + } + } } diff --git a/Blog.Core.Model/PaginationModel.cs b/Blog.Core.Model/PaginationModel.cs new file mode 100644 index 00000000..f79b3316 --- /dev/null +++ b/Blog.Core.Model/PaginationModel.cs @@ -0,0 +1,27 @@ +namespace Blog.Core.Model +{ + /// + /// 所需分页参数 + /// 作者:胡丁文 + /// 时间:2020-4-3 20:31:26 + /// + public class PaginationModel + { + /// + /// 当前页 + /// + public int PageIndex { get; set; } = 1; + /// + /// 每页大小 + /// + public int PageSize { get; set; } = 10; + /// + /// 排序字段(例如:id desc,time asc) + /// + public string OrderByFileds { get; set; } + /// + /// 查询条件( 例如:id = 1 and name = 小明) + /// + public string Conditions { get; set; } + } +} diff --git a/Blog.Core.Model/Seed/DBSeed.cs b/Blog.Core.Model/Seed/DBSeed.cs deleted file mode 100644 index bb649a84..00000000 --- a/Blog.Core.Model/Seed/DBSeed.cs +++ /dev/null @@ -1,296 +0,0 @@ -using Blog.Core.Common; -using Blog.Core.Common.DB; -using Blog.Core.Common.Helper; -using Blog.Core.Model.Models; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.Model.Seed -{ - public class DBSeed - { - private static string SeedDataFolder = "BlogCore.Data.json/{0}.tsv"; - - - /// - /// 异步添加种子数据 - /// - /// - /// - /// - public static async Task SeedAsync(MyContext myContext, string WebRootPath) - { - try - { - if (string.IsNullOrEmpty(WebRootPath)) - { - throw new Exception("获取wwwroot路径时,异常!"); - } - - 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(); - 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()) - { - var slaveIndex = 0; - BaseDBConfig.MutiConnectionString.allDbs.Where(x => x.ConnId != MainDb.CurrentDbConnId).ToList().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}"); - Console.WriteLine($"--------------------------------------"); - }); - } - else if (Appsettings.app(new string[] { "CQRSEnabled" }).ObjToBool()) - { - var slaveIndex = 0; - BaseDBConfig.MutiConnectionString.slaveDbs.Where(x => x.ConnId != MainDb.CurrentDbConnId).ToList().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}"); - Console.WriteLine($"--------------------------------------"); - }); - } - else - { - } - - Console.WriteLine(); - - // 创建数据库 - Console.WriteLine($"Create Database(The Db Id:{MyContext.ConnId})..."); - - if (MyContext.DbType != SqlSugar.DbType.Oracle) - { - myContext.Db.DbMaintenance.CreateDatabase(); - ConsoleHelper.WriteSuccessLine($"Database created successfully!"); - } - else - { - //Oracle 数据库不支持该操作 - ConsoleHelper.WriteSuccessLine($"Oracle 数据库不支持该操作,可手动创建Oracle数据库!"); - } - - // 创建数据库表,遍历指定命名空间下的class, - // 注意不要把其他命名空间下的也添加进来。 - Console.WriteLine("Create Tables..."); - var modelTypes = from t in Assembly.GetExecutingAssembly().GetTypes() - where t.IsClass && t.Namespace == "Blog.Core.Model.Models" - select t; - modelTypes.ToList().ForEach(t => - { - // 这里只支持添加表,不支持删除 - // 如果想要删除,数据库直接右键删除,或者联系SqlSugar作者; - if (!myContext.Db.DbMaintenance.IsAnyTable(t.Name)) - { - Console.WriteLine(t.Name); - myContext.Db.CodeFirst.InitTables(t); - } - }); - ConsoleHelper.WriteSuccessLine($"Tables created successfully!"); - Console.WriteLine(); - - - - if (Appsettings.app(new string[] { "AppSettings", "SeedDBDataEnabled" }).ObjToBool()) - { - JsonSerializerSettings setting = new JsonSerializerSettings(); - JsonConvert.DefaultSettings = new Func(() => - { - //日期类型默认格式化处理 - setting.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; - setting.DateFormatString = "yyyy-MM-dd HH:mm:ss"; - - //空值处理 - setting.NullValueHandling = NullValueHandling.Ignore; - - //高级用法九中的Bool类型转换 设置 - //setting.Converters.Add(new BoolConvert("是,否")); - - return setting; - }); - - Console.WriteLine($"Seeding database data (The Db Id:{MyContext.ConnId})..."); - - #region BlogArticle - if (!await myContext.Db.Queryable().AnyAsync()) - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:Modules created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:Permission created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:Role created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:RoleModulePermission created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:Topic created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:TopicDetail created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:UserRole created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:sysUserInfo created success!"); - } - else - { - 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); - - myContext.GetEntityDB().InsertRange(data); - Console.WriteLine("Table:TasksQz created success!"); - } - else - { - Console.WriteLine("Table:TasksQz already exists..."); - } - #endregion - - ConsoleHelper.WriteSuccessLine($"Done seeding database!"); - } - - Console.WriteLine(); - - } - catch (Exception ex) - { - throw new Exception( - $"1、若是Mysql,查看常见问题:https://github.com/anjoy8/Blog.Core/issues/148#issue-776281770 \n" + - $"2、若是Oracle,查看常见问题:https://github.com/anjoy8/Blog.Core/issues/148#issuecomment-752340231 \n" + - "3、其他错误:" + ex.Message); - } - } - } -} \ No newline at end of file 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/EnumDemoDto.cs b/Blog.Core.Model/ViewModels/EnumDemoDto.cs new file mode 100644 index 00000000..ae6ffbdd --- /dev/null +++ b/Blog.Core.Model/ViewModels/EnumDemoDto.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Model.ViewModels +{ + public class EnumDemoDto + { + public int Id { get; set; } + /// + /// Type Description balabala + /// + public EnumType Type { get; set; } + } + + + public enum EnumType + { + foo, bar, baz + } +} diff --git a/Blog.Core.Model/ViewModels/RootTKey/SysUserInfoDtoRoot.cs b/Blog.Core.Model/ViewModels/RootTKey/SysUserInfoDtoRoot.cs new file mode 100644 index 00000000..bcccb8e7 --- /dev/null +++ b/Blog.Core.Model/ViewModels/RootTKey/SysUserInfoDtoRoot.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace Blog.Core.Model.ViewModels +{ + public class SysUserInfoDtoRoot where Tkey : IEquatable + { + public Tkey uID { get; set; } + + public List RIDs { get; set; } + + } +} diff --git a/Blog.Core.Model/ViewModels/SysUserInfoDto.cs b/Blog.Core.Model/ViewModels/SysUserInfoDto.cs new file mode 100644 index 00000000..3b5451f0 --- /dev/null +++ b/Blog.Core.Model/ViewModels/SysUserInfoDto.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Blog.Core.Model.ViewModels +{ + 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 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; + public DateTime uLastErrTime { get; set; } = DateTime.Now; + public int uErrorCount { get; set; } + public string name { get; set; } + public int sex { get; set; } = 0; + public int age { get; set; } + public DateTime birth { get; set; } = DateTime.Now; + public string addr { get; set; } + public bool tdIsDelete { get; set; } + public List RoleNames { 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 new file mode 100644 index 00000000..dd0f51bb --- /dev/null +++ b/Blog.Core.Model/ViewModels/UploadFileDto.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Http; +using System.ComponentModel.DataAnnotations; + +namespace Blog.Core.Model.ViewModels +{ + public class UploadFileDto + { + //多文件 + [Required] + public IFormFileCollection file { get; set; } + + //单文件 + //public IFormFile File { get; set; } + + //其他数据 + public string Foo { get; set; } + + + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatApiDto.cs b/Blog.Core.Model/ViewModels/WeChatApiDto.cs new file mode 100644 index 00000000..e02a55b1 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatApiDto.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信接口消息DTO + /// 作者:胡丁文 + /// 时间:2020-03-25 + /// + public class WeChatApiDto + { + /// + /// 微信公众号ID(数据库查询) + /// + public string id { get; set; } + /// + /// 错误代码 + /// + public int errcode { get; set; } + /// + /// 错误信息 + /// + public string errmsg { get; set; } + + + /// + /// token + /// + public string access_token { get; set; } + /// + /// 过期时间(秒) + /// + public int expires_in { get; set; } + + + /// + /// 用户关注数 + /// + public int total { get; set; } + /// + /// 获取用户数量 + /// + public int count { get; set; } + /// + /// 获取用户OpenIDs + /// + public WeChatOpenIDsDto data { get; set; } + public List users { get; set; } + /// + /// 下一个关注用户 + /// + public string next_openid { get; set; } + + /// + /// 微信消息模板列表 + /// + + public WeChatTemplateList[] template_list { get; set; } + /// + /// 微信菜单 + /// + public WeChatMenuDto menu { get; set; } + + /// + /// 二维码票据 + /// + public string ticket { get; set; } + /// + /// 二维码过期时间 + /// + public int expire_seconds { get; set; } + /// + /// 二维码地址 + /// + public string url { get; set; } + /// + /// 关注状态 + /// + public string subscribe { get; set; } + /// + /// 用户微信ID + /// + public string openid { get; set; } + /// + /// 昵称 + /// + public string nickname { get; set; } + /// + /// 性别 + /// + public int sex { get; set; } + /// + /// 语言 + /// + public string language { get; set; } + /// + /// 城市 + /// + public string city { get; set; } + /// + /// 省份 + /// + public string province { get; set; } + /// + /// 城市 + /// + public string country { get; set; } + /// + /// 头像地址 + /// + public string headimgurl { get; set; } + + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatCardMsgDataDto.cs b/Blog.Core.Model/ViewModels/WeChatCardMsgDataDto.cs new file mode 100644 index 00000000..2448bdd4 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatCardMsgDataDto.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信推送消息Dto + /// 作者:胡丁文 + /// 时间:2020-4-8 09:16:16 + /// + public class WeChatCardMsgDataDto + { + /// + /// 推送关键信息 + /// + public WeChatUserInfo info { get; set; } + /// + /// 推送卡片消息Dto + /// + public WeChatCardMsgDetailDto cardMsg { set; get; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatCardMsgDataOpenIDDto.cs b/Blog.Core.Model/ViewModels/WeChatCardMsgDataOpenIDDto.cs new file mode 100644 index 00000000..e90e5f47 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatCardMsgDataOpenIDDto.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信推送消息Dto + /// 作者:胡丁文 + /// 时间:2020-11-23 16:29:05 + /// + public class WeChatCardMsgDataOpenIDDto + { + /// + /// 推送关键信息 + /// + public WeChatUserInfoOpenID info { get; set; } + /// + /// 推送卡片消息Dto + /// + public WeChatCardMsgDetailDto cardMsg { set; get; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatCardMsgDetailDto.cs b/Blog.Core.Model/ViewModels/WeChatCardMsgDetailDto.cs new file mode 100644 index 00000000..17db8c91 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatCardMsgDetailDto.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 消息模板dto(如何填写数据,请参考微信模板即可) + /// 作者:胡丁文 + /// 时间:2020-4-1 09:32:16 + /// + public class WeChatCardMsgDetailDto + { + /// + /// 消息模板 + /// + public string template_id { get; set; } + /// + /// 标题 + /// + public string first { get; set; } + /// + /// 标题颜色(颜色代码都必须为#开头的16进制代码) + /// + public string colorFirst { get; set; } = "#173177"; + /// + /// 内容1 + /// + public string keyword1 { get; set; } + /// + /// 内容1颜色 + /// + + public string color1 { get; set; } = "#173177"; + /// + /// 内容2 + /// + public string keyword2 { get; set; } + /// + /// 内容2颜色 + /// + public string color2 { get; set; } = "#173177"; + /// + /// 内容3 + /// + public string keyword3 { get; set; } + /// + /// 内容3颜色 + /// + public string color3 { get; set; } = "#173177"; + /// + /// 内容4 + /// + public string keyword4 { get; set; } + /// + /// 内容4颜色 + /// + public string color4 { get; set; } = "#173177"; + /// + /// 内容5 + /// + public string keyword5 { get; set; } + /// + /// 内容5颜色 + /// + public string color5 { get; set; } = "#173177"; + /// + /// 备注信息 + /// + public string remark { get; set; } + /// + /// 备注信息颜色 + /// + public string colorRemark { get; set; } = "#173177"; + /// + /// 跳转连接 + /// + public string url { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatMenuButtonDto.cs b/Blog.Core.Model/ViewModels/WeChatMenuButtonDto.cs new file mode 100644 index 00000000..df8952cd --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatMenuButtonDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 获取微信菜单DTO,用于存放具体菜单内容 + /// + public class WeChatMenuButtonDto + { + public string type { get; set; } + public string name { get; set; } + public string key { get; set; } + public string url { get; set; } + public WeChatMenuButtonDto[] sub_button { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatMenuDto.cs b/Blog.Core.Model/ViewModels/WeChatMenuDto.cs new file mode 100644 index 00000000..3015a2a7 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatMenuDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 获取微信菜单DTO + /// + public class WeChatMenuDto + { + /// + /// 按钮列表(最多三个) + /// + public WeChatMenuButtonDto[] button { get; set; } + + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatOpenIDsDto.cs b/Blog.Core.Model/ViewModels/WeChatOpenIDsDto.cs new file mode 100644 index 00000000..93c13490 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatOpenIDsDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信OpenID列表Dto + /// + public class WeChatOpenIDsDto + { + public List openid { get; set; } = new List(); + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushCardMsgDetailDto.cs b/Blog.Core.Model/ViewModels/WeChatPushCardMsgDetailDto.cs new file mode 100644 index 00000000..b203dacb --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushCardMsgDetailDto.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 推送详细数据 + /// 作者:胡丁文 + /// 时间:2020-4-8 09:16:16 + /// + public class WeChatPushCardMsgDetailDto + { + public WeChatPushCardMsgValueColorDto first { get; set; } + public WeChatPushCardMsgValueColorDto keyword1 { get; set; } + public WeChatPushCardMsgValueColorDto keyword2 { get; set; } + public WeChatPushCardMsgValueColorDto keyword3 { get; set; } + public WeChatPushCardMsgValueColorDto keyword4 { get; set; } + public WeChatPushCardMsgValueColorDto keyword5 { get; set; } + public WeChatPushCardMsgValueColorDto remark { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushCardMsgDto.cs b/Blog.Core.Model/ViewModels/WeChatPushCardMsgDto.cs new file mode 100644 index 00000000..588ee09c --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushCardMsgDto.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 推送给微信所需Dto + /// 作者:胡丁文 + /// 时间:2020-4-8 09:16:16 + /// + public class WeChatPushCardMsgDto + { + /// + /// 推送微信用户ID + /// + public string touser { get; set; } + /// + /// 推送的模板ID + /// + public string template_id { get; set; } + /// + /// 推送URL地址 + /// + public string url { get; set; } + /// + /// 推送的数据 + /// + public WeChatPushCardMsgDetailDto data { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushCardMsgValueColorDto.cs b/Blog.Core.Model/ViewModels/WeChatPushCardMsgValueColorDto.cs new file mode 100644 index 00000000..e9548fd8 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushCardMsgValueColorDto.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信keyword所需Dto + /// 作者:胡丁文 + /// 时间:2020-4-8 09:18:08 + /// + public class WeChatPushCardMsgValueColorDto + { + /// + /// 内容 + /// + public string value { get; set; } + /// + /// 文字颜色 + /// + public string color { get; set; } = "#173177"; + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushLinkMsgContentDto.cs b/Blog.Core.Model/ViewModels/WeChatPushLinkMsgContentDto.cs new file mode 100644 index 00000000..064eaab4 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushLinkMsgContentDto.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + public class WeChatPushLinkMsgContentDto + { + /// + /// 图文链接标题 + /// + public string title { get; set; } + /// + /// 图文描述 + /// + public string description { get; set; } + /// + /// 访问URL + /// + public string viewUrl { get; set; } + /// + /// 图片URL + /// + public string pictureUrl { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushPictureContentDto.cs b/Blog.Core.Model/ViewModels/WeChatPushPictureContentDto.cs new file mode 100644 index 00000000..3b29681a --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushPictureContentDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + public class WeChatPushPictureContentDto + { + /// + /// 图片mediaID + /// + public string pictureMediaID { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushTestDto.cs b/Blog.Core.Model/ViewModels/WeChatPushTestDto.cs new file mode 100644 index 00000000..a906fb80 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushTestDto.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 推送模拟消息Dto + /// 作者:胡丁文 + /// 时间:2020-4-24 14:52:44 + /// + public class WeChatPushTestDto + { + /// + /// 当前选中的微信公众号 + /// + public string selectWeChat { get; set; } + /// + /// 当前选中的操作集合 + /// + public string selectOperate { get; set; } + /// + /// 当前选中的绑定还是订阅 + /// + public string selectBindOrSub { get; set; } + /// + /// 当前选中的微信客户 + /// + public string selectCompany { get; set; } + /// + /// 当前选中的消息类型 + /// + public string selectMsgType { get; set; } + /// + /// 当前选中要发送的用户 + /// + public string selectUser { get; set; } + /// + /// 文本消息 + /// + public WeChatPushTextContentDto textContent { get; set; } + /// + /// 图片消息 + /// + public WeChatPushPictureContentDto pictureContent { get; set; } + /// + /// 语音消息 + /// + public WeChatPushVoiceContentDto voiceContent { get; set; } + /// + /// 视频消息 + /// + public WeChatPushVideoContentDto videoContent { get; set; } + /// + /// 链接消息 + /// + public WeChatPushLinkMsgContentDto linkMsgContent { get; set; } + + + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushTextContentDto.cs b/Blog.Core.Model/ViewModels/WeChatPushTextContentDto.cs new file mode 100644 index 00000000..55d6cc50 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushTextContentDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + public class WeChatPushTextContentDto + { + /// + /// 文字消息 + /// + public string text { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushVideoContentDto.cs b/Blog.Core.Model/ViewModels/WeChatPushVideoContentDto.cs new file mode 100644 index 00000000..a00ad6c1 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushVideoContentDto.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + public class WeChatPushVideoContentDto + { + /// + /// 视频标题 + /// + public string title { get; set; } + /// + /// 视频封面mediaID + /// + public string pictureMediaID { get; set; } + /// + /// 视频mediaID + /// + public string videoMediaID { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatPushVoiceContentDto.cs b/Blog.Core.Model/ViewModels/WeChatPushVoiceContentDto.cs new file mode 100644 index 00000000..1a9da49f --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatPushVoiceContentDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + public class WeChatPushVoiceContentDto + { + /// + /// 语音mediaID + /// + public string voiceMediaID { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatQRActionDto.cs b/Blog.Core.Model/ViewModels/WeChatQRActionDto.cs new file mode 100644 index 00000000..13001b6a --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatQRActionDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信二维码预装发送信息dto + /// + public class WeChatQRActionDto + { + public WeChatQRActionInfoDto scene { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatQRActionInfoDto.cs b/Blog.Core.Model/ViewModels/WeChatQRActionInfoDto.cs new file mode 100644 index 00000000..ba38072a --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatQRActionInfoDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信二维码预装具体消息 + /// + public class WeChatQRActionInfoDto + { + public string scene_str { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatQRDto.cs b/Blog.Core.Model/ViewModels/WeChatQRDto.cs new file mode 100644 index 00000000..eca876f3 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatQRDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信二维码预装信息DTO + /// + public class WeChatQRDto + { + public int expire_seconds { get; set; } + public string action_name { get; set; } + public WeChatQRActionDto action_info { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatResponseUserInfo.cs b/Blog.Core.Model/ViewModels/WeChatResponseUserInfo.cs new file mode 100644 index 00000000..9d28fe51 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatResponseUserInfo.cs @@ -0,0 +1,28 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 返回给调用者的Dto + /// 作者:胡丁文 + /// 时间:2020-4-8 09:52:06 + /// + public class WeChatResponseUserInfo + { + /// + /// 微信公众号ID + /// + public string id { get; set; } + /// + /// 公司代码 + /// + public string companyCode { get; set; } + /// + /// 数据 + /// + public WeChatApiDto usersData { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatTemplateList.cs b/Blog.Core.Model/ViewModels/WeChatTemplateList.cs new file mode 100644 index 00000000..820266f0 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatTemplateList.cs @@ -0,0 +1,15 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信消息模板Dto + /// + public class WeChatTemplateList + { + public string template_id { get; set; } + public string title { get; set; } + public string primary_industry { get; set; } + public string deputy_industry { get; set; } + public string content { get; set; } + public string example { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatUserInfo.cs b/Blog.Core.Model/ViewModels/WeChatUserInfo.cs new file mode 100644 index 00000000..faf65b1d --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatUserInfo.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信推送所需信息(公司版本) + /// 作者:胡丁文 + /// 时间:2020-4-8 09:04:36 + /// + public class WeChatUserInfo + { + /// + /// 微信公众号ID + /// + public string id { get; set; } + /// + /// 公司代码 + /// + public string companyCode { get; set; } + /// + /// 用户id + /// + public string userID { get; set; } + /// + /// 用户昵称 + /// + public string userNick { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatUserInfoOpenID.cs b/Blog.Core.Model/ViewModels/WeChatUserInfoOpenID.cs new file mode 100644 index 00000000..ef01aad2 --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatUserInfoOpenID.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信推送所需信息(OpenID版本) + /// 作者:胡丁文 + /// 时间:2020-11-23 16:27:29 + /// + public class WeChatUserInfoOpenID + { + /// + /// 微信公众号ID + /// + public string id { get; set; } + /// + /// 微信OpenID + /// + public List userID { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatValidDto.cs b/Blog.Core.Model/ViewModels/WeChatValidDto.cs new file mode 100644 index 00000000..c0d038df --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatValidDto.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信验证Dto + /// 作者:胡丁文 + /// 时间:2020-4-1 21:34:07 + /// + public class WeChatValidDto + { + /// + /// 微信公众号唯一标识 + /// + public string publicAccount { get; set; } + /// + /// 验证成功后返回给微信的字符串 + /// + public string echoStr { get; set; } + /// + /// 签名 + /// + public string signature { get; set; } + /// + /// 时间戳 + /// + public string timestamp { get; set; } + /// + /// 随机数 + /// + public string nonce { get; set; } + + } +} diff --git a/Blog.Core.Model/ViewModels/WeChatXMLDto.cs b/Blog.Core.Model/ViewModels/WeChatXMLDto.cs new file mode 100644 index 00000000..e293849d --- /dev/null +++ b/Blog.Core.Model/ViewModels/WeChatXMLDto.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 微信XmlDto + /// 作者:胡丁文 + /// 时间:2020-4-3 20:31:26 + /// + [XmlRoot(ElementName ="xml")] + public class WeChatXMLDto + { + /// + /// 微信公众号唯一表示 + /// + public string publicAccount { get; set; } + /// + /// 微信开发者 + /// + public string ToUserName { get; set; } + /// + /// 来自谁 + /// + public string FromUserName { get; set; } + /// + /// 创建时间 + /// + public string CreateTime { get; set; } + /// + /// 消息类型 + /// + public string MsgType { get; set; } + /// + /// 文字内容 + /// + public string Content { get; set; } + /// + /// 消息ID + /// + public string MsgId { get; set; } + /// + /// 消息事件 + /// + public string Event { get; set; } + /// + /// 事件key值 + /// + public string EventKey { get; set; } + /// + /// 图片地址 + /// + public string PicUrl { get; set; } + /// + /// 多媒体ID + /// + public string MediaId { get; set; } + /// + /// 格式 + /// + public string Format { get; set; } + /// + /// 语音失败 + /// + public string Recognition { get; set; } + /// + /// 缩略媒体ID + /// + public string ThumbMediaId { get; set; } + /// + /// 地理位置维度 + /// + public string Location_X { get; set; } + /// + /// 地理位置经度 + /// + public string Location_Y { get; set; } + /// + /// 地图缩放大小 + /// + public string Scale { get; set; } + /// + /// 地理位置信息 + /// + public string Label { get; set; } + /// + /// 消息标题 + /// + public string Title { get; set; } + /// + /// 消息描述 + /// + public string Description { get; set; } + /// + /// 消息链接 + /// + public string Url { get; set; } + /// + /// 二维码的ticket,可用来换取二维码图片 + /// + public string Ticket { get; set; } + /// + /// 地理位置纬度 + /// + public string Latitude { get; set; } + /// + /// 地理位置经度 + /// + public string Longitude { get; set; } + /// + /// 地理位置精度 + /// + public string Precision { get; set; } + } +} diff --git a/Blog.Core.Publish.Docker.Jenkins.sh b/Blog.Core.Publish.Docker.Jenkins.sh index 42f074a7..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/net5.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 eb01105c..7599f204 100644 --- a/Blog.Core.Publish.Linux.sh +++ b/Blog.Core.Publish.Linux.sh @@ -1,6 +1,9 @@ -git pull; -rm -rf .PublishFiles; + +find .PublishFiles/ -type f -and ! -path '*/wwwroot/images/*' ! -name 'appsettings.*' |xargs rm -rf dotnet build; -dotnet publish -o /home/Blog.Core/Blog.Core.Api/bin/Debug/net5.0; -cp -r /home/Blog.Core/Blog.Core.Api/bin/Debug/net5.0 .PublishFiles; +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 84cfccf6..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\net5.0\ +dotnet publish -o ..\Blog.Core.Api\bin\Debug\net7.0\ md ..\.PublishFiles -xcopy ..\Blog.Core.Api\bin\Debug\net5.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 55fd0e11..3a1c1ee8 100644 --- a/Blog.Core.Repository/BASE/BaseRepository.cs +++ b/Blog.Core.Repository/BASE/BaseRepository.cs @@ -1,66 +1,82 @@ using Blog.Core.Common; using Blog.Core.Common.DB; using Blog.Core.IRepository.Base; -using Blog.Core.IRepository.UnitOfWork; 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; using System.Data; -using System.Linq; using System.Linq.Expressions; +using System.Reflection; using System.Threading.Tasks; namespace Blog.Core.Repository.Base { public class BaseRepository : IBaseRepository where TEntity : class, new() { - private readonly IUnitOfWork _unitOfWork; - private SqlSugarClient _dbBase; + private readonly IUnitOfWorkManage _unitOfWorkManage; + private readonly SqlSugarScope _dbBase; private ISqlSugarClient _db { get { - /* 如果要开启多库支持, - * 1、在appsettings.json 中开启MutiDBEnabled节点为true,必填 - * 2、设置一个主连接的数据库ID,节点MainDB,对应的连接字符串的Enabled也必须true,必填 - */ - if (Appsettings.app(new string[] { "MutiDBEnabled" }).ObjToBool()) + ISqlSugarClient db = _dbBase; + + //修改使用 model备注字段作为切换数据库条件,使用sqlsugar TenantAttribute存放数据库ConnId + //参考 https://www.donet5.com/Home/Doc?typeId=2246 + var tenantAttr = typeof(TEntity).GetCustomAttribute(); + if (tenantAttr != null) { - if (typeof(TEntity).GetTypeInfo().GetCustomAttributes(typeof(SugarTable), true).FirstOrDefault((x => x.GetType() == typeof(SugarTable))) is SugarTable sugarTable && !string.IsNullOrEmpty(sugarTable.TableDescription)) - { - _dbBase.ChangeDatabase(sugarTable.TableDescription.ToLower()); - } - else + //统一处理 configId 小写 + db = _dbBase.GetConnectionScope(tenantAttr.configId.ToString().ToLower()); + return db; + } + + //多租户 + var mta = typeof(TEntity).GetCustomAttribute(); + if (mta is { TenantType: TenantTypeEnum.Db }) + { + //获取租户信息 租户信息可以提前缓存下来 + if (App.User is { TenantId: > 0 }) { - _dbBase.ChangeDatabase(MainDb.CurrentDbConnId.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); + } } } - return _dbBase; + return db; } } - public ISqlSugarClient Db - { - get { return _db; } - } + public ISqlSugarClient Db => _db; - public BaseRepository(IUnitOfWork unitOfWork) + public BaseRepository(IUnitOfWorkManage unitOfWorkManage) { - _unitOfWork = unitOfWork; - _dbBase = unitOfWork.GetDbClient(); + _unitOfWorkManage = unitOfWorkManage; + _dbBase = unitOfWorkManage.GetDbClient(); } - public async Task QueryById(object objId) { //return await Task.Run(() => _db.Queryable().InSingle(objId)); return await _db.Queryable().In(objId).SingleAsync(); } + /// /// 功能描述:根据ID查询一条数据 /// 作  者:Blog.Core @@ -71,7 +87,7 @@ public async Task QueryById(object objId) public async Task QueryById(object objId, bool blnUseCache = false) { //return await Task.Run(() => _db.Queryable().WithCacheIF(blnUseCache).InSingle(objId)); - return await _db.Queryable().WithCacheIF(blnUseCache).In(objId).SingleAsync(); + return await _db.Queryable().WithCacheIF(blnUseCache, 10).In(objId).SingleAsync(); } /// @@ -91,37 +107,36 @@ 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类型,这里你可以根据你的业务需要进行处理 //return (int)i; var insert = _db.Insertable(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(); } } @@ -130,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(); } /// @@ -148,17 +163,24 @@ 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 strWhere) + public async Task Update(TEntity entity, string where) { - //return await Task.Run(() => _db.Updateable(entity).Where(strWhere).ExecuteCommand() > 0); - return await _db.Updateable(entity).Where(strWhere).ExecuteCommandHasChangeAsync(); + return await _db.Updateable(entity).Where(where).ExecuteCommandHasChangeAsync(); } - public async Task Update(string strSql, SugarParameter[] parameters = null) + public async Task Update(string sql, SugarParameter[] parameters = null) { - //return await Task.Run(() => _db.Ado.ExecuteCommand(strSql, parameters) > 0); - return await _db.Ado.ExecuteCommandAsync(strSql, parameters) > 0; + return await _db.Ado.ExecuteCommandAsync(sql, parameters) > 0; } public async Task Update(object operateAnonymousObjects) @@ -167,40 +189,28 @@ public async Task Update(object operateAnonymousObjects) } public async Task Update( - TEntity entity, - List lstColumns = null, - List lstIgnoreColumns = null, - string strWhere = "" - ) - { - //IUpdateable up = await Task.Run(() => _db.Updateable(entity)); - //if (lstIgnoreColumns != null && lstIgnoreColumns.Count > 0) - //{ - // up = await Task.Run(() => up.IgnoreColumns(it => lstIgnoreColumns.Contains(it))); - //} - //if (lstColumns != null && lstColumns.Count > 0) - //{ - // up = await Task.Run(() => up.UpdateColumns(it => lstColumns.Contains(it))); - //} - //if (!string.IsNullOrEmpty(strWhere)) - //{ - // up = await Task.Run(() => up.Where(strWhere)); - //} - //return await Task.Run(() => up.ExecuteCommand()) > 0; - + 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(strWhere)) + + if (!string.IsNullOrEmpty(where)) { - up = up.Where(strWhere); + up = up.Where(where); } + return await up.ExecuteCommandHasChangeAsync(); } @@ -211,8 +221,6 @@ public async Task Update( /// public async Task Delete(TEntity entity) { - //var i = await Task.Run(() => _db.Deleteable(entity).ExecuteCommand()); - //return i > 0; return await _db.Deleteable(entity).ExecuteCommandHasChangeAsync(); } @@ -223,9 +231,7 @@ public async Task Delete(TEntity entity) /// public async Task DeleteById(object id) { - //var i = await Task.Run(() => _db.Deleteable(id).ExecuteCommand()); - //return i > 0; - return await _db.Deleteable(id).ExecuteCommandHasChangeAsync(); + return await _db.Deleteable().In(id).ExecuteCommandHasChangeAsync(); } /// @@ -235,13 +241,10 @@ public async Task DeleteById(object id) /// public async Task DeleteByIds(object[] ids) { - //var i = await Task.Run(() => _db.Deleteable().In(ids).ExecuteCommand()); - //return i > 0; return await _db.Deleteable().In(ids).ExecuteCommandHasChangeAsync(); } - /// /// 功能描述:查询所有数据 /// 作  者:Blog.Core @@ -256,12 +259,11 @@ public async Task> Query() /// 功能描述:查询数据列表 /// 作  者:Blog.Core /// - /// 条件 + /// 条件 /// 数据列表 - public async Task> Query(string strWhere) + public async Task> Query(string where) { - //return await Task.Run(() => _db.Queryable().WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).ToList()); - return await _db.Queryable().WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).ToListAsync(); + return await _db.Queryable().WhereIF(!string.IsNullOrEmpty(where), where).ToListAsync(); } /// @@ -294,11 +296,11 @@ public async Task> Query(Expression /// 过滤条件 /// 查询实体条件 - /// 排序条件 + /// 排序条件 /// - public async Task> Query(Expression> expression, Expression> whereExpression, string strOrderByFileds) + public async Task> Query(Expression> expression, Expression> whereExpression, string orderByFields) { - return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(whereExpression != null, whereExpression).Select(expression).ToListAsync(); + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields).WhereIF(whereExpression != null, whereExpression).Select(expression).ToListAsync(); } /// @@ -306,13 +308,13 @@ public async Task> Query(Expression /// 条件表达式 - /// 排序字段,如name asc,age desc + /// 排序字段,如name asc,age desc /// 数据列表 - public async Task> Query(Expression> whereExpression, string strOrderByFileds) + public async Task> Query(Expression> whereExpression, string orderByFields) { - //return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(whereExpression != null, whereExpression).ToList()); - return await _db.Queryable().WhereIF(whereExpression != null, whereExpression).OrderByIF(strOrderByFileds != null, strOrderByFileds).ToListAsync(); + return await _db.Queryable().WhereIF(whereExpression != null, whereExpression).OrderByIF(orderByFields != null, orderByFields).ToListAsync(); } + /// /// 功能描述:查询一个列表 /// @@ -330,13 +332,12 @@ public async Task> Query(Expression> whereExpr /// 功能描述:查询一个列表 /// 作  者:Blog.Core /// - /// 条件 - /// 排序字段,如name asc,age desc + /// 条件 + /// 排序字段,如name asc,age desc /// 数据列表 - public async Task> Query(string strWhere, string strOrderByFileds) + public async Task> Query(string where, string orderByFields) { - //return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).ToList()); - return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).ToListAsync(); + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields).WhereIF(!string.IsNullOrEmpty(where), where).ToListAsync(); } @@ -345,55 +346,53 @@ public async Task> Query(string strWhere, string strOrderByFileds) /// 作  者:Blog.Core /// /// 条件表达式 - /// 前N条 - /// 排序字段,如name asc,age desc + /// 前N条 + /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( Expression> whereExpression, - int intTop, - string strOrderByFileds) + int top, + string orderByFields) { - //return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(whereExpression != null, whereExpression).Take(intTop).ToList()); - return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(whereExpression != null, whereExpression).Take(intTop).ToListAsync(); + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields).WhereIF(whereExpression != null, whereExpression).Take(top).ToListAsync(); } /// /// 功能描述:查询前N条数据 /// 作  者:Blog.Core /// - /// 条件 - /// 前N条 - /// 排序字段,如name asc,age desc + /// 条件 + /// 前N条 + /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( - string strWhere, - int intTop, - string strOrderByFileds) + string where, + int top, + string orderByFields) { - //return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).Take(intTop).ToList()); - return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).Take(intTop).ToListAsync(); + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields).WhereIF(!string.IsNullOrEmpty(where), where).Take(top).ToListAsync(); } /// /// 根据sql语句查询 /// - /// 完整的sql语句 + /// 完整的sql语句 /// 参数 /// 泛型集合 - public async Task> QuerySql(string strSql, SugarParameter[] parameters = null) + public async Task> QuerySql(string sql, SugarParameter[] parameters = null) { - return await _db.Ado.SqlQueryAsync(strSql, parameters); + return await _db.Ado.SqlQueryAsync(sql, parameters); } /// /// 根据sql语句查询 /// - /// 完整的sql语句 + /// 完整的sql语句 /// 参数 /// DataTable - public async Task QueryTable(string strSql, SugarParameter[] parameters = null) + public async Task QueryTable(string sql, SugarParameter[] parameters = null) { - return await _db.Ado.GetDataTableAsync(strSql, parameters); + return await _db.Ado.GetDataTableAsync(sql, parameters); } /// @@ -401,61 +400,57 @@ public async Task QueryTable(string strSql, SugarParameter[] paramete /// 作  者:Blog.Core /// /// 条件表达式 - /// 页码(下标0) - /// 页大小 - /// 排序字段,如name asc,age desc + /// 页码(下标0) + /// 页大小 + /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( Expression> whereExpression, - int intPageIndex, - int intPageSize, - string strOrderByFileds) + int pageIndex, + int pageSize, + string orderByFields) { - //return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(whereExpression != null, whereExpression).ToPageList(intPageIndex, intPageSize)); - return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(whereExpression != null, whereExpression).ToPageListAsync(intPageIndex, intPageSize); + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression).ToPageListAsync(pageIndex, pageSize); } /// /// 功能描述:分页查询 /// 作  者:Blog.Core /// - /// 条件 - /// 页码(下标0) - /// 页大小 - /// 排序字段,如name asc,age desc + /// 条件 + /// 页码(下标0) + /// 页大小 + /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( - string strWhere, - int intPageIndex, - int intPageSize, - - string strOrderByFileds) + string where, + int pageIndex, + int pageSize, + string orderByFields) { - //return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).ToPageList(intPageIndex, intPageSize)); - return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).ToPageListAsync(intPageIndex, intPageSize); + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(!string.IsNullOrEmpty(where), where).ToPageListAsync(pageIndex, pageSize); } - /// /// 分页查询[使用版本,其他分页未测试] /// /// 条件表达式 - /// 页码(下标0) - /// 页大小 - /// 排序字段,如name asc,age desc + /// 页码(下标0) + /// 页大小 + /// 排序字段,如name asc,age desc /// - public async Task> QueryPage(Expression> whereExpression, int intPageIndex = 1, int intPageSize = 20, string strOrderByFileds = null) + 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(strOrderByFileds), strOrderByFileds) - .WhereIF(whereExpression != null, whereExpression) - .ToPageListAsync(intPageIndex, intPageSize, totalCount); + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); - int pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intPageSize.ObjToDecimal())).ObjToInt(); - return new PageModel() { dataCount = totalCount, pageCount = pageCount, page = intPageIndex, PageSize = intPageSize, data = list }; + return new PageModel(pageIndex, totalCount, pageSize, list); } @@ -479,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(); } @@ -492,27 +488,25 @@ public async Task> QueryMuch( /// 关联表达式 /// 返回表达式 /// 查询表达式 - /// 页码 - /// 页大小 - /// 排序字段 + /// 页码 + /// 页大小 + /// 排序字段 /// public async Task> QueryTabsPage( Expression> joinExpression, Expression> selectExpression, Expression> whereExpression, - int intPageIndex = 1, - int intPageSize = 20, - string strOrderByFileds = null) + int pageIndex = 1, + int pageSize = 20, + string orderByFields = null) { - RefAsync totalCount = 0; var list = await _db.Queryable(joinExpression) - .Select(selectExpression) - .OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds) - .WhereIF(whereExpression != null, whereExpression) - .ToPageListAsync(intPageIndex, intPageSize, totalCount); - int pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intPageSize.ObjToDecimal())).ObjToInt(); - return new PageModel() { dataCount = totalCount, pageCount = pageCount, page = intPageIndex, PageSize = intPageSize, data = list }; + .Select(selectExpression) + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); + return new PageModel(pageIndex, totalCount, pageSize, list); } /// @@ -524,28 +518,27 @@ public async Task> QueryTabsPage( /// 关联表达式 /// 返回表达式 /// 查询表达式 - /// 页码 - /// 页大小 - /// 排序字段 + /// group表达式 + /// 页码 + /// 页大小 + /// 排序字段 /// public async Task> QueryTabsPage( Expression> joinExpression, Expression> selectExpression, Expression> whereExpression, Expression> groupExpression, - int intPageIndex = 1, - int intPageSize = 20, - string strOrderByFileds = null) + int pageIndex = 1, + int pageSize = 20, + string orderByFields = null) { - RefAsync totalCount = 0; var list = await _db.Queryable(joinExpression).GroupBy(groupExpression) - .Select(selectExpression) - .OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds) - .WhereIF(whereExpression != null, whereExpression) - .ToPageListAsync(intPageIndex, intPageSize, totalCount); - int pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intPageSize.ObjToDecimal())).ObjToInt(); - return new PageModel() { dataCount = totalCount, pageCount = pageCount, page = intPageIndex, PageSize = intPageSize, data = list }; + .Select(selectExpression) + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); + return new PageModel(pageIndex, totalCount, pageSize, list); } //var exp = Expressionable.Create() @@ -567,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 1d74d9af..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,14 +69,20 @@ public interface IBaseRepository where TEntity : class /// /// Task Update(TEntity model); + /// + /// 更新model + /// + /// + /// + Task Update(List model); /// /// 根据model,更新,带where条件 /// /// - /// + /// /// - Task Update(TEntity entity, string strWhere); + Task Update(TEntity entity, string where); Task Update(object operateAnonymousObjects); /// @@ -85,9 +91,9 @@ public interface IBaseRepository where TEntity : class /// /// /// - /// + /// /// - Task Update(TEntity entity, List lstColumns = null, List lstIgnoreColumns = null, string strWhere = ""); + Task Update(TEntity entity, List lstColumns = null, List lstIgnoreColumns = null, string where = ""); /// /// 查询 @@ -98,9 +104,9 @@ public interface IBaseRepository where TEntity : class /// /// 带sql where查询 /// - /// + /// /// - Task> Query(string strWhere); + Task> Query(string where); /// /// 根据表达式查询 @@ -123,31 +129,31 @@ public interface IBaseRepository where TEntity : class /// /// /// - /// + /// /// - Task> Query(Expression> expression, Expression> whereExpression, string strOrderByFileds); - Task> Query(Expression> whereExpression, string strOrderByFileds); + Task> Query(Expression> expression, Expression> whereExpression, string orderByFields); + Task> Query(Expression> whereExpression, string orderByFields); Task> Query(Expression> whereExpression, Expression> orderByExpression, bool isAsc = true); - Task> Query(string strWhere, string strOrderByFileds); + Task> Query(string where, string orderByFields); - Task> Query(Expression> whereExpression, int intTop, string strOrderByFileds); - Task> Query(string strWhere, int intTop, string strOrderByFileds); - Task> QuerySql(string strSql, SugarParameter[] parameters = null); - Task QueryTable(string strSql, SugarParameter[] parameters = null); + Task> Query(Expression> whereExpression, int intTop, string orderByFields); + Task> Query(string where, int intTop, string orderByFields); + Task> QuerySql(string sql, SugarParameter[] parameters = null); + Task QueryTable(string sql, SugarParameter[] parameters = null); Task> Query( - Expression> whereExpression, int intPageIndex, int intPageSize, string strOrderByFileds); - Task> Query(string strWhere, int intPageIndex, int intPageSize, string strOrderByFileds); + Expression> whereExpression, int pageIndex, int pageSize, string orderByFields); + Task> Query(string where, int pageIndex, int pageSize, string orderByFields); /// /// 根据表达式,排序字段,分页查询 /// /// - /// - /// - /// + /// + /// + /// /// - Task> QueryPage(Expression> whereExpression, int intPageIndex = 1, int intPageSize = 20, string strOrderByFileds = null); + Task> QueryPage(Expression> whereExpression, int pageIndex = 1, int pageSize = 20, string orderByFields = null); /// /// 三表联查 @@ -174,17 +180,17 @@ Task> QueryMuch( /// /// /// - /// - /// - /// + /// + /// + /// /// Task> QueryTabsPage( Expression> joinExpression, Expression> selectExpression, Expression> whereExpression, - int intPageIndex = 1, - int intPageSize = 20, - string strOrderByFileds = null); + int pageIndex = 1, + int pageSize = 20, + string orderByFields = null); /// /// 两表联合查询-分页-分组 @@ -196,17 +202,57 @@ Task> QueryTabsPage( /// /// /// - /// - /// - /// + /// + /// + /// /// Task> QueryTabsPage( Expression> joinExpression, Expression> selectExpression, Expression> whereExpression, Expression> groupExpression, - int intPageIndex = 1, - int intPageSize = 20, - string strOrderByFileds = null); + 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 db11e99d..fff34572 100644 --- a/Blog.Core.Repository/Blog.Core.Repository.csproj +++ b/Blog.Core.Repository/Blog.Core.Repository.csproj @@ -1,14 +1,11 @@  - - net5.0 - - - - + + + @@ -21,10 +18,11 @@ - + + 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/MongoRepository/IMongoBaseRepository.cs b/Blog.Core.Repository/MongoRepository/IMongoBaseRepository.cs index 28278d39..627ffff7 100644 --- a/Blog.Core.Repository/MongoRepository/IMongoBaseRepository.cs +++ b/Blog.Core.Repository/MongoRepository/IMongoBaseRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using MongoDB.Driver; +using System.Collections.Generic; using System.Threading.Tasks; namespace Blog.Core.Repository.MongoRepository @@ -9,5 +10,7 @@ public interface IMongoBaseRepository where TEntity : class Task AddAsync(TEntity entity); Task GetAsync(int Id); Task> GetListAsync(); + Task GetByObjectIdAsync(string Id); + Task> GetListFilterAsync(FilterDefinition filter); } } diff --git a/Blog.Core.Repository/MongoRepository/MongoBaseRepository.cs b/Blog.Core.Repository/MongoRepository/MongoBaseRepository.cs index 20899f71..feca1f51 100644 --- a/Blog.Core.Repository/MongoRepository/MongoBaseRepository.cs +++ b/Blog.Core.Repository/MongoRepository/MongoBaseRepository.cs @@ -31,11 +31,27 @@ public async Task GetAsync(int Id) .FirstOrDefaultAsync(); } + public async Task GetByObjectIdAsync(string Id) + { + var filter = Builders.Filter.Eq("_id", ObjectId.Parse(Id)); + + return await _context.Db.GetCollection(typeof(TEntity).Name) + .Find(filter) + .FirstOrDefaultAsync(); + } + public async Task> GetListAsync() { return await _context.Db.GetCollection(typeof(TEntity).Name) .Find(new BsonDocument()) .ToListAsync(); } + + public async Task> GetListFilterAsync(FilterDefinition filter) + { + + return await _context.Db.GetCollection(typeof(TEntity).Name) + .Find(filter).ToListAsync(); + } } } diff --git a/Blog.Core.Repository/MongoRepository/MongoDbContext.cs b/Blog.Core.Repository/MongoRepository/MongoDbContext.cs index 29357fec..6eadedca 100644 --- a/Blog.Core.Repository/MongoRepository/MongoDbContext.cs +++ b/Blog.Core.Repository/MongoRepository/MongoDbContext.cs @@ -11,8 +11,8 @@ public class MongoDbContext public MongoDbContext() { - var client = new MongoClient(Appsettings.app(new string[] { "Mongo", "ConnectionString" })); - _database = client.GetDatabase(Appsettings.app(new string[] { "Mongo", "Database" })); + var client = new MongoClient(AppSettings.app(new string[] { "Mongo", "ConnectionString" })); + _database = client.GetDatabase(AppSettings.app(new string[] { "Mongo", "Database" })); } public IMongoDatabase Db diff --git a/Blog.Core.Repository/RoleModulePermissionRepository.cs b/Blog.Core.Repository/RoleModulePermissionRepository.cs index 1289615a..9438ff50 100644 --- a/Blog.Core.Repository/RoleModulePermissionRepository.cs +++ b/Blog.Core.Repository/RoleModulePermissionRepository.cs @@ -1,10 +1,10 @@ -using Blog.Core.Repository.Base; -using Blog.Core.Model.Models; using Blog.Core.IRepository; +using Blog.Core.Model.Models; +using Blog.Core.Repository.Base; +using SqlSugar; using System.Collections.Generic; using System.Threading.Tasks; -using SqlSugar; -using Blog.Core.IRepository.UnitOfWork; +using Blog.Core.Repository.UnitOfWorks; namespace Blog.Core.Repository { @@ -13,7 +13,7 @@ namespace Blog.Core.Repository /// public class RoleModulePermissionRepository : BaseRepository, IRoleModulePermissionRepository { - public RoleModulePermissionRepository(IUnitOfWork unitOfWork) : base(unitOfWork) + public RoleModulePermissionRepository(IUnitOfWorkManage unitOfWorkManage) : base(unitOfWorkManage) { } @@ -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/UnitOfWork/IUnitOfWork.cs b/Blog.Core.Repository/UnitOfWork/IUnitOfWork.cs deleted file mode 100644 index b764cf5e..00000000 --- a/Blog.Core.Repository/UnitOfWork/IUnitOfWork.cs +++ /dev/null @@ -1,14 +0,0 @@ -using SqlSugar; - -namespace Blog.Core.IRepository.UnitOfWork -{ - public interface IUnitOfWork - { - SqlSugarClient GetDbClient(); - - void BeginTran(); - - void CommitTran(); - void RollbackTran(); - } -} diff --git a/Blog.Core.Repository/UnitOfWork/UnitOfWork.cs b/Blog.Core.Repository/UnitOfWork/UnitOfWork.cs deleted file mode 100644 index 4eb2a695..00000000 --- a/Blog.Core.Repository/UnitOfWork/UnitOfWork.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Blog.Core.IRepository.UnitOfWork; -using Microsoft.Extensions.Logging; -using SqlSugar; -using System; - -namespace Blog.Core.Repository.UnitOfWork -{ - public class UnitOfWork : IUnitOfWork - { - private readonly ISqlSugarClient _sqlSugarClient; - private readonly ILogger _logger; - - public UnitOfWork(ISqlSugarClient sqlSugarClient, ILogger logger) - { - _sqlSugarClient = sqlSugarClient; - _logger = logger; - } - - /// - /// 获取DB,保证唯一性 - /// - /// - public SqlSugarClient GetDbClient() - { - // 必须要as,后边会用到切换数据库操作 - return _sqlSugarClient as SqlSugarClient; - } - - public void BeginTran() - { - GetDbClient().BeginTran(); - } - - public void CommitTran() - { - try - { - GetDbClient().CommitTran(); // - } - catch (Exception ex) - { - GetDbClient().RollbackTran(); - _logger.LogError($"{ex.Message}\r\n{ex.InnerException}"); - } - } - - public void RollbackTran() - { - GetDbClient().RollbackTran(); - } - - } - -} diff --git a/Blog.Core.Repository/UnitOfWorks/IUnitOfWorkManage.cs b/Blog.Core.Repository/UnitOfWorks/IUnitOfWorkManage.cs new file mode 100644 index 00000000..535bd145 --- /dev/null +++ b/Blog.Core.Repository/UnitOfWorks/IUnitOfWorkManage.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using SqlSugar; + +namespace Blog.Core.Repository.UnitOfWorks +{ + public interface IUnitOfWorkManage + { + SqlSugarScope GetDbClient(); + int TranCount { get; } + + UnitOfWork CreateUnitOfWork(); + + void BeginTran(); + void BeginTran(MethodInfo method); + void CommitTran(); + void CommitTran(MethodInfo method); + void RollbackTran(); + void RollbackTran(MethodInfo method); + } +} \ No newline at end of file diff --git a/Blog.Core.Repository/UnitOfWorks/UnitOfWork.cs b/Blog.Core.Repository/UnitOfWorks/UnitOfWork.cs new file mode 100644 index 00000000..44124a77 --- /dev/null +++ b/Blog.Core.Repository/UnitOfWorks/UnitOfWork.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.Extensions.Logging; +using SqlSugar; + +namespace Blog.Core.Repository.UnitOfWorks; + +public class UnitOfWork : IDisposable +{ + public ILogger Logger { get; set; } + public ISqlSugarClient Db { get; internal set; } + + public ITenant Tenant { get; internal set; } + + public bool IsTran { get; internal set; } + + public bool IsCommit { get; internal set; } + + public bool IsClose { get; internal set; } + + public void Dispose() + { + if (this.IsTran && !this.IsCommit) + { + Logger.LogDebug("UnitOfWork RollbackTran"); + this.Tenant.RollbackTran(); + } + + if (this.Db.Ado.Transaction != null || this.IsClose) + return; + this.Db.Close(); + } + + public bool Commit() + { + if (this.IsTran && !this.IsCommit) + { + Logger.LogDebug("UnitOfWork CommitTran"); + this.Tenant.CommitTran(); + this.IsCommit = true; + } + + if (this.Db.Ado.Transaction == null && !this.IsClose) + { + this.Db.Close(); + this.IsClose = true; + } + + return this.IsCommit; + } +} \ No newline at end of file diff --git a/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs b/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs new file mode 100644 index 00000000..cfaffd9b --- /dev/null +++ b/Blog.Core.Repository/UnitOfWorks/UnitOfWorkManage.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using System.Threading; +using Blog.Core.Common.Extensions; +using Microsoft.Extensions.Logging; +using SqlSugar; + +namespace Blog.Core.Repository.UnitOfWorks +{ + public class UnitOfWorkManage : IUnitOfWorkManage + { + private readonly ILogger _logger; + private readonly ISqlSugarClient _sqlSugarClient; + + private int _tranCount { get; set; } + public int TranCount => _tranCount; + public readonly ConcurrentStack TranStack = new(); + + public UnitOfWorkManage(ISqlSugarClient sqlSugarClient, ILogger logger) + { + _sqlSugarClient = sqlSugarClient; + _logger = logger; + _tranCount = 0; + } + + /// + /// 获取DB,保证唯一性 + /// + /// + public SqlSugarScope GetDbClient() + { + // 必须要as,后边会用到切换数据库操作 + return _sqlSugarClient as SqlSugarScope; + } + + + public UnitOfWork CreateUnitOfWork() + { + UnitOfWork uow = new UnitOfWork(); + uow.Logger = _logger; + uow.Db = _sqlSugarClient; + uow.Tenant = (ITenant) _sqlSugarClient; + uow.IsTran = true; + + uow.Db.Open(); + uow.Tenant.BeginTran(); + _logger.LogDebug("UnitOfWork Begin"); + return uow; + } + + 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() + { + lock (this) + { + _tranCount--; + if (_tranCount == 0) + { + try + { + GetDbClient().CommitTran(); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + GetDbClient().RollbackTran(); + } + } + } + } + + public void CommitTran(MethodInfo method) + { + lock (this) + { + string result = ""; + while (!TranStack.IsEmpty && !TranStack.TryPeek(out result)) + { + Thread.Sleep(1); + } + + + if (result == method.GetFullName()) + { + try + { + GetDbClient().CommitTran(); + + _logger.LogDebug($"Commit Transaction"); + Console.WriteLine($"Commit Transaction"); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + GetDbClient().RollbackTran(); + _logger.LogDebug($"Commit Error , Rollback Transaction"); + } + finally + { + while (!TranStack.TryPop(out _)) + { + Thread.Sleep(1); + } + + _tranCount = TranStack.Count; + } + } + } + } + + public void RollbackTran() + { + lock (this) + { + _tranCount--; + GetDbClient().RollbackTran(); + } + } + + public void RollbackTran(MethodInfo method) + { + lock (this) + { + string result = ""; + while (!TranStack.IsEmpty && !TranStack.TryPeek(out result)) + { + Thread.Sleep(1); + } + + if (result == method.GetFullName()) + { + GetDbClient().RollbackTran(); + _logger.LogDebug($"Rollback Transaction"); + Console.WriteLine($"Rollback Transaction"); + while (!TranStack.TryPop(out _)) + { + Thread.Sleep(1); + } + + _tranCount = TranStack.Count; + } + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj b/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj index b5c2e0ed..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 @@ - - net5.0 - - - - - - - + + + + + + diff --git a/Blog.Core.Serilog.Es/HttpInfo/ParamsHelper.cs b/Blog.Core.Serilog.Es/HttpInfo/ParamsHelper.cs index 3b39c0bf..fbbd9e39 100644 --- a/Blog.Core.Serilog.Es/HttpInfo/ParamsHelper.cs +++ b/Blog.Core.Serilog.Es/HttpInfo/ParamsHelper.cs @@ -72,7 +72,7 @@ public static string GetParams(HttpContext context) } return data; } - catch(Exception ex) + catch(Exception) { return string.Empty; } 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/AccessTrendLogServices.cs b/Blog.Core.Services/AccessTrendLogServices.cs new file mode 100644 index 00000000..ee9ee378 --- /dev/null +++ b/Blog.Core.Services/AccessTrendLogServices.cs @@ -0,0 +1,12 @@ +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; + +namespace Blog.Core.Services +{ + public partial class AccessTrendLogServices : BaseServices, IAccessTrendLogServices + { + + } +} diff --git a/Blog.Core.Services/AdvertisementServices.cs b/Blog.Core.Services/AdvertisementServices.cs index 6f2cec8b..348048e8 100644 --- a/Blog.Core.Services/AdvertisementServices.cs +++ b/Blog.Core.Services/AdvertisementServices.cs @@ -7,20 +7,13 @@ namespace Blog.Core.Services { public class AdvertisementServices : BaseServices, IAdvertisementServices { - IBaseRepository _dal; - public AdvertisementServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - 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 ba553082..44042382 100644 --- a/Blog.Core.Services/BASE/BaseServices.cs +++ b/Blog.Core.Services/BASE/BaseServices.cs @@ -1,4 +1,5 @@ -using Blog.Core.IRepository.Base; +using Blog.Core.Common.Helper; +using Blog.Core.IRepository.Base; using Blog.Core.IServices.BASE; using Blog.Core.Model; using SqlSugar; @@ -12,13 +13,21 @@ namespace Blog.Core.Services.BASE { public class BaseServices : IBaseServices where TEntity : class, new() { + 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 @@ -47,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); } @@ -57,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); } @@ -71,23 +80,34 @@ public async Task Update(TEntity entity) { return await BaseDal.Update(entity); } - public async Task Update(TEntity entity, string strWhere) + /// + /// 更新实体数据 + /// + /// 博文实体类 + /// + public async Task Update(List entity) { - return await BaseDal.Update(entity, strWhere); + 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 strWhere = "" - ) + TEntity entity, + List lstColumns = null, + List lstIgnoreColumns = null, + string where = "" + ) { - return await BaseDal.Update(entity, lstColumns, lstIgnoreColumns, strWhere); + return await BaseDal.Update(entity, lstColumns, lstIgnoreColumns, where); } @@ -122,7 +142,6 @@ public async Task DeleteByIds(object[] ids) } - /// /// 功能描述:查询所有数据 /// 作  者:AZLinli.Blog.Core @@ -137,11 +156,11 @@ public async Task> Query() /// 功能描述:查询数据列表 /// 作  者:AZLinli.Blog.Core /// - /// 条件 + /// 条件 /// 数据列表 - public async Task> Query(string strWhere) + public async Task> Query(string where) { - return await BaseDal.Query(strWhere); + return await BaseDal.Query(where); } /// @@ -174,11 +193,11 @@ public async Task> Query(Expression /// 过滤条件 /// 查询实体条件 - /// 排序条件 + /// 排序条件 /// - public async Task> Query(Expression> expression, Expression> whereExpression,string strOrderByFileds) + public async Task> Query(Expression> expression, Expression> whereExpression, string orderByFileds) { - return await BaseDal.Query(expression, whereExpression, strOrderByFileds); + return await BaseDal.Query(expression, whereExpression, orderByFileds); } /// @@ -193,73 +212,72 @@ public async Task> Query(Expression> whereExpr return await BaseDal.Query(whereExpression, orderByExpression, isAsc); } - public async Task> Query(Expression> whereExpression, string strOrderByFileds) + public async Task> Query(Expression> whereExpression, string orderByFileds) { - return await BaseDal.Query(whereExpression, strOrderByFileds); + return await BaseDal.Query(whereExpression, orderByFileds); } /// /// 功能描述:查询一个列表 /// 作  者:AZLinli.Blog.Core /// - /// 条件 - /// 排序字段,如name asc,age desc + /// 条件 + /// 排序字段,如name asc,age desc /// 数据列表 - public async Task> Query(string strWhere, string strOrderByFileds) + public async Task> Query(string where, string orderByFileds) { - return await BaseDal.Query(strWhere, strOrderByFileds); + return await BaseDal.Query(where, orderByFileds); } /// /// 根据sql语句查询 /// - /// 完整的sql语句 + /// 完整的sql语句 /// 参数 /// 泛型集合 - public async Task> QuerySql(string strSql, SugarParameter[] parameters = null) + public async Task> QuerySql(string sql, SugarParameter[] parameters = null) { - return await BaseDal.QuerySql(strSql, parameters); - + return await BaseDal.QuerySql(sql, parameters); } /// /// 根据sql语句查询 /// - /// 完整的sql语句 + /// 完整的sql语句 /// 参数 /// DataTable - public async Task QueryTable(string strSql, SugarParameter[] parameters = null) + public async Task QueryTable(string sql, SugarParameter[] parameters = null) { - return await BaseDal.QueryTable(strSql, parameters); - + return await BaseDal.QueryTable(sql, parameters); } + /// /// 功能描述:查询前N条数据 /// 作  者:AZLinli.Blog.Core /// /// 条件表达式 - /// 前N条 - /// 排序字段,如name asc,age desc + /// 前N条 + /// 排序字段,如name asc,age desc /// 数据列表 - public async Task> Query(Expression> whereExpression, int intTop, string strOrderByFileds) + public async Task> Query(Expression> whereExpression, int top, string orderByFileds) { - return await BaseDal.Query(whereExpression, intTop, strOrderByFileds); + return await BaseDal.Query(whereExpression, top, orderByFileds); } /// /// 功能描述:查询前N条数据 /// 作  者:AZLinli.Blog.Core /// - /// 条件 - /// 前N条 - /// 排序字段,如name asc,age desc + /// 条件 + /// 前N条 + /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( - string strWhere, - int intTop, - string strOrderByFileds) + string where, + int top, + string orderByFileds) { - return await BaseDal.Query(strWhere, intTop, strOrderByFileds); + return await BaseDal.Query(where, top, orderByFileds); } /// @@ -267,56 +285,97 @@ public async Task> Query( /// 作  者:AZLinli.Blog.Core /// /// 条件表达式 - /// 页码(下标0) - /// 页大小 - /// 排序字段,如name asc,age desc + /// 页码(下标0) + /// 页大小 + /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( Expression> whereExpression, - int intPageIndex, - int intPageSize, - string strOrderByFileds) + int pageIndex, + int pageSize, + string orderByFileds) { return await BaseDal.Query( - whereExpression, - intPageIndex, - intPageSize, - strOrderByFileds); + whereExpression, + pageIndex, + pageSize, + orderByFileds); } /// /// 功能描述:分页查询 /// 作  者:AZLinli.Blog.Core /// - /// 条件 - /// 页码(下标0) - /// 页大小 - /// 排序字段,如name asc,age desc + /// 条件 + /// 页码(下标0) + /// 页大小 + /// 排序字段,如name asc,age desc /// 数据列表 public async Task> Query( - string strWhere, - int intPageIndex, - int intPageSize, - string strOrderByFileds) + string where, + int pageIndex, + int pageSize, + string orderByFileds) { return await BaseDal.Query( - strWhere, - intPageIndex, - intPageSize, - strOrderByFileds); + where, + pageIndex, + pageSize, + orderByFileds); } public async Task> QueryPage(Expression> whereExpression, - int intPageIndex = 1, int intPageSize = 20, string strOrderByFileds = null) + int pageIndex = 1, int pageSize = 20, string orderByFileds = null) { return await BaseDal.QueryPage(whereExpression, - intPageIndex, intPageSize, strOrderByFileds); + 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 ca56a61a..215dde29 100644 --- a/Blog.Core.Services/Blog.Core.Services.csproj +++ b/Blog.Core.Services/Blog.Core.Services.csproj @@ -1,8 +1,5 @@  - - net5.0 - ..\Blog.Core.Api\bin\Debug\ diff --git a/Blog.Core.Services/BlogArticleServices.cs b/Blog.Core.Services/BlogArticleServices.cs index f04de9a1..67ea2b9e 100644 --- a/Blog.Core.Services/BlogArticleServices.cs +++ b/Blog.Core.Services/BlogArticleServices.cs @@ -13,7 +13,6 @@ namespace Blog.Core.Services { public class BlogArticleServices : BaseServices, IBlogArticleServices { - public IBaseRepository _dal { get; set; } IMapper _mapper; public BlogArticleServices(IMapper mapper) { @@ -24,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/DepartmentServices.cs b/Blog.Core.Services/DepartmentServices.cs new file mode 100644 index 00000000..954d0ca7 --- /dev/null +++ b/Blog.Core.Services/DepartmentServices.cs @@ -0,0 +1,15 @@ +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; +using Blog.Core.IRepository.Base; + +namespace Blog.Core.Services +{ + /// + /// DepartmentServices + /// + public class DepartmentServices : BaseServices, IDepartmentServices + { + + } +} \ No newline at end of file diff --git a/Blog.Core.Services/File/CCB_B2CPay_Util.dll b/Blog.Core.Services/File/CCB_B2CPay_Util.dll deleted file mode 100644 index dae0a266..00000000 Binary files a/Blog.Core.Services/File/CCB_B2CPay_Util.dll and /dev/null differ diff --git a/Blog.Core.Services/GuestbookServices.cs b/Blog.Core.Services/GuestbookServices.cs index 8da0e7fb..33364a51 100644 --- a/Blog.Core.Services/GuestbookServices.cs +++ b/Blog.Core.Services/GuestbookServices.cs @@ -1,26 +1,33 @@ using Blog.Core.Common; using Blog.Core.IRepository.Base; -using Blog.Core.IRepository.UnitOfWork; using Blog.Core.IServices; using Blog.Core.Model; using Blog.Core.Model.Models; using Blog.Core.Services.BASE; 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 { public class GuestbookServices : BaseServices, IGuestbookServices { - private readonly IBaseRepository _dal; - private readonly IUnitOfWork _unitOfWork; + private readonly IUnitOfWorkManage _unitOfWorkManage; private readonly IBaseRepository _passwordLibRepository; - public GuestbookServices(IUnitOfWork unitOfWork, IBaseRepository dal, 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, ISqlSugarClient db) { - this._dal = dal; - base.BaseDal = dal; - _unitOfWork = unitOfWork; + _unitOfWorkManage = unitOfWorkManage; _passwordLibRepository = passwordLibRepository; + _passwordLibServices = passwordLibServices; + _db = db; } public async Task> TestTranInRepository() @@ -29,45 +36,46 @@ public async Task> TestTranInRepository() { Console.WriteLine($""); Console.WriteLine($"事务操作开始"); - _unitOfWork.BeginTran(); - Console.WriteLine($""); - - Console.WriteLine($"insert a data into the table PasswordLib now."); - var insertPassword = await _passwordLibRepository.Add(new PasswordLib() + using (var uow = _unitOfWorkManage.CreateUnitOfWork()) { - IsDeleted = false, - plAccountName = "aaa", - plCreateTime = DateTime.Now - }); + Console.WriteLine($""); + Console.WriteLine($"insert a data into the table PasswordLib now."); + var insertPassword = await _passwordLibRepository.Add(new PasswordLib() + { + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }); - var passwords = await _passwordLibRepository.Query(d => d.IsDeleted == false); - Console.WriteLine($"second time : the count of passwords is :{passwords.Count}"); - //...... + var passwords = await _passwordLibRepository.Query(d => d.IsDeleted == false); + Console.WriteLine($"second time : the count of passwords is :{passwords.Count}"); - Console.WriteLine($""); - var guestbooks = await _dal.Query(); - Console.WriteLine($"first time : the count of guestbooks is :{guestbooks.Count}"); + //...... - int ex = 0; - Console.WriteLine($"\nThere's an exception!!"); - int throwEx = 1 / ex; + Console.WriteLine($""); + var guestbooks = await BaseDal.Query(); + Console.WriteLine($"first time : the count of guestbooks is :{guestbooks.Count}"); - Console.WriteLine($"insert a data into the table Guestbook now."); - var insertGuestbook = await _dal.Add(new Guestbook() - { - username = "bbb", - blogId = 1, - createdate = DateTime.Now, - isshow = true - }); + int ex = 0; + Console.WriteLine($"\nThere's an exception!!"); + int throwEx = 1 / ex; - guestbooks = await _dal.Query(); - Console.WriteLine($"second time : the count of guestbooks is :{guestbooks.Count}"); + Console.WriteLine($"insert a data into the table Guestbook now."); + var insertGuestbook = await BaseDal.Add(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }); + guestbooks = await BaseDal.Query(); + Console.WriteLine($"second time : the count of guestbooks is :{guestbooks.Count}"); - _unitOfWork.CommitTran(); + uow.Commit(); + } return new MessageModel() { @@ -77,11 +85,10 @@ public async Task> TestTranInRepository() } catch (Exception) { - _unitOfWork.RollbackTran(); var passwords = await _passwordLibRepository.Query(); Console.WriteLine($"third time : the count of passwords is :{passwords.Count}"); - var guestbooks = await _dal.Query(); + var guestbooks = await BaseDal.Query(); Console.WriteLine($"third time : the count of guestbooks is :{guestbooks.Count}"); return new MessageModel() @@ -114,7 +121,7 @@ public async Task TestTranInRepositoryAOP() //...... Console.WriteLine($""); - var guestbooks = await _dal.Query(); + var guestbooks = await BaseDal.Query(); Console.WriteLine($"first time : the count of guestbooks is :{guestbooks.Count}"); int ex = 0; @@ -122,7 +129,7 @@ public async Task TestTranInRepositoryAOP() int throwEx = 1 / ex; Console.WriteLine($"insert a data into the table Guestbook now."); - var insertGuestbook = await _dal.Add(new Guestbook() + var insertGuestbook = await BaseDal.Add(new Guestbook() { username = "bbb", blogId = 1, @@ -130,11 +137,144 @@ public async Task TestTranInRepositoryAOP() isshow = true }); - guestbooks = await _dal.Query(); + guestbooks = await BaseDal.Query(); Console.WriteLine($"second time : the count of guestbooks is :{guestbooks.Count}"); return true; } + /// + /// 测试使用同事务 + /// + /// + [UseTran(Propagation = Propagation.Required)] + public async Task TestTranPropagation() + { + var guestbooks = await base.Query(); + Console.WriteLine($"first time : the count of guestbooks is :{guestbooks.Count}"); + + var insertGuestbook = await base.Add(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }); + + await _passwordLibServices.TestTranPropagation2(); + + return true; + } + + + /// + /// 测试无事务 Mandatory传播机制报错 + /// + /// + public async Task TestTranPropagationNoTran() + { + var guestbooks = await base.Query(); + Console.WriteLine($"first time : the count of guestbooks is :{guestbooks.Count}"); + + var insertGuestbook = await base.Add(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }); + + await _passwordLibServices.TestTranPropagationNoTranError(); + + return true; + } + + + /// + /// 测试嵌套事务 + /// + /// + [UseTran(Propagation = Propagation.Required)] + 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", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }); + + await _passwordLibServices.TestTranPropagationTran2(); + + 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 6e39c4a8..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,13 +9,10 @@ namespace Blog.Core.IServices { public class ApplicationUserServices : BaseServices, IApplicationUserServices { - - IBaseRepository _dal; - public ApplicationUserServices(IBaseRepository dal) + public bool IsEnable() { - this._dal = dal; - base.BaseDal = dal; + var configId = typeof(ApplicationUser).GetEntityTenant(); + return Db.AsTenant().IsAnyConnection(configId); } - } } \ No newline at end of file diff --git a/Blog.Core.Services/ModulePermissionServices.cs b/Blog.Core.Services/ModulePermissionServices.cs deleted file mode 100644 index cf80e492..00000000 --- a/Blog.Core.Services/ModulePermissionServices.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Blog.Core.Services.BASE; -using Blog.Core.Model.Models; -using Blog.Core.IRepository; -using Blog.Core.IServices; -using Blog.Core.IRepository.Base; - -namespace Blog.Core.Services -{ - /// - /// ModulePermissionServices - /// - public class ModulePermissionServices : BaseServices, IModulePermissionServices - { - - IBaseRepository _dal; - public ModulePermissionServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - - } -} diff --git a/Blog.Core.Services/ModuleServices.cs b/Blog.Core.Services/ModuleServices.cs index a27c2ba8..c27a63e8 100644 --- a/Blog.Core.Services/ModuleServices.cs +++ b/Blog.Core.Services/ModuleServices.cs @@ -11,12 +11,5 @@ namespace Blog.Core.Services public class ModuleServices : BaseServices, IModuleServices { - IBaseRepository _dal; - public ModuleServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - } } diff --git a/Blog.Core.Services/OperateLogServices.cs b/Blog.Core.Services/OperateLogServices.cs index 8715b0d7..6527ba95 100644 --- a/Blog.Core.Services/OperateLogServices.cs +++ b/Blog.Core.Services/OperateLogServices.cs @@ -7,12 +7,6 @@ namespace Blog.Core.Services { public partial class OperateLogServices : BaseServices, IOperateLogServices { - IBaseRepository _dal; - public OperateLogServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } } } diff --git a/Blog.Core.Services/PasswordLibServices.cs b/Blog.Core.Services/PasswordLibServices.cs index 8aad0279..c2f39391 100644 --- a/Blog.Core.Services/PasswordLibServices.cs +++ b/Blog.Core.Services/PasswordLibServices.cs @@ -1,18 +1,90 @@ -using Blog.Core.IRepository.Base; +using System; +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; - public PasswordLibServices(IBaseRepository dal) + private readonly IUnitOfWorkManage _unitOfWorkManage; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; + + public PasswordLibServices(IBaseRepository dal, IUnitOfWorkManage unitOfWorkManage, ISqlSugarClient db) { this._dal = dal; + _unitOfWorkManage = unitOfWorkManage; + _db = db; base.BaseDal = dal; } + [UseTran(Propagation = Propagation.Required)] + public async Task TestTranPropagation2() + { + await _dal.Add(new PasswordLib() + { + PLID = IdGeneratorUtility.NextId(), + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }); + + return true; + } + + [UseTran(Propagation = Propagation.Mandatory)] + public async Task TestTranPropagationNoTranError() + { + await _dal.Add(new PasswordLib() + { + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }); + + return true; + } + + [UseTran(Propagation = Propagation.Nested)] + public async Task TestTranPropagationTran2() + { + 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; + } } -} +} \ No newline at end of file diff --git a/Blog.Core.Services/PayServices.cs b/Blog.Core.Services/PayServices.cs index 73331b2d..b257d088 100644 --- a/Blog.Core.Services/PayServices.cs +++ b/Blog.Core.Services/PayServices.cs @@ -1,6 +1,6 @@ using Blog.Core.Common; using Blog.Core.Common.Helper; -using Blog.Core.Common.StaticHelper; +using Blog.Core.Common.Static; using Blog.Core.IRepository.Base; using Blog.Core.IServices; using Blog.Core.Model; @@ -11,7 +11,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,13 +20,10 @@ namespace Blog.Core.Services { public partial class PayServices : BaseServices>, IPayServices { - IBaseRepository> _dal; IHttpContextAccessor _httpContextAccessor; ILogger _logger; - public PayServices(IBaseRepository> dal, ILogger logger, IHttpContextAccessor httpContextAccessor) + public PayServices(ILogger logger, IHttpContextAccessor httpContextAccessor) { - this._dal = dal; - base.BaseDal = dal; _logger = logger; _httpContextAccessor = httpContextAccessor; } @@ -213,26 +210,13 @@ public async Task> PayRefund(PayRefundN _logger.LogInformation("请求地址:" + sUrl); _logger.LogInformation("请求报文:" + sRequestMsg); - HttpWebRequest request = (System.Net.HttpWebRequest)HttpWebRequest.Create(sUrl); - request.Method = "POST"; - request.ContentType = "application/x-www-form-urlencoded"; - request.KeepAlive = false; - request.Connection = ""; - - //外联平台使用GB18030编码,这里进行转码处理 + HttpClient request = new HttpClient(); byte[] byteRquest = Encoding.GetEncoding("GB18030").GetBytes(sRequestMsg); - request.ContentLength = byteRquest.Length; - - //发送请求 - Stream writerStream = request.GetRequestStream(); - await writerStream.WriteAsync(byteRquest, 0, byteRquest.Length); - writerStream.Flush(); - writerStream.Close(); + ByteArrayContent bytemsg = new ByteArrayContent(byteRquest); + HttpResponseMessage resulthd = await request.PostAsync(sUrl, bytemsg); + Stream result = await resulthd.Content.ReadAsStreamAsync(); - //接收请求 - HttpWebResponse response = (HttpWebResponse)request.GetResponse(); - Stream result = response.GetResponseStream(); StreamReader readerResult = new StreamReader(result, System.Text.Encoding.GetEncoding("GB18030")); string sResult = await readerResult.ReadToEndAsync(); _logger.LogInformation("响应报文:" + sResult); @@ -257,6 +241,7 @@ public async Task> PayRefund(PayRefundN messageModel.response.AMOUNT = Xmlresult.TX_INFO?.AMOUNT; messageModel.response.PAY_AMOUNT = Xmlresult.TX_INFO?.PAY_AMOUNT; messageModel.response.ORDER_NUM = Xmlresult.TX_INFO?.ORDER_NUM; + request.Dispose(); } catch (Exception ex) { @@ -270,6 +255,7 @@ public async Task> PayRefund(PayRefundN { _logger.LogInformation($"返回数据->{JsonHelper.GetJSON>(messageModel)}"); _logger.LogInformation("退款结束"); + } return messageModel; diff --git a/Blog.Core.Services/PermissionServices.cs b/Blog.Core.Services/PermissionServices.cs index 2282a38f..6c64954d 100644 --- a/Blog.Core.Services/PermissionServices.cs +++ b/Blog.Core.Services/PermissionServices.cs @@ -11,12 +11,5 @@ namespace Blog.Core.Services public class PermissionServices : BaseServices, IPermissionServices { - IBaseRepository _dal; - public PermissionServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - } } diff --git a/Blog.Core.Services/RoleModulePermissionServices.cs b/Blog.Core.Services/RoleModulePermissionServices.cs index 04c9b0f9..d3834f89 100644 --- a/Blog.Core.Services/RoleModulePermissionServices.cs +++ b/Blog.Core.Services/RoleModulePermissionServices.cs @@ -21,14 +21,13 @@ public class RoleModulePermissionServices : BaseServices, // 将多个仓储接口注入 public RoleModulePermissionServices( - IRoleModulePermissionRepository dal, + IRoleModulePermissionRepository dal, IBaseRepository moduleRepository, IBaseRepository roleRepository) { this._dal = dal; this._moduleRepository = moduleRepository; this._roleRepository = roleRepository; - base.BaseDal = dal; } /// @@ -84,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/RoleServices.cs b/Blog.Core.Services/RoleServices.cs index 93b0c80f..50861f7c 100644 --- a/Blog.Core.Services/RoleServices.cs +++ b/Blog.Core.Services/RoleServices.cs @@ -13,13 +13,6 @@ namespace Blog.Core.Services /// public class RoleServices : BaseServices, IRoleServices { - - IBaseRepository _dal; - public RoleServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } /// /// /// 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/TasksQzServices.cs b/Blog.Core.Services/TasksQzServices.cs index 14c515e4..68560a20 100644 --- a/Blog.Core.Services/TasksQzServices.cs +++ b/Blog.Core.Services/TasksQzServices.cs @@ -7,12 +7,6 @@ namespace Blog.Core.Services { public partial class TasksQzServices : BaseServices, ITasksQzServices { - IBaseRepository _dal; - public TasksQzServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } } } 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/TopicDetailServices.cs b/Blog.Core.Services/TopicDetailServices.cs index 6f8667c5..49fb5343 100644 --- a/Blog.Core.Services/TopicDetailServices.cs +++ b/Blog.Core.Services/TopicDetailServices.cs @@ -10,13 +10,6 @@ namespace Blog.Core.Services { public class TopicDetailServices : BaseServices, ITopicDetailServices { - IBaseRepository _dal; - public TopicDetailServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - /// /// 获取开Bug数据(缓存) /// diff --git a/Blog.Core.Services/TopicServices.cs b/Blog.Core.Services/TopicServices.cs index 697c946c..7d1b3f0a 100644 --- a/Blog.Core.Services/TopicServices.cs +++ b/Blog.Core.Services/TopicServices.cs @@ -10,14 +10,6 @@ namespace Blog.Core.Services { public class TopicServices: BaseServices, ITopicServices { - - IBaseRepository _dal; - public TopicServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - /// /// 获取开Bug专题分类(缓存) /// diff --git a/Blog.Core.Services/UserRoleServices.cs b/Blog.Core.Services/UserRoleServices.cs index df73d2b8..26194837 100644 --- a/Blog.Core.Services/UserRoleServices.cs +++ b/Blog.Core.Services/UserRoleServices.cs @@ -13,20 +13,13 @@ namespace Blog.Core.Services /// public class UserRoleServices : BaseServices, IUserRoleServices { - - IBaseRepository _dal; - public UserRoleServices(IBaseRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } /// /// /// /// /// /// - public async Task SaveUserRole(int uid, int rid) + public async Task SaveUserRole(long uid, long rid) { UserRole userRole = new UserRole(uid, rid); @@ -49,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.Services/WeChatCompanyServices.cs b/Blog.Core.Services/WeChatCompanyServices.cs new file mode 100644 index 00000000..cd686ad0 --- /dev/null +++ b/Blog.Core.Services/WeChatCompanyServices.cs @@ -0,0 +1,31 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using Blog.Core.Services.BASE; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Blog.Core.Repository.UnitOfWorks; + +namespace Blog.Core.Services +{ + /// + /// WeChatCompanyServices + /// + public class WeChatCompanyServices : BaseServices, IWeChatCompanyServices + { + readonly IUnitOfWorkManage _unitOfWorkManage; + readonly ILogger _logger; + public WeChatCompanyServices(IUnitOfWorkManage unitOfWorkManage, ILogger logger) + { + this._unitOfWorkManage = unitOfWorkManage; + this._logger = logger; + } + + } +} \ No newline at end of file diff --git a/Blog.Core.Services/WeChatConfigServices.cs b/Blog.Core.Services/WeChatConfigServices.cs new file mode 100644 index 00000000..0952ce1c --- /dev/null +++ b/Blog.Core.Services/WeChatConfigServices.cs @@ -0,0 +1,903 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using Blog.Core.Services.BASE; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Blog.Core.Repository.UnitOfWorks; + +namespace Blog.Core.Services +{ + /// + /// WeChatConfigServices + /// + public class WeChatConfigServices : BaseServices, IWeChatConfigServices + { + readonly IUnitOfWorkManage _unitOfWorkManage; + readonly ILogger _logger; + public WeChatConfigServices(IUnitOfWorkManage unitOfWorkManage, ILogger logger) + { + this._unitOfWorkManage = unitOfWorkManage; + this._logger = logger; + } + public async Task> GetToken(string publicAccount) + { + var config = await this.QueryById(publicAccount); + if (config == null) MessageModel.Success($"公众号{publicAccount}未维护至系统");//还没过期,直接返回 + if (config.tokenExpiration > DateTime.Now) + { + //再次判断token在微信服务器是否正确 + var wechatIP = await WeChatHelper.GetWechatIP(config.token); + if (wechatIP.errcode == 0) + MessageModel.Success("", new WeChatApiDto { access_token = config.token });//还没过期,直接返回 + } + //过期了,重新获取 + var data = await WeChatHelper.GetToken(config.appid, config.appsecret); + if (data.errcode.Equals(0)) + { + config.token = data.access_token; + config.tokenExpiration = DateTime.Now.AddSeconds(data.expires_in); + await this.Update(config); + return MessageModel.Success("",data); + } + else + { + return MessageModel.Fail($"\r\n获取Token失败\r\n错误代码:{data.errcode}\r\n错误信息:{data.errmsg}"); + } + } + public async Task> RefreshToken(string publicAccount) + { + var config = await this.QueryById(publicAccount); + if (config == null) MessageModel.Success($"公众号{publicAccount}未维护至系统");//还没过期,直接返回 + //过期了,重新获取 + var data = await WeChatHelper.GetToken(config.appid, config.appsecret); + if (data.errcode.Equals(0)) + { + config.token = data.access_token; + config.tokenExpiration = DateTime.Now.AddSeconds(data.expires_in); + await this.Update(config); + return MessageModel.Success("", data); + } + else + { + return MessageModel.Fail($"\r\n获取Token失败\r\n错误代码:{data.errcode}\r\n错误信息:{data.errmsg}"); + } + } + public async Task> GetTemplate(string id) + { + var res = await GetToken(id); + if (!res.success) return res; + var data = await WeChatHelper.GetTemplate(res.response.access_token); + if (data.errcode.Equals(0)) + { + return MessageModel.Success("", data); + } + else + { + return MessageModel.Success($"\r\n获取模板失败\r\n错误代码:{data.errcode}\r\n错误信息:{data.errmsg}", data); + } + } + /// + /// 获取菜单 + /// + /// + /// + public async Task> GetMenu(string id) + { + var res = await GetToken(id); + if (!res.success) return res; + var data = await WeChatHelper.GetMenu(res.response.access_token); + if (data.errcode.Equals(0)) + { + return MessageModel.Success("", data); + } + else + { + return MessageModel.Success($"\r\n获取菜单失败\r\n错误代码:{data.errcode}\r\n错误信息:{data.errmsg}", data); + } + } + public async Task> GetSubUsers(string id) + { + var res = await GetToken(id); + if (!res.success) return res; + var data = await WeChatHelper.GetUsers(res.response.access_token); + if (data.errcode.Equals(0)) + { + data.users = new List(); + foreach (var openid in data.data.openid) + { + data.users.Add(await WeChatHelper.GetUserInfo(res.response.access_token, openid)); + } + return MessageModel.Success("", data); + } + else + { + return MessageModel.Success($"\r\n获取订阅用户失败\r\n错误代码:{data.errcode}\r\n错误信息:{data.errmsg}", data); + } + } + public async Task> GetSubUser(string id,string openid) + { + var res = await GetToken(id); + if (!res.success) return res; + var data = await WeChatHelper.GetUserInfo(res.response.access_token,openid); + if (data.errcode.Equals(0)) + { + return MessageModel.Success("", data); + } + else + { + return MessageModel.Success($"\r\n获取订阅用户失败\r\n错误代码:{data.errcode}\r\n错误信息:{data.errmsg}", data); + } + } + public async Task Valid(WeChatValidDto validDto,string body) + { + WeChatXMLDto weChatData = null; + string objReturn = null; + try + { + _logger.LogInformation("会话开始"); + if (string.IsNullOrEmpty(validDto.publicAccount)) throw new Exception("没有微信公众号唯一标识id数据"); + var config = await QueryById(validDto.publicAccount); + if (config == null) throw new Exception($"公众号不存在=>{validDto.publicAccount}"); + _logger.LogInformation(JsonHelper.GetJSON(validDto)); + var token = config.interactiveToken;//验证用的token 和access_token不一样 + string[] arrTmp = { token, validDto.timestamp, validDto.nonce }; + Array.Sort(arrTmp); + string combineString = string.Join("", arrTmp); + string encryption = MD5Helper.Sha1(combineString).ToLower(); + + _logger.LogInformation( + $"来自公众号:{validDto.publicAccount}\r\n" + + $"微信signature:{validDto.signature}\r\n" + + $"微信timestamp:{validDto.timestamp}\r\n" + + $"微信nonce:{validDto.nonce}\r\n" + + $"合并字符串:{combineString}\r\n" + + $"微信服务器signature:{validDto.signature}\r\n" + + $"本地服务器signature:{encryption}" + ); + if (encryption == validDto.signature) + { + //判断是首次验证还是交互? + if (string.IsNullOrEmpty(validDto.echoStr)) + { + //非首次验证 + weChatData = XmlHelper.ParseFormByXml(body, "xml"); + weChatData.publicAccount = validDto.publicAccount; + objReturn = await HandleWeChat(weChatData); + } + else + { + //首次接口地址验证 + objReturn = validDto.echoStr; + } + } + else + { + objReturn = "签名验证失败"; + } + + } + catch (Exception ex) + { + _logger.LogInformation($"会话出错(信息)=>\r\n{ex.Message}"); + _logger.LogInformation($"会话出错(堆栈)=>\r\n{ex.StackTrace}"); + //返回错误给用户 + objReturn = string.Format(@$" + + {DateTime.Now.Ticks.ToString()} + + "); + } + finally + { + _logger.LogInformation($"微信get数据=>\r\n{JsonHelper.GetJSON(validDto)}"); + _logger.LogInformation($"微信post数据=>\r\n{body}"); + _logger.LogInformation($"返回微信数据=>\r\n{objReturn}"); + _logger.LogInformation($"会话结束"); + } + return objReturn; + } + public async Task> GetQRBind(WeChatUserInfo info) + { + var res = await GetToken(info?.id); + if (!res.success) return MessageModel.Fail(res.msg); + var push = new WeChatQRDto + { + expire_seconds = 604800, + action_name = "QR_STR_SCENE", + action_info = new WeChatQRActionDto + { + scene = new WeChatQRActionInfoDto + { + scene_str = $"bind_{info?.id}" + } + } + }; + WeChatResponseUserInfo reData = new WeChatResponseUserInfo(); + reData.companyCode = info.companyCode; + reData.id = info.id; + var pushJosn = JsonHelper.GetJSON(push); + var data = await WeChatHelper.GetQRCode(res.response.access_token, pushJosn); + WeChatQR weChatQR = new WeChatQR + { + QRbindCompanyID = info.companyCode, + QRbindJobID = info.userID, + QRbindJobNick = info.userNick, + QRcrateTime = DateTime.Now, + QRpublicAccount = info.id, + QRticket = data.ticket + }; + data.id = info.userID; + await this.BaseDal.Db.Insertable(weChatQR).ExecuteCommandAsync(); + reData.usersData= data; + return MessageModel.Success("获取二维码成功", reData); + } + public async Task> PushCardMsg(WeChatCardMsgDataDto msg,string ip) + { + var bindUser = await BaseDal.Db.Queryable().Where(t => t.SubFromPublicAccount == msg.info.id && t.CompanyID == msg.info.companyCode && t.IsUnBind == false && msg.info.userID.Contains(t.SubJobID)).SingleAsync(); + if (bindUser == null) + return MessageModel.Fail("用户不存在或者已经解绑!"); + var res = await GetToken(msg?.info?.id); + if(!res.success) + return MessageModel.Fail(res.msg); + WeChatResponseUserInfo reData = new WeChatResponseUserInfo(); + reData.companyCode = msg.info.companyCode; + reData.id = msg.info.id; + try + { + var pushData = new WeChatPushCardMsgDto + { + template_id = msg.cardMsg.template_id, + url = msg.cardMsg.url, + touser = bindUser.SubUserOpenID, + data = new WeChatPushCardMsgDetailDto + { + first = new WeChatPushCardMsgValueColorDto + { + value = msg.cardMsg.first, + color = msg.cardMsg.color1 + }, + keyword1 = new WeChatPushCardMsgValueColorDto + { + value = msg.cardMsg.keyword1, + color = msg.cardMsg.color1 + }, + keyword2 = new WeChatPushCardMsgValueColorDto + { + value = msg.cardMsg.keyword2, + color = msg.cardMsg.color2 + }, + keyword3 = new WeChatPushCardMsgValueColorDto + { + value = msg.cardMsg.keyword3, + color = msg.cardMsg.color3 + }, + keyword4 = new WeChatPushCardMsgValueColorDto + { + value = msg.cardMsg.keyword4, + color = msg.cardMsg.color4 + }, + keyword5 = new WeChatPushCardMsgValueColorDto + { + value = msg.cardMsg.keyword5, + color = msg.cardMsg.color5 + }, + remark = new WeChatPushCardMsgValueColorDto + { + value = msg.cardMsg.remark, + color = msg.cardMsg.colorRemark + } + } + }; + var pushJson = JsonHelper.GetJSON(pushData); + var data = await WeChatHelper.SendCardMsg(res.response.access_token, pushJson); + reData.usersData = data; + try + { + var pushLog = new WeChatPushLog + { + PushLogCompanyID = msg.info.companyCode, + PushLogPublicAccount = msg.info.id, + PushLogContent = pushJson, + PushLogOpenid = bindUser.SubUserOpenID, + PushLogToUserID = bindUser.SubJobID, + PushLogStatus = data.errcode == 0 ? "Y" : "N", + PushLogRemark = data.errmsg, + PushLogTime = DateTime.Now, + PushLogTemplateID = msg.cardMsg.template_id, + PushLogIP = ip + }; + await BaseDal.Db.Insertable(pushLog).ExecuteCommandAsync(); + } + catch (Exception ex) + { + _logger.LogInformation($"记录失败\r\n{ex.Message}\r\n{ex.StackTrace}"); + } + if (reData.usersData.errcode.Equals(0)) + { + return MessageModel.Success("卡片消息推送成功", reData); + } + else + { + return MessageModel.Success("卡片消息推送失败", reData); + } + + } + catch (Exception ex) + { + return MessageModel.Success($"卡片消息推送错误=>{ex.Message}", reData); + } + + } + + public async Task> PushTxtMsg(WeChatPushTestDto msg) { + var res = await GetToken(msg.selectWeChat); + if (!res.success) return res; + var token = res.response.access_token; + if (msg.selectBindOrSub.Equals("sub")) + { + return await PushText(token, msg); + } + else + { + MessageModel messageModel = new MessageModel(); + messageModel.success = true; + //绑定用户 + if (msg.selectOperate.Equals("one")) + { + //发送单个 + var usrs = BaseDal.Db.Queryable().Where(t => t.SubFromPublicAccount.Equals(msg.selectWeChat) && t.CompanyID.Equals(msg.selectCompany) && t.SubJobID.Equals(msg.selectUser)).ToList(); + foreach (var item in usrs) + { + msg.selectUser = item.SubUserOpenID; + var info = await PushText(token, msg); + if (!info.success) + { + messageModel.success = false; + } + messageModel.msg += info.msg; + } + } + else + { + //发送所有 + var usrs = BaseDal.Db.Queryable().Where(t => t.SubFromPublicAccount.Equals(msg.selectWeChat) && t.CompanyID.Equals(msg.selectCompany)).ToList(); + foreach (var item in usrs) + { + msg.selectUser = item.SubUserOpenID; + var info = await PushText(token, msg); + if (!info.success) + { + messageModel.success = false; + } + messageModel.msg += info.msg; + } + } + return messageModel; + } + + } + public async Task> PushText(string token,WeChatPushTestDto msg) { + + object data = null; ; + WeChatApiDto pushres = null; ; + //订阅用户 + switch (msg.selectMsgType) + { + case "text": + //发送文本 + data = new + { + filter = new + { + is_to_all = msg.selectOperate.Equals("one") ? false : true, + tag_id = 0, + }, + touser = msg.selectUser, + msgtype = msg.selectMsgType, + text = new + { + content = msg.textContent.text + } + }; + + if (msg.selectOperate.Equals("one")) + { + pushres = await WeChatHelper.SendMsg(token, JsonHelper.ObjToJson(data)); + } + else + { + pushres = await WeChatHelper.SendMsgToAll(token, JsonHelper.ObjToJson(data)); + } + break; + case "image": + //发送图片 + data = new + { + filter = new + { + is_to_all = msg.selectOperate.Equals("one") ? false : true, + tag_id = 0, + }, + touser = msg.selectUser, + msgtype = msg.selectMsgType, + images = new + { + media_ids = new List { + msg.pictureContent.pictureMediaID + }, + recommend = "xxx", + need_open_comment = 1, + only_fans_can_comment = 0 + } + }; + if (msg.selectOperate.Equals("one")) + { + pushres = await WeChatHelper.SendMsg(token, JsonHelper.ObjToJson(data)); + } + else + { + pushres = await WeChatHelper.SendMsgToAll(token, JsonHelper.ObjToJson(data)); + } + break; + case "voice": + //发送音频 + data = new + { + filter = new + { + is_to_all = msg.selectOperate.Equals("one") ? false : true, + tag_id = 0, + }, + touser = msg.selectUser, + msgtype = msg.selectMsgType, + voice = new + { + media_id = msg.voiceContent.voiceMediaID + } + }; + if (msg.selectOperate.Equals("one")) + { + pushres = await WeChatHelper.SendMsg(token, JsonHelper.ObjToJson(data)); + } + else + { + pushres = await WeChatHelper.SendMsgToAll(token, JsonHelper.ObjToJson(data)); + } + break; + case "mpvideo": + //发送视频 + data = new + { + filter = new + { + is_to_all = msg.selectOperate.Equals("one") ? false : true, + tag_id = 0, + }, + touser = msg.selectUser, + msgtype = msg.selectMsgType, + mpvideo = new + { + media_id = msg.videoContent.videoMediaID, + } + }; + if (msg.selectOperate.Equals("one")) + { + pushres = await WeChatHelper.SendMsg(token, JsonHelper.ObjToJson(data)); + } + else + { + pushres = await WeChatHelper.SendMsgToAll(token, JsonHelper.ObjToJson(data)); + } + break; + default: + pushres = new WeChatApiDto() { errcode = -1, errmsg = $"未找到推送类型{msg.selectMsgType}" }; + break; + } + if (pushres.errcode.Equals(0)) + { + return MessageModel.Success("推送成功", pushres); + + } + else + { + return MessageModel.Fail($"\r\n推送失败\r\n错误代码:{pushres.errcode}\r\n错误信息:{pushres.errmsg}", pushres); + } + } + public async Task> UpdateMenu(WeChatApiDto menu) + { + WeChatHelper.ConverMenuButtonForEvent(menu); + var res = await GetToken(menu.id); + if (!res.success) return res; + var data = await WeChatHelper.SetMenu(res.response.access_token, JsonHelper.ObjToJson(menu.menu)); + if (data.errcode.Equals(0)) + { + + return MessageModel.Success("更新成功", data); + } + else + { + return MessageModel.Success("更新失败", data); + } + } + public async Task> GetBindUserInfo(WeChatUserInfo info) + { + var bindUser = await BaseDal.Db.Queryable().Where(t => t.SubFromPublicAccount == info.id && t.CompanyID == info.companyCode && info.userID.Equals(t.SubJobID) && t.IsUnBind == false ).FirstAsync(); + if (bindUser == null) return MessageModel.Fail("用户不存在或者已经解绑!"); + var res = await GetToken(info.id); + if(!res.success) return MessageModel.Fail(res.msg); + var token = res.response.access_token; + WeChatResponseUserInfo reData = new WeChatResponseUserInfo(); + reData.companyCode = info.companyCode; + reData.id = info.id; + var data = await WeChatHelper.GetUserInfo(token, bindUser.SubUserOpenID); + reData.usersData = data; + if (data.errcode.Equals(0)) + { + return MessageModel.Success("用户信息获取成功", reData); + } + else + { + return MessageModel.Fail("用户信息获取失败", reData); + } + } + public async Task> UnBind(WeChatUserInfo info) + { + var bindUser = await BaseDal.Db.Queryable().Where(t => t.SubFromPublicAccount == info.id && t.CompanyID == info.companyCode && info.userID.Equals(t.SubJobID) && t.IsUnBind == false ).FirstAsync(); + if (bindUser == null) return MessageModel.Fail("用户不存在或者已经解绑!"); + WeChatResponseUserInfo reData = new WeChatResponseUserInfo(); + reData.companyCode = info.companyCode; + reData.id = info.id; + bindUser.IsUnBind = true; + bindUser.SubUserRefTime = DateTime.Now; + await BaseDal.Db.Updateable(bindUser).UpdateColumns(t=> new{ t.IsUnBind,t.SubUserRefTime}).ExecuteCommandAsync(); + return MessageModel.Success("用户解绑成功", reData); + } + + public async Task HandleWeChat(WeChatXMLDto weChat) + { + + switch (weChat.MsgType) + { + case "text": + return await HandText(weChat); + case "image": + return await HandImage(weChat); + case "voice": + return await HandVoice(weChat); + case "shortvideo": + return await HandShortvideo(weChat); + case "location": + return await HandLocation(weChat); + case "link": + return await HandLink(weChat); + case "event": + return await HandEvent(weChat); + default: + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.MsgType}]]>"; + }); + } + + } + /// + /// 处理文本 + /// + /// + /// + private async Task HandText(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.Content}]]>"; + }); + } + /// + /// 处理图片 + /// + /// + /// + private async Task HandImage(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.PicUrl}]]>"; + }); + } + /// + /// 处理声音 + /// + /// + /// + private async Task HandVoice(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.MediaId}]]>"; + }); + } + /// + /// 处理小视频 + /// + /// + /// + private async Task HandShortvideo(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.MediaId}]]>"; + }); + } + /// + /// 处理地理位置 + /// + /// + /// + private async Task HandLocation(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.Label}]]>"; + }); + } + /// + /// 处理链接消息 + /// + /// + /// + private async Task HandLink(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.Url}]]>"; + }); + } + /// + /// 处理事件 + /// + /// + /// + private async Task HandEvent(WeChatXMLDto weChat) + { + + switch (weChat.Event) + { + case "subscribe": + return await EventSubscribe(weChat); + case "unsubscribe": + return await EventUnsubscribe(weChat); + case "SCAN": + return await EventSCAN(weChat); + case "LOCATION": + return await EventLOCATION(weChat); + case "CLICK": + return await EventCLICK(weChat); + case "VIEW": + return await EventVIEW(weChat); + default: + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.Event}]]>"; + }); + } + } + /// + /// 关注事件 + /// + /// + /// + private async Task EventSubscribe(WeChatXMLDto weChat) + { + if (weChat.EventKey != null && (weChat.EventKey.Equals("bind") || weChat.EventKey.Equals("qrscene_bind"))) + { + return await QRBind(weChat); + } + else + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + key:{weChat.EventKey}=>ticket:{weChat.Ticket}]]>"; + }); + } + } + /// + /// 取消关注事件 + /// + /// + /// + private async Task EventUnsubscribe(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.Event}]]>"; + }); + } + /// + /// 已关注扫码事件 + /// + /// + /// + private async Task EventSCAN(WeChatXMLDto weChat) + { + if (weChat.EventKey != null && (weChat.EventKey.StartsWith("bind_") || weChat.EventKey.StartsWith("qrscene_bind_"))) + { + + return await QRBind(weChat); + } + else + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + key:{weChat.EventKey}=>ticket:{weChat.Ticket}]]>"; + }); + + } + + } + /// + /// 扫码绑定 + /// + /// + /// + + private async Task QRBind(WeChatXMLDto weChat) + { + var ticket = await BaseDal.Db.Queryable().InSingleAsync(weChat.Ticket); + if (ticket == null) throw new Exception("ticket未找到"); + if (ticket.QRisUsed) throw new Exception("ticket已被使用"); + if (!ticket.QRpublicAccount.Equals(weChat.publicAccount)) throw new Exception($"公众号错误 need:{ticket.QRpublicAccount} but:{weChat.publicAccount}"); + + var bindUser = await BaseDal.Db.Queryable().Where(t => t.SubFromPublicAccount == ticket.QRpublicAccount && t.CompanyID == ticket.QRbindCompanyID && t.SubJobID == ticket.QRbindJobID).SingleAsync(); + bool isNewBind; + if (bindUser == null ) + { + isNewBind = true; + bindUser = new WeChatSub + { + SubFromPublicAccount = ticket.QRpublicAccount, + CompanyID = ticket.QRbindCompanyID, + SubJobID = ticket.QRbindJobID, + SubUserOpenID = weChat.FromUserName, + SubUserRegTime = DateTime.Now, + }; + } + else + { + isNewBind = false; + //订阅过的就更新 + if (bindUser.SubUserOpenID != weChat.FromUserName) + { + //记录上一次的订阅此工号的微信号 + bindUser.LastSubUserOpenID = bindUser.SubUserOpenID; + } + bindUser.SubUserOpenID = weChat.FromUserName; + bindUser.SubUserRefTime = DateTime.Now; + bindUser.IsUnBind = false; + } + ticket.QRisUsed = true; + ticket.QRuseTime = DateTime.Now; + ticket.QRuseOpenid = weChat.FromUserName; + + try + { + _unitOfWorkManage.BeginTran(); + await BaseDal.Db.Updateable(ticket).ExecuteCommandAsync(); + if (isNewBind) + await BaseDal.Db.Insertable(bindUser).ExecuteCommandAsync(); + else + await BaseDal.Db.Updateable(bindUser).ExecuteCommandAsync(); + _unitOfWorkManage.CommitTran(); + } + catch + { + _unitOfWorkManage.RollbackTran(); + throw; + } + return @$" + + {DateTime.Now.Ticks.ToString()} + + "; + } + /// + /// 上报位置地理事件 + /// + /// + /// + private async Task EventLOCATION(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + 维度:{weChat.Latitude}经度:{weChat.Longitude}位置精度:{weChat.Precision}]]>"; + }); + } + /// + /// 点击菜单按钮事件 + /// + /// + /// + private async Task EventCLICK(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.EventKey}]]>"; + }); + } + /// + /// 点击菜单网址事件 + /// + /// + /// + private async Task EventVIEW(WeChatXMLDto weChat) + { + return await Task.Run(() => + { + return @$" + + {DateTime.Now.Ticks.ToString()} + + {weChat.EventKey}]]>"; + }); + } + + } +} \ No newline at end of file diff --git a/Blog.Core.Services/WeChatPushLogServices.cs b/Blog.Core.Services/WeChatPushLogServices.cs new file mode 100644 index 00000000..e3891474 --- /dev/null +++ b/Blog.Core.Services/WeChatPushLogServices.cs @@ -0,0 +1,23 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using Blog.Core.Services.BASE; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Services +{ + /// + /// WeChatPushLogServices + /// + public class WeChatPushLogServices : BaseServices, IWeChatPushLogServices + { + + } +} \ No newline at end of file diff --git a/Blog.Core.Services/WeChatSubServices.cs b/Blog.Core.Services/WeChatSubServices.cs new file mode 100644 index 00000000..7f66dd29 --- /dev/null +++ b/Blog.Core.Services/WeChatSubServices.cs @@ -0,0 +1,23 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using Blog.Core.Services.BASE; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Services +{ + /// + /// WeChatSubServices + /// + public class WeChatSubServices : BaseServices, IWeChatSubServices + { + + } +} \ No newline at end of file diff --git a/Blog.Core.Services/sysUserInfoServices.cs b/Blog.Core.Services/sysUserInfoServices.cs index 3d5521d2..282ae73c 100644 --- a/Blog.Core.Services/sysUserInfoServices.cs +++ b/Blog.Core.Services/sysUserInfoServices.cs @@ -10,18 +10,14 @@ namespace Blog.Core.FrameWork.Services /// /// sysUserInfoServices /// - public class SysUserInfoServices : BaseServices, ISysUserInfoServices + public class SysUserInfoServices : BaseServices, ISysUserInfoServices { - - private readonly IBaseRepository _dal; private readonly IBaseRepository _userRoleRepository; private readonly IBaseRepository _roleRepository; - public SysUserInfoServices(IBaseRepository dal, IBaseRepository userRoleRepository, IBaseRepository roleRepository) + public SysUserInfoServices(IBaseRepository userRoleRepository, IBaseRepository roleRepository) { - this._dal = dal; _userRoleRepository = userRoleRepository; _roleRepository = roleRepository; - base.BaseDal = dal; } /// /// @@ -29,11 +25,11 @@ public SysUserInfoServices(IBaseRepository dal, IBaseRepository /// /// - public async Task SaveUserInfo(string loginName, string loginPwd) + public async Task SaveUserInfo(string loginName, string loginPwd) { - sysUserInfo sysUserInfo = new sysUserInfo(loginName, loginPwd); - sysUserInfo model = new sysUserInfo(); - var userList = await base.Query(a => a.uLoginName == sysUserInfo.uLoginName && a.uLoginPWD == sysUserInfo.uLoginPWD); + SysUserInfo sysUserInfo = new SysUserInfo(loginName, loginPwd); + SysUserInfo model = new SysUserInfo(); + var userList = await base.Query(a => a.LoginName == sysUserInfo.LoginName && a.LoginPWD == sysUserInfo.LoginPWD); if (userList.Count > 0) { model = userList.FirstOrDefault(); @@ -57,11 +53,11 @@ public async Task SaveUserInfo(string loginName, string loginPwd) public async Task GetUserRoleNameStr(string loginName, string loginPwd) { string roleName = ""; - var user = (await base.Query(a => a.uLoginName == loginName && a.uLoginPWD == loginPwd)).FirstOrDefault(); + var user = (await base.Query(a => a.LoginName == loginName && a.LoginPWD == loginPwd)).FirstOrDefault(); var roleList = await _roleRepository.Query(a => a.IsDeleted == false); if (user != null) { - var userRoles = await _userRoleRepository.Query(ur => ur.UserId == user.uID); + var userRoles = await _userRoleRepository.Query(ur => ur.UserId == user.Id); if (userRoles.Count > 0) { var arr = userRoles.Select(ur => ur.RoleId.ObjToString()).ToList(); diff --git a/Blog.Core.Tasks/Blog.Core.Tasks.csproj b/Blog.Core.Tasks/Blog.Core.Tasks.csproj index e41f726e..d93eb8bd 100644 --- a/Blog.Core.Tasks/Blog.Core.Tasks.csproj +++ b/Blog.Core.Tasks/Blog.Core.Tasks.csproj @@ -1,15 +1,14 @@  - - net5.0 - - + + + diff --git a/Blog.Core.Tasks/HostedService/Job1TimedService.cs b/Blog.Core.Tasks/HostedService/Job1TimedService.cs index 64032a49..9777affc 100644 --- a/Blog.Core.Tasks/HostedService/Job1TimedService.cs +++ b/Blog.Core.Tasks/HostedService/Job1TimedService.cs @@ -1,4 +1,4 @@ -using Blog.Core.Common.Helper; +using Blog.Core.Common; using Blog.Core.IServices; using Microsoft.Extensions.Hosting; using System; diff --git a/Blog.Core.Tasks/HostedService/Job2TimedService.cs b/Blog.Core.Tasks/HostedService/Job2TimedService.cs index 94cd27a0..ee7f2c5c 100644 --- a/Blog.Core.Tasks/HostedService/Job2TimedService.cs +++ b/Blog.Core.Tasks/HostedService/Job2TimedService.cs @@ -1,4 +1,4 @@ -using Blog.Core.Common.Helper; +using Blog.Core.Common; using Microsoft.Extensions.Hosting; using System; using System.Threading; 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 new file mode 100644 index 00000000..88b79e8b --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_AccessTrendLog_Quartz.cs @@ -0,0 +1,75 @@ +using Blog.Core.Common.LogHelper; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Quartz; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +/// +/// 这里要注意下,命名空间和程序集是一样的,不然反射不到 +/// +namespace Blog.Core.Tasks +{ + 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, + IWebHostEnvironment environment, ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices, + ILogger logger) + : base(tasksQzServices, tasksLogServices) + { + _accessTrendLogServices = accessTrendLogServices; + _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; + // 也可以通过数据库配置,获取传递过来的参数 + JobDataMap data = context.JobDetail.JobDataMap; + + var lastestLogDatetime = (await _accessTrendLogServices.Query(null, d => d.UpdateTime, false)) + .FirstOrDefault()?.UpdateTime; + if (lastestLogDatetime == null) + { + lastestLogDatetime = Convert.ToDateTime("2021-09-01"); + } + + // 重新拉取 + var actUsers = await _accessTrendLogServices.Query(d => d.UserInfo != "", d => d.Count, false); + actUsers = actUsers.Take(15).ToList(); + + List activeUserVMs = new(); + foreach (var item in actUsers) + { + activeUserVMs.Add(new ActiveUserVM() + { + user = item.UserInfo, + count = item.Count + }); + } + + _logger.LogInformation("Job_AccessTrendLog_Quartz: {ActiveUserVMs}", + JsonConvert.SerializeObject(activeUserVMs)); + } + } +} \ 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 e2c479ed..49e95847 100644 --- a/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs @@ -2,7 +2,6 @@ using Blog.Core.IServices; using Blog.Core.Model.Models; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Logging; using Quartz; using System; using System.Collections.Generic; @@ -18,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 ced47c1f..50a63431 100644 --- a/Blog.Core.Tests/Blog.Core.Tests.csproj +++ b/Blog.Core.Tests/Blog.Core.Tests.csproj @@ -1,9 +1,8 @@  - net5.0 - false + false @@ -18,10 +17,11 @@ - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -29,8 +29,21 @@ - - + + + + + + Always + + + + + + 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 new file mode 100644 index 00000000..513d283d --- /dev/null +++ b/Blog.Core.Tests/Common_Test/DynamicLambdaTest.cs @@ -0,0 +1,158 @@ +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; + +namespace Blog.Core.Tests.Common_Test; + +public class DynamicLambdaTest +{ + private readonly ITestOutputHelper _testOutputHelper; + private readonly IBaseRepository _baseRepository; + DI_Test dI_Test = new DI_Test(); + + public DynamicLambdaTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + + var container = dI_Test.DICollections(); + + _baseRepository = container.Resolve>(); + _baseRepository.Db.Aop.OnLogExecuting = (sql, p) => + { + _testOutputHelper.WriteLine(UtilMethods.GetNativeSql(sql, p)); + }; + + 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()); + + await TestConditions(""); + await TestConditions("bId=1"); + 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("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)); + } +} \ 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 new file mode 100644 index 00000000..16053f43 --- /dev/null +++ b/Blog.Core.Tests/Common_Test/HttpHelper_Should.cs @@ -0,0 +1,29 @@ +using Blog.Core.Common.Helper; +using Xunit; + +namespace Blog.Core.Tests.Common_Test +{ + public class HttpHelper_Should + { + + [Fact] + public void Get_Async_Test() + { + var responseString = HttpHelper.GetAsync("http://apk.neters.club/api/Blog").Result; + + Assert.NotNull(responseString); + } + + [Fact] + public void Post_Async_Test() + { + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; + + using var client = new HttpClient(handler); + } + + } +} diff --git a/Blog.Core.Tests/Common_Test/SM4Helper_Should.cs b/Blog.Core.Tests/Common_Test/SM4Helper_Should.cs new file mode 100644 index 00000000..9c5ad566 --- /dev/null +++ b/Blog.Core.Tests/Common_Test/SM4Helper_Should.cs @@ -0,0 +1,42 @@ +using Blog.Core.Common.Helper; +using Blog.Core.Common.Helper.SM; +using System; +using Xunit; + +namespace Blog.Core.Tests.Common_Test +{ + public class SM4Helper_Should + { + + [Fact] + public void Encrypt_ECB_Test() + { + var plainText = "暗号"; + + var sm4 = new SM4Helper(); + + Console.Out.WriteLine("ECB模式"); + var cipherText = sm4.Encrypt_ECB(plainText); + Console.Out.WriteLine("密文: " + cipherText); + + Assert.NotNull(cipherText); + Assert.Equal("VhVDC0KzyZjAVMpwz0GyQA==", cipherText); + } + + [Fact] + public void Decrypt_ECB_Test() + { + var cipherText = "Y9ygWexdpuLQjW/qsnZNQw=="; + + var sm4 = new SM4Helper(); + + Console.Out.WriteLine("ECB模式"); + var plainText = sm4.Decrypt_ECB(cipherText); + Console.Out.WriteLine("明文: " + plainText); + + Assert.NotNull(plainText); + Assert.Equal("老张的哲学", plainText); + } + + } +} diff --git a/Blog.Core.Tests/Controller_Test/BlogController_Should.cs b/Blog.Core.Tests/Controller_Test/BlogController_Should.cs index fce9f39e..59d42ae0 100644 --- a/Blog.Core.Tests/Controller_Test/BlogController_Should.cs +++ b/Blog.Core.Tests/Controller_Test/BlogController_Should.cs @@ -1,10 +1,13 @@ using Autofac; using Blog.Core.Controllers; using Blog.Core.IServices; +using Blog.Core.Model; using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; using Microsoft.Extensions.Logging; using Moq; using System; +using System.Collections.Generic; using Xunit; namespace Blog.Core.Tests @@ -24,10 +27,10 @@ public BlogController_Should() { mockBlogSev.Setup(r => r.Query()); - var container = dI_Test.DICollections(); blogArticleServices = container.Resolve(); blogController = new BlogController(mockLogger.Object); + blogController._blogArticleServices = blogArticleServices; } [Fact] @@ -37,12 +40,59 @@ public void TestEntity() Assert.True(blogArticle.bID >= 0); } + + [Fact] + public async void Get_Blog_Page_Test() + { + MessageModel> blogs = await blogController.Get(1, 1, "技术博文", ""); + Assert.NotNull(blogs); + Assert.NotNull(blogs.response); + Assert.True(blogs.response.dataCount >= 0); + } + + [Fact] + public async void Get_Blog_Test() + { + MessageModel blogVo = await blogController.Get(1.ObjToLong()); + + Assert.NotNull(blogVo); + } + + [Fact] + public async void Get_Blog_For_Nuxt_Test() + { + MessageModel blogVo = await blogController.DetailNuxtNoPer(1); + + Assert.NotNull(blogVo); + } + + [Fact] + public async void Get_Go_Url_Test() + { + object urlAction = await blogController.GoUrl(1); + + Assert.NotNull(urlAction); + } + [Fact] - public async void GetBlogsTest() + public async void Get_Blog_By_Type_For_MVP_Test() { - object blogs =await blogController.Get(1); + MessageModel> blogs = await blogController.GetBlogsByTypesForMVP("技术博文"); Assert.NotNull(blogs); + Assert.True(blogs.success); + Assert.NotNull(blogs.response); + Assert.True(blogs.response.Count >= 0); + } + + [Fact] + public async void Get_Blog_By_Id_For_MVP_Test() + { + MessageModel blog = await blogController.GetBlogByIdForMVP(1); + + Assert.NotNull(blog); + Assert.True(blog.success); + Assert.NotNull(blog.response); } [Fact] @@ -53,7 +103,7 @@ public async void PostTest() bCreateTime = DateTime.Now, bUpdateTime = DateTime.Now, btitle = "xuint :test controller addEntity", - + bcontent = "xuint :test controller addEntity. this is content.this is content." }; var res = await blogController.Post(blogArticle); @@ -64,5 +114,66 @@ public async void PostTest() Assert.NotNull(data); } + + [Fact] + public async void Post_Insert_For_MVP_Test() + { + BlogArticle blogArticle = new BlogArticle() + { + bCreateTime = DateTime.Now, + bUpdateTime = DateTime.Now, + btitle = "xuint :test controller addEntity", + bcontent = "xuint :test controller addEntity. this is content.this is content." + }; + + var res = await blogController.AddForMVP(blogArticle); + + Assert.True(res.success); + + var data = res.response; + + Assert.NotNull(data); + } + + [Fact] + public async void Put_Test() + { + BlogArticle blogArticle = new BlogArticle() + { + bID = 1, + bCreateTime = DateTime.Now, + bUpdateTime = DateTime.Now, + btitle = "xuint put :test controller addEntity", + bcontent = "xuint put :test controller addEntity. this is content.this is content." + }; + + var res = await blogController.Put(blogArticle); + + Assert.True(res.success); + + var data = res.response; + + Assert.NotNull(data); + } + + [Fact] + public async void Delete_Test() + { + var res = await blogController.Delete(99); + + Assert.False(res.success); + + var data = res.response; + + Assert.Null(data); + } + + [Fact] + public async void Apache_Update_Test() + { + var res = await blogController.ApacheTestUpdate(); + + Assert.True(res.success); + } } } 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 0f1b08ba..bb037efd 100644 --- a/Blog.Core.Tests/DependencyInjection/DI_Test.cs +++ b/Blog.Core.Tests/DependencyInjection/DI_Test.cs @@ -1,89 +1,31 @@ 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; -using Blog.Core.IServices; -using Blog.Core.Model.Seed; using Blog.Core.Repository.Base; -using Blog.Core.Services; +using Blog.Core.Repository.MongoRepository; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.IdentityModel.Tokens; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using System.Security.Claims; using System.Text; -using Xunit; +using Blog.Core.Common.Core; +using Blog.Core.Extensions.ServiceExtensions; +using Microsoft.Extensions.Logging; namespace Blog.Core.Tests { public class DI_Test { - - [Fact] - public void DI_Connet_Test() - { - var basePath = AppContext.BaseDirectory; - - IServiceCollection services = new ServiceCollection().AddLogging(); - services.AddAutoMapper(typeof(Startup)); - - services.AddScoped(o => - { - return new SqlSugar.SqlSugarClient(new SqlSugar.ConnectionConfig() - { - ConnectionString = GetMainConnectionDb().Connection,//必填, 数据库连接字符串 - DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType,//必填, 数据库类型 - IsAutoCloseConnection = true,//默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作 - IsShardSameThread = true,//共享线程 - InitKeyType = SqlSugar.InitKeyType.SystemTable//默认SystemTable, 字段信息读取, 如:该属性是不是主键,标识列等等信息 - }); - }); - - //services.AddSingleton(new Appsettings(Env)); - - - //实例化 AutoFac 容器 - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - - //指定已扫描程序集中的类型注册为提供所有其实现的接口。 - //var assemblysServices = Assembly.Load("Blog.Core.Services"); - //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces(); - //var assemblysRepository = Assembly.Load("Blog.Core.Repository"); - //builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); - - var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); - var assemblysServices = Assembly.LoadFrom(servicesDllFile); - builder.RegisterAssemblyTypes(assemblysServices) - .AsImplementedInterfaces() - .InstancePerLifetimeScope() - .EnableInterfaceInterceptors(); - - var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); - var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); - builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); - - //将services填充到Autofac容器生成器中 - builder.Populate(services); - - //使用已进行的组件登记创建新容器 - var ApplicationContainer = builder.Build(); - - var blogservice = ApplicationContainer.Resolve(); - - Assert.True(ApplicationContainer.ComponentRegistry.Registrations.Count() > 0); - } - - /// /// 连接字符串 /// Blog.Core @@ -111,10 +53,13 @@ public IContainer DICollections() var basePath = AppContext.BaseDirectory; IServiceCollection services = new ServiceCollection(); - services.AddAutoMapper(typeof(Startup)); + services.ConfigureApplication(); - services.AddSingleton(new Appsettings(basePath)); - services.AddSingleton(new LogLock(basePath)); + services.AddLogging(); + services.AddAutoMapperSetup(); + services.AddCacheSetup(); + + services.AddSingleton(new AppSettings(basePath)); services.AddScoped(); services.AddScoped(); @@ -129,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); @@ -143,50 +88,72 @@ 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.SqlSugarClient(new SqlSugar.ConnectionConfig() + return new SqlSugar.SqlSugarScope(new SqlSugar.ConnectionConfig() { - ConnectionString = GetMainConnectionDb().Connection,//必填, 数据库连接字符串 - DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType,//必填, 数据库类型 - IsAutoCloseConnection = true,//默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作 - IsShardSameThread = true,//共享线程 - InitKeyType = SqlSugar.InitKeyType.SystemTable//默认SystemTable, 字段信息读取, 如:该属性是不是主键,标识列等等信息 + ConnectionString = GetMainConnectionDb().Connection, //必填, 数据库连接字符串 + DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType, //必填, 数据库类型 + IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作 }); }); //实例化 AutoFac 容器 var builder = new ContainerBuilder(); //builder.RegisterType().As(); + builder.RegisterInstance(new LoggerFactory()) + .As(); + builder.RegisterGeneric(typeof(Logger<>)) + .As(typeof(ILogger<>)) + .SingleInstance(); //指定已扫描程序集中的类型注册为提供所有其实现的接口。 - builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency();//注册仓储 + builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储 + builder.RegisterGeneric(typeof(MongoBaseRepository<>)).As(typeof(IMongoBaseRepository<>)).InstancePerDependency(); //注册仓储 + // 属性注入 + var controllerBaseType = typeof(ControllerBase); + //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() - .EnableInterfaceInterceptors(); + .AsImplementedInterfaces() + .InstancePerLifetimeScope() + .PropertiesAutowired() + .EnableInterfaceInterceptors(); var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); - builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); + builder.RegisterAssemblyTypes(assemblysRepository) + .PropertiesAutowired().AsImplementedInterfaces(); + + services.Replace(ServiceDescriptor.Transient()); + + services.AddAutoMapperSetup(); //将services填充到Autofac容器生成器中 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/MongoRepository_Base_Should.cs b/Blog.Core.Tests/Repository_Test/MongoRepository_Base_Should.cs new file mode 100644 index 00000000..678125c3 --- /dev/null +++ b/Blog.Core.Tests/Repository_Test/MongoRepository_Base_Should.cs @@ -0,0 +1,60 @@ +using Blog.Core.Model.Models; +using Xunit; +using System; +using System.Linq; +using Autofac; +using Blog.Core.IRepository.Base; +using Blog.Core.Repository.MongoRepository; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Blog.Core.Tests +{ + public class MongoRepository_Base_Should + { + public class MongoTest + { + [BsonId] + public ObjectId id { get; set; } + public string name { get; set; } + public bool isDel { get; set; } + public DateTime time { get; set; } + } + + private IMongoBaseRepository baseRepository; + DI_Test dI_Test = new DI_Test(); + + public MongoRepository_Base_Should() + { + + var container = dI_Test.DICollections(); + + baseRepository = container.Resolve>(); + } + + + [Fact] + public async void Add_Test() + { + await baseRepository.AddAsync(new MongoTest { isDel = false, name = "test", time = DateTime.UtcNow }); + } + + [Fact] + public async void GetObjectId_Test() + { + var data = await baseRepository.GetByObjectIdAsync("612b9b0be677976fa0f0cfa2"); + + Assert.NotNull(data); + } + + + [Fact] + public async void GetListFilter_Test() + { + var data = await baseRepository.GetListFilterAsync(new FilterDefinitionBuilder().Gte("time", DateTime.Parse("2022-06-01"))); + + Assert.NotNull(data); + } + } +} 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/BlogArticleService_Should.cs b/Blog.Core.Tests/Service_Test/BlogArticleService_Should.cs index 00073c6c..df16d174 100644 --- a/Blog.Core.Tests/Service_Test/BlogArticleService_Should.cs +++ b/Blog.Core.Tests/Service_Test/BlogArticleService_Should.cs @@ -60,6 +60,8 @@ public async void Add_Blog_Test() [Fact] public async void Delete_Blog_Test() { + Add_Blog_Test(); + var deleteModel = (await blogArticleServices.Query(d => d.btitle == "xuint test title")).FirstOrDefault(); Assert.NotNull(deleteModel); 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/appsettings.json b/Blog.Core.Tests/appsettings.json index 95a95f16..f91f6df8 100644 --- a/Blog.Core.Tests/appsettings.json +++ b/Blog.Core.Tests/appsettings.json @@ -1,61 +1,96 @@ { - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" + "urls": "http://*:9291", //web服务端口,如果用IIS部署,把这个去掉 + "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" } - }, - "Console": { - "LogLevel": { - "Default": "Warning", - "Microsoft.Hosting.Lifetime": "Debug" - } - }, - "Log4Net": { - "Name": "Blog.Core" } }, "AllowedHosts": "*", "Redis": { - "ConnectionString": "127.0.0.1:6319" + "Enable": false, + "ConnectionString": "127.0.0.1:6379", + "InstanceName": "" //前缀 + }, + "RabbitMQ": { + "Enabled": false, + "Connection": "118.25.251.13", + "UserName": "", + "Password": "!", + "RetryCount": 3 + }, + "Kafka": { + "Enabled": false, + "Servers": "localhost:9092", + "Topic": "blog", + "GroupId": "blog-consumer", + "NumPartitions": 3 //主题分区数量 + }, + "EventBus": { + "Enabled": false, + "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": false, - "OutToLogFile": { - "Enabled": false + "Enabled": true, + "LogToFile": { + "Enabled": true }, - "OutToConsole": { - "Enabled": false + "LogToDB": { + "Enabled": true + }, + "LogToConsole": { + "Enabled": true } }, "Date": "2018-08-28", "SeedDBEnabled": true, //只生成表结构 "SeedDBDataEnabled": true, //生成表,并初始化数据 - "Author": "Blog.Core" + "Author": "Blog.Core", + "SvcName": "", // /svc/blog + "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 @@ -63,20 +98,45 @@ SqlServer = 1, Sqlite = 2, Oracle = 3, - PostgreSQL = 4 + PostgreSQL = 4, + Dm = 5,//达梦 + 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" }, @@ -84,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" }, @@ -92,23 +151,31 @@ "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=ddd;Uid=root;Pwd=123456;Port=10060;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": "Provider=OraOLEDB.Oracle; Data Source=WMBlogDB; User Id=sss; Password=789;", - "OracleConnection_other1": "User ID=sss;Password=789;Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.8.65)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME = orcl)))" + "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, + "Connection": "Server=xxxxx:5236;User Id=xxxxx;PWD=xxxxx;SCHEMA=TESTDBA;" + }, + { + "ConnId": "WMBLOG_KDBNDP", + "DBType": 6, + "Enabled": false, + "Connection": "Server=127.0.0.1;Port=54321;UID=SYSTEM;PWD=system;database=SQLSUGAR4XTEST1;" } ], "Audience": { @@ -117,13 +184,18 @@ "Issuer": "Blog.Core", "Audience": "wr" }, + "Mongo": { + "ConnectionString": "mongodb://nosql.data", + "Database": "BlogCoreDb" + }, "Startup": { + "Domain": "http://localhost:9291", "Cors": { "PolicyName": "CorsIpAccess", //策略名称 - "EnableAllIPs": false, //是否应用所有的IP + "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 @@ -131,25 +203,59 @@ "ApiName": "Blog.Core", "IdentityServer4": { "Enabled": false, // 这里默认是false,表示使用jwt,如果设置为true,则表示系统使用Ids4模式 - "AuthorizationUrl": "https://ids.neters.club", // 认证中心域名 + "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 + "Enabled": false //redis 消息队列 + }, + "MiniProfiler": { + "Enabled": false //性能分析开启 + }, + "Nacos": { + "Enabled": false //Nacos注册中心 } }, "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 + "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 @@ -169,6 +275,11 @@ "IpWhitelist": [], //白名单 "EndpointWhitelist": [ "get:/api/xxx", "*:/api/yyy" ], "ClientWhitelist": [ "dev-client-1", "dev-client-2" ], + "QuotaExceededResponse": { + "Content": "{{\"status\":429,\"msg\":\"访问过于频繁,请稍后重试\",\"success\":false}}", + "ContentType": "application/json", + "StatusCode": 429 + }, "HttpStatusCode": 429, //返回状态码 "GeneralRules": [ //api规则,结尾一定要带* { @@ -200,5 +311,38 @@ "ServicePort": "9291", "ServiceHealthCheck": "/healthcheck", "ConsulAddress": "http://localhost:8500" + }, + "PayInfo": { //建行聚合支付信息 + "MERCHANTID": "", //商户号 + "POSID": "", //柜台号 + "BRANCHID": "", //分行号 + "pubKey": "", //公钥 + "USER_ID": "", //操作员号 + "PASSWORD": "", //密码 + "OutAddress": "http://127.0.0.1:12345" //外联地址 + }, + "nacos": { + "ServerAddresses": [ "http://localhost:8848" ], // nacos 连接地址 + "DefaultTimeOut": 15000, // 默认超时时间 + "Namespace": "public", // 命名空间 + "ListenInterval": 10000, // 监听的频率 + "ServiceName": "blog.Core.Api", // 服务名 + "Port": "9291", // 服务端口号 + "RegisterEnabled": true // 是否直接注册nacos + }, + "LogFiedOutPutConfigs": { + "tcpAddressHost": "", // 输出elk的tcp连接地址 + "tcpAddressPort": 0, // 输出elk的tcp端口号 + "ConfigsInfo": [ // 配置的输出elk节点内容 常用语动态标识 + { + "FiedName": "applicationName", + "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 e587ddae..c04e775b 100644 --- a/Blog.Core.sln +++ b/Blog.Core.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32014.148 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Api", "Blog.Core.Api\Blog.Core.Api.csproj", "{6F47A41A-085E-4422-BB73-5A2CBAA07D9F}" EndProject @@ -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}" @@ -51,12 +52,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBus", "EventBus", "{A5 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.EventBus", "Blog.Core.EventBus\Blog.Core.EventBus.csproj", "{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.ConsoleApp", "Blog.Core.ConsoleApp\Blog.Core.ConsoleApp.csproj", "{0B3265A9-6716-4D28-8648-C64D5E692ACA}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Gateway", "Blog.Core.Gateway\Blog.Core.Gateway.csproj", "{A11C0DF2-1E13-4EED-BA49-44A57136B189}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Serilog.Es", "Blog.Core.Serilog.Es\Blog.Core.Serilog.Es.csproj", "{52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}" 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 @@ -107,10 +116,6 @@ Global {17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Debug|Any CPU.Build.0 = Debug|Any CPU {17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Release|Any CPU.ActiveCfg = Release|Any CPU {17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Release|Any CPU.Build.0 = Release|Any CPU - {0B3265A9-6716-4D28-8648-C64D5E692ACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B3265A9-6716-4D28-8648-C64D5E692ACA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B3265A9-6716-4D28-8648-C64D5E692ACA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B3265A9-6716-4D28-8648-C64D5E692ACA}.Release|Any CPU.Build.0 = Release|Any CPU {A11C0DF2-1E13-4EED-BA49-44A57136B189}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A11C0DF2-1E13-4EED-BA49-44A57136B189}.Debug|Any CPU.Build.0 = Debug|Any CPU {A11C0DF2-1E13-4EED-BA49-44A57136B189}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -119,6 +124,14 @@ Global {52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}.Release|Any CPU.ActiveCfg = Release|Any CPU {52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}.Release|Any CPU.Build.0 = Release|Any CPU + {6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 @@ -127,8 +140,17 @@ Global {300A8113-8033-4184-BE28-FC48D8349CD0} = {EDA8901E-541E-4ADC-B71E-59697D5F9549} {52D318A2-F44E-4CB7-8DD4-483357D4333F} = {047A9723-9AAC-42E3-8C69-B3835F15FF96} {17C9E9DC-E926-4C90-9025-3DAC55D7EDA3} = {A592C96A-4E44-4F2A-AC21-30683AF6C493} - {0B3265A9-6716-4D28-8648-C64D5E692ACA} = {047A9723-9AAC-42E3-8C69-B3835F15FF96} {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/CreateYourProject.bat b/CreateYourProject.bat index 43a2a429..9f8dcc22 100644 --- a/CreateYourProject.bat +++ b/CreateYourProject.bat @@ -1,11 +1,11 @@ color 5 -echo "if u install template error,pls use:>>dotnet new -i .template.config\Blog.Core.Webapi.Template.2.5.2.nupkg" +echo "if u install template error,pls connect QQ:3143422472" color 3 -dotnet new -i Blog.Core.Webapi.Template::2.5.3 +dotnet new -i Blog.Core.Webapi.Template -set /p OP=Please set your project name(for example:Baidu.Api): +set /p OP=Please set your project name(for example:BlogMicService): md .1YourProject 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 7445e602..575d3583 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,31 +5,47 @@ #如果你想先手动dotnet build成可执行的二进制文件,然后再构建镜像,请看.Api层下的dockerfile。 -FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-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:5.0-buster-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 COPY --from=publish /app/publish . EXPOSE 9291 -ENTRYPOINT ["dotnet", "Blog.Core.Api.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "Blog.Core.Api.dll"] diff --git a/Ocelot.Provider.Nacos/Nacos.cs b/Ocelot.Provider.Nacos/Nacos.cs new file mode 100644 index 00000000..b1d4cd4f --- /dev/null +++ b/Ocelot.Provider.Nacos/Nacos.cs @@ -0,0 +1,57 @@ +using Ocelot.ServiceDiscovery.Providers; +using Ocelot.Values; +using Nacos.V2; +using Microsoft.Extensions.Options; +using Ocelot.Provider.Nacos.NacosClient.V2; +using NacosConstants = Nacos.V2.Common.Constants; + +namespace Ocelot.Provider.Nacos +{ + public class Nacos : IServiceDiscoveryProvider + { + private readonly INacosNamingService _client; + private readonly string _serviceName; + private readonly string _groupName; + private readonly List _clusters; + + public Nacos(string serviceName, INacosNamingService client, IOptions options) + { + _serviceName = serviceName; + _client = client; + _groupName = string.IsNullOrWhiteSpace(options.Value.GroupName) ? + NacosConstants.DEFAULT_GROUP : options.Value.GroupName; + _clusters = (string.IsNullOrWhiteSpace(options.Value.ClusterName) ? NacosConstants.DEFAULT_CLUSTER_NAME : options.Value.ClusterName).Split(",").ToList(); + } + + public async Task> Get() + { + var services = new List(); + + var instances = await _client.GetAllInstances(_serviceName, _groupName, _clusters); + + if (instances != null && instances.Any()) + { + foreach (var Sitem in instances) + { + string sip = Sitem.Ip; + int sport = Sitem.Port; + if (Sitem.Metadata.ContainsKey("endpoint")) + { + string[] ipport = Sitem.Metadata["endpoint"].Split(':'); + sip = ipport[0]; + sport =int.Parse( ipport[1]); + } + services.Add(new Service(Sitem.InstanceId, new ServiceHostAndPort(sip, sport), "", "", new List())); + } + // services.AddRange(instances.Select(i => new Service(i.InstanceId, new ServiceHostAndPort(i.Ip, i.Port), "", "", new List()))); + } + + return await Task.FromResult(services); + } + + public Task> GetAsync() + { + throw new NotImplementedException(); + } + } +} diff --git a/Ocelot.Provider.Nacos/NacosClient/LoadBalance/ILBStrategy.cs b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/ILBStrategy.cs new file mode 100644 index 00000000..5bae9078 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/ILBStrategy.cs @@ -0,0 +1,26 @@ +using Nacos; +using Nacos.V2.Naming.Dtos; +using System.Collections.Generic; + +namespace Ocelot.Provider.Nacos.NacosClient +{ + public interface ILBStrategy + { + /// + /// Strategy Name + /// + LBStrategyName Name { get; } + + /// + /// Get host + /// + /// host list + /// The Host + Instance GetHost(List list); + } + + + + + +} diff --git a/Ocelot.Provider.Nacos/NacosClient/LoadBalance/LBStrategyName.cs b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/LBStrategyName.cs new file mode 100644 index 00000000..a01a0136 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/LBStrategyName.cs @@ -0,0 +1,20 @@ +namespace Ocelot.Provider.Nacos.NacosClient +{ + public enum LBStrategyName + { + /// + /// Weight Round Robin + /// + WeightRoundRobin, + + /// + /// Weight Random + /// + WeightRandom, + + /// + /// Ext1 + /// + Ext1 + } +} diff --git a/Ocelot.Provider.Nacos/NacosClient/LoadBalance/LbKv.cs b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/LbKv.cs new file mode 100644 index 00000000..da3969e4 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/LbKv.cs @@ -0,0 +1,9 @@ +namespace Ocelot.Provider.Nacos.NacosClient +{ + public class LbKv + { + public string InstanceId { get; set; } + + public double Weight { get; set; } + } +} diff --git a/Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRandomLBStrategy.cs b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRandomLBStrategy.cs new file mode 100644 index 00000000..e6c8fa1b --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRandomLBStrategy.cs @@ -0,0 +1,74 @@ +using Nacos; +using Nacos.V2.Naming.Dtos; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ocelot.Provider.Nacos.NacosClient +{ + public class WeightRandomLBStrategy : ILBStrategy + { + public LBStrategyName Name => LBStrategyName.WeightRandom; + + public Instance GetHost(List list) + { + var dict = BuildScore(list); + + Instance instance = null; + + var rd = new Random().NextDouble(); + + foreach (var item in dict) + { + if (item.Value >= rd) + { + instance = list.FirstOrDefault(x => x.InstanceId.Equals(item.Key)); + + if (instance == null) + { + var arr = item.Key.Split("#"); + var ip = arr[0]; + int.TryParse(arr[1], out var port); + var cluster = arr[2]; + + instance = list.First(x => x.Ip.Equals(ip) && x.Port == port && x.ClusterName.Equals(cluster)); + } + + break; + } + } + + return instance; + } + + private Dictionary BuildScore(List list) + { + var dict = new Dictionary(); + + // aliyun sae, the instanceid returns empty string + // when the instanceid is empty, create a new one, but the group was missed. + list.ForEach(x => { x.InstanceId = string.IsNullOrWhiteSpace(x.InstanceId) ? $"{x.Ip}#{x.Port}#{x.ClusterName}#{x.ServiceName}" : x.InstanceId; }); + + var tmp = list.Select(x => new LbKv + { + InstanceId = x.InstanceId, + Weight = x.Weight + }).GroupBy(x => x.InstanceId).Select(x => new LbKv + { + InstanceId = x.Key, + Weight = x.Max(y => y.Weight) + }).ToList(); + + var total = tmp.Sum(x => x.Weight); + var cur = 0d; + + foreach (var item in tmp) + { + cur += item.Weight; + dict.TryAdd(item.InstanceId, cur / total); + } + + return dict; + } + } +} diff --git a/Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRoundRobinLBStrategy.cs b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRoundRobinLBStrategy.cs new file mode 100644 index 00000000..c6efe7c9 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRoundRobinLBStrategy.cs @@ -0,0 +1,71 @@ +using Nacos; +using System.Collections.Generic; +using System.Linq; +using Nacos.V2.Naming.Dtos; +namespace Ocelot.Provider.Nacos.NacosClient +{ + public class WeightRoundRobinLBStrategy : ILBStrategy + { + public LBStrategyName Name => LBStrategyName.WeightRoundRobin; + + private int _pos; + + private static object obj = new object(); + + public Instance GetHost(List list) + { + // aliyun sae, the instanceid returns empty string + // when the instanceid is empty, create a new one, but the group was missed. + list.ForEach(x => { x.InstanceId = string.IsNullOrWhiteSpace(x.InstanceId) ? $"{x.Ip}#{x.Port}#{x.ClusterName}#{x.ServiceName}" : x.InstanceId; }); + + var tmp = list.Select(x => new LbKv + { + InstanceId = x.InstanceId, + Weight = x.Weight + }).GroupBy(x => x.InstanceId).Select(x => new LbKv + { + InstanceId = x.Key, + Weight = x.Max(y => y.Weight) + }).ToList(); + + // + var dic = tmp.ToDictionary(k => k.InstanceId, v => (int)v.Weight); + + var srcInstanceIdList = dic.Keys.ToList(); + var tagInstanceIdList = new List(); + + foreach (var item in srcInstanceIdList) + { + dic.TryGetValue(item, out var weight); + + for (int i = 0; i < weight; i++) + tagInstanceIdList.Add(item); + } + + var instanceId = string.Empty; + + lock (obj) + { + if (_pos >= tagInstanceIdList.Count) + _pos = 0; + + instanceId = tagInstanceIdList[_pos]; + _pos++; + } + + var instance = list.FirstOrDefault(x => x.InstanceId.Equals(instanceId)); + + if (instance == null) + { + var arr = instanceId.Split("#"); + var ip = arr[0]; + int.TryParse(arr[1], out var port); + var cluster = arr[2]; + + instance = list.First(x => x.Ip.Equals(ip) && x.Port == port && x.ClusterName.Equals(cluster)); + } + + return instance; + } + } +} diff --git a/Ocelot.Provider.Nacos/NacosClient/UriTool.cs b/Ocelot.Provider.Nacos/NacosClient/UriTool.cs new file mode 100644 index 00000000..dbb7cf8e --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/UriTool.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http.Features; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +namespace Ocelot.Provider.Nacos.NacosClient +{ + internal static class UriTool + { + public static IEnumerable GetUri(IFeatureCollection features, string ip, int port, string preferredNetworks) + { + var splitChars = new char[] { ',', ';' }; + var appPort = port <= 0 ? 80 : port; + + // 1. config + if (!string.IsNullOrWhiteSpace(ip)) + { + // it seems that nacos don't return the scheme + // so here use http only. + return new List { new Uri($"http://{ip}:{appPort}") }; + } + + // 1.1. Ip is null && Port has value + if (string.IsNullOrWhiteSpace(ip) && appPort != 80) + { + return new List { new Uri($"http://{GetCurrentIp(preferredNetworks)}:{appPort}") }; + } + + var address = string.Empty; + + // 2. IServerAddressesFeature + if (features != null) + { + var addresses = features.Get(); + var addressCollection = addresses?.Addresses; + + if (addressCollection != null && addressCollection.Any()) + { + var uris = new List(); + foreach (var item in addressCollection) + { + var url = ReplaceAddress(item, preferredNetworks); + uris.Add(new Uri(url)); + } + + return uris; + } + } + + // 3. ASPNETCORE_URLS + address = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); + if (!string.IsNullOrWhiteSpace(address)) + { + var url = ReplaceAddress(address, preferredNetworks); + + return url.Split(splitChars).Select(x => new Uri(x)); + } + + // 4. --urls + var cmdArgs = Environment.GetCommandLineArgs(); + if (cmdArgs != null && cmdArgs.Any()) + { + var cmd = cmdArgs.FirstOrDefault(x => x.StartsWith("--urls", StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrWhiteSpace(cmd)) + { + address = cmd.Split('=')[1]; + + var url = ReplaceAddress(address, preferredNetworks); + + return url.Split(splitChars).Select(x => new Uri(x)); + } + } + + // 5. current ip address third + address = $"http://{GetCurrentIp(preferredNetworks)}:{appPort}"; + + return new List { new Uri(address) }; + } + + private static string ReplaceAddress(string address, string preferredNetworks) + { + var ip = GetCurrentIp(preferredNetworks); + + if (address.Contains("*")) + { + address = address.Replace("*", ip); + } + else if (address.Contains("+")) + { + address = address.Replace("+", ip); + } + else if (address.Contains("localhost", StringComparison.OrdinalIgnoreCase)) + { + address = address.Replace("localhost", ip, StringComparison.OrdinalIgnoreCase); + } + else if (address.Contains("0.0.0.0", StringComparison.OrdinalIgnoreCase)) + { + address = address.Replace("0.0.0.0", ip, StringComparison.OrdinalIgnoreCase); + } + + return address; + } + + private static string GetCurrentIp(string preferredNetworks) + { + var instanceIp = "127.0.0.1"; + + try + { + // 获取可用网卡 + var nics = NetworkInterface.GetAllNetworkInterfaces()?.Where(network => network.OperationalStatus == OperationalStatus.Up); + + // 获取所有可用网卡IP信息 + var ipCollection = nics?.Select(x => x.GetIPProperties())?.SelectMany(x => x.UnicastAddresses); + + foreach (var ipadd in ipCollection) + { + if (!IPAddress.IsLoopback(ipadd.Address) && ipadd.Address.AddressFamily == AddressFamily.InterNetwork) + { + if (string.IsNullOrEmpty(preferredNetworks)) + { + instanceIp = ipadd.Address.ToString(); + break; + } + + if (!ipadd.Address.ToString().StartsWith(preferredNetworks)) continue; + instanceIp = ipadd.Address.ToString(); + break; + } + } + } + catch + { + // ignored + } + + return instanceIp; + } + } +} + diff --git a/Ocelot.Provider.Nacos/NacosClient/V2/NacosAspNetOptions.cs b/Ocelot.Provider.Nacos/NacosClient/V2/NacosAspNetOptions.cs new file mode 100644 index 00000000..12903962 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/V2/NacosAspNetOptions.cs @@ -0,0 +1,96 @@ +using Nacos.V2; +using Nacos.V2.Common; +using System.Collections.Generic; + +namespace Ocelot.Provider.Nacos.NacosClient.V2 +{ + public class NacosAspNetOptions : NacosSdkOptions + { + /// + /// the name of the service. + /// + public string ServiceName { get; set; } + + /// + /// the name of the group. + /// + public string GroupName { get; set; } = Constants.DEFAULT_GROUP; + + /// + /// the name of the cluster. + /// + /// The name of the cluster. + public string ClusterName { get; set; } = Constants.DEFAULT_CLUSTER_NAME; + + /// + /// the ip of this instance + /// + public string Ip { get; set; } + + /// + /// Select an IP that matches the prefix as the service registration IP + /// like the config of spring.cloud.inetutils.preferred-networks + /// + public string PreferredNetworks { get; set; } + + /// + /// the port of this instance + /// + public int Port { get; set; } + + /// + /// the weight of this instance. + /// + public double Weight { get; set; } = 100; + + /// + /// if you just want to subscribe, but don't want to register your service, set it to false. + /// + public bool RegisterEnabled { get; set; } = true; + + /// + /// the metadata of this instance + /// + public Dictionary Metadata { get; set; } = new Dictionary(); + + /// + /// If instance is enabled to accept request. The default value is true. + /// + public bool InstanceEnabled { get; set; } = true; + + /// + /// If instance is ephemeral.The default value is true. + /// + public bool Ephemeral { get; set; } = true; + + /// + /// whether your service is a https service. + /// + public bool Secure { get; set; } = false; + + /// + /// Load Balance Strategy + /// + public string LBStrategy { get; set; } = LBStrategyName.WeightRandom.ToString(); + + public NacosSdkOptions BuildSdkOptions() + { + return new NacosSdkOptions + { + AccessKey = this.AccessKey, + ConfigUseRpc = this.ConfigUseRpc, + ContextPath = this.ContextPath, + DefaultTimeOut = this.DefaultTimeOut, + EndPoint = this.EndPoint, + ListenInterval = this.ListenInterval, + Namespace = this.Namespace, + NamingLoadCacheAtStart = this.NamingLoadCacheAtStart, + NamingUseRpc = this.NamingUseRpc, + Password = this.Password, + SecretKey = this.SecretKey, + ServerAddresses = this.ServerAddresses, + UserName = this.UserName, + }; + } + } +} diff --git a/Ocelot.Provider.Nacos/NacosClient/V2/RegSvcBgTask.cs b/Ocelot.Provider.Nacos/NacosClient/V2/RegSvcBgTask.cs new file mode 100644 index 00000000..63709af1 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/V2/RegSvcBgTask.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Nacos.V2; +using Nacos.V2.Naming.Core; +using Nacos.V2.Naming.Dtos; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Ocelot.Provider.Nacos.NacosClient.V2 +{ + public class RegSvcBgTask + { + private static readonly string MetadataNetVersion = "DOTNET_VERSION"; + private static readonly string MetadataHostOs = "HOST_OS"; + private static readonly string MetadataSecure = "secure"; + + private readonly ILogger _logger; + private readonly INacosNamingService _svc; + private readonly IFeatureCollection _features; + private NacosAspNetOptions _options; + + private IEnumerable uris = null; + + public RegSvcBgTask( + ILoggerFactory loggerFactory, + INacosNamingService svc, + IServer server, + IOptionsMonitor optionsAccs) + { + _logger = loggerFactory.CreateLogger(); + _svc = svc; + _options = optionsAccs.CurrentValue; + _features = server.Features; + } + + public async Task StartAsync() + { + if (!_options.RegisterEnabled) + { + _logger.LogInformation("setting RegisterEnabled to false, will not register to nacos"); + return; + } + + uris = UriTool.GetUri(_features, _options.Ip, _options.Port, _options.PreferredNetworks); + + var metadata = new Dictionary() + { + { PreservedMetadataKeys.REGISTER_SOURCE, $"ASPNET_CORE" }, + { MetadataNetVersion, Environment.Version.ToString() }, + { MetadataHostOs, Environment.OSVersion.ToString() }, + }; + + if (_options.Secure) metadata[MetadataSecure] = "true"; + + foreach (var item in _options.Metadata) + { + if (!metadata.ContainsKey(item.Key)) + { + metadata.TryAdd(item.Key, item.Value); + } + } + + foreach (var uri in uris) + { + for (int i = 0; i < 3; i++) + { + try + { + var instance = new Instance + { + Ephemeral = _options.Ephemeral, + ServiceName = _options.ServiceName, + ClusterName = _options.ClusterName, + Enabled = _options.InstanceEnabled, + Healthy = true, + Ip = uri.Host, + Port = uri.Port, + Weight = _options.Weight, + Metadata = metadata, + InstanceId = "" + }; + + _logger.LogInformation("register instance to nacos server, 【{0}】", instance); + + await _svc.RegisterInstance(_options.ServiceName, _options.GroupName, instance); + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "register instance error, count = {0}", i + 1); + } + } + } + } + + public async Task StopAsync() + { + if (_options.RegisterEnabled) + { + _logger.LogWarning("deregister instance from nacos server, serviceName={0}", _options.ServiceName); + + foreach (var uri in uris) + { + for (int i = 0; i < 3; i++) + { + try + { + _logger.LogWarning("begin to remove instance"); + await _svc.DeregisterInstance(_options.ServiceName, _options.GroupName, uri.Host, uri.Port, _options.ClusterName); + _logger.LogWarning("removed instance"); + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "deregister instance error, count = {0}", i + 1); + } + } + } + } + } + } +} diff --git a/Ocelot.Provider.Nacos/NacosClient/V2/ServiceCollectionExtensions.cs b/Ocelot.Provider.Nacos/NacosClient/V2/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..43abb7e6 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosClient/V2/ServiceCollectionExtensions.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Nacos.V2.DependencyInjection; +using System; +using System.Threading.Tasks; + +namespace Ocelot.Provider.Nacos.NacosClient.V2 +{ + public static class ServiceCollectionExtensions + { + /// + /// Add Nacos AspNet. This will register and de-register instance automatically. + /// Mainly for nacos server 2.x + /// + /// services. + /// configuration + /// IServiceCollection + public static IServiceCollection AddNacosAspNet(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration.GetSection("nacos")); + services.AddNacosV2Naming(configuration); + services.AddSingleton(); + + return services; + } + + /// + /// Add Nacos AspNet. This will register and de-register instance automatically. + /// Mainly for nacos server 2.x + /// + /// services + /// optionsAccs + /// IServiceCollection + public static IServiceCollection AddNacosAspNet(this IServiceCollection services, Action optionsAccs) + { + services.Configure(optionsAccs); + + var options = new NacosAspNetOptions(); + optionsAccs.Invoke(options); + services.AddNacosV2Naming(x => options.BuildSdkOptions()); + services.AddSingleton(); + + return services; + } + + + public static async Task UseNacosAspNet(this IApplicationBuilder app, IHostApplicationLifetime lifetime) + { + RegSvcBgTask regSvcBgTask = app.ApplicationServices.GetRequiredService(); + await regSvcBgTask.StartAsync(); + lifetime.ApplicationStopping.Register(async () => { + await regSvcBgTask.StopAsync(); + }); + return app; + } + } +} diff --git a/Ocelot.Provider.Nacos/NacosMiddlewareConfigurationProvider.cs b/Ocelot.Provider.Nacos/NacosMiddlewareConfigurationProvider.cs new file mode 100644 index 00000000..f5f78641 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosMiddlewareConfigurationProvider.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Repository; +using Ocelot.Middleware; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Ocelot.Provider.Nacos.NacosClient.V2; + +namespace Ocelot.Provider.Nacos +{ + public class NacosMiddlewareConfigurationProvider + { + public static OcelotMiddlewareConfigurationDelegate Get = builder => + { + var internalConfigRepo = builder.ApplicationServices.GetService(); + var config = internalConfigRepo.Get(); + + var hostLifetime = builder.ApplicationServices.GetService(); + + if (UsingNacosServiceDiscoveryProvider(config.Data)) + { + builder.UseNacosAspNet(hostLifetime).GetAwaiter().GetResult(); + } + + return Task.CompletedTask; + }; + + private static bool UsingNacosServiceDiscoveryProvider(IInternalConfiguration configuration) + { + return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "nacos"; + } + } +} diff --git a/Ocelot.Provider.Nacos/NacosProviderFactory.cs b/Ocelot.Provider.Nacos/NacosProviderFactory.cs new file mode 100644 index 00000000..dc3dfb11 --- /dev/null +++ b/Ocelot.Provider.Nacos/NacosProviderFactory.cs @@ -0,0 +1,23 @@ +using System; +using Ocelot.ServiceDiscovery; +using Microsoft.Extensions.DependencyInjection; +using Nacos.V2; +using Ocelot.Provider.Nacos.NacosClient.V2; +using Microsoft.Extensions.Options; + +namespace Ocelot.Provider.Nacos +{ + public static class NacosProviderFactory + { + public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) => + { + var client = provider.GetService(); + if (config.Type?.ToLower() == "nacos" && client != null) + { + var option = provider.GetService>(); + return new Nacos(route.ServiceName, client, option); + } + return null; + }; + } +} diff --git a/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj b/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj new file mode 100644 index 00000000..a4c4225f --- /dev/null +++ b/Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj @@ -0,0 +1,19 @@ + + + + softlgl + softlgl + https://github.com/softlgl/Ocelot.Provider.Nacos + Ocelot.Provider.Nacos + Repo for Nacos integration with Ocelot + 1.2.1 + + + + + + + + + + diff --git a/Ocelot.Provider.Nacos/OcelotBuilderExtensions.cs b/Ocelot.Provider.Nacos/OcelotBuilderExtensions.cs new file mode 100644 index 00000000..93b176de --- /dev/null +++ b/Ocelot.Provider.Nacos/OcelotBuilderExtensions.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.Provider.Nacos.NacosClient.V2; +using Ocelot.ServiceDiscovery; + +namespace Ocelot.Provider.Nacos +{ + public static class OcelotBuilderExtensions + { + public static IOcelotBuilder AddNacosDiscovery(this IOcelotBuilder builder) + { + builder.Services.AddNacosAspNet(builder.Configuration); + builder.Services.AddSingleton(NacosProviderFactory.Get); + builder.Services.AddSingleton(NacosMiddlewareConfigurationProvider.Get); + return builder; + } + } +} diff --git a/README.md b/README.md index a83e2042..20b0dabf 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ [English](README-en.md) | 简体中文 -[![sdk](https://img.shields.io/badge/sdk-5.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/) +[![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)   @@ -19,43 +23,84 @@ - -Blog.Core 开箱即用的企业级前后端分离【 .NET Core5.0 Api + Vue 2.x + RBAC】权限框架。 +------------------------------- +Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x + RBAC】权限框架。 +其他版本看具体分支吧🎉 官网:http://apk.neters.club/.doc/ +--------------------- + +**已被近100家公司所使用(🐱‍🚀):[点击查看列表](https://github.com/anjoy8/Blog.Core/issues/75)** 欢迎盖楼,留下公司真实名字的,可得定制化指导服务。 +同时如果企业有付费咨询,欢迎联系老张(QQ:3143422472)。 -**已被多家公司所使用(70+🐱‍🚀):[点击查看列表](https://github.com/anjoy8/Blog.Core/issues/75)** - + +### 核心项目组成员(排名不分先后) + +[hudingwen](https://github.com/hudingwen)、[LemonNoCry](https://github.com/LemonNoCry)、、[Jamnine何拾玖](https://github.com/Jamnine)、 + + +#### ❤ 真实用户反馈 ❤ +``` 项目单体部署,并发在400~500,一切正常(不保证自己的各种错误写法)。 如果搭配负载,效果更好。 - +1、A~CoderDong: +应用场景:使用Blog.Core为基础骨架开发,搭建Client监控类守护进程项目,To C 客户群, +并发情况:目前压测并发5k正常8秒处理完,并发10k可15秒处理完毕,异常不会丢失。 +生产配置:一台服务器(Linux环境 + 至强8核的16G内存 + mysql数据库 + 3台Nginx负载) +``` + + +   -#### 联系我 -如果你对BCVP框架感兴趣,并有一定的框架设计经验,,欢迎加入架构师交流群,只要交流心得都可以进入,如果单纯提问问题的就算了哈。 - - +## 给个星星! ⭐️ +如果你喜欢这个项目或者它帮助你, 请给 Star~ +如果你的项目中借鉴了本项目,请稍微说明下[https://github.com/anjoy8/Blog.Core/issues/75](https://github.com/anjoy8/Blog.Core/issues/75),开源不易✨。 + ### 功能与进度 -框架模块: +#### 企业使用高级版本 + +- [x] 包含开源版 `框架模块/组件模块` 中的所有功能; +- [x] 全部表结构主键底层架构改成`string`类型(默认雪花,支持guid),更方便迁移; +- [x] 完善部门数据权限,可以基于策略配置查看数据范围; +- [x] 优化权限处理器,解决多实例分布式下,权限不同步问题(必须配置Redis); +- [x] 增加在线用户查看功能,并实现强制用户下线功能(必须配置Redis); +- [x] 增加用户黑名单功能(必须配置Redis); +- [x] 增加岗位功能(单独建表),配合部门使用; +- [ ] 后期优化站内通知功能,其实目前已经有SignalR来实现消息推送了,可以直接用; +- [ ] 前端`Blog.Admin.Pro`使用`AntDesignVue`框架(设计中,未完全实现); +- [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] 五种日志记录,审计/异常/请求响应/服务操作/Sql记录等; +- [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 做缓存处理; @@ -65,7 +110,7 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core5.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 做任务调度(目前单机多任务,集群调度暂不支持); @@ -78,34 +123,52 @@ Blog.Core 开箱即用的企业级前后端分离【 .NET Core5.0 Api + Vue 2.x - [x] 新增 - ES 搜索配置; - [x] 新增 - Apollo 配置; - [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) + + +### 自定义(中间件/服务)启动图 +![系统架构图](https://img.neters.club/github/load-tool.png) +   -## 给个星星! ⭐️ -如果你喜欢这个项目或者它帮助你, 请给 Star~ -如果你的项目中借鉴了本项目,请稍微说明下[https://github.com/anjoy8/Blog.Core/issues/75](https://github.com/anjoy8/Blog.Core/issues/75),开源不易✨。 ## 贡献者们 -Thanks goes to these wonderful people ([✨](https://github.com/anjoy8/Blog.Core/graphs/contributors)):(排名暂时按提交顺序) - - -| [
anjoy8](https://github.com/anjoy8)
💻📖 💡 | [
hudingwen](https://github.com/hudingwen)
💻 👀 | [
binyly ](https://github.com/binyly)
💻 👀 📖 👍 | [
wuare ](https://github.com/wuare)
💻😀 | [
skang0401 ](https://github.com/skang0401)
📖| [
Jamnine](https://github.com/Jamnine)
💻 🌍| -| :---: | :---: | :---: | :---: | :---: | :---: | -|[
aion1998 ](https://github.com/aion1998)
👍|[
RLei123 ](https://github.com/RLei123)
😄|[
cluyun ](https://github.com/cluyun)
🍬|[
blue20171027 ](https://github.com/blue20171027)
✈|[
anewboyz ](https://github.com/anewboyz)
💻|[
jxd728 ](https://github.com/jxd728)
🌍| -|[
wmchuang ](https://github.com/wmchuang)
🍟|[
liuzhenyulive ](https://github.com/liuzhenyulive)
💻|[
JsonBy ](https://github.com/JsonBy)
💻 💡 🤔|[
hsxian ](https://github.com/hsxian)
🎉|[
cuno92 ](https://github.com/cuno92)
📖|[
317447880 ](https://github.com/317447880)
💻| -|[
Shuisen ](https://github.com/Shuisen)
💻|[
www5255977 ](https://github.com/www5255977)
🌍|[
867824092 ](https://github.com/867824092)
🍳| +Thanks goes to these wonderful people ([✨](https://github.com/anjoy8/Blog.Core/graphs/contributors)):(排名暂时按提交顺序) + + + + + This project follows the [all-contributors](https://github.com/anjoy8/Blog.Core/graphs/contributors) specification. @@ -194,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/azure-pipelines.yml b/azure-pipelines.yml index 203013b7..5776c0a2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,34 +1,21 @@ -# ASP.NET Core (.NET Framework) -# Build and test ASP.NET Core projects targeting the full .NET Framework. -# Add steps that publish symbols, save build artifacts, and more: +# ASP.NET Core +# Build and test ASP.NET Core projects targeting .NET Core. +# Add steps that run tests, create a NuGet package, deploy, and more: # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core trigger: -- main +- master pool: - vmImage: 'windows-latest' + vmImage: ubuntu-latest variables: - solution: '**/*.sln' - buildPlatform: 'Any CPU' buildConfiguration: 'Release' steps: -- task: NuGetToolInstaller@1 - -- task: NuGetCommand@2 - inputs: - restoreSolution: '$(solution)' - -- task: VSBuild@1 - inputs: - solution: '$(solution)' - msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - -- task: VSTest@2 +- task: UseDotNet@2 + displayName: 'Use .NET Core sdk' inputs: - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' + packageType: sdk + version: 6.0.x + installationPath: $(Agent.ToolsDirectory)/dotnet \ No newline at end of file diff --git a/build/README.md b/build/README.md new file mode 100644 index 00000000..e69de29b