feat: 添加滑动验证码
This commit is contained in:
68
src/api/captcha/canvas.ts
Normal file
68
src/api/captcha/canvas.ts
Normal 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
97
src/api/captcha/index.ts
Normal 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',
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user