329 changed files with 20113 additions and 0 deletions
@ -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 |
@ -0,0 +1,391 @@ |
|||
# ---> VisualStudio |
|||
## Ignore Visual Studio temporary files, build results, and |
|||
## files generated by popular Visual Studio add-ons. |
|||
## |
|||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore |
|||
|
|||
# User-specific files |
|||
*.rsuser |
|||
*.suo |
|||
*.user |
|||
*.userosscache |
|||
*.sln.docstates |
|||
|
|||
# User-specific files (MonoDevelop/Xamarin Studio) |
|||
*.userprefs |
|||
|
|||
# Mono auto generated files |
|||
mono_crash.* |
|||
|
|||
# Build results |
|||
[Dd]ebug/ |
|||
[Dd]ebugPublic/ |
|||
[Rr]elease/ |
|||
[Rr]eleases/ |
|||
x64/ |
|||
x86/ |
|||
[Ww][Ii][Nn]32/ |
|||
[Aa][Rr][Mm]/ |
|||
[Aa][Rr][Mm]64/ |
|||
bld/ |
|||
[Bb]in/ |
|||
[Oo]bj/ |
|||
[Ll]og/ |
|||
[Ll]ogs/ |
|||
|
|||
# Visual Studio 2015/2017 cache/options directory |
|||
.vs/ |
|||
# Uncomment if you have tasks that create the project's static files in wwwroot |
|||
#wwwroot/ |
|||
|
|||
# Visual Studio 2017 auto generated files |
|||
Generated\ Files/ |
|||
|
|||
# MSTest test Results |
|||
[Tt]est[Rr]esult*/ |
|||
[Bb]uild[Ll]og.* |
|||
|
|||
# NUnit |
|||
*.VisualState.xml |
|||
TestResult.xml |
|||
nunit-*.xml |
|||
|
|||
# Build Results of an ATL Project |
|||
[Dd]ebugPS/ |
|||
[Rr]eleasePS/ |
|||
dlldata.c |
|||
|
|||
# Benchmark Results |
|||
BenchmarkDotNet.Artifacts/ |
|||
|
|||
# .NET Core |
|||
project.lock.json |
|||
project.fragment.lock.json |
|||
artifacts/ |
|||
|
|||
# ASP.NET Scaffolding |
|||
ScaffoldingReadMe.txt |
|||
|
|||
# StyleCop |
|||
StyleCopReport.xml |
|||
|
|||
# Files built by Visual Studio |
|||
*_i.c |
|||
*_p.c |
|||
*_h.h |
|||
*.ilk |
|||
*.meta |
|||
*.obj |
|||
*.iobj |
|||
*.pch |
|||
*.pdb |
|||
*.ipdb |
|||
*.pgc |
|||
*.pgd |
|||
*.rsp |
|||
*.sbr |
|||
*.tlb |
|||
*.tli |
|||
*.tlh |
|||
*.tmp |
|||
*.tmp_proj |
|||
*_wpftmp.csproj |
|||
*.log |
|||
*.tlog |
|||
*.vspscc |
|||
*.vssscc |
|||
.builds |
|||
*.pidb |
|||
*.svclog |
|||
*.scc |
|||
|
|||
# Chutzpah Test files |
|||
_Chutzpah* |
|||
|
|||
# Visual C++ cache files |
|||
ipch/ |
|||
*.aps |
|||
*.ncb |
|||
*.opendb |
|||
*.opensdf |
|||
*.sdf |
|||
*.cachefile |
|||
*.VC.db |
|||
*.VC.VC.opendb |
|||
|
|||
# Visual Studio profiler |
|||
*.psess |
|||
*.vsp |
|||
*.vspx |
|||
*.sap |
|||
|
|||
# Visual Studio Trace Files |
|||
*.e2e |
|||
|
|||
# TFS 2012 Local Workspace |
|||
$tf/ |
|||
|
|||
# Guidance Automation Toolkit |
|||
*.gpState |
|||
|
|||
# ReSharper is a .NET coding add-in |
|||
_ReSharper*/ |
|||
*.[Rr]e[Ss]harper |
|||
*.DotSettings.user |
|||
|
|||
# TeamCity is a build add-in |
|||
_TeamCity* |
|||
|
|||
# DotCover is a Code Coverage Tool |
|||
*.dotCover |
|||
|
|||
# AxoCover is a Code Coverage Tool |
|||
.axoCover/* |
|||
!.axoCover/settings.json |
|||
|
|||
# Coverlet is a free, cross platform Code Coverage Tool |
|||
coverage*.json |
|||
coverage*.xml |
|||
coverage*.info |
|||
|
|||
# Visual Studio code coverage results |
|||
*.coverage |
|||
*.coveragexml |
|||
|
|||
# NCrunch |
|||
_NCrunch_* |
|||
.*crunch*.local.xml |
|||
nCrunchTemp_* |
|||
|
|||
# MightyMoose |
|||
*.mm.* |
|||
AutoTest.Net/ |
|||
|
|||
# Web workbench (sass) |
|||
.sass-cache/ |
|||
|
|||
# Installshield output folder |
|||
[Ee]xpress/ |
|||
|
|||
# DocProject is a documentation generator add-in |
|||
DocProject/buildhelp/ |
|||
DocProject/Help/*.HxT |
|||
DocProject/Help/*.HxC |
|||
DocProject/Help/*.hhc |
|||
DocProject/Help/*.hhk |
|||
DocProject/Help/*.hhp |
|||
DocProject/Help/Html2 |
|||
DocProject/Help/html |
|||
|
|||
# Click-Once directory |
|||
publish/ |
|||
|
|||
# Publish Web Output |
|||
*.[Pp]ublish.xml |
|||
*.azurePubxml |
|||
# Note: Comment the next line if you want to checkin your web deploy settings, |
|||
# but database connection strings (with potential passwords) will be unencrypted |
|||
*.pubxml |
|||
*.publishproj |
|||
|
|||
# Microsoft Azure Web App publish settings. Comment the next line if you want to |
|||
# checkin your Azure Web App publish settings, but sensitive information contained |
|||
# in these scripts will be unencrypted |
|||
PublishScripts/ |
|||
|
|||
# NuGet Packages |
|||
*.nupkg |
|||
# NuGet Symbol Packages |
|||
*.snupkg |
|||
# The packages folder can be ignored because of Package Restore |
|||
**/[Pp]ackages/* |
|||
# except build/, which is used as an MSBuild target. |
|||
!**/[Pp]ackages/build/ |
|||
# Uncomment if necessary however generally it will be regenerated when needed |
|||
#!**/[Pp]ackages/repositories.config |
|||
# NuGet v3's project.json files produces more ignorable files |
|||
*.nuget.props |
|||
*.nuget.targets |
|||
|
|||
# Nuget personal access tokens and Credentials |
|||
nuget.config |
|||
|
|||
# Microsoft Azure Build Output |
|||
csx/ |
|||
*.build.csdef |
|||
|
|||
# Microsoft Azure Emulator |
|||
ecf/ |
|||
rcf/ |
|||
|
|||
# Windows Store app package directories and files |
|||
AppPackages/ |
|||
BundleArtifacts/ |
|||
Package.StoreAssociation.xml |
|||
_pkginfo.txt |
|||
*.appx |
|||
*.appxbundle |
|||
*.appxupload |
|||
|
|||
# Visual Studio cache files |
|||
# files ending in .cache can be ignored |
|||
*.[Cc]ache |
|||
# but keep track of directories ending in .cache |
|||
!?*.[Cc]ache/ |
|||
|
|||
# Others |
|||
ClientBin/ |
|||
~$* |
|||
*~ |
|||
*.dbmdl |
|||
*.dbproj.schemaview |
|||
*.jfm |
|||
*.pfx |
|||
*.publishsettings |
|||
orleans.codegen.cs |
|||
|
|||
# Including strong name files can present a security risk |
|||
# (https://github.com/github/gitignore/pull/2483#issue-259490424) |
|||
#*.snk |
|||
|
|||
# Since there are multiple workflows, uncomment next line to ignore bower_components |
|||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) |
|||
#bower_components/ |
|||
|
|||
# RIA/Silverlight projects |
|||
Generated_Code/ |
|||
|
|||
# Backup & report files from converting an old project file |
|||
# to a newer Visual Studio version. Backup files are not needed, |
|||
# because we have git ;-) |
|||
_UpgradeReport_Files/ |
|||
Backup*/ |
|||
UpgradeLog*.XML |
|||
UpgradeLog*.htm |
|||
ServiceFabricBackup/ |
|||
*.rptproj.bak |
|||
|
|||
# SQL Server files |
|||
*.mdf |
|||
*.ldf |
|||
*.ndf |
|||
|
|||
# Business Intelligence projects |
|||
*.rdl.data |
|||
*.bim.layout |
|||
*.bim_*.settings |
|||
*.rptproj.rsuser |
|||
*- [Bb]ackup.rdl |
|||
*- [Bb]ackup ([0-9]).rdl |
|||
*- [Bb]ackup ([0-9][0-9]).rdl |
|||
|
|||
# Microsoft Fakes |
|||
FakesAssemblies/ |
|||
|
|||
# GhostDoc plugin setting file |
|||
*.GhostDoc.xml |
|||
|
|||
# Node.js Tools for Visual Studio |
|||
.ntvs_analysis.dat |
|||
node_modules/ |
|||
|
|||
# Visual Studio 6 build log |
|||
*.plg |
|||
|
|||
# Visual Studio 6 workspace options file |
|||
*.opt |
|||
|
|||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) |
|||
*.vbw |
|||
|
|||
# Visual Studio LightSwitch build output |
|||
**/*.HTMLClient/GeneratedArtifacts |
|||
**/*.DesktopClient/GeneratedArtifacts |
|||
**/*.DesktopClient/ModelManifest.xml |
|||
**/*.Server/GeneratedArtifacts |
|||
**/*.Server/ModelManifest.xml |
|||
_Pvt_Extensions |
|||
|
|||
# Paket dependency manager |
|||
.paket/paket.exe |
|||
paket-files/ |
|||
|
|||
# FAKE - F# Make |
|||
.fake/ |
|||
|
|||
# CodeRush personal settings |
|||
.cr/personal |
|||
|
|||
# Python Tools for Visual Studio (PTVS) |
|||
__pycache__/ |
|||
*.pyc |
|||
|
|||
# Cake - Uncomment if you are using it |
|||
# tools/** |
|||
# !tools/packages.config |
|||
|
|||
# Tabs Studio |
|||
*.tss |
|||
|
|||
# Telerik's JustMock configuration file |
|||
*.jmconfig |
|||
|
|||
# BizTalk build output |
|||
*.btp.cs |
|||
*.btm.cs |
|||
*.odx.cs |
|||
*.xsd.cs |
|||
|
|||
# OpenCover UI analysis results |
|||
OpenCover/ |
|||
|
|||
# Azure Stream Analytics local run output |
|||
ASALocalRun/ |
|||
|
|||
# MSBuild Binary and Structured Log |
|||
*.binlog |
|||
|
|||
# NVidia Nsight GPU debugger configuration file |
|||
*.nvuser |
|||
|
|||
# MFractors (Xamarin productivity tool) working folder |
|||
.mfractor/ |
|||
|
|||
# Local History for Visual Studio |
|||
.localhistory/ |
|||
|
|||
# BeatPulse healthcheck temp database |
|||
healthchecksdb |
|||
|
|||
# Backup folder for Package Reference Convert tool in Visual Studio 2017 |
|||
MigrationBackup/ |
|||
|
|||
# Ionide (cross platform F# VS Code tools) working folder |
|||
.ionide/ |
|||
|
|||
# Fody - auto-generated XML schema |
|||
FodyWeavers.xsd |
|||
|
|||
# VS Code files for those working on multiple tools |
|||
.vscode/* |
|||
!.vscode/settings.json |
|||
!.vscode/tasks.json |
|||
!.vscode/launch.json |
|||
!.vscode/extensions.json |
|||
*.code-workspace |
|||
|
|||
# Local History for Visual Studio Code |
|||
.history/ |
|||
|
|||
# Windows Installer files from build outputs |
|||
*.cab |
|||
*.msi |
|||
*.msix |
|||
*.msm |
|||
*.msp |
|||
|
|||
# JetBrains Rider |
|||
.idea/ |
|||
*.sln.iml |
|||
|
|||
/Dockerfile.bak |
@ -0,0 +1,48 @@ |
|||
#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:6.0 AS base |
|||
WORKDIR /app |
|||
EXPOSE 8001 |
|||
EXPOSE 443 |
|||
|
|||
ENV TZ=Asia/Shanghai |
|||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone |
|||
|
|||
|
|||
|
|||
|
|||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build |
|||
WORKDIR /src |
|||
COPY ["Znyc.CloudCar/Znyc.CloudCar.csproj", "Znyc.CloudCar/"] |
|||
COPY ["Znyc.CloudCar.Services/Znyc.CloudCar.Services.csproj", "Znyc.CloudCar.Services/"] |
|||
COPY ["Znyc.CloudCar.Auth/Znyc.CloudCar.Auth.csproj", "Znyc.CloudCar.Auth/"] |
|||
COPY ["Znyc.CloudCar.Configuration/Znyc.CloudCar.Configuration.csproj", "Znyc.CloudCar.Configuration/"] |
|||
COPY ["Znyc.CloudCar.Utility/Znyc.CloudCar.Utility.csproj", "Znyc.CloudCar.Utility/"] |
|||
COPY ["Znyc.CloudCar.Model/Znyc.CloudCar.Model.csproj", "Znyc.CloudCar.Model/"] |
|||
COPY ["Znyc.CloudCar.FreeSql/Znyc.CloudCar.FreeSql.csproj", "Znyc.CloudCar.FreeSql/"] |
|||
COPY ["Znyc.CloudCar.Core/Znyc.CloudCar.Core.csproj", "Znyc.CloudCar.Core/"] |
|||
COPY ["Znyc.CloudCar.RedisMQ/Znyc.CloudCar.RedisMQ.csproj", "Znyc.CloudCar.RedisMQ/"] |
|||
COPY ["Znyc.CloudCar.Loging/Znyc.CloudCar.Loging.csproj", "Znyc.CloudCar.Loging/"] |
|||
COPY ["Znyc.CloudCar.Caching/Znyc.CloudCar.Caching.csproj", "Znyc.CloudCar.Caching/"] |
|||
COPY ["Zncy.CloudCar.WeChat.Service/Zncy.CloudCar.WeChat.Service.csproj", "Zncy.CloudCar.WeChat.Service/"] |
|||
COPY ["Znyc.CloudCar.IServices/Znyc.CloudCar.IServices.csproj", "Znyc.CloudCar.IServices/"] |
|||
COPY ["Znyc.CloudCar.Swagger/Znyc.CloudCar.Swagger.csproj", "Znyc.CloudCar.Swagger/"] |
|||
COPY ["Znyc.CloudCar.Mapster/Znyc.CloudCar.Mapster.csproj", "Znyc.CloudCar.Mapster/"] |
|||
COPY ["Znyc.CloudCar.Hangfire/Znyc.CloudCar.Hangfire.csproj", "Znyc.CloudCar.Hangfire/"] |
|||
COPY ["Znyc.CloudCar.IRepository/Znyc.CloudCar.IRepository.csproj", "Znyc.CloudCar.IRepository/"] |
|||
COPY ["Znyc.CloudCar.Filter/Znyc.CloudCar.Filter.csproj", "Znyc.CloudCar.Filter/"] |
|||
COPY ["Znyc.CloudCar.Repository/Znyc.CloudCar.Repository.csproj", "Znyc.CloudCar.Repository/"] |
|||
COPY ["Znyc.CloudCar.Task/Znyc.CloudCar.Task.csproj", "Znyc.CloudCar.Task/"] |
|||
COPY ["Znyc.CloudCar.Middlewares/Znyc.CloudCar.Middlewares.csproj", "Znyc.CloudCar.Middlewares/"] |
|||
RUN dotnet restore "Znyc.CloudCar/Znyc.CloudCar.csproj" |
|||
COPY . . |
|||
WORKDIR "/src/Znyc.CloudCar" |
|||
RUN dotnet build "Znyc.CloudCar.csproj" -c Release -o /app/build |
|||
|
|||
FROM build AS publish |
|||
RUN dotnet publish "Znyc.CloudCar.csproj" -c Release -o /app/publish |
|||
|
|||
FROM base AS final |
|||
WORKDIR /app |
|||
COPY --from=publish /app/publish . |
|||
ENTRYPOINT ["dotnet", "Znyc.CloudCar.dll"] |
@ -0,0 +1,3 @@ |
|||
# znyc.CloudCar |
|||
|
|||
云车二手 |
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Zncy.CloudCar.Tests |
|||
{ |
|||
public class UnitTest1 |
|||
{ |
|||
[Fact] |
|||
public void Test1() |
|||
{ |
|||
|
|||
Assert.Equal(4, Add(2, 3)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FailingTest() |
|||
{ |
|||
Assert.Equal(5, Add(2, 2)); |
|||
} |
|||
|
|||
int Add(int x, int y) |
|||
{ |
|||
return x + y; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="xunit" Version="2.4.1" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,55 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Configuration |
|||
{ |
|||
/// <summary>
|
|||
/// 常见消息类型
|
|||
/// </summary>
|
|||
public static class EventType |
|||
{ |
|||
|
|||
#region 公众号类型
|
|||
|
|||
/// <summary>
|
|||
/// 关注
|
|||
/// </summary>
|
|||
public const string Subscribe = "subscribe"; |
|||
/// <summary>
|
|||
/// 取消订阅
|
|||
/// </summary>
|
|||
public const string Unsubscribe = "unsubscribe"; |
|||
/// <summary>
|
|||
/// 上报地理位置事件
|
|||
/// 用户同意上报地理位置后,每次进入公众号会话时,都会在进入时上报地理位置,或在进入会话后每5秒上报一次地理位置,公众号可以在公众平台网站中修改以上设置。上报地理位置时,微信会将上报地理位置事件推送到开发者填写的URL。
|
|||
/// </summary>
|
|||
public const string Localtion = "LOCATION"; |
|||
|
|||
/// <summary>
|
|||
/// 自定义菜单事件-用户点击自定义菜单后,微信会把点击事件推送给开发者,请注意,点击菜单弹出子菜单,不会产生上报。
|
|||
/// </summary>
|
|||
public const string Click = "CLICK"; |
|||
|
|||
|
|||
#endregion
|
|||
|
|||
|
|||
#region 小程序
|
|||
|
|||
|
|||
|
|||
#endregion
|
|||
|
|||
#region 自定义交易组件
|
|||
|
|||
|
|||
|
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// 图片消息
|
|||
/// </summary>
|
|||
public const string Image = "image"; |
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,62 @@ |
|||
/*********************************************************************** |
|||
* Project: CoreCms |
|||
* ProjectName: 核心内容管理系统 |
|||
* Web: https://www.corecms.net
|
|||
* Author: 大灰灰 |
|||
* Email: jianweie@163.com |
|||
* CreateTime: 2021/7/30 14:19:49 |
|||
* Description: 暂无 |
|||
***********************************************************************/ |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Configuration |
|||
{ |
|||
/// <summary>
|
|||
/// 常用常量配置
|
|||
/// </summary>
|
|||
public static class RequestMsgType |
|||
{ |
|||
// 各种消息类型,除了扫带二维码事件
|
|||
/// <summary>
|
|||
/// 文本消息
|
|||
/// </summary>
|
|||
public const string Text = "text"; |
|||
|
|||
/// <summary>
|
|||
/// 图片消息
|
|||
/// </summary>
|
|||
public const string Image = "image"; |
|||
|
|||
/// <summary>
|
|||
/// 语音消息
|
|||
/// </summary>
|
|||
public const string Voice = "voice"; |
|||
|
|||
/// <summary>
|
|||
/// 视频消息
|
|||
/// </summary>
|
|||
public const string Video = "video"; |
|||
|
|||
/// <summary>
|
|||
/// 小视频消息
|
|||
/// </summary>
|
|||
public const string ShortVideo = "shortvideo"; |
|||
|
|||
/// <summary>
|
|||
/// 地理位置消息
|
|||
/// </summary>
|
|||
public const string Location = "location"; |
|||
|
|||
/// <summary>
|
|||
/// 链接消息
|
|||
/// </summary>
|
|||
public const string Link = "link"; |
|||
|
|||
/// <summary>
|
|||
/// 事件推送消息
|
|||
/// </summary>
|
|||
public const string MessageEvent = "event"; |
|||
|
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,271 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Enums |
|||
{ |
|||
public class WeChatReturnCode |
|||
{ |
|||
/// <summary>
|
|||
/// 公众号返回码(JSON)
|
|||
/// 应该更名为ReturnCode_MP,但为减少项目中的修改,此处依旧用ReturnCode命名
|
|||
/// </summary>
|
|||
public enum ReturnCode |
|||
{ |
|||
SenparcWeixinSDK配置错误 = -99, // 0xFFFFFF9D
|
|||
系统繁忙此时请开发者稍候再试 = -1, // 0xFFFFFFFF
|
|||
请求成功 = 0, |
|||
工商数据返回_企业已注销 = 101, // 0x00000065
|
|||
工商数据返回_企业不存在或企业信息未更新 = 102, // 0x00000066
|
|||
工商数据返回_企业法定代表人姓名不一致 = 103, // 0x00000067
|
|||
工商数据返回_企业法定代表人身份证号码不一致 = 104, // 0x00000068
|
|||
法定代表人身份证号码_工商数据未更新_请5_15个工作日之后尝试 = 105, // 0x00000069
|
|||
工商数据返回_企业信息或法定代表人信息不一致 = 1000, // 0x000003E8
|
|||
对方不是粉丝 = 10700, // 0x000029CC
|
|||
发送消息失败_对方关闭了接收消息 = 10703, // 0x000029CF
|
|||
发送消息失败_48小时内用户未互动 = 10706, // 0x000029D2
|
|||
POST参数非法 = 20002, // 0x00004E22
|
|||
获取access_token时AppSecret错误或者access_token无效 = 40001, // 0x00009C41
|
|||
/// <summary>
|
|||
/// <para>公众号:不合法的凭证类型</para>
|
|||
/// <para>小程序:暂无生成权限</para>
|
|||
/// </summary>
|
|||
不合法的凭证类型 = 40002, // 0x00009C42
|
|||
不合法的OpenID = 40003, // 0x00009C43
|
|||
不合法的媒体文件类型 = 40004, // 0x00009C44
|
|||
不合法的文件类型 = 40005, // 0x00009C45
|
|||
不合法的文件大小 = 40006, // 0x00009C46
|
|||
不合法的媒体文件id = 40007, // 0x00009C47
|
|||
不合法的消息类型_40008 = 40008, // 0x00009C48
|
|||
不合法的图片文件大小 = 40009, // 0x00009C49
|
|||
不合法的语音文件大小 = 40010, // 0x00009C4A
|
|||
不合法的视频文件大小 = 40011, // 0x00009C4B
|
|||
不合法的缩略图文件大小 = 40012, // 0x00009C4C
|
|||
/// <summary>
|
|||
/// <para>微信:不合法的APPID</para>
|
|||
/// <para>小程序:生成权限被封禁</para>
|
|||
/// </summary>
|
|||
不合法的APPID = 40013, // 0x00009C4D
|
|||
不合法的access_token = 40014, // 0x00009C4E
|
|||
不合法的菜单类型 = 40015, // 0x00009C4F
|
|||
不合法的按钮个数1 = 40016, // 0x00009C50
|
|||
不合法的按钮个数2 = 40017, // 0x00009C51
|
|||
不合法的按钮名字长度 = 40018, // 0x00009C52
|
|||
不合法的按钮KEY长度 = 40019, // 0x00009C53
|
|||
不合法的按钮URL长度 = 40020, // 0x00009C54
|
|||
不合法的菜单版本号 = 40021, // 0x00009C55
|
|||
不合法的子菜单级数 = 40022, // 0x00009C56
|
|||
不合法的子菜单按钮个数 = 40023, // 0x00009C57
|
|||
不合法的子菜单按钮类型 = 40024, // 0x00009C58
|
|||
不合法的子菜单按钮名字长度 = 40025, // 0x00009C59
|
|||
不合法的子菜单按钮KEY长度 = 40026, // 0x00009C5A
|
|||
不合法的子菜单按钮URL长度 = 40027, // 0x00009C5B
|
|||
不合法的自定义菜单使用用户 = 40028, // 0x00009C5C
|
|||
不合法的oauth_code = 40029, // 0x00009C5D
|
|||
不合法的refresh_token = 40030, // 0x00009C5E
|
|||
不合法的openid列表 = 40031, // 0x00009C5F
|
|||
不合法的openid列表长度 = 40032, // 0x00009C60
|
|||
不合法的请求字符不能包含uxxxx格式的字符 = 40033, // 0x00009C61
|
|||
不合法的参数 = 40035, // 0x00009C63
|
|||
template_id不正确 = 40037, // 0x00009C65
|
|||
不合法的请求格式 = 40038, // 0x00009C66
|
|||
不合法的URL长度 = 40039, // 0x00009C67
|
|||
不合法的分组id = 40050, // 0x00009C72
|
|||
分组名字不合法 = 40051, // 0x00009C73
|
|||
/// <summary>
|
|||
/// <para>公众号:输入参数有误</para>
|
|||
/// <para>小程序:参数expire_time填写错误</para>
|
|||
/// </summary>
|
|||
输入参数有误 = 40097, // 0x00009CA1
|
|||
appsecret不正确 = 40125, // 0x00009CBD
|
|||
调用接口的IP地址不在白名单中 = 40164, // 0x00009CE4
|
|||
参数path填写错误 = 40165, // 0x00009CE5
|
|||
小程序Appid不存在 = 40166, // 0x00009CE6
|
|||
参数query填写错误 = 40212, // 0x00009D14
|
|||
缺少access_token参数 = 41001, // 0x0000A029
|
|||
缺少appid参数 = 41002, // 0x0000A02A
|
|||
缺少refresh_token参数 = 41003, // 0x0000A02B
|
|||
缺少secret参数 = 41004, // 0x0000A02C
|
|||
缺少多媒体文件数据 = 41005, // 0x0000A02D
|
|||
缺少media_id参数 = 41006, // 0x0000A02E
|
|||
缺少子菜单数据 = 41007, // 0x0000A02F
|
|||
缺少oauth_code = 41008, // 0x0000A030
|
|||
缺少openid = 41009, // 0x0000A031
|
|||
form_id不正确_或者过期 = 41028, // 0x0000A044
|
|||
form_id已被使用 = 41029, // 0x0000A045
|
|||
page不正确 = 41030, // 0x0000A046
|
|||
access_token超时 = 42001, // 0x0000A411
|
|||
refresh_token超时 = 42002, // 0x0000A412
|
|||
oauth_code超时 = 42003, // 0x0000A413
|
|||
需要GET请求 = 43001, // 0x0000A7F9
|
|||
需要POST请求 = 43002, // 0x0000A7FA
|
|||
需要HTTPS请求 = 43003, // 0x0000A7FB
|
|||
需要接收者关注 = 43004, // 0x0000A7FC
|
|||
需要好友关系 = 43005, // 0x0000A7FD
|
|||
/// <summary>[小程序订阅消息]用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系</summary>
|
|||
用户拒绝接受消息 = 43101, // 0x0000A85D
|
|||
没有权限 = 43104, // 0x0000A860
|
|||
多媒体文件为空 = 44001, // 0x0000ABE1
|
|||
POST的数据包为空 = 44002, // 0x0000ABE2
|
|||
图文消息内容为空 = 44003, // 0x0000ABE3
|
|||
文本消息内容为空 = 44004, // 0x0000ABE4
|
|||
多媒体文件大小超过限制 = 45001, // 0x0000AFC9
|
|||
消息内容超过限制 = 45002, // 0x0000AFCA
|
|||
标题字段超过限制 = 45003, // 0x0000AFCB
|
|||
描述字段超过限制 = 45004, // 0x0000AFCC
|
|||
链接字段超过限制 = 45005, // 0x0000AFCD
|
|||
图片链接字段超过限制 = 45006, // 0x0000AFCE
|
|||
语音播放时间超过限制 = 45007, // 0x0000AFCF
|
|||
图文消息超过限制 = 45008, // 0x0000AFD0
|
|||
接口调用超过限制 = 45009, // 0x0000AFD1
|
|||
创建菜单个数超过限制 = 45010, // 0x0000AFD2
|
|||
回复时间超过限制 = 45015, // 0x0000AFD7
|
|||
系统分组不允许修改 = 45016, // 0x0000AFD8
|
|||
分组名字过长 = 45017, // 0x0000AFD9
|
|||
分组数量超过上限 = 45018, // 0x0000AFDA
|
|||
超出响应数量限制 = 45047, // 0x0000AFF7
|
|||
创建的标签数过多请注意不能超过100个 = 45056, // 0x0000B000
|
|||
标签名非法请注意不能和其他标签重名 = 45157, // 0x0000B065
|
|||
标签名长度超过30个字节 = 45158, // 0x0000B066
|
|||
不存在媒体数据 = 46001, // 0x0000B3B1
|
|||
不存在的菜单版本 = 46002, // 0x0000B3B2
|
|||
不存在的菜单数据 = 46003, // 0x0000B3B3
|
|||
解析JSON_XML内容错误 = 47001, // 0x0000B799
|
|||
/// <summary>[小程序订阅消息]模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错</summary>
|
|||
模板参数不准确 = 47003, // 0x0000B79B
|
|||
api功能未授权 = 48001, // 0x0000BB81
|
|||
用户未授权该api = 50001, // 0x0000C351
|
|||
名称格式不合法 = 53010, // 0x0000CF12
|
|||
名称检测命中频率限制 = 53011, // 0x0000CF13
|
|||
禁止使用该名称 = 53012, // 0x0000CF14
|
|||
公众号_名称与已有公众号名称重复_小程序_该名称与已有小程序名称重复 = 53013, // 0x0000CF15
|
|||
公众号_公众号已有_名称A_时_需与该帐号相同主体才可申请_名称A_小程序_小程序已有_名称A_时_需与该帐号相同主体才可申请_名称A_ = 53014, // 0x0000CF16
|
|||
公众号_该名称与已有小程序名称重复_需与该小程序帐号相同主体才可申请_小程序_该名称与已有公众号名称重复_需与该公众号帐号相同主体才可申请 = 53015, // 0x0000CF17
|
|||
公众号_该名称与已有多个小程序名称重复_暂不支持申请_小程序_该名称与已有多个公众号名称重复_暂不支持申请 = 53016, // 0x0000CF18
|
|||
公众号_小程序已有_名称A_时_需与该帐号相同主体才可申请_名称A_小程序_公众号已有_名称A_时_需与该帐号相同主体才可申请_名称A = 53017, // 0x0000CF19
|
|||
名称命中微信号 = 53018, // 0x0000CF1A
|
|||
名称在保护期内 = 53019, // 0x0000CF1B
|
|||
法人姓名与微信号不一致 = 61070, // 0x0000EE8E
|
|||
系统错误system_error = 61450, // 0x0000F00A
|
|||
参数错误invalid_parameter = 61451, // 0x0000F00B
|
|||
无效客服账号invalid_kf_account = 61452, // 0x0000F00C
|
|||
客服帐号已存在kf_account_exsited = 61453, // 0x0000F00D
|
|||
/// <summary>
|
|||
/// 客服帐号名长度超过限制(仅允许10个英文字符,不包括@及@后的公众号的微信号)(invalid kf_acount length)
|
|||
/// </summary>
|
|||
客服帐号名长度超过限制 = 61454, // 0x0000F00E
|
|||
/// <summary>
|
|||
/// 客服帐号名包含非法字符(仅允许英文+数字)(illegal character in kf_account)
|
|||
/// </summary>
|
|||
客服帐号名包含非法字符 = 61455, // 0x0000F00F
|
|||
/// <summary>客服帐号个数超过限制(10个客服账号)(kf_account count exceeded)</summary>
|
|||
客服帐号个数超过限制 = 61456, // 0x0000F010
|
|||
无效头像文件类型invalid_file_type = 61457, // 0x0000F011
|
|||
日期格式错误 = 61500, // 0x0000F03C
|
|||
日期范围错误 = 61501, // 0x0000F03D
|
|||
发送消息失败_该用户已被加入黑名单_无法向此发送消息 = 62751, // 0x0000F51F
|
|||
门店不存在 = 65115, // 0x0000FE5B
|
|||
该门店状态不允许更新 = 65118, // 0x0000FE5E
|
|||
标签格式错误 = 85006, // 0x00014C0E
|
|||
页面路径错误 = 85007, // 0x00014C0F
|
|||
类目填写错误 = 85008, // 0x00014C10
|
|||
已经有正在审核的版本 = 85009, // 0x00014C11
|
|||
item_list有项目为空 = 85010, // 0x00014C12
|
|||
标题填写错误 = 85011, // 0x00014C13
|
|||
无效的审核id = 85012, // 0x00014C14
|
|||
版本输入错误 = 85015, // 0x00014C17
|
|||
没有审核版本 = 85019, // 0x00014C1B
|
|||
审核状态未满足发布 = 85020, // 0x00014C1C
|
|||
状态不可变 = 85021, // 0x00014C1D
|
|||
action非法 = 85022, // 0x00014C1E
|
|||
审核列表填写的项目数不在1到5以内 = 85023, // 0x00014C1F
|
|||
需要补充相应资料_填写org_code和other_files参数 = 85024, // 0x00014C20
|
|||
管理员手机登记数量已超过上限 = 85025, // 0x00014C21
|
|||
该微信号已绑定5个管理员 = 85026, // 0x00014C22
|
|||
管理员身份证已登记过5次 = 85027, // 0x00014C23
|
|||
该主体登记数量已超过上限 = 85028, // 0x00014C24
|
|||
商家名称已被占用 = 85029, // 0x00014C25
|
|||
不能使用该名称 = 85031, // 0x00014C27
|
|||
该名称在侵权投诉保护期 = 85032, // 0x00014C28
|
|||
名称包含违规内容或微信等保留字 = 85033, // 0x00014C29
|
|||
商家名称在改名15天保护期内 = 85034, // 0x00014C2A
|
|||
需与该帐号相同主体才可申请 = 85035, // 0x00014C2B
|
|||
介绍中含有虚假混淆内容 = 85036, // 0x00014C2C
|
|||
头像或者简介修改达到每个月上限 = 85049, // 0x00014C39
|
|||
正在审核中_请勿重复提交 = 85050, // 0x00014C3A
|
|||
请先成功创建门店后再调用 = 85053, // 0x00014C3D
|
|||
临时mediaid无效 = 85056, // 0x00014C40
|
|||
链接错误 = 85066, // 0x00014C4A
|
|||
测试链接不是子链接 = 85068, // 0x00014C4C
|
|||
校验文件失败 = 85069, // 0x00014C4D
|
|||
个人类型小程序无法设置二维码规则 = 85070, // 0x00014C4E
|
|||
已添加该链接_请勿重复添加 = 85071, // 0x00014C4F
|
|||
该链接已被占用 = 85072, // 0x00014C50
|
|||
二维码规则已满 = 85073, // 0x00014C51
|
|||
小程序未发布_小程序必须先发布代码才可以发布二维码跳转规则 = 85074, // 0x00014C52
|
|||
个人类型小程序无法设置二维码规则1 = 85075, // 0x00014C53
|
|||
小程序没有线上版本_不能进行灰度 = 85079, // 0x00014C57
|
|||
小程序提交的审核未审核通过 = 85080, // 0x00014C58
|
|||
无效的发布比例 = 85081, // 0x00014C59
|
|||
当前的发布比例需要比之前设置的高 = 85082, // 0x00014C5A
|
|||
小程序提审数量已达本月上限 = 85085, // 0x00014C5D
|
|||
提交代码审核之前需提前上传代码 = 85086, // 0x00014C5E
|
|||
小程序已使用_api_navigateToMiniProgram_请声明跳转_appid_列表后再次提交 = 85087, // 0x00014C5F
|
|||
不是由第三方代小程序进行调用 = 86000, // 0x00014FF0
|
|||
不存在第三方的已经提交的代码 = 86001, // 0x00014FF1
|
|||
小程序还未设置昵称_头像_简介_请先设置完后再重新提交 = 86002, // 0x00014FF2
|
|||
无效微信号 = 86004, // 0x00014FF4
|
|||
/// <summary>
|
|||
/// 小程序为“签名错误”。对应公众号: 87009, “errmsg” : “reply is not exists” //该回复不存在
|
|||
/// </summary>
|
|||
签名错误 = 87009, // 0x000153E1
|
|||
现网已经在灰度发布_不能进行版本回退 = 87011, // 0x000153E3
|
|||
该版本不能回退_可能的原因_1_无上一个线上版用于回退_2_此版本为已回退版本_不能回退_3_此版本为回退功能上线之前的版本_不能回退 = 87012, // 0x000153E4
|
|||
内容含有违法违规内容 = 87014, // 0x000153E6
|
|||
没有留言权限 = 88000, // 0x000157C0
|
|||
该图文不存在 = 88001, // 0x000157C1
|
|||
文章存在敏感信息 = 88002, // 0x000157C2
|
|||
精选评论数已达上限 = 88003, // 0x000157C3
|
|||
已被用户删除_无法精选 = 88004, // 0x000157C4
|
|||
已经回复过了 = 88005, // 0x000157C5
|
|||
回复超过长度限制或为0 = 88007, // 0x000157C7
|
|||
该评论不存在 = 88008, // 0x000157C8
|
|||
获取评论数目不合法 = 88010, // 0x000157CA
|
|||
该公众号_小程序已经绑定了开放平台帐号 = 89000, // 0x00015BA8
|
|||
业务域名无更改_无需重复设置 = 89019, // 0x00015BBB
|
|||
尚未设置小程序业务域名_请先在第三方平台中设置小程序业务域名后在调用本接口 = 89020, // 0x00015BBC
|
|||
请求保存的域名不是第三方平台中已设置的小程序业务域名或子域名 = 89021, // 0x00015BBD
|
|||
业务域名数量超过限制_最多可以添加100个业务域名 = 89029, // 0x00015BC5
|
|||
个人小程序不支持调用_setwebviewdomain_接口 = 89231, // 0x00015C8F
|
|||
内部错误 = 89247, // 0x00015C9F
|
|||
企业代码类型无效_请选择正确类型填写 = 89248, // 0x00015CA0
|
|||
该主体已有任务执行中_距上次任务24h后再试 = 89249, // 0x00015CA1
|
|||
未找到该任务 = 89250, // 0x00015CA2
|
|||
待法人人脸核身校验 = 89251, // 0x00015CA3
|
|||
法人_企业信息一致性校验中 = 89252, // 0x00015CA4
|
|||
缺少参数 = 89253, // 0x00015CA5
|
|||
第三方权限集不全_补全权限集全网发布后生效 = 89254, // 0x00015CA6
|
|||
系统不稳定_请稍后再试_如多次失败请通过社区反馈 = 89401, // 0x00015D39
|
|||
该审核单不在待审核队列_请检查是否已提交审核或已审完 = 89402, // 0x00015D3A
|
|||
本单属于平台不支持加急种类_请等待正常审核流程 = 89403, // 0x00015D3B
|
|||
本单已加速成功_请勿重复提交 = 89404, // 0x00015D3C
|
|||
本月加急额度不足_请提升提审质量以获取更多额度 = 89405, // 0x00015D3D
|
|||
该经营资质已添加_请勿重复添加 = 92000, // 0x00016760
|
|||
附近地点添加数量达到上线_无法继续添加 = 92002, // 0x00016762
|
|||
地点已被其它小程序占用 = 92003, // 0x00016763
|
|||
附近功能被封禁 = 92004, // 0x00016764
|
|||
地点正在审核中 = 92005, // 0x00016765
|
|||
地点正在展示小程序 = 92006, // 0x00016766
|
|||
地点审核失败 = 92007, // 0x00016767
|
|||
程序未展示在该地点 = 92008, // 0x00016768
|
|||
小程序未上架或不可见 = 92009, // 0x00016769
|
|||
地点不存在 = 93010, // 0x00016B52
|
|||
个人类型小程序不可用 = 93011, // 0x00016B53
|
|||
已下发的模板消息法人并未确认且已超时_24h_未进行身份证校验 = 100001, // 0x000186A1
|
|||
已下发的模板消息法人并未确认且已超时_24h_未进行人脸识别校验 = 100002, // 0x000186A2
|
|||
已下发的模板消息法人并未确认且已超时_24h = 100003, // 0x000186A3
|
|||
此账号已被封禁_无法操作 = 200011, // 0x00030D4B
|
|||
私有模板数已达上限_上限_50_个 = 200012, // 0x00030D4C
|
|||
此模版已被封禁_无法选用 = 200013, // 0x00030D4D
|
|||
模版tid参数错误 = 200014, // 0x00030D4E
|
|||
关键词列表kidList参数错误 = 200020, // 0x00030D54
|
|||
场景描述sceneDesc参数错误 = 200021, // 0x00030D55
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
using MediatR; |
|||
using SKIT.FlurlHttpClient.Wechat.Api; |
|||
using SKIT.FlurlHttpClient.Wechat.Api.Events; |
|||
using Zncy.CloudCar.WeChat.Service.Models; |
|||
using Zncy.CloudCar.WeChat.Service.Services.HttpClients; |
|||
using Znyc.CloudCar.Utility.Helper; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Mediator |
|||
{ |
|||
/// <summary>
|
|||
/// 表示 TEXT 事件的数据
|
|||
/// </summary>
|
|||
public class ImageMessageEventCommand : IRequest<WeChatApiCallBack> |
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“EventObj”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public ImageMessageEvent EventObj { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“EventObj”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
} |
|||
|
|||
public class ImageMessageEventCommandHandler : IRequestHandler<ImageMessageEventCommand, WeChatApiCallBack> |
|||
{ |
|||
private readonly IWeChatApiHttpClientFactory _weChatApiHttpClientFactory; |
|||
|
|||
public ImageMessageEventCommandHandler(IWeChatApiHttpClientFactory weChatApiHttpClientFactory) |
|||
{ |
|||
_weChatApiHttpClientFactory = weChatApiHttpClientFactory; |
|||
} |
|||
|
|||
public async Task<WeChatApiCallBack> Handle(ImageMessageEventCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
var jm = new WeChatApiCallBack() { Status = true }; |
|||
if (request.EventObj != null) |
|||
{ |
|||
var client = _weChatApiHttpClientFactory.CreateWxOpenClient(); |
|||
var replyModel = new SKIT.FlurlHttpClient.Wechat.Api.Events.TransferCustomerServiceReply() |
|||
{ |
|||
ToUserName = request.EventObj.FromUserName, |
|||
FromUserName = request.EventObj.ToUserName, |
|||
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds() |
|||
}; |
|||
var replyXml = client.SerializeEventToXml(replyModel); |
|||
jm.Data = replyXml; |
|||
} |
|||
return await Task.FromResult(jm); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
using MediatR; |
|||
using SKIT.FlurlHttpClient.Wechat.Api; |
|||
using SKIT.FlurlHttpClient.Wechat.Api.Events; |
|||
using Zncy.CloudCar.WeChat.Service.Models; |
|||
using Zncy.CloudCar.WeChat.Service.Services.HttpClients; |
|||
using Znyc.CloudCar.Utility.Helper; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Mediator |
|||
{ |
|||
/// <summary>
|
|||
/// 表示 TEXT 事件的数据
|
|||
/// </summary>
|
|||
public class TextMessageEventCommand : IRequest<WeChatApiCallBack> |
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“EventObj”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public TextMessageEvent EventObj { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“EventObj”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
} |
|||
|
|||
public class TextMessageEventCommandHandler : IRequestHandler<TextMessageEventCommand, WeChatApiCallBack> |
|||
{ |
|||
private readonly IWeChatApiHttpClientFactory _weChatApiHttpClientFactory; |
|||
|
|||
public TextMessageEventCommandHandler(IWeChatApiHttpClientFactory weChatApiHttpClientFactory) |
|||
{ |
|||
_weChatApiHttpClientFactory = weChatApiHttpClientFactory; |
|||
} |
|||
|
|||
public Task<WeChatApiCallBack> Handle(TextMessageEventCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
var jm = new WeChatApiCallBack() { Status = true }; |
|||
if (request.EventObj != null) |
|||
{ |
|||
var client = _weChatApiHttpClientFactory.CreateWxOpenClient(); |
|||
var replyModel = new TransferCustomerServiceReply() |
|||
{ |
|||
ToUserName = request.EventObj.FromUserName, |
|||
FromUserName = request.EventObj.ToUserName, |
|||
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds() |
|||
}; |
|||
var replyXml = client.SerializeEventToXml(replyModel); |
|||
jm.Data = replyXml; |
|||
} |
|||
return Task.FromResult(jm); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
|
|||
using MediatR; |
|||
using SKIT.FlurlHttpClient.Wechat.Api; |
|||
using SKIT.FlurlHttpClient.Wechat.Api.Events; |
|||
using Zncy.CloudCar.WeChat.Service.Models; |
|||
using Zncy.CloudCar.WeChat.Service.Services.HttpClients; |
|||
using Znyc.CloudCar.Utility.Helper; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Mediator |
|||
{ |
|||
/// <summary>
|
|||
/// 表示 TEXT 事件的数据
|
|||
/// </summary>
|
|||
public class VoiceMessageEventCommand : IRequest<WeChatApiCallBack> |
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“EventObj”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public VoiceMessageEvent EventObj { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“EventObj”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
} |
|||
|
|||
public class VoiceMessageEventCommandHandler : IRequestHandler<VoiceMessageEventCommand, WeChatApiCallBack> |
|||
{ |
|||
private readonly IWeChatApiHttpClientFactory _weChatApiHttpClientFactory; |
|||
|
|||
public VoiceMessageEventCommandHandler(IWeChatApiHttpClientFactory weChatApiHttpClientFactory) |
|||
{ |
|||
_weChatApiHttpClientFactory = weChatApiHttpClientFactory; |
|||
} |
|||
|
|||
public async Task<WeChatApiCallBack> Handle(VoiceMessageEventCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
var jm = new WeChatApiCallBack() { Status = true }; |
|||
if (request.EventObj != null) |
|||
{ |
|||
var client = _weChatApiHttpClientFactory.CreateWxOpenClient(); |
|||
var replyModel = new SKIT.FlurlHttpClient.Wechat.Api.Events.TransferCustomerServiceReply() |
|||
{ |
|||
ToUserName = request.EventObj.FromUserName, |
|||
FromUserName = request.EventObj.ToUserName, |
|||
CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds() |
|||
}; |
|||
var replyXml = client.SerializeEventToXml(replyModel); |
|||
jm.Data = replyXml; |
|||
} |
|||
return await Task.FromResult(jm); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
public class CreateOrderByJsapiRequest |
|||
{ |
|||
public string MerchantId { get; set; } = default!; |
|||
|
|||
public string AppId { get; set; } = default!; |
|||
|
|||
public string OpenId { get; set; } = default!; |
|||
|
|||
// NOTICE:
|
|||
// 单机演示时金额来源于客户端请求,生产项目请改为服务端计算生成,切勿依赖客户端提供的金额结果。
|
|||
public int Amount { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 用户绑定手机号解密类
|
|||
/// </summary>
|
|||
public class DecodedPhoneNumber : DecodeEntityBase |
|||
{ |
|||
/// <summary>
|
|||
/// 用户绑定的手机号(国外手机号会有区号)
|
|||
/// </summary>
|
|||
public string phoneNumber { get; set; } |
|||
/// <summary>
|
|||
/// 没有区号的手机号
|
|||
/// </summary>
|
|||
public string purePhoneNumber { get; set; } |
|||
/// <summary>
|
|||
/// 区号(Senparc注:国别号)
|
|||
/// </summary>
|
|||
public string countryCode { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
[Serializable] |
|||
public class DecodedRunData : DecodeEntityBase |
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“stepInfoList”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public List<DecodedRunData_StepModel> stepInfoList { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“stepInfoList”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
} |
|||
|
|||
[Serializable] |
|||
public class DecodedRunData_StepModel |
|||
{ |
|||
public long timestamp { get; set; } |
|||
public long step { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>接收加密信息统一基类(同时也支持非加密信息)</summary>
|
|||
public abstract class EncryptPostModel : IEncryptPostModel |
|||
{ |
|||
/// <summary>指定当前服务账号的唯一领域定义(主要为 APM 服务),例如 AppId</summary>
|
|||
public abstract string DomainId { get; set; } |
|||
|
|||
/// <summary>Signature</summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“Signature”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string Signature { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“Signature”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>Msg_Signature</summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“Msg_Signature”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string Msg_Signature { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“Msg_Signature”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>Timestamp</summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“Timestamp”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string Timestamp { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“Timestamp”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>Nonce</summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“Nonce”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string Nonce { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“Nonce”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>Token</summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“Token”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string Token { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“Token”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>EncodingAESKey</summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“EncodingAESKey”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string EncodingAESKey { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“EncodingAESKey”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>设置服务器内部保密信息</summary>
|
|||
/// <param name="token"></param>
|
|||
/// <param name="encodingAESKey"></param>
|
|||
public virtual void SetSecretInfo(string token, string encodingAESKey) |
|||
{ |
|||
Token = token; |
|||
EncodingAESKey = encodingAESKey; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>接收加密信息统一接口(同时也支持非加密信息)</summary>
|
|||
public interface IEncryptPostModel |
|||
{ |
|||
/// <summary>指定当前服务账号的唯一领域定义(主要为 APM 服务),例如 AppId</summary>
|
|||
string DomainId { get; set; } |
|||
|
|||
/// <summary>Signature</summary>
|
|||
string Signature { get; set; } |
|||
|
|||
/// <summary>Msg_Signature</summary>
|
|||
string Msg_Signature { get; set; } |
|||
|
|||
/// <summary>Timestamp</summary>
|
|||
string Timestamp { get; set; } |
|||
|
|||
/// <summary>Nonce</summary>
|
|||
string Nonce { get; set; } |
|||
|
|||
/// <summary>Token</summary>
|
|||
string Token { get; set; } |
|||
|
|||
/// <summary>EncodingAESKey</summary>
|
|||
string EncodingAESKey { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 微信公众服务器Post过来的加密参数集合(不包括PostData)
|
|||
/// <para>如需使用 NeuChar,需要在 MessageHandler 中提供 PostModel 并设置 AppId</para>
|
|||
/// </summary>
|
|||
public class PostModel : EncryptPostModel |
|||
{ |
|||
public override string DomainId |
|||
{ |
|||
get => AppId; |
|||
set => AppId = value; |
|||
} |
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“AppId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string AppId { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“AppId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>设置服务器内部保密信息</summary>
|
|||
/// <param name="token"></param>
|
|||
/// <param name="encodingAESKey"></param>
|
|||
/// <param name="appId"></param>
|
|||
public void SetSecretInfo(string token, string encodingAESKey, string appId) |
|||
{ |
|||
Token = token; |
|||
EncodingAESKey = encodingAESKey; |
|||
AppId = appId; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
using Newtonsoft.Json.Linq; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 处理器-微信模板消息【小程序,公众号都走这里】
|
|||
/// </summary>
|
|||
public class SendWxTemplateMessage |
|||
{ |
|||
/// <summary>
|
|||
/// 用户序列
|
|||
/// </summary>
|
|||
public int userId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 类型
|
|||
/// </summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“code”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string code { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“code”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
/// <summary>
|
|||
/// 传递数据
|
|||
/// </summary>
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“parameters”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public JObject parameters { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“parameters”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
using Zncy.CloudCar.WeChat.Service.Utilities; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 水印
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public class Watermark |
|||
{ |
|||
public string appid { get; set; } |
|||
public long timestamp { get; set; } |
|||
|
|||
public DateTimeOffset DateTimeStamp |
|||
{ |
|||
get { return DateTimeHelper.GetDateTimeFromXml(timestamp); } |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 微信接口回调Json实体
|
|||
/// </summary>
|
|||
public class WeChatApiCallBack |
|||
{ |
|||
/// <summary>
|
|||
/// 提交数据
|
|||
/// </summary>
|
|||
#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
public object OtherData { get; set; } = null; |
|||
#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
|
|||
/// <summary>
|
|||
/// 状态码
|
|||
/// </summary>
|
|||
public bool Status { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// 信息说明。
|
|||
/// </summary>
|
|||
public string Msg { get; set; } = "响应成功"; |
|||
|
|||
/// <summary>
|
|||
/// 返回数据
|
|||
/// </summary>
|
|||
public string Data { get; set; } = "success"; |
|||
} |
|||
} |
@ -0,0 +1,85 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Models |
|||
{ |
|||
/// <summary>
|
|||
/// 微信小程序用户信息结构
|
|||
/// </summary>
|
|||
public class WeChatUserInfo |
|||
|
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“openId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string openId { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“openId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“nickName”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string nickName { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“nickName”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
public int gender { get; set; } |
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“city”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string city { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“city”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“province”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string province { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“province”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“country”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string country { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“country”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“avatarUrl”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string avatarUrl { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“avatarUrl”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“unionId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string unionId { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“unionId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“watermark”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public Watermark watermark { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“watermark”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
[Serializable] |
|||
public class DecodeEntityBase |
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“watermark”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public Watermark watermark { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“watermark”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解码后的用户信息
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public class DecodedUserInfo : DecodeEntityBase |
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“openId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string openId { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“openId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“nickName”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string nickName { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“nickName”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public int gender { get; set; } |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“city”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string city { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“city”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“province”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string province { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“province”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“country”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string country { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“country”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“avatarUrl”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string avatarUrl { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“avatarUrl”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“unionId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public string unionId { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“unionId”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,30 @@ |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Options |
|||
{ |
|||
public partial class TenpayOptions : IOptions<TenpayOptions> |
|||
{ |
|||
TenpayOptions IOptions<TenpayOptions>.Value => this; |
|||
|
|||
public Types.WechatMerchant[] Merchants { get; set; } = Array.Empty<Types.WechatMerchant>(); |
|||
|
|||
public string NotifyUrl { get; set; } = string.Empty; |
|||
|
|||
} |
|||
public partial class TenpayOptions |
|||
{ |
|||
public static class Types |
|||
{ |
|||
public class WechatMerchant |
|||
{ |
|||
public string MerchantId { get; set; } = string.Empty; |
|||
|
|||
public string SecretV3 { get; set; } = string.Empty; |
|||
|
|||
public string CertSerialNumber { get; set; } = string.Empty; |
|||
|
|||
public string CertPrivateKey { get; set; } = string.Empty; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
|
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Options |
|||
{ |
|||
public class WeChatOptions : IOptions<WeChatOptions> |
|||
{ |
|||
WeChatOptions IOptions<WeChatOptions>.Value => this; |
|||
|
|||
/// <summary>
|
|||
/// 微信公众号AppId
|
|||
/// </summary>
|
|||
public string WeiXinAppId { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 微信公众号Secret
|
|||
/// </summary>
|
|||
public string WeiXinAppSecret { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 微信公众号
|
|||
/// </summary>
|
|||
public string WeiXinEncodingAESKey { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 微信公众号token
|
|||
/// </summary>
|
|||
public string WeiXinToken { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 微信小程序AppId
|
|||
/// </summary>
|
|||
public string WxOpenAppId { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 微信小程序Secret
|
|||
/// </summary>
|
|||
public string WxOpenAppSecret { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 微信小程序token
|
|||
/// </summary>
|
|||
public string WxOpenToken { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 微信小程序
|
|||
/// </summary>
|
|||
public string WxOpenEncodingAESKey { get; set; } = string.Empty; |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
|
|||
using SKIT.FlurlHttpClient.Wechat.Api; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients |
|||
{ |
|||
public interface IWeChatApiHttpClientFactory |
|||
{ |
|||
/// <summary>
|
|||
/// 微信公众号请求
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
WechatApiClient CreateWeXinClient(); |
|||
|
|||
/// <summary>
|
|||
/// 微信小程序请求
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
WechatApiClient CreateWxOpenClient(); |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients |
|||
{ |
|||
public interface IWechatTenpayCertificateManagerFactory |
|||
{ |
|||
CertificateManager Create(string merchantId); |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using SKIT.FlurlHttpClient.Wechat.TenpayV3; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients |
|||
{ |
|||
public interface IWechatTenpayHttpClientFactory |
|||
{ |
|||
WechatTenpayClient Create(string merchantId); |
|||
} |
|||
} |
@ -0,0 +1,95 @@ |
|||
using Flurl; |
|||
using Flurl.Http; |
|||
using Flurl.Http.Configuration; |
|||
using Microsoft.Extensions.Options; |
|||
using SKIT.FlurlHttpClient; |
|||
using SKIT.FlurlHttpClient.Wechat.Api; |
|||
using Zncy.CloudCar.WeChat.Service.Options; |
|||
using IHttpClientFactory = System.Net.Http.IHttpClientFactory; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients |
|||
{ |
|||
public partial class WeChatApiHttpClientFactory : IWeChatApiHttpClientFactory |
|||
{ |
|||
private readonly IHttpClientFactory _httpClientFactory; |
|||
private readonly WeChatOptions _weChatOptions; |
|||
|
|||
public WeChatApiHttpClientFactory( |
|||
IHttpClientFactory httpClientFactory, |
|||
IOptions<WeChatOptions> weChatOptions |
|||
) |
|||
{ |
|||
_httpClientFactory = httpClientFactory; |
|||
_weChatOptions = weChatOptions.Value; |
|||
|
|||
FlurlHttp.GlobalSettings.FlurlClientFactory = new DelegatingFlurlClientFactory(_httpClientFactory); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 微信公众号请求
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public WechatApiClient CreateWeXinClient() |
|||
{ |
|||
if (string.IsNullOrEmpty(_weChatOptions.WeiXinAppId) || string.IsNullOrEmpty(_weChatOptions.WeiXinAppSecret)) |
|||
{ |
|||
throw new Exception("未在配置项中找到微信公众号配置讯息。"); |
|||
} |
|||
var wechatApiClient = new WechatApiClient(new WechatApiClientOptions() |
|||
{ |
|||
AppId = _weChatOptions.WeiXinAppId, |
|||
AppSecret = _weChatOptions.WeiXinAppSecret |
|||
}); |
|||
wechatApiClient.Configure(settings => |
|||
{ |
|||
settings.JsonSerializer = new FlurlNewtonsoftJsonSerializer(); |
|||
}); |
|||
return wechatApiClient; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 微信小程序请求
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public WechatApiClient CreateWxOpenClient() |
|||
{ |
|||
if (string.IsNullOrEmpty(_weChatOptions.WxOpenAppId) || string.IsNullOrEmpty(_weChatOptions.WxOpenAppSecret)) |
|||
{ |
|||
throw new Exception("未在配置项中找到微信小程序配置讯息。"); |
|||
} |
|||
var wechatApiClient = new WechatApiClient(new WechatApiClientOptions() |
|||
{ |
|||
AppId = _weChatOptions.WxOpenAppId, |
|||
AppSecret = _weChatOptions.WxOpenAppSecret |
|||
}); |
|||
wechatApiClient.Configure(settings => |
|||
{ |
|||
settings.JsonSerializer = new FlurlNewtonsoftJsonSerializer(); |
|||
}); |
|||
return wechatApiClient; |
|||
} |
|||
} |
|||
|
|||
public partial class WeChatApiHttpClientFactory |
|||
{ |
|||
internal class DelegatingFlurlClientFactory : IFlurlClientFactory |
|||
{ |
|||
private readonly IHttpClientFactory _httpClientFactory; |
|||
|
|||
public DelegatingFlurlClientFactory(IHttpClientFactory httpClientFactory) |
|||
{ |
|||
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); |
|||
} |
|||
public void Dispose() |
|||
{ |
|||
// Do Nothing
|
|||
} |
|||
|
|||
public IFlurlClient Get(Url url) |
|||
{ |
|||
return new FlurlClient(_httpClientFactory.CreateClient(url.ToUri().Host)); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; |
|||
using System.Collections.Concurrent; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients |
|||
{ |
|||
public class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory |
|||
{ |
|||
private readonly ConcurrentDictionary<string, CertificateManager> _dict; |
|||
|
|||
public WechatTenpayCertificateManagerFactory() |
|||
{ |
|||
_dict = new ConcurrentDictionary<string, CertificateManager>(); |
|||
} |
|||
|
|||
public CertificateManager Create(string merchantId) |
|||
{ |
|||
// NOTICE:
|
|||
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的证书管理器。
|
|||
// 如果你的项目只存在唯一一个租户,那么直接注入 `CertificateManager` 即可。
|
|||
|
|||
return _dict.GetOrAdd(merchantId, new InMemoryCertificateManager()); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,66 @@ |
|||
using Microsoft.Extensions.Options; |
|||
using SKIT.FlurlHttpClient.Wechat.TenpayV3; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients |
|||
{ |
|||
public partial class WechatTenpayHttpClientFactory : IWechatTenpayHttpClientFactory |
|||
{ |
|||
private readonly IHttpClientFactory _httpClientFactory; |
|||
private readonly Options.TenpayOptions _tenpayOptions; |
|||
private readonly IWechatTenpayCertificateManagerFactory _tenpayCertificateManagerFactory; |
|||
|
|||
public WechatTenpayHttpClientFactory( |
|||
IHttpClientFactory httpClientFactory, |
|||
IOptions<Options.TenpayOptions> tenpayOptions, |
|||
IWechatTenpayCertificateManagerFactory tenpayCertificateManagerFactory) |
|||
{ |
|||
_httpClientFactory = httpClientFactory; |
|||
_tenpayOptions = tenpayOptions.Value; |
|||
_tenpayCertificateManagerFactory = tenpayCertificateManagerFactory; |
|||
} |
|||
|
|||
public WechatTenpayClient Create(string merchantId) |
|||
{ |
|||
// NOTICE:
|
|||
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的 API 客户端。
|
|||
// 如果你的项目只存在唯一一个租户,那么直接注入 `WechatTenpayClient` 即可。
|
|||
|
|||
var tenpayMerchantConfig = _tenpayOptions.Merchants?.FirstOrDefault(e => string.Equals(merchantId, e.MerchantId)); |
|||
if (tenpayMerchantConfig == null) |
|||
throw new Exception("未在配置项中找到该 MerchantId 对应的微信商户号。"); |
|||
|
|||
var wechatTenpayClientOptions = new WechatTenpayClientOptions() |
|||
{ |
|||
MerchantId = tenpayMerchantConfig.MerchantId, |
|||
MerchantV3Secret = tenpayMerchantConfig.SecretV3, |
|||
MerchantCertificateSerialNumber = tenpayMerchantConfig.CertSerialNumber, |
|||
MerchantCertificatePrivateKey = tenpayMerchantConfig.CertPrivateKey, |
|||
PlatformCertificateManager = _tenpayCertificateManagerFactory.Create(tenpayMerchantConfig.MerchantId), |
|||
AutoEncryptRequestSensitiveProperty = true, |
|||
AutoDecryptResponseSensitiveProperty = true |
|||
}; |
|||
var wechatTenpayClient = new WechatTenpayClient(wechatTenpayClientOptions); |
|||
wechatTenpayClient.Configure((settings) => settings.FlurlHttpClientFactory = new DelegatingFlurlClientFactory(_httpClientFactory)); |
|||
return wechatTenpayClient; |
|||
} |
|||
} |
|||
|
|||
public partial class WechatTenpayHttpClientFactory |
|||
{ |
|||
public class DelegatingFlurlClientFactory : Flurl.Http.Configuration.DefaultHttpClientFactory |
|||
{ |
|||
private readonly IHttpClientFactory _httpClientFactory; |
|||
|
|||
public DelegatingFlurlClientFactory(IHttpClientFactory httpClientFactory) |
|||
{ |
|||
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); |
|||
} |
|||
|
|||
public override HttpClient CreateHttpClient(HttpMessageHandler handler) |
|||
{ |
|||
return _httpClientFactory.CreateClient(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
@ -0,0 +1,61 @@ |
|||
|
|||
|
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using Zncy.CloudCar.WeChat.Service.Models; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// 签名验证类
|
|||
/// </summary>
|
|||
public class CheckSignature |
|||
{ |
|||
/// <summary>在网站没有提供Token(或传入为null)的情况下的默认Token,建议在网站中进行配置。</summary>
|
|||
public const string Token = "weixin"; |
|||
|
|||
/// <summary>检查签名是否正确</summary>
|
|||
/// <param name="signature"></param>
|
|||
/// <param name="postModel">需要提供:Timestamp、Nonce、Token</param>
|
|||
/// <returns></returns>
|
|||
public static bool Check(string signature, PostModel postModel) => Check(signature, postModel.Timestamp, postModel.Nonce, postModel.Token); |
|||
|
|||
/// <summary>检查签名是否正确</summary>
|
|||
/// <param name="signature"></param>
|
|||
/// <param name="timestamp"></param>
|
|||
/// <param name="nonce"></param>
|
|||
/// <param name="token"></param>
|
|||
/// <returns></returns>
|
|||
#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
public static bool Check(string signature, string timestamp, string nonce, string token = null) => signature == GetSignature(timestamp, nonce, token); |
|||
#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
|
|||
/// <summary>返回正确的签名</summary>
|
|||
/// <param name="postModel">需要提供:Timestamp、Nonce、Token</param>
|
|||
/// <returns></returns>
|
|||
public static string GetSignature(PostModel postModel) => GetSignature(postModel.Timestamp, postModel.Nonce, postModel.Token); |
|||
|
|||
/// <summary>返回正确的签名</summary>
|
|||
/// <param name="timestamp"></param>
|
|||
/// <param name="nonce"></param>
|
|||
/// <param name="token"></param>
|
|||
/// <returns></returns>
|
|||
#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
public static string GetSignature(string timestamp, string nonce, string token = null) |
|||
#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
{ |
|||
token = token ?? "weixin"; |
|||
string s = string.Join("", ((IEnumerable<string>)new string[3] |
|||
{ |
|||
token, |
|||
timestamp, |
|||
nonce |
|||
}).OrderBy(z => z).ToArray()); |
|||
byte[] hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(s)); |
|||
StringBuilder stringBuilder = new StringBuilder(); |
|||
foreach (byte num in hash) |
|||
stringBuilder.AppendFormat("{0:x2}", num); |
|||
return stringBuilder.ToString(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,235 @@ |
|||
using System.Net; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
public class Cryptography |
|||
{ |
|||
public static uint HostToNetworkOrder(uint inval) |
|||
{ |
|||
uint outval = 0; |
|||
for (int i = 0; i < 4; i++) |
|||
outval = (outval << 8) + (inval >> i * 8 & 255); |
|||
return outval; |
|||
} |
|||
|
|||
public static int HostToNetworkOrder(int inval) |
|||
{ |
|||
int outval = 0; |
|||
for (int i = 0; i < 4; i++) |
|||
outval = (outval << 8) + (inval >> i * 8 & 255); |
|||
return outval; |
|||
} |
|||
/// <summary>
|
|||
/// 解密方法
|
|||
/// </summary>
|
|||
/// <param name="Input">密文</param>
|
|||
/// <param name="EncodingAESKey"></param>
|
|||
/// <returns></returns>
|
|||
///
|
|||
public static string AES_decrypt(string Input, string EncodingAESKey, ref string appid) |
|||
{ |
|||
byte[] Key; |
|||
Key = Convert.FromBase64String(EncodingAESKey + "="); |
|||
byte[] Iv = new byte[16]; |
|||
Array.Copy(Key, Iv, 16); |
|||
byte[] btmpMsg = AES_decrypt(Input, Iv, Key); |
|||
|
|||
int len = BitConverter.ToInt32(btmpMsg, 16); |
|||
len = IPAddress.NetworkToHostOrder(len); |
|||
|
|||
|
|||
byte[] bMsg = new byte[len]; |
|||
byte[] bAppid = new byte[btmpMsg.Length - 20 - len]; |
|||
Array.Copy(btmpMsg, 20, bMsg, 0, len); |
|||
Array.Copy(btmpMsg, 20 + len, bAppid, 0, btmpMsg.Length - 20 - len); |
|||
string oriMsg = Encoding.UTF8.GetString(bMsg); |
|||
appid = Encoding.UTF8.GetString(bAppid); |
|||
|
|||
|
|||
return oriMsg; |
|||
} |
|||
|
|||
public static string AES_encrypt(string Input, string EncodingAESKey, string appid) |
|||
{ |
|||
byte[] Key; |
|||
Key = Convert.FromBase64String(EncodingAESKey + "="); |
|||
byte[] Iv = new byte[16]; |
|||
Array.Copy(Key, Iv, 16); |
|||
string Randcode = CreateRandCode(16); |
|||
byte[] bRand = Encoding.UTF8.GetBytes(Randcode); |
|||
byte[] bAppid = Encoding.UTF8.GetBytes(appid); |
|||
byte[] btmpMsg = Encoding.UTF8.GetBytes(Input); |
|||
byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length)); |
|||
byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bAppid.Length + btmpMsg.Length]; |
|||
|
|||
Array.Copy(bRand, bMsg, bRand.Length); |
|||
Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length); |
|||
Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length); |
|||
Array.Copy(bAppid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bAppid.Length); |
|||
|
|||
return AES_encrypt(bMsg, Iv, Key); |
|||
|
|||
} |
|||
private static string CreateRandCode(int codeLen) |
|||
{ |
|||
string codeSerial = "2,3,4,5,6,7,a,c,d,e,f,h,i,j,k,m,n,p,r,s,t,A,C,D,E,F,G,H,J,K,M,N,P,Q,R,S,U,V,W,X,Y,Z"; |
|||
if (codeLen == 0) |
|||
{ |
|||
codeLen = 16; |
|||
} |
|||
string[] arr = codeSerial.Split(','); |
|||
string code = ""; |
|||
int randValue = -1; |
|||
Random rand = new Random(unchecked((int)DateTime.Now.Ticks)); |
|||
for (int i = 0; i < codeLen; i++) |
|||
{ |
|||
randValue = rand.Next(0, arr.Length - 1); |
|||
code += arr[randValue]; |
|||
} |
|||
return code; |
|||
} |
|||
|
|||
private static string AES_encrypt(string Input, byte[] Iv, byte[] Key) |
|||
{ |
|||
var aes = new RijndaelManaged(); |
|||
//秘钥的大小,以位为单位
|
|||
aes.KeySize = 256; |
|||
//支持的块大小
|
|||
aes.BlockSize = 128; |
|||
//填充模式
|
|||
aes.Padding = PaddingMode.PKCS7; |
|||
aes.Mode = CipherMode.CBC; |
|||
aes.Key = Key; |
|||
aes.IV = Iv; |
|||
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV); |
|||
#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
byte[] xBuff = null; |
|||
#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
|
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write)) |
|||
{ |
|||
byte[] xXml = Encoding.UTF8.GetBytes(Input); |
|||
cs.Write(xXml, 0, xXml.Length); |
|||
} |
|||
xBuff = ms.ToArray(); |
|||
} |
|||
string Output = Convert.ToBase64String(xBuff); |
|||
return Output; |
|||
} |
|||
|
|||
private static string AES_encrypt(byte[] Input, byte[] Iv, byte[] Key) |
|||
{ |
|||
var aes = new RijndaelManaged(); |
|||
//秘钥的大小,以位为单位
|
|||
aes.KeySize = 256; |
|||
//支持的块大小
|
|||
aes.BlockSize = 128; |
|||
//填充模式
|
|||
//aes.Padding = PaddingMode.PKCS7;
|
|||
aes.Padding = PaddingMode.None; |
|||
aes.Mode = CipherMode.CBC; |
|||
aes.Key = Key; |
|||
aes.IV = Iv; |
|||
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV); |
|||
#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
byte[] xBuff = null; |
|||
#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
|
|||
#region 自己进行PKCS7补位,用系统自己带的不行
|
|||
byte[] msg = new byte[Input.Length + 32 - Input.Length % 32]; |
|||
Array.Copy(Input, msg, Input.Length); |
|||
byte[] pad = KCS7Encoder(Input.Length); |
|||
Array.Copy(pad, 0, msg, Input.Length, pad.Length); |
|||
#endregion
|
|||
|
|||
#region 注释的也是一种方法,效果一样
|
|||
//ICryptoTransform transform = aes.CreateEncryptor();
|
|||
//byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);
|
|||
#endregion
|
|||
|
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write)) |
|||
{ |
|||
cs.Write(msg, 0, msg.Length); |
|||
} |
|||
xBuff = ms.ToArray(); |
|||
} |
|||
|
|||
string Output = Convert.ToBase64String(xBuff); |
|||
return Output; |
|||
} |
|||
|
|||
private static byte[] KCS7Encoder(int text_length) |
|||
{ |
|||
int block_size = 32; |
|||
// 计算需要填充的位数
|
|||
int amount_to_pad = block_size - text_length % block_size; |
|||
if (amount_to_pad == 0) |
|||
{ |
|||
amount_to_pad = block_size; |
|||
} |
|||
// 获得补位所用的字符
|
|||
char pad_chr = chr(amount_to_pad); |
|||
string tmp = ""; |
|||
for (int index = 0; index < amount_to_pad; index++) |
|||
{ |
|||
tmp += pad_chr; |
|||
} |
|||
return Encoding.UTF8.GetBytes(tmp); |
|||
} |
|||
/** |
|||
* 将数字转化成ASCII码对应的字符,用于对明文进行补码 |
|||
* |
|||
* @param a 需要转化的数字 |
|||
* @return 转化得到的字符 |
|||
*/ |
|||
static char chr(int a) |
|||
{ |
|||
|
|||
byte target = (byte)(a & 0xFF); |
|||
return (char)target; |
|||
} |
|||
private static byte[] AES_decrypt(string Input, byte[] Iv, byte[] Key) |
|||
{ |
|||
RijndaelManaged aes = new RijndaelManaged(); |
|||
aes.KeySize = 256; |
|||
aes.BlockSize = 128; |
|||
aes.Mode = CipherMode.CBC; |
|||
aes.Padding = PaddingMode.None; |
|||
aes.Key = Key; |
|||
aes.IV = Iv; |
|||
var decrypt = aes.CreateDecryptor(aes.Key, aes.IV); |
|||
#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
byte[] xBuff = null; |
|||
#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write)) |
|||
{ |
|||
byte[] xXml = Convert.FromBase64String(Input); |
|||
byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32]; |
|||
Array.Copy(xXml, msg, xXml.Length); |
|||
cs.Write(xXml, 0, xXml.Length); |
|||
} |
|||
xBuff = decode2(ms.ToArray()); |
|||
} |
|||
return xBuff; |
|||
} |
|||
private static byte[] decode2(byte[] decrypted) |
|||
{ |
|||
int pad = decrypted[decrypted.Length - 1]; |
|||
if (pad < 1 || pad > 32) |
|||
{ |
|||
pad = 0; |
|||
} |
|||
byte[] res = new byte[decrypted.Length - pad]; |
|||
Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad); |
|||
return res; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// 微信日期处理帮助类
|
|||
/// </summary>
|
|||
public class DateTimeHelper |
|||
{ |
|||
/// <summary>Unix起始时间</summary>
|
|||
public static readonly DateTimeOffset BaseTime = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); |
|||
|
|||
/// <summary>转换微信DateTime时间到C#时间</summary>
|
|||
/// <param name="dateTimeFromXml">微信DateTime</param>
|
|||
/// <returns></returns>
|
|||
public static DateTime GetDateTimeFromXml(long dateTimeFromXml) => GetDateTimeOffsetFromXml(dateTimeFromXml).LocalDateTime; |
|||
|
|||
/// <summary>转换微信DateTime时间到C#时间</summary>
|
|||
/// <param name="dateTimeFromXml">微信DateTime</param>
|
|||
/// <returns></returns>
|
|||
public static DateTime GetDateTimeFromXml(string dateTimeFromXml) => GetDateTimeFromXml(long.Parse(dateTimeFromXml)); |
|||
|
|||
/// <summary>转换微信DateTimeOffset时间到C#时间</summary>
|
|||
/// <param name="dateTimeFromXml">微信DateTime</param>
|
|||
/// <returns></returns>
|
|||
public static DateTimeOffset GetDateTimeOffsetFromXml(long dateTimeFromXml) => BaseTime.AddSeconds(dateTimeFromXml).ToLocalTime(); |
|||
|
|||
/// <summary>转换微信DateTimeOffset时间到C#时间</summary>
|
|||
/// <param name="dateTimeFromXml">微信DateTime</param>
|
|||
/// <returns></returns>
|
|||
public static DateTimeOffset GetDateTimeOffsetFromXml(string dateTimeFromXml) => GetDateTimeFromXml(long.Parse(dateTimeFromXml)); |
|||
|
|||
/// <summary>获取微信DateTime(UNIX时间戳)</summary>
|
|||
/// <param name="dateTime">时间</param>
|
|||
/// <returns></returns>
|
|||
[Obsolete("请使用 GetUnixDateTime(dateTime) 方法")] |
|||
public static long GetWeixinDateTime(DateTime dateTime) => GetUnixDateTime(dateTime); |
|||
|
|||
/// <summary>获取Unix时间戳</summary>
|
|||
/// <param name="dateTime"></param>
|
|||
/// <returns></returns>
|
|||
public static long GetUnixDateTime(DateTimeOffset dateTime) => (long)(dateTime - BaseTime).TotalSeconds; |
|||
|
|||
/// <summary>获取Unix时间戳</summary>
|
|||
/// <param name="dateTime"></param>
|
|||
/// <returns></returns>
|
|||
public static long GetUnixDateTime(DateTime dateTime) => (long)(dateTime.ToUniversalTime() - BaseTime).TotalSeconds; |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
public static class DocumentExtensions |
|||
{ |
|||
public static XmlDocument ToXmlDocument(this XDocument xDocument) |
|||
{ |
|||
var xmlDocument = new XmlDocument(); |
|||
using (var xmlReader = xDocument.CreateReader()) |
|||
{ |
|||
xmlDocument.Load(xmlReader); |
|||
} |
|||
return xmlDocument; |
|||
} |
|||
|
|||
public static XDocument ToXDocument(this XmlDocument xmlDocument) |
|||
{ |
|||
using (var nodeReader = new XmlNodeReader(xmlDocument)) |
|||
{ |
|||
nodeReader.MoveToContent(); |
|||
return XDocument.Load(nodeReader); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,320 @@ |
|||
|
|||
|
|||
using Newtonsoft.Json; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using Zncy.CloudCar.WeChat.Service.Models; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// 签名及加密帮助类
|
|||
/// </summary>
|
|||
public static class EncryptHelper |
|||
{ |
|||
///// <summary>
|
|||
///// SHA1加密
|
|||
///// </summary>
|
|||
///// <param name="str"></param>
|
|||
///// <returns></returns>
|
|||
//public static string EncryptToSHA1(string str)
|
|||
//{
|
|||
// SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
|
|||
// byte[] str1 = Encoding.UTF8.GetBytes(str);
|
|||
// byte[] str2 = sha1.ComputeHash(str1);
|
|||
// sha1.Clear();
|
|||
// (sha1 as IDisposable).Dispose();
|
|||
// return Convert.ToBase64String(str2);
|
|||
//}
|
|||
|
|||
#region 签名
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// 获得签名
|
|||
/// </summary>
|
|||
/// <param name="rawData"></param>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <returns></returns>
|
|||
public static string GetSignature(string rawData, string sessionKey) |
|||
{ |
|||
var signature = GetSha1(rawData + sessionKey); |
|||
//Senparc.Weixin.Helpers.EncryptHelper.SHA1_Encrypt(rawData + sessionKey);
|
|||
return signature; |
|||
} |
|||
|
|||
/// <summary>采用SHA-1算法加密字符串(小写)</summary>
|
|||
/// <param name="encypStr">需要加密的字符串</param>
|
|||
/// <returns></returns>
|
|||
public static string GetSha1(string encypStr) |
|||
{ |
|||
byte[] hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(encypStr)); |
|||
StringBuilder stringBuilder = new StringBuilder(); |
|||
foreach (byte num in hash) |
|||
stringBuilder.AppendFormat("{0:x2}", num); |
|||
return stringBuilder.ToString(); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 比较签名是否正确
|
|||
/// </summary>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <param name="rawData"></param>
|
|||
/// <param name="compareSignature"></param>
|
|||
/// <exception cref="WxOpenException">当SessionId或SessionKey无效时抛出异常</exception>
|
|||
/// <returns></returns>
|
|||
public static bool CheckSignature(string sessionKey, string rawData, string compareSignature) |
|||
{ |
|||
var signature = GetSignature(rawData, sessionKey); |
|||
return signature == compareSignature; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region 解密
|
|||
|
|||
#region 私有方法
|
|||
|
|||
private static byte[] AES_Decrypt(string Input, byte[] Iv, byte[] Key) |
|||
{ |
|||
#if NET45
|
|||
RijndaelManaged aes = new RijndaelManaged(); |
|||
#else
|
|||
SymmetricAlgorithm aes = Aes.Create(); |
|||
#endif
|
|||
aes.KeySize = 128;//原始:256
|
|||
aes.BlockSize = 128; |
|||
aes.Mode = CipherMode.CBC; |
|||
aes.Padding = PaddingMode.PKCS7; |
|||
aes.Key = Key; |
|||
aes.IV = Iv; |
|||
var decrypt = aes.CreateDecryptor(aes.Key, aes.IV); |
|||
#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
byte[] xBuff = null; |
|||
#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
|
|||
//using (ICryptoTransform decrypt = aes.CreateDecryptor(aes.Key, aes.IV) /*aes.CreateDecryptor()*/)
|
|||
//{
|
|||
// var src = Convert.FromBase64String(Input);
|
|||
// byte[] dest = decrypt.TransformFinalBlock(src, 0, src.Length);
|
|||
// return dest;
|
|||
// //return Encoding.UTF8.GetString(dest);
|
|||
//}
|
|||
|
|||
|
|||
try |
|||
{ |
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write)) |
|||
{ |
|||
//cs.Read(decryptBytes, 0, decryptBytes.Length);
|
|||
//cs.Close();
|
|||
//ms.Close();
|
|||
|
|||
//cs.FlushFinalBlock();//用于解决第二次获取小程序Session解密出错的情况
|
|||
|
|||
|
|||
byte[] xXml = Convert.FromBase64String(Input); |
|||
byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32]; |
|||
Array.Copy(xXml, msg, xXml.Length); |
|||
cs.Write(xXml, 0, xXml.Length); |
|||
} |
|||
//cs.Dispose();
|
|||
xBuff = decode2(ms.ToArray()); |
|||
} |
|||
} |
|||
catch (CryptographicException) |
|||
{ |
|||
//Padding is invalid and cannot be removed.
|
|||
Console.WriteLine("===== CryptographicException ====="); |
|||
|
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
//cs 不自动释放,用于避免“Padding is invalid and cannot be removed”的错误 —— 2019.07.27 Jeffrey
|
|||
var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write); |
|||
{ |
|||
//cs.Read(decryptBytes, 0, decryptBytes.Length);
|
|||
//cs.Close();
|
|||
//ms.Close();
|
|||
|
|||
//cs.FlushFinalBlock();//用于解决第二次获取小程序Session解密出错的情况
|
|||
|
|||
byte[] xXml = Convert.FromBase64String(Input); |
|||
byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32]; |
|||
Array.Copy(xXml, msg, xXml.Length); |
|||
cs.Write(xXml, 0, xXml.Length); |
|||
} |
|||
//cs.Dispose();
|
|||
xBuff = decode2(ms.ToArray()); |
|||
} |
|||
} |
|||
return xBuff; |
|||
} |
|||
|
|||
private static byte[] decode2(byte[] decrypted) |
|||
{ |
|||
int pad = decrypted[decrypted.Length - 1]; |
|||
if (pad < 1 || pad > 32) |
|||
{ |
|||
pad = 0; |
|||
} |
|||
byte[] res = new byte[decrypted.Length - pad]; |
|||
Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad); |
|||
return res; |
|||
} |
|||
|
|||
|
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// 解密所有消息的基础方法
|
|||
/// </summary>
|
|||
/// <param name="sessionKey">储存在 SessionBag 中的当前用户 会话 SessionKey</param>
|
|||
/// <param name="encryptedData">接口返回数据中的 encryptedData 参数</param>
|
|||
/// <param name="iv">接口返回数据中的 iv 参数,对称解密算法初始向量</param>
|
|||
/// <returns></returns>
|
|||
public static string DecodeEncryptedData(string sessionKey, string encryptedData, string iv) |
|||
{ |
|||
//var aesCipher = Convert.FromBase64String(encryptedData);
|
|||
var aesKey = Convert.FromBase64String(sessionKey); |
|||
var aesIV = Convert.FromBase64String(iv); |
|||
|
|||
var result = AES_Decrypt(encryptedData, aesIV, aesKey); |
|||
var resultStr = Encoding.UTF8.GetString(result); |
|||
return resultStr; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解密消息(通过SessionId获取)
|
|||
/// </summary>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <param name="encryptedData"></param>
|
|||
/// <param name="iv"></param>
|
|||
/// <exception cref="WxOpenException">当SessionId或SessionKey无效时抛出异常</exception>
|
|||
/// <returns></returns>
|
|||
public static string DecodeEncryptedDataBySessionId(string sessionKey, string encryptedData, string iv) |
|||
{ |
|||
var resultStr = DecodeEncryptedData(sessionKey, encryptedData, iv); |
|||
return resultStr; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 检查解密消息水印
|
|||
/// </summary>
|
|||
/// <param name="entity"></param>
|
|||
/// <param name="appId"></param>
|
|||
/// <returns>entity为null时也会返回false</returns>
|
|||
public static bool CheckWatermark(this DecodeEntityBase entity, string appId) |
|||
{ |
|||
if (entity == null) |
|||
{ |
|||
return false; |
|||
} |
|||
return entity.watermark.appid == appId; |
|||
} |
|||
|
|||
#region 解密实例信息
|
|||
|
|||
/// <summary>
|
|||
/// 解密到实例信息
|
|||
/// </summary>
|
|||
/// <typeparam name="T">DecodeEntityBase</typeparam>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <param name="encryptedData"></param>
|
|||
/// <param name="iv"></param>
|
|||
/// <returns></returns>
|
|||
public static T DecodeEncryptedDataToEntity<T>(string sessionKey, string encryptedData, string iv) |
|||
{ |
|||
var jsonStr = DecodeEncryptedDataBySessionId(sessionKey, encryptedData, iv); |
|||
|
|||
//Console.WriteLine("===== jsonStr =====");
|
|||
//Console.WriteLine(jsonStr);
|
|||
//Console.WriteLine();
|
|||
|
|||
var entity = JsonConvert.DeserializeObject<T>(jsonStr); |
|||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
|||
return entity; |
|||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
|||
} |
|||
/// <summary>
|
|||
/// 解密到实例信息
|
|||
/// </summary>
|
|||
/// <typeparam name="T">DecodeEntityBase</typeparam>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <param name="encryptedData"></param>
|
|||
/// <param name="iv"></param>
|
|||
/// <returns></returns>
|
|||
public static T DecodeEncryptedDataToEntityEasy<T>(string sessionKey, string encryptedData, string iv) |
|||
{ |
|||
var jsonStr = DecodeEncryptedData(sessionKey, encryptedData, iv); |
|||
var entity = JsonConvert.DeserializeObject<T>(jsonStr); |
|||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
|||
return entity; |
|||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解密UserInfo消息(通过SessionId获取)
|
|||
/// </summary>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <param name="encryptedData"></param>
|
|||
/// <param name="iv"></param>
|
|||
/// <exception cref="WxOpenException">当SessionId或SessionKey无效时抛出异常</exception>
|
|||
/// <returns></returns>
|
|||
public static DecodedUserInfo DecodeUserInfoBySessionId(string sessionKey, string encryptedData, string iv) |
|||
{ |
|||
return DecodeEncryptedDataToEntity<DecodedUserInfo>(sessionKey, encryptedData, iv); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解密手机号
|
|||
/// </summary>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <param name="encryptedData"></param>
|
|||
/// <param name="iv"></param>
|
|||
/// <returns></returns>
|
|||
public static DecodedPhoneNumber DecryptPhoneNumber(string sessionKey, string encryptedData, string iv) |
|||
{ |
|||
return DecodeEncryptedDataToEntity<DecodedPhoneNumber>(sessionKey, encryptedData, iv); |
|||
} |
|||
/// <summary>
|
|||
/// 解密手机号(根据sessionKey解密)
|
|||
/// </summary>
|
|||
/// <param name="sessionKey"></param>
|
|||
/// <param name="encryptedData"></param>
|
|||
/// <param name="iv"></param>
|
|||
/// <returns></returns>
|
|||
public static DecodedPhoneNumber DecryptPhoneNumberBySessionKey(string sessionKey, string encryptedData, string iv) |
|||
{ |
|||
//var resultStr = DecodeEncryptedData(sessionKey, encryptedData, iv);
|
|||
|
|||
//var entity = SerializerHelper.GetObject<DecodedPhoneNumber>(resultStr);
|
|||
//return entity;
|
|||
|
|||
return DecodeEncryptedDataToEntityEasy<DecodedPhoneNumber>(sessionKey, encryptedData, iv); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解密微信小程序运动步数
|
|||
/// 2019-04-02
|
|||
/// </summary>
|
|||
/// <param name="sessionId"></param>
|
|||
/// <param name="encryptedData"></param>
|
|||
/// <param name="iv"></param>
|
|||
/// <returns></returns>
|
|||
public static DecodedRunData DecryptRunData(string sessionId, string encryptedData, string iv) |
|||
{ |
|||
return DecodeEncryptedDataToEntity<DecodedRunData>(sessionId, encryptedData, iv); |
|||
} |
|||
|
|||
|
|||
#endregion
|
|||
|
|||
#endregion
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,56 @@ |
|||
|
|||
|
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Http.Features; |
|||
using System.Text; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// HTTP 请求工具类
|
|||
/// </summary>
|
|||
public static class RequestUtility |
|||
{ |
|||
/// <summary>【异步方法】从 Request.Body 中读取流,并复制到一个独立的 MemoryStream 对象中</summary>
|
|||
/// <param name="request"></param>
|
|||
/// <param name="allowSynchronousIO"></param>
|
|||
/// <returns></returns>
|
|||
public static async Task<Stream> GetRequestMemoryStreamAsync( |
|||
this HttpRequest request, |
|||
bool? allowSynchronousIO = true) |
|||
{ |
|||
IHttpBodyControlFeature bodyControlFeature = request.HttpContext.Features.Get<IHttpBodyControlFeature>(); |
|||
if (bodyControlFeature != null && allowSynchronousIO.HasValue) |
|||
bodyControlFeature.AllowSynchronousIO = allowSynchronousIO.Value; |
|||
return new MemoryStream(Encoding.UTF8.GetBytes(await new StreamReader(request.Body).ReadToEndAsync())); |
|||
} |
|||
|
|||
/// <summary>从 Request.Body 中读取流,并复制到一个独立的 MemoryStream 对象中</summary>
|
|||
/// <param name="request"></param>
|
|||
/// <param name="allowSynchronousIO"></param>
|
|||
/// <returns></returns>
|
|||
public static Stream GetRequestStream( |
|||
this HttpRequest request, |
|||
bool? allowSynchronousIO = true) |
|||
{ |
|||
IHttpBodyControlFeature bodyControlFeature = request.HttpContext.Features.Get<IHttpBodyControlFeature>(); |
|||
if (bodyControlFeature != null && allowSynchronousIO.HasValue) |
|||
bodyControlFeature.AllowSynchronousIO = allowSynchronousIO.Value; |
|||
return new MemoryStream(Encoding.UTF8.GetBytes(new StreamReader(request.Body).ReadToEnd())); |
|||
} |
|||
|
|||
/// <summary>从 Request.Body 中读取流,并复制到一个独立的 MemoryStream 对象中</summary>
|
|||
/// <param name="request"></param>
|
|||
/// <param name="allowSynchronousIO"></param>
|
|||
/// <returns></returns>
|
|||
public static MemoryStream GetRequestMemoryStream( |
|||
this HttpRequest request, |
|||
bool? allowSynchronousIO = true) |
|||
{ |
|||
IHttpBodyControlFeature bodyControlFeature = request.HttpContext.Features.Get<IHttpBodyControlFeature>(); |
|||
if (bodyControlFeature != null && allowSynchronousIO.HasValue) |
|||
bodyControlFeature.AllowSynchronousIO = allowSynchronousIO.Value; |
|||
return new MemoryStream(Encoding.UTF8.GetBytes(new StreamReader(request.Body).ReadToEnd())); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,235 @@ |
|||
using System.Collections; |
|||
//using System.Web;
|
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using System.Xml; |
|||
//-40001 : 签名验证错误
|
|||
//-40002 : xml解析失败
|
|||
//-40003 : sha加密生成签名失败
|
|||
//-40004 : AESKey 非法
|
|||
//-40005 : appid 校验错误
|
|||
//-40006 : AES 加密失败
|
|||
//-40007 : AES 解密失败
|
|||
//-40008 : 解密后得到的buffer非法
|
|||
//-40009 : base64加密异常
|
|||
//-40010 : base64解密异常
|
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
public class WXBizMsgCrypt |
|||
{ |
|||
readonly string m_sToken; |
|||
readonly string m_sEncodingAESKey; |
|||
readonly string m_sAppID; |
|||
enum WXBizMsgCryptErrorCode |
|||
{ |
|||
WXBizMsgCrypt_OK = 0, |
|||
WXBizMsgCrypt_ValidateSignature_Error = -40001, |
|||
WXBizMsgCrypt_ParseXml_Error = -40002, |
|||
WXBizMsgCrypt_ComputeSignature_Error = -40003, |
|||
WXBizMsgCrypt_IllegalAesKey = -40004, |
|||
WXBizMsgCrypt_ValidateAppid_Error = -40005, |
|||
WXBizMsgCrypt_EncryptAES_Error = -40006, |
|||
WXBizMsgCrypt_DecryptAES_Error = -40007, |
|||
WXBizMsgCrypt_IllegalBuffer = -40008, |
|||
WXBizMsgCrypt_EncodeBase64_Error = -40009, |
|||
WXBizMsgCrypt_DecodeBase64_Error = -40010 |
|||
}; |
|||
|
|||
//构造函数
|
|||
// @param sToken: 公众平台上,开发者设置的Token
|
|||
// @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
|
|||
// @param sAppID: 公众帐号的appid
|
|||
public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sAppID) |
|||
{ |
|||
m_sToken = sToken; |
|||
m_sAppID = sAppID; |
|||
m_sEncodingAESKey = sEncodingAESKey; |
|||
} |
|||
|
|||
|
|||
// 检验消息的真实性,并且获取解密后的明文
|
|||
// @param sMsgSignature: 签名串,对应URL参数的msg_signature
|
|||
// @param sTimeStamp: 时间戳,对应URL参数的timestamp
|
|||
// @param sNonce: 随机串,对应URL参数的nonce
|
|||
// @param sPostData: 密文,对应POST请求的数据
|
|||
// @param sMsg: 解密后的原文,当return返回0时有效
|
|||
// @return: 成功0,失败返回对应的错误码
|
|||
public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg) |
|||
{ |
|||
if (m_sEncodingAESKey.Length != 43) |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey; |
|||
} |
|||
XmlDocument doc = new XmlDocument(); |
|||
XmlNode root; |
|||
string sEncryptMsg; |
|||
try |
|||
{ |
|||
doc.LoadXml(sPostData); |
|||
#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
root = doc.FirstChild; |
|||
#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
|||
sEncryptMsg = root["Encrypt"].InnerText; |
|||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
|||
} |
|||
catch (Exception) |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error; |
|||
} |
|||
//verify signature
|
|||
int ret = 0; |
|||
ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature); |
|||
if (ret != 0) |
|||
return ret; |
|||
//decrypt
|
|||
string cpid = ""; |
|||
try |
|||
{ |
|||
sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid); |
|||
} |
|||
catch (FormatException) |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error; |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error; |
|||
} |
|||
if (cpid != m_sAppID) |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppid_Error; |
|||
return 0; |
|||
} |
|||
|
|||
//将企业号回复用户的消息加密打包
|
|||
// @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
|
|||
// @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp
|
|||
// @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
|
|||
// @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
|
|||
// 当return返回0时有效
|
|||
// return:成功0,失败返回对应的错误码
|
|||
public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg) |
|||
{ |
|||
if (m_sEncodingAESKey.Length != 43) |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey; |
|||
} |
|||
string raw = ""; |
|||
try |
|||
{ |
|||
raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID); |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error; |
|||
} |
|||
string MsgSigature = ""; |
|||
int ret = 0; |
|||
ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature); |
|||
if (0 != ret) |
|||
return ret; |
|||
sEncryptMsg = ""; |
|||
|
|||
string EncryptLabelHead = "<Encrypt><![CDATA["; |
|||
string EncryptLabelTail = "]]></Encrypt>"; |
|||
string MsgSigLabelHead = "<MsgSignature><![CDATA["; |
|||
string MsgSigLabelTail = "]]></MsgSignature>"; |
|||
string TimeStampLabelHead = "<TimeStamp><![CDATA["; |
|||
string TimeStampLabelTail = "]]></TimeStamp>"; |
|||
string NonceLabelHead = "<Nonce><![CDATA["; |
|||
string NonceLabelTail = "]]></Nonce>"; |
|||
sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail; |
|||
sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail; |
|||
sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail; |
|||
sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail; |
|||
sEncryptMsg += "</xml>"; |
|||
return 0; |
|||
} |
|||
|
|||
public class DictionarySort : IComparer |
|||
{ |
|||
#pragma warning disable CS8767 // “int DictionarySort.Compare(object oLeft, object oRight)”的参数“oLeft”类型中引用类型的为 Null 性与隐式实现的成员“int IComparer.Compare(object? x, object? y)”不匹配(可能是由于为 Null 性特性)。
|
|||
#pragma warning disable CS8767 // “int DictionarySort.Compare(object oLeft, object oRight)”的参数“oRight”类型中引用类型的为 Null 性与隐式实现的成员“int IComparer.Compare(object? x, object? y)”不匹配(可能是由于为 Null 性特性)。
|
|||
public int Compare(object oLeft, object oRight) |
|||
#pragma warning restore CS8767 // “int DictionarySort.Compare(object oLeft, object oRight)”的参数“oRight”类型中引用类型的为 Null 性与隐式实现的成员“int IComparer.Compare(object? x, object? y)”不匹配(可能是由于为 Null 性特性)。
|
|||
#pragma warning restore CS8767 // “int DictionarySort.Compare(object oLeft, object oRight)”的参数“oLeft”类型中引用类型的为 Null 性与隐式实现的成员“int IComparer.Compare(object? x, object? y)”不匹配(可能是由于为 Null 性特性)。
|
|||
{ |
|||
#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
string sLeft = oLeft as string; |
|||
#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
#pragma warning disable CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
string sRight = oRight as string; |
|||
#pragma warning restore CS8600 // 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
|||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
|||
int iLeftLength = sLeft.Length; |
|||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
|||
#pragma warning disable CS8602 // 解引用可能出现空引用。
|
|||
int iRightLength = sRight.Length; |
|||
#pragma warning restore CS8602 // 解引用可能出现空引用。
|
|||
int index = 0; |
|||
while (index < iLeftLength && index < iRightLength) |
|||
{ |
|||
if (sLeft[index] < sRight[index]) |
|||
return -1; |
|||
else if (sLeft[index] > sRight[index]) |
|||
return 1; |
|||
else |
|||
index++; |
|||
} |
|||
return iLeftLength - iRightLength; |
|||
|
|||
} |
|||
} |
|||
//Verify Signature
|
|||
private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture) |
|||
{ |
|||
string hash = ""; |
|||
int ret = 0; |
|||
ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash); |
|||
if (ret != 0) |
|||
return ret; |
|||
//System.Console.WriteLine(hash);
|
|||
if (hash == sSigture) |
|||
return 0; |
|||
else |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error; |
|||
} |
|||
} |
|||
|
|||
public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature) |
|||
{ |
|||
ArrayList AL = new ArrayList(); |
|||
AL.Add(sToken); |
|||
AL.Add(sTimeStamp); |
|||
AL.Add(sNonce); |
|||
AL.Add(sMsgEncrypt); |
|||
AL.Sort(new DictionarySort()); |
|||
string raw = ""; |
|||
for (int i = 0; i < AL.Count; ++i) |
|||
{ |
|||
raw += AL[i]; |
|||
} |
|||
|
|||
SHA1 sha; |
|||
ASCIIEncoding enc; |
|||
string hash = ""; |
|||
try |
|||
{ |
|||
sha = new SHA1CryptoServiceProvider(); |
|||
enc = new ASCIIEncoding(); |
|||
byte[] dataToHash = enc.GetBytes(raw); |
|||
byte[] dataHashed = sha.ComputeHash(dataToHash); |
|||
hash = BitConverter.ToString(dataHashed).Replace("-", ""); |
|||
hash = hash.ToLower(); |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error; |
|||
} |
|||
sMsgSignature = hash; |
|||
return 0; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
/*********************************************************************** |
|||
* Project: CoreCms |
|||
* ProjectName: 核心内容管理系统 |
|||
* Web: https://www.corecms.net
|
|||
* Author: 大灰灰 |
|||
* Email: jianweie@163.com |
|||
* CreateTime: 2021/7/29 12:25:49 |
|||
* Description: 暂无 |
|||
***********************************************************************/ |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// 微信公众号帮助类
|
|||
/// </summary>
|
|||
public static class WxOfficialHelper |
|||
{ |
|||
|
|||
public static string geturl(string url, string weXinAppId, int scope = 1) |
|||
{ |
|||
|
|||
return "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + weXinAppId + "&redirect_uri=" + url + "&response_type=code&scope=" + scope + "&state=jshop#wechat_redirect"; |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,101 @@ |
|||
/*********************************************************************** |
|||
* Project: CoreCms |
|||
* ProjectName: 核心内容管理系统 |
|||
* Web: https://www.corecms.net
|
|||
* Author: 大灰灰 |
|||
* Email: jianweie@163.com |
|||
* CreateTime: 2021/7/29 23:21:06 |
|||
* Description: 暂无 |
|||
***********************************************************************/ |
|||
|
|||
|
|||
using System.Xml; |
|||
using System.Xml.Linq; |
|||
using System.Xml.Serialization; |
|||
|
|||
namespace Zncy.CloudCar.WeChat.Service.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// XML 工具类
|
|||
/// </summary>
|
|||
public static class XmlUtility |
|||
{ |
|||
/// <summary>反序列化</summary>
|
|||
/// <param name="xml">XML字符串</param>
|
|||
/// <returns></returns>
|
|||
public static object Deserialize<T>(string xml) |
|||
{ |
|||
try |
|||
{ |
|||
using (StringReader stringReader = new StringReader(xml)) |
|||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
|||
return new XmlSerializer(typeof(T)).Deserialize(stringReader); |
|||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Console.WriteLine(ex); |
|||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
|||
return null; |
|||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
|||
} |
|||
} |
|||
|
|||
/// <summary>反序列化</summary>
|
|||
/// <param name="stream"></param>
|
|||
/// <returns></returns>
|
|||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
|||
public static object Deserialize<T>(Stream stream) => new XmlSerializer(typeof(T)).Deserialize(stream); |
|||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
|||
|
|||
/// <summary>
|
|||
/// 序列化
|
|||
/// 说明:此方法序列化复杂类,如果没有声明XmlInclude等特性,可能会引发“使用 XmlInclude 或 SoapInclude 特性静态指定非已知的类型。”的错误。
|
|||
/// </summary>
|
|||
/// <param name="obj">对象</param>
|
|||
/// <returns></returns>
|
|||
public static string Serializer<T>(T obj) |
|||
{ |
|||
MemoryStream memoryStream = new MemoryStream(); |
|||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); |
|||
try |
|||
{ |
|||
xmlSerializer.Serialize(memoryStream, obj); |
|||
} |
|||
catch (InvalidOperationException) |
|||
{ |
|||
throw; |
|||
} |
|||
memoryStream.Position = 0L; |
|||
StreamReader streamReader = new StreamReader(memoryStream); |
|||
string end = streamReader.ReadToEnd(); |
|||
streamReader.Dispose(); |
|||
memoryStream.Dispose(); |
|||
return end; |
|||
} |
|||
|
|||
/// <summary>序列化将流转成XML字符串</summary>
|
|||
/// <param name="stream"></param>
|
|||
/// <returns></returns>
|
|||
public static XDocument Convert(Stream stream) |
|||
{ |
|||
if (stream.CanSeek) |
|||
stream.Seek(0L, SeekOrigin.Begin); |
|||
|
|||
|
|||
using (XmlReader reader = XmlReader.Create(stream)) |
|||
return XDocument.Load(reader); |
|||
} |
|||
|
|||
/// <summary>序列化将流转成XML字符串</summary>
|
|||
/// <param name="stream"></param>
|
|||
/// <returns></returns>
|
|||
public static string ConvertToString(Stream stream) |
|||
{ |
|||
StreamReader reader = new StreamReader(stream); |
|||
string sHtml = reader.ReadToEnd(); |
|||
return sHtml; |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="MediatR" Version="10.0.1" /> |
|||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.13" /> |
|||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> |
|||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="2.4.0" /> |
|||
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="2.6.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Utility\Znyc.CloudCar.Utility.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,31 @@ |
|||
using Znyc.CloudCar.Auth.Manual; |
|||
using Znyc.CloudCar.Configuration; |
|||
using Znyc.CloudCar.Model.ViewModels.WeChat; |
|||
|
|||
namespace Znyc.CloudCar.Auth.AccressToken |
|||
{ |
|||
/// <summary>
|
|||
/// 微信帮助类
|
|||
/// </summary>
|
|||
public class WeChatCacheAccessTokenHelper |
|||
{ |
|||
/// <summary>
|
|||
/// 获取微信小程序accessToken
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public static string GetWxOpenAccessToken() |
|||
{ |
|||
var wxOpenAccessToken = ManualDataCache.Instance.Get<string>(GlobalEnumVars.AccessTokenEnum.WxOpenAccessToken.ToString()); |
|||
return wxOpenAccessToken; |
|||
} |
|||
|
|||
public static string GetWeChatAccessToken() |
|||
{ |
|||
//获取微信AccessToken
|
|||
var weChatAccessToken = ManualDataCache.Instance.Get<WeChatAccessToken>(GlobalEnumVars.AccessTokenEnum.WeiXinAccessToken.ToString()); |
|||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
|||
return weChatAccessToken?.AccessToken; |
|||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,125 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Znyc.CloudCar.Auth.Policys; |
|||
using Znyc.CloudCar.Utility.Extensions; |
|||
|
|||
namespace Znyc.CloudCar.Auth.HttpContextUser |
|||
{ |
|||
public class AspNetUser : IHttpContextUser |
|||
{ |
|||
|
|||
private readonly IHttpContextAccessor _accessor; |
|||
|
|||
public AspNetUser(IHttpContextAccessor accessor) |
|||
{ |
|||
_accessor = accessor; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 用户Id
|
|||
/// </summary>
|
|||
public virtual long Id |
|||
{ |
|||
get |
|||
{ |
|||
|
|||
System.Security.Claims.Claim id = _accessor?.HttpContext?.User?.FindFirst(Claims.UserId); |
|||
if (id != null && id.Value.NotNull()) |
|||
{ |
|||
return id.Value.ObjectToLong(); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 权限Id
|
|||
/// </summary>
|
|||
public string RoleId |
|||
{ |
|||
get |
|||
{ |
|||
System.Security.Claims.Claim name = _accessor?.HttpContext?.User?.FindFirst(Claims.RoleId); |
|||
|
|||
if (name != null && name.Value.NotNull()) |
|||
{ |
|||
return name.Value; |
|||
} |
|||
|
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 昵称
|
|||
/// </summary>
|
|||
public string UserName |
|||
{ |
|||
get |
|||
{ |
|||
System.Security.Claims.Claim name = _accessor?.HttpContext?.User?.FindFirst(Claims.UserName); |
|||
|
|||
if (name != null && name.Value.NotNull()) |
|||
{ |
|||
return name.Value; |
|||
} |
|||
|
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// OpenId
|
|||
/// </summary>
|
|||
public string OpenId |
|||
{ |
|||
get |
|||
{ |
|||
System.Security.Claims.Claim name = _accessor?.HttpContext?.User?.FindFirst(Claims.OpenId); |
|||
|
|||
if (name != null && name.Value.NotNull()) |
|||
{ |
|||
return name.Value; |
|||
} |
|||
|
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// UnionId
|
|||
/// </summary>
|
|||
public string UnionId |
|||
{ |
|||
get |
|||
{ |
|||
System.Security.Claims.Claim name = _accessor?.HttpContext?.User?.FindFirst(Claims.UnionId); |
|||
|
|||
if (name != null && name.Value.NotNull()) |
|||
{ |
|||
return name.Value; |
|||
} |
|||
|
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// SessionKey
|
|||
/// </summary>
|
|||
public string SessionKey |
|||
{ |
|||
get |
|||
{ |
|||
System.Security.Claims.Claim name = _accessor?.HttpContext?.User?.FindFirst(Claims.SessionKey); |
|||
|
|||
if (name != null && name.Value.NotNull()) |
|||
{ |
|||
return name.Value; |
|||
} |
|||
|
|||
return ""; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
|
|||
namespace Znyc.CloudCar.Auth.HttpContextUser |
|||
{ |
|||
public interface IHttpContextUser |
|||
{ |
|||
/// <summary>
|
|||
/// 主键
|
|||
/// </summary>
|
|||
long Id { get; } |
|||
|
|||
/// <summary>
|
|||
/// 权限Id
|
|||
/// </summary>
|
|||
string RoleId { get; } |
|||
|
|||
/// <summary>
|
|||
/// 昵称
|
|||
/// </summary>
|
|||
string UserName { get; } |
|||
|
|||
/// <summary>
|
|||
/// 头像
|
|||
/// </summary>
|
|||
//string AvatarUrl { get; }
|
|||
|
|||
/// <summary>
|
|||
/// 微信唯一用户信息
|
|||
/// </summary>
|
|||
string OpenId { get; } |
|||
|
|||
/// <summary>
|
|||
/// SessionKey
|
|||
/// </summary>
|
|||
string SessionKey { get; } |
|||
|
|||
/// <summary>
|
|||
/// UnionId
|
|||
/// </summary>
|
|||
string UnionId { get; } |
|||
} |
|||
} |
@ -0,0 +1,85 @@ |
|||
namespace Znyc.CloudCar.Auth.Manual |
|||
{ |
|||
/// <summary>
|
|||
/// 手动缓存操作接口
|
|||
/// </summary>
|
|||
public interface IManualCacheManager |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// 验证缓存项是否存在
|
|||
/// </summary>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <returns></returns>
|
|||
bool Exists(string key); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 添加缓存
|
|||
/// </summary>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <param name="value">缓存Value</param>
|
|||
/// <param name="expiresIn">缓存时长(分钟)</param>
|
|||
/// <returns></returns>
|
|||
bool Set(string key, object value, int expiresIn = 0); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 删除缓存
|
|||
/// </summary>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <returns></returns>
|
|||
void Remove(string key); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 批量删除缓存
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
void RemoveAll(IEnumerable<string> keys); |
|||
|
|||
/// <summary>
|
|||
/// 获取缓存
|
|||
/// </summary>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <returns></returns>
|
|||
T Get<T>(string key); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 获取缓存
|
|||
/// </summary>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <returns></returns>
|
|||
object Get(string key); |
|||
|
|||
/// <summary>
|
|||
/// 获取缓存集合
|
|||
/// </summary>
|
|||
/// <param name="keys">缓存Key集合</param>
|
|||
/// <returns></returns>
|
|||
IDictionary<string, object> GetAll(IEnumerable<string> keys); |
|||
|
|||
/// <summary>
|
|||
/// 删除所有缓存
|
|||
/// </summary>
|
|||
void RemoveCacheAll(); |
|||
|
|||
/// <summary>
|
|||
/// 删除匹配到的缓存
|
|||
/// </summary>
|
|||
/// <param name="pattern"></param>
|
|||
/// <returns></returns>
|
|||
void RemoveCacheRegex(string pattern); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 搜索 匹配到的缓存
|
|||
/// </summary>
|
|||
/// <param name="pattern"></param>
|
|||
/// <returns></returns>
|
|||
IList<string> SearchCacheRegex(string pattern); |
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
namespace Znyc.CloudCar.Auth.Manual |
|||
{ |
|||
/// <summary>
|
|||
/// 手动缓存调用
|
|||
/// </summary>
|
|||
public static partial class ManualDataCache |
|||
{ |
|||
#pragma warning disable CS0414 // 字段“ManualDataCache._instance”已被赋值,但从未使用过它的值
|
|||
#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
private static readonly IManualCacheManager _instance = null; |
|||
#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。
|
|||
#pragma warning restore CS0414 // 字段“ManualDataCache._instance”已被赋值,但从未使用过它的值
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// 静态实例,外部可直接调用
|
|||
/// </summary>
|
|||
public static IManualCacheManager Instance |
|||
{ |
|||
get |
|||
{ |
|||
return new RedisCacheManager(); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,232 @@ |
|||
|
|||
using StackExchange.Redis; |
|||
using Znyc.CloudCar.Configuration; |
|||
using Znyc.CloudCar.Utility.Extensions; |
|||
|
|||
namespace Znyc.CloudCar.Auth.Manual |
|||
{ |
|||
public class RedisCacheManager : IManualCacheManager |
|||
{ |
|||
private readonly string _redisConnenctionString; |
|||
|
|||
public volatile ConnectionMultiplexer RedisConnection; |
|||
|
|||
private readonly object _redisConnectionLock = new object(); |
|||
|
|||
public RedisCacheManager() |
|||
{ |
|||
string redisConfiguration = AppSettingsConstVars.RedisConfigConnectionString;//获取连接字符串
|
|||
|
|||
if (string.IsNullOrWhiteSpace(redisConfiguration)) |
|||
{ |
|||
throw new ArgumentException("redis config is empty", nameof(redisConfiguration)); |
|||
} |
|||
_redisConnenctionString = redisConfiguration; |
|||
RedisConnection = GetRedisConnection(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 核心代码,获取连接实例
|
|||
/// 通过双if 夹lock的方式,实现单例模式
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
private ConnectionMultiplexer GetRedisConnection() |
|||
{ |
|||
//如果已经连接实例,直接返回
|
|||
if (RedisConnection != null && RedisConnection.IsConnected) |
|||
{ |
|||
return RedisConnection; |
|||
} |
|||
//加锁,防止异步编程中,出现单例无效的问题
|
|||
lock (_redisConnectionLock) |
|||
{ |
|||
if (RedisConnection != null) |
|||
{ |
|||
//释放redis连接
|
|||
RedisConnection.Dispose(); |
|||
} |
|||
try |
|||
{ |
|||
RedisConnection = ConnectionMultiplexer.Connect(_redisConnenctionString); |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
throw new Exception("Redis服务未启用,请开启该服务,并且请注意端口号,Redis默认使用6379端口号。"); |
|||
} |
|||
} |
|||
return RedisConnection; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 判断key是否存在
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
public bool Exists(string key) |
|||
{ |
|||
return RedisConnection.GetDatabase().KeyExists(key); |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/// <summary>
|
|||
/// 添加缓存
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <param name="value">缓存Value</param>
|
|||
/// <param name="expiresIn">缓存时间</param>
|
|||
/// <returns></returns>
|
|||
public bool Set(string key, object value, int expiresIn = 0) |
|||
{ |
|||
if (value != null) |
|||
{ |
|||
//序列化,将object值生成RedisValue
|
|||
if (expiresIn > 0) |
|||
{ |
|||
return RedisConnection.GetDatabase().StringSet(key, SerializeExtensions.Serialize(value), TimeSpan.FromMinutes(expiresIn)); |
|||
} |
|||
else |
|||
{ |
|||
return RedisConnection.GetDatabase().StringSet(key, SerializeExtensions.Serialize(value)); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除缓存
|
|||
/// </summary>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <returns></returns>
|
|||
public void Remove(string key) |
|||
{ |
|||
RedisConnection.GetDatabase().KeyDelete(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 批量删除缓存
|
|||
/// </summary>
|
|||
/// <param name="keys">缓存Key集合</param>
|
|||
/// <returns></returns>
|
|||
public void RemoveAll(IEnumerable<string> keys) |
|||
{ |
|||
foreach (var key in keys) |
|||
{ |
|||
RedisConnection.GetDatabase().KeyDelete(key); |
|||
} |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取缓存对象
|
|||
/// </summary>
|
|||
/// <param name="key">缓存Key</param>
|
|||
/// <returns></returns>
|
|||
public T Get<T>(string key) |
|||
{ |
|||
var value = RedisConnection.GetDatabase().StringGet(key); |
|||
if (value.HasValue) |
|||
{ |
|||
//需要用的反序列化,将Redis存储的Byte[],进行反序列化
|
|||
return SerializeExtensions.Deserialize<T>(value); |
|||
} |
|||
|
|||
|
|||
return default; |
|||
|
|||
} |
|||
|
|||
public object Get(string key) |
|||
{ |
|||
return RedisConnection.GetDatabase().StringGet(key); |
|||
} |
|||
|
|||
public IDictionary<string, object> GetAll(IEnumerable<string> keys) |
|||
{ |
|||
if (keys == null) |
|||
throw new ArgumentNullException(nameof(keys)); |
|||
var dict = new Dictionary<string, object>(); |
|||
|
|||
keys.ToList().ForEach(item => dict.Add(item, RedisConnection.GetDatabase().StringGet(item))); |
|||
return dict; |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除所有缓存
|
|||
/// </summary>
|
|||
public void RemoveCacheAll() |
|||
{ |
|||
foreach (var endPoint in GetRedisConnection().GetEndPoints()) |
|||
{ |
|||
var server = GetRedisConnection().GetServer(endPoint); |
|||
foreach (var key in server.Keys()) |
|||
{ |
|||
RedisConnection.GetDatabase().KeyDelete(key); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除匹配到的缓存
|
|||
/// </summary>
|
|||
/// <param name="pattern"></param>
|
|||
public void RemoveCacheRegex(string pattern) |
|||
{ |
|||
var script = "return redis.call('keys',@pattern)"; |
|||
var prepared = LuaScript.Prepare(script); |
|||
var redisResult = RedisConnection.GetDatabase().ScriptEvaluate(prepared, new { pattern }); |
|||
if (!redisResult.IsNull) |
|||
{ |
|||
RedisConnection.GetDatabase().KeyDelete((RedisKey[])redisResult); //删除一组key
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 搜索匹配到的缓存
|
|||
/// </summary>
|
|||
/// <param name="pattern"></param>
|
|||
/// <returns></returns>
|
|||
public IList<string> SearchCacheRegex(string pattern) |
|||
{ |
|||
var list = new List<string>(); |
|||
var script = "return redis.call('keys',@pattern)"; |
|||
var prepared = LuaScript.Prepare(script); |
|||
var redisResult = RedisConnection.GetDatabase().ScriptEvaluate(prepared, new { pattern }); |
|||
if (!redisResult.IsNull) |
|||
{ |
|||
foreach (var key in (RedisKey[])redisResult) |
|||
{ |
|||
list.Add(RedisConnection.GetDatabase().StringGet(key)); |
|||
} |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 添加缓存
|
|||
/// </summary>
|
|||
/// <param name="key">缓存key</param>
|
|||
/// <param name="value">缓存Value</param>
|
|||
/// <param name="expiresIn">缓存时长(分钟)</param>
|
|||
/// <returns></returns>
|
|||
public bool Set(string key, string value, int expiresIn = 0) |
|||
{ |
|||
if (value != null) |
|||
{ |
|||
if (expiresIn > 0) |
|||
{ |
|||
return RedisConnection.GetDatabase().StringSet(key, SerializeExtensions.Serialize(value), TimeSpan.FromMinutes(expiresIn)); |
|||
} |
|||
else |
|||
{ |
|||
return RedisConnection.GetDatabase().StringSet(key, SerializeExtensions.Serialize(value)); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,111 @@ |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using System.IdentityModel.Tokens.Jwt; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
using Znyc.CloudCar.Configuration; |
|||
using Znyc.CloudCar.Utility.Extensions; |
|||
|
|||
namespace Znyc.CloudCar.Auth.OverWrite |
|||
{ |
|||
public class JwtHelper |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// 颁发JWT字符串
|
|||
/// </summary>
|
|||
/// <param name="tokenModel"></param>
|
|||
/// <returns></returns>
|
|||
public static string IssueJwt(TokenModelJwt tokenModel) |
|||
{ |
|||
string iss = AppSettingsConstVars.JwtConfigIssuer; |
|||
string aud = AppSettingsConstVars.JwtConfigAudience; |
|||
string secret = AppSettingsConstVars.JwtConfigSecretKey; |
|||
|
|||
//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()}") , |
|||
//这个就是过期时间,目前是过期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) |
|||
|
|||
//new Claim(ClaimTypes.Role,tokenModel.Role),//为了解决一个用户多个角色(比如:Admin,System),用下边的方法
|
|||
}; |
|||
|
|||
// 可以将一个用户的多个角色全部赋予;
|
|||
// 作者:DX 提供技术支持;
|
|||
claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s))); |
|||
|
|||
//秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
|
|||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); |
|||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); |
|||
|
|||
var jwt = new JwtSecurityToken( |
|||
issuer: iss, |
|||
claims: claims, |
|||
signingCredentials: creds); |
|||
|
|||
var jwtHandler = new JwtSecurityTokenHandler(); |
|||
var encodedJwt = jwtHandler.WriteToken(jwt); |
|||
|
|||
return encodedJwt; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 解析
|
|||
/// </summary>
|
|||
/// <param name="jwtStr"></param>
|
|||
/// <returns></returns>
|
|||
public static TokenModelJwt SerializeJwt(string jwtStr) |
|||
{ |
|||
var jwtHandler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr); |
|||
object role; |
|||
try |
|||
{ |
|||
jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e); |
|||
throw; |
|||
} |
|||
var tm = new TokenModelJwt |
|||
{ |
|||
Uid = jwtToken.Id.ObjectToInt(), |
|||
Role = role != null ? role.ObjectToString() : "" |
|||
}; |
|||
return tm; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 令牌
|
|||
/// </summary>
|
|||
public class TokenModelJwt |
|||
{ |
|||
/// <summary>
|
|||
/// Id
|
|||
/// </summary>
|
|||
public long Uid { get; set; } |
|||
/// <summary>
|
|||
/// 角色
|
|||
/// </summary>
|
|||
public string Role { get; set; } |
|||
/// <summary>
|
|||
/// 职能
|
|||
/// </summary>
|
|||
public string Work { get; set; } |
|||
|
|||
} |
|||
} |
@ -0,0 +1,81 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
|
|||
namespace Znyc.CloudCar.Auth.OverWrite |
|||
{ |
|||
/// <summary>
|
|||
/// 中间件
|
|||
/// 原做为自定义授权中间件
|
|||
/// 先做检查 header token的使用
|
|||
/// </summary>
|
|||
public class JwtTokenAuth |
|||
{ |
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
private readonly RequestDelegate _next; |
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
/// <param name="next"></param>
|
|||
public JwtTokenAuth(RequestDelegate next) |
|||
{ |
|||
_next = next; |
|||
} |
|||
|
|||
|
|||
private void PreProceed(HttpContext next) |
|||
{ |
|||
//Console.WriteLine($"{DateTime.Now} middleware invoke preproceed");
|
|||
//...
|
|||
} |
|||
private void PostProceed(HttpContext next) |
|||
{ |
|||
//Console.WriteLine($"{DateTime.Now} middleware invoke postproceed");
|
|||
//....
|
|||
} |
|||
|
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
/// <param name="httpContext"></param>
|
|||
/// <returns></returns>
|
|||
public Task Invoke(HttpContext httpContext) |
|||
{ |
|||
PreProceed(httpContext); |
|||
|
|||
//检测是否包含'Authorization'请求头
|
|||
if (!httpContext.Request.Headers.ContainsKey("Authorization")) |
|||
{ |
|||
PostProceed(httpContext); |
|||
|
|||
return _next(httpContext); |
|||
} |
|||
//var tokenHeader = httpContext.Request.Headers["Authorization"].ToString();
|
|||
var tokenHeader = httpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); |
|||
|
|||
try |
|||
{ |
|||
if (tokenHeader.Length >= 128) |
|||
{ |
|||
//Console.WriteLine($"{DateTime.Now} token :{tokenHeader}");
|
|||
TokenModelJwt tm = JwtHelper.SerializeJwt(tokenHeader); |
|||
//授权
|
|||
//var claimList = new List<Claim>();
|
|||
//var claim = new Claim(ClaimTypes.Role, tm.Role);
|
|||
//claimList.Add(claim);
|
|||
//var identity = new ClaimsIdentity(claimList);
|
|||
//var principal = new ClaimsPrincipal(identity);
|
|||
//httpContext.User = principal;
|
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine($"{DateTime.Now} middleware wrong:{e.Message}"); |
|||
} |
|||
|
|||
PostProceed(httpContext); |
|||
return _next(httpContext); |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
namespace Znyc.CloudCar.Auth.Policys |
|||
{ |
|||
public class ApiResponse |
|||
{ |
|||
public int Status { get; set; } = 404; |
|||
public object Value { get; set; } = "No Found"; |
|||
|
|||
public ApiResponse(StatusCode apiCode, object msg = null) |
|||
{ |
|||
switch (apiCode) |
|||
{ |
|||
case StatusCode.CODE401: |
|||
{ |
|||
Status = 401; |
|||
Value = "很抱歉,您无权访问该接口,请确保已经登录!"; |
|||
} |
|||
break; |
|||
case StatusCode.CODE403: |
|||
{ |
|||
Status = 403; |
|||
Value = "很抱歉,您的访问权限等级不够,联系管理员!"; |
|||
} |
|||
break; |
|||
case StatusCode.CODE500: |
|||
{ |
|||
Status = 500; |
|||
Value = msg; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public enum StatusCode |
|||
{ |
|||
CODE401, |
|||
CODE403, |
|||
CODE404, |
|||
CODE500 |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Newtonsoft.Json; |
|||
using System.Text.Encodings.Web; |
|||
using Znyc.CloudCar.Model.ViewModels.ReportsCallBack; |
|||
|
|||
namespace Znyc.CloudCar.Auth.Policys |
|||
{ |
|||
public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions> |
|||
{ |
|||
public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) |
|||
{ |
|||
} |
|||
|
|||
protected override Task<AuthenticateResult> HandleAuthenticateAsync() |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
protected override async Task HandleChallengeAsync(AuthenticationProperties properties) |
|||
{ |
|||
Response.ContentType = "application/json"; |
|||
//Response.StatusCode = StatusCodes.Status403Forbidden;
|
|||
var response = new ResponseOutput(); |
|||
response.Successed = false; |
|||
response.Code = 401; |
|||
response.Msg = "很抱歉,授权失效,请重新登录!"; |
|||
await Response.WriteAsync(JsonConvert.SerializeObject(response).ToLower()); |
|||
} |
|||
|
|||
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) |
|||
{ |
|||
Response.ContentType = "application/json"; |
|||
//Response.StatusCode = StatusCodes.Status403Forbidden;
|
|||
//await Response.WriteAsync(JsonConvert.SerializeObject(new ApiResponse(StatusCode.CODE403)));
|
|||
|
|||
var response = new ResponseOutput(); |
|||
response.Successed = false; |
|||
response.Code = 401; |
|||
response.Msg = "很抱歉,授权失效,请重新登录!"; |
|||
await Response.WriteAsync(JsonConvert.SerializeObject(response).ToLower()); |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,42 @@ |
|||
namespace Znyc.CloudCar.Auth.Policys |
|||
{ |
|||
/// <summary>
|
|||
/// Claim属性
|
|||
/// </summary>
|
|||
public static class Claims |
|||
{ |
|||
/// <summary>
|
|||
/// 用户Id
|
|||
/// </summary>
|
|||
public const string UserId = "UserId"; |
|||
|
|||
/// <summary>
|
|||
/// 姓名
|
|||
/// </summary>
|
|||
public const string UserName = "UserName"; |
|||
|
|||
/// <summary>
|
|||
/// 刷新有效期
|
|||
/// </summary>
|
|||
public const string RefreshExpires = "RefreshExpires"; |
|||
|
|||
/// <summary>
|
|||
/// 微信认证Id
|
|||
/// </summary>
|
|||
public const string OpenId = "OpenId"; |
|||
|
|||
/// <summary>
|
|||
/// 权限id
|
|||
/// </summary>
|
|||
public const string RoleId = "RoleId"; |
|||
|
|||
/// <summary>
|
|||
/// 开放平台Id(区分用户唯一性)
|
|||
/// </summary>
|
|||
public const string UnionId = "UnionId"; |
|||
/// <summary>
|
|||
/// SessionKey
|
|||
/// </summary>
|
|||
public const string SessionKey = "SessionKey"; |
|||
} |
|||
} |
@ -0,0 +1,42 @@ |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using System.IdentityModel.Tokens.Jwt; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
using Znyc.CloudCar.Configuration; |
|||
using Znyc.CloudCar.Utility.Helper; |
|||
|
|||
namespace Znyc.CloudCar.Auth.Policys |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// JWTToken生成类
|
|||
/// </summary>
|
|||
public class JwtToken |
|||
{ |
|||
public static string Create(Claim[] claims) |
|||
{ |
|||
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppSettingsConstVars.JwtConfigSecretKey)); |
|||
SigningCredentials signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); |
|||
string timestamp = DateTime.Now.AddMinutes(AppSettingsConstVars.JwtConfigExpires).ToTimestamp() |
|||
.ToString(); |
|||
claims = claims.Append(new Claim(Claims.RefreshExpires, timestamp)).ToArray(); |
|||
|
|||
JwtSecurityToken token = new JwtSecurityToken( |
|||
AppSettingsConstVars.JwtConfigIssuer, |
|||
AppSettingsConstVars.JwtConfigAudience, |
|||
claims, |
|||
DateTime.Now, |
|||
DateTime.Now.AddMinutes(AppSettingsConstVars.JwtConfigExpires), |
|||
signingCredentials |
|||
); |
|||
return new JwtSecurityTokenHandler().WriteToken(token); |
|||
} |
|||
|
|||
public Claim[] Decode(string jwtToken) |
|||
{ |
|||
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); |
|||
JwtSecurityToken jwtSecurityToken = jwtSecurityTokenHandler.ReadJwtToken(jwtToken); |
|||
return jwtSecurityToken?.Claims?.ToArray(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,156 @@ |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System.Security.Claims; |
|||
using System.Text.RegularExpressions; |
|||
using Znyc.CloudCar.Utility.Extensions; |
|||
|
|||
namespace Znyc.CloudCar.Auth.Policys |
|||
{ |
|||
/// <summary>
|
|||
/// 权限授权处理器
|
|||
/// </summary>
|
|||
public class PermissionHandler : AuthorizationHandler<PermissionRequirement> |
|||
{ |
|||
/// <summary>
|
|||
/// 验证方案提供对象
|
|||
/// </summary>
|
|||
public IAuthenticationSchemeProvider _schemes { get; set; } |
|||
private readonly IHttpContextAccessor _accessor; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数注入
|
|||
/// </summary>
|
|||
/// <param name="schemes"></param>
|
|||
/// <param name="navigationRepository"></param>
|
|||
/// <param name="accessor"></param>
|
|||
public PermissionHandler(IAuthenticationSchemeProvider schemes, IHttpContextAccessor accessor) |
|||
{ |
|||
_accessor = accessor; |
|||
_schemes = schemes; |
|||
} |
|||
|
|||
// 重写异步处理程序
|
|||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) |
|||
{ |
|||
|
|||
var httpContext = _accessor.HttpContext; |
|||
|
|||
if (!requirement.Permissions.Any()) |
|||
{ |
|||
//var data = await _navigationRepository.QueryAsync();
|
|||
//var list = (from item in data
|
|||
// where item.isLock == false
|
|||
// orderby item.id
|
|||
// select new PermissionItem
|
|||
// {
|
|||
// Url = item.linkUrl,
|
|||
// Role = item.identificationCode,
|
|||
// }).ToList();
|
|||
//requirement.Permissions = list;
|
|||
} |
|||
|
|||
//请求Url
|
|||
if (httpContext != null) |
|||
{ |
|||
var questUrl = httpContext.Request.Path.Value.ToLower(); |
|||
//判断请求是否停止
|
|||
var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); |
|||
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) |
|||
{ |
|||
// 将最新的角色和接口列表更新
|
|||
|
|||
// 这里暂时把代码移动到了Login获取token的api里,这样就不用每次都请求数据库,造成压力.
|
|||
// 但是这样有个问题,就是如果修改了某一个角色的菜单权限,不会立刻更新,
|
|||
// 需要让用户退出重新登录,如果你想实时更新,请把下边的注释打开即可.
|
|||
|
|||
//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,
|
|||
// }).ToList();
|
|||
//requirement.Permissions = list;
|
|||
|
|||
httpContext.User = result.Principal; |
|||
|
|||
//权限中是否存在请求的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?.ObjectToString().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; |
|||
} |
|||
} |
|||
|
|||
//判断过期时间(这里仅仅是最坏验证原则,你可以不要这个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); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
namespace Znyc.CloudCar.Auth.Policys |
|||
{ |
|||
/// <summary>
|
|||
/// 用户或角色或其他凭据实体
|
|||
/// </summary>
|
|||
public class PermissionItem |
|||
{ |
|||
/// <summary>
|
|||
/// 用户或角色或其他凭据名称
|
|||
/// </summary>
|
|||
public virtual string Role { get; set; } |
|||
/// <summary>
|
|||
/// 请求Url
|
|||
/// </summary>
|
|||
public virtual string Url { get; set; } |
|||
/// <summary>
|
|||
/// 权限标识
|
|||
/// </summary>
|
|||
public virtual string Authority { get; set; } |
|||
/// <summary>
|
|||
/// 路由标识Url
|
|||
/// </summary>
|
|||
public virtual string RouteUrl { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
|
|||
namespace Znyc.CloudCar.Auth.Policys |
|||
{ |
|||
/// <summary>
|
|||
/// 必要参数类
|
|||
/// 继承 IAuthorizationRequirement,用于设计自定义权限处理器PermissionHandler
|
|||
/// 因为AuthorizationHandler 中的泛型参数 TRequirement 必须继承 IAuthorizationRequirement
|
|||
/// </summary>
|
|||
public class PermissionRequirement : IAuthorizationRequirement |
|||
{ |
|||
/// <summary>
|
|||
/// 用户权限集合,一个订单包含了很多详情,
|
|||
/// 同理,一个网站的认证发行中,也有很多权限详情(这里是Role和URL的关系)
|
|||
/// </summary>
|
|||
public List<PermissionItem> Permissions { get; set; } |
|||
/// <summary>
|
|||
/// 无权限action
|
|||
/// </summary>
|
|||
public string DeniedAction { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 认证授权类型
|
|||
/// </summary>
|
|||
public string ClaimType { internal get; set; } |
|||
/// <summary>
|
|||
/// 请求路径
|
|||
/// </summary>
|
|||
public string LoginPath { get; set; } = "/Api/Login"; |
|||
/// <summary>
|
|||
/// 发行人
|
|||
/// </summary>
|
|||
public string Issuer { get; set; } |
|||
/// <summary>
|
|||
/// 订阅人
|
|||
/// </summary>
|
|||
public string Audience { get; set; } |
|||
/// <summary>
|
|||
/// 过期时间
|
|||
/// </summary>
|
|||
public TimeSpan Expiration { get; set; } |
|||
/// <summary>
|
|||
/// 签名验证
|
|||
/// </summary>
|
|||
public SigningCredentials SigningCredentials { get; set; } |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 构造
|
|||
/// </summary>
|
|||
/// <param name="deniedAction">拒约请求的url</param>
|
|||
/// <param name="permissions">权限集合</param>
|
|||
/// <param name="claimType">声明类型</param>
|
|||
/// <param name="issuer">发行人</param>
|
|||
/// <param name="audience">订阅人</param>
|
|||
/// <param name="signingCredentials">签名验证实体</param>
|
|||
/// <param name="expiration">过期时间</param>
|
|||
public PermissionRequirement(string deniedAction, List<PermissionItem> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration) |
|||
{ |
|||
ClaimType = claimType; |
|||
DeniedAction = deniedAction; |
|||
Permissions = permissions; |
|||
Issuer = issuer; |
|||
Audience = audience; |
|||
Expiration = expiration; |
|||
SigningCredentials = signingCredentials; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" /> |
|||
<PackageReference Include="StackExchange.Redis" Version="2.2.88" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Configuration\Znyc.CloudCar.Configuration.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Model\Znyc.CloudCar.Model.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Utility\Znyc.CloudCar.Utility.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,212 @@ |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Znyc.CloudCar.Caching |
|||
{ |
|||
/// <summary>
|
|||
/// 缓存接口
|
|||
/// </summary>
|
|||
public interface IRedisOperationRepository |
|||
{ |
|||
/// <summary>
|
|||
/// 检查给定 key 是否存在
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
bool Exists(string key); |
|||
|
|||
/// <summary>
|
|||
/// 检查给定 key 是否存在
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
Task<bool> ExistsAsync(string key); |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
string Get(string key); |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <typeparam name="T">byte[] 或其他类型</typeparam>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
T Get<T>(string key); |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
Task<string> GetAsync(string key); |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <typeparam name="T">byte[] 或其他类型</typeparam>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
Task<T> GetAsync<T>(string key); |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns></returns>
|
|||
bool Set(string key, object value); |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <param name="expire">有效期</param>
|
|||
/// <returns></returns>
|
|||
bool Set(string key, object value, TimeSpan expire); |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns></returns>
|
|||
Task<bool> SetAsync(string key, object value); |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <param name="expire">有效期</param>
|
|||
/// <returns></returns>
|
|||
Task<bool> SetAsync(string key, object value, TimeSpan expire); |
|||
|
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
bool Del(string key); |
|||
|
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> DelAsync(string key); |
|||
|
|||
/// <summary>
|
|||
/// 将 key 所储存的值加上给定的增量值(increment)
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
Task<long> IncrAsync(string key); |
|||
|
|||
/// <summary>
|
|||
/// 将 key 所储存的值加上给定的增量值(increment)
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
long Incr(string key); |
|||
|
|||
#region Hash
|
|||
/// <summary>
|
|||
/// 将哈希表 key 中的字段 field 的值设为 value
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="filed">字段</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns> 如果字段是哈希表中的一个新建字段,并且值设置成功,返回true。如果哈希表中域字段已经存在且旧值已被新值覆盖,返回false。</returns>
|
|||
bool HSet(string key, string filed, object value); |
|||
|
|||
/// <summary>
|
|||
/// 将哈希表 key 中的字段 field 的值设为 value
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="filed">字段</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns> 如果字段是哈希表中的一个新建字段,并且值设置成功,返回true。如果哈希表中域字段已经存在且旧值已被新值覆盖,返回false。</returns>
|
|||
Task<bool> HSetAsync(string key, string filed, object value); |
|||
|
|||
/// <summary>
|
|||
/// 获取在哈希表中指定 key 的所有字段和值
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
Task<IDictionary<string, object>> HGetAllAsync(string key); |
|||
|
|||
#endregion
|
|||
|
|||
#region Redis 有序集合(sorted set)
|
|||
/// <summary>
|
|||
/// 向有序集合添加一个或多个成员,或者更新已存在成员的分数
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="scoreMembers"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> ZAddAsync(string key, string member, double score); |
|||
|
|||
/// <summary>
|
|||
/// 用于计算集合中元素的数量
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
Task<long> ZCardAsync(string key); |
|||
|
|||
/// <summary>
|
|||
/// 有序集合中对指定成员的分数加上增量 increment
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="member"></param>
|
|||
/// <param name="increment"></param>
|
|||
/// <returns></returns>
|
|||
Task<double> ZIncrByAsync(string key, string member, double increment = 1); |
|||
|
|||
/// <summary>
|
|||
/// 通过索引区间返回有序集合成指定区间内的成员
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="start"></param>
|
|||
/// <param name="stop"></param>
|
|||
/// <param name="sortType">true倒序排列,false正序排列</param>
|
|||
/// <returns></returns>
|
|||
Task<List<string>> ZRemRangeByRankAsync(string key, long start, long stop, bool sortType = true); |
|||
|
|||
/// <summary>
|
|||
/// 返回有序集中的成员和分数,通过索引,分数从高到低
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="start"></param>
|
|||
/// <param name="stop"></param>
|
|||
/// <returns></returns>
|
|||
Task<IDictionary<string, double>> ZRevRangeWithScoresAsync(string key); |
|||
|
|||
/// <summary>
|
|||
/// 删除
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="member"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> SortedSetRemoveAsync(string key, string member); |
|||
|
|||
/// <summary>
|
|||
/// 数据转换
|
|||
/// </summary>
|
|||
/// <param name="keyValues"></param>
|
|||
/// <returns></returns>
|
|||
SortedSetEntry[] ConvertDictionaryToSortedSetEntry(IDictionary<string, double> keyValues); |
|||
|
|||
/// <summary>
|
|||
/// 数据转换字典
|
|||
/// </summary>
|
|||
/// <param name="keyValues"></param>
|
|||
/// <returns></returns>
|
|||
IDictionary<string, double> ConvertSortedSetEntryToDictionary(SortedSetEntry[] setEntries); |
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,367 @@ |
|||
using Newtonsoft.Json; |
|||
using StackExchange.Redis; |
|||
|
|||
namespace Znyc.CloudCar.Caching |
|||
{ |
|||
public class RedisOperationRepository : IRedisOperationRepository |
|||
{ |
|||
private readonly ConnectionMultiplexer _redis; |
|||
private readonly IDatabase _database; |
|||
|
|||
public RedisOperationRepository(ConnectionMultiplexer redis) |
|||
{ |
|||
_redis = redis; |
|||
_database = redis.GetDatabase(); |
|||
} |
|||
|
|||
private IServer GetServer() |
|||
{ |
|||
var endpoint = _redis.GetEndPoints(); |
|||
return _redis.GetServer(endpoint.First()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public async Task Clear() |
|||
{ |
|||
foreach (var endPoint in _redis.GetEndPoints()) |
|||
{ |
|||
var server = GetServer(); |
|||
foreach (var key in server.Keys()) |
|||
{ |
|||
await _database.KeyDeleteAsync(key); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查给定 key 是否存在
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
public bool Exists(string key) |
|||
{ |
|||
return _database.KeyExists(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 检查给定 key 是否存在
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> ExistsAsync(string key) |
|||
{ |
|||
return _database.KeyExistsAsync(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
public string Get(string key) |
|||
{ |
|||
return _database.StringGet(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <typeparam name="T">byte[] 或其他类型</typeparam>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
public T Get<T>(string key) |
|||
{ |
|||
var value = _database.StringGet(key); |
|||
if (value.HasValue) |
|||
{ |
|||
return JsonConvert.DeserializeObject<T>(value); |
|||
} |
|||
else |
|||
{ |
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
public async Task<string> GetAsync(string key) |
|||
{ |
|||
return await _database.StringGetAsync(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取指定 key 的值
|
|||
/// </summary>
|
|||
/// <typeparam name="T">byte[] 或其他类型</typeparam>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
public async Task<T> GetAsync<T>(string key) |
|||
{ |
|||
var value = await _database.StringGetAsync(key); |
|||
if (value.HasValue) |
|||
{ |
|||
|
|||
return JsonConvert.DeserializeObject<T>(value); |
|||
} |
|||
else |
|||
{ |
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns></returns>
|
|||
public bool Set(string key, object value) |
|||
{ |
|||
return _database.StringSet(key, JsonConvert.SerializeObject(value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <param name="expire">有效期</param>
|
|||
/// <returns></returns>
|
|||
public bool Set(string key, object value, TimeSpan expire) |
|||
{ |
|||
return _database.StringSet(key, JsonConvert.SerializeObject(value), expire); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns></returns>
|
|||
public async Task<bool> SetAsync(string key, object value) |
|||
{ |
|||
return await _database.StringSetAsync(key, JsonConvert.SerializeObject(value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 设置指定 key 的值,所有写入参数object都支持string | byte[] | 数值 | 对象
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <param name="expire">有效期</param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> SetAsync(string key, object value, TimeSpan expire) |
|||
{ |
|||
return _database.StringSetAsync(key, JsonConvert.SerializeObject(value), expire); |
|||
} |
|||
|
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
public bool Del(string key) |
|||
{ |
|||
return _database.KeyDelete(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
public Task<bool> DelAsync(string key) |
|||
{ |
|||
return _database.KeyDeleteAsync(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将 key 所储存的值加上给定的增量值(increment)
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
public async Task<long> IncrAsync(string key) |
|||
{ |
|||
return await _database.StringIncrementAsync(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将 key 所储存的值加上给定的增量值(increment)
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <returns></returns>
|
|||
public long Incr(string key) |
|||
{ |
|||
return _database.StringIncrement(key); |
|||
} |
|||
|
|||
#region Hash
|
|||
/// <summary>
|
|||
/// 将哈希表 key 中的字段 field 的值设为 value
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="filed">字段</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns> 如果字段是哈希表中的一个新建字段,并且值设置成功,返回true。如果哈希表中域字段已经存在且旧值已被新值覆盖,返回false。</returns>
|
|||
public bool HSet(string key, string filed, object value) |
|||
{ |
|||
return _database.HashSet(key, filed, JsonConvert.SerializeObject(value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 将哈希表 key 中的字段 field 的值设为 value
|
|||
/// </summary>
|
|||
/// <param name="key">键</param>
|
|||
/// <param name="filed">字段</param>
|
|||
/// <param name="value">值</param>
|
|||
/// <returns> 如果字段是哈希表中的一个新建字段,并且值设置成功,返回true。如果哈希表中域字段已经存在且旧值已被新值覆盖,返回false。</returns>
|
|||
public Task<bool> HSetAsync(string key, string filed, object value) |
|||
{ |
|||
return _database.HashSetAsync(key, filed, JsonConvert.SerializeObject(value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 同时将多个field-value对设置到哈希表key中
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="value"></param>
|
|||
/// <returns></returns>
|
|||
//public Task<bool> HMSetAsync(string key, object[] value)
|
|||
//{
|
|||
// return _database.HMSetAsync(key, value);
|
|||
//}
|
|||
|
|||
/// <summary>
|
|||
/// 获取在哈希表中指定 key 的所有字段和值
|
|||
/// </summary>
|
|||
/// <typeparam name="T"></typeparam>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
public async Task<IDictionary<string, object>> HGetAllAsync(string key) |
|||
{ |
|||
IDictionary<string, object> keyValues = new Dictionary<string, object>(); |
|||
var res = await _database.HashGetAllAsync(key); |
|||
foreach (var item in res) |
|||
{ |
|||
keyValues.Add(item.Name, item.Value); |
|||
} |
|||
return keyValues; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region Redis 有序集合(sorted set)
|
|||
/// <summary>
|
|||
/// 向有序集合添加一个或多个成员,或者更新已存在成员的分数
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="member"></param>
|
|||
/// <param name="score"></param>
|
|||
/// <returns></returns>
|
|||
public async Task<bool> ZAddAsync(string key, string member, double score) |
|||
{ |
|||
return await _database.SortedSetAddAsync(key, member, score); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 用于计算集合中元素的数量
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
public async Task<long> ZCardAsync(string key) |
|||
{ |
|||
return await _database.SortedSetLengthAsync(key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 有序集合中对指定成员的分数加上增量 increment
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="member"></param>
|
|||
/// <param name="increment"></param>
|
|||
/// <returns></returns>
|
|||
public async Task<double> ZIncrByAsync(string key, string member, double increment = 1) |
|||
{ |
|||
return await _database.SortedSetIncrementAsync(key, member, increment); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 通过索引区间返回有序集合成指定区间内的成员
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="start"></param>
|
|||
/// <param name="stop"></param>
|
|||
/// <param name="sortType">true倒序排列,false正序排列</param>
|
|||
/// <returns></returns>
|
|||
public async Task<List<string>> ZRemRangeByRankAsync(string key, long start, long stop, bool sortType = true) |
|||
{ |
|||
if (sortType) |
|||
{ |
|||
return (await _database.SortedSetRangeByRankAsync(key, start, stop, Order.Descending)).ToStringArray().ToList(); |
|||
} |
|||
else |
|||
{ |
|||
return (await _database.SortedSetRangeByRankAsync(key, start, stop, Order.Ascending)).ToStringArray().ToList(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 返回有序集中的成员和分数,通过索引,分数从高到低
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <returns></returns>
|
|||
public async Task<IDictionary<string, double>> ZRevRangeWithScoresAsync(string key) |
|||
{ |
|||
return ConvertSortedSetEntryToDictionary(await _database.SortedSetRangeByScoreWithScoresAsync(key)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 删除
|
|||
/// </summary>
|
|||
/// <param name="key"></param>
|
|||
/// <param name="member"></param>
|
|||
/// <returns></returns>
|
|||
public async Task<bool> SortedSetRemoveAsync(string key, string member) |
|||
{ |
|||
return await _database.SortedSetRemoveAsync(key, member); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 数据转换
|
|||
/// </summary>
|
|||
/// <param name="keyValues"></param>
|
|||
/// <returns></returns>
|
|||
public SortedSetEntry[] ConvertDictionaryToSortedSetEntry(IDictionary<string, double> keyValues) |
|||
{ |
|||
List<SortedSetEntry> setEntries = new List<SortedSetEntry>(); |
|||
foreach (var item in keyValues) |
|||
{ |
|||
setEntries.Add(new SortedSetEntry(item.Key, item.Value)); |
|||
} |
|||
return setEntries.ToArray(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 数据转换字典
|
|||
/// </summary>
|
|||
/// <param name="keyValues"></param>
|
|||
/// <returns></returns>
|
|||
public IDictionary<string, double> ConvertSortedSetEntryToDictionary(SortedSetEntry[] setEntries) |
|||
{ |
|||
IDictionary<string, double> keyValues = new Dictionary<string, double>(); |
|||
foreach (var item in setEntries) |
|||
{ |
|||
keyValues.Add(item.Element, item.Score); |
|||
} |
|||
return keyValues; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="StackExchange.Redis" Version="2.2.88" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Configuration\Znyc.CloudCar.Configuration.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Utility\Znyc.CloudCar.Utility.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,121 @@ |
|||
using Znyc.CloudCar.Utility.Extensions; |
|||
|
|||
namespace Znyc.CloudCar.Configuration |
|||
{ |
|||
/// <summary>
|
|||
/// 配置文件格式化
|
|||
/// </summary>
|
|||
public class AppSettingsConstVars |
|||
{ |
|||
|
|||
#region 数据库
|
|||
|
|||
/// <summary>
|
|||
/// 数据库连接字符串
|
|||
/// </summary>
|
|||
public static readonly string DbSqlConnection = AppSettingsHelper.GetContent("ConnectionStrings", "SqlConnection"); |
|||
|
|||
/// <summary>
|
|||
/// 监听操作
|
|||
/// </summary>
|
|||
public static readonly bool SqlMonitorCommand = AppSettingsHelper.GetContent("ConnectionStrings", "SqlMonitorCommand").ObjectToBool(); |
|||
|
|||
/// <summary>
|
|||
/// 监听CURD
|
|||
/// </summary>
|
|||
public static readonly bool SqlCurd = AppSettingsHelper.GetContent("ConnectionStrings", "SqlCurd").ObjectToBool(); |
|||
|
|||
#endregion
|
|||
|
|||
#region Redis
|
|||
|
|||
/// <summary>
|
|||
/// 获取redis连接字符串
|
|||
/// </summary>
|
|||
public static readonly string RedisConfigConnectionString = AppSettingsHelper.GetContent("RedisConfig", "ConnectionString"); |
|||
/// <summary>
|
|||
/// 启用redis作为定时任务
|
|||
/// </summary>
|
|||
public static readonly bool RedisUseTimedTask = AppSettingsHelper.GetContent("RedisConfig", "UseTimedTask").ObjectToBool(); |
|||
|
|||
#endregion
|
|||
|
|||
#region Cors
|
|||
/// <summary>
|
|||
/// 跨域设置
|
|||
/// </summary>
|
|||
public static readonly string CorsPolicyName = AppSettingsHelper.GetContent("Cors", "PolicyName"); |
|||
|
|||
public static readonly bool CorsEnableAllIPs = AppSettingsHelper.GetContent("Cors", "EnableAllIPs").ObjectToBool(); |
|||
|
|||
public static readonly string CorsIPs = AppSettingsHelper.GetContent("Cors", "IPs"); |
|||
#endregion
|
|||
|
|||
#region Jwt授权配置================================================================================
|
|||
|
|||
public static readonly string JwtConfigSecretKey = AppSettingsHelper.GetContent("JwtConfig", "SecretKey"); |
|||
public static readonly string JwtConfigIssuer = AppSettingsHelper.GetContent("JwtConfig", "Issuer"); |
|||
public static readonly string JwtConfigAudience = AppSettingsHelper.GetContent("JwtConfig", "Audience"); |
|||
public static readonly int JwtConfigExpires = AppSettingsHelper.GetContent("JwtConfig", "Expires").ObjectToInt(); |
|||
|
|||
|
|||
#endregion
|
|||
|
|||
#region Hangfire
|
|||
/// <summary>
|
|||
/// 登陆账号
|
|||
/// </summary>
|
|||
public static readonly string HangFireLogin = AppSettingsHelper.GetContent("HangFire", "Login"); |
|||
|
|||
/// <summary>
|
|||
/// 登陆密码
|
|||
/// </summary>
|
|||
public static readonly string HangFirePassWord = AppSettingsHelper.GetContent("HangFire", "PassWord"); |
|||
#endregion
|
|||
|
|||
#region Middleware中间件================================================================================
|
|||
/// <summary>
|
|||
/// Ip限流
|
|||
/// </summary>
|
|||
public static readonly bool MiddlewareIpLogEnabled = AppSettingsHelper.GetContent("Middleware", "IPLog", "Enabled").ObjectToBool(); |
|||
/// <summary>
|
|||
/// 记录请求与返回数据
|
|||
/// </summary>
|
|||
public static readonly bool MiddlewareRequestResponseLogEnabled = AppSettingsHelper.GetContent("Middleware", "RequestResponseLog", "Enabled").ObjectToBool(); |
|||
/// <summary>
|
|||
/// 用户访问记录-是否开启
|
|||
/// </summary>
|
|||
public static readonly bool MiddlewareRecordAccessLogsEnabled = AppSettingsHelper.GetContent("Middleware", "RecordAccessLogs", "Enabled").ObjectToBool(); |
|||
/// <summary>
|
|||
/// 用户访问记录-过滤ip
|
|||
/// </summary>
|
|||
public static readonly string MiddlewareRecordAccessLogsIgnoreApis = AppSettingsHelper.GetContent("Middleware", "RecordAccessLogs", "IgnoreApis"); |
|||
|
|||
#endregion
|
|||
|
|||
#region COS配置
|
|||
public static readonly string CosConfigBucket = AppSettingsHelper.GetContent("CosConfig", "bucket"); |
|||
|
|||
public static readonly string CosConfigRegion = AppSettingsHelper.GetContent("CosConfig", "region"); |
|||
|
|||
public static readonly string CosConfigAllowPrefix = AppSettingsHelper.GetContent("CosConfig", "allowPrefix"); |
|||
|
|||
public static readonly string[] CosConfigAllowActions = AppSettingsHelper.GetContent("CosConfig", "allowActions").Split(","); |
|||
|
|||
public static readonly int CosConfigDurationSeconds = AppSettingsHelper.GetContent("CosConfig", "durationSeconds").ObjectToInt(); |
|||
|
|||
public static readonly string CosConfigSecretId = AppSettingsHelper.GetContent("CosConfig", "secretId"); |
|||
|
|||
public static readonly string CosConfigSecretKey = AppSettingsHelper.GetContent("CosConfig", "secretKey"); |
|||
#endregion
|
|||
|
|||
#region 百度配置
|
|||
public static readonly string AppID = AppSettingsHelper.GetContent("BaiduConfig", "AppID"); |
|||
|
|||
public static readonly string ApiKey = AppSettingsHelper.GetContent("BaiduConfig", "ApiKey"); |
|||
|
|||
public static readonly string SecretKey = AppSettingsHelper.GetContent("BaiduConfig", "SecretKey"); |
|||
|
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Configuration.Json; |
|||
|
|||
namespace Znyc.CloudCar.Configuration |
|||
{ |
|||
/// <summary>
|
|||
/// 获取AppSettings配置信息
|
|||
/// </summary>
|
|||
public class AppSettingsHelper |
|||
{ |
|||
#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的 属性“Configuration”必须包含非 null 值。请考虑将 属性 声明为可以为 null。
|
|||
public static IConfiguration Configuration { get; set; } |
|||
#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的 属性“Configuration”必须包含非 null 值。请考虑将 属性 声明为可以为 null。、
|
|||
|
|||
public AppSettingsHelper(string contentPath, string environmentName) |
|||
{ |
|||
string Path = $"appsettings.{environmentName}.json"; |
|||
Configuration = new ConfigurationBuilder() |
|||
.SetBasePath(contentPath) |
|||
.Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true }) |
|||
.Build(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 封装要操作的字符串
|
|||
/// AppSettingsHelper.GetContent(new string[] { "JwtConfig", "SecretKey" });
|
|||
/// </summary>
|
|||
/// <param name="sections">节点配置</param>
|
|||
/// <returns></returns>
|
|||
public static string GetContent(params string[] sections) |
|||
{ |
|||
try |
|||
{ |
|||
if (sections.Any()) |
|||
{ |
|||
return Configuration[string.Join(":", sections)]; |
|||
} |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
} |
|||
return ""; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,91 @@ |
|||
using System.ComponentModel; |
|||
|
|||
namespace Znyc.CloudCar.Configuration |
|||
{ |
|||
/// <summary>
|
|||
/// 缓存键
|
|||
/// </summary>
|
|||
public class GlobalCacheKeyVars |
|||
{ |
|||
/// <summary>
|
|||
/// 设备分类信息列表
|
|||
/// </summary>
|
|||
[Description("设备分类信息列表")] public const string EquipmentCategoryList = "equipment:category:list"; |
|||
|
|||
/// <summary>
|
|||
/// 设备品牌列表
|
|||
/// </summary>
|
|||
[Description("设备品牌列表")] public const string EquipmentBrandList = "equipment:brand:list"; |
|||
|
|||
/// <summary>
|
|||
/// 数据字典
|
|||
/// </summary>
|
|||
[Description("数据字典")] public const string DictionaryList = "dictionary:list"; |
|||
|
|||
/// <summary>
|
|||
/// 用户信息
|
|||
/// </summary>
|
|||
[Description("用户信息")] public const string User = "user:{0}"; |
|||
|
|||
/// <summary>
|
|||
/// 浏览量
|
|||
/// </summary>
|
|||
[Description("浏览量")] public const string PageView = "PageView"; |
|||
|
|||
/// <summary>
|
|||
/// 浏览设备信息
|
|||
/// </summary>
|
|||
[Description("浏览设备信息")] public const string BrowseList = "browse:{0}"; |
|||
|
|||
/// <summary>
|
|||
/// 实名认证信息
|
|||
/// </summary>
|
|||
[Description("实名认证信息")] public const string Certification = "certification:{0}"; |
|||
|
|||
/// <summary>
|
|||
/// 用户未读信息
|
|||
/// </summary>
|
|||
[Description("用户未读信息")] public const string UnreadMessage = "unread:number:{0}"; |
|||
|
|||
/// <summary>
|
|||
/// 新用户昵称信息
|
|||
/// </summary>
|
|||
[Description("新用户昵称信息")] public const string RegisteredUserlist = "registeredUserlist"; |
|||
|
|||
/// <summary>
|
|||
/// 广告图片
|
|||
/// </summary>
|
|||
[Description("广告图片")] public const string BannerList = "banner:list"; |
|||
|
|||
/// <summary>
|
|||
/// 充值活动信息
|
|||
/// </summary>
|
|||
[Description("充值活动信息")] public const string RechargeList = "recharge:list"; |
|||
|
|||
/// <summary>
|
|||
/// 优惠卡信息
|
|||
/// </summary>
|
|||
[Description("优惠卡信息")] public const string CardIntroList = "cardintro:list"; |
|||
|
|||
/// <summary>
|
|||
/// 百度token
|
|||
/// </summary>
|
|||
[Description("百度token")] public const string BaiduAccessToken = "token:baidu:{0}"; |
|||
|
|||
/// <summary>
|
|||
/// 微信token
|
|||
/// </summary>
|
|||
[Description("微信token")] public const string WxAccessToken = "token:weixin:{0}"; |
|||
|
|||
/// <summary>
|
|||
/// 用户修改次数
|
|||
/// </summary>
|
|||
[Description("用户修改次数")] public const string UserUpdateCount = "userupdate"; |
|||
|
|||
/// <summary>
|
|||
/// 用户新增设备信息
|
|||
/// </summary>
|
|||
[Description("用户新增设备信息")] public const string UserEquipment = "user:equipment:{0}"; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,358 @@ |
|||
|
|||
namespace Znyc.CloudCar.Configuration |
|||
{ |
|||
/// <summary>
|
|||
/// 全局常量
|
|||
/// </summary>
|
|||
public static class GlobalConstVars |
|||
{ |
|||
/// <summary>
|
|||
/// 数据删除成功
|
|||
/// </summary>
|
|||
public const string DeleteSuccess = "数据删除成功"; |
|||
/// <summary>
|
|||
/// 数据删除失败
|
|||
/// </summary>
|
|||
public const string DeleteFailure = "数据删除失败"; |
|||
/// <summary>
|
|||
/// 系统禁止删除此数据
|
|||
/// </summary>
|
|||
public const string DeleteProhibitDelete = "系统禁止删除此数据"; |
|||
/// <summary>
|
|||
/// 此数据含有子类信息,禁止删除
|
|||
/// </summary>
|
|||
public const string DeleteIsHaveChildren = "此数据含有子类信息,禁止删除"; |
|||
/// <summary>
|
|||
/// 数据处理异常
|
|||
/// </summary>
|
|||
public const string DataHandleEx = "数据接口出现异常"; |
|||
/// <summary>
|
|||
/// 数据添加成功
|
|||
/// </summary>
|
|||
public const string CreateSuccess = "数据添加成功"; |
|||
/// <summary>
|
|||
/// 数据添加失败
|
|||
/// </summary>
|
|||
public const string CreateFailure = "数据添加失败"; |
|||
/// <summary>
|
|||
/// 数据移动成功
|
|||
/// </summary>
|
|||
public const string MoveSuccess = "数据移动成功"; |
|||
/// <summary>
|
|||
/// 数据移动失败
|
|||
/// </summary>
|
|||
public const string MoveFailure = "数据移动失败"; |
|||
/// <summary>
|
|||
/// 系统禁止添加数据
|
|||
/// </summary>
|
|||
public const string CreateProhibitCreate = "系统禁止添加数据"; |
|||
/// <summary>
|
|||
/// 数据编辑成功
|
|||
/// </summary>
|
|||
public const string EditSuccess = "数据编辑成功"; |
|||
/// <summary>
|
|||
/// 数据编辑失败
|
|||
/// </summary>
|
|||
public const string EditFailure = "数据编辑失败"; |
|||
/// <summary>
|
|||
/// 系统禁止编辑此数据
|
|||
/// </summary>
|
|||
public const string EditProhibitEdit = "系统禁止编辑此数据"; |
|||
/// <summary>
|
|||
/// 数据已存在
|
|||
/// </summary>
|
|||
public const string DataIsHave = "数据已存在"; |
|||
/// <summary>
|
|||
/// 数据不存在
|
|||
/// </summary>
|
|||
public const string DataisNo = "数据不存在"; |
|||
/// <summary>
|
|||
/// 请提交必要的参数
|
|||
/// </summary>
|
|||
public const string DataParameterError = "请提交必要的参数"; |
|||
/// <summary>
|
|||
/// 数据插入成功
|
|||
/// </summary>
|
|||
public const string InsertSuccess = "数据插入成功!"; |
|||
/// <summary>
|
|||
/// 数据插入失败
|
|||
/// </summary>
|
|||
public const string InsertFailure = "数据插入失败!"; |
|||
/// <summary>
|
|||
/// Excel导出失败
|
|||
/// </summary>
|
|||
public const string ExcelExportFailure = "Excel导出失败"; |
|||
/// <summary>
|
|||
/// Excel导出成功
|
|||
/// </summary>
|
|||
public const string ExcelExportSuccess = "Excel导出成功"; |
|||
/// <summary>
|
|||
/// 获取数据成功
|
|||
/// </summary>
|
|||
public const string GetDataSuccess = "获取数据成功!"; |
|||
/// <summary>
|
|||
/// 获取数据异常
|
|||
/// </summary>
|
|||
public const string GetDataException = "获取数据异常!"; |
|||
/// <summary>
|
|||
/// 获取数据失败
|
|||
/// </summary>
|
|||
public const string GetDataFailure = "获取数据失败!"; |
|||
/// <summary>
|
|||
/// 设置数据成功
|
|||
/// </summary>
|
|||
public const string SetDataSuccess = "设置数据成功!"; |
|||
/// <summary>
|
|||
/// 设置数据异常
|
|||
/// </summary>
|
|||
public const string SetDataException = "设置数据异常!"; |
|||
/// <summary>
|
|||
/// 设置数据失败
|
|||
/// </summary>
|
|||
public const string SetDataFailure = "设置数据失败!"; |
|||
|
|||
/// <summary>
|
|||
/// Tools工具常量
|
|||
/// </summary>
|
|||
public static class ToolsVars |
|||
{ |
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
public const string IllegalWordsCahceName = "IllegalWordsCahce"; |
|||
|
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 权限变量配置
|
|||
/// </summary>
|
|||
public static class Permissions |
|||
{ |
|||
public const string Name = "Permission"; |
|||
|
|||
/// <summary>
|
|||
/// 当前项目是否启用IDS4权限方案
|
|||
/// true:表示启动IDS4
|
|||
/// false:表示使用JWT
|
|||
public static bool IsUseIds4 = false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 路由变量前缀配置
|
|||
/// </summary>
|
|||
public static class RoutePrefix |
|||
{ |
|||
/// <summary>
|
|||
/// 前缀名
|
|||
/// 如果不需要,尽量留空,不要修改
|
|||
/// 除非一定要在所有的 api 前统一加上特定前缀
|
|||
/// </summary>
|
|||
public const string Name = ""; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 银行卡相关常量定义
|
|||
/// </summary>
|
|||
public static class BankConst |
|||
{ |
|||
public const string BankLogoUrl = "https://apimg.alipay.com/combo.png?d=cashier&t="; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// RedisMqKey队列
|
|||
/// </summary>
|
|||
public static class RedisMessageQueueKey |
|||
{ |
|||
/// <summary>
|
|||
/// 微信支付成功后推送到接口进行数据处理
|
|||
/// </summary>
|
|||
public const string WeChatPayNotice = "WeChatPayNoticeQueue"; |
|||
/// <summary>
|
|||
/// 微信模板消息
|
|||
/// </summary>
|
|||
public const string SendWxTemplateMessage = "SendWxTemplateMessage"; |
|||
|
|||
/// <summary>
|
|||
/// 订单完结后走代理或分销商提成处理
|
|||
/// </summary>
|
|||
public const string OrderAgentOrDistribution = "OrderAgentOrDistributionQueue"; |
|||
/// <summary>
|
|||
/// 订单完成时,结算该订单
|
|||
/// </summary>
|
|||
public const string OrderFinishCommand = "OrderFinishCommandQueue"; |
|||
/// <summary>
|
|||
/// 订单完成时,门店订单自动发货
|
|||
/// </summary>
|
|||
public const string OrderAutomaticDelivery = "OrderAutomaticDeliveryQueue"; |
|||
/// <summary>
|
|||
/// 订单完结后走打印模块
|
|||
/// </summary>
|
|||
public const string OrderPrint = "OrderPrintQueue"; |
|||
/// <summary>
|
|||
/// 售后审核通过后处理
|
|||
/// </summary>
|
|||
public const string AfterSalesReview = "AfterSalesReview"; |
|||
|
|||
/// <summary>
|
|||
/// 日志队列
|
|||
/// </summary>
|
|||
public const string LogingQueue = "LogingQueue"; |
|||
/// <summary>
|
|||
/// 短信发送队列
|
|||
/// </summary>
|
|||
public const string SmsQueue = "SmsQueue"; |
|||
|
|||
//用户相关
|
|||
|
|||
//订单支付成功后,用户升级处理
|
|||
public const string UserUpGrade = "UserUpGradeQueue"; |
|||
|
|||
|
|||
|
|||
} |
|||
#region JWT
|
|||
public class JWTClaim |
|||
{ |
|||
|
|||
|
|||
/// <summary>
|
|||
/// 用户Id
|
|||
/// </summary>
|
|||
public const string JwtUserId = "id"; |
|||
|
|||
/// <summary>
|
|||
/// 姓名
|
|||
/// </summary>
|
|||
public const string JwtUserName = "nn"; |
|||
|
|||
/// <summary>
|
|||
/// 刷新有效期
|
|||
/// </summary>
|
|||
public const string JwtRefreshExpires = "re"; |
|||
|
|||
/// <summary>
|
|||
/// 微信认证Id
|
|||
/// </summary>
|
|||
public const string JwtOpenId = "oid"; |
|||
|
|||
/// <summary>
|
|||
/// 权限id
|
|||
/// </summary>
|
|||
public const string JwtRoleId = "rid"; |
|||
|
|||
/// <summary>
|
|||
/// 开放平台Id(区分用户唯一性)
|
|||
/// </summary>
|
|||
public const string JwtUnionId = "uid"; |
|||
|
|||
/// <summary>
|
|||
/// 头像url
|
|||
/// </summary>
|
|||
public const string JwtAvatarUrl = "avatarurl"; |
|||
|
|||
/// <summary>
|
|||
/// SessionKey
|
|||
/// </summary>
|
|||
public const string JwtSessionKey = "sessionKey"; |
|||
|
|||
} |
|||
#endregion
|
|||
|
|||
#region User
|
|||
|
|||
public class User |
|||
{ |
|||
/// <summary>
|
|||
/// 默认头像名称
|
|||
/// </summary>
|
|||
public const string DefaultAvataUrl = "Default_AvatarUrl.png"; |
|||
|
|||
/// <summary>
|
|||
/// 默认图片地址
|
|||
/// </summary>
|
|||
public const string DefaultImagePrefix = "https://zhongnengyunche.com/cloudcar/wx/upload/avatar/"; |
|||
|
|||
/// <summary>
|
|||
/// 默认头像地址
|
|||
/// </summary>
|
|||
public const string Default_AvataUrl_Address = "https://zhongnengyunche.com/cloudcar/wx/upload/avatar/Default_AvatarUrl.png"; |
|||
|
|||
/// <summary>
|
|||
/// 默认名称
|
|||
/// </summary>
|
|||
public const string Default_UserName = "先生"; |
|||
} |
|||
#endregion
|
|||
|
|||
#region 云币
|
|||
public class Currency |
|||
{ |
|||
/// <summary>
|
|||
/// 默认
|
|||
/// </summary>
|
|||
public const int Default = 0; |
|||
|
|||
/// <summary>
|
|||
/// 首次登陆
|
|||
/// </summary>
|
|||
public const int FirstLogin = 10; |
|||
|
|||
/// <summary>
|
|||
/// 邀新用户
|
|||
/// </summary>
|
|||
public const int NewUsers = 10; |
|||
|
|||
/// <summary>
|
|||
/// 每日分享
|
|||
/// </summary>
|
|||
public const int DailyShare = 0; |
|||
/// <summary>
|
|||
/// 实名认证
|
|||
/// </summary>
|
|||
public const int RealName = 0; |
|||
|
|||
/// <summary>
|
|||
/// 每日签到
|
|||
/// </summary>
|
|||
public const int Signin = 0; |
|||
|
|||
/// <summary>
|
|||
/// 拔打电话
|
|||
/// </summary>
|
|||
public const int CallPhone = 10; |
|||
|
|||
/// <summary>
|
|||
/// 设备置顶
|
|||
/// </summary>
|
|||
public const int TopEquipment = 100; |
|||
|
|||
/// <summary>
|
|||
/// 刷新设备信息
|
|||
/// </summary>
|
|||
public const int Refresh = 10; |
|||
|
|||
/// <summary>
|
|||
/// 默认临时云币
|
|||
/// </summary>
|
|||
public const int Default_TemporarylCredits = 0; |
|||
} |
|||
#endregion
|
|||
|
|||
#region CurrencyOperatingType
|
|||
public class CurrencyOperatingType |
|||
{ |
|||
public const string Add = "+"; |
|||
|
|||
public const string Reduce = "-"; |
|||
} |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// 默认Banner地址
|
|||
/// </summary>
|
|||
public const string Default_Banner_Prefix = |
|||
"https://zhongnengyunche.com/cloudcar/wx/upload/banner/"; |
|||
} |
|||
} |
@ -0,0 +1,399 @@ |
|||
using System.ComponentModel; |
|||
|
|||
namespace Znyc.CloudCar.Configuration |
|||
{ |
|||
/// <summary>
|
|||
/// 全局枚举
|
|||
/// </summary>
|
|||
public class GlobalEnumVars |
|||
{ |
|||
#region 常用状态
|
|||
|
|||
public enum CommonEnum |
|||
{ |
|||
[Description("封禁")] |
|||
Disable = 99, |
|||
|
|||
[Description("有效")] |
|||
Normal = 10, |
|||
} |
|||
#endregion
|
|||
|
|||
#region 权限相关
|
|||
/// <summary>
|
|||
/// 状态码枚举
|
|||
/// </summary>
|
|||
public enum StatusCodes |
|||
{ |
|||
/// <summary>
|
|||
/// 操作失败
|
|||
/// </summary>
|
|||
[Description("操作失败")] Status0NotOk = 0, |
|||
|
|||
/// <summary>
|
|||
/// 操作成功
|
|||
/// </summary>
|
|||
[Description("操作成功")] Status1Ok = 1, |
|||
|
|||
/// <summary>
|
|||
/// 未登录(需要重新登录)
|
|||
/// </summary>
|
|||
[Description("未登录")] Status401Unauthorized = 401, |
|||
|
|||
/// <summary>
|
|||
/// 权限不足
|
|||
/// </summary>
|
|||
[Description("权限不足")] Status403Forbidden = 403, |
|||
|
|||
/// <summary>
|
|||
/// 资源不存在
|
|||
/// </summary>
|
|||
[Description("资源不存在")] Status404NotFound = 404, |
|||
|
|||
/// <summary>
|
|||
/// 系统内部错误(非业务代码里显式抛出的异常,例如由于数据不正确导致空指针异常、数据库异常等等)
|
|||
/// </summary>
|
|||
[Description("系统内部错误")] Status500InternalServerError = 500 |
|||
} |
|||
#endregion
|
|||
|
|||
#region HangFire定时任务相关
|
|||
public enum HangFireQueuesConfig |
|||
{ |
|||
/// <summary>
|
|||
/// 默认
|
|||
/// </summary>
|
|||
[Description("默认")] |
|||
@default = 1, |
|||
/// <summary>
|
|||
/// 接口
|
|||
/// </summary>
|
|||
[Description("接口")] |
|||
apis = 2, |
|||
/// <summary>
|
|||
/// 网站
|
|||
/// </summary>
|
|||
[Description("网站")] |
|||
web = 3, |
|||
/// <summary>
|
|||
/// 循环时间
|
|||
/// </summary>
|
|||
[Description("循环时间")] |
|||
recurring = 4, |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
#region redis缓存类型
|
|||
public enum AccessTokenEnum |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// 微信小程序
|
|||
/// </summary>
|
|||
WxOpenAccessToken = 1, |
|||
|
|||
/// <summary>
|
|||
/// 微信公众号
|
|||
/// </summary>
|
|||
WeiXinAccessToken = 2, |
|||
} |
|||
#endregion
|
|||
|
|||
#region 设备状态
|
|||
public enum StateEnum |
|||
{ |
|||
/// <summary>
|
|||
/// 未通过
|
|||
/// </summary>
|
|||
[Description("未通过")] Fail = 0, |
|||
|
|||
/// <summary>
|
|||
/// 审核中
|
|||
/// </summary>
|
|||
[Description("审核中")] InReview = 10, |
|||
|
|||
/// <summary>
|
|||
/// 出售中
|
|||
/// </summary>
|
|||
[Description("出售中")] Selling = 20, |
|||
|
|||
/// <summary>
|
|||
/// 已成交
|
|||
/// </summary>
|
|||
[Description("已成交")] Traded = 30, |
|||
|
|||
/// <summary>
|
|||
/// 已下架
|
|||
/// </summary>
|
|||
[Description("已下架")] Shelved = 99, |
|||
|
|||
/// <summary>
|
|||
/// 撤销审核
|
|||
/// </summary>
|
|||
[Description("撤销审核")] Revocation = 50 |
|||
} |
|||
#endregion
|
|||
|
|||
#region 图片类型
|
|||
public enum PictureTypeEnum |
|||
{ |
|||
/// <summary>
|
|||
/// 设备照片
|
|||
/// </summary>
|
|||
[Description("设备照片")] Equipment = 10, |
|||
|
|||
/// <summary>
|
|||
/// 行驶证或身份证
|
|||
/// </summary>
|
|||
[Description("行驶证或身份证")] DriverLicense = 20, |
|||
} |
|||
#endregion
|
|||
|
|||
#region 云币类型
|
|||
public enum CurrencyType |
|||
{ |
|||
/// <summary>
|
|||
/// 首次登陆
|
|||
/// </summary>
|
|||
[Description("首次登陆")] FirstLogin = 1, |
|||
|
|||
/// <summary>
|
|||
/// 邀新用户
|
|||
/// </summary>
|
|||
[Description("邀新用户")] NewUsers = 2, |
|||
|
|||
/// <summary>
|
|||
/// 每日分享
|
|||
/// </summary>
|
|||
[Description("每日分享")] DailyShare = 3, |
|||
|
|||
/// <summary>
|
|||
/// 关注公众号
|
|||
/// </summary>
|
|||
[Description("关注公众号")] FocusOn = 4, |
|||
|
|||
/// <summary>
|
|||
/// 购买优惠卡赠送
|
|||
/// </summary>
|
|||
[Description("购买优惠卡赠送")] PreferentialCard = 5, |
|||
|
|||
/// <summary>
|
|||
/// 发布设备信息
|
|||
/// </summary>
|
|||
[Description("发布设备信息")] Equipment = 6, |
|||
|
|||
/// <summary>
|
|||
/// 实名认证
|
|||
/// </summary>
|
|||
[Description("实名认证")] RealName = 7, |
|||
|
|||
/// <summary>
|
|||
/// 每日签到
|
|||
/// </summary>
|
|||
[Description("每日签到")] Signin = 8, |
|||
|
|||
/// <summary>
|
|||
/// 云币充值
|
|||
/// </summary>
|
|||
[Description("云币充值")] BuyCurrency = 9, |
|||
|
|||
/// <summary>
|
|||
/// 抽奖赚云币
|
|||
/// </summary>
|
|||
[Description("抽奖赚云币")] LuckyDraw = 10, |
|||
|
|||
/// <summary>
|
|||
/// 兑换奖品
|
|||
/// </summary>
|
|||
[Description("兑换奖品")] Conversion = 11, |
|||
|
|||
/// <summary>
|
|||
/// 拔打电话
|
|||
/// </summary>
|
|||
[Description("拔打电话")] CallPhone = 12, |
|||
|
|||
/// <summary>
|
|||
/// 设备信息置顶
|
|||
/// </summary>
|
|||
[Description("置顶设备")] TopEquipment = 13, |
|||
|
|||
/// <summary>
|
|||
/// 刷新设备信息
|
|||
/// </summary>
|
|||
[Description("刷新设备")] RefreshEquipment = 14, |
|||
|
|||
/// <summary>
|
|||
/// 置顶设备退还
|
|||
/// </summary>
|
|||
[Description("置顶设备退还")] ReturnTopEquipment = 15, |
|||
|
|||
} |
|||
#endregion
|
|||
|
|||
#region 云币操作类型
|
|||
public enum OperatingType |
|||
{ |
|||
/// <summary>
|
|||
/// 增加
|
|||
/// </summary>
|
|||
[Description("增加")] Add = 1, |
|||
|
|||
/// <summary>
|
|||
/// 减少
|
|||
/// </summary>
|
|||
[Description("减少")] Reduce = 2, |
|||
|
|||
/// <summary>
|
|||
/// 冻结
|
|||
/// </summary>
|
|||
[Description("冻结")] Freeze = 3 |
|||
} |
|||
#endregion
|
|||
|
|||
#region 用户实名状态
|
|||
public enum CertificationState |
|||
{ |
|||
/// <summary>
|
|||
/// 实名审核中
|
|||
/// </summary>
|
|||
[Description("实名审核中")] Review = 0, |
|||
|
|||
/// <summary>
|
|||
/// 实名通过
|
|||
/// </summary>
|
|||
[Description("实名通过")] Pass = 1, |
|||
|
|||
/// <summary>
|
|||
/// 实名失败
|
|||
/// </summary>
|
|||
[Description("实名失败")] Fail = 2, |
|||
|
|||
/// <summary>
|
|||
/// 未实名
|
|||
/// </summary>
|
|||
[Description("未实名")] None = 3 |
|||
} |
|||
#endregion
|
|||
|
|||
#region 支付类型
|
|||
public enum PaymentMethod |
|||
{ |
|||
/// <summary>
|
|||
/// 现金
|
|||
/// </summary>
|
|||
[Description("现金")] Cash = 1, |
|||
|
|||
/// <summary>
|
|||
/// 余额
|
|||
/// </summary>
|
|||
[Description("余额")] Balance = 2, |
|||
|
|||
/// <summary>
|
|||
/// 网银
|
|||
/// </summary>
|
|||
[Description("网银")] NetSilver = 3, |
|||
|
|||
/// <summary>
|
|||
/// 支付宝
|
|||
/// </summary>
|
|||
[Description("支付宝")] ZFB = 4, |
|||
|
|||
/// <summary>
|
|||
/// 微信
|
|||
/// </summary>
|
|||
[Description("微信")] WX = 5, |
|||
|
|||
/// <summary>
|
|||
/// 云币
|
|||
/// </summary>
|
|||
[Description("云币")] CloudCurrency = 6 |
|||
} |
|||
#endregion
|
|||
|
|||
#region 订单状态
|
|||
public enum OrderStatus |
|||
{ |
|||
/// <summary>
|
|||
/// 已取消
|
|||
/// </summary>
|
|||
[Description("已取消")] Cancel = 0, |
|||
|
|||
/// <summary>
|
|||
/// 未付款
|
|||
/// </summary>
|
|||
[Description("未付款")] NotPaying = 10, |
|||
|
|||
/// <summary>
|
|||
/// 已付款
|
|||
/// </summary>
|
|||
[Description("已付款")] Paying = 20, |
|||
|
|||
/// <summary>
|
|||
/// 已发货
|
|||
/// </summary>
|
|||
[Description("已发货")] Delivery = 40, |
|||
|
|||
/// <summary>
|
|||
/// 交易成功
|
|||
/// </summary>
|
|||
[Description("交易成功")] Success = 50, |
|||
|
|||
/// <summary>
|
|||
/// 交易关闭
|
|||
/// </summary>
|
|||
[Description("交易关闭")] Close = 60 |
|||
} |
|||
#endregion
|
|||
|
|||
#region 支付状态
|
|||
public enum PayStatus |
|||
{ |
|||
/// <summary>
|
|||
/// 已取消
|
|||
/// </summary>
|
|||
[Description("已取消")] Cancel = 0, |
|||
|
|||
/// <summary>
|
|||
/// 未成功
|
|||
/// </summary>
|
|||
[Description("未成功")] NotPaying = 10, |
|||
|
|||
/// <summary>
|
|||
/// 已成功
|
|||
/// </summary>
|
|||
[Description("已成功")] Paying = 20 |
|||
} |
|||
#endregion
|
|||
|
|||
#region 订单类型
|
|||
public enum OrderType |
|||
{ |
|||
/// <summary>
|
|||
/// 充值
|
|||
/// </summary>
|
|||
[Description("充值")] BuyCurrency = 1, |
|||
|
|||
/// <summary>
|
|||
/// 优惠卡
|
|||
/// </summary>
|
|||
[Description("优惠卡")] PreferentialCard = 2, |
|||
} |
|||
#endregion
|
|||
|
|||
#region 登陆日志来源平台
|
|||
public enum PlatformType |
|||
{ |
|||
/// <summary>
|
|||
/// 云车二手
|
|||
/// </summary>
|
|||
[Description("人才招聘")] CloudCar = 1, |
|||
|
|||
/// <summary>
|
|||
/// 后台管理系统
|
|||
/// </summary>
|
|||
[Description("后台管理系统")] Management = 3 |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> |
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Utility\Znyc.CloudCar.Utility.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,36 @@ |
|||
using Autofac; |
|||
using System.Reflection; |
|||
|
|||
namespace Znyc.CloudCar.Core.AutoFac |
|||
{ |
|||
public class AutofacModuleRegister : Autofac.Module |
|||
{ |
|||
protected override void Load(ContainerBuilder builder) |
|||
{ |
|||
var basePath = AppContext.BaseDirectory; |
|||
|
|||
#region 带有接口层的服务注入
|
|||
var servicesDllFile = Path.Combine(basePath, "Znyc.CloudCar.Services.dll"); |
|||
var repositoryDllFile = Path.Combine(basePath, "Znyc.CloudCar.Repository.dll"); |
|||
|
|||
if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile))) |
|||
{ |
|||
var msg = "Repository.dll和Services.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。"; |
|||
throw new Exception(msg); |
|||
} |
|||
|
|||
// 获取 Service.dll 程序集服务,并注册
|
|||
var assemblysServices = Assembly.LoadFrom(servicesDllFile); |
|||
//支持属性注入依赖重复
|
|||
builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces().InstancePerDependency() |
|||
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies); |
|||
|
|||
// 获取 Repository.dll 程序集服务,并注册
|
|||
var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); |
|||
//支持属性注入依赖重复
|
|||
builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces().InstancePerDependency() |
|||
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies); |
|||
#endregion
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,118 @@ |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using System.IdentityModel.Tokens.Jwt; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
using Znyc.CloudCar.Auth.Policys; |
|||
using Znyc.CloudCar.Configuration; |
|||
using Znyc.CloudCar.Utility.Extensions; |
|||
using static Znyc.CloudCar.Configuration.GlobalConstVars; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
public static class AuthorizationSetup |
|||
{ |
|||
public static void AddAuthorizationSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) throw new ArgumentNullException(nameof(services)); |
|||
|
|||
#region 参数
|
|||
//读取配置文件
|
|||
var symmetricKeyAsBase64 = AppSettingsConstVars.JwtConfigSecretKey; |
|||
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); |
|||
var signingKey = new SymmetricSecurityKey(keyByteArray); |
|||
var issuer = AppSettingsConstVars.JwtConfigIssuer; |
|||
var audience = AppSettingsConstVars.JwtConfigAudience; |
|||
|
|||
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); |
|||
|
|||
// 如果要数据库动态绑定,这里先留个空,后边处理器里动态赋值
|
|||
var permission = new List<PermissionItem>(); |
|||
|
|||
// 角色与接口的权限要求参数
|
|||
var permissionRequirement = new PermissionRequirement( |
|||
"/api/denied",// 拒绝授权的跳转地址(目前无用)
|
|||
permission, |
|||
ClaimTypes.Role,//基于角色的授权
|
|||
issuer,//发行人
|
|||
audience,//听众
|
|||
signingCredentials,//签名凭据
|
|||
expiration: TimeSpan.FromSeconds(60 * 60 * 24)//接口的过期时间
|
|||
); |
|||
#endregion
|
|||
|
|||
// 复杂的策略授权
|
|||
services.AddAuthorization(options => |
|||
{ |
|||
options.AddPolicy(Permissions.Name, policy => policy.Requirements.Add(permissionRequirement)); |
|||
}); |
|||
|
|||
|
|||
// 令牌验证参数
|
|||
var tokenValidationParameters = new TokenValidationParameters |
|||
{ |
|||
ValidateIssuerSigningKey = true, //是否验证SecurityKey
|
|||
IssuerSigningKey = signingKey, //拿到SecurityKey
|
|||
ValidateIssuer = true, //是否验证Issuer
|
|||
ValidIssuer = issuer,//发行人
|
|||
ValidateAudience = true, //是否验证Audience
|
|||
ValidAudience = audience,//订阅人
|
|||
ValidateLifetime = true, //是否验证失效时间
|
|||
ClockSkew = TimeSpan.FromSeconds(60), |
|||
RequireExpirationTime = true, |
|||
}; |
|||
|
|||
// core自带官方JWT认证,开启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 |
|||
{ |
|||
OnChallenge = context => |
|||
{ |
|||
context.Response.Headers.Add("Token-Error", context.ErrorDescription); |
|||
return Task.CompletedTask; |
|||
}, |
|||
OnAuthenticationFailed = context => |
|||
{ |
|||
var token = context.Request.Headers["Authorization"].ObjectToString().Replace("Bearer ", ""); |
|||
var jwtToken = (new JwtSecurityTokenHandler()).ReadJwtToken(token); |
|||
|
|||
if (jwtToken.Issuer != issuer) |
|||
{ |
|||
context.Response.Headers.Add("Token-Error-Iss", "issuer is wrong!"); |
|||
} |
|||
|
|||
if (jwtToken.Audiences.FirstOrDefault() != audience) |
|||
{ |
|||
context.Response.Headers.Add("Token-Error-Aud", "Audience is wrong!"); |
|||
} |
|||
|
|||
// 如果过期,则把<是否过期>添加到,返回头信息中
|
|||
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) |
|||
{ |
|||
context.Response.Headers.Add("Token-Expired", "true"); |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
}; |
|||
}) |
|||
.AddScheme<AuthenticationSchemeOptions, ApiResponseHandler>(nameof(ApiResponseHandler), o => { }); |
|||
|
|||
|
|||
// 注入权限处理器
|
|||
services.AddScoped<IAuthorizationHandler, PermissionHandler>(); |
|||
services.AddSingleton(permissionRequirement); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Znyc.CloudCar.Configuration; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
public static class CoreSetup |
|||
{ |
|||
|
|||
public static void AddCoresSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(services)); |
|||
} |
|||
services.AddCors(c => |
|||
{ |
|||
if (!AppSettingsConstVars.CorsEnableAllIPs) |
|||
{ |
|||
c.AddPolicy(AppSettingsConstVars.CorsPolicyName, policy => |
|||
{ |
|||
policy.WithOrigins(AppSettingsConstVars.CorsIPs.Split(',')); |
|||
policy.AllowAnyHeader(); |
|||
policy.AllowAnyMethod(); |
|||
policy.AllowCredentials(); |
|||
}); |
|||
} |
|||
else |
|||
{ |
|||
//允许任意跨域请求
|
|||
c.AddPolicy(AppSettingsConstVars.CorsPolicyName, policy => |
|||
{ |
|||
policy.SetIsOriginAllowed((host) => true) |
|||
.AllowAnyHeader() |
|||
.AllowAnyMethod() |
|||
.AllowCredentials(); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
using FreeSql; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Znyc.CloudCar.Auth.HttpContextUser; |
|||
using Znyc.CloudCar.Configuration; |
|||
using Znyc.CloudCar.Core.Db; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
/// <summary>
|
|||
/// FreeSql
|
|||
/// </summary>
|
|||
public static class FreeSqlSetup |
|||
{ |
|||
public static void AddFreeSqlSetup(this IServiceCollection services) |
|||
{ |
|||
var freeSqlBuiler = new FreeSqlBuilder() |
|||
.UseConnectionString(DataType.MySql, AppSettingsConstVars.DbSqlConnection) |
|||
.UseLazyLoading(false) |
|||
.UseNoneCommandParameter(true); |
|||
//监听所有命令
|
|||
if (AppSettingsConstVars.SqlMonitorCommand) |
|||
{ |
|||
freeSqlBuiler.UseMonitorCommand(cmd => { }, |
|||
(cmd, traceLog) => { Console.WriteLine($"{cmd.CommandText}\r\n"); }); |
|||
} |
|||
var fsql = freeSqlBuiler.Build(); |
|||
services.AddScoped<UnitOfWorkManager>(); |
|||
services.AddSingleton(fsql); |
|||
|
|||
//监听所有sql语句
|
|||
if (AppSettingsConstVars.SqlCurd) |
|||
{ |
|||
fsql.Aop.CurdAfter += (s, e) => |
|||
{ |
|||
Console.WriteLine($"{e.Sql}\r\n"); |
|||
Console.WriteLine($"耗时:{e.ElapsedMilliseconds}ms\r\n"); |
|||
}; |
|||
} |
|||
|
|||
//审计数据
|
|||
var user = services.BuildServiceProvider().GetService<IHttpContextUser>(); |
|||
fsql.Aop.AuditValue += (s, e) => { DbHelper.AuditValue(e, user); }; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
using Hangfire; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Znyc.CloudCar.Configuration; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
/// <summary>
|
|||
/// HangFire配置
|
|||
/// </summary>
|
|||
public static class HangFireSetup |
|||
{ |
|||
public static void AddHangFireSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(services)); |
|||
} |
|||
|
|||
services.AddHangfire(x => x.UseRedisStorage(AppSettingsConstVars.RedisConfigConnectionString)); |
|||
|
|||
services.AddHangfireServer(options => |
|||
{ |
|||
options.Queues = new[] { GlobalEnumVars.HangFireQueuesConfig.@default.ToString(), GlobalEnumVars.HangFireQueuesConfig.apis.ToString(), GlobalEnumVars.HangFireQueuesConfig.web.ToString(), GlobalEnumVars.HangFireQueuesConfig.recurring.ToString() }; |
|||
options.ServerTimeout = TimeSpan.FromMinutes(4); |
|||
options.SchedulePollingInterval = TimeSpan.FromSeconds(15);//秒级任务需要配置短点,一般任务可以配置默认时间,默认15秒
|
|||
options.ShutdownTimeout = TimeSpan.FromMinutes(30); //超时时间
|
|||
options.WorkerCount = Math.Max(Environment.ProcessorCount, 20); //工作线程数,当前允许的最大线程,默认20
|
|||
}); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
|
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Znyc.CloudCar.Auth.HttpContextUser; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
/// <summary>
|
|||
/// 上下文启动
|
|||
/// </summary>
|
|||
public static class HttpContextSetup |
|||
{ |
|||
public static void AddHttpContextSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) throw new AbandonedMutexException(nameof(services)); |
|||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); |
|||
services.AddScoped<IHttpContextUser, AspNetUser>(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
using Mapster; |
|||
using MapsterMapper; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.DependencyModel; |
|||
using System.Reflection; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
/// <summary>
|
|||
/// Mapster
|
|||
/// </summary>
|
|||
public static class MapsterSetup |
|||
{ |
|||
public static void AddMapsterSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(services)); |
|||
} |
|||
Assembly[] assemblies = DependencyContext.Default.RuntimeLibraries |
|||
.Where(x => x.Name.StartsWith("Znyc.CloudCar")) |
|||
.Select(x => Assembly.Load(new AssemblyName(x.Name))).ToArray(); |
|||
services.AddScoped<IMapper>(sp => new Mapper()); |
|||
TypeAdapterConfig.GlobalSettings.Scan(assemblies); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using StackExchange.Redis; |
|||
using Znyc.CloudCar.Caching; |
|||
using Znyc.CloudCar.Configuration; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
/// <summary>
|
|||
/// Redis缓存,启动服务
|
|||
/// </summary>
|
|||
public static class RedisCacheSetup |
|||
{ |
|||
public static void AddRedisCacheSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) |
|||
{ |
|||
throw new ArgumentException(nameof(services)); |
|||
} |
|||
services.AddSingleton(sp => |
|||
{ |
|||
string redisConfiguration = AppSettingsConstVars.RedisConfigConnectionString; |
|||
var configuration = ConfigurationOptions.Parse(redisConfiguration, true); |
|||
configuration.ResolveDns = true; |
|||
return ConnectionMultiplexer.Connect(configuration); |
|||
}); |
|||
services.AddTransient<IRedisOperationRepository, RedisOperationRepository>(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
|
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
/// <summary>
|
|||
/// Redis 消息队列 启动服务
|
|||
/// </summary>
|
|||
public static class RedisMessageQueueSetup |
|||
{ |
|||
public static void AddRedisMessageQueueSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(services)); |
|||
} |
|||
//services.AddInitQ(m =>
|
|||
//{
|
|||
// //时间间隔
|
|||
// m.SuspendTime = 1000;
|
|||
// //redis服务器地址
|
|||
// m.ConnectionString = AppSettingsConstVars.RedisConfigConnectionString;
|
|||
// //对应的订阅者类,需要new一个实例对象,当然你也可以传参,比如日志对象
|
|||
// m.ListSubscribe = new List<Type>()
|
|||
// {
|
|||
// typeof(LogingSubscribe)
|
|||
// };
|
|||
// //显示日志
|
|||
// m.ShowLog = false;
|
|||
//});
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,65 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.OpenApi.Models; |
|||
using Swashbuckle.AspNetCore.Filters; |
|||
using Znyc.CloudCar.Swagger; |
|||
|
|||
namespace Znyc.CloudCar.Core.Config |
|||
{ |
|||
/// <summary>
|
|||
/// swagger
|
|||
/// </summary>
|
|||
public static class SwaggerSetup |
|||
{ |
|||
/// <summary>
|
|||
///
|
|||
/// </summary>
|
|||
public static void AddSwaggerSetup(this IServiceCollection services) |
|||
{ |
|||
if (services == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(services)); |
|||
} |
|||
var apiName = "云车二手小程序API"; |
|||
services.AddSwaggerGen(s => |
|||
{ |
|||
//遍历出全部的版本,做文档信息展示
|
|||
typeof(CustomApiVersion.ApiVersion).GetEnumNames().ToList().ForEach(version => |
|||
{ |
|||
s.SwaggerDoc(version, new OpenApiInfo |
|||
{ |
|||
Version = version, |
|||
Title = $"{apiName} 接口文档", |
|||
Description = $"{apiName} HTTP API " + version, |
|||
Contact = new OpenApiContact { Name = apiName, Email = "faker@znyc.com", Url = new Uri("https://Znyc.com") }, |
|||
}); |
|||
s.OrderActionsBy(o => o.RelativePath); |
|||
}); |
|||
try |
|||
{ |
|||
//生成API XML文档
|
|||
var basePath = AppContext.BaseDirectory; |
|||
var xmlPath = Path.Combine(basePath, "doc.xml"); |
|||
s.IncludeXmlComments(xmlPath); |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
} |
|||
// 开启加权小锁
|
|||
s.OperationFilter<AddResponseHeadersFilter>(); |
|||
s.OperationFilter<AppendAuthorizeToSummaryOperationFilter>(); |
|||
|
|||
// 在header中添加token,传递到后台
|
|||
s.OperationFilter<SecurityRequirementsOperationFilter>(); |
|||
|
|||
// 必须是 oauth2
|
|||
s.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme |
|||
{ |
|||
Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", |
|||
Name = "Authorization",//jwt默认的参数名称
|
|||
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
|
|||
Type = SecuritySchemeType.ApiKey |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,61 @@ |
|||
using FreeSql.Aop; |
|||
using System.Reflection; |
|||
using Yitter.IdGenerator; |
|||
using Znyc.CloudCar.Auth.HttpContextUser; |
|||
using Znyc.CloudCar.Model; |
|||
using Znyc.CloudCar.Utility.Extensions; |
|||
|
|||
namespace Znyc.CloudCar.Core.Db |
|||
{ |
|||
public class DbHelper |
|||
{ |
|||
/// <summary>
|
|||
/// 审计数据
|
|||
/// </summary>
|
|||
/// <param name="e"></param>
|
|||
/// <param name="user"></param>
|
|||
public static void AuditValue(AuditValueEventArgs e, IHttpContextUser httpContextUser) |
|||
{ |
|||
if (e.Column.CsType == typeof(long) |
|||
&& e.Property.GetCustomAttribute<SnowflakeAttribute>(false) != null |
|||
&& (e.Value.IsNull() || (long)e.Value == default || (long?)e.Value == default)) |
|||
{ |
|||
e.Value = YitIdHelper.NextId(); |
|||
} |
|||
|
|||
if (httpContextUser.IsNull() || httpContextUser.Id <= 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (e.AuditValueType == AuditValueType.Insert) |
|||
{ |
|||
switch (e.Property.Name) |
|||
{ |
|||
case "CreatedUserId": |
|||
if (e.Value.IsNull() || (long)e.Value == default || (long?)e.Value == default) |
|||
{ |
|||
e.Value = httpContextUser.Id; |
|||
} |
|||
|
|||
break; |
|||
case "CreatedTime": |
|||
e.Value = DateTime.Now; |
|||
break; |
|||
} |
|||
} |
|||
else if (e.AuditValueType == AuditValueType.Update) |
|||
{ |
|||
switch (e.Property.Name) |
|||
{ |
|||
case "ModifiedUserId": |
|||
e.Value = httpContextUser.Id; |
|||
break; |
|||
case "ModifiedTime": |
|||
e.Value = DateTime.Now; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.2.0" /> |
|||
<PackageReference Include="Autofac.Extras.DynamicProxy" Version="6.0.0" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> |
|||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" /> |
|||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.12" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Auth\Znyc.CloudCar.Auth.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Caching\Znyc.CloudCar.Caching.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Configuration\Znyc.CloudCar.Configuration.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.FreeSql\Znyc.CloudCar.FreeSql.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Hangfire\Znyc.CloudCar.Hangfire.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Mapster\Znyc.CloudCar.Mapster.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.RedisMQ\Znyc.CloudCar.RedisMQ.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Swagger\Znyc.CloudCar.Swagger.csproj" /> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Utility\Znyc.CloudCar.Utility.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,50 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Microsoft.Extensions.Logging; |
|||
using Znyc.CloudCar.Model.ViewModels.ReportsCallBack; |
|||
|
|||
namespace Znyc.CloudCar.Filter |
|||
{ |
|||
/// <summary>
|
|||
/// 异常错误过滤
|
|||
/// </summary>
|
|||
public class ExceptionFilter : IExceptionFilter, IAsyncExceptionFilter |
|||
{ |
|||
private readonly ILogger _logger; |
|||
|
|||
public ExceptionFilter(ILogger<ExceptionFilter> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
public void OnException(ExceptionContext context) |
|||
{ |
|||
|
|||
Console.WriteLine(context.Exception); |
|||
Console.WriteLine(context.Exception.InnerException); |
|||
_logger.LogError(context.Exception, context.Exception.Message); |
|||
ResponseOutput response = new() |
|||
{ |
|||
Successed = false, |
|||
Msg = "系统内部错误" |
|||
}; |
|||
context.Result = new InternalServerErrorResult(response); |
|||
} |
|||
|
|||
public Task OnExceptionAsync(ExceptionContext context) |
|||
{ |
|||
OnException(context); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
public class InternalServerErrorResult : ObjectResult |
|||
{ |
|||
public InternalServerErrorResult(object value) : base(value) |
|||
{ |
|||
StatusCode = Microsoft.AspNetCore.Http.StatusCodes.Status500InternalServerError; |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,48 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
using Newtonsoft.Json; |
|||
using Znyc.CloudCar.Model.ViewModels.Basics; |
|||
using Znyc.CloudCar.Model.ViewModels.ReportsCallBack; |
|||
|
|||
namespace Znyc.CloudCar.Filter |
|||
{ |
|||
/// <summary>
|
|||
/// 请求验证错误处理
|
|||
/// </summary>
|
|||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)] |
|||
public class RequiredError : ResultFilterAttribute |
|||
{ |
|||
public override void OnResultExecuting(ResultExecutingContext context) |
|||
{ |
|||
var modelState = context.ModelState; |
|||
List<ErrorView> errors = new List<ErrorView>(); |
|||
if (!modelState.IsValid) |
|||
{ |
|||
var baseResult = new ResponseOutput() |
|||
{ |
|||
Successed = false, |
|||
Msg = "请提交必要的参数", |
|||
}; |
|||
foreach (var key in modelState.Keys) |
|||
{ |
|||
var state = modelState[key]; |
|||
if (state.Errors.Any()) |
|||
{ |
|||
ErrorView errorView = new ErrorView(); |
|||
errorView.ErrorName = key; |
|||
errorView.Error = state.Errors.First().ErrorMessage; |
|||
errors.Add(errorView); |
|||
} |
|||
} |
|||
ResponseOutput response = new ResponseOutput(); |
|||
response.Data = errors; |
|||
response.Successed = false; |
|||
context.Result = new ContentResult() |
|||
{ |
|||
Content = JsonConvert.SerializeObject(response), |
|||
ContentType = "application/json" |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
namespace Znyc.CloudCar.Filter |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Field)] |
|||
public class SnowflakeAttribute : Attribute |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Znyc.CloudCar.Configuration\Znyc.CloudCar.Configuration.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="FreeSql" Version="3.0.100" /> |
|||
<PackageReference Include="FreeSql.Provider.MySql" Version="3.0.100" /> |
|||
<PackageReference Include="FreeSql.Repository" Version="3.0.100" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Hangfire" Version="1.7.28" /> |
|||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.28" /> |
|||
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" /> |
|||
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.8.5" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Audit |
|||
{ |
|||
public interface IAuditRepository : IRepositoryBase<AuditEntity> |
|||
{ |
|||
Task<List<AuditEntity>> GetAuditFailListAsync(long userId); |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Banner |
|||
{ |
|||
public interface IBannerRepository : IRepositoryBase<BannerEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.CardIntro |
|||
{ |
|||
public interface ICardIntroRepository : IRepositoryBase<CardIntroEntity> |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Certification |
|||
{ |
|||
public interface ICertificationRepository : IRepositoryBase<CertificationEntity> |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Collection |
|||
{ |
|||
/// <summary>
|
|||
/// 收藏仓储接口
|
|||
/// </summary>
|
|||
public interface ICollectionRepository : IRepositoryBase<CollectionEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Currency |
|||
{ |
|||
public interface ICurrencyRecordRepository : IRepositoryBase<CurrencyRecordEntity> |
|||
{ |
|||
///// <summary>
|
|||
///// 获取邀请排行榜
|
|||
///// </summary>
|
|||
///// <returns></returns>
|
|||
//Task<List<InviteTopUser>> GetInviteTopAsync();
|
|||
|
|||
///// <summary>
|
|||
///// 获取用户获取电话数据
|
|||
///// </summary>
|
|||
///// <returns></returns>
|
|||
//Task<List<InviteTopUser>> GetCallPhoneAsync();
|
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Currency |
|||
{ |
|||
public interface ICurrencyRepository : IRepositoryBase<CurrencyEntity> |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Dictionary |
|||
{ |
|||
public interface IDictionaryRepository : IRepositoryBase<DictionaryEntity> |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Equipment |
|||
{ |
|||
public interface IEquipmentRepository : IRepositoryBase<EquipmentEntity> |
|||
{ |
|||
/// <summary>
|
|||
/// 同步浏览量
|
|||
/// </summary>
|
|||
/// <param name="id"></param>
|
|||
/// <param name="pageview"></param>
|
|||
/// <returns></returns>
|
|||
Task<int> UpdatePageView(long id, int pageview); |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.EquipmentPicture |
|||
{ |
|||
public interface IEquipmentPictureRepository : IRepositoryBase<EquipmentPictureEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Feedback |
|||
{ |
|||
public interface IFeedbackPicRepository : IRepositoryBase<FeedbackPicEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Feedback |
|||
{ |
|||
public interface IFeedbackRepository : IRepositoryBase<FeedbackEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
using FreeSql; |
|||
using System.Linq.Expressions; |
|||
|
|||
namespace Znyc.CloudCar.IRepository |
|||
{ |
|||
public interface IRepositoryBase<TEntity, TKey> : IBaseRepository<TEntity, TKey> where TEntity : class |
|||
{ |
|||
/// <summary>
|
|||
/// 获得Dto
|
|||
/// </summary>
|
|||
/// <typeparam name="TDto"></typeparam>
|
|||
/// <param name="id"></param>
|
|||
/// <returns></returns>
|
|||
Task<TDto> GetAsync<TDto>(TKey id); |
|||
|
|||
/// <summary>
|
|||
/// 根据条件获取实体
|
|||
/// </summary>
|
|||
/// <param name="exp"></param>
|
|||
/// <returns></returns>
|
|||
Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> exp); |
|||
|
|||
/// <summary>
|
|||
/// 根据条件获取Dto
|
|||
/// </summary>
|
|||
/// <typeparam name="TDto"></typeparam>
|
|||
/// <param name="exp"></param>
|
|||
/// <returns></returns>
|
|||
Task<TDto> GetAsync<TDto>(Expression<Func<TEntity, bool>> exp); |
|||
|
|||
/// <summary>
|
|||
/// 软删除
|
|||
/// </summary>
|
|||
/// <param name="id"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> SoftDeleteAsync(TKey id); |
|||
|
|||
/// <summary>
|
|||
/// 批量软删除
|
|||
/// </summary>
|
|||
/// <param name="ids"></param>
|
|||
/// <returns></returns>
|
|||
Task<bool> SoftDeleteAsync(TKey[] ids); |
|||
} |
|||
public interface IRepositoryBase<TEntity> : IRepositoryBase<TEntity, long> where TEntity : class |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.LoginLogs |
|||
{ |
|||
public interface ILoginLogsRepository : IRepositoryBase<LoginLogsEntity> |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Message |
|||
{ |
|||
public interface IMessageLogRepository : IRepositoryBase<MessageLogsEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Message |
|||
{ |
|||
public interface IMessageLogsRepository : IRepositoryBase<MessageLogsEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Znyc.CloudCar.Model.Entities; |
|||
|
|||
namespace Znyc.CloudCar.IRepository.Message |
|||
{ |
|||
public interface IMessageRepository : IRepositoryBase<MessageEntity> |
|||
{ |
|||
|
|||
} |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue