webviewH5调用小程序的接口jssdk扫一扫的坑

377 阅读10分钟

最麻烦的事情是这玩意要在正式上调试,相当于你不能在内网自己调试,每发布一次就调试一次转存失败,建议直接上传图片文件​编辑

看到无数次这个invalid signature都崩溃了

最后我看到了config ok,觉得简直是奇迹。

这个链接是jssdk的使用说明,简单来说就是这个要前后端配合的。相信后端!

后端工作:

1.要是公众号的appid而不是小程序的,申请h5的域名,自己配好白名单。权限问题害得是后端

2.看文档后端自己去拿AccessToken,通过AccessToken拿jsapi_ticket ,拿到之后最谜语人头疼的的就是这步----加密算法

但其实就是把jsapi_ticket 变成 三个东西signature,timestamp,nonceStr。

其中因为jsapi_ticket是不能频繁获取,2小时内有效,所以后端需要缓存到数据库,2小时内jsapi_ticket不变,signature,timestamp,nonceStr是给前端的,这三个随时变,怎么转?其实已经给了你函数了,自己去下载吧

www.weixinsxy.com/jssdk/sampl…

上面有java,nodejs,python等,直接用就行

3.url这个特别关键,url不能硬编码hardCode,开给前端,让前端传给你去拿signature,timestamp,nonceStr,所以这个接口就是这样写的,前端给url和token,后端return回signature,timestamp,nonceStr

 最后要用这个校验微信 JS 接口签名校验工具

概述 | 微信开放文档微信开发者平台文档https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62转存失败,建议直接上传图片文件​编辑

前端工作:

1.拿到接口,baseUrl必须是域名的,正在的接口叫www.xxxxx.com

(这也是白名单的接口),然后axios.post拿params是url,url是当前的url,url是最关键的,url后如果有?参数的话,可以把参数?后面的传入sessionToken里面,然后url传入?前面的那个短的就行,拿url也可以传入的时候写同一个就可以了。目的就是拿到正确的signature,timestamp,nonceStr,这步和后端联调校验确认好

2.下面就是引入sdk了,两种方法,第一种是转存失败,建议直接上传图片文件​编辑cnpm i weixin-js-sdk,然后import wx from "weixin-js-sdk";

但是由于我的框架是uniapp+vite+vue3+ts,uniapp它自带wx这个接口,所以会有变量冲突,污染的问题,所以我用了第二种方法在index.html里面

   <script
      type="text/javascript"
      src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"
    ></script>
/**wxx是jssdk*/
window\["wxx"] = wx;
console.log("引入的wxsdk", window\["wxx"]);** 然后这样就行,写在body的前面,打印看看有什么接口

转存失败,建议直接上传图片文件​编辑

我们看到确实有scanQRCode这个接口,和小程序原生的scanCode是不同滴

3.引入jssdk后,并且拿到那三个后signature,timestamp,nonceStr,配置,注意是公众号的appID,而不是小程序的,debug暂时先开着true,后面上线再关掉,主要是看弹出来的signature是否有效

   window["wxx"].config({
      debug: false, // 是否开启调试模式,可以在开发阶段设置为true,便于调试
      // appId: "wx5c1cdd31fc7a0898", // 小程序的AppID
      appId: "wxa9d93851b1f8a5a0", // 公众号的AppID
      timestamp: timestamp,
      nonceStr: nonceStr,
      signature: signature,
      jsApiList: ["scanQRCode"], // 需要使用的微信功能、列表
   });

4. 如果以上都没问题,那下面就简单了,直接调用就可以,注意这个needResult一般都是1,如果不是1的话success的回调函数就不能执行了,扫到什么就弹出什么

/**wx的jssdk相机扫码 */
function QRCode() {
    console.log("微信扫一扫")
    window["wxx"].scanQRCode({
        // onlyFromCamera: false,
        needResult: 1,
        scanType: ["qrCode", "barCode"],
        success: (result) => {
            console.log('result', result.resultStr);
            let QRCode = result.resultStr.trim() + ""
            // =======================================

            BS.ED.emit("聚焦铝模板", QRCode);
            selectedItem.value = QRCode
            inputValue.value = QRCode
            closeSearchContent()

        },
        fail: (res) => {
            console.log('fail', res);
            // let QRCode = "DGSSL-T4-yhy-A-Q40-59"
            // BS.ED.emit("聚焦铝模板", QRCode);
            wx.showToast({
                title: "打开失败" + res,
                icon: "none",
                image: "",
                duration: 1500,
                mask: true,
            });

        },
    });
}

5.这里的路由最好是用hash模式,因为ios端和android端的微信小程序webview是不同的,ios端据说是会存整个的url,所以你把#之前传给服务器就行,之前一直在ios系统扫不了码

