feat: 添加滑动验证码

This commit is contained in:
landaiqing
2024-04-24 16:29:29 +08:00
parent 46462c4514
commit 773dbe1e7e
10 changed files with 211 additions and 50 deletions

68
src/api/captcha/canvas.ts Normal file
View File

@@ -0,0 +1,68 @@
const calcSize = (img: HTMLImageElement, size: number) => {
const src_src = Math.max(Math.min(img.width, img.height, size), 160)
const dst_w = src_src
const dst_h = src_src
const dst_scale = dst_h / dst_w // Target image ratio
const src_scale = img.height / img.width // Original image aspect ratio
const info =
src_scale >= dst_scale
? [
Math.round(img.height * (src_src / img.width)),
0,
Math.round((img.height - img.width) / 2),
]
: [
Math.round(img.width * (src_src / img.height)),
Math.round((img.width - img.height) / 2),
0,
]
return [img.width, img.height, src_src, ...info]
}
const build = (img: HTMLImageElement, sizes: number[]): [number, string] => {
const [src_w, src_h, size, tar_size, x, y] = sizes
const canvas = document.createElement('canvas')
canvas.width = size
canvas.height = size
const max = 275 - 50
const min = 0
const ave = Math.round((360 / max) * 100) / 100
const ctx = canvas.getContext('2d')
const coordinate = size / 2
const moveX = Math.floor(Math.random() * (max - min + 1)) + 0
ctx?.beginPath()
ctx?.translate(coordinate, coordinate)
ctx?.rotate((moveX * -1 * ave * Math.PI) / 180)
ctx?.translate(-coordinate, -coordinate)
ctx?.drawImage(img, x, y, src_w, src_h, 0, 0, tar_size, size)
ctx!.globalCompositeOperation = 'destination-in'
ctx?.arc(size / 2, size / 2, size / 2, 0, (360 * Math.PI) / 180, false)
ctx?.fill()
ctx?.restore()
return [(360 / (max - min)) * moveX, canvas.toDataURL('image/png')]
}
export const handle = (url: string, size: number = 350) =>
new Promise<[number, string]>((resovle) => {
const img = new Image()
img.onerror = function () {
console.log('image load error')
}
img.onload = function () {
const sizes = calcSize(img, size)
const arc_img = build(img, sizes)
resovle(arc_img)
}
img.src = url
})

97
src/api/captcha/index.ts Normal file
View File

@@ -0,0 +1,97 @@
import type { TicketInfoType, TokenInfoType } from 'react-rotate-captcha'
import { handle } from './canvas'
import wallhaven from '@/assets/images/wallhaven.jpg'
export type ActionType = {
code: 0 | 1
msg: string
}
const tokenRaw = 'Nvuv8LdXUNRAVW022Gm7HkGc7RTDoEmU'
const info = {
angle: -1,
sid: '',
ticket: '',
}
export async function checkTicket(ticket?: TicketInfoType) {
const { sid, ticket: ticketRaw } = info
const { data } = ticket || {}
const isWait = sid !== '' && ticketRaw !== ''
const success = sid === data?.sid && ticketRaw === data.ticket
const result =
isWait && success
? {
code: 0,
msg: 'Successful',
}
: {
code: 1,
msg: 'Failed',
}
return result as ActionType
}
export async function get(): Promise<TokenInfoType> {
info.angle = -1
info.sid = ''
info.ticket = ''
return {
code: 0,
data: {
str: 'wallhaven',
token: tokenRaw,
},
msg: 'success',
}
}
export function isSupportWebp() {
try {
return (
document
.createElement('canvas')
.toDataURL('image/webp', 0.5)
.indexOf('data:image/webp') === 0
)
} catch (err) {
return false
}
}
export async function load() {
const [degree, src] = await handle(wallhaven)
info.angle = degree
return src
}
export function sleep(time: number) {
return new Promise((resolve) => {
setTimeout(() => resolve(true), time)
})
}
export async function verify(token: string, deg: number): Promise<TicketInfoType> {
const { angle } = info
const success = token === tokenRaw && Math.abs(deg - angle) <= 5
info.sid = Math.random().toString(36).slice(-8)
info.ticket = crypto.randomUUID()
return angle >= 0 && success
? {
code: 0,
data: {
sid: info.sid,
ticket: info.ticket,
},
msg: 'Success',
}
: {
code: 1,
msg: 'Fail verify',
}
}