thinkphp6: 使用前后端分離的驗證碼(thinkphp 6.0.9/php 8.0.14/vue 3.2.26)


一,通過composer安裝think-captcha擴展包

1,命令:

liuhongdi@lhdpc:/data/php/admapi$ composer require topthink/think-captcha

說明:我們不直接使用這個擴展,只是用它帶來的字體文件

2,在項目中查看:

說明:劉宏締的架構森林是一個專注架構的博客,

網站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/05/29/thinkphp6-shi-yong-qian-hou-duan-fen-li-de-yan-zheng-ma/

         對應的源碼可以訪問這里獲取: https://github.com/liuhongdi/
         或: https://gitee.com/liuhongdi

說明:作者:劉宏締 郵箱: 371125307@qq.com

二,編寫php代碼

1,lib/util/captcode.php
<?php
namespace app\lib\util;

use think\Config;
use think\facade\Cache;
use think\Session;

class captcode {

    /**
     * @var Config|null
     */
    private $config = null;

    /**
     * @var Session|null
     */
    private $session = null;

    // 驗證碼字符集合
    protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY';
    // 驗證碼過期時間(s)
    protected $expire = 1800;
    // 使用中文驗證碼
    protected $useZh = false;
    // 中文驗證碼字符串
    protected $zhSet = '們以我到他會作時要動國產的一是工就年階義發成部民可出能方進在了不和有大這主中人上為來分生對於學下級地個用同行面說種過命度革而多子后自社加小機也經力線本電高量長黨得實家定深法表着水理化爭現所二起政三好十戰無農使性前等反體合斗路圖把結第里正新開論之物從當兩些還天資事隊批點育重其思與間內去因件日利相由壓員氣業代全組數果期導平各基或月毛然如應形想制心樣干都向變關問比展那它最及外沒看治提五解系林者米群頭意只明四道馬認次文通但條較克又公孔領軍流入接席位情運器並飛原油放立題質指建區驗活眾很教決特此常石強極土少已根共直團統式轉別造切九你取西持總料連任志觀調七么山程百報更見必真保熱委手改管處己將修支識病象幾先老光專什六型具示復安帶每東增則完風回南廣勞輪科北打積車計給節做務被整聯步類集號列溫裝即毫知軸研單色堅據速防史拉世設達爾場織歷花受求傳口斷況采精金界品判參層止邊清至萬確究書術狀廠須離再目海交權且兒青才證低越際八試規斯近注辦布門鐵需走議縣兵固除般引齒千勝細影濟白格效置推空配刀葉率述今選養德話查差半敵始片施響收華覺備名紅續均葯標記難存測士身緊液派准斤角降維板許破述技消底床田勢端感往神便賀村構照容非搞亞磨族火段算適講按值美態黃易彪服早班麥削信排台聲該擊素張密害侯草何樹肥繼右屬市嚴徑螺檢左頁抗蘇顯苦英快稱壞移約巴材省黑武培著河帝僅針怎植京助升王眼她抓含苗副雜普談圍食射源例致酸舊卻充足短划劑宣環落首尺波承粉踐府魚隨考刻靠夠滿夫失包住促枝局菌桿周護岩師舉曲春元超負砂封換太模貧減陽揚江析畝木言球朝醫校古呢稻宋聽唯輸滑站另衛字鼓剛寫劉微略范供阿塊某功套友限項余倒卷創律雨讓骨遠幫初皮播優占死毒圈偉季訓控激找叫雲互跟裂糧粒母練塞鋼頂策雙留誤礎吸阻故寸盾晚絲女散焊功株親院冷徹彈錯散商視藝滅版烈零室輕血倍缺厘泵察絕富城沖噴壤簡否柱李望盤磁雄似困鞏益洲脫投送奴側潤蓋揮距觸星松送獲興獨官混紀依未突架寬冬章濕偏紋吃執閥礦寨責熟穩奪硬價努翻奇甲預職評讀背協損棉侵灰雖矛厚羅泥辟告卵箱掌氧恩愛停曾溶營終綱孟錢待盡俄縮沙退陳討奮械載胞幼哪剝迫旋征槽倒握擔仍呀鮮吧卡粗介鑽逐弱腳怕鹽末陰豐霧冠丙街萊貝輻腸付吉滲瑞驚頓擠秒懸姆爛森糖聖凹陶詞遲蠶億矩康遵牧遭幅園腔訂香肉弟屋敏恢忘編印蜂急拿擴傷飛露核緣游振操央伍域甚迅輝異序免紙夜鄉久隸缸夾念蘭映溝乙嗎儒殺汽磷艱晶插埃燃歡鐵補咱芽永瓦傾陣碳演威附牙芽永瓦斜灌歐獻順豬洋腐請透司危括脈宜笑若尾束壯暴企菜穗楚漢愈綠拖牛份染既秋遍鍛玉夏療尖殖井費州訪吹榮銅沿替滾客召旱悟刺腦措貫藏敢令隙爐殼硫煤迎鑄粘探臨薄旬善福縱擇禮願伏殘雷延煙句純漸耕跑澤慢栽魯赤繁境潮橫掉錐希池敗船假亮謂托伙哲懷割擺貢呈勁財儀沉煉麻罪祖息車穿貨銷齊鼠抽畫飼龍庫守築房歌寒喜哥洗蝕廢納腹乎錄鏡婦惡脂庄擦險贊鍾搖典柄辯竹谷賣亂虛橋奧伯趕垂途額壁網截野遺靜謀弄掛課鎮妄盛耐援扎慮鍵歸符慶聚繞摩忙舞遇索顧膠羊湖釘仁音跡碎伸燈避泛亡答勇頻皇柳哈揭甘諾概憲濃島襲誰洪謝炮澆斑訊懂靈蛋閉孩釋乳巨徒私銀伊景坦累勻霉杜樂勒隔彎績招紹胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗鹼殊崗挖氏刃劇堆赫荷胸衡勤膜篇登駐案刊秧緩凸役剪川雪鏈漁啦臉戶洛孢勃盟買楊宗焦賽旗濾硅炭股坐蒸凝竟陷槍黎救冒暗洞犯筒您宋弧爆謬塗味津臂障褐陸啊健尊豆拔莫抵桑坡縫警挑污冰柬嘴啥飯塑寄趙喊墊丹渡耳刨虎筆稀昆浪薩茶滴淺擁穴覆倫娘噸浸袖珠雌媽紫戲塔錘震歲貌潔剖牢鋒疑霸閃埔猛訴刷狠忽災鬧喬唐漏聞沈熔氯荒莖男凡搶像漿旁玻亦忠唱蒙予紛捕鎖尤乘烏智淡允叛畜俘摸銹掃畢璃寶芯爺鑒秘凈蔣鈣肩騰枯拋軌堂拌爸循誘祝勵肯酒繩窮塘燥泡袋朗喂鋁軟渠顆慣貿糞綜牆趨彼屆墨礙啟逆卸航衣孫齡嶺騙休借';

