Vue實現(xiàn)圖片驗證碼生成
圖片驗證碼主要用于注冊,登錄等提交場景中,目的是防止腳本進(jìn)行批量注冊、登錄、灌水,相比不帶圖片驗證的安全度有所提高,不過目前也有自動識別圖片驗證碼的程序出現(xiàn),基本都是付費(fèi)識別,隨之又出現(xiàn)了滑動驗證,選取正確選項驗證等更加安全的驗證方式。但圖片驗證碼碼仍用于大部分網(wǎng)站中。
一、前端圖片驗證碼生成
前端邏輯大體就是進(jìn)行圖形繪制,取幾個隨機(jī)數(shù)放入圖片中,加入干擾,進(jìn)行驗證
1.創(chuàng)建驗證碼組件identify.vue
<template> ? <div class="s-canvas" style="display: inline"> ? ? <canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas> ? </div> </template> <script> export default{ ? name: 'SIdentify', ? props: { ? ? identifyCode: { // 默認(rèn)注冊碼 ? ? ? type: String, ? ? ? default: '1234' ? ? }, ? ? fontSizeMin: { // 字體最小值 ? ? ? type: Number, ? ? ? default: 25 ? ? }, ? ? fontSizeMax: { // 字體最大值 ? ? ? type: Number, ? ? ? default: 35 ? ? }, ? ? backgroundColorMin: { // 驗證碼圖片背景色最小值 ? ? ? type: Number, ? ? ? default: 200 ? ? }, ? ? backgroundColorMax: { // 驗證碼圖片背景色最大值 ? ? ? type: Number, ? ? ? default: 220 ? ? }, ? ? dotColorMin: { // 背景干擾點(diǎn)最小值 ? ? ? type: Number, ? ? ? default: 60 ? ? }, ? ? dotColorMax: { // 背景干擾點(diǎn)最大值 ? ? ? type: Number, ? ? ? default: 120 ? ? }, ? ? contentWidth: { // 容器寬度 ? ? ? type: Number, ? ? ? default: 117 ? ? }, ? ? contentHeight: { // 容器高度 ? ? ? type: Number, ? ? ? default: 32 ? ? } ? }, ? methods: { ? ? // 生成一個隨機(jī)數(shù) ? ? randomNum (min, max) { ? ? ? return Math.floor(Math.random() * (max - min) + min) ? ? }, ? ? ? // 生成一個隨機(jī)的顏色 ? ? randomColor (min, max) { ? ? ? let r = this.randomNum(min, max) ? ? ? let g = this.randomNum(min, max) ? ? ? let b = this.randomNum(min, max) ? ? ? return 'rgb(' + r + ',' + g + ',' + b + ')' ? ? }, ? ? ? drawPic () { ? ? ? let canvas = document.getElementById('s-canvas') ? ? ? let ctx = canvas.getContext('2d') ? ? ? ctx.textBaseline = 'bottom' ? ? ? // 繪制背景 ? ? ? ctx.fillStyle = '#e6ecfd' ? ? ? ctx.fillRect(0, 0, this.contentWidth, this.contentHeight) ? ? ? // 繪制文字 ? ? ? for (let i = 0; i < this.identifyCode.length; i++) { ? ? ? ? this.drawText(ctx, this.identifyCode[i], i) ? ? ? } ? ? ? this.drawLine(ctx) ? ? ? this.drawDot(ctx) ? ? }, ? ? ? drawText (ctx, txt, i) { ? ? ? ctx.fillStyle = this.randomColor(50, 160) // 隨機(jī)生成字體顏色 ? ? ? ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei' // 隨機(jī)生成字體大小 ? ? ? let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1)) ? ? ? let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5) ? ? ? var deg = this.randomNum(-30, 30) ? ? ? // 修改坐標(biāo)原點(diǎn)和旋轉(zhuǎn)角度 ? ? ? ctx.translate(x, y) ? ? ? ctx.rotate(deg * Math.PI / 180) ? ? ? ctx.fillText(txt, 0, 0) ? ? ? // 恢復(fù)坐標(biāo)原點(diǎn)和旋轉(zhuǎn)角度 ? ? ? ctx.rotate(-deg * Math.PI / 180) ? ? ? ctx.translate(-x, -y) ? ? }, ? ? ? drawLine (ctx) { ? ? ? // 繪制干擾線 ? ? ? for (let i = 0; i < 4; i++) { ? ? ? ? ctx.strokeStyle = this.randomColor(100, 200) ? ? ? ? ctx.beginPath() ? ? ? ? ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight)) ? ? ? ? ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight)) ? ? ? ? ctx.stroke() ? ? ? } ? ? }, ? ? ? drawDot (ctx) { ? ? ? // 繪制干擾點(diǎn) ? ? ? for (let i = 0; i < 30; i++) { ? ? ? ? ctx.fillStyle = this.randomColor(0, 255) ? ? ? ? ctx.beginPath() ? ? ? ? ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI) ? ? ? ? ctx.fill() ? ? ? } ? ? } ? }, ? watch: { ? ? identifyCode () { ? ? ? this.drawPic() ? ? } ? }, ? mounted () { ? ? this.drawPic() ? } } </script>
2.父組件
前端生成驗證碼的校驗是自己生成驗證碼,用戶輸入的值和生成的驗證碼字符串進(jìn)行比對校驗的,校驗沒有走后端,在真正發(fā)送請求到后端的時候也是不將驗證碼傳給后臺的。
<template> ? ? <div slot="content" class="reg-body"> ? ? ? <div class="main-content"> ? ? ? ? <el-form ref="form" :model="form" :rules="rules" label-width="125px" label-position="left"> ? ? ? ? ? <el-form-item label="驗證碼" class="my-item" prop="code"> ? ? ? ? ? ? <el-input ? ? ? ? ? ? ? v-model.trim="form.code" ? ? ? ? ? ? ? placeholder="請輸入正確的驗證碼" ? ? ? ? ? ? ? size="small" ? ? ? ? ? ? ? style="width: 200px " ? ? ? ? ? ? /> ? ? ? ? ? ? <span class="login-code" style="position: absolute; right: 0; top: 4px; left: 257px"> ? ? ? ? ? ? ? <Identify :identifyCode="identifyCode"></Identify> ? ? ? ? ? ? </span> ? ? ? ? ? ? <span ? ? ? ? ? ? ? class="el-icon-refresh-right" ? ? ? ? ? ? ? style="position: absolute; ?left: 394px; top: 13px; cursor: pointer" ? ? ? ? ? ? ? @click="refreshCode" ? ? ? ? ? ? /> ? ? ? ? ? </el-form-item> ? ? ? ? </el-form> ? ? ? ? <div style="text-align: center"> ? ? ? ? ? <el-button ? ? ? ? ? ? v-if="form.agreement" ? ? ? ? ? ? style="background:#6BC6A1; color: white; border-radius:2px; width: 200px; height: 30px; line-height: 5px; border: none;margin-bottom: 10px" ? ? ? ? ? ? @click="submitForm()" ? ? ? ? ? ? :loading="loading" ? ? ? ? ? >提交</el-button> ? ? ? ? </div> ? ? ? </div> ? ? </div> </template> ? <script> import Identify from "./identify"; export default { ? name: "Reg", ? mixins: [region], ? components: {Identify }, ? data() { ? ? return { ? ? ? formSetting: { ? ? ? ? contentWidth: 340, ? ? ? }, ? ? ? form: { ? ? ? ? logoUrl: "", ? ? ? ? licenseUrl: "", ? ? ? ? description: [], ? ? ? ? account: "", ? ? ? ? name: "", ? ? ? }, ? ? ? code:this.$route.query.code, ? ? ? identifyCodes: "1234567890abcdefjhijklinopqrsduvwxyz", ? ? ? identifyCode: "", ? ? ? picList: [], ? ? ? props: { label: "name", value: "id", children: "children" }, ? ? ? rules: { ? ? ? ? code: [{ required: true, message: "請輸入驗證碼", trigger: "change" }], ? ? ? }, ? ? ? formList: [], ? ? }; ? }, ? created() { ? ? // this.getAllDict() ? }, ? mounted() { ? ? this.getRegion(); ? ? // 初始化驗證碼 ? ? this.identifyCode = ""; ? ? this.makeCode(this.identifyCodes, 4); ? ? localStorage.setItem("code", this.code); ? }, ? methods: { ? ? openHtml() { ? ? ? this.visible = true; ? ? }, ? ? //刷新驗證碼 ? ? refreshCode() { ? ? ? this.identifyCode = ""; ? ? ? this.makeCode(this.identifyCodes, 4); ? ? }, ? ? //生成驗證上的隨機(jī)數(shù),驗證碼中的數(shù)從identifyCodes中取, ? ? makeCode(o, l) { ? ? ? for (let i = 0; i < l; i++) { ? ? ? ? this.identifyCode += this.identifyCodes[ ? ? ? ? ? this.randomNum(0, this.identifyCodes.length) ? ? ? ? ]; ? ? ? } ? ? }, ? ? //生成隨機(jī)數(shù),這里是生成 ? ? //Math.random()方法返回大于等于0小于1的一個隨機(jī)數(shù) ? ? //隨機(jī)數(shù) = Math.floor(Math.random() * 可能的總數(shù) + 第一個可能的值) ? ? randomNum(min, max) { ? ? ? return Math.floor(Math.random() * (max - min) + min); ? ? }, ? ? // 提交表單 ? ? submitForm() { ? ? ? this.$refs["form"].validate((valid) => { ? ? ? ? if (valid) { ? ? ? ? ? if (this.loading) { ? ? ? ? ? ? return; ? ? ? ? ? } ? ? ? ? ? this.loading = true; ? ? ? ? ? this.submit(); ? ? ? ? } else { ? ? ? ? ? return false; ? ? ? ? } ? ? ? }); ? ? }, ? ? submit: async function () { ? ? ? if (this.form.code.toLowerCase() !== this.identifyCode.toLowerCase()) { ? ? ? ? this.$msg({ type: "error", message: "請?zhí)顚懻_驗證碼" }); ? ? ? ? this.refreshCode(); ? ? ? ? this.loading = false; ? ? ? ? return; ? ? ? } else { ? ? ? ? //驗證碼校驗成功就可以調(diào)接口提交表單了 ? ? }, ? }, }; </script>
二、后端生成圖片驗證碼
后臺思路很,利用BufferedImage類創(chuàng)建一張圖片,再用Graphics對圖片進(jìn)行繪制(生成隨機(jī)字符,添加噪點(diǎn),干擾線)即可。
package com.hengtiansoft.gxrc.base.util; ? import com.hengtiansoft.gxrc.common.constant.MagicNumConstant; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; ? import javax.imageio.ImageIO; import java.awt.Font; import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.Random; ? @Slf4j @Component public class ImgCaptchaUtil { ? ? ? ? private Random random = new Random(); ? ? /** ? ? ?* 驗證碼的寬 ? ? ?*/ ? ? private final int width = 160; ? ? /** ? ? ?* 驗證碼的高 ? ? ?*/ ? ? private final int height = 40; ? ? /** ? ? ?* 驗證碼的干擾線數(shù)量 ? ? ?*/ ? ? private final int lineSize = 30; ? ? /** ? ? ?* 驗證碼詞典 ? ? ?*/ ? ? private final String randomString = "0123456789abcdefghijklmnopqrstuvwxyz"; ? ? ? /** ? ? ?* 獲取字體 ? ? ?* @return ? ? ?*/ ? ? private Font getFont() { ? ? ? ? return new Font("Times New Roman", Font.ROMAN_BASELINE, MagicNumConstant.FORTY); ? ? } ? ? ? /** ? ? ?* 獲取顏色 ? ? ?* @param fc ? ? ?* @param bc ? ? ?* @return ? ? ?*/ ? ? private Color getRandomColor(int fc, int bc) { ? ? ? ? ? int fcc = Math.min(fc, MagicNumConstant.TWO_HUNDRED_FIFTY_FIVE); ? ? ? ? int bcc = Math.min(bc, MagicNumConstant.TWO_HUNDRED_FIFTY_FIVE); ? ? ? ? ? int r = fcc + random.nextInt(bcc - fcc - MagicNumConstant.SIXTEEN); ? ? ? ? int g = fcc + random.nextInt(bcc - fcc - MagicNumConstant.FOURTEEN); ? ? ? ? int b = fcc + random.nextInt(bcc - fcc - MagicNumConstant.TWELVE); ? ? ? ? ? return new Color(r, g, b); ? ? } ? ? ? /** ? ? ?* 繪制干擾線 ? ? ?* @param g ? ? ?*/ ? ? private void drawLine(Graphics g) { ? ? ? ? int x = random.nextInt(width); ? ? ? ? int y = random.nextInt(height); ? ? ? ? int xl = random.nextInt(MagicNumConstant.TWENTY); ? ? ? ? int yl = random.nextInt(MagicNumConstant.TEN); ? ? ? ? g.drawLine(x, y, x + xl, y + yl); ? ? } ? ? ? /** ? ? ?* 獲取隨機(jī)字符 ? ? ?* @param num ? ? ?* @return ? ? ?*/ ? ? private String getRandomString(int num) { ? ? ? ? int number = num > 0 ? num : randomString.length(); ? ? ? ? return String.valueOf(randomString.charAt(random.nextInt(number))); ? ? } ? ? ? /** ? ? ?* 繪制字符串 ? ? ?* @param g ? ? ?* @param randomStr ? ? ?* @param i ? ? ?* @return ? ? ?*/ ? ? private String drawString(Graphics g, String randomStr, int i) { ? ? ? ? g.setFont(getFont()); ? ? ? ? g.setColor(getRandomColor(MagicNumConstant.ONE_HUNDRED_EIGHT, MagicNumConstant.ONE_HUNDRED_NINETY)); ? ? ? ? String rand = getRandomString(random.nextInt(randomString.length())); ? ? ? ? String randomString = randomStr + rand; ? ? ? ? g.translate(random.nextInt(MagicNumConstant.THREE), random.nextInt(MagicNumConstant.SIX)); ? ? ? ? g.drawString(rand, MagicNumConstant.FORTY * i + MagicNumConstant.TEN, MagicNumConstant.TWENTY_FIVE); ? ? ? ? return randomString; ? ? } ? ? ? /** ? ? ?* 生成隨機(jī)圖片,返回 base64 字符串 ? ? ?* @param ? ? ?* @return ? ? ?*/ ? ? public Map<String, String> getImgCodeBaseCode(int length) { ? ? ? ? Map<String, String> result = new HashMap<>(); ? ? ? ? // BufferedImage類是具有緩沖區(qū)的Image類,Image類是用于描述圖像信息的類 ? ? ? ? BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); ? ? ? ? Graphics g = image.getGraphics(); ? ? ? ? g.fillRect(0, 0, width, height); ? ? ? ? // 獲取顏色 ? ? ? ? g.setColor(getRandomColor(MagicNumConstant.ONE_HUNDRED_FIVE, MagicNumConstant.ONE_HUNDRED_EIGHTY_NINE)); ? ? ? ? // 獲取字體 ? ? ? ? g.setFont(getFont()); ? ? ? ? // 繪制干擾線 ? ? ? ? for (int i = 0; i < lineSize; i++) { ? ? ? ? ? ? drawLine(g); ? ? ? ? } ? ? ? ? ? // 繪制隨機(jī)字符 ? ? ? ? String randomCode = ""; ? ? ? ? for (int i = 0; i < length; i++) { ? ? ? ? ? ? randomCode = drawString(g, randomCode, i); ? ? ? ? } ? ? ? ? g.dispose(); ? ? ? ? ? result.put("imgCode", randomCode); ? ? ? ? ? String base64Code = ""; ? ? ? ? try { ? ? ? ? ? ? //返回 base64 ? ? ? ? ? ? ByteArrayOutputStream bos = new ByteArrayOutputStream(); ? ? ? ? ? ? ImageIO.write(image, "PNG", bos); ? ? ? ? ? ? ? byte[] bytes = bos.toByteArray(); ? ? ? ? ? ? Base64.Encoder encoder = Base64.getEncoder(); ? ? ? ? ? ? base64Code = encoder.encodeToString(bytes); ? ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? log.debug(e.getMessage()); ? ? ? ? } ? ? ? ? result.put("data", "data:image/png;base64," + base64Code); ? ? ? ? return result; ? ? } ? }
后臺生成圖片base64,和一個唯一的key(通過這個key判斷是哪張圖片),后臺可以通過接口傳給前端圖片base64和key,前端輸入驗證碼,傳給后臺key和驗證碼去校驗驗證。
package com.hengtiansoft.gxrc.base.service.impl; ? import com.hengtiansoft.gxrc.base.service.ImgCaptchaService; import com.hengtiansoft.gxrc.base.util.ImgCaptchaUtil; import com.hengtiansoft.gxrc.common.constant.MagicNumConstant; import com.hengtiansoft.gxrc.common.entity.exception.BusinessException; import com.hengtiansoft.gxrc.common.redis.RedisOperation; import com.hengtiansoft.gxrc.common.util.UUIDUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; ? import java.util.Map; ? /** ?* @Description: ?* @Author: wu ?* @CreateDate: 2020/11/11 17:17 ?*/ @Service public class ImgCaptchaServiceImpl implements ImgCaptchaService { ? ? ? private static final String IMG_CAPTCHA = "imgCaptcha:"; ? ? ? @Autowired ? ? private RedisTemplate<String, Object> redisTemplate; ? ? ? @Autowired ? ? private ImgCaptchaUtil imgCaptchaUtil; ? ? ? @Override ? ? public Map<String, String> getImgCaptcha() { ? ? ? ? RedisOperation redisOperation = new RedisOperation(redisTemplate); ? ? ? ? Map<String, String> map = imgCaptchaUtil.getImgCodeBaseCode(MagicNumConstant.FOUR); ? ? ? ? String uuid = UUIDUtils.createUUID(); ? ? ? ? redisOperation.set(IMG_CAPTCHA + uuid, map.get("imgCode")); ? ? ? ? redisOperation.expire(IMG_CAPTCHA + uuid, MagicNumConstant.THIRTY_THOUSAND); ? ? ? ? map.remove("imgCode"); ? ? ? ? map.put("key", uuid); ? ? ? ? return map; ? ? } ? ? ? @Override ? ? public void checkImgCaptcha(String code, String key) { ? ? ? ? RedisOperation redisOperation = new RedisOperation(redisTemplate); ? ? ? ? String captcha = redisOperation.get(IMG_CAPTCHA + key); ? ? ? ? if (ObjectUtils.isEmpty(captcha) || !StringUtils.equals(captcha, code)) { ? ? ? ? ? ? throw new BusinessException("驗證碼錯誤"); ? ? ? ? } ? ? ? ? redisOperation.del(IMG_CAPTCHA + key); ? ? } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue中el-select中多選回顯數(shù)據(jù)后沒法重新選擇和更改的解決
本文主要介紹了vue中el-select中多選回顯數(shù)據(jù)后沒法重新選擇和更改解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01Vue3?$emit用法指南(含選項API、組合API及?setup?語法糖)
這篇文章主要介紹了Vue3?$emit用法指南,使用?emit,我們可以觸發(fā)事件并將數(shù)據(jù)傳遞到組件的層次結(jié)構(gòu)中,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Vue2.4+新增屬性.sync、$attrs、$listeners的具體使用
這篇文章主要介紹了Vue2.4+新增屬性.sync、$attrs、$listeners的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03vue使用vue-video-player插件播放視頻的步驟講解
在最近的項目中有一個視頻播放的功能,在之前的項目中沒有接觸過類似的功能,第一次接觸,把具體操作步驟一下,這篇文章主要給大家介紹了關(guān)于vue使用vue-video-player插件播放視頻的相關(guān)資料,需要的朋友可以參考下2022-12-12詳解Vue如何實現(xiàn)顏色選擇與調(diào)色板功能
顏色選擇和調(diào)色板是Web開發(fā)中常用的功能,Vue作為一個流行的JavaScript框架,可以方便地實現(xiàn)顏色選擇和調(diào)色板功能,本文講講如何在Vue中進(jìn)行顏色選擇和調(diào)色板吧2023-06-06vue截圖轉(zhuǎn)base64轉(zhuǎn)文件File異步獲取方式
這篇文章主要介紹了vue截圖轉(zhuǎn)base64轉(zhuǎn)文件File異步獲取方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03