基于Nodejs作为服务端,React作为前端框架,axios作为通讯框架,实现滑块验证

文章目录

基于Nodejs作为服务端,React作为前端框架,axios作为通讯框架,实现滑块验证

1. 为什么要自己写滑块验证

  • 之前我面试一位前端的童鞋,应聘的是高级前端开发工程师,我问他在项目中有没有实现过滑块验证,他说有,我说你怎么做的,他说有很多的现成的框架可以用,我说不用框架,现在想让你自己设计一个滑块验证,你会怎么设计,他支支吾吾好半天,大概表达这么几点:
    1. 前端实现一个容器图片和和滑块图片
    1. 拖动滑块图片,判断边界,到达制定边界则表示验证成功
  • 这听起来貌似没啥问题吧,听我接下来怎么问,我说你这验证都放前端了,那不相当于没验证,我直接模拟结果不就可以了
  • 他想了想,又说,可以在服务端设定一个坐标点(x,y)然后把前端的坐标点传过去进行比较,判断是否完成验证
  • 也貌似合理,我又问他,那你怎么保证重复验证和过期验证,或者说DOS的攻击
  • 这次他想了很久,最后告诉我说,平时框架用的多,这个真的没弄过,很诚实,但是能看出来,缺乏思考。
  • 这也是为什么我们要自己写滑块验证的根本原因,保证系统的安全性,防止DOS等安全问题
  • 那具体怎么实现一个滑块验证呢,我们来大概阐述一下思路

2. 滑块验证的整体思路

    1. 前端领取接口,告知服务端准备验证
    1. 服务端创建会话,并管理会话周期
    1. 进行DOS攻击验证(访问频率限制)
    1. 定义主图尺寸,滑块尺寸
    1. 生成主图和滑块图
    1. 生成滑块随机位置,并保存在会话里
    1. 返回会话ID和图像
    1. 前端生成生成图像和滑块
    1. 监听开始滑动,滑动,滑动结束等事件
    1. 滑动事件结束后,请求服务端接口,返回会话ID和位置信息
    1. 服务端验证会话信息和DOS攻击处理
    1. 服务端验证是否完整滑块验证/成功->返回suc->删除会话/失败->返回fail
    1. 前端验证成功/失败的逻辑业务

3. 具体实现

3.1 服务端

  • 涉及到的npm包请自行install
js 复制代码
// 导入所需的依赖库
// express:用于创建Web服务器和处理HTTP请求
const express = require('express');
// cors:用于处理跨域资源共享
const cors = require('cors');
// canvas:用于生成验证图像
const canvas = require('canvas');
// uuid:用于生成唯一的会话ID
const { v4: uuidv4 } = require('uuid');
// express-rate-limit:用于限制请求频率,防止恶意攻击
const rateLimit = require('express-rate-limit');

// 创建express应用实例
const app = express();

// 启用CORS中间件,允许跨域请求
app.use(cors());

// 启用JSON解析中间件,用于解析请求体中的JSON数据
app.use(express.json());

// 配置请求频率限制
// 针对生成验证图像的接口,限制更严格
const generateVerificationLimiter = rateLimit({
  windowMs: 60 * 1000, // 时间窗口:1分钟
  max: 10, // 每个IP在时间窗口内最多允许10次请求
  message: { success: false, message: '请求过于频繁,请1分钟后再试' },
  standardHeaders: true, // 向客户端返回速率限制信息
  legacyHeaders: false, // 禁用旧版速率限制头
  keyGenerator: (req) => {
    // 使用客户端IP作为限制的键
    return req.ip;
  }
});

// 针对验证结果的接口,限制相对宽松一些
const verifyLimiter = rateLimit({
  windowMs: 60 * 1000, // 时间窗口:1分钟
  max: 20, // 每个IP在时间窗口内最多允许20次请求
  message: { success: false, message: '验证请求过于频繁,请1分钟后再试' },
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => {
    return req.ip;
  }
});

// 创建一个Map对象存储验证会话信息
// key: 会话ID,value: 包含缺口位置和时间戳的对象
const verificationSessions = new Map();

// 创建一个Map对象存储IP地址的验证成功记录,用于进一步限制
const ipVerificationSuccess = new Map();

