uniapp App对接腾讯云滑块验证(增强加密)

参考文档

uniapp 各个端接入腾讯滑动行为验证码示例 https://blog.csdn.net/weixin_59685936/article/details/142057324
uni-app 与 web-view内嵌网页 双向通信 https://www.jianshu.com/p/1a28027a7dea
uniapp h5 和app(Android跟ios都可以)对接腾讯云验证码 https://blog.csdn.net/weixin_47403101/article/details/133070811

贴一下代码(优化版)

local.html,根目录下建立文件

image

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport"
      content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title>验证码</title>
    <style type="text/css">
      .app {
        width: 100%;
        height: 50px;
        padding: 20px 16px;
      }
      .btn {
        display: none;
        /* 默认隐藏,只在出错时显示 */
        width: 44px;
        height: 24px;
        border-radius: 4px;
        background-color: #efefef;
        color: #333;
        font-size: 12px;
        line-height: 24px;
        text-align: center;
        cursor: pointer;
      }
      .remind-text {
        padding-top: 300px;
        text-align: center;
        font-size: 14px;
        color: #6f6f6f;
        animation: pulse 1.5s ease-in-out infinite;
      }
      @keyframes pulse {
        0%,
        100% {
          opacity: 0.6;
        }
        50% {
          opacity: 1;
        }
      }
    </style>
  </head>
  <body bgcolor="#fff">
    <div class="app">
      <div class="btn" data-action="navigateBack">返回</div>
      <div class="remind-text">加载中,请稍候...</div>
    </div>
    <!-- uni 的 SDK -->
    <!-- 需要把 uni.webview.1.5.4.js 下载到自己的服务器 -->
    <script type="text/javascript" src="https://unpkg.com/@dcloudio/uni-webview-js@0.0.3/index.js">
    </script>
    <script type="text/javascript" src="https://turing.captcha.qcloud.com/TJCaptcha.js"></script>

    <script>
      document.addEventListener("UniAppJSBridgeReady", function() {
        // console.log("UniAppJSBridgeReady 已触发");

        // 隐藏加载提示
        function hideRemindText() {
          const remindText = document.querySelector(".remind-text");
          if (remindText) {
            remindText.style.display = "none";
          }
        }

        // 显示返回按钮(只在出错时调用)
        function showBackButton() {
          hideRemindText(); // 出错时先隐藏加载提示
          const btn = document.querySelector(".btn");
          if (btn) {
            btn.style.display = "block";
          }
        }

        // 从URL参数中获取appid
        function getUrlParam(name) {
          const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
          const r = window.location.search.substr(1).match(reg);
          if (r != null) return decodeURIComponent(r[2]);
          return null;
        }

        // 获取appid和aidEncrypted
        const appid = getUrlParam("appid");
        const aidEncrypted = getUrlParam("aidEncrypted");
        // console.log("获取到的 appid:", appid);
        // console.log("获取到的 aidEncrypted:", aidEncrypted);

        // 检查是否获取到必要参数
        if (!appid) {
          console.error("未获取到 appid");
          showBackButton(); // 显示返回按钮
          uni.postMessage({
            data: {
              res: {
                ret: -1,
                errorMsg: "验证码配置错误:缺少 appid 参数",
              },
            },
          });
          return;
        }

        if (!aidEncrypted) {
          console.error("未获取到 aidEncrypted");
          showBackButton(); // 显示返回按钮
          uni.postMessage({
            data: {
              res: {
                ret: -1,
                errorMsg: "验证码配置错误:缺少 aidEncrypted 参数",
              },
            },
          });
          return;
        }

        // 检查TencentCaptcha是否加载
        if (typeof TencentCaptcha === "undefined") {
          console.error("TencentCaptcha 未加载");
          showBackButton(); // 显示返回按钮
          uni.postMessage({
            data: {
              res: {
                ret: -1,
                errorMsg: "验证码SDK加载失败",
              },
            },
          });
          return;
        }

        // 验证码回调函数
        const callback = function(res) {
          // console.log("腾讯云验证码回调:", res);
          // 操作验证码后把回调信息传递给web-view
          // 页面回退由 captcha-webview.vue 统一处理
          uni.postMessage({
            data: {
              res: res,
            },
          });
        };

        // 创建验证码实例(使用加密方式)
        try {
          // console.log("使用加密方式创建验证码实例");
          const captcha = new TencentCaptcha(appid, callback, {
            aidEncrypted: aidEncrypted,
          });
          // 显示滑块验证码
          captcha.show();
          // 验证码显示后隐藏加载提示
          hideRemindText();
        } catch (error) {
          console.error("创建验证码实例失败:", error);
          showBackButton(); // 显示返回按钮
          uni.postMessage({
            data: {
              res: {
                ret: -1,
                errorMsg: "验证码初始化失败: " + error.message,
              },
            },
          });
        }

        // 返回按钮事件处理
        document
          .querySelector(".app")
          .addEventListener("click", function(evt) {
            const target = evt.target;
            // 检查是否有 data-action 属性(更通用的方式)
            const action = target.getAttribute("data-action");
            if (action) {
              switch (action) {
                case "navigateBack":
                  uni.navigateBack({
                    delta: 1,
                  });
                  break;
                default:
                  break;
              }
            }
          });
      });
    </script>
  </body>
