Browse Source

添加项目文件。

master
faker 2 years ago
parent
commit
aa29e93778
  1. 25
      .dockerignore
  2. 391
      .gitignore
  3. 48
      Dockerfile
  4. 3
      README.md
  5. 30
      Zncy.CloudCar.Tests/UnitTest1.cs
  6. 13
      Zncy.CloudCar.Tests/Zncy.CloudCar.Tests.csproj
  7. 55
      Zncy.CloudCar.WeChat.Service/Configuration/EventType.cs
  8. 62
      Zncy.CloudCar.WeChat.Service/Configuration/RequestMsgType.cs
  9. 271
      Zncy.CloudCar.WeChat.Service/Enums/WeChatReturnCode.cs
  10. 47
      Zncy.CloudCar.WeChat.Service/Mediator/ImageMessageEventCommand.cs
  11. 47
      Zncy.CloudCar.WeChat.Service/Mediator/TextMessageEventCommand.cs
  12. 48
      Zncy.CloudCar.WeChat.Service/Mediator/VoiceMessageEventCommand.cs
  13. 15
      Zncy.CloudCar.WeChat.Service/Models/CreateOrderByJsapiRequest.cs
  14. 21
      Zncy.CloudCar.WeChat.Service/Models/DecodedPhoneNumber.cs
  15. 17
      Zncy.CloudCar.WeChat.Service/Models/DecodedRunData.cs
  16. 48
      Zncy.CloudCar.WeChat.Service/Models/EncryptPostModel.cs
  17. 27
      Zncy.CloudCar.WeChat.Service/Models/IEncryptPostModel.cs
  18. 30
      Zncy.CloudCar.WeChat.Service/Models/PostModel.cs
  19. 30
      Zncy.CloudCar.WeChat.Service/Models/SendWxTemplateMessage.cs
  20. 19
      Zncy.CloudCar.WeChat.Service/Models/Watermark.cs
  21. 30
      Zncy.CloudCar.WeChat.Service/Models/WeChatApiCallBack.cs
  22. 85
      Zncy.CloudCar.WeChat.Service/Models/WeChatUserInfo.cs
  23. 30
      Zncy.CloudCar.WeChat.Service/Options/TenpayOptions.cs
  24. 50
      Zncy.CloudCar.WeChat.Service/Options/WeChatOptions.cs
  25. 20
      Zncy.CloudCar.WeChat.Service/Services/HttpClients/IWeChatApiHttpClientFactory.cs
  26. 9
      Zncy.CloudCar.WeChat.Service/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs
  27. 9
      Zncy.CloudCar.WeChat.Service/Services/HttpClients/IWechatTenpayHttpClientFactory.cs
  28. 95
      Zncy.CloudCar.WeChat.Service/Services/HttpClients/WeChatApiHttpClientFactory.cs
  29. 24
      Zncy.CloudCar.WeChat.Service/Services/HttpClients/WechatTenpayCertificateManagerFactory.cs
  30. 66
      Zncy.CloudCar.WeChat.Service/Services/HttpClients/WechatTenpayHttpClientFactory.cs
  31. 61
      Zncy.CloudCar.WeChat.Service/Utilities/CheckSignature.cs
  32. 235
      Zncy.CloudCar.WeChat.Service/Utilities/Cryptography.cs
  33. 47
      Zncy.CloudCar.WeChat.Service/Utilities/DateTimeHelper.cs
  34. 27
      Zncy.CloudCar.WeChat.Service/Utilities/DocumentExtensions.cs
  35. 320
      Zncy.CloudCar.WeChat.Service/Utilities/EncryptHelper.cs
  36. 56
      Zncy.CloudCar.WeChat.Service/Utilities/RequestUtility.cs
  37. 235
      Zncy.CloudCar.WeChat.Service/Utilities/WXBizMsgCrypt.cs
  38. 26
      Zncy.CloudCar.WeChat.Service/Utilities/WxOfficialHelper.cs
  39. 101
      Zncy.CloudCar.WeChat.Service/Utilities/XmlUtility.cs
  40. 23
      Zncy.CloudCar.WeChat.Service/Zncy.CloudCar.WeChat.Service.csproj
  41. 31
      Znyc.CloudCar.Auth/AccressToken/WeChatCacheAccessTokenHelper.cs
  42. 125
      Znyc.CloudCar.Auth/HttpContextUser/AspNetUser.cs
  43. 41
      Znyc.CloudCar.Auth/HttpContextUser/IHttpContextUser.cs
  44. 85
      Znyc.CloudCar.Auth/Manual/IManualCacheManager.cs
  45. 26
      Znyc.CloudCar.Auth/Manual/ManualDataCache.cs
  46. 232
      Znyc.CloudCar.Auth/Manual/RedisCacheManager.cs
  47. 111
      Znyc.CloudCar.Auth/OverWrite/JwtHelper.cs
  48. 81
      Znyc.CloudCar.Auth/OverWrite/JwtTokenAuth.cs
  49. 41
      Znyc.CloudCar.Auth/Policys/ApiResponse.cs
  50. 46
      Znyc.CloudCar.Auth/Policys/ApiResponseHandler.cs
  51. 42
      Znyc.CloudCar.Auth/Policys/Claims.cs
  52. 42
      Znyc.CloudCar.Auth/Policys/JwtToken.cs
  53. 156
      Znyc.CloudCar.Auth/Policys/PermissionHandler.cs
  54. 25
      Znyc.CloudCar.Auth/Policys/PermissionItem.cs
  55. 70
      Znyc.CloudCar.Auth/Policys/PermissionRequirement.cs
  56. 20
      Znyc.CloudCar.Auth/Znyc.CloudCar.Auth.csproj
  57. 212
      Znyc.CloudCar.Caching/IRedisOperationRepository.cs
  58. 367
      Znyc.CloudCar.Caching/RedisOperationRepository.cs
  59. 18
      Znyc.CloudCar.Caching/Znyc.CloudCar.Caching.csproj
  60. 121
      Znyc.CloudCar.Configuration/AppSettingsConstVars.cs
  61. 45
      Znyc.CloudCar.Configuration/AppSettingsHelper.cs
  62. 91
      Znyc.CloudCar.Configuration/GlobalCacheKeyVars.cs
  63. 358
      Znyc.CloudCar.Configuration/GlobalConstVars.cs
  64. 399
      Znyc.CloudCar.Configuration/GlobalEnumVars.cs
  65. 18
      Znyc.CloudCar.Configuration/Znyc.CloudCar.Configuration.csproj
  66. 36
      Znyc.CloudCar.Core/AutoFac/AutofacModuleRegister.cs
  67. 118
      Znyc.CloudCar.Core/Config/AuthorizationSetup.cs
  68. 41
      Znyc.CloudCar.Core/Config/CoreSetup.cs
  69. 45
      Znyc.CloudCar.Core/Config/FreeSqlSetup.cs
  70. 31
      Znyc.CloudCar.Core/Config/HangFireSetup.cs
  71. 20
      Znyc.CloudCar.Core/Config/HttpContextSetup.cs
  72. 27
      Znyc.CloudCar.Core/Config/MapsterSetup.cs
  73. 29
      Znyc.CloudCar.Core/Config/RedisCacheSetup.cs
  74. 33
      Znyc.CloudCar.Core/Config/RedisMessageQueueSetup.cs
  75. 65
      Znyc.CloudCar.Core/Config/SwaggerSetup.cs
  76. 61
      Znyc.CloudCar.Core/Db/DbHelper.cs
  77. 29
      Znyc.CloudCar.Core/Znyc.CloudCar.Core.csproj
  78. 50
      Znyc.CloudCar.Filter/ExceptionFilter.cs
  79. 48
      Znyc.CloudCar.Filter/RequiredError.cs
  80. 7
      Znyc.CloudCar.Filter/SnowflakeAttribute.cs
  81. 17
      Znyc.CloudCar.Filter/Znyc.CloudCar.Filter.csproj
  82. 15
      Znyc.CloudCar.FreeSql/Znyc.CloudCar.FreeSql.csproj
  83. 16
      Znyc.CloudCar.Hangfire/Znyc.CloudCar.Hangfire.csproj
  84. 9
      Znyc.CloudCar.IRepository/Audit/IAuditRepository.cs
  85. 9
      Znyc.CloudCar.IRepository/Banner/IBannerRepository.cs
  86. 8
      Znyc.CloudCar.IRepository/CardIntro/ICardIntroRepository.cs
  87. 8
      Znyc.CloudCar.IRepository/Certification/ICertificationRepository.cs
  88. 12
      Znyc.CloudCar.IRepository/Collection/ICollectionRepository.cs
  89. 19
      Znyc.CloudCar.IRepository/Currency/ICurrencyRecordRepository.cs
  90. 8
      Znyc.CloudCar.IRepository/Currency/ICurrencyRepository.cs
  91. 8
      Znyc.CloudCar.IRepository/Dictionary/IDictionaryRepository.cs
  92. 15
      Znyc.CloudCar.IRepository/Equipment/IEquipmentRepository.cs
  93. 9
      Znyc.CloudCar.IRepository/EquipmentPicture/IEquipmentPictureRepository.cs
  94. 9
      Znyc.CloudCar.IRepository/Feedback/IFeedbackPicRepository .cs
  95. 9
      Znyc.CloudCar.IRepository/Feedback/IFeedbackRepository.cs
  96. 48
      Znyc.CloudCar.IRepository/IRepositoryBase.cs
  97. 8
      Znyc.CloudCar.IRepository/LoginLogs/ILoginLogsRepository.cs
  98. 9
      Znyc.CloudCar.IRepository/Message/IMessageLogRepository .cs
  99. 9
      Znyc.CloudCar.IRepository/Message/IMessageLogsRepository.cs
  100. 9
      Znyc.CloudCar.IRepository/Message/IMessageRepository.cs

