Phase 3: 管理后台前端 (Vue3+Vite+Element Plus) - 登录页面/JWT认证/Pinia状态管理 - 首页大盘/站点管理/验证码日志/IP黑名单/套餐管理/系统设置 - 7个页面完整路由+API对接 Phase 4: 统计监控模块 - CaptchaLogAspect AOP自动记录 - RealtimeStatsService Redis实时计数 - HistoryStatsService 历史查询 - AnomalyDetectionService 异常检测 Phase 5: 部署和测试 - docker-compose.yml 5服务编排 - application-prod.yml 外部化配置 - CaptchaApiIntegrationTest 集成测试 - 前端 Dockerfile + nginx.conf
This commit is contained in:
@@ -1,101 +1,325 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<title>TianAI Captcha - 行为验证码演示</title>
|
||||
<style>
|
||||
#login-div {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
border: 1px solid #ff5d39;
|
||||
position: relative;
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 50px;
|
||||
.demo-container {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
margin: 20px auto;
|
||||
color: #ccc;
|
||||
line-height: 50px;
|
||||
text-align: left;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.input-left {
|
||||
border-right: 1px solid #ccc;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
/*margin: 0 auto;*/
|
||||
display: inline-block;
|
||||
width: 160px;
|
||||
height: 50px;
|
||||
background-color: #4BC065;
|
||||
.demo-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
line-height: 50px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.demo-header p {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.demo-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.captcha-type-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.type-btn {
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.type-btn:hover {
|
||||
border-color: #667eea;
|
||||
color: #667eea;
|
||||
background: #f8f9ff;
|
||||
}
|
||||
|
||||
.type-btn.active {
|
||||
border-color: #667eea;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.captcha-wrapper {
|
||||
position: relative;
|
||||
min-height: 340px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#captcha-box {
|
||||
position: absolute;
|
||||
left: 78px;
|
||||
top: 83px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.login-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.login-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.result-toast {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-100px);
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
z-index: 10000;
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.result-toast.show {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.result-toast.success {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.result-toast.error {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.demo-footer {
|
||||
padding: 16px 30px;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.demo-footer a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.demo-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.demo-container {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.demo-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.captcha-type-selector {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.type-btn {
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 验证码存放的div块 -->
|
||||
<div id="login-div">
|
||||
<!-- 装载验证码的DIV -->
|
||||
<div id="captcha-box"></div>
|
||||
<h1>用户登录</h1>
|
||||
<div class="input">
|
||||
<span class="input-left">用户名</span>
|
||||
xxxxx
|
||||
<div class="demo-container">
|
||||
<div class="demo-header">
|
||||
<h1>TianAI Captcha</h1>
|
||||
<p>行为验证码 SDK 演示</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-body">
|
||||
<div class="captcha-type-selector">
|
||||
<div class="type-btn active" data-type="SLIDER">滑块验证</div>
|
||||
<div class="type-btn" data-type="ROTATE">旋转验证</div>
|
||||
<div class="type-btn" data-type="CONCAT">滑动还原</div>
|
||||
<div class="type-btn" data-type="WORD_IMAGE_CLICK">文字点选</div>
|
||||
</div>
|
||||
|
||||
<div class="captcha-wrapper">
|
||||
<div id="captcha-box"></div>
|
||||
</div>
|
||||
|
||||
<div class="login-form">
|
||||
<div class="form-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" placeholder="请输入用户名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>密码</label>
|
||||
<input type="password" placeholder="请输入密码">
|
||||
</div>
|
||||
<button class="login-btn">登录</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-footer">
|
||||
Powered by <a href="https://github.com/dromara/tianai-captcha" target="_blank">TianAI Captcha</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input">
|
||||
<span class="input-left">密码</span>
|
||||
xxxxx
|
||||
</div>
|
||||
<div class="login-btn" data-type="ROTATE">登录(旋转)</div>
|
||||
<div class="login-btn" data-type="CONCAT">登录(拼接)</div>
|
||||
<div class="login-btn" data-type="WORD_IMAGE_CLICK">登录(汉字点选)</div>
|
||||
<div class="login-btn" data-type="SLIDER">登录(滑块拼图)</div>
|
||||
</div>
|
||||
|
||||
<script src="tac/js/tac.min.js"></script>
|
||||
<link href="tac/css/tac.css" rel="stylesheet">
|
||||
<script>
|
||||
let currentType = 'SLIDER';
|
||||
let captchaInstance = null;
|
||||
|
||||
function initCaptcha(type) {
|
||||
currentType = type;
|
||||
const box = document.getElementById('captcha-box');
|
||||
box.innerHTML = '';
|
||||
|
||||
<script>
|
||||
document.querySelectorAll(".login-btn").forEach(el => {
|
||||
el.addEventListener("click", e => {
|
||||
// 样式配置
|
||||
const config = {
|
||||
requestCaptchaDataUrl: "http://localhost:8080/gen?type=" + el.dataset.type,
|
||||
requestCaptchaDataUrl: `http://localhost:8080/gen?type=${type}`,
|
||||
validCaptchaUrl: "http://localhost:8080/check",
|
||||
bindEl: "#captcha-box"
|
||||
}
|
||||
// const style = {
|
||||
// logoUrl : null
|
||||
//
|
||||
// }
|
||||
bindEl: "#captcha-box",
|
||||
validSuccess: (res, c, tac) => {
|
||||
alert('验证成功!');
|
||||
},
|
||||
validFail: (res, c, tac) => {
|
||||
alert('验证失败,请重试');
|
||||
tac.reloadCaptcha();
|
||||
},
|
||||
btnRefreshFun: (el, tac) => {
|
||||
tac.reloadCaptcha();
|
||||
},
|
||||
btnCloseFun: (el, tac) => {
|
||||
tac.destroyWindow();
|
||||
}
|
||||
};
|
||||
|
||||
const captcha = new TAC(config, null);
|
||||
captcha.init();
|
||||
})
|
||||
})
|
||||
const style = {
|
||||
logoUrl: null
|
||||
};
|
||||
|
||||
window.initTAC("./tac", config, style).then(tac => {
|
||||
captchaInstance = tac;
|
||||
tac.init();
|
||||
}).catch(e => {
|
||||
console.error("初始化TAC失败", e);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
document.querySelectorAll('.type-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.type-btn').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
initCaptcha(this.dataset.type);
|
||||
});
|
||||
});
|
||||
|
||||
initCaptcha('SLIDER');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user