</html>

captcha-webview.vue

4b525be8-110a-4aab-9fd9-16dc0b25e7e9

<template>
  <view class="captcha-webview-container">
    <!-- #ifdef APP-PLUS -->
    <web-view :src="webviewUrl" @message="handleMessage"></web-view>
    <!-- #endif -->
    
    <!-- #ifdef H5 -->
    <view class="h5-tips">
      <text>H5环境请使用标准验证码组件</text>
    </view>
    <!-- #endif -->
  </view>
</template>

<script setup>
  import { onLoad, onUnload } from "@dcloudio/uni-app";
  import { ref, computed } from 'vue';
  import handleCache from "@/utils/cache/cache.js";

  // 定义组件名称(可选)
  defineOptions({
    name: 'CaptchaWebview'
  });

  // 响应式数据
  const appid = ref('');
  const aidEncrypted = ref('');
  const captchaResult = ref(null);

  // 构建webview的URL
  const webviewUrl = computed(() => {
    // 使用本地HTML文件路径
    const baseUrl = '/hybrid/html/local.html';
    const params = [];
    
    if (appid.value) {
      params.push(`appid=${encodeURIComponent(appid.value)}`);
    }
    if (aidEncrypted.value) {
      params.push(`aidEncrypted=${encodeURIComponent(aidEncrypted.value)}`);
    }
    
    if (params.length > 0) {
      return `${baseUrl}?${params.join('&')}`;
    }
    return baseUrl;
  });

  // 页面加载时从缓存中获取参数
  onLoad(() => {
    // 从缓存中获取验证码配置
    // 使用 'once' 类型的缓存,获取一次后自动清除
    const captchaConfig = handleCache.get('captchaConfig');
    // console.log('从缓存获取的验证码配置:', captchaConfig);
    
    if (captchaConfig) {
      appid.value = captchaConfig.appid || '';
      aidEncrypted.value = captchaConfig.aidEncrypted || '';
    } else {
      console.error('未获取到验证码配置');
      // 配置缺失,发送错误事件并返回
      uni.$emit('captchaResult', {
        ret: -1,
        errorMsg: '验证码配置获取失败'
      });
      setTimeout(() => {
        uni.navigateBack({ delta: 1 });
      }, 500);
    }
  });

  // 标记是否已经发送过结果(避免重复发送)
  const hasEmittedResult = ref(false);

  // 页面卸载时处理
  onUnload(() => {
    // console.log('验证码页面卸载');
    // 如果有验证结果且还没有发送过,才通过事件总线传递
    if (captchaResult.value && !hasEmittedResult.value) {
      // console.log('onUnload中发送验证结果');
      uni.$emit('captchaResult', captchaResult.value);
      hasEmittedResult.value = true;
    } else if (!captchaResult.value) {
      // 用户未完成验证就返回,发送取消事件
      // console.log('用户未完成验证就返回');
      uni.$emit('captchaResult', {
        ret: 2,
        errorMsg: '用户取消了验证'
      });
    }
  });

  // 处理web-view的消息回调
  const handleMessage = (event) => {
    // console.log('收到web-view消息:', event);
    
    // event.detail.data 是一个数组,包含多次 postMessage 的数据
    const dataArray = event.detail.data;
    if (dataArray && dataArray.length > 0) {
      // 取最后一次的数据
      const data = dataArray[dataArray.length - 1];
      // console.log('验证码结果:', data);
      
      if (data.res) {
        captchaResult.value = data.res;
        
        // 通过事件总线发送验证结果(只发送一次)
        if (!hasEmittedResult.value) {
          // console.log('handleMessage中发送验证结果');
          uni.$emit('captchaResult', data.res);
          hasEmittedResult.value = true;
        }
        
        // 验证完成后自动返回
        setTimeout(() => {
          uni.navigateBack({
            delta: 1
          });
        }, 1000);
      }
    }
  };