25
.dockerignore

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

391
.gitignore

@ -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

48
Dockerfile

@ -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"]

3
README.md

@ -0,0 +1,3 @@
# znyc.CloudCar
云车二手

30
Zncy.CloudCar.Tests/UnitTest1.cs

@ -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;
}
}
}

13
Zncy.CloudCar.Tests/Zncy.CloudCar.Tests.csproj

@ -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>

55
Zncy.CloudCar.WeChat.Service/Configuration/EventType.cs

@ -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";
}
}

62
Zncy.CloudCar.WeChat.Service/Configuration/RequestMsgType.cs

@ -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";
}
}

271
Zncy.CloudCar.WeChat.Service/Enums/WeChatReturnCode.cs

@ -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
15 = 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
}
}
}

47
Zncy.CloudCar.WeChat.Service/Mediator/ImageMessageEventCommand.cs

@ -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);
}
}
}

47
Zncy.CloudCar.WeChat.Service/Mediator/TextMessageEventCommand.cs

@ -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);
}
}
}

48
Zncy.CloudCar.WeChat.Service/Mediator/VoiceMessageEventCommand.cs

@ -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);
}
}
}

15
Zncy.CloudCar.WeChat.Service/Models/CreateOrderByJsapiRequest.cs

@ -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; }
}
}