// 定期清理过期的IP验证记录(每小时执行一次)
setInterval(() => {
  const now = Date.now();
  const oneHour = 60 * 60 * 1000; // 1小时的毫秒数
  
  ipVerificationSuccess.forEach((record, ip) => {
    // 清理超过1小时的记录
    if (now - record.timestamp > oneHour) {
      ipVerificationSuccess.delete(ip);
    }
  });
}, 60 * 60 * 1000);

// 定义GET接口,用于生成滑块验证图像
// 应用请求频率限制中间件
app.get('/api/generate-verification', generateVerificationLimiter, async (req, res) => {
  try {
    // 获取客户端IP
    const clientIp = req.ip;
    
    // 检查该IP最近的验证成功次数,若过多则进一步限制
    const successRecord = ipVerificationSuccess.get(clientIp) || { count: 0, timestamp: Date.now() };
    if (successRecord.count > 50) { // 1小时内超过50次验证成功,可能是自动化程序
      return res.status(429).json({
        success: false,
        message: '您的操作过于频繁,请稍后再试'
      });
    }
    
    // 定义验证图像的尺寸参数
    // 主图像宽度
    const width = 300;
    // 主图像高度
    const height = 150;
    // 滑块(拼图)的尺寸
    const puzzleSize = 40;
    
    // 随机生成缺口位置(确保在图像范围内)
    // 水平位置:确保滑块不会超出图像左侧和右侧边界
    const puzzleX = Math.floor(Math.random() * (width - puzzleSize * 2)) + puzzleSize;
    // 垂直位置:确保滑块不会超出图像顶部和底部边界
    const puzzleY = Math.floor(Math.random() * (height - puzzleSize));
    
    // 创建主画布(带缺口的背景图)
    const mainCanvas = canvas.createCanvas(width, height);
    // 获取2D绘图上下文,用于绘制图像
    const mainCtx = mainCanvas.getContext('2d');
    
    // 绘制背景色(浅灰色)
    mainCtx.fillStyle = '#f0f0f0';
    // 填充整个画布
    mainCtx.fillRect(0, 0, width, height);
    
    // 绘制一些随机形状作为背景干扰元素,增加验证难度
    // 循环绘制10个随机圆形
    for (let i = 0; i < 10; i++) {
      // 随机生成一个柔和的颜色(RGB值在100-200之间,透明度0.5)
      mainCtx.fillStyle = `rgba(${Math.random() * 100 + 100}, ${Math.random() * 100 + 100}, ${Math.random() * 100 + 100}, 0.5)`;
      // 随机生成圆的大小(5-25像素)
      const size = Math.random() * 20 + 5;
      // 开始绘制路径
      mainCtx.beginPath();
      // 绘制圆形
      mainCtx.arc(
        // 随机X坐标
        Math.random() * width,
        // 随机Y坐标
        Math.random() * height,
        // 半径
        size,
        // 起始角度(0弧度)
        0,
        // 结束角度(2π弧度,即360度)
        Math.PI * 2
      );
      // 填充圆形
      mainCtx.fill();
    }
    
    // 创建滑块(拼图)画布,用于绘制需要用户拖动的部分
    const puzzleCanvas = canvas.createCanvas(puzzleSize, puzzleSize);
    // 获取滑块画布的2D绘图上下文
    const puzzleCtx = puzzleCanvas.getContext('2d');
    
    // 从主画布复制缺口区域到滑块画布
    // 这样滑块就包含了缺口处的图像内容
    puzzleCtx.drawImage(
      mainCanvas,          // 源图像(主画布)
      puzzleX, puzzleY,    // 源图像中要复制的区域的左上角坐标
      puzzleSize, puzzleSize,  // 源图像中要复制的区域的宽度和高度
      0, 0,                // 目标画布(滑块画布)中放置图像的左上角坐标
      puzzleSize, puzzleSize   // 目标画布中图像的宽度和高度
    );
    
    // 在主画布上绘制缺口(挖空效果)
    // 使用背景色填充缺口区域,造成"缺失"的效果
    mainCtx.fillStyle = '#f0f0f0';
    mainCtx.fillRect(puzzleX, puzzleY, puzzleSize, puzzleSize);
    
    // 为缺口添加边框,使其更明显
    mainCtx.strokeStyle = '#ccc';  // 边框颜色(浅灰色)
    mainCtx.lineWidth = 2;         // 边框宽度
    mainCtx.strokeRect(puzzleX, puzzleY, puzzleSize, puzzleSize);  // 绘制矩形边框
    
    // 生成唯一的会话ID,用于标识本次验证
    const sessionId = uuidv4();
    
    // 存储会话信息(缺口位置和生成时间)
    verificationSessions.set(sessionId, {
      puzzleX,          // 缺口的X坐标
      puzzleY,          // 缺口的Y坐标
      timestamp: Date.now(),  // 会话生成时间戳
      clientIp          // 记录请求的IP地址,用于额外安全检查
    });
    
    // 设置定时清理过期会话(5分钟后)
    // 防止内存泄漏和重复使用旧会话
    setTimeout(() => {
      verificationSessions.delete(sessionId);
    }, 5 * 60 * 1000);  // 5分钟 = 5 * 60 * 1000毫秒
    
    // 将生成的图像和会话信息返回给客户端
    res.json({
      sessionId,        // 会话ID
      mainImage: mainCanvas.toDataURL('image/png'),  // 主图像(带缺口)的DataURL
      puzzleImage: puzzleCanvas.toDataURL('image/png'),  // 滑块图像的DataURL
      puzzleSize        // 滑块尺寸
    });
  } catch (error) {
    // 捕获并处理异常
    console.error('生成验证图像失败:', error);
    // 向客户端返回错误信息
    res.status(500).json({ error: '生成验证图像失败' });
  }
});

