├── .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 |
3 |
4 | SimCaptcha
5 |
6 | > :cake: 一个简单易用的点触验证码, 包含了前端与后端实现
7 |
8 | []()
9 | [](https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE)
10 | [](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 | [](https://www.nuget.org/packages/SimCaptcha/) | [](https://www.nuget.org/packages/SimCaptcha/) |
115 | | :-------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
116 | | SimCaptcha.AspNetCore | [](https://www.nuget.org/packages/SimCaptcha.AspNetCore/) | [](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 | []()
16 | [](https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE)
17 | [](https://www.nuget.org/packages/SimCaptcha/)
18 | [](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 | []()
12 | [](https://github.com/yiyungent/SimCaptcha/blob/master/LICENSE)
13 | [](https://www.nuget.org/packages/SimCaptcha/)
14 | [](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 | []()
10 | [](https://github.com/yiyungent/sim-captcha-js/blob/master/LICENSE)
11 | [](https://www.npmjs.com/package/sim-captcha)
12 | [](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 | []()
7 | [](https://github.com/yiyungent/vue-sim-captcha/blob/master/LICENSE)
8 | [](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 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
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
\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 |
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
--------------------------------------------------------------------------------