├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── SimCaptcha-nuget-push.yml │ ├── SimCaptcha-release.yml │ ├── SimCaptcha.AspNetCore-nuget-push.yml │ ├── SimCaptcha.AspNetCore-release.yml │ ├── deploy-docker.yml │ ├── deploy-docs.yml │ └── sync-gitee.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SimCaptcha.sln ├── docker-build-simcaptcha-client.ps1 ├── docker-build-simcaptcha.ps1 ├── docker-compose.Debug.ps1 ├── docker-compose.Debug.yml ├── docker-compose.yml ├── docker-run-simcaptcha-client.ps1 ├── docker-run-simcaptcha.ps1 ├── docs ├── .gitignore ├── README.md ├── deploy-docs.sh ├── docs │ ├── .vuepress │ │ ├── config.js │ │ └── public │ │ │ └── images │ │ │ ├── NETStandard.png │ │ │ ├── logo.gif │ │ │ ├── logo.png │ │ │ └── time.png │ ├── Guide │ │ ├── README.md │ │ └── dependence.md │ ├── README.md │ ├── SimCaptcha.AspNetCore │ │ └── README.md │ ├── SimCaptcha │ │ └── README.md │ └── WebSDK │ │ ├── sim-captcha-js │ │ ├── README.md │ │ └── api.md │ │ └── vue-sim-captcha │ │ ├── README.md │ │ └── api.md └── package.json ├── examples ├── AspNetCoreClient │ ├── .config │ │ └── dotnet-tools.json │ ├── AspNetCoreClient.csproj │ ├── Controllers │ │ └── HomeController.cs │ ├── Dockerfile │ ├── Dockerfile.Debug │ ├── Program.cs │ ├── Properties │ │ ├── PublishProfiles │ │ │ └── FolderProfile.pubxml │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.Docker.json │ ├── appsettings.DockerDebug.json │ ├── appsettings.json │ └── wwwroot │ │ └── index.html ├── AspNetCoreService │ ├── .config │ │ └── dotnet-tools.json │ ├── AspNetCoreService.csproj │ ├── Controllers │ │ └── VCodeController.cs │ ├── Program.cs │ ├── Properties │ │ ├── PublishProfiles │ │ │ └── FolderProfile.pubxml │ │ └── launchSettings.json │ ├── SimCaptcha │ │ ├── bgImages │ │ │ ├── 10.jpg │ │ │ ├── 11.jpg │ │ │ ├── 12.jpg │ │ │ ├── 14.jpg │ │ │ ├── 15.jpg │ │ │ ├── 16.jpg │ │ │ ├── 17.jpg │ │ │ ├── 18.jpg │ │ │ ├── 19.jpg │ │ │ ├── 2.jpg │ │ │ ├── 20.jpg │ │ │ ├── 5.jpg │ │ │ └── 8.jpg │ │ └── fonts │ │ │ ├── 站酷小薇LOGO体.otf │ │ │ ├── 站酷庆科黄油体.ttf │ │ │ ├── 站酷快乐体2016修订版.ttf │ │ │ ├── 站酷文艺体.ttf │ │ │ ├── 站酷酷黑体.ttf │ │ │ └── 站酷高端黑修订151105.ttf │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json └── EasyAspNetCoreService │ ├── .config │ └── dotnet-tools.json │ ├── Dockerfile │ ├── Dockerfile.Debug │ ├── EasyAspNetCoreService.csproj │ ├── Program.cs │ ├── Properties │ ├── PublishProfiles │ │ └── FolderProfile.pubxml │ └── launchSettings.json │ ├── SimCaptcha │ ├── bgImages │ │ ├── 10.jpg │ │ ├── 11.jpg │ │ ├── 12.jpg │ │ ├── 14.jpg │ │ ├── 15.jpg │ │ ├── 16.jpg │ │ ├── 17.jpg │ │ ├── 18.jpg │ │ ├── 19.jpg │ │ ├── 2.jpg │ │ ├── 20.jpg │ │ ├── 5.jpg │ │ └── 8.jpg │ └── fonts │ │ ├── 站酷小薇LOGO体.otf │ │ ├── 站酷庆科黄油体.ttf │ │ ├── 站酷快乐体2016修订版.ttf │ │ ├── 站酷文艺体.ttf │ │ ├── 站酷酷黑体.ttf │ │ └── 站酷高端黑修订151105.ttf │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.Docker.json │ ├── appsettings.DockerDebug.json │ └── appsettings.json └── src ├── SimCaptcha.AspNetCore ├── AspNetCoreJsonHelper.cs ├── AspNetCoreVCodeImage.cs ├── ConsoleLogHelper.cs ├── LocalCache.cs ├── SimCaptcha.AspNetCore.csproj ├── SimCaptcha │ ├── bgImages │ │ ├── 10.jpg │ │ ├── 11.jpg │ │ ├── 12.jpg │ │ ├── 14.jpg │ │ ├── 15.jpg │ │ ├── 16.jpg │ │ ├── 17.jpg │ │ ├── 18.jpg │ │ ├── 19.jpg │ │ ├── 2.jpg │ │ ├── 20.jpg │ │ ├── 5.jpg │ │ └── 8.jpg │ └── fonts │ │ ├── 站酷小薇LOGO体.otf │ │ ├── 站酷庆科黄油体.ttf │ │ ├── 站酷快乐体2016修订版.ttf │ │ ├── 站酷文艺体.ttf │ │ ├── 站酷酷黑体.ttf │ │ └── 站酷高端黑修订151105.ttf ├── SimCaptchaMiddleware.cs ├── SimCaptchaMiddlewareExtensions.cs ├── SimCaptchaServiceCollectionExtensions.cs ├── TicketVerifyMiddleware.cs ├── VCodeCheckMiddleware.cs ├── VCodeImgMiddleware.cs ├── nuget-pack.ps1 └── readme.txt └── SimCaptcha ├── Common ├── AesHelper.cs ├── DateTimeHelper.cs └── HttpAide.cs ├── Implement ├── AesEncryptHelper.cs ├── CacheHelper.cs ├── DefaultAppChecker.cs └── RandomCodeHanZi.cs ├── Interface ├── IAppChecker.cs ├── ICache.cs ├── ICacheHelper.cs ├── IEncryptHelper.cs ├── IJsonHelper.cs ├── ILogHelper.cs ├── IRandomCode.cs ├── ISimCaptchaOptions.cs └── IVCodeImage.cs ├── Models ├── AppCheckModel.cs ├── PointPosModel.cs ├── TicketModel.cs ├── TicketVerifyModel.cs ├── TicketVerifyResponseModel.cs ├── VCodeCheckResponseModel.cs ├── VCodeImgModel.cs ├── VCodeKeyModel.cs ├── VCodeResponseModel.cs └── VerifyInfoModel.cs ├── SimCaptcha.csproj ├── SimCaptchaClient.cs ├── SimCaptchaOptions.cs ├── SimCaptchaService.cs └── nuget-pack.ps1 /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/SimCaptcha-nuget-push.yml: -------------------------------------------------------------------------------- 1 | name: SimCaptcha NuGet Push 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'SimCaptcha-v*' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: windows-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 5.0.102 20 | 21 | - name: Build and Pack 22 | run: | 23 | cd ./src/SimCaptcha 24 | dotnet build --configuration Release 25 | dotnet pack --configuration Release 26 | dotnet pack --configuration Release -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg 27 | ls 28 | ls ./bin/Release/ 29 | 30 | - name: Install Nuget 31 | uses: nuget/setup-nuget@v1 32 | with: 33 | nuget-version: '5.x' 34 | 35 | - name: Add private GitHub registry to NuGet 36 | run: | 37 | nuget sources add -name github -Source https://nuget.pkg.github.com/yiyungent/index.json -Username yiyungent -Password ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Push generated package to GitHub registry and NuGet 40 | run: | 41 | nuget push .\src\SimCaptcha\bin\Release\*.nupkg -Source github -SkipDuplicate 42 | nuget push .\src\SimCaptcha\bin\Release\*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NugetKey }} 43 | -------------------------------------------------------------------------------- /.github/workflows/SimCaptcha-release.yml: -------------------------------------------------------------------------------- 1 | name: SimCaptcha Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'SimCaptcha-v*' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout source 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup .NET Core 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 5.0.102 21 | 22 | - name: Build 23 | run: | 24 | cd ./src/SimCaptcha 25 | dotnet build --configuration Release 26 | ls 27 | ls ./bin/Release/ 28 | 29 | - name: Zip the Build 30 | run: | 31 | cd ./src/SimCaptcha/bin/Release 32 | zip -r SimCaptcha-netstandard2.0.zip ./netstandard2.0/ 33 | zip -r SimCaptcha-netcoreapp3.0.zip ./netcoreapp3.0/ 34 | zip -r SimCaptcha-netcoreapp3.1.zip ./netcoreapp3.1/ 35 | zip -r SimCaptcha-net5.0.zip ./net5.0/ 36 | cd ../../../../ 37 | mv ./src/SimCaptcha/bin/Release/*.zip ./ 38 | 39 | - name: Create Release and Upload Release Asset 40 | uses: softprops/action-gh-release@v1 41 | if: startsWith(github.ref, 'refs/tags/') 42 | with: 43 | #tag_name: ${{ github.ref }} 44 | #name: ${{ github.ref }} 45 | body: TODO New Release. 46 | #body_path: CHANGELOG.txt 47 | draft: false 48 | prerelease: false 49 | files: | 50 | SimCaptcha-netstandard2.0.zip 51 | SimCaptcha-netcoreapp3.0.zip 52 | SimCaptcha-netcoreapp3.1.zip 53 | SimCaptcha-net5.0.zip 54 | LICENSE 55 | README.md -------------------------------------------------------------------------------- /.github/workflows/SimCaptcha.AspNetCore-nuget-push.yml: -------------------------------------------------------------------------------- 1 | name: SimCaptcha.AspNetCore NuGet Push 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'SimCaptcha.AspNetCore-v*' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: windows-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 5.0.102 20 | 21 | - name: Build and Pack 22 | run: | 23 | cd ./src/SimCaptcha.AspNetCore 24 | dotnet build --configuration Release 25 | dotnet pack --configuration Release 26 | dotnet pack --configuration Release -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg 27 | ls 28 | ls ./bin/Release/ 29 | 30 | - name: Install Nuget 31 | uses: nuget/setup-nuget@v1 32 | with: 33 | nuget-version: '5.x' 34 | 35 | - name: Add private GitHub registry to NuGet 36 | run: | 37 | nuget sources add -name github -Source https://nuget.pkg.github.com/yiyungent/index.json -Username yiyungent -Password ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Push generated package to GitHub registry and NuGet 40 | run: | 41 | nuget push .\src\SimCaptcha.AspNetCore\bin\Release\*.nupkg -Source github -SkipDuplicate 42 | nuget push .\src\SimCaptcha.AspNetCore\bin\Release\*.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NugetKey }} 43 | -------------------------------------------------------------------------------- /.github/workflows/SimCaptcha.AspNetCore-release.yml: -------------------------------------------------------------------------------- 1 | name: SimCaptcha.AspNetCore Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'SimCaptcha.AspNetCore-v*' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout source 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup .NET Core 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 5.0.102 21 | 22 | - name: Build 23 | run: | 24 | cd ./src/SimCaptcha.AspNetCore 25 | dotnet build --configuration Release 26 | ls 27 | ls ./bin/Release/ 28 | 29 | - name: Zip the Build 30 | run: | 31 | cd ./src/SimCaptcha.AspNetCore/bin/Release 32 | zip -r SimCaptcha.AspNetCore-netcoreapp3.0.zip ./netcoreapp3.0/ 33 | zip -r SimCaptcha.AspNetCore-netcoreapp3.1.zip ./netcoreapp3.1/ 34 | zip -r SimCaptcha.AspNetCore-net5.0.zip ./net5.0/ 35 | cd ../../../../ 36 | mv ./src/SimCaptcha.AspNetCore/bin/Release/*.zip ./ 37 | 38 | - name: Create Release and Upload Release Asset 39 | uses: softprops/action-gh-release@v1 40 | if: startsWith(github.ref, 'refs/tags/') 41 | with: 42 | #tag_name: ${{ github.ref }} 43 | #name: ${{ github.ref }} 44 | body: TODO New Release. 45 | #body_path: CHANGELOG.txt 46 | draft: false 47 | prerelease: false 48 | files: | 49 | SimCaptcha.AspNetCore-netcoreapp3.0.zip 50 | SimCaptcha.AspNetCore-netcoreapp3.1.zip 51 | SimCaptcha.AspNetCore-net5.0.zip 52 | LICENSE 53 | README.md -------------------------------------------------------------------------------- /.github/workflows/deploy-docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI/CD 2 | on: 3 | push: 4 | branches: [ master ] 5 | jobs: 6 | # 构建并上传 Docker镜像 7 | build: 8 | runs-on: ubuntu-latest # 依赖的环境 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Build Image 12 | run: | 13 | docker build -t yiyungent/simcaptcha -f examples/EasyAspNetCoreService/Dockerfile . 14 | docker build -t yiyungent/simcaptcha-client -f examples/AspNetCoreClient/Dockerfile . 15 | - name: Login to Registry 16 | run: docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} 17 | - name: Push Image 18 | run: | 19 | docker push yiyungent/simcaptcha 20 | docker push yiyungent/simcaptcha-client 21 | 22 | # Docker 自动部署 23 | deploy-docker: 24 | needs: [build] 25 | name: Deploy Docker 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Deploy 29 | uses: appleboy/ssh-action@master 30 | with: 31 | host: ${{ secrets.HOST }} # 服务器ip 32 | username: ${{ secrets.HOST_USERNAME }} # 服务器登录用户名 33 | password: ${{ secrets.HOST_PASSWORD }} # 服务器登录密码 34 | port: ${{ secrets.HOST_PORT }} # 服务器ssh端口 35 | command_timeout: 360m 36 | script: | 37 | # 切换工作区 38 | cd simcaptcha 39 | # 下载 docker-compose.yml 40 | wget -O docker-compose.yml https://raw.githubusercontent.com/yiyungent/SimCaptcha/master/docker-compose.yml 41 | # 停止并删除旧 容器、网络、挂载点 42 | #docker-compose down # TODO: docker-compose: command not found. 不知道为什么找不到 docker-compose,但直接连接服务器执行就可以 43 | /usr/local/python3/bin/docker-compose down 44 | # 删除旧镜像 45 | docker rmi yiyungent/simcaptcha 46 | docker rmi yiyungent/simcaptcha-client 47 | # 登录镜像服务器 48 | docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} 49 | # 创建并启动容器 50 | #docker-compose up -d --build 51 | /usr/local/python3/bin/docker-compose up -d --build -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@master 13 | 14 | - name: Use Node.js 12 15 | uses: actions/setup-node@v2-beta 16 | with: 17 | node-version: '12' 18 | 19 | - name: Install and Build 🔧 20 | run: | 21 | cd docs 22 | npm install 23 | npm run docs:build 24 | 25 | - name: Deploy 🚀 26 | uses: JamesIves/github-pages-deploy-action@3.7.1 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | BRANCH: gh-pages 30 | FOLDER: docs/docs/.vuepress/dist 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/sync-gitee.yml: -------------------------------------------------------------------------------- 1 | name: Sync to Gitee 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Sync to Gitee 12 | uses: wearerequired/git-mirror-action@master 13 | env: 14 | # 在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY 15 | SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} 16 | with: 17 | # GitHub 源仓库地址 18 | source-repo: git@github.com:yiyungent/SimCaptcha.git 19 | # Gitee 目标仓库地址 20 | destination-repo: git@gitee.com:yiyungent/SimCaptcha.git 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 (2020-07-17) 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 yiyun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | SimCaptcha 3 |

4 |

SimCaptcha