// 定义POST接口,用于验证用户滑动的结果
// 应用请求频率限制中间件
app.post('/api/verify', verifyLimiter, (req, res) => {
  // 从请求体中获取会话ID和用户滑动的最终X坐标
  const { sessionId, positionX } = req.body;
  // 获取客户端IP
  const clientIp = req.ip;
  
  // 检查会话是否存在
  const session = verificationSessions.get(sessionId);
  if (!session) {
    // 如果会话不存在或已过期,返回验证失败
    return res.json({ success: false, message: '验证会话已过期,请重试' });
  }
  
  // 检查会话的IP是否与当前请求IP一致,防止会话劫持
  if (session.clientIp !== clientIp) {
    verificationSessions.delete(sessionId); // 删除可疑会话
    return res.json({ success: false, message: '验证异常,请重试' });
  }
  
  // 验证完成后移除会话(防止重复使用同一个会话进行验证)
  verificationSessions.delete(sessionId);
  
  // 检查用户滑动的位置是否在可接受范围内
  // 允许±5像素的误差,提高用户体验
  const tolerance = 5;
  const isSuccess = Math.abs(positionX - session.puzzleX) <= tolerance;
  
  // 如果验证成功,更新该IP的成功记录
  if (isSuccess) {
    const now = Date.now();
    const successRecord = ipVerificationSuccess.get(clientIp) || { count: 0, timestamp: now };
    
    // 更新记录:计数+1,更新时间戳
    ipVerificationSuccess.set(clientIp, {
      count: successRecord.count + 1,
      timestamp: now
    });
  }
  
  // 向客户端返回验证结果
  res.json({
    success: isSuccess,
    message: isSuccess ? '验证成功' : '验证失败,请重试'
  });
});

// 定义服务器监听的端口号
const PORT = process.env.PORT || 5000;

// 启动服务器
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
  console.log(`已启用请求频率限制保护`);
});

3.2 前端

  • 使用React hooks实现
  • 滑块组件SliderVerification.jsx
js 复制代码
// 导入React库和所需的钩子函数
import React, { useState, useEffect, useRef } from 'react';
// 导入axios用于发送HTTP请求
import axios from 'axios';

