news 2026/6/9 7:59:45

鸿蒙实战:图形验证码随机数字+字母干扰线与噪点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙实战:图形验证码随机数字+字母干扰线与噪点

完整代码:GraphicCaptchaDemo

在应用登录、注册或敏感操作中,图形验证码是防止机器人恶意刷接口的常用手段。本文将完整实现一个图形验证码组件,支持随机字母+数字可变的干扰线随机噪点以及字符倾斜与偏移,并提供外部刷新和验证回调。

一、效果预览

组件效果如下图所示:

  • 背景浅灰色,4位随机字符(排除易混淆字符0/O/I/l/1)。
  • 6~10条彩色二次贝塞尔曲线作为干扰线。
  • 60~120个彩色小噪点。
  • 每个字符随机偏移位置、随机旋转角度,且使用中等亮度字体保证人眼可读。
  • 点击验证码图片即可刷新,也可通过按钮调用控制器刷新。

二、核心技术点

  1. 真随机数生成:使用cryptoFramework生成硬件真随机数,避免Math.random()的可预测性。
  2. Canvas绘图:利用CanvasRenderingContext2D绘制背景、曲线、点阵和文字。
  3. 字符集处理:剔除易混淆字符(0、O、I、l、1),最终转为小写用于比较。
  4. 外部控制器:通过自定义控制器ImageCodeController对外暴露refresh方法,实现组件外刷新。
  5. 响应式状态:通过@State管理画布尺寸,通过onCodeChange回调向上传递验证码字符串。

三、代码实现

1. 控制器文件:ImageCodeController.ets

exportclassImageCodeController{refresh:()=>void=()=>{};}

2. 组件文件:ImageCode.ets

背景先绘制之后的噪点 线条 文字 绘制顺序决定了识别难度。如果文字最后绘制会特别清晰、如果文字先绘制由噪点和线条覆盖则增加识别难度。