</script>

<style scoped lang="less">
  .captcha-webview-container {
    width: 100%;
    height: 100vh;
    
    .h5-tips {
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100vh;
      font-size: 32rpx;
      color: #999;
    }
  }
</style>

tencent-captcha.js

86f1b32d-a165-4075-b7a8-32a5fd76db93

/**
 * 腾讯云滑块验证码封装工具(App端专用 - 加密版本)
 * 文档地址:https://cloud.tencent.com/document/product/1110/36841
 * 
 * 使用说明:
 * - 通过 webview 方式跳转到验证码页面进行验证
 * - 每次验证都需要获取新的 aidEncrypted(一次性密钥)
 * 
 * 工作流程
 * 用户点击"获取验证码"
 * ↓
 * 调用 showCaptcha()
 * ↓
 * 从接口获取 captchaAppId 和 aidEncrypted(每次都调用)
 * ↓
 * 跳转到 captcha-webview.vue 页面
 * ↓
 * webview 加载 local.html(传递 appid 和 aidEncrypted)
 * ↓
 * 使用加密方式实例化滑块验证码
 * ↓
 * 用户完成验证
 * ↓
 * 通过 uni.postMessage 传递结果
 * ↓
 * captcha-webview.vue 接收结果
 * ↓
 * 通过 uni.$emit 发送事件
 * ↓
 * showCaptcha 的 Promise resolve
 * ↓
 * 执行 onSuccess 回调
 * ↓
 * 自动返回上一页
 * ↓
 * 发送短信验证码
 * 
 */

import { showToast } from '@/utils/vant';
import { commonApi } from '@/services/apis';
import routerUse from "@/utils/router/use.js";
import handleCache from "@/utils/cache/cache.js";

/**
 * 获取验证码配置(包含 captchaAppId 和 aidEncrypted)
 * 注意:aidEncrypted 是一次性密钥,每次都需要重新获取
 * @returns {Promise<Object>} 返回 { captchaAppId, aidEncrypted }
 */
async function getCaptchaConfig() {
  try {
    const res = await commonApi.getCaptchaAppId();
    
    if (res.code == 200 && res.data) {
      // console.log('获取验证码配置成功:', res.data);
      
      // 验证返回数据格式
      if (!res.data.captchaAppId) {
        throw new Error('验证码配置错误:缺少 captchaAppId');
      }
      if (!res.data.aidEncrypted) {
        throw new Error('验证码配置错误:缺少 aidEncrypted');
      }
      
      return {
        captchaAppId: res.data.captchaAppId,
        aidEncrypted: res.data.aidEncrypted
      };
    } else {
      throw new Error('获取验证码配置失败');
    }
  } catch (error) {
    console.error('获取验证码配置失败:', error);
    throw error;
  }
}

/**
 * 显示腾讯云滑块验证码(通过webview方式 - 加密版本)
 * @param {Object} options 配置项
 * @param {Function} options.onSuccess 验证成功回调
 * @param {Function} options.onError 验证失败回调  
 * @param {Function} options.onClose 用户关闭回调
 * @returns {Promise} 返回Promise,resolve为验证成功的结果
 * 
 * 示例
 * showCaptcha({
 *   onSuccess: (captchaResult) => {
 *     // 人机验证成功,调用发送验证码接口
 *     sendPhoneCode(captchaResult);
 *   },
 *   onClose: () => {
 *     console.log('用户取消了验证');
 *   },
 *   onError: (error) => {
 *     console.log('验证码错误');
 *   }
 * }).catch(err => {
 *   console.log('验证码调用失败');
 * });
 */