    // 驗證碼位數
    protected $length = 4;

    //算術驗證碼
    protected $math = false;

    /**
     * 創建驗證碼
     * @return array
     * @throws Exception
     */
    public function generate(): array
    {
        $bag = '';

        if ($this->math) {
            $this->useZh  = false;
            $this->length = 5;

            $x   = random_int(10, 30);
            $y   = random_int(1, 9);
            $bag = "{$x} + {$y} = ";
            $key = $x + $y;
            $key .= '';
        } else {
            if ($this->useZh) {
                $characters = preg_split('/(?<!^)(?!$)/u', $this->zhSet);
            } else {
                $characters = str_split($this->codeSet);
            }

            for ($i = 0; $i < $this->length; $i++) {
                $bag .= $characters[rand(0, count($characters) - 1)];
            }

            $key = mb_strtolower($bag, 'UTF-8');
        }

        $key = uniqid().mt_rand(1000,9999);
        return [
            'value' => $bag,
            'key'   => $key,
        ];
    }

    /**
     * 驗證驗證碼是否正確
     * @access public
     * @param string $code 用戶驗證碼
     * @return bool 用戶驗證碼是否正確
     */
    public function check(string $code,string $key)
    {
        $cacheCode = Cache::get($key);
        if(!$cacheCode){
            return ['code'=>400,"msg"=>'驗證碼失效了'];
        }
        if(mb_strtolower($cacheCode) == mb_strtolower($code)){
            Cache::delete($key);
            return ['code'=>200,'msg'=>'驗證成功'];
        }
        return ['code'=>400,'msg'=>'驗證碼錯誤'];
    }
}

2,lib/util/captcha.php

<?php
namespace app\lib\util;

use app\result\Result;
use think\facade\Cache;
use think\Response;

