From 5dc28906c2bbcccc5171fb63fc16532d158e3fa0 Mon Sep 17 00:00:00 2001 From: Perry <593281239@qq.com> Date: Mon, 13 Oct 2025 12:52:43 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E6=BC=94?= =?UTF-8?q?=E7=A4=BA=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LazyCaptcha.sln | 6 + README.md | 2 +- Sample.NetCore/.config/dotnet-tools.json | 5 + .../Controllers/CaptchaController.cs | 41 +++ Sample.NetCore/Sample.NetCore.csproj | 5 +- Sample.NetCore/wwwroot/index.css | 347 +++++++++++++++++- Sample.NetCore/wwwroot/index.html | 80 +++- Sample.NetCore/wwwroot/index.js | 144 +++++++- 8 files changed, 620 insertions(+), 10 deletions(-) create mode 100644 Sample.NetCore/.config/dotnet-tools.json diff --git a/LazyCaptcha.sln b/LazyCaptcha.sln index b27f72d..5ff646f 100644 --- a/LazyCaptcha.sln +++ b/LazyCaptcha.sln @@ -15,6 +15,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.NetCore", "Sample.Ne EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.MvcFramework", "Sample.MvcFramework\Sample.MvcFramework.csproj", "{F172A3CE-0295-40FC-B739-694EFE9A41C8}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + README_V1.md = README_V1.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/README.md b/README.md index 978dfa5..2d2f972 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ### [Deepwiki](https://deepwiki.com/pojianbing/LazyCaptcha) -### [在线演示](http://captcha.sunseeyou.com/index.html)(欠费失效) +### [在线演示](https://captcha.wosperry.com) ### 效果展示 diff --git a/Sample.NetCore/.config/dotnet-tools.json b/Sample.NetCore/.config/dotnet-tools.json new file mode 100644 index 0000000..b0e38ab --- /dev/null +++ b/Sample.NetCore/.config/dotnet-tools.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "isRoot": true, + "tools": {} +} \ No newline at end of file diff --git a/Sample.NetCore/Controllers/CaptchaController.cs b/Sample.NetCore/Controllers/CaptchaController.cs index 15970b8..a88c713 100644 --- a/Sample.NetCore/Controllers/CaptchaController.cs +++ b/Sample.NetCore/Controllers/CaptchaController.cs @@ -303,5 +303,46 @@ namespace Sample.NetCore.Controllers }); } } + + /// + /// 获取验证码配置信息 + /// + /// 验证码类型 + /// 字体 + /// 是否粗体 + /// + [HttpGet("config")] + public IActionResult GetCaptchaConfig(string type, string font, bool textBold = true) + { + var config = new + { + CaptchaType = type, + FontFamily = font, + FontSize = 26.0, + Width = 98, + Height = 35, + TextBold = textBold, + InterferenceLineCount = 2, + Animation = false, + BackgroundColor = "#00ffffff" + }; + + return Ok(config); + } + + /// + /// 演示专用:生成验证码(使用注入实例,可以正确校验) + /// 注意:此接口使用注入的ICaptcha实例,与validate接口共享同一个缓存,因此可以正确校验 + /// + /// 验证码ID + /// + [HttpGet("demo")] + public IActionResult DemoCaptcha(string id) + { + // 使用注入的_captcha实例,确保与validate方法使用同一个缓存 + var info = _captcha.Generate(id); + var stream = new MemoryStream(info.Bytes); + return File(stream, "image/gif"); + } } } \ No newline at end of file diff --git a/Sample.NetCore/Sample.NetCore.csproj b/Sample.NetCore/Sample.NetCore.csproj index ac71d9f..42dc50b 100644 --- a/Sample.NetCore/Sample.NetCore.csproj +++ b/Sample.NetCore/Sample.NetCore.csproj @@ -1,7 +1,7 @@  - net6.0 + net9.0 enable enable 8f38fd12-0eb2-4a15-992a-68e26293d7f5 @@ -22,7 +22,8 @@ - + + diff --git a/Sample.NetCore/wwwroot/index.css b/Sample.NetCore/wwwroot/index.css index bb6aee0..8378237 100644 --- a/Sample.NetCore/wwwroot/index.css +++ b/Sample.NetCore/wwwroot/index.css @@ -1,5 +1,5 @@ body{ - background-color: black; + background-color: #1e2127; } .grid { @@ -20,25 +20,362 @@ body{ flex-direction: column; align-items: center; justify-content: center; - color: white; + color: #abb2bf; font-size: 10px; - border: 1px solid #202020; + border: 1px solid #3e4451; word-break: break-word; + background: #282c34; } .grid .cell img{ border-radius: 1px; + cursor: pointer; + transition: transform 0.2s; +} + +.grid .cell img:hover{ + transform: scale(1.1); } .unsupport{ - color: #8f8f8f; + color: #5c6370; } .tip { - color: #8f8f8f; + color: #abb2bf; width: 1280px; margin: auto; margin-top: 30px; font-size: 12px; font-weight: bold; +} + +/* 遮罩层 */ +.config-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(2px); + z-index: 999; + animation: fadeIn 0.3s ease-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* 配置信息面板 - 右侧抽屉 */ +.config-panel { + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: 600px; + background: #282c34; + border-left: 2px solid #3e4451; + overflow-y: auto; + box-shadow: -4px 0 20px rgba(0, 0, 0, 0.3); + z-index: 1000; + animation: slideInRight 0.3s ease-out; +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + } + to { + transform: translateX(0); + } +} + +.config-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 25px; + background: #21252b; + border-bottom: 1px solid #3e4451; + position: sticky; + top: 0; + z-index: 10; +} + +.config-header h3 { + color: #abb2bf; + margin: 0; + font-size: 16px; + font-weight: 500; + max-width: 480px; + word-break: break-word; +} + +.close-btn { + background: transparent; + border: none; + color: #5c6370; + font-size: 32px; + cursor: pointer; + padding: 0; + width: 32px; + height: 32px; + line-height: 28px; + transition: color 0.2s; +} + +.close-btn:hover { + color: #abb2bf; +} + +.config-content { + padding: 20px 25px 30px; +} + +.config-section { + margin-bottom: 30px; +} + +.config-section h4 { + color: #61afef; + font-size: 16px; + margin: 0 0 10px 0; + font-weight: 500; +} + +.code-block { + background: #1e2127; + border: 1px solid #3e4451; + border-radius: 4px; + padding: 15px; + color: #abb2bf; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + line-height: 1.6; + overflow-x: auto; + margin: 0; + white-space: pre-wrap; + word-break: break-all; +} + +.image-preview { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 15px; + background: #1e2127; + border: 1px solid #3e4451; + border-radius: 4px; +} + +.image-preview img { + max-width: 300px; + border: 2px solid #3e4451; + border-radius: 4px; + margin-bottom: 10px; +} + +.captcha-image { + cursor: pointer; + transition: all 0.2s; +} + +.captcha-image:hover { + border-color: #98c379 !important; + transform: scale(1.05); +} + +.captcha-image:active { + transform: scale(0.98); +} + +.image-note { + color: #5c6370; + font-size: 12px; + margin: 5px 0; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.image-hint { + color: #61afef; + font-size: 11px; + margin: 5px 0 0 0; + font-style: italic; +} + +/* 验证码校验容器 */ +.validate-container { + background: #1e2127; + border: 1px solid #3e4451; + border-radius: 4px; + padding: 20px; +} + +.validate-input-group { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 15px; +} + +.validate-input-group label { + color: #abb2bf; + font-size: 14px; + font-weight: 500; +} + +.validate-input { + width: 100%; + padding: 10px 12px; + background: #282c34; + border: 1px solid #3e4451; + border-radius: 4px; + color: #abb2bf; + font-size: 14px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + transition: border-color 0.2s; + box-sizing: border-box; +} + +.validate-input:focus { + outline: none; + border-color: #61afef; +} + +.validate-input::placeholder { + color: #5c6370; +} + +.validate-buttons { + display: flex; + gap: 10px; + margin-bottom: 15px; +} + +.validate-btn { + flex: 1; + padding: 10px 20px; + border: none; + border-radius: 4px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.validate-btn-once { + background: #98c379; + color: #282c34; +} + +.validate-btn-once:hover { + background: #7cb668; + transform: translateY(-1px); +} + +.validate-btn-multi { + background: #61afef; + color: #282c34; +} + +.validate-btn-multi:hover { + background: #528bdb; + transform: translateY(-1px); +} + +/* 验证结果显示 */ +.validate-result { + margin-top: 20px; + border: 1px solid #3e4451; + border-radius: 4px; + overflow: hidden; + animation: slideDown 0.3s ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.result-header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 15px; + font-size: 16px; + font-weight: 600; +} + +.result-header.success { + background: rgba(152, 195, 121, 0.15); + color: #98c379; + border-bottom: 1px solid rgba(152, 195, 121, 0.3); +} + +.result-header.error { + background: rgba(224, 108, 117, 0.15); + color: #e06c75; + border-bottom: 1px solid rgba(224, 108, 117, 0.3); +} + +.result-icon { + font-size: 20px; + font-weight: bold; +} + +.result-details { + padding: 15px; + background: #282c34; +} + +.result-details p { + margin: 0 0 10px 0; + color: #abb2bf; + font-size: 13px; + line-height: 1.6; +} + +.result-details p:last-child { + margin-bottom: 0; +} + +.result-details strong { + color: #61afef; + font-weight: 500; +} + +.result-note { + margin-top: 15px !important; + padding: 10px 12px; + background: rgba(97, 175, 239, 0.1); + border-left: 3px solid #61afef; + color: #61afef !important; + font-size: 12px !important; + line-height: 1.5 !important; +} + +/* 响应式调整 */ +@media (max-width: 1400px) { + .config-panel { + width: 500px; + } +} + +@media (max-width: 768px) { + .config-panel { + width: 100%; + max-width: 100%; + } } \ No newline at end of file diff --git a/Sample.NetCore/wwwroot/index.html b/Sample.NetCore/wwwroot/index.html index af52f8c..44664dd 100644 --- a/Sample.NetCore/wwwroot/index.html +++ b/Sample.NetCore/wwwroot/index.html @@ -23,12 +23,90 @@
{{group.type}}
- + 不支持
+ + +
+ + +
+
+