在manifest.json文件中

"h5" : {
        "router" : {
            "mode" : "hash",
            // "mode" : "history",
            "base" : "/vr3d/"
            // "base" : "/"
        },
        "sdkConfigs" : {
            "maps" : {
                "google" : {
                    "key" : ""
                }
            }
        }
    }

这个弄了差不多一个星期有多,非常头疼,需要前后端和发布人员极度配合,之前我也走了很多弯路,比如github看源代码,h5跳回小程序又跳回h5,通过url传参等等,也看到很多人在文档下面骂这个功能为什么这么难,都不行,希望能把微信小程序能把和h5配合的文档弄得简单点吧...........希望这篇文章能用简练的语言给大家一点启发!!

补更:关于ipad的扫码问题,微信的jssdk的扫一扫好像没有做ipad端的,相机能打开,但是扫了之后就不返回了 我只能自己再在网上找插件补一下ipad扫码了

/**判断是否是ipad,ipad只能用html5QRCode插件了 */
function isPad() {
    let isiPad = navigator.userAgent.includes("iPad");
    return isiPad;
}
// 初始化二维码扫描
const WXCMD_scan = () => {
    console.log("点击微信扫一扫wxx", window["wxx"])
    console.log("点击微信扫一扫wx", wx)


    if (isPad()) {
        // alert('这是ipad')
        useCamera()
    } else {
        QRCode()
    }
}

vue文件的demo在这,自取

<template>
    <!-- h5自身扫码试验 -->
    <div class="readerrrrrr" id="readerrrrrr" ref="readerRef">
    </div>
    <div v-if="isOpenScan" class="exitScan">
        <button class="buttonn" @click="exitCamera()">退出扫码</button>
    </div>
    <div class="content2" v-if="showUI">
        <button @click="useCamera()">使用相机扫一扫方式</button>
        <div class="result">
            <h3 ref="resultsRef">ddddd</h3>
        </div>
    </div>
</template>
  
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import BottomNav from '../index/bottomNav.vue';
import ButtomSupport from '@/pages/index/bottomSupport.vue'
import { Html5Qrcode } from 'html5-qrcode'
let readerRef = ref(null)
const showUI = ref(true)
let resultsRef = ref(null)
let html5QrCode
let config
onMounted(() => {
    //1.Html5QrcodeScanner是js提供的ui; 2.Html5Qrcode是自定义面板
    html5QrCode = new Html5Qrcode("readerrrrrr");
    config = { fps: 10, qrbox: { width: 300, height: 300 } }; //扫一扫相关设置
    //1000/10=100毫秒扫描一次
})
const isOpenScan = ref(false)
function exitCamera() {
    readerRef.value.style.display = "none";
    isOpenScan.value = false
    stop()
}

function stop() {
    html5QrCode.stop().then((ignore) => {
        // QR Code scanning is stopped.
        console.log("QR Code scanning stopped.");
    })
        .catch((err) => {
            // Stop failed, handle it.
            console.log("Unable to stop scanning.");
        });

}
//相机授权
function useCamera() {
    hasScaned = false
    readerRef.value.style.display = "block";
    resultsRef.value.innerText = "";
    // showUI.value=false
    Html5Qrcode.getCameras()
        .then((devices) => {
            isOpenScan.value = true
            if (devices && devices.length) {
                let cameraId = "";
                if (devices.length == 1) {
                    cameraId = devices[0].id; //前置摄像头
                } else {
                    cameraId = devices[1].id;  //后置摄像头
                }
                if (cameraId) {
                    startWithCameraId(cameraId);
                }
            } else {
                startWithoutCameraId();
            }
        })
        .catch((err) => {
            console.log("没有获取摄像头设备...");
            alert("没有获取摄像头设备...")
        });
}

//带相机ID扫描
function startWithCameraId(cameraId) {
    html5QrCode
        .start(
            { deviceId: { exact: cameraId } },
            config,
            onScanSuccess,
            onScanFailure
        )
        .catch((err) => {
            console.log("通过摄像头扫码异常....", err);
        });
}

//不带相机ID扫描,允许传递约束来代替相机设备 ID
function startWithoutCameraId() {
    //environment 表示后置摄像头  换成user则表示前置摄像头
    html5QrCode.start(
        { facingMode: "environment" } || {
            facingMode: { exact: "environment" },
        },
        config,
        onScanSuccess,
        onScanFailure
    );
}
let hasScaned = false
//扫码解析成功后按照自己的需求做后续的操作
function onScanSuccess(decodedText, decodedResult) {
    resultsRef.value.innerText = "扫码成功结果:\n" + decodedText;
    if (hasScaned) return
    if (decodedText) {
        alert("扫码结果是 " + decodedText);
        hasScaned = true; // 将标志变量设为 true,表示已经弹出过对话框
        stop()
        readerRef.value.style.display = "none";
    }
}