21
Zncy.CloudCar.WeChat.Service/Models/DecodedPhoneNumber.cs

@ -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; }
}
}

17
Zncy.CloudCar.WeChat.Service/Models/DecodedRunData.cs

@ -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; }
}
}

48
Zncy.CloudCar.WeChat.Service/Models/EncryptPostModel.cs

@ -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;
}
}
}

27
Zncy.CloudCar.WeChat.Service/Models/IEncryptPostModel.cs

@ -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; }
}
}

30
Zncy.CloudCar.WeChat.Service/Models/PostModel.cs

@ -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;
}
}
}

30
Zncy.CloudCar.WeChat.Service/Models/SendWxTemplateMessage.cs

@ -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。
}
}

19
Zncy.CloudCar.WeChat.Service/Models/Watermark.cs

@ -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); }
}
}
}

30
Zncy.CloudCar.WeChat.Service/Models/WeChatApiCallBack.cs

@ -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";
}
}

85
Zncy.CloudCar.WeChat.Service/Models/WeChatUserInfo.cs

@ -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。
}
}

30
Zncy.CloudCar.WeChat.Service/Options/TenpayOptions.cs

@ -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;
}
}
}
}

50
Zncy.CloudCar.WeChat.Service/Options/WeChatOptions.cs

@ -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;
}
}

