Koa從零搭建之驗證碼實現

前言

目前咱們所能接收到的驗證碼有前端

  • 短信/郵件 - 使用數/字符串隨機組合,通常長度爲4~6node

    • 圖1
  • 識別圖片 - 字符串/運算跨域

    • 圖1
    • 圖1
  • 滑塊 - 拼圖緩存

    • 圖1
  • 點選 - 點擊目標文字/圖片,順序點擊等服務器

    • 圖1
    • 圖1

準備

接下來咱們使用Koa實現上述的集中驗證碼,在此以前咱們須要安裝一些插件app

  • svg-captcha - 實現圖片識別類驗證碼
  • gm - 實現滑動解鎖
  • GraphicsMagick - 圖片處理
  • ImageMagick - 圖片處理
  • nodemailer - 發送郵件

圖片識別類的驗證碼有不少,好比cors

  • captchapng
  • ccap
  • trek-captcha

基礎功能不盡相同,能夠自行嘗試。dom

因爲本文須要與前端進行交互,還須要koa

  • koa2-cors

來解決跨域。async

實現

跨域

在開始寫代碼以前 咱們固然要解決跨域的問題,在app.js中寫入配置

const cors = require('koa2-cors')
app.use(cors({
  origin: function (ctx) {
    return '*';
  },
  exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
  maxAge: 5,
  credentials: true,
  allowMethods: ['GET', 'POST', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}))

隨機字符

隨機字符,很簡單,咱們使用svg-captcha來隨機生成字符及圖片,將圖片返回前端,服務端存取其對應值來進行驗證便可。
直接上代碼

const svgCaptcha = require('svg-captcha')

const getString = async () => {
  const cap = svgCaptcha.create({
    size: 4, // 驗證碼長度
    width:160,
    height:60,
    fontSize: 50,
    ignoreChars: '0oO1ilI', // 驗證碼字符中排除 0o1i
    noise: 2, // 干擾線條的數量
    color: true, // 驗證碼的字符是否有顏色,默認沒有,若是設定了背景,則默認有
    background: '#eee' // 驗證碼圖片背景顏色
  })
  let img = cap.data // 驗證碼
  var text = cap.text.toLowerCase() // 驗證碼字符,忽略大小寫
  return {svg: `${img}<span >${text}</span>`}
}

咱們會返回給客戶端"svg",直接渲染便可。

計算類

一樣咱們也使用svg-captcha來實現

const svgCaptcha = require('svg-captcha')
const getNumber = async () => {
  const cap = svgCaptcha.createMathExpr({
    size: 4, // 驗證碼長度
    width:160,
    height:60,
    fontSize: 50,
    ignoreChars: '0oO1ilI', // 驗證碼字符中排除 0o1i
    noise: 2, // 干擾線條的數量
    color: true, // 驗證碼的字符是否有顏色,默認沒有,若是設定了背景,則默認有
    background: '#eee' // 驗證碼圖片背景顏色
  })
  let img = cap.data // 驗證碼
  var text = cap.text.toLowerCase() // 驗證碼字符,忽略大小寫
  return {svg: `${img}<span>${text}</span>`}
}

同上。

滑動

滑動模塊實現較爲複雜,但其邏輯仍是很簡單,咱們先來梳理一下實現邏輯。

服務端:

  1. 一張大圖,其中擁有50*50px的缺塊
  2. 一張 50*50px的圖來填補空缺
  3. 大圖空缺位置的座標(隨機生成)

服務端生成上述信息返回客戶端(座標值返回y)。

客戶端:

  1. 渲染大圖
  2. 初始化小圖位置(y已知,left:0)
  3. 滑動結束後像服務端發送x座標進行驗證

圖片生成

const gm = require('gm').subClass({imageMagick: true});
var arrBuffer = []
const getSlide = async () => {
  arrBuffer = []
  const width = 420
  const height = 250
  const fragmentSize = 50
  try {
    // 生成圖片
    const filePath = getRandomPath()
    const x = (Math.floor(Math.random() * 1000) % (width - 2 * fragmentSize)) + fragmentSize
    const y = Math.floor(Math.random() * 1000) % (height - fragmentSize)
    const { image, fragment } = await createImage(filePath, width, height, fragmentSize, x, y)
    // 緩存記錄
    arrBuffer.push({x})
    console.log(arrBuffer)
    return { msg: "ok", data: { image, fragment, y } }
  } catch (err) {
    return {  msg: "服務器錯誤:" + err, data: null }
  }
}
function getRandomPath() {
  const fileLength = 4
  const index = Math.floor(Math.random() * 1000) % fileLength
  return path.resolve(__dirname, `../static/images/${index + 1}.jpg`)
}
function createImage(filePath, w, h, s, x, y) {
  return new Promise((resolve, reject) => {
    const res = { image: "", fragment: "" }
    gm(filePath)
    .resize(w, h, "!")
    .fill("rgba(0,0,0,.5)")
      //繪製由座標對、寬度和高度指定的矩形。
      .drawRectangle(x, y, x + s - 1, y + s - 1)
      .noProfile()
      .setFormat('jpeg')
      .toBuffer( (err, buffer) => {
      if (err) {
        reject(err)
      }
      res.image = "data:image/jpg;base64," + buffer.toString("base64")
      gm(filePath)
      .resize(w, h, "!")
      .crop(s, s, x, y)
      .noProfile()
      .setFormat('jpeg')
      .toBuffer((err, buffer) => {
        if (err) {
          reject(err)
        }
        res.fragment = "data:image/jpg;base64," + buffer.toString("base64")
        resolve(res)
      })
    })
  })
}

這樣咱們就能夠拿到,兩張圖片及其座標。

  • 大圖

    • 1
  • 小圖

    • 2

前端根據兩張圖及y座標就能夠實現滑塊的初始化

1

滑動的過程相信你們都會寫!在這不盡行詳細描述。當咱們監聽到滑動結束後,將此時小圖的 clienX值記錄返回服務端,與服務端緩存的X座標進行匹配。

那麼咱們來實現Check接口

const check = async (data) => {
  const {x} = data
  const isMatch = Math.abs(x - arrBuffer[0].x) < 5
  if (isMatch) {
    return {success: true, msg: '驗證成功,已超過99.9%的用戶'}
  } else {
    return {success: false, msg: '驗證失敗'}
  }
}

郵件

郵件驗證碼難點在於發送郵件。咱們只須要將隨機生成的字符串經過郵件發送至目標客戶端便可,短信也是一樣的道理。

咱們來實現一個簡單的郵件發送接口。

const nodemailer = require('nodemailer')
const smtpConfig = {
  host: 'smtp.163.com',
  port: 465,
  secure: true,
  auth: {
    user: '',
    pass: ''
  }
}

const mail = async (data) => {
  var transporter = nodemailer.createTransport(smtpConfig);
  let mailOptions = {
    from: '',
    to: '',
    subject: '驗證碼',
    text: Math.random().toString(36).substr(2,4)
  }
  transporter.sendMail(mailOptions, (error, info) => {
    if(error){
      return console.log(error);
    }
    console.log(info)
  })
  return {success: true}
}
相關文章
相關標籤/搜索