vue3笔记(31)图形验证码

重写登录模块,里面有个忘记密码功能,需要发邮件找回,为了增加安全性,也需要增加一个输入验证码功能。本篇做一个记录。 

前期调研

网上查阅资料,关于图形验证码的生成,第一种是前端直接生成,第二种是后端生成一个二进制图片流,前端加载图片。
操作步骤:页面初始化时加载验证码,用户在输入框中输入图片中的数字/文字/答案,将输入传递给后端验证。有“刷新验证码”功能,点击后实现图片替换。

后端生成-图片流加载

将后端返回的二进制图片流转换成base64的图片,塞给image标签。这种方式,http的请求头responseType必须是arrayBuffer。
btoa() 方法可以将一个二进制字符串(例如,将字符串中的每一个字节都视为一个二进制数据字节)编码为 Base64 编码的 ASCII 字符串。

1
2
3
4
5
6
7
let bytes = new Uint8Array(data);
let storeData = "";
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
storeData += String.fromCharCode(bytes[i]);
}
this.imgUrl = "data:image/png;base64," + window.btoa(storeData);

前端生成-canvas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { GVerify } from '@/utils/piccode'
const props = defineProps({
width: {
type: Number,
default: 200
},
height: {
type: Number,
default: 60
}
})

let verifyCode = null
const picyzm = ref(null)

const state = reactive({
loading: false,
code: ''
})
const { code } = toRefs(state)
const code_content = ref('')

//刷新验证码
const OnRefresh = () => {
verifyCode.refresh()
const code = verifyCode.GetCode()
code_content.value = code
}

onMounted(() => {
picyzm.value && picyzm.value.focus()
//初始化验证码
verifyCode = new GVerify({
type: 'blend',
height: props.height,
con: picyzm.value
})

//获取验证码内容
const code = verifyCode.GetCode()
code_content.value = code
})

const handleLogin = () => {
if (code_content.value.toLowerCase() !== state.code.toLowerCase()) {
console.log('验证码输入不正确'):
verifyCode.refresh()
const code = verifyCode.GetCode()
code_content.value = code
return
}
}

const PassWordRandom = (count) => {
const str = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,0,1,2,3,4,5,6,7,8,9'
const arr = str.split(',')
let rand = ''

for (var i = 0; i < count; i++) {
rand += arr[Math.floor(Math.random() * 36)]
}

return rand
}

utils/piccode 代码如下:(基于开源版本修改)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/** 生成字母数组* */
function getAllLetter() {
const letterStr =
'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z'

return letterStr.split(',')
}

function randomNum(min, max) {
return Math.floor(Math.random() * (max - min) + min)
}

function randomColor(min, max) {
const r = randomNum(min, max)
const g = randomNum(min, max)
const b = randomNum(min, max)

return 'rgb(' + r + ',' + g + ',' + b + ')'
}

export function GVerify(options) {
// 创建一个图形验证码对象,接收options对象为参数
this.options = {
// 默认options参数值
id: '', // 容器Id
canvasId: 'verifyCanvas', // canvas的ID
width: '174', // 默认canvas宽度
height: '68', // 默认canvas高度
type: 'blend', // 图形验证码默认类型blend:数字字母混合类型、number:纯数字、letter:纯字母
code: '',
con: null
}

if (Object.prototype.toString.call(options) == '[object Object]') {
// 判断传入参数类型
for (const i in options) {
// 根据传入的参数,修改默认参数值
this.options[i] = options[i]
}
} else {
this.options.id = options
}

this.options.numArr =
'0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'.split(
','
)
this.options.letterArr = getAllLetter()

this._init()
this.refresh()
}

GVerify.prototype = {
/** 版本号* */
version: '1.0.0',

/** 初始化方法* */
_init: function () {
// var con = document.getElementById(this.options.id);
const con = this.options.con
const canvas = document.createElement('canvas')

this.options.width = con.offsetWidth > 0 ? con.offsetWidth : this.options.width
this.options.height = con.offsetHeight > 0 ? con.offsetHeight : this.options.height
canvas.id = this.options.canvasId
canvas.width = this.options.width
canvas.height = this.options.height
canvas.style.cursor = 'pointer'
canvas.innerHTML = '您的浏览器版本不支持canvas'
con.appendChild(canvas)
},

/** 生成验证码* */
refresh: function () {
this.options.code = ''
const canvas = document.getElementById(this.options.canvasId)
let ctx
if (canvas.getContext) {
ctx = canvas.getContext('2d')
} else {
return
}

ctx.textBaseline = 'middle'

ctx.fillStyle = randomColor(180, 240)
ctx.fillRect(0, 0, this.options.width, this.options.height)

let txtArr

if (this.options.type == 'blend') {
// 判断验证码类型
txtArr = this.options.numArr.concat(this.options.letterArr)
} else if (this.options.type == 'number') {
txtArr = this.options.numArr
} else {
txtArr = this.options.letterArr
}

for (let i = 1; i <= 4; i++) {
const txt = txtArr[randomNum(0, txtArr.length)]

this.options.code += txt
ctx.font = randomNum(this.options.height / 2, this.options.height) + 'px SimHei' // 随机生成字体大小
ctx.fillStyle = randomColor(50, 160) // 随机生成字体颜色
ctx.shadowOffsetX = randomNum(-3, 3)
ctx.shadowOffsetY = randomNum(-3, 3)
ctx.shadowBlur = randomNum(-3, 3)
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'

const x = (this.options.width / 5) * i
const y = this.options.height / 2
const deg = randomNum(-30, 30)

/** 设置旋转角度和坐标原点* */
ctx.translate(x, y)
ctx.rotate((deg * Math.PI) / 180)
ctx.fillText(txt, 0, 0)
/** 恢复旋转角度和坐标原点* */
ctx.rotate((-deg * Math.PI) / 180)
ctx.translate(-x, -y)
}

/** 绘制干扰线* */
for (let i = 0; i < 4; i++) {
ctx.strokeStyle = randomColor(40, 180)
ctx.beginPath()
ctx.moveTo(randomNum(0, this.options.width), randomNum(0, this.options.height))
ctx.lineTo(randomNum(0, this.options.width), randomNum(0, this.options.height))
ctx.stroke()
}

/** 绘制干扰点* */
for (let i = 0; i < this.options.width / 4; i++) {
ctx.fillStyle = randomColor(0, 255)
ctx.beginPath()
ctx.arc(
randomNum(0, this.options.width),
randomNum(0, this.options.height),
1,
0,
2 * Math.PI
)
ctx.fill()
}
},

/** 获取验证码* */
GetCode: function () {
return this.options.code
},
/** 验证验证码* */
validate: function (code) {
const code1 = code.toLowerCase()
const v_code = this.options.code.toLowerCase()

if (code1 == v_code) {
return true
} else {
return false
}
}
}

组件使用:

1
<captcha-fe></captcha-fe>