20
Zncy.CloudCar.WeChat.Service/Services/HttpClients/IWeChatApiHttpClientFactory.cs

@ -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();
}
}

9
Zncy.CloudCar.WeChat.Service/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs

@ -0,0 +1,9 @@
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients
{
public interface IWechatTenpayCertificateManagerFactory
{
CertificateManager Create(string merchantId);
}
}

9
Zncy.CloudCar.WeChat.Service/Services/HttpClients/IWechatTenpayHttpClientFactory.cs

@ -0,0 +1,9 @@
using SKIT.FlurlHttpClient.Wechat.TenpayV3;
namespace Zncy.CloudCar.WeChat.Service.Services.HttpClients
{
public interface IWechatTenpayHttpClientFactory
{
WechatTenpayClient Create(string merchantId);
}
}

95
Zncy.CloudCar.WeChat.Service/Services/HttpClients/WeChatApiHttpClientFactory.cs

@ -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));
}
}
}
}

24
Zncy.CloudCar.WeChat.Service/Services/HttpClients/WechatTenpayCertificateManagerFactory.cs

@ -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());
}
}
}

66
Zncy.CloudCar.WeChat.Service/Services/HttpClients/WechatTenpayHttpClientFactory.cs

@ -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();
}
}
}
}

61
Zncy.CloudCar.WeChat.Service/Utilities/CheckSignature.cs

@ -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();
}
}
}

235
Zncy.CloudCar.WeChat.Service/Utilities/Cryptography.cs

@ -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;
}
}
}

47
Zncy.CloudCar.WeChat.Service/Utilities/DateTimeHelper.cs

@ -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;
}
}

27
Zncy.CloudCar.WeChat.Service/Utilities/DocumentExtensions.cs

@ -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);
}
}
}
}

320
Zncy.CloudCar.WeChat.Service/Utilities/EncryptHelper.cs

@ -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
}
}

56
Zncy.CloudCar.WeChat.Service/Utilities/RequestUtility.cs

@ -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()));
}
}
}

235
Zncy.CloudCar.WeChat.Service/Utilities/WXBizMsgCrypt.cs

@ -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;
}
}
}

26
Zncy.CloudCar.WeChat.Service/Utilities/WxOfficialHelper.cs

@ -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";
}
}
}

101
Zncy.CloudCar.WeChat.Service/Utilities/XmlUtility.cs

@ -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;
}
}
}

23
Zncy.CloudCar.WeChat.Service/Zncy.CloudCar.WeChat.Service.csproj

@ -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>

31
Znyc.CloudCar.Auth/AccressToken/WeChatCacheAccessTokenHelper.cs

@ -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 引用。
}
}
}

125
Znyc.CloudCar.Auth/HttpContextUser/AspNetUser.cs

@ -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 "";
}
}
}
}

41
Znyc.CloudCar.Auth/HttpContextUser/IHttpContextUser.cs

@ -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; }
}
}

85
Znyc.CloudCar.Auth/Manual/IManualCacheManager.cs

@ -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);
}
}

26
Znyc.CloudCar.Auth/Manual/ManualDataCache.cs

@ -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();
}
}
}
}

232
Znyc.CloudCar.Auth/Manual/RedisCacheManager.cs