export async function showCaptcha(options = {}) {
  try {
    // 每次都获取新的配置(因为 aidEncrypted 只能用一次)
    const config = await getCaptchaConfig();

    if (!config.captchaAppId || !config.aidEncrypted) {
      const errorMsg = '验证码配置错误:缺少必要参数';
      console.error(errorMsg);
      showToast(errorMsg);
      throw new Error(errorMsg);
    }

    return new Promise((resolve, reject) => {
      // 定义事件监听器
      const handleCaptchaResult = (res) => {
        // console.log('收到验证码结果:', res);

        // 移除事件监听
        uni.$off('captchaResult', handleCaptchaResult);

        // 判断验证是否真正成功:ret === 0 且没有 errorCode
        if (res.ret === 0 && !res.errorCode) {
          // 验证成功
          const result = {
            ticket: res.ticket,
            randstr: res.randstr,
          };

          if (options.onSuccess && typeof options.onSuccess === 'function') {
            options.onSuccess(result);
          }

          resolve(result);
        } else if (res.ret === 0 && res.errorCode) {
          // ret === 0 但存在 errorCode,表示验证失败(如配置错误)
          const errorMsg = `验证失败:错误码 ${res.errorCode}`;
          console.error('验证失败:', res);
          if (options.onError && typeof options.onError === 'function') {
            options.onError(res);
          } else {
            showToast(errorMsg);
          }
          reject(new Error(errorMsg));
        } else if (res.ret === 2) {
          // 用户关闭验证码
          const errorMsg = '已取消验证';
          if (options.onClose && typeof options.onClose === 'function') {
            options.onClose();
          } else {
            showToast(errorMsg);
          }
          reject(new Error(errorMsg));
        } else if (res.ret === -1) {
          // 错误
          const errorMsg = res.errorMsg || '验证失败';
          if (options.onError && typeof options.onError === 'function') {
            options.onError(res);
          } else {
            showToast(errorMsg);
          }
          reject(new Error(errorMsg));
        } else {
          // 其他错误
          const errorMsg = '验证失败,请重试';
          if (options.onError && typeof options.onError === 'function') {
            options.onError(res);
          } else {
            showToast(errorMsg);
          }
          reject(new Error(errorMsg));
        }
      };

      // 监听验证结果事件
      uni.$on('captchaResult', handleCaptchaResult);

      // 使用 handleCache 存储验证码配置,避免路由参数的特殊字符问题
      // 使用 'once' 参数,让缓存在获取一次后自动清除
      handleCache.set('captchaConfig', {
        appid: config.captchaAppId,
        aidEncrypted: config.aidEncrypted
      }, 'once');
      
      // 跳转到验证码页面(不需要传递参数)
      routerUse.push({
        name: 'captcha-webview',
        animationType: 'zoom-fade-out'
      }).catch(err => {
        console.error('跳转验证码页面失败:', err);
        uni.$off('captchaResult', handleCaptchaResult);
        // 清除缓存
        handleCache.remove('captchaConfig');
        reject(err);
      });
    });
  } catch (error) {
    console.error('显示验证码失败:', error);
    const errorMsg = '验证码初始化失败,请稍后重试';
    showToast(errorMsg);
    throw error;
  }
}

export default {
  showCaptcha,
};

如何调用

99a3b2af-95fe-4bc5-91a4-06eb1aa10f9b

  示例
  showCaptcha({
    onSuccess: (captchaResult) => {
      // 人机验证成功,调用发送验证码接口
      sendPhoneCode(captchaResult);
    },
    onClose: () => {
      console.log('用户取消了验证');
    },
    onError: (error) => {
      console.log('验证码错误');
    }
  }).catch(err => {
    console.log('验证码调用失败');
  });
posted @ 2025-11-06 09:24  huihuihero  阅读(19)  评论(0)    收藏  举报