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,根目录下建立文件

<!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

<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

/**
* 腾讯云滑块验证码封装工具(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,
};
如何调用

示例
showCaptcha({
onSuccess: (captchaResult) => {
// 人机验证成功,调用发送验证码接口
sendPhoneCode(captchaResult);
},
onClose: () => {
console.log('用户取消了验证');
},
onError: (error) => {
console.log('验证码错误');
}
}).catch(err => {
console.log('验证码调用失败');
});

浙公网安备 33010602011771号