class captcha {
    private $im    = null; // 驗證碼圖片實例
    private $color = null; // 驗證碼字體顏色
    // 使用背景圖片
    protected $useImgBg = false;
    // 驗證碼字體大小(px)
    protected $fontSize = 25;
    // 是否畫混淆曲線
    protected $useCurve = true;
    // 是否添加雜點
    protected $useNoise = true;
    // 驗證碼圖片高度
    protected $imageH = 0;
    // 驗證碼圖片寬度
    protected $imageW = 0;
    // 驗證碼字體,不設置隨機獲取
    protected $fontttf = '';
    // 背景顏色
    protected $bg = [243, 251, 254];
    // 使用中文驗證碼
    protected $useZh = false;
    // 驗證碼位數
    protected $length = 4;
    //算術驗證碼
    protected $math = false;

    /**
     * 輸出驗證碼
     * @access public
     * @param null|string $config
     * @param bool        $api
     * @return Response
     */
    public function create($generator)
    {
        $config =    [
            // 驗證碼字體大小
            'fontSize'    =>    80,
            // 驗證碼位數
            'length'      =>    4,
            // 關閉驗證碼雜點
            'useNoise'    =>    true,
        ];
        $key = $generator['key'];
        // 圖片寬(px)
        $this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2;
        // 圖片高(px)
        $this->imageH || $this->imageH = $this->fontSize * 2.5;
        
        // 建立一幅 $this->imageW x $this->imageH 的圖像
        $this->im = imagecreate(intval($this->imageW), intval($this->imageH));
        // 設置背景
        imagecolorallocate($this->im, $this->bg[0], $this->bg[1], $this->bg[2]);

        // 驗證碼字體隨機顏色
        $this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));

        // 驗證碼使用隨機字體
        $ttfPath = root_path().'/vendor/topthink/think-captcha/assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/';

        if (empty($this->fontttf)) {
            $dir  = dir($ttfPath);
            $ttfs = [];
            while (false !== ($file = $dir->read())) {
                if ('.' != $file[0] && substr($file, -4) == '.ttf') {
                    $ttfs[] = $file;
                }
            }
            $dir->close();
            $this->fontttf = $ttfs[array_rand($ttfs)];
        }

        $fontttf = $ttfPath . $this->fontttf;

        if ($this->useImgBg) {
            $this->background();
        }

        if ($this->useNoise) {
            // 繪雜點
            $this->writeNoise();
        }
        if ($this->useCurve) {
            // 繪干擾線
            $this->writeCurve();
        }

        // 繪驗證碼
        $text = $this->useZh ? preg_split('/(?<!^)(?!$)/u', $generator['value']) : str_split($generator['value']); // 驗證碼

        foreach ($text as $index => $char) {
            //mt_rand(1.2, 1.6) *
            $x     = $this->fontSize * ($index + 1) *  ($this->math ? 1 : 1.5);
            $y     = $this->fontSize + mt_rand(10, 20);
            $angle = $this->math ? 0 : mt_rand(-40, 40);

            imagettftext($this->im, $this->fontSize, $angle, intval($x), intval($y), intval($this->color), $fontttf, $char);
        }

        ob_start();
        // 輸出圖像
        imagepng($this->im);
        $content = ob_get_clean();
        imagedestroy($this->im);
        $base64 = chunk_split(base64_encode($content));
        $data = [
            "key"=>$key,
            "image"=>"data:image/jpg/png/gif;base64,".$base64,
            //"code"=>$generator['value'],
        ];

        return $data;
   }

    /**
     * 畫一條由兩條連在一起構成的隨機正弦函數曲線作干擾線(你可以改成更帥的曲線函數)
     *
     *      高中的數學公式咋都忘了涅,寫出來
     *        正弦型函數解析式:y=Asin(ωx+φ)+b
     *      各常數值對函數圖像的影響:
     *        A:決定峰值(即縱向拉伸壓縮的倍數)
     *        b:表示波形在Y軸的位置關系或縱向移動距離(上加下減)
     *        φ:決定波形與X軸位置關系或橫向移動距離(左加右減)
     *        ω:決定周期(最小正周期T=2π/∣ω∣)
     *
     */
    protected function writeCurve(): void
    {
        $px = $py = 0;

        // 曲線前部分
        $A = mt_rand(1, intval($this->imageH / 2)); // 振幅
        $b = mt_rand(intval(-$this->imageH / 4), intval($this->imageH / 4)); // Y軸方向偏移量
        $f = mt_rand(intval(-$this->imageH / 4), intval($this->imageH / 4)); // X軸方向偏移量
        $T = mt_rand(intval($this->imageH), intval($this->imageW * 2)); // 周期
        $w = (2 * M_PI) / $T;

        $px1 = 0; // 曲線橫坐標起始位置
        $px2 = mt_rand(intval($this->imageW / 2), intval($this->imageW * 0.8)); // 曲線橫坐標結束位置

        for ($px = $px1; $px <= $px2; $px = $px + 1) {
            if (0 != $w) {
                $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
                $i  = (int) ($this->fontSize / 5);
                while ($i > 0) {
                    imagesetpixel($this->im, intval($px + $i), intval($py + $i), intval($this->color)); 
                    $i--;
                }
            }
        }

        // 曲線后部分
        $A   = mt_rand(1, intval($this->imageH / 2)); // 振幅
        $f   = mt_rand(intval(-$this->imageH / 4), intval($this->imageH / 4)); // X軸方向偏移量
        $T   = mt_rand(intval($this->imageH), intval($this->imageW * 2)); // 周期
        $w   = (2 * M_PI) / $T;
        $b   = $py - $A * sin($w * $px + $f) - $this->imageH / 2;
        $px1 = $px2;
        $px2 = $this->imageW;

        for ($px = $px1; $px <= $px2; $px = $px + 1) {
            if (0 != $w) {
                $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
                $i  = (int) ($this->fontSize / 5);
                while ($i > 0) {
                    imagesetpixel($this->im, intval($px + $i), intval($py + $i), intval($this->color));
                    $i--;
                }
            }
        }
    }

    /**
     * 畫雜點
     * 往圖片上寫不同顏色的字母或數字
     */
    protected function writeNoise(): void
    {
        $codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
        for ($i = 0; $i < 10; $i++) {
            //雜點顏色
            $noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
            for ($j = 0; $j < 5; $j++) {
                // 繪雜點
                imagestring($this->im, 5, mt_rand(-10, intval($this->imageW)), mt_rand(-10, intval($this->imageH)), $codeSet[mt_rand(0, 29)], $noiseColor);
            }
        }
    }

    /**
     * 繪制背景圖片
     * 注:如果驗證碼輸出圖片比較大,將占用比較多的系統資源
     */
    protected function background(): void
    {
        $path = __DIR__ . '/../assets/bgs/';
        $dir  = dir($path);

        $bgs = [];
        while (false !== ($file = $dir->read())) {
            if ('.' != $file[0] && substr($file, -4) == '.jpg') {
                $bgs[] = $path . $file;
            }
        }
        $dir->close();

        $gb = $bgs[array_rand($bgs)];

        list($width, $height) = @getimagesize($gb);
        // Resample
        $bgImage = @imagecreatefromjpeg($gb);
        @imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height);
        @imagedestroy($bgImage);
    }
}

