文章目录

  • 基于Nodejs作为服务端,React作为前端框架,axios作为通讯框架,实现滑块验证
  • 1. 为什么要自己写滑块验证
  • 2. 滑块验证的整体思路
  • 3. 具体实现
    • 3.1 服务端
    • 3.2 前端
  • 4. 总结

基于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
// 导入所需的依赖库
// 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 {// 获取客户端IPconst 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,        // 会话IDmainImage: mainCanvas.toDataURL('image/png'),  // 主图像(带缺口)的DataURLpuzzleImage: puzzleCanvas.toDataURL('image/png'),  // 滑块图像的DataURLpuzzleSize        // 滑块尺寸});} 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;// 获取客户端IPconst 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
// 导入React库和所需的钩子函数
import React, { useState, useEffect, useRef } from 'react';
// 导入axios用于发送HTTP请求
import axios from 'axios';// 定义滑块验证组件
// onVerifySuccess: 验证成功时的回调函数
const SliderVerification = ({ onVerifySuccess }) => {// 状态管理 - 图像相关// 主图像(带缺口)的DataURLconst [mainImage, setMainImage] = useState('');// 滑块(拼图)图像的DataURLconst [puzzleImage, setPuzzleImage] = useState('');// 滑块的尺寸const [puzzleSize, setPuzzleSize] = useState(40);// 本次验证的会话IDconst [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);         // 保存会话IDsetMainImage(mainImage);         // 保存主图像setPuzzleImage(puzzleImage);     // 保存滑块图像setPuzzleSize(puzzleSize);       // 保存滑块尺寸setPositionX(0);                 // 重置滑块位置到初始位置setMessage('请拖动滑块完成验证'); // 重置提示信息setIsSuccess(null);              // 重置验证结果} catch (error) {// 处理请求失败的情况console.error('获取验证图像失败:', error);setMessage('加载验证失败,请刷新重试');}};// 处理鼠标/触摸开始事件(用户开始拖动滑块)const handleStart = (e) => {// 如果正在验证中或已经验证过,则不执行任何操作if (isVerifying || isSuccess !== null) return;// 设置正在拖动状态为truesetIsDragging(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();};// 渲染组件UIreturn (<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'}}/>{/* 滑块按钮 */}<divref={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
// 导入React库和useState钩子
import React, { useState } from 'react';
// 导入滑块验证组件
import SliderVerification from './SliderVerification';// 定义应用的主组件
const App = () => {// 状态管理:记录用户是否已通过验证// 初始值为false,表示未验证const [isVerified, setIsVerified] = useState(false);// 处理验证成功的函数const handleVerificationSuccess = () => {// 设置验证状态为已验证setIsVerified(true);// 这里可以添加验证成功后的逻辑,如跳转到下一页、提交表单等// 例如:可以调用API获取用户数据,或者显示受保护的内容};// 渲染应用UIreturn (<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. 总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94072.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94072.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94072.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年物流大数据分析的主要趋势

大数据已为物流行业带来革命性变革&#xff0c;助力实现更智能的运营与实时洞察。如今&#xff0c;企业可精准识别瓶颈、优化供应链&#xff1b;自疫情以来&#xff0c;大数据的采用率大幅攀升&#xff0c;79% 的供应链负责人将分析培训列为优先事项。这一转变不仅提升了效率、…

【C2000常见问题】JTAG仿真器类型和JTAG Debug定位方法

【C2000常见问题】JTAG仿真器类型和JTAG Debug定位方法 母线继电保护动作行为仿真分析系统 【C2000常见问题】JTAG仿真器类型和JTAG Debug定位方法 1问题背景 2问题分析 3可能出现的问题 4JTAG问题总结 1问题背景 某客户产品应用中,使用JTAG仿真器时经常会遇到一启动负载或者…

LT8712SX,Type-C/DP1.4 /eDP转 DP1.4/HD-DVI2.0 带音频

简介LT8712SX是一款高性能Type-C/DP1.4 /eDP转 DP1.4/HD-DVI2.0 带音频,支持4K(3840*2316)60Hz 的分辨率,提供 I2S 和 SPDIF 两个数字音频输出接口&#xff0c;均支持 8 通道 LPCM 或压缩音频&#xff0c;最高采样率为 192KHz。应用场景便携式显示器例如&#xff0c;手机通过 T…

C语言基础:(二十)自定义类型:结构体

目录 前言 一、结构体类型的声明 1.1 结构体回顾 1.1.1 结构体的声明 1.1.2 结构体变量的创建和初始化 1.2 结构的特殊声明 1.3 结构的自引用 二、结构体内存对齐 2.1 对齐规则 2.1.1 练习1 2.1.2 练习2 2.1.3 练习3&#xff1a;结构体嵌套问题 2.2 为什…

数据仓库分层解析(详细)

目录 一、数据仓库为什么要分层 二、数据仓库怎么分层 1、ODS&#xff08;Operational Data Store&#xff09;&#xff1a;数据源层 2、DW&#xff08;Data Warehouse&#xff09;&#xff1a; 数据仓库层 2.1、DWD&#xff08;Data Warehouse Detail&#xff09;&#x…

智慧城管云平台源码,微服务vue+element+springboot+uniapp技术架构,数字化综合执法办案系统

智慧城管综合执法系统源码&#xff0c;包括PC端和移动端。微服务架构&#xff0c;vueelementspringbootuniapp技术框架开发。智慧城管建立了统一的城管执法案件数据库、法律法规库、档案信息库等&#xff0c;支持简易程序案件、一般程序案件、行政强制管理等执法业务的办理&…

VUE实现多个弹窗优先级变化实现思路

在开发复杂的单页应用&#xff08;SPA&#xff09;时&#xff0c;我们经常会遇到需要管理多个浮动窗口&#xff08;或称“弹窗”、“面板”&#xff09;的场景。一个核心的用户体验要求是&#xff1a;用户当前操作的窗口应该总是在最顶层。本文将结合代码示例&#xff0c;总结一…

集成算法和kmeans

一、集成算法&#xff08;Ensemble Learning&#xff09; 1. 基本概念 集成学习通过构建并结合多个学习器&#xff08;基分类器/回归器&#xff09;来完成学习任务&#xff0c;旨在通过集体决策提升模型性能&#xff0c;类似于“多个专家的综合判断优于单个专家”。 2. 结合策略…

图数据库性能与可扩展性评估

图数据库的性能与可扩展性直接决定业务场景&#xff08;如实时风控、知识图谱分析&#xff09;的落地效果&#xff0c;需结合业务场景特性&#xff08;OLTP/OLAP&#xff09;、技术指标&#xff08;响应时间、吞吐量&#xff09;和扩展能力&#xff08;数据量/节点扩展&#xf…

树莓派常用的国内镜像源列表以及配置方法

1. 常用的镜像源使用下来发现清华源经常访问不到&#xff0c;阿里源比较好用。其他源还未测试。源名称URL清华源https://pypi.tuna.tsinghua.edu.cn/simple阿里云https://mirrors.aliyun.com/pypi/simple/中科大https://pypi.mirrors.ustc.edu.cn/simple/华为云https://repo.hu…

Transformer在文本、图像和点云数据中的应用——经典工作梳理

摘要 最近在整一些3D检测和分割的任务&#xff0c;接触了一下ptv3&#xff0c;在之前梳理的工作owlv2中用到了vit&#xff0c;去年年假阅读《多模态大模型&#xff1a;算法、应用与微调》&#xff08;刘兆峰&#xff09;时学习了Transformer网络架构及其在文本数据中的应用&am…

训练后数据集后部署PaddleOCR转trt流程

训练后的模型部署&#xff0c;首先要进行训练 0.训练流程见文章 PaddleOCR字符识别&#xff0c;训练自己的数据集全流程&#xff08;环境、标注、训练、推理&#xff09;-CSDN博客文章浏览阅读1.6k次&#xff0c;点赞53次&#xff0c;收藏23次。PaddleOCR是基于百度飞桨框架的…

《MLB美职棒》美国国球是橄榄球还是棒球·棒球5号位

USAs National Sport Showdown: MLB⚾️ vs NFL Ultimate Guide!从商业价值到文化基因&#xff0c;360解析美国体育王座之争&#xff01;添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;️ 历史定位 Historical Roots⚾️ MLB&#xff1a;The "Classi…

常见 Linux 网络命令梳理

在日常运维和排障工作中&#xff0c;网络相关命令是最常用的一类工具。无论是检查网络连通性&#xff0c;还是定位路由问题&#xff0c;又或是分析端口和服务占用&#xff0c;熟悉这些命令都能让我们更高效地解决问题。本文将从几个常见的维度来梳理 Linux 下的网络命令&#x…

Docker 搭建 Gitlab 实现自动部署Vue项目

1、配置要求: 硬件要求: CPU:双核或以上 内存:4GB或以上 软件要求:Centos6 或更高版本 2、gitlab镜像: # 中文版仓库 #docker pull twang2218/gitlab-ce-zh docker pull gitlab/gitlab-ce 3、gitlab部署目录 说明:为了跟其他容器区分,gitlab相关容…

如何解决机器翻译的“幻觉“问题(Hallucination)?

更多内容请见: 机器翻译修炼-专栏介绍和目录 文章目录 一、数据层面优化 二、模型架构改进 三、训练策略调整 四、评估与迭代 五、前沿方向与挑战 六、案例:WMT2023幻觉缓解方案 机器翻译中的“幻觉”(Hallucination)指模型生成与源文本语义无关、逻辑矛盾或事实错误的翻译…

基于STM32+NBIOT设计的宿舍安防控制系统_264

文章目录 1.1 项目介绍 【1】开发背景 【2】实现需求 【3】项目硬件模块组成 【4】设计意义 【5】国内外研究现状 【6】摘要 1.2 系统总体设计 【1】系统功能需求分析 【2】系统总体方案设计 【3】系统工作原理 1.3 系统框架图 1.4 系统功能总结 1.5 系统原理图 1.6 实物图 1.7…

SLAM文献之-Globally Consistent and Tightly Coupled 3D LiDAR Inertial Mapping

一、简介 该论《Globally Consistent and Tightly Coupled 3D LiDAR Inertial Mapping》是日本先进工业科学技术研究所&#xff08;AIST&#xff09;的Koide等人于2022年在IEEE国际机器人与自动化会议&#xff08;ICRA&#xff09;上发表的一篇论文。该研究提出了一种基于全局…

【STM32】HAL库中的实现(七):DMA(直接存储器访问)

DMA 是什么&#xff1f; DMA&#xff08;Direct Memory Access&#xff09;是 外设直接和内存之间数据搬运的机制&#xff0c;不需要 CPU 参与。 ✅ 举个例子&#xff1a;传统方式&#xff1a; ADC → CPU → RAM 使用 DMA&#xff1a;ADC → DMA → RAM&#xff08;CPU 不需干…

【LeetCode热题100道笔记+动画】字母异位词分组

题目描述 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 示例 1: 输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”] 输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]] 解释: 在 strs 中没有字符串可…