import{cryptoFramework}from'@kit.CryptoArchitectureKit';import{ImageCodeController}from'../controller/ImageCodeController';@Componentexportstruct ImageCode{// Canvas 画布上下文privatesettings:RenderingContextSettings=newRenderingContextSettings(true);privatectx:CanvasRenderingContext2D=newCanvasRenderingContext2D(this.settings);// 组件尺寸(可自定义)@State canvasWidth:number=140;@State canvasHeight:number=44;// 回调:每次生成新验证码时把字符串(小写)传给父组件onCodeChange?:(code:string)=>void;// 外部控制器controller?:ImageCodeController;aboutToAppear():void{if(this.controller){this.controller.refresh=()=>{this.generateAndDraw();};}}// 生成随机验证码并绘制privategenerateAndDraw():void{constcodeStr=this.generateRandomCode();if(this.onCodeChange){this.onCodeChange(codeStr);}this.drawCodeToCanvas(codeStr);}// 生成随机验证码(字符集:大写+小写+数字,排除0/O/I/l/1等易混淆字符)privategenerateRandomCode():string{constcharset='ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789';constlen=4;letresult='';for(leti=0;i<len;i++){constrandomIndex=this.getRandomInt(0,charset.length);result+=charset.charAt(randomIndex);}returnresult.toLowerCase();}// 真随机整数 [min, max)privategetRandomInt(min:number,max:number):number{try{constrand=cryptoFramework.createRandom();constrandData=rand.generateRandomSync(4);letrandomValue=(randData.data[0]<<24|randData.data[1]<<16|randData.data[2]<<8|randData.data[3])>>>0;randomValue=randomValue/0xFFFFFFFF;returnMath.floor(randomValue*(max-min)+min);}catch(error){// 降级使用 Math.random()returnMath.floor(Math.random()*(max-min)+min);}}// 随机颜色privategetRandomColor():string{constr=this.getRandomInt(80,220);constg=this.getRandomInt(80,220);constb=this.getRandomInt(80,220);return`rgb(${r},${g},${b})`;}privatedrawCodeToCanvas(code:string):void{constw=this.canvasWidth;consth=this.canvasHeight;constctx=this.ctx;ctx.clearRect(0,0,w,h);// 1. 背景色ctx.fillStyle='#F8F9FA';ctx.fillRect(0,0,w,h);// 2. 先绘制文字(在最底层,之后会被噪点和线条覆盖部分)constcharCount=code.length;constbaseX=w*0.2;conststep=(w*0.6)/charCount;ctx.font=`bold${Math.floor(h*0.5*3.5)}px "Courier New", "Fira Code", monospace`;ctx.textAlign='center';ctx.textBaseline='middle';for(leti=0;i<charCount;i++){constchar=code.charAt(i).toUpperCase();constx=baseX+i*step+this.getRandomInt(-6,6);consty=h/2+this.getRandomInt(-h*0.18,h*0.18);constangle=(this.getRandomInt(-28,28)*Math.PI)/180;ctx.save();ctx.translate(x,y);ctx.rotate(angle);// 文字颜色constmidR=this.getRandomInt(90,170);constmidG=this.getRandomInt(90,170);constmidB=this.getRandomInt(90,170);ctx.fillStyle=`rgb(${midR},${midG},${midB})`;ctx.shadowBlur=1;ctx.shadowColor='rgba(0,0,0,0.25)';ctx.fillText(char,0,0);ctx.shadowBlur=0;ctx.restore();}// 3. 再绘制噪点(覆盖部分文字,增加干扰)constdotCount=this.getRandomInt(60,120);for(leti=0;i<dotCount;i++){ctx.fillStyle=this.getRandomColor();ctx.fillRect(this.getRandomInt(0,w),this.getRandomInt(0,h),1,1);}// 4. 最后绘制自由线条(干扰线,覆盖文字和噪点)constlineCount=this.getRandomInt(6,10);for(leti=0;i<lineCount;i++){ctx.beginPath();conststartX=this.getRandomInt(0,w);conststartY=this.getRandomInt(0,h);constendX=this.getRandomInt(0,w);constendY=this.getRandomInt(0,h);ctx.moveTo(startX,startY);constcpX=this.getRandomInt(0,w);constcpY=this.getRandomInt(0,h);ctx.quadraticCurveTo(cpX,cpY,endX,endY);ctx.strokeStyle=this.getRandomColor();ctx.lineWidth=this.getRandomInt(1,2);ctx.stroke();}// 5. 边框(最上层,无干扰)ctx.beginPath();ctx.strokeStyle='#CCCCCC';ctx.lineWidth=1;ctx.strokeRect(2,2,w-4,h-4);}build(){Canvas(this.ctx).width(this.canvasWidth).height(this.canvasHeight).borderRadius(8).onClick(()=>{this.generateAndDraw();}).onReady(()=>{this.generateAndDraw();});}}

3. 使用示例:Index.ets

import{promptAction}from'@kit.ArkUI';import{ImageCode}from'../components/ImageCode';import{ImageCodeController}from'../controller/ImageCodeController';@Entry @Component struct Index{@State inputCode:string='';@State currentCode:string='';privatecodeController:ImageCodeController=newImageCodeController();// 验证用户输入privatecheckCode(){if(this.inputCode.toLowerCase()===this.currentCode){promptAction.showToast({message:'验证成功'});}else{promptAction.showToast({message:'验证码错误'});}}build(){Column({space:20}){Text('图形验证码示例').fontSize(20).fontWeight(FontWeight.Bold)Row({space:12}){ImageCode({onCodeChange:(code)=>{this.currentCode=code;},controller:this.codeController})Button('刷新').onClick(()=>{this.codeController.refresh();})}Row({space:12}){TextInput({placeholder:'请输入验证码'}).layoutWeight(1).onChange((val)=>{this.inputCode=val;})Button('验证').onClick(()=>{this.checkCode()})}}.padding(20).width('100%').height('100%')}}

四、关键细节解析

  1. 真随机性:使用cryptoFramework生成4字节随机数,转换为0~1之间的浮点数,再映射到指定范围,比Math.random()更难以预测。
  2. 干扰元素设计
    • 干扰线采用二次贝塞尔曲线(quadraticCurveTo),比直线更难被程序识别。
    • 噪点密度可调,颜色鲜艳,增加了OCR识别难度。
  3. 字符样式
    • 每个字符独立设置中等亮度颜色(RGB分量90~170),确保与浅色背景和彩色线条有足够对比,同时避免过深导致过于清晰。
    • 随机偏移(-66px)和随机旋转(-28°28°),使字符位置错落有致。
  4. 易用性
    • 组件通过onCodeChange自动向上传递当前验证码(小写)。
    • 外部通过ImageCodeController可随时刷新验证码。
    • 用户输入验证码时自动转为小写进行比较,提升体验。

五、总结与扩展

本文实现了一个功能完整、视觉丰富的图形验证码组件,可作为鸿蒙Next应用的基础库。您可以根据实际需求轻松扩展:

  • 修改字符位数:调整generateRandomCode中的循环次数。
  • 更换字符集:增减charset字符串(注意保留易混淆字符处理)。
  • 调整画布尺寸:通过@State canvasWidth/Height或传入属性动态设置。
  • 增加算术验证码:例如显示“23 + 7 = ?”,只需修改generateRandomCode逻辑。

希望本篇实战对您有所帮助。如果您在集成中遇到任何问题,欢迎在评论区交流!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 7:59:42

室内测试没信号?可能是你方法不对!保姆级教程:用USB转TTL和NaviTrack软件快速验证GPS/北斗模块(避坑指南)

室内GPS/北斗模块测试全攻略&#xff1a;从零搭建验证环境到数据深度解析 刚拿到手的GPS/北斗模块在室内测试时一片空白&#xff1f;别急着怀疑硬件故障。作为硬件开发者&#xff0c;我们都经历过这种困惑——明明按照手册连接了线路&#xff0c;上位机却显示"无卫星信号&…

作者头像 李华
网站建设 2026/6/9 7:59:42

华硕笔记本终极性能控制神器:G-Helper 5分钟快速上手指南

华硕笔记本终极性能控制神器&#xff1a;G-Helper 5分钟快速上手指南 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook,…

作者头像 李华
网站建设 2026/6/9 7:59:24

语义动态分析与Allan偏差在文本分析中的应用

1. 语义动态分析的物理视角在自然语言处理领域&#xff0c;语义动态分析一直是个令人着迷又充满挑战的课题。想象一下&#xff0c;当你阅读一本小说时&#xff0c;每个句子都像是一个路标&#xff0c;引导你在意义的景观中穿行。这种意义的演变过程&#xff0c;我们称之为"…

作者头像 李华
网站建设 2026/6/9 7:59:23

小程序毕设项目:django大数据基于微信小程序的直播带货商品数据分析系统的设计与实现 (源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/9 7:59:22

webrtc neteq Nack_tracker重发(ARQ 的nack技术) 介绍

NackTracker 是 WebRTC NetEq 模块中用于跟踪丢失的 RTP 数据包并生成 NACK&#xff08;Negative Acknowledgement&#xff0c;&#xff09;列表的核心类。它的主要目的是在网络状况不佳导致丢包时&#xff0c;通过请求发送端重传丢失的数据包来恢复音频质量&#xff0c;同时避…

作者头像 李华