Vue3 实现图形验证码功能

5,561 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

需求来源:

应该平台项目演示会后,老板提出来的,因为看到平台的登录,总觉得少了点东西,说是要加上,那就加吧

1660737329928.png

准备时间:

时间蛮长的,架构组的同事提了2周,一直都没有给说什么做,因为在参与其他紧急项目救火。大概上周抽了1天时间做了相关开发。

内心独白:

觉得这个功能可有可无,对于ToB的平台,要这干什么。安全考虑还是。。。
不想了,问了一圈都没说出个什么

技术准备:

大概知道怎么弄,但是做之前习惯性的搜索搜索相关技术,就去翻了一遍掘金,发现大部分的验证码功能都是canvas前端自己绘制,封装成一个组件,传入一些参数,然后作为一个公共组件去调用。我觉得也应该这么做。

沟通结果:

与架构侧的同事沟通后,他更期望主要功能在后端实现,一来不太相信前端,二来是考虑安全问题。我这暴脾气,一听就来气,什么不相信前端的东西。我想了想,前端组件实现2套,根据配置切换,一可以切换前端canvas绘制图形验证码也可以远端获取。但是现阶段,没多少时间和精力,不能搞的太麻烦,先实现第一版。

最近决定后端来生成验证码,主要工作在后端,前端事情相对较少,这样出问题容易甩出去。以安全为主考虑为重(决策盾牌)

功能定位:

放在了业务侧的 businessComponents 目录下,业务组件 Security

组件实现:

<template>
  <div class="code" @click="refreshCode" title="刷新验证码">
    <img class="verification-code" :src="verificationUrl" alt="验证码" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { Service } from "@basic-library";

// 验证码请求地址
const verificationUrl = ref()

/**
 * 点击刷新
 */
const refreshCode = async (params) => {
  verificationUrl.value = Service.parse('queryCaptcha')
}

defineExpose({
  refreshCode,
});

</script>

<style lang="scss" scoped>
.verification-code {
  vertical-align: middle;
  cursor: pointer;
}
</style>

Service.parse()方法,主要是框架层提供,解析和拼装请求url地址的,因为接口返回的是图片的二进制流,直接扔给image的src,这样比较直接些

更好的方式:

使用http请求的方式,返回的二进制图片流,转换后,最终成为base64的图片塞给image标签去

     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);

这种方式,http的请求头,需要更改

responseType必须是arrayBuffer,json是不行的

后面根据情况,在去修改吧,先搞简单的版本。

上面只是说了验证码图片组件的实现,下面描述下场景使用

登录页面使用:

// 验证码区域
<el-form-item prop="captcha" label="">
   <div class="captcha-text">
    <el-input placeholder="请输入验证码" v-model="userForm.captcha" @keyup.enter="submitForm()"></el-input>
  </div>
  <div class="captcha-code">
      <Security ref="SecurityRefs"></Security>
  </div>
</el-form-item>
import { Security } from '@businessComponents'
/**
 * 加载验证码
 */
const loadVerifyCode = (params) => {
  SecurityRefs.value.refreshCode()
  userForm.captcha = ''
}

/**
 * @desc 登录
 * @param {Object} NO
 */
const jumpLogin = async () => {
  setConfig()

  const { accountName, password, captcha } = userForm
  const result = await Service.useHttp({
    name: "loginService",
    headers: { captcha }
  },{
    accountName,
    password,
  });

  loading.value = false
  // 刷新验证码
  loadVerifyCode()
  
  if (result?.success) {
    if(!loginAfterService(result)) return
    LoginLoader.redirectApp('/')
  }
};

onMounted(async () => {
  // 加载验证码
  loadVerifyCode()
});

上述代码中的大体实现思路已经有了,代码中有部分:

  • 框架提供:
    Service.useHttp 发送请求
    LoginLoader.redirectApp 应用跳转,默认当前应用首页
  • 页内方法:
    loginAfterService 请求成功后,登录数据校验部分,不合规的无法登录
    setConfig 登录前做相关数据配置

写完,午睡了

补充:

中午测试妹子,来找到我,这个验证码输入框,输入中文后报错,我默默的打开了控制台,看到了 fetch-like-axioshttpInstance.interceptors.response.use报错,ISO什么错来着,应该协议格式问题,默认是charset=utf-8

解决方式:

验证码输入框,禁止输入中文和控制,进行控制 onkeyup="value=value.replace(/[^\w\.\/]/ig,'')"

<el-input placeholder="请输入验证码" 
  v-model="userForm.captcha"  
  onkeyup="value=value.replace(/[^\w\.\/]/ig,'')" 
  @keyup.enter="submitForm()" 
  maxlength=20>
</el-input>