diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3729ff0c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file 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/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7711e946 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# IDE0005: Using 指令是不需要的。 +dotnet_diagnostic.IDE0005.severity = warning diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..7c3b4241 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: http://apk.neters.club/laozhangisphigood.jpg diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..96dd2c8f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,22 @@ +blank_issues_enabled: false +contact_links: + - name: 🚨 Bug report | Bug 提交 + url: https://github.com/anjoy8/Blog.Core/issues/new + about: | + Please report bugs here. + 请在此提交 Bug。 + - name: 🙋 Feature request | 新功能提案 + url: https://github.com/anjoy8/Blog.Core/issues/new + about: | + Please request features here. + 请在此提交新功能提案。 + - name: 🤔 Consulting from the Blog.Core team | 咨询 作者 + url: https://github.com/anjoy8/Blog.Core/issues/new + about: | + Get technical support, project audits, app deployments, and custom development from the core Blog.Core team. + 咨询核心 Blog.Core 团队以获得技术支持,项目审核,应用程序部署以及自定义开发等方面上的帮助。 + - name: ❗️ All other issues | 其他问题 + url: https://github.com/anjoy8/Blog.Core/issues/new + about: | + Please create all other issues here. + 请在此创建其他类型问题。 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. 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 new file mode 100644 index 00000000..f11fee1f --- /dev/null +++ b/.github/workflows/dotnetcore.yml @@ -0,0 +1,23 @@ +name: .NET Core + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + 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 86e9fc81..99804f89 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ [Rr]eleases/ x64/ x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ @@ -33,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.* @@ -209,7 +214,7 @@ _pkginfo.txt # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!*.[Cc]ache/ +!?*.[Cc]ache/ # Others ClientBin/ @@ -229,6 +234,8 @@ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ +# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true +**/wwwroot/lib/ # RIA/Silverlight projects Generated_Code/ @@ -333,5 +340,23 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ +# BeatPulse healthcheck temp database +healthchecksdb + + # wwwroot/images -*images/ \ No newline at end of file +*images/ +.1YourProject +.PublishFiles +!.template.config/*.nupkg +!Blog.Core.Webapi.Template.*.nupkg +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/.template.config/Blog.Core.Webapi.Template.2.5.2.nupkg b/.template.config/Blog.Core.Webapi.Template.2.5.2.nupkg new file mode 100644 index 00000000..1e04c646 Binary files /dev/null and b/.template.config/Blog.Core.Webapi.Template.2.5.2.nupkg differ diff --git a/.template.config/template.json b/.template.config/template.json new file mode 100644 index 00000000..3e074d94 --- /dev/null +++ b/.template.config/template.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "lao zhang", + "classifications": [ "Web/WebAPI" ], + "name": "Blog.Core Dotnet", + "identity": "Blog.Core.Template", + "shortName": "blogcoretpl", + "tags": { + "language": "C#" , + "type":"project" + }, + "sourceName": "Blog.Core", + "preferNameDirectory": true +} \ No newline at end of file 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 new file mode 100644 index 00000000..8bc327ab --- /dev/null +++ b/Blog.Core.Api/Blog.Core.Api.csproj @@ -0,0 +1,122 @@ + + + + Exe + enable + + Linux + true + default + + + + ..\Blog.Core.Api\Blog.Core.xml + 1701;1702;1591 + + + + ..\Blog.Core\Blog.Core.xml + 1701;1702;1591 + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + PreserveNewest + + + + + + Always + + + Always + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Blog.Core.Api/Blog.Core.Model.xml b/Blog.Core.Api/Blog.Core.Model.xml new file mode 100644 index 00000000..00b4c5be --- /dev/null +++ b/Blog.Core.Api/Blog.Core.Model.xml @@ -0,0 +1,3407 @@ + + + + Blog.Core.Model + + + + + 无任何权限 + + + + + 自定义权限 + + + + + 本部门 + + + + + 本部门及以下 + + + + + 仅自己 + + + + + 所有 + + + + + 以下model 来自ids4项目,多库模式,为了调取ids4数据 + 角色表 + + + + + 排序 + + + + + 是否激活 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + 以下model 来自ids4项目,多库模式,为了调取ids4数据 + 用户表 + + + + + 这是爱 + + + + + id + + + + + 姓名 + + + + + 年龄 + + + + + 通用返回信息类 + + + + + 状态码 + + + + + 操作是否成功 + + + + + 返回信息 + + + + + 开发者信息 + + + + + 返回数据集合 + + + + + 返回成功 + + 消息 + + + + + 返回成功 + + 消息 + 数据 + + + + + 返回失败 + + 消息 + + + + + 返回失败 + + 消息 + 数据 + + + + + 返回消息 + + 失败/成功 + 消息 + 数据 + + + + + 状态码 + + + + + 操作是否成功 + + + + + 返回信息 + + + + + 返回数据集合 + + + + + 用户访问趋势日志 + + + + + 用户 + + + + + 次数 + + + + + 更新时间 + + + + + 广告图片 + + + + + 广告标题 + + + + + 广告链接 + + + + + 备注 + + + + + 创建时间 + + + + + 博客文章 + + + + + 主键 + + 这里之所以没用RootEntity,是想保持和之前的数据库一致,主键是bID,不是Id + + + + 创建人 + + + + + 标题blog + + + + + 类别 + + + + + 内容 + + + + + 访问量 + + + + + 评论数量 + + + + + 修改时间 + + + + + 创建时间 + + + + + 备注 + + + + + 逻辑删除 + + + + + 评论 + + + + + 博客文章 评论 + + + + + 部门表 + + + + + 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 + + + + + 创建时间 + + + + + 手机 + + + + + qq + + + + + 留言内容 + + + + + ip地址 + + + + + 是否显示在前台,0否1是 + + + + + + 接口API地址信息表 + + + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + + + 名称 + + + + + 菜单链接地址 + + + + + 区域名称 + + + + + 控制器名称 + + + + + Action名称 + + + + + 图标 + + + + + 菜单编号 + + + + + 排序 + + + + + /描述 + + + + + 是否是右侧菜单 + + + + + 是否激活 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + 日志记录 + + + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + + + 区域名 + + + + + 区域控制器名 + + + + + Action名称 + + + + + IP地址 + + + + + 描述 + + + + + 登录时间 + + + + + 登录名称 + + + + + 用户ID + + + + + 密码库表 + + + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + + + 路由菜单表 + + + + + 菜单执行Action名 + + + + + 菜单显示名(如用户页、编辑(按钮)、删除(按钮)) + + + + + 是否是按钮 + + + + + 是否是隐藏菜单 + + + + + 是否keepAlive + + + + + 按钮事件 + + + + + 排序 + + + + + 菜单图标 + + + + + 菜单图标新 + + + + + 菜单描述 + + + + + 激活状态 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + + + 角色表 + + + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + + + 角色名 + + + + + 描述 + + + + + 排序 + + + + + 自定义权限的部门ids + + + + + 权限范围 + -1 无任何权限;1 自定义权限;2 本部门;3 本部门及以下;4 仅自己;9 全部; + + + + + 是否激活 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + 按钮跟权限关联表 + + + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + 状态
+ 中立字段,某些表可使用某些表不使用 +
+
+ + + 中立字段,某些表可使用某些表不使用
+ 逻辑上的删除,非物理删除
+ 例如:单据删除并非直接删除 +
+
+ + + 中立字段
+ 是否内置数据 +
+
+ + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 更新者 + + + + + 修改日期 + + + + + 数据版本 + + + + + 软删除 过滤器 + + + + + 系统租户表
+ 根据TenantType 分为两种方案:
+ 1.按租户字段区分
+ 2.按租户分库
+ +
+ + 注意:
+ 使用租户Id方案,无需配置分库的连接 +
+
+ + + 名称 + + + + + 租户类型 + + + + + 数据库/租户标识 不可重复
+ 使用Id方案,可无需配置 +
+
+ + + 主机
+ 使用Id方案,可无需配置 +
+
+ + + 数据库类型
+ 使用Id方案,可无需配置 +
+
+ + + 数据库连接
+ 使用Id方案,可无需配置 +
+
+ + + 状态 + + + + + 备注 + + + + + 用户信息表 + + + + + 登录账号 + + + + + 登录密码 + + + + + 真实姓名 + + + + + 状态 + + + + + 部门 + + + + + 备注 + + + + + 创建时间 + + + + + 更新时间 + + + + + 关键业务修改时间 + + + + + 最后异常时间 + + + + + 错误次数 + + + + + 登录账号 + + + + + 租户Id + + + + + 任务日志表 + + + + + 任务ID + + + + + 任务耗时 + + + + + 执行结果(0-失败 1-成功) + + + + + 运行时间 + + + + + 结束时间 + + + + + 执行参数 + + + + + 异常信息 + + + + + 异常堆栈 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + 任务名称 + + + + + 任务分组 + + + + + 任务计划表 + + + + + 任务名称 + + + + + 任务分组 + + + + + 任务运行时间表达式 + + + + + 任务所在DLL对应的程序集名称 + + + + + 任务所在类 + + + + + 任务描述 + + + + + 执行次数 + + + + + 开始时间 + + + + + 结束时间 + + + + + 触发器类型(0、simple 1、cron) + + + + + 执行间隔时间, 秒为单位 + + + + + 循环执行次数 + + + + + 已循环次数 + + + + + 是否启动 + + + + + 执行传参 + + + + + 创建时间 + + + + + 任务内存中的状态 + + + + + 业务数据
+ 多租户 (Id 隔离) +
+
+ + + 无需手动赋值 + + + + + 名称 + + + + + 金额 + + + + + 多租户-多表方案 业务表 子表
+
+
+ + + 多租户-多表方案 业务表
+
+
+ + + 名称 + + + + + 金额 + + + + + 多租户-多库方案 业务表
+ 公共库无需标记[MultiTenant]特性 +
+
+ + + 名称 + + + + + 金额 + + + + + Tibug 类别 + + + + + Tibug 博文 + + + + + 用户跟角色关联表 + + + + + 获取或设置是否禁用,逻辑上的删除,非物理删除 + + + + + 创建ID + + + + + 创建者 + + + + + 创建时间 + + + + + 修改ID + + + + + 修改者 + + + + + 修改时间 + + + + + + + + + + 公司ID + + + + + 公司名称 + + + + + 公司IP + + + + + 公司备注 + + + + + api地址 + + + + + 是否激活 + + + + + 创建者id + + + + + 创建人 + + + + + 创建时间 + + + + + 修改者id + + + + + 修改人 + + + + + 修改时间 + + + + + + + + + + 微信公众号唯一标识 + + + + + 微信公众号名称 + + + + + 微信账号 + + + + + 微信名称 + + + + + 应用ID + + + + + 应用秘钥 + + + + + 公众号推送token + + + + + 验证秘钥(验证消息是否真实) + + + + + 微信公众号token过期时间 + + + + + 备注 + + + + + 是否激活 + + + + + 创建者id + + + + + 创建人 + + + + + 创建时间 + + + + + 修改者id + + + + + 修改人 + + + + + 修改时间 + + + + + + + + + + 推送ID + + + + + 来自谁 + + + + + 推送IP + + + + + 推送客户 + + + + + 推送用户 + + + + + 推送模板ID + + + + + 推送内容 + + + + + 推送时间 + + + + + 推送状态(Y/N) + + + + + 备注 + + + + + 推送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 + + + + + 终端编号 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 + + + + + 昵称 + + + + + 性别 + + + + + 语言 + + + + + 城市 + + + + + 省份 + + + + + 城市 + + + + + 头像地址 + + + + + 微信推送消息Dto + 作者:胡丁文 + 时间:2020-4-8 09:16:16 + + + + + 推送关键信息 + + + + + 推送卡片消息Dto + + + + + 微信推送消息Dto + 作者:胡丁文 + 时间:2020-11-23 16:29:05 + + + + + 推送关键信息 + + + + + 推送卡片消息Dto + + + + + 消息模板dto(如何填写数据,请参考微信模板即可) + 作者:胡丁文 + 时间:2020-4-1 09:32:16 + + + + + 消息模板 + + + + + 标题 + + + + + 标题颜色(颜色代码都必须为#开头的16进制代码) + + + + + 内容1 + + + + + 内容1颜色 + + + + + 内容2 + + + + + 内容2颜色 + + + + + 内容3 + + + + + 内容3颜色 + + + + + 内容4 + + + + + 内容4颜色 + + + + + 内容5 + + + + + 内容5颜色 + + + + + 备注信息 + + + + + 备注信息颜色 + + + + + 跳转连接 + + + + + 获取微信菜单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 + + + + + 图片mediaID + + + + + 推送模拟消息Dto + 作者:胡丁文 + 时间:2020-4-24 14:52:44 + + + + + 当前选中的微信公众号 + + + + + 当前选中的操作集合 + + + + + 当前选中的绑定还是订阅 + + + + + 当前选中的微信客户 + + + + + 当前选中的消息类型 + + + + + 当前选中要发送的用户 + + + + + 文本消息 + + + + + 图片消息 + + + + + 语音消息 + + + + + 视频消息 + + + + + 链接消息 + + + + + 文字消息 + + + + + 视频标题 + + + + + 视频封面mediaID + + + + + 视频mediaID + + + + + 语音mediaID + + + + + 微信二维码预装发送信息dto + + + + + 微信二维码预装具体消息 + + + + + 微信二维码预装信息DTO + + + + + 返回给调用者的Dto + 作者:胡丁文 + 时间:2020-4-8 09:52:06 + + + + + 微信公众号ID + + + + + 公司代码 + + + + + 数据 + + + + + 微信消息模板Dto + + + + + 微信推送所需信息(公司版本) + 作者:胡丁文 + 时间:2020-4-8 09:04:36 + + + + + 微信公众号ID + + + + + 公司代码 + + + + + 用户id + + + + + 用户昵称 + + + + + 微信推送所需信息(OpenID版本) + 作者:胡丁文 + 时间:2020-11-23 16:27:29 + + + + + 微信公众号ID + + + + + 微信OpenID + + + + + 微信验证Dto + 作者:胡丁文 + 时间:2020-4-1 21:34:07 + + + + + 微信公众号唯一标识 + + + + + 验证成功后返回给微信的字符串 + + + + + 签名 + + + + + 时间戳 + + + + + 随机数 + + + + + 微信XmlDto + 作者:胡丁文 + 时间:2020-4-3 20:31:26 + + + + + 微信公众号唯一表示 + + + + + 微信开发者 + + + + + 来自谁 + + + + + 创建时间 + + + + + 消息类型 + + + + + 文字内容 + + + + + 消息ID + + + + + 消息事件 + + + + + 事件key值 + + + + + 图片地址 + + + + + 多媒体ID + + + + + 格式 + + + + + 语音失败 + + + + + 缩略媒体ID + + + + + 地理位置维度 + + + + + 地理位置经度 + + + + + 地图缩放大小 + + + + + 地理位置信息 + + + + + 消息标题 + + + + + 消息描述 + + + + + 消息链接 + + + + + 二维码的ticket,可用来换取二维码图片 + + + + + 地理位置纬度 + + + + + 地理位置经度 + + + + + 地理位置精度 + + +
+
diff --git a/Blog.Core.Api/Blog.Core.xml b/Blog.Core.Api/Blog.Core.xml new file mode 100644 index 00000000..0ed61c91 --- /dev/null +++ b/Blog.Core.Api/Blog.Core.xml @@ -0,0 +1,1662 @@ + + + + Blog.Core.Api + + + + + 博客管理 + + + + + 构造函数 + + + + + + + 获取博客列表【无权限】 + + + + + + + + + + 获取博客详情 + + + + + + + 获取详情【无权限】 + + + + + + + 获取博客测试信息 v2版本 + + + + + + 添加博客【无权限】 + + + + + + + + + + + + + + 更新博客信息 + + + + + + + 删除博客 + + + + + + + apache jemeter 压力测试 + 更新接口 + + + + + + 构造函数 + + + + + 获取 整体框架 文件(主库)(一般可用第一次生成) + + + + + + 获取仓储层和服务层(需指定表名和数据库) + + 数据库链接名称 + 需要生成的表名 + + + + + 获取实体(需指定表名和数据库) + + 数据库链接名称 + 需要生成的表名 + + + + + 获取控制器(需指定表名和数据库) + + 数据库链接名称 + 需要生成的表名 + + + + + DbFrist 根据数据库表名 生成整体框架,包含Model层(一般可用第一次生成) + + 数据库链接名称 + 需要生成的表名 + + + + + 获取权限部分Map数据(从库) + 迁移到新库(主库) + + + + + + 权限数据库导出tsv + + + + + + 权限数据库导出excel + + + + + + 健康检查 + + + + + 健康检查接口 + + + + + + 图片管理 + + + + + 下载图片(支持中文字符) + + + + + + 上传图片,多文件 + + + + + + + 登录管理【无权限】 + + + + + 构造函数注入 + + + + + + + + + + + 获取JWT的方法1 + + + + + + + + 获取JWT的方法2:给Nuxt提供 + + + + + + + + 获取JWT的方法3:整个系统主要方法 + + + + + + + + 请求刷新Token(以旧换新) + + + + + + + 获取JWT的方法4:给 JSONP 测试 + + + + + + + + + + + 测试 MD5 加密字符串 + + + + + + + swagger登录 + + + + + + + weixin登录 + + + + + + 接口管理 + + + + + 获取全部接口api + + + + + + + + 添加一条接口信息 + + + + + + + 更新接口信息 + + + + + + + 删除一条接口 + + + + + + + 导入多条接口信息 + + + + + + + 服务器配置信息 + + + + + + SignalR send data + + + + + + 建行聚合支付类 + + + + + 构造函数 + + + + + + + 被扫支付 + + + + + + + 被扫支付 + + + + + + + 支付结果查询-轮询 + + + + + + + 支付结果查询-轮询 + + + + + + + 退款 + + + + + + + 退款 + + + + + + + 菜单管理 + + + + + 构造函数 + + + + + + + + + + + + + + 获取菜单 + + + + + + + + + 查询树形 Table + + 父节点 + 关键字 + + + + + 添加一个菜单 + + + + + + + 保存菜单权限分配 + + + + + + + 获取菜单树 + + + + + + + + 获取路由树 + + + + + + + 获取路由树 + + + + + + + 通过角色获取菜单 + + + + + + + 更新菜单 + + + + + + + 删除菜单 + + + + + + + 导入多条菜单信息 + + + + + + + 系统接口菜单同步接口 + + + + + + 角色管理 + + + + + 获取全部角色 + + + + + + + + 添加角色 + + + + + + + 更新角色 + + + + + + + 删除角色 + + + + + + + 分页获取 + + + + + + + + 添加计划任务 + + + + + + + 修改计划任务 + + + + + + + 删除一个任务 + + + + + + + 启动计划任务 + + + + + + + 停止一个计划任务 + + + + + + + 暂停一个计划任务 + + + + + + + 恢复一个计划任务 + + + + + + + 重启一个计划任务 + + + + + + + 获取任务命名空间 + + + + + + 立即执行任务 + + + + + + + 获取任务运行日志 + + + + + + 任务概况 + + + + + + 类别管理【无权限】 + + + + + 构造函数 + + + + + + 获取Tibug所有分类 + + + + + + Tibug 管理 + + + + + 构造函数 + + + + + + + 获取Bug数据列表(带分页) + 【无权限】 + + 页数 + 专题类型 + 关键字 + + + + + + 获取详情【无权限】 + + + + + + + 添加一个 BUG 【无权限】 + + + + + + + 更新 bug + + + + + + + 删除 bug + + + + + + + 测试事务在AOP中的使用 + + + + + + + 用户管理 + + + + + 构造函数 + + + + + + + + + + + + + 获取全部用户 + + + + + + + + 获取用户详情根据token + 【无权限】 + + 令牌 + + + + + 添加一个用户 + + + + + + + 更新用户与角色 + + + + + + + 删除用户 + + + + + + + 用户角色关系 + + + + + 构造函数 + + + + + + + + + 新建用户 + + + + + + + + 新建Role + + + + + + + 新建用户角色关系 + + + + + + + + Values控制器 + + + + + 测试Rabbit消息队列发送 + + + + + 测试Rabbit消息队列订阅 + + + + + 测试SqlSugar二级缓存 + 可设置过期时间 + 或通过接口方式更新该数据,也会离开清除缓存 + + + + + + Get方法 + + + + + + 测试Redis消息队列 + + + + + + + 测试RabbitMQ事件总线 + + + + + + + + Get(int id)方法 + + + + + + + 测试参数是必填项 + + + + + + + 通过 HttpContext 获取用户信息 + + 声明类型,默认 jti + + + + + to redirect by route template name. + + + + + route with template name. + + + + + + 测试 post 一个对象 + 独立参数 + + model实体类参数 + 独立参数 + + + + 测试 post 参数 + + + + + + + 测试多库连接 + + + + + + 测试Fulent做参数校验 + + + + + + + Put方法 + + + + + + + Delete方法 + + + + + + 测试接入Apollo获取配置信息 + + + + + 通过此处的key格式为 xx:xx:x + + + + + 获取雪花Id + + + + + + 测试缓存 + + + + + + 雪花Id To DateTime + + + + + + + WeChatCompanyController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + WeChatConfigController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + 微信公众号管理 + + + + + 构造函数 + + + + + + + 更新Token + + + + + + + 刷新Token + + + + + + + 获取模板 + + + + + + + 获取菜单 + + + + + + + 更新菜单 + + + + + + + 获取订阅用户(所有) + + + + + + + 入口 + + + + + + + 获取订阅用户 + + + + + + + + 获取一个绑定员工公众号二维码 + + 消息 + + + + + 推送卡片消息接口 + + 卡片消息对象 + + + + + 推送卡片消息接口 + + 卡片消息对象 + + + + + 推送文本消息 + + 消息对象 + + + + + 通过绑定用户获取微信用户信息(一般用于初次绑定检测) + + 信息 + + + + + 用户解绑 + + 消息 + + + + + WeChatPushLogController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + WeChatSubController + + + + + 构造函数 + + + + + + 获取 + + 分页条件 + + + + + 获取(id) + + 主键ID + + + + + 添加 + + + + + + 更新 + + + + + + 删除 + + + + + + 批量删除 + + + + + + 查询树形 Table + + 父节点 + 关键字 + + + + + 获取部门树 + + + + + + + 服务管理 + + + + + INacosNamingService + + + + + + + + + + + 系统实例是否启动完成 + + + + + + 获取Nacos 状态 + + + + + + 服务上线 + + + + + + 服务下线 + + + + + + SignalR测试 + + + + + 向指定用户发送消息 + + + + + + + + 向指定角色发送消息 + + + + + + + + 分表demo + + + + + 分页获取数据 + + + + + + + + + + + 根据ID获取信息 + + + + + + + 添加一条测试数据 + + + + + + + 修改一条测试数据 + + + + + + + 根据id删除数据 + + + + + + + SqlSugar 相关测试 + + + + + SqlSugar 相关测试 + + + + + 测试建表后,SqlSugar缓存 + + + + + + 缓存管理 + + + + + 缓存管理 + + + + + 获取全部缓存 + + + + + + 获取缓存 + + + + + + 新增 + + + + + + 删除全部缓存 + + + + + + 删除缓存 + + + + + + 数据库管理 + + + + + 获取库配置 + + + + + + 获取表信息 + + 配置Id + 读取类型 + + + + + 获取表字段 + + 表名 + ConfigId + 读取类型 + + + + + 编辑表备注 + + + + + + 编辑列备注 + + + + + + 动态建表 CURD + + + + + 动态type + + + + + + 动态type 继承BaseEntity + + + + + + 测试建表 + + + + + + 测试查询 + + + + + + 测试写入 + + + + + + 多租户-多库方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增数据 + + + + + + 多租户-Id方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增业务数据 + + + + + + 多租户-多表方案 测试 + + + + + 获取租户下全部业务数据
+
+ +
+ + + 新增数据 + + + + + + 租户管理 + + + + + 获取全部租户 + + + + + + 获取租户信息 + + + + + + 新增租户信息
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 修改租户信息
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 删除租户
+ 此处只做演示,具体要以实际业务为准 +
+ +
+ + + 枚举测试 + + + + + 获取学生信息 + + 学生类型 + + + 学生信息 + + + + 学生类型 + + + + + 小学生 + + + + + 中学生 + + + + + 大学生 + + + + + 学生姓名 + + + + + 学生年龄 + + + + + 学生类型 + + + + + Summary:全局路由权限公约 + Remarks:目的是针对不同的路由,采用不同的授权过滤器 + 如果 controller 上不加 [Authorize] 特性,默认都是 Permission 策略 + 否则,如果想特例其他授权机制的话,需要在 controller 上带上 [Authorize],然后再action上自定义授权即可,比如 [Authorize(Roles = "Admin")] + + + + + 全局权限过滤器【无效】 + + + + + 全局异常错误日志 + + + + + 自定义返回格式 + + + + + + + + 生产环境的消息 + + + + + 开发环境的消息 + + + + + 全局路由前缀公约 + + + + + 自定义路由 /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/BlogController.cs b/Blog.Core.Api/Controllers/BlogController.cs new file mode 100644 index 00000000..d0e9a235 --- /dev/null +++ b/Blog.Core.Api/Controllers/BlogController.cs @@ -0,0 +1,278 @@ +using System.Linq.Expressions; +using System.Text.RegularExpressions; +using Blog.Core.Common.Helper; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using Blog.Core.SwaggerHelper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Serilog; +using StackExchange.Profiling; +using static Blog.Core.Extensions.CustomApiVersion; + +namespace Blog.Core.Controllers +{ + /// + /// 博客管理 + /// + [Produces("application/json")] + [Route("api/Blog")] + public class BlogController : BaseApiController + { + public IBlogArticleServices _blogArticleServices { get; set; } + private readonly ILogger _logger; + + /// + /// 构造函数 + /// + /// + /// + public BlogController(ILogger logger) + { + _logger = logger; + } + + + /// + /// 获取博客列表【无权限】 + /// + /// + /// + /// + /// + /// + [HttpGet] + public async Task>> Get(int id, int page = 1, string bcategory = "技术博文", string key = "") + { + int intPageSize = 6; + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + + Expression> whereExpression = a => (a.bcategory == bcategory && a.IsDeleted == false) && ((a.btitle != null && a.btitle.Contains(key)) || (a.bcontent != null && a.bcontent.Contains(key))); + + var pageModelBlog = await _blogArticleServices.QueryPage(whereExpression, page, intPageSize, " bID desc "); + + using (MiniProfiler.Current.Step("获取成功后,开始处理最终数据")) + { + foreach (var item in pageModelBlog.data) + { + if (!string.IsNullOrEmpty(item.bcontent)) + { + item.bRemark = (HtmlHelper.ReplaceHtmlTag(item.bcontent)).Length >= 200 ? (HtmlHelper.ReplaceHtmlTag(item.bcontent)).Substring(0, 200) : (HtmlHelper.ReplaceHtmlTag(item.bcontent)); + int totalLength = 500; + if (item.bcontent.Length > totalLength) + { + item.bcontent = item.bcontent.Substring(0, totalLength); + } + } + } + } + + return SuccessPage(pageModelBlog); + } + + + /// + /// 获取博客详情 + /// + /// + /// + [HttpGet("{id}")] + //[Authorize(Policy = "Scope_BlogModule_Policy")] + [Authorize] + public async Task> Get(long id) + { + return Success(await _blogArticleServices.GetBlogDetails(id)); + } + + + /// + /// 获取详情【无权限】 + /// + /// + /// + [HttpGet] + [Route("DetailNuxtNoPer")] + 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(long id = 0) + { + var response = await _blogArticleServices.QueryById(id); + if (response != null && response.bsubmitter.IsNotEmptyOrNull()) + { + string Url = @"^http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?$"; + if (Regex.IsMatch(response.bsubmitter, Url)) + { + response.btraffic += 1; + await _blogArticleServices.Update(response); + return Redirect(response.bsubmitter); + } + + } + + return Ok(); + } + + [HttpGet] + [Route("GetBlogsByTypesForMVP")] + public async Task>> GetBlogsByTypesForMVP(string types = "", int id = 0) + { + if (types.IsNotEmptyOrNull()) + { + 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() { }); + } + + [HttpGet] + [Route("GetBlogByIdForMVP")] + public async Task> GetBlogByIdForMVP(long id = 0) + { + if (id > 0) + { + return Success(await _blogArticleServices.QueryById(id)); + } + return Success(new BlogArticle()); + } + + /// + /// 获取博客测试信息 v2版本 + /// + /// + [HttpGet] + ////MVC自带特性 对 api 进行组管理 + //[ApiExplorerSettings(GroupName = "v2")] + ////路径 如果以 / 开头,表示绝对路径,反之相对 controller 的想u地路径 + //[Route("/api/v2/blog/Blogtest")] + //和上边的版本控制以及路由地址都是一样的 + + [CustomRoute(ApiVersions.V2, "Blogtest")] + public MessageModel V2_Blogtest() + { + return Success("我是第二版的博客信息"); + } + + /// + /// 添加博客【无权限】 + /// + /// + /// + [HttpPost] + //[Authorize(Policy = "Scope_BlogModule_Policy")] + [Authorize] + public async Task> Post([FromBody] BlogArticle blogArticle) + { + if (blogArticle.btitle.Length > 5 && blogArticle.bcontent.Length > 50) + { + + blogArticle.bCreateTime = DateTime.Now; + blogArticle.bUpdateTime = DateTime.Now; + blogArticle.IsDeleted = false; + blogArticle.bcategory = "技术博文"; + var id = (await _blogArticleServices.Add(blogArticle)); + return id > 0 ? Success(id.ObjToString()) : Failed("添加失败"); + } + else + { + return Failed("文章标题不能少于5个字符,内容不能少于50个字符!"); + } + } + + + /// + /// + /// + /// + /// + [HttpPost] + [Route("AddForMVP")] + [Authorize(Permissions.Name)] + public async Task> AddForMVP([FromBody] BlogArticle blogArticle) + { + blogArticle.bCreateTime = DateTime.Now; + blogArticle.bUpdateTime = DateTime.Now; + blogArticle.IsDeleted = false; + var id = (await _blogArticleServices.Add(blogArticle)); + return id > 0 ? Success(id.ObjToString()) : Failed("添加失败"); + } + /// + /// 更新博客信息 + /// + /// + /// + // PUT: api/User/5 + [HttpPut] + [Route("Update")] + [Authorize(Permissions.Name)] + public async Task> Put([FromBody] BlogArticle BlogArticle) + { + if (BlogArticle != null && BlogArticle.bID > 0) + { + var model = await _blogArticleServices.QueryById(BlogArticle.bID); + + if (model != null) + { + model.btitle = BlogArticle.btitle; + model.bcategory = BlogArticle.bcategory; + model.bsubmitter = BlogArticle.bsubmitter; + model.bcontent = BlogArticle.bcontent; + model.btraffic = BlogArticle.btraffic; + + if (await _blogArticleServices.Update(model)) + { + return Success(BlogArticle?.bID.ObjToString()); + } + } + } + return Failed("更新失败"); + } + + + + /// + /// 删除博客 + /// + /// + /// + [HttpDelete] + [Authorize(Permissions.Name)] + [Route("Delete")] + 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("删除失败"); + } + return Failed("入参无效"); + } + /// + /// apache jemeter 压力测试 + /// 更新接口 + /// + /// + [HttpGet] + [Route("ApacheTestUpdate")] + public async Task> ApacheTestUpdate() + { + return Success(await _blogArticleServices.Update(new { bsubmitter = $"laozhang{DateTime.Now.Millisecond}", bID = 1 }), "更新成功"); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs new file mode 100644 index 00000000..553dee75 --- /dev/null +++ b/Blog.Core.Api/Controllers/DbFirst/DbFirstController.cs @@ -0,0 +1,179 @@ +using Blog.Core.Common; +using Blog.Core.Common.DB; +using Blog.Core.Common.Seed; +using Blog.Core.Model; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; +using SqlSugar; +using System.Linq; + +namespace Blog.Core.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + //[Authorize(Permissions.Name)] + public class DbFirstController : ControllerBase + { + private readonly SqlSugarScope _sqlSugarClient; + private readonly IWebHostEnvironment Env; + + /// + /// 构造函数 + /// + public DbFirstController(ISqlSugarClient sqlSugarClient, IWebHostEnvironment env) + { + _sqlSugarClient = sqlSugarClient as SqlSugarScope; + Env = env; + } + + /// + /// 获取 整体框架 文件(主库)(一般可用第一次生成) + /// + /// + [HttpGet] + public MessageModel GetFrameFiles() + { + var data = new MessageModel() { success = true, msg = "" }; + data.response += @"file path is:C:\my-file\}"; + var isMuti = BaseDBConfig.IsMulti; + if (Env.IsDevelopment()) + { + data.response += $"Controller层生成:{FrameSeed.CreateControllers(_sqlSugarClient)} || "; + + BaseDBConfig.ValidConfig.ForEach(m => + { + _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)} || "; + }); + + // 切回主库 + _sqlSugarClient.ChangeDatabase(MainDb.CurrentDbConnId.ToLower()); + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + + return data; + } + + /// + /// 获取仓储层和服务层(需指定表名和数据库) + /// + /// 数据库链接名称 + /// 需要生成的表名 + /// + [HttpPost] + public MessageModel GetFrameFilesByTableNames([FromBody]string[] tableNames, [FromQuery]string ConnID = null) + { + ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; + + var isMuti = BaseDBConfig.IsMulti; + var data = new MessageModel() { success = true, msg = "" }; + if (Env.IsDevelopment()) + { + data.response += $"库{ConnID}-IRepositorys层生成:{FrameSeed.CreateIRepositorys(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-IServices层生成:{FrameSeed.CreateIServices(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-Repository层生成:{FrameSeed.CreateRepository(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-Services层生成:{FrameSeed.CreateServices(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + + return data; + } + /// + /// 获取实体(需指定表名和数据库) + /// + /// 数据库链接名称 + /// 需要生成的表名 + /// + [HttpPost] + public MessageModel GetFrameFilesByTableNamesForEntity([FromBody] string[] tableNames, [FromQuery] string ConnID = null) + { + ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; + + var isMuti = BaseDBConfig.IsMulti; + var data = new MessageModel() { success = true, msg = "" }; + if (Env.IsDevelopment()) + { + data.response += $"库{ConnID}-Models层生成:{FrameSeed.CreateModels(_sqlSugarClient, ConnID, isMuti, tableNames)}"; + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + return data; + } + /// + /// 获取控制器(需指定表名和数据库) + /// + /// 数据库链接名称 + /// 需要生成的表名 + /// + [HttpPost] + public MessageModel GetFrameFilesByTableNamesForController([FromBody] string[] tableNames, [FromQuery] string ConnID = null) + { + ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; + + var isMuti = BaseDBConfig.IsMulti; + var data = new MessageModel() { success = true, msg = "" }; + if (Env.IsDevelopment()) + { + data.response += $"库{ConnID}-Controllers层生成:{FrameSeed.CreateControllers(_sqlSugarClient, ConnID, isMuti, tableNames)}"; + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + return data; + } + + /// + /// DbFrist 根据数据库表名 生成整体框架,包含Model层(一般可用第一次生成) + /// + /// 数据库链接名称 + /// 需要生成的表名 + /// + [HttpPost] + public MessageModel GetAllFrameFilesByTableNames([FromBody]string[] tableNames, [FromQuery]string ConnID = null) + { + ConnID = ConnID == null ? MainDb.CurrentDbConnId.ToLower() : ConnID; + + var isMuti = BaseDBConfig.IsMulti; + var data = new MessageModel() { success = true, msg = "" }; + if (Env.IsDevelopment()) + { + _sqlSugarClient.ChangeDatabase(ConnID.ToLower()); + data.response += $"Controller层生成:{FrameSeed.CreateControllers(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-Model层生成:{FrameSeed.CreateModels(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-IRepositorys层生成:{FrameSeed.CreateIRepositorys(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-IServices层生成:{FrameSeed.CreateIServices(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-Repository层生成:{FrameSeed.CreateRepository(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + data.response += $"库{ConnID}-Services层生成:{FrameSeed.CreateServices(_sqlSugarClient, ConnID, isMuti, tableNames)} || "; + // 切回主库 + _sqlSugarClient.ChangeDatabase(MainDb.CurrentDbConnId.ToLower()); + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + + return data; + } + + + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs b/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs new file mode 100644 index 00000000..7865cc69 --- /dev/null +++ b/Blog.Core.Api/Controllers/DbFirst/MigrateController.cs @@ -0,0 +1,359 @@ +using Blog.Core.Common.Helper; +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 +{ + [Route("api/[controller]/[action]")] + [ApiController] + //[Authorize(Permissions.Name)] + public class MigrateController : ControllerBase + { + 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(IUnitOfWorkManage unitOfWorkManage, + IRoleModulePermissionServices roleModulePermissionServices, + IUserRoleServices userRoleServices, + IRoleServices roleServices, + IPermissionServices permissionServices, + IModuleServices moduleServices, + IDepartmentServices departmentServices, + ISysUserInfoServices sysUserInfoServices, + IWebHostEnvironment env) + { + _unitOfWorkManage = unitOfWorkManage; + _roleModulePermissionServices = roleModulePermissionServices; + _userRoleServices = userRoleServices; + _roleServices = roleServices; + _permissionServices = permissionServices; + _moduleServices = moduleServices; + _departmentServices = departmentServices; + _sysUserInfoServices = sysUserInfoServices; + _env = env; + } + + + /// + /// 获取权限部分Map数据(从库) + /// 迁移到新库(主库) + /// + /// + [HttpGet] + 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.PermissionId >= filterPermissionId).ToList(); + + InitPermissionTree(permissions, permissionsAllList, apiList); + + 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) + { + // 角色信息,防止重复添加,做了判断 + if (item.Role != null) + { + var isExit = (await _roleServices.Query(d => d.Name == item.Role.Name && d.IsDeleted == false)).FirstOrDefault(); + if (isExit == null) + { + rid = await _roleServices.Add(item.Role); + Console.WriteLine($"Role Added:{item.Role.Name}"); + } + else + { + rid = isExit.Id; + } + } + + 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) + { + rpmid = await _roleModulePermissionServices.Add(new RoleModulePermission() + { + IsDeleted = false, + CreateTime = DateTime.Now, + ModifyTime = DateTime.Now, + ModuleId = mid, + PermissionId = pid, + RoleId = rid, + }); + Console.WriteLine($"RMP Added:{rpmid}"); + } + + } + + + _unitOfWorkManage.CommitTran(); + + data.success = true; + data.msg = "导入成功!"; + } + catch (Exception) + { + _unitOfWorkManage.RollbackTran(); + + } + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + + return data; + } + + + /// + /// 权限数据库导出tsv + /// + /// + [HttpGet] + 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.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.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.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.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; + data.msg = "生成成功!"; + } + else + { + data.success = false; + data.msg = "当前不处于开发模式,代码生成不可用!"; + } + + 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/HealthCheckController.cs b/Blog.Core.Api/Controllers/HealthCheckController.cs new file mode 100644 index 00000000..04fac9d6 --- /dev/null +++ b/Blog.Core.Api/Controllers/HealthCheckController.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// 健康检查 + /// + [Route("[controller]")] + [ApiController] + public class HealthCheckController : ControllerBase + { + /// + /// 健康检查接口 + /// + /// + [HttpGet] + public IActionResult Get() + { + return Ok(); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/ImgController.cs b/Blog.Core.Api/Controllers/ImgController.cs new file mode 100644 index 00000000..5ba85388 --- /dev/null +++ b/Blog.Core.Api/Controllers/ImgController.cs @@ -0,0 +1,145 @@ +using Blog.Core.Model; +using Blog.Core.Model.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// 图片管理 + /// + [Route("api/[controller]")] + [ApiController] + [Authorize] + 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() + { + string foldername = ""; + string filepath = Path.Combine(_env.WebRootPath, foldername, "测试下载中文名称的图片.png"); + var stream = System.IO.File.OpenRead(filepath); + string fileExt = ".jpg"; // 这里可以写一个获取文件扩展名的方法,获取扩展名 + //获取文件的ContentType + var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider(); + var memi = provider.Mappings[fileExt]; + var fileName = Path.GetFileName(filepath); + + + return File(stream, memi, fileName); + } + + /// + /// 上传图片,多文件 + /// + /// + /// + [HttpPost] + [Route("/images/Upload/Pic")] + public async Task> InsertPicture([FromForm]UploadFileDto dto) + { + + 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 foldername = "images"; + string folderpath = Path.Combine(_env.WebRootPath, foldername); + if (!Directory.Exists(folderpath)) + { + Directory.CreateDirectory(folderpath); + } + foreach (var file in allowedFile) + { + 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)) + { + await file.CopyToAsync(stream); + } + } + + var excludeFiles = dto.file.Except(allowedFile); + + if (excludeFiles.Any()) + { + var infoMsg = $"{string.Join('、', excludeFiles.Select(c => c.FileName))} 图片格式错误"; + return Success(null, infoMsg); + } + + return Success(null, "上传成功"); + + } + + + + [HttpGet] + [Route("/images/Down/Bmd")] + [AllowAnonymous] + public FileStreamResult DownBmd(string filename) + { + if (string.IsNullOrEmpty(filename)) + { + return null; + } + // 前端 blob 接收,具体查看前端admin代码 + string filepath = Path.Combine(_env.WebRootPath, Path.GetFileName(filename)); + if (System.IO.File.Exists(filepath)) + { + var stream = System.IO.File.OpenRead(filepath); + //string fileExt = ".bmd"; + //获取文件的ContentType + var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider(); + //var memi = provider.Mappings[fileExt]; + var fileName = Path.GetFileName(filepath); + + HttpContext.Response.Headers.Add("fileName", fileName); + + return File(stream, "application/octet-stream", fileName); + } + else + { + return null; + } + } + + // POST: api/Img + [HttpPost] + public void Post([FromBody] object formdata) + { + } + + // PUT: api/Img/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE: api/ApiWithActions/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } + +} diff --git a/Blog.Core.Api/Controllers/LoginController.cs b/Blog.Core.Api/Controllers/LoginController.cs new file mode 100644 index 00000000..5b40773d --- /dev/null +++ b/Blog.Core.Api/Controllers/LoginController.cs @@ -0,0 +1,346 @@ +using Blog.Core.AuthHelper; +using Blog.Core.AuthHelper.OverWrite; +using Blog.Core.Common.Helper; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.ViewModels; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using Blog.Core.Common.Swagger; + + +namespace Blog.Core.Controllers +{ + /// + /// 登录管理【无权限】 + /// + [Produces("application/json")] + [Route("api/Login")] + [AllowAnonymous] + public class LoginController : BaseApiController + { + readonly ISysUserInfoServices _sysUserInfoServices; + readonly IUserRoleServices _userRoleServices; + readonly IRoleServices _roleServices; + readonly PermissionRequirement _requirement; + private readonly IRoleModulePermissionServices _roleModulePermissionServices; + private readonly ILogger _logger; + + /// + /// 构造函数注入 + /// + /// + /// + /// + /// + /// + /// + 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 + /// + /// + /// + /// + [HttpGet] + [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}; + + jwtStr = JwtHelper.IssueJwt(tokenModel); + suc = true; + } + else + { + jwtStr = "login fail!!!"; + } + + return new MessageModel() + { + success = suc, + msg = suc ? "获取成功" : "获取失败", + response = jwtStr + }; + } + + + /// + /// 获取JWT的方法2:给Nuxt提供 + /// + /// + /// + /// + [HttpGet] + [Route("GetTokenNuxt")] + public MessageModel GetJwtStrForNuxt(string name, string pass) + { + string jwtStr = string.Empty; + bool suc = false; + //这里就是用户登录以后,通过数据库去调取数据,分配权限的操作 + //这里直接写死了 + if (name == "admins" && pass == "admins") + { + TokenModelJwt tokenModel = new TokenModelJwt + { + Uid = 1, + Role = "Admin" + }; + + jwtStr = JwtHelper.IssueJwt(tokenModel); + suc = true; + } + else + { + jwtStr = "login fail!!!"; + } + + var result = new + { + data = new {success = suc, token = jwtStr} + }; + + return new MessageModel() + { + success = suc, + msg = suc ? "获取成功" : "获取失败", + response = jwtStr + }; + } + + #endregion + + + /// + /// 获取JWT的方法3:整个系统主要方法 + /// + /// + /// + /// + [HttpGet] + [Route("JWTToken3.0")] + public async Task> GetJwtToken3(string name = "", string pass = "") + + { + string jwtStr = string.Empty; + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(pass)) + return Failed("用户名或密码不能为空"); + + pass = MD5Helper.MD5Encrypt32(pass); + + 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 + { + new Claim(ClaimTypes.Name, name), + 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))); + + + // ids4和jwt切换 + // jwt + if (!Permissions.IsUseIds4) + { + var data = await _roleModulePermissionServices.RoleModuleMaps(); + var list = (from item in data + where item.IsDeleted == false + orderby item.Id + select new PermissionItem + { + Url = item.Module?.LinkUrl, + Role = item.Role?.Name.ObjToString(), + }).ToList(); + + _requirement.Permissions = list; + } + + var token = JwtToken.BuildJwtToken(claims.ToArray(), _requirement); + return Success(token, "获取成功"); + } + else + { + return Failed("认证失败"); + } + } + + [HttpGet] + [Route("GetJwtTokenSecret")] + public async Task> GetJwtTokenSecret(string name = "", string pass = "") + { + var rlt = await GetJwtToken3(name, pass); + return rlt; + } + + /// + /// 请求刷新Token(以旧换新) + /// + /// + /// + [HttpGet] + [Route("RefreshToken")] + public async Task> RefreshToken(string token = "") + { + string jwtStr = string.Empty; + + if (string.IsNullOrEmpty(token)) + return Failed("token无效,请重新登录!"); + var tokenModel = JwtHelper.SerializeJwt(token); + if (tokenModel != null && JwtHelper.customSafeVerify(token) && tokenModel.Uid > 0) + { + var user = await _sysUserInfoServices.QueryById(tokenModel.Uid); + var value = User.Claims.SingleOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value; + if (value != null && user.CriticalModifyTime > value.ObjToDate()) + { + 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.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))); + + //用户标识 + var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); + identity.AddClaims(claims); + + var refreshToken = JwtToken.BuildJwtToken(claims.ToArray(), _requirement); + return Success(refreshToken, "获取成功"); + } + } + + return Failed("认证失败!"); + } + + /// + /// 获取JWT的方法4:给 JSONP 测试 + /// + /// + /// + /// + /// + /// + /// + [HttpGet] + [Route("jsonp")] + public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, + int expiresAbsoulute = 30) + { + TokenModelJwt tokenModel = new TokenModelJwt + { + Uid = id, + Role = sub + }; + + string jwtStr = JwtHelper.IssueJwt(tokenModel); + + string response = string.Format("\"value\":\"{0}\"", jwtStr); + string call = callBack + "({" + response + "})"; + Response.WriteAsync(call); + } + + + /// + /// 测试 MD5 加密字符串 + /// + /// + /// + [HttpGet] + [Route("Md5Password")] + 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 new file mode 100644 index 00000000..3b286808 --- /dev/null +++ b/Blog.Core.Api/Controllers/ModuleController.cs @@ -0,0 +1,173 @@ +using System.Linq.Expressions; +using Blog.Core.Common.HttpContextUser; +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 +{ + /// + /// 接口管理 + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public class ModuleController : BaseApiController + { + readonly IModuleServices _moduleServices; + readonly IUser _user; + + + public ModuleController(IModuleServices moduleServices, IUser user) + { + _moduleServices = moduleServices; + _user = user; + } + + /// + /// 获取全部接口api + /// + /// + /// + /// + // GET: api/User + [HttpGet] + public async Task>> Get(int page = 1, string key = "", int pageSize = 50) + { + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + + Expression> whereExpression = a => a.IsDeleted != true && ((a.Name != null && a.Name.Contains(key) || (a.LinkUrl != null && a.LinkUrl.Contains(key)))); + + PageModel data = new PageModel(); + + if (page == -1) + { + var modules = await _moduleServices.Query(whereExpression, " Id desc "); + data.data = modules; + } + else + { + data = await _moduleServices.QueryPage(whereExpression, page, pageSize, " Id desc "); + } + + + return Success(data, "获取成功"); + + + } + + // GET: api/User/5 + [HttpGet("{id}")] + public string Get(string id) + { + return "value"; + } + + /// + /// 添加一条接口信息 + /// + /// + /// + // POST: api/User + [HttpPost] + public async Task> Post([FromBody] Modules module) + { + module.CreateId = _user.ID; + module.CreateBy = _user.Name; + var id = (await _moduleServices.Add(module)); + return id > 0 ? Success(id.ObjToString(), "添加成功") : Failed(); + + } + + /// + /// 更新接口信息 + /// + /// + /// + // PUT: api/User/5 + [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; + if (module == null || module.Id <= 0) + return Failed("缺少参数"); + return await _moduleServices.Update(module) ? Success(module?.Id.ObjToString(), "更新成功") : Failed(); + } + + /// + /// 删除一条接口 + /// + /// + /// + // DELETE: api/ApiWithActions/5 + [HttpDelete] + public async Task> Delete(long id) + { + 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 module = modules[i]; + if (module != null) + { + module.CreateId = _user.ID; + module.CreateBy = _user.Name; + ids += (await _moduleServices.Add(module)); + sucCount++; + } + } + return ids.IsNotEmptyOrNull() ? Success(ids, $"{sucCount}条数据添加成功") : Failed(); + } + } +} diff --git a/Blog.Core.Api/Controllers/MonitorController.cs b/Blog.Core.Api/Controllers/MonitorController.cs new file mode 100644 index 00000000..d707d497 --- /dev/null +++ b/Blog.Core.Api/Controllers/MonitorController.cs @@ -0,0 +1,280 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Blog.Core.Common.LogHelper; +using Blog.Core.Hubs; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using Newtonsoft.Json; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Blog.Core.Extensions.Middlewares; + +namespace Blog.Core.Controllers +{ + [Route("api/[Controller]/[action]")] + [ApiController] + [AllowAnonymous] + 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) + { + _hubContext = hubContext; + _env = env; + _applicationUserServices = applicationUserServices; + _logger = logger; + } + + /// + /// 服务器配置信息 + /// + /// + [HttpGet] + public MessageModel Server() + { + return Success(new ServerViewModel() + { + 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) + }, "获取服务器配置信息成功"); + } + + + /// + /// SignalR send data + /// + /// + // GET: api/Logs + [HttpGet] + public MessageModel> Get() + { + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + _hubContext.Clients.All.SendAsync("ReceiveUpdate", "执行成功").Wait(); + } + + return Success>(null, "执行成功"); + } + + + [HttpGet] + public MessageModel GetRequestApiinfoByWeek() + { + //后续补充扩展Log + return Success(new RequestApiWeekView(), "成功"); + } + + [HttpGet] + public MessageModel GetAccessApiByDate() + { + //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() + //}; + + //后续补充扩展Log + return Success(new AccessApiDateView(), "获取成功"); + } + + private List GetAccessLogsToday(IWebHostEnvironment environment) + { + List userAccessModels = new(); + //后续补充扩展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; + } + + [HttpGet] + public MessageModel GetActiveUsers([FromServices] IWebHostEnvironment environment) + { + var accessLogsToday = GetAccessLogsToday(environment).Where(d => d.BeginTime.ObjToDate() >= DateTime.Today); + + var Logs = accessLogsToday.OrderByDescending(d => d.BeginTime).Take(50).ToList(); + + 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(); + + int activeUsersCount = activeUsers.Count; + activeUsers = activeUsers.OrderByDescending(d => d.count).Take(10).ToList(); + + //return new MessageModel() + //{ + // msg = "获取成功", + // success = true, + // response = new WelcomeInitData() + // { + // activeUsers = activeUsers, + // activeUserCount = activeUsersCount, + // errorCount = errorCountToday, + // logs = Logs, + // activeCount = GetAccessLogsTrend(environment) + // } + //}; + + return Success(new WelcomeInitData() + { + activeUsers = activeUsers, + activeUserCount = activeUsersCount, + errorCount = default, + logs = Logs, + activeCount = GetAccessLogsTrend(environment) + }, "获取成功"); + } + + [HttpGet] + public async Task> GetIds4Users() + { + List apiDates = new List(); + + 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(); + + apiDates = apiDates.OrderByDescending(d => d.date).Take(30).ToList(); + } + + + if (apiDates.Count == 0) + { + apiDates.Add(new ApiDate() + { + date = "没数据,或未开启相应接口服务", + count = 0 + }); + } + //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 + { + columns = new string[] { "date", "count" }, + rows = apiDates.OrderBy(d => d.date).ToList(), + }, "获取成功"); + } + } + + public class WelcomeInitData + { + public List activeUsers { get; set; } + 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 new file mode 100644 index 00000000..e5223851 --- /dev/null +++ b/Blog.Core.Api/Controllers/NacosController.cs @@ -0,0 +1,140 @@ +using Blog.Core.Common.Helper; +using Blog.Core.Controllers; +using Blog.Core.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Nacos.V2; + +namespace Blog.Core.Api.Controllers +{ + /// + /// 服务管理 + /// + [Produces("application/json")] + [Route("api/[Controller]/[action]")] + [Authorize(Permissions.Name)] + public class NacosController : BaseApiController + { + + #region 变量 + + /// + /// INacosNamingService + /// + private readonly INacosNamingService NacosNamingService; + + #endregion + + #region 重载 + /// + /// + /// + /// + public NacosController(INacosNamingService nacosNamingService) + { + NacosNamingService = nacosNamingService; + } + + #endregion + + + /// + /// 系统实例是否启动完成 + /// + /// + [HttpGet] + + public MessageModel CheckSystemStartFinish() + { + //********************* 用当前接口做基本健康检查 确定 基础服务 数据库 缓存都已正常启动***** + // 然后再进行服务上线 + var data = new MessageModel(); + // *************** 此处请求一下db 跟redis连接等 项目中简介 保证项目已全部启动 + data.success = true; + data.msg = "SUCCESS"; + return data; + + } + + + /// + /// 获取Nacos 状态 + /// + /// + [HttpGet] + public async Task> GetStatus() + { + var data = new MessageModel(); + var instances = await NacosNamingService.GetAllInstances(JsonConfigSettings.NacosServiceName); + if (instances == null || instances.Count == 0) + { + data.status = 406; + data.msg = "DOWN"; + data.success = false; + return data; + } + // 获取当前程序IP + var currentIp = IpHelper.GetCurrentIp(null); + bool isUp = false; + instances.ForEach(item => + { + if (item.Ip == currentIp) + isUp = true; + }); + // var baseUrl = await NacosNamingService.GetServerStatus(); + if (isUp) + { + data.status = 200; + data.msg = "UP"; + data.success = true; + return data; + } + else + { + data.status = 406; + data.msg = "DOWN"; + data.success = false; + return data; + } + } + + /// + /// 服务上线 + /// + /// + + [HttpGet] + public async Task> Register() + { + var data = new MessageModel(); + var instance = new Nacos.V2.Naming.Dtos.Instance() + { + ServiceName = JsonConfigSettings.NacosServiceName, + ClusterName = Nacos.V2.Common.Constants.DEFAULT_CLUSTER_NAME, + Ip = IpHelper.GetCurrentIp(null), + Port = JsonConfigSettings.NacosPort, + Enabled = true, + Weight = 100, + Metadata = JsonConfigSettings.NacosMetadata + }; + await NacosNamingService.RegisterInstance(JsonConfigSettings.NacosServiceName, Nacos.V2.Common.Constants.DEFAULT_GROUP, instance); + data.success = true; + data.msg = "SUCCESS"; + return data; + } + + /// + /// 服务下线 + /// + /// + [HttpGet] + public async Task> Deregister() + { + var data = new MessageModel(); + await NacosNamingService.DeregisterInstance(JsonConfigSettings.NacosServiceName, Nacos.V2.Common.Constants.DEFAULT_GROUP, IpHelper.GetCurrentIp(null), JsonConfigSettings.NacosPort); + data.success = true; + data.msg = "SUCCESS"; + return data; + } + } +} diff --git a/Blog.Core.Api/Controllers/PayController.cs b/Blog.Core.Api/Controllers/PayController.cs new file mode 100644 index 00000000..6c05c249 --- /dev/null +++ b/Blog.Core.Api/Controllers/PayController.cs @@ -0,0 +1,101 @@ +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 +{ + /// + /// 建行聚合支付类 + /// + [Produces("application/json")] + [Route("api/Pay")] + [Authorize(Permissions.Name)] + public class PayController : Controller + { + private readonly ILogger _logger; + private readonly IPayServices _payServices; + /// + /// 构造函数 + /// + /// + /// + public PayController(ILogger logger, IPayServices payServices) + { + _logger = logger; + _payServices = payServices; + } + /// + /// 被扫支付 + /// + /// + /// + [HttpGet] + [Route("Pay")] + public async Task> PayGet([FromQuery]PayNeedModel payModel) + { + return await _payServices.Pay(payModel); + } + /// + /// 被扫支付 + /// + /// + /// + [HttpPost] + [Route("Pay")] + public async Task> PayPost([FromBody]PayNeedModel payModel) + { + return await _payServices.Pay(payModel); + } + /// + /// 支付结果查询-轮询 + /// + /// + /// + [HttpGet] + [Route("PayCheck")] + public async Task> PayCheckGet([FromQuery]PayNeedModel payModel) + { + return await _payServices.PayCheck(payModel, 1); + } + /// + /// 支付结果查询-轮询 + /// + /// + /// + [HttpPost] + [Route("PayCheck")] + public async Task> PayCheckPost([FromBody]PayNeedModel payModel) + { + return await _payServices.PayCheck(payModel, 1); + } + /// + /// 退款 + /// + /// + /// + [HttpGet] + [Route("PayRefund")] + public async Task> PayRefundGet([FromQuery]PayRefundNeedModel payModel) + { + return await _payServices.PayRefund(payModel); + } + /// + /// 退款 + /// + /// + /// + [HttpPost] + [Route("PayRefund")] + public async Task> PayRefundPost([FromBody]PayRefundNeedModel payModel) + { + return await _payServices.PayRefund(payModel); + } + + + + + + } +} \ No newline at end of file diff --git a/Blog.Core.Api/Controllers/PermissionController.cs b/Blog.Core.Api/Controllers/PermissionController.cs new file mode 100644 index 00000000..ccdb3dc4 --- /dev/null +++ b/Blog.Core.Api/Controllers/PermissionController.cs @@ -0,0 +1,799 @@ +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.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Security.Claims; + +namespace Blog.Core.Controllers +{ + /// + /// 菜单管理 + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + 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; + + /// + /// 构造函数 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + 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; + } + + /// + /// 获取菜单 + /// + /// + /// + /// + /// + // GET: api/User + [HttpGet] + public async Task>> Get(int page = 1, string key = "", int pageSize = 50) + { + PageModel permissions = new PageModel(); + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + + permissions = await _permissionServices.QueryPage(a => a.IsDeleted != true && (a.Name != null && a.Name.Contains(key)), page, pageSize, " Id desc "); + + + #region 单独处理 + + var apis = await _moduleServices.Query(d => d.IsDeleted == false); + var permissionsView = permissions.data; + + var permissionAll = await _permissionServices.Query(d => d.IsDeleted != true); + foreach (var item in permissionsView) + { + List pidarr = new() + { + item.Pid + }; + if (item.Pid > 0) + { + pidarr.Add(0); + } + var parent = permissionAll.FirstOrDefault(d => d.Id == item.Pid); + + while (parent != null) + { + pidarr.Add(parent.Id); + parent = permissionAll.FirstOrDefault(d => d.Id == parent.Pid); + } + + + item.PidArr = pidarr.OrderBy(d => d).Distinct().ToList(); + foreach (var pid in item.PidArr) + { + var per = permissionAll.FirstOrDefault(d => d.Id == pid); + item.PnameArr.Add((per != null ? per.Name : "根节点") + "/"); + //var par = Permissions.Where(d => d.Pid == item.Id ).ToList(); + //item.PCodeArr.Add((per != null ? $"/{per.Code}/{item.Code}" : "")); + //if (par.Count == 0 && item.Pid == 0) + //{ + // item.PCodeArr.Add($"/{item.Code}"); + //} + } + + item.MName = apis.FirstOrDefault(d => d.Id == item.Mid)?.LinkUrl; + } + + permissions.data = permissionsView; + + #endregion + + + //return new MessageModel>() + //{ + // msg = "获取成功", + // success = permissions.dataCount >= 0, + // response = permissions + //}; + + return permissions.dataCount >= 0 ? Success(permissions, "获取成功") : Failed>("获取失败"); + + } + + /// + /// 查询树形 Table + /// + /// 父节点 + /// 关键字 + /// + [HttpGet] + [AllowAnonymous] + public async Task>> GetTreeTable(long f = 0, string key = "") + { + List permissions = new List(); + var apiList = await _moduleServices.Query(d => d.IsDeleted == false); + var permissionsList = await _permissionServices.Query(d => d.IsDeleted == false); + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + + if (key != "") + { + permissions = permissionsList.Where(a => a.Name.Contains(key)).OrderBy(a => a.OrderSort).ToList(); + } + else + { + permissions = permissionsList.Where(a => a.Pid == f).OrderBy(a => a.OrderSort).ToList(); + } + + foreach (var item in permissions) + { + List pidarr = new() { }; + var parent = permissionsList.FirstOrDefault(d => d.Id == item.Pid); + + while (parent != null) + { + pidarr.Add(parent.Id); + parent = permissionsList.FirstOrDefault(d => d.Id == parent.Pid); + } + + //item.PidArr = pidarr.OrderBy(d => d).Distinct().ToList(); + + pidarr.Reverse(); + pidarr.Insert(0, 0); + item.PidArr = pidarr; + + item.MName = apiList.FirstOrDefault(d => d.Id == item.Mid)?.LinkUrl; + item.hasChildren = permissionsList.Where(d => d.Pid == item.Id).Any(); + } + + + //return new MessageModel>() + //{ + // msg = "获取成功", + // success = true, + // response = permissions + //}; + return Success(permissions, "获取成功"); + } + + /// + /// 添加一个菜单 + /// + /// + /// + // POST: api/User + [HttpPost] + public async Task> Post([FromBody] Permission permission) + { + //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 = "添加成功"; + //} + + + return id > 0 ? Success(id.ObjToString(), "添加成功") : Failed("添加失败"); + } + + /// + /// 保存菜单权限分配 + /// + /// + /// + [HttpPost] + public async Task> Assign([FromBody] AssignView assignView) + { + if (assignView.rid > 0) + { + //开启事务 + try + { + var old_rmps = await _roleModulePermissionServices.Query(d => d.RoleId == assignView.rid); + + _unitOfWorkManage.BeginTran(); + await _permissionServices.Db.Deleteable(t => t.RoleId == assignView.rid).ExecuteCommandAsync(); + var permissions = await _permissionServices.Query(d => d.IsDeleted == false); + + List new_rmps = new List(); + var nowTime = _permissionServices.Db.GetDate(); + foreach (var item in assignView.pids) + { + var moduleid = permissions.Find(p => p.Id == item)?.Mid; + var find_old_rmps = old_rmps.Find(p => p.PermissionId == item); + + RoleModulePermission roleModulePermission = new RoleModulePermission() + { + IsDeleted = false, + RoleId = assignView.rid, + ModuleId = moduleid.ObjToLong(), + PermissionId = item, + 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(); + } + catch (Exception) + { + _unitOfWorkManage.RollbackTran(); + throw; + } + _requirement.Permissions.Clear(); + return Success("保存成功"); + } + else + { + return Failed("请选择要操作的角色"); + } + } + + + /// + /// 获取菜单树 + /// + /// + /// + /// + [HttpGet] + public async Task> GetPermissionTree(long pid = 0, bool needbtn = false) + { + //var data = new MessageModel(); + + var permissions = await _permissionServices.Query(d => d.IsDeleted == false); + var permissionTrees = (from child in permissions + where child.IsDeleted == false + orderby child.Id + select new PermissionTree + { + value = child.Id, + label = child.Name, + Pid = child.Pid, + isbtn = child.IsButton, + order = child.OrderSort, + }).ToList(); + PermissionTree rootRoot = new PermissionTree + { + value = 0, + Pid = 0, + label = "根节点" + }; + + permissionTrees = permissionTrees.OrderBy(d => d.order).ToList(); + + + RecursionHelper.LoopToAppendChildren(permissionTrees, rootRoot, pid, needbtn); + + //data.success = true; + //if (data.success) + //{ + // data.response = rootRoot; + // data.msg = "获取成功"; + //} + + return Success(rootRoot, "获取成功"); + //return data; + } + + /// + /// 获取路由树 + /// + /// + /// + [HttpGet] + public async Task> GetNavigationBar(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))).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 + select new NavigationBar + { + id = child.Id, + name = child.Name, + pid = child.Pid, + order = child.OrderSort, + path = child.Code, + iconCls = child.Icon, + Func = child.Func, + IsHide = child.IsHide.ObjToBool(), + IsButton = child.IsButton.ObjToBool(), + meta = new NavigationBarMeta + { + icon = child.IconNew, + requireAuth = true, + title = child.Name, + NoTabPage = child.IsHide.ObjToBool(), + keepAlive = child.IskeepAlive.ObjToBool() + } + }).ToList(); + + + NavigationBar rootRoot = new NavigationBar() + { + id = 0, + pid = 0, + order = 0, + name = "根节点", + path = "", + iconCls = "", + meta = new NavigationBarMeta(), + + }; + + permissionTrees = permissionTrees.OrderBy(d => d.order).ToList(); + RecursionHelper.LoopNaviBarAppendChildren(permissionTrees, rootRoot); + + data.success = true; + if (data.success) + { + data.response = rootRoot; + data.msg = "获取成功"; + } + } + } + } + return data; + } + + /// + /// 获取路由树 + /// + /// + /// + [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(long rid = 0) + { + //var data = new MessageModel(); + + var rmps = await _roleModulePermissionServices.Query(d => d.IsDeleted == false && d.RoleId == rid); + var permissionTrees = (from child in rmps + orderby child.Id + select child.PermissionId.ObjToLong()).ToList(); + + var permissions = await _permissionServices.Query(d => d.IsDeleted == false); + List assignbtns = new List(); + + foreach (var item in permissionTrees) + { + var pername = permissions.FirstOrDefault(d => d.IsButton && d.Id == item)?.Name; + if (!string.IsNullOrEmpty(pername)) + { + //assignbtns.Add(pername + "_" + item); + assignbtns.Add(item.ObjToString()); + } + } + + //data.success = true; + //if (data.success) + //{ + // data.response = new AssignShow() + // { + // permissionids = permissionTrees, + // assignbtns = assignbtns, + // }; + // data.msg = "获取成功"; + //} + + return Success(new AssignShow() + { + permissionids = permissionTrees, + assignbtns = assignbtns, + }, "获取成功"); + + //return data; + } + + /// + /// 更新菜单 + /// + /// + /// + // PUT: api/User/5 + [HttpPut] + public async Task> Put([FromBody] Permission permission) + { + var data = new MessageModel(); + if (permission != null && permission.Id > 0) + { + data.success = await _permissionServices.Update(permission); + await _roleModulePermissionServices.UpdateModuleId(permission.Id, permission.Mid); + if (data.success) + { + data.msg = "更新成功"; + data.response = permission?.Id.ObjToString(); + } + } + + return data; + } + + /// + /// 删除菜单 + /// + /// + /// + // DELETE: api/ApiWithActions/5 + [HttpDelete] + public async Task> Delete(long id) + { + var data = new MessageModel(); + if (id > 0) + { + var userDetail = await _permissionServices.QueryById(id); + userDetail.IsDeleted = true; + data.success = await _permissionServices.Update(userDetail); + if (data.success) + { + data.msg = "删除成功"; + data.response = userDetail?.Id.ObjToString(); + } + } + + 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 long rid { get; set; } + } + public class AssignShow + { + 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 new file mode 100644 index 00000000..0b93e943 --- /dev/null +++ b/Blog.Core.Api/Controllers/RoleController.cs @@ -0,0 +1,139 @@ +using Blog.Core.Common.HttpContextUser; +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 +{ + /// + /// 角色管理 + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public class RoleController : BaseApiController + { + readonly IRoleServices _roleServices; + readonly IUser _user; + + + public RoleController(IRoleServices roleServices, IUser user) + { + _roleServices = roleServices; + _user = user; + } + + /// + /// 获取全部角色 + /// + /// + /// + /// + // GET: api/User + [HttpGet] + public async Task>> Get(int page = 1, string key = "") + { + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + + int intPageSize = 50; + + 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 Success(data, "获取成功"); + + } + + // GET: api/User/5 + [HttpGet("{id}")] + public string Get(string id) + { + return "value"; + } + + /// + /// 添加角色 + /// + /// + /// + // POST: api/User + [HttpPost] + public async Task> Post([FromBody] Role role) + { + role.CreateId = _user.ID; + role.CreateBy = _user.Name; + var id = (await _roleServices.Add(role)); + return id > 0 ? Success(id.ObjToString(), "添加成功") : Failed("添加失败"); + + } + + /// + /// 更新角色 + /// + /// + /// + // PUT: api/User/5 + [HttpPut] + public async Task> Put([FromBody] Role role) + { + 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; + } + + /// + /// 删除角色 + /// + /// + /// + // DELETE: api/ApiWithActions/5 + [HttpDelete] + 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) 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 new file mode 100644 index 00000000..887cfcfc --- /dev/null +++ b/Blog.Core.Api/Controllers/TasksQzController.cs @@ -0,0 +1,547 @@ +using System.Linq.Expressions; +using System.Reflection; +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; +using Quartz; + +namespace Blog.Core.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public class TasksQzController : ControllerBase + { + private readonly ITasksQzServices _tasksQzServices; + private readonly ITasksLogServices _tasksLogServices; + private readonly ISchedulerCenter _schedulerCenter; + private readonly IUnitOfWorkManage _unitOfWorkManage; + + public TasksQzController(ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, IUnitOfWorkManage unitOfWorkManage, ITasksLogServices tasksLogServices) + { + _unitOfWorkManage = unitOfWorkManage; + _tasksQzServices = tasksQzServices; + _schedulerCenter = schedulerCenter; + _tasksLogServices = tasksLogServices; + } + + /// + /// 分页获取 + /// + /// + /// + /// + // GET: api/Buttons/5 + [HttpGet] + public async Task>> Get(int page = 1, string key = "") + { + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) + { + key = ""; + } + int intPageSize = 50; + + Expression> whereExpression = a => a.IsDeleted != true && (a.Name != null && a.Name.Contains(key)); + + var data = await _tasksQzServices.QueryPage(whereExpression, page, intPageSize, " Id desc "); + if (data.dataCount > 0) + { + foreach (var item in data.data) + { + item.Triggers = await _schedulerCenter.GetTaskStaus(item); + } + } + return MessageModel>.Message(data.dataCount >= 0, "获取成功", data); + } + + /// + /// 添加计划任务 + /// + /// + /// + [HttpPost] + public async Task> Post([FromBody] TasksQz tasksQz) + { + var data = new MessageModel(); + _unitOfWorkManage.BeginTran(); + var id = (await _tasksQzServices.Add(tasksQz)); + data.success = id > 0; + try + { + if (data.success) + { + tasksQz.Id = id; + data.response = id.ObjToString(); + data.msg = "添加成功"; + if (tasksQz.IsStart) + { + //如果是启动自动 + 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) + _unitOfWorkManage.CommitTran(); + else + _unitOfWorkManage.RollbackTran(); + } + return data; + } + + + /// + /// 修改计划任务 + /// + /// + /// + [HttpPut] + public async Task> Put([FromBody] TasksQz tasksQz) + { + var data = new MessageModel(); + if (tasksQz != null && tasksQz.Id > 0) + { + _unitOfWorkManage.BeginTran(); + data.success = await _tasksQzServices.Update(tasksQz); + try + { + if (data.success) + { + 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); + data.success = ResuleModelStar.success; + data.msg = $"{data.msg}=>启动:{ResuleModelStar.msg}"; + } + else + { + var ResuleModelStop = await _schedulerCenter.StopScheduleJobAsync(tasksQz); + data.msg = $"{data.msg}=>停止:{ResuleModelStop.msg}"; + } + } + else + { + data.msg = "修改失败"; + } + } + catch (Exception) + { + throw; + } + finally + { + if (data.success) + _unitOfWorkManage.CommitTran(); + else + _unitOfWorkManage.RollbackTran(); + } + } + return data; + } + /// + /// 删除一个任务 + /// + /// + /// + [HttpDelete] + public async Task> Delete(long jobId) + { + var data = new MessageModel(); + + var model = await _tasksQzServices.QueryById(jobId); + if (model != null) + { + _unitOfWorkManage.BeginTran(); + data.success = await _tasksQzServices.Delete(model); + try + { + data.response = jobId.ObjToString(); + if (data.success) + { + data.msg = "删除成功"; + var ResuleModel = await _schedulerCenter.StopScheduleJobAsync(model); + data.msg = $"{data.msg}=>任务状态=>{ResuleModel.msg}"; + } + else + { + data.msg = "删除失败"; + } + + } + catch (Exception) + { + throw; + } + finally + { + if (data.success) + _unitOfWorkManage.CommitTran(); + else + _unitOfWorkManage.RollbackTran(); + } + } + else + { + data.msg = "任务不存在"; + } + return data; + + } + /// + /// 启动计划任务 + /// + /// + /// + [HttpGet] + public async Task> StartJob(long jobId) + { + var data = new MessageModel(); + + var model = await _tasksQzServices.QueryById(jobId); + if (model != null) + { + _unitOfWorkManage.BeginTran(); + try + { + model.IsStart = true; + data.success = await _tasksQzServices.Update(model); + data.response = jobId.ObjToString(); + if (data.success) + { + data.msg = "更新成功"; + var ResuleModel = await _schedulerCenter.AddScheduleJobAsync(model); + 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) + _unitOfWorkManage.CommitTran(); + else + _unitOfWorkManage.RollbackTran(); + } + } + else + { + data.msg = "任务不存在"; + } + return data; + } + /// + /// 停止一个计划任务 + /// + /// + /// + [HttpGet] + public async Task> StopJob(long jobId) + { + var data = new MessageModel(); + + var model = await _tasksQzServices.QueryById(jobId); + if (model != null) + { + model.IsStart = false; + data.success = await _tasksQzServices.Update(model); + data.response = jobId.ObjToString(); + if (data.success) + { + data.msg = "更新成功"; + var ResuleModel = await _schedulerCenter.StopScheduleJobAsync(model); + if (ResuleModel.success) + { + data.msg = $"{data.msg}=>停止成功=>{ResuleModel.msg}"; + } + else + { + data.msg = $"{data.msg}=>停止失败=>{ResuleModel.msg}"; + } + } + else + { + data.msg = "更新失败"; + } + } + else + { + data.msg = "任务不存在"; + } + return data; + } + /// + /// 暂停一个计划任务 + /// + /// + /// + [HttpGet] + public async Task> PauseJob(long jobId) + { + var data = new MessageModel(); + var model = await _tasksQzServices.QueryById(jobId); + if (model != null) + { + _unitOfWorkManage.BeginTran(); + try + { + data.success = await _tasksQzServices.Update(model); + data.response = jobId.ObjToString(); + if (data.success) + { + data.msg = "更新成功"; + var ResuleModel = await _schedulerCenter.PauseJob(model); + if (ResuleModel.success) + { + data.msg = $"{data.msg}=>暂停成功=>{ResuleModel.msg}"; + } + else + { + data.msg = $"{data.msg}=>暂停失败=>{ResuleModel.msg}"; + } + data.success = ResuleModel.success; + } + else + { + data.msg = "更新失败"; + } + } + catch (Exception) + { + throw; + } + finally + { + if (data.success) + _unitOfWorkManage.CommitTran(); + else + _unitOfWorkManage.RollbackTran(); + } + } + else + { + data.msg = "任务不存在"; + } + return data; + } + /// + /// 恢复一个计划任务 + /// + /// + /// + [HttpGet] + public async Task> ResumeJob(long jobId) + { + var data = new MessageModel(); + + var model = await _tasksQzServices.QueryById(jobId); + if (model != null) + { + _unitOfWorkManage.BeginTran(); + try + { + model.IsStart = true; + data.success = await _tasksQzServices.Update(model); + data.response = jobId.ObjToString(); + if (data.success) + { + data.msg = "更新成功"; + var ResuleModel = await _schedulerCenter.ResumeJob(model); + if (ResuleModel.success) + { + data.msg = $"{data.msg}=>恢复成功=>{ResuleModel.msg}"; + } + else + { + data.msg = $"{data.msg}=>恢复失败=>{ResuleModel.msg}"; + } + data.success = ResuleModel.success; + } + else + { + data.msg = "更新失败"; + } + } + catch (Exception) + { + throw; + } + finally + { + if (data.success) + _unitOfWorkManage.CommitTran(); + else + _unitOfWorkManage.RollbackTran(); + } + } + else + { + data.msg = "任务不存在"; + } + return data; + } + /// + /// 重启一个计划任务 + /// + /// + /// + [HttpGet] + public async Task> ReCovery(long jobId) + { + var data = new MessageModel(); + var model = await _tasksQzServices.QueryById(jobId); + if (model != null) + { + + _unitOfWorkManage.BeginTran(); + try + { + model.IsStart = true; + data.success = await _tasksQzServices.Update(model); + data.response = jobId.ObjToString(); + if (data.success) + { + data.msg = "更新成功"; + var ResuleModelStop = await _schedulerCenter.StopScheduleJobAsync(model); + var ResuleModelStar = await _schedulerCenter.AddScheduleJobAsync(model); + if (ResuleModelStar.success) + { + data.msg = $"{data.msg}=>停止:{ResuleModelStop.msg}=>启动:{ResuleModelStar.msg}"; + data.response = jobId.ObjToString(); + + } + else + { + data.msg = $"{data.msg}=>停止:{ResuleModelStop.msg}=>启动:{ResuleModelStar.msg}"; + data.response = jobId.ObjToString(); + } + data.success = ResuleModelStar.success; + } + else + { + data.msg = "更新失败"; + } + } + catch (Exception) + { + throw; + } + finally + { + if (data.success) + _unitOfWorkManage.CommitTran(); + else + _unitOfWorkManage.RollbackTran(); + } + } + else + { + data.msg = "任务不存在"; + } + return data; + + } + /// + /// 获取任务命名空间 + /// + /// + [HttpGet] + public MessageModel> GetTaskNameSpace() + { + var baseType = typeof(IJob); + var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; + var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Tasks.dll").Select(Assembly.LoadFrom).ToArray(); + var types = referencedAssemblies + .SelectMany(a => a.DefinedTypes) + .Select(type => type.AsType()) + .Where(x => x != baseType && baseType.IsAssignableFrom(x)).ToArray(); + 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(long jobId) + { + var data = new MessageModel(); + + var model = await _tasksQzServices.QueryById(jobId); + if (model != null) + { + return await _schedulerCenter.ExecuteJobAsync(model); + } + else + { + data.msg = "任务不存在"; + } + 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/Controllers/TopicController.cs b/Blog.Core.Api/Controllers/TopicController.cs similarity index 69% rename from Blog.Core/Controllers/TopicController.cs rename to Blog.Core.Api/Controllers/TopicController.cs index 4b2329d4..253f54ff 100644 --- a/Blog.Core/Controllers/TopicController.cs +++ b/Blog.Core.Api/Controllers/TopicController.cs @@ -1,23 +1,34 @@ -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; using Microsoft.AspNetCore.Mvc; namespace Blog.Core.Controllers { + /// + /// 类别管理【无权限】 + /// [Route("api/[controller]")] [ApiController] + [AllowAnonymous] public class TopicController : ControllerBase { readonly ITopicServices _topicServices; + /// + /// 构造函数 + /// + /// public TopicController(ITopicServices topicServices) { _topicServices = topicServices; } + /// + /// 获取Tibug所有分类 + /// + /// // GET: api/Topic [HttpGet] public async Task>> Get() @@ -33,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"; } @@ -46,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/Controllers/TopicDetailController.cs b/Blog.Core.Api/Controllers/TopicDetailController.cs similarity index 64% rename from Blog.Core/Controllers/TopicDetailController.cs rename to Blog.Core.Api/Controllers/TopicDetailController.cs index 61c8b812..374aca24 100644 --- a/Blog.Core/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; @@ -10,16 +7,19 @@ namespace Blog.Core.Controllers { + /// + /// Tibug 管理 + /// [Route("api/[controller]/[action]")] [ApiController] - [Authorize(PermissionNames.Permission)] + [Authorize(Permissions.Name)] public class TopicDetailController : ControllerBase { readonly ITopicServices _topicServices; readonly ITopicDetailServices _topicDetailServices; /// - /// TopicDetailController + /// 构造函数 /// /// /// @@ -31,66 +31,61 @@ public TopicDetailController(ITopicServices topicServices, ITopicDetailServices /// /// 获取Bug数据列表(带分页) + /// 【无权限】 /// /// 页数 /// 专题类型 + /// 关键字 + /// /// [HttpGet] [AllowAnonymous] - public async Task>> Get(int page = 1, string tname = "", string key = "") + public async Task>> Get(int page = 1, string tname = "", string key = "", int intPageSize = 12) { - var data = new MessageModel>(); - int intTotalCount = 6; - int totalCount = 0; - int pageCount = 1; + long tid = 0; - //总数据,使用AOP切面缓存 - //topicDetails = await _topicDetailServices.GetTopicDetails(); - var topicDetails = await _topicDetailServices.Query(a => !a.tdIsDelete && a.tdSectendDetail == "tbug"); - - if (!string.IsNullOrEmpty(key)) + if (string.IsNullOrEmpty(key) || string.IsNullOrWhiteSpace(key)) { - topicDetails = topicDetails.Where(t => (t.tdName != null && t.tdName.Contains(key)) || (t.tdDetail != null && t.tdDetail.Contains(key))).ToList(); + key = ""; + } + if (string.IsNullOrEmpty(tname) || string.IsNullOrWhiteSpace(tname)) + { + tname = ""; } - tname = UnicodeHelper.UnicodeToString(tname); if (!string.IsNullOrEmpty(tname)) { - var tid = (await _topicServices.Query(ts => ts.tName == tname)).FirstOrDefault()?.Id.ObjToInt(); - topicDetails = topicDetails.Where(t => t.TopicId == tid).ToList(); + tid = ((await _topicServices.Query(ts => ts.tName == tname)).FirstOrDefault()?.Id).ObjToLong(); } - //筛选后的数据总数 - totalCount = topicDetails.Count; - //筛选后的总页数 - pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intTotalCount.ObjToDecimal())).ObjToInt(); - topicDetails = topicDetails.OrderByDescending(d => d.Id).Skip((page - 1) * intTotalCount).Take(intTotalCount).ToList(); + var data = await _topicDetailServices.QueryPage(a => !a.tdIsDelete && a.tdSectendDetail == "tbug" && ((tid == 0 && true) || (tid > 0 && a.TopicId == tid)) && ((a.tdName != null && a.tdName.Contains(key)) || (a.tdDetail != null && a.tdDetail.Contains(key))), page, intPageSize, " Id desc "); + + return new MessageModel>() { msg = "获取成功", - success = totalCount >= 0, - response = new PageModel() - { - page = page, - pageCount = pageCount, - dataCount = totalCount, - data = topicDetails, - } + success = data.dataCount >= 0, + response = data }; } + /// + /// 获取详情【无权限】 + /// + /// + /// // 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 = await _topicDetailServices.QueryById(id); - data.response = response.tdIsDelete ? null : response; + var response = id > 0 ? await _topicDetailServices.QueryById(id) : new TopicDetail(); + data.response = (response?.tdIsDelete).ObjToBool() ? new TopicDetail() : response; if (data.response != null) { data.success = true; @@ -100,6 +95,11 @@ public async Task> Get(int id) return data; } + /// + /// 添加一个 BUG 【无权限】 + /// + /// + /// // POST: api/TopicDetail [HttpPost] [AllowAnonymous] @@ -124,6 +124,11 @@ public async Task> Post([FromBody] TopicDetail topicDetail) return data; } + /// + /// 更新 bug + /// + /// + /// // PUT: api/TopicDetail/5 [HttpPut] public async Task> Update([FromBody] TopicDetail topicDetail) @@ -142,9 +147,14 @@ public async Task> Update([FromBody] TopicDetail topicDetai return data; } + /// + /// 删除 bug + /// + /// + /// // 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 new file mode 100644 index 00000000..9853d985 --- /dev/null +++ b/Blog.Core.Api/Controllers/TransactionController.cs @@ -0,0 +1,144 @@ +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; + +namespace Blog.Core.Controllers +{ + [Route("api/[controller]/[action]")] + [ApiController] + [AllowAnonymous] + public class TransactionController : ControllerBase + { + private readonly IPasswordLibServices _passwordLibServices; + private readonly IGuestbookServices _guestbookServices; + private readonly IUnitOfWorkManage _unitOfWorkManage; + + + public TransactionController(IUnitOfWorkManage unitOfWorkManage, IPasswordLibServices passwordLibServices, IGuestbookServices guestbookServices) + { + _unitOfWorkManage = unitOfWorkManage; + _passwordLibServices = passwordLibServices; + _guestbookServices = guestbookServices; + } + + // GET: api/Transaction + [HttpGet] + public async Task>> Get() + { + List returnMsg = new List() { }; + try + { + returnMsg.Add($"Begin Transaction"); + + _unitOfWorkManage.BeginTran(); + var passwords = await _passwordLibServices.Query(d => d.IsDeleted == false); + returnMsg.Add($"first time : the count of passwords is :{passwords.Count}"); + + + returnMsg.Add($"insert a data into the table PasswordLib now."); + var insertPassword = await _passwordLibServices.Add(new PasswordLib() + { + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }); + + + passwords = await _passwordLibServices.Query(d => d.IsDeleted == false); + returnMsg.Add($"second time : the count of passwords is :{passwords.Count}"); + returnMsg.Add($" "); + + //...... + + var guestbooks = await _guestbookServices.Query(); + returnMsg.Add($"first time : the count of guestbooks is :{guestbooks.Count}"); + + int ex = 0; + returnMsg.Add($"There's an exception!!"); + returnMsg.Add($" "); + int throwEx = 1 / ex; + + var insertGuestbook = await _guestbookServices.Add(new Guestbook() + { + username = "bbb", + blogId = 1, + createdate = DateTime.Now, + isshow = true + }); + + guestbooks = await _guestbookServices.Query(); + returnMsg.Add($"first time : the count of guestbooks is :{guestbooks.Count}"); + returnMsg.Add($" "); + + _unitOfWorkManage.CommitTran(); + } + catch (Exception) + { + _unitOfWorkManage.RollbackTran(); + var passwords = await _passwordLibServices.Query(); + returnMsg.Add($"third time : the count of passwords is :{passwords.Count}"); + + var guestbooks = await _guestbookServices.Query(); + returnMsg.Add($"third time : the count of guestbooks is :{guestbooks.Count}"); + } + + return new MessageModel>() + { + success = true, + msg = "操作完成", + response = returnMsg + }; + } + + // GET: api/Transaction/5 + [HttpGet("{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) + { + } + + // PUT: api/Transaction/5 + [HttpPut("{id}")] + public void Put(long id, [FromBody] string value) + { + } + + /// + /// 测试事务在AOP中的使用 + /// + /// + /// + [HttpDelete("{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 new file mode 100644 index 00000000..95137c8e --- /dev/null +++ b/Blog.Core.Api/Controllers/UserController.cs @@ -0,0 +1,286 @@ +using AutoMapper; +using Blog.Core.AuthHelper.OverWrite; +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.Model.ViewModels; +using Blog.Core.Repository.UnitOfWorks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Blog.Core.Controllers +{ + /// + /// 用户管理 + /// + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public class UserController : BaseApiController + { + 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(IUnitOfWorkManage unitOfWorkManage, ISysUserInfoServices sysUserInfoServices, + IUserRoleServices userRoleServices, + IRoleServices roleServices, + IDepartmentServices departmentServices, + IUser user, IMapper mapper, ILogger logger) + { + _unitOfWorkManage = unitOfWorkManage; + _sysUserInfoServices = sysUserInfoServices; + _userRoleServices = userRoleServices; + _roleServices = roleServices; + _departmentServices = departmentServices; + _user = user; + _mapper = mapper; + _logger = logger; + } + + /// + /// 获取全部用户 + /// + /// + /// + /// + // GET: api/User + [HttpGet] + 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.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 + + // 这里可以封装到多表查询,此处简单处理 + 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.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 Success(data.ConvertTo(_mapper)); + } + + private (string, List) GetFullDepartmentName(List departments, long departmentId) + { + var departmentModel = departments.FirstOrDefault(d => d.Id == departmentId); + if (departmentModel == null) + { + 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 + [HttpGet("{id}")] + [AllowAnonymous] + public string Get(string id) + { + _logger.LogError("test wrong"); + return "value"; + } + + // GET: api/User/5 + /// + /// 获取用户详情根据token + /// 【无权限】 + /// + /// 令牌 + /// + [HttpGet] + [AllowAnonymous] + public async Task> GetInfoByToken(string token) + { + var data = new MessageModel(); + if (!string.IsNullOrEmpty(token)) + { + var tokenModel = JwtHelper.SerializeJwt(token); + if (tokenModel != null && tokenModel.Uid > 0) + { + var userinfo = await _sysUserInfoServices.QueryById(tokenModel.Uid); + if (userinfo != null) + { + data.response = _mapper.Map(userinfo); + data.success = true; + data.msg = "获取成功"; + } + } + } + + return data; + } + + /// + /// 添加一个用户 + /// + /// + /// + // POST: api/User + [HttpPost] + 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(_mapper.Map(sysUserInfo)); + data.success = id > 0; + if (data.success) + { + data.response = id.ObjToString(); + data.msg = "添加成功"; + } + + return data; + } + + /// + /// 更新用户与角色 + /// + /// + /// + // PUT: api/User/5 + [HttpPut] + 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 + { + if (sysUserInfo.uLoginPWD != oldUser.LoginPWD) + { + oldUser.CriticalModifyTime = DateTime.Now; + } + + _mapper.Map(sysUserInfo, oldUser); + + _unitOfWorkManage.BeginTran(); + // 无论 Update Or Add , 先删除当前用户的全部 U_R 关系 + var usreroles = (await _userRoleServices.Query(d => d.UserId == oldUser.Id)); + if (usreroles.Any()) + { + var ids = usreroles.Select(d => d.Id.ToString()).ToArray(); + var isAllDeleted = await _userRoleServices.DeleteByIds(ids); + if (!isAllDeleted) + { + return Failed("服务器更新异常"); + } + } + + // 然后再执行添加操作 + if (sysUserInfo.RIDs.Count > 0) + { + var userRolsAdd = new List(); + sysUserInfo.RIDs.ForEach(rid => { userRolsAdd.Add(new UserRole(oldUser.Id, rid)); }); + + 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)) + { + 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) + { + _unitOfWorkManage.RollbackTran(); + _logger.LogError(e, e.Message); + } + + return data; + } + + /// + /// 删除用户 + /// + /// + /// + // DELETE: api/ApiWithActions/5 + [HttpDelete] + public async Task> Delete(long id) + { + var data = new MessageModel(); + if (id > 0) + { + var userDetail = await _sysUserInfoServices.QueryById(id); + userDetail.IsDeleted = true; + data.success = await _sysUserInfoServices.Update(userDetail); + if (data.success) + { + data.msg = "删除成功"; + 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 new file mode 100644 index 00000000..693a68b8 --- /dev/null +++ b/Blog.Core.Api/Controllers/UserRoleController.cs @@ -0,0 +1,97 @@ +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; + +namespace Blog.Core.Controllers +{ + /// + /// 用户角色关系 + /// + [Produces("application/json")] + [Route("api/[controller]/[action]")] + [ApiController] + [Authorize(Permissions.Name)] + public class UserRoleController : Controller + { + private readonly ISysUserInfoServices _sysUserInfoServices; + private readonly IUserRoleServices _userRoleServices; + private readonly IRoleServices _roleServices; + private readonly IMapper _mapper; + + /// + /// 构造函数 + /// + /// + /// + /// + /// + public UserRoleController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IMapper mapper, IRoleServices roleServices) + { + _sysUserInfoServices = sysUserInfoServices; + _userRoleServices = userRoleServices; + _roleServices = roleServices; + _mapper = mapper; + } + + + + /// + /// 新建用户 + /// + /// + /// + /// + [HttpGet] + public async Task> AddUser(string loginName, string loginPwd) + { + var userInfo = await _sysUserInfoServices.SaveUserInfo(loginName, loginPwd); + return new MessageModel() + { + success = true, + msg = "添加成功", + response = _mapper.Map(userInfo) + }; + } + + /// + /// 新建Role + /// + /// + /// + [HttpGet] + public async Task> AddRole(string roleName) + { + return new MessageModel() + { + success = true, + msg = "添加成功", + response = await _roleServices.SaveRole(roleName) + }; + } + + /// + /// 新建用户角色关系 + /// + /// + /// + /// + [HttpGet] + public async Task> AddUserRole(long uid, long rid) + { + return new MessageModel() + { + success = true, + msg = "添加成功", + response = await _userRoleServices.SaveUserRole(uid, rid) + }; + } + + + + + } +} diff --git a/Blog.Core.Api/Controllers/ValuesController.cs b/Blog.Core.Api/Controllers/ValuesController.cs new file mode 100644 index 00000000..22d663e9 --- /dev/null +++ b/Blog.Core.Api/Controllers/ValuesController.cs @@ -0,0 +1,521 @@ +using AutoMapper; +using Blog.Core.Common; +using Blog.Core.Common.HttpContextUser; +using Blog.Core.Common.Https.HttpPolly; +using Blog.Core.Common.Option; +using Blog.Core.EventBus; +using Blog.Core.EventBus.EventHandling; +using Blog.Core.Extensions; +using Blog.Core.Filter; +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; +using Microsoft.Extensions.Options; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using System.ComponentModel.DataAnnotations; +using System.Linq.Expressions; +using System.Text; +using Blog.Core.Common.Caches.Interface; +using Blog.Core.Common.Utility; + +namespace Blog.Core.Controllers +{ + /// + /// Values控制器 + /// + [Route("api/[controller]/[action]")] + [ApiController] + //[Authorize] + //[Authorize(Roles = "Admin,Client")] + //[Authorize(Policy = "SystemOrAdmin")] + //[Authorize(PermissionNames.Permission)] + [Authorize] + public class ValuesController : BaseApiController + { + private IMapper _mapper; + private readonly IAdvertisementServices _advertisementServices; + private readonly Love _love; + private readonly IRoleModulePermissionServices _roleModulePermissionServices; + private readonly IUser _user; + private readonly IPasswordLibServices _passwordLibServices; + readonly IBlogArticleServices _blogArticleServices; + 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; + _roleModulePermissionServices = roleModulePermissionServices; + // 测试 Httpcontext + _user = user; + // 测试多库 + _passwordLibServices = passwordLibServices; + // 测试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方法 + /// + /// + // GET api/values + [HttpGet] + [AllowAnonymous] + public async Task> Get() + { + var data = new MessageModel(); + + /* + * 测试 sql 查询 + */ + 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 }); + + /* + * 测试按照指定列查询带多条件和排序方法 + */ + Expression> registerInfoWhere = a => a.btitle == "xxx" && a.bRemark == "XXX"; + var queryByColumsByMultiTerms = await _blogArticleServices + .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 }); + + + // 测试 AOP 缓存 + var blogArticles = await _blogArticleServices.GetBlogs(); + + + // 测试多表联查 + var roleModulePermissions = await _roleModulePermissionServices.QueryMuchTable(); + + + // 测试多个异步执行时间 + var roleModuleTask = _roleModulePermissionServices.Query(); + var listTask = _advertisementServices.Query(); + var ad = await roleModuleTask; + var list = await listTask; + + + // 测试service层返回异常 + _advertisementServices.ReturnExp(); + + 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消息队列 + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public async Task RedisMq([FromServices] IRedisBasketRepository _redisBasketRepository) + { + var msg = $"这里是一条日志{DateTime.Now}"; + await _redisBasketRepository.ListLeftPushAsync(RedisMqKey.Loging, msg); + } + + /// + /// 测试RabbitMQ事件总线 + /// + /// + /// + /// + [HttpGet] + [AllowAnonymous] + public void EventBusTry([FromServices] IEventBus _eventBus, string blogId = "1") + { + var blogDeletedEvent = new BlogQueryIntegrationEvent(blogId); + + _eventBus.Publish(blogDeletedEvent); + } + + /// + /// Get(int id)方法 + /// + /// + /// + // GET api/values/5 + [HttpGet("{id}")] + [AllowAnonymous] + [TypeFilter(typeof(UseServiceDIAttribute), Arguments = new object[] { "laozhang" })] + public ActionResult Get(int id) + { + var loveu = _love.SayLoveU(); + + return "value"; + } + + /// + /// 测试参数是必填项 + /// + /// + /// + [HttpGet] + [Route("/api/values/RequiredPara")] + public string RequiredP([Required] string id) + { + return id; + } + + + /// + /// 通过 HttpContext 获取用户信息 + /// + /// 声明类型,默认 jti + /// + [HttpGet] + [Route("/api/values/UserInfo")] + public MessageModel> GetUserInfo(string ClaimType = "jti") + { + var getUserInfoByToken = _user.GetUserInfoFromToken(ClaimType); + return new MessageModel>() + { + success = _user.IsAuthenticated(), + msg = _user.IsAuthenticated() ? _user.Name.ObjToString() : "未登录", + response = _user.GetClaimValueByType(ClaimType) + }; + } + + /// + /// to redirect by route template name. + /// + [HttpGet("/api/custom/go-destination")] + [AllowAnonymous] + public void Source() + { + var url = Url.RouteUrl("Destination_Route"); + Response.Redirect(url); + } + + /// + /// route with template name. + /// + /// + [HttpGet("/api/custom/destination", Name = "Destination_Route")] + [AllowAnonymous] + public string Destination() + { + return "555"; + } + + + /// + /// 测试 post 一个对象 + 独立参数 + /// + /// model实体类参数 + /// 独立参数 + [HttpPost] + [AllowAnonymous] + public object Post([FromBody] BlogArticle blogArticle, int id) + { + return Ok(new { success = true, data = blogArticle, id = id }); + } + + + /// + /// 测试 post 参数 + /// + /// + /// + [HttpPost] + [AllowAnonymous] + public object TestPostPara(string name) + { + return Ok(new { success = true, name = name }); + } + + /// + /// 测试多库连接 + /// + /// + [HttpGet("TestMutiDBAPI")] + [AllowAnonymous] + public async Task TestMutiDBAPI() + { + // 从主库中,操作blogs + 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 addPwd = await _passwordLibServices.Add(new PasswordLib() { }); + + return new + { + blogs, + pwds + }; + } + + /// + /// 测试Fulent做参数校验 + /// + /// + /// + [HttpPost] + [AllowAnonymous] + public async Task FluentVaTest([FromBody] UserRegisterVo param) + { + await Task.CompletedTask; + return "Okay"; + } + + /// + /// Put方法 + /// + /// + /// + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + /// + /// Delete方法 + /// + /// + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + + #region Apollo 配置 + + /// + /// 测试接入Apollo获取配置信息 + /// + [HttpGet("/apollo")] + [AllowAnonymous] + public async Task>> GetAllConfigByAppllo( + [FromServices] IConfiguration configuration) + { + return await Task.FromResult(configuration.AsEnumerable()); + } + + /// + /// 通过此处的key格式为 xx:xx:x + /// + [HttpGet("/apollo/{key}")] + [AllowAnonymous] + public async Task GetConfigByAppllo(string key) + { + return await Task.FromResult(AppSettings.app(key)); + } + + #endregion + + #region HttpPolly + + [HttpPost] + [AllowAnonymous] + public async Task HttpPollyPost() + { + var response = await _httpPollyHelper.PostAsync(HttpEnum.LocalHost, "/api/ElasticDemo/EsSearchTest", + "{\"from\": 0,\"size\": 10,\"word\": \"非那雄安\"}"); + + 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/Controllers/v1/ApbController.cs b/Blog.Core.Api/Controllers/v1/ApbController.cs similarity index 93% rename from Blog.Core/Controllers/v1/ApbController.cs rename to Blog.Core.Api/Controllers/v1/ApbController.cs index bdcf644d..6688c00e 100644 --- a/Blog.Core/Controllers/v1/ApbController.cs +++ b/Blog.Core.Api/Controllers/v1/ApbController.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Blog.Core.SwaggerHelper; using Microsoft.AspNetCore.Mvc; -using static Blog.Core.SwaggerHelper.CustomApiVersion; +using static Blog.Core.Extensions.CustomApiVersion; namespace Blog.Core.Controllers.v1 { diff --git a/Blog.Core/Controllers/v2/ApbController.cs b/Blog.Core.Api/Controllers/v2/ApbController.cs similarity index 78% rename from Blog.Core/Controllers/v2/ApbController.cs rename to Blog.Core.Api/Controllers/v2/ApbController.cs index c17404d8..f1728838 100644 --- a/Blog.Core/Controllers/v2/ApbController.cs +++ b/Blog.Core.Api/Controllers/v2/ApbController.cs @@ -1,19 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.SwaggerHelper; +using Blog.Core.SwaggerHelper; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using static Blog.Core.SwaggerHelper.CustomApiVersion; +using System.Collections.Generic; +using static Blog.Core.Extensions.CustomApiVersion; namespace Blog.Core.Controllers.v2 { [CustomRoute(ApiVersions.V2)] //[Route("api/[controller]")] [ApiController] - [Authorize(PermissionNames.Permission)] + [Authorize(Permissions.Name)] public class ApbController : ControllerBase { diff --git a/Blog.Core.Api/Dockerfile b/Blog.Core.Api/Dockerfile new file mode 100644 index 00000000..afb398c1 --- /dev/null +++ b/Blog.Core.Api/Dockerfile @@ -0,0 +1,41 @@ +#这种模式是先dotnet build后,然后再把dll进行构建镜像。 +#如果你想把这两步合在一起,可以看.sln根目录下的那个dockerfile。 + +#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:8.0 AS base +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +RUN echo 'Asia/Shanghai' >/etc/timezone + +#RUN apk add --no-cache ca-certificates python3 bash openssh git openssl-dev uwsgi uwsgi-python3 +#RUN apk add --no-cache --virtual .build-deps python3-dev gcc musl-dev libffi-dev make \ + #&& pip3 install --no-cache-dir --trusted-host mirrors.aliyun.com -i http://mirrors.aliyun.com/pypi/simple/ \ + #pymysql==0.8.1 \ + #Flask==1.0.2 \ + #Flask-RESTful==0.3.6 \ + #Flask-Script==2.0.6 \ + #Flask-SQLAlchemy==2.3.2 \ + #Flask-WTF==0.14.2 \ + #SQLAlchemy==1.2.7 \ + #simplejson==5.06.0 \ + #six==1.11.0 \ + #celery==4.2.1 \ + #xlrd==1.1.0 \ + #xlwt==1.3.0 \ + #msgpack==0.5.0 \ + #&& apk del .build-deps +# +#RUN git clone https://github.com/Supervisor/supervisor.git \ + #&& cd supervisor \ + #&& python3 setup.py install \ + #&& cd .. \ + #&& rm -rf supervisor \ + #&& cd /etc/ \ + #&& echo_supervisord_conf > supervisord.conf \ + #&& echo '[include]' >> supervisord.conf \ + #&& echo 'files = /code/supervisor/*.ini' >> supervisord.conf \ + #&& supervisord -c /etc/supervisord.conf +WORKDIR /app +COPY . . +EXPOSE 9291 +ENTRYPOINT ["dotnet", "Blog.Core.Api.dll","-b","0.0.0.0"] \ No newline at end of file diff --git a/Blog.Core.Api/Filter/AutofacPropertityModuleReg.cs b/Blog.Core.Api/Filter/AutofacPropertityModuleReg.cs new file mode 100644 index 00000000..605c4f5a --- /dev/null +++ b/Blog.Core.Api/Filter/AutofacPropertityModuleReg.cs @@ -0,0 +1,19 @@ +using Autofac; +using Microsoft.AspNetCore.Mvc; + +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) + .PropertiesAutowired(); + + } + } +} diff --git a/Blog.Core/SwaggerHelper/CustomRouteAttribute.cs b/Blog.Core.Api/Filter/CustomRouteAttribute.cs similarity index 90% rename from Blog.Core/SwaggerHelper/CustomRouteAttribute.cs rename to Blog.Core.Api/Filter/CustomRouteAttribute.cs index 84eea03f..128c53a8 100644 --- a/Blog.Core/SwaggerHelper/CustomRouteAttribute.cs +++ b/Blog.Core.Api/Filter/CustomRouteAttribute.cs @@ -1,10 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using static Blog.Core.SwaggerHelper.CustomApiVersion; +using static Blog.Core.Extensions.CustomApiVersion; namespace Blog.Core.SwaggerHelper { diff --git a/Blog.Core.Api/Filter/GlobalAuthorizeFilter.cs b/Blog.Core.Api/Filter/GlobalAuthorizeFilter.cs new file mode 100644 index 00000000..e83decd7 --- /dev/null +++ b/Blog.Core.Api/Filter/GlobalAuthorizeFilter.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Filters; +using System.Linq; +using System.Threading.Tasks; + +namespace Blog.Core.Filter +{ + /// + /// Summary:全局路由权限公约 + /// Remarks:目的是针对不同的路由,采用不同的授权过滤器 + /// 如果 controller 上不加 [Authorize] 特性,默认都是 Permission 策略 + /// 否则,如果想特例其他授权机制的话,需要在 controller 上带上 [Authorize],然后再action上自定义授权即可,比如 [Authorize(Roles = "Admin")] + /// + public class GlobalRouteAuthorizeConvention : IApplicationModelConvention + { + public void Apply(ApplicationModel application) + { + foreach (var c in application.Controllers) + { + if (!c.Filters.Any(e => e is AuthorizeFilter)) + { + // 没有写特性,就用全局的 Permission 授权 + c.Filters.Add(new AuthorizeFilter(Permissions.Name)); + } + else { + // 写了特性,[Authorize] 或 [AllowAnonymous] ,根据情况进行权限认证 + } + + } + } + } + + /// + /// 全局权限过滤器【无效】 + /// + public class GlobalAuthorizeFilter : AuthorizeFilter + { + + public override Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + if (context.Filters.Any(item => item is IAsyncAuthorizationFilter && item != this)) + { + return Task.FromResult(0); + } + + + return base.OnAuthorizationAsync(context); + + + } + } + + + +} diff --git a/Blog.Core.Api/Filter/GlobalExceptionFilter.cs b/Blog.Core.Api/Filter/GlobalExceptionFilter.cs new file mode 100644 index 00000000..e11d8dbd --- /dev/null +++ b/Blog.Core.Api/Filter/GlobalExceptionFilter.cs @@ -0,0 +1,100 @@ +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.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.SignalR; +using StackExchange.Profiling; + +namespace Blog.Core.Filter +{ + /// + /// 全局异常错误日志 + /// + public class GlobalExceptionsFilter : IExceptionFilter + { + private readonly IWebHostEnvironment _env; + private readonly IHubContext _hubContext; + private readonly ILogger _loggerHelper; + + public GlobalExceptionsFilter(IWebHostEnvironment env, ILogger loggerHelper, + IHubContext hubContext) + { + _env = env; + _loggerHelper = loggerHelper; + _hubContext = hubContext; + } + + public void OnException(ExceptionContext context) + { + var json = new MessageModel(); + + 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)) + { + json.msg = json.msg.Replace(errorAudit, $"(若新添加服务,需要重新编译项目){errorAudit}"); + } + + if (_env.EnvironmentName.ObjToString().Equals("Development")) + { + json.msgDev = context.Exception.StackTrace; //堆栈信息 + } + + var res = new ContentResult(); + res.Content = JsonHelper.GetJSON>(json); + + context.Result = res; + + MiniProfiler.Current.CustomTiming("Errors:", json.msg); + + + //进行错误日志记录 + _loggerHelper.LogError(json.msg + WriteLog(json.msg, context.Exception)); + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + _hubContext.Clients.All.SendAsync("ReceiveUpdate", json.msg).Wait(); + } + } + + /// + /// 自定义返回格式 + /// + /// + /// + /// + 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 + }); + } + } + + public class InternalServerErrorObjectResult : ObjectResult + { + public InternalServerErrorObjectResult(object value) : base(value) + { + StatusCode = StatusCodes.Status500InternalServerError; + } + } + + //返回错误信息 + 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/GlobalRoutePrefixFilter.cs b/Blog.Core.Api/Filter/GlobalRoutePrefixFilter.cs new file mode 100644 index 00000000..86201fcf --- /dev/null +++ b/Blog.Core.Api/Filter/GlobalRoutePrefixFilter.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Routing; +using System.Linq; + +namespace Blog.Core.Filter +{ + /// + /// 全局路由前缀公约 + /// + public class GlobalRoutePrefixFilter : IApplicationModelConvention + { + private readonly AttributeRouteModel _centralPrefix; + + public GlobalRoutePrefixFilter(IRouteTemplateProvider routeTemplateProvider) + { + _centralPrefix = new AttributeRouteModel(routeTemplateProvider); + } + + //接口的Apply方法 + public void Apply(ApplicationModel application) + { + //遍历所有的 Controller + foreach (var controller in application.Controllers) + { + // 已经标记了 RouteAttribute 的 Controller + var matchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList(); + if (matchedSelectors.Any()) + { + foreach (var selectorModel in matchedSelectors) + { + // 在 当前路由上 再 添加一个 路由前缀 + selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, + selectorModel.AttributeRouteModel); + } + } + + // 没有标记 RouteAttribute 的 Controller + var unmatchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel == null).ToList(); + if (unmatchedSelectors.Any()) + { + foreach (var selectorModel in unmatchedSelectors) + { + // 添加一个 路由前缀 + selectorModel.AttributeRouteModel = _centralPrefix; + } + } + } + } + } + +} diff --git a/Blog.Core.Api/Filter/UseServiceDIAttribute.cs b/Blog.Core.Api/Filter/UseServiceDIAttribute.cs new file mode 100644 index 00000000..2c487872 --- /dev/null +++ b/Blog.Core.Api/Filter/UseServiceDIAttribute.cs @@ -0,0 +1,35 @@ +using Blog.Core.IServices; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Blog.Core.Filter +{ + public class UseServiceDIAttribute : ActionFilterAttribute + { + + protected readonly ILogger _logger; + private readonly IBlogArticleServices _blogArticleServices; + private readonly string _name; + + public UseServiceDIAttribute(ILogger logger, IBlogArticleServices blogArticleServices, string Name = "") + { + _logger = logger; + _blogArticleServices = blogArticleServices; + _name = Name; + } + + + public override void OnActionExecuted(ActionExecutedContext context) + { + 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/Program.cs b/Blog.Core.Api/Program.cs new file mode 100644 index 00000000..bc2b51cc --- /dev/null +++ b/Blog.Core.Api/Program.cs @@ -0,0 +1,190 @@ +// 以下为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 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) +{ + 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 => + { + 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 new file mode 100644 index 00000000..e3113d39 --- /dev/null +++ b/Blog.Core.Api/Properties/launchSettings.json @@ -0,0 +1,35 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:60716", + "sslPort": 0 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Blog.Core": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + //"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore"// 如果要开始skywalking,请取消此行注释 + }, + "applicationUrl": "http://localhost:9291" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "publishAllPorts": true + } + } +} \ No newline at end of file diff --git a/Blog.Core.Api/StopContainerImg.sh b/Blog.Core.Api/StopContainerImg.sh new file mode 100644 index 00000000..0bffe029 --- /dev/null +++ b/Blog.Core.Api/StopContainerImg.sh @@ -0,0 +1,8 @@ +#!/bin/bash +docker ps|grep ${1}|while read i;do i; +echo "容器已启动,详细信息:${i}"; +docker stop ${1}; +docker rm ${1}; +docker rmi ${2}; +echo "已关闭容器,${1}" ; +done; \ No newline at end of file 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.apollo.json b/Blog.Core.Api/appsettings.apollo.json new file mode 100644 index 00000000..826c75ca --- /dev/null +++ b/Blog.Core.Api/appsettings.apollo.json @@ -0,0 +1,18 @@ +{ + //apollo 配置 + "Apollo": { + "Enable": false, + "Config": { + "AppId": "blog.core", + "Env": "DEV", + "MetaServer": "http://localhost:8080/", + "ConfigServer": [ "http://localhost:8080/" ] + }, + "Namespaces": [ //Namespaces的数据格式Properties,Xml,Json,Yml,Yaml,Txt + { + "Name": "test", + "Format": "json" + } + ] + } +} \ No newline at end of file diff --git a/Blog.Core.Api/appsettings.json b/Blog.Core.Api/appsettings.json new file mode 100644 index 00000000..d62aae9e --- /dev/null +++ b/Blog.Core.Api/appsettings.json @@ -0,0 +1,364 @@ +{ + "urls": "http://*:9291", //web服务端口,如果用IIS部署,把这个去掉 + "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" + } + } + }, + "AllowedHosts": "*", + "Redis": { + "Enable": false, + "ConnectionString": "127.0.0.1:6379,allowAdmin=true", + "InstanceName": "" //前缀 + }, + "RabbitMQ": { + "Enabled": true, + "Connection": "101xxxx57", + "UserName": "xxxx", + "Password": "xxxxx", + "Port": "5672", + "RetryCount": 2 + }, + "Kafka": { + "Enabled": false, + "Servers": "localhost:9092", + "Topic": "blog", + "GroupId": "blog-consumer", + "NumPartitions": 3 //主题分区数量 + }, + "EventBus": { + "Enabled": false, + "SubscriptionClientName": "Blog.Core" + }, + "AppSettings": { + "CachingAOP": { + "Enabled": true + }, + "LogToDb": true, + "LogAOP": { + "Enabled": false, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + } + }, + "TranAOP": { + "Enabled": true + }, + "UserAuditAOP": { + "Enabled": false + }, + "SqlAOP": { + "Enabled": true, //关闭日志2:修改Sql日志是否显示(也可以精准配置,是否生成到文件、数据库、控制台) + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + }, + "LogToConsole": { + "Enabled": true + } + }, + "Date": "2018-08-28", + "SeedDBEnabled": true, //只生成表结构 + "SeedDBDataEnabled": true, //生成表,并初始化数据 + "Author": "Blog.Core", + "SvcName": "", // /svc/blog + "UseLoadTest": false, + "CacheDbEnabled": false + }, + + //优化DB配置、不会再区分单库多库 + //MainDb:标识当前项目的主库,所对应的连接字符串的Enabled必须为true + //Log:标识日志库,所对应的连接字符串的Enabled必须为true + //从库只需配置Slaves数组,要求数据库类型一致!,比如都是SqlServer + // + //新增,故障转移方案 + //如果主库挂了,会自动切换到备用连接(比如说主库+备用库) + //备用连接的ConnId配置为主库的ConnId+数字即可,比如主库的ConnId为Main,那么备用连接的ConnId为Mian1 + //主库、备用库无需数据库类型一致! + //备用库不会有程序维护,需要手动维护 + "MainDB": "Main", //当前项目的主库,所对应的连接字符串的Enabled必须为true + "DBS": [ + /* + 对应下边的 DBType + MySql = 0, + SqlServer = 1, + Sqlite = 2, + Oracle = 3, + PostgreSQL = 4, + Dm = 5,//达梦 + Kdbndp = 6,//人大金仓 + */ + { + "ConnId": "Main", + "DBType": 2, + "Enabled": true, + "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, + "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" + }, + { + "ConnId": "WMBLOG_MSSQL_2", + "DBType": 1, + "Enabled": false, + "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" + }, + { + "ConnId": "WMBLOG_MYSQL", + "DBType": 0, + "Enabled": false, + "Connection": "server=localhost;Database=blog;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" + }, + { + "ConnId": "WMBLOG_MYSQL_2", + "DBType": 0, + "Enabled": false, + "Connection": "server=localhost;Database=blogcore001;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" + }, + { + "ConnId": "WMBLOG_ORACLE", + "DBType": 3, + "Enabled": false, + "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": { + "Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", //不要太短,16位+ + "SecretFile": "C:\\my-file\\blog.core.audience.secret.txt", //安全。内容就是Secret + "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,http://127.0.0.1:6688,http://localhost:6688" + }, + "AppConfigAlert": { + "Enabled": true + }, + "ApiName": "Blog.Core", + "IdentityServer4": { + "Enabled": false, // 这里默认是false,表示使用jwt,如果设置为true,则表示系统使用Ids4模式 + "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": true //性能分析开启 + }, + "Nacos": { + "Enabled": false //Nacos注册中心 + } + }, + "Middleware": { + "RequestResponseLog": { + "Enabled": true, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + } + }, + "IPLog": { + "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": true + }, + "SignalRSendLog": { + "Enabled": true + }, + "QuartzNetJob": { + "Enabled": true + }, + "Consul": { + "Enabled": false + }, + "IpRateLimit": { + "Enabled": true + }, + "EncryptionResponse": { + "Enabled": true, + "AllApis": false, + "LimitApis": [ + "/api/Login/GetJwtTokenSecret" + ] + }, + "EncryptionRequest": { + "Enabled": true, + "AllApis": false, + "LimitApis": [ + "/api/Login/GetJwtTokenSecret" + ] + } + }, + "IpRateLimiting": { + "EnableEndpointRateLimiting": true, //False: globally executed, true: executed for each + "StackBlockedRequests": false, //False: Number of rejections should be recorded on another counter + "RealIpHeader": "X-Real-IP", + "ClientIdHeader": "X-ClientId", + "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规则,结尾一定要带* + { + "Endpoint": "*:/api/blog*", + "Period": "1m", + "Limit": 20 + }, + { + "Endpoint": "*/api/*", + "Period": "1s", + "Limit": 3 + }, + { + "Endpoint": "*/api/*", + "Period": "1m", + "Limit": 30 + }, + { + "Endpoint": "*/api/*", + "Period": "12h", + "Limit": 500 + } + ] + + }, + "ConsulSetting": { + "ServiceName": "BlogCoreService", + "ServiceIP": "localhost", + "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": 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 new file mode 100644 index 00000000..99b4caa4 --- /dev/null +++ b/Blog.Core.Api/index.html @@ -0,0 +1,212 @@ + + + + + + + + + + + + + %(DocumentTitle) + + + + + + + %(HeadContent) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + 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/BlogArticle.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/BlogArticle.tsv new file mode 100644 index 00000000..b6969787 --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/BlogArticle.tsv @@ -0,0 +1,15 @@ +[ + { + "bID": 1, + "bsubmitter": "admin", + "btitle": "测试数据:IIS new add website ,some wrong:The requested page cannot be accessed because the related configuration data for the page is invalid.", + "bcategory": "技术博文", + "bcontent": "

问题:

The requested page cannot be accessed because the related configuration data for the page is invalid.

HTTP Error 500.19 - Internal Server Error The requested page cannot be accessed because the related configuration data for the page is invalid.

Detailed Error Information:

Module IIS Web Core

Notification Unknown

Handler Not yet determined

Error Code 0x80070003

Config Error Cannot read configuration file

Config File \\?\\D:\\Projects\\...\\web.config

Requested URL http:// localhost:8080/

Physical Path

Logon Method Not yet determined

Logon User Not yet determined

Request Tracing Directory C:\\Users\\...\\TraceLogFiles\\

Config Source:

Answer:

1,find the site's application pools

2,\"Advanced Settings\" ==> Indentity ==>  Custom account



", + "btraffic": 127, + "bcommentNum": 1, + "bUpdateTime": "\/Date(1546272000000+0800)\/", + "bCreateTime": "\/Date(1546272000000+0800)\/", + "bRemark": null, + "IsDeleted": 0 + } +] 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 new file mode 100644 index 00000000..76c55a22 --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Modules.tsv @@ -0,0 +1,1583 @@ +[ + { + "IsDeleted": 0, + "ParentId": null, + "Name": "values接口", + "LinkUrl": "\/api\/values", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 1 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "claims的接口", + "LinkUrl": "\/api\/claims", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 2 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "UserRole接口", + "LinkUrl": "\/api\/UserRole", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 3 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": null, + "LinkUrl": "\/api\/v2\/Apb\/apbs", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 4 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "修改 tibug 文章", + "LinkUrl": "\/api\/TopicDetail\/update", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 5 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "删除tibug文章", + "LinkUrl": "\/api\/TopicDetail\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 6 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取用户", + "LinkUrl": "\/api\/user\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 7 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取用户详情", + "LinkUrl": "\/api\/user\/get\/\\d+", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 1, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 8 + }, + { + "IsDeleted": 1, + "ParentId": null, + "Name": "角色接口", + "LinkUrl": "\/api\/role", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 9 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "添加用户", + "LinkUrl": "\/api\/user\/post", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 10 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "删除用户", + "LinkUrl": "\/api\/user\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 11 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "修改用户", + "LinkUrl": "\/api\/user\/put", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 12 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取api接口", + "LinkUrl": "\/api\/module\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 13 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "删除api接口", + "LinkUrl": "\/api\/module\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 14 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "修改api接口", + "LinkUrl": "\/api\/module\/put", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 15 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "添加api接口", + "LinkUrl": "\/api\/module\/post", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 16 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取菜单", + "LinkUrl": "\/api\/permission\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 17 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "删除菜单", + "LinkUrl": "\/api\/permission\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 18 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "修改菜单", + "LinkUrl": "\/api\/permission\/put", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 19 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "添加菜单", + "LinkUrl": "\/api\/permission\/post", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 20 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取菜单树", + "LinkUrl": "\/api\/permission\/getpermissiontree", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 21 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取角色", + "LinkUrl": "\/api\/role\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 22 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "删除角色", + "LinkUrl": "\/api\/role\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 23 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "修改角色", + "LinkUrl": "\/api\/role\/put", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 24 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "添加角色", + "LinkUrl": "\/api\/role\/post", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 25 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取bug", + "LinkUrl": "\/api\/TopicDetail\/Get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 26 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取博客", + "LinkUrl": "\/api\/Blog", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 27 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "保存分配", + "LinkUrl": "\/api\/permission\/Assign", + "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": 28 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "Get导航条", + "LinkUrl": "\/api\/permission\/GetNavigationBar", + "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": 29 + }, + { + "IsDeleted": 1, + "ParentId": null, + "Name": "test", + "LinkUrl": "\/api\/Blog\/delete1", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 30 + }, + { + "IsDeleted": 1, + "ParentId": null, + "Name": "test", + "LinkUrl": "\/api\/Blog\/delete2", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 31 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "删除博客", + "LinkUrl": "\/api\/Blog\/delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 32 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取全部日志", + "LinkUrl": "\/api\/Monitor\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 33 + }, + { + "IsDeleted": 1, + "ParentId": null, + "Name": "Agent -测试- 快速添加接口权限", + "LinkUrl": "\/api\/Agent\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 34 + }, + { + "IsDeleted": 1, + "ParentId": null, + "Name": "test", + "LinkUrl": "\/api\/test\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 35 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "Department - 测试新建api - 部门管控", + "LinkUrl": "\/api\/Department\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 36 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取任务调取分页", + "LinkUrl": "\/api\/TasksQz\/get", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 37 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "添加任务", + "LinkUrl": "\/api\/TasksQz\/Post", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 38 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "编辑任务", + "LinkUrl": "\/api\/TasksQz\/put", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 39 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "开启任务", + "LinkUrl": "\/api\/TasksQz\/StartJob", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 40 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "停止任务", + "LinkUrl": "\/api\/TasksQz\/StopJob", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 41 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "重启任务", + "LinkUrl": "\/api\/TasksQz\/ReCovery", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 42 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "删除任务", + "LinkUrl": "\/api\/TasksQz\/Delete", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 43 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "暂停任务", + "LinkUrl": "\/api\/TasksQz\/PauseJob", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 44 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "恢复任务", + "LinkUrl": "\/api\/TasksQz\/ResumeJob", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 45 + }, + { + "IsDeleted": 0, + "ParentId": null, + "Name": "获取任务类名", + "LinkUrl": "\/api\/TasksQz\/GetTaskNameSpace", + "Area": null, + "Controller": null, + "Action": null, + "Icon": null, + "Code": null, + "OrderSort": 0, + "Description": null, + "IsMenu": 0, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "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 new file mode 100644 index 00000000..66b4313e --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Permission.tsv @@ -0,0 +1,2634 @@ +[ + { + "Code": "\/", + "Name": "首页", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": -90, + "Icon": "fa-home", + "IconNew": "HomeFilled", + "Description": "33", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 1, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "用户角色管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-users", + "IconNew": "UserFilled", + "Description": "11", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 2, + "IsHide": 0 + }, + { + "Code": "\/User\/Roles", + "Name": "角色管理", + "IsButton": 0, + "Pid": 2, + "Mid": 22, + "OrderSort": 0, + "Icon": null, + "IconNew": "Menu", + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 3, + "IsHide": 0 + }, + { + "Code": "\/User\/Users", + "Name": "用户管理", + "IsButton": 0, + "Pid": 2, + "Mid": 7, + "OrderSort": 0, + "Icon": null, + "IconNew": "Menu", + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 4, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "菜单权限管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-sitemap", + "IconNew": "Lock", + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 5, + "IsHide": 0 + }, + { + "Code": "\/Permission\/Module", + "Name": "接口管理", + "IsButton": 0, + "Pid": 5, + "Mid": 13, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 6, + "IsHide": 0 + }, + { + "Code": "\/Permission\/Permission", + "Name": "菜单管理", + "IsButton": 0, + "Pid": 5, + "Mid": 17, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 7, + "IsHide": 0 + }, + { + "Code": "\/System\/BasicSetting", + "Name": "个人设置", + "IsButton": 0, + "Pid": 68, + "Mid": 0, + "OrderSort": 5, + "Icon": "fa-star ", + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 8, + "IsHide": 0 + }, + { + "Code": "无", + "Name": "查询", + "IsButton": 1, + "Pid": 4, + "Mid": 7, + "OrderSort": 0, + "Icon": null, + "Description": "这个用户页的查询按钮", + "Enabled": 1, + "Func": "handleQuery", + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 9, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "报表管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-line-chart", + "IconNew": "Histogram", + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 10, + "IsHide": 0 + }, + { + "Code": "\/Form\/Charts", + "Name": "图表", + "IsButton": 0, + "Pid": 10, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 11, + "IsHide": 0 + }, + { + "Code": "\/Form\/Form", + "Name": "表单", + "IsButton": 0, + "Pid": 10, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 12, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "新增", + "IsButton": 1, + "Pid": 4, + "Mid": 10, + "OrderSort": 0, + "Icon": null, + "Description": "新增用户", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "Func": "handleAdd", + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 13, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "Pid": 4, + "Mid": 12, + "OrderSort": 0, + "Icon": null, + "Description": "编辑用户", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "Func": "handleEdit", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 14, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": 1, + "Pid": 4, + "Mid": 11, + "OrderSort": 0, + "Icon": null, + "Description": "删除用户", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "Func": "handleDel", + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 15, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 3, + "Mid": 22, + "OrderSort": 0, + "Icon": null, + "Description": "查询 角色", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Func": "handleQuery", + "IsDeleted": 0, + "Id": 16, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "新增", + "IsButton": 1, + "Pid": 3, + "Mid": 25, + "OrderSort": 0, + "Icon": null, + "Description": "新增 角色", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Func": "handleAdd", + "IsDeleted": 0, + "Id": 17, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "Pid": 3, + "Mid": 24, + "OrderSort": 0, + "Icon": null, + "Description": "编辑角色", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Func": "handleEdit", + "IsDeleted": 0, + "Id": 18, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": 1, + "Pid": 3, + "Mid": 23, + "OrderSort": 0, + "Icon": null, + "Description": "删除角色", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Func": "handleDel", + "IsDeleted": 0, + "Id": 19, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 6, + "Mid": 13, + "OrderSort": 0, + "Icon": null, + "Description": "查询 接口", + "Func": "handleQuery", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 20, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "新增", + "IsButton": 1, + "Pid": 6, + "Mid": 16, + "OrderSort": 0, + "Icon": null, + "Description": "新增 接口", + "Enabled": 1, + "Func": "handleAdd", + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 21, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "Pid": 6, + "Mid": 15, + "OrderSort": 0, + "Icon": null, + "Description": "编辑 接口", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "Func": "handleEdit", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 22, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": 1, + "Pid": 6, + "Mid": 14, + "OrderSort": 0, + "Icon": null, + "Description": "删除接口", + "Enabled": 1, + "CreateId": 18, + "Func": "handleDel", + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 23, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 7, + "Mid": 17, + "OrderSort": 0, + "Icon": null, + "Description": "查询 菜单", + "Func": "handleQuery", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 24, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "新增", + "IsButton": 1, + "Pid": 7, + "Mid": 20, + "OrderSort": 0, + "Icon": null, + "Description": "新增菜单", + "Enabled": 1, + "Func": "handleAdd", + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 25, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "Pid": 7, + "Mid": 19, + "OrderSort": 0, + "Icon": null, + "Description": "编辑菜单", + "Enabled": 1, + "Func": "handleEdit", + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 26, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": 1, + "Pid": 7, + "Mid": 18, + "OrderSort": 0, + "Icon": null, + "Description": "删除 菜单", + "Enabled": 1, + "CreateId": 18, + "Func": "handleDel", + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 27, + "IsHide": 0 + }, + { + "Code": "\/Tibug\/Bugs", + "Name": "TiBug", + "IsButton": 0, + "Pid": 42, + "Mid": 26, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 28, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "博客管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-file-word-o", + "IconNew": "List", + "Description": null, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 29, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "Pid": 28, + "Mid": 5, + "OrderSort": 0, + "Icon": null, + "Description": "编辑 tibug ", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "Func": "handleEdit", + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 30, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": 1, + "Pid": 28, + "Mid": 6, + "OrderSort": 0, + "Icon": null, + "Description": "删除 tibug", + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "Func": "handleDel", + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 31, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 28, + "Mid": 26, + "OrderSort": 0, + "Icon": null, + "Description": "查询 tibug", + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "Func": "handleQuery", + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 32, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "菜单树", + "IsButton": 1, + "Pid": 7, + "Mid": 21, + "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": 33, + "IsHide": 1 + }, + { + "Code": "\/Permission\/Assign", + "Name": "权限分配", + "IsButton": 0, + "Pid": 5, + "Mid": 0, + "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": 34, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "保存权限", + "IsButton": 1, + "Pid": 34, + "Mid": 28, + "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": 35, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "左侧导航", + "IsButton": 1, + "Pid": 7, + "Mid": 29, + "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": 36, + "IsHide": 1 + }, + { + "Code": "-", + "Name": "测试页面管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-flask", + "IconNew": "WarningFilled", + "Description": null, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 37, + "IsHide": 0 + }, + { + "Code": "\/TestShow\/TestOne", + "Name": "测试页面1", + "IsButton": 0, + "Pid": 37, + "Mid": 0, + "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": 38, + "IsHide": 0 + }, + { + "Code": "\/TestShow\/TestTwo", + "Name": "测试页面2", + "IsButton": 0, + "Pid": 37, + "Mid": 0, + "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": 39, + "IsHide": 0 + }, + { + "Code": "\/I18n\/index", + "Name": "国际化", + "IsButton": 0, + "Pid": 41, + "Mid": 0, + "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": 40, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "多语言管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-language", + "IconNew": "HelpFilled", + "Description": null, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 41, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "问题管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-bug", + "IconNew": "Flag", + "Description": null, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 42, + "IsHide": 0 + }, + { + "Code": "\/Blog\/Blogs", + "Name": "博客", + "IsButton": 0, + "Pid": 29, + "Mid": 27, + "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": 43, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "多级路由", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-sort-amount-asc", + "IconNew": "ChromeFilled", + "Description": null, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 44, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "Menu-1", + "IsButton": 0, + "Pid": 44, + "Mid": 0, + "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": 45, + "IsHide": 0 + }, + { + "Code": "\/Recursion\/Menu_1\/Menu_1_2", + "Name": "Menu-1-2", + "IsButton": 0, + "Pid": 45, + "Mid": 0, + "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": 46, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "Menu-1-1", + "IsButton": 0, + "Pid": 45, + "Mid": 0, + "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": 47, + "IsHide": 0 + }, + { + "Code": "\/Recursion\/Menu_1\/Menu_1_1\/Menu_1_1_1", + "Name": "Menu-1-1-1", + "IsButton": 0, + "Pid": 47, + "Mid": 0, + "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": 48, + "IsHide": 0 + }, + { + "Code": "\/Recursion\/Menu_1\/Menu_1_1\/Menu_1_1_2", + "Name": "Menu-1-1-2", + "IsButton": 0, + "Pid": 47, + "Mid": 0, + "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": 49, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 50, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 51, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 52, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 53, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 54, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 55, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 56, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 57, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 58, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 59, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 60, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 61, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 62, + "IsHide": 0 + }, + { + "Code": "s", + "Name": "s", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 0, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 63, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": 1, + "Pid": 43, + "Mid": 32, + "OrderSort": 0, + "Icon": null, + "Description": "删除博客按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleDel", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 64, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "日志管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 0, + "Icon": "fa-diamond", + "IconNew": "Stamp", + "Description": null, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 65, + "IsHide": 0 + }, + { + "Code": "\/Logs\/Index", + "Name": "全部日志", + "IsButton": 0, + "Pid": 65, + "Mid": 33, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 66, + "IsHide": 0 + }, + { + "Code": "\/Blog\/Detail\/:id", + "Name": "博客详情", + "IsButton": 0, + "Pid": 29, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 67, + "IsHide": 1 + }, + { + "Code": "-", + "Name": "系统管理", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 1, + "Icon": "el-icon-s-operation", + "IconNew": "Tools", + "Description": null, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 68, + "IsHide": 0 + }, + { + "Code": "\/System\/My", + "Name": "个人中心", + "IsButton": 0, + "Pid": 68, + "Mid": 0, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 69, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 69, + "Mid": 34, + "OrderSort": 0, + "Icon": null, + "Description": "Agent 代理的查询接口", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 70, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 69, + "Mid": 35, + "OrderSort": 0, + "Icon": null, + "Description": "查询 部门 Department get", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 1, + "Id": 71, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 69, + "Mid": 36, + "OrderSort": 0, + "Icon": null, + "Description": null, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 72, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 43, + "Mid": 27, + "OrderSort": 0, + "Icon": null, + "Description": "查询博客按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleQuery", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 73, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "Pid": 43, + "Mid": 27, + "OrderSort": 0, + "Icon": null, + "Description": "编辑博客按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleEdit", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 74, + "IsHide": 0 + }, + { + "Code": "-", + "Name": "任务调度", + "IsButton": 0, + "Pid": 0, + "Mid": 0, + "OrderSort": 1, + "Icon": "fa-history", + "IconNew": "Ticket", + "Description": null, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 75, + "IsHide": 0 + }, + { + "Code": "\/Task\/QuartzJob", + "Name": "任务列表", + "IsButton": 0, + "Pid": 75, + "Mid": 37, + "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": 76, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "查询", + "IsButton": 1, + "Pid": 76, + "Mid": 37, + "OrderSort": 0, + "Icon": null, + "Description": "查询任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleQuery", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 77, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "添加", + "IsButton": 1, + "Pid": 76, + "Mid": 38, + "OrderSort": 0, + "Icon": null, + "Description": "添加任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleAdd", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 78, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "编辑", + "IsButton": 1, + "Pid": 76, + "Mid": 39, + "OrderSort": 0, + "Icon": null, + "Description": "编辑任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleEdit", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 79, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "开启", + "IsButton": 1, + "Pid": 76, + "Mid": 40, + "OrderSort": 0, + "Icon": null, + "Description": "开启任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleStartJob", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 80, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "停止", + "IsButton": 1, + "Pid": 76, + "Mid": 41, + "OrderSort": 0, + "Icon": null, + "Description": "停止任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleStopJob", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 81, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "重启", + "IsButton": 1, + "Pid": 76, + "Mid": 42, + "OrderSort": 0, + "Icon": null, + "Description": "重启任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleReCoveryJob", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 82, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "删除", + "IsButton": 1, + "Pid": 76, + "Mid": 43, + "OrderSort": 0, + "Icon": null, + "Description": "删除任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleDel", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 83, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "暂停", + "IsButton": 1, + "Pid": 76, + "Mid": 44, + "OrderSort": 0, + "Icon": null, + "Description": "暂停任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handlePauseJob", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 84, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "恢复", + "IsButton": 1, + "Pid": 76, + "Mid": 45, + "OrderSort": 0, + "Icon": null, + "Description": "恢复任务按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "handleResumeJob", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "IsDeleted": 0, + "Id": 85, + "IsHide": 0 + }, + { + "Code": " ", + "Name": "获取任务名称", + "IsButton": 1, + "Pid": 76, + "Mid": 46, + "OrderSort": 0, + "Icon": null, + "Description": "获取任务名称按钮", + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "Func": "", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "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/Role.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Role.tsv new file mode 100644 index 00000000..d29deacc --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Role.tsv @@ -0,0 +1,408 @@ +[ + { + "IsDeleted": 0, + "Name": "Admin", + "Description": "普通管理", + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 1 + }, + { + "IsDeleted": 0, + "Name": "System", + "Description": "系统管理", + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 2 + }, + { + "IsDeleted": 0, + "Name": "Tibug", + "Description": "tibug系统管理", + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 3 + }, + { + "IsDeleted": 0, + "Name": "SuperAdmin", + "Description": "超级管理", + "OrderSort": 0, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "blogadmin", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 4 + }, + { + "IsDeleted": 1, + "Name": "AdminTest", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": 18, + "CreateBy": "提bug账号", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 5 + }, + { + "IsDeleted": 0, + "Name": "AdminTest", + "Description": "测试管理", + "OrderSort": 1, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 6 + }, + { + "IsDeleted": 0, + "Name": "AdminTest2", + "Description": "测试管理2", + "OrderSort": 1, + "Enabled": 1, + "CreateId": 23, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 7 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 8 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 9 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 10 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 11 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 12 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 13 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 14 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 15 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 16 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 17 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 18 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 19 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 20 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 21 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 22 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 23 + }, + { + "IsDeleted": 1, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 24 + }, + { + "IsDeleted": null, + "Name": "sss", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 25 + }, + { + "IsDeleted": null, + "Name": "213", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 26 + }, + { + "IsDeleted": null, + "Name": "抬头填", + "Description": null, + "OrderSort": 1, + "Enabled": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 27 + }, + { + "IsDeleted": 0, + "Name": "hello1", + "Description": "测试 常用 get post put 请求", + "OrderSort": 1, + "Enabled": 1, + "CreateId": 12, + "CreateBy": "后台总管理员", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 28 + }, + { + "IsDeleted": 1, + "Name": "55", + "Description": "555", + "OrderSort": 1, + "Enabled": 0, + "CreateId": 39, + "CreateBy": "Kawhi", + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 29 + } +] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv new file mode 100644 index 00000000..5626b81f --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/RoleModulePermission.tsv @@ -0,0 +1,3098 @@ +[ + { + "IsDeleted": 0, + "RoleId": 1, + "ModuleId": 1, + "PermissionId": null, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 1 + }, + { + "IsDeleted": 0, + "RoleId": 1, + "ModuleId": 2, + "PermissionId": null, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 2 + }, + { + "IsDeleted": 0, + "RoleId": 1, + "ModuleId": 3, + "PermissionId": null, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 3 + }, + { + "IsDeleted": 0, + "RoleId": 1, + "ModuleId": 4, + "PermissionId": null, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 4 + }, + { + "IsDeleted": 0, + "RoleId": 2, + "ModuleId": 4, + "PermissionId": null, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 5 + }, + { + "IsDeleted": 0, + "RoleId": 3, + "ModuleId": 5, + "PermissionId": 30, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 6 + }, + { + "IsDeleted": 0, + "RoleId": 3, + "ModuleId": 6, + "PermissionId": 31, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 7 + }, + { + "IsDeleted": 0, + "RoleId": 3, + "ModuleId": 7, + "PermissionId": 9, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 8 + }, + { + "IsDeleted": 0, + "RoleId": 3, + "ModuleId": 26, + "PermissionId": 28, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 9 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 7, + "PermissionId": 3, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 10 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 7, + "PermissionId": 9, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 11 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 10, + "PermissionId": 13, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 12 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 12, + "PermissionId": 14, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 13 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 11, + "PermissionId": 15, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 14 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 2, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 15 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 22, + "PermissionId": 4, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 16 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 22, + "PermissionId": 16, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 17 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 25, + "PermissionId": 17, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 18 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 24, + "PermissionId": 18, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 19 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 23, + "PermissionId": 19, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 20 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 21 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 5, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 22 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 13, + "PermissionId": 6, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 23 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 13, + "PermissionId": 20, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 24 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 16, + "PermissionId": 21, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 25 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 15, + "PermissionId": 22, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 26 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 14, + "PermissionId": 23, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 27 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 17, + "PermissionId": 7, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 28 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 17, + "PermissionId": 24, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 29 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 20, + "PermissionId": 25, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 30 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 19, + "PermissionId": 26, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 31 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 18, + "PermissionId": 27, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 32 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 8, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 33 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 10, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 34 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 11, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 35 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 12, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 36 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 26, + "PermissionId": 28, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 37 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 5, + "PermissionId": 30, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 38 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 6, + "PermissionId": 31, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 39 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 27, + "PermissionId": 29, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 40 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 26, + "PermissionId": 32, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 41 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 42 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 8, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 43 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 10, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 44 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 11, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 45 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 12, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 46 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 27, + "PermissionId": 29, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 47 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 2, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 48 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 7, + "PermissionId": 3, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 49 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 22, + "PermissionId": 4, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 50 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 5, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 51 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 13, + "PermissionId": 6, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 52 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 17, + "PermissionId": 7, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 53 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 26, + "PermissionId": 28, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 54 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 28, + "PermissionId": 34, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 55 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 21, + "PermissionId": 33, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 56 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 7, + "PermissionId": 9, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 57 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 22, + "PermissionId": 16, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 58 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 13, + "PermissionId": 20, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 59 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 17, + "PermissionId": 24, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 60 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 26, + "PermissionId": 32, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 61 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 28, + "PermissionId": 35, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 62 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 21, + "PermissionId": 33, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 63 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 29, + "PermissionId": 36, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 64 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 34, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 65 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 0, + "PermissionId": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 66 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 0, + "PermissionId": 2, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 67 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 7, + "PermissionId": 4, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 68 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 0, + "PermissionId": 10, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 69 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 0, + "PermissionId": 12, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 70 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 0, + "PermissionId": 8, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 71 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 22, + "PermissionId": 16, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 72 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 37, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 73 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 38, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 74 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 39, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 75 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 40, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 76 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 40, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 77 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 37, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 78 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 38, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 79 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 39, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 80 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 41, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 81 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 41, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 82 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 42, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 83 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 42, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 84 + }, + { + "IsDeleted": 0, + "RoleId": 3, + "ModuleId": 0, + "PermissionId": 42, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 85 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 43, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 86 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 43, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 87 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 44, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 88 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 45, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 89 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 46, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 90 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 47, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 91 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 48, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 92 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 32, + "PermissionId": 64, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 128 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 65, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 129 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 33, + "PermissionId": 66, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 130 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 65, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 131 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 33, + "PermissionId": 66, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 132 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 67, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 133 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 67, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 134 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 68, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 135 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 0, + "PermissionId": 69, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 136 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 68, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 137 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 0, + "PermissionId": 69, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 138 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 29, + "PermissionId": 36, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 139 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 29, + "PermissionId": 36, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 140 + }, + { + "IsDeleted": 0, + "RoleId": 7, + "ModuleId": 27, + "PermissionId": 33, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 141 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 36, + "PermissionId": 72, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 144 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 0, + "PermissionId": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 145 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 0, + "PermissionId": 2, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 146 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 22, + "PermissionId": 3, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 147 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 7, + "PermissionId": 4, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 148 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 22, + "PermissionId": 16, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 149 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 7, + "PermissionId": 9, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 150 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 25, + "PermissionId": 17, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 151 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 10, + "PermissionId": 13, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 152 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 21, + "PermissionId": 33, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 153 + }, + { + "IsDeleted": 0, + "RoleId": 28, + "ModuleId": 29, + "PermissionId": 36, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 154 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 32, + "PermissionId": 73, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 155 + }, + { + "IsDeleted": 0, + "RoleId": 6, + "ModuleId": 32, + "PermissionId": 73, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 156 + }, + { + "IsDeleted": 0, + "RoleId": 4, + "ModuleId": 27, + "PermissionId": 74, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "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 new file mode 100644 index 00000000..5aaef106 --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TasksQz.tsv @@ -0,0 +1,21 @@ +[ + { + "Name": "博客管理", + "JobGroup": "博客测试组", + "TriggerType": 1, + "Cron": "0 */1 * * * ?", + "AssemblyName": "Blog.Core.Tasks", + "ClassName": "Job_Blogs_Quartz", + "Remark": "", + "RunTimes": 0, + "BeginTime": "\/Date(1546272000000+0800)\/", + "EndTime": "\/Date(8888888800000+0800)\/", + "IntervalSecond": 0, + "CycleRunTimes": 0, + "IsStart": true, + "JobParams": 1, + "IsDeleted": false, + "CreateTime": "\/Date(1546272000000+0800)\/", + "Id": 1 + } +] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/Topic.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Topic.tsv new file mode 100644 index 00000000..8da69479 --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/Topic.tsv @@ -0,0 +1,16 @@ +[ + { + "tLogo": "\/Upload\/20180626\/95445c8e288e47e3af7a180b8a4cc0c7.jpg", + "tName": "《罗马人的故事》", + "tDetail": "这是一个荡气回肠的故事", + "tAuthor": "Laozhang", + "tSectendDetail": null, + "tIsDelete": 0, + "tRead": 0, + "tCommend": 0, + "tGood": 0, + "tCreatetime": "\/Date(1546272000000+0800)\/", + "tUpdatetime": "\/Date(1546272000000+0800)\/", + "Id": 1 + } +] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/TopicDetail.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TopicDetail.tsv new file mode 100644 index 00000000..ff5f8d68 --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/TopicDetail.tsv @@ -0,0 +1,19 @@ +[ + { + "TopicId": 1, + "tdLogo": null, + "tdName": "第一章 罗马的诞生 第一节 传说的年代", + "tdContent": "

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

每个民族都有自己的神话传说。大概希望知道本民族的来源是个很自然的愿望吧。但这是一个难题,因为这几乎不可能用科学的方法来解释清楚。", + "tdDetail": "标题", + "tdSectendDetail": null, + "tdIsDelete": 0, + "tdRead": 8, + "tdCommend": 0, + "tdGood": 0, + "tdCreatetime": "\/Date(1546272000000+0800)\/", + "tdUpdatetime": "\/Date(1546272000000+0800)\/", + "tdTop": 0, + "tdAuthor": null, + "Id": 1 + } +] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/UserRole.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/UserRole.tsv new file mode 100644 index 00000000..9da3befe --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/UserRole.tsv @@ -0,0 +1,134 @@ +[ + { + "IsDeleted": 0, + "UserId": 4, + "RoleId": 1, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 1 + }, + { + "IsDeleted": 0, + "UserId": 3, + "RoleId": 2, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 2 + }, + { + "IsDeleted": 0, + "UserId": 7, + "RoleId": 4, + "CreateId": null, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 3 + }, + { + "IsDeleted": 0, + "UserId": 12, + "RoleId": 4, + "CreateId": 23, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 4 + }, + { + "IsDeleted": 0, + "UserId": 1, + "RoleId": 2, + "CreateId": 1, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 5 + }, + { + "IsDeleted": 0, + "UserId": 1, + "RoleId": 1, + "CreateId": 1, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 6 + }, + { + "IsDeleted": 0, + "UserId": 2, + "RoleId": 1, + "CreateId": 13, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 7 + }, + { + "IsDeleted": 0, + "UserId": 8, + "RoleId": 6, + "CreateId": 19, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 8 + }, + { + "IsDeleted": 0, + "UserId": 13, + "RoleId": 7, + "CreateId": 24, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": "\/Date(1546272000000+0800)\/", + "Id": 9 + }, + { + "IsDeleted": 0, + "UserId": 0, + "RoleId": 0, + "CreateId": 0, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": null, + "Id": 10 + }, + { + "IsDeleted": 0, + "UserId": 39, + "RoleId": 28, + "CreateId": 39, + "CreateBy": null, + "CreateTime": "\/Date(1546272000000+0800)\/", + "ModifyId": null, + "ModifyBy": null, + "ModifyTime": null, + "Id": 11 + } +] diff --git a/Blog.Core.Api/wwwroot/BlogCore.Data.json/sysUserInfo.tsv b/Blog.Core.Api/wwwroot/BlogCore.Data.json/sysUserInfo.tsv new file mode 100644 index 00000000..4eb742bd --- /dev/null +++ b/Blog.Core.Api/wwwroot/BlogCore.Data.json/sysUserInfo.tsv @@ -0,0 +1,704 @@ +[ + { + "Id": 1, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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/wwwroot/CorsPost.html b/Blog.Core.Api/wwwroot/CorsPost.html similarity index 91% rename from Blog.Core/wwwroot/CorsPost.html rename to Blog.Core.Api/wwwroot/CorsPost.html index f568366e..8ebbf01a 100644 --- a/Blog.Core/wwwroot/CorsPost.html +++ b/Blog.Core.Api/wwwroot/CorsPost.html @@ -15,13 +15,13 @@ $(document).ready(function () { $("#jsonp").click(function () { - $.getJSON("/api/Login/jsonp?callBack=?", function (data) { + $.getJSON("http://localhost:9291/api/Login/jsonp?callBack=?", function (data) { $("#data-jsonp").html("数据: " + data.value); }); }); $("#cors").click(function () { - $.get("/api/Login/Token", function (data, status) { + $.get("http://localhost:9291/api/Login/Token", function (data, status) { console.log(data); $("#status-cors").html("状态: " + status); $("#data-cors").html("数据: " + data? data.token:"失败"); @@ -43,7 +43,7 @@ }; $.ajax({ type: 'post', - url: '/api/Values', + url: 'http://localhost:9291/api/Values', contentType: 'application/json', data: JSON.stringify(postdata), success: function (data, status) { diff --git a/Blog.Core.Api/wwwroot/JMeterTest.png b/Blog.Core.Api/wwwroot/JMeterTest.png new file mode 100644 index 00000000..78f42c7d Binary files /dev/null and b/Blog.Core.Api/wwwroot/JMeterTest.png differ diff --git a/Blog.Core.Api/wwwroot/MVP_Logo_Horizontal_Preferred_Cyan300_CMYK_72ppi.png b/Blog.Core.Api/wwwroot/MVP_Logo_Horizontal_Preferred_Cyan300_CMYK_72ppi.png new file mode 100644 index 00000000..0315b2ef Binary files /dev/null and b/Blog.Core.Api/wwwroot/MVP_Logo_Horizontal_Preferred_Cyan300_CMYK_72ppi.png differ diff --git a/Blog.Core/wwwroot/NoInterAutofacIOC.rar b/Blog.Core.Api/wwwroot/NoInterAutofacIOC.rar similarity index 100% rename from Blog.Core/wwwroot/NoInterAutofacIOC.rar rename to Blog.Core.Api/wwwroot/NoInterAutofacIOC.rar 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/wwwroot/index.html b/Blog.Core.Api/wwwroot/index.html similarity index 89% rename from Blog.Core/wwwroot/index.html rename to Blog.Core.Api/wwwroot/index.html index 4d44e41e..2e1be63e 100644 --- a/Blog.Core/wwwroot/index.html +++ b/Blog.Core.Api/wwwroot/index.html @@ -18,7 +18,7 @@ "bRemark": "string" }; $.ajax({ - url: "http://localhost:58427/api/Values", + url: "http://localhost:9291/api/Values", type: "POST", contentType: "application/json; charset=utf-8", data: JSON.stringify(postdata), @@ -34,7 +34,7 @@

-
+
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/jquery-3.3.1.min.js b/Blog.Core.Api/wwwroot/js/jquery-3.3.1.min.js new file mode 100644 index 00000000..4d9b3a25 --- /dev/null +++ b/Blog.Core.Api/wwwroot/js/jquery-3.3.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w(" + + + + +
+
+
+
+ + +
欢迎使用!
+
使用真实用户账号登录,测试账号: 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/wwwroot/web.config b/Blog.Core.Api/wwwroot/web.config similarity index 100% rename from Blog.Core/wwwroot/web.config rename to Blog.Core.Api/wwwroot/web.config diff --git a/Blog.Core.Build.bat b/Blog.Core.Build.bat new file mode 100644 index 00000000..7d614aa4 --- /dev/null +++ b/Blog.Core.Build.bat @@ -0,0 +1,20 @@ + + +@echo off +for /f "tokens=5" %%i in ('netstat -aon ^| findstr ":9291"') do ( + set n=%%i +) +taskkill /f /pid %n% + + + + +dotnet build + +cd Blog.Core.Api + + + +dotnet run + +cmd \ No newline at end of file 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 new file mode 100644 index 00000000..5a2b0215 --- /dev/null +++ b/Blog.Core.Common/Attribute/UseTranAttribute.cs @@ -0,0 +1,17 @@ +using System; +using Blog.Core.Common.DB; + +namespace Blog.Core.Common +{ + /// + /// 这个Attribute就是使用时候的验证,把它添加到需要执行事务的方法中,即可完成事务的操作。 + /// + [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/Authorization/PermissionNames.cs b/Blog.Core.Common/Authorization/PermissionNames.cs deleted file mode 100644 index d7ede51a..00000000 --- a/Blog.Core.Common/Authorization/PermissionNames.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Blog.Core -{ - public static class PermissionNames - { - public const string Permission = "Permission"; - } -} diff --git a/Blog.Core.Common/Blog.Core.Common.csproj b/Blog.Core.Common/Blog.Core.Common.csproj index 9233d558..6e1624a3 100644 --- a/Blog.Core.Common/Blog.Core.Common.csproj +++ b/Blog.Core.Common/Blog.Core.Common.csproj @@ -1,13 +1,57 @@ - + - - netcoreapp2.2 - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 00000000..befbc8ae --- /dev/null +++ b/Blog.Core.Common/DB/AppSecretConfig.cs @@ -0,0 +1,47 @@ +using System.IO; + +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" }); + + + public static string Audience_Secret_String => InitAudience_Secret(); + + + private static string InitAudience_Secret() + { + var securityString = DifDBConnOfSecurity(Audience_Secret_File); + if (!string.IsNullOrEmpty(Audience_Secret_File)&& !string.IsNullOrEmpty(securityString)) + { + return securityString; + } + else + { + return Audience_Secret; + } + + } + + private static string DifDBConnOfSecurity(params string[] conn) + { + foreach (var item in conn) + { + try + { + if (File.Exists(item)) + { + return File.ReadAllText(item).Trim(); + } + } + catch (System.Exception) { } + } + + return ""; + } + + } + +} diff --git a/Blog.Core.Common/DB/BaseDBConfig.cs b/Blog.Core.Common/DB/BaseDBConfig.cs new file mode 100644 index 00000000..7eb74fb8 --- /dev/null +++ b/Blog.Core.Common/DB/BaseDBConfig.cs @@ -0,0 +1,155 @@ +using System; +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) + { + try + { + if (File.Exists(item)) + { + return File.ReadAllText(item).Trim(); + } + } + catch (System.Exception) + { + } + } + + return conn[conn.Length - 1]; + } + + + public static (List, List) MutiInitConn() + { + List listdatabase = AppSettings.app("DBS") + .Where(i => i.Enabled).ToList(); + var mainDbId = AppSettings.app(new string[] {"MainDB"}).ObjToString(); + var mainDbModel = listdatabase.Single(d => d.ConnId == mainDbId); + listdatabase.Remove(mainDbModel); + listdatabase.Insert(0, mainDbModel); + + foreach (var i in listdatabase) SpecialDbString(i); + return (listdatabase, mainDbModel.Slaves); + } + + /// + /// 定制Db字符串 + /// 目的是保证安全:优先从本地txt文件获取,若没有文件则从appsettings.json中获取 + /// + /// + /// + private static MutiDBOperate SpecialDbString(MutiDBOperate mutiDBOperate) + { + if (mutiDBOperate.DbType == DataBaseType.Sqlite) + { + 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); + } + else if (mutiDBOperate.DbType == DataBaseType.MySql) + { + 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); + } + + return mutiDBOperate; + } + } + + + public enum DataBaseType + { + MySql = 0, + SqlServer = 1, + Sqlite = 2, + Oracle = 3, + PostgreSQL = 4, + 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 new file mode 100644 index 00000000..3de863f3 --- /dev/null +++ b/Blog.Core.Common/DB/MainDb.cs @@ -0,0 +1,7 @@ +namespace Blog.Core.Common.DB +{ + public static class MainDb + { + 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 new file mode 100644 index 00000000..6c067773 --- /dev/null +++ b/Blog.Core.Common/Extensions/GenericTypeExtensions.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; + +namespace Blog.Core.Common.Extensions +{ + public static class GenericTypeExtensions + { + 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(); + } + + /// + /// 判断类型是否实现某个泛型 + /// + /// 类型 + /// 泛型类型 + /// 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 new file mode 100644 index 00000000..84dd9e19 --- /dev/null +++ b/Blog.Core.Common/GlobalVar/GlobalVars.cs @@ -0,0 +1,52 @@ +namespace Blog.Core +{ + /// + /// 权限变量配置 + /// + public static class Permissions + { + public const string Name = "Permission"; + + /// + /// 测试网关授权 + /// 可以使用Blog.Core项目中的test用户 + /// 账号:test + /// 密码:test + /// + public const string GWName = "GW"; + + /// + /// 当前项目是否启用IDS4权限方案 + /// true:表示启动IDS4 + /// false:表示使用JWT + public static bool IsUseIds4 = false; + + /// + /// 当前项目是否启用Authing权限方案 + /// true:表示启动 + /// false:表示使用JWT + public static bool IsUseAuthing = false; + } + + /// + /// 路由变量前缀配置 + /// + public static class RoutePrefix + { + /// + /// 前缀名 + /// 如果不需要,尽量留空,不要修改 + /// 除非一定要在所有的 api 前统一加上特定前缀 + /// 前缀在appsettings.json中配置 + /// + public static string Name = ""; + } + + /// + /// RedisMqKey + /// + public static class RedisMqKey + { + public const string Loging = "Loging"; + } +} diff --git a/Blog.Core.Common/Helper/Appsettings.cs b/Blog.Core.Common/Helper/Appsettings.cs index 776eaa17..83e3d7e7 100644 --- a/Blog.Core.Common/Helper/Appsettings.cs +++ b/Blog.Core.Common/Helper/Appsettings.cs @@ -2,45 +2,91 @@ using Microsoft.Extensions.Configuration.Json; using System; using System.Collections.Generic; -using System.Text; +using System.Linq; namespace Blog.Core.Common { /// /// appsettings.json操作类 /// - public class Appsettings + public class AppSettings { - static IConfiguration Configuration { get; set; } - static Appsettings() + public static IConfiguration Configuration { get; set; } + static string contentPath { get; set; } + + public AppSettings(string contentPath) { - //ReloadOnChange = true 当appsettings.json被修改时重新加载 + string Path = "appsettings.json"; + + //如果你把配置文件 是 根据环境变量来分开了,可以这样写 + //Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json"; + Configuration = new ConfigurationBuilder() - .Add(new JsonConfigurationSource { Path = "appsettings.json", ReloadOnChange = true })//请注意要把当前appsetting.json 文件->右键->属性->复制到输出目录->始终复制 - .Build(); + .SetBasePath(contentPath) + .Add(new JsonConfigurationSource + { + Path = Path, Optional = false, ReloadOnChange = true + }) //这样的话,可以直接读目录里的json文件,而不是 bin 文件夹下的,所以不用修改复制属性 + .Build(); } + + public AppSettings(IConfiguration configuration) + { + Configuration = configuration; + } + /// /// 封装要操作的字符 /// - /// + /// 节点配置 /// public static string app(params string[] sections) { try { - var val = string.Empty; - for (int i = 0; i < sections.Length; i++) + if (sections.Any()) { - val += sections[i] + ":"; + return Configuration[string.Join(":", sections)]; } + } + catch (Exception) + { + } + + return ""; + } - return Configuration[val.TrimEnd(':')]; + /// + /// 递归获取配置信息数组 + /// + /// + /// + /// + public static List app(params string[] sections) + { + List list = new List(); + // 引用 Microsoft.Extensions.Configuration.Binder 包 + Configuration.Bind(string.Join(":", sections), list); + return list; + } + + + /// + /// 根据路径 configuration["App:Name"]; + /// + /// + /// + public static string GetValue(string sectionsPath) + { + try + { + return Configuration[sectionsPath]; } catch (Exception) { - return ""; } + return ""; } } -} +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/Base32Helper.cs b/Blog.Core.Common/Helper/Base32Helper.cs new file mode 100644 index 00000000..68e592bb --- /dev/null +++ b/Blog.Core.Common/Helper/Base32Helper.cs @@ -0,0 +1,101 @@ +using System; +using System.Text; + +namespace DPE.Core.Common.Helper +{ + public sealed class Base32Helper + { + + // the valid chars for the encoding + private static string ValidChars = "QAZ2WSX3" + "EDC4RFV5" + "TGB6YHN7" + "UJM8K9LP"; + + /// + /// Converts an array of bytes to a Base32-k string. + /// + public static string ToString(byte[] bytes) + { + StringBuilder sb = new StringBuilder(); // holds the base32 chars + byte index; + int hi = 5; + int currentByte = 0; + + while (currentByte < bytes.Length) + { + // do we need to use the next byte? + if (hi > 8) + { + // get the last piece from the current byte, shift it to the right + // and increment the byte counter + index = (byte)(bytes[currentByte++] >> (hi - 5)); + if (currentByte != bytes.Length) + { + // if we are not at the end, get the first piece from + // the next byte, clear it and shift it to the left + index = (byte)(((byte)(bytes[currentByte] << (16 - hi)) >> 3) | index); + } + + hi -= 3; + } + else if (hi == 8) + { + index = (byte)(bytes[currentByte++] >> 3); + hi -= 3; + } + else + { + + // simply get the stuff from the current byte + index = (byte)((byte)(bytes[currentByte] << (8 - hi)) >> 3); + hi += 5; + } + + sb.Append(ValidChars[index]); + } + + return sb.ToString(); + } + + + /// + /// Converts a Base32-k string into an array of bytes. + /// + /// + /// Input string s contains invalid Base32-k characters. + /// + public static byte[] FromBase32String(string str) + { + int numBytes = str.Length * 5 / 8; + byte[] bytes = new Byte[numBytes]; + + // all UPPERCASE chars + str = str.ToUpper(); + + int bit_buffer; + int currentCharIndex; + int bits_in_buffer; + + if (str.Length < 3) + { + bytes[0] = (byte)(ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5); + return bytes; + } + + bit_buffer = (ValidChars.IndexOf(str[0]) | ValidChars.IndexOf(str[1]) << 5); + bits_in_buffer = 10; + currentCharIndex = 2; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)bit_buffer; + bit_buffer >>= 8; + bits_in_buffer -= 8; + while (bits_in_buffer < 8 && currentCharIndex < str.Length) + { + bit_buffer |= ValidChars.IndexOf(str[currentCharIndex++]) << bits_in_buffer; + bits_in_buffer += 5; + } + } + + return bytes; + } + } +} 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 new file mode 100644 index 00000000..59ac0dde --- /dev/null +++ b/Blog.Core.Common/Helper/CCBPayUtil.cs @@ -0,0 +1,527 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Web; + +namespace Blog.Core.Common +{ + /// + /// 建行支付助手(根据官方提供的dll反编译过来的) + /// + public class CCBPayUtil + { + // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250 + public string makeCCBParam(string param, string pubkey) + { + string text = this.dicSort(param); + text += this.MD5KEY; + string str = new MessageDigest_MD5().Md5_32(text); + param = param + "&SIGN=" + str; + if (pubkey.Length >= 30) + { + pubkey = pubkey.Substring(pubkey.Length - 30); + } + if (pubkey.Length >= 8) + { + pubkey = pubkey.Substring(0, 8); + } + string text2 = new DES_ENCRY_DECRY().doEncrypt(param, pubkey); + text2 = text2.Replace("+", ","); + return HttpUtility.UrlEncode(text2, Encoding.GetEncoding("ISO-8859-1")); + } + + // Token: 0x06000002 RID: 2 RVA: 0x00002104 File Offset: 0x00000304 + public bool verifyNotifySign(string src, string sign, string pubKey) + { + return new RSASign().verifySigature(src, sign, pubKey); + } + + // Token: 0x06000003 RID: 3 RVA: 0x00002124 File Offset: 0x00000324 + private string dicSort(string param) + { + return this.GetSignContent(this.strToMap(param)); + } + + // Token: 0x06000004 RID: 4 RVA: 0x00002144 File Offset: 0x00000344 + private IDictionary strToMap(string param) + { + IDictionary dictionary = new Dictionary(); + string[] array = param.Split(new char[] + { + '&' + }); + for (int i = 0; i < array.Length; i++) + { + if (!"".Equals(array[i])) + { + string[] array2 = array[i].Split(new char[] + { + '=' + }); + if (array2.Length == 1) + { + dictionary.Add(array2[0], ""); + } + else + { + dictionary.Add(array2[0], array2[1]); + } + } + } + return dictionary; + } + + // Token: 0x06000005 RID: 5 RVA: 0x000021F0 File Offset: 0x000003F0 + private string GetSignContent(IDictionary parameters) + { + IDictionary dictionary = new SortedDictionary(parameters); + IEnumerator> enumerator = dictionary.GetEnumerator(); + StringBuilder stringBuilder = new StringBuilder(""); + while (enumerator.MoveNext()) + { + KeyValuePair keyValuePair = enumerator.Current; + string key = keyValuePair.Key; + keyValuePair = enumerator.Current; + string value = keyValuePair.Value; + if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value)) + { + stringBuilder.Append(key).Append("=").Append(value).Append("&"); + } + } + return stringBuilder.ToString().Substring(0, stringBuilder.Length - 1); + } + + // Token: 0x04000001 RID: 1 + //private string VERSION = "1.0.0"; + + // Token: 0x04000002 RID: 2 + private string MD5KEY = "20120315201809041004"; + } + internal class RSASign + { + // Token: 0x06000007 RID: 7 RVA: 0x000022C4 File Offset: 0x000004C4 + protected internal bool verifySigature(string signContent, string sign, string pubKey) + { + byte[] inArray = this.hexStrToBytes(pubKey); + pubKey = Convert.ToBase64String(inArray); + string text = "-----BEGIN PUBLIC KEY-----\r\n"; + text += pubKey; + text += "-----END PUBLIC KEY-----\r\n\r\n"; + byte[] sign2 = this.hexStrToBytes(sign); + byte[] bytes = Encoding.GetEncoding(RSASign.DEFAULT_CHARSET).GetBytes(signContent); + return this.RSACheckContent(bytes, sign2, text, "MD5"); + } + + // Token: 0x06000008 RID: 8 RVA: 0x00002330 File Offset: 0x00000530 + private bool RSACheckContent(byte[] signContent, byte[] sign, string publicKeyPem, string signType) + { + bool result; + try + { + RSACryptoServiceProvider rsacryptoServiceProvider = new RSACryptoServiceProvider(); + rsacryptoServiceProvider.PersistKeyInCsp = false; + RSACryptoServiceProviderExtension.LoadPublicKeyPEM(rsacryptoServiceProvider, publicKeyPem); + bool flag = rsacryptoServiceProvider.VerifyData(signContent, signType, sign); + result = flag; + } + catch + { + result = false; + } + return result; + } + + // Token: 0x06000009 RID: 9 RVA: 0x0000237C File Offset: 0x0000057C + private byte[] hexStrToBytes(string s) + { + s = s.Replace(" ", ""); + if (s.Length % 2 != 0) + { + s += " "; + } + byte[] array = new byte[s.Length / 2]; + for (int i = 0; i < array.Length; i++) + { + array[i] = Convert.ToByte(s.Substring(i * 2, 2), 16); + } + return array; + } + + // Token: 0x04000003 RID: 3 + private static string DEFAULT_CHARSET = "GBK"; + } + public class DES_ENCRY_DECRY + { + protected internal string doEncrypt(string param, string pubkey) + { + this.tdesKey = ((pubkey.Length > 8) ? pubkey.Substring(0, 8) : pubkey); + byte[] bytes = this.DESEncrypt(this.UTF_16BE, param, this.ISO_8859_1, this.tdesKey); + return this.Base64Encode(bytes); + } + + // Token: 0x0600001A RID: 26 RVA: 0x00002684 File Offset: 0x00000884 + protected internal string doDecrypt(string param, string pubkey) + { + this.tdesKey = ((pubkey.Length > 8) ? pubkey.Substring(0, 8) : pubkey); + return this.DESDecrypt(this.UTF_16BE, param, this.ISO_8859_1, this.tdesKey); + } + + // Token: 0x0600001B RID: 27 RVA: 0x000026CC File Offset: 0x000008CC + private byte[] DESEncrypt(string dataCharset, string data, string keyCharset, string key) + { + byte[] result; + try + { + byte[] bytes = Encoding.GetEncoding(keyCharset).GetBytes(key); + byte[] rgbIV = bytes; + byte[] bytes2 = Encoding.GetEncoding(dataCharset).GetBytes(data); + var descryptoServiceProvider = DES.Create(); + descryptoServiceProvider.Mode = CipherMode.ECB; + descryptoServiceProvider.Padding = PaddingMode.PKCS7; + MemoryStream memoryStream = new MemoryStream(); + CryptoStream cryptoStream = new CryptoStream(memoryStream, descryptoServiceProvider.CreateEncryptor(bytes, rgbIV), CryptoStreamMode.Write); + cryptoStream.Write(bytes2, 0, bytes2.Length); + cryptoStream.FlushFinalBlock(); + result = memoryStream.ToArray(); + } + catch + { + result = null; + } + return result; + } + + // Token: 0x0600001C RID: 28 RVA: 0x00002764 File Offset: 0x00000964 + private string DESDecrypt(string dataCharset, string data, string keyCoding, string key) + { + string result; + try + { + byte[] bytes = Encoding.GetEncoding(keyCoding).GetBytes(key); + byte[] rgbIV = bytes; + byte[] array = this.Base64Decode(data); + var descryptoServiceProvider = DES.Create(); + descryptoServiceProvider.Mode = CipherMode.ECB; + descryptoServiceProvider.Padding = PaddingMode.PKCS7; + MemoryStream memoryStream = new MemoryStream(); + CryptoStream cryptoStream = new CryptoStream(memoryStream, descryptoServiceProvider.CreateDecryptor(bytes, rgbIV), CryptoStreamMode.Write); + cryptoStream.Write(array, 0, array.Length); + cryptoStream.FlushFinalBlock(); + result = Encoding.GetEncoding(dataCharset).GetString(memoryStream.ToArray()); + } + catch + { + result = null; + } + return result; + } + + // Token: 0x0600001D RID: 29 RVA: 0x00002800 File Offset: 0x00000A00 + private string Base64Encode(byte[] bytes) + { + string result = string.Empty; + try + { + result = Convert.ToBase64String(bytes); + } + catch + { + } + return result; + } + + // Token: 0x0600001E RID: 30 RVA: 0x0000283C File Offset: 0x00000A3C + private byte[] Base64Decode(string source) + { + byte[] result = null; + try + { + result = Convert.FromBase64String(source); + } + catch + { + } + return result; + } + + // Token: 0x04000031 RID: 49 + private string tdesKey = "12345678"; + + // Token: 0x04000032 RID: 50 + private string UTF_16BE = "utf-16BE"; + + // Token: 0x04000033 RID: 51 + private string ISO_8859_1 = "ISO-8859-1"; + } + internal class MessageDigest_MD5 + { + // Token: 0x06000020 RID: 32 RVA: 0x000028A0 File Offset: 0x00000AA0 + protected internal string Md5_32(string src) + { + var md = MD5.Create(); + byte[] bytes = Encoding.UTF8.GetBytes(src); + byte[] array = md.ComputeHash(bytes); + string text = ""; + for (int i = 0; i < array.Length; i++) + { + text += array[i].ToString("x2"); + } + return text; + } + } + internal class RSACryptoServiceProviderExtension + { + // Token: 0x0600000C RID: 12 RVA: 0x00002408 File Offset: 0x00000608 + private static void LoadPublicKeyDER(RSACryptoServiceProvider provider, byte[] DERData) + { + byte[] rsafromDER = RSACryptoServiceProviderExtension.GetRSAFromDER(DERData); + byte[] publicKeyBlobFromRSA = RSACryptoServiceProviderExtension.GetPublicKeyBlobFromRSA(rsafromDER); + provider.ImportCspBlob(publicKeyBlobFromRSA); + } + + // Token: 0x0600000D RID: 13 RVA: 0x0000242C File Offset: 0x0000062C + internal static void LoadPublicKeyPEM(RSACryptoServiceProvider provider, string sPEM) + { + byte[] derfromPEM = RSACryptoServiceProviderExtension.GetDERFromPEM(sPEM); + RSACryptoServiceProviderExtension.LoadPublicKeyDER(provider, derfromPEM); + } + + // Token: 0x0600000E RID: 14 RVA: 0x0000244C File Offset: 0x0000064C + private static byte[] GetPublicKeyBlobFromRSA(byte[] RSAData) + { + byte[] array = null; + uint num = 0U; + if (!RSACryptoServiceProviderExtension.CryptDecodeObject((RSACryptoServiceProviderExtension.CRYPT_ENCODING_FLAGS)65537U, new IntPtr(19), RSAData, (uint)RSAData.Length, RSACryptoServiceProviderExtension.CRYPT_DECODE_FLAGS.NONE, array, ref num)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + array = new byte[num]; + if (!RSACryptoServiceProviderExtension.CryptDecodeObject((RSACryptoServiceProviderExtension.CRYPT_ENCODING_FLAGS)65537U, new IntPtr(19), RSAData, (uint)RSAData.Length, RSACryptoServiceProviderExtension.CRYPT_DECODE_FLAGS.NONE, array, ref num)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + return array; + } + + // Token: 0x0600000F RID: 15 RVA: 0x000024C4 File Offset: 0x000006C4 + internal static byte[] GetRSAFromDER(byte[] DERData) + { + byte[] array = null; + byte[] array2 = null; + uint num = 0U; + IntPtr zero = IntPtr.Zero; + if (!RSACryptoServiceProviderExtension.CryptDecodeObject((RSACryptoServiceProviderExtension.CRYPT_ENCODING_FLAGS)65537U, new IntPtr(8), DERData, (uint)DERData.Length, RSACryptoServiceProviderExtension.CRYPT_DECODE_FLAGS.NONE, array, ref num)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + array = new byte[num]; + if (RSACryptoServiceProviderExtension.CryptDecodeObject((RSACryptoServiceProviderExtension.CRYPT_ENCODING_FLAGS)65537U, new IntPtr(8), DERData, (uint)DERData.Length, RSACryptoServiceProviderExtension.CRYPT_DECODE_FLAGS.NONE, array, ref num)) + { + GCHandle gchandle = GCHandle.Alloc(array, GCHandleType.Pinned); + try + { + RSACryptoServiceProviderExtension.CERT_PUBLIC_KEY_INFO cert_PUBLIC_KEY_INFO = (RSACryptoServiceProviderExtension.CERT_PUBLIC_KEY_INFO)Marshal.PtrToStructure(gchandle.AddrOfPinnedObject(), typeof(RSACryptoServiceProviderExtension.CERT_PUBLIC_KEY_INFO)); + array2 = new byte[cert_PUBLIC_KEY_INFO.PublicKey.cbData]; + Marshal.Copy(cert_PUBLIC_KEY_INFO.PublicKey.pbData, array2, 0, array2.Length); + } + finally + { + gchandle.Free(); + } + return array2; + } + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + // Token: 0x06000010 RID: 16 RVA: 0x000025C0 File Offset: 0x000007C0 + internal static byte[] GetDERFromPEM(string sPEM) + { + uint num = 0U; + uint num2; + uint num3; + if (!RSACryptoServiceProviderExtension.CryptStringToBinary(sPEM, (uint)sPEM.Length, RSACryptoServiceProviderExtension.CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, null, ref num, out num2, out num3)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + byte[] array = new byte[num]; + if (!RSACryptoServiceProviderExtension.CryptStringToBinary(sPEM, (uint)sPEM.Length, RSACryptoServiceProviderExtension.CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, array, ref num, out num2, out num3)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + return array; + } + + // Token: 0x06000011 RID: 17 + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptDestroyKey(IntPtr hKey); + + // Token: 0x06000012 RID: 18 + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptImportKey(IntPtr hProv, byte[] pbKeyData, uint dwDataLen, IntPtr hPubKey, uint dwFlags, ref IntPtr hKey); + + // Token: 0x06000013 RID: 19 + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptReleaseContext(IntPtr hProv, int dwFlags); + + // Token: 0x06000014 RID: 20 + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptAcquireContext(ref IntPtr hProv, string pszContainer, string pszProvider, RSACryptoServiceProviderExtension.CRYPT_PROVIDER_TYPE dwProvType, RSACryptoServiceProviderExtension.CRYPT_ACQUIRE_CONTEXT_FLAGS dwFlags); + + // Token: 0x06000015 RID: 21 + [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptStringToBinary(string sPEM, uint sPEMLength, RSACryptoServiceProviderExtension.CRYPT_STRING_FLAGS dwFlags, [Out] byte[] pbBinary, ref uint pcbBinary, out uint pdwSkip, out uint pdwFlags); + + // Token: 0x06000016 RID: 22 + [DllImport("crypt32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptDecodeObjectEx(RSACryptoServiceProviderExtension.CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, uint cbEncoded, RSACryptoServiceProviderExtension.CRYPT_DECODE_FLAGS dwFlags, IntPtr pDecodePara, ref byte[] pvStructInfo, ref uint pcbStructInfo); + + // Token: 0x06000017 RID: 23 + [DllImport("crypt32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CryptDecodeObject(RSACryptoServiceProviderExtension.CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, uint cbEncoded, RSACryptoServiceProviderExtension.CRYPT_DECODE_FLAGS flags, [In][Out] byte[] pvStructInfo, ref uint cbStructInfo); + + // Token: 0x02000005 RID: 5 + internal enum CRYPT_ACQUIRE_CONTEXT_FLAGS : uint + { + // Token: 0x04000005 RID: 5 + CRYPT_NEWKEYSET = 8U, + // Token: 0x04000006 RID: 6 + CRYPT_DELETEKEYSET = 16U, + // Token: 0x04000007 RID: 7 + CRYPT_MACHINE_KEYSET = 32U, + // Token: 0x04000008 RID: 8 + CRYPT_SILENT = 64U, + // Token: 0x04000009 RID: 9 + CRYPT_DEFAULT_CONTAINER_OPTIONAL = 128U, + // Token: 0x0400000A RID: 10 + CRYPT_VERIFYCONTEXT = 4026531840U + } + + // Token: 0x02000006 RID: 6 + internal enum CRYPT_PROVIDER_TYPE : uint + { + // Token: 0x0400000C RID: 12 + PROV_RSA_FULL = 1U + } + + // Token: 0x02000007 RID: 7 + internal enum CRYPT_DECODE_FLAGS : uint + { + // Token: 0x0400000E RID: 14 + NONE, + // Token: 0x0400000F RID: 15 + CRYPT_DECODE_ALLOC_FLAG = 32768U + } + + // Token: 0x02000008 RID: 8 + internal enum CRYPT_ENCODING_FLAGS : uint + { + // Token: 0x04000011 RID: 17 + PKCS_7_ASN_ENCODING = 65536U, + // Token: 0x04000012 RID: 18 + X509_ASN_ENCODING = 1U + } + + // Token: 0x02000009 RID: 9 + internal enum CRYPT_OUTPUT_TYPES + { + // Token: 0x04000014 RID: 20 + X509_PUBLIC_KEY_INFO = 8, + // Token: 0x04000015 RID: 21 + RSA_CSP_PUBLICKEYBLOB = 19, + // Token: 0x04000016 RID: 22 + PKCS_RSA_PRIVATE_KEY = 43, + // Token: 0x04000017 RID: 23 + PKCS_PRIVATE_KEY_INFO + } + + // Token: 0x0200000A RID: 10 + internal enum CRYPT_STRING_FLAGS : uint + { + // Token: 0x04000019 RID: 25 + CRYPT_STRING_BASE64HEADER, + // Token: 0x0400001A RID: 26 + CRYPT_STRING_BASE64, + // Token: 0x0400001B RID: 27 + CRYPT_STRING_BINARY, + // Token: 0x0400001C RID: 28 + CRYPT_STRING_BASE64REQUESTHEADER, + // Token: 0x0400001D RID: 29 + CRYPT_STRING_HEX, + // Token: 0x0400001E RID: 30 + CRYPT_STRING_HEXASCII, + // Token: 0x0400001F RID: 31 + CRYPT_STRING_BASE64_ANY, + // Token: 0x04000020 RID: 32 + CRYPT_STRING_ANY, + // Token: 0x04000021 RID: 33 + CRYPT_STRING_HEX_ANY, + // Token: 0x04000022 RID: 34 + CRYPT_STRING_BASE64X509CRLHEADER, + // Token: 0x04000023 RID: 35 + CRYPT_STRING_HEXADDR, + // Token: 0x04000024 RID: 36 + CRYPT_STRING_HEXASCIIADDR, + // Token: 0x04000025 RID: 37 + CRYPT_STRING_HEXRAW, + // Token: 0x04000026 RID: 38 + CRYPT_STRING_NOCRLF = 1073741824U, + // Token: 0x04000027 RID: 39 + CRYPT_STRING_NOCR = 2147483648U + } + + // Token: 0x0200000B RID: 11 + internal class CRYPT_OBJID_BLOB + { + // Token: 0x04000028 RID: 40 + internal uint cbData = default; + + // Token: 0x04000029 RID: 41 + internal IntPtr pbData = default; + } + + // Token: 0x0200000C RID: 12 + internal class CRYPT_ALGORITHM_IDENTIFIER + { + // Token: 0x0400002A RID: 42 + internal IntPtr pszObjId = default; + + // Token: 0x0400002B RID: 43 + internal RSACryptoServiceProviderExtension.CRYPT_OBJID_BLOB Parameters = default; + } + + // Token: 0x0200000D RID: 13 + private class CRYPT_BIT_BLOB + { + // Token: 0x0400002C RID: 44 + internal uint cbData = default; + + // Token: 0x0400002D RID: 45 + internal IntPtr pbData = default; + + // Token: 0x0400002E RID: 46 + internal uint cUnusedBits = default; + } + + // Token: 0x0200000E RID: 14 + private class CERT_PUBLIC_KEY_INFO + { + // Token: 0x0400002F RID: 47 + internal RSACryptoServiceProviderExtension.CRYPT_ALGORITHM_IDENTIFIER Algorithm = default; + + // Token: 0x04000030 RID: 48 + internal RSACryptoServiceProviderExtension.CRYPT_BIT_BLOB PublicKey = default; + } + } +} + diff --git a/Blog.Core.Common/Helper/Console/ConsoleHelper.cs b/Blog.Core.Common/Helper/Console/ConsoleHelper.cs new file mode 100644 index 00000000..036c4769 --- /dev/null +++ b/Blog.Core.Common/Helper/Console/ConsoleHelper.cs @@ -0,0 +1,53 @@ +using System; + +namespace Blog.Core.Common +{ + public static class ConsoleHelper + { + private static readonly object _objLock = new(); + + /// + /// 在控制台输出 + /// + /// 文本 + /// 前颜色 + public static void WriteColorLine(string str, ConsoleColor color) + { + lock (_objLock) + { + ConsoleColor currentForeColor = Console.ForegroundColor; + Console.ForegroundColor = color; + Console.WriteLine(str); + Console.ForegroundColor = currentForeColor; + } + } + + /// + /// 打印错误信息 + /// + /// 待打印的字符串 + /// 想要打印的颜色 + 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 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); + } +} 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/DateHelper.cs b/Blog.Core.Common/Helper/DateHelper.cs new file mode 100644 index 00000000..ea8ca492 --- /dev/null +++ b/Blog.Core.Common/Helper/DateHelper.cs @@ -0,0 +1,58 @@ +using System; + +namespace Blog.Core.Common.Helper +{ + public class DateHelper + { + public static DateTime StampToDateTime(string time) + { + time = time.Substring(0, 10); + double timestamp = Convert.ToInt64(time); + System.DateTime dateTime = new System.DateTime(1970, 1, 1, 0, 0, 0, 0); + dateTime = dateTime.AddSeconds(timestamp).ToLocalTime(); + return dateTime; + } + + public static string TimeSubTract(DateTime time1,DateTime time2) + { + TimeSpan subTract = time1.Subtract(time2); + return $"{subTract.Days} 天 {subTract.Hours} 时 {subTract.Minutes} 分 "; + } + /// + /// 时间戳转本地时间-时间戳精确到秒 + /// + public static DateTime ToLocalTimeDateBySeconds(long unix) + { + var dto = DateTimeOffset.FromUnixTimeSeconds(unix); + return dto.ToLocalTime().DateTime; + } + + /// + /// 时间转时间戳Unix-时间戳精确到秒 + /// + public static long ToUnixTimestampBySeconds(DateTime dt) + { + DateTimeOffset dto = new DateTimeOffset(dt); + return dto.ToUnixTimeSeconds(); + } + + + /// + /// 时间戳转本地时间-时间戳精确到毫秒 + /// + public static DateTime ToLocalTimeDateByMilliseconds(long unix) + { + var dto = DateTimeOffset.FromUnixTimeMilliseconds(unix); + return dto.ToLocalTime().DateTime; + } + + /// + /// 时间转时间戳Unix-时间戳精确到毫秒 + /// + public static long ToUnixTimestampByMilliseconds(DateTime dt) + { + DateTimeOffset dto = new DateTimeOffset(dt); + return dto.ToUnixTimeMilliseconds(); + } + } +} 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 new file mode 100644 index 00000000..01c65a7c --- /dev/null +++ b/Blog.Core.Common/Helper/FileHelper.cs @@ -0,0 +1,412 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace Blog.Core.Common.Helper +{ + public class FileHelper : IDisposable + { + + private bool _alreadyDispose = false; + + #region 构造函数 + public FileHelper() + { + // + // TODO: 在此处添加构造函数逻辑 + // + } + ~FileHelper() + { + Dispose(); ; + } + + protected virtual void Dispose(bool isDisposing) + { + if (_alreadyDispose) return; + _alreadyDispose = true; + } + #endregion + + #region IDisposable 成员 + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + #region 取得文件后缀名 + /**************************************** + * 函数名称:GetPostfixStr + * 功能说明:取得文件后缀名 + * 参 数:filename:文件名称 + * 调用示列: + * string filename = "aaa.aspx"; + * string s = EC.FileObj.GetPostfixStr(filename); + *****************************************/ + /// + /// 取后缀名 + /// + /// 文件名 + /// .gif|.html格式 + public static string GetPostfixStr(string filename) + { + int start = filename.LastIndexOf("."); + int length = filename.Length; + string postfix = filename.Substring(start, length - start); + return postfix; + } + #endregion + + #region 根据文件大小获取指定前缀的可用文件名 + /// + /// 根据文件大小获取指定前缀的可用文件名 + /// + /// 文件夹 + /// 文件前缀 + /// 文件大小(1m) + /// 文件后缀(.log) + /// 可用文件名 + 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(); + + if (selectFiles.Count > 0) + { + return selectFiles.FirstOrDefault().FullName; + } + + return Path.Combine(folderPath, $@"{prefix}_{DateTime.Now.DateToTimeStamp()}.log"); + } + public static string GetAvailableFileNameWithPrefixOrderSize(string _contentRoot, string prefix, int size = 1 * 1024 * 1024, string ext = ".log") + { + var folderPath = Path.Combine(_contentRoot, "Log"); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + 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(); + + if (selectFiles.Count > 0) + { + return selectFiles.FirstOrDefault().Name.Replace(".log", ""); + } + + return $@"{prefix}_{DateTime.Now.DateToTimeStamp()}"; + } + #endregion + + #region 写文件 + /**************************************** + * 函数名称:WriteFile + * 功能说明:写文件,会覆盖掉以前的内容 + * 参 数:Path:文件路径,Strings:文本内容 + * 调用示列: + * string Path = Server.MapPath("Default2.aspx"); + * string Strings = "这是我写的内容啊"; + * EC.FileObj.WriteFile(Path,Strings); + *****************************************/ + /// + /// 写文件 + /// + /// 文件路径 + /// 文件内容 + public static void WriteFile(string Path, string Strings) + { + if (!File.Exists(Path)) + { + FileStream f = File.Create(Path); + f.Close(); + } + StreamWriter f2 = new StreamWriter(Path, false, System.Text.Encoding.GetEncoding("gb2312")); + f2.Write(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(); + } + + /// + /// 写文件 + /// + /// 文件路径 + /// 文件内容 + /// 编码格式 + public static void WriteFile(string Path, string Strings, Encoding encode) + { + if (!File.Exists(Path)) + { + FileStream f = File.Create(Path); + f.Close(); + } + StreamWriter f2 = new StreamWriter(Path, false, encode); + f2.Write(Strings); + f2.Close(); + f2.Dispose(); + } + #endregion + + #region 读文件 + /**************************************** + * 函数名称:ReadFile + * 功能说明:读取文本内容 + * 参 数:Path:文件路径 + * 调用示列: + * string Path = Server.MapPath("Default2.aspx"); + * string s = EC.FileObj.ReadFile(Path); + *****************************************/ + /// + /// 读文件 + /// + /// 文件路径 + /// + public static string ReadFile(string Path) + { + string s = ""; + if (!File.Exists(Path)) + s = "不存在相应的目录"; + else + { + StreamReader f2 = new StreamReader(Path, System.Text.Encoding.GetEncoding("gb2312")); + s = f2.ReadToEnd(); + f2.Close(); + f2.Dispose(); + } + + return s; + } + + /// + /// 读文件 + /// + /// 文件路径 + /// 编码格式 + /// + public static string ReadFile(string Path, Encoding encode) + { + string s = ""; + if (!File.Exists(Path)) + s = "不存在相应的目录"; + else + { + StreamReader f2 = new StreamReader(Path, encode); + s = f2.ReadToEnd(); + f2.Close(); + f2.Dispose(); + } + + return s; + } + #endregion + + #region 追加文件 + /**************************************** + * 函数名称:FileAdd + * 功能说明:追加文件内容 + * 参 数:Path:文件路径,strings:内容 + * 调用示列: + * string Path = Server.MapPath("Default2.aspx"); + * string Strings = "新追加内容"; + * EC.FileObj.FileAdd(Path, Strings); + *****************************************/ + /// + /// 追加文件 + /// + /// 文件路径 + /// 内容 + public static void FileAdd(string Path, string strings) + { + StreamWriter sw = File.AppendText(Path); + sw.Write(strings); + sw.Flush(); + sw.Close(); + } + #endregion + + #region 拷贝文件 + /**************************************** + * 函数名称:FileCoppy + * 功能说明:拷贝文件 + * 参 数:OrignFile:原始文件,NewFile:新文件路径 + * 调用示列: + * string orignFile = Server.MapPath("Default2.aspx"); + * string NewFile = Server.MapPath("Default3.aspx"); + * EC.FileObj.FileCoppy(OrignFile, NewFile); + *****************************************/ + /// + /// 拷贝文件 + /// + /// 原始文件 + /// 新文件路径 + public static void FileCoppy(string orignFile, string NewFile) + { + File.Copy(orignFile, NewFile, true); + } + + #endregion + + #region 删除文件 + /**************************************** + * 函数名称:FileDel + * 功能说明:删除文件 + * 参 数:Path:文件路径 + * 调用示列: + * string Path = Server.MapPath("Default3.aspx"); + * EC.FileObj.FileDel(Path); + *****************************************/ + /// + /// 删除文件 + /// + /// 路径 + public static void FileDel(string Path) + { + File.Delete(Path); + } + #endregion + + #region 移动文件 + /**************************************** + * 函数名称:FileMove + * 功能说明:移动文件 + * 参 数:OrignFile:原始路径,NewFile:新文件路径 + * 调用示列: + * string orignFile = Server.MapPath("../说明.txt"); + * string NewFile = Server.MapPath("http://www.cnblogs.com/说明.txt"); + * EC.FileObj.FileMove(OrignFile, NewFile); + *****************************************/ + /// + /// 移动文件 + /// + /// 原始路径 + /// 新路径 + public static void FileMove(string orignFile, string NewFile) + { + File.Move(orignFile, NewFile); + } + #endregion + + #region 在当前目录下创建目录 + /**************************************** + * 函数名称:FolderCreate + * 功能说明:在当前目录下创建目录 + * 参 数:OrignFolder:当前目录,NewFloder:新目录 + * 调用示列: + * string orignFolder = Server.MapPath("test/"); + * string NewFloder = "new"; + * EC.FileObj.FolderCreate(OrignFolder, NewFloder); + *****************************************/ + /// + /// 在当前目录下创建目录 + /// + /// 当前目录 + /// 新目录 + public static void FolderCreate(string orignFolder, string NewFloder) + { + Directory.SetCurrentDirectory(orignFolder); + Directory.CreateDirectory(NewFloder); + } + #endregion + + #region 递归删除文件夹目录及文件 + /**************************************** + * 函数名称:DeleteFolder + * 功能说明:递归删除文件夹目录及文件 + * 参 数:dir:文件夹路径 + * 调用示列: + * string dir = Server.MapPath("test/"); + * EC.FileObj.DeleteFolder(dir); + *****************************************/ + /// + /// 递归删除文件夹目录及文件 + /// + /// + /// + public static void DeleteFolder(string dir) + { + if (Directory.Exists(dir)) //如果存在这个文件夹删除之 + { + foreach (string d in Directory.GetFileSystemEntries(dir)) + { + if (File.Exists(d)) + File.Delete(d); //直接删除其中的文件 + else + DeleteFolder(d); //递归删除子文件夹 + } + Directory.Delete(dir); //删除已空文件夹 + } + + } + #endregion + + #region 将指定文件夹下面的所有内容copy到目标文件夹下面 果目标文件夹为只读属性就会报错。 + /**************************************** + * 函数名称:CopyDir + * 功能说明:将指定文件夹下面的所有内容copy到目标文件夹下面 果目标文件夹为只读属性就会报错。 + * 参 数:srcPath:原始路径,aimPath:目标文件夹 + * 调用示列: + * string srcPath = Server.MapPath("test/"); + * string aimPath = Server.MapPath("test1/"); + * EC.FileObj.CopyDir(srcPath,aimPath); + *****************************************/ + /// + /// 指定文件夹下面的所有内容copy到目标文件夹下面 + /// + /// 原始路径 + /// 目标文件夹 + public static void CopyDir(string srcPath, string aimPath) + { + try + { + // 检查目标目录是否以目录分割字符结束如果不是则添加之 + if (aimPath[aimPath.Length - 1] != Path.DirectorySeparatorChar) + aimPath += Path.DirectorySeparatorChar; + // 判断目标目录是否存在如果不存在则新建之 + if (!Directory.Exists(aimPath)) + Directory.CreateDirectory(aimPath); + // 得到源目录的文件列表,该里面是包含文件以及目录路径的一个数组 + //如果你指向copy目标文件下面的文件而不包含目录请使用下面的方法 + //string[] fileList = Directory.GetFiles(srcPath); + string[] fileList = Directory.GetFileSystemEntries(srcPath); + //遍历所有的文件和目录 + foreach (string file in fileList) + { + //先当作目录处理如果存在这个目录就递归Copy该目录下面的文件 + + if (Directory.Exists(file)) + CopyDir(file, aimPath + Path.GetFileName(file)); + //否则直接Copy文件 + else + File.Copy(file, aimPath + Path.GetFileName(file), true); + } + + } + catch (Exception ee) + { + throw new Exception(ee.ToString()); + } + } + #endregion + } +} 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/HtmlHelper.cs b/Blog.Core.Common/Helper/HtmlHelper.cs index 6a61ec87..43f716fe 100644 --- a/Blog.Core.Common/Helper/HtmlHelper.cs +++ b/Blog.Core.Common/Helper/HtmlHelper.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Blog.Core.Common.Helper +namespace Blog.Core.Common.Helper { public static class HtmlHelper { diff --git a/Blog.Core.Common/Helper/HttpHelper.cs b/Blog.Core.Common/Helper/HttpHelper.cs new file mode 100644 index 00000000..82f9c32f --- /dev/null +++ b/Blog.Core.Common/Helper/HttpHelper.cs @@ -0,0 +1,58 @@ +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 readonly HttpClient Httpclient = new HttpClient(); + + public static async Task GetAsync(string serviceAddress) + { + try + { + 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; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + return null; + } + + public static async Task PostAsync(string serviceAddress, string requestJson = null) + { + try + { + string result = string.Empty; + Uri postUrl = new Uri(serviceAddress); + + using (HttpContent httpContent = new StringContent(requestJson)) + { + 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; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + return null; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/IpHelper.cs b/Blog.Core.Common/Helper/IpHelper.cs new file mode 100644 index 00000000..3f391e3b --- /dev/null +++ b/Blog.Core.Common/Helper/IpHelper.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace Blog.Core.Common.Helper +{ + public class IpHelper + { + /// + /// 获取当前IP地址 + /// + /// + /// + public 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/Blog.Core.Common/Helper/JsonConfigUtils.cs b/Blog.Core.Common/Helper/JsonConfigUtils.cs new file mode 100644 index 00000000..3986a698 --- /dev/null +++ b/Blog.Core.Common/Helper/JsonConfigUtils.cs @@ -0,0 +1,242 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; + +namespace Blog.Core.Common.Helper +{ + /// + /// Json 配置文件通用类 + /// + public static class JsonConfigUtils + { + #region 变量 + + /// + /// 锁 + /// + private static object __Lock__ = new object(); + + #endregion + + /// + /// 读取配置文件的信息 + /// + /// + /// 要读取json的名称 + /// 要读取的json节点名称 + /// + public static T GetAppSettings(IConfiguration config, string AppSettingsFileName, string key) where T : class, new() + { + lock (__Lock__) + { + if (config == null) + { + config = new ConfigurationBuilder() + .Add(new JsonConfigurationSource + { + Path = AppSettingsFileName, + Optional = false, + ReloadOnChange = true + }) + .Build(); + } + var appconfig = new ServiceCollection() + .AddOptions() + .Configure(config.GetSection(key)) + .BuildServiceProvider() + .GetService>() + .Value; + + return appconfig; + } + } + + + public static string GetJson(string jsonPath, string key) + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile(jsonPath).Build(); //json文件地址 + string s = config.GetSection(key).Value; //json某个对象 + return s; + } + } + + #region Nacos 配置清单 + public class JsonConfigSettings + { + // 从nacos 读取到的系统配置信息 + public static IConfiguration Configuration { get; set; } + + + /// + /// 配置文件名称常量 + /// + private static string AppSettingsFileName = $"appsettings{ GetAppSettingsConfigName() }json"; + + /// + /// 根据环境变量定向配置文件名称 + /// + /// + private static string GetAppSettingsConfigName() + { + if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != null + && Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "") + { + return $".{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}."; + } + else + { + return "."; + } + } + /// + /// 获取Nacos配置 + /// + public static List NacosServerAddresses + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").ServerAddresses; + } + } + + /// + /// 获取Nacos配置 + /// + public static int NacosDefaultTimeOut + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").DefaultTimeOut; + } + } + + /// + /// 获取Nacos配置 + /// + public static string NacosNamespace + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").Namespace; + } + } + /// + /// 获取Nacos配置 + /// + public static string NacosServiceName + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").ServiceName; + } + } + + /// + /// 获取Nacos配置 + /// + public static int ListenInterval + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").ListenInterval; + } + } + + /// + /// 获取Nacos配置 + /// + public static string NacosIp + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").Ip; + + } + } + /// + /// 获取Nacos配置 + /// + public static int NacosPort + { + get + { + return int.Parse(JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").Port); + } + } + /// + /// 获取Nacos配置 + /// + public static bool NacosRegisterEnabled + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").RegisterEnabled; + } + } + + /// + /// 获取Nacos配置 + /// + public static Dictionary NacosMetadata + { + get + { + return JsonConfigUtils.GetAppSettings(Configuration, AppSettingsFileName, "nacos").Metadata; + } + } + + #endregion + + #region Nacos配置 + + /// + /// Nacos配置实体 + /// + public class NacosConfigDTO + { + /// + /// 服务IP地址 + /// + public List ServerAddresses { get; set; } + /// + /// 默认超时时间 + /// + public int DefaultTimeOut { get; set; } + /// + /// 监听间隔 + /// + public int ListenInterval { get; set; } + /// + /// 服务命名空间 + /// + public string Namespace { get; set; } + /// + /// 服务名称 + /// + public string ServiceName { get; set; } + /// + /// IP地址 + /// + public string Ip { get; set; } + /// + /// 端口 + /// + public string Port { get; set; } + /// + /// 服务命名空间 + /// + public bool RegisterEnabled { get; set; } + /// + /// 其他配置 + /// + public Dictionary Metadata { get; set; } + } + + #endregion + + } + +} diff --git a/Blog.Core.Common/Helper/JsonHelper.cs b/Blog.Core.Common/Helper/JsonHelper.cs new file mode 100644 index 00000000..a615abf0 --- /dev/null +++ b/Blog.Core.Common/Helper/JsonHelper.cs @@ -0,0 +1,532 @@ +using System; +using System.Collections.Generic; + + +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格式数据 + /// + /// + /// 对象 + /// 字符格式的JSON数据 + public static string GetJSON(object obj) + { + string result = String.Empty; + try + { + System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = + new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(T)); + using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) + { + serializer.WriteObject(ms, obj); + result = System.Text.Encoding.UTF8.GetString(ms.ToArray()); + } + } + catch (Exception) + { + throw; + } + return result; + } + /// + /// 转换List的数据为JSON格式 + /// + /// + /// 列表值 + /// JSON格式数据 + public string JSON(List vals) + { + System.Text.StringBuilder st = new System.Text.StringBuilder(); + try + { + System.Runtime.Serialization.Json.DataContractJsonSerializer s = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(T)); + + foreach (T city in vals) + { + using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) + { + s.WriteObject(ms, city); + st.Append(System.Text.Encoding.UTF8.GetString(ms.ToArray())); + } + } + } + catch (Exception) + { + } + + return st.ToString(); + } + /// + /// JSON格式字符转换为T类型的对象 + /// + /// + /// + /// + public static T ParseFormByJson(string jsonStr) + { + T obj = Activator.CreateInstance(); + using (System.IO.MemoryStream ms = + new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonStr))) + { + System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = + new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(T)); + return (T)serializer.ReadObject(ms); + } + } + + public string JSON1(List vals) + { + System.Text.StringBuilder st = new System.Text.StringBuilder(); + try + { + System.Runtime.Serialization.Json.DataContractJsonSerializer s = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(SendData)); + + foreach (SendData city in vals) + { + using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) + { + s.WriteObject(ms, city); + st.Append(System.Text.Encoding.UTF8.GetString(ms.ToArray())); + } + } + } + catch (Exception) + { + } + + return st.ToString(); + } + + private static bool IsJsonStart(ref string json) + { + if (!string.IsNullOrEmpty(json)) + { + json = json.Trim('\r', '\n', ' '); + if (json.Length > 1) + { + char s = json[0]; + char e = json[json.Length - 1]; + return (s == '{' && e == '}') || (s == '[' && e == ']'); + } + } + return false; + } + public static bool IsJson(string json) + { + int errIndex; + return IsJson(json, out errIndex); + } + public static bool IsJson(string json, out int errIndex) + { + errIndex = 0; + if (IsJsonStart(ref json)) + { + CharState cs = new CharState(); + char c; + for (int i = 0; i < json.Length; i++) + { + c = json[i]; + if (SetCharState(c, ref cs) && cs.childrenStart)//设置关键符号状态。 + { + string item = json.Substring(i); + int err; + int length = GetValueLength(item, true, out err); + cs.childrenStart = false; + if (err > 0) + { + errIndex = i + err; + return false; + } + i = i + length - 1; + } + if (cs.isError) + { + errIndex = i; + return false; + } + } + + return !cs.arrayStart && !cs.jsonStart; + } + return false; + } + + /// + /// 获取值的长度(当Json值嵌套以"{"或"["开头时) + /// + private static int GetValueLength(string json, bool breakOnErr, out int errIndex) + { + errIndex = 0; + int len = 0; + if (!string.IsNullOrEmpty(json)) + { + CharState cs = new CharState(); + char c; + for (int i = 0; i < json.Length; i++) + { + c = json[i]; + if (!SetCharState(c, ref cs))//设置关键符号状态。 + { + if (!cs.jsonStart && !cs.arrayStart)//json结束,又不是数组,则退出。 + { + break; + } + } + else if (cs.childrenStart)//正常字符,值状态下。 + { + int length = GetValueLength(json.Substring(i), breakOnErr, out errIndex);//递归子值,返回一个长度。。。 + cs.childrenStart = false; + cs.valueStart = 0; + //cs.state = 0; + i = i + length - 1; + } + if (breakOnErr && cs.isError) + { + errIndex = i; + return i; + } + if (!cs.jsonStart && !cs.arrayStart)//记录当前结束位置。 + { + len = i + 1;//长度比索引+1 + break; + } + } + } + return len; + } + + /// + /// 设置字符状态(返回true则为关键词,返回false则当为普通字符处理) + /// + private static bool SetCharState(char c, ref CharState cs) + { + cs.CheckIsError(c); + switch (c) + { + case '{'://[{ "[{A}]":[{"[{B}]":3,"m":"C"}]}] + #region 大括号 + if (cs.keyStart <= 0 && cs.valueStart <= 0) + { + cs.keyStart = 0; + cs.valueStart = 0; + if (cs.jsonStart && cs.state == 1) + { + cs.childrenStart = true; + } + else + { + cs.state = 0; + } + cs.jsonStart = true;//开始。 + return true; + } + #endregion + break; + case '}': + #region 大括号结束 + if (cs.keyStart <= 0 && cs.valueStart < 2 && cs.jsonStart) + { + cs.jsonStart = false;//正常结束。 + cs.state = 0; + cs.keyStart = 0; + cs.valueStart = 0; + cs.setDicValue = true; + return true; + } + // cs.isError = !cs.jsonStart && cs.state == 0; + #endregion + break; + case '[': + #region 中括号开始 + if (!cs.jsonStart) + { + cs.arrayStart = true; + return true; + } + else if (cs.jsonStart && cs.state == 1) + { + cs.childrenStart = true; + return true; + } + #endregion + break; + case ']': + #region 中括号结束 + if (cs.arrayStart && !cs.jsonStart && cs.keyStart <= 2 && cs.valueStart <= 0)//[{},333]//这样结束。 + { + cs.keyStart = 0; + cs.valueStart = 0; + cs.arrayStart = false; + return true; + } + #endregion + break; + case '"': + case '\'': + #region 引号 + if (cs.jsonStart || cs.arrayStart) + { + if (cs.state == 0)//key阶段,有可能是数组["aa",{}] + { + if (cs.keyStart <= 0) + { + cs.keyStart = (c == '"' ? 3 : 2); + return true; + } + else if ((cs.keyStart == 2 && c == '\'') || (cs.keyStart == 3 && c == '"')) + { + if (!cs.escapeChar) + { + cs.keyStart = -1; + return true; + } + else + { + cs.escapeChar = false; + } + } + } + else if (cs.state == 1 && cs.jsonStart)//值阶段必须是Json开始了。 + { + if (cs.valueStart <= 0) + { + cs.valueStart = (c == '"' ? 3 : 2); + return true; + } + else if ((cs.valueStart == 2 && c == '\'') || (cs.valueStart == 3 && c == '"')) + { + if (!cs.escapeChar) + { + cs.valueStart = -1; + return true; + } + else + { + cs.escapeChar = false; + } + } + + } + } + #endregion + break; + case ':': + #region 冒号 + if (cs.jsonStart && cs.keyStart < 2 && cs.valueStart < 2 && cs.state == 0) + { + if (cs.keyStart == 1) + { + cs.keyStart = -1; + } + cs.state = 1; + return true; + } + // cs.isError = !cs.jsonStart || (cs.keyStart < 2 && cs.valueStart < 2 && cs.state == 1); + #endregion + break; + case ',': + #region 逗号 //["aa",{aa:12,}] + + if (cs.jsonStart) + { + if (cs.keyStart < 2 && cs.valueStart < 2 && cs.state == 1) + { + cs.state = 0; + cs.keyStart = 0; + cs.valueStart = 0; + //if (cs.valueStart == 1) + //{ + // cs.valueStart = 0; + //} + cs.setDicValue = true; + return true; + } + } + else if (cs.arrayStart && cs.keyStart <= 2) + { + cs.keyStart = 0; + //if (cs.keyStart == 1) + //{ + // cs.keyStart = -1; + //} + return true; + } + #endregion + break; + case ' ': + case '\r': + case '\n'://[ "a",\r\n{} ] + case '\0': + case '\t': + if (cs.keyStart <= 0 && cs.valueStart <= 0) //cs.jsonStart && + { + return true;//跳过空格。 + } + break; + default: //值开头。。 + if (c == '\\') //转义符号 + { + if (cs.escapeChar) + { + cs.escapeChar = false; + } + else + { + cs.escapeChar = true; + return true; + } + } + else + { + cs.escapeChar = false; + } + if (cs.jsonStart || cs.arrayStart) // Json 或数组开始了。 + { + if (cs.keyStart <= 0 && cs.state == 0) + { + cs.keyStart = 1;//无引号的 + } + else if (cs.valueStart <= 0 && cs.state == 1 && cs.jsonStart)//只有Json开始才有值。 + { + cs.valueStart = 1;//无引号的 + } + } + break; + } + return false; + } + } + /// + /// 字符状态 + /// + public class CharState + { + internal bool jsonStart = false;//以 "{"开始了... + internal bool setDicValue = false;// 可以设置字典值了。 + internal bool escapeChar = false;//以"\"转义符号开始了 + /// + /// 数组开始【仅第一开头才算】,值嵌套的以【childrenStart】来标识。 + /// + internal bool arrayStart = false;//以"[" 符号开始了 + internal bool childrenStart = false;//子级嵌套开始了。 + /// + /// 【0 初始状态,或 遇到“,”逗号】;【1 遇到“:”冒号】 + /// + internal int state = 0; + + /// + /// 【-1 取值结束】【0 未开始】【1 无引号开始】【2 单引号开始】【3 双引号开始】 + /// + internal int keyStart = 0; + /// + /// 【-1 取值结束】【0 未开始】【1 无引号开始】【2 单引号开始】【3 双引号开始】 + /// + internal int valueStart = 0; + internal bool isError = false;//是否语法错误。 + + internal void CheckIsError(char c)//只当成一级处理(因为GetLength会递归到每一个子项处理) + { + if (keyStart > 1 || valueStart > 1) + { + return; + } + //示例 ["aa",{"bbbb":123,"fff","ddd"}] + switch (c) + { + case '{'://[{ "[{A}]":[{"[{B}]":3,"m":"C"}]}] + isError = jsonStart && state == 0;//重复开始错误 同时不是值处理。 + break; + case '}': + isError = !jsonStart || (keyStart != 0 && state == 0);//重复结束错误 或者 提前结束{"aa"}。正常的有{} + break; + case '[': + isError = arrayStart && state == 0;//重复开始错误 + break; + case ']': + isError = !arrayStart || jsonStart;//重复开始错误 或者 Json 未结束 + break; + case '"': + case '\'': + isError = !(jsonStart || arrayStart); //json 或数组开始。 + if (!isError) + { + //重复开始 [""",{"" "}] + isError = (state == 0 && keyStart == -1) || (state == 1 && valueStart == -1); + } + if (!isError && arrayStart && !jsonStart && c == '\'')//['aa',{}] + { + isError = true; + } + break; + case ':': + isError = !jsonStart || state == 1;//重复出现。 + break; + case ',': + isError = !(jsonStart || arrayStart); //json 或数组开始。 + if (!isError) + { + if (jsonStart) + { + isError = state == 0 || (state == 1 && valueStart > 1);//重复出现。 + } + else if (arrayStart)//["aa,] [,] [{},{}] + { + isError = keyStart == 0 && !setDicValue; + } + } + break; + case ' ': + case '\r': + case '\n'://[ "a",\r\n{} ] + case '\0': + case '\t': + break; + default: //值开头。。 + isError = (!jsonStart && !arrayStart) || (state == 0 && keyStart == -1) || (valueStart == -1 && state == 1);// + break; + } + //if (isError) + //{ + + //} + } + } +} 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 new file mode 100644 index 00000000..2f46f0b5 --- /dev/null +++ b/Blog.Core.Common/Helper/MD5Hepler.cs @@ -0,0 +1,98 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Blog.Core.Common.Helper +{ + public class MD5Helper + { + /// + /// 16位MD5加密 + /// + /// + /// + public static string MD5Encrypt16(string password) + { + var md5 = MD5.Create(); + string t2 = BitConverter.ToString(md5.ComputeHash(Encoding.Default.GetBytes(password)), 4, 8); + t2 = t2.Replace("-", string.Empty); + return t2; + } + + /// + /// 32位MD5加密 + /// + /// + /// + public static string MD5Encrypt32(string password = "") + { + string pwd = string.Empty; + try + { + if (!string.IsNullOrEmpty(password) && !string.IsNullOrWhiteSpace(password)) + { + MD5 md5 = MD5.Create(); //实例化一个md5对像 + // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择  + byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(password)); + // 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得 + foreach (var item in s) + { + // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符 + pwd = string.Concat(pwd, item.ToString("X2")); + } + } + } + catch + { + throw new Exception($"错误的 password 字符串:【{password}】"); + } + return pwd; + } + + /// + /// 64位MD5加密 + /// + /// + /// + public static string MD5Encrypt64(string password) + { + // 实例化一个md5对像 + // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择  + MD5 md5 = MD5.Create(); + 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/RSAHelper.cs b/Blog.Core.Common/Helper/RSAHelper.cs new file mode 100644 index 00000000..4322c1ae --- /dev/null +++ b/Blog.Core.Common/Helper/RSAHelper.cs @@ -0,0 +1,390 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Blog.Core.Common.Helper +{ + /// + /// RSA加解密 使用OpenSSL的公钥加密/私钥解密 + /// 公私钥请使用openssl生成 + /// + public class RSAHelper + { + public readonly RSA _privateKeyRsaProvider; + public readonly RSA _publicKeyRsaProvider; + private readonly HashAlgorithmName _hashAlgorithmName; + private readonly Encoding _encoding; + + /// + /// 实例化RSAHelper + /// + /// 加密算法类型 RSA SHA1;RSA2 SHA256 密钥长度至少为2048 + /// 编码类型 + /// 私钥 + /// 公钥 + public RSAHelper(RSAType rsaType, Encoding encoding, string privateKey, string publicKey = null) + { + _encoding = encoding; + if (!string.IsNullOrEmpty(privateKey)) + { + _privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(privateKey); + } + + if (!string.IsNullOrEmpty(publicKey)) + { + _publicKeyRsaProvider = CreateRsaProviderFromPublicKey(publicKey); + } + + _hashAlgorithmName = rsaType == RSAType.RSA ? HashAlgorithmName.SHA1 : HashAlgorithmName.SHA256; + } + + #region 使用私钥签名 + + /// + /// 使用私钥签名 + /// + /// 原始数据 + /// + public string Sign(string data) + { + byte[] dataBytes = _encoding.GetBytes(data); + + var signatureBytes = _privateKeyRsaProvider.SignData(dataBytes, _hashAlgorithmName, RSASignaturePadding.Pkcs1); + + return Convert.ToBase64String(signatureBytes); + } + + #endregion + + #region 使用公钥验签 + + /// + /// 使用公钥验签 + /// + /// 原始数据 + /// 签名 + /// + public bool Verify(string data, string sign) + { + byte[] dataBytes = _encoding.GetBytes(data); + byte[] signBytes = Convert.FromBase64String(sign); + + var verify = _publicKeyRsaProvider.VerifyData(dataBytes, signBytes, _hashAlgorithmName, RSASignaturePadding.Pkcs1); + + return verify; + } + + #endregion + + #region 解密 + /// + /// 私钥解密(原) + /// + /// 解密字符串(base64) + /// + + //public string Decrypt(string cipherText) + //{ + // if (_privateKeyRsaProvider == null) + // { + // throw new Exception("_privateKeyRsaProvider is null"); + // } + // return _encoding.GetString(_privateKeyRsaProvider.Decrypt(Convert.FromBase64String(cipherText), RSAEncryptionPadding.Pkcs1)); + //} + /// + /// 私钥解密(支持大量数据) + /// + /// + /// + public string Decrypt(string cipherText) + { + if (_privateKeyRsaProvider == null) + { + throw new Exception("_privateKeyRsaProvider is null"); + } + var bufferSize = (_privateKeyRsaProvider.KeySize / 8); + byte[] buffer = new byte[bufferSize];//待解密块 + using (MemoryStream msInput = new MemoryStream(Convert.FromBase64String(cipherText))) + { + using (MemoryStream msOutput = new MemoryStream()) + { + int readLen; while ((readLen = msInput.Read(buffer, 0, bufferSize)) > 0) + { + byte[] dataToEnc = new byte[readLen]; + Array.Copy(buffer, 0, dataToEnc, 0, readLen); byte[] encData = _privateKeyRsaProvider.Decrypt(dataToEnc, RSAEncryptionPadding.Pkcs1); + msOutput.Write(encData, 0, encData.Length); + } + byte[] result = msOutput.ToArray(); + return _encoding.GetString(result); + } + } + } + + #endregion + + #region 加密 + + /// + /// 公钥加密(原) + /// + /// + /// + //public string Encrypt(string text) + //{ + // if (_publicKeyRsaProvider == null) + // { + // throw new Exception("_publicKeyRsaProvider is null"); + // } + // return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text), RSAEncryptionPadding.Pkcs1)); + //} + /// + /// 公钥加密(支持大量数据) + /// + /// + /// + public string Encrypt(string text) + { + if (_publicKeyRsaProvider == null) + { + throw new Exception("_publicKeyRsaProvider is null"); + } + var bufferSize = (_publicKeyRsaProvider.KeySize / 8 - 11); + byte[] buffer = new byte[bufferSize];//待加密块 + + using (MemoryStream msInput = new MemoryStream(_encoding.GetBytes(text))) + { + using (MemoryStream msOutput = new MemoryStream()) + { + int readLen; while ((readLen = msInput.Read(buffer, 0, bufferSize)) > 0) + { + byte[] dataToEnc = new byte[readLen]; + Array.Copy(buffer, 0, dataToEnc, 0, readLen); byte[] encData = _publicKeyRsaProvider.Encrypt(dataToEnc, RSAEncryptionPadding.Pkcs1); + msOutput.Write(encData, 0, encData.Length); + } + byte[] result = msOutput.ToArray(); + return Convert.ToBase64String(result); + } + } + } + + #endregion + + #region 使用私钥创建RSA实例 + /// + /// 使用私钥创建RSA实例 + /// + /// + /// + private RSA CreateRsaProviderFromPrivateKey(string privateKey) + { + var privateKeyBits = Convert.FromBase64String(privateKey); + + var rsa = RSA.Create(); + var rsaParameters = new RSAParameters(); + + using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits))) + { + byte bt = 0; + ushort twobytes = 0; + twobytes = binr.ReadUInt16(); + if (twobytes == 0x8130) + binr.ReadByte(); + else if (twobytes == 0x8230) + binr.ReadInt16(); + else + throw new Exception("Unexpected value read binr.ReadUInt16()"); + + twobytes = binr.ReadUInt16(); + if (twobytes != 0x0102) + throw new Exception("Unexpected version"); + + bt = binr.ReadByte(); + if (bt != 0x00) + throw new Exception("Unexpected value read binr.ReadByte()"); + + rsaParameters.Modulus = binr.ReadBytes(GetIntegerSize(binr)); + rsaParameters.Exponent = binr.ReadBytes(GetIntegerSize(binr)); + rsaParameters.D = binr.ReadBytes(GetIntegerSize(binr)); + rsaParameters.P = binr.ReadBytes(GetIntegerSize(binr)); + rsaParameters.Q = binr.ReadBytes(GetIntegerSize(binr)); + rsaParameters.DP = binr.ReadBytes(GetIntegerSize(binr)); + rsaParameters.DQ = binr.ReadBytes(GetIntegerSize(binr)); + rsaParameters.InverseQ = binr.ReadBytes(GetIntegerSize(binr)); + } + + rsa.ImportParameters(rsaParameters); + return rsa; + } + + #endregion + + #region 使用公钥创建RSA实例 + /// + /// 使用公钥创建RSA实例 + /// + /// + /// + public RSA CreateRsaProviderFromPublicKey(string publicKeyString) + { + // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" + byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; + byte[] seq = new byte[15]; + + var x509Key = Convert.FromBase64String(publicKeyString); + + // --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------ + using (MemoryStream mem = new MemoryStream(x509Key)) + { + using (BinaryReader binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading + { + byte bt = 0; + ushort twobytes = 0; + + twobytes = binr.ReadUInt16(); + if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) + binr.ReadByte(); //advance 1 byte + else if (twobytes == 0x8230) + binr.ReadInt16(); //advance 2 bytes + else + return null; + + seq = binr.ReadBytes(15); //read the Sequence OID + if (!CompareBytearrays(seq, seqOid)) //make sure Sequence for OID is correct + return null; + + twobytes = binr.ReadUInt16(); + if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81) + binr.ReadByte(); //advance 1 byte + else if (twobytes == 0x8203) + binr.ReadInt16(); //advance 2 bytes + else + return null; + + bt = binr.ReadByte(); + if (bt != 0x00) //expect null byte next + return null; + + twobytes = binr.ReadUInt16(); + if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) + binr.ReadByte(); //advance 1 byte + else if (twobytes == 0x8230) + binr.ReadInt16(); //advance 2 bytes + else + return null; + + twobytes = binr.ReadUInt16(); + byte lowbyte = 0x00; + byte highbyte = 0x00; + + if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81) + lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus + else if (twobytes == 0x8202) + { + highbyte = binr.ReadByte(); //advance 2 bytes + lowbyte = binr.ReadByte(); + } + else + return null; + byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order + int modsize = BitConverter.ToInt32(modint, 0); + + int firstbyte = binr.PeekChar(); + if (firstbyte == 0x00) + { //if first byte (highest order) of modulus is zero, don't include it + binr.ReadByte(); //skip this null byte + modsize -= 1; //reduce modulus buffer size by 1 + } + + byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes + + if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data + return null; + int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values) + byte[] exponent = binr.ReadBytes(expbytes); + + // ------- create RSACryptoServiceProvider instance and initialize with public key ----- + var rsa = RSA.Create(); + RSAParameters rsaKeyInfo = new RSAParameters + { + Modulus = modulus, + Exponent = exponent + }; + rsa.ImportParameters(rsaKeyInfo); + + return rsa; + } + + } + } + + #endregion + + #region 导入密钥算法 + + private int GetIntegerSize(BinaryReader binr) + { + byte bt = 0; + int count = 0; + bt = binr.ReadByte(); + if (bt != 0x02) + return 0; + bt = binr.ReadByte(); + + if (bt == 0x81) + count = binr.ReadByte(); + else + if (bt == 0x82) + { + var highbyte = binr.ReadByte(); + var lowbyte = binr.ReadByte(); + byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; + count = BitConverter.ToInt32(modint, 0); + } + else + { + count = bt; + } + + while (binr.ReadByte() == 0x00) + { + count -= 1; + } + binr.BaseStream.Seek(-1, SeekOrigin.Current); + return count; + } + + private bool CompareBytearrays(byte[] a, byte[] b) + { + if (a.Length != b.Length) + return false; + int i = 0; + foreach (byte c in a) + { + if (c != b[i]) + return false; + i++; + } + return true; + } + + #endregion + + } + + /// + /// RSA算法类型 + /// + public enum RSAType + { + /// + /// SHA1 + /// + RSA = 0, + /// + /// RSA2 密钥长度至少为2048 + /// SHA256 + /// + RSA2 + } +} diff --git a/Blog.Core.Common/Helper/RSAHelperExtend.cs b/Blog.Core.Common/Helper/RSAHelperExtend.cs new file mode 100644 index 00000000..87d6c2d4 --- /dev/null +++ b/Blog.Core.Common/Helper/RSAHelperExtend.cs @@ -0,0 +1,385 @@ +using Org.BouncyCastle.Asn1.Pkcs; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Xml; +namespace Blog.Core.Common.Helper +{ + /// + /// RSA加解密 使用OpenSSL的私钥加密/公钥解密 (一般不用) + /// 公私钥请使用openssl生成 + /// + public class RSAHelperExtend + { + /// + /// 生成公钥与私钥方法 + /// + /// + public static string[] CreateKey(KeyType keyType) + { + string[] sKeys = new String[2]; + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); + switch (keyType) + { + case KeyType.XML: + { + //私钥 + sKeys[0] = rsa.ToXmlString(true); + //公钥 + sKeys[1] = rsa.ToXmlString(false); + } + break; + case KeyType.PKS8: + { + //JAVA私钥 + sKeys[0] = RSAPrivateKeyDotNet2Java(sKeys[0]); + //JAVA公钥 + sKeys[1] = RSAPublicKeyDotNet2Java(sKeys[1]); + } + break; + default: + break; + } + return sKeys; + } + + /// + /// 密钥类型 + /// + public enum KeyType + { + /// + /// xml类型 + /// + XML, + + /// + /// pks8类型 + /// + PKS8 + } + + + /// + /// RSA私钥格式转换,.net->java + /// + /// .net生成的私钥 + /// + public static string RSAPrivateKeyDotNet2Java(string privateKey) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(privateKey); + BigInteger m = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText)); + BigInteger exp = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText)); + BigInteger d = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("D")[0].InnerText)); + BigInteger p = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("P")[0].InnerText)); + BigInteger q = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Q")[0].InnerText)); + BigInteger dp = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DP")[0].InnerText)); + BigInteger dq = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DQ")[0].InnerText)); + BigInteger qinv = new BigInteger(Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("InverseQ")[0].InnerText)); + RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(m, exp, d, p, q, dp, dq, qinv); + + PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam); + byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetEncoded(); + return Convert.ToBase64String(serializedPrivateBytes); + } + + /// + /// RSA公钥格式转换,.net->java + /// + /// .net生成的公钥 + /// + public static string RSAPublicKeyDotNet2Java(string publicKey) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(publicKey); + BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText)); + BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText)); + RsaKeyParameters pub = new RsaKeyParameters(false, m, p); + + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pub); + byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded(); + return Convert.ToBase64String(serializedPublicBytes); + } + + + /// + /// RSA私钥格式转换,java->.net + /// + /// java生成的RSA私钥 + /// + public static string RSAPrivateKeyJavaToDotNet(string privateKey) + { + RsaPrivateCrtKeyParameters privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey)); + + return string.Format("{0}{1}

{2}

{3}{4}{5}{6}{7}
", + Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()), + Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()), + Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()), + Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()), + Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()), + Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()), + Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()), + Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned())); + + } + + /// + /// RSA公钥格式转换,java->.net + /// + /// java生成的公钥 + /// + public static string RSAPublicKeyJavaToDotNet(string publicKey) + { + RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey)); + return string.Format("{0}{1}", + Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()), + Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned())); + } + + + /// + /// 最大加密长度 + /// + private static int MAX_ENCRYPT_BLOCK = 245; + + /// + /// 最大解密长度 + /// + private static int MAX_DECRYPT_BLOCK = 256; + + + /// + /// 用私钥给数据进行RSA加密 + /// + /// + /// + /// + public static string PrivateKeyEncrypt(string xmlPrivateKey, string strEncryptString) + { + + RSAHelper rs = new RSAHelper(RSAType.RSA, Encoding.UTF8, xmlPrivateKey); + var xml = rs._privateKeyRsaProvider.ToXmlString(true); + MAX_ENCRYPT_BLOCK = rs._privateKeyRsaProvider.KeySize / 8 - 11; + //加载私钥 + RSACryptoServiceProvider privateRsa = new RSACryptoServiceProvider(); + privateRsa.FromXmlString(xml); + + //转换密钥 + AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetKeyPair(privateRsa); + IBufferedCipher c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding"); //使用RSA/ECB/PKCS1Padding格式 + + c.Init(true, keyPair.Private);//第一个参数为true表示加密,为false表示解密;第二个参数表示密钥 + byte[] DataToEncrypt = Encoding.UTF8.GetBytes(strEncryptString);//获取字节 + + byte[] cache; + int time = 0;//次数 + int inputLen = DataToEncrypt.Length; + int offSet = 0; + using (MemoryStream outStream = new MemoryStream()) + { + while (inputLen - offSet > 0) + { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) + { + cache = c.DoFinal(DataToEncrypt, offSet, MAX_ENCRYPT_BLOCK); + } + else + { + cache = c.DoFinal(DataToEncrypt, offSet, inputLen - offSet); + } + //写入 + outStream.Write(cache, 0, cache.Length); + + time++; + offSet = time * MAX_ENCRYPT_BLOCK; + } + byte[] resData = outStream.ToArray(); + + string strBase64 = Convert.ToBase64String(resData); + return strBase64; + } + + } + + /// + /// 用公钥给数据进行RSA解密 + /// + /// 公钥(XML格式字符串) + /// 要解密数据 + /// 解密后的数据 + public static string PublicKeyDecrypt(string xmlPublicKey, string strDecryptString) + { + RSAHelper rs = new RSAHelper(RSAType.RSA, Encoding.UTF8, "", xmlPublicKey); + var xml = rs._publicKeyRsaProvider.ToXmlString(false); + MAX_DECRYPT_BLOCK = rs._publicKeyRsaProvider.KeySize / 8; + + //加载公钥 + RSACryptoServiceProvider publicRsa = new RSACryptoServiceProvider(); + publicRsa.FromXmlString(xml); + RSAParameters rp = publicRsa.ExportParameters(false); + + //转换密钥 + AsymmetricKeyParameter pbk = DotNetUtilities.GetRsaPublicKey(rp); + + IBufferedCipher c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding"); + //第一个参数为true表示加密,为false表示解密;第二个参数表示密钥 + c.Init(false, pbk); + + byte[] DataToDecrypt = Convert.FromBase64String(strDecryptString); + //byte[] outBytes = c.DoFinal(DataToDecrypt);//解密 + + //string strDec = Encoding.UTF8.GetString(outBytes); + + byte[] cache; + int time = 0;//次数 + int inputLen = DataToDecrypt.Length; + int offSet = 0; + using (MemoryStream outStream = new MemoryStream()) + { + while (inputLen - offSet > 0) + { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) + { + cache = c.DoFinal(DataToDecrypt, offSet, MAX_DECRYPT_BLOCK); + } + else + { + cache = c.DoFinal(DataToDecrypt, offSet, inputLen - offSet); + } + //写入 + outStream.Write(cache, 0, cache.Length); + + time++; + offSet = time * MAX_DECRYPT_BLOCK; + } + byte[] resData = outStream.ToArray(); + + string strDec = Encoding.UTF8.GetString(resData); + return strDec; + } + } + + /// + /// 私钥签名 + /// + /// 需签名的数据 + /// 私钥 + /// hash算法 + /// 签名后的值 + public static string Sign(string str, string privateKey, SignAlgType signAlgType) + { + //根据需要加签时的哈希算法转化成对应的hash字符节 + byte[] bt = Encoding.UTF8.GetBytes(str); + byte[] rgbHash = null; + switch (signAlgType) + { + case SignAlgType.SHA1: + { + var csp = SHA1.Create(); + rgbHash = csp.ComputeHash(bt); + } + break; + case SignAlgType.SHA256: + { + var csp = SHA256.Create(); + rgbHash = csp.ComputeHash(bt); + } + break; + case SignAlgType.MD5: + { + var csp = MD5.Create(); + rgbHash = csp.ComputeHash(bt); + } + break; + default: + break; + } + RSACryptoServiceProvider key = new RSACryptoServiceProvider(); + + RSAHelper rs = new RSAHelper(RSAType.RSA, Encoding.UTF8, privateKey); + var xml = rs._privateKeyRsaProvider.ToXmlString(true); + key.FromXmlString(xml); + RSAPKCS1SignatureFormatter formatter = new RSAPKCS1SignatureFormatter(key); + formatter.SetHashAlgorithm(signAlgType.ToString());//此处是你需要加签的hash算法,需要和上边你计算的hash值的算法一致,不然会报错。 + byte[] inArray = formatter.CreateSignature(rgbHash); + return Convert.ToBase64String(inArray); + } + + /// + /// 公钥验签 + /// + /// 待验证的字符串 + /// 加签之后的字符串 + /// 公钥 + /// hash算法 + /// 签名是否符合 + public static bool Verify(string str, string sign, string publicKey, SignAlgType signAlgType) + { + + byte[] bt = Encoding.UTF8.GetBytes(str); + byte[] rgbHash = null; + switch (signAlgType) + { + case SignAlgType.SHA1: + { + var csp = SHA1.Create(); + rgbHash = csp.ComputeHash(bt); + } + break; + case SignAlgType.SHA256: + { + var csp = SHA256.Create(); + rgbHash = csp.ComputeHash(bt); + } + break; + case SignAlgType.MD5: + { + var csp = MD5.Create(); + rgbHash = csp.ComputeHash(bt); + } + break; + default: + break; + } + RSACryptoServiceProvider key = new RSACryptoServiceProvider(); + RSAHelper rs = new RSAHelper(RSAType.RSA, Encoding.UTF8, "", publicKey); + var xml = rs._publicKeyRsaProvider.ToXmlString(false); + key.FromXmlString(xml); + RSAPKCS1SignatureDeformatter deformatter = new RSAPKCS1SignatureDeformatter(key); + deformatter.SetHashAlgorithm(signAlgType.ToString()); + byte[] rgbSignature = Convert.FromBase64String(sign); + if (deformatter.VerifySignature(rgbHash, rgbSignature)) + return true; + return false; + } + + /// + /// 签名算法类型 + /// + public enum SignAlgType + { + /// + /// sha1 + /// + SHA1, + /// + /// sha256 + /// + SHA256, + + /// + /// md5 + /// + MD5 + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Helper/RecursionHelper.cs b/Blog.Core.Common/Helper/RecursionHelper.cs index 2ab55643..8b9874db 100644 --- a/Blog.Core.Common/Helper/RecursionHelper.cs +++ b/Blog.Core.Common/Helper/RecursionHelper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace Blog.Core.Common.Helper { @@ -10,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(); @@ -30,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(); @@ -51,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) @@ -80,10 +102,9 @@ 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(); + var subItems = all.Where(ee => ee.GetType().GetProperty(parentIdName).GetValue(ee, null).ToString() == curItem.GetType().GetProperty(idName).GetValue(curItem, null).ToString()).ToList(); if (subItems.Count > 0) curItem.GetType().GetField(childrenName).SetValue(curItem, subItems); foreach (var subItem in subItems) @@ -91,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; } @@ -104,13 +160,27 @@ 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; + public bool IsButton { get; set; } = false; public string path { get; set; } + public string Func { get; set; } public string iconCls { get; set; } public NavigationBarMeta meta { get; set; } public List children { get; set; } @@ -120,6 +190,31 @@ public class NavigationBarMeta { public string title { get; set; } 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/SerializeHelper.cs b/Blog.Core.Common/Helper/SerializeHelper.cs index a50674e0..4af8af15 100644 --- a/Blog.Core.Common/Helper/SerializeHelper.cs +++ b/Blog.Core.Common/Helper/SerializeHelper.cs @@ -1,9 +1,5 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Common { 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/StringHelper.cs b/Blog.Core.Common/Helper/StringHelper.cs new file mode 100644 index 00000000..ab9c31ae --- /dev/null +++ b/Blog.Core.Common/Helper/StringHelper.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Blog.Core.Common.Helper +{ + public class StringHelper + { + /// + /// 根据分隔符返回前n条数据 + /// + /// 数据内容 + /// 分隔符 + /// 前n条 + /// 是否倒序(默认false) + /// + public static List GetTopDataBySeparator(string content, string separator, int top, bool isDesc = false) + { + if (string.IsNullOrEmpty(content)) + { + return new List() { }; + } + + if (string.IsNullOrEmpty(separator)) + { + throw new ArgumentException("message", nameof(separator)); + } + + var dataArray = content.Split(separator).Where(d => !string.IsNullOrEmpty(d)).ToArray(); + if (isDesc) + { + Array.Reverse(dataArray); + } + + if (top > 0) + { + dataArray = dataArray.Take(top).ToArray(); + } + + return dataArray.ToList(); + } + /// + /// 根据字段拼接get参数 + /// + /// + /// + public static string GetPars(Dictionary dic) + { + + StringBuilder sb = new StringBuilder(); + string urlPars = null; + bool isEnter = false; + foreach (var item in dic) + { + sb.Append($"{(isEnter ? "&" : "")}{item.Key}={item.Value}"); + isEnter = true; + } + urlPars = sb.ToString(); + return urlPars; + } + /// + /// 根据字段拼接get参数 + /// + /// + /// + public static string GetPars(Dictionary dic) + { + + StringBuilder sb = new StringBuilder(); + string urlPars = null; + bool isEnter = false; + foreach (var item in dic) + { + sb.Append($"{(isEnter ? "&" : "")}{item.Key}={item.Value}"); + isEnter = true; + } + urlPars = sb.ToString(); + return urlPars; + } + /// + /// 获取一个GUID + /// + /// 格式-默认为N + /// + public static string GetGUID(string format="N") { + return Guid.NewGuid().ToString(format); + } + /// + /// 根据GUID获取19位的唯一数字序列 + /// + /// + public static long GetGuidToLongID() + { + byte[] buffer = Guid.NewGuid().ToByteArray(); + return BitConverter.ToInt64(buffer, 0); + } + /// + /// 获取字符串最后X行 + /// + /// + /// + /// + public static string GetCusLine(string resourceStr, int length) { + string[] arrStr = resourceStr.Split("\r\n"); + return string.Join("", (from q in arrStr select q).Skip(arrStr.Length - length + 1).Take(length).ToArray()); + } + + } +} diff --git a/Blog.Core.Common/Helper/UnicodeHelper.cs b/Blog.Core.Common/Helper/UnicodeHelper.cs index f79c1405..56b00398 100644 --- a/Blog.Core.Common/Helper/UnicodeHelper.cs +++ b/Blog.Core.Common/Helper/UnicodeHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; @@ -36,13 +35,13 @@ public static string UnicodeToString(string unicode) return new Regex(@"\\u([0-9A-F]{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled).Replace( unicode, x => string.Empty + Convert.ToChar(Convert.ToUInt16(x.Result("$1"), 16))); - string resultStr = ""; - string[] strList = unicode.Split('u'); - for (int i = 1; i < strList.Length; i++) - { - resultStr += (char)int.Parse(strList[i], System.Globalization.NumberStyles.HexNumber); - } - return resultStr; + //string resultStr = ""; + //string[] strList = unicode.Split('u'); + //for (int i = 1; i < strList.Length; i++) + //{ + // resultStr += (char)int.Parse(strList[i], System.Globalization.NumberStyles.HexNumber); + //} + //return resultStr; } } } diff --git a/Blog.Core.Common/Helper/UrlHelper.cs b/Blog.Core.Common/Helper/UrlHelper.cs new file mode 100644 index 00000000..2814ec67 --- /dev/null +++ b/Blog.Core.Common/Helper/UrlHelper.cs @@ -0,0 +1,23 @@ +namespace Blog.Core.Common.Helper +{ + public class UrlHelper + { + /// + /// UrlEncode编码 + /// + /// url + /// + public static string UrlEncode(string url) { + return System.Web.HttpUtility.UrlEncode(url, System.Text.Encoding.UTF8); + } + /// + /// UrlEncode解码 + /// + /// 数据 + /// + public static string UrlDecode(string data) + { + return System.Web.HttpUtility.UrlDecode(data, System.Text.Encoding.UTF8); + } + } +} diff --git a/Blog.Core.Model/UtilConvert.cs b/Blog.Core.Common/Helper/UtilConvert.cs similarity index 52% rename from Blog.Core.Model/UtilConvert.cs rename to Blog.Core.Common/Helper/UtilConvert.cs index b3feb143..a75005ac 100644 --- a/Blog.Core.Model/UtilConvert.cs +++ b/Blog.Core.Common/Helper/UtilConvert.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using Newtonsoft.Json; + namespace Blog.Core { /// @@ -18,12 +19,14 @@ public static int ObjToInt(this object thisValue) { int reval = 0; if (thisValue == null) return 0; - if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval)) + if (thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval)) { return reval; } + return reval; } + /// /// /// @@ -37,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; + } + /// /// /// @@ -51,8 +68,10 @@ public static double ObjToMoney(this object thisValue) { return reval; } + return 0; } + /// /// /// @@ -66,8 +85,10 @@ public static double ObjToMoney(this object thisValue, double errorValue) { return reval; } + return errorValue; } + /// /// /// @@ -78,6 +99,18 @@ public static string ObjToString(this object thisValue) if (thisValue != null) return thisValue.ToString().Trim(); return ""; } + + /// + /// + /// + /// + /// + public static bool IsNotEmptyOrNull(this object thisValue) + { + return ObjToString(thisValue) != "" && ObjToString(thisValue) != "undefined" && + ObjToString(thisValue) != "null"; + } + /// /// /// @@ -89,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()); + /// /// /// @@ -101,8 +138,10 @@ public static Decimal ObjToDecimal(this object thisValue) { return reval; } + return 0; } + /// /// /// @@ -116,8 +155,10 @@ public static Decimal ObjToDecimal(this object thisValue, decimal errorValue) { return reval; } + return errorValue; } + /// /// /// @@ -130,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; } + /// /// /// @@ -145,8 +198,10 @@ public static DateTime ObjToDate(this object thisValue, DateTime errorValue) { return reval; } + return errorValue; } + /// /// /// @@ -159,7 +214,99 @@ public static bool ObjToBool(this object thisValue) { return reval; } + return reval; } + + + /// + /// 获取当前时间的时间戳 + /// + /// + /// + 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 new file mode 100644 index 00000000..f81d6817 --- /dev/null +++ b/Blog.Core.Common/Helper/XmlHelper.cs @@ -0,0 +1,63 @@ +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, string rootName = "root") + { + XmlSerializer xs; + var xsType = typeof(T); + hasTypes.TryGetValue(xsType, out xs); + if(xs == null) + { + xs = new XmlSerializer(typeof(T)); + hasTypes.TryAdd(xsType, xs); + } + using (TextWriter tw = new StringWriter()) + { + xs.Serialize(tw, obj); + return tw.ObjToString(); + } + } + + /// + /// Xml格式字符转换为T类型的对象 + /// + /// + /// + /// + public static T ParseFormByXml(string xml, string rootName = "root") + { + 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 new file mode 100644 index 00000000..dfa87949 --- /dev/null +++ b/Blog.Core.Common/HttpContextUser/AspNetUser.cs @@ -0,0 +1,140 @@ +using Blog.Core.Common.Swagger; +using Blog.Core.Model; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; + +namespace Blog.Core.Common.HttpContextUser +{ + public class AspNetUser : IUser + { + private readonly IHttpContextAccessor _accessor; + private readonly ILogger _logger; + + public AspNetUser(IHttpContextAccessor accessor, ILogger logger) + { + _accessor = accessor; + _logger = logger; + } + + public string Name => GetName(); + + private string GetName() + { + if (IsAuthenticated() && _accessor.HttpContext.User.Identity.Name.IsNotEmptyOrNull()) + { + return _accessor.HttpContext.User.Identity.Name; + } + else + { + if (!string.IsNullOrEmpty(GetToken())) + { + var getNameType = Permissions.IsUseIds4 + ? "name" + : "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"; + return GetUserInfoFromToken(getNameType).FirstOrDefault().ObjToString(); + } + } + + return ""; + } + + public long ID => GetClaimValueByType("jti").FirstOrDefault().ObjToLong(); + public long TenantId => GetClaimValueByType("TenantId").FirstOrDefault().ObjToLong(); + + public bool IsAuthenticated() + { + return _accessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false; + } + + + public string GetToken() + { + 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) + { + var jwtHandler = new JwtSecurityTokenHandler(); + var token = ""; + + token = GetToken(); + // token校验 + if (token.IsNotEmptyOrNull() && jwtHandler.CanReadToken(token)) + { + JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(token); + + return (from item in jwtToken.Claims + where item.Type == ClaimType + select item.Value).ToList(); + } + + return new List() { }; + } + + public MessageModel MessageModel { get; set; } + + public IEnumerable GetClaimsIdentity() + { + if (_accessor.HttpContext == null) return ArraySegment.Empty; + + if (!IsAuthenticated()) return GetClaimsIdentity(GetToken()); + + var claims = _accessor.HttpContext.User.Claims.ToList(); + var headers = _accessor.HttpContext.Request.Headers; + foreach (var header in headers) + { + 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(); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/HttpContextUser/IUser.cs b/Blog.Core.Common/HttpContextUser/IUser.cs new file mode 100644 index 00000000..aa6094b1 --- /dev/null +++ b/Blog.Core.Common/HttpContextUser/IUser.cs @@ -0,0 +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; } + 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/HttpRestSharp/HttpHelper.cs b/Blog.Core.Common/HttpRestSharp/HttpHelper.cs new file mode 100644 index 00000000..9e46808b --- /dev/null +++ b/Blog.Core.Common/HttpRestSharp/HttpHelper.cs @@ -0,0 +1,67 @@ +using RestSharp; +using System; +using System.Net; + +namespace Blog.Core.Common.HttpRestSharp +{ + /// + /// 基于 RestSharp 封装HttpHelper + /// + public static class HttpHelper + { + /// + /// Get 请求 + /// + /// 泛型 + /// 根域名:http://apk.neters.club/ + /// 接口:api/xx/yy + /// 参数:id=2&name=老张 + /// + public static T GetApi(string baseUrl, string url, string pragm = "") + { + var client = new RestSharpClient(baseUrl); + + var request = client.Execute(string.IsNullOrEmpty(pragm) + ? new RestRequest(url, Method.GET) + : new RestRequest($"{url}?{pragm}", Method.GET)); + + if (request.StatusCode != HttpStatusCode.OK) + { + return (T)Convert.ChangeType(request.ErrorMessage, typeof(T)); + } + + dynamic temp = Newtonsoft.Json.JsonConvert.DeserializeObject(request.Content, typeof(T)); + + //T result = (T)Convert.ChangeType(request.Content, typeof(T)); + + return (T)temp; + } + + /// + /// Post 请求 + /// + /// 泛型 + /// 完整的url + /// post body,可以匿名或者反序列化 + /// + public static T PostApi(string url, object body = null) + { + var client = new RestClient($"{url}"); + IRestRequest queest = new RestRequest(); + queest.Method = Method.POST; + queest.AddHeader("Accept", "application/json"); + queest.AddJsonBody(body); // 可以使用 JsonSerializer + var result = client.Execute(queest); + if (result.StatusCode != HttpStatusCode.OK) + { + return (T)Convert.ChangeType(result.ErrorMessage, typeof(T)); + } + + dynamic temp = Newtonsoft.Json.JsonConvert.DeserializeObject(result.Content, typeof(T)); + + //T result = (T)Convert.ChangeType(request.Content, typeof(T)); + + return (T)temp; + } + } +} diff --git a/Blog.Core.Common/HttpRestSharp/IRestSharp.cs b/Blog.Core.Common/HttpRestSharp/IRestSharp.cs new file mode 100644 index 00000000..f59703ac --- /dev/null +++ b/Blog.Core.Common/HttpRestSharp/IRestSharp.cs @@ -0,0 +1,43 @@ +using RestSharp; +using System; + +namespace Blog.Core.Common.HttpRestSharp +{ + /// + /// API请求执行者接口 + /// + public interface IRestSharp + { + /// + /// 同步执行方法 + /// + /// + /// + IRestResponse Execute(IRestRequest request); + + /// + /// 同步执行方法 + /// + /// 返回值 + /// 请求参数 + /// + T Execute(IRestRequest request) where T : new(); + + /// + /// 异步执行方法 + /// + /// 请求参数 + /// + /// + RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action callback); + + /// + /// 异步执行方法 + /// + /// + /// + /// + /// + RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action> callback) where T : new(); + } +} diff --git a/Blog.Core.Common/HttpRestSharp/RestSharpClient.cs b/Blog.Core.Common/HttpRestSharp/RestSharpClient.cs new file mode 100644 index 00000000..adbb8582 --- /dev/null +++ b/Blog.Core.Common/HttpRestSharp/RestSharpClient.cs @@ -0,0 +1,135 @@ +using RestSharp; +using RestSharp.Authenticators; +using System; + +namespace Blog.Core.Common.HttpRestSharp +{ + /// + /// Rest接口执行者 + /// + public class RestSharpClient : IRestSharp + { + /// + /// 请求客户端 + /// + private RestClient client; + + /// + /// 接口基地址 格式:http://apk.neters.club/ + /// + private string BaseUrl { get; set; } + + /// + /// 默认的时间参数格式 + /// + private string DefaultDateParameterFormat { get; set; } + + /// + /// 默认验证器 + /// + private IAuthenticator DefaultAuthenticator { get; set; } + + /// + /// 构造函数 + /// + /// + /// + public RestSharpClient(string baseUrl, IAuthenticator authenticator = null) + { + BaseUrl = baseUrl; + client = new RestClient(BaseUrl); + DefaultAuthenticator = authenticator; + + //默认时间显示格式 + DefaultDateParameterFormat = "yyyy-MM-dd HH:mm:ss"; + + //默认校验器 + if (DefaultAuthenticator != null) + { + client.Authenticator = DefaultAuthenticator; + } + } + + /// + /// 通用执行方法 + /// + /// 请求参数 + /// + /// 调用实例: + /// var client = new RestSharpClient("http://apk.neters.club/"); + /// var result = client.Execute(new RestRequest("v2/movie/in_theaters", Method.GET)); + /// var content = result.Content;//返回的字符串数据 + /// + /// + public IRestResponse Execute(IRestRequest request) + { + request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; + var response = client.Execute(request); + return response; + } + + /// + /// 同步执行方法 + /// + /// 返回的泛型对象 + /// 请求参数 + /// + /// var client = new RestSharpClient("http://apk.neters.club/"); + /// var result = client.Execute>(new RestRequest("v2/movie/in_theaters", Method.GET)); + /// + /// + public T Execute(IRestRequest request) where T : new() + { + request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; + var response = client.Execute(request); + return response.Data; + } + + /// + /// 异步执行方法 + /// + /// 请求参数 + /// 回调函数 + /// + /// 调用实例: + /// var client = new RestSharpClient("http://apk.neters.club/"); + /// client.ExecuteAsync>(new RestRequest("v2/movie/in_theaters", Method.GET), result => + /// { + /// var content = result.Content;//返回的字符串数据 + /// }); + /// + /// + [Obsolete] + public RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action callback) + { + request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; + return client.ExecuteAsync(request, callback); + } + + /// + /// 异步执行方法 + /// + /// 返回的泛型对象 + /// 请求参数 + /// 回调函数 + /// + /// 调用实例: + /// var client = new RestSharpClient("http://apk.neters.club/"); + /// client.ExecuteAsync>(new RestRequest("v2/movie/in_theaters", Method.GET), result => + /// { + /// if (result.StatusCode != HttpStatusCode.OK) + /// { + /// return; + /// } + /// var data = result.Data;//返回数据 + /// }); + /// + /// + [Obsolete] + public RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action> callback) where T : new() + { + request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; + return client.ExecuteAsync(request, callback); + } + } +} 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 new file mode 100644 index 00000000..63803d2f --- /dev/null +++ b/Blog.Core.Common/Hubs/ChatHub.cs @@ -0,0 +1,109 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.Common.LogHelper; +using Microsoft.AspNetCore.SignalR; + +namespace Blog.Core.Hubs +{ + public class ChatHub : Hub + { + /// + /// 向指定群组发送信息 + /// + /// 组名 + /// 信息内容 + /// + public async Task SendMessageToGroupAsync(string groupName, string message) + { + await Clients.Group(groupName).ReceiveMessage(message); + } + + /// + /// 加入指定组 + /// + /// 组名 + /// + public async Task AddToGroup(string groupName) + { + await Groups.AddToGroupAsync(Context.ConnectionId, groupName); + } + + /// + /// 退出指定组 + /// + /// 组名 + /// + public async Task RemoveFromGroup(string groupName) + { + await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); + } + + /// + /// 向指定成员发送信息 + /// + /// 成员名 + /// 信息内容 + /// + public async Task SendPrivateMessage(string user, string message) + { + await Clients.User(user).ReceiveMessage(message); + } + + /// + /// 当连接建立时运行 + /// + /// + public override async Task 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); + } + } + } + + /// + /// 当链接断开时运行 + /// + /// + /// + public override Task OnDisconnectedAsync(System.Exception ex) + { + //TODO.. + return base.OnDisconnectedAsync(ex); + } + + + public async Task SendMessage(string user, string message) + { + await Clients.All.ReceiveMessage(user, message); + } + + //定于一个通讯管道,用来管理我们和客户端的连接 + //1、客户端调用 GetLatestCount,就像订阅 + public async Task GetLatestCount(string random) + { + //2、服务端主动向客户端发送数据,名字千万不能错 + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + //TODO 主动发送错误消息 + await Clients.All.ReceiveUpdate("这是一个LOG"); + } + + + //3、客户端再通过 ReceiveUpdate ,来接收 + } + } +} \ No newline at end of file diff --git a/Blog.Core.Common/Hubs/IChatClient.cs b/Blog.Core.Common/Hubs/IChatClient.cs new file mode 100644 index 00000000..45ac1147 --- /dev/null +++ b/Blog.Core.Common/Hubs/IChatClient.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace Blog.Core.Hubs +{ + public interface IChatClient + { + /// + /// SignalR接收信息 + /// + /// 信息内容 + /// + Task ReceiveMessage(object message); + + /// + /// SignalR接收信息 + /// + /// 指定接收客户端 + /// 信息内容 + /// + Task ReceiveMessage(string user, string message); + + Task ReceiveUpdate(object message); + } +} 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/LogInfo.cs b/Blog.Core.Common/LogHelper/LogInfo.cs new file mode 100644 index 00000000..303c0ed9 --- /dev/null +++ b/Blog.Core.Common/LogHelper/LogInfo.cs @@ -0,0 +1,13 @@ +using System; + +namespace Blog.Core.Common.LogHelper +{ + public class LogInfo + { + public DateTime Datetime { get; set; } + public string Content { get; set; } + public string IP { get; set; } + public string LogColor { get; set; } + public int Import { get; set; } = 0; + } +} diff --git a/Blog.Core.Common/LogHelper/RequestInfo.cs b/Blog.Core.Common/LogHelper/RequestInfo.cs new file mode 100644 index 00000000..02160475 --- /dev/null +++ b/Blog.Core.Common/LogHelper/RequestInfo.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; + +namespace Blog.Core.Common.LogHelper +{ + public class ApiWeek + { + public string week { get; set; } + public string url { get; set; } + public int count { get; set; } + } + public class ApiDate + { + public string date { get; set; } + public int count { get; set; } + } + + public class ActiveUserVM + { + public string user { get; set; } + public int count { get; set; } + } + + public class RequestApiWeekView + { + public List columns { get; set; } + public string rows { get; set; } + } + public class AccessApiDateView + { + public string[] columns { get; set; } + public List rows { get; set; } + } + public class RequestInfo + { + public string Ip { get; set; } + public string Url { get; set; } + public string Datetime { get; set; } + public string Date { get; set; } + 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/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/Redis/IRedisCacheManager.cs b/Blog.Core.Common/Redis/IRedisCacheManager.cs deleted file mode 100644 index 80105f43..00000000 --- a/Blog.Core.Common/Redis/IRedisCacheManager.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.Common -{ - /// - /// 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.Common/Redis/RedisCacheManager.cs b/Blog.Core.Common/Redis/RedisCacheManager.cs deleted file mode 100644 index 993c5ad3..00000000 --- a/Blog.Core.Common/Redis/RedisCacheManager.cs +++ /dev/null @@ -1,148 +0,0 @@ -using StackExchange.Redis; -using System; - -namespace Blog.Core.Common -{ - 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", "RedisCaching", "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 - { - this.redisConnection = ConnectionMultiplexer.Connect(redisConnenctionString); - } - 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) - { - //序列化,将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.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.Common/Seed/FrameSeed.cs b/Blog.Core.Common/Seed/FrameSeed.cs new file mode 100644 index 00000000..9e1fd145 --- /dev/null +++ b/Blog.Core.Common/Seed/FrameSeed.cs @@ -0,0 +1,581 @@ +using SqlSugar; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Blog.Core.Common.Seed +{ + public class FrameSeed + { + + /// + /// 生成Controller层 + /// + /// sqlsugar实例 + /// 数据库链接ID + /// 数据库表名数组,默认空,生成所有表 + /// + /// + 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; + } + + /// + /// 生成Model层 + /// + /// sqlsugar实例 + /// 数据库链接ID + /// 数据库表名数组,默认空,生成所有表 + /// + /// + 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; + } + + /// + /// 生成IRepository层 + /// + /// sqlsugar实例 + /// 数据库链接ID + /// + /// 数据库表名数组,默认空,生成所有表 + /// + 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; + } + + + + /// + /// 生成 IService 层 + /// + /// sqlsugar实例 + /// 数据库链接ID + /// + /// 数据库表名数组,默认空,生成所有表 + /// + 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; + } + + + + /// + /// 生成 Repository 层 + /// + /// sqlsugar实例 + /// 数据库链接ID + /// + /// 数据库表名数组,默认空,生成所有表 + /// + 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; + } + + + + /// + /// 生成 Service 层 + /// + /// sqlsugar实例 + /// 数据库链接ID + /// + /// 数据库表名数组,默认空,生成所有表 + /// + 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; + } + + + #region 根据数据库表生产Controller层 + + /// + /// 功能描述:根据数据库表生产Controller层 + /// 作  者:Blog.Core + /// + /// + /// 数据库链接ID + /// 实体类存放路径 + /// 命名空间 + /// 生产指定的表 + /// 实现接口 + /// + /// 是否序列化 + private static void Create_Controller_ClassFileByDBTalbe( + SqlSugarScope sqlSugarClient, + string ConnId, + string strPath, + string strNameSpace, + string[] lstTableNames, + string strInterface, + bool isMuti = false, + bool blnSerializable = false) + { + var IDbFirst = sqlSugarClient.DbFirst; + if (lstTableNames != null && lstTableNames.Length > 0) + { + IDbFirst = IDbFirst.Where(lstTableNames); + } + var ls = IDbFirst.IsCreateDefaultValue().IsCreateAttribute() + + .SettingClassTemplate(p => p = +@"using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace " + strNameSpace + @" +{ + [Route(""api/[controller]/[action]"")] + [ApiController] + [Authorize(Permissions.Name)] + public class {ClassName}Controller : ControllerBase + { + /// + /// 服务器接口,因为是模板生成,所以首字母是大写的,自己可以重构下 + /// + private readonly I{ClassName}Services _{ClassName}Services; + + public {ClassName}Controller(I{ClassName}Services {ClassName}Services) + { + _{ClassName}Services = {ClassName}Services; + } + + [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 _{ClassName}Services.QueryPage(whereExpression, page, intPageSize) + }; + + } + + [HttpGet(""{id}"")] + public async Task> Get(string id) + { + return new MessageModel<{ClassName}>() + { + msg = ""获取成功"", + success = true, + response = await _{ClassName}Services.QueryById(id) + }; + } + + [HttpPost] + 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(); + data.msg = ""添加成功""; + } + + return data; + } + + [HttpPut] + public async Task> Put([FromBody] {ClassName} request) + { + var data = new MessageModel(); + data.success = await _{ClassName}Services.Update(request); + if (data.success) + { + data.msg = ""更新成功""; + data.response = request?.id.ObjToString(); + } + + return data; + } + + [HttpDelete] + public async Task> Delete(int id) + { + var data = new MessageModel(); + var model = await _{ClassName}Services.QueryById(id); + model.IsDeleted = true; + data.success = await _departmentServices.Update(model); + if (data.success) + { + data.msg = ""删除成功""; + data.response = model?.Id.ObjToString(); + } + + return data; + } + } +}") + + .ToClassStringList(strNameSpace); + + Dictionary newdic = new Dictionary(); + //循环处理 首字母小写 并插入新的 Dictionary + foreach (KeyValuePair item in ls) + { + string newkey = "_" + item.Key.First().ToString().ToLower() + item.Key.Substring(1); + string newvalue = item.Value.Replace("_" + item.Key, newkey); + newdic.Add(item.Key, newvalue); + } + CreateFilesByClassStringList(newdic, strPath, "{0}Controller"); + } + #endregion + + + #region 根据数据库表生产Model层 + + /// + /// 功能描述:根据数据库表生产Model层 + /// 作  者:Blog.Core + /// + /// + /// 数据库链接ID + /// 实体类存放路径 + /// 命名空间 + /// 生产指定的表 + /// 实现接口 + /// + /// 是否序列化 + private static void Create_Model_ClassFileByDBTalbe( + SqlSugarScope sqlSugarClient, + string ConnId, + string strPath, + string strNameSpace, + string[] lstTableNames, + string strInterface, + bool isMuti = false, + bool blnSerializable = false) + { + //多库文件分离 + if (isMuti) + { + strPath = strPath + @"\Models\" + ConnId; + strNameSpace = strNameSpace + "." + ConnId; + } + + var IDbFirst = sqlSugarClient.DbFirst; + if (lstTableNames != null && lstTableNames.Length > 0) + { + IDbFirst = IDbFirst.Where(lstTableNames); + } + var ls = IDbFirst.IsCreateDefaultValue().IsCreateAttribute() + + .SettingClassTemplate(p => p = +@"{using} + +namespace " + strNameSpace + @" +{ +{ClassDescription} + [SugarTable( ""{ClassName}"", """ + ConnId + @""")]" + (blnSerializable ? "\n [Serializable]" : "") + @" + public class {ClassName}" + (string.IsNullOrEmpty(strInterface) ? "" : (" : " + strInterface)) + @" + { + public {ClassName}() + { + } +{PropertyName} + } +}") + //.SettingPropertyDescriptionTemplate(p => p = string.Empty) + .SettingPropertyTemplate(p => p = +@"{SugarColumn} + public {PropertyType} {PropertyName} { get; set; }") + + //.SettingConstructorTemplate(p => p = " this._{PropertyName} ={DefaultValue};") + + .ToClassStringList(strNameSpace); + CreateFilesByClassStringList(ls, strPath, "{0}"); + } + #endregion + + + #region 根据数据库表生产IRepository层 + + /// + /// 功能描述:根据数据库表生产IRepository层 + /// 作  者:Blog.Core + /// + /// + /// 数据库链接ID + /// 实体类存放路径 + /// 命名空间 + /// 生产指定的表 + /// 实现接口 + /// + private static void Create_IRepository_ClassFileByDBTalbe( + SqlSugarScope sqlSugarClient, + string ConnId, + string strPath, + string strNameSpace, + string[] lstTableNames, + string strInterface, + bool isMuti = false + ) + { + //多库文件分离 + if (isMuti) + { + strPath = strPath + @"\" + ConnId; + strNameSpace = strNameSpace + "." + ConnId; + } + + var IDbFirst = sqlSugarClient.DbFirst; + if (lstTableNames != null && lstTableNames.Length > 0) + { + IDbFirst = IDbFirst.Where(lstTableNames); + } + var ls = IDbFirst.IsCreateDefaultValue().IsCreateAttribute() + + .SettingClassTemplate(p => p = +@"using Blog.Core.IRepository.Base; +using Blog.Core.Model.Models" + (isMuti ? "." + ConnId + "" : "") + @"; + +namespace " + strNameSpace + @" +{ + /// + /// I{ClassName}Repository + /// + public interface I{ClassName}Repository : IBaseRepository<{ClassName}>" + (string.IsNullOrEmpty(strInterface) ? "" : (" , " + strInterface)) + @" + { + } +}") + + .ToClassStringList(strNameSpace); + CreateFilesByClassStringList(ls, strPath, "I{0}Repository"); + } + #endregion + + + #region 根据数据库表生产IServices层 + + /// + /// 功能描述:根据数据库表生产IServices层 + /// 作  者:Blog.Core + /// + /// + /// 数据库链接ID + /// 实体类存放路径 + /// 命名空间 + /// 生产指定的表 + /// 实现接口 + /// + private static void Create_IServices_ClassFileByDBTalbe( + SqlSugarScope sqlSugarClient, + string ConnId, + string strPath, + string strNameSpace, + string[] lstTableNames, + string strInterface, + bool isMuti = false) + { + //多库文件分离 + if (isMuti) + { + strPath = strPath + @"\" + ConnId; + strNameSpace = strNameSpace + "." + ConnId; + } + + var IDbFirst = sqlSugarClient.DbFirst; + if (lstTableNames != null && lstTableNames.Length > 0) + { + IDbFirst = IDbFirst.Where(lstTableNames); + } + var ls = IDbFirst.IsCreateDefaultValue().IsCreateAttribute() + + .SettingClassTemplate(p => p = +@"using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models" + (isMuti ? "." + ConnId + "" : "") + @"; + +namespace " + strNameSpace + @" +{ + /// + /// I{ClassName}Services + /// + public interface I{ClassName}Services :IBaseServices<{ClassName}>" + (string.IsNullOrEmpty(strInterface) ? "" : (" , " + strInterface)) + @" + { + } +}") + + .ToClassStringList(strNameSpace); + CreateFilesByClassStringList(ls, strPath, "I{0}Services"); + } + #endregion + + + + #region 根据数据库表生产 Repository 层 + + /// + /// 功能描述:根据数据库表生产 Repository 层 + /// 作  者:Blog.Core + /// + /// + /// 数据库链接ID + /// 实体类存放路径 + /// 命名空间 + /// 生产指定的表 + /// 实现接口 + /// + private static void Create_Repository_ClassFileByDBTalbe( + SqlSugarScope sqlSugarClient, + string ConnId, + string strPath, + string strNameSpace, + string[] lstTableNames, + string strInterface, + bool isMuti = false) + { + //多库文件分离 + if (isMuti) + { + strPath = strPath + @"\" + ConnId; + strNameSpace = strNameSpace + "." + ConnId; + } + + var IDbFirst = sqlSugarClient.DbFirst; + if (lstTableNames != null && lstTableNames.Length > 0) + { + IDbFirst = IDbFirst.Where(lstTableNames); + } + var ls = IDbFirst.IsCreateDefaultValue().IsCreateAttribute() + + .SettingClassTemplate(p => p = +@"using Blog.Core.IRepository" + (isMuti ? "." + ConnId + "" : "") + @"; +using Blog.Core.IRepository.UnitOfWork; +using Blog.Core.Model.Models" + (isMuti ? "." + ConnId + "" : "") + @"; +using Blog.Core.Repository.Base; + +namespace " + strNameSpace + @" +{ + /// + /// {ClassName}Repository + /// + public class {ClassName}Repository : BaseRepository<{ClassName}>, I{ClassName}Repository" + (string.IsNullOrEmpty(strInterface) ? "" : (" , " + strInterface)) + @" + { + public {ClassName}Repository(IUnitOfWork unitOfWork) : base(unitOfWork) + { + } + } +}") + .ToClassStringList(strNameSpace); + + + CreateFilesByClassStringList(ls, strPath, "{0}Repository"); + } + #endregion + + + #region 根据数据库表生产 Services 层 + + /// + /// 功能描述:根据数据库表生产 Services 层 + /// 作  者:Blog.Core + /// + /// + /// 数据库链接ID + /// 实体类存放路径 + /// 命名空间 + /// 生产指定的表 + /// 实现接口 + /// + private static void Create_Services_ClassFileByDBTalbe( + SqlSugarScope sqlSugarClient, + string ConnId, + string strPath, + string strNameSpace, + string[] lstTableNames, + string strInterface, + bool isMuti = false) + { + //多库文件分离 + if (isMuti) + { + strPath = strPath + @"\" + ConnId; + strNameSpace = strNameSpace + "." + ConnId; + } + + var IDbFirst = sqlSugarClient.DbFirst; + if (lstTableNames != null && lstTableNames.Length > 0) + { + IDbFirst = IDbFirst.Where(lstTableNames); + } + var ls = IDbFirst.IsCreateDefaultValue().IsCreateAttribute() + + .SettingClassTemplate(p => p = +@" +using Blog.Core.IServices" + (isMuti ? "." + ConnId + "" : "") + @"; +using Blog.Core.Model.Models" + (isMuti ? "." + ConnId + "" : "") + @"; +using Blog.Core.Services.BASE; +using Blog.Core.IRepository.Base; + +namespace " + strNameSpace + @" +{ + public class {ClassName}Services : BaseServices<{ClassName}>, I{ClassName}Services" + (string.IsNullOrEmpty(strInterface) ? "" : (" , " + strInterface)) + @" + { + private readonly IBaseRepository<{ClassName}> _dal; + public {ClassName}Services(IBaseRepository<{ClassName}> dal) + { + this._dal = dal; + base.BaseDal = dal; + } + } +}") + .ToClassStringList(strNameSpace); + + CreateFilesByClassStringList(ls, strPath, "{0}Services"); + } + #endregion + + + #region 根据模板内容批量生成文件 + /// + /// 根据模板内容批量生成文件 + /// + /// 类文件字符串list + /// 生成路径 + /// 文件名格式模板 + private static void CreateFilesByClassStringList(Dictionary ls, string strPath, string fileNameTp) + { + + foreach (var item in ls) + { + var fileName = $"{string.Format(fileNameTp, item.Key)}.cs"; + var fileFullPath = Path.Combine(strPath, fileName); + if (!Directory.Exists(strPath)) + { + Directory.CreateDirectory(strPath); + } + File.WriteAllText(fileFullPath, item.Value, Encoding.UTF8); + } + } + #endregion + } +} diff --git a/Blog.Core.Common/Seed/IEntitySeedData.cs b/Blog.Core.Common/Seed/IEntitySeedData.cs new file mode 100644 index 00000000..3e2f4859 --- /dev/null +++ b/Blog.Core.Common/Seed/IEntitySeedData.cs @@ -0,0 +1,36 @@ +using SqlSugar; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Common.Seed; + +/// +/// 种子数据 接口 +/// +/// +public interface IEntitySeedData + where T : class, new() +{ + /// + /// 初始化种子数据
+ /// 只要表不存在数据,程序启动就会自动初始化 + ///
+ /// + IEnumerable InitSeedData(); + + /// + /// 种子数据
+ /// 存在不操作、不存在Insert
+ /// 适合系统内置数据,项目开发后续增加内置数据 + ///
+ /// + IEnumerable SeedData(); + + /// + /// 自定义操作
+ /// 以上满不足了,可以自己编写 + ///
+ /// + /// + Task CustomizeSeedData(ISqlSugarClient db); +} \ No newline at end of file diff --git a/Blog.Core.Common/Seed/MyContext.cs b/Blog.Core.Common/Seed/MyContext.cs new file mode 100644 index 00000000..1251f825 --- /dev/null +++ b/Blog.Core.Common/Seed/MyContext.cs @@ -0,0 +1,227 @@ +using Blog.Core.Common.DB; +using SqlSugar; +using System; + +namespace Blog.Core.Common.Seed +{ + public class MyContext + { + + private static MutiDBOperate connectObject => GetMainConnectionDb(); + private static string _connectionString = connectObject.Connection; + private static DbType _dbType = (DbType)connectObject.DbType; + public static string ConnId = connectObject.ConnId; + private SqlSugarScope _db; + + /// + /// 连接字符串 + /// Blog.Core + /// + public static MutiDBOperate GetMainConnectionDb() + { + var mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs.Find(x => x.ConnId == MainDb.CurrentDbConnId); + if (BaseDBConfig.MutiConnectionString.allDbs.Count > 0) + { + if (mainConnetctDb == null) + { + mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs[0]; + } + } + else + { + throw new Exception("请确保appsettigns.json中配置连接字符串,并设置Enabled为true;"); + } + + return mainConnetctDb; + } + /// + /// 连接字符串 + /// Blog.Core + /// + public static string ConnectionString + { + get { return _connectionString; } + set { _connectionString = value; } + } + /// + /// 数据库类型 + /// Blog.Core + /// + public static DbType DbType + { + get { return _dbType; } + set { _dbType = value; } + } + /// + /// 数据连接对象 + /// Blog.Core + /// + public SqlSugarScope Db + { + get { return _db; } + private set { _db = value; } + } + + /// + /// 功能描述:构造函数 + /// 作  者:Blog.Core + /// + public MyContext(ISqlSugarClient sqlSugarClient) + { + if (string.IsNullOrEmpty(_connectionString)) + throw new ArgumentNullException("数据库连接字符串为空"); + + _db = sqlSugarClient as SqlSugarScope; + + } + + + #region 实例方法 + /// + /// 功能描述:获取数据库处理对象 + /// 作  者:Blog.Core + /// + /// 返回值 + public SimpleClient GetEntityDB() where T : class, new() + { + return new SimpleClient(_db); + } + /// + /// 功能描述:获取数据库处理对象 + /// 作  者:Blog.Core + /// + /// db + /// 返回值 + //public SimpleClient GetEntityDB(SqlSugarClient db) where T : class, new() + //{ + // return new SimpleClient(db); + //} + + + + #endregion + + + #region 根据实体类生成数据库表 + /// + /// 功能描述:根据实体类生成数据库表 + /// 作  者:Blog.Core + /// + /// 是否备份表 + /// 指定的实体 + public void CreateTableByEntity(bool blnBackupTable, params T[] lstEntitys) where T : class, new() + { + Type[] lstTypes = null; + if (lstEntitys != null) + { + lstTypes = new Type[lstEntitys.Length]; + for (int i = 0; i < lstEntitys.Length; i++) + { + T t = lstEntitys[i]; + lstTypes[i] = typeof(T); + } + } + CreateTableByEntity(blnBackupTable, lstTypes); + } + + /// + /// 功能描述:根据实体类生成数据库表 + /// 作  者:Blog.Core + /// + /// 是否备份表 + /// 指定的实体 + public void CreateTableByEntity(bool blnBackupTable, params Type[] lstEntitys) + { + if (blnBackupTable) + { + _db.CodeFirst.BackupTable().InitTables(lstEntitys); //change entity backupTable + } + else + { + _db.CodeFirst.InitTables(lstEntitys); + } + } + #endregion + + + #region 静态方法 + + ///// + ///// 功能描述:获得一个DbContext + ///// 作  者:Blog.Core + ///// + ///// + //public static MyContext GetDbContext() + //{ + // return new MyContext(); + //} + + /// + /// 功能描述:设置初始化参数 + /// 作  者:Blog.Core + /// + /// 连接字符串 + /// 数据库类型 + public static void Init(string strConnectionString, DbType enmDbType = SqlSugar.DbType.SqlServer) + { + _connectionString = strConnectionString; + _dbType = enmDbType; + } + + /// + /// 功能描述:创建一个链接配置 + /// 作  者:Blog.Core + /// + /// 是否自动关闭连接 + /// 是否夸类事务 + /// ConnectionConfig + public static ConnectionConfig GetConnectionConfig(bool blnIsAutoCloseConnection = true, bool blnIsShardSameThread = false) + { + ConnectionConfig config = new ConnectionConfig() + { + ConnectionString = _connectionString, + DbType = _dbType, + IsAutoCloseConnection = blnIsAutoCloseConnection, + ConfigureExternalServices = new ConfigureExternalServices() + { + //DataInfoCacheService = new HttpRuntimeCache() + }, + //IsShardSameThread = blnIsShardSameThread + }; + return config; + } + + /// + /// 功能描述:获取一个自定义的DB + /// 作  者:Blog.Core + /// + /// config + /// 返回值 + public static SqlSugarScope GetCustomDB(ConnectionConfig config) + { + return new SqlSugarScope(config); + } + /// + /// 功能描述:获取一个自定义的数据库处理对象 + /// 作  者:Blog.Core + /// + /// sugarClient + /// 返回值 + public static SimpleClient GetCustomEntityDB(SqlSugarScope sugarClient) where T : class, new() + { + return new SimpleClient(sugarClient); + } + /// + /// 功能描述:获取一个自定义的数据库处理对象 + /// 作  者:Blog.Core + /// + /// config + /// 返回值 + public static SimpleClient GetCustomEntityDB(ConnectionConfig config) where T : class, new() + { + 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/Static/StaticPayInfo.cs b/Blog.Core.Common/Static/StaticPayInfo.cs new file mode 100644 index 00000000..a4242bfd --- /dev/null +++ b/Blog.Core.Common/Static/StaticPayInfo.cs @@ -0,0 +1,37 @@ + + +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 POSID = AppSettings.app(new string[] { "PayInfo", "POSID" }).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 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 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.EventBus/Blog.Core.EventBus.csproj b/Blog.Core.EventBus/Blog.Core.EventBus.csproj new file mode 100644 index 00000000..622856f5 --- /dev/null +++ b/Blog.Core.EventBus/Blog.Core.EventBus.csproj @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Blog.Core.EventBus/EventBusKafka/EventBusKafka.cs b/Blog.Core.EventBus/EventBusKafka/EventBusKafka.cs new file mode 100644 index 00000000..6b2dd0fb --- /dev/null +++ b/Blog.Core.EventBus/EventBusKafka/EventBusKafka.cs @@ -0,0 +1,118 @@ +using Blog.Core.Common.Extensions; +using Confluent.Kafka; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System; + +namespace Blog.Core.EventBus +{ + /// + /// 基于Kafka的事件总线 + /// + public class EventBusKafka : IEventBus + { + private readonly ILogger _logger; + private readonly IEventBusSubscriptionsManager _subsManager; + private readonly IKafkaConnectionPool _connectionPool; + private readonly KafkaOptions _options; + public EventBusKafka(ILogger logger, + IEventBusSubscriptionsManager subsManager, + IKafkaConnectionPool connectionPool, + IOptions options) + { + _logger = logger; + _subsManager = subsManager; + _connectionPool = connectionPool; + _options = options.Value; + } + /// + /// 发布 + /// + public void Publish(IntegrationEvent @event) + { + var producer = _connectionPool.Producer(); + try + { + var eventName = @event.GetType().Name; + var body = Protobuf.Serialize(JsonConvert.SerializeObject(@event)); + DeliveryResult result = producer.ProduceAsync(_options.Topic, new Message + { + Key = eventName, + Value = body + }).ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + _logger.LogWarning($"Could not publish event: {@event.Id.ToString("N")} ({ex.Message}); Message:{ JsonConvert.SerializeObject(@event)}"); + } + finally + { + //放入连接池中 + _connectionPool.Return(producer); + } + } + + /// + /// 订阅 + /// 动态 + /// + /// 事件处理器 + /// 事件名 + public void SubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler + { + _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + + _subsManager.AddDynamicSubscription(eventName); + } + + /// + /// 订阅 + /// + /// 约束:事件模型 + /// 约束:事件处理器<事件模型> + public void Subscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = _subsManager.GetEventKey(); + + _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + + _subsManager.AddSubscription(); + } + + /// + /// 取消订阅 + /// + /// + /// + public void Unsubscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = _subsManager.GetEventKey(); + + _logger.LogInformation("Unsubscribing from event {EventName}", eventName); + + _subsManager.RemoveSubscription(); + } + + public void UnsubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler + { + _subsManager.RemoveDynamicSubscription(eventName); + } + + public void Dispose() + { + if (_connectionPool != null) + { + _connectionPool.Dispose(); + } + _subsManager.Clear(); + } + + } +} diff --git a/Blog.Core.EventBus/EventBusKafka/IKafkaConnectionPool.cs b/Blog.Core.EventBus/EventBusKafka/IKafkaConnectionPool.cs new file mode 100644 index 00000000..0968e0e3 --- /dev/null +++ b/Blog.Core.EventBus/EventBusKafka/IKafkaConnectionPool.cs @@ -0,0 +1,25 @@ +using Confluent.Kafka; +using System; + + +namespace Blog.Core.EventBus +{ + /// + /// Kafka连接池 + /// + public interface IKafkaConnectionPool:IDisposable + { + /// + /// 取对象 + /// + /// + IProducer Producer(); + + /// + /// 将对象放入连接池 + /// + /// + /// + bool Return(IProducer producer); + } +} diff --git a/Blog.Core.EventBus/EventBusKafka/KafkaConnectionPool.cs b/Blog.Core.EventBus/EventBusKafka/KafkaConnectionPool.cs new file mode 100644 index 00000000..addd1f6b --- /dev/null +++ b/Blog.Core.EventBus/EventBusKafka/KafkaConnectionPool.cs @@ -0,0 +1,79 @@ +using Confluent.Kafka; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Collections.Concurrent; +using System.Threading; + + +namespace Blog.Core.EventBus +{ + /// + /// Kafka producer 连接池管理 + /// 可以使用微软官方的对象池进行构造ObjectPool + /// + public class KafkaConnectionPool : IKafkaConnectionPool + { + private readonly KafkaOptions _options; + private ConcurrentQueue> _producerPool = new(); + private int _currentCount; + private int _maxSize; + public KafkaConnectionPool(IOptions options) + { + _options = options.Value; + _maxSize = _options.ConnectionPoolSize; + } + + /// + /// 取对象 + /// + /// + public IProducer Producer() + { + if (_producerPool.TryDequeue(out var producer)) + { + Interlocked.Decrement(ref _currentCount); + return producer; + } + + var config = new ProducerConfig() + { + BootstrapServers = _options.Servers, + QueueBufferingMaxMessages = 10, + MessageTimeoutMs = 5000, + RequestTimeoutMs = 3000 + }; + + producer = new ProducerBuilder(config) + .Build(); + return producer; + } + /// + /// 将对象放入连接池 + /// + /// + /// + public bool Return(IProducer producer) + { + if (Interlocked.Increment(ref _currentCount) <= _maxSize) + { + _producerPool.Enqueue(producer); + return true; + } + + producer.Dispose(); + Interlocked.Decrement(ref _currentCount); + + return false; + } + public void Dispose() + { + _maxSize = 0; + _currentCount = 0; + while (_producerPool.TryDequeue(out var context)) + { + context?.Dispose(); + } + } + + } +} diff --git a/Blog.Core.EventBus/EventBusKafka/KafkaConsumerHostService.cs b/Blog.Core.EventBus/EventBusKafka/KafkaConsumerHostService.cs new file mode 100644 index 00000000..27fcd000 --- /dev/null +++ b/Blog.Core.EventBus/EventBusKafka/KafkaConsumerHostService.cs @@ -0,0 +1,162 @@ +using Autofac; +using Confluent.Kafka; +using Confluent.Kafka.Admin; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.EventBus +{ + /// + /// Kafka consumer 监听服务 + /// + public class KafkaConsumerHostService : BackgroundService + { + private readonly string AUTOFAC_SCOPE_NAME = "blogcore_event_bus"; + private readonly ILogger _logger; + private readonly IConsumer _consumer; + private readonly KafkaOptions _options; + private readonly IEventBusSubscriptionsManager _subsManager; + private readonly ILifetimeScope _autofac; + private CancellationTokenSource cts = new(); + public KafkaConsumerHostService(ILogger logger, + IOptions options, + IEventBusSubscriptionsManager eventBusSubscriptionsManager, + ILifetimeScope autofac) + { + _autofac = autofac; + _subsManager = eventBusSubscriptionsManager; + _logger = logger; + _options = options.Value; + _consumer = new ConsumerBuilder(new ConsumerConfig + { + BootstrapServers = _options.Servers, + GroupId = _options.GroupId, + AutoOffsetReset = AutoOffsetReset.Earliest, + AllowAutoCreateTopics = true, + EnableAutoCommit = false, + LogConnectionClose = false + }).SetErrorHandler(ConsumerClient_OnConsumeError) + .Build(); + } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var result = await FetchTopicAsync(); + if (result) + { + _consumer.Subscribe(_options.Topic); + while (!cts.Token.IsCancellationRequested) + { + var consumerResult = _consumer.Consume(cts.Token); + try + { + if (consumerResult.IsPartitionEOF || consumerResult.Message.Value == null) continue; + + var @event = Protobuf.Deserialize(consumerResult.Message.Value); + await ProcessEvent(consumerResult.Message.Key, @event); + } + catch (ConsumeException e) + { + _logger.LogError($"Error occured: {e.Error.Reason}"); + } + finally + { + _consumer.Commit(consumerResult); + } + } + } + } + public override Task StopAsync(CancellationToken cancellationToken) + { + cts.Cancel(); + _logger.LogInformation("kafka consumer stop and disposable"); + _consumer.Dispose(); + return base.StopAsync(cancellationToken); + } + /// + /// 检测当前Topic是否存在 + /// + /// + private async Task FetchTopicAsync() + { + if (string.IsNullOrEmpty(_options.Topic)) + throw new ArgumentNullException(nameof(_options.Topic)); + + try + { + var config = new AdminClientConfig { BootstrapServers = _options.Servers }; + using var adminClient = new AdminClientBuilder(config).Build(); + await adminClient.CreateTopicsAsync(Enumerable.Range(0,1).Select(u=> new TopicSpecification + { + Name = _options.Topic, + NumPartitions = _options.NumPartitions + })); + } + catch (CreateTopicsException ex) when (ex.Message.Contains("already exists")) + { + } + catch (Exception ex) + { + _logger.LogError("An error was encountered when automatically creating topic! -->" + ex.Message); + return false; + } + return true; + } + /// + /// 接收到消息进行处理 + /// + /// 事件名称 + /// 消息内容 + /// + private async Task ProcessEvent(string eventName, string message) + { + _logger.LogTrace("Processing Kafka event: {EventName}", eventName); + + if (_subsManager.HasSubscriptionsForEvent(eventName)) + { + using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME)) + { + var subscriptions = _subsManager.GetHandlersForEvent(eventName); + foreach (var subscription in subscriptions) + { + if (subscription.IsDynamic) + { + var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + if (handler == null) continue; + dynamic eventData = JObject.Parse(message); + + await Task.Yield(); + await handler.Handle(eventData); + } + else + { + var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; + var eventType = _subsManager.GetEventTypeByName(eventName); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); + + await Task.Yield(); + await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + } + } + } + } + else + { + _logger.LogWarning("No subscription for Kafka event: {EventName}", eventName); + } + } + + private void ConsumerClient_OnConsumeError(IConsumer consumer, Error e) + { + _logger.LogError("An error occurred during connect kafka:" + e.Reason); + } + } +} diff --git a/Blog.Core.EventBus/EventBusKafka/KafkaOptions.cs b/Blog.Core.EventBus/EventBusKafka/KafkaOptions.cs new file mode 100644 index 00000000..637da2aa --- /dev/null +++ b/Blog.Core.EventBus/EventBusKafka/KafkaOptions.cs @@ -0,0 +1,28 @@ + + +namespace Blog.Core.EventBus +{ + /// + /// Kafka 配置项 + /// + public class KafkaOptions + { + public int ConnectionPoolSize { get; set; } = 10; + /// + /// 地址 + /// + public string Servers { get; set; } + /// + /// 主题 + /// + public string Topic { get; set; } + /// + /// 消费者组Id + /// + public string GroupId { get; set; } + /// + /// 主题分区 + /// + public int NumPartitions { get; set; } + } +} diff --git a/Blog.Core.EventBus/EventBusKafka/ProtobufTransfer.cs b/Blog.Core.EventBus/EventBusKafka/ProtobufTransfer.cs new file mode 100644 index 00000000..c365db12 --- /dev/null +++ b/Blog.Core.EventBus/EventBusKafka/ProtobufTransfer.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +namespace Blog.Core.EventBus +{ + public class Protobuf + { + /// + /// Protobuf 反序列化 + /// + public static T Deserialize(ReadOnlySpan data) + { + Stream stream = new MemoryStream(data.ToArray()); + var info = ProtoBuf.Serializer.Deserialize(stream); + return info; + } + /// + /// 通过Protobuf 转字节 + /// + public static byte[] Serialize(T data) + { + byte[] datas; + using (var stream = new MemoryStream()) + { + ProtoBuf.Serializer.Serialize(stream, data); + datas = stream.ToArray(); + } + return datas; + + + } + } +} diff --git a/Blog.Core.EventBus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs b/Blog.Core.EventBus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs new file mode 100644 index 00000000..2612a277 --- /dev/null +++ b/Blog.Core.EventBus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Blog.Core.EventBus +{ + /// + /// 基于内存 + /// 事件总线订阅管理器 + /// 单例模式 + /// + public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager + { + private readonly Dictionary> _handlers; + private readonly List _eventTypes; + + public event EventHandler OnEventRemoved; + + public InMemoryEventBusSubscriptionsManager() + { + _handlers = new Dictionary>(); + _eventTypes = new List(); + } + + public bool IsEmpty => !_handlers.Keys.Any(); + public void Clear() => _handlers.Clear(); + + /// + /// 添加动态订阅 + /// + /// 约束:动态事件处理器接口 + /// + public void AddDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler + { + DoAddSubscription(typeof(TH), eventName, isDynamic: true); + } + + /// + /// 添加订阅 + /// + /// 约束:事件 + /// 约束:事件处理器接口<事件> + public void AddSubscription() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = GetEventKey(); + + DoAddSubscription(typeof(TH), eventName, isDynamic: false); + + if (!_eventTypes.Contains(typeof(T))) + { + _eventTypes.Add(typeof(T)); + } + } + + private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic) + { + if (!HasSubscriptionsForEvent(eventName)) + { + _handlers.Add(eventName, new List()); + } + + if (_handlers[eventName].Any(s => s.HandlerType == handlerType)) + { + throw new ArgumentException( + $"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType)); + } + + if (isDynamic) + { + _handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType)); + } + else + { + _handlers[eventName].Add(SubscriptionInfo.Typed(handlerType)); + } + } + + /// + /// 移除动态订阅 + /// + /// + /// + public void RemoveDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler + { + var handlerToRemove = FindDynamicSubscriptionToRemove(eventName); + DoRemoveHandler(eventName, handlerToRemove); + } + + + public void RemoveSubscription() + where TH : IIntegrationEventHandler + where T : IntegrationEvent + { + var handlerToRemove = FindSubscriptionToRemove(); + var eventName = GetEventKey(); + DoRemoveHandler(eventName, handlerToRemove); + } + + + private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove) + { + if (subsToRemove != null) + { + _handlers[eventName].Remove(subsToRemove); + if (!_handlers[eventName].Any()) + { + _handlers.Remove(eventName); + var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName); + if (eventType != null) + { + _eventTypes.Remove(eventType); + } + RaiseOnEventRemoved(eventName); + } + + } + } + + public IEnumerable GetHandlersForEvent() where T : IntegrationEvent + { + var key = GetEventKey(); + return GetHandlersForEvent(key); + } + public IEnumerable GetHandlersForEvent(string eventName) => _handlers[eventName]; + + private void RaiseOnEventRemoved(string eventName) + { + var handler = OnEventRemoved; + handler?.Invoke(this, eventName); + } + + + private SubscriptionInfo FindDynamicSubscriptionToRemove(string eventName) + where TH : IDynamicIntegrationEventHandler + { + return DoFindSubscriptionToRemove(eventName, typeof(TH)); + } + + /// + /// 查询订阅并移除 + /// + /// + /// + /// + private SubscriptionInfo FindSubscriptionToRemove() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = GetEventKey(); + return DoFindSubscriptionToRemove(eventName, typeof(TH)); + } + + private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType) + { + if (!HasSubscriptionsForEvent(eventName)) + { + return null; + } + + return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType); + + } + + public bool HasSubscriptionsForEvent() where T : IntegrationEvent + { + var key = GetEventKey(); + return HasSubscriptionsForEvent(key); + } + public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName); + + public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName); + + public string GetEventKey() + { + return typeof(T).Name; + } + } + +} diff --git a/Blog.Core.EventBus/EventBusSubscriptions/SubscriptionInfo.cs b/Blog.Core.EventBus/EventBusSubscriptions/SubscriptionInfo.cs new file mode 100644 index 00000000..456a4cc9 --- /dev/null +++ b/Blog.Core.EventBus/EventBusSubscriptions/SubscriptionInfo.cs @@ -0,0 +1,29 @@ +using System; + +namespace Blog.Core.EventBus +{ + /// + /// 订阅信息模型 + /// + public class SubscriptionInfo + { + public bool IsDynamic { get; } + public Type HandlerType { get; } + + private SubscriptionInfo(bool isDynamic, Type handlerType) + { + IsDynamic = isDynamic; + HandlerType = handlerType; + } + + public static SubscriptionInfo Dynamic(Type handlerType) + { + return new SubscriptionInfo(true, handlerType); + } + public static SubscriptionInfo Typed(Type handlerType) + { + return new SubscriptionInfo(false, handlerType); + } + } + +} diff --git a/Blog.Core.EventBus/Eventbus/IDynamicIntegrationEventHandler.cs b/Blog.Core.EventBus/Eventbus/IDynamicIntegrationEventHandler.cs new file mode 100644 index 00000000..b183ed5f --- /dev/null +++ b/Blog.Core.EventBus/Eventbus/IDynamicIntegrationEventHandler.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Blog.Core.EventBus +{ + /// + /// 动态集成事件处理程序 + /// 接口 + /// + public interface IDynamicIntegrationEventHandler + { + Task Handle(dynamic eventData); + } +} diff --git a/Blog.Core.EventBus/Eventbus/IEventBus.cs b/Blog.Core.EventBus/Eventbus/IEventBus.cs new file mode 100644 index 00000000..7f3f60b2 --- /dev/null +++ b/Blog.Core.EventBus/Eventbus/IEventBus.cs @@ -0,0 +1,49 @@ +namespace Blog.Core.EventBus +{ + /// + /// 事件总线 + /// 接口 + /// + public interface IEventBus + { + /// + /// 发布 + /// + /// 事件模型 + void Publish(IntegrationEvent @event); + + /// + /// 订阅 + /// + /// 约束:事件模型 + /// 约束:事件处理器<事件模型> + void Subscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler; + + /// + /// 取消订阅 + /// + /// + /// + void Unsubscribe() + where TH : IIntegrationEventHandler + where T : IntegrationEvent; + + /// + /// 动态订阅 + /// + /// 约束:事件处理器 + /// + void SubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler; + + /// + /// 动态取消订阅 + /// + /// + /// + void UnsubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler; + } +} diff --git a/Blog.Core.EventBus/Eventbus/IEventBusSubscriptionsManager.cs b/Blog.Core.EventBus/Eventbus/IEventBusSubscriptionsManager.cs new file mode 100644 index 00000000..bfd3a0db --- /dev/null +++ b/Blog.Core.EventBus/Eventbus/IEventBusSubscriptionsManager.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Blog.Core.EventBus +{ + /// + /// 事件总线订阅管理器 + /// 接口 + /// + public interface IEventBusSubscriptionsManager + { + bool IsEmpty { get; } + event EventHandler OnEventRemoved; + void AddDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler; + + void AddSubscription() + where T : IntegrationEvent + where TH : IIntegrationEventHandler; + + void RemoveSubscription() + where TH : IIntegrationEventHandler + where T : IntegrationEvent; + void RemoveDynamicSubscription(string eventName) + where TH : IDynamicIntegrationEventHandler; + + bool HasSubscriptionsForEvent() where T : IntegrationEvent; + bool HasSubscriptionsForEvent(string eventName); + Type GetEventTypeByName(string eventName); + void Clear(); + IEnumerable GetHandlersForEvent() where T : IntegrationEvent; + IEnumerable GetHandlersForEvent(string eventName); + string GetEventKey(); + } + +} diff --git a/Blog.Core.EventBus/Eventbus/IIntegrationEventHandler.cs b/Blog.Core.EventBus/Eventbus/IIntegrationEventHandler.cs new file mode 100644 index 00000000..d955cddc --- /dev/null +++ b/Blog.Core.EventBus/Eventbus/IIntegrationEventHandler.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; + +namespace Blog.Core.EventBus +{ + /// + /// 集成事件处理程序 + /// 泛型接口 + /// + /// + public interface IIntegrationEventHandler : IIntegrationEventHandler + where TIntegrationEvent : IntegrationEvent + { + Task Handle(TIntegrationEvent @event); + } + + /// + /// 集成事件处理程序 + /// 基 接口 + /// + public interface IIntegrationEventHandler + { + } +} diff --git a/Blog.Core.EventBus/Eventbus/IntegrationEvent.cs b/Blog.Core.EventBus/Eventbus/IntegrationEvent.cs new file mode 100644 index 00000000..a9f6a5eb --- /dev/null +++ b/Blog.Core.EventBus/Eventbus/IntegrationEvent.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using System; + +namespace Blog.Core.EventBus +{ + /// + /// 事件模型 + /// 基类 + /// + public class IntegrationEvent + { + public IntegrationEvent() + { + Id = Guid.NewGuid(); + CreationDate = DateTime.UtcNow; + } + + [JsonConstructor] + public IntegrationEvent(Guid id, DateTime createDate) + { + Id = id; + CreationDate = createDate; + } + + [JsonProperty] + public Guid Id { get; private set; } + + [JsonProperty] + public DateTime CreationDate { get; private set; } + } +} diff --git a/Blog.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs b/Blog.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs new file mode 100644 index 00000000..7438ddd4 --- /dev/null +++ b/Blog.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs @@ -0,0 +1,354 @@ +using Autofac; +using Blog.Core.Common.Extensions; +using Blog.Core.Common; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Polly; +using Polly.Retry; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using RabbitMQ.Client.Exceptions; +using System; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.EventBus +{ + /// + /// 基于RabbitMQ的事件总线 + /// + public class EventBusRabbitMQ : IEventBus, IDisposable + { + const string BROKER_NAME = "blogcore_event_bus"; + + private readonly IRabbitMQPersistentConnection _persistentConnection; + private readonly ILogger _logger; + private readonly IEventBusSubscriptionsManager _subsManager; + private readonly ILifetimeScope _autofac; + private readonly string AUTOFAC_SCOPE_NAME = "blogcore_event_bus"; + private readonly int _retryCount; + + private IModel _consumerChannel; + private string _queueName; + + /// + /// RabbitMQ事件总线 + /// + /// RabbitMQ持久连接 + /// 日志 + /// autofac容器 + /// 事件总线订阅管理器 + /// 队列名称 + /// 重试次数 + public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger logger, + ILifetimeScope autofac, + IEventBusSubscriptionsManager subsManager, + string queueName = null, + int retryCount = 5) + { + _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); + _queueName = queueName; + _consumerChannel = CreateConsumerChannel(); + _autofac = autofac; + _retryCount = retryCount; + _subsManager.OnEventRemoved += SubsManager_OnEventRemoved; + } + + /// + /// 订阅管理器事件 + /// + /// + /// + private void SubsManager_OnEventRemoved(object sender, string eventName) + { + if (!_persistentConnection.IsConnected) + { + _persistentConnection.TryConnect(); + } + + using (var channel = _persistentConnection.CreateModel()) + { + channel.QueueUnbind(queue: _queueName, + exchange: BROKER_NAME, + routingKey: eventName); + + if (_subsManager.IsEmpty) + { + _queueName = string.Empty; + _consumerChannel.Close(); + } + } + } + + /// + /// 发布 + /// + /// 事件模型 + public void Publish(IntegrationEvent @event) + { + if (!_persistentConnection.IsConnected) + { + _persistentConnection.TryConnect(); + } + + var policy = RetryPolicy.Handle() + .Or() + .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => + { + _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message); + }); + + var eventName = @event.GetType().Name; + + _logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, eventName); + + using (var channel = _persistentConnection.CreateModel()) + { + + _logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id); + + channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct"); + + var message = JsonConvert.SerializeObject(@event); + var body = Encoding.UTF8.GetBytes(message); + + policy.Execute(() => + { + var properties = channel.CreateBasicProperties(); + properties.DeliveryMode = 2; // persistent + + _logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id); + + channel.BasicPublish( + exchange: BROKER_NAME, + routingKey: eventName, + mandatory: true, + basicProperties: properties, + body: body); + }); + } + } + + /// + /// 订阅 + /// 动态 + /// + /// 事件处理器 + /// 事件名 + public void SubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler + { + _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + + DoInternalSubscription(eventName); + _subsManager.AddDynamicSubscription(eventName); + StartBasicConsume(); + } + + /// + /// 订阅 + /// + /// 约束:事件模型 + /// 约束:事件处理器<事件模型> + public void Subscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = _subsManager.GetEventKey(); + DoInternalSubscription(eventName); + + _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); + + ConsoleHelper.WriteSuccessLine($"Subscribing to event {eventName} with {typeof(TH).GetGenericTypeName()}"); + + _subsManager.AddSubscription(); + StartBasicConsume(); + } + + private void DoInternalSubscription(string eventName) + { + var containsKey = _subsManager.HasSubscriptionsForEvent(eventName); + if (!containsKey) + { + if (!_persistentConnection.IsConnected) + { + _persistentConnection.TryConnect(); + } + + using (var channel = _persistentConnection.CreateModel()) + { + channel.QueueBind(queue: _queueName, + exchange: BROKER_NAME, + routingKey: eventName); + } + } + } + + /// + /// 取消订阅 + /// + /// + /// + public void Unsubscribe() + where T : IntegrationEvent + where TH : IIntegrationEventHandler + { + var eventName = _subsManager.GetEventKey(); + + _logger.LogInformation("Unsubscribing from event {EventName}", eventName); + + _subsManager.RemoveSubscription(); + } + + public void UnsubscribeDynamic(string eventName) + where TH : IDynamicIntegrationEventHandler + { + _subsManager.RemoveDynamicSubscription(eventName); + } + + public void Dispose() + { + if (_consumerChannel != null) + { + _consumerChannel.Dispose(); + } + + _subsManager.Clear(); + } + + /// + /// 开始基本消费 + /// + private void StartBasicConsume() + { + _logger.LogTrace("Starting RabbitMQ basic consume"); + + if (_consumerChannel != null) + { + var consumer = new AsyncEventingBasicConsumer(_consumerChannel); + + consumer.Received += Consumer_Received; + + _consumerChannel.BasicConsume( + queue: _queueName, + autoAck: false, + consumer: consumer); + } + else + { + _logger.LogError("StartBasicConsume can't call on _consumerChannel == null"); + } + } + + /// + /// 消费者接受到 + /// + /// + /// + /// + private async Task Consumer_Received(object sender, BasicDeliverEventArgs eventArgs) + { + var eventName = eventArgs.RoutingKey; + var message = Encoding.UTF8.GetString(eventArgs.Body.Span); + + try + { + if (message.ToLowerInvariant().Contains("throw-fake-exception")) + { + throw new InvalidOperationException($"Fake exception requested: \"{message}\""); + } + + await ProcessEvent(eventName, message); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message); + } + + // Even on exception we take the message off the queue. + // in a REAL WORLD app this should be handled with a Dead Letter Exchange (DLX). + // For more information see: https://www.rabbitmq.com/dlx.html + _consumerChannel.BasicAck(eventArgs.DeliveryTag, multiple: false); + } + + /// + /// 创造消费通道 + /// + /// + private IModel CreateConsumerChannel() + { + if (!_persistentConnection.IsConnected) + { + _persistentConnection.TryConnect(); + } + + _logger.LogTrace("Creating RabbitMQ consumer channel"); + + var channel = _persistentConnection.CreateModel(); + + channel.ExchangeDeclare(exchange: BROKER_NAME, + type: "direct"); + + channel.QueueDeclare(queue: _queueName, + durable: true, + exclusive: false, + autoDelete: false, + arguments: null); + + channel.CallbackException += (sender, ea) => + { + _logger.LogWarning(ea.Exception, "Recreating RabbitMQ consumer channel"); + + _consumerChannel.Dispose(); + _consumerChannel = CreateConsumerChannel(); + StartBasicConsume(); + }; + + return channel; + } + + private async Task ProcessEvent(string eventName, string message) + { + _logger.LogTrace("Processing RabbitMQ event: {EventName}", eventName); + + if (_subsManager.HasSubscriptionsForEvent(eventName)) + { + using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME)) + { + var subscriptions = _subsManager.GetHandlersForEvent(eventName); + foreach (var subscription in subscriptions) + { + if (subscription.IsDynamic) + { + var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; + if (handler == null) continue; + dynamic eventData = JObject.Parse(message); + + await Task.Yield(); + await handler.Handle(eventData); + } + else + { + var handler = scope.ResolveOptional(subscription.HandlerType); + if (handler == null) continue; + var eventType = _subsManager.GetEventTypeByName(eventName); + var integrationEvent = JsonConvert.DeserializeObject(message, eventType); + var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); + + await Task.Yield(); + await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); + } + } + } + } + else + { + _logger.LogWarning("No subscription for RabbitMQ event: {EventName}", eventName); + } + } + } +} diff --git a/Blog.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs b/Blog.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs new file mode 100644 index 00000000..bad482ae --- /dev/null +++ b/Blog.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs @@ -0,0 +1,44 @@ +using RabbitMQ.Client; +using System; + +namespace Blog.Core.EventBus +{ + /// + /// RabbitMQ持久连接 + /// 接口 + /// + 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 new file mode 100644 index 00000000..1fa3bd08 --- /dev/null +++ b/Blog.Core.EventBus/RabbitMQPersistent/RabbitMQPersistentConnection.cs @@ -0,0 +1,211 @@ +using Microsoft.Extensions.Logging; +using Polly; +using Polly.Retry; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using RabbitMQ.Client.Exceptions; +using System; +using System.IO; +using System.Net.Sockets; +using System.Text; + +namespace Blog.Core.EventBus +{ + /// + /// RabbitMQ持久连接 + /// + public class RabbitMQPersistentConnection + : IRabbitMQPersistentConnection + { + private readonly IConnectionFactory _connectionFactory; + private readonly ILogger _logger; + private readonly int _retryCount; + IConnection _connection; + bool _disposed; + + object sync_root = new object(); + + public RabbitMQPersistentConnection(IConnectionFactory connectionFactory, ILogger logger, + int retryCount = 5) + { + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _retryCount = retryCount; + } + + /// + /// 是否已连接 + /// + public bool IsConnected + { + get + { + return _connection != null && _connection.IsOpen && !_disposed; + } + } + + /// + /// 创建Model + /// + /// + public IModel CreateModel() + { + if (!IsConnected) + { + throw new InvalidOperationException("No RabbitMQ connections are available to perform this action"); + } + + return _connection.CreateModel(); + } + + /// + /// 释放 + /// + public void Dispose() + { + if (_disposed) return; + + _disposed = true; + + try + { + _connection.Dispose(); + } + catch (IOException ex) + { + _logger.LogCritical(ex.ToString()); + } + } + + /// + /// 连接 + /// + /// + public bool TryConnect() + { + _logger.LogInformation("RabbitMQ Client is trying to connect"); + + lock (sync_root) + { + var policy = RetryPolicy.Handle() + .Or() + .WaitAndRetry(_retryCount, + retryAttempt => + TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => + { + _logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message); + } + ); + + policy.Execute(() => + { + _connection = _connectionFactory + .CreateConnection(); + }); + + if (IsConnected) + { + _connection.ConnectionShutdown += OnConnectionShutdown; + _connection.CallbackException += OnCallbackException; + _connection.ConnectionBlocked += OnConnectionBlocked; + + _logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName); + + return true; + } + else + { + _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened"); + + return false; + } + } + } + + /// + /// 连接被阻断 + /// + /// + /// + private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) + { + if (_disposed) return; + + _logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect..."); + + TryConnect(); + } + + /// + /// 连接出现异常 + /// + /// + /// + void OnCallbackException(object sender, CallbackExceptionEventArgs e) + { + if (_disposed) return; + + _logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect..."); + + TryConnect(); + } + + /// + /// 连接被关闭 + /// + /// + /// + void OnConnectionShutdown(object sender, ShutdownEventArgs reason) + { + if (_disposed) return; + + _logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect..."); + + 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 new file mode 100644 index 00000000..2fb8f7bd --- /dev/null +++ b/Blog.Core.Extensions/AOP/BlogCacheAOP.cs @@ -0,0 +1,84 @@ +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); + 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 new file mode 100644 index 00000000..a52d3ad8 --- /dev/null +++ b/Blog.Core.Extensions/AOP/BlogLogAOP.cs @@ -0,0 +1,279 @@ +using Blog.Core.Common; +using Blog.Core.Common.LogHelper; +using Blog.Core.Hubs; +using Castle.DynamicProxy; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Newtonsoft.Json; +using StackExchange.Profiling; +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Blog.Core.AOP +{ + /// + /// 拦截器BlogLogAOP 继承IInterceptor接口 + /// + public class BlogLogAOP : IInterceptor + { + private readonly IHubContext _hubContext; + private readonly IHttpContextAccessor _accessor; + private readonly ILogger _logger; + + public BlogLogAOP(IHubContext hubContext, IHttpContextAccessor accessor, ILogger logger) + { + _hubContext = hubContext; + _accessor = accessor; + _logger = logger; + } + + + /// + /// 实例化IInterceptor唯一方法 + /// + /// 包含被拦截方法的信息 + 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"; + + try + { + MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> "); + //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的 + invocation.Proceed(); + + + // 异步获取异常,先执行 + if (IsAsyncMethod(invocation.Method)) + { + #region 方案一 + + //Wait task execution and modify return value + if (invocation.Method.ReturnType == typeof(Task)) + { + invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( + (Task)invocation.ReturnValue, + async () => await SuccessAction(invocation, apiLogAopInfo, startTime), /*成功时执行*/ + ex => { LogEx(ex, apiLogAopInfo); }); + } + //Task + else + { + invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( + invocation.Method.ReturnType.GenericTypeArguments[0], + invocation.ReturnValue, + //async () => await SuccessAction(invocation, dataIntercept),/*成功时执行*/ + async (o) => await SuccessAction(invocation, apiLogAopInfo, startTime, o), /*成功时执行*/ + ex => { LogEx(ex, apiLogAopInfo); }); + } + + #endregion + + + // 如果方案一不行,试试这个方案 + //#region 方案二 + + //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)); + + ////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.OutLogAOP("AOPLog", new string[] { dataIntercept }); + // LogLock.OutLogAOP("AOPLog", new string[] { apiLogAopInfo.GetType().ToString() + " - ResponseJsonDataType:" + type, JsonConvert.SerializeObject(apiLogAopInfo) }); + //}); + + //#endregion + } + else + { + // 同步1 + string jsonResult; + try + { + jsonResult = JsonConvert.SerializeObject(invocation.ReturnValue); + } + catch (Exception ex) + { + 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 + { + LogEx(ex, apiLogAopInfo); + throw; + } + + if (AppSettings.app(new string[] { "Middleware", "SignalRSendLog", "Enabled" }).ObjToBool()) + { + //发送日志 + _hubContext.Clients.All.SendAsync("ReceiveUpdate", JsonConvert.SerializeObject(apiLogAopInfo)).Wait(); + } + } + + private async Task SuccessAction(IInvocation invocation, AOPLogInfo apiLogAopInfo, DateTime startTime, + object o = null) + { + //invocation.ReturnValue = o; + //var type = invocation.Method.ReturnType; + //if (typeof(Task).IsAssignableFrom(type)) + //{ + // //var resultProperty = type.GetProperty("Result"); + // //类型错误 都可以不要invocation参数,直接将o系列化保存到日记中 + // dataIntercept += ($"【执行完成结果】:{JsonConvert.SerializeObject(invocation.ReturnValue)}"); + //} + //else + //{ + // dataIntercept += ($"【执行完成结果】:{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(o); + + _logger.LogInformation("AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopInfo)); + } + + 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"); + AOPLogExInfo apiLogAopExInfo = new AOPLogExInfo + { + ExMessage = ex.Message, + InnerException = "InnerException-内部异常:\r\n" + + (ex.InnerException == null ? "" : ex.InnerException.InnerException.ToString()) + + ("\r\nStackTrace-堆栈跟踪:\r\n") + (ex.StackTrace == null ? "" : ex.StackTrace.ToString()), + ApiLogAopInfo = dataIntercept + }; + // 异常日志里有详细的堆栈信息 + _logger.LogError(ex, "AOPLog,{TraceIdentifier}: {ApiLogAopInfo}", + _accessor.HttpContext?.TraceIdentifier, JsonConvert.SerializeObject(apiLogAopExInfo)); + } + } + + + 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) + { + Exception exception = null; + + try + { + await actualReturnValue; + await postAction(); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + finalAction(exception); + } + } + + public static async Task AwaitTaskWithPostActionAndFinallyAndGetResult(Task actualReturnValue, + Func postAction, + Action finalAction) + { + Exception exception = null; + try + { + var result = await actualReturnValue; + await postAction(result); + return result; + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + finalAction(exception); + } + } + + 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 }); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/AOP/BlogTranAOP.cs b/Blog.Core.Extensions/AOP/BlogTranAOP.cs new file mode 100644 index 00000000..2c252ef6 --- /dev/null +++ b/Blog.Core.Extensions/AOP/BlogTranAOP.cs @@ -0,0 +1,139 @@ +using Blog.Core.Common; +using Castle.DynamicProxy; +using Microsoft.Extensions.Logging; +using System; +using System.Reflection; +using System.Threading.Tasks; +using Blog.Core.Common.DB; +using Blog.Core.Repository.UnitOfWorks; + +namespace Blog.Core.AOP +{ + /// + /// 事务拦截器BlogTranAOP 继承IInterceptor接口 + /// + public class BlogTranAOP : IInterceptor + { + private readonly ILogger _logger; + private readonly IUnitOfWorkManage _unitOfWorkManage; + + public BlogTranAOP(IUnitOfWorkManage unitOfWorkManage, ILogger logger) + { + _unitOfWorkManage = unitOfWorkManage; + _logger = logger; + } + + /// + /// 实例化IInterceptor唯一方法 + /// + /// 包含被拦截方法的信息 + public void Intercept(IInvocation invocation) + { + var method = invocation.MethodInvocationTarget ?? invocation.Method; + //对当前方法的特性验证 + //如果需要验证 + if (method.GetCustomAttribute(true) is { } uta) + { + try + { + Before(method, uta.Propagation); + + invocation.Proceed(); + + // 异步获取异常,先执行 + if (IsAsyncMethod(invocation.Method)) + { + var result = invocation.ReturnValue; + if (result is Task) + { + Task.WaitAll(result as Task); + } + } + + After(method); + } + catch (Exception ex) + { + _logger.LogError(ex.ToString()); + AfterException(method); + throw; + } + } + else + { + 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) + { + await Task.Run(() => + { + //... + }); + } + + 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/AOP/BlogUserAuditAOP.cs b/Blog.Core.Extensions/AOP/BlogUserAuditAOP.cs new file mode 100644 index 00000000..2681ce1d --- /dev/null +++ b/Blog.Core.Extensions/AOP/BlogUserAuditAOP.cs @@ -0,0 +1,69 @@ +using Castle.DynamicProxy; +using Microsoft.AspNetCore.Http; +using System; + +namespace Blog.Core.AOP +{ + /// + /// 面向切面的缓存使用 + /// + public class BlogUserAuditAOP : CacheAOPbase + { + private readonly IHttpContextAccessor _accessor; + + public BlogUserAuditAOP(IHttpContextAccessor accessor) + { + _accessor = accessor; + } + + public override void Intercept(IInvocation invocation) + { + string UserName = _accessor.HttpContext?.User?.Identity?.Name; + + //对当前方法的特性验证 + if (invocation.Method.Name?.ToLower() == "add" || invocation.Method.Name?.ToLower() == "update") + { + + if (invocation.Arguments.Length == 1) + { + if (invocation.Arguments[0].GetType().IsClass) + { + dynamic argModel = invocation.Arguments[0]; + var getType = argModel.GetType(); + if (invocation.Method.Name?.ToLower() == "add") + { + if (getType.GetProperty("CreateBy") != null) + { + argModel.CreateBy = UserName; + } + if (getType.GetProperty("bCreateTime") != null) + { + argModel.bCreateTime = DateTime.Now; + } + } + if (getType.GetProperty("bUpdateTime") != null) + { + argModel.bUpdateTime = DateTime.Now; + } + if (getType.GetProperty("ModifyBy") != null) + { + argModel.ModifyBy = UserName; + } + if (getType.GetProperty("bsubmitter") != null) + { + argModel.bsubmitter = UserName; + } + + invocation.Arguments[0] = argModel; + } + } + invocation.Proceed(); + } + else + { + invocation.Proceed(); + } + } + } + +} diff --git a/Blog.Core.Extensions/AOP/CacheAOPbase.cs b/Blog.Core.Extensions/AOP/CacheAOPbase.cs new file mode 100644 index 00000000..04791830 --- /dev/null +++ b/Blog.Core.Extensions/AOP/CacheAOPbase.cs @@ -0,0 +1,184 @@ +using Blog.Core.Common.Helper; +using Castle.DynamicProxy; +using Newtonsoft.Json; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Blog.Core.AOP +{ + public abstract class CacheAOPbase : IInterceptor + { + /// + /// AOP的拦截方法 + /// + /// + public abstract void Intercept(IInvocation invocation); + + /// + /// 自定义缓存的key + /// + /// + /// + protected string CustomCacheKey(IInvocation invocation) + { + var typeName = invocation.TargetType.Name; + var methodName = invocation.Method.Name; + var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,最多三个 + + string key = $"{typeName}:{methodName}:"; + foreach (var param in methodArguments) + { + key = $"{key}{param}:"; + } + + return key.TrimEnd(':'); + } + + /// + /// object 转 string + /// + /// + /// + protected static string GetArgumentValue(object arg) + { + if (arg is DateTime) + return ((DateTime)arg).ToString("yyyyMMddHHmmss"); + + if (!arg.IsNotEmptyOrNull()) + return arg.ObjToString(); + + if (arg != null) + { + if (arg is Expression) + { + var obj = arg as Expression; + var result = Resolve(obj); + return MD5Helper.MD5Encrypt16(result); + } + else if (arg.GetType().IsClass) + { + return MD5Helper.MD5Encrypt16(JsonConvert.SerializeObject(arg)); + } + + return $"value:{arg.ObjToString()}"; + } + return string.Empty; + } + + private static string Resolve(Expression expression) + { + ExpressionContext expContext = new ExpressionContext(); + expContext.Resolve(expression, ResolveExpressType.WhereSingle); + var value = expContext.Result.GetString(); + var pars = expContext.Parameters; + + pars.ForEach(s => + { + value = value.Replace(s.ParameterName, s.Value.ObjToString()); + }); + + return value; + } + + private static string GetOperator(ExpressionType expressiontype) + { + switch (expressiontype) + { + case ExpressionType.And: + return "and"; + case ExpressionType.AndAlso: + return "and"; + case ExpressionType.Or: + return "or"; + case ExpressionType.OrElse: + return "or"; + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "<>"; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + default: + throw new Exception($"不支持{expressiontype}此种运算符查找!"); + } + } + + private static string ResolveFunc(Expression left, Expression right, ExpressionType expressiontype) + { + var Name = (left as MemberExpression).Member.Name; + var Value = (right as ConstantExpression).Value; + var Operator = GetOperator(expressiontype); + return Name + Operator + Value ?? "null"; + } + + private static string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null) + { + var MethodCall = expression as MethodCallExpression; + var MethodName = MethodCall.Method.Name; + switch (MethodName) + { + case "Contains": + if (MethodCall.Object != null) + return Like(MethodCall); + return In(MethodCall, value); + case "Count": + return Len(MethodCall, value, expressiontype.Value); + case "LongCount": + return Len(MethodCall, value, expressiontype.Value); + default: + throw new Exception($"不支持{MethodName}方法的查找!"); + } + } + + private static string In(MethodCallExpression expression, object isTrue) + { + var Argument1 = (expression.Arguments[0] as MemberExpression).Expression as ConstantExpression; + var Argument2 = expression.Arguments[1] as MemberExpression; + var Field_Array = Argument1.Value.GetType().GetFields().First(); + object[] Array = Field_Array.GetValue(Argument1.Value) as object[]; + List SetInPara = new List(); + for (int i = 0; i < Array.Length; i++) + { + string Name_para = "InParameter" + i; + string Value = Array[i].ToString(); + SetInPara.Add(Value); + } + string Name = Argument2.Member.Name; + string Operator = Convert.ToBoolean(isTrue) ? "in" : " not in"; + string CompName = string.Join(",", SetInPara); + string Result = $"{Name} {Operator} ({CompName})"; + return Result; + } + private static string Like(MethodCallExpression expression) + { + + var Temp = expression.Arguments[0]; + LambdaExpression lambda = Expression.Lambda(Temp); + Delegate fn = lambda.Compile(); + var tempValue = Expression.Constant(fn.DynamicInvoke(null), Temp.Type); + string Value = $"%{tempValue}%"; + string Name = (expression.Object as MemberExpression).Member.Name; + string Result = $"{Name} like {Value}"; + return Result; + } + + + private static string Len(MethodCallExpression expression, object value, ExpressionType expressiontype) + { + object Name = (expression.Arguments[0] as MemberExpression).Member.Name; + string Operator = GetOperator(expressiontype); + string Result = $"len({Name}){Operator}{value.ToString()}"; + return Result; + } + + } +} diff --git a/Blog.Core.Extensions/Apollo/ApolloOptions.cs b/Blog.Core.Extensions/Apollo/ApolloOptions.cs new file mode 100644 index 00000000..6610ba47 --- /dev/null +++ b/Blog.Core.Extensions/Apollo/ApolloOptions.cs @@ -0,0 +1,27 @@ + +using System.Collections.Generic; + + +namespace Blog.Core.Extensions.Apollo +{ + /// + /// Apollo配置项 + /// + public class ApolloOptions + { + public bool Enable { get; set; } + public List Namespaces { get; set; } + + public class ChildNamespace + { + /// + /// 命名空间名字 + /// + public string Name { get; set; } + /// + /// 数据格式 Json/Yml/Yaml等 + /// + public string Format { get; set; } + } + } +} diff --git a/Blog.Core.Extensions/Apollo/ConfigurationBuilderExtensions.cs b/Blog.Core.Extensions/Apollo/ConfigurationBuilderExtensions.cs new file mode 100644 index 00000000..909e9a19 --- /dev/null +++ b/Blog.Core.Extensions/Apollo/ConfigurationBuilderExtensions.cs @@ -0,0 +1,84 @@ +using Com.Ctrip.Framework.Apollo; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using Com.Ctrip.Framework.Apollo.Enums; +using Com.Ctrip.Framework.Apollo.Logging; +using Microsoft.Extensions.Primitives; +using System.Reflection; + +namespace Blog.Core.Extensions.Apollo +{ + public static class ConfigurationBuilderExtensions + { + /// + /// 接入Apollo + /// + /// + /// apollo配置文件路径 如果写入appsettings.json中 则jsonPath传null即可 + public static void AddConfigurationApollo(this IConfigurationBuilder builder,string jsonPath) + { + if (!string.IsNullOrEmpty(jsonPath)) + { + builder.AddJsonFile(jsonPath, true, false); + } + //阿波罗的日志级别调整 + LogManager.UseConsoleLogging(LogLevel.Warn); + var options = new ApolloOptions(); + var root = builder.Build(); + root.Bind("Apollo", options); + if (options.Enable) + { + var apolloBuilder = builder.AddApollo(root.GetSection("Apollo:Config")); + + foreach (var item in options.Namespaces) + { + apolloBuilder.AddNamespace(item.Name, MatchConfigFileFormat(item.Format)); + } + //监听apollo配置 + Monitor(builder.Build()); + } + + } + #region private + /// + /// 监听配置 + /// + private static void Monitor(IConfigurationRoot root) + { + //TODO 需要根据改变执行特定的操作 如 mq redis 等其他跟配置相关的中间件 + //TODO 初步思路:将需要执行特定的操作key和value放入内存字典中,在赋值操作时通过标准事件来执行特定的操作。 + + //要重新Build 此时才将Apollo provider加入到ConfigurationBuilder中 + ChangeToken.OnChange(() => root.GetReloadToken(), () => + { + foreach (var apolloProvider in root.Providers.Where(p => p is ApolloConfigurationProvider)) + { + var property = apolloProvider.GetType().BaseType.GetProperty("Data", BindingFlags.Instance | BindingFlags.NonPublic); + var data = property.GetValue(apolloProvider) as IDictionary; + foreach (var item in data) + { + Console.WriteLine($"key {item.Key} value {item.Value}"); + } + } + }); + } + + //匹配格式 + private static ConfigFileFormat MatchConfigFileFormat(string value) => value switch + { + "json" => ConfigFileFormat.Json, + "properties" => ConfigFileFormat.Properties, + "xml" => ConfigFileFormat.Xml, + "yml" => ConfigFileFormat.Yml, + "yaml" => ConfigFileFormat.Yaml, + "txt" => ConfigFileFormat.Txt, + _ => throw new FormatException($"与apollo命名空间的所允许的类型不匹配:{string.Join(",", GetConfigFileFormat())}"), + }; + //获取数据格式对应的枚举 + private static IEnumerable GetConfigFileFormat() => Enum.GetValues().Select(u => u.ToString().ToLower()); + #endregion + + } +} diff --git a/Blog.Core.Extensions/Authorizations/Behaviors/IUserBehaviorService.cs b/Blog.Core.Extensions/Authorizations/Behaviors/IUserBehaviorService.cs new file mode 100644 index 00000000..520ca3f7 --- /dev/null +++ b/Blog.Core.Extensions/Authorizations/Behaviors/IUserBehaviorService.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace Blog.Core.Extensions.Authorizations.Behaviors +{ + public interface IUserBehaviorService + { + + Task CreateOrUpdateUserAccessByUid(); + + Task RemoveAllUserAccessByUid(); + + Task CheckUserIsNormal(); + + Task CheckTokenIsNormal(); + } +} diff --git a/Blog.Core.Extensions/Authorizations/Behaviors/UserBehaviorService.cs b/Blog.Core.Extensions/Authorizations/Behaviors/UserBehaviorService.cs new file mode 100644 index 00000000..9fcc944d --- /dev/null +++ b/Blog.Core.Extensions/Authorizations/Behaviors/UserBehaviorService.cs @@ -0,0 +1,48 @@ +using Blog.Core.Common.HttpContextUser; +using Blog.Core.IServices; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions.Authorizations.Behaviors +{ + public class UserBehaviorService : IUserBehaviorService + { + private readonly IUser _user; + private readonly ISysUserInfoServices _sysUserInfoServices; + private readonly ILogger _logger; + private readonly string _uid; + private readonly string _token; + + public UserBehaviorService(IUser user + , ISysUserInfoServices sysUserInfoServices + , ILogger logger) + { + _user = user; + _sysUserInfoServices = sysUserInfoServices; + _logger = logger; + _uid = _user.ID.ObjToString(); + _token = _user.GetToken(); + } + + + public Task CheckTokenIsNormal() + { + throw new System.NotImplementedException(); + } + + public Task CheckUserIsNormal() + { + throw new System.NotImplementedException(); + } + + public Task CreateOrUpdateUserAccessByUid() + { + throw new System.NotImplementedException(); + } + + public Task RemoveAllUserAccessByUid() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Blog.Core/AuthHelper/OverWrite/JwtHelper.cs b/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs similarity index 52% rename from Blog.Core/AuthHelper/OverWrite/JwtHelper.cs rename to Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs index c6478ccd..6fa9c9b6 100644 --- a/Blog.Core/AuthHelper/OverWrite/JwtHelper.cs +++ b/Blog.Core.Extensions/Authorizations/Helpers/JwtHelper.cs @@ -5,13 +5,14 @@ using System.Security.Claims; using System.Text; using Blog.Core.Common; +using Blog.Core.Common.AppConfig; using Microsoft.IdentityModel.Tokens; namespace Blog.Core.AuthHelper.OverWrite { public class JwtHelper { - + /// /// 颁发JWT字符串 /// @@ -19,19 +20,27 @@ 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 secret = Appsettings.app(new string[] { "Audience", "Secret" }); + 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 var claims = new List { - //下边为Claim的默认配置 + /* + * 特别重要: + 1、这里将用户的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方将这个 uid从 Token 中取出来,请看下边的SerializeJwt() 方法,或者在整个解决方案,搜索这个方法,看哪里使用了! + 2、你也可以研究下 HttpContext.User.Claims ,具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。 + */ + + + 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()}") , - //这个就是过期时间,目前是过期100秒,可自定义,注意JWT有自己的缓冲过期时间 - new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(100)).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()), new Claim(JwtRegisteredClaimNames.Iss,iss), new Claim(JwtRegisteredClaimNames.Aud,aud), @@ -67,23 +76,37 @@ public static string IssueJwt(TokenModelJwt tokenModel) public static TokenModelJwt SerializeJwt(string jwtStr) { var jwtHandler = new JwtSecurityTokenHandler(); - JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr); - object role; - try + TokenModelJwt tokenModelJwt = new TokenModelJwt(); + + // token校验 + if (jwtStr.IsNotEmptyOrNull() && jwtHandler.CanReadToken(jwtStr)) { + + JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr); + + object role; + jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role); + + tokenModelJwt = new TokenModelJwt + { + Uid = (jwtToken.Id).ObjToLong(), + Role = role != null ? role.ObjToString() : "", + }; } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - var tm = new TokenModelJwt - { - Uid = (jwtToken.Id).ObjToInt(), - Role = role != null ? role.ObjToString() : "", - }; - return tm; + 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 new file mode 100644 index 00000000..9c8f4e5c --- /dev/null +++ b/Blog.Core.Extensions/Authorizations/Policys/ApiResponseHandler.cs @@ -0,0 +1,50 @@ +using Blog.Core.Model; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Blog.Core.Common.HttpContextUser; + +namespace Blog.Core.AuthHelper +{ + public class ApiResponseHandler : AuthenticationHandler + { + 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"; + Response.StatusCode = StatusCodes.Status401Unauthorized; + await Response.WriteAsync(JsonConvert.SerializeObject((new ApiResponse(StatusCode.CODE401)).MessageModel)); + } + + protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) + { + Response.ContentType = "application/json"; + 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/AuthHelper/Policys/JwtToken.cs b/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs similarity index 81% rename from Blog.Core/AuthHelper/Policys/JwtToken.cs rename to Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs index 6281e38a..2b9435a5 100644 --- a/Blog.Core/AuthHelper/Policys/JwtToken.cs +++ b/Blog.Core.Extensions/Authorizations/Policys/JwtToken.cs @@ -1,4 +1,5 @@ -using System; +using Blog.Core.Model.ViewModels; +using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; @@ -12,10 +13,10 @@ public class JwtToken /// /// 获取基于JWT的Token /// - /// 需要在登陆的时候配置 + /// 需要在登录的时候配置 /// 在startup中定义的参数 /// - public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) + public static TokenInfoViewModel BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement) { var now = DateTime.Now; // 实例化JwtSecurityToken @@ -31,7 +32,7 @@ public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permis var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); //打包返回前台 - var responseJson = new + var responseJson = new TokenInfoViewModel { success = true, token = encodedJwt, diff --git a/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs b/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs new file mode 100644 index 00000000..8d09be4f --- /dev/null +++ b/Blog.Core.Extensions/Authorizations/Policys/PermissionHandler.cs @@ -0,0 +1,269 @@ +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 +{ + /// + /// 权限授权处理器 + /// + 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; + + /// + /// 构造函数注入 + /// + /// + /// + /// + /// + /// + 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) + { + var httpContext = _accessor.HttpContext; + + // 获取系统中所有的角色和菜单的关系集合 + if (!requirement.Permissions.Any()) + { + var data = await _roleModulePermissionServices.RoleModuleMaps(); + var list = new List(); + // ids4和jwt切换 + // ids4 + if (Permissions.IsUseIds4) + { + list = (from item in data + where item.IsDeleted == false + orderby item.Id + select new PermissionItem + { + Url = item.Module?.LinkUrl, + Role = item.Role?.Id.ObjToString(), + }).ToList(); + } + // jwt + else + { + list = (from item in data + where item.IsDeleted == false + orderby item.Id + select new PermissionItem + { + Url = item.Module?.LinkUrl, + Role = item.Role?.Name.ObjToString(), + }).ToList(); + } + + requirement.Permissions = list; + } + + if (httpContext != null) + { + var questUrl = httpContext.Request.Path.Value.ToLower(); + + // 整体结构类似认证中间件UseAuthentication的逻辑,具体查看开源地址 + // https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs + httpContext.Features.Set(new AuthenticationFeature + { + OriginalPath = httpContext.Request.Path, + OriginalPathBase = httpContext.Request.PathBase + }); + + // Give any IAuthenticationRequestHandler schemes a chance to handle the request + // 主要作用是: 判断当前是否需要进行远程验证,如果是就进行远程验证 + 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()) + { + context.Fail(); + return; + } + } + + //判断请求是否拥有凭据,即有没有登录 + var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); + if (defaultAuthenticate != null) + { + var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); + + // 是否开启测试环境 + var isTestCurrent = AppSettings.app(new string[] { "AppSettings", "UseLoadTest" }).ObjToBool(); + + //result?.Principal不为空即登录成功 + if (result?.Principal != null || isTestCurrent || httpContext.IsSuccessSwagger()) + { + if (!isTestCurrent) httpContext.User = result.Principal; + + //应该要先校验用户的信息 再校验菜单权限相关的 + // JWT模式下校验当前用户状态 + // IDS4也可以校验,可以通过服务或者接口形式 + SysUserInfo user = new(); + if (!Permissions.IsUseIds4) + { + //校验用户 + user = await _userServices.QueryById(_user.ID, true); + if (user == null) + { + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户不存在或已被删除").MessageModel; + context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); + return; + } + + if (user.IsDeleted) + { + _user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被删除,禁止登录!").MessageModel; + context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg)); + 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.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.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 (!Permissions.IsUseIds4) + { + 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; + } + } + } + + // 获取当前用户的角色信息 + var currentUserRoles = new List(); + currentUserRoles = (from item in httpContext.User.Claims + where item.Type == ClaimTypes.Role + select item.Value).ToList(); + if (!currentUserRoles.Any()) + { + currentUserRoles = (from item in httpContext.User.Claims + where item.Type == "role" + select item.Value).ToList(); + } + + //超级管理员 默认拥有所有权限 + if (currentUserRoles.All(s => s != "SuperAdmin")) + { + var isMatchRole = false; + var permisssionRoles = + requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role)); + foreach (var item in permisssionRoles) + { + try + { + if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl) + { + isMatchRole = true; + break; + } + } + catch (Exception) + { + // ignored + } + } + + //验证权限 + if (currentUserRoles.Count <= 0 || !isMatchRole) + { + context.Fail(); + return; + } + } + + + context.Succeed(requirement); + return; + } + } + + //判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败 + if (!(questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && + (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))) + { + context.Fail(); + return; + } + } + + //context.Succeed(requirement); + } + } +} \ No newline at end of file diff --git a/Blog.Core/AuthHelper/Policys/PermissionItem.cs b/Blog.Core.Extensions/Authorizations/Policys/PermissionItem.cs similarity index 100% rename from Blog.Core/AuthHelper/Policys/PermissionItem.cs rename to Blog.Core.Extensions/Authorizations/Policys/PermissionItem.cs diff --git a/Blog.Core/AuthHelper/Policys/PermissionRequirement.cs b/Blog.Core.Extensions/Authorizations/Policys/PermissionRequirement.cs similarity index 100% rename from Blog.Core/AuthHelper/Policys/PermissionRequirement.cs rename to Blog.Core.Extensions/Authorizations/Policys/PermissionRequirement.cs diff --git a/Blog.Core.Extensions/AutoMapper/AutoMapperConfig.cs b/Blog.Core.Extensions/AutoMapper/AutoMapperConfig.cs new file mode 100644 index 00000000..2509feff --- /dev/null +++ b/Blog.Core.Extensions/AutoMapper/AutoMapperConfig.cs @@ -0,0 +1,18 @@ +using AutoMapper; + +namespace Blog.Core.AutoMapper +{ + /// + /// 静态全局 AutoMapper 配置文件 + /// + public class AutoMapperConfig + { + public static MapperConfiguration RegisterMappings() + { + return new MapperConfiguration(cfg => + { + cfg.AddProfile(new CustomProfile()); + }); + } + } +} diff --git a/Blog.Core.Extensions/AutoMapper/CustomProfile.cs b/Blog.Core.Extensions/AutoMapper/CustomProfile.cs new file mode 100644 index 00000000..7b05f079 --- /dev/null +++ b/Blog.Core.Extensions/AutoMapper/CustomProfile.cs @@ -0,0 +1,55 @@ +using AutoMapper; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; + +namespace Blog.Core.AutoMapper +{ + public class CustomProfile : Profile + { + /// + /// 配置构造函数,用来创建关系映射 + /// + 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 new file mode 100644 index 00000000..3ea16203 --- /dev/null +++ b/Blog.Core.Extensions/Blog.Core.Extensions.csproj @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEvent.cs b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEvent.cs new file mode 100644 index 00000000..f84842d6 --- /dev/null +++ b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEvent.cs @@ -0,0 +1,10 @@ +namespace Blog.Core.EventBus.EventHandling +{ + public class BlogQueryIntegrationEvent : IntegrationEvent + { + public string BlogId { get; private set; } + + public BlogQueryIntegrationEvent(string blogid) + => BlogId = blogid; + } +} diff --git a/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEventHandler.cs b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEventHandler.cs new file mode 100644 index 00000000..4e6384c2 --- /dev/null +++ b/Blog.Core.Extensions/EventHandling/BlogQueryIntegrationEventHandler.cs @@ -0,0 +1,33 @@ +using Blog.Core.Common; +using Blog.Core.EventBus.EventHandling; +using Blog.Core.IServices; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; + +namespace Blog.Core.EventBus +{ + public class BlogQueryIntegrationEventHandler : IIntegrationEventHandler + { + private readonly IBlogArticleServices _blogArticleServices; + private readonly ILogger _logger; + + public BlogQueryIntegrationEventHandler( + IBlogArticleServices blogArticleServices, + ILogger logger) + { + _blogArticleServices = blogArticleServices; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + 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.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/AllServicesMiddleware.cs b/Blog.Core.Extensions/Middlewares/AllServicesMiddleware.cs new file mode 100644 index 00000000..2346967c --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/AllServicesMiddleware.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// 查看所有注入的服务 + /// + public static class AllServicesMiddleware + { + public static void UseAllServicesMiddle(this IApplicationBuilder app, IServiceCollection _services) + { + if (app == null) throw new ArgumentNullException(nameof(app)); + + //List tsDIAutofac = new List(); + //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 autofacContainers = (app.ApplicationServices.GetAutofacRoot())?.ComponentRegistry?.Registrations; + + + app.Map("/allservices", builder => builder.Run(async context => + { + context.Response.ContentType = "text/html; charset=utf-8"; + await context.Response.WriteAsync(""); + + await context.Response.WriteAsync($"

    所有服务{_services.Count}个

    "); + + foreach (var svc in _services) + { + await context.Response.WriteAsync(""); + await context.Response.WriteAsync($""); + await context.Response.WriteAsync($""); + await context.Response.WriteAsync($""); + await context.Response.WriteAsync(""); + } + foreach (var item in autofacContainers.ToList()) + { + var interfaceType = item.Services; + foreach (var typeArray in interfaceType) + { + await context.Response.WriteAsync(""); + await context.Response.WriteAsync($""); + await context.Response.WriteAsync($""); + await context.Response.WriteAsync($""); + await context.Response.WriteAsync(""); + } + } + await context.Response.WriteAsync("
    类型生命周期Instance
    {svc.ServiceType.FullName}{svc.Lifetime}{svc.ImplementationType?.Name}
    {typeArray?.Description}{item.Lifetime}{item?.Target.Activator.ObjToString().Replace("(ReflectionActivator)", "")}
    "); + })); + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/ByPassAuthMiddleware.cs b/Blog.Core.Extensions/Middlewares/ByPassAuthMiddleware.cs new file mode 100644 index 00000000..c1f1f4a5 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/ByPassAuthMiddleware.cs @@ -0,0 +1,118 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// 测试用户,用来通过鉴权 + /// JWT:?userid=8&rolename=AdminTest + /// + public class ByPassAuthMiddleware + { + private readonly RequestDelegate _next; + // 定义变量:当前用户Id,会常驻内存。 + private string _currentUserId; + // 同理定义:当前角色名 + private string _currentRoleName; + public ByPassAuthMiddleware(RequestDelegate next) + { + _next = next; + _currentUserId = null; + _currentRoleName = null; + } + + + public async Task Invoke(HttpContext context) + { + var path = context.Request.Path; + // 请求地址,通过Url参数的形式,设置用户id和rolename + if (path == "/noauth") + { + var userid = context.Request.Query["userid"]; + if (!string.IsNullOrEmpty(userid)) + { + _currentUserId = userid; + } + + var rolename = context.Request.Query["rolename"]; + if (!string.IsNullOrEmpty(rolename)) + { + _currentRoleName = rolename; + } + + await SendOkResponse(context, $"User set to {_currentUserId} and Role set to {_currentRoleName}."); + } + // 重置角色信息 + else if (path == "/noauth/reset") + { + _currentUserId = null; + _currentRoleName = null; + + await SendOkResponse(context, $"User set to none. Token required for protected endpoints."); + } + else + { + var currentUserId = _currentUserId; + var currentRoleName = _currentRoleName; + + // 你也可以通过Header的形式。 + + //var authHeader = context.Request.Headers["Authorization"]; + //if (authHeader != StringValues.Empty) + //{ + // var header = authHeader.FirstOrDefault(); + // if (!string.IsNullOrEmpty(header) && header.StartsWith("User ") && header.Length > "User ".Length) + // { + // currentUserId = header.Substring("User ".Length); + // } + //} + + // 如果用户id和rolename都不为空 + // 可以配置HttpContext.User信息了,也就相当于登录了。 + if (!string.IsNullOrEmpty(currentUserId) && !string.IsNullOrEmpty(currentRoleName)) + { + var user = new ClaimsIdentity(new[] { + // 用户id + new Claim("sub", currentUserId), + + // 用户名、角色名 + new Claim("name", "Test user"), + new Claim(ClaimTypes.Name, "Test user"), + new Claim("role", currentRoleName), + new Claim(ClaimTypes.Role, currentRoleName), + + // 过期时间,两个:jwt/ids4 + new Claim ("exp",$"{new DateTimeOffset(DateTime.Now.AddDays(10100)).ToUnixTimeSeconds()}"), + new Claim(ClaimTypes.Expiration, DateTime.Now.AddDays(1).ToString()), + + // 其他参数 + new Claim("nonce", Guid.NewGuid().ToString()), + new Claim("http://schemas.microsoft.com/identity/claims/identityprovider", "ByPassAuthMiddleware"), + new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname","User"), + new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname","Microsoft")} + , "ByPassAuth"); + + context.User = new ClaimsPrincipal(user); + } + + await _next.Invoke(context); + } + } + + /// + /// 返回相应 + /// + /// + /// + /// + private async Task SendOkResponse(HttpContext context, string message) + { + context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK; + context.Response.ContentType = "text/plain"; + await context.Response.WriteAsync(message); + } + } +} + diff --git a/Blog.Core.Extensions/Middlewares/EncryptionRequestMiddleware.cs b/Blog.Core.Extensions/Middlewares/EncryptionRequestMiddleware.cs new file mode 100644 index 00000000..9cb78a30 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/EncryptionRequestMiddleware.cs @@ -0,0 +1,116 @@ +using Blog.Core.Common; +using Blog.Core.Common.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions +{ + /// + /// 自定义中间件 + /// 通过配置,对指定接口返回数据进行加密返回 + /// 可过滤文件流 + /// + public class EncryptionRequestMiddleware + { + private readonly RequestDelegate _next; + + public EncryptionRequestMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // 配置开关,过滤接口 + if (AppSettings.app("Middleware", "EncryptionRequest", "Enabled").ObjToBool()) + { + var isAllApis = AppSettings.app("Middleware", "EncryptionRequest", "AllApis").ObjToBool(); + var needEnApis = AppSettings.app("Middleware", "EncryptionRequest", "LimitApis"); + var path = context.Request.Path.Value.ToLower(); + if (isAllApis || (path.Length > 5 && needEnApis.Any(d => d.ToLower().Contains(path)))) + { + Console.WriteLine($"{isAllApis} -- {path}"); + + if (context.Request.Method.ToLower() == "post") + { + // 读取请求主体 + using StreamReader reader = new(context.Request.Body, Encoding.UTF8); + string requestBody = await reader.ReadToEndAsync(); + + // 检查是否有要解密的数据 + if (!string.IsNullOrEmpty(requestBody) && context.Request.Headers.ContainsKey("Content-Type") && + context.Request.Headers["Content-Type"].ToString().ToLower().Contains("application/json")) + { + // 解密数据 + string decryptedString = DecryptData(requestBody); + + // 更新请求主体中的数据 + context.Request.Body = GenerateStreamFromString(decryptedString); + } + } + else if (context.Request.Method.ToLower() == "get") + { + // 获取url参数 + string param = context.Request.Query["param"]; + + // 检查是否有要解密的数据 + if (!string.IsNullOrEmpty(param)) + { + // 解密数据 + string decryptedString = DecryptData(param); + + // 更新url参数值 + context.Request.QueryString = new QueryString($"?{decryptedString}"); + } + } + + await _next(context); + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + private string DecryptData(string encryptedData) + { + // 解密逻辑实现,可以根据你使用的加密算法和密钥进行自定义 + byte[] bytes = Convert.FromBase64String(encryptedData); + string originalString = Encoding.UTF8.GetString(bytes); + Console.WriteLine(originalString); + return originalString; + } + private static Stream GenerateStreamFromString(string s) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(s); + writer.Flush(); + stream.Position = 0; + return stream; + } + } + + public static class EncryptionRequestExtensions + { + /// + /// 自定义中间件 + /// 通过配置,对指定接口入参进行解密操作 + /// 注意:放到管道最外层 + /// + public static IApplicationBuilder UseEncryptionRequest(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/EncryptionResponseMiddleware.cs b/Blog.Core.Extensions/Middlewares/EncryptionResponseMiddleware.cs new file mode 100644 index 00000000..188c6f8d --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/EncryptionResponseMiddleware.cs @@ -0,0 +1,118 @@ +using Blog.Core.Common; +using Blog.Core.Common.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions +{ + /// + /// 自定义中间件 + /// 通过配置,对指定接口返回数据进行加密返回 + /// 可过滤文件流 + /// + public class EncryptionResponseMiddleware + { + private readonly RequestDelegate _next; + + public EncryptionResponseMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + // 配置开关,过滤接口 + if (AppSettings.app("Middleware", "EncryptionResponse", "Enabled").ObjToBool()) + { + var isAllApis = AppSettings.app("Middleware", "EncryptionResponse", "AllApis").ObjToBool(); + var needEnApis = AppSettings.app("Middleware", "EncryptionResponse", "LimitApis"); + var path = context.Request.Path.Value.ToLower(); + if (isAllApis || (path.Length > 5 && needEnApis.Any(d => d.ToLower().Contains(path)))) + { + Console.WriteLine($"{isAllApis} -- {path}"); + var responseCxt = context.Response; + var originalBodyStream = responseCxt.Body; + + // 创建一个新的内存流用于存储加密后的数据 + using var encryptedBodyStream = new MemoryStream(); + // 用新的内存流替换 responseCxt.Body + responseCxt.Body = encryptedBodyStream; + + // 执行下一个中间件请求管道 + await _next(context); + + //encryptedBodyStream.Seek(0, SeekOrigin.Begin); + //encryptedBodyStream.Position = 0; + + // 可以去掉某些流接口 + if (!context.Response.ContentType.ToLower().Contains("application/json")) + { + Console.WriteLine($"非json返回格式 {context.Response.ContentType}"); + //await encryptedBodyStream.CopyToAsync(originalBodyStream); + context.Response.Body = originalBodyStream; + return; + } + + // 读取加密后的数据 + //var encryptedBody = await new StreamReader(encryptedBodyStream).ReadToEndAsync(); + var encryptedBody = responseCxt.GetResponseBody(); + + if (encryptedBody.IsNotEmptyOrNull()) + { + dynamic jsonObject = JsonConvert.DeserializeObject(encryptedBody); + string statusCont = jsonObject.status; + var status = statusCont.ObjToInt(); + string msg = jsonObject.msg; + string successCont = jsonObject.success; + var success = successCont.ObjToBool(); + dynamic responseCnt = success ? jsonObject.response : ""; + string s = "1"; + // 这里换成自己的任意加密方式 + var response = responseCnt.ToString() != "" ? Convert.ToBase64String(Encoding.UTF8.GetBytes(responseCnt.ToString())) : ""; + string resJson = JsonConvert.SerializeObject(new { response, msg, status, s, success }); + + context.Response.Clear(); + responseCxt.ContentType = "application/json"; + + //await using var streamlriter = new StreamWriter(originalBodyStream, leaveOpen: true); + //await streamlriter.WriteAsync(resJson); + + var encryptedData = Encoding.UTF8.GetBytes(resJson); + responseCxt.ContentLength = encryptedData.Length; + await originalBodyStream.WriteAsync(encryptedData, 0, encryptedData.Length); + + responseCxt.Body = originalBodyStream; + } + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + } + + public static class EncryptionResponseExtensions + { + /// + /// 自定义中间件 + /// 通过配置,对指定接口返回数据进行加密返回 + /// 可过滤文件流 + /// 注意:放到管道最外层 + /// + public static IApplicationBuilder UseEncryptionResponse(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs b/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs new file mode 100644 index 00000000..dc2cd17d --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/ExceptionHandlerMiddleware.cs @@ -0,0 +1,58 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Blog.Core.Model; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; + +namespace Blog.Core.Extensions.Middlewares +{ + public class ExceptionHandlerMiddleware + { + private readonly RequestDelegate _next; + + public ExceptionHandlerMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex); + } + } + + private async Task HandleExceptionAsync(HttpContext context, Exception e) + { + if (e == null) return; + + await WriteExceptionAsync(context, e).ConfigureAwait(false); + } + + private static async Task WriteExceptionAsync(HttpContext context, Exception e) + { + 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, message).MessageModel)) + .ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/FluentResponseBodyMiddleware.cs b/Blog.Core.Extensions/Middlewares/FluentResponseBodyMiddleware.cs new file mode 100644 index 00000000..dfb5d19e --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/FluentResponseBodyMiddleware.cs @@ -0,0 +1,21 @@ +using System.IO; +using Blog.Core.Common.Https; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Features; + +namespace Blog.Core.Extensions.Middlewares; + +public static class FluentResponseBodyMiddleware +{ + public static IApplicationBuilder UseResponseBodyRead(this IApplicationBuilder app) + { + return app.Use(async (context, next) => + { + await using var swapStream = new FluentHttpResponseStream(context!.Features!.Get()!, + context!.Features!.Get()!); + context.Response.Body = swapStream; + await next(context); + context.Response.Body.Seek(0, SeekOrigin.Begin); + }); + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs b/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs new file mode 100644 index 00000000..7fe68fc4 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/IpLimitMiddleware.cs @@ -0,0 +1,32 @@ +using AspNetCoreRateLimit; +using Blog.Core.Common; +using Microsoft.AspNetCore.Builder; +using System; +using Serilog; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// ip 限流 + /// + public static class IpLimitMiddleware + { + public static void UseIpLimitMiddle(this IApplicationBuilder app) + { + if (app == null) throw new ArgumentNullException(nameof(app)); + + try + { + if (AppSettings.app("Middleware", "IpRateLimit", "Enabled").ObjToBool()) + { + app.UseIpRateLimiting(); + } + } + catch (Exception e) + { + 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/AuthHelper/OverWrite/JwtTokenAuth.cs b/Blog.Core.Extensions/Middlewares/JwtTokenAuthMiddleware.cs similarity index 65% rename from Blog.Core/AuthHelper/OverWrite/JwtTokenAuth.cs rename to Blog.Core.Extensions/Middlewares/JwtTokenAuthMiddleware.cs index ff0c368d..76786f87 100644 --- a/Blog.Core/AuthHelper/OverWrite/JwtTokenAuth.cs +++ b/Blog.Core.Extensions/Middlewares/JwtTokenAuthMiddleware.cs @@ -1,17 +1,16 @@ -using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; +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 { /// /// @@ -21,7 +20,7 @@ public class JwtTokenAuth /// /// /// - public JwtTokenAuth(RequestDelegate next) + public JwtTokenAuthMiddleware(RequestDelegate next) { _next = next; } @@ -29,12 +28,12 @@ public JwtTokenAuth(RequestDelegate next) private void PreProceed(HttpContext next) { - Console.WriteLine($"{DateTime.Now} middleware invoke preproceed"); + //Console.WriteLine($"{DateTime.Now} middleware invoke preproceed"); //... } private void PostProceed(HttpContext next) { - Console.WriteLine($"{DateTime.Now} middleware invoke postproceed"); + //Console.WriteLine($"{DateTime.Now} middleware invoke postproceed"); //.... } @@ -62,15 +61,16 @@ public Task Invoke(HttpContext httpContext) { if (tokenHeader.Length >= 128) { + //Console.WriteLine($"{DateTime.Now} token :{tokenHeader}"); TokenModelJwt tm = JwtHelper.SerializeJwt(tokenHeader); //授权 - var claimList = new List(); - var claim = new Claim(ClaimTypes.Role, tm.Role); - claimList.Add(claim); - var identity = new ClaimsIdentity(claimList); - var principal = new ClaimsPrincipal(identity); - httpContext.User = principal; + //var claimList = new List(); + //var claim = new Claim(ClaimTypes.Role, tm.Role); + //claimList.Add(claim); + //var identity = new ClaimsIdentity(claimList); + //var principal = new ClaimsPrincipal(identity); + //httpContext.User = principal; } } @@ -87,5 +87,6 @@ public Task Invoke(HttpContext httpContext) } } + } diff --git a/Blog.Core.Extensions/Middlewares/MiddlewareHelpers.cs b/Blog.Core.Extensions/Middlewares/MiddlewareHelpers.cs new file mode 100644 index 00000000..fa5c3835 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/MiddlewareHelpers.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Builder; + +namespace Blog.Core.Extensions.Middlewares +{ + public static class MiddlewareHelpers + { + /// + /// 自定义授权中间件 + /// + /// + /// + public static IApplicationBuilder UseJwtTokenAuth(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + + /// + /// 请求响应中间件 + /// + /// + /// + public static IApplicationBuilder UseRequestResponseLogMiddle(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + + /// + /// SignalR中间件 + /// + /// + /// + public static IApplicationBuilder UseSignalRSendMiddle(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + + /// + /// 异常处理中间件 + /// + /// + /// + public static IApplicationBuilder UseExceptionHandlerMiddle(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + + /// + /// IP请求中间件 + /// + /// + /// + public static IApplicationBuilder UseIpLogMiddle(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + + /// + /// 用户访问中间件 + /// + /// + /// + public static IApplicationBuilder UseRecordAccessLogsMiddle(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs b/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs new file mode 100644 index 00000000..49e3b70d --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/MiniProfilerMiddleware.cs @@ -0,0 +1,33 @@ +using Blog.Core.Common; +using Microsoft.AspNetCore.Builder; +using System; +using Serilog; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// MiniProfiler性能分析 + /// + public static class MiniProfilerMiddleware + { + public static void UseMiniProfilerMiddleware(this IApplicationBuilder app) + { + if (app == null) throw new ArgumentNullException(nameof(app)); + + try + { + if (AppSettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool()) + { + // 性能分析 + app.UseMiniProfiler(); + + } + } + catch (Exception e) + { + Log.Error($"An error was reported when starting the MiniProfilerMildd.\n{e.Message}"); + throw; + } + } + } +} diff --git a/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs new file mode 100644 index 00000000..cee13581 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/RecordAccessLogsMiddleware.cs @@ -0,0 +1,131 @@ +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; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// 中间件 + /// 记录用户方访问数据 + /// + public class RecordAccessLogsMiddleware + { + /// + /// + /// + private readonly RequestDelegate _next; + + private readonly IUser _user; + private readonly ILogger _logger; + private readonly IWebHostEnvironment _environment; + private Stopwatch _stopwatch; + + /// + /// + /// + /// + public RecordAccessLogsMiddleware(RequestDelegate next, IUser user, ILogger logger, + IWebHostEnvironment environment) + { + _next = next; + _user = user; + _logger = logger; + _environment = environment; + _stopwatch = new Stopwatch(); + } + + public async Task InvokeAsync(HttpContext context) + { + if (AppSettings.app("Middleware", "RecordAccessLogs", "Enabled").ObjToBool()) + { + var api = context.Request.Path.ObjToString().TrimEnd('/').ToLower(); + var ignoreApis = AppSettings.app("Middleware", "RecordAccessLogs", "IgnoreApis"); + + // 过滤,只有接口 + if (api.Contains("api") && !ignoreApis.Contains(api)) + { + _stopwatch.Restart(); + var userAccessModel = new UserAccessModel(); + + HttpRequest request = context.Request; + + userAccessModel.API = api; + userAccessModel.User = _user.Name; + userAccessModel.IP = IpLogMiddleware.GetClientIP(context); + userAccessModel.BeginTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + userAccessModel.RequestMethod = request.Method; + userAccessModel.Agent = request.Headers["User-Agent"].ObjToString(); + + + // 获取请求body内容 + if (request.Method.ToLower().Equals("post") || request.Method.ToLower().Equals("put")) + { + // 启用倒带功能,就可以让 Request.Body 可以再次读取 + request.EnableBuffering(); + + Stream stream = request.Body; + byte[] buffer = new byte[request.ContentLength.Value]; + stream.Read(buffer, 0, buffer.Length); + userAccessModel.RequestData = Encoding.UTF8.GetString(buffer); + + request.Body.Position = 0; + } + else if (request.Method.ToLower().Equals("get") || request.Method.ToLower().Equals("delete")) + { + userAccessModel.RequestData = + HttpUtility.UrlDecode(request.QueryString.ObjToString(), Encoding.UTF8); + } + + await _next(context); + + // 响应完成记录时间和存入日志 + context.Response.OnCompleted(() => + { + _stopwatch.Stop(); + + userAccessModel.OPTime = _stopwatch.ElapsedMilliseconds + "ms"; + + // 自定义log输出 + var requestInfo = JsonConvert.SerializeObject(userAccessModel); + _logger.LogInformation("RecordAccessLogs:{TraceIdentifier}: {RequestInfo}", + context.TraceIdentifier, + requestInfo); + return Task.CompletedTask; + }); + } + else + { + await _next(context); + } + } + else + { + await _next(context); + } + } + } + + public class UserAccessModel + { + public string User { get; set; } + public string IP { get; set; } + public string API { get; set; } + public string BeginTime { get; set; } + public string OPTime { get; set; } + public string RequestMethod { get; set; } + public string 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/SignalRSendMiddleware.cs b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs new file mode 100644 index 00000000..f1e4ee51 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/SignalRSendMiddleware.cs @@ -0,0 +1,46 @@ +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; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// 中间件 + /// SignalR发送数据 + /// + public class SignalRSendMiddleware + { + /// + /// + /// + private readonly RequestDelegate _next; + + private readonly IHubContext _hubContext; + + /// + /// + /// + /// + /// + public SignalRSendMiddleware(RequestDelegate next, IHubContext hubContext) + { + _next = next; + _hubContext = hubContext; + } + + + public async Task InvokeAsync(HttpContext context) + { + if (AppSettings.app("Middleware", "SignalR", "Enabled").ObjToBool()) + { + //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/SwaggerMiddleware.cs b/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs new file mode 100644 index 00000000..141ffe40 --- /dev/null +++ b/Blog.Core.Extensions/Middlewares/SwaggerMiddleware.cs @@ -0,0 +1,54 @@ +using Blog.Core.Common; +using Microsoft.AspNetCore.Builder; +using Swashbuckle.AspNetCore.SwaggerUI; +using System; +using System.IO; +using System.Linq; +using Serilog; +using static Blog.Core.Extensions.CustomApiVersion; + +namespace Blog.Core.Extensions.Middlewares +{ + /// + /// Swagger中间件 + /// + public static class SwaggerMiddleware + { + public static void UseSwaggerMiddle(this IApplicationBuilder app, Func streamHtml) + { + if (app == null) throw new ArgumentNullException(nameof(app)); + + app.UseSwagger(); + 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}"); }); + + 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); + throw new Exception(msg); + } + + c.IndexStream = streamHtml; + c.DocExpansion(DocExpansion.None); //->修改界面打开时自动折叠 + + if (Permissions.IsUseIds4) + { + 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/NacosListenConfigurationTask.cs b/Blog.Core.Extensions/NacosConfig/NacosListenConfigurationTask.cs new file mode 100644 index 00000000..d1554d15 --- /dev/null +++ b/Blog.Core.Extensions/NacosConfig/NacosListenConfigurationTask.cs @@ -0,0 +1,88 @@ +using Blog.Core.Common.Helper; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Nacos.V2; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions.NacosConfig +{ + /// + /// Nacos配置文件变更事件 + /// + public class NacosListenConfigurationTask : BackgroundService + { + private readonly INacosConfigService _configClient; + /// + /// Nacos 配置文件监听事件 + /// + private NacosConfigListener nacosConfigListener = new NacosConfigListener(); + + /// + /// 重载方法 + /// + /// + /// + public NacosListenConfigurationTask(INacosConfigService configClient, IServiceProvider serviceProvider) + { + _configClient = configClient; + } + + /// + /// 执行 + /// + /// + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + // Add listener + await _configClient.AddListener("blog.Core.Api.json", "DEFAULT_GROUP", nacosConfigListener); + } + catch (Exception) + { + } + } + + /// + /// 停止 + /// + /// + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + // Remove listener + await _configClient.RemoveListener("blog.Core.Api.json", "DEFAULT_GROUP", nacosConfigListener); + + await base.StopAsync(cancellationToken); + } + } + + /// + /// 配置监听事件 + /// + public class NacosConfigListener : IListener + { + /// + /// 收到配置文件变更 + /// + /// + public void ReceiveConfigInfo(string configInfo) + { + var _configurationBuilder = new ConfigurationBuilder(); + _configurationBuilder.Sources.Clear(); + var buffer = System.Text.Encoding.Default.GetBytes(configInfo); + System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer); + _configurationBuilder.AddJsonStream(ms); + var configuration = _configurationBuilder.Build(); + ms.Dispose(); + + // 读取配置 将nacos配置中心读取到的配置 替换掉.net core 内存中的 configuration + // 当前监听到配置配置 应该重新断开 重连 刷新等一些中间件操作 + // 比如 mq redis 等其他跟配置相关的中间件 + JsonConfigSettings.Configuration = configuration; + } + } +} diff --git a/Blog.Core.Extensions/NacosConfig/NacosListenNamingTask.cs b/Blog.Core.Extensions/NacosConfig/NacosListenNamingTask.cs new file mode 100644 index 00000000..3215703d --- /dev/null +++ b/Blog.Core.Extensions/NacosConfig/NacosListenNamingTask.cs @@ -0,0 +1,97 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Microsoft.Extensions.Hosting; +using Nacos.V2; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions.NacosConfig +{ + /// + /// + /// + public class NacosListenNamingTask : BackgroundService + { + private readonly INacosNamingService _nacosNamingService; + + /// + /// 监听事件 + /// + private NamingServiceEventListener eventListener = new NamingServiceEventListener(); + + /// + /// + /// + /// + /// + /// + public NacosListenNamingTask(INacosNamingService nacosNamingService) + { + _nacosNamingService = nacosNamingService; + } + + /// + /// 订阅服务变化 + /// + /// + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + // Add listener + await _nacosNamingService.Subscribe(JsonConfigSettings.NacosServiceName, Nacos.V2.Common.Constants.DEFAULT_GROUP, eventListener); + var instance = new Nacos.V2.Naming.Dtos.Instance() + { + ServiceName = JsonConfigSettings.NacosServiceName, + ClusterName = Nacos.V2.Common.Constants.DEFAULT_CLUSTER_NAME, + Ip = IpHelper.GetCurrentIp(null), + Port = JsonConfigSettings.NacosPort, + Enabled = true, + Weight = 1000,// 权重 默认1000 + Metadata = JsonConfigSettings.NacosMetadata + }; + await _nacosNamingService.RegisterInstance(JsonConfigSettings.NacosServiceName, Nacos.V2.Common.Constants.DEFAULT_GROUP, instance); + ConsoleHelper.WriteSuccessLine($"Nacos connect: Success!"); + } + + // 程序停止 + public override async Task StopAsync(CancellationToken cancellationToken) + { + // Remove listener + await _nacosNamingService.Unsubscribe(JsonConfigSettings.NacosServiceName, Nacos.V2.Common.Constants.DEFAULT_GROUP, eventListener); + await _nacosNamingService.DeregisterInstance(JsonConfigSettings.NacosServiceName, Nacos.V2.Common.Constants.DEFAULT_GROUP, IpHelper.GetCurrentIp(null), JsonConfigSettings.NacosPort); + + await base.StopAsync(cancellationToken); + } + } + + /// + /// 服务变更事件监听 + /// + public class NamingServiceEventListener : IEventListener + { + /// + /// + /// + //public static redisHelper _redisCachqManager = new redisHelper(); + + /// + /// 监听事件 + /// + /// + /// + public Task OnEvent(Nacos.V2.IEvent @event) + { + if (@event is Nacos.V2.Naming.Event.InstancesChangeEvent e) + { + Console.WriteLine($"==========收到服务变更事件=======》{Newtonsoft.Json.JsonConvert.SerializeObject(e)}"); + + // 配置有变动后 刷新redis配置 刷新 mq配置 + + //_redisCachqManager.DisposeRedisConnection(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Blog.Core.Extensions/Redis/IRedisBasketRepository.cs b/Blog.Core.Extensions/Redis/IRedisBasketRepository.cs new file mode 100644 index 00000000..85408160 --- /dev/null +++ b/Blog.Core.Extensions/Redis/IRedisBasketRepository.cs @@ -0,0 +1,49 @@ +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions +{ + /// + /// Redis缓存接口 + /// + [Description("普通缓存考虑直接使用ICaching,如果要使用Redis队列等还是使用此类")] + public interface IRedisBasketRepository + { + + //获取 Reids 缓存值 + Task GetValue(string key); + + //获取值,并序列化 + Task Get(string key); + + //保存 + Task Set(string key, object value, TimeSpan cacheTime); + + //判断是否存在 + Task Exist(string key); + + //移除某一个缓存值 + Task Remove(string key); + + //全部清除 + Task Clear(); + + Task ListRangeAsync(string redisKey); + Task ListLeftPushAsync(string redisKey, string redisValue, int db = -1); + Task ListRightPushAsync(string redisKey, string redisValue, int db = -1); + Task ListRightPushAsync(string redisKey, IEnumerable redisValue, int db = -1); + Task ListLeftPopAsync(string redisKey, int db = -1) where T : class; + Task ListRightPopAsync(string redisKey, int db = -1) where T : class; + Task ListLeftPopAsync(string redisKey, int db = -1); + Task ListRightPopAsync(string redisKey, int db = -1); + Task ListLengthAsync(string redisKey, int db = -1); + Task> ListRangeAsync(string redisKey, int db = -1); + Task> ListRangeAsync(string redisKey, int start, int stop, int db = -1); + Task ListDelRangeAsync(string redisKey, string redisValue, long type = 0, int db = -1); + Task ListClearAsync(string redisKey, int db = -1); + + } +} diff --git a/Blog.Core.Extensions/Redis/RedisBasketRepository.cs b/Blog.Core.Extensions/Redis/RedisBasketRepository.cs new file mode 100644 index 00000000..822f9253 --- /dev/null +++ b/Blog.Core.Extensions/Redis/RedisBasketRepository.cs @@ -0,0 +1,246 @@ +using Blog.Core.Common; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +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; + private readonly ConnectionMultiplexer _redis; + private readonly IDatabase _database; + + public RedisBasketRepository(ILogger logger, ConnectionMultiplexer redis) + { + _logger = logger; + _redis = redis; + _database = redis.GetDatabase(); + } + + private IServer GetServer() + { + var endpoint = _redis.GetEndPoints(); + return _redis.GetServer(endpoint.First()); + } + + public async Task Clear() + { + foreach (var endPoint in _redis.GetEndPoints()) + { + var server = GetServer(); + foreach (var key in server.Keys()) + { + await _database.KeyDeleteAsync(key); + } + } + } + + public async Task Exist(string key) + { + return await _database.KeyExistsAsync(key); + } + + public async Task GetValue(string key) + { + return await _database.StringGetAsync(key); + } + + public async Task Remove(string key) + { + await _database.KeyDeleteAsync(key); + } + + public async Task Set(string key, object value, TimeSpan cacheTime) + { + if (value != null) + { + if (value is string cacheValue) + { + // 字符串无需序列化 + await _database.StringSetAsync(key, cacheValue, cacheTime); + } + else + { + //序列化,将object值生成RedisValue + await _database.StringSetAsync(key, SerializeHelper.Serialize(value), cacheTime); + } + } + } + + public async Task Get(string key) + { + var value = await _database.StringGetAsync(key); + if (value.HasValue) + { + //需要用的反序列化,将Redis存储的Byte[],进行反序列化 + return SerializeHelper.Deserialize(value); + } + else + { + return default(TEntity); + } + } + + + + + /// + /// 根据key获取RedisValue + /// + /// + /// + /// + public async Task ListRangeAsync(string redisKey) + { + return await _database.ListRangeAsync(redisKey); + } + + /// + /// 在列表头部插入值。如果键不存在,先创建再插入值 + /// + /// + /// + /// + public async Task ListLeftPushAsync(string redisKey, string redisValue, int db = -1) + { + return await _database.ListLeftPushAsync(redisKey, redisValue); + } + /// + /// 在列表尾部插入值。如果键不存在,先创建再插入值 + /// + /// + /// + /// + public async Task ListRightPushAsync(string redisKey, string redisValue, int db = -1) + { + return await _database.ListRightPushAsync(redisKey, redisValue); + } + + /// + /// 在列表尾部插入数组集合。如果键不存在,先创建再插入值 + /// + /// + /// + /// + public async Task ListRightPushAsync(string redisKey, IEnumerable redisValue, int db = -1) + { + var redislist = new List(); + foreach (var item in redisValue) + { + redislist.Add(item); + } + return await _database.ListRightPushAsync(redisKey, redislist.ToArray()); + } + + + /// + /// 移除并返回存储在该键列表的第一个元素 反序列化 + /// + /// + /// + public async Task ListLeftPopAsync(string redisKey, int db = -1) where T : class + { + return JsonConvert.DeserializeObject(await _database.ListLeftPopAsync(redisKey)); + } + + /// + /// 移除并返回存储在该键列表的最后一个元素 反序列化 + /// 只能是对象集合 + /// + /// + /// + public async Task ListRightPopAsync(string redisKey, int db = -1) where T : class + { + return JsonConvert.DeserializeObject(await _database.ListRightPopAsync(redisKey)); + } + + /// + /// 移除并返回存储在该键列表的第一个元素 + /// + /// + /// + /// + public async Task ListLeftPopAsync(string redisKey, int db = -1) + { + return await _database.ListLeftPopAsync(redisKey); + } + + /// + /// 移除并返回存储在该键列表的最后一个元素 + /// + /// + /// + /// + /// + public async Task ListRightPopAsync(string redisKey, int db = -1) + { + return await _database.ListRightPopAsync(redisKey); + } + + /// + /// 列表长度 + /// + /// + /// + /// + public async Task ListLengthAsync(string redisKey, int db = -1) + { + return await _database.ListLengthAsync(redisKey); + } + + /// + /// 返回在该列表上键所对应的元素 + /// + /// + /// + public async Task> ListRangeAsync(string redisKey, int db = -1) + { + var result = await _database.ListRangeAsync(redisKey); + return result.Select(o => o.ToString()); + } + + /// + /// 根据索引获取指定位置数据 + /// + /// + /// + /// + /// + /// + public async Task> ListRangeAsync(string redisKey, int start, int stop, int db = -1) + { + var result = await _database.ListRangeAsync(redisKey, start, stop); + return result.Select(o => o.ToString()); + } + + /// + /// 删除List中的元素 并返回删除的个数 + /// + /// key + /// 元素 + /// 大于零 : 从表头开始向表尾搜索,小于零 : 从表尾开始向表头搜索,等于零:移除表中所有与 VALUE 相等的值 + /// + /// + public async Task ListDelRangeAsync(string redisKey, string redisValue, long type = 0, int db = -1) + { + return await _database.ListRemoveAsync(redisKey, redisValue, type); + } + + /// + /// 清空List + /// + /// + /// + public async Task ListClearAsync(string redisKey, int db = -1) + { + await _database.ListTrimAsync(redisKey, 1, 0); + } + } +} diff --git a/Blog.Core.Extensions/Redis/RedisSubscribe.cs b/Blog.Core.Extensions/Redis/RedisSubscribe.cs new file mode 100644 index 00000000..55c46fa6 --- /dev/null +++ b/Blog.Core.Extensions/Redis/RedisSubscribe.cs @@ -0,0 +1,26 @@ +using Blog.Core.IServices; +using InitQ.Abstractions; +using InitQ.Attributes; +using System; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions.Redis +{ + public class RedisSubscribe : IRedisSubscribe + { + private readonly IBlogArticleServices _blogArticleServices; + + public RedisSubscribe(IBlogArticleServices blogArticleServices) + { + _blogArticleServices = blogArticleServices; + } + + [Subscribe(RedisMqKey.Loging)] + private async Task SubRedisLoging(string msg) + { + Console.WriteLine($"订阅者 1 从 队列{RedisMqKey.Loging} 消费到/接受到 消息:{msg}"); + + await Task.CompletedTask; + } + } +} diff --git a/Blog.Core.Extensions/Redis/RedisSubscribe2.cs b/Blog.Core.Extensions/Redis/RedisSubscribe2.cs new file mode 100644 index 00000000..6261b061 --- /dev/null +++ b/Blog.Core.Extensions/Redis/RedisSubscribe2.cs @@ -0,0 +1,18 @@ +using InitQ.Abstractions; +using InitQ.Attributes; +using System; +using System.Threading.Tasks; + +namespace Blog.Core.Extensions.Redis +{ + public class RedisSubscribe2 : IRedisSubscribe + { + [Subscribe(RedisMqKey.Loging)] + private async Task SubRedisLoging(string msg) + { + Console.WriteLine($"订阅者 2 从 队列{RedisMqKey.Loging} 消费到/接受到 消息:{msg}"); + + await Task.CompletedTask; + } + } +} 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 new file mode 100644 index 00000000..7cb098ff --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/AppConfigSetup.cs @@ -0,0 +1,109 @@ +using Blog.Core.Common; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Text; +using Blog.Core.Common.DB; + +namespace Blog.Core.Extensions +{ + /// + /// 项目 启动服务 + /// + public static class AppConfigSetup + { + public static void AddAppTableConfigSetup(this IServiceCollection services, IHostEnvironment env) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + if (AppSettings.app(new string[] { "Startup", "AppConfigAlert", "Enabled" }).ObjToBool()) + { + if (env.IsDevelopment()) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + Console.OutputEncoding = Encoding.GetEncoding("GB2312"); + } + + #region 程序配置 + + List configInfos = new() + { + 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" }, + }; + + new ConsoleTable() + { + TitleString = "Blog.Core 配置集", + Columns = new string[] { "配置名称", "配置信息/是否启动" }, + Rows = configInfos, + EnableCount = false, + Alignment = Alignment.Left, + ColumnBlankNum = 4, + TableStyle = TableStyle.Alternative + }.Writer(ConsoleColor.Blue); + Console.WriteLine(); + + #endregion 程序配置 + + #region AOP + + List aopInfos = new() + { + new string[] { "缓存AOP", AppSettings.app("AppSettings", "CachingAOP", "Enabled") }, + new string[] { "服务日志AOP", AppSettings.app("AppSettings", "LogAOP", "Enabled") }, + new string[] { "事务AOP", AppSettings.app("AppSettings", "TranAOP", "Enabled") }, + new string[] { "服务审计AOP", AppSettings.app("AppSettings", "UserAuditAOP", "Enabled") }, + new string[] { "Sql执行AOP", AppSettings.app("AppSettings", "SqlAOP", "Enabled") }, + new string[] { "Sql执行AOP控制台输出", AppSettings.app("AppSettings", "SqlAOP", "LogToConsole", "Enabled") }, + }; + + new ConsoleTable + { + TitleString = "AOP", + Columns = new string[] { "配置名称", "配置信息/是否启动" }, + Rows = aopInfos, + EnableCount = false, + Alignment = Alignment.Left, + ColumnBlankNum = 7, + TableStyle = TableStyle.Alternative + }.Writer(ConsoleColor.Blue); + Console.WriteLine(); + + #endregion AOP + + #region 中间件 + + List MiddlewareInfos = new() + { + new string[] { "请求纪录中间件", AppSettings.app("Middleware", "RecordAccessLogs", "Enabled") }, + new string[] { "IP记录中间件", AppSettings.app("Middleware", "IPLog", "Enabled") }, + new string[] { "请求响应日志中间件", AppSettings.app("Middleware", "RequestResponseLog", "Enabled") }, + new string[] { "SingnalR实时发送请求数据中间件", AppSettings.app("Middleware", "SignalR", "Enabled") }, + new string[] { "IP限流中间件", AppSettings.app("Middleware", "IpRateLimit", "Enabled") }, + new string[] { "性能分析中间件", AppSettings.app("Startup", "MiniProfiler", "Enabled") }, + new string[] { "Consul注册服务", AppSettings.app("Middleware", "Consul", "Enabled") }, + }; + + new ConsoleTable + { + 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 new file mode 100644 index 00000000..aa8c8bd0 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/Authentication_Ids4Setup.cs @@ -0,0 +1,36 @@ +using Blog.Core.AuthHelper; +using Blog.Core.Common; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// Ids4权限 认证服务 + /// + public static class Authentication_Ids4Setup + { + public static void AddAuthentication_Ids4Setup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + + // 添加Identityserver4认证 + services.AddAuthentication(o => + { + o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + o.DefaultChallengeScheme = nameof(ApiResponseHandler); + o.DefaultForbidScheme = nameof(ApiResponseHandler); + }) + .AddJwtBearer(options => + { + options.Authority = AppSettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" }); + options.RequireHttpsMetadata = false; + 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 new file mode 100644 index 00000000..d9048c6e --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/Authentication_JWTSetup.cs @@ -0,0 +1,111 @@ +using Blog.Core.AuthHelper; +using Blog.Core.Common; +using Blog.Core.Common.AppConfig; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Text; + +namespace Blog.Core.Extensions +{ + /// + /// JWT权限 认证服务 + /// + public static class Authentication_JWTSetup + { + public static void AddAuthentication_JWTSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(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 signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); + + // 令牌验证参数 + var tokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = signingKey, + ValidateIssuer = true, + ValidIssuer = Issuer,//发行人 + ValidateAudience = true, + ValidAudience = Audience,//订阅人 + ValidateLifetime = true, + ClockSkew = TimeSpan.FromSeconds(30), + RequireExpirationTime = true, + }; + + // 开启Bearer认证 + services.AddAuthentication(o => + { + o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + o.DefaultChallengeScheme = nameof(ApiResponseHandler); + o.DefaultForbidScheme = nameof(ApiResponseHandler); + }) + // 添加JwtBearer服务 + .AddJwtBearer(o => + { + 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["Token-Error"] = context.ErrorDescription; + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + var jwtHandler = new JwtSecurityTokenHandler(); + var token = context.Request.Headers["Authorization"].ObjToString().Replace("Bearer ", ""); + + if (token.IsNotEmptyOrNull() && jwtHandler.CanReadToken(token)) + { + var jwtToken = jwtHandler.ReadJwtToken(token); + + if (jwtToken.Issuer != Issuer) + { + context.Response.Headers["Token-Error-Iss"] = "issuer is wrong!"; + } + + if (jwtToken.Audiences.FirstOrDefault() != Audience) + { + context.Response.Headers["Token-Error-Aud"] = "Audience is wrong!"; + } + } + + + // 如果过期,则把<是否过期>添加到,返回头信息中 + if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) + { + context.Response.Headers["Token-Expired"] = "true"; + } + return Task.CompletedTask; + } + }; + }) + .AddScheme(nameof(ApiResponseHandler), o => { }); + + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/AuthorizationSetup.cs b/Blog.Core.Extensions/ServiceExtensions/AuthorizationSetup.cs new file mode 100644 index 00000000..ae0a6383 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/AuthorizationSetup.cs @@ -0,0 +1,97 @@ +using Blog.Core.AuthHelper; +using Blog.Core.Common; +using Blog.Core.Common.AppConfig; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Text; + +namespace Blog.Core.Extensions +{ + /// + /// 系统 授权服务 配置 + /// + public static class AuthorizationSetup + { + public static void AddAuthorizationSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + // 以下四种常见的授权方式。 + + // 1、这个很简单,其他什么都不用做, 只需要在API层的controller上边,增加特性即可 + // [Authorize(Roles = "Admin,System")] + + + // 2、这个和上边的异曲同工,好处就是不用在controller中,写多个 roles 。 + // 然后这么写 [Authorize(Policy = "Admin")] + services.AddAuthorization(options => + { + options.AddPolicy("Client", policy => policy.RequireRole("Client").Build()); + options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build()); + options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System")); + options.AddPolicy("A_S_O", policy => policy.RequireRole("Admin", "System", "Others")); + }); + + + + + #region 参数 + //读取配置文件 + 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 signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); + + // 如果要数据库动态绑定,这里先留个空,后边处理器里动态赋值 + var permission = new List(); + + // 角色与接口的权限要求参数 + var permissionRequirement = new PermissionRequirement( + "/api/denied",// 拒绝授权的跳转地址(目前无用) + permission, + ClaimTypes.Role,//基于角色的授权 + Issuer,//发行人 + Audience,//听众 + signingCredentials,//签名凭据 + expiration: TimeSpan.FromSeconds(60 * 60)//接口的过期时间 + ); + #endregion + // 3、自定义复杂的策略授权 + services.AddAuthorization(options => + { + options.AddPolicy(Permissions.Name, + policy => policy.Requirements.Add(permissionRequirement)); + }); + + + // 4、基于Scope策略授权 + //services.AddAuthorization(options => + //{ + // options.AddPolicy("Scope_BlogModule_Policy", builder => + // { + // //客户端Scope中包含blog.core.api.BlogModule才能访问 + // // 同时引用nuget包:IdentityServer4.AccessTokenValidation + // builder.RequireScope("blog.core.api.BlogModule"); + // }); + + // // 其他 Scope 策略 + // // ... + + //}); + + // 这里冗余写了一次,因为很多人看不到 + services.AddSingleton(); + // 注入权限处理器 + services.AddScoped(); + services.AddSingleton(permissionRequirement); + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/AutoMapperSetup.cs b/Blog.Core.Extensions/ServiceExtensions/AutoMapperSetup.cs new file mode 100644 index 00000000..55755d9e --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/AutoMapperSetup.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using Blog.Core.AutoMapper; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// Automapper 启动服务 + /// + public static class AutoMapperSetup + { + public static void AddAutoMapperSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddAutoMapper(typeof(AutoMapperConfig)); + AutoMapperConfig.RegisterMappings(); + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs b/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs new file mode 100644 index 00000000..4236bfb7 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/AutofacModuleRegister.cs @@ -0,0 +1,122 @@ +using Autofac; +using Autofac.Extras.DynamicProxy; +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 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 + { + protected override void Load(ContainerBuilder builder) + { + var basePath = AppContext.BaseDirectory; + //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))) + { + var msg = "Repository.dll和service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。"; + Log.Error(msg); + throw new Exception(msg); + } + + + // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 + var cacheType = new List(); + if (AppSettings.app(new string[] { "AppSettings", "CachingAOP", "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)); + } + + 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()); //允许将拦截器服务的列表分配给注册。 + + // 获取 Repository.dll 程序集服务,并注册 + var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); + builder.RegisterAssemblyTypes(assemblysRepository) + .AsImplementedInterfaces() + .PropertiesAutowired() + .InstancePerDependency(); + + builder.RegisterType().As() + .AsImplementedInterfaces() + .InstancePerLifetimeScope() + .PropertiesAutowired(); + + #endregion + + #region 没有接口层的服务层注入 + + //因为没有接口层,所以不能实现解耦,只能用 Load 方法。 + //注意如果使用没有接口的服务,并想对其使用 AOP 拦截,就必须设置为虚方法 + //var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); + //builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); + + #endregion + + #region 没有接口的单独类,启用class代理拦截 + + //只能注入该类中的虚方法,且必须是public + //这里仅仅是一个单独类无接口测试,不用过多追问 + builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) + .EnableClassInterceptors() + .InterceptedBy(cacheType.ToArray()); + + #endregion + + #region 单独注册一个含有接口的类,启用interface代理拦截 + + //不用虚方法 + //builder.RegisterType().As() + // .AsImplementedInterfaces() + // .EnableInterfaceInterceptors() + // .InterceptedBy(typeof(BlogCacheAOP)); + + #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 new file mode 100644 index 00000000..d9961ff9 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/CorsSetup.cs @@ -0,0 +1,48 @@ +using Blog.Core.Common; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// Cors 启动服务 + /// + public static class CorsSetup + { + public static void AddCorsSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddCors(c => + { + if (!AppSettings.app(new string[] { "Startup", "Cors", "EnableAllIPs" }).ObjToBool()) + { + c.AddPolicy(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" }), + + policy => + { + + policy + .WithOrigins(AppSettings.app(new string[] { "Startup", "Cors", "IPs" }).Split(',')) + .AllowAnyHeader()//Ensures that the policy allows any header. + .AllowAnyMethod(); + }); + } + else + { + //允许任意跨域请求 + c.AddPolicy(AppSettings.app(new string[] { "Startup", "Cors", "PolicyName" }), + policy => + { + policy + .SetIsOriginAllowed((host) => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); + } + + }); + } + } +} 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 new file mode 100644 index 00000000..1f377bcc --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/DbSetup.cs @@ -0,0 +1,19 @@ +using Blog.Core.Common.Seed; +using Microsoft.Extensions.DependencyInjection; + +namespace Blog.Core.Extensions +{ + /// + /// Db 启动服务 + /// + public static class DbSetup + { + public static void AddDbSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddScoped(); + services.AddScoped(); + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/EventBusSetup.cs b/Blog.Core.Extensions/ServiceExtensions/EventBusSetup.cs new file mode 100644 index 00000000..4ae98830 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/EventBusSetup.cs @@ -0,0 +1,52 @@ +using System; +using Autofac; +using Blog.Core.Common; +using Blog.Core.EventBus; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Blog.Core.Extensions +{ + /// + /// EventBus 事件总线服务 + /// + public static class EventBusSetup + { + public static void AddEventBusSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + if (AppSettings.app(new string[] { "EventBus", "Enabled" }).ObjToBool()) + { + var subscriptionClientName = AppSettings.app(new string[] { "EventBus", "SubscriptionClientName" }); + + services.AddSingleton(); + services.AddTransient(); + + if (AppSettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool()) + { + services.AddSingleton(sp => + { + var rabbitMQPersistentConnection = sp.GetRequiredService(); + var iLifetimeScope = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + + var retryCount = 5; + if (!string.IsNullOrEmpty(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()) + { + services.AddHostedService(); + services.AddSingleton(); + } + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/GenericTypeExtensions.cs b/Blog.Core.Extensions/ServiceExtensions/GenericTypeExtensions.cs new file mode 100644 index 00000000..7cb6c17d --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/GenericTypeExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; + +namespace Blog.Core.Extensions +{ + public static class GenericTypeExtensions + { + 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(); + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/HttpContextSetup.cs b/Blog.Core.Extensions/ServiceExtensions/HttpContextSetup.cs new file mode 100644 index 00000000..edb6118a --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/HttpContextSetup.cs @@ -0,0 +1,21 @@ +using Blog.Core.Common.HttpContextUser; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// HttpContext 相关服务 + /// + public static class HttpContextSetup + { + public static void AddHttpContextSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddSingleton(); + services.AddScoped(); + } + } +} 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 new file mode 100644 index 00000000..c92c077e --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/IpPolicyRateLimitSetup.cs @@ -0,0 +1,33 @@ +using AspNetCoreRateLimit; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Blog.Core.Extensions +{ + /// + /// IPLimit限流 启动服务 + /// + public static class IpPolicyRateLimitSetup + { + public static void AddIpPolicyRateLimitSetup(this IServiceCollection services, IConfiguration Configuration) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + //load general configuration from appsettings.json + services.Configure(Configuration.GetSection("IpRateLimiting")); + + // inject counter and rules distributed cache stores + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + + // the clientId/clientIp resolvers use it. + services.AddSingleton(); + // configuration (resolvers, counter key builders) + services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Extensions/ServiceExtensions/JobSetup.cs b/Blog.Core.Extensions/ServiceExtensions/JobSetup.cs new file mode 100644 index 00000000..da881cb3 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/JobSetup.cs @@ -0,0 +1,35 @@ +using Blog.Core.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Quartz; +using Quartz.Spi; +using System.Reflection; + +namespace Blog.Core.Extensions +{ + /// + /// 任务调度 启动服务 + /// + public static class JobSetup + { + public static void AddJobSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddSingleton(); + services.AddSingleton(); + //任务注入 + var baseType = typeof(IJob); + var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; + var referencedAssemblies = System.IO.Directory.GetFiles(path, "Blog.Core.Tasks.dll").Select(Assembly.LoadFrom).ToArray(); + var types = referencedAssemblies + .SelectMany(a => a.DefinedTypes) + .Select(type => type.AsType()) + .Where(x => x != baseType && baseType.IsAssignableFrom(x)).ToArray(); + var implementTypes = types.Where(x => x.IsClass).ToArray(); + foreach (var implementType in implementTypes) + { + services.AddTransient(implementType); + } + } + } +} 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 new file mode 100644 index 00000000..0dbc3d07 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/KafkaSetup.cs @@ -0,0 +1,26 @@ +using Blog.Core.Common; +using Blog.Core.EventBus; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// 注入Kafka相关配置 + /// + public static class KafkaSetup + { + public static void AddKafkaSetup(this IServiceCollection services,IConfiguration configuration) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + if (AppSettings.app(new string[] { "Kafka", "Enabled" }).ObjToBool()) + { + services.Configure(configuration.GetSection("kafka")); + services.AddSingleton(); + } + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs b/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs new file mode 100644 index 00000000..1bd601f4 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/MiniProfilerSetup.cs @@ -0,0 +1,88 @@ +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 +{ + /// + /// MiniProfiler 启动服务 + /// + public static class MiniProfilerSetup + { + public static void AddMiniProfilerSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + 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 new file mode 100644 index 00000000..1854f3ed --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/NacosSetup.cs @@ -0,0 +1,53 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Blog.Core.Extensions.NacosConfig; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Nacos.V2.DependencyInjection; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// Nacos + /// + public static class NacosSetup + { + public static void AddNacosSetup(this IServiceCollection services, IConfiguration Configuration) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + // 在实际生产工作中 本地开发是不需要注册nacos的 所以根据环境变量去判断 + // 比如 开发环境 dev 测试环境 test 生产 prod 只有这几种环境变量的时候才需要去注册nacos + if (AppSettings.app(new string[] { "Startup", "Nacos", "Enabled" }).ObjToBool()) + { + // 从当前配置取文件去注册naocs + services.AddNacosV2Config(x => + { + x.ServerAddresses = JsonConfigSettings.NacosServerAddresses; + x.EndPoint = ""; + x.Namespace = JsonConfigSettings.NacosNamespace; + x.DefaultTimeOut = JsonConfigSettings.NacosDefaultTimeOut; + x.ListenInterval = JsonConfigSettings.ListenInterval; + // swich to use http or rpc + x.ConfigUseRpc = false; + }); + services.AddNacosV2Naming(x => + { + x.ServerAddresses = JsonConfigSettings.NacosServerAddresses; + x.EndPoint = ""; + x.Namespace = JsonConfigSettings.NacosNamespace; + x.DefaultTimeOut = JsonConfigSettings.NacosDefaultTimeOut; + x.ListenInterval = JsonConfigSettings.ListenInterval; + // swich to use http or rpc + x.NamingUseRpc = false; + }); + services.AddHostedService(); //增加服务注入,删除事件 + // 监听nacos中的配置中心 如果有新配置变更 执行相关逻辑 + services.AddHostedService();//增加配置文件监听事件 + } + + services.AddSingleton(Configuration); + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/RabbitMQSetup.cs b/Blog.Core.Extensions/ServiceExtensions/RabbitMQSetup.cs new file mode 100644 index 00000000..72b72f43 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/RabbitMQSetup.cs @@ -0,0 +1,57 @@ +using Blog.Core.Common; +using Blog.Core.EventBus; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using RabbitMQ.Client; +using System; + +namespace Blog.Core.Extensions +{ + /// + /// Db 启动服务 + /// + public static class RabbitMQSetup + { + public static void AddRabbitMQSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + if (AppSettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool()) + { + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + var factory = new ConnectionFactory() + { + HostName = AppSettings.app(new string[] { "RabbitMQ", "Connection" }), + DispatchConsumersAsync = true + }; + + if (!string.IsNullOrEmpty(AppSettings.app(new string[] { "RabbitMQ", "UserName" }))) + { + factory.UserName = AppSettings.app(new string[] { "RabbitMQ", "UserName" }); + } + + if (!string.IsNullOrEmpty(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" }))) + { + retryCount = AppSettings.app(new string[] { "RabbitMQ", "RetryCount" }).ObjToInt(); + } + + return new RabbitMQPersistentConnection(factory, logger, retryCount); + }); + } + } + } +} diff --git a/Blog.Core.Extensions/ServiceExtensions/RedisInitMqSetup.cs b/Blog.Core.Extensions/ServiceExtensions/RedisInitMqSetup.cs new file mode 100644 index 00000000..b96b81b3 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/RedisInitMqSetup.cs @@ -0,0 +1,39 @@ +using Blog.Core.Common; +using Blog.Core.Extensions.Redis; +using InitQ; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; + +namespace Blog.Core.Extensions +{ + /// + /// Redis 消息队列 启动服务 + /// + public static class RedisInitMqSetup + { + public static void AddRedisInitMqSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + if (AppSettings.app(new string[] { "Startup", "RedisMq", "Enabled" }).ObjToBool()) + { + // + services.AddInitQ(m => + { + //时间间隔 + m.SuspendTime = 2000; + //redis服务器地址 + m.ConnectionString = AppSettings.app(new string[] { "Redis", "ConnectionString" }); + //对应的订阅者类,需要new一个实例对象,当然你也可以传参,比如日志对象 + m.ListSubscribe = new List() { + typeof(RedisSubscribe), + typeof(RedisSubscribe2) + }; + //显示日志 + m.ShowLog = false; + }); + } + } + } +} 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 new file mode 100644 index 00000000..71e1f7fd --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/SqlsugarSetup.cs @@ -0,0 +1,175 @@ +using Blog.Core.Common; +using Blog.Core.Common.Const; +using Blog.Core.Common.DB; +using Blog.Core.Common.DB.Aop; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using SqlSugar; +using Blog.Core.Common.Caches; +using System.Text.RegularExpressions; +using Blog.Core.Common.Option; +using Blog.Core.Common.Utility; + +namespace Blog.Core.Extensions +{ + /// + /// SqlSugar 启动服务 + /// + 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; + + // 默认添加主数据库连接 + if (!AppSettings.app("MainDB").IsNullOrEmpty()) + { + MainDb.CurrentDbConnId = AppSettings.app("MainDB"); + } + + BaseDBConfig.MutiConnectionString.allDbs.ForEach(m => + { + var config = new ConnectionConfig() + { + 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() + { + //IsWithNoLockQuery = true, + IsAutoRemoveDataCache = true, + SqlServerCodeFirstNvarchar = true, + }, + // 从库 + SlaveConnectionConfigs = m.Slaves?.Where(s => s.HitRate > 0).Select(s => new SlaveConnectionConfig + { + ConnectionString = s.Connection, + HitRate = s.HitRate + }).ToList(), + // 自定义特性 + ConfigureExternalServices = new ConfigureExternalServices() + { + //不建议使用,性能有很大问题,会导致redis堆积 + //核心问题在于SqlSugar,每次query都会查缓存, insert\update\delete,又会频繁GetAllKey,导致性能特别低 + DataInfoCacheService = new SqlSugarCacheService(), + EntityService = (property, column) => + { + if (column.IsPrimarykey && property.PropertyType == typeof(int)) + { + column.IsIdentity = true; + } + } + }, + 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); + }); + }); + services.AddTransient(s => s.GetService() as SqlSugarScope); + } + + private static string GetWholeSql(SugarParameter[] paramArr, string sql) + { + foreach (var param in paramArr) + { + 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; + } + + 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 new file mode 100644 index 00000000..ade9f993 --- /dev/null +++ b/Blog.Core.Extensions/ServiceExtensions/SwaggerSetup.cs @@ -0,0 +1,132 @@ +using Blog.Core.Common; +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 +{ + /// + /// Swagger 启动服务 + /// + public static class 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" }); + + services.AddSwaggerGen(c => + { + //遍历出全部的版本,做文档信息展示 + typeof(ApiVersions).GetEnumNames().ToList().ForEach(version => + { + c.SwaggerDoc(version, new OpenApiInfo + { + Version = version, + Title = $"{ApiName} 接口文档——{RuntimeInformation.FrameworkDescription}", + Description = $"{ApiName} HTTP API " + version, + Contact = new OpenApiContact { Name = ApiName, Email = "Blog.Core@xxx.com", Url = new Uri("https://neters.club") }, + License = new OpenApiLicense { Name = ApiName + " 官方文档", Url = new Uri("http://apk.neters.club/.doc/") } + }); + c.OrderActionsBy(o => o.RelativePath); + }); + + c.UseInlineDefinitionsForEnums(); + try + { + //这个就是刚刚配置的xml文件名 + var xmlPath = Path.Combine(basePath, "Blog.Core.xml"); + //默认的第二个参数是false,这个是controller的注释,记得修改 + c.IncludeXmlComments(xmlPath, true); + + //这个就是Model层的xml文件名 + var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml"); + c.IncludeXmlComments(xmlModelPath); + } + catch (Exception ex) + { + Log.Error("Blog.Core.xml和Blog.Core.Model.xml 丢失,请检查并拷贝。\n" + ex.Message); + } + + // 开启加权小锁 + c.OperationFilter(); + c.OperationFilter(); + + // 在header中添加token,传递到后台 + c.OperationFilter(); + + //自定义过滤器 + c.SchemaFilter(); + c.DocumentFilter(); + + // ids4和jwt切换 + if (Permissions.IsUseIds4) + { + //接入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" + } + } + } + } + }); + } + else + { + // Jwt Bearer 认证,必须是 oauth2 + c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme + { + Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", + Name = "Authorization", //jwt默认的参数名称 + In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中) + Type = SecuritySchemeType.ApiKey + }); + } + }); + services.AddSwaggerGenNewtonsoftSupport(); + } + } + + /// + /// 自定义版本 + /// + public class CustomApiVersion + { + /// + /// Api接口版本 自定义 + /// + 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.FrameWork/Blog.Core.FrameWork.Entity/Blog.Core.FrameWork.cs b/Blog.Core.FrameWork/Blog.Core.FrameWork.Entity/Blog.Core.FrameWork.cs deleted file mode 100644 index fb0a0711..00000000 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.Entity/Blog.Core.FrameWork.cs +++ /dev/null @@ -1,67 +0,0 @@ -//如果要获取主机信息,记得把 hostspecific 设置成true - -//导入命名空间组件 - -//引入我们的公共模板文件 -//引入命名空间 - - - -//定义我们的输出文件夹 - - - -//-------------------------------------------------------------------- -// 此代码由T4模板自动生成 -// 生成时间 2018-12-06 11:17:11 -// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 -//-------------------------------------------------------------------- - //连接数据库,打开 connect 连接 - //遍历全部数据库表 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - - //开始启动block块,参数是实体类文件名 - - diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.Entity/Blog.Core.FrameWork.tt b/Blog.Core.FrameWork/Blog.Core.FrameWork.Entity/Blog.Core.FrameWork.tt index c7747d92..4d88e636 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.Entity/Blog.Core.FrameWork.tt +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.Entity/Blog.Core.FrameWork.tt @@ -1,106 +1,60 @@ -//如果要获取主机信息,记得把 hostspecific 设置成true -<#@ template debug="false" hostspecific="True" language="C#" #> -<#@ output extension=".cs" #> - -//导入命名空间组件 -<#@ assembly name="System.Data" #> -<#@ assembly name="System.xml" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ import namespace="System.Data.SqlClient" #> -<#@ import namespace="System.Data" #> +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ output extension="/" #> <#@ assembly name="System.Core.dll" #> +<#@ assembly name="System.Data.dll" #> <#@ assembly name="System.Data.DataSetExtensions.dll" #> +<#@ assembly name="System.Xml.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.Xml" #> <#@ import namespace="System.Linq" #> +<#@ import namespace="System.Data" #> +<#@ import namespace="System.Data.SqlClient" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> - -//引入我们的公共模板文件 <#@ include file="$(ProjectDir)DbHelper.ttinclude" #> <#@ include file="$(ProjectDir)ModelAuto.ttinclude" #> - -//定义我们的输出文件夹 +<# var manager = new Manager(Host, GenerationEnvironment, true); #> <# - var OutputPath1 = Path.GetDirectoryName(Host.TemplateFile)+"\\work"; + var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+".."); + OutputPath1=Path.Combine(OutputPath1,"Blog.Core.Model","Models_New"); if (!Directory.Exists(OutputPath1)) { Directory.CreateDirectory(OutputPath1); } - var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = OutputPath1 }; #> - - - -//-------------------------------------------------------------------- -// 此代码由T4模板自动生成 -// 生成时间 <#=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")#> -// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 -//-------------------------------------------------------------------- -<# - var tableName=config.TableName;//获取config配置中的表名,为单一生产使用 - #> -<# -if(tableName!=""){//如果表名有值,表示是生成单一文件 - #> +<# foreach (var item in DbHelper.GetDbTablesNew(config.ConnectionString, config.DbDatabase,config.TableName)) + { + var tableName=item.ToString(); + manager.StartBlock(tableName+".cs",OutputPath1);//文件名 + #> using System; -namespace Blog.Core.FrameWork.Entity -{ - /// - /// <#=tableName#> - /// - public class <#=tableName#>//可以在这里加上基类等 - { - //将全部字段遍历出来 -<# foreach(DbColumn column in DbHelper.GetDbColumns(config.ConnectionString, config.DbDatabase, config.TableName)){#> - public <#= column.CSharpType#><# if(column.CommonType.IsValueType && column.IsNullable){#>?<#}#> <#=column.ColumnName#> { get; set; } -<#}#> - } -} -//如果为空,表示要将整个数据库都生成出来 -<# - } else{ - - #> - //连接数据库,打开 connect 连接 -<# - SqlConnection conn = new SqlConnection(config.ConnectionString); - conn.Open(); - System.Data.DataTable schema = conn.GetSchema("TABLES"); - #> - //遍历全部数据库表 -<# - foreach(System.Data.DataRow row in schema.Rows) - { #> - - //开始启动block块,参数是实体类文件名 - <# - manager.StartBlock(row["TABLE_NAME"]+".cs"); - #> - //----------<#=row["TABLE_NAME"].ToString()#>开始---------- - -using System; -namespace Blog.Core.FrameWork.Entity -{ - /// - /// <#=row["TABLE_NAME"].ToString()#> - /// - public class <#=row["TABLE_NAME"].ToString()#>//可以在这里加上基类等 - { - //将该表下的字段都遍历出来,可以自定义获取数据描述等信息 -<# foreach(DbColumn column in DbHelper.GetDbColumns(config.ConnectionString, config.DbDatabase, row["TABLE_NAME"].ToString() )){ #> - - public <#= column.CSharpType#> <# if(column.CommonType.IsValueType && column.IsNullable){#> ?<#}#> <#=column.ColumnName #> { get; set; } -<#}#> - - } -} - - //----------<#=row["TABLE_NAME"].ToString()#>结束---------- - <# - manager.EndBlock(); - } - manager.Process(true); - } - #> - +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +namespace Blog.Core.Model.Models +{ + /// + ///<#=tableName#> + /// + [Table("<#=tableName#>")] + public class <#=tableName#> + { + <# foreach(DbColumn column in DbHelper.GetDbColumns(config.ConnectionString, config.DbDatabase, tableName)){#> + + /// + /// <#= column.Remark == "" ? column.ColumnName : column.Remark.Replace("\r\n"," ") #> + /// + <# + if(column.IsPrimaryKey) + {#>[Key] + <#}#><# if(!column.IsNullable) {#>[Required] + <# }#>public <#= column.CSharpType#><# if(column.CommonType.IsValueType && column.IsNullable){#>?<#}#> <#=column.ColumnName#> { get; set; } + <# + } + #> + } +} +<# + manager.EndBlock(); + } + manager.Process(true); + #> \ No newline at end of file diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.cs b/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.cs index 1926e918..5283a86f 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.cs +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.cs @@ -1,17 +1,11 @@  -//引入命名空间 - - - - - //-------------------------------------------------------------------- // 此代码由T4模板自动生成 -// 生成时间 2018-12-17 13:02:18 +// 生成时间 2019-12-10 12:14:04 // 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 //-------------------------------------------------------------------- diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.tt b/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.tt index c8b7bc58..97bfd67c 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.tt +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.IRepository/Blog.Core.FrameWork.IRepository.tt @@ -12,20 +12,18 @@ <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ include file="$(ProjectDir)DbHelper.ttinclude" #> - <#@ include file="$(ProjectDir)ModelAuto.ttinclude" #> +<# var manager = new Manager(Host, GenerationEnvironment, true); #> <# - var OutputPath1 = Path.GetDirectoryName(Host.TemplateFile)+"\\work"; + var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+".."); + OutputPath1=Path.Combine(OutputPath1,"Blog.Core.IRepository","IRepositories_New"); if (!Directory.Exists(OutputPath1)) { Directory.CreateDirectory(OutputPath1); } - #> -<# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = OutputPath1 }; #> - - +#> //-------------------------------------------------------------------- @@ -39,9 +37,9 @@ <# if(tableName!=""){ #> -using System; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.IRepository +using Blog.Core.IRepository.Base; +using Blog.Core.Model.Models; +namespace Blog.Core.IRepository { /// /// I<#=tableName#>Repository @@ -67,14 +65,14 @@ namespace Blog.Core.FrameWork.IRepository <# foreach(System.Data.DataRow row in schema.Rows) { - manager.StartBlock("I"+row["TABLE_NAME"].ToString()+"Repository"+".cs");//文件名 + manager.StartBlock("I"+row["TABLE_NAME"].ToString()+"Repository"+".cs",OutputPath1);//文件名 #> //----------<#=row["TABLE_NAME"].ToString()#>开始---------- -using System; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.IRepository +using Blog.Core.IRepository.Base; +using Blog.Core.Model.Models; +namespace Blog.Core.IRepository { /// /// I<#=row["TABLE_NAME"].ToString()#>Repository @@ -92,14 +90,14 @@ namespace Blog.Core.FrameWork.IRepository } { - manager.StartBlock("IBaseRepository.cs");//文件名 + manager.StartBlock("IBaseRepository.cs",OutputPath1);//文件名 #> //----------开始---------- -using System; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.IRepository +using Blog.Core.IRepository.Base; +using Blog.Core.Model.Models; +namespace Blog.Core.IRepository { /// /// IBaseRepository diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.cs b/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.cs index ee8ad830..fe84bf42 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.cs +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.cs @@ -1,17 +1,12 @@  -//引入命名空间 - - - - //-------------------------------------------------------------------- // 此代码由T4模板自动生成 -// 生成时间 2018-12-17 13:05:54 +// 生成时间 2019-12-10 12:14:01 // 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 //-------------------------------------------------------------------- diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.tt b/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.tt index 13d88893..a964849e 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.tt +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.IServices/Blog.Core.FrameWork.IServices.tt @@ -12,19 +12,18 @@ <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ include file="$(ProjectDir)DbHelper.ttinclude" #> - <#@ include file="$(ProjectDir)ModelAuto.ttinclude" #> +<# var manager = new Manager(Host, GenerationEnvironment, true); #> <# - var OutputPath1 = Path.GetDirectoryName(Host.TemplateFile)+"\\work"; + var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+".."); + OutputPath1=Path.Combine(OutputPath1,"Blog.Core.IServices","IServices_New"); if (!Directory.Exists(OutputPath1)) { Directory.CreateDirectory(OutputPath1); } - #> -<# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = OutputPath1 }; #> - +#> @@ -41,9 +40,10 @@ if(tableName!=""){ #> -using System; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.IServices +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices { /// /// I<#=tableName#>Services @@ -69,15 +69,15 @@ namespace Blog.Core.FrameWork.IServices <# foreach(System.Data.DataRow row in schema.Rows) { - manager.StartBlock("I"+row["TABLE_NAME"].ToString()+"Services"+".cs"); + manager.StartBlock("I"+row["TABLE_NAME"].ToString()+"Services"+".cs",OutputPath1); #> //----------<#=row["TABLE_NAME"].ToString()#>开始---------- -using System; -using Blog.Core.FrameWork.Entity; +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; -namespace Blog.Core.FrameWork.IServices +namespace Blog.Core.IServices { /// /// <#=row["TABLE_NAME"].ToString()#>Services @@ -95,14 +95,15 @@ namespace Blog.Core.FrameWork.IServices } { - manager.StartBlock("IBaseServices.cs");//文件名 + manager.StartBlock("IBaseServices.cs",OutputPath1);//文件名 #> //----------开始---------- -using System; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.IServices +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices { /// /// IBaseRepository diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.cs b/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.cs index 8eaa2340..bfe45a2a 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.cs +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.cs @@ -1,17 +1,11 @@  -//引入命名空间 - - - - - //-------------------------------------------------------------------- // 此代码由T4模板自动生成 -// 生成时间 2018-12-17 13:07:29 +// 生成时间 2019-12-10 12:14:21 // 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 //-------------------------------------------------------------------- diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.tt b/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.tt index 4714e666..d9694958 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.tt +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.Repository/Blog.Core.FrameWork.Repository.tt @@ -12,20 +12,18 @@ <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ include file="$(ProjectDir)DbHelper.ttinclude" #> - <#@ include file="$(ProjectDir)ModelAuto.ttinclude" #> +<# var manager = new Manager(Host, GenerationEnvironment, true); #> <# - var OutputPath1 = Path.GetDirectoryName(Host.TemplateFile)+"\\work"; + var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+".."); + OutputPath1=Path.Combine(OutputPath1,"Blog.Core.Repository","Repositories_New"); if (!Directory.Exists(OutputPath1)) { Directory.CreateDirectory(OutputPath1); } - #> -<# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = OutputPath1 }; #> - - +#> //-------------------------------------------------------------------- @@ -41,17 +39,20 @@ if(tableName!=""){ #> -using System; -using Blog.Core.FrameWork.IRepository; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.Repository +using Blog.Core.Repository.Base; +using Blog.Core.Model.Models; +using Blog.Core.IRepository; +using Blog.Core.IRepository.UnitOfWork; +namespace Blog.Core.Repository { /// /// <#=tableName#>Repository /// public class <#=tableName#>Repository : BaseRepository<<#=tableName#>>, I<#=tableName#>Repository { - + public <#=tableName#>Repository(IUnitOfWork unitOfWork) : base(unitOfWork) + { + } } } @@ -70,23 +71,25 @@ namespace Blog.Core.FrameWork.Repository <# foreach(System.Data.DataRow row in schema.Rows) { - manager.StartBlock(row["TABLE_NAME"].ToString()+"Repository"+".cs"); + manager.StartBlock(row["TABLE_NAME"].ToString()+"Repository"+".cs",OutputPath1); #> //----------<#=row["TABLE_NAME"].ToString()#>开始---------- -using System; -using Blog.Core.FrameWork.IRepository; -using Blog.Core.FrameWork.Entity; - -namespace Blog.Core.FrameWork.Repository +using Blog.Core.Repository.Base; +using Blog.Core.Model.Models; +using Blog.Core.IRepository; +using Blog.Core.IRepository.UnitOfWork; +namespace Blog.Core.Repository { /// /// <#=row["TABLE_NAME"].ToString()#>Repository /// public class <#=row["TABLE_NAME"].ToString()#>Repository : BaseRepository<<#=row["TABLE_NAME"].ToString()#>>, I<#=row["TABLE_NAME"].ToString() #>Repository { - + public <#=row["TABLE_NAME"].ToString()#>Repository(IUnitOfWork unitOfWork) : base(unitOfWork) + { + } } } @@ -97,15 +100,16 @@ namespace Blog.Core.FrameWork.Repository } { - manager.StartBlock("BaseRepository.cs");//文件名 + manager.StartBlock("BaseRepository.cs",OutputPath1);//文件名 #> //----------开始---------- -using System; -using Blog.Core.FrameWork.Entity; -using Blog.Core.FrameWork.IRepository; -namespace Blog.Core.FrameWork.Repository +using Blog.Core.Repository.Base; +using Blog.Core.Model.Models; +using Blog.Core.IRepository; +using Blog.Core.IRepository.UnitOfWork; +namespace Blog.Core.Repository { /// /// IBaseRepository diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.cs b/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.cs index 4cbc43ee..b148012a 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.cs +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.cs @@ -1,17 +1,12 @@  -//引入命名空间 - - - - //-------------------------------------------------------------------- // 此代码由T4模板自动生成 -// 生成时间 2018-12-17 13:08:24 +// 生成时间 2021-05-08 09:48:27 // 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 //-------------------------------------------------------------------- diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.tt b/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.tt index 2517f32b..943cfb88 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.tt +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.Services/Blog.Core.FrameWork.Services.tt @@ -12,19 +12,18 @@ <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ include file="$(ProjectDir)DbHelper.ttinclude" #> - <#@ include file="$(ProjectDir)ModelAuto.ttinclude" #> +<# var manager = new Manager(Host, GenerationEnvironment, true); #> <# - var OutputPath1 = Path.GetDirectoryName(Host.TemplateFile)+"\\work"; + var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+".."); + OutputPath1=Path.Combine(OutputPath1,"Blog.Core.Services","Services_New"); if (!Directory.Exists(OutputPath1)) { Directory.CreateDirectory(OutputPath1); } - #> -<# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = OutputPath1 }; #> - +#> @@ -42,10 +41,15 @@ if(tableName!=""){ using System; -using Blog.Core.FrameWork.IServices; -using Blog.Core.FrameWork.IRepository; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.Services +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.IRepository; +using Blog.Core.IRepository.Base; +using Blog.Core.IRepository.UnitOfWork; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; +namespace Blog.Core.Services { /// /// <#=tableName#>Services @@ -53,11 +57,11 @@ namespace Blog.Core.FrameWork.Services public class <#=tableName#>Services : BaseServices<<#=tableName#>>, I<#=tableName#>Services { - I<#=tableName#>Repository dal; - public <#=tableName#>Services(I<#=tableName#>Repository dal) + IBaseRepository<<#=tableName#>> dal; + public <#=tableName#>Services(IBaseRepository<<#=tableName#>> dal) { this.dal = dal; - base.baseDal = dal; + base.BaseDal = dal; } } @@ -77,17 +81,22 @@ namespace Blog.Core.FrameWork.Services <# foreach(System.Data.DataRow row in schema.Rows) { - manager.StartBlock(row["TABLE_NAME"].ToString()+"Services"+".cs"); + manager.StartBlock(row["TABLE_NAME"].ToString()+"Services"+".cs",OutputPath1); #> //----------<#=row["TABLE_NAME"].ToString()#>开始---------- -using System; -using Blog.Core.FrameWork.IServices; -using Blog.Core.FrameWork.IRepository; -using Blog.Core.FrameWork.Entity; -namespace Blog.Core.FrameWork.Services +using System; +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.IRepository; +using Blog.Core.IRepository.UnitOfWork; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; +namespace Blog.Core.Services { /// /// <#=row["TABLE_NAME"].ToString()#>Services @@ -95,11 +104,11 @@ namespace Blog.Core.FrameWork.Services public class <#=row["TABLE_NAME"].ToString()#>Services : BaseServices<<#=row["TABLE_NAME"].ToString()#>>, I<#=row["TABLE_NAME"].ToString() #>Services { - I<#=row["TABLE_NAME"].ToString() #>Repository dal; - public <#=row["TABLE_NAME"].ToString() #>Services(I<#=row["TABLE_NAME"].ToString() #>Repository dal) + IBaseRepository<<#=row["TABLE_NAME"].ToString() #>> dal; + public <#=row["TABLE_NAME"].ToString() #>Services(IBaseRepository<<#=row["TABLE_NAME"].ToString() #>> dal) { this.dal = dal; - base.baseDal = dal; + base.BaseDal = dal; } } @@ -111,23 +120,29 @@ namespace Blog.Core.FrameWork.Services } { - manager.StartBlock("BaseServices.cs");//文件名 + manager.StartBlock("BaseServices.cs",OutputPath1);//文件名 #> //----------开始---------- + using System; -using Blog.Core.FrameWork.Entity; -using Blog.Core.FrameWork.IServices; -using Blog.Core.FrameWork.IRepository; -namespace Blog.Core.FrameWork.Services +using System.Threading.Tasks; +using Blog.Core.Common; +using Blog.Core.IRepository; +using Blog.Core.IRepository.UnitOfWork; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; +namespace Blog.Core.Services { /// /// IBaseRepository /// public class BaseServices : IBaseServices where TEntity : class, new() { - public IBaseRepository baseDal; + public IBaseRepository BaseDal; } } diff --git a/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj b/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj index dbe6ec52..76d6f7e6 100644 --- a/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj +++ b/Blog.Core.FrameWork/Blog.Core.FrameWork.csproj @@ -1,8 +1,5 @@ - - netcoreapp2.2 - @@ -32,11 +29,6 @@ - - True - True - Blog.Core.FrameWork.tt - True True diff --git a/Blog.Core.FrameWork/DbHelper.ttinclude b/Blog.Core.FrameWork/DbHelper.ttinclude index 4b174491..d8bf9d75 100644 --- a/Blog.Core.FrameWork/DbHelper.ttinclude +++ b/Blog.Core.FrameWork/DbHelper.ttinclude @@ -1,333 +1,322 @@ -<#+ -class DbHelper - { - #region 获取数据库表结构 - - public static List GetDbTables(string connectionString, string database, string tables = null) - { - - if (!string.IsNullOrEmpty(tables)) - { - tables = string.Format(" and obj.name in ('{0}')", tables.Replace(",", "','")); - } - #region 执行sql命令 - string sql = string.Format(@"SELECT - obj.name tablename, - schem.name schemname, - idx.rows, - CAST - ( - CASE - WHEN (SELECT COUNT(1) FROM sys.indexes WHERE object_id= obj.OBJECT_ID AND is_primary_key=1) >=1 THEN 1 - ELSE 0 - END - AS BIT) HasPrimaryKey - from {0}.sys.objects obj - inner join {0}.dbo.sysindexes idx on obj.object_id=idx.id and idx.indid<=1 - INNER JOIN {0}.sys.schemas schem ON obj.schema_id=schem.schema_id - where type='U' {1} - order by obj.name", database, tables); - #endregion - DataTable dt = GetDataTable(connectionString, sql); - return dt.Rows.Cast().Select(row => new DbTable - { - TableName = row.Field("tablename"), - SchemaName = row.Field("schemname"), - Rows = row.Field("rows"), - HasPrimaryKey = row.Field("HasPrimaryKey") - }).ToList(); - } - #endregion - - #region 获取表字段信息 - - public static List GetDbColumns(string connectionString, string database, string tableName, string schema = "dbo") - { - #region 执行sql命令 - string sql = string.Format(@" - WITH indexCTE AS - ( - SELECT - ic.column_id, - ic.index_column_id, - ic.object_id - FROM {0}.sys.indexes idx - INNER JOIN {0}.sys.index_columns ic ON idx.index_id = ic.index_id AND idx.object_id = ic.object_id - WHERE idx.object_id =OBJECT_ID(@tableName) AND idx.is_primary_key=1 - ) - select - colm.column_id ColumnID, - CAST(CASE WHEN indexCTE.column_id IS NULL THEN 0 ELSE 1 END AS BIT) IsPrimaryKey, - colm.name ColumnName, - systype.name ColumnType, - colm.is_identity IsIdentity, - colm.is_nullable IsNullable, - cast(colm.max_length as int) ByteLength, - ( - case - when systype.name='nvarchar' and colm.max_length>0 then colm.max_length/2 - when systype.name='nchar' and colm.max_length>0 then colm.max_length/2 - when systype.name='ntext' and colm.max_length>0 then colm.max_length/2 - else colm.max_length - end - ) CharLength, - cast(colm.precision as int) Precision, - cast(colm.scale as int) Scale, - prop.value Remark - from {0}.sys.columns colm - inner join {0}.sys.types systype on colm.system_type_id=systype.system_type_id and colm.user_type_id=systype.user_type_id - left join {0}.sys.extended_properties prop on colm.object_id=prop.major_id and colm.column_id=prop.minor_id - LEFT JOIN indexCTE ON colm.column_id=indexCTE.column_id AND colm.object_id=indexCTE.object_id - where colm.object_id=OBJECT_ID(@tableName) - order by colm.column_id", database); - #endregion - SqlParameter param = new SqlParameter("@tableName", SqlDbType.NVarChar, 100) { Value = string.Format("{0}.{1}.{2}", database, schema, tableName) }; - DataTable dt = GetDataTable(connectionString, sql, param); - return dt.Rows.Cast().Select(row => new DbColumn() - { - ColumnID = row.Field("ColumnID"), - IsPrimaryKey = row.Field("IsPrimaryKey"), - ColumnName = row.Field("ColumnName"), - ColumnType = row.Field("ColumnType"), - IsIdentity = row.Field("IsIdentity"), - IsNullable = row.Field("IsNullable"), - ByteLength = row.Field("ByteLength"), - CharLength = row.Field("CharLength"), - Scale = row.Field("Scale"), - Remark = row["Remark"].ToString() - }).ToList(); - } - - #endregion - - #region 获取 DataTable - - public static DataTable GetDataTable(string connectionString, string commandText, params SqlParameter[] parms) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - SqlCommand command = connection.CreateCommand(); - command.CommandText = commandText; - command.Parameters.AddRange(parms); - SqlDataAdapter adapter = new SqlDataAdapter(command); - - DataTable dt = new DataTable(); - adapter.Fill(dt); - - return dt; - } - } - - #endregion - - #region 获取主键 - public static string GetPrimaryKey(List dbColumns) - { - string primaryKey = string.Empty; - if (dbColumns!=null&&dbColumns.Count>0) - { - foreach (var item in dbColumns) - { - if (item.IsPrimaryKey==true) - { - primaryKey = item.ColumnName; - } - } - } - return primaryKey; - } - #endregion - } - -class DbTable - { - /// - /// 表名称 - /// - public string TableName { get; set; } - /// - /// 表的架构 - /// - public string SchemaName { get; set; } - /// - /// 表的记录数 - /// - public int Rows { get; set; } - - /// - /// 是否含有主键 - /// - public bool HasPrimaryKey { get; set; } - } - -class DbColumn - { - /// - /// 字段ID - /// - public int ColumnID { get; set; } - - /// - /// 是否主键 - /// - public bool IsPrimaryKey { get; set; } - - /// - /// 字段名称 - /// - public string ColumnName { get; set; } - - /// - /// 字段类型 - /// - public string ColumnType { get; set; } - - /// - /// 数据库类型对应的C#类型 - /// - public string CSharpType - { - get - { - return SqlServerDbTypeMap.MapCsharpType(ColumnType); - } - } - - /// - /// - /// - public Type CommonType - { - get - { - return SqlServerDbTypeMap.MapCommonType(ColumnType); - } - } - - /// - /// 字节长度 - /// - public int ByteLength { get; set; } - - /// - /// 字符长度 - /// - public int CharLength { get; set; } - - /// - /// 小数位 - /// - public int Scale { get; set; } - - /// - /// 是否自增列 - /// - public bool IsIdentity { get; set; } - - /// - /// 是否允许空 - /// - public bool IsNullable { get; set; } - - /// - /// 描述 - /// - public string Remark { get; set; } - } - -class SqlServerDbTypeMap - { - public static string MapCsharpType(string dbtype) - { - if (string.IsNullOrEmpty(dbtype)) return dbtype; - dbtype = dbtype.ToLower(); - string csharpType = "object"; - switch (dbtype) - { - case "bigint": csharpType = "long"; break; - case "binary": csharpType = "byte[]"; break; - case "bit": csharpType = "bool"; break; - case "char": csharpType = "string"; break; - case "date": csharpType = "DateTime"; break; - case "datetime": csharpType = "DateTime"; break; - case "datetime2": csharpType = "DateTime"; break; - case "datetimeoffset": csharpType = "DateTimeOffset"; break; - case "decimal": csharpType = "decimal"; break; - case "float": csharpType = "double"; break; - case "image": csharpType = "byte[]"; break; - case "int": csharpType = "int"; break; - case "money": csharpType = "decimal"; break; - case "nchar": csharpType = "string"; break; - case "ntext": csharpType = "string"; break; - case "numeric": csharpType = "decimal"; break; - case "nvarchar": csharpType = "string"; break; - case "real": csharpType = "Single"; break; - case "smalldatetime": csharpType = "DateTime"; break; - case "smallint": csharpType = "short"; break; - case "smallmoney": csharpType = "decimal"; break; - case "sql_variant": csharpType = "object"; break; - case "sysname": csharpType = "object"; break; - case "text": csharpType = "string"; break; - case "time": csharpType = "TimeSpan"; break; - case "timestamp": csharpType = "byte[]"; break; - case "tinyint": csharpType = "byte"; break; - case "uniqueidentifier": csharpType = "Guid"; break; - case "varbinary": csharpType = "byte[]"; break; - case "varchar": csharpType = "string"; break; - case "xml": csharpType = "string"; break; - default: csharpType = "object"; break; - } - return csharpType; - } - - public static Type MapCommonType(string dbtype) - { - if (string.IsNullOrEmpty(dbtype)) return Type.Missing.GetType(); - dbtype = dbtype.ToLower(); - Type commonType = typeof(object); - switch (dbtype) - { - case "bigint": commonType = typeof(long); break; - case "binary": commonType = typeof(byte[]); break; - case "bit": commonType = typeof(bool); break; - case "char": commonType = typeof(string); break; - case "date": commonType = typeof(DateTime); break; - case "datetime": commonType = typeof(DateTime); break; - case "datetime2": commonType = typeof(DateTime); break; - case "datetimeoffset": commonType = typeof(DateTimeOffset); break; - case "decimal": commonType = typeof(decimal); break; - case "float": commonType = typeof(double); break; - case "image": commonType = typeof(byte[]); break; - case "int": commonType = typeof(int); break; - case "money": commonType = typeof(decimal); break; - case "nchar": commonType = typeof(string); break; - case "ntext": commonType = typeof(string); break; - case "numeric": commonType = typeof(decimal); break; - case "nvarchar": commonType = typeof(string); break; - case "real": commonType = typeof(Single); break; - case "smalldatetime": commonType = typeof(DateTime); break; - case "smallint": commonType = typeof(short); break; - case "smallmoney": commonType = typeof(decimal); break; - case "sql_variant": commonType = typeof(object); break; - case "sysname": commonType = typeof(object); break; - case "text": commonType = typeof(string); break; - case "time": commonType = typeof(TimeSpan); break; - case "timestamp": commonType = typeof(byte[]); break; - case "tinyint": commonType = typeof(byte); break; - case "uniqueidentifier": commonType = typeof(Guid); break; - case "varbinary": commonType = typeof(byte[]); break; - case "varchar": commonType = typeof(string); break; - case "xml": commonType = typeof(string); break; - default: commonType = typeof(object); break; - } - return commonType; - } - } -class config - { - - //public static readonly string ConnectionString = "Data Source=.;Initial Catalog=WMBlogDB;User ID=sa;Password=666;"; - public static readonly string ConnectionString = File.Exists(@"D:\my-file\dbCountPsw2.txt") ? File.ReadAllText(@"D:\my-file\dbCountPsw2.txt").Trim(): "server=.;uid=sa;pwd=sa;database=WMBlogDB"; - public static readonly string DbDatabase = ""; - public static readonly string TableName = ""; - }#> \ No newline at end of file +<#+ + public class config + { + public static readonly string ConnectionString = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WMBLOG_MSSQL_1;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"; + public static readonly string DbDatabase = ""; + public static readonly string TableName = ""; + } +#> +<#+ + public class DbHelper + { + #region GetDbTables + + public static List GetDbTablesNew(string connectionString, string database,string tables = null) + { + if (!string.IsNullOrEmpty(tables)) + { + tables = string.Format(" and obj.name in ('{0}')", tables.Replace(",", "','")); + } + string sql = string.Format(@"SELECT + obj.name tablename + from {0}.sys.objects obj + inner join {0}.dbo.sysindexes idx on obj.object_id=idx.id and idx.indid<=1 + INNER JOIN {0}.sys.schemas schem ON obj.schema_id=schem.schema_id + left join {0}.sys.extended_properties g ON (obj.object_id = g.major_id AND g.minor_id = 0 AND g.name= 'MS_Description') + where type='U' {1} + order by obj.name", database,tables); + DataTable dt = GetDataTable(connectionString, sql); + return dt.Rows.Cast().Select(row =>row.Field("tablename")).ToList(); + } + + public static List GetDbTables(string connectionString, string database, string tables = null) + { + + if (!string.IsNullOrEmpty(tables)) + { + tables = string.Format(" and obj.name in ('{0}')", tables.Replace(",", "','")); + } + #region SQL + string sql = string.Format(@"SELECT + obj.name tablename, + schem.name schemname, + idx.rows, + CAST + ( + CASE + WHEN (SELECT COUNT(1) FROM sys.indexes WHERE object_id= obj.OBJECT_ID AND is_primary_key=1) >=1 THEN 1 + ELSE 0 + END + AS BIT) HasPrimaryKey + from {0}.sys.objects obj + inner join {0}.dbo.sysindexes idx on obj.object_id=idx.id and idx.indid<=1 + INNER JOIN {0}.sys.schemas schem ON obj.schema_id=schem.schema_id + where type='U' {1} + order by obj.name", database, tables); + #endregion + DataTable dt = GetDataTable(connectionString, sql); + return dt.Rows.Cast().Select(row => new DbTable + { + TableName = row.Field("tablename"), + SchemaName = row.Field("schemname"), + Rows = row.Field("rows"), + HasPrimaryKey = row.Field("HasPrimaryKey") + }).ToList(); + } + #endregion + + #region GetDbColumns + + public static List GetDbColumns(string connectionString, string database, string tableName, string schema = "dbo") + { + #region SQL + string sql = string.Format(@" + WITH indexCTE AS + ( + SELECT + ic.column_id, + ic.index_column_id, + ic.object_id + FROM {0}.sys.indexes idx + INNER JOIN {0}.sys.index_columns ic ON idx.index_id = ic.index_id AND idx.object_id = ic.object_id + WHERE idx.object_id =OBJECT_ID(@tableName) AND idx.is_primary_key=1 + ) + select + colm.column_id ColumnID, + CAST(CASE WHEN indexCTE.column_id IS NULL THEN 0 ELSE 1 END AS BIT) IsPrimaryKey, + colm.name ColumnName, + systype.name ColumnType, + colm.is_identity IsIdentity, + colm.is_nullable IsNullable, + cast(colm.max_length as int) ByteLength, + ( + case + when systype.name='nvarchar' and colm.max_length>0 then colm.max_length/2 + when systype.name='nchar' and colm.max_length>0 then colm.max_length/2 + when systype.name='ntext' and colm.max_length>0 then colm.max_length/2 + else colm.max_length + end + ) CharLength, + cast(colm.precision as int) Precision, + cast(colm.scale as int) Scale, + prop.value Remark + from {0}.sys.columns colm + inner join {0}.sys.types systype on colm.system_type_id=systype.system_type_id and colm.user_type_id=systype.user_type_id + left join {0}.sys.extended_properties prop on colm.object_id=prop.major_id and colm.column_id=prop.minor_id + LEFT JOIN indexCTE ON colm.column_id=indexCTE.column_id AND colm.object_id=indexCTE.object_id + where colm.object_id=OBJECT_ID(@tableName) + order by colm.column_id", database); + #endregion + SqlParameter param = new SqlParameter("@tableName", SqlDbType.NVarChar, 100) { Value = string.Format("{0}.{1}.{2}", database, schema, tableName) }; + DataTable dt = GetDataTable(connectionString, sql, param); + return dt.Rows.Cast().Select(row => new DbColumn() + { + ColumnID = row.Field("ColumnID"), + IsPrimaryKey = row.Field("IsPrimaryKey"), + ColumnName = row.Field("ColumnName"), + ColumnType = row.Field("ColumnType"), + IsIdentity = row.Field("IsIdentity"), + IsNullable = row.Field("IsNullable"), + ByteLength = row.Field("ByteLength"), + CharLength = row.Field("CharLength"), + Precision=row.Field("Precision"), + Scale = row.Field("Scale"), + Remark = row["Remark"].ToString() + }).ToList(); + } + + #endregion + + #region GetDataTable + + public static DataTable GetDataTable(string connectionString, string commandText, params SqlParameter[] parms) + { + using (SqlConnection connection = new SqlConnection(connectionString)) + { + SqlCommand command = connection.CreateCommand(); + command.CommandText = commandText; + command.Parameters.AddRange(parms); + SqlDataAdapter adapter = new SqlDataAdapter(command); + + DataTable dt = new DataTable(); + adapter.Fill(dt); + + return dt; + } + } + + #endregion + + #region GetPrimaryKey + public static string GetPrimaryKey(List dbColumns) + { + string primaryKey = string.Empty; + if (dbColumns!=null&&dbColumns.Count>0) + { + foreach (var item in dbColumns) + { + if (item.IsPrimaryKey==true) + { + primaryKey = item.ColumnName; + } + } + } + return primaryKey; + } + #endregion + } + + #region DbTable + public sealed class DbTable + { + public string TableName { get; set; } + public string SchemaName { get; set; } + public int Rows { get; set; } + + public bool HasPrimaryKey { get; set; } + } + #endregion + + #region DbColumn + + public sealed class DbColumn + { + + public int ColumnID { get; set; } + + + public bool IsPrimaryKey { get; set; } + + + public string ColumnName { get; set; } + + + public string ColumnType { get; set; } + + + public string CSharpType + { + get + { + return SqlServerDbTypeMap.MapCsharpType(ColumnType); + } + } + + /// + /// + /// + public Type CommonType + { + get + { + return SqlServerDbTypeMap.MapCommonType(ColumnType); + } + } + + public int ByteLength { get; set; } + + public int CharLength { get; set; } + + public int Precision{get;set;} + public int Scale { get; set; } + + public bool IsIdentity { get; set; } + + public bool IsNullable { get; set; } + + public string Remark { get; set; } + } + #endregion + + #region SqlServerDbTypeMap + + public class SqlServerDbTypeMap + { + public static string MapCsharpType(string dbtype) + { + if (string.IsNullOrEmpty(dbtype)) return dbtype; + dbtype = dbtype.ToLower(); + string csharpType = "object"; + switch (dbtype) + { + case "bigint": csharpType = "long"; break; + case "binary": csharpType = "byte[]"; break; + case "bit": csharpType = "bool"; break; + case "char": csharpType = "string"; break; + case "date": csharpType = "DateTime"; break; + case "datetime": csharpType = "DateTime"; break; + case "datetime2": csharpType = "DateTime"; break; + case "datetimeoffset": csharpType = "DateTimeOffset"; break; + case "decimal": csharpType = "decimal"; break; + case "float": csharpType = "double"; break; + case "image": csharpType = "byte[]"; break; + case "int": csharpType = "int"; break; + case "money": csharpType = "decimal"; break; + case "nchar": csharpType = "string"; break; + case "ntext": csharpType = "string"; break; + case "numeric": csharpType = "decimal"; break; + case "nvarchar": csharpType = "string"; break; + case "real": csharpType = "Single"; break; + case "smalldatetime": csharpType = "DateTime"; break; + case "smallint": csharpType = "short"; break; + case "smallmoney": csharpType = "decimal"; break; + case "sql_variant": csharpType = "object"; break; + case "sysname": csharpType = "object"; break; + case "text": csharpType = "string"; break; + case "time": csharpType = "TimeSpan"; break; + case "timestamp": csharpType = "byte[]"; break; + case "tinyint": csharpType = "byte"; break; + case "uniqueidentifier": csharpType = "Guid"; break; + case "varbinary": csharpType = "byte[]"; break; + case "varchar": csharpType = "string"; break; + case "xml": csharpType = "string"; break; + default: csharpType = "object"; break; + } + return csharpType; + } + + public static Type MapCommonType(string dbtype) + { + if (string.IsNullOrEmpty(dbtype)) return Type.Missing.GetType(); + dbtype = dbtype.ToLower(); + Type commonType = typeof(object); + switch (dbtype) + { + case "bigint": commonType = typeof(long); break; + case "binary": commonType = typeof(byte[]); break; + case "bit": commonType = typeof(bool); break; + case "char": commonType = typeof(string); break; + case "date": commonType = typeof(DateTime); break; + case "datetime": commonType = typeof(DateTime); break; + case "datetime2": commonType = typeof(DateTime); break; + case "datetimeoffset": commonType = typeof(DateTimeOffset); break; + case "decimal": commonType = typeof(decimal); break; + case "float": commonType = typeof(double); break; + case "image": commonType = typeof(byte[]); break; + case "int": commonType = typeof(int); break; + case "money": commonType = typeof(decimal); break; + case "nchar": commonType = typeof(string); break; + case "ntext": commonType = typeof(string); break; + case "numeric": commonType = typeof(decimal); break; + case "nvarchar": commonType = typeof(string); break; + case "real": commonType = typeof(Single); break; + case "smalldatetime": commonType = typeof(DateTime); break; + case "smallint": commonType = typeof(short); break; + case "smallmoney": commonType = typeof(decimal); break; + case "sql_variant": commonType = typeof(object); break; + case "sysname": commonType = typeof(object); break; + case "text": commonType = typeof(string); break; + case "time": commonType = typeof(TimeSpan); break; + case "timestamp": commonType = typeof(byte[]); break; + case "tinyint": commonType = typeof(byte); break; + case "uniqueidentifier": commonType = typeof(Guid); break; + case "varbinary": commonType = typeof(byte[]); break; + case "varchar": commonType = typeof(string); break; + case "xml": commonType = typeof(string); break; + default: commonType = typeof(object); break; + } + return commonType; + } + } + #endregion + #> \ No newline at end of file diff --git a/Blog.Core.FrameWork/ModelAuto.ttinclude b/Blog.Core.FrameWork/ModelAuto.ttinclude index 15fe9836..5933972f 100644 --- a/Blog.Core.FrameWork/ModelAuto.ttinclude +++ b/Blog.Core.FrameWork/ModelAuto.ttinclude @@ -1,19 +1,15 @@ -//引入命名空间 -<#@ assembly name="System.Core"#> +<#@ assembly name="System.Core"#> <#@ assembly name="EnvDTE"#> <#@ import namespace="System.Collections.Generic"#> <#@ import namespace="System.IO"#> <#@ import namespace="System.Text"#> <#@ import namespace="Microsoft.VisualStudio.TextTemplating"#> - <#+ -//定义管理者 manager 实体类 class Manager { - //定义一个 block 块,主要是应用在批量生产中 public struct Block { - public String Name; public int Start, Length; + public String Name,OutputPath; } public List blocks = new List(); @@ -23,17 +19,13 @@ class Manager public ITextTemplatingEngineHost host; public ManagementStrategy strategy; public StringBuilder template; - public String OutputPath { get; set; } - //构造函数,包含 host主机,模板,输出路径,创建管理策略 public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) { this.host = host; this.template = template; - OutputPath = String.Empty; strategy = ManagementStrategy.Create(host); } - //开辟一个 block 块 - public void StartBlock(String name) { - currentBlock = new Block { Name = name, Start = template.Length }; + public void StartBlock(String name,String outputPath) { + currentBlock = new Block { Name = name, Start = template.Length ,OutputPath=outputPath}; } public void StartFooter() { @@ -56,14 +48,12 @@ class Manager currentBlock.Length = template.Length - currentBlock.Start; blocks.Add(currentBlock); } - //定义进程,用来将所有的 blocks 块执行出来 public void Process(bool split) { String header = template.ToString(headerBlock.Start, headerBlock.Length); String footer = template.ToString(footerBlock.Start, footerBlock.Length); blocks.Reverse(); - foreach(Block block in blocks) {//遍历 - //输出文件 - String fileName = Path.Combine(OutputPath, block.Name); + foreach(Block block in blocks) { + String fileName = Path.Combine(block.OutputPath, block.Name); if (split) { String content = header + template.ToString(block.Start, block.Length) + footer; strategy.CreateFile(fileName, content); @@ -74,7 +64,6 @@ class Manager } } } -//定义管理策略类 class ManagementStrategy { internal static ManagementStrategy Create(ITextTemplatingEngineHost host) { @@ -108,16 +97,13 @@ class VSManagementStrategy : ManagementStrategy templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile); } - //创建文件 internal override void CreateFile(String fileName, String content) { base.CreateFile(fileName, content); - ((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null); + //((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null); } - //删除文件 internal override void DeleteFile(String fileName) { ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null); } - //根据文件名删除文件 private void FindAndDeleteFile(String fileName) { foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) { if (projectItem.get_FileNames(0) == fileName) { diff --git a/Blog.Core.Gateway/Blog.Core.Gateway.csproj b/Blog.Core.Gateway/Blog.Core.Gateway.csproj new file mode 100644 index 00000000..d45e9ba5 --- /dev/null +++ b/Blog.Core.Gateway/Blog.Core.Gateway.csproj @@ -0,0 +1,37 @@ + + + + + ..\Blog.Core.Gateway\Blog.Core.Gateway.xml + 1701;1702;1591 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Blog.Core.Gateway/Blog.Core.Gateway.xml b/Blog.Core.Gateway/Blog.Core.Gateway.xml new file mode 100644 index 00000000..34543342 --- /dev/null +++ b/Blog.Core.Gateway/Blog.Core.Gateway.xml @@ -0,0 +1,55 @@ + + + + Blog.Core.Gateway + + + + + 中间件 + 原做为自定义授权中间件 + 先做检查 header token的使用 + + + + + 验证方案提供对象 + + + + + 请求上下文 + + + + + 网关授权 + + + + + + + 返回相应 + + + + + + + + + 判断是否在白名单内,支持通配符 **** + + + + + + ┌──────────────────────────────────────────────────────────────┐ + │ 描 述:模拟一个网关项目 + │ 测 试:在网关swagger中查看具体的服务 + │ 作 者:anson zhang + └──────────────────────────────────────────────────────────────┘ + + + diff --git a/Blog.Core.Gateway/Controllers/UserController.cs b/Blog.Core.Gateway/Controllers/UserController.cs new file mode 100644 index 00000000..36532021 --- /dev/null +++ b/Blog.Core.Gateway/Controllers/UserController.cs @@ -0,0 +1,42 @@ +using Blog.Core.Common.HttpContextUser; +using Blog.Core.Model; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; + +namespace Blog.Core.Gateway.Controllers +{ + [Authorize(AuthenticationSchemes = Permissions.GWName)] + [Route("/gateway/[controller]/[action]")] + public class UserController : ControllerBase + { + private readonly IUser _user; + + public UserController(IUser user) + { + _user = user; + } + + [HttpGet] + public MessageModel> MyClaims() + { + return new MessageModel>() + { + success = true, + response = (_user.GetClaimsIdentity().ToList()).Select(d => + new ClaimDto + { + Type = d.Type, + Value = d.Value + } + ).ToList() + }; + } + } + public class ClaimDto + { + public string Type { get; set; } + public string Value { get; set; } + } +} diff --git a/Blog.Core.Gateway/Extensions/ApiResponseHandler.cs b/Blog.Core.Gateway/Extensions/ApiResponseHandler.cs new file mode 100644 index 00000000..c4af6455 --- /dev/null +++ b/Blog.Core.Gateway/Extensions/ApiResponseHandler.cs @@ -0,0 +1,77 @@ +using Blog.Core.Model; +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 +{ + /// + /// 这里不需要,目前集成的是 Blog.Core.Extensions 下的接口处理器 + /// 但是你可以单独在网关中使用这个。 + /// + public class ApiResponseHandler : DelegatingHandler + { + JsonSerializerSettings jsonSerializerSettings = 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 + { + result = JsonConvert.DeserializeObject(resultStr); + } + catch (Exception) + { + return response; + } + + if (result != null && result.code == 500) resultStr = result.msg.ToString(); + + var apiResponse = new ApiResponse(StatusCode.CODE200).MessageModel; + if (response.StatusCode != HttpStatusCode.OK || result.code == (int)HttpStatusCode.InternalServerError) + { + var exception = new Exception(resultStr); + apiResponse = new ApiResponse(StatusCode.CODE500).MessageModel; + } + else if (result.code == (int)HttpStatusCode.Unauthorized) + { + apiResponse = new ApiResponse(StatusCode.CODE401).MessageModel; + + } + else if (result.code == (int)HttpStatusCode.Forbidden) + { + apiResponse = new ApiResponse(StatusCode.CODE403).MessageModel; + + } + else + { + + } + + var statusCode = apiResponse.status == 500 ? HttpStatusCode.InternalServerError + : apiResponse.status == 401 ? HttpStatusCode.Unauthorized + : apiResponse.status == 403 ? HttpStatusCode.Forbidden + : HttpStatusCode.OK; + + response.StatusCode = statusCode; + response.Content = new StringContent(JsonConvert.SerializeObject(apiResponse, jsonSerializerSettings), Encoding.UTF8, "application/json"); + + return response; + } + } + + +} 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 new file mode 100644 index 00000000..b197c824 --- /dev/null +++ b/Blog.Core.Gateway/Extensions/CustomOcelotSetup.cs @@ -0,0 +1,34 @@ +using Blog.Core.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.Provider.Nacos; +using Ocelot.Provider.Polly; +using System; +using System.Threading.Tasks; + +namespace Blog.Core.Gateway.Extensions +{ + public static class CustomOcelotSetup + { + public static void AddCustomOcelotSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddAuthentication_JWTSetup(); + services.AddOcelot() + .AddDelegatingHandler() + //.AddNacosDiscovery() + //.AddConsul() + .AddPolly(); + } + + public static async Task UseCustomOcelotMildd(this IApplicationBuilder app) + { + await app.UseOcelot(); + return 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 new file mode 100644 index 00000000..033feeb5 --- /dev/null +++ b/Blog.Core.Gateway/Extensions/CustomSwaggerSetup.cs @@ -0,0 +1,81 @@ +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 + { + public static void AddCustomSwaggerSetup(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + var basePath = AppContext.BaseDirectory; + + services.AddMvc(option => option.EnableEndpointRouting = false); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "自定义网关 接口文档", + }); + + var xmlPath = Path.Combine(basePath, "Blog.Core.Gateway.xml"); + c.IncludeXmlComments(xmlPath, true); + + c.OperationFilter(); + c.OperationFilter(); + + c.OperationFilter(); + + c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme + { + Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey + }); + }); + } + + 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", "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 new file mode 100644 index 00000000..9c1ba1ce --- /dev/null +++ b/Blog.Core.Gateway/Program.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Blog.Core.AdminMvc +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostingContext, config) => + { + 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 => + { + webBuilder.UseStartup().UseUrls("http://*:9000"); + }); + } +} diff --git a/Blog.Core/Properties/launchSettings.json b/Blog.Core.Gateway/Properties/launchSettings.json similarity index 57% rename from Blog.Core/Properties/launchSettings.json rename to Blog.Core.Gateway/Properties/launchSettings.json index 059251aa..6265ef34 100644 --- a/Blog.Core/Properties/launchSettings.json +++ b/Blog.Core.Gateway/Properties/launchSettings.json @@ -1,13 +1,12 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { - "Blog.Core": { + "Blog.Core.Gateway": { "commandName": "Project", "launchBrowser": true, - "applicationUrl": "http://localhost:8081", + "applicationUrl": "http://localhost:9000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} \ No newline at end of file +} diff --git a/Blog.Core.Gateway/Startup.cs b/Blog.Core.Gateway/Startup.cs new file mode 100644 index 00000000..d8f81253 --- /dev/null +++ b/Blog.Core.Gateway/Startup.cs @@ -0,0 +1,80 @@ +using Blog.Core.AuthHelper; +using Blog.Core.Common; +using Blog.Core.Common.Caches; +using Blog.Core.Extensions; +using Blog.Core.Gateway.Extensions; +using Microsoft.AspNetCore.Authentication; +using System.Reflection; +using Blog.Core.Common.Caches.Interface; + +namespace Blog.Core.AdminMvc +{ + public class Startup + { + /** + *┌──────────────────────────────────────────────────────────────┐ + *│ 描 述:模拟一个网关项目 + *│ 测 试:在网关swagger中查看具体的服务 + *│ 作 者:anson zhang + *└──────────────────────────────────────────────────────────────┘ + */ + public Startup(IConfiguration configuration, IWebHostEnvironment env) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + // 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.AddAuthentication() + .AddScheme(Permissions.GWName, _ => { }); + + + services.AddCustomSwaggerSetup(); + + services.AddControllers(); + + services.AddHttpContextSetup(); + + services.AddCorsSetup(); + + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + services.AddSingleton(); + + services.AddCustomOcelotSetup(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseCustomSwaggerMildd(() => Assembly.GetExecutingAssembly().GetManifestResourceStream("Blog.Core.Gateway.index.html")); + + 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.gw.Development.json b/Blog.Core.Gateway/appsettings.gw.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/Blog.Core.Gateway/appsettings.gw.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} 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/index.html b/Blog.Core.Gateway/index.html similarity index 78% rename from Blog.Core/index.html rename to Blog.Core.Gateway/index.html index d918c7ed..9d9cbcfa 100644 --- a/Blog.Core/index.html +++ b/Blog.Core.Gateway/index.html @@ -1,8 +1,7 @@ - @@ -10,17 +9,18 @@ + + + %(DocumentTitle) - - + %(HeadContent) @@ -71,6 +79,11 @@
    + diff --git a/Blog.Core.Gateway/ocelot.Development.json b/Blog.Core.Gateway/ocelot.Development.json new file mode 100644 index 00000000..589a5a6e --- /dev/null +++ b/Blog.Core.Gateway/ocelot.Development.json @@ -0,0 +1,54 @@ +{ + "Routes": [ + // blog-svc + { + "UpstreamPathTemplate": "/svc/blog/{url}", + "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], + "LoadBalancerOptions": { + "Type": "RoundRobin" + }, + "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": "/swagger/apiswg/blog-svc/swagger.json", + "UpstreamHttpMethod": [ "GET" ], + "DownstreamPathTemplate": "/swagger/V2/swagger.json", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 9291 + } + ], + "LoadBalancer": "RoundRobin" + } + + ], + "GlobalConfiguration": { + "BaseUrl": "http://localhost:9000" + } +} \ No newline at end of file diff --git a/Blog.Core.Gateway/ocelot.Production.json b/Blog.Core.Gateway/ocelot.Production.json new file mode 100644 index 00000000..0db3279e --- /dev/null +++ b/Blog.Core.Gateway/ocelot.Production.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/Blog.Core.Gateway/ocelot.Staging.json b/Blog.Core.Gateway/ocelot.Staging.json new file mode 100644 index 00000000..0db3279e --- /dev/null +++ b/Blog.Core.Gateway/ocelot.Staging.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/Blog.Core.Gateway/ocelot.json b/Blog.Core.Gateway/ocelot.json new file mode 100644 index 00000000..29091fa5 --- /dev/null +++ b/Blog.Core.Gateway/ocelot.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/Blog.Core.IRepository/BASE/IBaseRepository.cs b/Blog.Core.IRepository/BASE/IBaseRepository.cs deleted file mode 100644 index ce928c7a..00000000 --- a/Blog.Core.IRepository/BASE/IBaseRepository.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository.Base -{ - public interface IBaseRepository where TEntity : class - { - - Task QueryById(object objId); - Task QueryById(object objId, bool blnUseCache = false); - Task> QueryByIDs(object[] lstIds); - - Task Add(TEntity model); - - Task DeleteById(object id); - - Task Delete(TEntity model); - - Task DeleteByIds(object[] ids); - - Task Update(TEntity model); - Task Update(TEntity entity, string strWhere); - - Task Update(TEntity entity, List lstColumns = null, List lstIgnoreColumns = null, string strWhere = ""); - - Task> Query(); - Task> Query(string strWhere); - Task> Query(Expression> whereExpression); - Task> Query(Expression> whereExpression, string strOrderByFileds); - Task> Query(Expression> whereExpression, Expression> orderByExpression, bool isAsc = true); - Task> Query(string strWhere, string strOrderByFileds); - - Task> Query(Expression> whereExpression, int intTop, string strOrderByFileds); - Task> Query(string strWhere, int intTop, string strOrderByFileds); - - Task> Query( - Expression> whereExpression, int intPageIndex, int intPageSize, string strOrderByFileds); - Task> Query(string strWhere, int intPageIndex, int intPageSize, string strOrderByFileds); - - - Task> QueryPage(Expression> whereExpression, int intPageIndex = 0, int intPageSize = 20, string strOrderByFileds = null); - } -} diff --git a/Blog.Core.IRepository/Blog.Core.IRepository.csproj b/Blog.Core.IRepository/Blog.Core.IRepository.csproj deleted file mode 100644 index bc72ec5d..00000000 --- a/Blog.Core.IRepository/Blog.Core.IRepository.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netcoreapp2.2 - - - - - - - diff --git a/Blog.Core.IRepository/IAdvertisementRepository.cs b/Blog.Core.IRepository/IAdvertisementRepository.cs deleted file mode 100644 index 93734e12..00000000 --- a/Blog.Core.IRepository/IAdvertisementRepository.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository -{ - public interface IAdvertisementRepository : IBaseRepository - { - //int Sum(int i, int j); - - //int Add(Advertisement model); - //bool Delete(Advertisement model); - //bool Update(Advertisement model); - //List Query(Expression> whereExpression); - - } -} diff --git a/Blog.Core.IRepository/IBlogArticleRepository.cs b/Blog.Core.IRepository/IBlogArticleRepository.cs deleted file mode 100644 index c734accc..00000000 --- a/Blog.Core.IRepository/IBlogArticleRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository -{ - public interface IBlogArticleRepository : IBaseRepository - { - } -} diff --git a/Blog.Core.IRepository/IGuestbookRepository.cs b/Blog.Core.IRepository/IGuestbookRepository.cs deleted file mode 100644 index e74863a3..00000000 --- a/Blog.Core.IRepository/IGuestbookRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository -{ - public interface IGuestbookRepository : IBaseRepository - { - } -} diff --git a/Blog.Core.IRepository/IModulePermissionRepository.cs b/Blog.Core.IRepository/IModulePermissionRepository.cs deleted file mode 100644 index 93adeafc..00000000 --- a/Blog.Core.IRepository/IModulePermissionRepository.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Blog.Core.IRepository -{ - using Blog.Core.IRepository.Base; - using Blog.Core.Model.Models; - - public partial interface IModulePermissionRepository : IBaseRepository//类名 - { - } -} \ No newline at end of file diff --git a/Blog.Core.IRepository/IModuleRepository.cs b/Blog.Core.IRepository/IModuleRepository.cs deleted file mode 100644 index 2e3e2337..00000000 --- a/Blog.Core.IRepository/IModuleRepository.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; - -namespace Blog.Core.IRepository -{ - /// - /// IModuleRepository - /// - public interface IModuleRepository : IBaseRepository//类名 - { - - - } -} - - - \ No newline at end of file diff --git a/Blog.Core.IRepository/IPasswordLibRepository.cs b/Blog.Core.IRepository/IPasswordLibRepository.cs deleted file mode 100644 index 06cdb2b2..00000000 --- a/Blog.Core.IRepository/IPasswordLibRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository -{ - public partial interface IPasswordLibRepository : IBaseRepository - { - - } -} diff --git a/Blog.Core.IRepository/IPermissionRepository.cs b/Blog.Core.IRepository/IPermissionRepository.cs deleted file mode 100644 index 9d46a3c8..00000000 --- a/Blog.Core.IRepository/IPermissionRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ - -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; - -namespace Blog.Core.IRepository -{ - public partial interface IPermissionRepository : IBaseRepository - { - } -} \ No newline at end of file diff --git a/Blog.Core.IRepository/IRoleModulePermissionRepository.cs b/Blog.Core.IRepository/IRoleModulePermissionRepository.cs deleted file mode 100644 index aef9b72b..00000000 --- a/Blog.Core.IRepository/IRoleModulePermissionRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository -{ - /// - /// IRoleModulePermissionRepository - /// - public interface IRoleModulePermissionRepository : IBaseRepository//类名 - { - Task> WithChildrenModel(); - } -} diff --git a/Blog.Core.IRepository/IRoleRepository.cs b/Blog.Core.IRepository/IRoleRepository.cs deleted file mode 100644 index d8ead263..00000000 --- a/Blog.Core.IRepository/IRoleRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; - -namespace Blog.Core.IRepository -{ - /// - /// IRoleRepository - /// - public interface IRoleRepository : IBaseRepository//类名 - { - - - } -} diff --git a/Blog.Core.IRepository/ITopicDetailRepository.cs b/Blog.Core.IRepository/ITopicDetailRepository.cs deleted file mode 100644 index 536f12c7..00000000 --- a/Blog.Core.IRepository/ITopicDetailRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository -{ - public interface ITopicDetailRepository : IBaseRepository - { - } -} diff --git a/Blog.Core.IRepository/ITopicRepository.cs b/Blog.Core.IRepository/ITopicRepository.cs deleted file mode 100644 index 516eee39..00000000 --- a/Blog.Core.IRepository/ITopicRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.IRepository -{ - public interface ITopicRepository : IBaseRepository - { - } -} diff --git a/Blog.Core.IRepository/IUserRoleRepository.cs b/Blog.Core.IRepository/IUserRoleRepository.cs deleted file mode 100644 index cb538cc3..00000000 --- a/Blog.Core.IRepository/IUserRoleRepository.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; - -namespace Blog.Core.FrameWork.IRepository -{ - /// - /// IUserRoleRepository - /// - public interface IUserRoleRepository : IBaseRepository//类名 - { - - - } -} - - \ No newline at end of file diff --git a/Blog.Core.IRepository/IsysUserInfoRepository.cs b/Blog.Core.IRepository/IsysUserInfoRepository.cs deleted file mode 100644 index a9fa82cd..00000000 --- a/Blog.Core.IRepository/IsysUserInfoRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Blog.Core.IRepository.Base; -using Blog.Core.Model.Models; - -namespace Blog.Core.IRepository -{ - /// - /// IsysUserInfoRepository - /// - public interface IsysUserInfoRepository : IBaseRepository//类名 - { - - - } -} diff --git a/Blog.Core.IServices/BASE/IBaseServices.cs b/Blog.Core.IServices/BASE/IBaseServices.cs index 16871928..a496b59f 100644 --- a/Blog.Core.IServices/BASE/IBaseServices.cs +++ b/Blog.Core.IServices/BASE/IBaseServices.cs @@ -1,47 +1,73 @@ -using System; +using Blog.Core.Model; +using SqlSugar; +using System; using System.Collections.Generic; +using System.Data; using System.Linq.Expressions; -using System.Text; using System.Threading.Tasks; 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 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 orderByFields); Task> Query(Expression> whereExpression, Expression> orderByExpression, bool isAsc = true); - Task> Query(string strWhere, string strOrderByFileds); + 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 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); - Task> QueryPage(Expression> whereExpression, int intPageIndex = 0, int intPageSize = 20, string strOrderByFileds = null); + #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 bc72ec5d..b5cc2f2c 100644 --- a/Blog.Core.IServices/Blog.Core.IServices.csproj +++ b/Blog.Core.IServices/Blog.Core.IServices.csproj @@ -1,10 +1,8 @@ - + - - netcoreapp2.2 - + 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/IAdvertisementServices.cs b/Blog.Core.IServices/IAdvertisementServices.cs index 4b121bae..e4b43aff 100644 --- a/Blog.Core.IServices/IAdvertisementServices.cs +++ b/Blog.Core.IServices/IAdvertisementServices.cs @@ -1,11 +1,5 @@ using Blog.Core.IServices.BASE; using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.IServices { diff --git a/Blog.Core.IServices/IBlogArticleServices.cs b/Blog.Core.IServices/IBlogArticleServices.cs index dcfa92ca..a38826fb 100644 --- a/Blog.Core.IServices/IBlogArticleServices.cs +++ b/Blog.Core.IServices/IBlogArticleServices.cs @@ -1,10 +1,7 @@ using Blog.Core.IServices.BASE; using Blog.Core.Model.Models; using Blog.Core.Model.ViewModels; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Blog.Core.IServices @@ -12,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 new file mode 100644 index 00000000..4be6a876 --- /dev/null +++ b/Blog.Core.IServices/IDS4Db/IApplicationUserServices.cs @@ -0,0 +1,11 @@ +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 5a822250..cd8b2287 100644 --- a/Blog.Core.IServices/IGuestbookServices.cs +++ b/Blog.Core.IServices/IGuestbookServices.cs @@ -1,14 +1,21 @@ using Blog.Core.IServices.BASE; +using Blog.Core.Model; using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Blog.Core.IServices { 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/IModuleServices.cs b/Blog.Core.IServices/IModuleServices.cs index 74bf65ed..fd3c50a4 100644 --- a/Blog.Core.IServices/IModuleServices.cs +++ b/Blog.Core.IServices/IModuleServices.cs @@ -6,7 +6,7 @@ namespace Blog.Core.IServices /// /// ModuleServices /// - public interface IModuleServices :IBaseServices + public interface IModuleServices :IBaseServices { diff --git a/Blog.Core.IServices/IOperateLogServices.cs b/Blog.Core.IServices/IOperateLogServices.cs new file mode 100644 index 00000000..6f0ef3fe --- /dev/null +++ b/Blog.Core.IServices/IOperateLogServices.cs @@ -0,0 +1,14 @@ +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices +{ + /// + /// IOperateLogServices + /// + public interface IOperateLogServices : IBaseServices + { + + } +} + \ No newline at end of file diff --git a/Blog.Core.IServices/IPasswordLibServices.cs b/Blog.Core.IServices/IPasswordLibServices.cs index 4d167273..67532fd6 100644 --- a/Blog.Core.IServices/IPasswordLibServices.cs +++ b/Blog.Core.IServices/IPasswordLibServices.cs @@ -1,14 +1,14 @@ -using Blog.Core.IServices.BASE; +using System.Threading.Tasks; +using Blog.Core.IServices.BASE; using Blog.Core.Model.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.IServices { public partial interface IPasswordLibServices :IBaseServices { + Task TestTranPropagation2(); + Task TestTranPropagationNoTranError(); + Task TestTranPropagationTran2(); + Task TestTranPropagationTran3(); } } diff --git a/Blog.Core.IServices/IPayServices.cs b/Blog.Core.IServices/IPayServices.cs new file mode 100644 index 00000000..712ada39 --- /dev/null +++ b/Blog.Core.IServices/IPayServices.cs @@ -0,0 +1,42 @@ + +using Blog.Core.IServices.BASE; +using Blog.Core.Model; +using Blog.Core.Model.ViewModels; +using System.Threading.Tasks; + +namespace Blog.Core.IServices +{ + /// + /// IPayServices + /// + public interface IPayServices : IBaseServices> + { + /// + /// 被扫支付 + /// + /// + Task> Pay(PayNeedModel payModel); + /// + /// 退款 + /// + /// + /// + Task> PayRefund(PayRefundNeedModel payModel); + /// + /// 轮询查询 + /// + /// + /// 轮询次数 + /// + Task> PayCheck(PayNeedModel payModel,int times); + /// + /// 验证签名 + /// + /// 参数 + /// 签名 + /// 公钥 + /// + bool NotifyCheck(string strSrc, string sign, string pubKey); + + } +} diff --git a/Blog.Core.IServices/IRoleModulePermissionServices.cs b/Blog.Core.IServices/IRoleModulePermissionServices.cs index 54ce0889..2a5c7345 100644 --- a/Blog.Core.IServices/IRoleModulePermissionServices.cs +++ b/Blog.Core.IServices/IRoleModulePermissionServices.cs @@ -4,14 +4,23 @@ using System.Threading.Tasks; namespace Blog.Core.IServices -{ - /// - /// RoleModulePermissionServices - /// +{ + /// + /// RoleModulePermissionServices + /// public interface IRoleModulePermissionServices :IBaseServices { Task> GetRoleModule(); - Task> TestModelWithChildren(); + Task> QueryMuchTable(); + Task> RoleModuleMaps(); + Task> GetRMPMaps(); + /// + /// ²˵ӿڵĹϵ + /// + /// ˵ + /// ӿ + /// + 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/ITasksQzServices.cs b/Blog.Core.IServices/ITasksQzServices.cs new file mode 100644 index 00000000..f2717969 --- /dev/null +++ b/Blog.Core.IServices/ITasksQzServices.cs @@ -0,0 +1,16 @@ + +using Blog.Core.IServices.BASE; +using Blog.Core.Model.Models; + +namespace Blog.Core.IServices +{ + /// + /// ITasksQzServices + /// + public interface ITasksQzServices :IBaseServices + { + + + } +} + \ 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/ITopicDetailServices.cs b/Blog.Core.IServices/ITopicDetailServices.cs index b147e78a..745ab7f3 100644 --- a/Blog.Core.IServices/ITopicDetailServices.cs +++ b/Blog.Core.IServices/ITopicDetailServices.cs @@ -1,9 +1,6 @@ using Blog.Core.IServices.BASE; using Blog.Core.Model.Models; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Blog.Core.IServices diff --git a/Blog.Core.IServices/ITopicServices.cs b/Blog.Core.IServices/ITopicServices.cs index 55863516..9fcccb24 100644 --- a/Blog.Core.IServices/ITopicServices.cs +++ b/Blog.Core.IServices/ITopicServices.cs @@ -1,9 +1,6 @@ using Blog.Core.IServices.BASE; using Blog.Core.Model.Models; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Blog.Core.IServices 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.Model/ApiResponse.cs b/Blog.Core.Model/ApiResponse.cs new file mode 100644 index 00000000..74944aea --- /dev/null +++ b/Blog.Core.Model/ApiResponse.cs @@ -0,0 +1,56 @@ +namespace Blog.Core.Model +{ + public class ApiResponse + { + public int Status { get; set; } = 200; + public string Value { get; set; } = ""; + public MessageModel MessageModel = new MessageModel() { }; + + public ApiResponse(StatusCode apiCode, string msg = null) + { + switch (apiCode) + { + case StatusCode.CODE401: + { + Status = 401; + Value = msg ?? "很抱歉,您无权访问该接口,请确保已经登录!"; + } + break; + case StatusCode.CODE403: + { + Status = 403; + Value = msg ?? "很抱歉,您的访问权限等级不够,联系管理员!"; + } + break; + case StatusCode.CODE404: + { + Status = 404; + Value = "资源不存在!"; + } + break; + case StatusCode.CODE500: + { + Status = 500; + Value = msg; + } + break; + } + + MessageModel = new MessageModel() + { + status = Status, + msg = Value, + success = apiCode == StatusCode.CODE200 + }; + } + } + + public enum StatusCode + { + CODE200, + CODE401, + CODE403, + 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 89ee9130..fb04c45c 100644 --- a/Blog.Core.Model/Blog.Core.Model.csproj +++ b/Blog.Core.Model/Blog.Core.Model.csproj @@ -1,20 +1,25 @@ - + - - netcoreapp2.2 - - - ..\Blog.Core\Blog.Core.Model.xml - 1701;1702;1591 - + + ..\Blog.Core.Api\Blog.Core.Model.xml + 1701;1702;1591 + - - - + + ..\Blog.Core\Blog.Core.Model.xml + 1701;1702;1591 + - - - + + + + + + + + + + diff --git a/Blog.Core.Model/Blog.Core.Model.xml b/Blog.Core.Model/Blog.Core.Model.xml deleted file mode 100644 index a779cd2c..00000000 --- a/Blog.Core.Model/Blog.Core.Model.xml +++ /dev/null @@ -1,963 +0,0 @@ - - - - Blog.Core.Model - - - - - 这是爱 - - - - - id - - - - - 姓名 - - - - - 年龄 - - - - - 通用返回信息类 - - - - - 操作是否成功 - - - - - 返回信息 - - - - - 返回数据集合 - - - - - 分类ID - - - - - 创建时间 - - - - - 广告图片 - - - - - 广告标题 - - - - - 广告链接 - - - - - 备注 - - - - - - - - - - - 创建人 - - - - - 博客标题 - - - - - 类别 - - - - - 内容 - - - - - 访问量 - - - - - 评论数量 - - - - - 修改时间 - - - - - 创建时间 - - - - - 备注 - - - - 留言表 - - - - - 博客ID - - - - - 创建时间 - - - - - 手机 - - - - - qq - - - - - 留言内容 - - - - - ip地址 - - - - - 是否显示在前台,0否1是 - - - - - - 菜单类 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 父ID - - - - - 名称 - - - - - 菜单链接地址 - - - - - 区域名称 - - - - - 控制器名称 - - - - - Action名称 - - - - - 图标 - - - - - 菜单编号 - - - - - 排序 - - - - - /描述 - - - - - 是否是右侧菜单 - - - - - 是否激活 - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 菜单与按钮关系表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 菜单ID - - - - - 按钮ID - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 日志记录 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 区域名 - - - - - 区域控制器名 - - - - - Action名称 - - - - - IP地址 - - - - - 描述 - - - - - 登录时间 - - - - - 登录名称 - - - - - 用户ID - - - - - 用户跟角色关联表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 按钮表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 菜单执行Action名 - - - - - 菜单名 - - - - - 排序 - - - - - 菜单图标 - - - - - 菜单描述 - - - - - 激活状态 - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 角色表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 角色名 - - - - - 描述 - - - - - 排序 - - - - - 是否激活 - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 按钮跟权限关联表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 角色ID - - - - - 菜单ID - - - - - 按钮ID - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 用户ID - - - - - 登录账号 - - - - - 登录密码 - - - - - 真实姓名 - - - - - 状态 - - - - - 备注 - - - - - 创建时间 - - - - - 更新时间 - - - - - 最后登录时间 - - - - - 错误次数 - - - - - - - 用户跟角色关联表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 用户ID - - - - - 角色ID - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 表格数据,支持分页 - - - - - 返回编码 - - - - - 返回信息 - - - - - 记录总数 - - - - - 返回数据集 - - - - - 广告类 - - - - - 分类ID - - - - - 创建时间 - - - - - 广告图片 - - - - - 广告标题 - - - - - 广告链接 - - - - - 备注 - - - - - 博客信息展示类 - - - - - - - - - 创建人 - - - - - 博客标题 - - - - - 摘要 - - - - - - 上一篇 - - - - - 上一篇id - - - - - 下一篇 - - - - - 下一篇id - - - - 类别 - - - - - 内容 - - - - - - 访问量 - - - - - 评论数量 - - - - 修改时间 - - - - - - 创建时间 - - - - 备注 - - - - - - 留言信息展示类 - - - - 留言表 - - - - - 博客ID - - - - - 创建时间 - - - - - 手机 - - - - - qq - - - - - 留言内容 - - - - - ip地址 - - - - - 是否显示在前台,0否1是 - - - - - - 菜单展示model - - - - - 留言排名展示类 - - - - 博客ID - - - - - - 评论数量 - - - - 博客标题 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 new file mode 100644 index 00000000..8baf76ea --- /dev/null +++ b/Blog.Core.Model/IDS4DbModels/ApplicationRole.cs @@ -0,0 +1,49 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model.IDS4DbModels +{ + /// + /// 以下model 来自ids4项目,多库模式,为了调取ids4数据 + /// 角色表 + /// + [SugarTable("ApplicationRole", "角色表")] //('数据库表名','数据库表备注') + [TenantAttribute("WMBLOG_MYSQL_2")] //('代表是哪个数据库,名字是appsettings.json 的 ConnId') + public class ApplicationRole + { + public bool IsDeleted { get; set; } + public string Description { get; set; } + /// + ///排序 + /// + public int OrderSort { get; set; } + /// + /// 是否激活 + /// + public bool Enabled { get; set; } + /// + /// 创建ID + /// + public int? CreateId { get; set; } + /// + /// 创建者 + /// + public string CreateBy { get; set; } + /// + /// 创建时间 + /// + public DateTime? CreateTime { get; set; } = DateTime.Now; + /// + /// 修改ID + /// + public int? ModifyId { get; set; } + /// + /// 修改者 + /// + public string ModifyBy { get; set; } + /// + /// 修改时间 + /// + public DateTime? ModifyTime { get; set; } = DateTime.Now; + } +} diff --git a/Blog.Core.Model/IDS4DbModels/ApplicationUser.cs b/Blog.Core.Model/IDS4DbModels/ApplicationUser.cs new file mode 100644 index 00000000..50529166 --- /dev/null +++ b/Blog.Core.Model/IDS4DbModels/ApplicationUser.cs @@ -0,0 +1,29 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model.IDS4DbModels +{ + /// + /// 以下model 来自ids4项目,多库模式,为了调取ids4数据 + /// 用户表 + /// + [SugarTable("AspNetUsers", "用户表")]//('数据库表名','数据库表备注') + [TenantAttribute("WMBLOG_MYSQL_2")] //('代表是哪个数据库,名字是appsettings.json 的 ConnId') + public class ApplicationUser + { + public string LoginName { get; set; } + + public string RealName { 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; } + + } +} 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/Love.cs b/Blog.Core.Model/Love.cs index ccd65c41..752677d1 100644 --- a/Blog.Core.Model/Love.cs +++ b/Blog.Core.Model/Love.cs @@ -1,6 +1,4 @@ -using System; - -namespace Blog.Core.Model +namespace Blog.Core.Model { /// /// 这是爱 diff --git a/Blog.Core.Model/MessageModel.cs b/Blog.Core.Model/MessageModel.cs index e72115a1..4b7adf13 100644 --- a/Blog.Core.Model/MessageModel.cs +++ b/Blog.Core.Model/MessageModel.cs @@ -1,14 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Blog.Core.Model +namespace Blog.Core.Model { /// /// 通用返回信息类 /// public class MessageModel { + /// + /// 状态码 + /// + public int status { get; set; } = 200; /// /// 操作是否成功 /// @@ -16,11 +16,83 @@ public class MessageModel /// /// 返回信息 /// - public string msg { get; set; } = "服务器异常"; + public string msg { get; set; } = ""; + /// + /// 开发者信息 + /// + public string msgDev { get; set; } /// /// 返回数据集合 /// public T response { get; set; } + /// + /// 返回成功 + /// + /// 消息 + /// + public static MessageModel Success(string msg) { + return Message(true, msg, default); + } + /// + /// 返回成功 + /// + /// 消息 + /// 数据 + /// + public static MessageModel Success(string msg, T response) + { + return Message(true, msg, response); + } + /// + /// 返回失败 + /// + /// 消息 + /// + public static MessageModel Fail(string msg) + { + return Message(false, msg, default); + } + /// + /// 返回失败 + /// + /// 消息 + /// 数据 + /// + public static MessageModel Fail(string msg, T response) + { + return Message(false, msg, response); + } + /// + /// 返回消息 + /// + /// 失败/成功 + /// 消息 + /// 数据 + /// + public static MessageModel Message(bool success,string msg, T response ) + { + return new MessageModel() { msg = msg, response = response, success = success }; + } + } + + public class MessageModel + { + /// + /// 状态码 + /// + public int status { get; set; } = 200; + /// + /// 操作是否成功 + /// + public bool success { get; set; } = false; + /// + /// 返回信息 + /// + public string msg { get; set; } = ""; + /// + /// 返回数据集合 + /// + public object response { get; set; } } } 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 eed2b987..3b11b21f 100644 --- a/Blog.Core.Model/Models/Advertisement.cs +++ b/Blog.Core.Model/Models/Advertisement.cs @@ -3,7 +3,7 @@ namespace Blog.Core.Model.Models { - public class Advertisement : RootEntity + public class Advertisement : RootEntityTkey { /// @@ -27,7 +27,7 @@ public class Advertisement : RootEntity /// /// 备注 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [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 e9d7e297..8b75c8df 100644 --- a/Blog.Core.Model/Models/BlogArticle.cs +++ b/Blog.Core.Model/Models/BlogArticle.cs @@ -1,25 +1,30 @@ using SqlSugar; using System; +using System.Collections.Generic; namespace Blog.Core.Model.Models { /// /// 博客文章 - /// public class BlogArticle { /// /// 主键 /// /// 这里之所以没用RootEntity,是想保持和之前的数据库一致,主键是bID,不是Id - [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] - public int bID { get; set; } + [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = false)] + public long bID { get; set; } + /// /// 创建人 /// - [SugarColumn(Length = 60, IsNullable = true)] + [SugarColumn(Length = 600, IsNullable = true)] public string bsubmitter { get; set; } + [Navigate(NavigateType.OneToOne, nameof(bsubmitter))] + public SysUserInfo User { get; set; } + /// /// 标题blog /// @@ -29,13 +34,13 @@ public class BlogArticle /// /// 类别 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string bcategory { get; set; } /// /// 内容 /// - [SugarColumn(IsNullable = true, ColumnDataType = "text")] + [SugarColumn(Length = 2000, IsNullable = true)] public string bcontent { get; set; } /// @@ -57,10 +62,11 @@ public class BlogArticle /// 创建时间 /// public System.DateTime bCreateTime { get; set; } + /// /// 备注 /// - [SugarColumn(Length = int.MaxValue, 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 e50ccb9e..0cd5dcef 100644 --- a/Blog.Core.Model/Models/Guestbook.cs +++ b/Blog.Core.Model/Models/Guestbook.cs @@ -3,47 +3,41 @@ namespace Blog.Core.Model.Models { - public class Guestbook + public class Guestbook : RootEntityTkey { - /// - /// 留言表 - /// - [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] - public int id { get; set; } - /// 博客ID /// /// - public int? blogId { get; set; } + public long? blogId { get; set; } /// 创建时间 /// /// public DateTime createdate { get; set; } - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string username { get; set; } /// 手机 /// /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string phone { get; set; } /// qq /// /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string QQ { get; set; } /// 留言内容 /// /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string body { get; set; } /// ip地址 /// /// - [SugarColumn(Length = int.MaxValue, 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 a291e88b..00000000 --- a/Blog.Core.Model/Models/ModulePermission.cs +++ /dev/null @@ -1,63 +0,0 @@ -using SqlSugar; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.Model.Models -{ - /// - /// 菜单与按钮关系表 - /// - public class ModulePermission : RootEntity - { - - /// - ///获取或设置是否禁用,逻辑上的删除,非物理删除 - /// - [SugarColumn(IsNullable = true)] - public bool? IsDeleted { get; set; } - /// - /// 菜单ID - /// - public int ModuleId { get; set; } - /// - /// 按钮ID - /// - public int PermissionId { 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; } - /// - /// 修改ID - /// - [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } - /// - /// 修改者 - /// - [SugarColumn(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/Module.cs b/Blog.Core.Model/Models/Modules.cs similarity index 84% rename from Blog.Core.Model/Models/Module.cs rename to Blog.Core.Model/Models/Modules.cs index 6b5c5ff8..684cfcd0 100644 --- a/Blog.Core.Model/Models/Module.cs +++ b/Blog.Core.Model/Models/Modules.cs @@ -1,18 +1,14 @@ using SqlSugar; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { /// /// 接口API地址信息表 /// - public class Module : RootEntity + public class Modules : ModulesRoot { - public Module() + public Modules() { //this.ChildModule = new List(); //this.ModulePermission = new List(); @@ -25,11 +21,7 @@ public Module() /// [SugarColumn(IsNullable = true)] public bool? IsDeleted { get; set; } - /// - /// 父ID - /// - [SugarColumn(IsNullable = true)] - public int? ParentId { get; set; } + /// /// 名称 /// @@ -43,17 +35,17 @@ public Module() /// /// 区域名称 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Area { get; set; } /// /// 控制器名称 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Controller { get; set; } /// /// Action名称 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Action { get; set; } /// /// 图标 @@ -86,7 +78,7 @@ public Module() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// @@ -101,7 +93,7 @@ public Module() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// diff --git a/Blog.Core.Model/Models/OperateLog.cs b/Blog.Core.Model/Models/OperateLog.cs index 8add3703..3c2fb54c 100644 --- a/Blog.Core.Model/Models/OperateLog.cs +++ b/Blog.Core.Model/Models/OperateLog.cs @@ -1,16 +1,12 @@ using SqlSugar; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { /// /// 日志记录 /// - public class OperateLog : RootEntity + public class OperateLog : RootEntityTkey { /// @@ -21,27 +17,27 @@ public class OperateLog : RootEntity /// /// 区域名 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Area { get; set; } /// /// 区域控制器名 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Controller { get; set; } /// /// Action名称 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Action { get; set; } /// /// IP地址 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string IPAddress { get; set; } /// /// 描述 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string Description { get; set; } /// /// 登录时间 @@ -51,7 +47,7 @@ public class OperateLog : RootEntity /// /// 登录名称 /// - [SugarColumn(Length = int.MaxValue, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string LoginName { get; set; } /// /// 用户ID @@ -59,6 +55,6 @@ public class OperateLog : RootEntity 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 fcb347a6..2037df4d 100644 --- a/Blog.Core.Model/Models/PasswordLib.cs +++ b/Blog.Core.Model/Models/PasswordLib.cs @@ -1,19 +1,17 @@ using SqlSugar; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { /// /// 密码库表 /// + [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; } /// ///获取或设置是否禁用,逻辑上的删除,非物理删除 @@ -30,10 +28,10 @@ public class PasswordLib [SugarColumn(Length = 200, IsNullable = true)] public string plAccountName { get; set; } - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public int? plStatus { get; set; } - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public int? plErrorCount { get; set; } [SugarColumn(Length = 200, IsNullable = true)] @@ -42,13 +40,13 @@ public class PasswordLib [SugarColumn(Length = 200, IsNullable = true)] public string plHintquestion { get; set; } - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public DateTime? plCreateTime { get; set; } - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public DateTime? plUpdateTime { get; set; } - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public DateTime? plLastErrTime { get; set; } [SugarColumn(Length = 200, IsNullable = true)] diff --git a/Blog.Core.Model/Models/Permission.cs b/Blog.Core.Model/Models/Permission.cs index 3ee1358b..95a46b85 100644 --- a/Blog.Core.Model/Models/Permission.cs +++ b/Blog.Core.Model/Models/Permission.cs @@ -1,16 +1,13 @@ using SqlSugar; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { /// /// 路由菜单表 /// - public class Permission : RootEntity + public class Permission : PermissionRoot { public Permission() { @@ -32,16 +29,23 @@ public Permission() /// 是否是按钮 /// public bool IsButton { get; set; } = false; - /// - /// 上一级菜单(0表示上一级无菜单) + /// 是否是隐藏菜单 /// - public int Pid { get; set; } + [SugarColumn(IsNullable = true)] + public bool? IsHide { get; set; } = false; + /// + /// 是否keepAlive + /// + [SugarColumn(IsNullable = true)] + public bool? IskeepAlive { get; set; } = false; + /// - /// 接口api + /// 按钮事件 /// - public int Mid { get; set; } + [SugarColumn(Length = 100, IsNullable = true)] + public string Func { get; set; } /// /// 排序 @@ -53,6 +57,11 @@ public Permission() [SugarColumn(Length = 100, IsNullable = true)] public string Icon { get; set; } /// + /// 菜单图标新 + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string IconNew { get; set; } + /// /// 菜单描述 /// [SugarColumn(Length = 100, IsNullable = true)] @@ -65,7 +74,7 @@ public Permission() /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// @@ -80,7 +89,7 @@ public Permission() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// @@ -99,10 +108,6 @@ public Permission() public bool? IsDeleted { get; set; } - - - [SugarColumn(IsIgnore = true)] - public List PidArr { get; set; } [SugarColumn(IsIgnore = true)] public List PnameArr { get; set; } = new List(); [SugarColumn(IsIgnore = true)] @@ -110,7 +115,14 @@ public Permission() [SugarColumn(IsIgnore = true)] public string MName { get; set; } + [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 262513bf..0e65bcaf 100644 --- a/Blog.Core.Model/Models/Role.cs +++ b/Blog.Core.Model/Models/Role.cs @@ -1,16 +1,12 @@ using SqlSugar; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { /// /// 角色表 /// - public class Role : RootEntity + public class Role : RootEntityTkey { public Role() { @@ -50,14 +46,25 @@ public Role(string name) /// 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; } /// /// 创建ID /// - [SugarColumn( IsNullable = true)] - public int? CreateId { get; set; } + [SugarColumn(IsNullable = true)] + public long? CreateId { get; set; } /// /// 创建者 /// @@ -66,22 +73,22 @@ public Role(string name) /// /// 创建时间 /// - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public DateTime? CreateTime { get; set; } = DateTime.Now; /// /// 修改ID /// - [SugarColumn( IsNullable = true)] - public int? ModifyId { get; set; } + [SugarColumn(IsNullable = true)] + public long? ModifyId { get; set; } /// /// 修改者 /// - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public string ModifyBy { get; set; } /// /// 修改时间 /// - [SugarColumn( IsNullable = true)] + [SugarColumn(IsNullable = true)] public DateTime? ModifyTime { get; set; } = DateTime.Now; diff --git a/Blog.Core.Model/Models/RoleModulePermission.cs b/Blog.Core.Model/Models/RoleModulePermission.cs index d9d52157..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 : RootEntity + public class RoleModulePermission : RoleModulePermissionRoot { public RoleModulePermission() { @@ -21,24 +21,12 @@ public RoleModulePermission() /// [SugarColumn(IsNullable = true)] public bool? IsDeleted { get; set; } - /// - /// 角色ID - /// - public int RoleId { get; set; } - /// - /// 菜单ID - /// - public int ModuleId { get; set; } - /// - /// 按钮ID - /// - [SugarColumn(IsNullable = true)] - public int? PermissionId { get; set; } + /// /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { get; set; } /// /// 创建者 /// @@ -53,7 +41,7 @@ public RoleModulePermission() /// 修改ID /// [SugarColumn(IsNullable = true)] - public int? ModifyId { get; set; } + public long? ModifyId { get; set; } /// /// 修改者 /// @@ -69,7 +57,7 @@ public RoleModulePermission() [SugarColumn(IsIgnore = true)] public Role Role { get; set; } [SugarColumn(IsIgnore = true)] - public Module Module { get; set; } + public Modules Module { get; set; } [SugarColumn(IsIgnore = true)] public Permission Permission { get; set; } } 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/ModulesRoot.cs b/Blog.Core.Model/Models/RootTkey/ModulesRoot.cs new file mode 100644 index 00000000..64d2eef4 --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/ModulesRoot.cs @@ -0,0 +1,19 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model +{ + /// + /// 接口API地址信息表 + /// 父类 + /// + public class ModulesRoot : RootEntityTkey where Tkey : IEquatable + { + /// + /// 父ID + /// + [SugarColumn(IsNullable = true)] + public Tkey ParentId { get; set; } + + } +} diff --git a/Blog.Core.Model/Models/RootTkey/PermissionRoot.cs b/Blog.Core.Model/Models/RootTkey/PermissionRoot.cs new file mode 100644 index 00000000..a001b99f --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/PermissionRoot.cs @@ -0,0 +1,27 @@ +using SqlSugar; +using System; +using System.Collections.Generic; + +namespace Blog.Core.Model +{ + /// + /// 路由菜单表 + /// + public class PermissionRoot : RootEntityTkey where Tkey : IEquatable + { + + /// + /// 上一级菜单(0表示上一级无菜单) + /// + public Tkey Pid { get; set; } + + /// + /// 接口api + /// + public Tkey Mid { get; set; } + + [SugarColumn(IsIgnore = true)] + public List PidArr { get; set; } + + } +} diff --git a/Blog.Core.Model/Models/RootTkey/RoleModulePermissionRoot.cs b/Blog.Core.Model/Models/RootTkey/RoleModulePermissionRoot.cs new file mode 100644 index 00000000..72c4e3d8 --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/RoleModulePermissionRoot.cs @@ -0,0 +1,28 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model +{ + /// + /// 按钮跟权限关联表 + /// 父类 + /// + public class RoleModulePermissionRoot : RootEntityTkey where Tkey : IEquatable + { + + /// + /// 角色ID + /// + public Tkey RoleId { get; set; } + /// + /// 菜单ID + /// + public Tkey ModuleId { get; set; } + /// + /// api ID + /// + [SugarColumn(IsNullable = true)] + public Tkey PermissionId { get; set; } + + } +} diff --git a/Blog.Core.Model/Models/RootTkey/RootEntityTkey.cs b/Blog.Core.Model/Models/RootTkey/RootEntityTkey.cs new file mode 100644 index 00000000..20bdda0d --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/RootEntityTkey.cs @@ -0,0 +1,15 @@ +using SqlSugar; +using System; + +namespace Blog.Core.Model +{ + public class RootEntityTkey where Tkey : IEquatable + { + /// + /// ID + /// 泛型主键Tkey + /// + [SugarColumn(IsNullable = false, IsPrimaryKey = true)] + public Tkey Id { get; set; } + } +} \ No newline at end of file diff --git a/Blog.Core.Model/Models/RootTkey/TopicDetailRoot.cs b/Blog.Core.Model/Models/RootTkey/TopicDetailRoot.cs new file mode 100644 index 00000000..8ae42152 --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/TopicDetailRoot.cs @@ -0,0 +1,12 @@ +using System; + +namespace Blog.Core.Model +{ + /// + /// Tibug 博文 + /// + public class TopicDetailRoot : RootEntityTkey where Tkey : IEquatable + { + public Tkey TopicId { get; set; } + } +} diff --git a/Blog.Core.Model/Models/RootTkey/UserRoleRoot.cs b/Blog.Core.Model/Models/RootTkey/UserRoleRoot.cs new file mode 100644 index 00000000..f4cd4a58 --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/UserRoleRoot.cs @@ -0,0 +1,21 @@ +using System; + +namespace Blog.Core.Model +{ + /// + /// 用户跟角色关联表 + /// 父类 + /// + public class UserRoleRoot : RootEntityTkey where Tkey : IEquatable + { + /// + /// 用户ID + /// + public Tkey UserId { get; set; } + /// + /// 角色ID + /// + public Tkey RoleId { get; set; } + + } +} diff --git a/Blog.Core.Model/Models/RootTkey/sysUserInfoRoot.cs b/Blog.Core.Model/Models/RootTkey/sysUserInfoRoot.cs new file mode 100644 index 00000000..60531b1c --- /dev/null +++ b/Blog.Core.Model/Models/RootTkey/sysUserInfoRoot.cs @@ -0,0 +1,23 @@ +using SqlSugar; +using System; +using System.Collections.Generic; + +namespace Blog.Core.Model +{ + /// + /// 用户信息表 + /// + public class SysUserInfoRoot where Tkey : IEquatable + { + /// + /// Id + /// 泛型主键Tkey + /// + [SugarColumn(IsNullable = false, IsPrimaryKey = true)] + 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 new file mode 100644 index 00000000..b029a995 --- /dev/null +++ b/Blog.Core.Model/Models/TasksQz.cs @@ -0,0 +1,94 @@ +using Blog.Core.Model.ViewModels; +using SqlSugar; +using System; +using System.Collections.Generic; + +namespace Blog.Core.Model.Models +{ + /// + /// 任务计划表 + /// + public class TasksQz : RootEntityTkey + { + /// + /// 任务名称 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string Name { get; set; } + /// + /// 任务分组 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string JobGroup { get; set; } + /// + /// 任务运行时间表达式 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string Cron { get; set; } + /// + /// 任务所在DLL对应的程序集名称 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string AssemblyName { get; set; } + /// + /// 任务所在类 + /// + [SugarColumn(Length = 200, IsNullable = true)] + public string ClassName { get; set; } + /// + /// 任务描述 + /// + [SugarColumn(Length = 1000, IsNullable = true)] + public string Remark { get; set; } + /// + /// 执行次数 + /// + public int RunTimes { get; set; } + /// + /// 开始时间 + /// + public DateTime? BeginTime { get; set; } + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } + /// + /// 触发器类型(0、simple 1、cron) + /// + public int TriggerType { get; set; } + /// + /// 执行间隔时间, 秒为单位 + /// + public int IntervalSecond { get; set; } + /// + /// 循环执行次数 + /// + public int CycleRunTimes { get; set; } + /// + /// 已循环次数 + /// + public int CycleHasRunTimes { get; set; } + /// + /// 是否启动 + /// + public bool IsStart { get; set; } = false; + /// + /// 执行传参 + /// + public string JobParams { get; set; } + + + [SugarColumn(IsNullable = true)] + public bool? IsDeleted { get; set; } + /// + /// 创建时间 + /// + [SugarColumn(IsNullable = true)] + public DateTime CreateTime { get; set; } = DateTime.Now; + /// + /// 任务内存中的状态 + /// + [SugarColumn(IsIgnore = true)] + public List Triggers { get; set; } + } +} 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 new file mode 100644 index 00000000..8a8d123c --- /dev/null +++ b/Blog.Core.Model/Models/TestModels.cs @@ -0,0 +1,13 @@ +namespace Blog.Core.Model.Models +{ + + public class TestMuchTableResult + { + public string moduleName { get; set; } + public string permName { 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 725f1326..e57bd561 100644 --- a/Blog.Core.Model/Models/Topic.cs +++ b/Blog.Core.Model/Models/Topic.cs @@ -1,16 +1,13 @@ using SqlSugar; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { - /// 博客文章 - /// - /// + /// Tibug 类别 + ///
    + public class Topic : RootEntityTkey { public Topic() { diff --git a/Blog.Core.Model/Models/TopicDetail.cs b/Blog.Core.Model/Models/TopicDetail.cs index 20f6b7e7..6cb69c67 100644 --- a/Blog.Core.Model/Models/TopicDetail.cs +++ b/Blog.Core.Model/Models/TopicDetail.cs @@ -1,34 +1,28 @@ using SqlSugar; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { - /// 博客文章 - /// - /// + /// Tibug 博文 + ///
    + public class TopicDetail : TopicDetailRoot { public TopicDetail() { this.tdUpdatetime = DateTime.Now; } - public int TopicId { get; set; } - [SugarColumn(Length = 200, IsNullable = true)] - public string tdLogo { get; set; } + public string tdLogo { get; set; } [SugarColumn(Length = 200, IsNullable = true)] public string tdName { get; set; } - [SugarColumn(Length = int.MaxValue , IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string tdContent { get; set; } - [SugarColumn(Length = 400, IsNullable = true)] + [SugarColumn(Length = 2000, IsNullable = true)] public string tdDetail { get; set; } [SugarColumn(Length = 200, IsNullable = true)] diff --git a/Blog.Core.Model/Models/UserRole.cs b/Blog.Core.Model/Models/UserRole.cs index ed4b31c0..7ed9c6be 100644 --- a/Blog.Core.Model/Models/UserRole.cs +++ b/Blog.Core.Model/Models/UserRole.cs @@ -1,20 +1,16 @@ using SqlSugar; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { /// /// 用户跟角色关联表 /// - public class UserRole : RootEntity + public class UserRole : UserRoleRoot { public UserRole() { } - public UserRole(int uid, int rid) + public UserRole(long uid, long rid) { UserId = uid; RoleId = rid; @@ -25,25 +21,17 @@ public UserRole(int uid, int rid) } - /// ///获取或设置是否禁用,逻辑上的删除,非物理删除 /// [SugarColumn(IsNullable = true)] public bool? IsDeleted { get; set; } - /// - /// 用户ID - /// - public int UserId { get; set; } - /// - /// 角色ID - /// - public int RoleId { get; set; } + /// /// 创建ID /// [SugarColumn(IsNullable = true)] - public int? CreateId { get; set; } + public long? CreateId { 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 54780015..1137d91c 100644 --- a/Blog.Core.Model/Models/sysUserInfo.cs +++ b/Blog.Core.Model/Models/sysUserInfo.cs @@ -1,109 +1,144 @@ using SqlSugar; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.Models { - /// - /// 用户信息表 - /// - public class sysUserInfo - { - 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 = ""; - - } - /// - /// 用户ID - /// - [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] - public int uID { get; set; } - /// - /// 登录账号 - /// - [SugarColumn(Length = 60, IsNullable = true)] - public string uLoginName { get; set; } - /// - /// 登录密码 - /// - [SugarColumn(Length = 60, IsNullable = true)] - public string uLoginPWD { get; set; } - /// - /// 真实姓名 - /// - [SugarColumn(Length = 60, IsNullable = true)] - public string uRealName { get; set; } - /// - /// 状态 - /// - public int uStatus { get; set; } - /// - /// 备注 - /// - [SugarColumn(Length = int.MaxValue, 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(Length = 60, 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 addr { get; set; } - - [SugarColumn(IsNullable = true)] - public bool tdIsDelete { get; set; } - - - [SugarColumn(IsIgnore = true)] - public int RID { get; set; } - [SugarColumn(IsIgnore = true)] - public string RoleName { 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 20172d70..f6872b66 100644 --- a/Blog.Core.Model/PageModel.cs +++ b/Blog.Core.Model/PageModel.cs @@ -1,6 +1,6 @@ -using System; +using AutoMapper; +using System; using System.Collections.Generic; -using System.Text; namespace Blog.Core.Model { @@ -10,21 +10,67 @@ namespace Blog.Core.Model public class PageModel { /// - /// 当前页数 + /// 当前页标 /// public int page { get; set; } = 1; /// /// 总页数 /// - public int pageCount { get; set; } = 6; + public int pageCount => (int)Math.Ceiling((decimal)dataCount / PageSize); /// /// 数据总数 /// public int dataCount { get; set; } = 0; /// + /// 每页大小 + /// + 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/ResponseEnum.cs b/Blog.Core.Model/ResponseEnum.cs index 677a1e35..e17e04e5 100644 --- a/Blog.Core.Model/ResponseEnum.cs +++ b/Blog.Core.Model/ResponseEnum.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Text; +using System.ComponentModel; namespace Blog.Core.Model { diff --git a/Blog.Core.Model/RootEntity.cs b/Blog.Core.Model/RootEntity.cs deleted file mode 100644 index 8a5bd251..00000000 --- a/Blog.Core.Model/RootEntity.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SqlSugar; -using System; - -namespace Blog.Core.Model.Models -{ - public class RootEntity - { - /// - /// ID - /// - [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } - - - } -} \ No newline at end of file diff --git a/Blog.Core.Model/Seed/DBSeed.cs b/Blog.Core.Model/Seed/DBSeed.cs deleted file mode 100644 index 2574ba01..00000000 --- a/Blog.Core.Model/Seed/DBSeed.cs +++ /dev/null @@ -1,230 +0,0 @@ -using Blog.Core.Model.Models; -using SqlSugar; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Blog.Core.Model.Models -{ - public class DBSeed - { - /// - /// 异步添加种子数据 - /// - /// - /// - public static async Task SeedAsync(MyContext myContext) - { - try - { - // 注意!一定要手动先创建要给空的数据库 - // 会覆盖,可以设置为true,来备份数据 - // 如果生成过了,第二次,就不用再执行一遍了,注释掉该方法即可 - myContext.CreateTableByEntity(false, typeof(Advertisement), typeof(BlogArticle), typeof(Guestbook), typeof(Module), typeof(ModulePermission), typeof(OperateLog), typeof(PasswordLib), typeof(Permission), typeof(Role), typeof(RoleModulePermission), typeof(sysUserInfo), typeof(Topic), typeof(TopicDetail), typeof(UserRole)); - - // 后期单独处理某些表 - //myContext.Db.CodeFirst.InitTables(typeof(sysUserInfo)); - //myContext.Db.CodeFirst.InitTables(typeof(Permission)); - //myContext.Db.CodeFirst.InitTables(typeof(Advertisement)); - - - #region Advertisement - if (!await myContext.Db.Queryable().AnyAsync()) - { - myContext.GetEntityDB().Insert( - new Advertisement() - { - Createdate = DateTime.Now, - Remark = "mark", - Title = "good" - }); - } - #endregion - - #region BlogArticle Guestbook - if (!await myContext.Db.Queryable().AnyAsync()) - { - int bid = myContext.GetEntityDB().InsertReturnIdentity( - new BlogArticle() - { - bsubmitter = "admins", - btitle = "老张的哲学", - bcategory = "技术博文", - bcontent = "

    1,ctrl+alt+delete 去修改密码

    2、打开“服务器管理器”,选择“配置”-“本地用户和组”-“用户,右击administrator,选择“属性”,在“常规”选项中勾上“密码永不过期”,点击“应用”和“确定”。

    3、在开始菜单中选择“管理工具”-“本地安全策略”,选择“安全策略”-“账户策略”-“密码策略”,编辑“密码最短使用期限”和“密码最长使用期限”,天数设置为0,即永不过期,点击“确定”即可。


    ", - btraffic = 1, - bcommentNum = 0, - bUpdateTime = DateTime.Now, - bCreateTime = DateTime.Now - }); - - if (bid > 0) - { - - if (!await myContext.Db.Queryable().AnyAsync()) - { - myContext.GetEntityDB().Insert( - new Guestbook() - { - blogId = bid, - createdate = DateTime.Now, - username = "user", - phone = "110", - QQ = "100", - body = "很不错", - ip = "127.0.0.1", - isshow = true, - }); - } - } - } - #endregion - - #region Module - int mid = 0; - if (!await myContext.Db.Queryable().AnyAsync()) - { - mid = myContext.GetEntityDB().InsertReturnIdentity( - new Module() - { - IsDeleted = false, - Name = "values的接口信息", - LinkUrl = "/api/values", - OrderSort = 1, - IsMenu = false, - Enabled = true, - CreateTime = DateTime.Now, - }); - - } - #endregion - - #region Role - int rid = 0; - if (!await myContext.Db.Queryable().AnyAsync()) - { - rid = myContext.GetEntityDB().InsertReturnIdentity( - new Role() - { - IsDeleted = false, - Name = "Admin", - Description = "我是一个admin管理员", - OrderSort = 1, - CreateTime = DateTime.Now, - Enabled = true, - ModifyTime = DateTime.Now - }); - - } - #endregion - - #region RoleModulePermission - if (mid > 0 && rid > 0) - { - if (!await myContext.Db.Queryable().AnyAsync()) - { - myContext.GetEntityDB().Insert( - new RoleModulePermission() - { - IsDeleted = false, - RoleId = rid, - ModuleId = mid, - CreateTime = DateTime.Now, - ModifyTime = DateTime.Now - }); - } - } - #endregion - - #region sysUserInfo - int uid = 0; - if (!await myContext.Db.Queryable().AnyAsync()) - { - uid = myContext.GetEntityDB().InsertReturnIdentity( - new sysUserInfo() - { - uLoginName = "admins", - uLoginPWD = "admins", - uRealName = "admins", - uStatus = 0, - uCreateTime = DateTime.Now, - uUpdateTime = DateTime.Now, - uLastErrTime = DateTime.Now, - uErrorCount = 0 - - }); - - } - #endregion - - #region UserRole - if (uid > 0 && rid > 0) - { - if (!await myContext.Db.Queryable().AnyAsync()) - { - myContext.GetEntityDB().Insert( - new UserRole() - { - IsDeleted = false, - UserId = uid, - RoleId = rid, - CreateTime = DateTime.Now, - ModifyTime = DateTime.Now - }); - } - } - #endregion - - #region Topic TopicDetail - if (!await myContext.Db.Queryable().AnyAsync()) - { - int tid = myContext.GetEntityDB().InsertReturnIdentity( - new Topic() - { - tLogo = "/Upload/20180626/95445c8e288e47e3af7a180b8a4cc0c7.jpg", - tName = "《罗马人的故事》", - tDetail = "这是一个荡气回肠的故事", - tIsDelete = false, - tRead = 0, - tCommend = 0, - tGood = 0, - tCreatetime = DateTime.Now, - tUpdatetime = DateTime.Now, - tAuthor = "laozhang" - }); - - if (tid > 0) - { - - if (!await myContext.Db.Queryable().AnyAsync()) - { - myContext.GetEntityDB().Insert( - new TopicDetail() - { - TopicId = tid, - tdLogo = "/Upload/20180627/7548de20944c45d48a055111b5a6c1b9.jpg", - tdName = "第一章 罗马的诞生 第一节 传说的年代", - tdContent = "

    第一节 传说的年代

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


    ", - tdDetail = "第一回", - tdIsDelete = false, - tdRead = 1, - tdCommend = 0, - tdGood = 0, - tdCreatetime = DateTime.Now, - tdUpdatetime = DateTime.Now, - tdTop = 0, - }); - } - } - } - #endregion - - - - } - catch (Exception ex) - { - throw new Exception("1、注意要先创建空的数据库\n2、"+ex.Message); - } - } - } -} diff --git a/Blog.Core.Model/Seed/MyContext.cs b/Blog.Core.Model/Seed/MyContext.cs deleted file mode 100644 index 92185a29..00000000 --- a/Blog.Core.Model/Seed/MyContext.cs +++ /dev/null @@ -1,366 +0,0 @@ -using Blog.Core.Common; -using SqlSugar; -using System; -using System.IO; - -namespace Blog.Core.Model.Models -{ - public class MyContext - { - - private static string sqlServerConnection = Appsettings.app(new string[] { "AppSettings", "SqlServer", "SqlServerConnection" }); - private static string _connectionString = File.Exists(@"D:\my-file\dbCountPsw1.txt") ? File.ReadAllText(@"D:\my-file\dbCountPsw1.txt").Trim() : (!string.IsNullOrEmpty(sqlServerConnection) ? sqlServerConnection : "server=.;uid=sa;pwd=sa;database=WMBlogDB"); - private static DbType _dbType; - private SqlSugarClient _db; - - /// - /// 连接字符串 - /// Blog.Core - /// - public static string ConnectionString - { - get { return _connectionString; } - set { _connectionString = value; } - } - /// - /// 数据库类型 - /// Blog.Core - /// - public static DbType DbType - { - get { return _dbType; } - set { _dbType = value; } - } - /// - /// 数据连接对象 - /// Blog.Core - /// - public SqlSugarClient Db - { - get { return _db; } - private set { _db = value; } - } - - /// - /// 数据库上下文实例(自动关闭连接) - /// Blog.Core - /// - public static MyContext Context - { - get - { - return new MyContext(); - } - - } - - - /// - /// 功能描述:构造函数 - /// 作  者:Blog.Core - /// - public MyContext() - { - if (string.IsNullOrEmpty(_connectionString)) - throw new ArgumentNullException("数据库连接字符串为空"); - _db = new SqlSugarClient(new ConnectionConfig() - { - ConnectionString = _connectionString, - DbType = DbType.SqlServer, - IsAutoCloseConnection = true, - IsShardSameThread = false, - InitKeyType = InitKeyType.Attribute,//mark - ConfigureExternalServices = new ConfigureExternalServices() - { - //DataInfoCacheService = new HttpRuntimeCache() - }, - MoreSettings = new ConnMoreSettings() - { - //IsWithNoLockQuery = true, - IsAutoRemoveDataCache = true - } - }); - } - - - - #region 实例方法 - /// - /// 功能描述:获取数据库处理对象 - /// 作  者:Blog.Core - /// - /// 返回值 - public SimpleClient GetEntityDB() where T : class, new() - { - return new SimpleClient(_db); - } - /// - /// 功能描述:获取数据库处理对象 - /// 作  者:Blog.Core - /// - /// db - /// 返回值 - public SimpleClient GetEntityDB(SqlSugarClient db) where T : class, new() - { - return new SimpleClient(db); - } - - #region 根据数据库表生产实体类 - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - public void CreateClassFileByDBTalbe(string strPath) - { - CreateClassFileByDBTalbe(strPath, "Blog.Core.Entity"); - } - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - /// 命名空间 - public void CreateClassFileByDBTalbe(string strPath, string strNameSpace) - { - CreateClassFileByDBTalbe(strPath, strNameSpace, null); - } - - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - /// 命名空间 - /// 生产指定的表 - public void CreateClassFileByDBTalbe( - string strPath, - string strNameSpace, - string[] lstTableNames) - { - CreateClassFileByDBTalbe(strPath, strNameSpace, lstTableNames, string.Empty); - } - - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - /// 命名空间 - /// 生产指定的表 - /// 实现接口 - public void CreateClassFileByDBTalbe( - string strPath, - string strNameSpace, - string[] lstTableNames, - string strInterface, - bool blnSerializable = false) - { - if (lstTableNames != null && lstTableNames.Length > 0) - { - _db.DbFirst.Where(lstTableNames).IsCreateDefaultValue().IsCreateAttribute() - .SettingClassTemplate(p => p = @" -{using} - -namespace {Namespace} -{ - {ClassDescription}{SugarTable}" + (blnSerializable ? "[Serializable]" : "") + @" - public partial class {ClassName}" + (string.IsNullOrEmpty(strInterface) ? "" : (" : " + strInterface)) + @" - { - public {ClassName}() - { -{Constructor} - } -{PropertyName} - } -} -") - .SettingPropertyTemplate(p => p = @" - {SugarColumn} - public {PropertyType} {PropertyName} - { - get - { - return _{PropertyName}; - } - set - { - if(_{PropertyName}!=value) - { - base.SetValueCall(" + "\"{PropertyName}\",_{PropertyName}" + @"); - } - _{PropertyName}=value; - } - }") - .SettingPropertyDescriptionTemplate(p => p = " private {PropertyType} _{PropertyName};\r\n" + p) - .SettingConstructorTemplate(p => p = " this._{PropertyName} ={DefaultValue};") - .CreateClassFile(strPath, strNameSpace); - } - else - { - _db.DbFirst.IsCreateAttribute().IsCreateDefaultValue() - .SettingClassTemplate(p => p = @" -{using} - -namespace {Namespace} -{ - {ClassDescription}{SugarTable}" + (blnSerializable ? "[Serializable]" : "") + @" - public partial class {ClassName}" + (string.IsNullOrEmpty(strInterface) ? "" : (" : " + strInterface)) + @" - { - public {ClassName}() - { -{Constructor} - } -{PropertyName} - } -} -") - .SettingPropertyTemplate(p => p = @" - {SugarColumn} - public {PropertyType} {PropertyName} - { - get - { - return _{PropertyName}; - } - set - { - if(_{PropertyName}!=value) - { - base.SetValueCall(" + "\"{PropertyName}\",_{PropertyName}" + @"); - } - _{PropertyName}=value; - } - }") - .SettingPropertyDescriptionTemplate(p => p = " private {PropertyType} _{PropertyName};\r\n" + p) - .SettingConstructorTemplate(p => p = " this._{PropertyName} ={DefaultValue};") - .CreateClassFile(strPath, strNameSpace); - } - } - #endregion - - #region 根据实体类生成数据库表 - /// - /// 功能描述:根据实体类生成数据库表 - /// 作  者:Blog.Core - /// - /// 是否备份表 - /// 指定的实体 - public void CreateTableByEntity(bool blnBackupTable, params T[] lstEntitys) where T : class, new() - { - Type[] lstTypes = null; - if (lstEntitys != null) - { - lstTypes = new Type[lstEntitys.Length]; - for (int i = 0; i < lstEntitys.Length; i++) - { - T t = lstEntitys[i]; - lstTypes[i] = typeof(T); - } - } - CreateTableByEntity(blnBackupTable, lstTypes); - } - - /// - /// 功能描述:根据实体类生成数据库表 - /// 作  者:Blog.Core - /// - /// 是否备份表 - /// 指定的实体 - public void CreateTableByEntity(bool blnBackupTable, params Type[] lstEntitys) - { - if (blnBackupTable) - { - _db.CodeFirst.BackupTable().InitTables(lstEntitys); //change entity backupTable - } - else - { - _db.CodeFirst.InitTables(lstEntitys); - } - } - #endregion - - #endregion - - #region 静态方法 - - /// - /// 功能描述:获得一个DbContext - /// 作  者:Blog.Core - /// - /// 是否自动关闭连接(如果为false,则使用接受时需要手动关闭Db) - /// 返回值 - public static MyContext GetDbContext() - { - return new MyContext(); - } - - /// - /// 功能描述:设置初始化参数 - /// 作  者:Blog.Core - /// - /// 连接字符串 - /// 数据库类型 - public static void Init(string strConnectionString, DbType enmDbType = SqlSugar.DbType.SqlServer) - { - _connectionString = strConnectionString; - _dbType = enmDbType; - } - - /// - /// 功能描述:创建一个链接配置 - /// 作  者:Blog.Core - /// - /// 是否自动关闭连接 - /// 是否夸类事务 - /// ConnectionConfig - public static ConnectionConfig GetConnectionConfig(bool blnIsAutoCloseConnection = true, bool blnIsShardSameThread = false) - { - ConnectionConfig config = new ConnectionConfig() - { - ConnectionString = _connectionString, - DbType = _dbType, - IsAutoCloseConnection = blnIsAutoCloseConnection, - ConfigureExternalServices = new ConfigureExternalServices() - { - //DataInfoCacheService = new HttpRuntimeCache() - }, - IsShardSameThread = blnIsShardSameThread - }; - return config; - } - - /// - /// 功能描述:获取一个自定义的DB - /// 作  者:Blog.Core - /// - /// config - /// 返回值 - public static SqlSugarClient GetCustomDB(ConnectionConfig config) - { - return new SqlSugarClient(config); - } - /// - /// 功能描述:获取一个自定义的数据库处理对象 - /// 作  者:Blog.Core - /// - /// sugarClient - /// 返回值 - public static SimpleClient GetCustomEntityDB(SqlSugarClient sugarClient) where T : class, new() - { - return new SimpleClient(sugarClient); - } - /// - /// 功能描述:获取一个自定义的数据库处理对象 - /// 作  者:Blog.Core - /// - /// config - /// 返回值 - public static SimpleClient GetCustomEntityDB(ConnectionConfig config) where T : class, new() - { - SqlSugarClient sugarClient = GetCustomDB(config); - return GetCustomEntityDB(sugarClient); - } - #endregion - } -} 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/TableModel.cs b/Blog.Core.Model/TableModel.cs index deeba2e8..289e9c8c 100644 --- a/Blog.Core.Model/TableModel.cs +++ b/Blog.Core.Model/TableModel.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; namespace Blog.Core.Model { 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/AdvertisementViewModels.cs b/Blog.Core.Model/ViewModels/AdvertisementViewModels.cs index fcf9b818..81a1974a 100644 --- a/Blog.Core.Model/ViewModels/AdvertisementViewModels.cs +++ b/Blog.Core.Model/ViewModels/AdvertisementViewModels.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.ViewModels { diff --git a/Blog.Core.Model/ViewModels/BlogViewModels.cs b/Blog.Core.Model/ViewModels/BlogViewModels.cs index 0c784ff9..86c16618 100644 --- a/Blog.Core.Model/ViewModels/BlogViewModels.cs +++ b/Blog.Core.Model/ViewModels/BlogViewModels.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Blog.Core.Model.ViewModels { @@ -14,7 +10,7 @@ public class BlogViewModels /// /// /// - public int bID { get; set; } + public long bID { get; set; } /// 创建人 /// /// @@ -38,7 +34,7 @@ public class BlogViewModels /// /// 上一篇id /// - public int previousID { get; set; } + public long previousID { get; set; } /// /// 下一篇 @@ -48,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/GuestbookViewModels.cs b/Blog.Core.Model/ViewModels/GuestbookViewModels.cs index 5058724e..1729bdb4 100644 --- a/Blog.Core.Model/ViewModels/GuestbookViewModels.cs +++ b/Blog.Core.Model/ViewModels/GuestbookViewModels.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Blog.Core.Model.Models; namespace Blog.Core.Model.ViewModels diff --git a/Blog.Core.Model/ViewModels/LoginInfoViewModels.cs b/Blog.Core.Model/ViewModels/LoginInfoViewModels.cs index 3f7cf1d3..77851fcb 100644 --- a/Blog.Core.Model/ViewModels/LoginInfoViewModels.cs +++ b/Blog.Core.Model/ViewModels/LoginInfoViewModels.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.Model.ViewModels +namespace Blog.Core.Model.ViewModels { public class LoginInfoViewModels { diff --git a/Blog.Core.Model/ViewModels/ModuleViewModels.cs b/Blog.Core.Model/ViewModels/ModuleViewModels.cs index 03bf1319..235865b8 100644 --- a/Blog.Core.Model/ViewModels/ModuleViewModels.cs +++ b/Blog.Core.Model/ViewModels/ModuleViewModels.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.Model.ViewModels +namespace Blog.Core.Model.ViewModels { public class ModuleViewModels { diff --git a/Blog.Core.Model/ViewModels/PayModel.cs b/Blog.Core.Model/ViewModels/PayModel.cs new file mode 100644 index 00000000..d94b6914 --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayModel.cs @@ -0,0 +1,102 @@ +namespace Blog.Core.Model.ViewModels +{ + public class PayModel + { + /// + /// 商户号 + /// + public string MERCHANTID { get; set; }//105910100190000");// => self::MERCHANTID, // 商户号 + /// + /// 柜台号 + /// + public string POSID { get; set; } //610000000");// => self::POSID, // 柜台号 + /// + /// 分行号 + /// + public string BRANCHID { get; set; } //610000000");// => self::BRANCHID, // 分行号 + /// + /// 集团商户信息 + /// + public string GROUPMCH { get; set; } //;// => '', // 集团商户信息 + /// + /// 交易码 + /// + public string TXCODE { get; set; } //PAY100");// => 'PAY100', // 交易码 + /// + /// 商户类型 + /// + public string MERFLAG { get; set; } //// => '', // 商户类型 + /// + /// 终端编号 1 + /// + public string TERMNO1 { get; set; } //// => '', // 终端编号 1 + /// + /// 终端编号 2 + /// + public string TERMNO2 { get; set; } //// => '', // 终端编号 2 + /// + /// 订单号 + /// + public string ORDERID { get; set; }//// => '', // 订单号 + /// + /// 码信息(一维码、二维码) + /// + public string QRCODE { get; set; } //// => '', // 码信息(一维码、二维码) + /// + /// 订单金额,单位:元 + /// + public string AMOUNT { get; set; } //");// => '0.01', // 订单金额,单位:元 + /// + /// 商品名称 + /// + public string PROINFO { get; set; } //// => '', // 商品名称 + /// + /// 备注 1 + /// + public string REMARK1 { get; set; } //// => '', // 备注 1 + /// + /// 备注 2 + /// + public string REMARK2 { get; set; }//// => '', // 备注 2 + /// + /// 分账信息一 + /// + public string FZINFO1 { get; set; } //// => '', // 分账信息一 + /// + /// 分账信息二 + /// + public string FZINFO2 { get; set; } //// => '', // 分账信息二 + /// + /// 子商户公众账号 ID + /// + public string SUB_APPID { get; set; } //);// => '', // 子商户公众账号 ID + /// + /// 返回信息位图 + /// + public string RETURN_FIELD { get; set; }// "");// => '', // 返回信息位图 + /// + /// 实名支付 + /// + public string USERPARAM { get; set; } //);// => '', // 实名支付 + /// + /// 商品详情 + /// + public string detail { get; set; }//// => '', // 商品详情 + /// + /// 订单优惠标记 + /// + public string goods_tag { get; set; } //);// => '', // 订单优惠标记 + /// + /// 公钥 + /// + public string pubKey { get; set; } + /// + /// 请求地址 + /// + public string url { get; set; } + /// + /// 是否删除空值 + /// + public bool deleteEmpty { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/PayNeedModel.cs b/Blog.Core.Model/ViewModels/PayNeedModel.cs new file mode 100644 index 00000000..103b124b --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayNeedModel.cs @@ -0,0 +1,34 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 退款参数 + /// + public class PayNeedModel + { + + /// + /// 订单ID + /// + public string ORDERID { get; set; } + /// + /// 商品名称 + /// + public string PROINFO { get; set; } + /// + /// 支付金额(小数点最多两位) + /// + public string AMOUNT { get; set; } + /// + /// 二维码/条码信息 + /// + public string QRCODE { get; set; } + /// + /// 备注信息1 + /// + public string REMARK1 { get; set; } + /// + /// 备注信息2 + /// + public string REMARK2 { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/PayRefundNeedModel.cs b/Blog.Core.Model/ViewModels/PayRefundNeedModel.cs new file mode 100644 index 00000000..995d9891 --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayRefundNeedModel.cs @@ -0,0 +1,21 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 订单参数 + /// + public class PayRefundNeedModel + { + /// + /// 订单号 + /// + public string ORDER { get; set; } + /// + /// 退款金额 + /// + public string MONEY { get; set; } + /// + /// 退款流水号(可选) + /// + public string REFUND_CODE { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/PayRefundReturnModel.cs b/Blog.Core.Model/ViewModels/PayRefundReturnModel.cs new file mode 100644 index 00000000..20835bbc --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayRefundReturnModel.cs @@ -0,0 +1,37 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 退款返回消息 + /// + public class PayRefundReturnModel + { + /// + /// 序列号 + /// + public string REQUEST_SN { get; set; } + /// + /// 商户号 + /// + public string CUST_ID { get; set; } + /// + /// 交易码 + /// + public string TX_CODE { get; set; } + /// + /// 返回码 + /// + public string RETURN_CODE { get; set; } + /// + /// 返回码说明 + /// + public string RETURN_MSG { get; set; } + /// + /// 语言 + /// + public string LANGUAGE { get; set; } + /// + /// 订单信息 + /// + public PayRefundReturnOrderInfoModel TX_INFO { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/PayRefundReturnOrderInfoModel.cs b/Blog.Core.Model/ViewModels/PayRefundReturnOrderInfoModel.cs new file mode 100644 index 00000000..1704e3b5 --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayRefundReturnOrderInfoModel.cs @@ -0,0 +1,30 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 订单信息 + /// + public class PayRefundReturnOrderInfoModel + { + /// + /// 订单号 + /// + public string ORDER_NUM { get; set; } + /// + /// 支付金额 + /// + public string PAY_AMOUNT { get; set; } + /// + /// 退款金额 + /// + public string AMOUNT { get; set; } + /// + /// 备注1 + /// + public string REM1 { get; set; } + /// + /// 备注2 + /// + public string REM2 { get; set; } + + } +} diff --git a/Blog.Core.Model/ViewModels/PayRefundReturnResultModel.cs b/Blog.Core.Model/ViewModels/PayRefundReturnResultModel.cs new file mode 100644 index 00000000..1841a1e5 --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayRefundReturnResultModel.cs @@ -0,0 +1,45 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 退款返回结果消息 + /// + public class PayRefundReturnResultModel + { + /// + /// 订单号 + /// + public string ORDER_NUM { get; set; } + /// + /// 支付金额 + /// + public string PAY_AMOUNT { get; set; } + /// + /// 退款金额 + /// + public string AMOUNT { get; set; } + /// + /// 序列号 + /// + public string REQUEST_SN { get; set; } + /// + /// 商户号 + /// + public string CUST_ID { get; set; } + /// + /// 交易码 + /// + public string TX_CODE { get; set; } + /// + /// 返回码 + /// + public string RETURN_CODE { get; set; } + /// + /// 返回码说明 + /// + public string RETURN_MSG { get; set; } + /// + /// 语言 + /// + public string LANGUAGE { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/PayResultModel.cs b/Blog.Core.Model/ViewModels/PayResultModel.cs new file mode 100644 index 00000000..8cfa607e --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayResultModel.cs @@ -0,0 +1,53 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 支付结果dto + /// + public class PayResultModel + { + /// + /// 支付结果 + /// Y:成功 + /// N:失败 + /// U:不确定 + /// Q:待轮询 + /// + public string RESULT { get; set; } + /// + /// 订单ID + /// + public string ORDERID { get; set; } + /// + /// 支付金额 + /// + public string AMOUNT { get; set; } + /// + /// 二维码类型 + /// 1:龙支付 + /// 2:微信 + /// 3:支付宝 + /// 4:银联 + /// + public string QRCODETYPE { get; set; } + /// + /// 等待时间-轮询等待时间 + /// + public string WAITTIME { get; set; } + /// + /// 全局事件跟踪号-建行交易流水号 + /// + public string TRACEID { get; set; } + /// + /// 错误码 + /// + public string ERRCODE { get; set; } + /// + /// 错误信息 + /// + public string ERRMSG { get; set; } + /// + /// 验证签名-防止伪造攻击 + /// + public string SIGN { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/PayReturnResultModel.cs b/Blog.Core.Model/ViewModels/PayReturnResultModel.cs new file mode 100644 index 00000000..4cb8b624 --- /dev/null +++ b/Blog.Core.Model/ViewModels/PayReturnResultModel.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 返回支付结果 + /// + public class PayReturnResultModel + { + /// + /// 发起的订单ID + /// + public string ORDERID { get; set; } + /// + /// 返回支付的金额 + /// + public string AMOUNT { get; set; } + /// + /// 返回支付的类型 1:龙支付 2:微信 3:支付宝 4:银联 + /// + public string QRCODETYPE { get; set; } + /// + /// 返回建行的流水号 + /// + public string TRACEID { get; set; } + /// + /// 错误代码 + /// + public string ERRCODE { get; set; } + /// + /// 错误信息 + /// + public string ERRMSG { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/QuartzReflectionViewModel.cs b/Blog.Core.Model/ViewModels/QuartzReflectionViewModel.cs new file mode 100644 index 00000000..8b5e70ee --- /dev/null +++ b/Blog.Core.Model/ViewModels/QuartzReflectionViewModel.cs @@ -0,0 +1,21 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 实现IJob的类 + /// + public class QuartzReflectionViewModel + { + /// + /// 命名空间 + /// + public string nameSpace{ get; set; } + /// + /// 类名 + /// + public string nameClass { get; set; } + /// + /// 备注 + /// + public string remark { get; set; } + } +} 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/ServerViewModel.cs b/Blog.Core.Model/ViewModels/ServerViewModel.cs new file mode 100644 index 00000000..993c4c58 --- /dev/null +++ b/Blog.Core.Model/ViewModels/ServerViewModel.cs @@ -0,0 +1,39 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 服务器VM + /// + public class ServerViewModel + { + /// + /// 环境变量 + /// + public string EnvironmentName { get; set; } + /// + /// 系统架构 + /// + public string OSArchitecture { get; set; } + /// + /// ContentRootPath + /// + public string ContentRootPath { get; set; } + /// + /// WebRootPath + /// + public string WebRootPath { get; set; } + /// + /// .NET Core版本 + /// + public string FrameworkDescription { get; set; } + /// + /// 内存占用 + /// + public string MemoryFootprint { get; set; } + /// + /// 启动时间 + /// + public string WorkingTime { get; set; } + + + } +} diff --git a/Blog.Core.Model/ViewModels/SidebarMenuViewModel.cs b/Blog.Core.Model/ViewModels/SidebarMenuViewModel.cs index 9d0dbf7d..d098f397 100644 --- a/Blog.Core.Model/ViewModels/SidebarMenuViewModel.cs +++ b/Blog.Core.Model/ViewModels/SidebarMenuViewModel.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Blog.Core.Model.ViewModels { 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/TaskInfoDto.cs b/Blog.Core.Model/ViewModels/TaskInfoDto.cs new file mode 100644 index 00000000..49b02085 --- /dev/null +++ b/Blog.Core.Model/ViewModels/TaskInfoDto.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 调度任务触发器信息实体 + /// + public class TaskInfoDto + { + /// + /// 任务ID + /// + public string jobId { get; set; } + /// + /// 任务名称 + /// + public string jobName { get; set; } + /// + /// 任务分组 + /// + public string jobGroup { get; set; } + /// + /// 触发器ID + /// + public string triggerId { get; set; } + /// + /// 触发器名称 + /// + public string triggerName { get; set; } + /// + /// 触发器分组 + /// + public string triggerGroup { get; set; } + /// + /// 触发器状态 + /// + public string triggerStatus { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/TestRestSharpGetDto.cs b/Blog.Core.Model/ViewModels/TestRestSharpGetDto.cs new file mode 100644 index 00000000..9eb63b36 --- /dev/null +++ b/Blog.Core.Model/ViewModels/TestRestSharpGetDto.cs @@ -0,0 +1,19 @@ +using Blog.Core.Model.Models; + +namespace Blog.Core.Model.ViewModels +{ + /// + /// 用来测试 RestSharp Get 请求 + /// + public class TestRestSharpGetDto + { + /// + /// + /// + public string success { get; set; } + /// + /// + /// + public BlogArticle data { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/TestRestSharpPostDto.cs b/Blog.Core.Model/ViewModels/TestRestSharpPostDto.cs new file mode 100644 index 00000000..7d72478d --- /dev/null +++ b/Blog.Core.Model/ViewModels/TestRestSharpPostDto.cs @@ -0,0 +1,11 @@ +namespace Blog.Core.Model.ViewModels +{ + /// + /// 用来测试 RestSharp Post 请求 + /// + public class TestRestSharpPostDto + { + public bool success { get; set; } + public string name { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/TokenInfoViewModel.cs b/Blog.Core.Model/ViewModels/TokenInfoViewModel.cs new file mode 100644 index 00000000..51728f01 --- /dev/null +++ b/Blog.Core.Model/ViewModels/TokenInfoViewModel.cs @@ -0,0 +1,10 @@ +namespace Blog.Core.Model.ViewModels +{ + public class TokenInfoViewModel + { + public bool success { get; set; } + public string token { get; set; } + public double expires_in { get; set; } + public string token_type { get; set; } + } +} diff --git a/Blog.Core.Model/ViewModels/TopgbViewModels.cs b/Blog.Core.Model/ViewModels/TopgbViewModels.cs index 94d718c0..1b13db63 100644 --- a/Blog.Core.Model/ViewModels/TopgbViewModels.cs +++ b/Blog.Core.Model/ViewModels/TopgbViewModels.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.Model.ViewModels +namespace Blog.Core.Model.ViewModels { /// /// 留言排名展示类 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 new file mode 100644 index 00000000..d82c2d2f --- /dev/null +++ b/Blog.Core.Publish.Docker.Jenkins.sh @@ -0,0 +1,20 @@ +dotnet restore +dotnet build +cd Blog.Core.Api + +dotnet publish +echo "Successfully!!!! ^ please see the file ." +cd bin/Debug/net7.0/publish/ + +#rm -f appsettings.json +#\cp -rf /var/jenkins_home/workspace/SecurityConfig/Blog.Core/appsettings.json appsettings.json + +#docker stop apkcontainer +#docker rm apkcontainer +#docker rmi laozhangisphi/apkimg + +chmod 777 StopContainerImg.sh +./StopContainerImg.sh apkcontainer laozhangisphi/apkimg + +docker build -t laozhangisphi/apkimg . +docker run --name=apkcontainer -d -v /data/blogcore/appsettings.json:/app/appsettings.json -v /data/blogcore/Log/:/app/Log -v /etc/localtime:/etc/localtime -it -p 9291:9291 laozhangisphi/apkimg \ No newline at end of file diff --git a/Blog.Core.Publish.Docker.sh b/Blog.Core.Publish.Docker.sh new file mode 100644 index 00000000..eadc23c4 --- /dev/null +++ b/Blog.Core.Publish.Docker.sh @@ -0,0 +1,18 @@ +# 停止容器 +docker stop apkcontainer +# 删除容器 +docker rm apkcontainer +# 删除镜像 +docker rmi laozhangisphi/apkimg +# 切换目录 +cd /home/Blog.Core +# 发布项目 +./Blog.Core.Publish.Linux.sh +# 进入目录 +cd /home/Blog.Core/.PublishFiles +# 编译镜像 +docker build -t laozhangisphi/apkimg . +# 生成容器 +docker run --name=apkcontainer -d -v /etc/localtime:/etc/localtime -it -p 9291:9291 laozhangisphi/apkimg +# 启动容器 +docker start apkcontainer diff --git a/Blog.Core.Publish.Linux.sh b/Blog.Core.Publish.Linux.sh new file mode 100644 index 00000000..7599f204 --- /dev/null +++ b/Blog.Core.Publish.Linux.sh @@ -0,0 +1,9 @@ + +find .PublishFiles/ -type f -and ! -path '*/wwwroot/images/*' ! -name 'appsettings.*' |xargs rm -rf +dotnet build; +rm -rf /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles; +dotnet publish -o /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles; +rm -rf /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles/WMBlog.db; +# cp -r /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles ./; +awk 'BEGIN { cmd="cp -ri /home/Blog.Core/Blog.Core.Api/bin/Debug/.PublishFiles ./"; print "n" |cmd; }' +echo "Successfully!!!! ^ please see the file .PublishFiles"; \ No newline at end of file diff --git a/Blog.Core.Publish.bat b/Blog.Core.Publish.bat new file mode 100644 index 00000000..cebbbf58 --- /dev/null +++ b/Blog.Core.Publish.bat @@ -0,0 +1,19 @@ +color B + +del .PublishFiles\*.* /s /q + +dotnet restore + +dotnet build + +cd Blog.Core.Api + +dotnet publish -o ..\Blog.Core.Api\bin\Debug\net7.0\ + +md ..\.PublishFiles + +xcopy ..\Blog.Core.Api\bin\Debug\net7.0\*.* ..\.PublishFiles\ /s /e + +echo "Successfully!!!! ^ please see the file .PublishFiles" + +cmd \ No newline at end of file diff --git a/Blog.Core.Repository/AdvertisementRepository.cs b/Blog.Core.Repository/AdvertisementRepository.cs deleted file mode 100644 index 5a0b25b2..00000000 --- a/Blog.Core.Repository/AdvertisementRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; -using SqlSugar; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; - -namespace Blog.Core.Repository -{ - public class AdvertisementRepository : BaseRepository, IAdvertisementRepository - { - - - } -} diff --git a/Blog.Core.Repository/BASE/BaseRepository.cs b/Blog.Core.Repository/BASE/BaseRepository.cs index 8af7cd1e..3a1c1ee8 100644 --- a/Blog.Core.Repository/BASE/BaseRepository.cs +++ b/Blog.Core.Repository/BASE/BaseRepository.cs @@ -1,49 +1,82 @@ -using Blog.Core.IRepository.Base; +using Blog.Core.Common; +using Blog.Core.Common.DB; +using Blog.Core.IRepository.Base; using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.Tenants; +using Blog.Core.Repository.UnitOfWorks; +using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime; using SqlSugar; using System; using System.Collections.Generic; +using System.Data; using System.Linq.Expressions; -using System.Text; +using System.Reflection; using System.Threading.Tasks; namespace Blog.Core.Repository.Base { - public class BaseRepository : IBaseRepository where TEntity : class, new() + public class BaseRepository : IBaseRepository where TEntity : class, new() { - private DbContext _context; - private SqlSugarClient _db; - private SimpleClient _entityDb; + private readonly IUnitOfWorkManage _unitOfWorkManage; + private readonly SqlSugarScope _dbBase; - public DbContext Context + private ISqlSugarClient _db { - get { return _context; } - set { _context = value; } - } - internal SqlSugarClient Db - { - get { return _db; } - private set { _db = value; } - } - internal SimpleClient entityDb - { - get { return _entityDb; } - private set { _entityDb = value; } + get + { + ISqlSugarClient db = _dbBase; + + //修改使用 model备注字段作为切换数据库条件,使用sqlsugar TenantAttribute存放数据库ConnId + //参考 https://www.donet5.com/Home/Doc?typeId=2246 + var tenantAttr = typeof(TEntity).GetCustomAttribute(); + if (tenantAttr != null) + { + //统一处理 configId 小写 + db = _dbBase.GetConnectionScope(tenantAttr.configId.ToString().ToLower()); + return db; + } + + //多租户 + var mta = typeof(TEntity).GetCustomAttribute(); + if (mta is { TenantType: TenantTypeEnum.Db }) + { + //获取租户信息 租户信息可以提前缓存下来 + if (App.User is { TenantId: > 0 }) + { + 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 db; + } } - public BaseRepository() + + public ISqlSugarClient Db => _db; + + public BaseRepository(IUnitOfWorkManage unitOfWorkManage) { - DbContext.Init(BaseDBConfig.ConnectionString); - _context = DbContext.GetDbContext(); - _db = _context.Db; - _entityDb = _context.GetEntityDB(_db); + _unitOfWorkManage = unitOfWorkManage; + _dbBase = unitOfWorkManage.GetDbClient(); } - public async Task QueryById(object objId) { - return await Task.Run(() => _db.Queryable().InSingle(objId)); + //return await Task.Run(() => _db.Queryable().InSingle(objId)); + return await _db.Queryable().In(objId).SingleAsync(); } + /// /// 功能描述:根据ID查询一条数据 /// 作  者:Blog.Core @@ -53,7 +86,8 @@ 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 Task.Run(() => _db.Queryable().WithCacheIF(blnUseCache).InSingle(objId)); + return await _db.Queryable().WithCacheIF(blnUseCache, 10).In(objId).SingleAsync(); } /// @@ -64,7 +98,8 @@ public async Task QueryById(object objId, bool blnUseCache = false) /// 数据实体列表 public async Task> QueryByIDs(object[] lstIds) { - return await Task.Run(() => _db.Queryable().In(lstIds).ToList()); + //return await Task.Run(() => _db.Queryable().In(lstIds).ToList()); + return await _db.Queryable().In(lstIds).ToListAsync(); } /// @@ -72,11 +107,47 @@ 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 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.ExecuteReturnSnowflakeIdAsync(); + } + + /// + /// 写入实体数据 + /// + /// 实体类 + /// 指定只插入列 + /// 返回自增量列 + public async Task Add(TEntity entity, Expression> insertColumns = null) + { + var insert = _db.Insertable(entity); + if (insertColumns == null) + { + return await insert.ExecuteReturnSnowflakeIdAsync(); + } + else + { + return await insert.InsertColumns(insertColumns).ExecuteReturnSnowflakeIdAsync(); + } + } + + /// + /// 批量插入实体(速度快) + /// + /// 实体集合 + /// 影响行数 + public async Task> Add(List listEntity) + { + return await _db.Insertable(listEntity.ToArray()).ExecuteReturnSnowflakeIdListAsync(); } /// @@ -86,42 +157,61 @@ public async Task Add(TEntity entity) /// public async Task Update(TEntity entity) { + ////这种方式会以主键为条件 + //var i = await Task.Run(() => _db.Updateable(entity).ExecuteCommand()); + //return i > 0; //这种方式会以主键为条件 - var i = await Task.Run(() => _db.Updateable(entity).ExecuteCommand()); - return i > 0; + 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(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(sql, parameters) > 0; + } + + public async Task Update(object operateAnonymousObjects) + { + return await _db.Updateable(operateAnonymousObjects).ExecuteCommandAsync() > 0; } public async Task Update( - TEntity entity, - List lstColumns = null, - List lstIgnoreColumns = null, - string strWhere = "" - ) + TEntity entity, + List lstColumns = null, + List lstIgnoreColumns = null, + string where = "" + ) { - IUpdateable up = await Task.Run(() => _db.Updateable(entity)); + IUpdateable up = _db.Updateable(entity); if (lstIgnoreColumns != null && lstIgnoreColumns.Count > 0) { - up = await Task.Run(() => up.IgnoreColumns(it => lstIgnoreColumns.Contains(it))); + up = up.IgnoreColumns(lstIgnoreColumns.ToArray()); } + if (lstColumns != null && lstColumns.Count > 0) { - up = await Task.Run(() => up.UpdateColumns(it => lstColumns.Contains(it))); + up = up.UpdateColumns(lstColumns.ToArray()); } - if (!string.IsNullOrEmpty(strWhere)) + + if (!string.IsNullOrEmpty(where)) { - up = await Task.Run(() => up.Where(strWhere)); + up = up.Where(where); } - return await Task.Run(() => up.ExecuteCommand()) > 0; + + return await up.ExecuteCommandHasChangeAsync(); } /// @@ -131,8 +221,7 @@ 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(); } /// @@ -142,8 +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().In(id).ExecuteCommandHasChangeAsync(); } /// @@ -153,12 +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 @@ -166,18 +252,18 @@ public async Task DeleteByIds(object[] ids) /// 数据列表 public async Task> Query() { - return await Task.Run(() => _entityDb.GetList()); + return await _db.Queryable().ToListAsync(); } /// /// 功能描述:查询数据列表 /// 作  者: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(where), where).ToListAsync(); } /// @@ -188,7 +274,33 @@ public async Task> Query(string strWhere) /// 数据列表 public async Task> Query(Expression> whereExpression) { - return await Task.Run(() => _entityDb.GetList(whereExpression)); + return await _db.Queryable().WhereIF(whereExpression != null, whereExpression).ToListAsync(); + } + + /// + /// 功能描述:按照特定列查询数据列表 + /// 作  者:Blog.Core + /// + /// + /// + /// + public async Task> Query(Expression> expression) + { + return await _db.Queryable().Select(expression).ToListAsync(); + } + + /// + /// 功能描述:按照特定列查询数据列表带条件排序 + /// 作  者:Blog.Core + /// + /// + /// 过滤条件 + /// 查询实体条件 + /// 排序条件 + /// + public async Task> Query(Expression> expression, Expression> whereExpression, string orderByFields) + { + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields).WhereIF(whereExpression != null, whereExpression).Select(expression).ToListAsync(); } /// @@ -196,12 +308,13 @@ public async Task> Query(Expression> whereExpr /// 作  者:Blog.Core /// /// 条件表达式 - /// 排序字段,如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(orderByFields != null, orderByFields).ToListAsync(); } + /// /// 功能描述:查询一个列表 /// @@ -211,19 +324,20 @@ public async Task> Query(Expression> whereExpr /// public async Task> Query(Expression> whereExpression, Expression> orderByExpression, bool isAsc = true) { - return await Task.Run(() => _db.Queryable().OrderByIF(orderByExpression != null, orderByExpression, isAsc ? OrderByType.Asc : OrderByType.Desc).WhereIF(whereExpression != null, whereExpression).ToList()); + //return await Task.Run(() => _db.Queryable().OrderByIF(orderByExpression != null, orderByExpression, isAsc ? OrderByType.Asc : OrderByType.Desc).WhereIF(whereExpression != null, whereExpression).ToList()); + return await _db.Queryable().OrderByIF(orderByExpression != null, orderByExpression, isAsc ? OrderByType.Asc : OrderByType.Desc).WhereIF(whereExpression != null, whereExpression).ToListAsync(); } /// /// 功能描述:查询一个列表 /// 作  者: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(orderByFields), orderByFields).WhereIF(!string.IsNullOrEmpty(where), where).ToListAsync(); } @@ -232,90 +346,294 @@ 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(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 _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields).WhereIF(!string.IsNullOrEmpty(where), where).Take(top).ToListAsync(); + } + + /// + /// 根据sql语句查询 + /// + /// 完整的sql语句 + /// 参数 + /// 泛型集合 + public async Task> QuerySql(string sql, SugarParameter[] parameters = null) { - return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).Take(intTop).ToList()); + return await _db.Ado.SqlQueryAsync(sql, parameters); } - + /// + /// 根据sql语句查询 + /// + /// 完整的sql语句 + /// 参数 + /// DataTable + public async Task QueryTable(string sql, SugarParameter[] parameters = null) + { + return await _db.Ado.GetDataTableAsync(sql, parameters); + } /// /// 功能描述:分页查询 /// 作  者: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(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 where, + int pageIndex, + int pageSize, + string orderByFields) + { + return await _db.Queryable().OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(!string.IsNullOrEmpty(where), where).ToPageListAsync(pageIndex, pageSize); + } + - string strOrderByFileds) + /// + /// 分页查询[使用版本,其他分页未测试] + /// + /// 条件表达式 + /// 页码(下标0) + /// 页大小 + /// 排序字段,如name asc,age desc + /// + public async Task> QueryPage(Expression> whereExpression, int pageIndex = 1, int pageSize = 20, string orderByFields = null) { - return await Task.Run(() => _db.Queryable().OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds).WhereIF(!string.IsNullOrEmpty(strWhere), strWhere).ToPageList(intPageIndex, intPageSize)); + RefAsync totalCount = 0; + var list = await _db.Queryable() + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); + + return new PageModel(pageIndex, totalCount, pageSize, list); } - + /// + ///查询-多表查询 + /// + /// 实体1 + /// 实体2 + /// 实体3 + /// 返回对象 + /// 关联表达式 (join1,join2) => new object[] {JoinType.Left,join1.UserNo==join2.UserNo} + /// 返回表达式 (s1, s2) => new { Id =s1.UserNo, Id1 = s2.UserNo} + /// 查询表达式 (w1, w2) =>w1.UserNo == "") + /// + public async Task> QueryMuch( + Expression> joinExpression, + Expression> selectExpression, + Expression> whereLambda = null) where T : class, new() + { + if (whereLambda == null) + { + return await _db.Queryable(joinExpression).Select(selectExpression).ToListAsync(); + } - public async Task> QueryPage(Expression> whereExpression, - int intPageIndex = 0, int intPageSize = 20, string strOrderByFileds = null) + return await _db.Queryable(joinExpression).Where(whereLambda).Select(selectExpression).ToListAsync(); + } + + + /// + /// 两表联合查询-分页 + /// + /// 实体1 + /// 实体1 + /// 返回对象 + /// 关联表达式 + /// 返回表达式 + /// 查询表达式 + /// 页码 + /// 页大小 + /// 排序字段 + /// + public async Task> QueryTabsPage( + Expression> joinExpression, + Expression> selectExpression, + Expression> whereExpression, + int pageIndex = 1, + int pageSize = 20, + string orderByFields = null) { - return await Task.Run(() => _db.Queryable() - .OrderByIF(!string.IsNullOrEmpty(strOrderByFileds), strOrderByFileds) - .WhereIF(whereExpression != null, whereExpression) - .ToPageList(intPageIndex, intPageSize)); + RefAsync totalCount = 0; + var list = await _db.Queryable(joinExpression) + .Select(selectExpression) + .OrderByIF(!string.IsNullOrEmpty(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); + return new PageModel(pageIndex, totalCount, pageSize, list); } + /// + /// 两表联合查询-分页-分组 + /// + /// 实体1 + /// 实体1 + /// 返回对象 + /// 关联表达式 + /// 返回表达式 + /// 查询表达式 + /// group表达式 + /// 页码 + /// 页大小 + /// 排序字段 + /// + public async Task> QueryTabsPage( + Expression> joinExpression, + Expression> selectExpression, + Expression> whereExpression, + Expression> groupExpression, + 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(orderByFields), orderByFields) + .WhereIF(whereExpression != null, whereExpression) + .ToPageListAsync(pageIndex, pageSize, totalCount); + return new PageModel(pageIndex, totalCount, pageSize, list); + } + //var exp = Expressionable.Create() + // .And(s => s.tdIsDelete != true) + // .And(p => p.IsDeleted != true) + // .And(p => p.pmId != null) + // .AndIF(!string.IsNullOrEmpty(model.paramCode1), (s) => s.uID == model.paramCode1.ObjToInt()) + // .AndIF(!string.IsNullOrEmpty(model.searchText), (s) => (s.groupName != null && s.groupName.Contains(model.searchText)) + // || (s.jobName != null && s.jobName.Contains(model.searchText)) + // || (s.uRealName != null && s.uRealName.Contains(model.searchText))) + // .ToExpression();//拼接表达式 + //var data = await _projectMemberServices.QueryTabsPage( + // (s, p) => new object[] { JoinType.Left, s.uID == p.uId }, + // (s, p) => new ProjectToUser + // { + // uID = s.uID, + // uRealName = s.uRealName, + // groupName = s.groupName, + // 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 new file mode 100644 index 00000000..77e9a808 --- /dev/null +++ b/Blog.Core.Repository/BASE/IBaseRepository.cs @@ -0,0 +1,258 @@ +using Blog.Core.Model; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Blog.Core.IRepository.Base +{ + public interface IBaseRepository where TEntity : class + { + /// + /// SqlsugarClient实体 + /// + ISqlSugarClient Db { get;} + /// + /// 根据Id查询实体 + /// + /// + /// + Task QueryById(object objId); + Task QueryById(object objId, bool blnUseCache = false); + /// + /// 根据id数组查询实体list + /// + /// + /// + Task> QueryByIDs(object[] lstIds); + + /// + /// 添加 + /// + /// + /// + Task Add(TEntity model); + + /// + /// 批量添加 + /// + /// + /// + Task> Add(List listEntity); + + /// + /// 根据id 删除某一实体 + /// + /// + /// + Task DeleteById(object id); + + /// + /// 根据对象,删除某一实体 + /// + /// + /// + Task Delete(TEntity model); + + /// + /// 根据id数组,删除实体list + /// + /// + /// + Task DeleteByIds(object[] ids); + + /// + /// 更新model + /// + /// + /// + Task Update(TEntity model); + /// + /// 更新model + /// + /// + /// + Task Update(List model); + + /// + /// 根据model,更新,带where条件 + /// + /// + /// + /// + Task Update(TEntity entity, string where); + Task Update(object operateAnonymousObjects); + + /// + /// 根据model,更新,指定列 + /// + /// + /// + /// + /// + /// + Task Update(TEntity entity, List lstColumns = null, List lstIgnoreColumns = null, string where = ""); + + /// + /// 查询 + /// + /// + Task> Query(); + + /// + /// 带sql where查询 + /// + /// + /// + Task> Query(string where); + + /// + /// 根据表达式查询 + /// + /// + /// + Task> Query(Expression> whereExpression); + + /// + /// 根据表达式,指定返回对象模型,查询 + /// + /// + /// + /// + Task> Query(Expression> expression); + + /// + /// 根据表达式,指定返回对象模型,排序,查询 + /// + /// + /// + /// + /// + /// + 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 where, string orderByFields); + + 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 pageIndex, int pageSize, string orderByFields); + Task> Query(string where, int pageIndex, int pageSize, string orderByFields); + + /// + /// 根据表达式,排序字段,分页查询 + /// + /// + /// + /// + /// + /// + 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> QueryTabsPage( + Expression> joinExpression, + Expression> selectExpression, + Expression> whereExpression, + int pageIndex = 1, + int pageSize = 20, + string orderByFields = null); + + /// + /// 两表联合查询-分页-分组 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task> QueryTabsPage( + Expression> joinExpression, + Expression> selectExpression, + Expression> whereExpression, + Expression> groupExpression, + 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 795dec00..fff34572 100644 --- a/Blog.Core.Repository/Blog.Core.Repository.csproj +++ b/Blog.Core.Repository/Blog.Core.Repository.csproj @@ -1,16 +1,29 @@ - + - - netcoreapp2.2 - - - ..\Blog.Core\bin\Debug\ - - - + + + + + + + ..\Blog.Core.Api\bin\Debug\ + + + + ..\Blog.Core\bin\Release\ + + + + + + + + + + diff --git a/Blog.Core.Repository/BlogArticleRepository.cs b/Blog.Core.Repository/BlogArticleRepository.cs deleted file mode 100644 index 34416caf..00000000 --- a/Blog.Core.Repository/BlogArticleRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - public class BlogArticleRepository: BaseRepository, IBlogArticleRepository - { - } -} diff --git a/Blog.Core.Repository/GuestbookRepository.cs b/Blog.Core.Repository/GuestbookRepository.cs deleted file mode 100644 index 88f0c970..00000000 --- a/Blog.Core.Repository/GuestbookRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - public class GuestbookRepository : BaseRepository, IGuestbookRepository - { - } -} diff --git a/Blog.Core.Repository/IRoleModulePermissionRepository.cs b/Blog.Core.Repository/IRoleModulePermissionRepository.cs new file mode 100644 index 00000000..9ba3d4ed --- /dev/null +++ b/Blog.Core.Repository/IRoleModulePermissionRepository.cs @@ -0,0 +1,24 @@ +using Blog.Core.IRepository.Base; +using Blog.Core.Model.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.IRepository +{ + /// + /// IRoleModulePermissionRepository + /// + public interface IRoleModulePermissionRepository : IBaseRepository//类名 + { + Task> QueryMuchTable(); + Task> RoleModuleMaps(); + Task> GetRMPMaps(); + /// + /// 批量更新菜单与接口的关系 + /// + /// 菜单主键 + /// 接口主键 + /// + Task UpdateModuleId(long permissionId, long moduleId); + } +} diff --git a/Blog.Core.Repository/ModulePermissionRepository.cs b/Blog.Core.Repository/ModulePermissionRepository.cs deleted file mode 100644 index 9f1eb7c2..00000000 --- a/Blog.Core.Repository/ModulePermissionRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - public class ModulePermissionRepository : BaseRepository, IModulePermissionRepository - { - } -} diff --git a/Blog.Core.Repository/ModuleRepository.cs b/Blog.Core.Repository/ModuleRepository.cs deleted file mode 100644 index f95906a0..00000000 --- a/Blog.Core.Repository/ModuleRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blog.Core.Repository.Base; -using Blog.Core.Model.Models; -using Blog.Core.IRepository; - -namespace Blog.Core.Repository -{ - /// - /// ModuleRepository - /// - public class ModuleRepository : BaseRepository, IModuleRepository - { - - - } -} diff --git a/Blog.Core.Repository/MongoRepository/IMongoBaseRepository.cs b/Blog.Core.Repository/MongoRepository/IMongoBaseRepository.cs new file mode 100644 index 00000000..627ffff7 --- /dev/null +++ b/Blog.Core.Repository/MongoRepository/IMongoBaseRepository.cs @@ -0,0 +1,16 @@ +using MongoDB.Driver; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Repository.MongoRepository +{ + + 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 new file mode 100644 index 00000000..feca1f51 --- /dev/null +++ b/Blog.Core.Repository/MongoRepository/MongoBaseRepository.cs @@ -0,0 +1,57 @@ +using MongoDB.Bson; +using MongoDB.Driver; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Repository.MongoRepository +{ + + + public class MongoBaseRepository : IMongoBaseRepository where TEntity : class, new() + { + private readonly MongoDbContext _context; + + public MongoBaseRepository() + { + _context = new MongoDbContext(); + } + + public async Task AddAsync(TEntity entity) + { + await _context.Db.GetCollection(typeof(TEntity).Name) + .InsertOneAsync(entity); + } + + public async Task GetAsync(int Id) + { + var filter = Builders.Filter.Eq("Id", Id); + + return await _context.Db.GetCollection(typeof(TEntity).Name) + .Find(filter) + .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 new file mode 100644 index 00000000..6eadedca --- /dev/null +++ b/Blog.Core.Repository/MongoRepository/MongoDbContext.cs @@ -0,0 +1,31 @@ + +using Blog.Core.Common; +using MongoDB.Driver; + +namespace Blog.Core.Repository.MongoRepository +{ + + public class MongoDbContext + { + private readonly IMongoDatabase _database = null; + + public MongoDbContext() + { + var client = new MongoClient(AppSettings.app(new string[] { "Mongo", "ConnectionString" })); + _database = client.GetDatabase(AppSettings.app(new string[] { "Mongo", "Database" })); + } + + public IMongoDatabase Db + { + get { return _database; } + } + + //public IMongoCollection Query + //{ + // get + // { + // return _database.GetCollection(nameof(TEntity)); + // } + //} + } +} diff --git a/Blog.Core.Repository/PasswordLibRepository.cs b/Blog.Core.Repository/PasswordLibRepository.cs deleted file mode 100644 index f2d618a3..00000000 --- a/Blog.Core.Repository/PasswordLibRepository.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - public partial class PasswordLibRepository : BaseRepository, IPasswordLibRepository - { - - } -} diff --git a/Blog.Core.Repository/PermissionRepository.cs b/Blog.Core.Repository/PermissionRepository.cs deleted file mode 100644 index 15c100df..00000000 --- a/Blog.Core.Repository/PermissionRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - public class PermissionRepository : BaseRepository, IPermissionRepository - { - } -} diff --git a/Blog.Core.Repository/RoleModulePermissionRepository.cs b/Blog.Core.Repository/RoleModulePermissionRepository.cs index 257267ae..9438ff50 100644 --- a/Blog.Core.Repository/RoleModulePermissionRepository.cs +++ b/Blog.Core.Repository/RoleModulePermissionRepository.cs @@ -1,8 +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 Blog.Core.Repository.UnitOfWorks; namespace Blog.Core.Repository { @@ -11,17 +13,97 @@ namespace Blog.Core.Repository /// public class RoleModulePermissionRepository : BaseRepository, IRoleModulePermissionRepository { + public RoleModulePermissionRepository(IUnitOfWorkManage unitOfWorkManage) : base(unitOfWorkManage) + { + } - public async Task> WithChildrenModel() + public async Task> QueryMuchTable() { - var list = await Task.Run(() => Db.Queryable() - .Mapper(it => it.Role, it => it.RoleId) - .Mapper(it => it.Permission, it => it.PermissionId) - .Mapper(it => it.Module, it => it.ModuleId).ToList()); + return await QueryMuch( + (rmp, m, p) => new object[] { + JoinType.Left, rmp.ModuleId == m.Id, + JoinType.Left, rmp.PermissionId == p.Id + }, + + (rmp, m, p) => new TestMuchTableResult() + { + moduleName = m.Name, + permName = p.Name, + rid = rmp.RoleId, + mid = rmp.ModuleId, + pid = rmp.PermissionId + }, + + (rmp, m, p) => rmp.IsDeleted == false + ); + } + + /// + /// 角色权限Map + /// RoleModulePermission, Module, Role 三表联合 + /// 第四个类型 RoleModulePermission 是返回值 + /// + /// + public async Task> RoleModuleMaps() + { + return await QueryMuch( + (rmp, m, r) => new object[] { + JoinType.Left, rmp.ModuleId == m.Id, + JoinType.Left, rmp.RoleId == r.Id + }, + + (rmp, m, r) => new RoleModulePermission() + { + Role = r, + Module = m, + IsDeleted = rmp.IsDeleted + }, - return null; + (rmp, m, r) => rmp.IsDeleted == false && m.IsDeleted == false && r.IsDeleted == false + ); } + + + /// + /// 查询出角色-菜单-接口关系表全部Map属性数据 + /// + /// + public async Task> GetRMPMaps() + { + return await Db.Queryable() + .Mapper(rmp => rmp.Module, rmp => rmp.ModuleId) + .Mapper(rmp => rmp.Permission, rmp => rmp.PermissionId) + .Mapper(rmp => rmp.Role, rmp => rmp.RoleId) + .Where(d => d.IsDeleted == false) + .ToListAsync(); + } + + + /// + /// 查询出角色-菜单-接口关系表全部Map属性数据 + /// + /// + public async Task> GetRMPMapsPage() + { + return await Db.Queryable() + .Mapper(rmp => rmp.Module, rmp => rmp.ModuleId) + .Mapper(rmp => rmp.Permission, rmp => rmp.PermissionId) + .Mapper(rmp => rmp.Role, rmp => rmp.RoleId) + .ToPageListAsync(1, 5, 10); + } + + /// + /// 批量更新菜单与接口的关系 + /// + /// 菜单主键 + /// 接口主键 + /// + public async Task UpdateModuleId(long permissionId, long moduleId) + { + await Db.Updateable(it => it.ModuleId == moduleId).Where( + it => it.PermissionId == permissionId).ExecuteCommandAsync(); + } } -} +} \ No newline at end of file diff --git a/Blog.Core.Repository/RoleRepository.cs b/Blog.Core.Repository/RoleRepository.cs deleted file mode 100644 index 8c87b158..00000000 --- a/Blog.Core.Repository/RoleRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blog.Core.IRepository; -using Blog.Core.Repository.Base; -using Blog.Core.Model.Models; - -namespace Blog.Core.Repository -{ - /// - /// RoleRepository - /// - public class RoleRepository : BaseRepository, IRoleRepository - { - - - } -} diff --git a/Blog.Core.Repository/TopicDetailRepository.cs b/Blog.Core.Repository/TopicDetailRepository.cs deleted file mode 100644 index 6101e43b..00000000 --- a/Blog.Core.Repository/TopicDetailRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - public class TopicDetailRepository: BaseRepository, ITopicDetailRepository - { - } -} diff --git a/Blog.Core.Repository/TopicRepository.cs b/Blog.Core.Repository/TopicRepository.cs deleted file mode 100644 index 005626ec..00000000 --- a/Blog.Core.Repository/TopicRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - public class TopicRepository: BaseRepository, ITopicRepository - { - } -} 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.Repository/UserRoleRepository.cs b/Blog.Core.Repository/UserRoleRepository.cs deleted file mode 100644 index 3cdef40d..00000000 --- a/Blog.Core.Repository/UserRoleRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blog.Core.FrameWork.IRepository; -using Blog.Core.Repository.Base; -using Blog.Core.Model.Models; - -namespace Blog.Core.Repository -{ - /// - /// UserRoleRepository - /// - public class UserRoleRepository : BaseRepository, IUserRoleRepository - { - - - } -} diff --git a/Blog.Core.Repository/sugar/BaseDBConfig.cs b/Blog.Core.Repository/sugar/BaseDBConfig.cs deleted file mode 100644 index 0c3e2da4..00000000 --- a/Blog.Core.Repository/sugar/BaseDBConfig.cs +++ /dev/null @@ -1,23 +0,0 @@ - -using Blog.Core.Common; -using Microsoft.Extensions.Configuration; -using System; -using System.IO; -using System.Linq; - -namespace Blog.Core.Repository -{ - public class BaseDBConfig - { - static string sqlServerConnection = Appsettings.app(new string[] { "AppSettings", "SqlServer", "SqlServerConnection" });//获取连接字符串 - - public static string ConnectionString = File.Exists(@"D:\my-file\dbCountPsw1.txt") ? File.ReadAllText(@"D:\my-file\dbCountPsw1.txt").Trim() : (!string.IsNullOrEmpty(sqlServerConnection) ? sqlServerConnection : "server=.;uid=sa;pwd=sa;database=WMBlogDB"); - - //正常格式是 - - //public static string ConnectionString = "server=.;uid=sa;pwd=sa;database=WMBlogDB"; - - //原谅我用配置文件的形式,因为我直接调用的是我的服务器账号和密码,安全起见 - - } -} diff --git a/Blog.Core.Repository/sugar/DbContext.cs b/Blog.Core.Repository/sugar/DbContext.cs deleted file mode 100644 index 2af4074f..00000000 --- a/Blog.Core.Repository/sugar/DbContext.cs +++ /dev/null @@ -1,488 +0,0 @@ -using SqlSugar; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Blog.Core.Repository -{ - public class DbContext - { - - private static string _connectionString; - private static DbType _dbType; - private SqlSugarClient _db; - - /// - /// 连接字符串 - /// Blog.Core - /// - public static string ConnectionString - { - get { return _connectionString; } - set { _connectionString = value; } - } - /// - /// 数据库类型 - /// Blog.Core - /// - public static DbType DbType - { - get { return _dbType; } - set { _dbType = value; } - } - /// - /// 数据连接对象 - /// Blog.Core - /// - public SqlSugarClient Db - { - get { return _db; } - private set { _db = value; } - } - - /// - /// 数据库上下文实例(自动关闭连接) - /// Blog.Core - /// - public static DbContext Context - { - get - { - return new DbContext(); - } - - } - - - /// - /// 功能描述:构造函数 - /// 作  者:Blog.Core - /// - private DbContext() - { - if (string.IsNullOrEmpty(_connectionString)) - throw new ArgumentNullException("数据库连接字符串为空"); - _db = new SqlSugarClient(new ConnectionConfig() - { - ConnectionString = _connectionString, - DbType = _dbType, - IsAutoCloseConnection = true, - IsShardSameThread = true, - ConfigureExternalServices = new ConfigureExternalServices() - { - //DataInfoCacheService = new HttpRuntimeCache() - }, - MoreSettings = new ConnMoreSettings() - { - //IsWithNoLockQuery = true, - IsAutoRemoveDataCache = true - } - }); - } - - /// - /// 功能描述:构造函数 - /// 作  者:Blog.Core - /// - /// 是否自动关闭连接 - private DbContext(bool blnIsAutoCloseConnection) - { - if (string.IsNullOrEmpty(_connectionString)) - throw new ArgumentNullException("数据库连接字符串为空"); - _db = new SqlSugarClient(new ConnectionConfig() - { - ConnectionString = _connectionString, - DbType = _dbType, - IsAutoCloseConnection = blnIsAutoCloseConnection, - IsShardSameThread = true, - ConfigureExternalServices = new ConfigureExternalServices() - { - //DataInfoCacheService = new HttpRuntimeCache() - }, - MoreSettings = new ConnMoreSettings() - { - //IsWithNoLockQuery = true, - IsAutoRemoveDataCache = true - } - }); - - //_db.Aop.OnLogExecuted = (sql, pars) => //SQL执行完事件 - //{ - // OutSql2Log(sql, GetParas(pars)); - //}; - - _db.Aop.OnLogExecuting = (sql, pars) => //SQL执行中事件 - { - Parallel.For(0, 1, e => - { - OutSql2Log(sql, GetParas(pars)); - }); - }; - - } - - #region 实例方法 - /// - /// 功能描述:获取数据库处理对象 - /// 作  者:Blog.Core - /// - /// 返回值 - public SimpleClient GetEntityDB() where T : class, new() - { - return new SimpleClient(_db); - } - /// - /// 功能描述:获取数据库处理对象 - /// 作  者:Blog.Core - /// - /// db - /// 返回值 - public SimpleClient GetEntityDB(SqlSugarClient db) where T : class, new() - { - return new SimpleClient(db); - } - - #region 根据数据库表生产实体类 - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - public void CreateClassFileByDBTalbe(string strPath) - { - CreateClassFileByDBTalbe(strPath, "Blog.Core.Entity"); - } - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - /// 命名空间 - public void CreateClassFileByDBTalbe(string strPath, string strNameSpace) - { - CreateClassFileByDBTalbe(strPath, strNameSpace, null); - } - - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - /// 命名空间 - /// 生产指定的表 - public void CreateClassFileByDBTalbe( - string strPath, - string strNameSpace, - string[] lstTableNames) - { - CreateClassFileByDBTalbe(strPath, strNameSpace, lstTableNames, string.Empty); - } - - /// - /// 功能描述:根据数据库表生产实体类 - /// 作  者:Blog.Core - /// - /// 实体类存放路径 - /// 命名空间 - /// 生产指定的表 - /// 实现接口 - public void CreateClassFileByDBTalbe( - string strPath, - string strNameSpace, - string[] lstTableNames, - string strInterface, - bool blnSerializable = false) - { - if (lstTableNames != null && lstTableNames.Length > 0) - { - _db.DbFirst.Where(lstTableNames).IsCreateDefaultValue().IsCreateAttribute() - .SettingClassTemplate(p => p = @" -{using} - -namespace {Namespace} -{ - {ClassDescription}{SugarTable}" + (blnSerializable ? "[Serializable]" : "") + @" - public partial class {ClassName}" + (string.IsNullOrEmpty(strInterface) ? "" : (" : " + strInterface)) + @" - { - public {ClassName}() - { -{Constructor} - } -{PropertyName} - } -} -") - .SettingPropertyTemplate(p => p = @" - {SugarColumn} - public {PropertyType} {PropertyName} - { - get - { - return _{PropertyName}; - } - set - { - if(_{PropertyName}!=value) - { - base.SetValueCall(" + "\"{PropertyName}\",_{PropertyName}" + @"); - } - _{PropertyName}=value; - } - }") - .SettingPropertyDescriptionTemplate(p => p = " private {PropertyType} _{PropertyName};\r\n" + p) - .SettingConstructorTemplate(p => p = " this._{PropertyName} ={DefaultValue};") - .CreateClassFile(strPath, strNameSpace); - } - else - { - _db.DbFirst.IsCreateAttribute().IsCreateDefaultValue() - .SettingClassTemplate(p => p = @" -{using} - -namespace {Namespace} -{ - {ClassDescription}{SugarTable}" + (blnSerializable ? "[Serializable]" : "") + @" - public partial class {ClassName}" + (string.IsNullOrEmpty(strInterface) ? "" : (" : " + strInterface)) + @" - { - public {ClassName}() - { -{Constructor} - } -{PropertyName} - } -} -") - .SettingPropertyTemplate(p => p = @" - {SugarColumn} - public {PropertyType} {PropertyName} - { - get - { - return _{PropertyName}; - } - set - { - if(_{PropertyName}!=value) - { - base.SetValueCall(" + "\"{PropertyName}\",_{PropertyName}" + @"); - } - _{PropertyName}=value; - } - }") - .SettingPropertyDescriptionTemplate(p => p = " private {PropertyType} _{PropertyName};\r\n" + p) - .SettingConstructorTemplate(p => p = " this._{PropertyName} ={DefaultValue};") - .CreateClassFile(strPath, strNameSpace); - } - } - #endregion - - #region 根据实体类生成数据库表 - /// - /// 功能描述:根据实体类生成数据库表 - /// 作  者:Blog.Core - /// - /// 是否备份表 - /// 指定的实体 - public void CreateTableByEntity(bool blnBackupTable, params T[] lstEntitys) where T : class, new() - { - Type[] lstTypes = null; - if (lstEntitys != null) - { - lstTypes = new Type[lstEntitys.Length]; - for (int i = 0; i < lstEntitys.Length; i++) - { - T t = lstEntitys[i]; - lstTypes[i] = typeof(T); - } - } - CreateTableByEntity(blnBackupTable, lstTypes); - } - - /// - /// 功能描述:根据实体类生成数据库表 - /// 作  者:Blog.Core - /// - /// 是否备份表 - /// 指定的实体 - public void CreateTableByEntity(bool blnBackupTable, params Type[] lstEntitys) - { - if (blnBackupTable) - { - _db.CodeFirst.BackupTable().InitTables(lstEntitys); //change entity backupTable - } - else - { - _db.CodeFirst.InitTables(lstEntitys); - } - } - #endregion - - #endregion - - private string GetParas(SugarParameter[] pars) - { - string key = ""; - foreach (var param in pars) - { - key += $"{param.ParameterName}:{param.Value}\n"; - } - - return key; - } - - #region 输出到当前项目日志 - //读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 - static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); - static int LogCount = 100; - static int WritedCount = 0; - static int FailedCount = 0; - static void OutSql2Log(string sql, string pars) - { - try - { - //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入 - //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。 - // 从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度 - // 因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常 - LogWriteLock.EnterWriteLock(); - - var path = Directory.GetCurrentDirectory() + @"\Log"; - string logFilePath = path + $@"\SqlLog.log"; - var now = DateTime.Now; - var logContent = string.Format("--------------------------------\n" + - DateTime.Now+"\n"+ - pars+"\n"+ - sql + "\n"+ - "--------------------------------\n" - ); - - File.AppendAllText(logFilePath, logContent); - WritedCount++; - } - catch (Exception) - { - FailedCount++; - } - finally - { - //退出写入模式,释放资源占用 - //注意:一次请求对应一次释放 - // 若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放] - // 若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定] - LogWriteLock.ExitWriteLock(); - } - } - - - private void OutSql2Log2(string sql, string pars) - { - - - var path = Directory.GetCurrentDirectory() + @"\Log"; - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - string fileName = path + $@"\SqlLog.log"; - - StreamWriter sw = File.AppendText(fileName); - sw.WriteLine("--------------------------------"); - sw.WriteLine(DateTime.Now); - sw.WriteLine(pars); - sw.WriteLine(sql); - sw.WriteLine("--------------------------------"); - sw.WriteLine(); - sw.Close(); - - } - - #endregion - - - - #region 静态方法 - - /// - /// 功能描述:获得一个DbContext - /// 作  者:Blog.Core - /// - /// 是否自动关闭连接(如果为false,则使用接受时需要手动关闭Db) - /// 返回值 - public static DbContext GetDbContext(bool blnIsAutoCloseConnection = true) - { - return new DbContext(blnIsAutoCloseConnection); - } - - /// - /// 功能描述:设置初始化参数 - /// 作  者:Blog.Core - /// - /// 连接字符串 - /// 数据库类型 - public static void Init(string strConnectionString, DbType enmDbType = SqlSugar.DbType.SqlServer) - { - _connectionString = strConnectionString; - _dbType = enmDbType; - } - - /// - /// 功能描述:创建一个链接配置 - /// 作  者:Blog.Core - /// - /// 是否自动关闭连接 - /// 是否夸类事务 - /// ConnectionConfig - public static ConnectionConfig GetConnectionConfig(bool blnIsAutoCloseConnection = true, bool blnIsShardSameThread = false) - { - ConnectionConfig config = new ConnectionConfig() - { - ConnectionString = _connectionString, - DbType = _dbType, - IsAutoCloseConnection = blnIsAutoCloseConnection, - ConfigureExternalServices = new ConfigureExternalServices() - { - //DataInfoCacheService = new HttpRuntimeCache() - }, - IsShardSameThread = blnIsShardSameThread - }; - return config; - } - - /// - /// 功能描述:获取一个自定义的DB - /// 作  者:Blog.Core - /// - /// config - /// 返回值 - public static SqlSugarClient GetCustomDB(ConnectionConfig config) - { - return new SqlSugarClient(config); - } - /// - /// 功能描述:获取一个自定义的数据库处理对象 - /// 作  者:Blog.Core - /// - /// sugarClient - /// 返回值 - public static SimpleClient GetCustomEntityDB(SqlSugarClient sugarClient) where T : class, new() - { - return new SimpleClient(sugarClient); - } - /// - /// 功能描述:获取一个自定义的数据库处理对象 - /// 作  者:Blog.Core - /// - /// config - /// 返回值 - public static SimpleClient GetCustomEntityDB(ConnectionConfig config) where T : class, new() - { - SqlSugarClient sugarClient = GetCustomDB(config); - return GetCustomEntityDB(sugarClient); - } - #endregion - } -} diff --git a/Blog.Core.Repository/sysUserInfoRepository.cs b/Blog.Core.Repository/sysUserInfoRepository.cs deleted file mode 100644 index 86bbb21d..00000000 --- a/Blog.Core.Repository/sysUserInfoRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blog.Core.IRepository; -using Blog.Core.Model.Models; -using Blog.Core.Repository.Base; - -namespace Blog.Core.Repository -{ - /// - /// sysUserInfoRepository - /// - public class sysUserInfoRepository : BaseRepository, IsysUserInfoRepository - { - - - } -} diff --git a/Blog.Core.Serilog.Es/AppSettingsFileNameConfig.cs b/Blog.Core.Serilog.Es/AppSettingsFileNameConfig.cs new file mode 100644 index 00000000..c4f2cda0 --- /dev/null +++ b/Blog.Core.Serilog.Es/AppSettingsFileNameConfig.cs @@ -0,0 +1,30 @@ +using System; + +namespace Blog.Core.Serilog.Es +{ + public class AppSettingsFileNameConfig + { + /// + /// 配置文件名称常量 + /// + public static string AppSettingsFileName = $"appsettings{ GetAppSettingsConfigName() }json"; + + + /// + /// 根据环境变量定向配置文件名称 + /// + /// + 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.Serilog.Es/Blog.Core.Serilog.Es.csproj b/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj new file mode 100644 index 00000000..2024571e --- /dev/null +++ b/Blog.Core.Serilog.Es/Blog.Core.Serilog.Es.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Blog.Core.Serilog.Es/Formatters/JsonConfigUtils.cs b/Blog.Core.Serilog.Es/Formatters/JsonConfigUtils.cs new file mode 100644 index 00000000..e9425040 --- /dev/null +++ b/Blog.Core.Serilog.Es/Formatters/JsonConfigUtils.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Blog.Core.Serilog.Es.Formatters +{ + /// + /// Json 配置文件通用类 + /// + public static class JsonConfigUtils + { + #region 变量 + + /// + /// 锁 + /// + private static object __Lock__ = new object(); + + // 读取到的系统配置信息 + public static IConfiguration Configuration { get; set; } + + #endregion + + /// + /// 读取配置文件的信息 + /// + /// + /// 要读取json的名称 + /// 要读取的json节点名称 + /// + public static T GetAppSettings(string AppSettingsFileName, string key) where T : class, new() + { + lock (__Lock__) + { + if (Configuration == null) + { + Configuration = new ConfigurationBuilder() + .Add(new JsonConfigurationSource + { + Path = AppSettingsFileName, + Optional = false, + ReloadOnChange = true + }) + .Build(); + } + var appconfig = new ServiceCollection() + .AddOptions() + .Configure(Configuration.GetSection(key)) + .BuildServiceProvider() + .GetService>() + .Value; + + return appconfig; + } + } + + + public static string GetJson(string jsonPath, string key) + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile(jsonPath).Build(); //json文件地址 + string s = config.GetSection(key).Value; //json某个对象 + return s; + } + } +} diff --git a/Blog.Core.Serilog.Es/Formatters/LogConfigRootDTO.cs b/Blog.Core.Serilog.Es/Formatters/LogConfigRootDTO.cs new file mode 100644 index 00000000..36a9916c --- /dev/null +++ b/Blog.Core.Serilog.Es/Formatters/LogConfigRootDTO.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Blog.Core.Serilog.Es.Formatters +{ + + public class LogConfigRootDTO + { + /// + /// tcp日志的host地址 + /// + public string tcpAddressHost { set; get; } + + /// + /// tcp日志的port地址 + /// + public int tcpAddressPort { set; get; } + + public List ConfigsInfo { get; set; } + } + + public class Configsinfo + { + public string FiedName { get; set; } + public string FiedValue { get; set; } + } +} diff --git a/Blog.Core.Serilog.Es/Formatters/LogstashJsonFormatter.cs b/Blog.Core.Serilog.Es/Formatters/LogstashJsonFormatter.cs new file mode 100644 index 00000000..f123c177 --- /dev/null +++ b/Blog.Core.Serilog.Es/Formatters/LogstashJsonFormatter.cs @@ -0,0 +1,152 @@ +// Adapted from RawJsonFormatter in Serilog.Sinks.Seq Copyright 2016 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Serilog.Events; +using Serilog.Formatting; +using Serilog.Formatting.Json; +using Blog.Core.Serilog.Es.HttpInfo; + +namespace Blog.Core.Serilog.Es.Formatters +{ + public class LogstashJsonFormatter : ITextFormatter + { + private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(); + + public void Format(LogEvent logEvent, TextWriter output) + { + FormatContent(logEvent, output); + + output.WriteLine(); + } + + + /// + /// 格式化 最终输出到elk的核心部分 + /// + /// + /// + private static void FormatContent(LogEvent logEvent, TextWriter output) + { + if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); + if (output == null) throw new ArgumentNullException(nameof(output)); + + output.Write('{'); + + // 读取相关配置 + var logConfigRootDTOInfo = JsonConfigUtils.GetAppSettings(AppSettingsFileNameConfig.AppSettingsFileName, "LogFiedOutPutConfigs"); + if (logConfigRootDTOInfo == null) + { + return; + } + + // 写入所有的项目配置项的字段 在appsetting中配置的 输出elk节点的数据字段 + foreach (var item in logConfigRootDTOInfo.ConfigsInfo) + { + switch (item.FiedName) + { + //case "orgid": + // WritePropertyAndValue(output, "method", HttpContextProvider.GetCurrent().Request.Method); + // output.Write(","); + // break; + default: + WritePropertyAndValue(output, item.FiedName, item.FiedValue); + output.Write(","); + break; + } + } + // 写入http对应的信息数据 + if (HttpContextProvider.GetCurrent()!=null && HttpContextProvider.GetCurrent().Request!=null) + { + if (!string.IsNullOrEmpty(HttpContextProvider.GetCurrent().Request.Method)) + { + WritePropertyAndValue(output, "method", HttpContextProvider.GetCurrent().Request.Method); + output.Write(","); + } + // 输出请求页面url + if (!string.IsNullOrEmpty(HttpContextProvider.GetCurrent().Request.Path)) + { + WritePropertyAndValue(output, "requestUrl", HttpContextProvider.GetCurrent().Request.Path.ToString()); + output.Write(","); + } + // 输出携带token + if (HttpContextProvider.GetCurrent().Request.Headers["Authorization"].FirstOrDefault() != null) + { + WritePropertyAndValue(output, "Authorization", HttpContextProvider.GetCurrent().Request.Headers["Authorization"].FirstOrDefault()); + output.Write(","); + } + // 输出请求参数 + if (!string.IsNullOrEmpty(HttpContextProvider.GetCurrent().Request.Method)) + { + string contentFromBody = ParamsHelper.GetParams(HttpContextProvider.GetCurrent()); + WritePropertyAndValue(output, "requestParam", contentFromBody); + output.Write(","); + } + // 输出请求方法类型 + if (!string.IsNullOrEmpty(HttpContextProvider.GetCurrent().Request.Method)) + { + WritePropertyAndValue(output, "method", HttpContextProvider.GetCurrent().Request.Method); + output.Write(","); + } + } + // 输出请求时间戳 + WritePropertyAndValue(output, "timestamp", logEvent.Timestamp.ToString("o")); + output.Write(","); + + // 输出日志级别 + WritePropertyAndValue(output, "level", logEvent.Level.ToString()); + output.Write(","); + + // 输出log内容 + WritePropertyAndValue(output, "executeResult", logEvent.MessageTemplate.Render(logEvent.Properties)); + + if (logEvent.Exception != null) + { + output.Write(","); + WritePropertyAndValue(output, "exception", logEvent.Exception.ToString()); + } + + WriteProperties(logEvent.Properties, output); + + output.Write('}'); + } + + private static void WritePropertyAndValue(TextWriter output, string propertyKey, string propertyValue) + { + JsonValueFormatter.WriteQuotedJsonString(propertyKey, output); + output.Write(":"); + JsonValueFormatter.WriteQuotedJsonString(propertyValue, output); + } + + private static void WriteProperties(IReadOnlyDictionary properties, TextWriter output) + { + if (properties.Any()) output.Write(","); + + var precedingDelimiter = ""; + foreach (var property in properties) + { + output.Write(precedingDelimiter); + precedingDelimiter = ","; + + var camelCasePropertyKey = property.Key[0].ToString().ToLower() + property.Key.Substring(1); + JsonValueFormatter.WriteQuotedJsonString(camelCasePropertyKey, output); + output.Write(':'); + ValueFormatter.Format(property.Value, output); + } + } + } +} \ No newline at end of file diff --git a/Blog.Core.Serilog.Es/HttpInfo/HttpContextProvider.cs b/Blog.Core.Serilog.Es/HttpInfo/HttpContextProvider.cs new file mode 100644 index 00000000..612f20b5 --- /dev/null +++ b/Blog.Core.Serilog.Es/HttpInfo/HttpContextProvider.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Http; + +namespace Blog.Core.Serilog.Es.HttpInfo +{ + public static class HttpContextProvider + { + private static IHttpContextAccessor _accessor; + + public static HttpContext GetCurrent() + { + var context = _accessor?.HttpContext; + return context; + } + public static void ConfigureAccessor(IHttpContextAccessor accessor) + { + _accessor = accessor; + } + } + +} diff --git a/Blog.Core.Serilog.Es/HttpInfo/ParamsHelper.cs b/Blog.Core.Serilog.Es/HttpInfo/ParamsHelper.cs new file mode 100644 index 00000000..fbbd9e39 --- /dev/null +++ b/Blog.Core.Serilog.Es/HttpInfo/ParamsHelper.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Text; +using System.Web; + +namespace Blog.Core.Serilog.Es.HttpInfo +{ + /// + /// 获取参数帮助类 + /// + public class ParamsHelper + { + /// + /// 获取参数值 + /// + /// + /// + public static string GetParams(HttpContext context) + { + try + { + NameValueCollection form = HttpUtility.ParseQueryString(context.Request.QueryString.ToString()); + HttpRequest request = context.Request; + + string data = string.Empty; + switch (request.Method) + { + case "POST": + + request.Body.Position = 0; + using (var ms = new MemoryStream()) + { + request.Body.CopyTo(ms); + var b = ms.ToArray(); + data = Encoding.UTF8.GetString(b); //把body赋值给bodyStr + + } + break; + case "GET": + //第一步:取出所有get参数 + IDictionary parameters = new Dictionary(); + for (int f = 0; f < form.Count; f++) + { + string key = form.Keys[f]; + parameters.Add(key, form[key]); + } + + // 第二步:把字典按Key的字母顺序排序 + IDictionary sortedParams = new SortedDictionary(parameters); + IEnumerator> dem = sortedParams.GetEnumerator(); + + // 第三步:把所有参数名和参数值串在一起 + StringBuilder query = new StringBuilder(); + while (dem.MoveNext()) + { + string key = dem.Current.Key; + string value = dem.Current.Value; + if (!string.IsNullOrEmpty(key)) + { + query.Append(key).Append("=").Append(value).Append("&"); + } + } + data = query.ToString().TrimEnd('&'); + break; + default: + data = string.Empty; + + break; + } + return data; + } + catch(Exception) + { + return string.Empty; + } + } + + } +} diff --git a/Blog.Core.Serilog.Es/NetworkLoggerConfigurationExtensions.cs b/Blog.Core.Serilog.Es/NetworkLoggerConfigurationExtensions.cs new file mode 100644 index 00000000..d77f80f2 --- /dev/null +++ b/Blog.Core.Serilog.Es/NetworkLoggerConfigurationExtensions.cs @@ -0,0 +1,102 @@ +using System; +using System.Linq; +using System.Net; +using Serilog; +using Serilog.Configuration; +using Serilog.Debugging; +using Serilog.Events; +using Serilog.Formatting; +using Blog.Core.Serilog.Es.Formatters; +using Blog.Core.Serilog.Es.Sinks.TCP; + +namespace Blog.Core.Serilog.Es +{ + /// + /// Extends Serilog configuration to write events to the network. + /// + public static class NetworkLoggerConfigurationExtensions + { + private static string TcpAddressHost = ""; + private static int TcpAddressProt = 0; + /// + /// 获得tcpAddress + /// + private static void GetTcpAddress() + { + // 读取相关配置 + var logConfigRootDTOInfo = JsonConfigUtils.GetAppSettings(AppSettingsFileNameConfig.AppSettingsFileName, "LogFiedOutPutConfigs"); + if (logConfigRootDTOInfo == null) + { + return; + } + TcpAddressHost = logConfigRootDTOInfo.tcpAddressHost; + TcpAddressProt = logConfigRootDTOInfo.tcpAddressPort; + } + + public static LoggerConfiguration TCPSink( + this LoggerSinkConfiguration loggerConfiguration, + ITextFormatter textFormatter = null, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum) + { + GetTcpAddress(); + if (!string.IsNullOrEmpty(TcpAddressHost)) + { + var sink = new TCPSink(BuildUri($"tcp://{TcpAddressHost}:{TcpAddressProt}"), textFormatter ?? new LogstashJsonFormatter()); + return loggerConfiguration.Sink(sink, restrictedToMinimumLevel); + } + else { + return new LoggerConfiguration(); + } + } + + private static IPAddress ResolveAddress(string uri) + { + // Check if it is IP address + IPAddress address; + + if (IPAddress.TryParse(uri, out address)) + return address; + + address = ResolveIP(uri); + if (address != null) + return address; + + SelfLog.WriteLine("Unable to determine the destination IP-Address"); + return IPAddress.Loopback; + } + + private static IPAddress ResolveIP(string uri) + { + try + { + var ipHostEntry = Dns.GetHostEntryAsync(uri).Result; + if (!ipHostEntry.AddressList.Any()) + return null; + return ipHostEntry.AddressList.First(); + } + catch (Exception) + { + SelfLog.WriteLine("Could not resolve " + uri); + return null; + } + } + + private static Uri BuildUri(string s) + { + Uri uri; + try + { + uri = new Uri(s); + } + catch (UriFormatException ex) + { + throw new ArgumentNullException("Uri should be in the format tcp://server:port", ex); + } + if (uri.Port == 0) + throw new UriFormatException("Uri port cannot be 0"); + if (!(uri.Scheme.ToLower() == "tcp" || uri.Scheme.ToLower() == "tls")) + throw new UriFormatException("Uri scheme must be tcp or tls"); + return uri; + } + } +} \ No newline at end of file diff --git a/Blog.Core.Serilog.Es/Sinks/TCP/TCPSink.cs b/Blog.Core.Serilog.Es/Sinks/TCP/TCPSink.cs new file mode 100644 index 00000000..fc5a43c9 --- /dev/null +++ b/Blog.Core.Serilog.Es/Sinks/TCP/TCPSink.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using System.Net; +using System.Text; +using Serilog.Core; +using Serilog.Events; +using Serilog.Formatting; + +namespace Blog.Core.Serilog.Es.Sinks.TCP +{ + public class TCPSink : ILogEventSink, IDisposable + { + private readonly ITextFormatter _formatter; + private readonly TcpSocketWriter _socketWriter; + + public TCPSink(IPAddress ipAddress, int port, ITextFormatter formatter) + { + _socketWriter = new TcpSocketWriter(new Uri($"tcp://{ipAddress}:{port}")); + _formatter = formatter; + } + + public TCPSink(Uri uri, ITextFormatter formatter) + { + _socketWriter = new TcpSocketWriter(uri); + _formatter = formatter; + } + + public void Emit(LogEvent logEvent) + { + var sb = new StringBuilder(); + + using (var sw = new StringWriter(sb)) + _formatter.Format(logEvent, sw); + + sb.Replace("RenderedMessage", "message"); + _socketWriter.Enqueue(sb.ToString()); + } + + public void Dispose() + { + _socketWriter.Dispose(); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Serilog.Es/Sinks/TCP/TCPSocketWriter.cs b/Blog.Core.Serilog.Es/Sinks/TCP/TCPSocketWriter.cs new file mode 100644 index 00000000..f1f592c9 --- /dev/null +++ b/Blog.Core.Serilog.Es/Sinks/TCP/TCPSocketWriter.cs @@ -0,0 +1,325 @@ +/* + * Copyright 2014 Splunk, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"): you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +using Serilog; +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.Serilog.Es.Sinks.TCP +{ + /// + /// TcpSocketWriter encapsulates queueing strings to be written to a TCP _socket + /// and handling reconnections (according to a TcpConnectionPolicy object passed + /// to it) when a TCP session drops. + /// + /// + /// TcpSocketWriter maintains a fixed sized queue of strings to be sent via + /// the TCP _port and, while the _socket is open, sends them as quickly as possible. + /// + /// If the TCP session drops, TcpSocketWriter will stop pulling strings off the + /// queue until it can reestablish a connection. Any SocketErrors emitted during this + /// process will be passed as arguments to invocations of LoggingFailureHandler. + /// If the TcpConnectionPolicy.Connect method throws an exception (in particular, + /// TcpReconnectFailure to indicate that the policy has reached a point where it + /// will no longer try to establish a connection) then the LoggingFailureHandler + /// event is invoked, and no further attempt to log anything will be made. + /// + public class TcpSocketWriter : IDisposable + { + private readonly FixedSizeQueue _eventQueue; + private readonly ExponentialBackoffTcpReconnectionPolicy _reconnectPolicy = new ExponentialBackoffTcpReconnectionPolicy(); + private readonly CancellationTokenSource _tokenSource; // Must be private or Dispose will not function properly. + private readonly TaskCompletionSource _disposed = new TaskCompletionSource(); + + private Stream _stream; + + /// + /// Event that is invoked when reconnecting after a TCP session is dropped fails. + /// + public event Action LoggingFailureHandler = ex => + { + UnexpectedErrorLogger( + ex, + (x, socketError) => + { + if (socketError == null) + { + //Log.Error(x, "failure inside TCP socket: {message}", x.Message); + } + else + { + //Log.Error( + // x, + // "failure inside TCP socket: {message} - socket error found {socketErrorCode}", + // x.Message, + // socketError); + } + + }); + }; + + public static void UnexpectedErrorLogger(Exception ex, Action log) + { + SocketError? socketErrorCode = null; + var current = ex; + do + { + if (current is SocketException) + { + socketErrorCode = ((SocketException) current).SocketErrorCode; + } + + current = current.InnerException; + } while (socketErrorCode == null && current != null); + + log(ex, socketErrorCode); + } + + /// + /// Construct a TCP _socket writer that writes to the given endPoint and _port. + /// + /// Uri to open a TCP socket to. + /// The maximum number of log entries to queue before starting to drop entries. + public TcpSocketWriter(Uri uri, int maxQueueSize = 5000) + { + _eventQueue = new FixedSizeQueue(maxQueueSize); + _tokenSource = new CancellationTokenSource(); + + Func> tryOpenSocket = async h => + { + try + { + TcpClient client = new TcpClient(); + await client.ConnectAsync(uri.Host, uri.Port); + Stream stream = client.GetStream(); + if (uri.Scheme.ToLower() != "tls") + return stream; + + var sslStream = new SslStream(client.GetStream(), false, null, null); + await sslStream.AuthenticateAsClientAsync(uri.Host); + return sslStream; + } + catch (Exception e) + { + LoggingFailureHandler(e); + throw; + } + }; + + var threadReady = new TaskCompletionSource(); + + Task queueListener = Task.Factory.StartNew(async () => + { + try + { + bool sslEnabled = uri.Scheme.ToLower() == "tls"; + _stream = await _reconnectPolicy.ConnectAsync(tryOpenSocket, uri, _tokenSource.Token); + threadReady.SetResult(true); // Signal the calling thread that we are ready. + + string entry = null; + while (_stream != null) // null indicates that the thread has been cancelled and cleaned up. + { + if (_tokenSource.Token.IsCancellationRequested) + { + _eventQueue.CompleteAdding(); + // Post-condition: no further items will be added to the queue, so there will be a finite number of items to handle. + while (_eventQueue.Count > 0) + { + entry = _eventQueue.Dequeue(); + try + { + byte[] messsage = Encoding.UTF8.GetBytes(entry); + await _stream.WriteAsync(messsage, 0, messsage.Length); + await _stream.FlushAsync(); + } + catch (SocketException ex) + { + LoggingFailureHandler(ex); + } + } + break; + } + if (entry == null) + { + entry = _eventQueue.Dequeue(_tokenSource.Token); + } + else + { + try + { + byte[] messsage = Encoding.UTF8.GetBytes(entry); + await _stream.WriteAsync(messsage, 0, messsage.Length); + await _stream.FlushAsync(); + // No exception, it was sent + entry = null; + } + catch (IOException ex) + { + LoggingFailureHandler(ex); + _stream = await _reconnectPolicy.ConnectAsync(tryOpenSocket, uri, _tokenSource.Token); + } + catch (SocketException ex) + { + LoggingFailureHandler(ex); + _stream = await _reconnectPolicy.ConnectAsync(tryOpenSocket, uri, _tokenSource.Token); + } + } + } + } + catch (Exception e) + { + LoggingFailureHandler(e); + } + finally + { + if (_stream != null) + { + _stream.Dispose(); + } + + _disposed.SetResult(true); + } + }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + threadReady.Task.Wait(TimeSpan.FromSeconds(5)); + } + + public void Dispose() + { + // The following operations are idempotent. Issue a cancellation to tell the + // writer thread to stop the queue from accepting entries and write what it has + // before cleaning up, then wait until that cleanup is finished. + _tokenSource.Cancel(); + Task.Run(async () => await _disposed.Task).Wait(); + } + + /// + /// Push a string onto the queue to be written. + /// + /// The string to be written to the TCP _socket. + public void Enqueue(string entry) + { + _eventQueue.Enqueue(entry); + } + } + + /// + /// TcpConnectionPolicy implementation that tries to reconnect after + /// increasingly long intervals. + /// + /// + /// The intervals double every time, starting from 0s, 1s, 2s, 4s, ... + /// until 10 minutes between connections, when it plateaus and does + /// not increase the interval length any further. + /// + public class ExponentialBackoffTcpReconnectionPolicy + { + private readonly int ceiling = 10 * 60; // 10 minutes in seconds + + public async Task ConnectAsync(Func> connect, Uri host, CancellationToken cancellationToken) + { + int delay = 1; // in seconds + while (!cancellationToken.IsCancellationRequested) + { + try + { + //Log.Debug("Attempting to connect to TCP endpoint {host} after delay of {delay} seconds", host, delay); + return await connect(host); + } + catch (SocketException) { } + + // If this is cancelled via the cancellationToken instead of + // completing its delay, the next while-loop test will fail, + // the loop will terminate, and the method will return null + // with no additional connection attempts. + await Task.Delay(delay * 1000, cancellationToken); + // The nth delay is min(10 minutes, 2^n - 1 seconds). + delay = Math.Min((delay + 1) * 2 - 1, ceiling); + } + + // cancellationToken has been cancelled. + return null; + } + } + + /// + /// A queue with a maximum size. When the queue is at its maximum size + /// and a new item is queued, the oldest item in the queue is dropped. + /// + /// + internal class FixedSizeQueue + { + private int Size { get; } + private readonly IProgress _progress = new Progress(); + private bool IsCompleted { get; set; } + + private readonly BlockingCollection _collection = new BlockingCollection(); + + public FixedSizeQueue(int size) + { + Size = size; + IsCompleted = false; + } + + public void Enqueue(T obj) + { + lock (this) + { + if (IsCompleted) + { + throw new InvalidOperationException("Tried to add an item to a completed queue."); + } + + _collection.Add(obj); + + while (_collection.Count > Size) + { + _collection.Take(); + } + _progress.Report(true); + } + } + + public void CompleteAdding() + { + lock (this) + { + IsCompleted = true; + } + } + + public T Dequeue(CancellationToken cancellationToken) + { + return _collection.Take(cancellationToken); + } + + public T Dequeue() + { + return _collection.Take(); + } + + + public decimal Count => _collection.Count; + } +} \ No newline at end of file diff --git a/Blog.Core.Serilog.Es/Sinks/UDP/UDPSink.cs b/Blog.Core.Serilog.Es/Sinks/UDP/UDPSink.cs new file mode 100644 index 00000000..f07c24e1 --- /dev/null +++ b/Blog.Core.Serilog.Es/Sinks/UDP/UDPSink.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using Serilog.Core; +using Serilog.Events; +using Serilog.Formatting; + + +namespace Serilog.Sinks.Network.Sinks.UDP +{ + public class UDPSink : ILogEventSink, IDisposable + { + private Socket _socket = new Socket(SocketType.Dgram, ProtocolType.Udp); + private readonly ITextFormatter _formatter; + + public UDPSink(IPAddress ipAddress, int port, ITextFormatter formatter) + { + _socket.Connect(ipAddress, port); + _formatter = formatter; + } + + public void Emit(LogEvent logEvent) + { + var sb = new StringBuilder(); + + using (var sw = new StringWriter(sb)) + _formatter.Format(logEvent, sw); + + sb.Replace("RenderedMessage", "message"); + + _socket.Send(Encoding.UTF8.GetBytes(sb.ToString())); + } + + public void Dispose() + { + _socket?.Dispose(); + _socket = null; + } + } +} 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 daf02aad..348048e8 100644 --- a/Blog.Core.Services/AdvertisementServices.cs +++ b/Blog.Core.Services/AdvertisementServices.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; using Blog.Core.Model.Models; using Blog.Core.Services.BASE; @@ -13,20 +7,13 @@ namespace Blog.Core.Services { public class AdvertisementServices : BaseServices, IAdvertisementServices { - IAdvertisementRepository _dal; - public AdvertisementServices(IAdvertisementRepository 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 82ecd8a0..44042382 100644 --- a/Blog.Core.Services/BASE/BaseServices.cs +++ b/Blog.Core.Services/BASE/BaseServices.cs @@ -1,22 +1,33 @@ -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; using System; using System.Collections.Generic; +using System.Data; using System.Linq.Expressions; -using System.Text; using System.Threading.Tasks; 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;//通过在子类的构造函数中注入,这里是基类,不用构造函数 + 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 @@ -45,11 +56,21 @@ public async Task> QueryByIDs(object[] lstIds) /// /// 博文实体类 /// - public async Task Add(TEntity entity) + public async Task Add(TEntity entity) { return await BaseDal.Add(entity); } + /// + /// 批量插入实体(速度快) + /// + /// 实体集合 + /// 影响行数 + public async Task> Add(List listEntity) + { + return await BaseDal.Add(listEntity); + } + /// /// 更新实体数据 /// @@ -59,19 +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); + } + + public async Task Update(TEntity entity, string where) + { + return await BaseDal.Update(entity, where); + } + + public async Task Update(object operateAnonymousObjects) { - return await BaseDal.Update(entity, strWhere); + 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); } @@ -106,7 +142,6 @@ public async Task DeleteByIds(object[] ids) } - /// /// 功能描述:查询所有数据 /// 作  者:AZLinli.Blog.Core @@ -121,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); } /// @@ -138,6 +173,33 @@ public async Task> Query(Expression> whereExpr { return await BaseDal.Query(whereExpression); } + + /// + /// 功能描述:按照特定列查询数据列表 + /// 作  者:Blog.Core + /// + /// + /// + /// + public async Task> Query(Expression> expression) + { + return await BaseDal.Query(expression); + } + + /// + /// 功能描述:按照特定列查询数据列表带条件排序 + /// 作  者:Blog.Core + /// + /// + /// 过滤条件 + /// 查询实体条件 + /// 排序条件 + /// + public async Task> Query(Expression> expression, Expression> whereExpression, string orderByFileds) + { + return await BaseDal.Query(expression, whereExpression, orderByFileds); + } + /// /// 功能描述:查询一个列表 /// 作  者:AZLinli.Blog.Core @@ -150,21 +212,43 @@ 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语句 + /// 参数 + /// 泛型集合 + public async Task> QuerySql(string sql, SugarParameter[] parameters = null) + { + return await BaseDal.QuerySql(sql, parameters); + } + + /// + /// 根据sql语句查询 + /// + /// 完整的sql语句 + /// 参数 + /// DataTable + public async Task QueryTable(string sql, SugarParameter[] parameters = null) + { + return await BaseDal.QueryTable(sql, parameters); } /// @@ -172,28 +256,28 @@ public async Task> Query(string strWhere, string strOrderByFileds) /// 作  者: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); } /// @@ -201,54 +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 = 0, int intPageSize = 20, string strOrderByFileds = null) + public async Task> QueryPage(Expression> whereExpression, + int pageIndex = 1, int pageSize = 20, string orderByFileds = null) { return await BaseDal.QueryPage(whereExpression, - intPageIndex = 0, 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 deab2601..215dde29 100644 --- a/Blog.Core.Services/Blog.Core.Services.csproj +++ b/Blog.Core.Services/Blog.Core.Services.csproj @@ -1,22 +1,18 @@ - + - - netcoreapp2.2 - - - ..\Blog.Core\bin\Debug\ - + + ..\Blog.Core.Api\bin\Debug\ + + + + ..\Blog.Core\bin\Release\ + + - - - - - - - - - - + + + + diff --git a/Blog.Core.Services/BlogArticleServices.cs b/Blog.Core.Services/BlogArticleServices.cs index 22c7f5d8..67ea2b9e 100644 --- a/Blog.Core.Services/BlogArticleServices.cs +++ b/Blog.Core.Services/BlogArticleServices.cs @@ -1,26 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; +using AutoMapper; using Blog.Core.Common; -using Blog.Core.IRepository; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; using Blog.Core.Model.Models; using Blog.Core.Model.ViewModels; using Blog.Core.Services.BASE; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace Blog.Core.Services { public class BlogArticleServices : BaseServices, IBlogArticleServices { - IBlogArticleRepository _dal; IMapper _mapper; - public BlogArticleServices(IBlogArticleRepository dal, IMapper mapper) + public BlogArticleServices(IMapper mapper) { - this._dal = dal; - base.BaseDal = dal; this._mapper = mapper; } /// @@ -28,47 +23,32 @@ public BlogArticleServices(IBlogArticleRepository dal, IMapper mapper) /// /// /// - public async Task GetBlogDetails(int id) + public async Task GetBlogDetails(long id) { - var bloglist = await base.Query(a => a.bID > 0, a => a.bID); - var blogArticle = (await base.Query(a => a.bID == id)).FirstOrDefault(); + // 此处想获取上一条下一条数据,因此将全部数据list出来,有好的想法请提出 + //var bloglist = await base.Query(a => a.IsDeleted==false, a => a.bID); + var blogArticle = (await base.Query(a => a.bID == id && a.bcategory == "技术博文")).FirstOrDefault(); BlogViewModels models = null; if (blogArticle != null) { - BlogArticle prevblog; - BlogArticle nextblog; + models = _mapper.Map(blogArticle); - - int blogIndex = bloglist.FindIndex(item => item.bID == id); - if (blogIndex >= 0) + //要取下一篇和上一篇,以当前id开始,按id排序后top(2),而不用取出所有记录 + //这样在记录很多的时候也不会有多大影响 + var nextBlogs = await base.Query(a => a.bID >= id && a.IsDeleted == false && a.bcategory == "技术博文", 2, "bID"); + if (nextBlogs.Count == 2) { - try - { - prevblog = blogIndex > 0 ? (((BlogArticle)(bloglist[blogIndex - 1]))) : null; - nextblog = blogIndex + 1 < bloglist.Count() ? (BlogArticle)(bloglist[blogIndex + 1]) : null; - - - // 注意就是这里,mapper - models = _mapper.Map(blogArticle); - - if (nextblog != null) - { - models.next = nextblog.btitle; - models.nextID = nextblog.bID; - } - - if (prevblog != null) - { - models.previous = prevblog.btitle; - models.previousID = prevblog.bID; - } - - } - catch (Exception) { } + models.next = nextBlogs[1].btitle; + models.nextID = nextBlogs[1].bID; + } + var prevBlogs = await base.Query(a => a.bID <= id && a.IsDeleted == false && a.bcategory == "技术博文", 2, "bID desc"); + if (prevBlogs.Count == 2) + { + models.previous = prevBlogs[1].btitle; + models.previousID = prevBlogs[1].bID; } - blogArticle.btraffic += 1; await base.Update(blogArticle, new List { "btraffic" }); @@ -82,7 +62,6 @@ public async Task GetBlogDetails(int id) /// /// 获取博客列表 /// - /// /// [Caching(AbsoluteExpiration = 10)] public async Task> GetBlogs() 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/GuestbookServices.cs b/Blog.Core.Services/GuestbookServices.cs index 415ee4ac..33364a51 100644 --- a/Blog.Core.Services/GuestbookServices.cs +++ b/Blog.Core.Services/GuestbookServices.cs @@ -1,22 +1,280 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.IRepository; +using Blog.Core.Common; +using Blog.Core.IRepository.Base; 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 { - IGuestbookRepository _dal; - public GuestbookServices(IGuestbookRepository dal) + private readonly IUnitOfWorkManage _unitOfWorkManage; + private readonly IBaseRepository _passwordLibRepository; + private readonly IPasswordLibServices _passwordLibServices; + private readonly ISqlSugarClient _db; + private SqlSugarScope db => _db as SqlSugarScope; + + public GuestbookServices(IUnitOfWorkManage unitOfWorkManage, IBaseRepository dal, + IBaseRepository passwordLibRepository, IPasswordLibServices passwordLibServices, ISqlSugarClient db) + { + _unitOfWorkManage = unitOfWorkManage; + _passwordLibRepository = passwordLibRepository; + _passwordLibServices = passwordLibServices; + _db = db; + } + + public async Task> TestTranInRepository() + { + try + { + Console.WriteLine($""); + Console.WriteLine($"事务操作开始"); + using (var uow = _unitOfWorkManage.CreateUnitOfWork()) + { + 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}"); + + //...... + + Console.WriteLine($""); + var guestbooks = await BaseDal.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($"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}"); + + uow.Commit(); + } + + return new MessageModel() + { + success = true, + msg = "操作完成" + }; + } + catch (Exception) + { + var passwords = await _passwordLibRepository.Query(); + Console.WriteLine($"third time : the count of passwords is :{passwords.Count}"); + + var guestbooks = await BaseDal.Query(); + Console.WriteLine($"third time : the count of guestbooks is :{guestbooks.Count}"); + + return new MessageModel() + { + success = false, + msg = "操作异常" + }; + } + } + + [UseTran] + public async Task TestTranInRepositoryAOP() + { + var passwords = await _passwordLibRepository.Query(); + Console.WriteLine($"first time : the count of passwords is :{passwords.Count}"); + + + Console.WriteLine($"insert a data into the table PasswordLib now."); + var insertPassword = await _passwordLibRepository.Add(new PasswordLib() + { + IsDeleted = false, + plAccountName = "aaa", + plCreateTime = DateTime.Now + }); + + + passwords = await _passwordLibRepository.Query(d => d.IsDeleted == false); + Console.WriteLine($"second time : the count of passwords is :{passwords.Count}"); + + //...... + + Console.WriteLine($""); + var guestbooks = await BaseDal.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($"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}"); + + return true; + } + + /// + /// 测试使用同事务 + /// + /// + [UseTran(Propagation = Propagation.Required)] + public async Task TestTranPropagation() { - this._dal = dal; - base.BaseDal = dal; + 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 new file mode 100644 index 00000000..bfaf5dfc --- /dev/null +++ b/Blog.Core.Services/IDS4Db/ApplicationUserServices.cs @@ -0,0 +1,18 @@ +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; + +namespace Blog.Core.IServices +{ + public class ApplicationUserServices : BaseServices, IApplicationUserServices + { + public bool IsEnable() + { + var configId = typeof(ApplicationUser).GetEntityTenant(); + return Db.AsTenant().IsAnyConnection(configId); + } + } +} \ No newline at end of file diff --git a/Blog.Core.Services/ModulePermissionServices.cs b/Blog.Core.Services/ModulePermissionServices.cs deleted file mode 100644 index 3d687eb5..00000000 --- a/Blog.Core.Services/ModulePermissionServices.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Blog.Core.Services.BASE; -using Blog.Core.Model.Models; -using Blog.Core.IRepository; -using Blog.Core.IServices; - -namespace Blog.Core.Services -{ - /// - /// ModulePermissionServices - /// - public class ModulePermissionServices : BaseServices, IModulePermissionServices - { - - IModulePermissionRepository _dal; - public ModulePermissionServices(IModulePermissionRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - - } -} diff --git a/Blog.Core.Services/ModuleServices.cs b/Blog.Core.Services/ModuleServices.cs index 8096fd27..c27a63e8 100644 --- a/Blog.Core.Services/ModuleServices.cs +++ b/Blog.Core.Services/ModuleServices.cs @@ -1,22 +1,15 @@ -using Blog.Core.Services.BASE; -using Blog.Core.Model.Models; -using Blog.Core.IRepository; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; namespace Blog.Core.Services -{ - /// - /// ModuleServices - /// - public class ModuleServices : BaseServices, IModuleServices +{ + /// + /// ModuleServices + /// + public class ModuleServices : BaseServices, IModuleServices { - - IModuleRepository _dal; - public ModuleServices(IModuleRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - + } } diff --git a/Blog.Core.Services/OperateLogServices.cs b/Blog.Core.Services/OperateLogServices.cs new file mode 100644 index 00000000..6527ba95 --- /dev/null +++ b/Blog.Core.Services/OperateLogServices.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 OperateLogServices : BaseServices, IOperateLogServices + { + + } +} diff --git a/Blog.Core.Services/PasswordLibServices.cs b/Blog.Core.Services/PasswordLibServices.cs index 7534ee8b..c2f39391 100644 --- a/Blog.Core.Services/PasswordLibServices.cs +++ b/Blog.Core.Services/PasswordLibServices.cs @@ -1,23 +1,90 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; -using Blog.Core.IRepository; +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 { - IPasswordLibRepository _dal; - public PasswordLibServices(IPasswordLibRepository dal) + 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 new file mode 100644 index 00000000..b257d088 --- /dev/null +++ b/Blog.Core.Services/PayServices.cs @@ -0,0 +1,415 @@ +using Blog.Core.Common; +using Blog.Core.Common.Helper; +using Blog.Core.Common.Static; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model; +using Blog.Core.Model.ViewModels; +using Blog.Core.Services.BASE; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Blog.Core.Services +{ + public partial class PayServices : BaseServices>, IPayServices + { + IHttpContextAccessor _httpContextAccessor; + ILogger _logger; + public PayServices(ILogger logger, IHttpContextAccessor httpContextAccessor) + { + _logger = logger; + _httpContextAccessor = httpContextAccessor; + } + + public async Task> Pay(PayNeedModel payModel) + { + _logger.LogInformation("支付开始"); + MessageModel messageModel = new MessageModel(); + messageModel.response = new PayReturnResultModel(); + string url = string.Empty; + string param = string.Empty; + string returnData = string.Empty; + try + { + + _logger.LogInformation($"原始GET参数->{_httpContextAccessor.HttpContext.Request.QueryString}"); + //被扫支付 + string host = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY?"; + ////商户信息 + //string merInfo = "MERCHANTID=105910100190000&POSID=000000000&BRANCHID=610000000"; + ////获取柜台完整公钥 + //string pubKey = "30819d300d06092a864886f70d010101050003818b0030818702818100a32fb2d51dda418f65ca456431bd2f4173e41a82bb75c2338a6f649f8e9216204838d42e2a028c79cee19144a72b5b46fe6a498367bf4143f959e4f73c9c4f499f68831f8663d6b946ae9fa31c74c9332bebf3cba1a98481533a37ffad944823bd46c305ec560648f1b6bcc64d54d32e213926b26cd10d342f2c61ff5ac2d78b020111"; + ////加密原串 + //string param = merInfo + "&MERFLAG=1&TERMNO1=&TERMNO2=&ORDERID=937857156" + + // "&QRCODE=134737690209713400&AMOUNT=0.01&TXCODE=PAY100&PROINFO=&REMARK1=&REMARK2=&SMERID=&SMERNAME=&SMERTYPEID=" + + // "&SMERTYPE=&TRADECODE=&TRADENAME=&SMEPROTYPE=&PRONAME="; + + Dictionary dic = new Dictionary(); + + //支付信息 + dic.Add("MERCHANTID", StaticPayInfo.MERCHANTID);// => self::MERCHANTID, // 商户号 + dic.Add("POSID", StaticPayInfo.POSID);// => self::POSID, // 柜台号 + dic.Add("BRANCHID", StaticPayInfo.BRANCHID);// => self::BRANCHID, // 分行号 + dic.Add("TXCODE", "PAY100");// => 'PAY100', // 交易码 + dic.Add("MERFLAG", "1");// => '', // 商户类型 1线上 2线下 + dic.Add("ORDERID", payModel.ORDERID);//payModel.ORDERID);// => '', // 订单号 + dic.Add("QRCODE", payModel.QRCODE);// => '', // 码信息(一维码、二维码) + dic.Add("AMOUNT", payModel.AMOUNT);// => '0.01', // 订单金额,单位:元 + dic.Add("PROINFO", payModel.PROINFO);// => '', // 商品名称 + dic.Add("REMARK1", payModel.REMARK1);// => '', // 备注 1 + dic.Add("REMARK2", payModel.REMARK2);// => '', // 备注 2 + + //dic.Add("TERMNO1", "");// => '', // 终端编号 1 + //dic.Add("TERMNO2", "");// => '', // 终端编号 2 + //dic.Add("GROUPMCH", "");// => '', // 集团商户信息 + //dic.Add("FZINFO1", "");// => '', // 分账信息一 + //dic.Add("FZINFO2", "");// => '', // 分账信息二 + //dic.Add("SUB_APPID", "");// => '', // 子商户公众账号 ID + //dic.Add("RETURN_FIELD", "");// => '', // 返回信息位图 + //dic.Add("USERPARAM", "");// => '', // 实名支付 + //dic.Add("detail", "");// => '', // 商品详情 + //dic.Add("goods_tag", "");// => '', // 订单优惠标记 + + //商户信息 + Dictionary dicInfo = new Dictionary(); + dicInfo.Add("MERCHANTID", StaticPayInfo.MERCHANTID);// => self::MERCHANTID, // 商户号 + dicInfo.Add("POSID", StaticPayInfo.POSID);// => self::POSID, // 柜台号 + dicInfo.Add("BRANCHID", StaticPayInfo.BRANCHID);// => self::BRANCHID, // 分行号 + var Info = StringHelper.GetPars(dicInfo); + + + //获取拼接请求串 + param = StringHelper.GetPars(dic); + + //加密 + var paramEncryption = new CCBPayUtil().makeCCBParam(param, StaticPayInfo.pubKey); + //拼接请求串 + url = host + Info + "&ccbParam=" + paramEncryption; + //请求 + _logger.LogInformation($"请求地址->{url}"); + _logger.LogInformation($"请求参数->{param}"); + PayResultModel payResult; + try + { + returnData = await HttpHelper.PostAsync(url); + //转换数据 + try + { + payResult = JsonHelper.ParseFormByJson(returnData); + } + catch + { + payResult = new PayResultModel { RESULT = "N", ERRMSG = "参数错误", ORDERID = payModel.ORDERID, AMOUNT = payModel.AMOUNT }; + returnData = StringHelper.GetCusLine(returnData, 15); + } + _logger.LogInformation($"响应数据->{returnData}"); + } + catch (Exception ex) + { + _logger.LogInformation($"异常信息:{ex.Message}"); + _logger.LogInformation($"异常堆栈:{ex.StackTrace}"); + messageModel = await PayCheck(payModel, 1); + return messageModel; + } + switch (payResult.RESULT) + { + case "Y": + Dictionary dicCheckPars = new Dictionary(); + dicCheckPars.Add("RESULT", payResult.RESULT); + dicCheckPars.Add("ORDERID", payResult.ORDERID); + dicCheckPars.Add("AMOUNT", payResult.AMOUNT); + dicCheckPars.Add("WAITTIME", payResult.WAITTIME); + dicCheckPars.Add("TRACEID", payResult.TRACEID); + string strCheckPars = StringHelper.GetPars(dicCheckPars); + if (NotifyCheck(strCheckPars, payResult.SIGN, StaticPayInfo.pubKey)) + { + messageModel.success = true; + messageModel.msg = "支付成功"; + } + else + { + messageModel.success = false; + messageModel.msg = "签名失败"; + } + break; + case "N": + messageModel.success = false; + messageModel.msg = "支付失败"; + break; + case "U": + case "Q": + int waittime = payResult.WAITTIME.ObjToInt(); + if (waittime <= 0) waittime = 5;//如果需要等待默认等待5秒后再次查询 + Thread.Sleep(waittime * 1000); + //轮询查询 + messageModel = await PayCheck(payModel, 1); + break; + default: + messageModel.success = false; + messageModel.msg = "支付失败"; + break; + } + messageModel.response.ORDERID = payResult.ORDERID; + messageModel.response.ERRCODE = payResult.ERRCODE; + messageModel.response.ERRMSG = payResult.ERRMSG; + messageModel.response.TRACEID = payResult.TRACEID; + messageModel.response.AMOUNT = payResult.AMOUNT; + messageModel.response.QRCODETYPE = payResult.QRCODETYPE; + } + catch (Exception ex) + { + messageModel.success = false; + messageModel.msg = "服务错误"; + messageModel.response.ERRMSG = ex.Message; + _logger.LogInformation($"异常信息:{ex.Message}"); + _logger.LogInformation($"异常堆栈:{ex.StackTrace}"); + } + finally + { + _logger.LogInformation($"返回数据->{JsonHelper.GetJSON>(messageModel)}"); + _logger.LogInformation("支付结束"); + } + return messageModel; + } + public async Task> PayRefund(PayRefundNeedModel payModel) + { + _logger.LogInformation("退款开始"); + MessageModel messageModel = new MessageModel(); + messageModel.response = new PayRefundReturnResultModel(); + try + { + _logger.LogInformation($"原始GET参数->{_httpContextAccessor.HttpContext.Request.QueryString}"); + + string REQUEST_SN = StringHelper.GetGuidToLongID().ToString().Substring(0, 16);//请求序列码 + string CUST_ID = StaticPayInfo.MERCHANTID;//商户号 + string USER_ID = StaticPayInfo.USER_ID;//操作员号 + string PASSWORD = StaticPayInfo.PASSWORD;//密码 + string TX_CODE = "5W1004";//交易码 + string LANGUAGE = "CN";//语言 + //string SIGN_INFO = "";//签名信息 + //string SIGNCERT = "";//签名CA信息 + //外联平台客户端服务部署的地址+设置的监听端口 + string sUrl = StaticPayInfo.OutAddress; + + //XML请求报文 + //string sRequestMsg = $" requestXml={REQUEST_SN}{CUST_ID}{USER_ID}{PASSWORD}{TX_CODE}{LANGUAGE}{payModel.MONEY}{payModel.ORDER}{payModel.REFUND_CODE} "; + string sRequestMsg = $"{REQUEST_SN}{CUST_ID}{USER_ID}{PASSWORD}{TX_CODE}{LANGUAGE}{payModel.MONEY}{payModel.ORDER}{payModel.REFUND_CODE} "; + + //string sRequestMsg = readRequestFile("E:/02-外联平台/06-测试/测试报文/商户网银/客户端连接-5W1001-W06.txt"); + + + //注意:请求报文必须放在requestXml参数送 + sRequestMsg = "requestXml=" + sRequestMsg; + + _logger.LogInformation("请求地址:" + sUrl); + _logger.LogInformation("请求报文:" + sRequestMsg); + + HttpClient request = new HttpClient(); + byte[] byteRquest = Encoding.GetEncoding("GB18030").GetBytes(sRequestMsg); + ByteArrayContent bytemsg = new ByteArrayContent(byteRquest); + HttpResponseMessage resulthd = await request.PostAsync(sUrl, bytemsg); + Stream result = await resulthd.Content.ReadAsStreamAsync(); + + StreamReader readerResult = new StreamReader(result, System.Text.Encoding.GetEncoding("GB18030")); + string sResult = await readerResult.ReadToEndAsync(); + _logger.LogInformation("响应报文:" + sResult); + var Xmlresult = XmlHelper.ParseFormByXml(sResult, "TX"); + if (Xmlresult.RETURN_CODE.Equals("000000")) + { + messageModel.success = true; + messageModel.msg = "退款成功"; + } + else + { + messageModel.success = false; + messageModel.msg = "退款失败"; + } + messageModel.response.RETURN_MSG = Xmlresult.RETURN_MSG; + messageModel.response.TX_CODE = Xmlresult.TX_CODE; + messageModel.response.REQUEST_SN = Xmlresult.REQUEST_SN; + messageModel.response.RETURN_CODE = Xmlresult.RETURN_CODE; + messageModel.response.CUST_ID = Xmlresult.CUST_ID; + messageModel.response.LANGUAGE = Xmlresult.LANGUAGE; + + 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) + { + messageModel.success = false; + messageModel.msg = "服务错误"; + messageModel.response.RETURN_MSG = ex.Message; + _logger.LogInformation($"异常信息:{ex.Message}"); + _logger.LogInformation($"异常堆栈:{ex.StackTrace}"); + } + finally + { + _logger.LogInformation($"返回数据->{JsonHelper.GetJSON>(messageModel)}"); + _logger.LogInformation("退款结束"); + + } + return messageModel; + + } + public async Task> PayCheck(PayNeedModel payModel, int times) + { + _logger.LogInformation("轮询开始"); + + MessageModel messageModel = new MessageModel(); + messageModel.response = new PayReturnResultModel(); + string url = string.Empty; + string param = string.Empty; + string returnData = string.Empty; + try + { + //设置最大轮询次数,跟建行保持一致 + int theLastTime = 6; + if (times > theLastTime) throw new Exception($"轮询次数超过最大次数{theLastTime}"); + + string host = "https://ibsbjstar.ccb.com.cn/CCBIS/B2CMainPlat_00_BEPAY?"; + + Dictionary dic = new Dictionary(); + + dic.Add("MERCHANTID", StaticPayInfo.MERCHANTID);// => self::MERCHANTID, // 商户号 + dic.Add("POSID", StaticPayInfo.POSID);// => self::POSID, // 柜台号 + dic.Add("BRANCHID", StaticPayInfo.BRANCHID);// => self::BRANCHID, // 分行号 + dic.Add("TXCODE", "PAY101");// => 'PAY100', // 交易码 + dic.Add("QRYTIME", times.ToString());// => '', // 查询此时(每次加1) + dic.Add("MERFLAG", "1");// => '', // 商户类型 + dic.Add("ORDERID", payModel.ORDERID);// => '', // 订单号 + dic.Add("QRCODE", payModel.QRCODE);// => '', // 码信息(一维码、二维码) + + + //dic.Add("GROUPMCH", "");// => '', // 集团商户信息 + //dic.Add("QRCODETYPE", "");// => '', // 支付类型1:龙支付 2:微信 3:支付宝 4:银联 + //dic.Add("TERMNO1", "");// => '', // 终端编号 1 + //dic.Add("TERMNO2", "");// => '', // 终端编号 2 + //dic.Add("AMOUNT", "");// => '0.01', // 订单金额,单位:元 + //dic.Add("PROINFO", "");// => '', // 商品名称 + //dic.Add("REMARK1", "");// => '', // 备注 1 + //dic.Add("REMARK2", "");// => '', // 备注 2 + //dic.Add("FZINFO1", "");// => '', // 分账信息一 + //dic.Add("FZINFO2", "");// => '', // 分账信息二 + //dic.Add("SUB_APPID", "");// => '', // 子商户公众账号 ID + //dic.Add("RETURN_FIELD", "");// => '', // 返回信息位图 + //dic.Add("USERPARAM", "");// => '', // 实名支付 + //dic.Add("detail", "");// => '', // 商品详情 + //dic.Add("goods_tag", "");// => '', // 订单优惠标记 + + //商户信息 + Dictionary dicInfo = new Dictionary(); + dicInfo.Add("MERCHANTID", StaticPayInfo.MERCHANTID);// => self::MERCHANTID, // 商户号 + dicInfo.Add("POSID", StaticPayInfo.POSID);// => self::POSID, // 柜台号 + dicInfo.Add("BRANCHID", StaticPayInfo.BRANCHID);// => self::BRANCHID, // 分行号 + var Info = StringHelper.GetPars(dicInfo); + + //var newDic = dic.OrderBy(t => t.Key).ToDictionary(o => o.Key, p => p.Value); + //参数信息 + param = StringHelper.GetPars(dic); + //加密 + var paramEncryption = new CCBPayUtil().makeCCBParam(param, StaticPayInfo.pubKey); + //拼接请求串 + url = host + Info + "&ccbParam=" + paramEncryption; + //请求 + _logger.LogInformation($"请求地址->{url}"); + _logger.LogInformation($"请求参数->{param}"); + //转换数据 + PayResultModel payResult; + try + { + returnData = await HttpHelper.PostAsync(url); + _logger.LogInformation($"响应数据->{returnData}"); + } + catch (Exception ex) + { + _logger.LogInformation($"异常信息:{ex.Message}"); + _logger.LogInformation($"异常堆栈:{ex.StackTrace}"); + return await PayCheck(payModel, ++times); + } + + + try + { + payResult = JsonHelper.ParseFormByJson(returnData); + } + catch + { + payResult = new PayResultModel { RESULT = "N", ERRMSG = "参数错误", ORDERID = payModel.ORDERID, AMOUNT = payModel.AMOUNT }; + } + + switch (payResult.RESULT) + { + case "Y": + Dictionary dicCheckPars = new Dictionary(); + dicCheckPars.Add("RESULT", payResult.RESULT); + dicCheckPars.Add("ORDERID", payResult.ORDERID); + dicCheckPars.Add("AMOUNT", payResult.AMOUNT); + dicCheckPars.Add("WAITTIME", payResult.WAITTIME); + string strCheckPars = StringHelper.GetPars(dicCheckPars); + if (NotifyCheck(strCheckPars, payResult.SIGN, StaticPayInfo.pubKey)) + { + messageModel.success = true; + messageModel.msg = "支付成功"; + } + else + { + messageModel.success = false; + messageModel.msg = "签名失败"; + } + break; + case "N": + messageModel.success = false; + messageModel.msg = "支付失败"; + break; + case "U": + case "Q": + int waittime = payResult.WAITTIME.ObjToInt(); + if (waittime <= 0) waittime = 5;//如果需要等待默认等待5秒后再次查询 + Thread.Sleep(waittime * 1000); + //改成轮询查询 + messageModel = await PayCheck(payModel, ++times); + break; + default: + messageModel.success = false; + messageModel.msg = "支付失败"; + break; + } + messageModel.response.ORDERID = payResult.ORDERID; + messageModel.response.ERRCODE = payResult.ERRCODE; + messageModel.response.ERRMSG = payResult.ERRMSG; + messageModel.response.TRACEID = payResult.TRACEID; + messageModel.response.AMOUNT = payResult.AMOUNT; + messageModel.response.QRCODETYPE = payResult.QRCODETYPE; + } + catch (Exception ex) + { + messageModel.success = false; + messageModel.msg = "服务错误"; + messageModel.response.ERRMSG = ex.Message; + _logger.LogInformation($"异常信息:{ex.Message}"); + _logger.LogInformation($"异常堆栈:{ex.StackTrace}"); + } + finally + { + _logger.LogInformation($"返回数据->{JsonHelper.GetJSON>(messageModel)}"); + _logger.LogInformation("轮序结束"); + } + return messageModel; + } + + public bool NotifyCheck(string strSrc, string sign, string pubKey) + { + + return new CCBPayUtil().verifyNotifySign(strSrc, sign, pubKey); + } + } +} diff --git a/Blog.Core.Services/PermissionServices.cs b/Blog.Core.Services/PermissionServices.cs index b6db5d6b..6c64954d 100644 --- a/Blog.Core.Services/PermissionServices.cs +++ b/Blog.Core.Services/PermissionServices.cs @@ -1,22 +1,15 @@ -using Blog.Core.Services.BASE; -using Blog.Core.Model.Models; -using Blog.Core.IRepository; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; namespace Blog.Core.Services -{ - /// - /// PermissionServices - /// - public class PermissionServices : BaseServices, IPermissionServices +{ + /// + /// PermissionServices + /// + public class PermissionServices : BaseServices, IPermissionServices { - - IPermissionRepository _dal; - public PermissionServices(IPermissionRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - + } } diff --git a/Blog.Core.Services/RoleModulePermissionServices.cs b/Blog.Core.Services/RoleModulePermissionServices.cs index 6efc4522..d3834f89 100644 --- a/Blog.Core.Services/RoleModulePermissionServices.cs +++ b/Blog.Core.Services/RoleModulePermissionServices.cs @@ -1,10 +1,12 @@ -using Blog.Core.Services.BASE; -using Blog.Core.Model.Models; -using Blog.Core.IServices; +using Blog.Core.Common; using Blog.Core.IRepository; -using System.Threading.Tasks; +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Blog.Core.Services.BASE; using System.Collections.Generic; -using Blog.Core.Common; +using System.Linq; +using System.Threading.Tasks; namespace Blog.Core.Services { @@ -14,16 +16,18 @@ namespace Blog.Core.Services public class RoleModulePermissionServices : BaseServices, IRoleModulePermissionServices { readonly IRoleModulePermissionRepository _dal; - readonly IModuleRepository _moduleRepository; - readonly IRoleRepository _roleRepository; + readonly IBaseRepository _moduleRepository; + readonly IBaseRepository _roleRepository; // 将多个仓储接口注入 - public RoleModulePermissionServices(IRoleModulePermissionRepository dal, IModuleRepository moduleRepository, IRoleRepository roleRepository) + public RoleModulePermissionServices( + IRoleModulePermissionRepository dal, + IBaseRepository moduleRepository, + IBaseRepository roleRepository) { this._dal = dal; this._moduleRepository = moduleRepository; this._roleRepository = roleRepository; - base.BaseDal = dal; } /// @@ -34,21 +38,54 @@ public RoleModulePermissionServices(IRoleModulePermissionRepository dal, IModule public async Task> GetRoleModule() { var roleModulePermissions = await base.Query(a => a.IsDeleted == false); + var roles = await _roleRepository.Query(a => a.IsDeleted == false); + var modules = await _moduleRepository.Query(a => a.IsDeleted == false); + + //var roleModulePermissionsAsync = base.Query(a => a.IsDeleted == false); + //var rolesAsync = _roleRepository.Query(a => a.IsDeleted == false); + //var modulesAsync = _moduleRepository.Query(a => a.IsDeleted == false); + + //var roleModulePermissions = await roleModulePermissionsAsync; + //var roles = await rolesAsync; + //var modules = await modulesAsync; + + if (roleModulePermissions.Count > 0) { foreach (var item in roleModulePermissions) { - item.Role = await _roleRepository.QueryById(item.RoleId); - item.Module = await _moduleRepository.QueryById(item.ModuleId); + item.Role = roles.FirstOrDefault(d => d.Id == item.RoleId); + item.Module = modules.FirstOrDefault(d => d.Id == item.ModuleId); } } return roleModulePermissions; } - public async Task> TestModelWithChildren() + public async Task> QueryMuchTable() + { + return await _dal.QueryMuchTable(); + } + + public async Task> RoleModuleMaps() + { + return await _dal.RoleModuleMaps(); + } + + public async Task> GetRMPMaps() + { + return await _dal.GetRMPMaps(); + } + + /// + /// 批量更新菜单与接口的关系 + /// + /// 菜单主键 + /// 接口主键 + /// + public async Task UpdateModuleId(long permissionId, long moduleId) { - return await _dal.WithChildrenModel(); + await _dal.UpdateModuleId(permissionId, moduleId); } } } diff --git a/Blog.Core.Services/RoleServices.cs b/Blog.Core.Services/RoleServices.cs index 1c27b95c..50861f7c 100644 --- a/Blog.Core.Services/RoleServices.cs +++ b/Blog.Core.Services/RoleServices.cs @@ -1,25 +1,18 @@ +using Blog.Core.Common; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; -using Blog.Core.IRepository; -using Blog.Core.Services.BASE; using Blog.Core.Model.Models; -using System.Threading.Tasks; +using Blog.Core.Services.BASE; using System.Linq; -using Blog.Core.Common; +using System.Threading.Tasks; namespace Blog.Core.Services -{ - /// - /// RoleServices - /// - public class RoleServices : BaseServices, IRoleServices +{ + /// + /// RoleServices + /// + public class RoleServices : BaseServices, IRoleServices { - - IRoleRepository _dal; - public RoleServices(IRoleRepository 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 new file mode 100644 index 00000000..68560a20 --- /dev/null +++ b/Blog.Core.Services/TasksQzServices.cs @@ -0,0 +1,13 @@ +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 TasksQzServices : BaseServices, ITasksQzServices + { + + } +} + \ No newline at end of file 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 5bcf339e..49fb5343 100644 --- a/Blog.Core.Services/TopicDetailServices.cs +++ b/Blog.Core.Services/TopicDetailServices.cs @@ -1,25 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.Common; -using Blog.Core.IRepository; +using Blog.Core.Common; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; using Blog.Core.Model.Models; using Blog.Core.Services.BASE; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Blog.Core.Services { public class TopicDetailServices : BaseServices, ITopicDetailServices { - ITopicDetailRepository _dal; - public TopicDetailServices(ITopicDetailRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - /// /// 获取开Bug数据(缓存) /// diff --git a/Blog.Core.Services/TopicServices.cs b/Blog.Core.Services/TopicServices.cs index 34227573..7d1b3f0a 100644 --- a/Blog.Core.Services/TopicServices.cs +++ b/Blog.Core.Services/TopicServices.cs @@ -1,26 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Blog.Core.Common; -using Blog.Core.IRepository; +using Blog.Core.Common; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; using Blog.Core.Model.Models; using Blog.Core.Services.BASE; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Blog.Core.Services { public class TopicServices: BaseServices, ITopicServices { - - ITopicRepository _dal; - public TopicServices(ITopicRepository dal) - { - this._dal = dal; - base.BaseDal = dal; - } - /// /// 获取开Bug专题分类(缓存) /// diff --git a/Blog.Core.Services/UserRoleServices.cs b/Blog.Core.Services/UserRoleServices.cs index 019d5c1d..26194837 100644 --- a/Blog.Core.Services/UserRoleServices.cs +++ b/Blog.Core.Services/UserRoleServices.cs @@ -1,32 +1,25 @@ +using Blog.Core.Common; +using Blog.Core.IRepository.Base; using Blog.Core.IServices; -using Blog.Core.FrameWork.IRepository; -using Blog.Core.Services.BASE; using Blog.Core.Model.Models; -using System.Threading.Tasks; +using Blog.Core.Services.BASE; using System.Linq; -using Blog.Core.Common; +using System.Threading.Tasks; namespace Blog.Core.Services -{ - /// - /// UserRoleServices - /// - public class UserRoleServices : BaseServices, IUserRoleServices +{ + /// + /// UserRoleServices + /// + public class UserRoleServices : BaseServices, IUserRoleServices { - - IUserRoleRepository _dal; - public UserRoleServices(IUserRoleRepository 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 192f6902..282ae73c 100644 --- a/Blog.Core.Services/sysUserInfoServices.cs +++ b/Blog.Core.Services/sysUserInfoServices.cs @@ -1,27 +1,23 @@ +using Blog.Core.IRepository.Base; +using Blog.Core.IServices; using Blog.Core.Model.Models; using Blog.Core.Services.BASE; -using Blog.Core.IServices; -using Blog.Core.IRepository; -using System.Threading.Tasks; using System.Linq; +using System.Threading.Tasks; namespace Blog.Core.FrameWork.Services { /// /// sysUserInfoServices /// - public class SysUserInfoServices : BaseServices, ISysUserInfoServices + public class SysUserInfoServices : BaseServices, ISysUserInfoServices { - - IsysUserInfoRepository _dal; - IUserRoleServices _userRoleServices; - IRoleRepository _roleRepository; - public SysUserInfoServices(IsysUserInfoRepository dal, IUserRoleServices userRoleServices, IRoleRepository roleRepository) + private readonly IBaseRepository _userRoleRepository; + private readonly IBaseRepository _roleRepository; + public SysUserInfoServices(IBaseRepository userRoleRepository, IBaseRepository roleRepository) { - this._dal = dal; - this._userRoleServices = userRoleServices; - this._roleRepository = roleRepository; - base.BaseDal = dal; + _userRoleRepository = userRoleRepository; + _roleRepository = roleRepository; } /// /// @@ -29,11 +25,11 @@ public SysUserInfoServices(IsysUserInfoRepository dal, IUserRoleServices userRol /// /// /// - 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,13 +53,15 @@ 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 _userRoleServices.Query(ur => ur.UserId == user.uID); + var userRoles = await _userRoleRepository.Query(ur => ur.UserId == user.Id); if (userRoles.Count > 0) { - var roles = await _roleRepository.QueryByIDs(userRoles.Select(ur => ur.RoleId.ObjToString()).ToArray()); + var arr = userRoles.Select(ur => ur.RoleId.ObjToString()).ToList(); + var roles = roleList.Where(d => arr.Contains(d.Id.ObjToString())); roleName = string.Join(',', roles.Select(r => r.Name).ToArray()); } diff --git a/Blog.Core.System.Architecture.png b/Blog.Core.System.Architecture.png new file mode 100644 index 00000000..994a1e28 Binary files /dev/null and b/Blog.Core.System.Architecture.png differ diff --git a/Blog.Core.Tasks/Blog.Core.Tasks.csproj b/Blog.Core.Tasks/Blog.Core.Tasks.csproj new file mode 100644 index 00000000..d93eb8bd --- /dev/null +++ b/Blog.Core.Tasks/Blog.Core.Tasks.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Blog.Core.Tasks/HostedService/Job1TimedService.cs b/Blog.Core.Tasks/HostedService/Job1TimedService.cs new file mode 100644 index 00000000..9777affc --- /dev/null +++ b/Blog.Core.Tasks/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.Tasks +{ + 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.Tasks/HostedService/Job2TimedService.cs b/Blog.Core.Tasks/HostedService/Job2TimedService.cs new file mode 100644 index 00000000..ee7f2c5c --- /dev/null +++ b/Blog.Core.Tasks/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.Tasks +{ + 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.Tasks/QuartzNet/ISchedulerCenter.cs b/Blog.Core.Tasks/QuartzNet/ISchedulerCenter.cs new file mode 100644 index 00000000..c8598540 --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/ISchedulerCenter.cs @@ -0,0 +1,78 @@ +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blog.Core.Tasks +{ + /// + /// 服务调度接口 + /// + public interface ISchedulerCenter + { + + /// + /// 开启任务调度 + /// + /// + Task> StartScheduleAsync(); + /// + /// 停止任务调度 + /// + /// + Task> StopScheduleAsync(); + /// + /// + /// + /// + /// + Task> AddScheduleJobAsync(TasksQz sysSchedule); + /// + /// 停止一个任务 + /// + /// + /// + Task> StopScheduleJobAsync(TasksQz sysSchedule); + /// + /// 检测任务是否存在 + /// + /// + /// + Task IsExistScheduleJobAsync(TasksQz sysSchedule); + /// + /// 暂停指定的计划任务 + /// + /// + /// + Task> PauseJob(TasksQz sysSchedule); + /// + /// 恢复一个任务 + /// + /// + /// + Task> ResumeJob(TasksQz sysSchedule); + + /// + /// 获取任务触发器状态 + /// + /// + /// + Task> GetTaskStaus(TasksQz sysSchedule); + /// + /// 获取触发器标识 + /// + /// + /// + string GetTriggerState(string key); + + /// + /// 立即执行 一个任务 + /// + /// + /// + Task> ExecuteJobAsync(TasksQz tasksQz); + + } + +} diff --git a/Blog.Core.Tasks/QuartzNet/JobFactory.cs b/Blog.Core.Tasks/QuartzNet/JobFactory.cs new file mode 100644 index 00000000..b1318dc9 --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/JobFactory.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.DependencyInjection; +using Quartz; +using Quartz.Spi; +using System; + +namespace Blog.Core.Tasks +{ + public class JobFactory : IJobFactory + { + /// + /// 注入反射获取依赖对象 + /// + private readonly IServiceProvider _serviceProvider; + public JobFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + /// + /// 实现接口Job + /// + /// + /// + /// + public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) + { + try + { + var serviceScope = _serviceProvider.CreateScope(); + var job = serviceScope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob; + return job; + + } + catch (Exception) + { + throw; + } + } + + public void ReturnJob(IJob job) + { + var disposable = job as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + + } + } + +} diff --git a/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs b/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs new file mode 100644 index 00000000..8da86165 --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/Jobs/JobBase.cs @@ -0,0 +1,85 @@ +using Blog.Core.Common.Helper; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Quartz; +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Blog.Core.Tasks +{ + public class JobBase + { + public ITasksQzServices _tasksQzServices; + public ITasksLogServices _tasksLogServices; + public JobBase(ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + { + _tasksQzServices = tasksQzServices; + _tasksLogServices = tasksLogServices; + } + /// + /// 执行指定任务 + /// + /// + /// + public async Task ExecuteJob(IJobExecutionContext context, Func func) + { + //记录Job + TasksLog tasksLog = new TasksLog(); + //JOBID + long jobid = context.JobDetail.Key.Name.ObjToLong(); + //JOB组名 + string groupName = context.JobDetail.Key.Group; + //日志 + tasksLog.JobId = jobid; + tasksLog.RunTime = DateTime.Now; + string jobHistory = $"【{tasksLog.RunTime.ToString("yyyy-MM-dd HH:mm:ss")}】【执行开始】【Id:{jobid},组别:{groupName}】"; + try + { + await func();//执行任务 + 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) + { + tasksLog.EndTime = DateTime.Now; + tasksLog.RunResult = false; + //JobExecutionException e2 = new JobExecutionException(ex); + //true 是立即重新执行任务 + //e2.RefireImmediately = true; + tasksLog.ErrMessage = ex.Message; + tasksLog.ErrStackTrace = ex.StackTrace; + jobHistory += $",【{tasksLog.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}】【执行失败:{ex.Message}】"; + } + finally + { + 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 = + $"{jobHistory}{separator}" + string.Join(separator, StringHelper.GetTopDataBySeparator(model.Remark, separator, 9)); + await _tasksQzServices.Update(model); + } + } + } + + //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 new file mode 100644 index 00000000..6d1276c8 --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_Blogs_Quartz.cs @@ -0,0 +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, ITasksLogServices tasksLogServices, + IGuestbookServices guestbookServices, IUnitOfWorkManage uowm, ISqlSugarClient db) + : base(tasksQzServices, tasksLogServices) + { + _blogArticleServices = blogArticleServices; + _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 new file mode 100644 index 00000000..49e95847 --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs @@ -0,0 +1,85 @@ +using Blog.Core.Common.LogHelper; +using Blog.Core.IServices; +using Blog.Core.Model.Models; +using Microsoft.AspNetCore.Hosting; +using Quartz; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +/// +/// 这里要注意下,命名空间和程序集是一样的,不然反射不到 +/// +namespace Blog.Core.Tasks +{ + public class Job_OperateLog_Quartz : JobBase, IJob + { + private readonly IOperateLogServices _operateLogServices; + private readonly IWebHostEnvironment _environment; + + public Job_OperateLog_Quartz(IOperateLogServices operateLogServices, IWebHostEnvironment environment, + ITasksQzServices tasksQzServices, ITasksLogServices tasksLogServices) + : base(tasksQzServices, tasksLogServices) + { + _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; + // 也可以通过数据库配置,获取传递过来的参数 + JobDataMap data = context.JobDetail.JobDataMap; + + //[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 new file mode 100644 index 00000000..474a2a9a --- /dev/null +++ b/Blog.Core.Tasks/QuartzNet/SchedulerCenterServer.cs @@ -0,0 +1,497 @@ +using Blog.Core.Model; +using Blog.Core.Model.Models; +using Blog.Core.Model.ViewModels; +using Quartz; +using Quartz.Impl; +using Quartz.Impl.Triggers; +using Quartz.Spi; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Reflection; +using System.Threading.Tasks; + +namespace Blog.Core.Tasks +{ + /// + /// 任务调度管理中心 + /// + public class SchedulerCenterServer : ISchedulerCenter + { + private Task _scheduler; + private readonly IJobFactory _iocjobFactory; + public SchedulerCenterServer(IJobFactory jobFactory) + { + _iocjobFactory = jobFactory; + _scheduler = GetSchedulerAsync(); + } + private Task GetSchedulerAsync() + { + if (_scheduler != null) + return this._scheduler; + else + { + // 从Factory中获取Scheduler实例 + NameValueCollection collection = new NameValueCollection + { + { "quartz.serializer.type", "binary" }, + }; + StdSchedulerFactory factory = new StdSchedulerFactory(collection); + return _scheduler = factory.GetScheduler(); + } + } + + /// + /// 开启任务调度 + /// + /// + public async Task> StartScheduleAsync() + { + var result = new MessageModel(); + try + { + this._scheduler.Result.JobFactory = this._iocjobFactory; + if (!this._scheduler.Result.IsStarted) + { + //等待任务运行完成 + await this._scheduler.Result.Start(); + await Console.Out.WriteLineAsync("任务调度开启!"); + result.success = true; + result.msg = $"任务调度开启成功"; + return result; + } + else + { + result.success = false; + result.msg = $"任务调度已经开启"; + return result; + } + } + catch (Exception) + { + throw; + } + } + + /// + /// 停止任务调度 + /// + /// + public async Task> StopScheduleAsync() + { + var result = new MessageModel(); + try + { + if (!this._scheduler.Result.IsShutdown) + { + //等待任务运行完成 + await this._scheduler.Result.Shutdown(); + await Console.Out.WriteLineAsync("任务调度停止!"); + result.success = true; + result.msg = $"任务调度停止成功"; + return result; + } + else + { + result.success = false; + result.msg = $"任务调度已经停止"; + return result; + } + } + catch (Exception) + { + throw; + } + } + + /// + /// 添加一个计划任务(映射程序集指定IJob实现类) + /// + /// + /// + /// + public async Task> AddScheduleJobAsync(TasksQz tasksQz) + { + var result = new MessageModel(); + + if (tasksQz != null) + { + try + { + JobKey jobKey = new JobKey(tasksQz.Id.ToString(), tasksQz.JobGroup); + if (await _scheduler.Result.CheckExists(jobKey)) + { + result.success = false; + 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) + { + tasksQz.BeginTime = DateTime.Now; + } + DateTimeOffset starRunTime = DateBuilder.NextGivenSecondDate(tasksQz.BeginTime, 1);//设置开始时间 + if (tasksQz.EndTime == null) + { + tasksQz.EndTime = DateTime.MaxValue.AddDays(-1); + } + DateTimeOffset endRunTime = DateBuilder.NextGivenSecondDate(tasksQz.EndTime, 1);//设置暂停时间 + + #endregion + + #region 通过反射获取程序集类型和类 + + Assembly assembly = Assembly.Load(new AssemblyName(tasksQz.AssemblyName)); + Type jobType = assembly.GetType(tasksQz.AssemblyName + "." + tasksQz.ClassName); + + #endregion + //判断任务调度是否开启 + if (!_scheduler.Result.IsStarted) + { + await StartScheduleAsync(); + } + + //传入反射出来的执行程序集 + IJobDetail job = new JobDetailImpl(tasksQz.Id.ToString(), tasksQz.JobGroup, jobType); + job.JobDataMap.Add("JobParam", tasksQz.JobParams); + ITrigger trigger; + + #region 泛型传递 + //IJobDetail job = JobBuilder.Create() + // .WithIdentity(sysSchedule.Name, sysSchedule.JobGroup) + // .Build(); + #endregion + + if (tasksQz.Cron != null && CronExpression.IsValidExpression(tasksQz.Cron) && tasksQz.TriggerType > 0) + { + trigger = CreateCronTrigger(tasksQz); + + ((CronTriggerImpl)trigger).MisfireInstruction = MisfireInstruction.CronTrigger.DoNothing; + } + else + { + trigger = CreateSimpleTrigger(tasksQz); + } + + // 告诉Quartz使用我们的触发器来安排作业 + await _scheduler.Result.ScheduleJob(job, trigger); + //await Task.Delay(TimeSpan.FromSeconds(120)); + //await Console.Out.WriteLineAsync("关闭了调度器!"); + //await _scheduler.Result.Shutdown(); + result.success = true; + result.msg = $"【{tasksQz.Name}】成功"; + return result; + } + catch (Exception ex) + { + result.success = false; + result.msg = $"任务计划异常:【{ex.Message}】"; + return result; + } + } + else + { + result.success = false; + result.msg = $"任务计划不存在:【{tasksQz?.Name}】"; + return result; + } + } + + /// + /// 任务是否存在? + /// + /// + public async Task IsExistScheduleJobAsync(TasksQz sysSchedule) + { + JobKey jobKey = new JobKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup); + if (await _scheduler.Result.CheckExists(jobKey)) + { + return true; + } + else + { + return false; + } + } + /// + /// 暂停一个指定的计划任务 + /// + /// + public async Task> StopScheduleJobAsync(TasksQz sysSchedule) + { + var result = new MessageModel(); + try + { + JobKey jobKey = new JobKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup); + if (!await _scheduler.Result.CheckExists(jobKey)) + { + result.success = false; + result.msg = $"未找到要暂停的任务:【{sysSchedule.Name}】"; + return result; + } + else + { + await this._scheduler.Result.DeleteJob(jobKey); + result.success = true; + result.msg = $"【{sysSchedule.Name}】成功"; + return result; + } + } + catch (Exception) + { + throw; + } + } + + /// + /// 恢复指定的计划任务 + /// + /// + /// + public async Task> ResumeJob(TasksQz sysSchedule) + { + var result = new MessageModel(); + try + { + JobKey jobKey = new JobKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup); + if (!await _scheduler.Result.CheckExists(jobKey)) + { + result.success = false; + result.msg = $"未找到要恢复的任务:【{sysSchedule.Name}】"; + return result; + } + await this._scheduler.Result.ResumeJob(jobKey); + result.success = true; + result.msg = $"【{sysSchedule.Name}】成功"; + return result; + } + catch (Exception) + { + throw; + } + } + /// + /// 暂停指定的计划任务 + /// + /// + /// + public async Task> PauseJob(TasksQz sysSchedule) + { + var result = new MessageModel(); + try + { + JobKey jobKey = new JobKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup); + if (!await _scheduler.Result.CheckExists(jobKey)) + { + result.success = false; + result.msg = $"未找到要暂停的任务:【{sysSchedule.Name}】"; + return result; + } + await this._scheduler.Result.PauseJob(jobKey); + result.success = true; + result.msg = $"【{sysSchedule.Name}】成功"; + return result; + } + catch (Exception) + { + throw; + } + } + #region 状态状态帮助方法 + public async Task> GetTaskStaus(TasksQz sysSchedule) + { + + var ls = new List(); + var noTask = new List{ new TaskInfoDto { + jobId = sysSchedule.Id.ObjToString(), + jobGroup = sysSchedule.JobGroup, + triggerId = "", + triggerGroup = "", + triggerStatus = "不存在" + } }; + JobKey jobKey = new JobKey(sysSchedule.Id.ToString(), sysSchedule.JobGroup); + IJobDetail job = await this._scheduler.Result.GetJobDetail(jobKey); + if (job == null) + { + return noTask; + } + //info.Append(string.Format("任务ID:{0}\r\n任务名称:{1}\r\n", job.Key.Name, job.Description)); + var triggers = await this._scheduler.Result.GetTriggersOfJob(jobKey); + if (triggers == null || triggers.Count == 0) + { + return noTask; + } + foreach (var trigger in triggers) + { + var triggerStaus = await this._scheduler.Result.GetTriggerState(trigger.Key); + string state = GetTriggerState(triggerStaus.ObjToString()); + ls.Add(new TaskInfoDto + { + jobId = job.Key.Name, + jobGroup = job.Key.Group, + triggerId = trigger.Key.Name, + triggerGroup = trigger.Key.Group, + triggerStatus = state + }); + //info.Append(string.Format("触发器ID:{0}\r\n触发器名称:{1}\r\n状态:{2}\r\n", item.Key.Name, item.Description, state)); + + } + return ls; + } + public string GetTriggerState(string key) + { + string state = null; + if (key != null) + key = key.ToUpper(); + switch (key) + { + case "1": + state = "暂停"; + break; + case "2": + state = "完成"; + break; + case "3": + state = "出错"; + break; + case "4": + state = "阻塞"; + break; + case "0": + state = "正常"; + break; + case "-1": + state = "不存在"; + break; + case "BLOCKED": + state = "阻塞"; + break; + case "COMPLETE": + state = "完成"; + break; + case "ERROR": + state = "出错"; + break; + case "NONE": + state = "不存在"; + break; + case "NORMAL": + state = "正常"; + break; + case "PAUSED": + state = "暂停"; + break; + } + return state; + } + #endregion + #region 创建触发器帮助方法 + + /// + /// 创建SimpleTrigger触发器(简单触发器) + /// + /// + /// + /// + /// + private ITrigger CreateSimpleTrigger(TasksQz sysSchedule) + { + if (sysSchedule.CycleRunTimes > 0) + { + ITrigger trigger = TriggerBuilder.Create() + .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup) + .StartAt(sysSchedule.BeginTime.Value) + .WithSimpleSchedule(x => x + .WithIntervalInSeconds(sysSchedule.IntervalSecond) + .WithRepeatCount(sysSchedule.CycleRunTimes - 1)) + .EndAt(sysSchedule.EndTime.Value) + .Build(); + return trigger; + } + else + { + ITrigger trigger = TriggerBuilder.Create() + .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup) + .StartAt(sysSchedule.BeginTime.Value) + .WithSimpleSchedule(x => x + .WithIntervalInSeconds(sysSchedule.IntervalSecond) + .RepeatForever() + ) + .EndAt(sysSchedule.EndTime.Value) + .Build(); + return trigger; + } + // 触发作业立即运行,然后每10秒重复一次,无限循环 + + } + /// + /// 创建类型Cron的触发器 + /// + /// + /// + private ITrigger CreateCronTrigger(TasksQz sysSchedule) + { + // 作业触发器 + return TriggerBuilder.Create() + .WithIdentity(sysSchedule.Id.ToString(), sysSchedule.JobGroup) + .StartAt(sysSchedule.BeginTime.Value)//开始时间 + .EndAt(sysSchedule.EndTime.Value)//结束数据 + .WithCronSchedule(sysSchedule.Cron)//指定cron表达式 + .ForJob(sysSchedule.Id.ToString(), sysSchedule.JobGroup)//作业名称 + .Build(); + } + #endregion + + + /// + /// 立即执行 一个任务 + /// + /// + /// + public async Task> ExecuteJobAsync(TasksQz tasksQz) + { + var result = new MessageModel(); + try + { + JobKey jobKey = new JobKey(tasksQz.Id.ToString(), tasksQz.JobGroup); + + //判断任务是否存在,存在则 触发一次,不存在则先添加一个任务,触发以后再 停止任务 + if (!await _scheduler.Result.CheckExists(jobKey)) + { + //不存在 则 添加一个计划任务 + await AddScheduleJobAsync(tasksQz); + + //触发执行一次 + await _scheduler.Result.TriggerJob(jobKey); + + //停止任务 + await StopScheduleJobAsync(tasksQz); + + result.success = true; + result.msg = $"立即执行计划任务:【{tasksQz.Name}】成功"; + } + else + { + await _scheduler.Result.TriggerJob(jobKey); + result.success = true; + result.msg = $"立即执行计划任务:【{tasksQz.Name}】成功"; + } + } + catch (Exception ex) + { + result.msg = $"立即执行计划任务失败:【{ex.Message}】"; + } + + return result; + } + + + } +} diff --git a/Blog.Core.Tests/Blog.Core.Tests.csproj b/Blog.Core.Tests/Blog.Core.Tests.csproj index 976ce9c9..50a63431 100644 --- a/Blog.Core.Tests/Blog.Core.Tests.csproj +++ b/Blog.Core.Tests/Blog.Core.Tests.csproj @@ -1,9 +1,8 @@ - + - netcoreapp2.2 - false + false @@ -18,17 +17,35 @@ - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + 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/BlogArticleController_Should.cs b/Blog.Core.Tests/Controller_Test/BlogArticleController_Should.cs deleted file mode 100644 index bc96cf88..00000000 --- a/Blog.Core.Tests/Controller_Test/BlogArticleController_Should.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Blog.Core.Common; -using Blog.Core.Controllers; -using Blog.Core.IRepository; -using Blog.Core.IServices; -using Blog.Core.Model.Models; -using Moq; -using Xunit; -using System; - -namespace Blog.Core.Tests -{ - public class BlogArticleController_Should - { - Mock mockBlogSev = new Mock(); - Mock mockRedisMag = new Mock(); - BlogController blogController; - - public BlogArticleController_Should() - { - mockBlogSev.Setup(r => r.Query()); - blogController = new BlogController(mockBlogSev.Object, mockRedisMag.Object); - } - - [Fact] - public void TestEntity() - { - BlogArticle blogArticle = new BlogArticle(); - - Assert.True(blogArticle.bID >= 0); - } - [Fact] - public async void AddEntity() - { - BlogArticle blogArticle = new BlogArticle() - { - bCreateTime = DateTime.Now, - bUpdateTime = DateTime.Now, - btitle = "xuint :test controller addEntity", - - }; - - var res = await blogController.Post(blogArticle); - - Assert.True(res.success); - - var data = res.response; - - Assert.NotNull(data); - } - } -} diff --git a/Blog.Core.Tests/Controller_Test/BlogController_Should.cs b/Blog.Core.Tests/Controller_Test/BlogController_Should.cs new file mode 100644 index 00000000..59d42ae0 --- /dev/null +++ b/Blog.Core.Tests/Controller_Test/BlogController_Should.cs @@ -0,0 +1,179 @@ +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 +{ + public class BlogController_Should + { + Mock mockBlogSev = new Mock(); + Mock> mockLogger = new Mock>(); + BlogController blogController; + + private IBlogArticleServices blogArticleServices; + DI_Test dI_Test = new DI_Test(); + + + + 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] + public void TestEntity() + { + BlogArticle blogArticle = new BlogArticle(); + + 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 Get_Blog_By_Type_For_MVP_Test() + { + 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] + public async void PostTest() + { + 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.Post(blogArticle); + + Assert.True(res.success); + + var data = res.response; + + 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 new file mode 100644 index 00000000..c9387f72 --- /dev/null +++ b/Blog.Core.Tests/Controller_Test/LoginController_Should.cs @@ -0,0 +1,78 @@ +using Blog.Core.Controllers; +using Blog.Core.IServices; +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; + 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 77dcc84b..bb037efd 100644 --- a/Blog.Core.Tests/DependencyInjection/DI_Test.cs +++ b/Blog.Core.Tests/DependencyInjection/DI_Test.cs @@ -1,41 +1,159 @@ using Autofac; using Autofac.Extensions.DependencyInjection; -using Blog.Core.IServices; -using Blog.Core.Services; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using Autofac.Extras.DynamicProxy; +using Blog.Core.AuthHelper; +using Blog.Core.Common; +using Blog.Core.Common.AppConfig; +using Blog.Core.Common.DB; +using Blog.Core.Common.Seed; +using Blog.Core.Extensions; +using Blog.Core.IRepository.Base; +using Blog.Core.Repository.Base; +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.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 { + /// + /// 连接字符串 + /// Blog.Core + /// + public static MutiDBOperate GetMainConnectionDb() + { + var mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs.Find(x => x.ConnId == MainDb.CurrentDbConnId); + if (BaseDBConfig.MutiConnectionString.allDbs.Count > 0) + { + if (mainConnetctDb == null) + { + mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs[0]; + } + } + else + { + throw new Exception("请确保appsettigns.json中配置连接字符串,并设置Enabled为true;"); + } + + return mainConnetctDb; + } - [Fact] - public void DI_Connet_Test() + public IContainer DICollections() { + var basePath = AppContext.BaseDirectory; + + IServiceCollection services = new ServiceCollection(); + services.ConfigureApplication(); + + services.AddLogging(); + services.AddAutoMapperSetup(); + services.AddCacheSetup(); + + services.AddSingleton(new AppSettings(basePath)); + services.AddScoped(); + services.AddScoped(); + + //读取配置文件 + 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 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) //接口的过期时间 + ); + services.AddSingleton(permissionRequirement); + + //【授权】 + services.AddAuthorization(options => + { + options.AddPolicy(Permissions.Name, + policy => policy.Requirements.Add(permissionRequirement)); + }); + + services.AddScoped(o => + { + return new SqlSugar.SqlSugarScope(new SqlSugar.ConnectionConfig() + { + ConnectionString = GetMainConnectionDb().Connection, //必填, 数据库连接字符串 + DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType, //必填, 数据库类型 + IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作 + }); + }); + //实例化 AutoFac 容器 var builder = new ContainerBuilder(); - builder.RegisterType().As(); + //builder.RegisterType().As(); + builder.RegisterInstance(new LoggerFactory()) + .As(); + builder.RegisterGeneric(typeof(Logger<>)) + .As(typeof(ILogger<>)) + .SingleInstance(); //指定已扫描程序集中的类型注册为提供所有其实现的接口。 - var assemblysServices = Assembly.Load("Blog.Core.Services"); - builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces(); - var assemblysRepository = Assembly.Load("Blog.Core.Repository"); - builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); + 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() + .PropertiesAutowired() + .EnableInterfaceInterceptors(); + + var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); + var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); + builder.RegisterAssemblyTypes(assemblysRepository) + .PropertiesAutowired().AsImplementedInterfaces(); + + services.Replace(ServiceDescriptor.Transient()); + + services.AddAutoMapperSetup(); //将services填充到Autofac容器生成器中 - //builder.Populate(services); + builder.Populate(services); //使用已进行的组件登记创建新容器 - var ApplicationContainer = builder.Build(); + var applicationContainer = builder.Build(); + return applicationContainer; + } - Assert.True(ApplicationContainer.ComponentRegistry.Registrations.Count() > 0); + 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/Redis_Test/Redis_Should.cs b/Blog.Core.Tests/Redis_Test/Redis_Should.cs index 5fac64d6..d85fb237 100644 --- a/Blog.Core.Tests/Redis_Test/Redis_Should.cs +++ b/Blog.Core.Tests/Redis_Test/Redis_Should.cs @@ -1,31 +1,25 @@ -using Blog.Core.Common; -using Blog.Core.Controllers; -using Blog.Core.IRepository; -using Blog.Core.IServices; -using Blog.Core.Model.Models; -using Moq; using Xunit; -using System; namespace Blog.Core.Tests { public class Redis_Should { - //IRedisCacheManager _redisCacheManager; + DI_Test dI_Test = new DI_Test(); - //public Redis_Should(IRedisCacheManager redisCacheManager) - //{ - // _redisCacheManager = redisCacheManager; - //} + public Redis_Should() + { + //var container = dI_Test.DICollections(); + //_redisCacheManager = container.Resolve(); + + } [Fact] public void Connect_Redis_Test() { - RedisCacheManager _redisCacheManager = new RedisCacheManager(); - var redisBlogCache = _redisCacheManager.Get("Redis.Blog"); + //var redisBlogCache = _redisCacheManager.Get("Redis.Blog"); - Assert.NotNull(redisBlogCache); + //Assert.Null(redisBlogCache); } } 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/Repository_Test/Repository_Base_Should.cs b/Blog.Core.Tests/Repository_Test/Repository_Base_Should.cs index 90e27eae..26d3a1c3 100644 --- a/Blog.Core.Tests/Repository_Test/Repository_Base_Should.cs +++ b/Blog.Core.Tests/Repository_Test/Repository_Base_Should.cs @@ -1,24 +1,25 @@ -using Blog.Core.Common; -using Blog.Core.Controllers; -using Blog.Core.IRepository; -using Blog.Core.IServices; -using Blog.Core.Model.Models; -using Moq; +using Blog.Core.Model.Models; using Xunit; using System; -using Blog.Core.Repository.Base; -using Blog.Core.Repository; using System.Linq; +using Autofac; +using Blog.Core.IRepository.Base; namespace Blog.Core.Tests { public class Repository_Base_Should { - BaseRepository baseRepository = new BaseRepository(); + private IBaseRepository baseRepository; + DI_Test dI_Test = new DI_Test(); public Repository_Base_Should() { - DbContext.Init(BaseDBConfig.ConnectionString); + + var container = dI_Test.DICollections(); + + baseRepository = container.Resolve>(); + + //DbContext.Init(BaseDBConfig.ConnectionString,(DbType)BaseDBConfig.DbType); } diff --git a/Blog.Core.Tests/Service_Test/BlogArticleService_Should.cs b/Blog.Core.Tests/Service_Test/BlogArticleService_Should.cs index cd81a3e1..df16d174 100644 --- a/Blog.Core.Tests/Service_Test/BlogArticleService_Should.cs +++ b/Blog.Core.Tests/Service_Test/BlogArticleService_Should.cs @@ -1,43 +1,41 @@ -using Blog.Core.Common; -using Blog.Core.Controllers; -using Blog.Core.IRepository; using Blog.Core.IServices; using Blog.Core.Model.Models; -using Moq; using Xunit; using System; using System.Linq; -using Blog.Core.Services; -using AutoMapper; -using Blog.Core.Repository; +using Autofac; namespace Blog.Core.Tests { public class BlogArticleService_Should { - Mock mockBlogRep = new Mock(); - Mock mockMap = new Mock(); - BlogArticleServices _blogArticleServices; + private IBlogArticleServices blogArticleServices; + DI_Test dI_Test = new DI_Test(); + public BlogArticleService_Should() { //mockBlogRep.Setup(r => r.Query()); - _blogArticleServices = new BlogArticleServices(mockBlogRep.Object, mockMap.Object); + + var container = dI_Test.DICollections(); + + blogArticleServices = container.Resolve(); + } [Fact] public void BlogArticleServices_Test() { - Assert.NotNull(_blogArticleServices); + Assert.NotNull(blogArticleServices); } [Fact] public async void Get_Blogs_Test() { - var data = await _blogArticleServices.GetBlogs(); + var data = await blogArticleServices.GetBlogs(); Assert.True(data.Any()); } @@ -54,7 +52,7 @@ public async void Add_Blog_Test() bsubmitter = "xuint test submitter", }; - var BId = await _blogArticleServices.Add(blogArticle); + var BId = await blogArticleServices.Add(blogArticle); Assert.True(BId > 0); } @@ -62,11 +60,13 @@ public async void Add_Blog_Test() [Fact] public async void Delete_Blog_Test() { - var deleteModel = (await _blogArticleServices.Query(d => d.btitle == "xuint test title")).FirstOrDefault(); + Add_Blog_Test(); + + var deleteModel = (await blogArticleServices.Query(d => d.btitle == "xuint test title")).FirstOrDefault(); Assert.NotNull(deleteModel); - var IsDel = await _blogArticleServices.Delete(deleteModel); + var IsDel = await blogArticleServices.Delete(deleteModel); Assert.True(IsDel); } 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 82ea0922..f91f6df8 100644 --- a/Blog.Core.Tests/appsettings.json +++ b/Blog.Core.Tests/appsettings.json @@ -1,39 +1,348 @@ { - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "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" } } }, "AllowedHosts": "*", + "Redis": { + "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": { - "RedisCaching": { + "CachingAOP": { + "Enabled": true + }, + "LogToDb": true, + "LogAOP": { "Enabled": false, - "ConnectionString": "127.0.0.1:6319" + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + } }, - "MemoryCachingAOP": { + "TranAOP": { "Enabled": true }, - "LogoAOP": { + "UserAuditAOP": { "Enabled": false }, - "SqlServer": { - "SqlServerConnection": "Server=.;Database=WMBlogDB;User ID=sa;Password=123;", //עҪѵǰappsetting.json ļ->Ҽ->->ƵĿ¼->ʼո - "ProviderName": "System.Data.SqlClient" + "SqlAOP": { + "Enabled": true, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + }, + "LogToConsole": { + "Enabled": true + } }, "Date": "2018-08-28", - "Author": "Blog.Core" + "SeedDBEnabled": true, //只生成表结构 + "SeedDBDataEnabled": true, //生成表,并初始化数据 + "Author": "Blog.Core", + "SvcName": "", // /svc/blog + "UseLoadTest": false }, + + //优化DB配置、不会再区分单库多库 + //MainDb:标识当前项目的主库,所对应的连接字符串的Enabled必须为true + //Log:标识日志库,所对应的连接字符串的Enabled必须为true + //从库只需配置Slaves数组,要求数据库类型一致!,比如都是SqlServer + // + //新增,故障转移方案 + //如果主库挂了,会自动切换到备用连接(比如说主库+备用库) + //备用连接的ConnId配置为主库的ConnId+数字即可,比如主库的ConnId为Main,那么备用连接的ConnId为Mian1 + //主库、备用库无需数据库类型一致! + //备用库不会有程序维护,需要手动维护 + "MainDB": "Main", //当前项目的主库,所对应的连接字符串的Enabled必须为true + "DBS": [ + /* + 对应下边的 DBType + MySql = 0, + SqlServer = 1, + Sqlite = 2, + Oracle = 3, + PostgreSQL = 4, + Dm = 5,//达梦 + Kdbndp = 6,//人大金仓 + */ + { + "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": "WMBlogLog.db" //sqlite只写数据库名就行 + }, + { + "ConnId": "WMBLOG_MSSQL_1", + "DBType": 1, + "Enabled": false, + "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" + }, + { + "ConnId": "WMBLOG_MSSQL_2", + "DBType": 1, + "Enabled": false, + "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" + }, + { + "ConnId": "WMBLOG_MYSQL", + "DBType": 0, + "Enabled": false, + "Connection": "server=localhost;Database=blog;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" + }, + { + "ConnId": "WMBLOG_MYSQL_2", + "DBType": 0, + "Enabled": false, + "Connection": "server=localhost;Database=blogcore001;Uid=root;Pwd=root;Port=3306;Allow User Variables=True;" + }, + { + "ConnId": "WMBLOG_ORACLE", + "DBType": 3, + "Enabled": false, + "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": { - "Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", + "Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", //不要太短,16位+ + "SecretFile": "C:\\my-file\\blog.core.audience.secret.txt", //安全。内容就是Secret "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,http://127.0.0.1:6688,http://localhost:6688" + }, + "AppConfigAlert": { + "Enabled": true + }, + "ApiName": "Blog.Core", + "IdentityServer4": { + "Enabled": false, // 这里默认是false,表示使用jwt,如果设置为true,则表示系统使用Ids4模式 + "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 //性能分析开启 + }, + "Nacos": { + "Enabled": false //Nacos注册中心 + } + }, + "Middleware": { + "RequestResponseLog": { + "Enabled": true, + "LogToFile": { + "Enabled": true + }, + "LogToDB": { + "Enabled": true + } + }, + "IPLog": { + "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": true + }, + "SignalRSendLog": { + "Enabled": true + }, + "QuartzNetJob": { + "Enabled": true + }, + "Consul": { + "Enabled": false + }, + "IpRateLimit": { + "Enabled": true + } + }, + "IpRateLimiting": { + "EnableEndpointRateLimiting": true, //False: globally executed, true: executed for each + "StackBlockedRequests": false, //False: Number of rejections should be recorded on another counter + "RealIpHeader": "X-Real-IP", + "ClientIdHeader": "X-ClientId", + "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规则,结尾一定要带* + { + "Endpoint": "*:/api/blog*", + "Period": "1m", + "Limit": 20 + }, + { + "Endpoint": "*/api/*", + "Period": "1s", + "Limit": 3 + }, + { + "Endpoint": "*/api/*", + "Period": "1m", + "Limit": 30 + }, + { + "Endpoint": "*/api/*", + "Period": "12h", + "Limit": 500 + } + ] + + }, + "ConsulSetting": { + "ServiceName": "BlogCoreService", + "ServiceIP": "localhost", + "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 f01394d5..c04e775b 100644 --- a/Blog.Core.sln +++ b/Blog.Core.sln @@ -1,14 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2003 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32014.148 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core", "Blog.Core\Blog.Core.csproj", "{6F47A41A-085E-4422-BB73-5A2CBAA07D9F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Api", "Blog.Core.Api\Blog.Core.Api.csproj", "{6F47A41A-085E-4422-BB73-5A2CBAA07D9F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Model", "Blog.Core.Model\Blog.Core.Model.csproj", "{E725F0A1-0B03-406F-B84B-0F486C6137FC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.IRepository", "Blog.Core.IRepository\Blog.Core.IRepository.csproj", "{54D53B3C-F7B3-4EE0-AFD4-89931218BF9A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Repository", "Blog.Core.Repository\Blog.Core.Repository.csproj", "{8D651E7F-49D3-4D27-8486-ADCF000BB24D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.IServices", "Blog.Core.IServices\Blog.Core.IServices.csproj", "{37BB8600-94DA-4A2C-9230-DE93EA1EB0BD}" @@ -17,9 +15,56 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Services", "Blog. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Common", "Blog.Core.Common\Blog.Core.Common.csproj", "{97D32A49-994C-44C5-A167-51E71D173B6F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.FrameWork", "Blog.Core.FrameWork\Blog.Core.FrameWork.csproj", "{44A2006E-3EFC-4179-B400-866178C66556}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Tasks", "Blog.Core.Tasks\Blog.Core.Tasks.csproj", "{F8E9FA1F-4079-4F62-B717-E389BC0014E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Extensions", "Blog.Core.Extensions\Blog.Core.Extensions.csproj", "{558F1B39-07E4-4FAB-BE7E-5B6104607064}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{D9833F24-7BD0-486F-B028-F1FD098AA1E1}" + ProjectSection(SolutionItems) = preProject + .dockerignore = .dockerignore + .editorconfig = .editorconfig + .gitignore = .gitignore + Blog.Core.Build.bat = Blog.Core.Build.bat + Blog.Core.Publish.bat = Blog.Core.Publish.bat + Blog.Core.Publish.Docker.Jenkins.sh = Blog.Core.Publish.Docker.Jenkins.sh + Blog.Core.Publish.Docker.sh = Blog.Core.Publish.Docker.sh + Blog.Core.Publish.Linux.sh = Blog.Core.Publish.Linux.sh + codecov.yml = codecov.yml + CreateYourProject.bat = CreateYourProject.bat + DockerBuild.bat = DockerBuild.bat + 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}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Tests", "Blog.Core.Tests\Blog.Core.Tests.csproj", "{300A8113-8033-4184-BE28-FC48D8349CD0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateways", "Gateways", "{E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Generators", "Generators", "{047A9723-9AAC-42E3-8C69-B3835F15FF96}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.FrameWork", "Blog.Core.FrameWork\Blog.Core.FrameWork.csproj", "{52D318A2-F44E-4CB7-8DD4-483357D4333F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBus", "EventBus", "{A592C96A-4E44-4F2A-AC21-30683AF6C493}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blog.Core.Tests", "Blog.Core.Tests\Blog.Core.Tests.csproj", "{69A9CD28-39CE-415E-8150-A4A876E708FD}" +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.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 @@ -35,10 +80,6 @@ Global {E725F0A1-0B03-406F-B84B-0F486C6137FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {E725F0A1-0B03-406F-B84B-0F486C6137FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {E725F0A1-0B03-406F-B84B-0F486C6137FC}.Release|Any CPU.Build.0 = Release|Any CPU - {54D53B3C-F7B3-4EE0-AFD4-89931218BF9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54D53B3C-F7B3-4EE0-AFD4-89931218BF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54D53B3C-F7B3-4EE0-AFD4-89931218BF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54D53B3C-F7B3-4EE0-AFD4-89931218BF9A}.Release|Any CPU.Build.0 = Release|Any CPU {8D651E7F-49D3-4D27-8486-ADCF000BB24D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D651E7F-49D3-4D27-8486-ADCF000BB24D}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D651E7F-49D3-4D27-8486-ADCF000BB24D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -55,18 +96,62 @@ Global {97D32A49-994C-44C5-A167-51E71D173B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU {97D32A49-994C-44C5-A167-51E71D173B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU {97D32A49-994C-44C5-A167-51E71D173B6F}.Release|Any CPU.Build.0 = Release|Any CPU - {44A2006E-3EFC-4179-B400-866178C66556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44A2006E-3EFC-4179-B400-866178C66556}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44A2006E-3EFC-4179-B400-866178C66556}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44A2006E-3EFC-4179-B400-866178C66556}.Release|Any CPU.Build.0 = Release|Any CPU - {69A9CD28-39CE-415E-8150-A4A876E708FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69A9CD28-39CE-415E-8150-A4A876E708FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69A9CD28-39CE-415E-8150-A4A876E708FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69A9CD28-39CE-415E-8150-A4A876E708FD}.Release|Any CPU.Build.0 = Release|Any CPU + {F8E9FA1F-4079-4F62-B717-E389BC0014E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8E9FA1F-4079-4F62-B717-E389BC0014E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8E9FA1F-4079-4F62-B717-E389BC0014E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8E9FA1F-4079-4F62-B717-E389BC0014E8}.Release|Any CPU.Build.0 = Release|Any CPU + {558F1B39-07E4-4FAB-BE7E-5B6104607064}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {558F1B39-07E4-4FAB-BE7E-5B6104607064}.Debug|Any CPU.Build.0 = Debug|Any CPU + {558F1B39-07E4-4FAB-BE7E-5B6104607064}.Release|Any CPU.ActiveCfg = Release|Any CPU + {558F1B39-07E4-4FAB-BE7E-5B6104607064}.Release|Any CPU.Build.0 = Release|Any CPU + {300A8113-8033-4184-BE28-FC48D8349CD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {300A8113-8033-4184-BE28-FC48D8349CD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {300A8113-8033-4184-BE28-FC48D8349CD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {300A8113-8033-4184-BE28-FC48D8349CD0}.Release|Any CPU.Build.0 = Release|Any CPU + {52D318A2-F44E-4CB7-8DD4-483357D4333F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52D318A2-F44E-4CB7-8DD4-483357D4333F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52D318A2-F44E-4CB7-8DD4-483357D4333F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52D318A2-F44E-4CB7-8DD4-483357D4333F}.Release|Any CPU.Build.0 = Release|Any CPU + {17C9E9DC-E926-4C90-9025-3DAC55D7EDA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + {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 + {A11C0DF2-1E13-4EED-BA49-44A57136B189}.Release|Any CPU.Build.0 = Release|Any CPU + {52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {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} + {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} EndGlobalSection diff --git a/Blog.Core/AOP/BlogCacheAOP.cs b/Blog.Core/AOP/BlogCacheAOP.cs deleted file mode 100644 index ffdef8bd..00000000 --- a/Blog.Core/AOP/BlogCacheAOP.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Blog.Core.Common; -using Castle.DynamicProxy; -using System; -using System.Linq; - -namespace Blog.Core.AOP -{ - /// - /// 面向切面的缓存使用 - /// - public class BlogCacheAOP : IInterceptor - { - //通过注入的方式,把缓存操作接口通过构造函数注入 - private readonly ICaching _cache; - public BlogCacheAOP(ICaching cache) - { - _cache = cache; - } - //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义 - public void Intercept(IInvocation invocation) - { - var method = invocation.MethodInvocationTarget ?? invocation.Method; - //对当前方法的特性验证 - //如果需要验证 - if (method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(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); - } - } - else - { - invocation.Proceed();//直接执行被拦截方法 - } - } - - //自定义缓存键 - private string CustomCacheKey(IInvocation invocation) - { - var typeName = invocation.TargetType.Name; - var methodName = invocation.Method.Name; - var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,最多三个 - - string key = $"{typeName}:{methodName}:"; - foreach (var param in methodArguments) - { - key += $"{param}:"; - } - - return key.TrimEnd(':'); - } - //object 转 string - private string GetArgumentValue(object arg) - { - if (arg is int || arg is long || arg is string) - return arg.ToString(); - - if (arg is DateTime) - return ((DateTime)arg).ToString("yyyyMMddHHmmss"); - - return ""; - } - } - -} diff --git a/Blog.Core/AOP/BlogLogAOP.cs b/Blog.Core/AOP/BlogLogAOP.cs deleted file mode 100644 index 9399b1b1..00000000 --- a/Blog.Core/AOP/BlogLogAOP.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Castle.DynamicProxy; -using StackExchange.Profiling; -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Blog.Core.AOP -{ - /// - /// 拦截器BlogLogAOP 继承IInterceptor接口 - /// - public class BlogLogAOP : IInterceptor - { - - static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); - static int LogCount = 100; - static int WritedCount = 0; - static int FailedCount = 0; - - /// - /// 实例化IInterceptor唯一方法 - /// - /// 包含被拦截方法的信息 - public void Intercept(IInvocation invocation) - { - //记录被拦截方法信息的日志信息 - var dataIntercept = $"{DateTime.Now:yyyyMMdd HH:mm:ss} " + - $"当前执行方法:{ invocation.Method.Name} " + - $"参数是: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; - - try - { - MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> "); - //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的 - invocation.Proceed(); - } - catch (Exception e) - { - //执行的 service 中,收录异常 - MiniProfiler.Current.CustomTiming("Errors:", e.Message); - //执行的 service 中,捕获异常 - dataIntercept += ($"方法执行中出现异常:{e.Message + e.InnerException}"); - } - - dataIntercept += ($"方法执行完毕,返回结果:{invocation.ReturnValue}"); - - Parallel.For(0, 1, e => - { - OutSql2Log(dataIntercept); - }); - - #region //输出到当前项目日志,多线程可能会出现争抢资源的问题,舍弃// - //var path = Directory.GetCurrentDirectory() + @"\Log"; - //if (!Directory.Exists(path)) - //{ - // Directory.CreateDirectory(path); - //} - - //string fileName = path + $@"\AOPLog.log"; - - //StreamWriter sw = File.AppendText(fileName); - //sw.WriteLine(dataIntercept); - //sw.WriteLine(); - //sw.Close(); - #endregion - - } - - static void OutSql2Log(string dataIntercept) - { - try - { - //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入 - //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。 - // 从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度 - // 因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常 - LogWriteLock.EnterWriteLock(); - - var path = Directory.GetCurrentDirectory() + @"\Log"; - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - string logFilePath = path + $@"\AOPLog.log"; - - var now = DateTime.Now; - var logContent = string.Format( - "--------------------------------\n" + - DateTime.Now + "\n" + - dataIntercept + "\n" + - "--------------------------------\n" - ); - - File.AppendAllText(logFilePath, logContent); - WritedCount++; - } - catch (Exception) - { - FailedCount++; - } - finally - { - //退出写入模式,释放资源占用 - //注意:一次请求对应一次释放 - // 若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放] - // 若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定] - LogWriteLock.ExitWriteLock(); - } - } - - } -} diff --git a/Blog.Core/AOP/BlogRedisCacheAOP.cs b/Blog.Core/AOP/BlogRedisCacheAOP.cs deleted file mode 100644 index 6ad98e16..00000000 --- a/Blog.Core/AOP/BlogRedisCacheAOP.cs +++ /dev/null @@ -1,133 +0,0 @@ -using Blog.Core.Common; -using Castle.DynamicProxy; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace Blog.Core.AOP -{ - /// - /// 面向切面的缓存使用 - /// - public class BlogRedisCacheAOP : IInterceptor - { - //通过注入的方式,把缓存操作接口通过构造函数注入 - private readonly IRedisCacheManager _cache; - public BlogRedisCacheAOP(IRedisCacheManager cache) - { - _cache = cache; - } - - - //Intercept方法是拦截的关键所在,也是IInterceptor接口中的唯一定义 - public void Intercept(IInvocation invocation) - { - var method = invocation.MethodInvocationTarget ?? invocation.Method; - //对当前方法的特性验证 - 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); - if (cacheValue != null) - { - //将当前获取到的缓存值,赋值给当前执行方法 - var type = invocation.Method.ReturnType; - var resultTypes = type.GenericTypeArguments; - if (type.FullName == "System.Void") - { - return; - } - object response; - if (typeof(Task).IsAssignableFrom(type)) - { - //返回Task - if (resultTypes.Any()) - { - var resultType = resultTypes.FirstOrDefault(); - // 核心1,直接获取 dynamic 类型 - dynamic temp = Newtonsoft.Json.JsonConvert.DeserializeObject(cacheValue, resultType); - //dynamic temp = System.Convert.ChangeType(cacheValue, resultType); - // System.Convert.ChangeType(Task.FromResult(temp), type); - response = Task.FromResult(temp); - - } - else - { - //Task 无返回方法 指定时间内不允许重新运行 - response = Task.Yield(); - } - } - else - { - // 核心2,要进行 ChangeType - response = Convert.ChangeType(_cache.Get(cacheKey), type); - } - - invocation.ReturnValue = response; - 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)); - } - } - else - { - invocation.Proceed();//直接执行被拦截方法 - } - } - - - - //自定义缓存键 - private string CustomCacheKey(IInvocation invocation) - { - var typeName = invocation.TargetType.Name; - var methodName = invocation.Method.Name; - var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,最多三个 - - string key = $"{typeName}:{methodName}:"; - foreach (var param in methodArguments) - { - key += $"{param}:"; - } - - return key.TrimEnd(':'); - } - //object 转 string - private string GetArgumentValue(object arg) - { - if (arg is int || arg is long || arg is string) - return arg.ToString(); - - if (arg is DateTime) - return ((DateTime)arg).ToString("yyyyMMddHHmmss"); - - return ""; - } - } - -} diff --git a/Blog.Core/AOP/ICachingProvider.cs b/Blog.Core/AOP/ICachingProvider.cs deleted file mode 100644 index 7d86596a..00000000 --- a/Blog.Core/AOP/ICachingProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Blog.Core.AOP -{ - /// - /// 简单的缓存接口,只有查询和添加,以后会进行扩展 - /// - public interface ICaching - { - object Get(string cacheKey); - - void Set(string cacheKey, object cacheValue); - } -} diff --git a/Blog.Core/AOP/MemoryCaching.cs b/Blog.Core/AOP/MemoryCaching.cs deleted file mode 100644 index 44b6e0dc..00000000 --- a/Blog.Core/AOP/MemoryCaching.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using System; - -namespace Blog.Core.AOP -{ - /// - /// 实例化缓存接口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) - { - _cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(7200)); - } - } - -} diff --git a/Blog.Core/AuthHelper/Policys/PermissionHandler.cs b/Blog.Core/AuthHelper/Policys/PermissionHandler.cs deleted file mode 100644 index 9d8b5707..00000000 --- a/Blog.Core/AuthHelper/Policys/PermissionHandler.cs +++ /dev/null @@ -1,171 +0,0 @@ -using Blog.Core.IServices; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Linq; -using System.Security.Claims; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace Blog.Core.AuthHelper -{ - /// - /// 权限授权处理器 - /// - public class PermissionHandler : AuthorizationHandler - { - /// - /// 验证方案提供对象 - /// - public IAuthenticationSchemeProvider Schemes { get; set; } - - /// - /// services 层注入 - /// - public IRoleModulePermissionServices roleModulePermissionServices { get; set; } - - /// - /// 构造函数注入 - /// - /// - /// - public PermissionHandler(IAuthenticationSchemeProvider schemes, IRoleModulePermissionServices roleModulePermissionServices) - { - Schemes = schemes; - this.roleModulePermissionServices = roleModulePermissionServices; - } - - // 重载异步处理程序 - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) - { - // 将最新的角色和接口列表更新 - var data = await roleModulePermissionServices.GetRoleModule(); - var list = (from item in data - where item.IsDeleted == false - orderby item.Id - select new PermissionItem - { - Url = item.Module?.LinkUrl, - Role = item.Role?.Name, - }).ToList(); - - requirement.Permissions = list; - - - //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息 - var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)?.HttpContext; - //请求Url - if (httpContext != null) - { - var questUrl = httpContext.Request.Path.Value.ToLower(); - //判断请求是否停止 - 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()) - { - context.Fail(); - return; - } - } - //判断请求是否拥有凭据,即有没有登录 - var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); - if (defaultAuthenticate != null) - { - var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); - //result?.Principal不为空即登录成功 - if (result?.Principal != null) - { - - httpContext.User = result.Principal; - - // 取消对URL的判断,因为只需判断该角色下是否匹配当前URL即可,若不匹配都是无效请求 - //var isMatchUrl = false; - //var permisssionGroup = requirement.Permissions.GroupBy(g => g.Url); - //foreach (var item in permisssionGroup) - //{ - // try - // { - // if (Regex.Match(questUrl, item.Key?.ObjToString().ToLower())?.Value == questUrl) - // { - // isMatchUrl = true; - // break; - // } - // } - // catch (Exception) - // { - // } - //} - - //权限中是否存在请求的url - //if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key?.ToLower() == questUrl).Count() > 0) - //if (isMatchUrl) - if (true) - { - // 获取当前用户的角色信息 - var 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 - { - if (Regex.Match(questUrl, item.Url?.ObjToString().ToLower())?.Value == questUrl) - { - isMatchRole = true; - break; - } - } - catch (Exception) - { - // ignored - } - } - - //验证权限 - //if (currentUserRoles.Count <= 0 || requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role) && w.Url.ToLower() == questUrl).Count() <= 0) - if (currentUserRoles.Count <= 0 || !isMatchRole) - { - - context.Fail(); - return; - // 可以在这里设置跳转页面,不过还是会访问当前接口地址的 - httpContext.Response.Redirect(requirement.DeniedAction); - } - } - else - { - context.Fail(); - return; - - } - //判断过期时间(这里仅仅是最坏验证原则,你可以不要这个if else的判断,因为我们使用的官方验证,Token过期后上边的result?.Principal 就为 null 了,进不到这里了,因此这里其实可以不用验证过期时间,只是做最后严谨判断) - if ((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) - { - context.Succeed(requirement); - } - else - { - context.Fail(); - return; - } - return; - } - } - //判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败 - if (!questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") - || !httpContext.Request.HasFormContentType)) - { - context.Fail(); - return; - } - } - - context.Succeed(requirement); - } - } -} diff --git a/Blog.Core/AutoMapper/CustomProfile.cs b/Blog.Core/AutoMapper/CustomProfile.cs deleted file mode 100644 index 14319cf0..00000000 --- a/Blog.Core/AutoMapper/CustomProfile.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Blog.Core.Model.Models; -using Blog.Core.Model.ViewModels; - -namespace Blog.Core.AutoMapper -{ - public class CustomProfile : Profile - { - /// - /// 配置构造函数,用来创建关系映射 - /// - public CustomProfile() - { - CreateMap(); - } - } -} diff --git a/Blog.Core/Blog.Core.Model.xml b/Blog.Core/Blog.Core.Model.xml deleted file mode 100644 index ac9eac0d..00000000 --- a/Blog.Core/Blog.Core.Model.xml +++ /dev/null @@ -1,1185 +0,0 @@ - - - - Blog.Core.Model - - - - - 这是爱 - - - - - id - - - - - 姓名 - - - - - 年龄 - - - - - 通用返回信息类 - - - - - 操作是否成功 - - - - - 返回信息 - - - - - 返回数据集合 - - - - - 广告图片 - - - - - 广告标题 - - - - - 广告链接 - - - - - 备注 - - - - - 创建时间 - - - - - - 主键 - - 这里之所以没用RootEntity,是想保持和之前的数据库一致,主键是bID,不是Id - - - - 创建人 - - - - - 标题blog - - - - - 类别 - - - - - 内容 - - - - - 访问量 - - - - - 评论数量 - - - - - 修改时间 - - - - - 创建时间 - - - - - 备注 - - - - - 逻辑删除 - - - - - 留言表 - - - - 博客ID - - - - - 创建时间 - - - - - 手机 - - - - - qq - - - - - 留言内容 - - - - - ip地址 - - - - - 是否显示在前台,0否1是 - - - - - - 接口API地址信息表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 父ID - - - - - 名称 - - - - - 菜单链接地址 - - - - - 区域名称 - - - - - 控制器名称 - - - - - Action名称 - - - - - 图标 - - - - - 菜单编号 - - - - - 排序 - - - - - /描述 - - - - - 是否是右侧菜单 - - - - - 是否激活 - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 菜单与按钮关系表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 菜单ID - - - - - 按钮ID - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 日志记录 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 区域名 - - - - - 区域控制器名 - - - - - Action名称 - - - - - IP地址 - - - - - 描述 - - - - - 登录时间 - - - - - 登录名称 - - - - - 用户ID - - - - - 密码库表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 路由菜单表 - - - - - 菜单执行Action名 - - - - - 菜单显示名(如用户页、编辑(按钮)、删除(按钮)) - - - - - 是否是按钮 - - - - - 上一级菜单(0表示上一级无菜单) - - - - - 接口api - - - - - 排序 - - - - - 菜单图标 - - - - - 菜单描述 - - - - - 激活状态 - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 角色表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 角色名 - - - - - 描述 - - - - - 排序 - - - - - 是否激活 - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 按钮跟权限关联表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 角色ID - - - - - 菜单ID - - - - - 按钮ID - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - 用户信息表 - - - - - 用户ID - - - - - 登录账号 - - - - - 登录密码 - - - - - 真实姓名 - - - - - 状态 - - - - - 备注 - - - - - 创建时间 - - - - - 更新时间 - - - - - 最后登录时间 - - - - - 错误次数 - - - - - 登录账号 - - - - - - - 用户跟角色关联表 - - - - - 获取或设置是否禁用,逻辑上的删除,非物理删除 - - - - - 用户ID - - - - - 角色ID - - - - - 创建ID - - - - - 创建者 - - - - - 创建时间 - - - - - 修改ID - - - - - 修改者 - - - - - 修改时间 - - - - - ID - - - - - 异步添加种子数据 - - - - - - - 连接字符串 - Blog.Core - - - - - 数据库类型 - Blog.Core - - - - - 数据连接对象 - Blog.Core - - - - - 数据库上下文实例(自动关闭连接) - Blog.Core - - - - - 功能描述:构造函数 - 作  者:Blog.Core - - - - - 功能描述:获取数据库处理对象 - 作  者:Blog.Core - - 返回值 - - - - 功能描述:获取数据库处理对象 - 作  者:Blog.Core - - db - 返回值 - - - - 功能描述:根据数据库表生产实体类 - 作  者:Blog.Core - - 实体类存放路径 - - - - 功能描述:根据数据库表生产实体类 - 作  者:Blog.Core - - 实体类存放路径 - 命名空间 - - - - 功能描述:根据数据库表生产实体类 - 作  者:Blog.Core - - 实体类存放路径 - 命名空间 - 生产指定的表 - - - - 功能描述:根据数据库表生产实体类 - 作  者:Blog.Core - - 实体类存放路径 - 命名空间 - 生产指定的表 - 实现接口 - - - - 功能描述:根据实体类生成数据库表 - 作  者:Blog.Core - - 是否备份表 - 指定的实体 - - - - 功能描述:根据实体类生成数据库表 - 作  者:Blog.Core - - 是否备份表 - 指定的实体 - - - - 功能描述:获得一个DbContext - 作  者:Blog.Core - - 是否自动关闭连接(如果为false,则使用接受时需要手动关闭Db) - 返回值 - - - - 功能描述:设置初始化参数 - 作  者:Blog.Core - - 连接字符串 - 数据库类型 - - - - 功能描述:创建一个链接配置 - 作  者:Blog.Core - - 是否自动关闭连接 - 是否夸类事务 - ConnectionConfig - - - - 功能描述:获取一个自定义的DB - 作  者:Blog.Core - - config - 返回值 - - - - 功能描述:获取一个自定义的数据库处理对象 - 作  者:Blog.Core - - sugarClient - 返回值 - - - - 功能描述:获取一个自定义的数据库处理对象 - 作  者:Blog.Core - - config - 返回值 - - - - 通用分页信息类 - - - - - 当前页数 - - - - - 总页数 - - - - - 数据总数 - - - - - 返回数据 - - - - - 无权限 - - - - - 找不到指定资源 - - - - - 找不到指定资源 - - - - - 表格数据,支持分页 - - - - - 返回编码 - - - - - 返回信息 - - - - - 记录总数 - - - - - 返回数据集 - - - - - 广告类 - - - - - 分类ID - - - - - 创建时间 - - - - - 广告图片 - - - - - 广告标题 - - - - - 广告链接 - - - - - 备注 - - - - - 博客信息展示类 - - - - - - - - - 创建人 - - - - - 博客标题 - - - - - 摘要 - - - - - - 上一篇 - - - - - 上一篇id - - - - - 下一篇 - - - - - 下一篇id - - - - 类别 - - - - - 内容 - - - - - - 访问量 - - - - - 评论数量 - - - - 修改时间 - - - - - - 创建时间 - - - - 备注 - - - - - - 留言信息展示类 - - - - 留言表 - - - - - 博客ID - - - - - 创建时间 - - - - - 手机 - - - - - qq - - - - - 留言内容 - - - - - ip地址 - - - - - 是否显示在前台,0否1是 - - - - - - 菜单展示model - - - - - 留言排名展示类 - - - - 博客ID - - - - - - 评论数量 - - - - 博客标题 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Blog.Core/Blog.Core.csproj b/Blog.Core/Blog.Core.csproj deleted file mode 100644 index 0640a8e9..00000000 --- a/Blog.Core/Blog.Core.csproj +++ /dev/null @@ -1,51 +0,0 @@ - - - - netcoreapp2.2 - - - - ..\Blog.Core\Blog.Core.xml - 1701;1702;1591 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - - - - - - - - diff --git a/Blog.Core/Blog.Core.xml b/Blog.Core/Blog.Core.xml deleted file mode 100644 index a1abaed2..00000000 --- a/Blog.Core/Blog.Core.xml +++ /dev/null @@ -1,851 +0,0 @@ - - - - Blog.Core - - - - - 面向切面的缓存使用 - - - - - 拦截器BlogLogAOP 继承IInterceptor接口 - - - - - 实例化IInterceptor唯一方法 - - 包含被拦截方法的信息 - - - - 面向切面的缓存使用 - - - - - 简单的缓存接口,只有查询和添加,以后会进行扩展 - - - - - 实例化缓存接口ICaching - - - - - 颁发JWT字符串 - - - - - - - 解析 - - - - - - - 令牌 - - - - - Id - - - - - 角色 - - - - - 职能 - - - - - - - - - - - - - - - - - - - - - - - - - - - - JWTToken生成类 - - - - - 获取基于JWT的Token - - 需要在登陆的时候配置 - 在startup中定义的参数 - - - - - 权限授权处理器 - - - - - 验证方案提供对象 - - - - - services 层注入 - - - - - 构造函数注入 - - - - - - - 用户或角色或其他凭据实体,就像是订单详情一样 - 之前的名字是 Permission - - - - - 用户或角色或其他凭据名称 - - - - - 请求Url - - - - - 必要参数类,类似一个订单信息 - 继承 IAuthorizationRequirement,用于设计自定义权限处理器PermissionHandler - 因为AuthorizationHandler 中的泛型参数 TRequirement 必须继承 IAuthorizationRequirement - - - - - 用户权限集合,一个订单包含了很多详情, - 同理,一个网站的认证发行中,也有很多权限详情(这里是Role和URL的关系) - - - - - 无权限action - - - - - 认证授权类型 - - - - - 请求路径 - - - - - 发行人 - - - - - 订阅人 - - - - - 过期时间 - - - - - 签名验证 - - - - - 构造 - - 拒约请求的url - 权限集合 - 声明类型 - 发行人 - 订阅人 - 签名验证实体 - 过期时间 - - - - 配置构造函数,用来创建关系映射 - - - - - Blog控制器所有接口 - - - - - 构造函数 - - - - - - - 获取博客列表 - - - - - - - - - 获取详情 - - - - - - - 获取博客测试信息 v2版本 - - - - - - 测试批量删除,如果是axios,记得要把数组格式化成 stringQuery - - - - - - - 下载图片(支持中文字符) - - - - - - - 上传图片 - - - - - - - - - - - - 构造函数注入 - - - - - - - - - 获取JWT的方法 - - id - 角色 - - - - - 获取JWT的方法 3.0 - - - - - - - - - - - - - - - - - - 构造函数 - - - - - - 构造函数 - - - - - - - - - 构造函数 - - - - - - TopicDetailController - - - - - - - 获取Bug数据列表(带分页) - - 页数 - 专题类型 - - - - - 构造函数 - - - - - - - - 获取用户详情根据token - - 令牌 - - - - - 用户权限控制器所有接口 - - - - - 构造函数 - - - - - - - - 新建用户 - - - - - - - - 新建Role - - - - - - - 新建用户角色关系 - - - - - - - - Values控制器 - - - - - ValuesController - - - - - - - - - Get方法 - - - - - - Get(int id)方法 - - - - - - - 参数必填项 - - - - - - - post - - model实体类参数 - - - - Put方法 - - - - - - - Delete方法 - - - - - - 全局异常错误日志 - - - - - 自定义返回格式 - - - - - - - - 生产环境的消息 - - - - - 开发环境的消息 - - - - - 日志接口 - - - - - 调试信息 - - source - message - - - - 调试信息 - - source - message - ps - - - - 调试信息 - - source - message - - - - 关键信息 - - source - message - - - - 关键信息 - - source - message - - - - 警告信息 - - source - message - - - - 警告信息 - - source - message - - - - 错误信息 - - source - message - - - - 错误信息 - - source - message - - - - 失败信息 - - source - message - - - - 失败信息 - - source - message - - - - 调试信息 - - source - message - ex - - - - 调试信息 - - source - message - ex - - - - 关键信息 - - source - message - ex - - - - 关键信息 - - source - message - ex - - - - 警告信息 - - source - message - ex - - - - 警告信息 - - source - message - ex - - - - 错误信息 - - source - message - ex - - - - 错误信息 - - source - message - ex - - - - 失败信息 - - source - message - ex - - - - 失败信息 - - source - message - ex - - - - - - - - - 获取记录器 - - soruce - - - - - 调试信息 - - source - message - - - - 调试信息 - - source - message - ps - - - - 调试信息 - - source - message - - - - 关键信息 - - source - message - - - - 关键信息 - - source - message - - - - 警告信息 - - source - message - - - - 警告信息 - - source - message - - - - 错误信息 - - source - message - - - - 错误信息 - - source - message - - - - 失败信息 - - source - message - - - - 失败信息 - - source - message - - - - 调试信息 - - source - message - ex - - - - 调试信息 - - source - message - ex - - - - 关键信息 - - source - message - ex - - - - 关键信息 - - source - message - ex - - - - 警告信息 - - source - message - ex - - - - 警告信息 - - source - message - ex - - - - 错误信息 - - source - message - ex - - - - 错误信息 - - source - message - ex - - - - 失败信息 - - source - message - ex - - - - 失败信息 - - source - message - ex - - - - log4net 仓储库 - - - - - 自定义版本 - - - - - Api接口版本 自定义 - - - - - V1 版本 - - - - - V2 版本 - - - - - 自定义路由 /api/{version}/[controler]/[action] - - - - - 分组名称,是来实现接口 IApiDescriptionGroupNameProvider - - - - - 自定义路由构造函数,继承基类路由 - - - - - - 自定义版本+路由构造函数,继承基类路由 - - - - - - diff --git a/Blog.Core/Controllers/BlogController.cs b/Blog.Core/Controllers/BlogController.cs deleted file mode 100644 index 79336391..00000000 --- a/Blog.Core/Controllers/BlogController.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.Common; -using Blog.Core.Common.Helper; -using Blog.Core.IServices; -using Blog.Core.Model; -using Blog.Core.Model.Models; -using Blog.Core.SwaggerHelper; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using StackExchange.Profiling; -using static Blog.Core.SwaggerHelper.CustomApiVersion; - -namespace Blog.Core.Controllers -{ - /// - /// Blog控制器所有接口 - /// - [Produces("application/json")] - [Route("api/Blog")] - public class BlogController : Controller - { - readonly IBlogArticleServices _blogArticleServices; - readonly IRedisCacheManager _redisCacheManager; - - /// - /// 构造函数 - /// - /// - /// - public BlogController(IBlogArticleServices blogArticleServices, IRedisCacheManager redisCacheManager) - { - _blogArticleServices = blogArticleServices; - _redisCacheManager = redisCacheManager; - } - - - /// - /// 获取博客列表 - /// - /// - /// - /// - /// - [HttpGet] - [AllowAnonymous] - public async Task Get(int id, int page = 1, string bcategory = "技术博文") - { - int intTotalCount = 6; - int total; - int totalCount = 1; - List blogArticleList = new List(); - - using (MiniProfiler.Current.Step("开始加载数据:")) - { - try - { - if (_redisCacheManager.Get("Redis.Blog") != null) - { - MiniProfiler.Current.Step("从Redis服务器中加载数据:"); - blogArticleList = _redisCacheManager.Get>("Redis.Blog"); - } - else - { - MiniProfiler.Current.Step("从MSSQL服务器中加载数据:"); - blogArticleList = await _blogArticleServices.Query(a => a.bcategory == bcategory); - _redisCacheManager.Set("Redis.Blog", blogArticleList, TimeSpan.FromHours(2)); - } - - } - catch (Exception e) - { - MiniProfiler.Current.CustomTiming("Errors:", e.Message); - blogArticleList = await _blogArticleServices.Query(a => a.bcategory == bcategory); - } - } - - total = blogArticleList.Count(); - totalCount = blogArticleList.Count() / intTotalCount; - - using (MiniProfiler.Current.Step("获取成功后,开始处理最终数据")) - { - blogArticleList = blogArticleList.OrderByDescending(d => d.bID).Skip((page - 1) * intTotalCount).Take(intTotalCount).ToList(); - - foreach (var item in blogArticleList) - { - if (!string.IsNullOrEmpty(item.bcontent)) - { - item.bRemark = (HtmlHelper.ReplaceHtmlTag(item.bcontent)).Length >= 200 ? (HtmlHelper.ReplaceHtmlTag(item.bcontent)).Substring(0, 200) : (HtmlHelper.ReplaceHtmlTag(item.bcontent)); - int totalLength = 500; - if (item.bcontent.Length > totalLength) - { - item.bcontent = item.bcontent.Substring(0, totalLength); - } - } - } - } - - return Ok(new - { - success = true, - page = page, - total = total, - pageCount = totalCount, - data = blogArticleList - }); - } - - - // GET: api/Blog/5 - /// - /// 获取详情 - /// - /// - /// - [HttpGet("{id}")] - [Authorize(Roles = "Admin")] - //[Authorize(PermissionNames.Permission)] - public async Task Get(int id) - { - var model = await _blogArticleServices.GetBlogDetails(id); - return Ok(new - { - success = true, - data = model - }); - } - - - - [HttpGet] - [Route("DetailNuxtNoPer")] - public async Task DetailNuxtNoPer(int id) - { - var model = await _blogArticleServices.GetBlogDetails(id); - return Ok(new - { - success = true, - data = model - }); - } - - - /// - /// 获取博客测试信息 v2版本 - /// - /// - [HttpGet] - ////MVC自带特性 对 api 进行组管理 - //[ApiExplorerSettings(GroupName = "v2")] - ////路径 如果以 / 开头,表示绝对路径,反之相对 controller 的想u地路径 - //[Route("/api/v2/blog/Blogtest")] - - //和上边的版本控制以及路由地址都是一样的 - [CustomRoute(ApiVersions.V2, "Blogtest")] - public async Task V2_Blogtest() - { - return Ok(new { status = 220, data = "我是第二版的博客信息" }); - } - - - [HttpPost] - [AllowAnonymous] - public async Task> Post([FromBody] BlogArticle blogArticle) - { - var data = new MessageModel(); - - blogArticle.bCreateTime = DateTime.Now; - blogArticle.bUpdateTime = DateTime.Now; - - var id = (await _blogArticleServices.Add(blogArticle)); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } - - return data; - } - - } -} diff --git a/Blog.Core/Controllers/ClaimsController.cs b/Blog.Core/Controllers/ClaimsController.cs deleted file mode 100644 index c1e6d571..00000000 --- a/Blog.Core/Controllers/ClaimsController.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc; - -namespace Blog.Core.Controllers -{ - //[Authorize(PermissionNames.Permission)] - [Route("api/Claims/[action]")] - [ApiController] - public class ClaimsController : Controller - { - // *****这是一个测试的控制器,主要为了测试基于Claim的验证机制***** - // *****[Authorize(PermissionNames.Permission)]***** - - - - - // GET: api/Claims - [HttpGet] - public IEnumerable Get() - { - return new string[] { "value1", "value2" }; - } - - // GET: api/Claims/5 - [HttpGet("{id}")] - public string Get(int id) - { - return "value"; - } - - // POST: api/Claims - [HttpPost] - public void Post([FromBody] string value) - { - } - - // PUT: api/Claims/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) - { - } - - // DELETE: api/ApiWithActions/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - - /// - /// 测试批量删除,如果是axios,记得要把数组格式化成 stringQuery - /// - /// - /// - [HttpDelete] - public IEnumerable BatchDelete(string[] ids) - { - return ids; - } - } -} diff --git a/Blog.Core/Controllers/ImgController.cs b/Blog.Core/Controllers/ImgController.cs deleted file mode 100644 index 26f92051..00000000 --- a/Blog.Core/Controllers/ImgController.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.Model; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; - -namespace Blog.Core.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class ImgController : Controller - { - // GET: api/Img - [HttpGet] - public IEnumerable Get() - { - return new string[] { "value1", "value2" }; - } - - // GET: api/Img/5 - [HttpGet("{id}", Name = "Get")] - public string Get(int id) - { - return "value"; - } - - // GET: api/Download - /// - /// 下载图片(支持中文字符) - /// - /// - /// - [HttpGet] - [Route("down")] - public FileStreamResult DownImg([FromServices]IHostingEnvironment environment) - { - string foldername = ""; - string filepath = Path.Combine(environment.WebRootPath, foldername, "测试下载中文名称的图片.png"); - var stream = System.IO.File.OpenRead(filepath); - string fileExt = ".jpg"; // 这里可以写一个获取文件扩展名的方法,获取扩展名 - //获取文件的ContentType - var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider(); - var memi = provider.Mappings[fileExt]; - var fileName = Path.GetFileName(filepath); - - - return File(stream, memi, fileName); - } - - /// - /// 上传图片 - /// - /// - /// - [HttpPost] - [Route("Pic")] - public async Task> InsertPicture([FromServices]IHostingEnvironment environment) - { - var data = new MessageModel(); - string path = string.Empty; - string foldername = "images"; - var files = Request.Form.Files; - if (files == null || !files.Any()) { data.msg = "请选择上传的文件。"; return data; } - //格式限制 - var allowType = new string[] { "image/jpg", "image/png", "image/jpeg" }; - - string folderpath = Path.Combine(environment.WebRootPath, foldername); - if (!System.IO.Directory.Exists(folderpath)) - { - System.IO.Directory.CreateDirectory(folderpath); - } - - if (files.Any(c => allowType.Contains(c.ContentType))) - { - 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") + 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 - { - data.msg = "图片过大"; - return data; - } - } - else - - { - data.msg = "图片格式错误"; - return data; - } - } - - - // POST: api/Img - [HttpPost] - public void Post([FromBody] object formdata) - { - } - - // PUT: api/Img/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) - { - } - - // DELETE: api/ApiWithActions/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - } - -} diff --git a/Blog.Core/Controllers/LoginController.cs b/Blog.Core/Controllers/LoginController.cs deleted file mode 100644 index 129a411d..00000000 --- a/Blog.Core/Controllers/LoginController.cs +++ /dev/null @@ -1,243 +0,0 @@ -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.OverWrite; -using Blog.Core.IServices; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Cors; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Blog.Core.Controllers -{ - /// - /// - /// - [Produces("application/json")] - [Route("api/Login")] - public class LoginController : Controller - { - readonly ISysUserInfoServices _sysUserInfoServices; - IUserRoleServices _userRoleServices; - IRoleServices _roleServices; - readonly PermissionRequirement _requirement; - - - /// - /// 构造函数注入 - /// - /// - /// - /// - /// - public LoginController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IRoleServices roleServices, PermissionRequirement requirement) - { - this._sysUserInfoServices = sysUserInfoServices; - this._userRoleServices = userRoleServices; - this._roleServices = roleServices; - _requirement = requirement; - } - - - #region 获取token的第1种方法 - /// - /// 获取JWT的方法 - /// - /// id - /// 角色 - /// - [HttpGet] - [Route("Token")] - public async Task GetJwtStr(string name, string pass) - { - string jwtStr = string.Empty; - bool suc = false; - //这里就是用户登陆以后,通过数据库去调取数据,分配权限的操作 - //这里直接写死了 - - var user = await _sysUserInfoServices.GetUserRoleNameStr(name, pass); - if (user != null) - { - - TokenModelJwt tokenModel = new TokenModelJwt {Uid = 1, Role = user}; - - jwtStr = JwtHelper.IssueJwt(tokenModel); - suc = true; - } - else - { - jwtStr = "login fail!!!"; - } - - return Ok(new - { - success = suc, - token = jwtStr - }); - } - - - - [HttpGet] - [Route("GetTokenNuxt")] - public async Task GetJwtStrForNuxt(string name, string pass) - { - string jwtStr = string.Empty; - bool suc = false; - //这里就是用户登陆以后,通过数据库去调取数据,分配权限的操作 - //这里直接写死了 - if (name == "admins" && pass == "admins") - { - TokenModelJwt tokenModel = new TokenModelJwt(); - tokenModel.Uid = 1; - tokenModel.Role = "Admin"; - - jwtStr = JwtHelper.IssueJwt(tokenModel); - suc = true; - } - else - { - jwtStr = "login fail!!!"; - } - var result = new - { - data = new { success = suc, token = jwtStr } - }; - - return Ok(new - { - success = suc, - data = new { success = suc, token = jwtStr } - }); - } - #endregion - - - - /// - /// 获取JWT的方法 3.0 - /// - /// - /// - /// - [HttpGet] - [Route("JWTToken3.0")] - public async Task GetJwtToken3(string name = "", string pass = "") - { - string jwtStr = string.Empty; - bool suc = false; - - if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(pass)) - { - return new JsonResult(new - { - Status = false, - message = "用户名或密码不能为空" - }); - } - - var user = await _sysUserInfoServices.Query(d => d.uLoginName == name && d.uLoginPWD == pass); - if (user.Count > 0) - { - var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(name, pass); - //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色 - 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()) }; - claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s))); - - //用户标识 - var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); - identity.AddClaims(claims); - - var token = JwtToken.BuildJwtToken(claims.ToArray(), _requirement); - return new JsonResult(token); - } - else - { - return new JsonResult(new - { - success = false, - message = "认证失败" - }); - } - - - - } - - - [HttpGet] - [Route("RefreshToken")] - public async Task RefreshToken(string token = "") - { - string jwtStr = string.Empty; - bool suc = false; - - if (string.IsNullOrEmpty(token)) - { - return new JsonResult(new - { - Status = false, - message = "token无效,请重新登录!" - }); - } - var tokenModel = JwtHelper.SerializeJwt(token); - if (tokenModel != null && tokenModel.Uid > 0) - { - var user = await _sysUserInfoServices.QueryById(tokenModel.Uid); - if (user != null) - { - var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(user.uLoginName, user.uLoginPWD); - //如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色 - 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()) }; - claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s))); - - //用户标识 - var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme); - identity.AddClaims(claims); - - var refreshToken = JwtToken.BuildJwtToken(claims.ToArray(), _requirement); - return new JsonResult(refreshToken); - } - } - - return new JsonResult(new - { - success = false, - message = "认证失败" - }); - } - - /// - /// - /// - /// - /// - /// - /// - /// - [HttpGet] - [Route("jsonp")] - public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, int expiresAbsoulute = 30) - { - TokenModelJwt tokenModel = new TokenModelJwt(); - tokenModel.Uid = id; - tokenModel.Role = sub; - - string jwtStr = JwtHelper.IssueJwt(tokenModel); - - string response = string.Format("\"value\":\"{0}\"", jwtStr); - string call = callBack + "({" + response + "})"; - Response.WriteAsync(call); - } - } -} \ No newline at end of file diff --git a/Blog.Core/Controllers/ModuleController.cs b/Blog.Core/Controllers/ModuleController.cs deleted file mode 100644 index 833016c6..00000000 --- a/Blog.Core/Controllers/ModuleController.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -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 -{ - [Route("api/[controller]/[action]")] - [ApiController] - [Authorize(PermissionNames.Permission)] - public class ModuleController : ControllerBase - { - readonly IModuleServices _moduleServices; - - /// - /// 构造函数 - /// - /// - public ModuleController(IModuleServices moduleServices ) - { - _moduleServices = moduleServices; - } - - // GET: api/User - [HttpGet] - public async Task>> Get(int page = 1, string key = "") - { - var data = new MessageModel>(); - int intTotalCount = 50; - int totalCount = 0; - int pageCount = 1; - List modules = new List(); - - modules = await _moduleServices.Query(a => a.IsDeleted != true ); - - if (!string.IsNullOrEmpty(key)) - { - modules = modules.Where(t => (t.Name != null && t.Name.Contains(key))).ToList(); - } - - - //筛选后的数据总数 - totalCount = modules.Count; - //筛选后的总页数 - pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intTotalCount.ObjToDecimal())).ObjToInt(); - - modules = modules.OrderByDescending(d => d.Id).Skip((page - 1) * intTotalCount).Take(intTotalCount).ToList(); - - return new MessageModel>() - { - msg = "获取成功", - success = totalCount >= 0, - response = new PageModel() - { - page = page, - pageCount = pageCount, - dataCount = totalCount, - data = modules, - } - }; - - } - - // GET: api/User/5 - [HttpGet("{id}")] - public string Get(string id) - { - return "value"; - } - - // POST: api/User - [HttpPost] - public async Task> Post([FromBody] Module module) - { - var data = new MessageModel(); - - var id = (await _moduleServices.Add(module)); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } - - return data; - } - - // PUT: api/User/5 - [HttpPut] - public async Task> Put([FromBody] Module 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; - } - - // DELETE: api/ApiWithActions/5 - [HttpDelete] - public async Task> Delete(int id) - { - 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; - } - } -} diff --git a/Blog.Core/Controllers/PermissionController.cs b/Blog.Core/Controllers/PermissionController.cs deleted file mode 100644 index 6339c082..00000000 --- a/Blog.Core/Controllers/PermissionController.cs +++ /dev/null @@ -1,380 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.Common.Helper; -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 -{ - [Route("api/[controller]/[action]")] - [ApiController] - [Authorize(PermissionNames.Permission)] - public class PermissionController : ControllerBase - { - readonly IPermissionServices _permissionServices; - readonly IModuleServices _moduleServices; - readonly IRoleModulePermissionServices _roleModulePermissionServices; - readonly IUserRoleServices _userRoleServices; - - /// - /// 构造函数 - /// - /// - /// - /// - /// - public PermissionController(IPermissionServices permissionServices, IModuleServices moduleServices, IRoleModulePermissionServices roleModulePermissionServices, IUserRoleServices userRoleServices) - { - _permissionServices = permissionServices; - _moduleServices = moduleServices; - _roleModulePermissionServices = roleModulePermissionServices; - _userRoleServices = userRoleServices; - - } - - // GET: api/User - [HttpGet] - public async Task>> Get(int page = 1, string key = "") - { - var data = new MessageModel>(); - int intTotalCount = 50; - int totalCount = 0; - int pageCount = 1; - - 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(); - var apis = await _moduleServices.Query(d => d.IsDeleted == false); - - foreach (var item in permissions) - { - List pidarr = new List(); - pidarr.Add(item.Pid); - if (item.Pid > 0) - { - pidarr.Add(0); - } - var parent = permissions.FirstOrDefault(d => d.Id == item.Pid); - - while (parent != null) - { - pidarr.Add(parent.Id); - parent = permissions.FirstOrDefault(d => d.Id == parent.Pid); - } - - - item.PidArr = pidarr.OrderBy(d => d).Distinct().ToList(); - foreach (var pid in item.PidArr) - { - var per = permissions.FirstOrDefault(d => d.Id == pid); - item.PnameArr.Add((per != null ? per.Name : "根节点") + "/"); - //var par = Permissions.Where(d => d.Pid == item.Id ).ToList(); - //item.PCodeArr.Add((per != null ? $"/{per.Code}/{item.Code}" : "")); - //if (par.Count == 0 && item.Pid == 0) - //{ - // item.PCodeArr.Add($"/{item.Code}"); - //} - } - - item.MName = apis.FirstOrDefault(d => d.Id == item.Mid)?.LinkUrl; - } - - return new MessageModel>() - { - msg = "获取成功", - success = totalCount >= 0, - response = new PageModel() - { - page = page, - pageCount = pageCount, - dataCount = totalCount, - data = permissions, - } - }; - - } - - // GET: api/User/5 - [HttpGet("{id}")] - public string Get(string id) - { - return "value"; - } - - // POST: api/User - [HttpPost] - public async Task> Post([FromBody] Permission permission) - { - var data = new MessageModel(); - - var id = (await _permissionServices.Add(permission)); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } - - return data; - } - - - [HttpPost] - public async Task> Assign([FromBody] AssignView assignView) - { - var data = new MessageModel(); - - try - { - if (assignView.rid > 0) - { - - data.success = true; - - var roleModulePermissions = 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 |= await _roleModulePermissionServices.DeleteByIds(remove.ToArray()); - - foreach (var item in assignView.pids) - { - var rmpitem = roleModulePermissions.Where(d => d.PermissionId == item); - if (!rmpitem.Any()) - { - var moduleid = (await _permissionServices.Query(p => p.Id == item)).FirstOrDefault()?.Mid; - RoleModulePermission roleModulePermission = new RoleModulePermission() - { - IsDeleted = false, - RoleId = assignView.rid, - ModuleId = moduleid.ObjToInt(), - PermissionId = item, - }; - - data.success |= (await _roleModulePermissionServices.Add(roleModulePermission)) > 0; - - } - } - - if (data.success) - { - data.response = ""; - data.msg = "保存成功"; - } - - } - } - catch (Exception) - { - data.success = false; - } - - return data; - } - - [HttpGet] - public async Task> GetPermissionTree(int pid = 0, bool needbtn = false) - { - var data = new MessageModel(); - - var permissions = await _permissionServices.Query(d => d.IsDeleted == false); - var permissionTrees = (from child in permissions - where child.IsDeleted == false - orderby child.Id - select new PermissionTree - { - value = child.Id, - label = child.Name, - Pid = child.Pid, - isbtn = child.IsButton, - order = child.OrderSort, - }).ToList(); - PermissionTree rootRoot = new PermissionTree(); - rootRoot.value = 0; - rootRoot.Pid = 0; - rootRoot.label = "根节点"; - - permissionTrees = permissionTrees.OrderBy(d => d.order).ToList(); - - - RecursionHelper.LoopToAppendChildren(permissionTrees, rootRoot, pid, needbtn); - - data.success = true; - if (data.success) - { - data.response = rootRoot; - data.msg = "获取成功"; - } - - return data; - } - - - [HttpGet] - [AllowAnonymous] - public async Task> GetNavigationBar(int uid) - { - var data = new MessageModel(); - - if (uid > 0) - { - var roleId = ((await _userRoleServices.Query(d => d.IsDeleted == false && d.UserId == uid)).FirstOrDefault()?.RoleId).ObjToInt(); - if (roleId > 0) - { - var pids = (await _roleModulePermissionServices.Query(d => d.IsDeleted == false && d.RoleId == roleId)).Select(d => d.PermissionId.ObjToInt()).Distinct(); - - if (pids.Any()) - { - var rolePermissionMoudles = (await _permissionServices.Query(d => pids.Contains(d.Id) && d.IsButton == false)).OrderBy(c => c.OrderSort); - var permissionTrees = (from child in rolePermissionMoudles - where child.IsDeleted == false - orderby child.Id - select new NavigationBar - { - id = child.Id, - name = child.Name, - pid = child.Pid, - order = child.OrderSort, - path = child.Code, - iconCls = child.Icon, - meta = new NavigationBarMeta - { - requireAuth = true, - title = child.Name, - } - }).ToList(); - - - NavigationBar rootRoot = new NavigationBar() - { - id = 0, - pid = 0, - order = 0, - name = "根节点", - path = "", - iconCls = "", - meta = new NavigationBarMeta(), - - }; - - permissionTrees = permissionTrees.OrderBy(d => d.order).ToList(); - - RecursionHelper.LoopNaviBarAppendChildren(permissionTrees, rootRoot); - - data.success = true; - if (data.success) - { - data.response = rootRoot; - data.msg = "获取成功"; - } - } - } - } - return data; - } - - - [HttpGet] - [AllowAnonymous] - public async Task> GetPermissionIdByRoleId(int rid = 0) - { - var data = new MessageModel(); - - var rmps = await _roleModulePermissionServices.Query(d => d.IsDeleted == false && d.RoleId == rid); - var permissionTrees = (from child in rmps - orderby child.Id - select child.PermissionId.ObjToInt()).ToList(); - - var permissions = await _permissionServices.Query(d => d.IsDeleted == false); - List assignbtns = new List(); - - foreach (var item in permissionTrees) - { - var pername = permissions.FirstOrDefault(d => d.IsButton && d.Id == item)?.Name; - if (!string.IsNullOrEmpty(pername)) - { - assignbtns.Add(pername + "_" + item); - } - } - - data.success = true; - if (data.success) - { - data.response = new AssignShow() - { - permissionids = permissionTrees, - assignbtns = assignbtns, - }; - data.msg = "获取成功"; - } - - return data; - } - - - // PUT: api/User/5 - [HttpPut] - public async Task> Put([FromBody] Permission permission) - { - var data = new MessageModel(); - if (permission != null && permission.Id > 0) - { - data.success = await _permissionServices.Update(permission); - if (data.success) - { - data.msg = "更新成功"; - data.response = permission?.Id.ObjToString(); - } - } - - return data; - } - - // DELETE: api/ApiWithActions/5 - [HttpDelete] - public async Task> Delete(int id) - { - var data = new MessageModel(); - if (id > 0) - { - var userDetail = await _permissionServices.QueryById(id); - userDetail.IsDeleted = true; - data.success = await _permissionServices.Update(userDetail); - if (data.success) - { - data.msg = "删除成功"; - data.response = userDetail?.Id.ObjToString(); - } - } - - return data; - } - } - - public class AssignView - { - public List pids { get; set; } - public int rid { get; set; } - } - public class AssignShow - { - public List permissionids { get; set; } - public List assignbtns { get; set; } - } - -} diff --git a/Blog.Core/Controllers/RoleController.cs b/Blog.Core/Controllers/RoleController.cs deleted file mode 100644 index 22ba36bd..00000000 --- a/Blog.Core/Controllers/RoleController.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -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 -{ - [Route("api/[controller]/[action]")] - [ApiController] - [Authorize(PermissionNames.Permission)] - public class RoleController : ControllerBase - { - readonly IRoleServices _roleServices; - - /// - /// 构造函数 - /// - /// - public RoleController(IRoleServices roleServices ) - { - _roleServices = roleServices; - } - - // GET: api/User - [HttpGet] - public async Task>> Get(int page = 1, string key = "") - { - var data = new MessageModel>(); - int intTotalCount = 50; - int totalCount = 0; - int pageCount = 1; - - var roles = await _roleServices.Query(a => a.IsDeleted != true ); - - if (!string.IsNullOrEmpty(key)) - { - roles = roles.Where(t => (t.Name != null && t.Name.Contains(key))).ToList(); - } - - - //筛选后的数据总数 - totalCount = roles.Count; - //筛选后的总页数 - pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intTotalCount.ObjToDecimal())).ObjToInt(); - - roles = roles.OrderByDescending(d => d.Id).Skip((page - 1) * intTotalCount).Take(intTotalCount).ToList(); - - return new MessageModel>() - { - msg = "获取成功", - success = totalCount >= 0, - response = new PageModel() - { - page = page, - pageCount = pageCount, - dataCount = totalCount, - data = roles, - } - }; - - } - - // GET: api/User/5 - [HttpGet("{id}")] - public string Get(string id) - { - return "value"; - } - - // POST: api/User - [HttpPost] - public async Task> Post([FromBody] Role role) - { - var data = new MessageModel(); - - var id = (await _roleServices.Add(role)); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } - - return data; - } - - // PUT: api/User/5 - [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; - } - - // DELETE: api/ApiWithActions/5 - [HttpDelete] - public async Task> Delete(int 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; - } - } -} diff --git a/Blog.Core/Controllers/UserController.cs b/Blog.Core/Controllers/UserController.cs deleted file mode 100644 index 6b544895..00000000 --- a/Blog.Core/Controllers/UserController.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Blog.Core.AuthHelper.OverWrite; -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 -{ - [Route("api/[controller]/[action]")] - [ApiController] - [Authorize(PermissionNames.Permission)] - public class UserController : ControllerBase - { - readonly ISysUserInfoServices _sysUserInfoServices; - readonly IUserRoleServices _userRoleServices; - readonly IRoleServices _roleServices; - - /// - /// 构造函数 - /// - /// - /// - /// - public UserController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IRoleServices roleServices) - { - _sysUserInfoServices = sysUserInfoServices; - _userRoleServices = userRoleServices; - _roleServices = roleServices; - } - - // GET: api/User - [HttpGet] - public async Task>> Get(int page = 1, string key = "") - { - var data = new MessageModel>(); - int intTotalCount = 50; - int totalCount = 0; - int pageCount = 1; - List sysUserInfos = new List(); - - sysUserInfos = await _sysUserInfoServices.Query(a => a.tdIsDelete != true && a.uStatus >= 0); - - if (!string.IsNullOrEmpty(key)) - { - sysUserInfos = sysUserInfos.Where(t => (t.uLoginName != null && t.uLoginName.Contains(key)) || (t.uRealName != null && t.uRealName.Contains(key))).ToList(); - } - - - //筛选后的数据总数 - totalCount = sysUserInfos.Count; - //筛选后的总页数 - pageCount = (Math.Ceiling(totalCount.ObjToDecimal() / intTotalCount.ObjToDecimal())).ObjToInt(); - - sysUserInfos = sysUserInfos.OrderByDescending(d => d.uID).Skip((page - 1) * intTotalCount).Take(intTotalCount).ToList(); - - - var allUserRoles = await _userRoleServices.Query(d => d.IsDeleted == false); - var allRoles = await _roleServices.Query(d => d.IsDeleted == false); - foreach (var item in sysUserInfos) - { - item.uLoginPWD = "no see me"; - item.RID = (allUserRoles.FirstOrDefault(d => d.UserId == item.uID)?.RoleId).ObjToInt(); - item.RoleName = allRoles.FirstOrDefault(d => d.Id==item.RID)?.Name; - } - - return new MessageModel>() - { - msg = "获取成功", - success = totalCount >= 0, - response = new PageModel() - { - page = page, - pageCount = pageCount, - dataCount = totalCount, - data = sysUserInfos, - } - }; - - } - - // GET: api/User/5 - [HttpGet("{id}")] - public string Get(string id) - { - return "value"; - } - - // GET: api/User/5 - /// - /// 获取用户详情根据token - /// - /// 令牌 - /// - [HttpGet] - [AllowAnonymous] - public async Task> GetInfoByToken(string token) - { - var data = new MessageModel(); - if (!string.IsNullOrEmpty(token)) - { - var tokenModel = JwtHelper.SerializeJwt(token); - if (tokenModel != null && tokenModel.Uid > 0) - { - var userinfo = await _sysUserInfoServices.QueryById(tokenModel.Uid); - if (userinfo != null) - { - data.response = userinfo; - data.success = true; - data.msg = "获取成功"; - } - } - - } - return data; - } - - // POST: api/User - [HttpPost] - public async Task> Post([FromBody] sysUserInfo sysUserInfo) - { - var data = new MessageModel(); - - var id = await _sysUserInfoServices.Add(sysUserInfo); - data.success = id > 0; - if (data.success) - { - data.response = id.ObjToString(); - data.msg = "添加成功"; - } - - return data; - } - - // PUT: api/User/5 - [HttpPut] - public async Task> Put([FromBody] sysUserInfo sysUserInfo) - { - var data = new MessageModel(); - if (sysUserInfo != null && sysUserInfo.uID > 0) - { - if (sysUserInfo.RID > 0) - { - var usrerole = await _userRoleServices.Query(d => d.UserId == sysUserInfo.uID && d.RoleId == sysUserInfo.RID); - if (usrerole.Count == 0) - { - await _userRoleServices.Add(new UserRole(sysUserInfo.uID, sysUserInfo.RID)); - } - } - - data.success = await _sysUserInfoServices.Update(sysUserInfo); - if (data.success) - { - data.msg = "更新成功"; - data.response = sysUserInfo?.uID.ObjToString(); - } - } - - return data; - } - - // DELETE: api/ApiWithActions/5 - [HttpDelete] - public async Task> Delete(int id) - { - var data = new MessageModel(); - if (id > 0) - { - var userDetail = await _sysUserInfoServices.QueryById(id); - userDetail.tdIsDelete = true; - data.success = await _sysUserInfoServices.Update(userDetail); - if (data.success) - { - data.msg = "删除成功"; - data.response = userDetail?.uID.ObjToString(); - } - } - - return data; - } - } -} diff --git a/Blog.Core/Controllers/UserRoleController.cs b/Blog.Core/Controllers/UserRoleController.cs deleted file mode 100644 index 8b1129e0..00000000 --- a/Blog.Core/Controllers/UserRoleController.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Threading.Tasks; -using Blog.Core.IServices; -using Microsoft.AspNetCore.Mvc; - -namespace Blog.Core.Controllers -{ - /// - /// 用户权限控制器所有接口 - /// - //[Authorize(PermissionNames.Permission)] - [Produces("application/json")] - [Route("api/[controller]/[action]")] - [ApiController] - public class UserRoleController : Controller - { - readonly ISysUserInfoServices _sysUserInfoServices; - readonly IUserRoleServices _userRoleServices; - readonly IRoleServices _roleServices; - - /// - /// 构造函数 - /// - /// - /// - /// - public UserRoleController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IRoleServices roleServices) - { - this._sysUserInfoServices = sysUserInfoServices; - this._userRoleServices = userRoleServices; - this._roleServices = roleServices; - } - - - - /// - /// 新建用户 - /// - /// - /// - /// - [HttpGet] - public async Task AddUser(string loginName, string loginPwd) - { - var model = await _sysUserInfoServices.SaveUserInfo(loginName, loginPwd); - return Ok(new - { - success = true, - data = model - }); - } - - /// - /// 新建Role - /// - /// - /// - [HttpGet] - public async Task AddRole(string roleName) - { - var model = await _roleServices.SaveRole(roleName); - return Ok(new - { - success = true, - data = model - }); - } - - /// - /// 新建用户角色关系 - /// - /// - /// - /// - [HttpGet] - public async Task AddUserRole(int uid, int rid) - { - var model = await _userRoleServices.SaveUserRole(uid, rid); - return Ok(new - { - success = true, - data = model - }); - } - - - - - } -} diff --git a/Blog.Core/Controllers/ValuesController.cs b/Blog.Core/Controllers/ValuesController.cs deleted file mode 100644 index b62e1928..00000000 --- a/Blog.Core/Controllers/ValuesController.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Threading.Tasks; -using AutoMapper; -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 -{ - /// - /// Values控制器 - /// - [Route("api/[controller]")] - [ApiController] - //[Authorize] - //[Authorize(Roles = "Admin,Client")] - //[Authorize(Policy = "SystemOrAdmin")] - //[Authorize(PermissionNames.Permission)] - public class ValuesController : ControllerBase - { - private IMapper _mapper; - private readonly IAdvertisementServices _advertisementServices; - private readonly Love _love; - readonly IRoleModulePermissionServices _roleModulePermissionServices; - - /// - /// ValuesController - /// - /// - /// - /// - /// - public ValuesController(IMapper mapper, IAdvertisementServices advertisementServices, Love love, IRoleModulePermissionServices roleModulePermissionServices) - { - // 测试 Authorize 和 mapper - _mapper = mapper; - _advertisementServices = advertisementServices; - _love = love; - _roleModulePermissionServices = roleModulePermissionServices; - } - /// - /// Get方法 - /// - /// - // GET api/values - [HttpGet] - [AllowAnonymous] - public async Task> Get() - { - var data = new MessageModel(); - - var list = await _roleModulePermissionServices.TestModelWithChildren(); - - - _advertisementServices.ReturnExp(); - - Love love = null; - love.SayLoveU(); - - return data; - } - /// - /// Get(int id)方法 - /// - /// - /// - // GET api/values/5 - [HttpGet("{id}")] - [AllowAnonymous] - public ActionResult Get(int id) - { - var loveu = _love.SayLoveU(); - - return "value"; - } - - /// - /// 参数必填项 - /// - /// - /// - [HttpGet] - [Route("/api/values/RequiredPara")] - public string RequiredP([Required]string id) - { - return id; - } - - - - /// - /// post - /// - /// model实体类参数 - [HttpPost] - [AllowAnonymous] - public object Post([FromBody] BlogArticle blogArticle) - { - return Ok(new { success = true, data = blogArticle }); - } - /// - /// Put方法 - /// - /// - /// - // PUT api/values/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) - { - } - /// - /// Delete方法 - /// - /// - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - } -} diff --git a/Blog.Core/Filter/GlobalExceptionFilter.cs b/Blog.Core/Filter/GlobalExceptionFilter.cs deleted file mode 100644 index a8625ae3..00000000 --- a/Blog.Core/Filter/GlobalExceptionFilter.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Blog.Core.Log; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using StackExchange.Profiling; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace Blog.Core.Filter -{ - /// - /// 全局异常错误日志 - /// - public class GlobalExceptionsFilter : IExceptionFilter - { - private readonly IHostingEnvironment _env; - private readonly ILoggerHelper _loggerHelper; - public GlobalExceptionsFilter(IHostingEnvironment env, ILoggerHelper loggerHelper) - { - _env = env; - _loggerHelper = loggerHelper; - } - public void OnException(ExceptionContext context) - { - var json = new JsonErrorResponse(); - - json.Message = context.Exception.Message;//错误信息 - if (_env.IsDevelopment()) - { - json.DevelopmentMessage = context.Exception.StackTrace;//堆栈信息 - } - context.Result = new InternalServerErrorObjectResult(json); - - MiniProfiler.Current.CustomTiming("Errors:", json.Message); - - - //采用log4net 进行错误日志记录 - _loggerHelper.Error(json.Message, WriteLog(json.Message, context.Exception)); - - } - - /// - /// 自定义返回格式 - /// - /// - /// - /// - public string WriteLog(string throwMsg, Exception ex) - { - return string.Format("【自定义错误】:{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) - { - StatusCode = StatusCodes.Status500InternalServerError; - } - } - //返回错误信息 - public class JsonErrorResponse - { - /// - /// 生产环境的消息 - /// - public string Message { get; set; } - /// - /// 开发环境的消息 - /// - public string DevelopmentMessage { get; set; } - } - -} diff --git a/Blog.Core/Log4net.config b/Blog.Core/Log4net.config deleted file mode 100644 index ea15839f..00000000 --- a/Blog.Core/Log4net.config +++ /dev/null @@ -1,38 +0,0 @@ - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Blog.Core/LogHelper/ILoggerHelper.cs b/Blog.Core/LogHelper/ILoggerHelper.cs deleted file mode 100644 index f395174c..00000000 --- a/Blog.Core/LogHelper/ILoggerHelper.cs +++ /dev/null @@ -1,157 +0,0 @@ -using log4net; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Blog.Core.Log -{ - /// - /// 日志接口 - /// - public interface ILoggerHelper - { - - /// - /// 调试信息 - /// - /// source - /// message - void Debug(object source, string message); - /// - /// 调试信息 - /// - /// source - /// message - /// ps - void Debug(object source, string message, params object[] ps); - /// - /// 调试信息 - /// - /// source - /// message - void Debug(Type source, string message); - /// - /// 关键信息 - /// - /// source - /// message - void Info(object source, object message); - /// - /// 关键信息 - /// - /// source - /// message - void Info(Type source, object message); - /// - /// 警告信息 - /// - /// source - /// message - void Warn(object source, object message); - /// - /// 警告信息 - /// - /// source - /// message - void Warn(Type source, object message); - /// - /// 错误信息 - /// - /// source - /// message - void Error(object source, object message); - /// - /// 错误信息 - /// - /// source - /// message - void Error(Type source, object message); - /// - /// 失败信息 - /// - /// source - /// message - void Fatal(object source, object message); - /// - /// 失败信息 - /// - /// source - /// message - void Fatal(Type source, object message); - - /* Log a message object and exception */ - - /// - /// 调试信息 - /// - /// source - /// message - /// ex - void Debug(object source, object message, Exception exception); - /// - /// 调试信息 - /// - /// source - /// message - /// ex - void Debug(Type source, object message, Exception exception); - /// - /// 关键信息 - /// - /// source - /// message - /// ex - void Info(object source, object message, Exception exception); - /// - /// 关键信息 - /// - /// source - /// message - /// ex - void Info(Type source, object message, Exception exception); - /// - /// 警告信息 - /// - /// source - /// message - /// ex - void Warn(object source, object message, Exception exception); - /// - /// 警告信息 - /// - /// source - /// message - /// ex - void Warn(Type source, object message, Exception exception); - /// - /// 错误信息 - /// - /// source - /// message - /// ex - void Error(object source, object message, Exception exception); - /// - /// 错误信息 - /// - /// source - /// message - /// ex - void Error(Type source, object message, Exception exception); - /// - /// 失败信息 - /// - /// source - /// message - /// ex - void Fatal(object source, object message, Exception exception); - /// - /// 失败信息 - /// - /// source - /// message - /// ex - void Fatal(Type source, object message, Exception exception); - } - -} diff --git a/Blog.Core/LogHelper/LogHelper.cs b/Blog.Core/LogHelper/LogHelper.cs deleted file mode 100644 index 4d6ee953..00000000 --- a/Blog.Core/LogHelper/LogHelper.cs +++ /dev/null @@ -1,283 +0,0 @@ -using log4net; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Blog.Core.Log -{ - /// - /// - /// - public class LogHelper : ILoggerHelper - { - private readonly ConcurrentDictionary Loggers = new ConcurrentDictionary(); - - /// - /// 获取记录器 - /// - /// soruce - /// - private ILog GetLogger(Type source) - { - if (Loggers.ContainsKey(source)) - { - return Loggers[source]; - } - else - { - ILog logger = LogManager.GetLogger(Startup.repository.Name, source); - Loggers.TryAdd(source, logger); - return logger; - } - } - - /* Log a message object */ - - /// - /// 调试信息 - /// - /// source - /// message - public void Debug(object source, string message) - { - Debug(source.GetType(), message); - } - - /// - /// 调试信息 - /// - /// source - /// message - /// ps - public void Debug(object source, string message, params object[] ps) - { - Debug(source.GetType(), string.Format(message, ps)); - } - - /// - /// 调试信息 - /// - /// source - /// message - public void Debug(Type source, string message) - { - ILog logger = GetLogger(source); - if (logger.IsDebugEnabled) - { - logger.Debug(message); - } - } - - /// - /// 关键信息 - /// - /// source - /// message - public void Info(object source, object message) - { - Info(source.GetType(), message); - } - - /// - /// 关键信息 - /// - /// source - /// message - public void Info(Type source, object message) - { - ILog logger = GetLogger(source); - if (logger.IsInfoEnabled) - { - logger.Info(message); - } - } - - /// - /// 警告信息 - /// - /// source - /// message - public void Warn(object source, object message) - { - Warn(source.GetType(), message); - } - - /// - /// 警告信息 - /// - /// source - /// message - public void Warn(Type source, object message) - { - ILog logger = GetLogger(source); - if (logger.IsWarnEnabled) - { - logger.Warn(message); - } - } - - /// - /// 错误信息 - /// - /// source - /// message - public void Error(object source, object message) - { - Error(source.GetType(), message); - } - - /// - /// 错误信息 - /// - /// source - /// message - public void Error(Type source, object message) - { - ILog logger = GetLogger(source); - if (logger.IsErrorEnabled) - { - logger.Error(message); - } - } - - /// - /// 失败信息 - /// - /// source - /// message - public void Fatal(object source, object message) - { - Fatal(source.GetType(), message); - } - - /// - /// 失败信息 - /// - /// source - /// message - public void Fatal(Type source, object message) - { - ILog logger = GetLogger(source); - if (logger.IsFatalEnabled) - { - logger.Fatal(message); - } - } - - /* Log a message object and exception */ - - /// - /// 调试信息 - /// - /// source - /// message - /// ex - public void Debug(object source, object message, Exception exception) - { - Debug(source.GetType(), message, exception); - } - - /// - /// 调试信息 - /// - /// source - /// message - /// ex - public void Debug(Type source, object message, Exception exception) - { - GetLogger(source).Debug(message, exception); - } - - /// - /// 关键信息 - /// - /// source - /// message - /// ex - public void Info(object source, object message, Exception exception) - { - Info(source.GetType(), message, exception); - } - - /// - /// 关键信息 - /// - /// source - /// message - /// ex - public void Info(Type source, object message, Exception exception) - { - GetLogger(source).Info(message, exception); - } - - /// - /// 警告信息 - /// - /// source - /// message - /// ex - public void Warn(object source, object message, Exception exception) - { - Warn(source.GetType(), message, exception); - } - - /// - /// 警告信息 - /// - /// source - /// message - /// ex - public void Warn(Type source, object message, Exception exception) - { - GetLogger(source).Warn(message, exception); - } - - /// - /// 错误信息 - /// - /// source - /// message - /// ex - public void Error(object source, object message, Exception exception) - { - Error(source.GetType(), message, exception); - } - - /// - /// 错误信息 - /// - /// source - /// message - /// ex - public void Error(Type source, object message, Exception exception) - { - GetLogger(source).Error(message, exception); - } - - /// - /// 失败信息 - /// - /// source - /// message - /// ex - public void Fatal(object source, object message, Exception exception) - { - Fatal(source.GetType(), message, exception); - } - - /// - /// 失败信息 - /// - /// source - /// message - /// ex - public void Fatal(Type source, object message, Exception exception) - { - GetLogger(source).Fatal(message, exception); - } - - - } -} diff --git a/Blog.Core/Program.cs b/Blog.Core/Program.cs deleted file mode 100644 index f698deb3..00000000 --- a/Blog.Core/Program.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Blog.Core.Model.Models; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Blog.Core -{ - public class Program - { - public static void Main(string[] args) - { - // 生成承载 web 应用程序的 Microsoft.AspNetCore.Hosting.IWebHost。Build是WebHostBuilder最终的目的,将返回一个构造的WebHost,最终生成宿主。 - var host = CreateWebHostBuilder(args).Build(); - - // 创建可用于解析作用域服务的新 Microsoft.Extensions.DependencyInjection.IServiceScope。 - using (var scope = host.Services.CreateScope()) - { - var services = scope.ServiceProvider; - var loggerFactory = services.GetRequiredService(); - - try - { - // 从 system.IServicec提供程序获取 T 类型的服务。 - var myContext = services.GetRequiredService(); - // 为了大家的数据安全,这里先注释掉了,大家自己先测试玩一玩吧。 - // 数据库连接字符串是在 Model 层的 Seed 文件夹下的 MyContext.cs 中 - //DBSeed.SeedAsync(myContext).Wait(); - } - catch (Exception e) - { - var logger = loggerFactory.CreateLogger(); - logger.LogError(e, "Error occured seeding the Database."); - throw; - } - } - - // 运行 web 应用程序并阻止调用线程, 直到主机关闭。 - // 创建完 WebHost 之后,便调用它的 Run 方法,而 Run 方法会去调用 WebHost 的 StartAsync 方法 - // 将Initialize方法创建的Application管道传入以供处理消息 - // 执行HostedServiceExecutor.StartAsync方法 - host.Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - //使用预配置的默认值初始化 Microsoft.AspNetCore.Hosting.WebHostBuilder 类的新实例。 - WebHost.CreateDefaultBuilder(args) - //指定要由 web 主机使用的启动类型。相当于注册了一个IStartup服务。可以自定义启动服务,比如.UseStartup(typeof(StartupDevelopment).GetTypeInfo().Assembly.FullName) - //.UseUrls("http://localhost:5012") - .UseStartup(); - } -} diff --git a/Blog.Core/Startup.cs b/Blog.Core/Startup.cs deleted file mode 100644 index 4d45a932..00000000 --- a/Blog.Core/Startup.cs +++ /dev/null @@ -1,484 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.Claims; -using System.Text; -using System.Threading.Tasks; -using Autofac; -using Autofac.Extensions.DependencyInjection; -using Autofac.Extras.DynamicProxy; -using AutoMapper; -using Blog.Core.AOP; -using Blog.Core.AuthHelper; -using Blog.Core.Common; -using Blog.Core.Filter; -using Blog.Core.Log; -using Blog.Core.Model; -using log4net; -using log4net.Config; -using log4net.Repository; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json.Serialization; -using StackExchange.Profiling.Storage; -using Swashbuckle.AspNetCore.Swagger; -using static Blog.Core.SwaggerHelper.CustomApiVersion; - -namespace Blog.Core -{ - public class Startup - { - - /// - /// log4net 仓储库 - /// - public static ILoggerRepository repository { get; set; } - - public Startup(IConfiguration configuration) - { - Configuration = configuration; - //log4net - repository = LogManager.CreateRepository("Blog.Core"); - //指定配置文件,如果这里你遇到问题,应该是使用了InProcess模式,请查看Blog.Core.csproj,并删之 - XmlConfigurator.Configure(repository, new FileInfo("log4net.config")); - - } - - public IConfiguration Configuration { get; } - private const string ApiName = "Blog.Core"; - - // This method gets called by the runtime. Use this method to add services to the container. - public IServiceProvider ConfigureServices(IServiceCollection services) - { - #region 部分服务注入-netcore自带方法 - //缓存注入 - services.AddScoped(); - services.AddSingleton(factory => - { - var cache = new MemoryCache(new MemoryCacheOptions()); - return cache; - }); - //Redis注入 - services.AddSingleton(); - //log日志注入 - services.AddSingleton(); - #endregion - - #region 初始化DB - services.AddScoped(); - services.AddScoped(); - #endregion - - #region Automapper - services.AddAutoMapper(typeof(Startup)); - #endregion - - #region CORS - //跨域第二种方法,声明策略,记得下边app中配置 - services.AddCors(c => - { - //↓↓↓↓↓↓↓注意正式环境不要使用这种全开放的处理↓↓↓↓↓↓↓↓↓↓ - c.AddPolicy("AllRequests", policy => - { - policy - .AllowAnyOrigin()//允许任何源 - .AllowAnyMethod()//允许任何方式 - .AllowAnyHeader()//允许任何头 - .AllowCredentials();//允许cookie - }); - //↑↑↑↑↑↑↑注意正式环境不要使用这种全开放的处理↑↑↑↑↑↑↑↑↑↑ - - - //一般采用这种方法 - c.AddPolicy("LimitRequests", policy => - { - policy - .WithOrigins("http://127.0.0.1:1818", "http://localhost:8080", "http://localhost:8021", "http://localhost:8081", "http://localhost:1818")//支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的 - .AllowAnyHeader()//Ensures that the policy allows any header. - .AllowAnyMethod(); - }); - }); - - //跨域第一种办法,注意下边 Configure 中进行配置 - //services.AddCors(); - #endregion - - #region MiniProfiler - - services.AddMiniProfiler(options => - { - options.RouteBasePath = "/profiler"; - (options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10); - - } - ); - - #endregion - - #region Swagger UI Service - - var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; - services.AddSwaggerGen(c => - { - //遍历出全部的版本,做文档信息展示 - typeof(ApiVersions).GetEnumNames().ToList().ForEach(version => - { - c.SwaggerDoc(version, new Info - { - // {ApiName} 定义成全局变量,方便修改 - Version = version, - Title = $"{ApiName} 接口文档", - Description = $"{ApiName} HTTP API " + version, - TermsOfService = "None", - Contact = new Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" } - }); - // 按相对路径排序,作者:Alby - c.OrderActionsBy(o => o.RelativePath); - }); - - - //就是这里 - var xmlPath = Path.Combine(basePath, "Blog.Core.xml");//这个就是刚刚配置的xml文件名 - c.IncludeXmlComments(xmlPath, true);//默认的第二个参数是false,这个是controller的注释,记得修改 - - var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");//这个就是Model层的xml文件名 - c.IncludeXmlComments(xmlModelPath); - - #region Token绑定到ConfigureServices - - //添加header验证信息 - //c.OperationFilter(); - - // 发行人 - var IssuerName = (Configuration.GetSection("Audience"))["Issuer"]; - var security = new Dictionary> { { IssuerName, new string[] { } }, }; - c.AddSecurityRequirement(security); - - //方案名称“Blog.Core”可自定义,上下一致即可 - c.AddSecurityDefinition(IssuerName, new ApiKeyScheme - { - Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", - Name = "Authorization",//jwt默认的参数名称 - In = "header",//jwt默认存放Authorization信息的位置(请求头中) - Type = "apiKey" - }); - #endregion - }); - - #endregion - - #region MVC + GlobalExceptions - - //注入全局异常捕获 - services.AddMvc(o => - { - // 全局异常过滤 - o.Filters.Add(typeof(GlobalExceptionsFilter)); - }) - .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) - // 取消默认驼峰 - .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver(); }); - - #endregion - - #region Authorize权限设置三种情况 - - //使用说明: - //如果你只是简单的基于角色授权的,第一步:【1/2 简单角色授权】,第二步:配置【统一认证】,第三步:开启中间件app.UseMiddleware()不能验证过期,或者 app.UseAuthentication();可以验证过期时间 - //如果你是用的复杂的策略授权,配置权限在数据库,第一步:【3复杂策略授权】,第二步:配置【统一认证】,第三步:开启中间件app.UseAuthentication(); - //综上所述,设置权限,必须要三步走,涉及授权策略 + 配置认证 + 开启授权中间件,只不过自定义的中间件不能验证过期时间,所以我都是用官方的。 - - #region 【1/2、简单角色授权】 - #region 1、基于角色的API授权 - - // 1【授权】、这个很简单,其他什么都不用做, - // 无需配置服务,只需要在API层的controller上边,增加特性即可,注意,只能是角色的: - // [Authorize(Roles = "Admin")] - - // 2【认证】、然后在下边的configure里,配置中间件即可:app.UseMiddleware();但是这个方法,无法验证过期时间,所以如果需要验证过期时间,还是需要下边的第三种方法,官方认证 - - #endregion - - #region 2、基于策略的授权(简单版) - - // 1【授权】、这个和上边的异曲同工,好处就是不用在controller中,写多个 roles 。 - // 然后这么写 [Authorize(Policy = "Admin")] - services.AddAuthorization(options => - { - options.AddPolicy("Client", policy => policy.RequireRole("Client").Build()); - options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build()); - options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System")); - }); - - - // 2【认证】、然后在下边的configure里,配置中间件即可:app.UseMiddleware();但是这个方法,无法验证过期时间,所以如果需要验证过期时间,还是需要下边的第三种方法,官方认证 - #endregion - #endregion - - - #region 【3、复杂策略授权】 - - #region 参数 - //读取配置文件 - var audienceConfig = Configuration.GetSection("Audience"); - var symmetricKeyAsBase64 = audienceConfig["Secret"]; - var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); - var signingKey = new SymmetricSecurityKey(keyByteArray); - - - var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); - - // 如果要数据库动态绑定,这里先留个空,后边处理器里动态赋值 - var permission = new List(); - - // 角色与接口的权限要求参数 - var permissionRequirement = new PermissionRequirement( - "/api/denied",// 拒绝授权的跳转地址(目前无用) - permission, - ClaimTypes.Role,//基于角色的授权 - audienceConfig["Issuer"],//发行人 - audienceConfig["Audience"],//听众 - signingCredentials,//签名凭据 - expiration: TimeSpan.FromSeconds(60*5)//接口的过期时间 - ); - #endregion - - //【授权】 - services.AddAuthorization(options => - { - options.AddPolicy("Permission", - policy => policy.Requirements.Add(permissionRequirement)); - }); - - - #endregion - - - #region 【统一认证】 - // 令牌验证参数 - var tokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = signingKey, - ValidateIssuer = true, - ValidIssuer = audienceConfig["Issuer"],//发行人 - ValidateAudience = true, - ValidAudience = audienceConfig["Audience"],//订阅人 - ValidateLifetime = true, - ClockSkew = TimeSpan.FromSeconds(30), - RequireExpirationTime = true, - }; - - //2.1【认证】、core自带官方JWT认证 - services.AddAuthentication(x => - { - x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .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; - } - }; - }); - - - //2.2【认证】、IdentityServer4 认证 (暂时忽略) - //services.AddAuthentication("Bearer") - // .AddIdentityServerAuthentication(options => - // { - // options.Authority = "http://localhost:5002"; - // options.RequireHttpsMetadata = false; - // options.ApiName = "blog.core.api"; - // }); - // 注入权限处理器 - - services.AddSingleton(); - services.AddSingleton(permissionRequirement); - #endregion - - #endregion - - - #region AutoFac DI - //实例化 AutoFac 容器 - var builder = new ContainerBuilder(); - //注册要通过反射创建的组件 - //builder.RegisterType().As(); - builder.RegisterType();//可以直接替换其他拦截器 - builder.RegisterType();//可以直接替换其他拦截器 - builder.RegisterType();//这样可以注入第二个 - - // ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ - - #region 带有接口层的服务注入 - - #region Service.dll 注入,有对应接口 - //获取项目绝对路径,请注意,这个是实现类的dll文件,不是接口 IService.dll ,注入容器当然是Activatore - try - { - var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); - var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加载文件的方法 ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ - - //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。 - - - // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 - var cacheType = new List(); - if (Appsettings.app(new string[] { "AppSettings", "RedisCaching", "Enabled" }).ObjToBool()) - { - cacheType.Add(typeof(BlogRedisCacheAOP)); - } - if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) - { - cacheType.Add(typeof(BlogCacheAOP)); - } - if (Appsettings.app(new string[] { "AppSettings", "LogoAOP", "Enabled" }).ObjToBool()) - { - cacheType.Add(typeof(BlogLogAOP)); - } - - builder.RegisterAssemblyTypes(assemblysServices) - .AsImplementedInterfaces() - .InstancePerLifetimeScope() - .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; - // 如果你想注入两个,就这么写 InterceptedBy(typeof(BlogCacheAOP), typeof(BlogLogAOP)); - // 如果想使用Redis缓存,请必须开启 redis 服务,端口号我的是6319,如果不一样还是无效,否则请使用memory缓存 BlogCacheAOP - .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。 - #endregion - - #region Repository.dll 注入,有对应接口 - var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); - var assemblysRepository = Assembly.LoadFile(repositoryDllFile); - builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); - } - catch (Exception) - { - throw new Exception("※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,因为解耦了,如果你是发布的模式,请检查bin文件夹是否存在Repository.dll和service.dll ※※★※※"); - } - #endregion - #endregion - - - #region 没有接口层的服务层注入 - - ////因为没有接口层,所以不能实现解耦,只能用 Load 方法。 - ////var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); - ////builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); - - #endregion - - #region 没有接口的单独类 class 注入 - ////只能注入该类中的虚方法 - builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) - .EnableClassInterceptors() - .InterceptedBy(typeof(BlogLogAOP)); - - #endregion - - - //将services填充到Autofac容器生成器中 - builder.Populate(services); - - //使用已进行的组件登记创建新容器 - var ApplicationContainer = builder.Build(); - - #endregion - - return new AutofacServiceProvider(ApplicationContainer);//第三方IOC接管 core内置DI容器 - - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { - #region Environment - if (env.IsDevelopment()) - { - // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 - // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection - //app.UseHsts(); - - } - #endregion - - #region MiniProfiler - app.UseMiniProfiler(); - #endregion - - #region Swagger - app.UseSwagger(); - app.UseSwaggerUI(c => - { - //根据版本名称倒序 遍历展示 - typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => - { - c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); - }); - // 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:解决方案名.index.html - c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html");//这里是配合MiniProfiler进行性能监控的,《文章:完美基于AOP的接口性能分析》,如果你不需要,可以暂时先注释掉,不影响大局。 - c.RoutePrefix = ""; //路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉 - }); - #endregion - - #region Authen - - //此授权认证方法已经放弃,请使用下边的官方验证方法。但是如果你还想传User的全局变量,还是可以继续使用中间件 - //app.UseMiddleware(); - - //如果你想使用官方认证,必须在上边ConfigureService 中,配置JWT的认证服务 (.AddAuthentication 和 .AddJwtBearer 二者缺一不可) - app.UseAuthentication(); - #endregion - - #region CORS - //跨域第二种方法,使用策略,详细策略信息在ConfigureService中 - app.UseCors("LimitRequests");//将 CORS 中间件添加到 web 应用程序管线中, 以允许跨域请求。 - - - #region 跨域第一种版本 - //跨域第一种版本,请要ConfigureService中配置服务 services.AddCors(); - // app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader() - //.AllowAnyMethod()); - #endregion - - #endregion - - // 跳转https - app.UseHttpsRedirection(); - // 使用静态文件 - app.UseStaticFiles(); - // 使用cookie - app.UseCookiePolicy(); - // 返回错误码 - app.UseStatusCodePages();//把错误码返回前台,比如是404 - - app.UseMvc(); - } - - } -} diff --git a/Blog.Core/SwaggerHelper/CustomApiVersion.cs b/Blog.Core/SwaggerHelper/CustomApiVersion.cs deleted file mode 100644 index d82469b0..00000000 --- a/Blog.Core/SwaggerHelper/CustomApiVersion.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Blog.Core.SwaggerHelper -{ - /// - /// 自定义版本 - /// - public class CustomApiVersion - { - /// - /// Api接口版本 自定义 - /// - public enum ApiVersions - { - /// - /// V1 版本 - /// - V1 = 1, - /// - /// V2 版本 - /// - V2 = 2, - } - } -} diff --git a/Blog.Core/appsettings.Development.json b/Blog.Core/appsettings.Development.json deleted file mode 100644 index e203e940..00000000 --- a/Blog.Core/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/Blog.Core/appsettings.json b/Blog.Core/appsettings.json deleted file mode 100644 index 82ea0922..00000000 --- a/Blog.Core/appsettings.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "LogLevel": { - "Default": "Warning" - } - } - }, - "AllowedHosts": "*", - "AppSettings": { - "RedisCaching": { - "Enabled": false, - "ConnectionString": "127.0.0.1:6319" - }, - "MemoryCachingAOP": { - "Enabled": true - }, - "LogoAOP": { - "Enabled": false - }, - "SqlServer": { - "SqlServerConnection": "Server=.;Database=WMBlogDB;User ID=sa;Password=123;", //עҪѵǰappsetting.json ļ->Ҽ->->ƵĿ¼->ʼո - "ProviderName": "System.Data.SqlClient" - }, - "Date": "2018-08-28", - "Author": "Blog.Core" - }, - "Audience": { - "Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", - "Issuer": "Blog.Core", - "Audience": "wr" - } -} diff --git a/Blog.Core/wwwroot/Autho.jwt.rar b/Blog.Core/wwwroot/Autho.jwt.rar deleted file mode 100644 index 28e200fc..00000000 Binary files a/Blog.Core/wwwroot/Autho.jwt.rar and /dev/null differ diff --git a/Blog.Core/wwwroot/Blog.Core.Table&Data.sql b/Blog.Core/wwwroot/Blog.Core.Table&Data.sql deleted file mode 100644 index ae841584..00000000 Binary files a/Blog.Core/wwwroot/Blog.Core.Table&Data.sql and /dev/null differ diff --git a/Blog.Core/wwwroot/Blog.Core.Table.sql b/Blog.Core/wwwroot/Blog.Core.Table.sql deleted file mode 100644 index 9195839d..00000000 Binary files a/Blog.Core/wwwroot/Blog.Core.Table.sql and /dev/null differ diff --git a/Blog.Core/wwwroot/QQGroup.png b/Blog.Core/wwwroot/QQGroup.png deleted file mode 100644 index 413d7996..00000000 Binary files a/Blog.Core/wwwroot/QQGroup.png and /dev/null differ diff --git "a/Blog.Core/wwwroot/\345\212\250\350\275\257\344\273\243\347\240\201\347\224\237\346\210\220\351\241\271\347\233\256\351\252\250\346\236\266.rar" "b/Blog.Core/wwwroot/\345\212\250\350\275\257\344\273\243\347\240\201\347\224\237\346\210\220\351\241\271\347\233\256\351\252\250\346\236\266.rar" deleted file mode 100644 index 773f9914..00000000 Binary files "a/Blog.Core/wwwroot/\345\212\250\350\275\257\344\273\243\347\240\201\347\224\237\346\210\220\351\241\271\347\233\256\351\252\250\346\236\266.rar" and /dev/null differ diff --git "a/Blog.Core/wwwroot/\346\265\213\350\257\225\344\270\213\350\275\275\344\270\255\346\226\207\345\220\215\347\247\260\347\232\204\345\233\276\347\211\207.png" "b/Blog.Core/wwwroot/\346\265\213\350\257\225\344\270\213\350\275\275\344\270\255\346\226\207\345\220\215\347\247\260\347\232\204\345\233\276\347\211\207.png" deleted file mode 100644 index 83ec6b8b..00000000 Binary files "a/Blog.Core/wwwroot/\346\265\213\350\257\225\344\270\213\350\275\275\344\270\255\346\226\207\345\220\215\347\247\260\347\232\204\345\233\276\347\211\207.png" and /dev/null differ diff --git a/CreateYourProject.bat b/CreateYourProject.bat new file mode 100644 index 00000000..9f8dcc22 --- /dev/null +++ b/CreateYourProject.bat @@ -0,0 +1,26 @@ +color 5 +echo "if u install template error,pls connect QQ:3143422472" + + +color 3 +dotnet new -i Blog.Core.Webapi.Template + +set /p OP=Please set your project name(for example:BlogMicService): + +md .1YourProject + +cd .1YourProject + +dotnet new blogcoretpl -n %OP% + +cd ../ + + +echo "Create Successfully!!!! ^ please see the folder .1YourProject" + +dotnet new -u Blog.Core.Webapi.Template + + +echo "Delete Template Successfully" + +pause 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/DockerBuild.bat b/DockerBuild.bat new file mode 100644 index 00000000..7617488c --- /dev/null +++ b/DockerBuild.bat @@ -0,0 +1,18 @@ +echo off +echo "Press B to build images, P to push to registry, any other key to cancel" +set /p op= : +if "%op%"=="B" goto build +if "%op%"=="P" goto push +exit + +:build +docker rmi laozhangisphi/apkimg +docker build -f "Dockerfile" --force-rm -t laozhangisphi/apkimg . +goto end + +:push +docker push laozhangisphi/apkimg +goto end + +:end +pause \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..575d3583 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +#这种模式是直接在构建镜像的内部编译发布dotnet项目。 +#注意下容器内输出端口是9291 +#如果你想先手动dotnet build成可执行的二进制文件,然后再构建镜像,请看.Api层下的dockerfile。 + + +#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: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.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.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 /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +EXPOSE 9291 +ENTRYPOINT ["dotnet", "Blog.Core.Api.dll"] diff --git a/LICENSE b/LICENSE index a8d2ebc8..261eeb9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2019 ansonzhang - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. 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-en.md b/README-en.md index e4e7aa5c..33d8a749 100644 --- a/README-en.md +++ b/README-en.md @@ -1,50 +1,107 @@ [ENGLISH](https://github.com/anjoy8/Blog.Core/blob/master/README-en.md) | [中文版](https://github.com/anjoy8/Blog.Core/blob/master/README.md) -![Logo](https://github.com/anjoy8/Blog.Core/blob/master/Blog.Core/wwwroot/logocore.png) -Build your own front and rear end separation from scratch ". NET Core2.1 Api + Vue 2.0" framework, currently version 2.2, each version see branch. -It's just. Netcore back-end section, Front end section, see my other engineering vue +[![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) [![Cnblogs](https://img.shields.io/badge/博客园-老张的哲学-brightgreen.svg)](https://www.cnblogs.com/laozhang-is-phi/) -Https://github.com/anjoy8/Blog.Vue -# Give a star!⭐️ +  +  -If you like this project or it helps you, please give star~ (hard Star) -********************************************************* -# Tips: -1. Blog.Core.FrameWork project is a simple implementation of generating files using T4 templates. -If there is an error, you can contact me, -QQ Group: 867095512 -If you don't want to deal with this error, you can uninstall the project first without affecting the overall operation. +
    + + MVP + + + + .netfoundation + +
    + + +Blog.Core is an enterprise-class back-to-back separation framework for.NET Core5.0 API + Vue 2.x + RBAC. +Website: http://apk.neters.club/.doc/ +Has been used by several companies: [click to view list] (https://github.com/anjoy8/Blog.Core/issues/75) + +Project single deployment, concurrent at 400~500, all normal (do not guarantee their own various error writing). +The effect is even better if the load is matched. + + + +  + +### Features and Progress + +Framework module: +- [x] adopts the form of 'repository + service + interface' to encapsulate the framework; +- [X] async/await development; +- [x] access to domestic database ORM component - SQLSUGAR, encapsulate database operation; +- [x] support free switching multiple database, MySql/used/Sqlite/Oracle/Postgresql/reach/NPC Jin Cang dreams; +- [x] realize project startup, automatically generate seed data ✨; +- [X] five types of logging, audit/exception/request response/service operation/SQL logging, etc. +- [x] Support for project transaction processing (use CAP if you want to distribute) ✨; +- [x] Design 4 types of AOP facets programming, including: logging, caching, auditing, transaction ✨; +- [x] Support T4 code template, automatically generate code for each layer; +- [x] or use DbFirst one key to create their own project four layer files (support multiple libraries); +- [x] encapsulation ` Blog. Core. Webapi. Template ` project Template, a key ✨ rebuild their projects; +- [x] with multiple front-end cases for reference and reference: blog.vue, blog.admin, nuxt.tbug, blog.mvp. Blazor ✨; +- [x] Uniform Integrated IdentityServer4 Authentication ✨; + +Component module: +- [x] provides Redis for caching; +- [x] API file with Swagger; +- [x] Use Miniprofiler for interface performance analysis ✨; +- [x] uses Automapper to handle object mapping; +- [x] uses Autofac as a dependency injection container and provides batch service injection ✨; +- [x] supports CORS cross-domain; +- [x] encapsulates JWT custom policy authorization; +- [x] uses the Log4Net logging framework and integrates the native iLogger interface for logging; +- [x] using Signalr duplex communication ✨; +- [x] Added iprateLimiting for API current limiting; +- [X] Use Quartz.net for task scheduling (currently single machine multi-task, cluster scheduling is not currently supported); +- [x] Support for database 'read/write separation' and multi-library operations ✨; +- [x] Added Redis Message Queuing ✨; +- [x] new RabbitMQ message queue ✨; +- [x] New EventBus ✨; +- [x] Debugging - Unified Aggregate Payment; +- [ ] Plan - Data department authority; +- [ ] plan -es search; + +Micro service module: +- [x] can cooperate with Docker to achieve containerization; +- [x] can cooperate with Jenkins to achieve CI/CD; +- [x] enables service discovery with Consul; +- [x] can cooperate with Ocelot to achieve gateway processing; +- [x] can cooperate with NGINX to achieve load balancing; +- [x] can cooperate with IDS4 certification center; + + +  + +## Give a star! ⭐ ️ +If you like this project or it helps you, please send it to STAR ~ +If your project from the project, please explain a little bit down [https://github.com/anjoy8/Blog.Core/issues/75] (https://github.com/anjoy8/Blog.Core/issues/75), Open source is not easy ✨. + + + +  + +## Official document 📕 + +Still in the process of sorting out, but the basic operations, including how to get started, configure data, connect to DB, and so on + +[the official documentation] (http://apk.neters.club/.doc/) +[Official account important articles + video address](https://mvp.neters.club/) + -2. when the project is executed after downloading, the Redis server needs to be installed, installation and use of the description address: -https://www.cnblogs.com/laozhang-is-phi/p/9554210.html#autoid-5-0-0 -3. the system new automated Generation database, and the ability to generate seed data, In the Progrm.cs in the Blog.core layer, cancel the Dbseed.seedasync (mycontext). Wait (); -The comment can be. -4. If you do not want to use Codefirst and seed data, you can use the database table structure SQL file to execute in the database, +  -In the Wwwroot folder under the Blog.core project. -********************************************************* -### Modify Database connection string -1, in the Blog.Core.Repository layer under the Sugar folder under the BaseDBConfig.cs, configure their own strings -``` -public static string connectionstring = File.exists (@ "D:my-filedbCountPsw1.txt")? -File.readalltext (@ "D:my-filedbCountPsw1.txt"). Trim (): "server=.; -Uid=sa;pwd=sa;database=blogdb "; -``` -2, in the Blog.Core.FrameWork layer of the dbhelper.ttinclude, configure their own strings -``` -public static readonly String connectionstring = File.exists (@ "D:my-filedbCountPsw2.txt")? -File.readalltext (@ "D:my-filedbCountPsw2.txt"). Trim (): "server=.; -Uid=sa;pwd=sa;database=blogdb "; -``` ***************************************************** diff --git a/README.md b/README.md index 58000f6f..20b0dabf 100644 --- a/README.md +++ b/README.md @@ -1,342 +1,287 @@ -[ENGLISH](https://github.com/anjoy8/Blog.Core/blob/master/README-en.md) | [中文版](https://github.com/anjoy8/Blog.Core/blob/master/README.md) + -![Logo](https://github.com/anjoy8/Blog.Core/blob/master/Blog.Core/wwwroot/logocore.png) +# Blog.Core +[English](README-en.md) | 简体中文 -从零开始搭建自己的前后端分离【 .NET Core2.1 Api + Vue 2.0 】框架,目前是2.2版本,各个版本见分支。 +[![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/) -如果你感觉看着这整个项目比较费劲,我单抽出来了几个子Demo,方便学习,项目地址 :[https://github.com/anjoy8/BlogArti](https://github.com/anjoy8/BlogArti) - - -这只是 .netCore 后端部分,前端部分请看我的另三个Vue工程项目 - -  -  -  -  - -|个人博客Vue版本|tBug项目Nuxt版本|VueAdmin管理后台(更新中)| -|-|-|-| -|[https://github.com/anjoy8/Blog.Vue](https://github.com/anjoy8/Blog.Vue)|[https://github.com/anjoy8/Nuxt.tBug](https://github.com/anjoy8/Nuxt.tBug)|[https://github.com/anjoy8/Blog.Admin](https://github.com/anjoy8/Blog.Admin)| -|[http://123.206.33.109:8077](http://123.206.33.109:8077)|[http://123.206.33.109:7090](http://123.206.33.109:7090)|[http://123.206.33.109:2364](http://123.206.33.109:2364)| +## 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)     -## Nuget Packages - -| Package | NuGet Stable | Downloads | -| ------- | -------- | ------- | -| [Blog.Core.Webapi.Template](https://www.nuget.org/packages/Blog.Core.Webapi.Template/) | [![Blog.Core.Webapi.Template](https://img.shields.io/nuget/v/Blog.Core.Webapi.Template.svg)](https://www.nuget.org/packages/Blog.Core.Webapi.Template/) | [![Blog.Core.Webapi.Template](https://img.shields.io/nuget/dt/Blog.Core.Webapi.Template.svg)](https://www.nuget.org/packages/Blog.Core.Webapi.Template/) | - - -关于如何使用,点击这里:https://www.cnblogs.com/laozhang-is-phi/p/10205495.html - -  -  - -## 其他后端框架 -目前一共开源四个框架项目,感兴趣的可以看看 - -|单层项目|简单仓储框架|仓储+服务+接口|DDD框架| -|-|-|-|-| -|CURD+Seed|CURD+Seed+DI|CURD+Seed+DI+AOP等|DDD+EFCore+DI+EventBus等| -|[NetCore-Sugar-Demo](https://github.com/anjoy8/NetCore-Sugar-Demo)|[Blog.SplRepository.Demo](https://github.com/anjoy8/Blog.SplRepository.Demo)|[Blog.Core](https://github.com/anjoy8/Blog.Core)|[ChristDDD](https://github.com/anjoy8/ChristDDD)| - - -  -  - -## 给个星星! ⭐️ -如果你喜欢这个项目或者它帮助你, 请给 Star~(辛苦星咯) - -********************************************************* - -## Tips: -``` - -1【重要】、Blog.Core.FrameWork 项目是用T4模板生成文件的简单实现。如果有错误,可以联系我, -QQ群:867095512 -如果你不想处理这个错误,你可以先把项目卸载,不影响整体运行。 - - -2【重要】、项目中,有三个AOP的操作类,分别是Redis缓存切面,memory缓存切面、Log日志切面 -你可以在自定义开关,对其进行是否启用,在 appsettings.json 中的: - - "RedisCaching": { - "Enabled": false, - "ConnectionString": "127.0.0.1:6319" - }, - "MemoryCachingAOP": { - "Enabled": true - }, - "LogoAOP": { - "Enabled": false - }, - - -3【重要】、如何你使用Redis,需要安装Redis服务端,安装和使用说明地址: -https://www.cnblogs.com/laozhang-is-phi/p/9554210.html#autoid-3-4-0 -端口是 6319 ,注意! - - -4【重要+】、系统新增自动化生成数据库,和生成种子数据的功能, -在Blog.Core层中的 Progrm.cs 中,取消对 DBSeed.SeedAsync(myContext).Wait(); 的注释即可。 -注意不要注释Blog.Core.Model层DBSeed -> SeedAsync -》 myContext.CreateTableByEntity(false, typ)。 - - -5、如果你不想用CodeFirst 和种子数据,可以用数据库表结构Sql文件在数据库里执行, -在Blog.Core 项目下的 wwwroot 文件夹中Blog.Core.Table.sql(表结构)、Blog.Core.Table&Data.sql(结构和数据)。 -或者来群里,群文件的是最新的。 - + +
    + + MVP + -6、如果想单独查看关于【JWT授权】的相关内容,可以在wwwroot 文件夹中找到【Autho.jwt.rar】,我单拎出来的一个demo。 + +
    +------------------------------- +Blog.Core 开箱即用的企业级前后端分离【 .NET Core6.0 Api + Vue 2.x + RBAC】权限框架。 +其他版本看具体分支吧🎉 +官网:http://apk.neters.club/.doc/ -7、项目后期发布的时候可以有两个办法,一种是dotnet的kestrel部署,另一种是 IIS 发布部署,但是在发布的时候, -因为解耦了,所以会导致无法把 service.dll & repository.dll 拷贝到生成目录下,大家可以采用: -Blog.Core -> 属性 -> Build Events -> Post-build event command ->>>> + +--------------------- -Copy "$(ProjectDir)bin\Debug\netcoreapp2.2\" "$(SolutionDir)Blog.Core\bin\Debug\" +**已被近100家公司所使用(🐱‍🚀):[点击查看列表](https://github.com/anjoy8/Blog.Core/issues/75)** 欢迎盖楼,留下公司真实名字的,可得定制化指导服务。 +同时如果企业有付费咨询,欢迎联系老张(QQ:3143422472)。 + -``` -********************************************************* -### 修改数据库连接字符串 +### 核心项目组成员(排名不分先后) -注意:修改完数据库连接字符串以后,一定要F6重新编译项目或者重启项目。 +[hudingwen](https://github.com/hudingwen)、[LemonNoCry](https://github.com/LemonNoCry)、、[Jamnine何拾玖](https://github.com/Jamnine)、 -1、在Blog.Core层 appsettings.json 中,配置自己的字符串 -``` - "SqlServer": { - "SqlServerConnection": "Server=.;Database=WMBlogDB;User ID=sa;Password=123;", - "ProviderName": "System.Data.SqlClient" - }, -``` -2、文章中有三个地方用到了数据库连接字符串 +#### ❤ 真实用户反馈 ❤ ``` -A、系统中使用 Blog.Core.Repository -> BaseDBConfig.cs -B、Seed数据库 Blog.Core.Model -> MyContext.cs -C、T4 模板 Blog.Core.FrameWork -> DbHelper.ttinclude +项目单体部署,并发在400~500,一切正常(不保证自己的各种错误写法)。 +如果搭配负载,效果更好。 -其实针对AB两个情况,只需要配置 appsettings.json 即可 +1、A~CoderDong: +应用场景:使用Blog.Core为基础骨架开发,搭建Client监控类守护进程项目,To C 客户群, +并发情况:目前压测并发5k正常8秒处理完,并发10k可15秒处理完毕,异常不会丢失。 +生产配置:一台服务器(Linux环境 + 至强8核的16G内存 + mysql数据库 + 3台Nginx负载) ``` - -3、如果想使用T4模板,在Blog.Core.FrameWork层的DbHelper.ttinclude 中,配置自己的字符串 -``` -public static readonly string ConnectionString = File.Exists(@"D:\my-file\dbCountPsw2.txt") ? -File.ReadAllText(@"D:\my-file\dbCountPsw2.txt").Trim(): "server=.;uid=sa;pwd=sa;database=BlogDB"; -``` - - -***************************************************** -### 三大平台同步直播 - -简 书:https://www.jianshu.com/notebooks/28621653 - -博客园:https://www.cnblogs.com/laozhang-is-phi/ - - CSDN:https://blog.csdn.net/baidu_35726140 + - 码云:https://gitee.com/laozhangIsPhi/Blog.Core - -``` -``` - - -
    -

    .NetCore与Vue 框架学习目录如下

    - +## Nuget Packages +| Package | NuGet Stable | Downloads | +| ------- | -------- | ------- | +| [Blog.Core.Webapi.Template](https://www.nuget.org/packages/Blog.Core.Webapi.Template/) | [![Blog.Core.Webapi.Template](https://img.shields.io/nuget/v/Blog.Core.Webapi.Template.svg)](https://www.nuget.org/packages/Blog.Core.Webapi.Template/) | [![Blog.Core.Webapi.Template](https://img.shields.io/nuget/dt/Blog.Core.Webapi.Template.svg)](https://www.nuget.org/packages/Blog.Core.Webapi.Template/) | -
    -************************************************************** - 系统环境 +关于如何使用,点击这里:https://www.cnblogs.com/laozhang-is-phi/p/10205495.html - windows 10、SQL server 2012、Visual Studio 2017、Windows Server 2008 R2 +  +  - 后端技术: +## 其他后端框架 +目前一共开源四个框架项目,感兴趣的可以看看 - * .Net Core 2.0 API(因为想单纯搭建前后端分离,因此就选用的API,如果想了解.Net Core MVC,也可以交流) - - * Swagger 前后端文档说明,基于RESTful风格编写接口 +|单层项目|简单仓储框架|仓储+服务+接口|DDD框架| +|-|-|-|-| +|CURD+Seed|CURD+Seed+DI|CURD+Seed+DI+AOP等|DDD+EFCore+DI+EventBus等| +|[NetCore-Sugar-Demo](https://github.com/anjoy8/NetCore-Sugar-Demo)|[Blog.SplRepository.Demo](https://github.com/anjoy8/Blog.SplRepository.Demo)|[Blog.Core](https://github.com/anjoy8/Blog.Core)|[ChristDDD](https://github.com/anjoy8/ChristDDD)| +| -|[Blog-EFCore-Sqlite](https://github.com/anjoy8/Blog-EFCore-Sqlite)|- | -| - * Repository + Service 仓储模式编程 - * Async和Await 异步编程 +  - * Cors 简单的跨域解决方案 - * AOP基于切面编程技术 - * Autofac 轻量级IoC和DI依赖注入 +  - * Vue 本地代理跨域方案,Nginx跨域代理 +## 售后服务与支持 - * JWT权限验证 +鼓励作者,简单打赏~~ +如果你喜欢,就给作者加个鸡腿吧 +赞赏码 +[图片若加载不出来,点这里](http://apk.neters.club/laozhangisphigood.jpg) - 数据库技术 - * SqlSugar 轻量级ORM框架,CodeFirst - * T4 模板生成 +***************************************************** +### 文章+视频+直播 - * AutoMapper 自动对象映射 +博客园:https://www.cnblogs.com/laozhang-is-phi/ + Bilibili:https://space.bilibili.com/387802716 + 直播间:https://live.bilibili.com/21507364 - 分布式缓存技术 +``` +``` - * Redis 轻量级分布式缓存 - +  - 前端技术 +如果你感觉看着这整个项目比较费劲,我单抽出来了几个子Demo,方便学习,项目地址 :[https://github.com/anjoy8/BlogArti](https://github.com/anjoy8/BlogArti) - * Vue 2.0 框架全家桶 Vue2 + VueRouter2 + Webpack + Axios + vue-cli + vuex - * ElementUI 基于Vue 2.0的组件库 - * Nuxt.js服务端渲染SSR diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..5776c0a2 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,21 @@ +# 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: +- master + +pool: + vmImage: ubuntu-latest + +variables: + buildConfiguration: 'Release' + +steps: +- task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + 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 diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..38e58063 --- /dev/null +++ b/codecov.yml @@ -0,0 +1 @@ +cat codecov.yml | curl --data-binary @- https://codecov.io/validate