//扫码解析失败后按照自己的需求做后续的操作
function onScanFailure(error) {
    resultsRef.value.innerText = "扫码失败:\n" + error;
}
</script>
  
<style scoped>
.content2 {
    position: fixed;
    display: flex;
    width: 100vw;
    height: 100vh;
    background: linear-gradient(rgb(238, 238, 238), rgb(38, 79, 55));
}

.readerrrrrr {
    position: absolute;
    width: 100%;
    height: 100%;
    /* background: #414141; */
    pointer-events: none;
    z-index: 999;
    display: flex;
    justify-content: center;
    /* align-items: center; */
}

/* .readerrrrrr{
    position: absolute;
    margin-top: 100px;
    width:50%;
    height: 50%;
    background: #414141;
    pointer-events: none;
    z-index: 999;
  } */
button {
    display: block;
    width: 100%;
    margin: 6px;
    outline: none;
    height: 40px;
    line-height: 40px;
    color: #fff;
    background-color: #26a2ff;
    text-align: center;
    border-radius: 4px;
    border: none;
    cursor: pointer;
}

.result {
    margin-top: 40px;
    position: fixed;
    width: 100%;
    height: 30%;
    background: #e4e4e4;
    z-index: 9999;
}

.buttonn {
    position: fixed;
    display: block;
    width: 50%;
    height: 40px;
    bottom: 20px;
    color: #fff;
    background-color: #26a2ff;
    text-align: center;
    border-radius: 4px;
    cursor: pointer;
    pointer-events: visible;

}

.exitScan {
    position: fixed;
    pointer-events: none;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 9999;
    /* background: #000; */
}
</style>
  

还有这个要求https的才能有权限,那我这里有两本证书

image.png

192.168.110.176-key.pem是

-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC56EdZU9X7tDVO
1O1yJNqwK/lsEWUCV97GiFAJmL+cxahZ3CjI3tZJP11h+AtmBiWETRoPRsOzkTSn
b6YNZvGyMpR59PlCPwEcTc0Cz4DP0TqfLahht+2DtOuDt8TAsnzfjn9fcEJ7XNZf
eh5luWXJFrULb8F4z71c9wAH4oD4Ga1FproZsoDS1GapJvrt/NkZgmVUwE1VMqXj
SFgDLL65zuXEJ3Jb0ETgtA21eVb66KsZeXGeXWfBf4VAcPIeelFIyRIs3S6Z22D/
9efmkaQozyi5HU4EcBDjiTcF/H+eeq+YBlYalFdhN9tNw4aj+B0ZU1P+NqdecWoh
5+Ei4lfXAgMBAAECggEBALWGUOSwL6judX7b+l4SDlcnGQykj3SSSDyirUQxmieB
+LiFeG1q6OHstUoL4VVlewyMHH3+IukbR8aWBhXgBvBw4vmScjB1RKhWb2/nHWfO
bOcwtdF0vr5z0eNzdPKTVcsUYNrXypKaO9hObY/2T6TiPzVDki5c6hH0PFt2l2eo
QKvuglu15a4LHefWLo4ne89Nje9Q+nYvVaGMxnF+8lynPNVUWRkhad32TeI/Dlvp
LHw6hFjvpb3kphtyZUQjMxN82nqn2YL8g/7fqNlN9iCSVvtahTiHJM9CSZBMcc3q
j2jB1oJpCarF4sZwtm0yCBaBcUyKf/W7eBYWYDRgJoECgYEA3WMCtcrL4etG1528
756Of7ubboT2F7gYaFyCIK3jYneM8YSMZudb3czgve1otipWFvs8ztLEDXh8A4bR
3HiKP1VLGmZpQ4j2TAT9I8bVfg8lWXKJkZKit1ZSBOEhM2CROCi/Bi6WgwmCcKL7
f7ThefN1Hr4QsS6nL5KUD+lEzWkCgYEA1vk08BKi5QqgTBzGq5GnuXKadB6vIH6Q
QX3NtIH5frZ1L8H0BawZf9GsJ8Y4kRbCW+XA+Fw1iZjSdepMKMreV9I1S3/oG6sx
q+3+2RUtq1a5nxY+uy8nAPBeHATUHtnJN5r9tLeqsuIYgsfkG2zELIHHAYIX5Nbu
4c56OpptEz8CgYEAgBy8w8DAbVM+oqW4YR3rLoW9c55j3uP809+8ufaGoEO4f0cK
DL6Tze2ynJYXQ1uKiDAJR1J2e6kectgA98mVjwLnvDZJcfh/NwyoBJ0ajKFtJq7+
ZwxpNxkvy30QPnACeXIy4Pvyw4+sOUxHp+ZfmLfHLewlMrNhskjrf62o0AkCgYAT
yGUtvplM+JhdyLwjp8jGkRxTmUtGcz81N62JcfiSx5mrJm8dYoQKNjJgiqZD+9Mw
/8itUlb+7ZhYj5IootqpPEf5RbEHcs6kYsd0FLXaMaXtVO+67BDrzjLq4yreF+6j
dljom4pS4emdh2WffHflHKBCrEUkov7iusyWvgVRowKBgQCtbDogoaox+KtG0BWU
v/mub/E9o6L0wAWie9/VSPvsOYR5ykBSJcuOAvoAGbw23NTE7Zr45sD5t8knzbU5
wkS69rKVbdIt7W6xrvIH/WcRyjD1eOkKBCNz8lK4HD1KN1NQzNwW3zk3xQrL+pXm
TVnawjE7aPOQr1eVTp9YYPCwCA==
-----END PRIVATE KEY-----