验证码配置信息 - {{selectedConfig.type}} / {{selectedConfig.font}}

+ +
+
+
+

配置信息 (Configuration)

+
{{configJson}}
+
+ +
+

cURL 请求示例

+
{{curlCommand}}
+
+ +
+

HTTP 请求原文

+
{{httpRequest}}
+
+ +
+

返回图片示例

+
+ 验证码示例 +

Content-Type: image/gif

+

验证码ID: {{captchaId}}

+

💡 点击图片可刷新验证码

+
+
+ +
+

验证码校验测试

+
+
+ + +
+
+ + +
+
+
+ {{validateResult.success ? '✓' : '✗'}} + {{validateResult.success ? '校验成功' : '校验失败'}} +
+
+

校验方式:{{validateResult.isReusable ? '可重复校验' : '一次性校验'}}

+

请求URL:{{validateResult.url}}

+

响应结果:{{validateResult.response}}

+

+ 💡 提示:一次性校验模式下,验证成功后验证码会失效,后续请求会返回 false +

+

+ 💡 提示:可重复校验模式下,验证码可以多次验证(通过设置 removeIfSuccess=false) +

+
+
+
+
+
+
diff --git a/Sample.NetCore/wwwroot/index.js b/Sample.NetCore/wwwroot/index.js index f2b4c9f..e2fadf7 100644 --- a/Sample.NetCore/wwwroot/index.js +++ b/Sample.NetCore/wwwroot/index.js @@ -46,7 +46,15 @@ new Vue({ ], fonts: ['Actionj', 'Fresnel', 'Kaiti', 'Prefix', 'Ransom', 'Scandal', 'Epilog', 'Headache', 'Lexo', 'Progbot', 'Robot'], groupCaptchas: [], - textBold: true + textBold: true, + selectedConfig: null, + configJson: '', + curlCommand: '', + httpRequest: '', + captchaImageUrl: '', + captchaId: '', + validateCode: '', + validateResult: null } }, mounted() { @@ -86,6 +94,140 @@ new Vue({ if (type === 'ARITHMETIC' && font === 'Progbot') return false return true + }, + async showConfig(captcha) { + try { + // 获取配置信息 + const response = await fetch(`captcha/config?type=${captcha.type}&font=${captcha.font}&textBold=${this.textBold}`) + const config = await response.json() + + this.selectedConfig = captcha + this.configJson = JSON.stringify(config, null, 2) + + // 生成验证码 + await this.generateCaptcha(captcha) + + } catch (error) { + console.error('获取配置失败:', error) + } + }, + async generateCaptcha(captcha) { + // 生成新的验证码URL(添加时间戳确保每次都是新图片) + const timestamp = new Date().getTime() + this.captchaId = `demo_${timestamp}` + + // 使用demo接口,它使用注入的ICaptcha实例,与validate共享缓存 + this.captchaImageUrl = `captcha/demo?id=${encodeURIComponent(this.captchaId)}&_t=${timestamp}` + + // 生成请求示例 + const baseUrl = window.location.origin + const apiUrl = `${baseUrl}/captcha/demo?id=${encodeURIComponent(this.captchaId)}` + + // cURL命令 + this.curlCommand = `curl -X GET "${apiUrl}" \\ + -H "Accept: image/gif" \\ + --output captcha.gif` + + // HTTP请求原文 + this.httpRequest = `GET /captcha/demo?id=${this.captchaId} HTTP/1.1 +Host: ${window.location.host} +Accept: image/gif +User-Agent: Mozilla/5.0 +Connection: keep-alive` + + // 添加说明信息 + this.httpRequest += ` + +注意: 此演示使用 /captcha/demo 接口 +- 类型: ${captcha.type} +- 字体: ${captcha.font} +- 粗体: ${this.textBold} +配置信息见上方 Configuration 区域` + + // 重置验证结果 + this.validateCode = '' + this.validateResult = null + + // 预加载图片,确保验证码已生成 + await this.preloadCaptchaImage() + }, + preloadCaptchaImage() { + return new Promise((resolve, reject) => { + const img = new Image() + img.onload = () => resolve() + img.onerror = () => reject(new Error('验证码图片加载失败')) + img.src = this.captchaImageUrl + }) + }, + async refreshCaptcha() { + if (!this.selectedConfig) return + try { + await this.generateCaptcha(this.selectedConfig) + } catch (error) { + console.error('刷新验证码失败:', error) + alert('刷新验证码失败,请重试') + } + }, + closeConfig() { + this.selectedConfig = null + this.configJson = '' + this.curlCommand = '' + this.httpRequest = '' + this.captchaImageUrl = '' + this.captchaId = '' + this.validateCode = '' + this.validateResult = null + }, + async validateCaptcha(isReusable) { + if (!this.validateCode) { + alert('请先输入验证码') + return + } + + try { + let apiUrl + if (isReusable) { + // 可重复校验 - 使用 validate2 接口(不删除验证码) + apiUrl = `captcha/validate2?id=${encodeURIComponent(this.captchaId)}&code=${encodeURIComponent(this.validateCode)}` + } else { + // 一次性校验 - 使用 validate 接口(验证后删除) + apiUrl = `captcha/validate?id=${encodeURIComponent(this.captchaId)}&code=${encodeURIComponent(this.validateCode)}` + } + + console.log('校验请求:', { + id: this.captchaId, + code: this.validateCode, + url: apiUrl, + isReusable: isReusable + }) + + const response = await fetch(apiUrl) + const result = await response.json() + + console.log('校验响应:', result) + + this.validateResult = { + success: result, + isReusable: isReusable, + url: window.location.origin + '/' + apiUrl, + response: JSON.stringify(result), + timestamp: new Date().toLocaleTimeString(), + requestId: this.captchaId, + requestCode: this.validateCode + } + + } catch (error) { + console.error('校验失败:', error) + this.validateResult = { + success: false, + isReusable: isReusable, + url: `captcha/validate${isReusable ? '2' : ''}`, + response: `Error: ${error.message}`, + timestamp: new Date().toLocaleTimeString(), + requestId: this.captchaId, + requestCode: this.validateCode + } + } } }, }) \ No newline at end of file -- Gitee From 6580d7007e2db478ccaf0708892238434820d7e0 Mon Sep 17 00:00:00 2001 From: Perry <593281239@qq.com> Date: Mon, 13 Oct 2025 13:04:33 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E4=BB=8B=E7=BB=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sample.NetCore/wwwroot/index.css | 143 +++++++++++++++++++++++++++++- Sample.NetCore/wwwroot/index.html | 81 +++++++++++++++++ 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/Sample.NetCore/wwwroot/index.css b/Sample.NetCore/wwwroot/index.css index 8378237..78adf57 100644 --- a/Sample.NetCore/wwwroot/index.css +++ b/Sample.NetCore/wwwroot/index.css @@ -1,5 +1,67 @@ body{ background-color: #1e2127; + margin: 0; + padding: 0; +} + +/* 项目头部 */ +.project-header { + width: 1280px; + margin: 20px auto 0; + padding: 20px 0; + border-bottom: 2px solid #3e4451; +} + +.project-title { + color: #61afef; + font-size: 28px; + font-weight: 600; + margin: 0 0 15px 0; + text-align: center; +} + +.project-links { + display: flex; + justify-content: center; + gap: 20px; +} + +.repo-link { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + border-radius: 6px; + text-decoration: none; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; +} + +.repo-link.gitee { + background: #c71d23; + color: #fff; +} + +.repo-link.gitee:hover { + background: #a91a1e; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(199, 29, 35, 0.3); +} + +.repo-link.github { + background: #24292e; + color: #fff; +} + +.repo-link.github:hover { + background: #1a1e22; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(36, 41, 46, 0.3); +} + +.link-icon { + font-size: 18px; } .grid { @@ -45,11 +107,90 @@ body{ color: #abb2bf; width: 1280px; margin: auto; - margin-top: 30px; + margin-top: 20px; font-size: 12px; font-weight: bold; } +/* 项目介绍区域 */ +.project-intro { + width: 1280px; + margin: 30px auto 50px; + padding: 30px; + background: #282c34; + border-radius: 8px; + border: 1px solid #3e4451; +} + +.intro-section { + margin-bottom: 30px; +} + +.intro-section:last-child { + margin-bottom: 0; +} + +.intro-section h2 { + color: #61afef; + font-size: 22px; + font-weight: 600; + margin: 0 0 15px 0; + padding-bottom: 10px; + border-bottom: 2px solid #3e4451; +} + +.intro-section p { + color: #abb2bf; + font-size: 14px; + line-height: 1.8; + margin: 0; +} + +.code-example { + margin-bottom: 20px; +} + +.code-example:last-child { + margin-bottom: 0; +} + +.code-example h3 { + color: #98c379; + font-size: 16px; + font-weight: 500; + margin: 0 0 10px 0; +} + +.code-example pre { + background: #1e2127; + border: 1px solid #3e4451; + border-radius: 4px; + padding: 15px; + margin: 0; + overflow-x: auto; +} + +.code-example code { + color: #abb2bf; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + line-height: 1.6; +} + +.intro-footer { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #3e4451; + text-align: center; +} + +.intro-footer p { + color: #61afef; + font-size: 14px; + margin: 0; + font-style: italic; +} + /* 遮罩层 */ .config-overlay { position: fixed; diff --git a/Sample.NetCore/wwwroot/index.html b/Sample.NetCore/wwwroot/index.html index 44664dd..040a488 100644 --- a/Sample.NetCore/wwwroot/index.html +++ b/Sample.NetCore/wwwroot/index.html @@ -9,6 +9,18 @@
+ +
+

LazyCaptcha - .NET 验证码组件

+ +
粗体 @@ -30,6 +42,75 @@
+ +
+
+

项目介绍

+

LazyCaptcha 是一个简单易用的 .NET 验证码组件,支持多种验证码类型、字体样式和图片配置。

+
+ +
+

快速开始

+
+

1. 安装 NuGet 包

+
dotnet add package Lazy.Captcha.Core
+
+ +
+

2. 配置服务(Program.cs)

+
builder.Services.AddCaptcha(builder.Configuration);
+
+ +
+

3. 注入使用

+
public class CaptchaController : Controller
+{
+    private readonly ICaptcha _captcha;
+
+    public CaptchaController(ICaptcha captcha)
+    {
+        _captcha = captcha;
+    }
+
+    // 生成验证码
+    public IActionResult Generate(string id)
+    {
+        var info = _captcha.Generate(id);
+        return File(info.Bytes, "image/gif");
+    }
+
+    // 校验验证码
+    public bool Validate(string id, string code)
+    {
+        return _captcha.Validate(id, code);
+    }
+}
+
+ +
+

4. 配置选项(appsettings.json)

+
{
+  "CaptchaOptions": {
+    "CaptchaType": 10,          // 验证码类型
+    "CodeLength": 4,            // 验证码长度
+    "ExpirySeconds": 60,        // 过期时间(秒)
+    "IgnoreCase": true,         // 忽略大小写
+    "ImageOption": {
+      "Animation": true,        // 是否启用动画
+      "FontSize": 36,           // 字体大小
+      "Width": 150,             // 宽度
+      "Height": 50              // 高度
+    }
+  }
+}
+
+
+ + +
+
-- Gitee From 694501849a6b0ba58c562f8e998443fef53ba7b6 Mon Sep 17 00:00:00 2001 From: Perry <593281239@qq.com> Date: Mon, 13 Oct 2025 13:42:53 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E5=B0=8F=E9=94=99=E8=AF=AF=EF=BC=8C=E7=82=B9=E5=BC=80?= =?UTF-8?q?=E5=90=8E=E5=B1=95=E7=A4=BA=E7=9A=84=E5=9B=BE=E7=89=87=E5=B9=B6?= =?UTF-8?q?=E9=9D=9E=E7=82=B9=E5=87=BB=E7=9A=84=E9=82=A3=E4=B8=AA=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sample.NetCore/wwwroot/index.css | 255 ++++++++++++++++++++++++++++++ Sample.NetCore/wwwroot/index.html | 14 +- Sample.NetCore/wwwroot/index.js | 49 ++++-- 3 files changed, 299 insertions(+), 19 deletions(-) diff --git a/Sample.NetCore/wwwroot/index.css b/Sample.NetCore/wwwroot/index.css index 78adf57..f675bd8 100644 --- a/Sample.NetCore/wwwroot/index.css +++ b/Sample.NetCore/wwwroot/index.css @@ -2,6 +2,202 @@ body{ background-color: #1e2127; margin: 0; padding: 0; + transition: background-color 0.3s ease; +} + +/* 深色模式(默认) */ +body.dark-mode { + background-color: #1e2127; +} + +/* 浅色模式 */ +body.light-mode { + background-color: #f5f5f5; +} + +body.light-mode .project-header { + border-bottom-color: #e0e0e0; +} + +body.light-mode .project-title { + color: #2c3e50; +} + +body.light-mode .tip { + color: #2c3e50; +} + +body.light-mode .grid .cell { + color: #2c3e50; + border-color: #e0e0e0; + background: #fff; +} + +body.light-mode .unsupport { + color: #999; +} + +body.light-mode .project-intro { + background: #fff; + border-color: #e0e0e0; +} + +body.light-mode .intro-section h2 { + color: #2c3e50; + border-bottom-color: #e0e0e0; +} + +body.light-mode .intro-section p { + color: #555; +} + +body.light-mode .code-example h3 { + color: #27ae60; +} + +body.light-mode .code-example pre { + background: #f8f8f8; + border-color: #e0e0e0; +} + +body.light-mode .code-example code { + color: #2c3e50; +} + +body.light-mode .intro-footer p { + color: #3498db; +} + +body.light-mode .config-overlay { + background: rgba(0, 0, 0, 0.3); +} + +body.light-mode .config-panel { + background: #fff; + border-left-color: #e0e0e0; +} + +body.light-mode .config-header { + background: #f8f8f8; + border-bottom-color: #e0e0e0; +} + +body.light-mode .config-header h3 { + color: #2c3e50; +} + +body.light-mode .close-btn { + color: #999; +} + +body.light-mode .close-btn:hover { + color: #333; +} + +body.light-mode .config-section h4 { + color: #3498db; +} + +body.light-mode .code-block { + background: #f8f8f8; + border-color: #e0e0e0; + color: #2c3e50; +} + +body.light-mode .image-preview { + background: #f8f8f8; + border-color: #e0e0e0; +} + +body.light-mode .image-preview img { + border-color: #e0e0e0; +} + +body.light-mode .captcha-image:hover { + border-color: #27ae60 !important; +} + +body.light-mode .image-note { + color: #999; +} + +body.light-mode .image-hint { + color: #3498db; +} + +body.light-mode .validate-container { + background: #f8f8f8; + border-color: #e0e0e0; +} + +body.light-mode .validate-input-group label { + color: #2c3e50; +} + +body.light-mode .validate-input { + background: #fff; + border-color: #e0e0e0; + color: #2c3e50; +} + +body.light-mode .validate-input:focus { + border-color: #3498db; +} + +body.light-mode .validate-input::placeholder { + color: #999; +} + +body.light-mode .validate-btn-once { + background: #27ae60; + color: #fff; +} + +body.light-mode .validate-btn-once:hover { + background: #229954; +} + +body.light-mode .validate-btn-multi { + background: #3498db; + color: #fff; +} + +body.light-mode .validate-btn-multi:hover { + background: #2980b9; +} + +body.light-mode .validate-result { + border-color: #e0e0e0; +} + +body.light-mode .result-header.success { + background: rgba(39, 174, 96, 0.1); + color: #27ae60; + border-bottom-color: rgba(39, 174, 96, 0.3); +} + +body.light-mode .result-header.error { + background: rgba(231, 76, 60, 0.1); + color: #e74c3c; + border-bottom-color: rgba(231, 76, 60, 0.3); +} + +body.light-mode .result-details { + background: #fff; +} + +body.light-mode .result-details p { + color: #555; +} + +body.light-mode .result-details strong { + color: #3498db; +} + +body.light-mode .result-note { + background: rgba(52, 152, 219, 0.1); + border-left-color: #3498db; + color: #3498db !important; } /* 项目头部 */ @@ -350,6 +546,65 @@ body{ font-style: italic; } +.image-warning { + color: #e5c07b; + font-size: 11px; + margin: 5px 0 0 0; +} + +/* 测试验证码区域 */ +.test-captcha-area { + margin-bottom: 15px; + padding: 15px; + background: #1e2127; + border: 1px solid #3e4451; + border-radius: 4px; + text-align: center; +} + +.test-note { + color: #abb2bf; + font-size: 12px; + margin: 0 0 15px 0; + line-height: 1.5; +} + +.test-captcha-image { + max-width: 200px; + border: 2px solid #3e4451; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; + display: block; + margin: 0 auto 10px; +} + +.test-captcha-image:hover { + border-color: #98c379; + transform: scale(1.05); +} + +body.light-mode .image-warning { + color: #e67e22; +} + +body.light-mode .test-captcha-area { + background: #f8f8f8; + border-color: #e0e0e0; +} + +body.light-mode .test-note { + color: #555; +} + +body.light-mode .test-captcha-image { + border-color: #e0e0e0; +} + +body.light-mode .test-captcha-image:hover { + border-color: #27ae60; +} + /* 验证码校验容器 */ .validate-container { background: #1e2127; diff --git a/Sample.NetCore/wwwroot/index.html b/Sample.NetCore/wwwroot/index.html index 040a488..e7cdf79 100644 --- a/Sample.NetCore/wwwroot/index.html +++ b/Sample.NetCore/wwwroot/index.html @@ -23,7 +23,8 @@
- 粗体 + 主题 + 粗体
@@ -137,18 +138,23 @@
-

返回图片示例

+

返回图片示例(当前配置)

验证码示例

Content-Type: image/gif

-

验证码ID: {{captchaId}}

💡 点击图片可刷新验证码

+

⚠️ 此验证码用于展示配置效果,无法校验(使用独立缓存)

-

验证码校验测试

+

验证码校验测试(使用默认配置)

+
+

以下验证码使用 appsettings.json 配置,与上方展示的验证码不同,但可以正常校验

+ 测试验证码 +

验证码ID: {{captchaId}}

+
Date: Mon, 13 Oct 2025 14:10:37 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sample.NetCore/wwwroot/index.css | 123 +++++++++++++++++++++++++++--- Sample.NetCore/wwwroot/index.html | 28 ++++--- Sample.NetCore/wwwroot/index.js | 45 +++++++---- 3 files changed, 160 insertions(+), 36 deletions(-) diff --git a/Sample.NetCore/wwwroot/index.css b/Sample.NetCore/wwwroot/index.css index f675bd8..0011d31 100644 --- a/Sample.NetCore/wwwroot/index.css +++ b/Sample.NetCore/wwwroot/index.css @@ -552,21 +552,88 @@ body.light-mode .result-note { margin: 5px 0 0 0; } -/* 测试验证码区域 */ -.test-captcha-area { - margin-bottom: 15px; +/* 验证说明区域 */ +.validate-note { padding: 15px; background: #1e2127; border: 1px solid #3e4451; border-radius: 4px; - text-align: center; + margin-bottom: 20px; } -.test-note { +.validate-note p { + margin: 0 0 10px 0; color: #abb2bf; + font-size: 13px; + line-height: 1.6; +} + +.validate-note p:last-child { + margin-bottom: 0; +} + +.validate-note code { + background: #282c34; + padding: 2px 6px; + border-radius: 3px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: 12px; - margin: 0 0 15px 0; - line-height: 1.5; + color: #98c379; +} + +.validate-note ul { + margin: 10px 0 10px 20px; + padding: 0; +} + +.validate-note li { + margin: 5px 0; + color: #abb2bf; + font-size: 13px; +} + +.architecture-note { + color: #61afef !important; + font-style: italic; + margin-top: 10px !important; +} + +/* 校验演示区域 */ +.validate-demo-section { + margin-bottom: 20px; +} + +.validate-demo-section h5 { + color: #98c379; + font-size: 14px; + margin: 0 0 10px 0; +} + +.generate-test-btn { + padding: 10px 20px; + background: #61afef; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + margin-bottom: 15px; +} + +.generate-test-btn:hover { + background: #528bdb; + transform: translateY(-1px); +} + +/* 测试验证码区域 */ +.test-captcha-area { + padding: 15px; + background: #1e2127; + border: 1px solid #3e4451; + border-radius: 4px; + text-align: center; } .test-captcha-image { @@ -584,19 +651,49 @@ body.light-mode .result-note { transform: scale(1.05); } -body.light-mode .image-warning { - color: #e67e22; +.test-config-note { + color: #e5c07b; + font-size: 11px; + margin: 5px 0 0 0; } -body.light-mode .test-captcha-area { +/* 浅色模式样式 */ +body.light-mode .validate-note { background: #f8f8f8; border-color: #e0e0e0; } -body.light-mode .test-note { +body.light-mode .validate-note p, +body.light-mode .validate-note li { color: #555; } +body.light-mode .validate-note code { + background: #e8e8e8; + color: #27ae60; +} + +body.light-mode .architecture-note { + color: #3498db !important; +} + +body.light-mode .validate-demo-section h5 { + color: #27ae60; +} + +body.light-mode .generate-test-btn { + background: #3498db; +} + +body.light-mode .generate-test-btn:hover { + background: #2980b9; +} + +body.light-mode .test-captcha-area { + background: #f8f8f8; + border-color: #e0e0e0; +} + body.light-mode .test-captcha-image { border-color: #e0e0e0; } @@ -605,6 +702,10 @@ body.light-mode .test-captcha-image:hover { border-color: #27ae60; } +body.light-mode .test-config-note { + color: #e67e22; +} + /* 验证码校验容器 */ .validate-container { background: #1e2127; diff --git a/Sample.NetCore/wwwroot/index.html b/Sample.NetCore/wwwroot/index.html index e7cdf79..a057698 100644 --- a/Sample.NetCore/wwwroot/index.html +++ b/Sample.NetCore/wwwroot/index.html @@ -138,29 +138,37 @@
-

返回图片示例(当前配置)

+

验证码示例与校验测试

验证码示例

Content-Type: image/gif

+

验证码ID: {{captchaId}}

💡 点击图片可刷新验证码

-

⚠️ 此验证码用于展示配置效果,无法校验(使用独立缓存)

-
-
-

验证码校验测试(使用默认配置)

-
-

以下验证码使用 appsettings.json 配置,与上方展示的验证码不同,但可以正常校验

- 测试验证码 -

验证码ID: {{captchaId}}

+
+

⚠️ 架构说明:

+

上方展示的验证码使用 /captcha/dynamic 接口,通过 CaptchaServiceBuilder 创建独立实例(支持自定义配置),因此无法直接校验。

+

下方提供使用注入实例的验证码用于校验测试(使用 appsettings.json 配置)。

+

💡 这是 LazyCaptcha 的架构设计:Builder方式适合灵活展示,注入方式适合业务校验。

+
+ +
+
校验功能测试(使用注入实例)
+
+ 测试验证码 +

验证码ID: {{testCaptchaId}}

+

使用 appsettings.json 配置(CodeLength=2, CaptchaType=10)

+
+
diff --git a/Sample.NetCore/wwwroot/index.js b/Sample.NetCore/wwwroot/index.js index 50d8537..3b26e3a 100644 --- a/Sample.NetCore/wwwroot/index.js +++ b/Sample.NetCore/wwwroot/index.js @@ -55,6 +55,7 @@ new Vue({ captchaImageUrl: '', testCaptchaUrl: '', captchaId: '', + testCaptchaId: '', validateCode: '', validateResult: null } @@ -137,18 +138,14 @@ new Vue({ async generateCaptcha(captcha) { // 生成新的验证码URL(添加时间戳确保每次都是新图片) const timestamp = new Date().getTime() - - // 展示区域:使用dynamic接口显示用户选择的类型和字体(仅供视觉展示) - const displayId = `display_${timestamp}` - this.captchaImageUrl = `captcha/dynamic?id=${encodeURIComponent(displayId)}&type=${captcha.type}&font=${captcha.font}&textBold=${this.textBold}&_t=${timestamp}` - - // 校验区域:使用demo接口生成可校验的验证码 this.captchaId = `demo_${timestamp}` - this.testCaptchaUrl = `captcha/demo?id=${encodeURIComponent(this.captchaId)}&_t=${timestamp}` + + // 使用dynamic接口,显示用户选择的类型和字体,同时这个ID也用于校验 + this.captchaImageUrl = `captcha/dynamic?id=${encodeURIComponent(this.captchaId)}&type=${captcha.type}&font=${captcha.font}&textBold=${this.textBold}&_t=${timestamp}` // 生成请求示例(基于dynamic接口) const baseUrl = window.location.origin - const apiUrl = `${baseUrl}/captcha/dynamic?id=${encodeURIComponent(displayId)}&type=${captcha.type}&font=${captcha.font}&textBold=${this.textBold}` + const apiUrl = `${baseUrl}/captcha/dynamic?id=${encodeURIComponent(this.captchaId)}&type=${captcha.type}&font=${captcha.font}&textBold=${this.textBold}` // cURL命令 this.curlCommand = `curl -X GET "${apiUrl}" \\ @@ -156,11 +153,21 @@ new Vue({ --output captcha.gif` // HTTP请求原文 - this.httpRequest = `GET /captcha/dynamic?id=${displayId}&type=${captcha.type}&font=${captcha.font}&textBold=${this.textBold} HTTP/1.1 + this.httpRequest = `GET /captcha/dynamic?id=${this.captchaId}&type=${captcha.type}&font=${captcha.font}&textBold=${this.textBold} HTTP/1.1 Host: ${window.location.host} Accept: image/gif User-Agent: Mozilla/5.0 -Connection: keep-alive` +Connection: keep-alive + +注意: +- 此接口使用 CaptchaServiceBuilder 创建独立实例 +- 配置参数: type=${captcha.type}, font=${captcha.font}, textBold=${this.textBold} +- 由于使用独立缓存,此验证码无法通过 /captcha/validate 接口校验 +- 如需测试校验功能,请使用注入实例的接口(见下方说明)` + + // 自动生成测试验证码(使用注入实例,可以校验) + this.testCaptchaId = `test_${timestamp}` + this.testCaptchaUrl = `captcha/demo?id=${encodeURIComponent(this.testCaptchaId)}&_t=${timestamp}` // 重置验证结果 this.validateCode = '' @@ -194,6 +201,14 @@ Connection: keep-alive` this.captchaImageUrl = '' this.testCaptchaUrl = '' this.captchaId = '' + this.testCaptchaId = '' + this.validateCode = '' + this.validateResult = null + }, + generateTestCaptcha() { + const timestamp = new Date().getTime() + this.testCaptchaId = `test_${timestamp}` + this.testCaptchaUrl = `captcha/demo?id=${encodeURIComponent(this.testCaptchaId)}&_t=${timestamp}` this.validateCode = '' this.validateResult = null }, @@ -207,14 +222,14 @@ Connection: keep-alive` let apiUrl if (isReusable) { // 可重复校验 - 使用 validate2 接口(不删除验证码) - apiUrl = `captcha/validate2?id=${encodeURIComponent(this.captchaId)}&code=${encodeURIComponent(this.validateCode)}` + apiUrl = `captcha/validate2?id=${encodeURIComponent(this.testCaptchaId)}&code=${encodeURIComponent(this.validateCode)}` } else { // 一次性校验 - 使用 validate 接口(验证后删除) - apiUrl = `captcha/validate?id=${encodeURIComponent(this.captchaId)}&code=${encodeURIComponent(this.validateCode)}` + apiUrl = `captcha/validate?id=${encodeURIComponent(this.testCaptchaId)}&code=${encodeURIComponent(this.validateCode)}` } console.log('校验请求:', { - id: this.captchaId, + id: this.testCaptchaId, code: this.validateCode, url: apiUrl, isReusable: isReusable @@ -231,7 +246,7 @@ Connection: keep-alive` url: window.location.origin + '/' + apiUrl, response: JSON.stringify(result), timestamp: new Date().toLocaleTimeString(), - requestId: this.captchaId, + requestId: this.testCaptchaId, requestCode: this.validateCode } @@ -243,7 +258,7 @@ Connection: keep-alive` url: `captcha/validate${isReusable ? '2' : ''}`, response: `Error: ${error.message}`, timestamp: new Date().toLocaleTimeString(), - requestId: this.captchaId, + requestId: this.testCaptchaId, requestCode: this.validateCode } } -- Gitee