// 定义滑块验证组件
// onVerifySuccess: 验证成功时的回调函数
const SliderVerification = ({ onVerifySuccess }) => {
  // 状态管理 - 图像相关
  // 主图像(带缺口)的DataURL
  const [mainImage, setMainImage] = useState('');
  // 滑块(拼图)图像的DataURL
  const [puzzleImage, setPuzzleImage] = useState('');
  // 滑块的尺寸
  const [puzzleSize, setPuzzleSize] = useState(40);
  // 本次验证的会话ID
  const [sessionId, setSessionId] = useState('');
  
  // 状态管理 - 交互相关
  // 是否正在拖动滑块
  const [isDragging, setIsDragging] = useState(false);
  // 滑块当前的X坐标位置
  const [positionX, setPositionX] = useState(0);
  // 显示给用户的提示信息
  const [message, setMessage] = useState('请拖动滑块完成验证');
  // 是否正在验证过程中(等待后端响应)
  const [isVerifying, setIsVerifying] = useState(false);
  // 验证结果:null(未验证)、true(成功)、false(失败)
  const [isSuccess, setIsSuccess] = useState(null);
  
  // 创建引用,用于访问DOM元素
  // 滑块元素的引用
  const sliderRef = useRef(null);
  // 拼图元素的引用
  const puzzleRef = useRef(null);
  // 整个验证容器的引用
  const containerRef = useRef(null);
  
  // 组件挂载时初始化验证
  useEffect(() => {
    // 从后端获取验证图像
    fetchVerificationImage();
  }, []);  // 空依赖数组表示只在组件挂载时执行一次
  
  // 从后端获取验证图像的函数
  const fetchVerificationImage = async () => {
    try {
      // 更新提示信息
      setMessage('加载验证图像中...');
      
      // 向后端发送请求,获取验证图像
      const response = await axios.get('http://localhost:5000/api/generate-verification');
      
      // 从响应中提取数据
      const { sessionId, mainImage, puzzleImage, puzzleSize } = response.data;
      
      // 更新状态
      setSessionId(sessionId);         // 保存会话ID
      setMainImage(mainImage);         // 保存主图像
      setPuzzleImage(puzzleImage);     // 保存滑块图像
      setPuzzleSize(puzzleSize);       // 保存滑块尺寸
      setPositionX(0);                 // 重置滑块位置到初始位置
      setMessage('请拖动滑块完成验证'); // 重置提示信息
      setIsSuccess(null);              // 重置验证结果
    } catch (error) {
      // 处理请求失败的情况
      console.error('获取验证图像失败:', error);
      setMessage('加载验证失败,请刷新重试');
    }
  };
  
  // 处理鼠标/触摸开始事件(用户开始拖动滑块)
  const handleStart = (e) => {
    // 如果正在验证中或已经验证过,则不执行任何操作
    if (isVerifying || isSuccess !== null) return;
    
    // 设置正在拖动状态为true
    setIsDragging(true);
    // 防止拖动时选中文本或其他默认行为
    e.preventDefault();
  };
  
  // 处理鼠标/触摸移动事件(用户拖动滑块过程中)
  const handleMove = (e) => {
    // 如果不在拖动状态,则不执行任何操作
    if (!isDragging) return;
    
    // 获取容器元素的位置信息(相对于视口)
    const containerRect = containerRef.current.getBoundingClientRect();
    // 存储鼠标或触摸点的X坐标
    let clientX;
    
    // 区分鼠标事件和触摸事件,获取对应的X坐标
    if (e.type.includes('mouse')) {
      // 鼠标事件
      clientX = e.clientX;
    } else {
      // 触摸事件(取第一个触摸点)
      clientX = e.touches[0].clientX;
    }
    
    // 计算滑块相对于容器的位置
    // 减去容器左边界和滑块一半宽度,使滑块中心与鼠标/触摸点对齐
    let newPositionX = clientX - containerRect.left - puzzleSize / 2;
    
    // 限制滑块在容器范围内移动
    // 最大X坐标 = 容器宽度 - 滑块宽度
    const maxX = containerRect.width - puzzleSize;
    // 确保滑块不会超出左边界(最小0)和右边界(最大maxX)
    newPositionX = Math.max(0, Math.min(newPositionX, maxX));
    
    // 更新滑块位置状态
    setPositionX(newPositionX);
  };
  
  // 处理鼠标/触摸结束事件(用户释放滑块)
  const handleEnd = async () => {
    // 如果不在拖动状态,则不执行任何操作
    if (!isDragging) return;
    
    // 结束拖动状态
    setIsDragging(false);
    // 设置正在验证状态
    setIsVerifying(true);
    // 更新提示信息
    setMessage('验证中...');
    
    try {
      // 向后端发送验证请求,包含会话ID和滑块最终位置
      const response = await axios.post('http://localhost:5000/api/verify', {
        sessionId,    // 会话ID,用于标识本次验证
        positionX     // 滑块最终的X坐标位置
      });
      
      // 从响应中提取验证结果
      const { success, message } = response.data;
      
      // 更新状态
      setIsSuccess(success);       // 保存验证结果
      setMessage(message);         // 更新提示信息
      setIsVerifying(false);       // 结束验证状态
      
      // 如果验证成功且提供了成功回调函数,则调用回调
      if (success && onVerifySuccess) {
        onVerifySuccess();
      }
    } catch (error) {
      // 处理验证请求失败的情况
      console.error('验证失败:', error);
      setMessage('验证失败,请重试');
      setIsVerifying(false);
    }
  };
  
  // 重新加载验证图像(用户点击刷新按钮时调用)
  const handleRefresh = () => {
    fetchVerificationImage();
  };
  
  // 渲染组件UI
  return (
    <div 
      className="verification-container" 
      ref={containerRef} 
      style={{
        width: '300px',
        border: '1px solid #ddd',
        borderRadius: '8px',
        padding: '15px',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
        background: '#fff'
      }}
    >
      {/* 验证图像区域 */}
      <div 
        className="image-container" 
        style={{
          width: '100%',
          height: '150px',
          position: 'relative',  // 相对定位,使内部元素可以绝对定位
          overflow: 'hidden',    // 隐藏超出容器的部分
          borderRadius: '4px',
          marginBottom: '15px'
        }}
      >
        {/* 主图像(带缺口) */}
        {mainImage && (
          <img 
            src={mainImage} 
            alt="验证背景图,包含一个需要填充的缺口" 
            style={{ width: '100%', height: '100%', objectFit: 'cover' }}
          />
        )}
        
        {/* 滑块拼图 */}
        {puzzleImage && (
          <div 
            ref={puzzleRef}
            style={{
              position: 'absolute',  // 绝对定位,可通过left属性控制位置
              left: `${positionX}px`, // 动态设置left值,控制滑块水平位置
              top: '0',
              width: `${puzzleSize}px`,
              height: `${puzzleSize}px`,
              boxShadow: '0 0 10px rgba(0,0,0,0.3)', // 添加阴影,增强立体感
              pointerEvents: 'none'  // 使鼠标事件穿透此元素,不干扰拖动
            }}
          >
            <img 
              src={puzzleImage} 
              alt="需要拖动到缺口位置的滑块拼图" 
              style={{ width: '100%', height: '100%', objectFit: 'cover' }}
            />
          </div>
        )}
      </div>
      
      {/* 滑块区域 */}
      <div 
        className="slider-container" 
        style={{
          width: '100%',
          height: '40px',
          background: '#f0f0f0',
          borderRadius: '20px',
          position: 'relative',  // 相对定位,使内部元素可以绝对定位
          overflow: 'hidden'     // 隐藏超出容器的部分
        }}
      >
        {/* 滑块轨道进度条 */}
        <div 
          style={{
            // 根据滑块位置设置进度条宽度
            width: isDragging || isSuccess ? `${(positionX / (300 - puzzleSize)) * 100}%` : '0%',
            height: '100%',
            // 验证成功时显示绿色,否则显示蓝色
            background: isSuccess ? '#52c41a' : '#1890ff',
            // 验证成功时添加过渡动画
            transition: isSuccess ? 'width 0.3s' : 'none'
          }}
        />
        
        {/* 滑块按钮 */}
        <div
          ref={sliderRef}
          style={{
            position: 'absolute',  // 绝对定位,可通过left属性控制位置
            left: `${positionX}px`, // 动态设置left值,控制滑块水平位置
            top: '0',
            width: '40px',
            height: '40px',
            background: '#fff',
            border: '1px solid #ddd',
            borderRadius: '50%',   // 圆形滑块
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            cursor: 'pointer',
            boxShadow: '0 2px 5px rgba(0,0,0,0.2)' // 添加阴影,增强立体感
          }}
          // 鼠标事件处理
          onMouseDown={handleStart}  // 鼠标按下时开始拖动
          onMouseMove={handleMove}   // 鼠标移动时更新位置
          onMouseUp={handleEnd}      // 鼠标释放时结束拖动并验证
          onMouseLeave={handleEnd}   // 鼠标离开滑块区域时结束拖动
          // 触摸事件处理(适配移动设备)
          onTouchStart={handleStart} // 触摸开始时开始拖动
          onTouchMove={handleMove}   // 触摸移动时更新位置
          onTouchEnd={handleEnd}     // 触摸结束时结束拖动并验证
        >
          {/* 滑块图标 */}
          <svg 
            width="20" 
            height="20" 
            viewBox="0 0 24 24" 
            fill="none" 
            // 验证成功时显示绿色,否则显示蓝色
            stroke={isSuccess ? '#52c41a' : '#1890ff'} 
            strokeWidth="2"
            style={{ pointerEvents: 'none' }} // 使鼠标事件穿透图标
          >
            {/* 根据验证结果显示不同图标 */}
            {isSuccess ? (
              // 验证成功时显示对勾图标
              <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
            ) : (
              // 未验证或验证失败时显示箭头图标
              <path d="M5 12h14M12 5l7 7-7 7" />
            )}
          </svg>
        </div>
        
        {/* 滑块区域文字提示 */}
        <div style={{
          position: 'absolute',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          pointerEvents: 'none',  // 使鼠标事件穿透文字
          fontSize: '14px',
          // 验证成功时显示绿色,否则显示灰色
          color: isSuccess ? '#52c41a' : '#666'
        }}>
          {message}
        </div>
      </div>
      
      {/* 刷新按钮,用于重新加载验证图像 */}
      <button 
        onClick={handleRefresh}
        style={{
          marginTop: '10px',
          background: 'none',
          border: 'none',
          color: '#1890ff',
          cursor: 'pointer',
          fontSize: '12px',
          display: 'flex',
          alignItems: 'center',
          padding: '5px 0',
        }}
      >
        {/* 刷新图标 */}
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#1890ff" strokeWidth="2" style={{ marginRight: '5px' }}>
          <path d="M23 4v6h-6M1 20v-6h6" />
          <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
        </svg>
        刷新验证
      </button>
    </div>
  );
};