3,controller/Article.php

<?php
declare (strict_types = 1);

namespace app\controller;

use app\BaseController;
use app\lib\util\captcha;
use app\result\Result;
use think\Request;
use think\facade\Cache;
use app\model\Article as ArticleModel;
use Exception;
use think\Config;
use think\Response;
use think\Session;
//use think\facade\Cache;
use app\lib\util\captcode;

class Article extends BaseController
{
    /**
     * 生成驗證碼
     *
     * @return \think\Response
     */
    public function captcha() {
        $code = new captcode();
        $generator = $code->generate();
        //var_dump($generator);

        $capt = new captcha();
        $image = $capt->create($generator);

        Cache::set($generator['key'],$generator['value'],300);

        //var_dump($image);
        return  Result::Success($image);
    }

    /**
     * 檢查驗證碼是否正確
     *
     * @return \think\Response
     */
    public function captcheck() {
        //得到參數
        $code = $this->request->param("captcha");
        $key = $this->request->param("key");

        //比較
        $ccode = new captcode();
        $resCheck = $ccode->check($code,$key);
        if ($resCheck['code'] == 200) {
            return  Result::Success("驗證成功");
        } else {
            return  Result::Error($resCheck['code'],$resCheck['msg']);
        }
    }
}

 4,app/result/Result.php

<?php

namespace app\result;

use think\response\Json;