192.168.110.176.pem是

-----BEGIN CERTIFICATE-----
MIIEXDCCAsSgAwIBAgIQf2yv1NlZ3DZc/QtEmSVNYDANBgkqhkiG9w0BAQsFADCB
lzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTYwNAYDVQQLDC1ERVNL
VE9QLTVSMVE3QzlcQWRtaW5pc3RyYXRvckBERVNLVE9QLTVSMVE3QzkxPTA7BgNV
BAMMNG1rY2VydCBERVNLVE9QLTVSMVE3QzlcQWRtaW5pc3RyYXRvckBERVNLVE9Q
LTVSMVE3QzkwHhcNMjMwODA5MDk0NzI4WhcNMjUxMTA5MDk0NzI4WjBhMScwJQYD
VQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxNjA0BgNVBAsMLURF
U0tUT1AtNVIxUTdDOVxBZG1pbmlzdHJhdG9yQERFU0tUT1AtNVIxUTdDOTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnoR1lT1fu0NU7U7XIk2rAr+WwR
ZQJX3saIUAmYv5zFqFncKMje1kk/XWH4C2YGJYRNGg9Gw7ORNKdvpg1m8bIylHn0
+UI/ARxNzQLPgM/ROp8tqGG37YO064O3xMCyfN+Of19wQntc1l96HmW5ZckWtQtv
wXjPvVz3AAfigPgZrUWmuhmygNLUZqkm+u382RmCZVTATVUypeNIWAMsvrnO5cQn
clvQROC0DbV5Vvroqxl5cZ5dZ8F/hUBw8h56UUjJEizdLpnbYP/15+aRpCjPKLkd
TgRwEOOJNwX8f556r5gGVhqUV2E3203DhqP4HRlTU/42p15xaiHn4SLiV9cCAwEA
AaNZMFcwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1Ud
IwQYMBaAFE4/+pGaLmHU/O/gcfWZoXCUQnxiMA8GA1UdEQQIMAaHBMCobrAwDQYJ
KoZIhvcNAQELBQADggGBALCY4RL5iLvKQ4yMZ4ywUDElWhvifxrFdQYkxDFZLze8
OxT8KzMaJoK+OiU6pHcqlHa/azDgOgRNh60zQh0m3XOG5xDoq2TH0ooZp4ejY16f
UN53hGUIEnhRcmDmR6wvpXwp2xUBIgD0j24RJ85ryVEnsxYsGzGvl7FAR/g1mJLA
96DP8/b5dQckDUYhucJD3JHL1O6UQ5q/hOuuPMliqgGb8yo5Vc43G4t4/DrIugrn
Cke2pYZV5iwHjYadJqYZjn2vrVwE9KKtWjPmKa9x98/Hf/I8WB6hd5fcM2AT09hH
sF7z4uDzEYmZcx/6OAijXrFOX0ZnuupWhj3Lh29b37Cuk3QrSUV/v5MMfXS2rFSB
UNVYgTXfstZ+ZwRaUQzFYP4p8KNAFQ/r8Fw9q+i0yHyqeeMEb3kSpuSHEsGNJGZm
i1s6vklU4G5VDfi0iVVN31HzZi0fsyZJhZdB38gshVrsWnzoFKTf7YgSRg61hetu
v5WYoPUclENS8vc3mWazzw==
-----END CERTIFICATE-----


本地IP不同,重命名一下就可以了

在vite.config.ts设置,我是用uniapp写的,运行就npm run dev:h5

import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import path from "path";
export default defineConfig({
  // base: "./", // 新增
  plugins: [uni()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
  server: {
    https: {
      key: "./public/192.168.110.176-key.pem",
      cert: "./public/192.168.110.176.pem",
    },
    port: 6600,
  },
});