// 导出组件,供其他组件使用
export default SliderVerification;
  • 主组件App.jsx
js 复制代码
// 导入React库和useState钩子
import React, { useState } from 'react';
// 导入滑块验证组件
import SliderVerification from './SliderVerification';

// 定义应用的主组件
const App = () => {
  // 状态管理:记录用户是否已通过验证
  // 初始值为false,表示未验证
  const [isVerified, setIsVerified] = useState(false);
  
  // 处理验证成功的函数
  const handleVerificationSuccess = () => {
    // 设置验证状态为已验证
    setIsVerified(true);
    // 这里可以添加验证成功后的逻辑,如跳转到下一页、提交表单等
    // 例如:可以调用API获取用户数据,或者显示受保护的内容
  };
  
  // 渲染应用UI
  return (
    <div style={{
      // 使用flex布局使内容居中显示
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      justifyContent: 'center',
      minHeight: '100vh',  // 最小高度为视口高度,确保内容垂直居中
      background: '#f5f5f5', // 浅灰色背景
      padding: '20px'       // 内边距,防止内容贴边
    }}>
      {/* 页面标题 */}
      <h2 style={{ color: '#333', marginBottom: '30px' }}>
        滑块验证示例
      </h2>
      
      {/* 根据验证状态显示不同内容 */}
      {!isVerified ? (
        // 未验证时显示验证提示和滑块验证组件
        <div>
          <p style={{ color: '#666', textAlign: 'center', marginBottom: '20px' }}>
            请完成下方滑块验证以证明您不是机器人
          </p>
          {/* 滑块验证组件 */}
          {/* 传入验证成功的回调函数 */}
          <SliderVerification onVerifySuccess={handleVerificationSuccess} />
        </div>
      ) : (
        // 已验证时显示成功信息
        <div style={{
          textAlign: 'center',
          padding: '30px',
          background: 'white',
          borderRadius: '8px',
          boxShadow: '0 2px 10px rgba(0,0,0,0.1)' // 添加阴影,增强立体感
        }}>
          {/* 成功图标 */}
          <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#52c41a" strokeWidth="2" style={{ margin: '0 auto 20px' }}>
            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
            <polyline points="22 4 12 14.01 9 11.01" />
          </svg>
          {/* 成功标题 */}
          <h3 style={{ color: '#333', marginBottom: '10px' }}>验证成功!</h3>
          {/* 成功信息 */}
          <p style={{ color: '#666' }}>
            您已成功完成验证,可以继续使用服务。
          </p>
        </div>
      )}
    </div>
  );
};

// 导出主组件
export default App;

4. 总结

  • 作为一个高级前端开发工程师或者再往上技术专家/架构师,一定要有自己设计实现的思考能力,才能在具体的业务中做到安全防控