完整代码GraphicCaptchaDemo在应用登录、注册或敏感操作中图形验证码是防止机器人恶意刷接口的常用手段。本文将完整实现一个图形验证码组件支持随机字母数字、可变的干扰线、随机噪点以及字符倾斜与偏移并提供外部刷新和验证回调。一、效果预览组件效果如下图所示背景浅灰色4位随机字符排除易混淆字符0/O/I/l/1。6~10条彩色二次贝塞尔曲线作为干扰线。60~120个彩色小噪点。每个字符随机偏移位置、随机旋转角度且使用中等亮度字体保证人眼可读。点击验证码图片即可刷新也可通过按钮调用控制器刷新。二、核心技术点真随机数生成使用cryptoFramework生成硬件真随机数避免Math.random()的可预测性。Canvas绘图利用CanvasRenderingContext2D绘制背景、曲线、点阵和文字。字符集处理剔除易混淆字符0、O、I、l、1最终转为小写用于比较。外部控制器通过自定义控制器ImageCodeController对外暴露refresh方法实现组件外刷新。响应式状态通过State管理画布尺寸通过onCodeChange回调向上传递验证码字符串。三、代码实现1. 控制器文件ImageCodeController.etsexportclassImageCodeController{refresh:()void(){};}2. 组件文件ImageCode.ets背景先绘制之后的噪点 线条 文字 绘制顺序决定了识别难度。如果文字最后绘制会特别清晰、如果文字先绘制由噪点和线条覆盖则增加识别难度。import{cryptoFramework}fromkit.CryptoArchitectureKit;import{ImageCodeController}from../controller/ImageCodeController;Componentexportstruct ImageCode{// Canvas 画布上下文privatesettings:RenderingContextSettingsnewRenderingContextSettings(true);privatectx:CanvasRenderingContext2DnewCanvasRenderingContext2D(this.settings);// 组件尺寸可自定义State canvasWidth:number140;State canvasHeight:number44;// 回调每次生成新验证码时把字符串小写传给父组件onCodeChange?:(code:string)void;// 外部控制器controller?:ImageCodeController;aboutToAppear():void{if(this.controller){this.controller.refresh(){this.generateAndDraw();};}}// 生成随机验证码并绘制privategenerateAndDraw():void{constcodeStrthis.generateRandomCode();if(this.onCodeChange){this.onCodeChange(codeStr);}this.drawCodeToCanvas(codeStr);}// 生成随机验证码字符集大写小写数字排除0/O/I/l/1等易混淆字符privategenerateRandomCode():string{constcharsetABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789;constlen4;letresult;for(leti0;ilen;i){constrandomIndexthis.getRandomInt(0,charset.length);resultcharset.charAt(randomIndex);}returnresult.toLowerCase();}// 真随机整数 [min, max)privategetRandomInt(min:number,max:number):number{try{constrandcryptoFramework.createRandom();constrandDatarand.generateRandomSync(4);letrandomValue(randData.data[0]24|randData.data[1]16|randData.data[2]8|randData.data[3])0;randomValuerandomValue/0xFFFFFFFF;returnMath.floor(randomValue*(max-min)min);}catch(error){// 降级使用 Math.random()returnMath.floor(Math.random()*(max-min)min);}}// 随机颜色privategetRandomColor():string{constrthis.getRandomInt(80,220);constgthis.getRandomInt(80,220);constbthis.getRandomInt(80,220);returnrgb(${r},${g},${b});}privatedrawCodeToCanvas(code:string):void{constwthis.canvasWidth;consththis.canvasHeight;constctxthis.ctx;ctx.clearRect(0,0,w,h);// 1. 背景色ctx.fillStyle#F8F9FA;ctx.fillRect(0,0,w,h);// 2. 先绘制文字在最底层之后会被噪点和线条覆盖部分constcharCountcode.length;constbaseXw*0.2;conststep(w*0.6)/charCount;ctx.fontbold${Math.floor(h*0.5*3.5)}px Courier New, Fira Code, monospace;ctx.textAligncenter;ctx.textBaselinemiddle;for(leti0;icharCount;i){constcharcode.charAt(i).toUpperCase();constxbaseXi*stepthis.getRandomInt(-6,6);constyh/2this.getRandomInt(-h*0.18,h*0.18);constangle(this.getRandomInt(-28,28)*Math.PI)/180;ctx.save();ctx.translate(x,y);ctx.rotate(angle);// 文字颜色constmidRthis.getRandomInt(90,170);constmidGthis.getRandomInt(90,170);constmidBthis.getRandomInt(90,170);ctx.fillStylergb(${midR},${midG},${midB});ctx.shadowBlur1;ctx.shadowColorrgba(0,0,0,0.25);ctx.fillText(char,0,0);ctx.shadowBlur0;ctx.restore();}// 3. 再绘制噪点覆盖部分文字增加干扰constdotCountthis.getRandomInt(60,120);for(leti0;idotCount;i){ctx.fillStylethis.getRandomColor();ctx.fillRect(this.getRandomInt(0,w),this.getRandomInt(0,h),1,1);}// 4. 最后绘制自由线条干扰线覆盖文字和噪点constlineCountthis.getRandomInt(6,10);for(leti0;ilineCount;i){ctx.beginPath();conststartXthis.getRandomInt(0,w);conststartYthis.getRandomInt(0,h);constendXthis.getRandomInt(0,w);constendYthis.getRandomInt(0,h);ctx.moveTo(startX,startY);constcpXthis.getRandomInt(0,w);constcpYthis.getRandomInt(0,h);ctx.quadraticCurveTo(cpX,cpY,endX,endY);ctx.strokeStylethis.getRandomColor();ctx.lineWidththis.getRandomInt(1,2);ctx.stroke();}// 5. 边框最上层无干扰ctx.beginPath();ctx.strokeStyle#CCCCCC;ctx.lineWidth1;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.etsimport{promptAction}fromkit.ArkUI;import{ImageCode}from../components/ImageCode;import{ImageCodeController}from../controller/ImageCodeController;Entry Component struct Index{State inputCode:string;State currentCode:string;privatecodeController:ImageCodeControllernewImageCodeController();// 验证用户输入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.currentCodecode;},controller:this.codeController})Button(刷新).onClick((){this.codeController.refresh();})}Row({space:12}){TextInput({placeholder:请输入验证码}).layoutWeight(1).onChange((val){this.inputCodeval;})Button(验证).onClick((){this.checkCode()})}}.padding(20).width(100%).height(100%)}}四、关键细节解析真随机性使用cryptoFramework生成4字节随机数转换为0~1之间的浮点数再映射到指定范围比Math.random()更难以预测。干扰元素设计干扰线采用二次贝塞尔曲线quadraticCurveTo比直线更难被程序识别。噪点密度可调颜色鲜艳增加了OCR识别难度。字符样式每个字符独立设置中等亮度颜色RGB分量90~170确保与浅色背景和彩色线条有足够对比同时避免过深导致过于清晰。随机偏移-66px和随机旋转-28°28°使字符位置错落有致。易用性组件通过onCodeChange自动向上传递当前验证码小写。外部通过ImageCodeController可随时刷新验证码。用户输入验证码时自动转为小写进行比较提升体验。五、总结与扩展本文实现了一个功能完整、视觉丰富的图形验证码组件可作为鸿蒙Next应用的基础库。您可以根据实际需求轻松扩展修改字符位数调整generateRandomCode中的循环次数。更换字符集增减charset字符串注意保留易混淆字符处理。调整画布尺寸通过State canvasWidth/Height或传入属性动态设置。增加算术验证码例如显示“23 7 ”只需修改generateRandomCode逻辑。希望本篇实战对您有所帮助。如果您在集成中遇到任何问题欢迎在评论区交流