5 | 6 | > :cake: 一个简单易用的点触验证码, 包含了前端与后端实现 7 | 8 | [![repo size](https://img.shields.io/github/repo-size/yiyungent/SimCaptcha.svg?style=flat)]() 9 | [![LICENSE](https://img.shields.io/github/license/yiyungent/SimCaptcha.svg?style=flat)](https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE) 10 | [![QQ Group](https://img.shields.io/badge/QQ%20Group-894031109-deepgreen)](https://jq.qq.com/?_wv=1027&k=q5R82fYN) 11 | 12 | 13 | 14 | ## 介绍 15 | 16 | 一个简单易用的点触验证码促进你的开发 17 | 18 | - **简单** - 约定优于配置, 以最少的配置帮助你专注于业务 19 | - **易扩展** - 通过实现各个验证组件接口,再借助于 ASP.NET Core 依赖注入,轻松扩展自己的验证策略 20 | - **开箱即用** - 使用现有 Web SDK 接入后端验证 21 | - **安全** - 验证图片,效验信息均服务端生成并保存 22 | - **分布式** - 支持通过实现 ICache 接口替换默认本地缓存方案 23 | - **轻松定制** - 简单配置即可自定义过期时间,失效次数,背景图片,字体等 24 | 25 | ## 在线演示 26 | 27 | - http://captcha-client.yiyungent.eu.org:10002/index.html 28 | - 仅供演示, 不稳定, 且非最新版 29 | - 用户名,密码 均为 admin 30 | 31 | ## 前后端调用时序图 32 | 33 | 34 | 35 | ## 依赖 36 | 37 | 只需要满足下方其中一条. 38 | 39 | - .NET Framework (>= 4.0) 被安装. 40 | - .NET Standard (>= 2.0) 被安装. 41 | 42 | ## 安装 43 | 44 | 推荐使用 [NuGet](https://www.nuget.org/packages/SimCaptcha), 在你项目的根目录 执行下方的命令, 如果你使用 Visual Studio, 这时依次点击 **Tools** -> **NuGet Package Manager** -> **Package Manager Console** , 确保 "Default project" 是你想要安装的项目, 输入下方的命令进行安装. 45 | 46 | ```bash 47 | PM> Install-Package SimCaptcha 48 | ``` 49 | 50 | #### 在 ASP.NET Core 下使用 51 | 52 | ```bash 53 | PM> Install-Package SimCaptcha.AspNetCore 54 | ``` 55 | 56 | ## 快速开始 57 | 58 | #### 在 ASP.NET Core 下 三步搭建验证服务端 59 | 60 | ```csharp 61 | // Startup.cs 62 | // 注意: 省略了部分代码, 只保留主要部分, 详见示例(/examples/EasyAspNetCoreService) 63 | // 仅适用于 SimCaptcha.AspNetCore v0.3.0+ 64 | public void ConfigureServices(IServiceCollection services) 65 | { 66 | // 1.重要: 注册验证码配置 67 | services.Configure(Configuration.GetSection(SimCaptchaOptions.SimCaptcha)); 68 | 69 | // 2.添加 SimCaptcha 70 | services.AddSimCaptcha(); 71 | } 72 | 73 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 74 | { 75 | // 3.启用 SimCaptcha 中间件 76 | app.UseSimCaptcha(); 77 | 78 | // 现在 79 | // "https://yourdomain.com/api/SimCaptcha/Img", "https://yourdomain.com/api/SimCaptcha/Check", "https://yourdomain.com/api/SimCaptcha/TicketVerify" 80 | // 将开始工作 81 | } 82 | ``` 83 | 84 | ## Docker 快速部署 85 | 86 | 下方为部署验证码服务端: 87 | 88 | ```bash 89 | docker run -d -p 5004:80 -e ASPNETCORE_URLS="http://*:80" --name simcaptcha-container yiyungent/simcaptcha 90 | ``` 91 | 92 | > 注意:若使用 Docker 同时部署验证码服务端,业务(客户)端, 93 | > 需注意Docker容器隔离,默认容器之间无法网络通信, 94 | > 需使其在一个网络下,用于客户端访问服务端验证票据,可参考仓库根目录 `docker-compose.yml`, 95 | > 同时,若客户端与服务端非同源域名, 96 | > 需注意跨域问题,Docker下验证码服务端配置文件:`/app/appsettings.Docker.json` 97 | > 事实上,若使用 Docker快速部署验证服务端, 98 | > 则一定要修改 `/app/appsettings.Docker.json` 其中的 `SimCaptcha.AppList.CorsWhiteList`,添加上你的客户端域名。 99 | 100 | ## 使用 101 | 102 | - [详细文档(/docs)](https://moeci.com/SimCaptcha "在线文档") 文档构建中 103 | - [见示例(/examples)](https://github.com/yiyungent/SimCaptcha/tree/master/examples) 104 | 105 | ## 版本依赖 106 | 107 | | SimCaptcha | 0.0.1 | 0.1.0 | 0.2.0 | 108 | | :-------------------: | :---------: | :---------: | :---------: | 109 | | SimCaptcha.AspNetCore | 0.0.1 | 0.1.0-0.2.0 | 0.3.0 | 110 | | sim-captcha-js | 0.0.1-0.0.4 | 0.0.1-0.1.0 | 0.0.1-0.1.0 | 111 | | vue-sim-captcha | 0.0.1-0.0.3 | 0.0.1-0.1.1 | 0.0.1-0.1.1 | 112 | 113 | 114 | | SimCaptcha | [![nuget](https://img.shields.io/nuget/v/SimCaptcha.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha/) | [![downloads](https://img.shields.io/nuget/dt/SimCaptcha.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha/) | 115 | | :-------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | 116 | | SimCaptcha.AspNetCore | [![nuget](https://img.shields.io/nuget/v/SimCaptcha.AspNetCore.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha.AspNetCore/) | [![downloads](https://img.shields.io/nuget/dt/SimCaptcha.AspNetCore.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha.AspNetCore/) | 117 | 118 | ## Q&A 119 | 120 | > Q: 为什么选择 SimCaptcha ? 121 | > A: 流行开源验证码及商业验证码 对照表如下: 122 | > TODO: 流行开源验证码及商业验证码 对照表 123 | 124 | ## 环境 125 | 126 | - 运行环境: .NET Framework (>= 4.0) or .NET Standard (>= 2.0) 127 | - 开发环境: Visual Studio Community 2019 128 | 129 | ## 相关项目 130 | 131 | - [sim-captcha-js](https://github.com/yiyungent/sim-captcha-js) 132 | - [vue-sim-captcha](https://github.com/yiyungent/vue-sim-captcha) 133 | 134 | ## 鸣谢 135 | 136 | - 点触验证码设计参考自 NetCoreVerificationCode,感谢作者 wangchengqun 的贡献 137 | - 验证码默认字体来自:[站酷字库](https://www.zcool.com.cn/special/zcoolfonts/),感谢授权 138 | 139 | ## Donate 140 | 141 | SimCaptcha is an MIT licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing. 142 | 143 | We accept donations through these channels: 144 | 145 | - 爱发电 146 | 147 | ## Author 148 | 149 | **SimCaptcha** © [yiyun](https://github.com/yiyungent), Released under the [MIT](./LICENSE) License.
150 | Authored and maintained by yiyun with help from contributors ([list](https://github.com/yiyungent/SimCaptcha/contributors)). 151 | 152 | > GitHub [@yiyungent](https://github.com/yiyungent) 153 | -------------------------------------------------------------------------------- /SimCaptcha.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1A94319A-8C57-4094-85D8-BEEEDFA07D79}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{2E277363-C944-427E-B8BE-3936A23F79DC}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreService", "examples\ASPNETCoreService\AspNetCoreService.csproj", "{CC92648F-9D94-4867-ABE5-7830A4EE34D6}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimCaptcha.AspNetCore", "src\SimCaptcha.AspNetCore\SimCaptcha.AspNetCore.csproj", "{5AB6137D-0901-41C7-9DB9-84701B4DBD9E}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreClient", "examples\ASPNETCoreClient\AspNetCoreClient.csproj", "{9FBFBE09-330A-46E3-8E9B-E49F2B93C13F}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimCaptcha", "src\SimCaptcha\SimCaptcha.csproj", "{1B152F2C-C845-4D5F-9B4B-739A494C43CF}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAspNetCoreService", "examples\EasyAspNetCoreService\EasyAspNetCoreService.csproj", "{3B1F8B7A-2DDE-47C5-A63C-4D59B8B18648}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {CC92648F-9D94-4867-ABE5-7830A4EE34D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {CC92648F-9D94-4867-ABE5-7830A4EE34D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {CC92648F-9D94-4867-ABE5-7830A4EE34D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {CC92648F-9D94-4867-ABE5-7830A4EE34D6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {5AB6137D-0901-41C7-9DB9-84701B4DBD9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {5AB6137D-0901-41C7-9DB9-84701B4DBD9E}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {5AB6137D-0901-41C7-9DB9-84701B4DBD9E}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {5AB6137D-0901-41C7-9DB9-84701B4DBD9E}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {9FBFBE09-330A-46E3-8E9B-E49F2B93C13F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {9FBFBE09-330A-46E3-8E9B-E49F2B93C13F}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {9FBFBE09-330A-46E3-8E9B-E49F2B93C13F}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {9FBFBE09-330A-46E3-8E9B-E49F2B93C13F}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {1B152F2C-C845-4D5F-9B4B-739A494C43CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {1B152F2C-C845-4D5F-9B4B-739A494C43CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {1B152F2C-C845-4D5F-9B4B-739A494C43CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {1B152F2C-C845-4D5F-9B4B-739A494C43CF}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {3B1F8B7A-2DDE-47C5-A63C-4D59B8B18648}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {3B1F8B7A-2DDE-47C5-A63C-4D59B8B18648}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {3B1F8B7A-2DDE-47C5-A63C-4D59B8B18648}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {3B1F8B7A-2DDE-47C5-A63C-4D59B8B18648}.Release|Any CPU.Build.0 = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {CC92648F-9D94-4867-ABE5-7830A4EE34D6} = {2E277363-C944-427E-B8BE-3936A23F79DC} 52 | {5AB6137D-0901-41C7-9DB9-84701B4DBD9E} = {1A94319A-8C57-4094-85D8-BEEEDFA07D79} 53 | {9FBFBE09-330A-46E3-8E9B-E49F2B93C13F} = {2E277363-C944-427E-B8BE-3936A23F79DC} 54 | {1B152F2C-C845-4D5F-9B4B-739A494C43CF} = {1A94319A-8C57-4094-85D8-BEEEDFA07D79} 55 | {3B1F8B7A-2DDE-47C5-A63C-4D59B8B18648} = {2E277363-C944-427E-B8BE-3936A23F79DC} 56 | EndGlobalSection 57 | GlobalSection(ExtensibilityGlobals) = postSolution 58 | SolutionGuid = {4C40FA22-1CE6-4D25-A50A-5772997B9D28} 59 | EndGlobalSection 60 | EndGlobal 61 | -------------------------------------------------------------------------------- /docker-build-simcaptcha-client.ps1: -------------------------------------------------------------------------------- 1 | docker build -t yiyungent/simcaptcha-client -f examples/AspNetCoreClient/Dockerfile . -------------------------------------------------------------------------------- /docker-build-simcaptcha.ps1: -------------------------------------------------------------------------------- 1 | docker build -t yiyungent/simcaptcha -f examples/EasyAspNetCoreService/Dockerfile . -------------------------------------------------------------------------------- /docker-compose.Debug.ps1: -------------------------------------------------------------------------------- 1 | docker-compose -f docker-compose.Debug.yml up -d --build -------------------------------------------------------------------------------- /docker-compose.Debug.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | # 本文件 仅用作 Debug 用 4 | 5 | services: 6 | simcaptcha.service: 7 | build: 8 | context: . 9 | dockerfile: examples/EasyAspNetCoreService/Dockerfile.Debug 10 | # 当同时设置 build, image 时,image 名将作为最后的镜像名 11 | image: simcaptcha-debug 12 | container_name: simcaptcha-debug-container 13 | ports: 14 | - "5004:80" 15 | restart: always 16 | environment: 17 | - TZ=Asia/Shanghai 18 | - ASPNETCORE_ENVIRONMENT=DockerDebug 19 | # 注意:http://*:80 不要用 双引号 "" 引起来 20 | - ASPNETCORE_URLS=http://*:80 21 | networks: 22 | - simcaptcha-net 23 | 24 | simcaptcha.client: 25 | build: 26 | context: . 27 | dockerfile: examples/AspNetCoreClient/Dockerfile.Debug 28 | image: simcaptcha-client-debug 29 | container_name: simcaptcha-client-debug-container 30 | ports: 31 | - "5002:80" 32 | restart: always 33 | depends_on: 34 | - simcaptcha.service 35 | environment: 36 | - TZ=Asia/Shanghai 37 | - ASPNETCORE_ENVIRONMENT=DockerDebug 38 | - ASPNETCORE_URLS=http://*:80 39 | networks: 40 | - simcaptcha-net 41 | 42 | networks: 43 | simcaptcha-net: 44 | driver: bridge -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | # 本文件 用作 Release 用,用作 Docker 部署 4 | 5 | services: 6 | simcaptcha.service: 7 | image: yiyungent/simcaptcha 8 | container_name: simcaptcha-container 9 | ports: 10 | - "5004:80" 11 | restart: always 12 | environment: 13 | - TZ=Asia/Shanghai 14 | - ASPNETCORE_ENVIRONMENT=Docker 15 | # 注意:http://*:80 不要用 双引号 "" 引起来 16 | - ASPNETCORE_URLS=http://*:80 17 | networks: 18 | - simcaptcha-net 19 | 20 | simcaptcha.client: 21 | image: yiyungent/simcaptcha-client 22 | container_name: simcaptcha-client-container 23 | ports: 24 | - "5002:80" 25 | restart: always 26 | depends_on: 27 | - simcaptcha.service 28 | environment: 29 | - TZ=Asia/Shanghai 30 | - ASPNETCORE_ENVIRONMENT=Docker 31 | - ASPNETCORE_URLS=http://*:80 32 | networks: 33 | - simcaptcha-net 34 | 35 | networks: 36 | simcaptcha-net: 37 | driver: bridge -------------------------------------------------------------------------------- /docker-run-simcaptcha-client.ps1: -------------------------------------------------------------------------------- 1 | docker stop simcaptcha-client-container 2 | docker rm simcaptcha-client-container 3 | docker run -d -p 5002:80 -p 5001:443 --name simcaptcha-client-container yiyungent/simcaptcha-client -------------------------------------------------------------------------------- /docker-run-simcaptcha.ps1: -------------------------------------------------------------------------------- 1 | docker stop simcaptcha-container 2 | docker rm simcaptcha-container 3 | docker run -d -p 5004:80 -p 5003:443 -e ASPNETCORE_URLS="http://*:80" --name simcaptcha-container yiyungent/simcaptcha -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # VuePress 2 | docs/.vuepress/dist/ 3 | 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 本项目文档采用 `VuePress` 构建 4 | 5 | 6 | # Build 7 | 8 | ```shell 9 | npm install 10 | ``` 11 | 12 | ```shell 13 | npm run docs:dev 14 | ``` 15 | 16 | ```shell 17 | npm run docs:build 18 | ``` -------------------------------------------------------------------------------- /docs/deploy-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | #set -e 5 | 6 | # 生成静态文件 7 | npm run docs:build 8 | 9 | # 进入生成的文件夹 10 | cd .vuepress/dist 11 | 12 | # 如果是发布到自定义域名 13 | # echo 'www.example.com' > CNAME 14 | 15 | git init 16 | git add -A 17 | git commit -m 'deploy docs' 18 | 19 | # 如果发布到 https://.github.io 20 | # git push -f git@github.com:/.github.io.git master 21 | 22 | # 如果发布到 https://.github.io/ 23 | git push -f git@github.com:yiyungent/SimCaptcha.git master:gh-pages 24 | 25 | # 失败, 还是直接退出了 26 | Write-Host "deploy docs finished." 27 | 28 | Read-Host "Press any key to exit..." ; 29 | [Console]::Readkey() | Out-Null ; 30 | Exit ; -------------------------------------------------------------------------------- /docs/docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: yiyun 3 | * @Description: 4 | */ 5 | module.exports = { 6 | title: "SimCaptcha", 7 | description: "一个简单易用的点触验证码", 8 | base: "/SimCaptcha/", 9 | themeConfig: { 10 | // 假定是 GitHub. 同时也可以是一个完整的 GitLab URL 11 | repo: "yiyungent/SimCaptcha", 12 | // 自定义仓库链接文字。默认从 `themeConfig.repo` 中自动推断为 13 | // "GitHub"/"GitLab"/"Bitbucket" 其中之一,或是 "Source"。 14 | repoLabel: "GitHub", 15 | 16 | // 以下为可选的编辑链接选项 17 | 18 | // 假如你的文档仓库和项目本身不在一个仓库: 19 | //docsRepo: 'vuejs/vuepress', 20 | // 假如文档不是放在仓库的根目录下: 21 | docsDir: "docs", 22 | // 假如文档放在一个特定的分支下: 23 | docsBranch: "master", 24 | // 默认是 false, 设置为 true 来启用 25 | editLinks: true, 26 | // 默认为 "Edit this page" 27 | editLinkText: "帮助我们改善此页面!", 28 | lastUpdated: "Last Updated", // string | boolean 29 | 30 | nav: [ 31 | { text: "首页", link: "/" }, 32 | { text: "指南", link: "/Guide/" }, 33 | { 34 | text: "SimCaptcha", 35 | items: [ 36 | { text: "SimCaptcha", link: "/SimCaptcha/" }, 37 | { text: "SimCaptcha.AspNetCore", link: "/SimCaptcha.AspNetCore/" }, 38 | ], 39 | }, 40 | { 41 | text: "Web SDK", 42 | items: [ 43 | { text: "sim-captcha-js", link: "/WebSDK/sim-captcha-js/" }, 44 | { text: "vue-sim-captcha", link: "/WebSDK/vue-sim-captcha/" }, 45 | ], 46 | }, 47 | ], 48 | 49 | sidebarDepth: 2, 50 | sidebar: { 51 | "/Guide/": [ 52 | { 53 | title: "指南", // 必要的 54 | path: "/Guide/", 55 | collapsable: false, // 可选的, 默认值是 true, 56 | children: [ 57 | { 58 | title: "依赖", 59 | path: "dependence", 60 | }, 61 | ], 62 | }, 63 | ], 64 | "/SimCaptcha/": [ 65 | { 66 | title: "SimCaptcha", // 必要的 67 | path: "/SimCaptcha/", 68 | collapsable: false, // 可选的, 默认值是 true, 69 | // children: [{ title: "API", path: "api" }], 70 | }, 71 | ], 72 | "/SimCaptcha.AspNetCore/": [ 73 | { 74 | title: "SimCaptcha.AspNetCore", // 必要的 75 | path: "/SimCaptcha.AspNetCore/", 76 | collapsable: false, // 可选的, 默认值是 true, 77 | // children: [{ title: "API", path: "api" }], 78 | }, 79 | ], 80 | "/WebSDK/sim-captcha-js/": [ 81 | { 82 | title: "sim-captcha-js", // 必要的 83 | path: "/WebSDK/sim-captcha-js/", 84 | collapsable: false, // 可选的, 默认值是 true, 85 | children: [{ title: "API", path: "api" }], 86 | }, 87 | ], 88 | "/WebSDK/vue-sim-captcha/": [ 89 | { 90 | title: "vue-sim-captcha", // 必要的 91 | path: "/WebSDK/vue-sim-captcha/", 92 | collapsable: false, // 可选的, 默认值是 true, 93 | children: [{ title: "API", path: "api" }], 94 | }, 95 | ], 96 | }, 97 | }, 98 | }; 99 | -------------------------------------------------------------------------------- /docs/docs/.vuepress/public/images/NETStandard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/docs/docs/.vuepress/public/images/NETStandard.png -------------------------------------------------------------------------------- /docs/docs/.vuepress/public/images/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/docs/docs/.vuepress/public/images/logo.gif -------------------------------------------------------------------------------- /docs/docs/.vuepress/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/docs/docs/.vuepress/public/images/logo.png -------------------------------------------------------------------------------- /docs/docs/.vuepress/public/images/time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/docs/docs/.vuepress/public/images/time.png -------------------------------------------------------------------------------- /docs/docs/Guide/README.md: -------------------------------------------------------------------------------- 1 | 5 | 9 | # 指南 10 | 11 |

SimCaptcha

12 | 13 | > :cake: 一个简单易用的点触验证码, 包含了客户端与服务端实现 14 | 15 | [![repo size](https://img.shields.io/github/repo-size/yiyungent/SimCaptcha.svg?style=flat)]() 16 | [![LICENSE](https://img.shields.io/github/license/yiyungent/SimCaptcha.svg?style=flat)](https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE) 17 | [![nuget](https://img.shields.io/nuget/v/SimCaptcha.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha/) 18 | [![downloads](https://img.shields.io/nuget/dt/SimCaptcha.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha/) 19 | 20 | 21 | 22 | ## 介绍 23 | 24 | 一个简单易用的点触验证码促进你的开发 25 | 26 | - **简单** - 约定优于配置, 以最少的配置帮助你专注于业务. 27 | - **易扩展** - 松散架构, 轻松扩展. 28 | - **开箱即用** - 使用现成 Web SDK 接入后端验证 29 | 30 | ## 在线演示 31 | 32 | - https://captcha-client.moeci.com/index.html 33 | - 仅供演示, 不稳定, 且非最新版, SSL 证书链尚不完整,可能在手机浏览器异常 34 | 35 | ## 前后端调用时序图 36 | 37 | 38 | 39 | ## 依赖 40 | 41 | 只需要满足下方其中一条. 42 | 43 | - .NET Framework (>= 4.0) 被安装. 44 | - .NET Standard (>= 2.0) 被安装. 45 | 46 | ## 安装 47 | 48 | 推荐使用 [NuGet](https://www.nuget.org/packages/SimCaptcha), 在你项目的根目录 执行下方的命令, 如果你使用 Visual Studio, 这时依次点击 **Tools** -> **NuGet Package Manager** -> **Package Manager Console** , 确保 "Default project" 是你想要安装的项目, 输入下方的命令进行安装. 49 | 50 | ```bash 51 | PM> Install-Package SimCaptcha 52 | ``` 53 | 54 | #### 在 ASP.NET Core 下使用 55 | 56 | ```bash 57 | PM> Install-Package SimCaptcha.AspNetCore 58 | ``` 59 | 60 | ## 快速开始 61 | 62 | #### 在 ASP.NET Core 下 三步搭建验证服务端 63 | 64 | ```csharp 65 | // Startup.cs 66 | // 注意: 省略了部分代码, 只保留主要部分, 详见示例(/examples/EasyAspNetCoreService) 67 | // 仅适用于 SimCaptcha.AspNetCore v0.2.0+ 68 | public void ConfigureServices(IServiceCollection services) 69 | { 70 | // 1.重要: 注册验证码配置 71 | services.Configure(Configuration.GetSection(SimCaptchaOptions.SimCaptcha)); 72 | 73 | // 2.添加 SimCaptcha 74 | services.AddSimCaptcha(); 75 | } 76 | 77 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 78 | { 79 | // 3.启用 SimCaptcha 中间件 80 | app.UseSimCaptcha(); 81 | 82 | // 现在 83 | // "https://yourdomain.com/api/vCode/VCodeImg", "https://yourdomain.com/api/vCode/VCodeCheck", "https://yourdomain.com/api/vCode/TicketVerify" 84 | // 将开始工作 85 | } 86 | ``` 87 | 88 | ## 使用 89 | 90 | - [详细文档(/docs)](https://yiyungent.github.io/SimCaptcha "在线文档") 文档构建中 91 | - [见示例(/examples)](https://github.com/yiyungent/SimCaptcha/tree/master/examples) 92 | 93 | ## 环境 94 | 95 | - 运行环境: .NET Framework (>= 4.0) or .NET Standard (>= 2.0) 96 | - 开发环境: Visual Studio Community 2019 97 | 98 | ## 相关项目 99 | 100 | - [sim-captcha-js](https://github.com/yiyungent/sim-captcha-js) 101 | - [vue-sim-captcha](https://github.com/yiyungent/vue-sim-captcha) -------------------------------------------------------------------------------- /docs/docs/Guide/dependence.md: -------------------------------------------------------------------------------- 1 | 5 | # 依赖 6 | 7 | 8 | 9 | 10 | | SimCaptcha | 0.0.1 | 0.1.0 | 0.2.0 | 11 | | :-------------------: | :---------: | :---------: | :---------: | 12 | | SimCaptcha.AspNetCore | 0.0.1 | 0.1.0-0.2.0 | 0.3.0 | 13 | | sim-captcha-js | 0.0.1-0.0.4 | 0.0.1-0.1.0 | 0.0.1-0.1.0 | 14 | | vue-sim-captcha | 0.0.1-0.0.3 | 0.0.1-0.1.1 | 0.0.1-0.1.1 | 15 | 16 | -------------------------------------------------------------------------------- /docs/docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /images/logo.gif 4 | heroText: SimCaptcha 5 | tagline: 文档构建中 6 | actionText: 快速上手 → 7 | actionLink: /Guide/ 8 | features: 9 | - title: 简洁至上 10 | details: 以 约定优于配置 为中心的项目结构,以最少的配置帮助你专注于业务。 11 | - title: 易扩展 12 | details: 享受 面向接口编程 的开发体验,使用 DI 注入验证组件,同时可以开发自定义验证。 13 | - title: 开箱即用 14 | details: SimCaptcha(社区) 为流行前端框架提供了开箱即用的解决方案, 不再需要自己实现, 请见 sim-captcha-js, vue-sim-captcha。 15 | footer: MIT Licensed | Copyright © 2020-present yiyun 16 | --- 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/docs/SimCaptcha.AspNetCore/README.md: -------------------------------------------------------------------------------- 1 | 5 | # SimCaptcha.AspNetCore 6 | 7 | #### 在 ASP.NET Core 下使用 8 | 9 | ```bash 10 | PM> Install-Package SimCaptcha.AspNetCore 11 | ``` 12 | 13 | ## 快速开始 14 | 15 | #### 在 ASP.NET Core 下 三步搭建验证服务端 16 | 17 | ```csharp 18 | // Startup.cs 19 | // 注意: 省略了部分代码, 只保留主要部分, 详见示例(/examples/EasyAspNetCoreService) 20 | // 仅适用于 SimCaptcha.AspNetCore v0.2.0+ 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | // 1.重要: 注册验证码配置 24 | services.Configure(Configuration.GetSection(SimCaptchaOptions.SimCaptcha)); 25 | 26 | // 2.添加 SimCaptcha 27 | services.AddSimCaptcha(); 28 | } 29 | 30 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 31 | { 32 | // 3.启用 SimCaptcha 中间件 33 | app.UseSimCaptcha(); 34 | 35 | // 现在 36 | // "https://yourdomain.com/api/vCode/VCodeImg", "https://yourdomain.com/api/vCode/VCodeCheck", "https://yourdomain.com/api/vCode/TicketVerify" 37 | // 将开始工作 38 | } 39 | ``` -------------------------------------------------------------------------------- /docs/docs/SimCaptcha/README.md: -------------------------------------------------------------------------------- 1 | 5 | # SimCaptcha 6 | 7 |

SimCaptcha

8 | 9 | > :cake: 一个简单易用的点触验证码, 包含了客户端与服务端实现 10 | 11 | [![repo size](https://img.shields.io/github/repo-size/yiyungent/SimCaptcha.svg?style=flat)]() 12 | [![LICENSE](https://img.shields.io/github/license/yiyungent/SimCaptcha.svg?style=flat)](https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE) 13 | [![nuget](https://img.shields.io/nuget/v/SimCaptcha.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha/) 14 | [![downloads](https://img.shields.io/nuget/dt/SimCaptcha.svg?style=flat)](https://www.nuget.org/packages/SimCaptcha/) 15 | 16 | 17 | 18 | ## 介绍 19 | 20 | 一个简单易用的点触验证码促进你的开发 21 | 22 | - **简单** - 约定优于配置, 以最少的配置帮助你专注于业务. 23 | - **易扩展** - 松散架构, 轻松扩展. 24 | - **开箱即用** - 使用现成 Web SDK 接入后端验证 25 | 26 | ## 在线演示 27 | 28 | - https://captcha-client.moeci.com/index.html 29 | - 仅供演示, 不稳定, 且非最新版, SSL 证书链尚不完整,可能在手机浏览器异常 30 | 31 | ## 前后端调用时序图 32 | 33 | 34 | 35 | ## 依赖 36 | 37 | 只需要满足下方其中一条. 38 | 39 | - .NET Framework (>= 4.0) 被安装. 40 | - .NET Standard (>= 2.0) 被安装. 41 | 42 | ## 安装 43 | 44 | 推荐使用 [NuGet](https://www.nuget.org/packages/SimCaptcha), 在你项目的根目录 执行下方的命令, 如果你使用 Visual Studio, 这时依次点击 **Tools** -> **NuGet Package Manager** -> **Package Manager Console** , 确保 "Default project" 是你想要安装的项目, 输入下方的命令进行安装. 45 | 46 | ```bash 47 | PM> Install-Package SimCaptcha 48 | ``` -------------------------------------------------------------------------------- /docs/docs/WebSDK/sim-captcha-js/README.md: -------------------------------------------------------------------------------- 1 | 5 |

sim-captcha-js

6 | 7 | > :cake: 一个简单易用的点触验证码, SimCaptcha 前端的 JavaScript 实现 8 | 9 | [![repo size](https://img.shields.io/github/repo-size/yiyungent/sim-captcha-js.svg?style=flat)]() 10 | [![LICENSE](https://img.shields.io/github/license/yiyungent/sim-captcha-js.svg?style=flat)](https://github.com/yiyungent/sim-captcha-js/blob/master/LICENSE) 11 | [![NPM version](https://img.shields.io/npm/v/sim-captcha.svg)](https://www.npmjs.com/package/sim-captcha) 12 | [![](https://data.jsdelivr.com/v1/package/npm/sim-captcha/badge)](https://www.jsdelivr.com/package/npm/sim-captcha) 13 | 14 | 15 | 16 | 17 | ## 介绍 18 | 19 | 轻松将点触验证码带入你的Web应用 20 | + **简单** - 简单易用. 21 | + **免费** - MIT协议 发布 22 | 23 | ## 在线演示 24 | 25 | - https://captcha-client.moeci.com/index.html 26 | - 仅供演示, 不稳定, 且非最新版, SSL证书链尚不完整,可能在手机浏览器异常 27 | 28 | ## 安装 29 | 30 | #### CDN 31 | ```html 32 | 33 | ``` 34 | 35 | #### npm 36 | ```bash 37 | npm install sim-captcha --save 38 | ``` 39 | 40 | ## 使用 41 | 42 | ### 使用模块管理器 43 | ```js 44 | import SimCaptcha from 'sim-captcha'; 45 | 46 | const cap = new SimCaptcha(options); 47 | ``` 48 | ### 在页面中使用 49 | ```html 50 | 51 |
52 |
53 | 54 | 55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 | 63 |
64 |
65 | 66 | 111 | 112 | ``` 113 | -------------------------------------------------------------------------------- /docs/docs/WebSDK/sim-captcha-js/api.md: -------------------------------------------------------------------------------- 1 | 5 | # API 6 | 7 | ## show() 显示验证码 8 | 9 | 显示验证码层 10 | 11 | ```js 12 | show() 13 | ``` 14 | 15 | ## hidden() 隐藏验证码 16 | 17 | 隐藏当前验证码弹出层,下次show 将使用当前验证码图片base64 18 | 用于用户手动点击关闭按钮 19 | 20 | ```js 21 | hidden() 22 | ``` 23 | 24 | ## destroy() 摧毁验证码 25 | 26 | 摧毁当前验证码(隐藏验证码弹出层,清除验证码图片base64),下次show 将请求新验证码图片base64 27 | 28 | ```js 29 | destroy() 30 | ``` 31 | 32 | ## getTicket() 获取票据 33 | 34 | return: Object:{"appId":"","ticket":""} 35 | 36 | ```js 37 | getTicket() 38 | ``` 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/docs/WebSDK/vue-sim-captcha/README.md: -------------------------------------------------------------------------------- 1 | 2 |

vue-sim-captcha

3 | 4 | > :cake: 一个简单易用的点触验证码, SimCaptcha 前端的 Vue.js 实现 5 | 6 | [![repo size](https://img.shields.io/github/repo-size/yiyungent/vue-sim-captcha.svg?style=flat)]() 7 | [![LICENSE](https://img.shields.io/github/license/yiyungent/vue-sim-captcha.svg?style=flat)](https://github.com/yiyungent/vue-sim-captcha/blob/master/LICENSE) 8 | [![NPM version](https://img.shields.io/npm/v/vue-sim-captcha.svg)](https://www.npmjs.com/package/vue-sim-captcha) 9 | 10 | 11 | 12 | 13 | ## 介绍 14 | 15 | 轻松在 Vue.js 下使用点触验证码 16 | + **简单** - 简单易用. 17 | + **免费** - MIT协议 发布 18 | 19 | ## 安装 20 | 21 | ```bash 22 | PM> npm install vue-sim-captcha --save 23 | ``` 24 | 25 | ## 使用 26 | ```js 27 | // main.js 或单个组件中 Login.vue 28 | import Vue from 'vue' 29 | import VueSimCaptcha from 'vue-sim-captcha' 30 | 31 | Vue.use(VueSimCaptcha) 32 | ``` 33 | 34 | ```html 35 | 36 | 53 | ``` 54 | 55 | ```js 56 | // Login.vue 57 | // 以下仅适用于 v0.1.1 及以上 58 | 119 | ``` 120 | 121 | -------------------------------------------------------------------------------- /docs/docs/WebSDK/vue-sim-captcha/api.md: -------------------------------------------------------------------------------- 1 | 5 | # API -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SimCaptcha", 3 | "version": "0.1.0", 4 | "description": "

\r \"SimCaptcha\"\r

\r

SimCaptcha

", 5 | "main": "index.js", 6 | "scripts": { 7 | "docs:test": "echo \"Error: no test specified\" && exit 1", 8 | "docs:dev": "vuepress dev docs", 9 | "docs:build": "vuepress build docs", 10 | "commit": "git-cz", 11 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/yiyungent/SimCaptcha.git" 16 | }, 17 | "keywords": [ 18 | "SimCaptcha", 19 | "Captcha" 20 | ], 21 | "author": "yiyun", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/yiyungent/SimCaptcha/issues" 25 | }, 26 | "homepage": "https://github.com/yiyungent/SimCaptcha#readme", 27 | "dependencies": { 28 | "vuepress": "^1.5.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "3.1.6", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/AspNetCoreClient/AspNetCoreClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | AspNetCoreClient 6 | AspNetCoreClient 7 | 069babb5-5232-4d55-bcc0-973adf2bef35 8 | Linux 9 | ..\.. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Options; 4 | using SimCaptcha; 5 | using SimCaptcha.AspNetCore; 6 | 7 | namespace AspNetCoreClient.Controllers 8 | { 9 | /// 10 | /// 后台业务 11 | /// 12 | [ApiController] 13 | [Route("[controller]")] 14 | public class HomeController : ControllerBase 15 | { 16 | private readonly IHttpContextAccessor _accessor; 17 | 18 | /// 19 | /// 验证码配置信息 20 | /// 21 | private readonly SimCaptchaOptions _options; 22 | 23 | private readonly SimCaptchaClient _client; 24 | 25 | #region Ctor 26 | public HomeController(IOptions options, IHttpContextAccessor accessor) 27 | { 28 | _options = options.Value; 29 | this._client = new SimCaptchaClient(options.Value, new AspNetCoreJsonHelper(), new ConsoleLogHelper()); 30 | 31 | this._accessor = accessor; 32 | } 33 | #endregion 34 | 35 | #region 登录 36 | /// 37 | /// 登陆 38 | /// 注意:标记才能接收到 39 | /// 40 | /// 41 | [Route(nameof(Login))] 42 | [HttpPost] 43 | public ActionResult Login([FromForm] string userName, [FromForm] string password, [FromForm] string ticket, [FromForm] string userId) 44 | { 45 | if (string.IsNullOrEmpty(ticket) || string.IsNullOrEmpty(userId)) 46 | { 47 | return Ok(new { code = -11, message = "请点击验证" }); 48 | } 49 | if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) 50 | { 51 | return Ok(new { code = -12, message = "请输入账号,密码" }); 52 | } 53 | 54 | string userIp = _accessor.HttpContext.Connection.RemoteIpAddress.ToString(); 55 | SimCaptcha.Models.TicketVerifyResponseModel ticketVerifyResult = _client.Verify(ticket, userId, userIp); 56 | if (ticketVerifyResult.code != 0) 57 | { 58 | return Ok(ticketVerifyResult); 59 | } 60 | 61 | if (userName == "admin" && password == "admin") 62 | { 63 | return Ok(new { code = 1, message = "登录成功" }); 64 | } 65 | else 66 | { 67 | return Ok(new { code = -10, message = "账号密码错误!" }); 68 | } 69 | } 70 | #endregion 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build 9 | WORKDIR /src 10 | COPY ["examples/AspNetCoreClient/AspNetCoreClient.csproj", "examples/AspNetCoreClient/"] 11 | COPY ["src/SimCaptcha.AspNetCore/SimCaptcha.AspNetCore.csproj", "src/SimCaptcha.AspNetCore/"] 12 | COPY ["src/SimCaptcha/SimCaptcha.csproj", "src/SimCaptcha/"] 13 | RUN dotnet restore "examples/AspNetCoreClient/AspNetCoreClient.csproj" 14 | COPY . . 15 | WORKDIR "/src/examples/AspNetCoreClient" 16 | RUN dotnet build "AspNetCoreClient.csproj" -c Release -o /app/build 17 | 18 | FROM build AS publish 19 | RUN dotnet publish "AspNetCoreClient.csproj" -c Release -o /app/publish 20 | 21 | FROM base AS final 22 | WORKDIR /app 23 | COPY --from=publish /app/publish . 24 | ENTRYPOINT ["dotnet", "AspNetCoreClient.dll"] -------------------------------------------------------------------------------- /examples/AspNetCoreClient/Dockerfile.Debug: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build 9 | WORKDIR /src 10 | COPY ["examples/AspNetCoreClient/AspNetCoreClient.csproj", "examples/AspNetCoreClient/"] 11 | COPY ["src/SimCaptcha.AspNetCore/SimCaptcha.AspNetCore.csproj", "src/SimCaptcha.AspNetCore/"] 12 | COPY ["src/SimCaptcha/SimCaptcha.csproj", "src/SimCaptcha/"] 13 | RUN dotnet restore "examples/AspNetCoreClient/AspNetCoreClient.csproj" 14 | COPY . . 15 | WORKDIR "/src/examples/AspNetCoreClient" 16 | RUN dotnet build "AspNetCoreClient.csproj" -c Debug -o /app/build 17 | 18 | FROM build AS publish 19 | RUN dotnet publish "AspNetCoreClient.csproj" -c Debug -o /app/publish 20 | 21 | FROM base AS final 22 | WORKDIR /app 23 | COPY --from=publish /app/publish . 24 | ENTRYPOINT ["dotnet", "AspNetCoreClient.dll"] -------------------------------------------------------------------------------- /examples/AspNetCoreClient/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace AspNetCoreClient 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | true 8 | false 9 | true 10 | Release 11 | Any CPU 12 | FileSystem 13 | bin\Release\netcoreapp3.1\publish\ 14 | FileSystem 15 | 16 | netcoreapp3.1 17 | 9fbfbe09-330a-46e3-8e9b-e49f2b93c13f 18 | false 19 | 20 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "AspNetCoreClient": { 5 | "commandName": "Project", 6 | "launchBrowser": true, 7 | "launchUrl": "index.html", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | }, 11 | "applicationUrl": "https://localhost:5001;http://localhost:5002" 12 | }, 13 | "Docker": { 14 | "commandName": "Docker", 15 | "launchBrowser": true, 16 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/index.html", 17 | "publishAllPorts": true, 18 | "useSSL": true 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /examples/AspNetCoreClient/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreClient/Startup.cs -------------------------------------------------------------------------------- /examples/AspNetCoreClient/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/appsettings.Docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "SimCaptcha": { 11 | "AppId": "132132", 12 | "AppSecret": "qwertyuiopasdfghjklzxcvbnm123456", 13 | "TicketVerifyUrl": "http://simcaptcha-container:80/api/SimCaptcha/TicketVerify" 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/appsettings.DockerDebug.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "SimCaptcha": { 11 | "AppId": "132132", 12 | "AppSecret": "qwertyuiopasdfghjklzxcvbnm123456", 13 | "TicketVerifyUrl": "http://simcaptcha-debug-container:80/api/SimCaptcha/TicketVerify" 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "SimCaptcha": { 11 | "AppId": "132132", 12 | "AppSecret": "qwertyuiopasdfghjklzxcvbnm123456", 13 | "TicketVerifyUrl": "http://localhost:5004/api/SimCaptcha/TicketVerify" 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /examples/AspNetCoreClient/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | sim-captcha 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 | 31 | 32 |
33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/AspNetCoreService/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "3.1.6", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/AspNetCoreService/AspNetCoreService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | AspNetCoreService 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | Always 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/AspNetCoreService/Controllers/VCodeController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Cors; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using Microsoft.Extensions.Options; 7 | using SimCaptcha; 8 | using SimCaptcha.AspNetCore; 9 | using SimCaptcha.Models; 10 | 11 | namespace AspNetCoreService.Controllers 12 | { 13 | /// 14 | /// 验证码服务端 15 | /// 16 | [Route("api/[controller]")] 17 | [ApiController] 18 | [EnableCors("_VCodeAllowSpecificOrigins")] 19 | // TODO: 目前未做 CorsWhiteList 对于每一个App 的严格限制(使用此AppId的只能是对应的CorsWhiteList) 20 | public class VCodeController : ControllerBase 21 | { 22 | private readonly IHttpContextAccessor _accessor; 23 | 24 | /// 25 | /// 验证码配置信息 26 | /// 27 | private readonly SimCaptchaOptions _options; 28 | 29 | private readonly SimCaptchaService _service; 30 | 31 | #region Ctor 32 | public VCodeController( 33 | IOptions options, 34 | IHttpContextAccessor accessor, 35 | IMemoryCache memoryCache) 36 | { 37 | _options = options.Value; 38 | this._service = new SimCaptchaService( 39 | options.Value, 40 | new LocalCache(memoryCache) { TimeOut = options.Value.ExpiredSec }, 41 | new AspNetCoreVCodeImage(), 42 | new AspNetCoreJsonHelper(), 43 | new ConsoleLogHelper() 44 | ); 45 | 46 | this._accessor = accessor; 47 | } 48 | #endregion 49 | 50 | #region 获取验证码 51 | /// 52 | /// 获取验证码 - 配置好验证码服务端的SimCaptcha.js后, 由SimCaptcha.js自动处理(无需业务后台关注) 53 | /// 54 | /// 55 | [Route(nameof(VCodeImg))] 56 | [HttpGet] 57 | public async Task VCodeImg() 58 | { 59 | VCodeResponseModel rtnResult = await _service.VCode(); 60 | 61 | return Ok(rtnResult); 62 | } 63 | #endregion 64 | 65 | #region 验证码效验 66 | /// 67 | /// 效验验证码 - 配置好验证码服务端的SimCaptcha.js后, 由SimCaptcha.js自动处理(往返于用户浏览器与验证码服务端,无需业务后台关注) 68 | /// 69 | /// 70 | /// 71 | [Route(nameof(VCodeCheck))] 72 | [HttpPost] 73 | public IActionResult VCodeCheck(VerifyInfoModel verifyInfo) 74 | { 75 | VCodeCheckResponseModel responseModel = null; 76 | 77 | // 获取ip地址 78 | string userIp = _accessor.HttpContext.Connection.RemoteIpAddress.ToString(); 79 | responseModel = _service.VCodeCheck(verifyInfo, userIp); 80 | 81 | return Ok(responseModel); 82 | } 83 | #endregion 84 | 85 | #region ticket效验 86 | /// 87 | /// ticket效验 - 给业务后台验证使用 88 | /// 89 | /// 90 | /// 91 | /// 92 | /// 93 | /// 94 | /// 95 | [Route(nameof(TicketVerify))] 96 | [HttpPost] 97 | public IActionResult TicketVerify(TicketVerifyModel ticketVerify) 98 | { 99 | TicketVerifyResponseModel responseModel = null; 100 | 101 | // ticket 效验 102 | responseModel = _service.TicketVerify(ticketVerify.AppId, ticketVerify.AppSecret, ticketVerify.Ticket, ticketVerify.UserId, ticketVerify.UserIp); 103 | 104 | return Ok(responseModel); 105 | } 106 | #endregion 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/AspNetCoreService/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace AspNetCoreService 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/AspNetCoreService/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | False 8 | False 9 | True 10 | Release 11 | Any CPU 12 | FileSystem 13 | bin\Release\netcoreapp3.1\publish\ 14 | FileSystem 15 | 16 | -------------------------------------------------------------------------------- /examples/AspNetCoreService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "AspNetCoreService": { 5 | "commandName": "Project", 6 | "launchBrowser": true, 7 | "launchUrl": "", 8 | "applicationUrl": "https://localhost:5003;http://localhost:5004", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/10.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/11.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/12.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/14.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/15.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/16.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/17.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/18.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/19.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/2.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/20.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/5.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/bgImages/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/bgImages/8.jpg -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/fonts/站酷小薇LOGO体.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/fonts/站酷小薇LOGO体.otf -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/fonts/站酷庆科黄油体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/fonts/站酷庆科黄油体.ttf -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/fonts/站酷快乐体2016修订版.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/fonts/站酷快乐体2016修订版.ttf -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/fonts/站酷文艺体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/fonts/站酷文艺体.ttf -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/fonts/站酷酷黑体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/fonts/站酷酷黑体.ttf -------------------------------------------------------------------------------- /examples/AspNetCoreService/SimCaptcha/fonts/站酷高端黑修订151105.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/SimCaptcha/fonts/站酷高端黑修订151105.ttf -------------------------------------------------------------------------------- /examples/AspNetCoreService/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/Startup.cs -------------------------------------------------------------------------------- /examples/AspNetCoreService/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/AspNetCoreService/appsettings.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/AspNetCoreService/appsettings.json -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "7.0.1", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | #FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base 4 | # 微软 base镜像 存在中文无法显示(方框)问题,尽管是加载自己本地中文字体,依然有此问题,因此用下方自己做的镜像 5 | FROM yiyungent/aspnetcore-runtime-3.1 AS base 6 | # 解决 Linux 下缺少 'libgdiplus' 7 | RUN apt-get update 8 | RUN apt-get install -y --no-install-recommends libgdiplus libc6-dev 9 | WORKDIR /app 10 | EXPOSE 80 11 | EXPOSE 443 12 | 13 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build 14 | WORKDIR /src 15 | COPY ["examples/EasyAspNetCoreService/EasyAspNetCoreService.csproj", "examples/EasyAspNetCoreService/"] 16 | COPY ["src/SimCaptcha.AspNetCore/SimCaptcha.AspNetCore.csproj", "src/SimCaptcha.AspNetCore/"] 17 | COPY ["src/SimCaptcha/SimCaptcha.csproj", "src/SimCaptcha/"] 18 | RUN dotnet restore "examples/EasyAspNetCoreService/EasyAspNetCoreService.csproj" 19 | COPY . . 20 | WORKDIR "/src/examples/EasyAspNetCoreService" 21 | RUN dotnet build "EasyAspNetCoreService.csproj" -c Release -o /app/build 22 | 23 | FROM build AS publish 24 | RUN dotnet publish "EasyAspNetCoreService.csproj" -c Release -o /app/publish 25 | 26 | FROM base AS final 27 | WORKDIR /app 28 | COPY --from=publish /app/publish . 29 | ENTRYPOINT ["dotnet", "EasyAspNetCoreService.dll"] -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/Dockerfile.Debug: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | #FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base 4 | # 微软 base镜像 存在中文无法显示(方框)问题,尽管是加载自己本地中文字体,依然有此问题,因此用下方自己做的镜像 5 | FROM yiyungent/aspnetcore-runtime-3.1 AS base 6 | # 解决 Linux 下缺少 'libgdiplus' 7 | RUN apt-get update 8 | RUN apt-get install -y --no-install-recommends libgdiplus libc6-dev 9 | WORKDIR /app 10 | EXPOSE 80 11 | EXPOSE 443 12 | 13 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build 14 | WORKDIR /src 15 | COPY ["examples/EasyAspNetCoreService/EasyAspNetCoreService.csproj", "examples/EasyAspNetCoreService/"] 16 | COPY ["src/SimCaptcha.AspNetCore/SimCaptcha.AspNetCore.csproj", "src/SimCaptcha.AspNetCore/"] 17 | COPY ["src/SimCaptcha/SimCaptcha.csproj", "src/SimCaptcha/"] 18 | RUN dotnet restore "examples/EasyAspNetCoreService/EasyAspNetCoreService.csproj" 19 | COPY . . 20 | WORKDIR "/src/examples/EasyAspNetCoreService" 21 | RUN dotnet build "EasyAspNetCoreService.csproj" -c Debug -o /app/build 22 | 23 | FROM build AS publish 24 | RUN dotnet publish "EasyAspNetCoreService.csproj" -c Debug -o /app/publish 25 | 26 | FROM base AS final 27 | WORKDIR /app 28 | COPY --from=publish /app/publish . 29 | ENTRYPOINT ["dotnet", "EasyAspNetCoreService.dll"] -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/EasyAspNetCoreService.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | EasyAspNetCoreService 6 | 1955bcda-6f7b-491c-9fab-0e7e7a2cdb45 7 | Linux 8 | ..\.. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Always 22 | Always 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace EasyAspNetCoreService 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | true 8 | false 9 | true 10 | Release 11 | Any CPU 12 | FileSystem 13 | bin\Release\netcoreapp3.1\publish\ 14 | FileSystem 15 | 16 | netcoreapp3.1 17 | 3b1f8b7a-2dde-47c5-a63c-4d59b8b18648 18 | false 19 | 20 | -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "EasyAspNetCoreService": { 5 | "commandName": "Project", 6 | "launchBrowser": true, 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "applicationUrl": "https://localhost:5003;http://localhost:5004" 11 | }, 12 | "Docker": { 13 | "commandName": "Docker", 14 | "launchBrowser": true, 15 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 16 | "publishAllPorts": true, 17 | "useSSL": true 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/10.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/11.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/12.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/14.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/15.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/16.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/17.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/18.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/19.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/2.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/20.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/5.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/bgImages/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/bgImages/8.jpg -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷小薇LOGO体.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷小薇LOGO体.otf -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷庆科黄油体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷庆科黄油体.ttf -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷快乐体2016修订版.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷快乐体2016修订版.ttf -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷文艺体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷文艺体.ttf -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷酷黑体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷酷黑体.ttf -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷高端黑修订151105.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/SimCaptcha/fonts/站酷高端黑修订151105.ttf -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/Startup.cs -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/appsettings.Docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "SimCaptcha": { 11 | "EncryptKey": "8G5M4Ff9hel8fUA9", // 必须为16位 12 | //"AllowErrorNum": 1, // 允许错误次数, 再次错误则刷新验证码, 默认为1 13 | //"ExpiredSec": 60, // 验证码以及ticket在被创建多少秒后过期, 默认为60 14 | "AppList": [ 15 | { 16 | "appId": "132132", 17 | "appSecret": "qwertyuiopasdfghjklzxcvbnm123456", 18 | "CorsWhiteList": [ "https://localhost:5001", "http://localhost:5002", "http://captcha-client.moeci.com", "https://captcha-client.moeci.com" ] 19 | }, 20 | { 21 | "appId": "131432", 22 | "appSecret": "edfewerffskagkg", 23 | "CorsWhiteList": [ "https://a.yourdomain.com", "https://b.yourdomain.com" ] 24 | }, 25 | { 26 | "appId": "13113432", 27 | "appSecret": "edfadsdaewerffskagkg", 28 | "CorsWhiteList": [ "https://c.yourdomain.com", "https://d.yourdomain.com" ] 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/appsettings.DockerDebug.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "SimCaptcha": { 11 | "EncryptKey": "8G5M4Ff9hel8fUA9", // 必须为16位 12 | //"AllowErrorNum": 1, // 允许错误次数, 再次错误则刷新验证码, 默认为1 13 | //"ExpiredSec": 60, // 验证码以及ticket在被创建多少秒后过期, 默认为60 14 | "AppList": [ 15 | { 16 | "appId": "132132", 17 | "appSecret": "qwertyuiopasdfghjklzxcvbnm123456", 18 | "CorsWhiteList": [ "https://localhost:5001", "http://localhost:5002" ] 19 | }, 20 | { 21 | "appId": "131432", 22 | "appSecret": "edfewerffskagkg", 23 | "CorsWhiteList": [ "https://a.yourdomain.com", "https://b.yourdomain.com" ] 24 | }, 25 | { 26 | "appId": "13113432", 27 | "appSecret": "edfadsdaewerffskagkg", 28 | "CorsWhiteList": [ "https://c.yourdomain.com", "https://d.yourdomain.com" ] 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /examples/EasyAspNetCoreService/appsettings.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/examples/EasyAspNetCoreService/appsettings.json -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/AspNetCoreJsonHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using SimCaptcha.Interface; 3 | // Project: SimCaptcha 4 | // https://github.com/yiyungent/SimCaptcha 5 | // Author: yiyun 6 | 7 | namespace SimCaptcha.AspNetCore 8 | { 9 | public class AspNetCoreJsonHelper : IJsonHelper 10 | { 11 | public JsonSerializerOptions JsonSerializerOptions { get; set; } 12 | 13 | public AspNetCoreJsonHelper() 14 | { 15 | this.JsonSerializerOptions = new JsonSerializerOptions(); 16 | // 属性名大小写不敏感 17 | this.JsonSerializerOptions.PropertyNameCaseInsensitive = true; 18 | } 19 | 20 | public T Deserialize(string jsonStr) 21 | { 22 | return System.Text.Json.JsonSerializer.Deserialize(jsonStr, JsonSerializerOptions); 23 | } 24 | 25 | public string Serialize(object jsonObj) 26 | { 27 | return System.Text.Json.JsonSerializer.Serialize(jsonObj); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/AspNetCoreVCodeImage.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Interface; 2 | using SimCaptcha.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | using System.IO; 8 | using System.Linq; 9 | // Project: SimCaptcha 10 | // https://github.com/yiyungent/SimCaptcha 11 | // Author: yiyun 12 | 13 | namespace SimCaptcha.AspNetCore 14 | { 15 | public class AspNetCoreVCodeImage : IVCodeImage 16 | { 17 | public VCodeImgModel Create(string code, int width, int height) 18 | { 19 | VCodeImgModel rtnResult = new VCodeImgModel { VCodePos = new List() }; 20 | 21 | // TODO: 变化点: 答案: 4个字 22 | int rightCodeLength = 4; 23 | 24 | Bitmap bitmap = null; 25 | Graphics g = null; 26 | MemoryStream ms = null; 27 | Random random = new Random(); 28 | 29 | Color[] colorArray = { Color.Black, Color.DarkBlue, Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple }; 30 | 31 | string bgImagesDir = Path.Combine(Environment.CurrentDirectory, "SimCaptcha", "bgImages"); 32 | string[] bgImagesFiles = System.IO.Directory.GetFiles(bgImagesDir); 33 | 34 | if (bgImagesFiles == null || bgImagesFiles.Length == 0) 35 | { 36 | Console.WriteLine("SimCaptcha/bgImages 需放置背景图片"); 37 | throw new Exception("SimCaptcha/bgImages 需放置背景图片"); 38 | } 39 | 40 | // 字体来自:https://www.zcool.com.cn/special/zcoolfonts/ 41 | string fontsDir = Path.Combine(Environment.CurrentDirectory, "SimCaptcha", "fonts"); 42 | string[] fontFiles = new DirectoryInfo(fontsDir)?.GetFiles() 43 | ?.Where(m => m.Extension.ToLower() == ".ttf") 44 | ?.Select(m => m.FullName).ToArray(); 45 | if (fontFiles == null || fontFiles.Length == 0) 46 | { 47 | Console.WriteLine("SimCaptcha/fonts 需放置字体文件 .ttf"); 48 | throw new Exception("SimCaptcha/fonts 需放置字体文件 .ttf"); 49 | } 50 | 51 | 52 | int imgIndex = random.Next(bgImagesFiles.Length); 53 | string randomImgFile = bgImagesFiles[imgIndex]; 54 | var imageStream = Image.FromFile(randomImgFile); 55 | 56 | bitmap = new Bitmap(imageStream, width, height); 57 | imageStream.Dispose(); 58 | g = Graphics.FromImage(bitmap); 59 | Color[] penColor = { Color.LightGray, Color.Green, Color.Blue }; 60 | int code_length = code.Length; 61 | List words = new List(); 62 | for (int i = 0; i < code_length; i++) 63 | { 64 | int colorIndex = random.Next(colorArray.Length); 65 | int fontIndex = random.Next(fontFiles.Length); 66 | Font f = LoadFont(fontFiles[fontIndex], 15, FontStyle.Bold); 67 | Brush b = new SolidBrush(colorArray[colorIndex]); 68 | int _y = random.Next(height); 69 | if (_y > (height - 30)) 70 | _y = _y - 60; 71 | 72 | int _x = width / (i + 1); 73 | if ((width - _x) < 50) 74 | { 75 | _x = width - 60; 76 | } 77 | string word = code.Substring(i, 1); 78 | if (rtnResult.VCodePos.Count < rightCodeLength) 79 | { 80 | (int, int) percentPos = ToPercentPos((width, height), (_x, _y)); 81 | // 添加正确答案 位置数据 82 | rtnResult.VCodePos.Add(new PointPosModel() 83 | { 84 | X = percentPos.Item1, 85 | Y = percentPos.Item2, 86 | }); 87 | words.Add(word); 88 | } 89 | g.DrawString(word, f, b, _x, _y); 90 | } 91 | rtnResult.Words = words; 92 | rtnResult.VCodeTip = "请依次点击: " + string.Join(",", words); 93 | 94 | ms = new MemoryStream(); 95 | bitmap.Save(ms, ImageFormat.Jpeg); 96 | g.Dispose(); 97 | bitmap.Dispose(); 98 | ms.Dispose(); 99 | rtnResult.ImgBase64 = "data:image/jpg;base64," + Convert.ToBase64String(ms.GetBuffer()); 100 | 101 | return rtnResult; 102 | } 103 | 104 | 105 | /// 106 | /// 转换为相对于图片的百分比单位 107 | /// 108 | /// 图片宽高 109 | /// 相对于图片的绝对尺寸 110 | /// (int:xPercent, int:yPercent) 111 | protected (int, int) ToPercentPos((int, int) widthAndHeight, (int, int) xAndy) 112 | { 113 | (int, int) rtnResult = (0, 0); 114 | // 注意: int / int = int (小数部分会被截断) 115 | rtnResult.Item1 = (int)(((double)xAndy.Item1) / ((double)widthAndHeight.Item1) * 100); 116 | rtnResult.Item2 = (int)(((double)xAndy.Item2) / ((double)widthAndHeight.Item2) * 100); 117 | 118 | return rtnResult; 119 | } 120 | 121 | 122 | /// 123 | /// 加载字体 124 | /// 125 | /// 字体文件路径,包含字体文件名和后缀名 126 | /// 大小 127 | /// 字形(常规/粗体/斜体/粗斜体) 128 | protected Font LoadFont(string path, int size, FontStyle fontStyle) 129 | { 130 | try 131 | { 132 | System.Drawing.Text.PrivateFontCollection pfc = new System.Drawing.Text.PrivateFontCollection(); 133 | 134 | pfc.AddFontFile(path);// 字体文件的路径 135 | 136 | Font myFont = new Font(pfc.Families[0], size, fontStyle); 137 | 138 | return myFont; 139 | } 140 | catch (System.Exception) 141 | { 142 | return null; 143 | } 144 | } 145 | } 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/ConsoleLogHelper.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Interface; 2 | using System; 3 | // Project: SimCaptcha 4 | // https://github.com/yiyungent/SimCaptcha 5 | // Author: yiyun 6 | 7 | namespace SimCaptcha.AspNetCore 8 | { 9 | public class ConsoleLogHelper : ILogHelper 10 | { 11 | public void Write(string message) 12 | { 13 | Console.WriteLine(message); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/LocalCache.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using SimCaptcha.Interface; 3 | using System; 4 | // Project: SimCaptcha 5 | // https://github.com/yiyungent/SimCaptcha 6 | // Author: yiyun 7 | 8 | namespace SimCaptcha.AspNetCore 9 | { 10 | /// 11 | /// 实现缓存 12 | /// 注意: 均为绝对过期,非滑动过期 13 | /// 14 | public class LocalCache : ICache 15 | { 16 | private IMemoryCache _cache; 17 | 18 | public LocalCache(IMemoryCache memoryCache) 19 | { 20 | this._cache = memoryCache; 21 | } 22 | 23 | /// 24 | /// 默认超时/过期时间(秒) 25 | /// 26 | public int TimeOut { get; set; } = 60; 27 | 28 | public bool Exists(string key) 29 | { 30 | object result = null; 31 | try 32 | { 33 | result = _cache.Get(key); 34 | } 35 | catch (Exception ex) 36 | { 37 | } 38 | return result != null; 39 | } 40 | 41 | public object Get(string key) 42 | { 43 | return _cache.Get(key); 44 | } 45 | 46 | public T Get(string key) 47 | { 48 | return _cache.Get(key); 49 | } 50 | 51 | public void Insert(string key, object data) 52 | { 53 | _cache.Set(key, data, TimeSpan.FromSeconds(TimeOut)); 54 | } 55 | 56 | public void Insert(string key, T data) 57 | { 58 | _cache.Set(key, data, TimeSpan.FromSeconds(TimeOut)); 59 | } 60 | 61 | public void Insert(string key, object data, int cacheTime) 62 | { 63 | _cache.Set(key, data, TimeSpan.FromSeconds(cacheTime)); 64 | } 65 | 66 | public void Insert(string key, T data, int cacheTime) 67 | { 68 | _cache.Set(key, data, TimeSpan.FromSeconds(cacheTime)); 69 | } 70 | 71 | public void Insert(string key, object data, DateTime cacheTime) 72 | { 73 | _cache.Set(key, data, cacheTime); 74 | } 75 | 76 | public void Insert(string key, T data, DateTime cacheTime) 77 | { 78 | _cache.Set(key, data, cacheTime); 79 | } 80 | 81 | public void Remove(string key) 82 | { 83 | _cache.Remove(key); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0;netcoreapp3.1 5 | 0.3.0 6 | yiyun 7 | yiyun 8 | Easy-to-use SimCaptcha for ASP.NET Core 9 | Copyright (c) 2020 yiyun 10 | https://github.com/yiyungent/SimCaptcha 11 | https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE 12 | Captcha SimCaptcha ASP.NET Core 13 | true 14 | 15 | 16 | 17 | 18 | 19 | Always 20 | Always 21 | 22 | 23 | Always 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | bin\Release\netcoreapp3.0\SimCaptcha.AspNetCore.xml 45 | 46 | 47 | bin\Release\netcoreapp3.1\SimCaptcha.AspNetCore.xml 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/10.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/11.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/12.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/14.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/15.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/16.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/17.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/18.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/19.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/2.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/20.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/5.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/bgImages/8.jpg -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷小薇LOGO体.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷小薇LOGO体.otf -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷庆科黄油体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷庆科黄油体.ttf -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷快乐体2016修订版.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷快乐体2016修订版.ttf -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷文艺体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷文艺体.ttf -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷酷黑体.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷酷黑体.ttf -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷高端黑修订151105.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yiyungent/SimCaptcha/23a4972e7983a055de5a1335f357fe1a06eedb76/src/SimCaptcha.AspNetCore/SimCaptcha/fonts/站酷高端黑修订151105.ttf -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptchaMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Microsoft.Extensions.Options; 6 | using System.Threading.Tasks; 7 | using SimCaptcha.Interface; 8 | using Microsoft.Extensions.Caching.Memory; 9 | 10 | namespace SimCaptcha.AspNetCore 11 | { 12 | public abstract class SimCaptchaMiddleware 13 | { 14 | protected readonly RequestDelegate _next; 15 | 16 | protected readonly SimCaptchaOptions _options; 17 | 18 | protected readonly SimCaptchaService _service; 19 | 20 | protected readonly IJsonHelper _jsonHelper; 21 | 22 | protected readonly IHttpContextAccessor _accessor; 23 | 24 | public SimCaptchaMiddleware(RequestDelegate next, IOptions optionsAccessor, ICache cache, IHttpContextAccessor accessor, IVCodeImage vCodeImage, IJsonHelper jsonHelper, ILogHelper logHelper) 25 | { 26 | _next = next; 27 | _options = optionsAccessor.Value; 28 | 29 | cache.TimeOut = optionsAccessor.Value.ExpiredSec; 30 | 31 | _service = new SimCaptchaService( 32 | optionsAccessor.Value, 33 | cache, 34 | vCodeImage, 35 | jsonHelper, 36 | logHelper 37 | ); 38 | _accessor = accessor; 39 | _jsonHelper = jsonHelper; 40 | } 41 | 42 | public abstract Task InvokeAsync(HttpContext context); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptchaMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.Options; 4 | using SimCaptcha.Interface; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Runtime.CompilerServices; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimCaptcha.AspNetCore 12 | { 13 | public static class SimCaptchaMiddlewareExtensions 14 | { 15 | public static IApplicationBuilder UseSimCaptcha(this IApplicationBuilder builder) 16 | { 17 | builder.Map("/api/SimCaptcha/Img", app => app.UseMiddleware()); 18 | builder.Map("/api/SimCaptcha/Check", app => app.UseMiddleware()); 19 | builder.Map("/api/SimCaptcha/TicketVerify", app => app.UseMiddleware()); 20 | 21 | return builder; 22 | } 23 | 24 | public static IApplicationBuilder UseSimCaptcha(this IApplicationBuilder builder, SimCaptchaOptions options) 25 | { 26 | builder.Map("/api/SimCaptcha/Img", app => app.UseMiddleware(new OptionsWrapper(options))); 27 | builder.Map("/api/SimCaptcha/Check", app => app.UseMiddleware(new OptionsWrapper(options))); 28 | builder.Map("/api/SimCaptcha/TicketVerify", app => app.UseMiddleware(new OptionsWrapper(options))); 29 | 30 | return builder; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/SimCaptchaServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Caching.Memory; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | using SimCaptcha.Interface; 9 | 10 | namespace SimCaptcha.AspNetCore 11 | { 12 | public static class SimCaptchaServiceCollectionExtensions 13 | { 14 | public static IServiceCollection AddSimCaptcha( 15 | this IServiceCollection services) 16 | { 17 | if (services == null) 18 | throw new ArgumentNullException(nameof(services)); 19 | 20 | // 1.用于 SimCaptcha.AspNetCore.LocalCache 缓存 21 | services.AddMemoryCache(); 22 | // 2.用于获取ip地址 23 | //services.AddSingleton(); 24 | services.TryAdd(ServiceDescriptor.Singleton()); 25 | services.AddSingleton(); 26 | services.AddSingleton(); 27 | services.AddSingleton(); 28 | services.AddSingleton(); 29 | services.AddSingleton(); 30 | 31 | return services; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/TicketVerifyMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using Microsoft.Extensions.Options; 4 | using SimCaptcha.Interface; 5 | using SimCaptcha.Models; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimCaptcha.AspNetCore 12 | { 13 | /// 14 | /// ticket效验 - 给业务后台验证使用 15 | /// 16 | public class TicketVerifyMiddleware : SimCaptchaMiddleware 17 | { 18 | public TicketVerifyMiddleware(RequestDelegate next, IOptions optionsAccessor, ICache cache, IHttpContextAccessor accessor, IVCodeImage vCodeImage, IJsonHelper jsonHelper, ILogHelper logHelper) : base(next, optionsAccessor, cache, accessor, vCodeImage, jsonHelper, logHelper) 19 | { } 20 | 21 | public override async Task InvokeAsync(HttpContext context) 22 | { 23 | string inputBody; 24 | using (var reader = new System.IO.StreamReader( 25 | context.Request.Body, Encoding.UTF8)) 26 | { 27 | inputBody = await reader.ReadToEndAsync(); 28 | } 29 | TicketVerifyModel ticketVerify = _jsonHelper.Deserialize(inputBody); 30 | 31 | // ticket 效验 32 | TicketVerifyResponseModel responseModel = _service.TicketVerify(ticketVerify.AppId, ticketVerify.AppSecret, ticketVerify.Ticket, ticketVerify.UserId, ticketVerify.UserIp); 33 | string responseJsonStr = _jsonHelper.Serialize(responseModel); 34 | 35 | context.Response.ContentType = "application/json"; 36 | await context.Response.WriteAsync(responseJsonStr, Encoding.UTF8); 37 | 38 | // Response.Write 开始, 不要再 Call next 39 | // Call the next delegate/middleware in the pipeline 40 | //await _next(context); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/VCodeCheckMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Options; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Memory; 8 | using SimCaptcha.Models; 9 | using SimCaptcha.Interface; 10 | 11 | namespace SimCaptcha.AspNetCore 12 | { 13 | /// 14 | /// 效验验证码 - 配置好验证码服务端的SimCaptcha.js后, 由SimCaptcha.js自动处理(往返于用户浏览器与验证码服务端,无需业务后台关注) 15 | /// 16 | public class VCodeCheckMiddleware : SimCaptchaMiddleware 17 | { 18 | public VCodeCheckMiddleware(RequestDelegate next, IOptions optionsAccessor, ICache cache, IHttpContextAccessor accessor, IVCodeImage vCodeImage, IJsonHelper jsonHelper, ILogHelper logHelper) : base(next, optionsAccessor, cache, accessor, vCodeImage, jsonHelper, logHelper) 19 | { } 20 | 21 | public override async Task InvokeAsync(HttpContext context) 22 | { 23 | string inputBody; 24 | using (var reader = new System.IO.StreamReader( 25 | context.Request.Body, Encoding.UTF8)) 26 | { 27 | inputBody = await reader.ReadToEndAsync(); 28 | } 29 | VerifyInfoModel verifyInfo = _jsonHelper.Deserialize(inputBody); 30 | 31 | // 获取ip地址 32 | string userIp = _accessor.HttpContext.Connection.RemoteIpAddress.ToString(); 33 | VCodeCheckResponseModel responseModel = _service.VCodeCheck(verifyInfo, userIp); 34 | string responseJsonStr = _jsonHelper.Serialize(responseModel); 35 | 36 | context.Response.ContentType = "application/json"; 37 | await context.Response.WriteAsync(responseJsonStr, Encoding.UTF8); 38 | 39 | // Response.Write 开始, 不要再 Call next 40 | // Call the next delegate/middleware in the pipeline 41 | //await _next(context); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/VCodeImgMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.Extensions.Options; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Memory; 8 | using SimCaptcha.Models; 9 | using SimCaptcha.Interface; 10 | 11 | namespace SimCaptcha.AspNetCore 12 | { 13 | /// 14 | /// 获取验证码 - 配置好验证码服务端的SimCaptcha.js后, 由SimCaptcha.js自动处理(无需业务后台关注) 15 | /// 16 | public class VCodeImgMiddleware : SimCaptchaMiddleware 17 | { 18 | public VCodeImgMiddleware(RequestDelegate next, IOptions optionsAccessor, ICache cache, IHttpContextAccessor accessor, IVCodeImage vCodeImage, IJsonHelper jsonHelper, ILogHelper logHelper) : base(next, optionsAccessor, cache, accessor, vCodeImage, jsonHelper, logHelper) 19 | { } 20 | 21 | public override async Task InvokeAsync(HttpContext context) 22 | { 23 | VCodeResponseModel responseModel = await _service.VCode(); 24 | string responseJsonStr = _jsonHelper.Serialize(responseModel); 25 | 26 | context.Response.ContentType = "application/json"; 27 | await context.Response.WriteAsync(responseJsonStr, Encoding.UTF8); 28 | 29 | // Response.Write 开始, 不要再 Call next 30 | // Call the next delegate/middleware in the pipeline 31 | //await _next(context); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/nuget-pack.ps1: -------------------------------------------------------------------------------- 1 | dotnet pack -c Release -------------------------------------------------------------------------------- /src/SimCaptcha.AspNetCore/readme.txt: -------------------------------------------------------------------------------- 1 | SimCaptcha/bgImages 为验证码背景图片文件夹,可以自己替换图片 2 | 3 | SimCaptcha/fonts 为验证码字体文件夹,字体文件只支持 xxx.ttf 4 | 5 | https://github.com/yiyungent/SimCaptcha 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/SimCaptcha/Common/AesHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Security.Cryptography; 4 | // Project: SimCaptcha 5 | // https://github.com/yiyungent/SimCaptcha 6 | // Author: yiyun 7 | 8 | namespace SimCaptcha.Common 9 | { 10 | /// 11 | /// AES加解密 12 | /// 13 | public class AesHelper 14 | { 15 | /// 16 | /// AES加解密 17 | /// 18 | private const string ALGORITHM = "AES"; 19 | 20 | #region 其它填充模式(但是php目前所知填充模式只有ZeroPadding,于是其他语言就只能跟着它来了) 21 | /// 22 | /// 工作模式:CBC 23 | /// 24 | private const PaddingMode TRANSFORM_CBC_PKCS5 = PaddingMode.PKCS7; 25 | 26 | /// 27 | /// 工作模式:ECB 28 | /// 29 | private const PaddingMode TRANSFORM_ECB_PKCS5 = PaddingMode.PKCS7; 30 | 31 | /// 32 | /// 工作模式:CBC 33 | /// 34 | private const PaddingMode TRANSFORM_CBC_ZEROS = PaddingMode.Zeros; 35 | 36 | /// 37 | /// 工作模式:ECB 38 | /// 39 | private const PaddingMode TRANSFORM_ECB_ZEROS = PaddingMode.Zeros; 40 | #endregion 41 | 42 | /// 43 | /// 工作模式:CBC 44 | /// 45 | private const PaddingMode TRANSFORM_CBC = PaddingMode.Zeros; 46 | 47 | /// 48 | /// 工作模式:ECB 49 | /// 50 | private const PaddingMode TRANSFORM_ECB = PaddingMode.Zeros; 51 | 52 | #region 基于CBC工作模式的AES加密 53 | /// 54 | /// 基于CBC工作模式的AES加密 55 | /// 56 | /// 待加密字符串 57 | /// 秘钥,如果不填则使用默认值 58 | /// 初始化向量值,如果不填则使用默认值 59 | /// 60 | [Obsolete("SimCaptcha 加解密默认全使用ECB模式, 请注意", true)] 61 | public static string EncryptCbcMode(string toEncrypt, string key, string iv) 62 | { 63 | if (!string.IsNullOrEmpty(toEncrypt)) 64 | { 65 | // 如果key或iv为空,则使用默认值 66 | if (key == null || key.Length != 16) 67 | { 68 | throw new Exception(nameof(key) + "必须为16位!"); 69 | } 70 | if (iv == null || iv.Length != 16) 71 | { 72 | throw new Exception(nameof(iv) + "必须为16位!"); 73 | } 74 | 75 | byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key); 76 | byte[] ivArray = UTF8Encoding.UTF8.GetBytes(iv); 77 | byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt); 78 | 79 | try 80 | { 81 | RijndaelManaged rDel = new RijndaelManaged 82 | { 83 | Key = keyArray, 84 | IV = ivArray, 85 | // 加密模式 86 | Mode = CipherMode.CBC, 87 | Padding = TRANSFORM_CBC 88 | }; 89 | ICryptoTransform cTransform = rDel.CreateEncryptor(); 90 | byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); 91 | 92 | // 然后转成BASE64返回 93 | return Convert.ToBase64String(resultArray, 0, resultArray.Length); 94 | } 95 | catch (Exception ex) 96 | { 97 | System.Diagnostics.Debug.WriteLine(string.Format("基于CBC工作模式的AES加密失败,toEncrypt:{0},key:{1}", toEncrypt, key)); 98 | throw ex; 99 | } 100 | } 101 | 102 | return null; 103 | } 104 | #endregion 105 | 106 | #region 基于CBC工作模式的AES解密 107 | /// 108 | /// 基于CBC工作模式的AES解密 109 | /// 110 | /// AES加密之后的字符串 111 | /// 秘钥,如果不填则使用默认值 112 | /// 初始化向量值,如果不填则使用默认值 113 | /// 114 | [Obsolete("SimCaptcha 加解密默认全使用ECB模式, 请注意", true)] 115 | public static string DecryptCbcMode(string toDecrypt, string key, string iv) 116 | { 117 | if (!string.IsNullOrEmpty(toDecrypt)) 118 | { 119 | // 如果key或iv为空,则使用默认值 120 | if (key == null || key.Length != 16) 121 | { 122 | throw new Exception(nameof(key) + "必须为16位!"); 123 | } 124 | if (iv == null || iv.Length != 16) 125 | { 126 | throw new Exception(nameof(iv) + "必须为16位!"); 127 | } 128 | 129 | byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key); 130 | byte[] ivArray = UTF8Encoding.UTF8.GetBytes(iv); 131 | byte[] toEncryptArray = Convert.FromBase64String(toDecrypt); 132 | 133 | try 134 | { 135 | RijndaelManaged rDel = new RijndaelManaged 136 | { 137 | Key = keyArray, 138 | IV = ivArray, 139 | Mode = CipherMode.CBC, 140 | Padding = TRANSFORM_CBC 141 | }; 142 | ICryptoTransform cTransform = rDel.CreateDecryptor(); 143 | byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); 144 | 145 | return UTF8Encoding.UTF8.GetString(resultArray); 146 | } 147 | catch (Exception ex) 148 | { 149 | System.Diagnostics.Debug.WriteLine(string.Format("基于CBC工作模式的AES解密失败,toDecrypt:{0},key:{1}", toDecrypt, key)); 150 | throw ex; 151 | } 152 | } 153 | 154 | return null; 155 | } 156 | #endregion 157 | 158 | #region 基于ECB工作模式的AES加密 159 | /// 160 | /// 基于ECB工作模式的AES加密 161 | /// 162 | /// 待加密字符串 163 | /// 秘钥,如果不填则使用默认值 164 | /// 165 | public static string EncryptEcbMode(string toEncrypt, string key) 166 | { 167 | if (!string.IsNullOrEmpty(toEncrypt)) 168 | { 169 | // 如果key或iv为空,则使用默认值 170 | if (key == null || key.Length != 16) 171 | { 172 | throw new Exception(nameof(key) + "必须为16位!"); 173 | } 174 | 175 | byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key); 176 | byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt); 177 | 178 | try 179 | { 180 | RijndaelManaged rDel = new RijndaelManaged 181 | { 182 | Key = keyArray, 183 | Mode = CipherMode.ECB, 184 | // 加密模式 185 | Padding = TRANSFORM_ECB, 186 | }; 187 | ICryptoTransform cTransform = rDel.CreateEncryptor(); 188 | byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); 189 | 190 | // 然后转成BASE64返回 191 | return Convert.ToBase64String(resultArray, 0, resultArray.Length); 192 | } 193 | catch (Exception ex) 194 | { 195 | System.Diagnostics.Debug.WriteLine(string.Format("基于ECB工作模式的AES加密失败,toEncrypt:{0},key:{1}", toEncrypt, key)); 196 | throw ex; 197 | } 198 | } 199 | 200 | return null; 201 | } 202 | #endregion 203 | 204 | #region 基于ECB工作模式的AES解密 205 | /// 206 | /// 基于ECB工作模式的AES解密 207 | /// 208 | /// AES加密之后的字符串 209 | /// 秘钥,如果不填则使用默认值 210 | /// 211 | public static string DecryptEcbMode(string toDecrypt, string key) 212 | { 213 | if (!string.IsNullOrEmpty(toDecrypt)) 214 | { 215 | // 如果key为空,则使用默认值 216 | if (key == null || key.Length != 16) 217 | { 218 | throw new Exception(nameof(key) + "必须为16位!"); 219 | } 220 | 221 | byte[] keyArray = UTF8Encoding.UTF8.GetBytes(key); 222 | byte[] toEncryptArray = Convert.FromBase64String(toDecrypt); 223 | 224 | try 225 | { 226 | RijndaelManaged rDel = new RijndaelManaged 227 | { 228 | Key = keyArray, 229 | Mode = CipherMode.ECB, 230 | Padding = TRANSFORM_ECB 231 | }; 232 | ICryptoTransform cTransform = rDel.CreateDecryptor(); 233 | byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); 234 | 235 | return UTF8Encoding.UTF8.GetString(resultArray); 236 | } 237 | catch (Exception ex) 238 | { 239 | System.Diagnostics.Debug.WriteLine(string.Format("基于ECB工作模式的AES解密失败,toDecrypt:{0},key:{1}", toDecrypt, key)); 240 | throw ex; 241 | } 242 | } 243 | 244 | return null; 245 | } 246 | #endregion 247 | } 248 | } -------------------------------------------------------------------------------- /src/SimCaptcha/Common/DateTimeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | /// 7 | /// JavaScript时间戳:是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。(13 位数字) 8 | /// 9 | /// Unix时间戳:是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。(10 位数字) 10 | /// 11 | namespace SimCaptcha.Common 12 | { 13 | public static class DateTimeHelper 14 | { 15 | public static DateTime DateTime1970 = new DateTime(1970, 1, 1).ToLocalTime(); 16 | 17 | #region Unix 10位时间戳-总秒数 18 | /// 19 | /// C# DateTime转换为Unix时间戳 20 | /// 21 | public static long ToTimeStamp10(this DateTime dateTime) 22 | { 23 | // 相差秒数 24 | long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalSeconds; 25 | 26 | return timeStamp; 27 | } 28 | 29 | /// 30 | /// C# DateTime转换为Unix时间戳 31 | /// 32 | public static long ToTimeStamp10(this DateTime? dateTime) 33 | { 34 | if (dateTime == null) 35 | { 36 | return 0; 37 | } 38 | // 相差秒数 39 | long timeStamp = ToTimeStamp10((DateTime)dateTime); 40 | 41 | return timeStamp; 42 | } 43 | 44 | /// 45 | /// Unix时间戳转换为C# DateTime 46 | /// 47 | public static DateTime ToDateTime10(this long timeStamp10) 48 | { 49 | DateTime dateTime = DateTime1970.AddSeconds(timeStamp10).ToLocalTime(); 50 | 51 | return dateTime; 52 | } 53 | #endregion 54 | 55 | #region JavaScript 13位时间戳-总毫秒数 56 | /// 57 | /// C# DateTime转换为JavaScript时间戳 58 | /// 59 | public static long ToTimeStamp13(this DateTime dateTime) 60 | { 61 | // 相差毫秒数 62 | long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalMilliseconds; 63 | 64 | return timeStamp; 65 | } 66 | 67 | /// 68 | /// C# DateTime转换为JavaScript时间戳 69 | /// 70 | public static long ToTimeStamp13(this DateTime? dateTime) 71 | { 72 | if (dateTime == null) 73 | { 74 | return 0; 75 | } 76 | // 相差秒数 77 | long timeStamp = ToTimeStamp13((DateTime)dateTime); 78 | 79 | return timeStamp; 80 | } 81 | 82 | /// 83 | /// JavaScript时间戳转换为C# DateTime 84 | /// 85 | public static DateTime ToDateTime13(this long timeStamp13) 86 | { 87 | DateTime dateTime = DateTime1970.AddMilliseconds(timeStamp13).ToLocalTime(); 88 | 89 | return dateTime; 90 | } 91 | #endregion 92 | 93 | #region 获取当前Unix时间戳 94 | public static long NowTimeStamp10() 95 | { 96 | return ToTimeStamp10(DateTime.Now); 97 | } 98 | #endregion 99 | 100 | #region 获取当前JavaScript时间戳 101 | public static long NowTimeStamp13() 102 | { 103 | return ToTimeStamp13(DateTime.Now); 104 | } 105 | #endregion 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/SimCaptcha/Common/HttpAide.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Net; 5 | using System.Text; 6 | // Project: SimCaptcha 7 | // https://github.com/yiyungent/SimCaptcha 8 | // Author: yiyun 9 | 10 | namespace SimCaptcha.Common 11 | { 12 | /// 13 | /// HTTP助手 14 | /// 15 | public class HttpAide 16 | { 17 | #region Http Get 18 | /// 19 | /// HTTP Get请求 20 | /// 21 | /// 请求目标URL 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 返回请求回复字符串 27 | public static string HttpGet(string url, StringBuilder responseHeadersSb = null, string[] headers = null, WebProxy proxy = null) 28 | { 29 | string rtResult = string.Empty; 30 | try 31 | { 32 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 33 | request.Method = "GET"; 34 | request.KeepAlive = false; 35 | 36 | if (headers != null) 37 | { 38 | foreach (string header in headers) 39 | { 40 | string[] temp = header.Split(new string[] { ": " }, StringSplitOptions.RemoveEmptyEntries); 41 | if (temp[0].Equals("Referer", StringComparison.InvariantCultureIgnoreCase)) 42 | { 43 | request.Referer = temp[1]; 44 | } 45 | else if (temp[0].Equals("User-Agent", StringComparison.InvariantCultureIgnoreCase)) 46 | { 47 | request.UserAgent = temp[1]; 48 | } 49 | else if (temp[0].Equals("Accept", StringComparison.InvariantCultureIgnoreCase)) 50 | { 51 | request.Accept = temp[1]; 52 | } 53 | else if (temp[0].Equals("Connection", StringComparison.InvariantCultureIgnoreCase) && temp[1].Equals("keep-alive", StringComparison.InvariantCultureIgnoreCase)) 54 | { 55 | request.KeepAlive = true; 56 | } 57 | else if (temp[0].Equals("Connection", StringComparison.InvariantCultureIgnoreCase)) 58 | { 59 | request.KeepAlive = false; 60 | } 61 | else if (temp[0].Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase)) 62 | { 63 | request.ContentType = temp[1]; 64 | } 65 | else 66 | { 67 | request.Headers.Add(header); 68 | } 69 | } 70 | } 71 | if (proxy != null) 72 | { 73 | request.Proxy = proxy; 74 | } 75 | request.Timeout = 10000; 76 | 77 | HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 78 | if (responseHeadersSb != null) 79 | { 80 | foreach (string name in response.Headers.AllKeys) 81 | { 82 | responseHeadersSb.AppendLine(name + ": " + response.Headers[name]); 83 | } 84 | } 85 | Stream responseStream = response.GetResponseStream(); 86 | //如果http头中接受gzip的话,这里就要判断是否为有压缩,有的话,直接解压缩即可 87 | if (response.Headers["Content-Encoding"] != null && response.Headers["Content-Encoding"].ToLower().Contains("gzip")) 88 | { 89 | responseStream = new GZipStream(responseStream, CompressionMode.Decompress); 90 | } 91 | using (StreamReader sReader = new StreamReader(responseStream, System.Text.Encoding.UTF8)) 92 | { 93 | rtResult = sReader.ReadToEnd(); 94 | } 95 | responseStream.Close(); 96 | } 97 | catch (Exception ex) 98 | { 99 | throw ex; 100 | } 101 | 102 | return rtResult; 103 | } 104 | #endregion 105 | 106 | #region Http Post 107 | public static string HttpPost(string url, string postDataStr = "", StringBuilder responseHeadersSb = null, string[] headers = null, WebProxy proxy = null) 108 | { 109 | string rtResult = string.Empty; 110 | try 111 | { 112 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 113 | request.Method = "POST"; 114 | request.KeepAlive = false; 115 | if (headers != null) 116 | { 117 | foreach (string header in headers) 118 | { 119 | string[] temp = header.Split(new string[] { ": " }, StringSplitOptions.RemoveEmptyEntries); 120 | if (temp[0].Equals("Referer", StringComparison.InvariantCultureIgnoreCase)) 121 | { 122 | request.Referer = temp[1]; 123 | } 124 | else if (temp[0].Equals("User-Agent", StringComparison.InvariantCultureIgnoreCase)) 125 | { 126 | request.UserAgent = temp[1]; 127 | } 128 | else if (temp[0].Equals("Accept", StringComparison.InvariantCultureIgnoreCase)) 129 | { 130 | request.Accept = temp[1]; 131 | } 132 | else if (temp[0].Equals("Connection", StringComparison.InvariantCultureIgnoreCase) && temp[1].Equals("keep-alive", StringComparison.InvariantCultureIgnoreCase)) 133 | { 134 | request.KeepAlive = true; 135 | } 136 | else if (temp[0].Equals("Connection", StringComparison.InvariantCultureIgnoreCase)) 137 | { 138 | request.KeepAlive = false; 139 | } 140 | else if (temp[0].Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase)) 141 | { 142 | request.ContentType = temp[1]; 143 | } 144 | else 145 | { 146 | request.Headers.Add(header); 147 | } 148 | } 149 | } 150 | if (proxy != null) 151 | { 152 | request.Proxy = proxy; 153 | } 154 | request.Timeout = 10000; 155 | byte[] postBytes = Encoding.UTF8.GetBytes(postDataStr); 156 | request.ContentLength = postBytes.Length; 157 | // 写 content-body 一定要在属性设置之后 158 | Stream requestStream = request.GetRequestStream(); 159 | requestStream.Write(postBytes, 0, postBytes.Length); 160 | requestStream.Close(); 161 | 162 | HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 163 | if (responseHeadersSb != null) 164 | { 165 | foreach (string name in response.Headers.AllKeys) 166 | { 167 | responseHeadersSb.AppendLine(name + ": " + response.Headers[name]); 168 | } 169 | } 170 | Stream responseStream = response.GetResponseStream(); 171 | //如果http头中接受gzip的话,这里就要判断是否为有压缩,有的话,直接解压缩即可 172 | if (response.Headers["Content-Encoding"] != null && response.Headers["Content-Encoding"].ToLower().Contains("gzip")) 173 | { 174 | responseStream = new GZipStream(responseStream, CompressionMode.Decompress); 175 | } 176 | using (StreamReader sReader = new StreamReader(responseStream, System.Text.Encoding.UTF8)) 177 | { 178 | rtResult = sReader.ReadToEnd(); 179 | } 180 | responseStream.Close(); 181 | } 182 | catch (Exception ex) 183 | { 184 | throw ex; 185 | } 186 | 187 | return rtResult; 188 | } 189 | #endregion 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/SimCaptcha/Implement/AesEncryptHelper.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Interface; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Implement 7 | { 8 | public class AesEncryptHelper : IEncryptHelper 9 | { 10 | public string Decrypt(string toDecrypt, string key, params string[] others) 11 | { 12 | return Common.AesHelper.DecryptEcbMode(toDecrypt, key); 13 | } 14 | 15 | public string Encrypt(string toEncrypt, string key, params string[] others) 16 | { 17 | return Common.AesHelper.EncryptEcbMode(toEncrypt, key); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SimCaptcha/Implement/CacheHelper.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Interface; 2 | using System; 3 | // Project: SimCaptcha 4 | // https://github.com/yiyungent/SimCaptcha 5 | // Author: yiyun 6 | 7 | namespace SimCaptcha.Implement 8 | { 9 | public class CacheHelper : ICacheHelper 10 | { 11 | #region Fields 12 | private static object cacheLocker = new object();// 缓存锁对象 13 | private ICache _cache = null;// 缓存接口 14 | #endregion 15 | 16 | #region Ctor 17 | public CacheHelper(ICache cache) 18 | { 19 | this._cache = cache; 20 | } 21 | #endregion 22 | 23 | #region Methods 24 | public ICache GetCache() 25 | { 26 | return _cache; 27 | } 28 | 29 | /// 30 | /// 缓存过期时间 31 | /// 32 | public int TimeOut 33 | { 34 | get 35 | { 36 | return _cache.TimeOut; 37 | } 38 | set 39 | { 40 | lock (cacheLocker) 41 | { 42 | _cache.TimeOut = value; 43 | } 44 | } 45 | } 46 | 47 | /// 48 | /// 获得指定键的缓存值 49 | /// 50 | /// 缓存键 51 | /// 缓存值 52 | public object Get(string key) 53 | { 54 | if (string.IsNullOrWhiteSpace(key)) 55 | return null; 56 | return _cache.Get(key); 57 | } 58 | 59 | /// 60 | /// 获得指定键的缓存值 61 | /// 62 | /// 缓存键 63 | /// 缓存值 64 | public T Get(string key) 65 | { 66 | return _cache.Get(key); 67 | } 68 | 69 | /// 70 | /// 将指定键的对象添加到缓存中 71 | /// 72 | /// 缓存键 73 | /// 缓存值 74 | public void Insert(string key, object data) 75 | { 76 | if (string.IsNullOrWhiteSpace(key) || data == null) 77 | return; 78 | //lock (cacheLocker) 79 | { 80 | _cache.Insert(key, data); 81 | } 82 | } 83 | /// 84 | /// 将指定键的对象添加到缓存中 85 | /// 86 | /// 缓存键 87 | /// 缓存值 88 | public void Insert(string key, T data) 89 | { 90 | if (string.IsNullOrWhiteSpace(key) || data == null) 91 | return; 92 | //lock (cacheLocker) 93 | { 94 | _cache.Insert(key, data); 95 | } 96 | } 97 | /// 98 | /// 将指定键的对象添加到缓存中,并指定过期时间 99 | /// 100 | /// 缓存键 101 | /// 缓存值 102 | /// 缓存过期时间(分钟) 103 | public void Insert(string key, object data, int cacheTime) 104 | { 105 | if (!string.IsNullOrWhiteSpace(key) && data != null) 106 | { 107 | //lock (cacheLocker) 108 | { 109 | _cache.Insert(key, data, cacheTime); 110 | } 111 | } 112 | } 113 | 114 | /// 115 | /// 将指定键的对象添加到缓存中,并指定过期时间 116 | /// 117 | /// 缓存键 118 | /// 缓存值 119 | /// 缓存过期时间(分钟) 120 | public void Insert(string key, T data, int cacheTime) 121 | { 122 | if (!string.IsNullOrWhiteSpace(key) && data != null) 123 | { 124 | //lock (cacheLocker) 125 | { 126 | _cache.Insert(key, data, cacheTime); 127 | } 128 | } 129 | } 130 | 131 | /// 132 | /// 将指定键的对象添加到缓存中,并指定过期时间 133 | /// 134 | /// 缓存键 135 | /// 缓存值 136 | /// 缓存过期时间 137 | public void Insert(string key, object data, DateTime cacheTime) 138 | { 139 | if (!string.IsNullOrWhiteSpace(key) && data != null) 140 | { 141 | //lock (cacheLocker) 142 | { 143 | _cache.Insert(key, data, cacheTime); 144 | } 145 | } 146 | } 147 | 148 | /// 149 | /// 将指定键的对象添加到缓存中,并指定过期时间 150 | /// 151 | /// 缓存键 152 | /// 缓存值 153 | /// 缓存过期时间 154 | public void Insert(string key, T data, DateTime cacheTime) 155 | { 156 | if (!string.IsNullOrWhiteSpace(key) && data != null) 157 | { 158 | //lock (cacheLocker) 159 | { 160 | _cache.Insert(key, data, cacheTime); 161 | } 162 | } 163 | } 164 | 165 | /// 166 | /// 从缓存中移除指定键的缓存值 167 | /// 168 | /// 缓存键 169 | public void Remove(string key) 170 | { 171 | if (string.IsNullOrWhiteSpace(key)) 172 | return; 173 | lock (cacheLocker) 174 | { 175 | _cache.Remove(key); 176 | } 177 | } 178 | 179 | /// 180 | /// 判断key是否存在 181 | /// 182 | public bool Exists(string key) 183 | { 184 | return _cache.Exists(key); 185 | } 186 | #endregion 187 | 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/SimCaptcha/Implement/DefaultAppChecker.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Interface; 2 | using SimCaptcha.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | // Project: SimCaptcha 7 | // https://github.com/yiyungent/SimCaptcha 8 | // Author: yiyun 9 | 10 | namespace SimCaptcha.Implement 11 | { 12 | public class DefaultAppChecker : IAppChecker 13 | { 14 | public ISimCaptchaOptions Options { get; set; } 15 | 16 | public DefaultAppChecker(SimCaptchaOptions options) 17 | { 18 | this.Options = options; 19 | } 20 | 21 | public AppCheckModel Check(string appId, string appSecret) 22 | { 23 | AppCheckModel rtnModel = new AppCheckModel(); 24 | IList appList = null; 25 | try 26 | { 27 | appList = ((SimCaptchaOptions)Options).AppList; 28 | } 29 | catch (Exception ex) 30 | { 31 | throw new Exception("DefaultAppChecker 必需使用 SimCaptchaOptions(因为这里面有AppList), 如果你需要使用AppChecker, 请自行实现 IAppChecker"); 32 | } 33 | if (appList == null) 34 | { 35 | rtnModel.Pass = false; 36 | rtnModel.Message = "appId或appSecret不正确"; 37 | return rtnModel; 38 | } 39 | bool isExist = appList.Where(m => m.AppId == appId && m.AppSecret == appSecret)?.Count() >= 1; 40 | if (!isExist) 41 | { 42 | rtnModel.Pass = false; 43 | rtnModel.Message = "appId或appSecret不正确"; 44 | return rtnModel; 45 | } 46 | rtnModel.Pass = true; 47 | rtnModel.Message = "appId和appSecret效验通过"; 48 | return rtnModel; 49 | } 50 | 51 | public AppCheckModel CheckAppId(string appId) 52 | { 53 | AppCheckModel rtnModel = new AppCheckModel(); 54 | IList appList = null; 55 | try 56 | { 57 | appList = ((SimCaptchaOptions)Options).AppList; 58 | } 59 | catch (Exception ex) 60 | { 61 | throw new Exception("DefaultAppChecker 必需使用 SimCaptchaOptions(因为这里面有AppList), 如果你需要使用AppChecker, 请自行实现 IAppChecker"); 62 | } 63 | bool isExist = appList?.Select(m => m.AppId).Contains(appId) ?? false; 64 | if (!isExist) 65 | { 66 | rtnModel.Pass = false; 67 | rtnModel.Message = "appId 不存在"; 68 | return rtnModel; 69 | } 70 | rtnModel.Pass = true; 71 | rtnModel.Message = "appId 效验通过"; 72 | return rtnModel; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/SimCaptcha/Implement/RandomCodeHanZi.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Interface; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | // Project: SimCaptcha 6 | // https://github.com/yiyungent/SimCaptcha 7 | // Author: yiyun 8 | 9 | namespace SimCaptcha.Implement 10 | { 11 | public class RandomCodeHanZi : IRandomCode 12 | { 13 | public string Create(int number) 14 | { 15 | var str = "天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔龙师火帝鸟官人皇始制文字乃服衣裳推位让国有虞陶唐吊民伐罪周发殷汤坐朝问道垂拱平章爱育黎首臣伏戎羌遐迩体率宾归王"; 16 | char[] str_char_arrary = str.ToArray(); 17 | Random rand = new Random(); 18 | HashSet hs = new HashSet(); 19 | bool randomBool = true; 20 | while (randomBool) 21 | { 22 | if (hs.Count == number) 23 | break; 24 | int rand_number = rand.Next(str_char_arrary.Length); 25 | hs.Add(str_char_arrary[rand_number].ToString()); 26 | } 27 | string code = string.Join("", hs); 28 | return code; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/IAppChecker.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Models; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Interface 7 | { 8 | public interface IAppChecker 9 | { 10 | ISimCaptchaOptions Options { get; set; } 11 | 12 | /// 13 | /// 效验 appId, appSecret 是否有效 14 | /// 15 | /// 16 | /// 17 | /// 是否有效 错误消息 18 | AppCheckModel Check(string appId, string appSecret); 19 | 20 | /// 21 | /// 效验 appId 是否有效 22 | /// 23 | /// 24 | /// 是否有效 错误消息 25 | AppCheckModel CheckAppId(string appId); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/ICache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Interface 7 | { 8 | public interface ICache 9 | { 10 | 11 | /// 12 | /// 缓存过期时间 (秒) 13 | /// 14 | int TimeOut { set; get; } 15 | /// 16 | /// 获得指定键的缓存值 17 | /// 18 | /// 缓存键 19 | /// 缓存值 20 | object Get(string key); 21 | /// 22 | /// 获得指定键的缓存值 23 | /// 24 | T Get(string key); 25 | /// 26 | /// 从缓存中移除指定键的缓存值 27 | /// 28 | /// 缓存键 29 | void Remove(string key); 30 | /// 31 | /// 清空所有缓存对象 32 | /// 33 | //void Clear(); 34 | /// 35 | /// 将指定键的对象添加到缓存中 36 | /// 37 | /// 缓存键 38 | /// 缓存值 39 | void Insert(string key, object data); 40 | /// 41 | /// 将指定键的对象添加到缓存中 42 | /// 43 | /// 缓存键 44 | /// 缓存值 45 | void Insert(string key, T data); 46 | /// 47 | /// 将指定键的对象添加到缓存中,并指定过期时间 48 | /// 49 | /// 缓存键 50 | /// 缓存值 51 | /// 缓存过期时间(秒钟) 52 | void Insert(string key, object data, int cacheTime); 53 | 54 | /// 55 | /// 将指定键的对象添加到缓存中,并指定过期时间 56 | /// 57 | /// 缓存键 58 | /// 缓存值 59 | /// 缓存过期时间(秒钟) 60 | void Insert(string key, T data, int cacheTime); 61 | /// 62 | /// 将指定键的对象添加到缓存中,并指定过期时间 63 | /// 64 | /// 缓存键 65 | /// 缓存值 66 | /// 缓存过期时间 67 | void Insert(string key, object data, DateTime cacheTime); 68 | /// 69 | /// 将指定键的对象添加到缓存中,并指定过期时间 70 | /// 71 | /// 缓存键 72 | /// 缓存值 73 | /// 缓存过期时间 74 | void Insert(string key, T data, DateTime cacheTime); 75 | /// 76 | /// 判断key是否存在 77 | /// 78 | bool Exists(string key); 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/ICacheHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Interface 7 | { 8 | public interface ICacheHelper 9 | { 10 | ICache GetCache(); 11 | 12 | /// 13 | /// 缓存过期时间 14 | /// 15 | int TimeOut { get; set; } 16 | 17 | /// 18 | /// 获得指定键的缓存值 19 | /// 20 | /// 缓存键 21 | /// 缓存值 22 | object Get(string key); 23 | 24 | /// 25 | /// 获得指定键的缓存值 26 | /// 27 | /// 缓存键 28 | /// 缓存值 29 | T Get(string key); 30 | 31 | /// 32 | /// 将指定键的对象添加到缓存中 33 | /// 34 | /// 缓存键 35 | /// 缓存值 36 | void Insert(string key, object data); 37 | 38 | /// 39 | /// 将指定键的对象添加到缓存中 40 | /// 41 | /// 缓存键 42 | /// 缓存值 43 | void Insert(string key, T data); 44 | 45 | /// 46 | /// 将指定键的对象添加到缓存中,并指定过期时间 47 | /// 48 | /// 缓存键 49 | /// 缓存值 50 | /// 缓存过期时间(分钟) 51 | void Insert(string key, object data, int cacheTime); 52 | 53 | /// 54 | /// 将指定键的对象添加到缓存中,并指定过期时间 55 | /// 56 | /// 缓存键 57 | /// 缓存值 58 | /// 缓存过期时间(分钟) 59 | void Insert(string key, T data, int cacheTime); 60 | 61 | /// 62 | /// 将指定键的对象添加到缓存中,并指定过期时间 63 | /// 64 | /// 缓存键 65 | /// 缓存值 66 | /// 缓存过期时间 67 | void Insert(string key, object data, DateTime cacheTime); 68 | 69 | /// 70 | /// 将指定键的对象添加到缓存中,并指定过期时间 71 | /// 72 | /// 缓存键 73 | /// 缓存值 74 | /// 缓存过期时间 75 | void Insert(string key, T data, DateTime cacheTime); 76 | 77 | /// 78 | /// 从缓存中移除指定键的缓存值 79 | /// 80 | /// 缓存键 81 | void Remove(string key); 82 | 83 | /// 84 | /// 判断key是否存在 85 | /// 86 | bool Exists(string key); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/IEncryptHelper.cs: -------------------------------------------------------------------------------- 1 | // Project: SimCaptcha 2 | // https://github.com/yiyungent/SimCaptcha 3 | // Author: yiyun 4 | 5 | namespace SimCaptcha.Interface 6 | { 7 | public interface IEncryptHelper 8 | { 9 | string Encrypt(string toEncrypt, string key, params string[] others); 10 | 11 | string Decrypt(string toDecrypt, string key, params string[] others); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/IJsonHelper.cs: -------------------------------------------------------------------------------- 1 | // Project: SimCaptcha 2 | // https://github.com/yiyungent/SimCaptcha 3 | // Author: yiyun 4 | 5 | namespace SimCaptcha.Interface 6 | { 7 | public interface IJsonHelper 8 | { 9 | /// 10 | /// 将对象转化为json字符串 11 | /// 12 | /// 对象 13 | string Serialize(object jsonObj); 14 | 15 | /// 16 | /// 将json字符串还原为目标对象 17 | /// 18 | /// json字符串 19 | T Deserialize(string jsonStr); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/ILogHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SimCaptcha.Interface 7 | { 8 | /// 9 | /// 错误日志记录(非必需,可能为null) 10 | /// 11 | public interface ILogHelper 12 | { 13 | void Write(string message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/IRandomCode.cs: -------------------------------------------------------------------------------- 1 | // Project: SimCaptcha 2 | // https://github.com/yiyungent/SimCaptcha 3 | // Author: yiyun 4 | 5 | namespace SimCaptcha.Interface 6 | { 7 | public interface IRandomCode 8 | { 9 | string Create(int number); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/ISimCaptchaOptions.cs: -------------------------------------------------------------------------------- 1 | // Project: SimCaptcha 2 | // https://github.com/yiyungent/SimCaptcha 3 | // Author: yiyun 4 | 5 | namespace SimCaptcha.Interface 6 | { 7 | public interface ISimCaptchaOptions 8 | { 9 | string AppId { get; set; } 10 | 11 | string AppSecret { get; set; } 12 | 13 | string TicketVerifyUrl { get; set; } 14 | 15 | 16 | // ↑以上仅供业务后台使用 17 | // ↓以下仅供验证码服务端使用 18 | 19 | 20 | string EncryptKey { get; set; } 21 | 22 | /// 23 | /// 允许的错误次数,例如允许2次,那么错误2次后,还可以再次尝试check,但第3次错误后就不能再尝试了 24 | /// 25 | int AllowErrorNum { get; set; } 26 | 27 | /// 28 | /// 验证码在被创建多少秒后过期 29 | /// ticket在被创建多少秒后过期 30 | /// 均使用此属性 31 | /// 32 | int ExpiredSec { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SimCaptcha/Interface/IVCodeImage.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Models; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Interface 7 | { 8 | public interface IVCodeImage 9 | { 10 | VCodeImgModel Create(string code, int width, int height); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/AppCheckModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | // Project: SimCaptcha 6 | // https://github.com/yiyungent/SimCaptcha 7 | // Author: yiyun 8 | 9 | namespace SimCaptcha.Models 10 | { 11 | public class AppCheckModel 12 | { 13 | public bool Pass { get; set; } 14 | 15 | public string Message { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/PointPosModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | // Project: SimCaptcha 5 | // https://github.com/yiyungent/SimCaptcha 6 | // Author: yiyun 7 | 8 | namespace SimCaptcha.Models 9 | { 10 | public class PointPosModel 11 | { 12 | public int X { get; set; } 13 | 14 | public int Y { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/TicketModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | // Project: SimCaptcha 5 | // https://github.com/yiyungent/SimCaptcha 6 | // Author: yiyun 7 | 8 | namespace SimCaptcha.Models 9 | { 10 | public class TicketModel 11 | { 12 | /// 13 | /// 是否验证通过 14 | /// 15 | public bool IsPass { get; set; } 16 | /// 17 | /// 下发票据时间(创建时间) 18 | /// js 13位 毫秒时间戳 19 | /// 20 | public long TS { get; set; } 21 | 22 | /// 23 | /// 下发给目标客户端的IP地址 24 | /// 25 | public string IP { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/TicketVerifyModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | // Project: SimCaptcha 5 | // https://github.com/yiyungent/SimCaptcha 6 | // Author: yiyun 7 | 8 | namespace SimCaptcha.Models 9 | { 10 | public class TicketVerifyModel 11 | { 12 | // string appId, string appSecret, string ticket, string userId, string userIp 13 | 14 | public string AppId { get; set; } 15 | 16 | public string AppSecret { get; set; } 17 | 18 | public string Ticket { get; set; } 19 | 20 | public string UserId { get; set; } 21 | 22 | public string UserIp { get; set; } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/TicketVerifyResponseModel.cs: -------------------------------------------------------------------------------- 1 | // Project: SimCaptcha 2 | // https://github.com/yiyungent/SimCaptcha 3 | // Author: yiyun 4 | 5 | namespace SimCaptcha.Models 6 | { 7 | public class TicketVerifyResponseModel 8 | { 9 | /// 10 | /// 0 OK. 验证通过 11 | /// -1 captcha no match 验证不通过 12 | /// -2 verify ticket timeout. 验证码ticket过期 13 | /// -3 verify ip no match. ip不匹配 14 | /// -4 decrypt fail. 验证码ticket解密失败 15 | /// -5 验证码ticket无效, 可能已被使用过一次以至于被删除,或则过期被自动删除 16 | /// -6 验证码ticket无效, 篡改ticket, 与验证码服务端保存的此会话ticket不一致 17 | /// -7 AppId,AppSecret效验不通过 18 | /// 19 | public int code { get; set; } 20 | 21 | public string message { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/VCodeCheckResponseModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | // Project: SimCaptcha 5 | // https://github.com/yiyungent/SimCaptcha 6 | // Author: yiyun 7 | 8 | namespace SimCaptcha.Models 9 | { 10 | public class VCodeCheckResponseModel 11 | { 12 | /// 13 | /// 0 正确 -> 下发票据 ticket 14 | /// -1 错误 -> 验证码错误 且 错误次数未达上限 -> message: 点错啦,请重试 15 | /// -2 错误 -> 验证码错误 且 错误次数已达上限 -> message: 这题有点难,为你换一个试试吧 16 | /// -3 错误 -> 验证码无效(被篡改导致解密失败), 获取新验证码 17 | /// -4 错误 -> 验证码无效(过期)(Cache里有,但它标识已过期), 获取新验证码 18 | /// -5 错误 -> 验证码无效(过期)(Cache无此vCodeKey,可能过期被自动删除), 获取新验证码 19 | /// -6 错误 -> appId 效验不通过 -> 不允许验证, 提示错误信息 20 | /// 21 | public int code { get; set; } 22 | 23 | public string message { get; set; } 24 | 25 | /// 26 | /// 第一个string 27 | /// 28 | public DataModel data { get; set; } 29 | 30 | public class DataModel 31 | { 32 | public string appId { get; set; } 33 | 34 | /// 35 | /// (先被json字符串化,再被AES加密后的) 票据字符串 36 | /// 37 | public string ticket { get; set; } 38 | 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/VCodeImgModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Models 7 | { 8 | public class VCodeImgModel 9 | { 10 | /// 11 | /// 图片的base64 字符串 12 | /// 13 | public string ImgBase64 { get; set; } 14 | 15 | /// 16 | /// 验证提示: eg: 请依次点击 望,我,哈,他 17 | /// 18 | public string VCodeTip { get; set; } 19 | 20 | 21 | 22 | /// 23 | /// 答案: 字 (有顺序) eg: 望,我,哈,他 24 | /// 可以为null,目前前端只用到了 VCodeTip 25 | /// 26 | public IList Words { get; set; } 27 | 28 | /// 29 | /// 答案: 点触位置数据 30 | /// 31 | public IList VCodePos { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/VCodeKeyModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Models 7 | { 8 | /// 9 | /// 存储正确的点触验证位置数据等 10 | /// 11 | public class VCodeKeyModel 12 | { 13 | /// 14 | /// 正确的 验证码点触位置数据 15 | /// 用于效验 用户点触位置 16 | /// 17 | public IList VCodePos { get; set; } 18 | 19 | /// 20 | /// 验证码创建时间 21 | /// js 13位 毫秒时间戳 22 | /// 23 | public long TS { get; set; } 24 | 25 | /// 26 | /// 已经错误次数 27 | /// 28 | public int ErrorNum { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/VCodeResponseModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Models 7 | { 8 | public class VCodeResponseModel 9 | { 10 | /// 11 | /// 0 成功 12 | /// 13 | public int code { get; set; } 14 | 15 | public string message { get; set; } 16 | 17 | public DataModel data { get; set; } 18 | 19 | public class DataModel 20 | { 21 | /// 22 | /// 用户此次会话唯一标识 23 | /// 24 | public string userId { get; set; } 25 | 26 | /// 27 | /// 验证图片base64 28 | /// 29 | public string vCodeImg { get; set; } 30 | 31 | /// 32 | /// 验证提示 33 | /// 34 | public string vCodeTip { get; set; } 35 | 36 | /// 37 | /// 答案: 字(有顺序 eg: 望,我,哈,他), 也可以为空, 目前前端只用vCodeTip,无用,保留 38 | /// 39 | public IList words { get; set; } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SimCaptcha/Models/VerifyInfoModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | // Project: SimCaptcha 3 | // https://github.com/yiyungent/SimCaptcha 4 | // Author: yiyun 5 | 6 | namespace SimCaptcha.Models 7 | { 8 | /// 9 | /// verifyInfo = { vcodePos: vCodePos, ua: navigator.userAgent, ts: ts } 10 | /// 11 | public class VerifyInfoModel 12 | { 13 | public string AppId { get; set; } 14 | 15 | public IList VCodePos { get; set; } 16 | 17 | /// 18 | /// 用户会话唯一标识 19 | /// 20 | public string UserId { get; set; } 21 | 22 | /// 23 | /// 未用,保留 24 | /// 25 | public string UA { get; set; } 26 | 27 | /// 28 | /// js 13位 毫秒时间戳 29 | /// 未用,保留 30 | /// 31 | public long TS { get; set; } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/SimCaptcha/SimCaptcha.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net40;net45;netstandard2.0;netcoreapp3.0;netcoreapp3.1 5 | 0.2.0 6 | yiyun 7 | yiyun 8 | 一个简单易用的点触验证码, 包含客户端, 服务端实现 9 | Copyright (c) 2020 yiyun 10 | https://github.com/yiyungent/SimCaptcha 11 | https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE 12 | Captcha SimCaptcha 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | bin\Release\net40\SimCaptcha.xml 24 | 25 | 26 | bin\Release\net45\SimCaptcha.xml 27 | 28 | 29 | bin\Release\netcoreapp3.0\SimCaptcha.xml 30 | 31 | 32 | bin\Release\netcoreapp3.1\SimCaptcha.xml 33 | 34 | 35 | 36 | NETFULL40 37 | 38 | 39 | NETFULL45 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/SimCaptcha/SimCaptchaClient.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Common; 2 | using SimCaptcha.Interface; 3 | using SimCaptcha.Models; 4 | using System; 5 | // Project: SimCaptcha 6 | // https://github.com/yiyungent/SimCaptcha 7 | // Author: yiyun 8 | 9 | namespace SimCaptcha 10 | { 11 | /// 12 | /// 验证码客户端 13 | /// 14 | public class SimCaptchaClient 15 | { 16 | #region Fields 17 | private ISimCaptchaOptions _options; 18 | 19 | /// 20 | /// 错误日志记录(非必需,可能为null) 21 | /// 22 | private ILogHelper _logHelper; 23 | #endregion 24 | 25 | #region Properties 26 | public IJsonHelper JsonHelper { get; set; } 27 | #endregion 28 | 29 | #region Ctor 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 验证码服务端提供的验证ticket的url 36 | public SimCaptchaClient(ISimCaptchaOptions options, IJsonHelper jsonHelper, ILogHelper logHelper) 37 | { 38 | this._options = options; 39 | this.JsonHelper = jsonHelper; 40 | this._logHelper = logHelper; 41 | } 42 | #endregion 43 | 44 | #region Set 45 | public SimCaptchaClient Set(ISimCaptchaOptions options) 46 | { 47 | this._options = options; 48 | return this; 49 | } 50 | 51 | public SimCaptchaClient Set(IJsonHelper jsonHelper) 52 | { 53 | this.JsonHelper = jsonHelper; 54 | return this; 55 | } 56 | 57 | public SimCaptchaClient Set(ILogHelper logHelper) 58 | { 59 | this._logHelper = logHelper; 60 | return this; 61 | } 62 | #endregion 63 | 64 | 65 | 66 | #region 效验票据有效性 67 | /// 68 | /// 效验票据有效性 69 | /// 70 | /// 验证码客户端验证回调的票据 71 | /// 用户会话唯一标识 72 | /// 提交验证的用户的IP地址(eg: 10.127.10.2) 73 | /// 74 | public TicketVerifyResponseModel Verify(string ticket, string userId, string userIp) 75 | { 76 | TicketVerifyResponseModel ticketVerifyModel = new TicketVerifyResponseModel { code = -1, message = "效验失败" }; 77 | string reqJsonStr = JsonHelper.Serialize(new { appId = _options.AppId, appSecret = _options.AppSecret, ticket, userId, userIp }); 78 | //string reqStr = $"appId={_options.AppId}&appSecret={_options.AppSecret}&ticket={ticket}&userId={userId}&userIp={userIp}"; 79 | // 效验票据 80 | try 81 | { 82 | string[] headers = { "Content-Type: application/json" }; 83 | //string[] headers = { "Content-Type: application/x-www-form-urlencoded" }; 84 | string resJsonStr = HttpAide.HttpPost(_options.TicketVerifyUrl, postDataStr: reqJsonStr, headers: headers); 85 | //string resJsonStr = HttpAide.HttpPost(_options.TicketVerifyUrl, postDataStr: reqStr); 86 | ticketVerifyModel = JsonHelper.Deserialize(resJsonStr); 87 | } 88 | catch (Exception ex) 89 | { 90 | this._logHelper?.Write(ex.ToString()); 91 | } 92 | 93 | return ticketVerifyModel; 94 | } 95 | #endregion 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/SimCaptcha/SimCaptchaOptions.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Interface; 2 | using System.Collections.Generic; 3 | // Project: SimCaptcha 4 | // https://github.com/yiyungent/SimCaptcha 5 | // Author: yiyun 6 | 7 | namespace SimCaptcha 8 | { 9 | public class SimCaptchaOptions : ISimCaptchaOptions 10 | { 11 | public const string SimCaptcha = "SimCaptcha"; 12 | 13 | public string AppId { get; set; } 14 | 15 | public string AppSecret { get; set; } 16 | 17 | public string TicketVerifyUrl { get; set; } 18 | 19 | 20 | // ↑以上仅供业务后台使用 21 | // ↓以下仅供验证码服务端使用 22 | 23 | 24 | public string EncryptKey { get; set; } 25 | 26 | /// 27 | /// 允许的错误次数,例如允许2次,那么错误2次后,还可以再次尝试check,但第3次错误后就不能再尝试了 28 | /// 29 | public int AllowErrorNum { get; set; } 30 | 31 | /// 32 | /// 验证码在被创建多少秒后过期 33 | /// ticket在被创建多少秒后过期 34 | /// 均使用此属性 35 | /// 36 | public int ExpiredSec { get; set; } 37 | 38 | public IList AppList { get; set; } 39 | 40 | #region Ctor 41 | public SimCaptchaOptions() 42 | { 43 | // 初始默认值 44 | this.AllowErrorNum = 1; 45 | this.ExpiredSec = 60; 46 | this.AppList = new List(); 47 | } 48 | #endregion 49 | 50 | } 51 | 52 | public class AppItemModel 53 | { 54 | public string AppId { get; set; } 55 | public string AppSecret { get; set; } 56 | 57 | /// 58 | /// 允许跨域的白名单 59 | /// 60 | public List CorsWhiteList { get; set; } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/SimCaptcha/SimCaptchaService.cs: -------------------------------------------------------------------------------- 1 | using SimCaptcha.Common; 2 | using SimCaptcha.Implement; 3 | using SimCaptcha.Interface; 4 | using SimCaptcha.Models; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | // Project: SimCaptcha 11 | // https://github.com/yiyungent/SimCaptcha 12 | // Author: yiyun 13 | 14 | namespace SimCaptcha 15 | { 16 | /// 17 | /// 验证码服务端 18 | /// 19 | public class SimCaptchaService 20 | { 21 | #region Const 22 | private const string CachePrefixTicket = "Cache:SimCaptcha:Ticket:"; 23 | 24 | private const string CachePrefixVCodeKey = "Cache:SimCaptcha:VCodeKey:"; 25 | #endregion 26 | 27 | #region Fields 28 | private ISimCaptchaOptions _options; 29 | 30 | private ICacheHelper _cacheHelper; 31 | 32 | private IEncryptHelper _encryptHelper; 33 | 34 | private ILogHelper _logHelper; 35 | #endregion 36 | 37 | #region Properties 38 | public IRandomCode RandomCode { get; set; } 39 | 40 | public IVCodeImage VCodeImage { get; set; } 41 | 42 | public IJsonHelper JsonHelper { get; set; } 43 | 44 | public IAppChecker AppChecker { get; set; } 45 | #endregion 46 | 47 | #region Ctor 48 | public SimCaptchaService(ISimCaptchaOptions options, ICache cache, IVCodeImage vCodeImage, IJsonHelper jsonHelper, ILogHelper logHelper) 49 | { 50 | this._options = options; 51 | this._cacheHelper = new CacheHelper(cache); 52 | 53 | this.VCodeImage = vCodeImage; 54 | this.JsonHelper = jsonHelper; 55 | 56 | this.AppChecker = new DefaultAppChecker((SimCaptchaOptions)options); 57 | this.RandomCode = new RandomCodeHanZi(); 58 | this._encryptHelper = new AesEncryptHelper(); 59 | this._logHelper = logHelper; 60 | } 61 | public SimCaptchaService(ISimCaptchaOptions options, ICache cache, IVCodeImage vCodeImage, IJsonHelper jsonHelper, IRandomCode randomCode, ILogHelper logHelper) 62 | { 63 | this._options = options; 64 | this._cacheHelper = new CacheHelper(cache); 65 | 66 | this.VCodeImage = vCodeImage; 67 | this.JsonHelper = jsonHelper; 68 | 69 | this.AppChecker = new DefaultAppChecker((SimCaptchaOptions)options); 70 | this.RandomCode = randomCode; 71 | this._encryptHelper = new AesEncryptHelper(); 72 | this._logHelper = logHelper; 73 | } 74 | public SimCaptchaService(ISimCaptchaOptions options, ICacheHelper cacheHelper, IVCodeImage vCodeImage, IJsonHelper jsonHelper, IAppChecker appChecker, ILogHelper logHelper) 75 | { 76 | this._options = options; 77 | this._cacheHelper = cacheHelper; 78 | 79 | this.VCodeImage = vCodeImage; 80 | this.JsonHelper = jsonHelper; 81 | 82 | this.AppChecker = appChecker; 83 | this.RandomCode = new RandomCodeHanZi(); 84 | this._encryptHelper = new AesEncryptHelper(); 85 | this._logHelper = logHelper; 86 | } 87 | 88 | public SimCaptchaService(ISimCaptchaOptions options, ICacheHelper cacheHelper, IVCodeImage vCodeImage, IJsonHelper jsonHelper, IAppChecker appChecker, IRandomCode randomCode, ILogHelper logHelper) 89 | { 90 | this._options = options; 91 | this._cacheHelper = cacheHelper; 92 | 93 | this.VCodeImage = vCodeImage; 94 | this.JsonHelper = jsonHelper; 95 | 96 | this.AppChecker = appChecker; 97 | this.RandomCode = randomCode; 98 | this._encryptHelper = new AesEncryptHelper(); 99 | this._logHelper = logHelper; 100 | } 101 | #endregion 102 | 103 | #region Set 104 | public SimCaptchaService Set(ISimCaptchaOptions options) 105 | { 106 | this._options = options; 107 | return this; 108 | } 109 | 110 | public SimCaptchaService Set(IJsonHelper jsonHelper) 111 | { 112 | this.JsonHelper = jsonHelper; 113 | return this; 114 | } 115 | public SimCaptchaService Set(ICacheHelper cacheHelper) 116 | { 117 | this._cacheHelper = cacheHelper; 118 | return this; 119 | } 120 | public SimCaptchaService Set(IVCodeImage vCodeImage) 121 | { 122 | this.VCodeImage = vCodeImage; 123 | return this; 124 | } 125 | public SimCaptchaService Set(IAppChecker appChecker) 126 | { 127 | this.AppChecker = appChecker; 128 | return this; 129 | } 130 | public SimCaptchaService Set(IRandomCode randomCode) 131 | { 132 | this.RandomCode = randomCode; 133 | return this; 134 | } 135 | public SimCaptchaService Set(ILogHelper logHelper) 136 | { 137 | this._logHelper = logHelper; 138 | return this; 139 | } 140 | public SimCaptchaService Set(IEncryptHelper encryptHelper) 141 | { 142 | this._encryptHelper = encryptHelper; 143 | return this; 144 | } 145 | #endregion 146 | 147 | #region 验证码效验 148 | public VCodeCheckResponseModel VCodeCheck(VerifyInfoModel verifyInfo, string userIp) 149 | { 150 | VCodeCheckResponseModel rtnResult = new VCodeCheckResponseModel(); 151 | // 允许的偏移量(点触容错) 152 | int allowOffset = 10; 153 | 154 | // appId 效验: 这通常需要你自己根据业务实现 IAppChecker 155 | #region AppId效验 156 | AppCheckModel appCheckResult = AppChecker.CheckAppId(verifyInfo.AppId); 157 | if (!appCheckResult.Pass) 158 | { 159 | // -6 appId 效验不通过 -> 不允许验证, 提示错误信息 160 | rtnResult = new VCodeCheckResponseModel { code = -6, message = appCheckResult.Message }; 161 | return rtnResult; 162 | } 163 | #endregion 164 | 165 | #region 尝试从内存中取出对应的 VCodeKey 166 | // 获取此用户会话的验证码效验 vCodeKey 167 | string cacheKeyVCodeKey = CachePrefixVCodeKey + verifyInfo.UserId; 168 | if (!_cacheHelper.Exists(cacheKeyVCodeKey)) 169 | { 170 | // 验证码无效,1.此验证码已被销毁 171 | rtnResult = new VCodeCheckResponseModel { code = -5, message = "验证码过期, 获取新验证码" }; 172 | return rtnResult; 173 | } 174 | string rightVCodeKey = _cacheHelper.Get(cacheKeyVCodeKey); 175 | // AES解密 176 | string vCodeKeyJsonStr = _encryptHelper.Decrypt(rightVCodeKey, _options.EncryptKey); 177 | // json -> 对象 178 | VCodeKeyModel vCodeKeyModel = null; 179 | try 180 | { 181 | // TODO: fixed: 临时修复, 直接将全部为0的字节去除, 182 | byte[] bytes = Encoding.UTF8.GetBytes(vCodeKeyJsonStr); 183 | byte[] remove0Bytes = bytes.Where(m => m != 0).ToArray(); 184 | string remove0ByteStr = Encoding.UTF8.GetString(remove0Bytes); 185 | 186 | // 能够转换为 对象, 则说明 vCodeKey 无误, 可以使用 187 | //vCodeKeyModel = JsonHelper.Deserialize(vCodeKeyJsonStr); 188 | vCodeKeyModel = JsonHelper.Deserialize(remove0ByteStr); 189 | } 190 | catch (Exception ex) 191 | { 192 | // TODO: BUG: 经加密再解密后的jsonStr,虽然看起来一样,但发生了一点改变, 导致无法转换 193 | // '0x00' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 110. 194 | _logHelper?.Write(ex.ToString()); 195 | } 196 | if (vCodeKeyModel == null) 197 | { 198 | // 验证码无效,被篡改导致解密失败 199 | rtnResult.code = -3; 200 | rtnResult.message = "验证码无效, 获取新验证码"; 201 | return rtnResult; 202 | } 203 | #endregion 204 | 205 | #region 验证码是否过期 206 | // 验证码是否过期 207 | bool isExpired = ((DateTimeHelper.NowTimeStamp13() - vCodeKeyModel.TS) / 1000) > _options.ExpiredSec; 208 | if (isExpired) 209 | { 210 | // 验证码过期 211 | rtnResult.code = -4; 212 | rtnResult.message = "验证码过期, 获取新验证码"; 213 | RemoveCacheVCodeKey(verifyInfo.UserId); 214 | return rtnResult; 215 | } 216 | #endregion 217 | 218 | #region 效验点触位置数据 219 | // 效验点触位置数据 220 | IList rightVCodePos = vCodeKeyModel.VCodePos; 221 | IList userVCodePos = verifyInfo.VCodePos; 222 | // 验证码是否正确 223 | bool isPass = false; 224 | if (userVCodePos.Count != rightVCodePos.Count) 225 | { 226 | // 验证不通过 227 | isPass = false; 228 | } 229 | else 230 | { 231 | isPass = true; 232 | for (int i = 0; i < userVCodePos.Count; i++) 233 | { 234 | int xOffset = userVCodePos[i].X - rightVCodePos[i].X; 235 | int yOffset = userVCodePos[i].Y - rightVCodePos[i].Y; 236 | // x轴偏移量 237 | xOffset = Math.Abs(xOffset); 238 | // y轴偏移量 239 | yOffset = Math.Abs(yOffset); 240 | // 只要有一个点的任意一个轴偏移量大于allowOffset,则验证不通过 241 | if (xOffset > allowOffset || yOffset > allowOffset) 242 | { 243 | isPass = false; 244 | } 245 | } 246 | } 247 | 248 | #endregion 249 | 250 | #region 未通过->错误次数达到上限? 251 | if (!isPass) 252 | { 253 | // 本次没通过验证 -> 错误次数+1 254 | vCodeKeyModel.ErrorNum++; 255 | // 错误次数是否达上限 256 | bool isMoreThanErrorNum = vCodeKeyModel.ErrorNum > _options.AllowErrorNum; 257 | if (isMoreThanErrorNum) 258 | { 259 | // 错误 -> 2.code:-2 验证码错误 且 错误次数已达上限 -> message: 这题有点难,为你换一个试试吧 260 | rtnResult.code = -2; 261 | rtnResult.message = "这题有点难, 为你换一个试试吧"; 262 | RemoveCacheVCodeKey(verifyInfo.UserId); 263 | return rtnResult; 264 | } 265 | else 266 | { 267 | // 错误 -> 1.code:-1 验证码错误 且 错误次数未达上限 -> message: 点错啦,请重试 268 | string vCodekeyJsonStrTemp = JsonHelper.Serialize(vCodeKeyModel); 269 | // AES加密 vCodekeyJsonStrTemp 270 | string vCodeKeyStrTemp = _encryptHelper.Encrypt(vCodekeyJsonStrTemp, _options.EncryptKey); 271 | // 更新 Cache 中的 vCodeKey 272 | _cacheHelper.Insert(CachePrefixVCodeKey + verifyInfo.UserId, vCodeKeyStrTemp); 273 | 274 | rtnResult.code = -1; 275 | rtnResult.message = "点错啦,请重试"; 276 | return rtnResult; 277 | } 278 | } 279 | #endregion 280 | 281 | #region 验证通过->下发ticket 282 | // 正确 -> code:0 下发票据 ticket 283 | TicketModel ticketModel = new TicketModel { IP = userIp, IsPass = true, TS = DateTimeHelper.NowTimeStamp13() }; 284 | string ticketJsonStr = JsonHelper.Serialize(ticketModel); 285 | // 对 ticketJsonStr 加密 286 | string ticket = _encryptHelper.Encrypt(ticketJsonStr, _options.EncryptKey); 287 | // 内存中存一份ticket, 用于效验 288 | _cacheHelper.Insert(CachePrefixTicket + verifyInfo.UserId, ticket); 289 | 290 | rtnResult.code = 0; 291 | rtnResult.message = "验证通过"; 292 | rtnResult.data = new VCodeCheckResponseModel.DataModel { appId = verifyInfo.AppId, ticket = ticket }; 293 | return rtnResult; 294 | #endregion 295 | } 296 | #endregion 297 | 298 | #region ticket效验 299 | /// 300 | /// ticket效验 301 | /// 302 | /// 303 | /// 304 | /// 305 | /// 用户唯一标识 306 | /// 307 | /// 308 | /// 309 | public TicketVerifyResponseModel TicketVerify(string appId, string appSecret, string ticket, string userId, string userIp) 310 | { 311 | TicketVerifyResponseModel rtnResult = null; 312 | 313 | // appId, appSecret效验: 这通常需要你自己根据业务实现 IAppChecker 314 | #region AppId,AppSecret效验 315 | AppCheckModel appCheckResult = AppChecker.Check(appId, appSecret); 316 | if (!appCheckResult.Pass) 317 | { 318 | // -7 AppId,AppSecret效验不通过 319 | rtnResult = new TicketVerifyResponseModel { code = -7, message = appCheckResult.Message }; 320 | return rtnResult; 321 | } 322 | #endregion 323 | 324 | // 解密ticket -> 转为实体对象 325 | TicketModel ticketModel = null; 326 | try 327 | { 328 | string ticketJsonStr = _encryptHelper.Decrypt(ticket, _options.EncryptKey); 329 | 330 | // TODO: fixed: 临时修复, 直接将全部为0的字节去除, 331 | byte[] bytes = Encoding.UTF8.GetBytes(ticketJsonStr); 332 | byte[] remove0Bytes = bytes.Where(m => m != 0).ToArray(); 333 | string remove0ByteStr = Encoding.UTF8.GetString(remove0Bytes); 334 | 335 | // 能够转换为 对象, 则说明 vCodeKey 无误, 可以使用 336 | ticketModel = JsonHelper.Deserialize(remove0ByteStr); 337 | 338 | //ticketModel = JsonHelper.Deserialize(ticketJsonStr); 339 | } 340 | catch (Exception ex) 341 | { 342 | // TODO: AES加解密后多出0, 导致无法转为json对象, 和验证码效验时一样 343 | // '0x00' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 110. 344 | _logHelper?.Write(ex.Message); 345 | } 346 | if (ticketModel == null) 347 | { 348 | // ticket无效,被篡改 349 | rtnResult = new TicketVerifyResponseModel { code = -4, message = "ticket无效" }; 350 | return rtnResult; 351 | } 352 | // 从内存中取出此用户会话保存的独有Ticket,进行比对 353 | string cacheKeyTicket = CachePrefixTicket + userId; 354 | if (!_cacheHelper.Exists(cacheKeyTicket)) 355 | { 356 | // ticket无效,1.此ticket已被效验过一次,用完销毁 2.其它原因: 伪造ticket 357 | rtnResult = new TicketVerifyResponseModel { code = -5, message = "ticket无效" }; 358 | return rtnResult; 359 | } 360 | string rightTicket = _cacheHelper.Get(cacheKeyTicket).ToString(); 361 | if (ticket != rightTicket) 362 | { 363 | // ticket无效,1.篡改ticket 364 | rtnResult = new TicketVerifyResponseModel { code = -6, message = "ticket无效" }; 365 | RemoveCacheTicket(userId); 366 | return rtnResult; 367 | } 368 | if (!ticketModel.IsPass) 369 | { 370 | // ticket 标识 验证不通过 371 | rtnResult = new TicketVerifyResponseModel { code = -1, message = "验证不通过" }; 372 | RemoveCacheTicket(userId); 373 | return rtnResult; 374 | } 375 | int secOffset = (int)((DateTimeHelper.NowTimeStamp13() - ticketModel.TS) / 1000); 376 | if (secOffset > _options.ExpiredSec) 377 | { 378 | // ticket 已过期 379 | rtnResult = new TicketVerifyResponseModel { code = -2, message = "ticket过期" }; 380 | RemoveCacheTicket(userId); 381 | return rtnResult; 382 | } 383 | if (ticketModel.IP != userIp) 384 | { 385 | // ip不匹配 386 | rtnResult = new TicketVerifyResponseModel { code = -3, message = "ip不匹配" }; 387 | RemoveCacheTicket(userId); 388 | return rtnResult; 389 | } 390 | // 验证通过 391 | rtnResult = new TicketVerifyResponseModel { code = 0, message = "验证通过" }; 392 | RemoveCacheTicket(userId); 393 | 394 | return rtnResult; 395 | } 396 | #endregion 397 | 398 | #region 响应验证码,用户会话唯一标识 399 | /// 400 | /// 响应验证码,用户会话唯一标识 401 | /// 402 | /// 403 | public Task VCode() 404 | { 405 | VCodeResponseModel rtnResult = new VCodeResponseModel(); 406 | try 407 | { 408 | VCodeImgModel model = CreateVCodeImg(); 409 | string userId = Guid.NewGuid().ToString(); 410 | rtnResult.code = 0; 411 | rtnResult.message = "获取验证码成功"; 412 | rtnResult.data = new VCodeResponseModel.DataModel 413 | { 414 | userId = userId, 415 | vCodeImg = model.ImgBase64, 416 | vCodeTip = model.VCodeTip, 417 | words = model.Words 418 | }; 419 | // 生成 vCodeKey: 转为json字符串 -> AES加密 420 | string vCodekeyJsonStr = JsonHelper.Serialize(new VCodeKeyModel 421 | { 422 | ErrorNum = 0, 423 | TS = DateTimeHelper.NowTimeStamp13(), 424 | VCodePos = model.VCodePos 425 | }); 426 | string vCodeKey = _encryptHelper.Encrypt(vCodekeyJsonStr, _options.EncryptKey); 427 | // 答案 保存到 此次用户会话对应的 Cache 中 428 | _cacheHelper.Insert(CachePrefixVCodeKey + userId, vCodeKey); 429 | } 430 | catch (Exception ex) 431 | { 432 | rtnResult.code = -1; 433 | rtnResult.message = "获取验证码失败"; 434 | 435 | _logHelper?.Write(ex.ToString()); 436 | } 437 | 438 | // TODO: 在.net framework 4.0下未测试 439 | // Task 参考: https://www.cnblogs.com/yaopengfei/p/8183530.html 440 | #if NETFULL40 441 | return Task.Factory.StartNew(() => { return rtnResult; }); 442 | #else 443 | return Task.FromResult(rtnResult); 444 | #endif 445 | } 446 | #endregion 447 | 448 | 449 | 450 | 451 | #region 清除目标用户会话存在内存中的ticket 452 | /// 453 | /// 清除目标用户会话存在内存中的ticket 454 | /// 455 | /// 456 | private void RemoveCacheTicket(string userId) 457 | { 458 | if (_cacheHelper.Exists(CachePrefixTicket + userId)) 459 | { 460 | _cacheHelper.Remove(CachePrefixTicket + userId); 461 | } 462 | } 463 | #endregion 464 | 465 | #region 清除目标用户会话存在内存中的vCodeKey 466 | /// 467 | /// 清除目标用户会话存在内存中的vCodeKey 468 | /// 469 | /// 470 | private void RemoveCacheVCodeKey(string userId) 471 | { 472 | if (_cacheHelper.Exists(CachePrefixVCodeKey + userId)) 473 | { 474 | _cacheHelper.Remove(CachePrefixVCodeKey + userId); 475 | } 476 | } 477 | #endregion 478 | 479 | #region 创建验证码图片及提示,答案 480 | private VCodeImgModel CreateVCodeImg() 481 | { 482 | VCodeImgModel rtnResult = new VCodeImgModel { VCodePos = new List() }; 483 | string code = RandomCode.Create(6); 484 | rtnResult = VCodeImage.Create(code, 200, 200); 485 | 486 | return rtnResult; 487 | } 488 | #endregion 489 | 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /src/SimCaptcha/nuget-pack.ps1: -------------------------------------------------------------------------------- 1 | dotnet pack -c Release --------------------------------------------------------------------------------