@ -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;
}
}
}

111
Znyc.CloudCar.Auth/OverWrite/JwtHelper.cs

@ -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; }
}
}

81
Znyc.CloudCar.Auth/OverWrite/JwtTokenAuth.cs

@ -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);
}
}
}

41
Znyc.CloudCar.Auth/Policys/ApiResponse.cs

@ -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
}
}

46
Znyc.CloudCar.Auth/Policys/ApiResponseHandler.cs

@ -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());
}
}
}

42
Znyc.CloudCar.Auth/Policys/Claims.cs

@ -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";
}
}

42
Znyc.CloudCar.Auth/Policys/JwtToken.cs

@ -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();
}
}
}

156
Znyc.CloudCar.Auth/Policys/PermissionHandler.cs

@ -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);
}
}
}

25
Znyc.CloudCar.Auth/Policys/PermissionItem.cs

@ -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; }
}
}

70
Znyc.CloudCar.Auth/Policys/PermissionRequirement.cs

@ -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;
}
}
}

20
Znyc.CloudCar.Auth/Znyc.CloudCar.Auth.csproj

@ -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>

212
Znyc.CloudCar.Caching/IRedisOperationRepository.cs

@ -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
}
}

367
Znyc.CloudCar.Caching/RedisOperationRepository.cs

@ -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
}
}

18
Znyc.CloudCar.Caching/Znyc.CloudCar.Caching.csproj

@ -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>

121
Znyc.CloudCar.Configuration/AppSettingsConstVars.cs

@ -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
}
}

45
Znyc.CloudCar.Configuration/AppSettingsHelper.cs

@ -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 "";
}
}
}

91
Znyc.CloudCar.Configuration/GlobalCacheKeyVars.cs

@ -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}";
}
}

358
Znyc.CloudCar.Configuration/GlobalConstVars.cs

@ -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/";
}
}

399
Znyc.CloudCar.Configuration/GlobalEnumVars.cs

@ -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
}
}

18
Znyc.CloudCar.Configuration/Znyc.CloudCar.Configuration.csproj

@ -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>

36
Znyc.CloudCar.Core/AutoFac/AutofacModuleRegister.cs

@ -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
}
}
}

118
Znyc.CloudCar.Core/Config/AuthorizationSetup.cs

@ -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);
}
}
}

41
Znyc.CloudCar.Core/Config/CoreSetup.cs

@ -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();
});
}
});
}
}
}

45
Znyc.CloudCar.Core/Config/FreeSqlSetup.cs

@ -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); };
}
}
}

31
Znyc.CloudCar.Core/Config/HangFireSetup.cs

@ -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
});
}
}
}

20
Znyc.CloudCar.Core/Config/HttpContextSetup.cs

@ -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>();
}
}
}

27
Znyc.CloudCar.Core/Config/MapsterSetup.cs

@ -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);
}
}
}

29
Znyc.CloudCar.Core/Config/RedisCacheSetup.cs

@ -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>();
}
}
}

33
Znyc.CloudCar.Core/Config/RedisMessageQueueSetup.cs

@ -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;
//});
}
}
}

65
Znyc.CloudCar.Core/Config/SwaggerSetup.cs

@ -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
});
});
}
}
}

61
Znyc.CloudCar.Core/Db/DbHelper.cs

@ -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;
}
}
}
}
}

29
Znyc.CloudCar.Core/Znyc.CloudCar.Core.csproj

@ -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>

50
Znyc.CloudCar.Filter/ExceptionFilter.cs

@ -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;
}
}
}

48
Znyc.CloudCar.Filter/RequiredError.cs

@ -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"
};
}
}
}
}

7
Znyc.CloudCar.Filter/SnowflakeAttribute.cs

@ -0,0 +1,7 @@
namespace Znyc.CloudCar.Filter
{
[AttributeUsage(AttributeTargets.Field)]
public class SnowflakeAttribute : Attribute
{
}
}

17
Znyc.CloudCar.Filter/Znyc.CloudCar.Filter.csproj

@ -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>

15
Znyc.CloudCar.FreeSql/Znyc.CloudCar.FreeSql.csproj

@ -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>

16
Znyc.CloudCar.Hangfire/Znyc.CloudCar.Hangfire.csproj

@ -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>

9
Znyc.CloudCar.IRepository/Audit/IAuditRepository.cs

@ -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);
}
}

9
Znyc.CloudCar.IRepository/Banner/IBannerRepository.cs

@ -0,0 +1,9 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Banner
{
public interface IBannerRepository : IRepositoryBase<BannerEntity>
{
}
}

8
Znyc.CloudCar.IRepository/CardIntro/ICardIntroRepository.cs

@ -0,0 +1,8 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.CardIntro
{
public interface ICardIntroRepository : IRepositoryBase<CardIntroEntity>
{
}
}

8
Znyc.CloudCar.IRepository/Certification/ICertificationRepository.cs

@ -0,0 +1,8 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Certification
{
public interface ICertificationRepository : IRepositoryBase<CertificationEntity>
{
}
}

12
Znyc.CloudCar.IRepository/Collection/ICollectionRepository.cs

@ -0,0 +1,12 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Collection
{
/// <summary>
/// 收藏仓储接口
/// </summary>
public interface ICollectionRepository : IRepositoryBase<CollectionEntity>
{
}
}

19
Znyc.CloudCar.IRepository/Currency/ICurrencyRecordRepository.cs

@ -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();
}
}

8
Znyc.CloudCar.IRepository/Currency/ICurrencyRepository.cs

@ -0,0 +1,8 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Currency
{
public interface ICurrencyRepository : IRepositoryBase<CurrencyEntity>
{
}
}

8
Znyc.CloudCar.IRepository/Dictionary/IDictionaryRepository.cs

@ -0,0 +1,8 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Dictionary
{
public interface IDictionaryRepository : IRepositoryBase<DictionaryEntity>
{
}
}

15
Znyc.CloudCar.IRepository/Equipment/IEquipmentRepository.cs

@ -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);
}
}

9
Znyc.CloudCar.IRepository/EquipmentPicture/IEquipmentPictureRepository.cs

@ -0,0 +1,9 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.EquipmentPicture
{
public interface IEquipmentPictureRepository : IRepositoryBase<EquipmentPictureEntity>
{
}
}

9
Znyc.CloudCar.IRepository/Feedback/IFeedbackPicRepository .cs

@ -0,0 +1,9 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Feedback
{
public interface IFeedbackPicRepository : IRepositoryBase<FeedbackPicEntity>
{
}
}

9
Znyc.CloudCar.IRepository/Feedback/IFeedbackRepository.cs

@ -0,0 +1,9 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Feedback
{
public interface IFeedbackRepository : IRepositoryBase<FeedbackEntity>
{
}
}

48
Znyc.CloudCar.IRepository/IRepositoryBase.cs

@ -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
{
}
}

8
Znyc.CloudCar.IRepository/LoginLogs/ILoginLogsRepository.cs

@ -0,0 +1,8 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.LoginLogs
{
public interface ILoginLogsRepository : IRepositoryBase<LoginLogsEntity>
{
}
}

9
Znyc.CloudCar.IRepository/Message/IMessageLogRepository .cs

@ -0,0 +1,9 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Message
{
public interface IMessageLogRepository : IRepositoryBase<MessageLogsEntity>
{
}
}

9
Znyc.CloudCar.IRepository/Message/IMessageLogsRepository.cs

@ -0,0 +1,9 @@
using Znyc.CloudCar.Model.Entities;
namespace Znyc.CloudCar.IRepository.Message
{
public interface IMessageLogsRepository : IRepositoryBase<MessageLogsEntity>
{
}
}

9
Znyc.CloudCar.IRepository/Message/IMessageRepository.cs

@ -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…
Cancel
Save