class Result {
    //success,返回數據
    static public function Success($data):Json {
        $rs = [
            'code'=>0,
            'msg'=>"success",
            'data'=>$data,
        ];
        return json($rs);
    }
    //error需要code/msg參數
    static public function ErrorCode($code,$msg):Json {
        $rs = [
            'code'=>$code,
            'msg'=>$msg,
            'data'=>"",
        ];
        return json($rs);
    }
    //error,傳入定義的數組常量
    static public function Error($arr):Json {
        $rs = [
            'code'=>$arr['code'],
            'msg'=>$arr['msg'],
            'data'=>"",
        ];
        return json($rs);
    }
}

三,編寫vue代碼

1,Captcha.vue

<template>
<div style="text-align: center;">
  <img ref="imgRef" @click="getCaptcha" :src="captData.image" /><br/>
  <input id="captInput" v-model="captValue"  type="text" placeholder="請輸入驗證碼" style="width:392px;margin-top:10px;font-size:16px;" /><br/>
  <!--v-loading.fullscreen.lock="isLoading"-->
  <div    @click="authcapt"  style="margin:0px auto;margin-top:10px;width: 400px;height:40px;line-height:40px;background: #ff0000;border-radius: 10px;">
    登錄
  </div>
</div>
</template>
<script>
import { ref} from "vue";
import {ElMessage} from "element-plus";
import {apiCaptcha, apiCaptCheck} from "../api/api";
export default {
  name: "Captcha",
  setup() {
      const imgRef =  ref(null);
      const captData = ref([]);
      const captValue = ref("");
      const getCaptcha = () => {
        apiCaptcha().then(res => {
          //成功
          if (res.code == 0) {
             console.log(res.data);
            captData.value = res.data;
          } else {
            ElMessage.error("獲取驗證碼失敗:"+res.msg);
          }
        }).catch((error) => {
          console.log(error)
        })
      }
      getCaptcha();
      const authcapt=()=> {
        //alert("當前輸入的驗證碼是:"+captValue.value);
        if (captValue.value.length < 4) {
          alert("驗證碼長度不可小於4");
          document.getElementById('captInput').focus();
          return;
        }
        var data = new FormData();
        data.append("key",captData.value.key);
        data.append("captcha",captValue.value);
        apiCaptCheck(data).then(res => {
          //成功
          if (res.code == 0) {
            //保存jwt token到本地
            //localStorage.setItem('token', res.data.token);
            //提示
            ElMessage.success("驗證成功!");

          } else {
            ElMessage.error("驗證失敗:"+res.msg);
          }

        }).catch((error) => {
          console.log(error)
        })
      }

      return {
        imgRef,
        captData,
        captValue,
        authcapt,
        //reloadCapt,
      }
  }
}
</script>

<style scoped>

</style>

2,api.js

//get,
import { get, postForm} from './axios'

//user
export const apiCaptcha = pCaptcha => get('/api/article/captcha', pCaptcha)
export const apiCaptCheck = pCaptCheck => postForm('/api/article/captcheck', pCaptCheck)

 

四,測試效果

五,查看php和thinkphp的版本

php:
root@lhdpc:~# php --version
PHP 8.0.14 (cli) (built: Dec 23 2021 11:52:42) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.14, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.14, Copyright (c), by Zend Technologies
thinkphp:
root@lhdpc:~# cd /data/php/admapi/
root@lhdpc:/data/php/admapi# php think version
v6.0.9 

 

六,查看vue的版本:

liuhongdi@lhdpc:/data/vue/demo1$ npm list vue
demo1@0.1.0 /data/vue/demo1
├─┬ @vue/cli-plugin-babel@4.5.15
│ └─┬ @vue/babel-preset-app@4.5.15
│   └── vue@3.2.26 deduped
├─┬ element-plus@1.2.0-beta.6
│ ├─┬ @element-plus/icons-vue@0.2.4
│ │ └── vue@3.2.26 deduped
│ ├─┬ @vueuse/core@7.4.1
│ │ ├─┬ @vueuse/shared@7.4.1
│ │ │ └── vue@3.2.26 deduped
│ │ ├─┬ vue-demi@0.12.1
│ │ │ └── vue@3.2.26 deduped
│ │ └── vue@3.2.26 deduped
│ └── vue@3.2.26 deduped
└─┬ vue@3.2.26
  └─┬ @vue/server-renderer@3.2.26
    └── vue@3.2.26 deduped

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM