记录一次实际项目中,uni-app对接企业微信跳转应用页面的处理方案
若想让企业微信的信息点击后,能跳转到原先做的APP中的某个页面,该怎么处理?
前言
- 我上面提到的需求,在很多小型公司里应该挺常见的吧
- 毕竟小公司首要目的是节约开支,包括开发人员上的开支,所以会选择uni-app开发移动端应用,希望一处开发多平台部署,比如即要跑在原生移动设备上、又要跑在微信之类的小程序上,然后有希望能够从企业微信之类的公众号通知信息中跳转到指定的h5页面上
- 这篇文章就是针对“从企业微信之类的公众号通知信息中跳转到指定的h5页面上”这种需求的,由于原先的APP是通过从登录页,登录成功后获取token,然后让用户正常进入应用进行操作的,而直接跳转到指定页面这种需求,我们为了尽量避免麻烦,当然就选择把uni-app打包成h5项目直接部署到web上进行访问,然后由于是直接跳转到某个页面的,当然就需要解决token问题
- 下面就开始展示处理方案了
- 【本文仅是我的个人见解,若写的不好欢迎评论(特别是我的实际项目属于老项目,都没写Promise,都粗暴得使用了回调嵌套,当然你可以改用Promise,这样代码就优雅些)】
方案关键处展示(源码在文章最后)
- 1 - http请求函数封装
-
- 在 commmon/ 目录下分别为 App 和 H5 分开封装了两个 http 请求函数
-
- 因为在 App 和 H5 两个环境下,token处理以及请求拦截的处理逻辑完全不同
-
-
- (1)App环境下是正常登录逻辑,那么请求函数中主要包含的逻辑大概是:token由用户登录时进行存储,若无token时,拦截器需要重定向路由到登录页;
-
-
- (2)H5环境下,需要实现企业微信通过url链接直接跳转到某个页面,那么请求函数中主要逻辑包含的是:进入页面时,必须先通过url中的code(企业微信提供用于获取用户信息的东东)向后端相应接口获取一次token和用户数据,前端存储起来,且若token若过期了,要为用户实现无感知的token刷新功能;

// request.js
import requestApp from './requestApp.js'
import requestWebWxwork from './requestWebWxwork.js'
// 若从企业微信跳转到指定页面,则一定会带上此参数,此参数需要用于获取token
export * from './weChatWorkHttp.js'
/**
* 适用于一些需要多平台上展示的页面(目前仅支持 企业微信 和 本APP应用两种来源)
* @param {*} options
* @returns
*/
export default function request (options) {
// #ifdef H5
// --------------- 备注 -------------------
// 这里我根据实际项目情况写的,我遇到的项目本身没有H5的需求,原先的代码中使用的都是 requestApp.js 中的请求函数,后来有了让部分页面需要直接从企业微信跳转链接的需求,
// 那么我只需要强行判定凡是用这个新 request.js 请求函数的话,在H5环境下都一律使用 requestWebWxwork.js 请求函数即可
// 至于平时开发在h5环境下调试的话,只需要转存用户登录的token,就能模拟出企业微信跳转到指定页的效果了
// 若你的项目比较复杂,即本身就有h5环境下需求,那么推荐可以根据白名单进行判定,比如凡事记录在白名单中的路由,都使用 requestWebWxwork.js 请求函数,因为凡是有关链接跳转的路由,一定需要加入到白名单中的,不然就会走入登录流程,就会遇到被重定向到登录页的情况
// ----------------------------------
// 可暴力解决针对企业微信跳转过来时需要使用的http函数(凡是web都认为是用于企业微信,反正暂时也没有完整移动h5APP的需求,只需要部分页面满足企业微信能够直接跳转即可)
// 对于平时开发在 web 上调试开发时,无视弹窗提示登录成功之后,把token转存一下即可:window.localStorage.setItem('wechartwork_token',window.localStorage.getItem('token'))
return requestWebWxwork(options)
// #endif
// #ifdef APP-PLUS
return requestApp(options)
// #endif
}
// requestWebWxwork.js
import { BASE_URL } from './requestConfig.js'
import { setWxworkUserInfo, getWxworkUserInfo, setWxworkToken, getWxworkToken } from './wxworkInfo.js'
import { getUrlParams, readRouteQuery } from './utils.js'
const RECALL_API_MAX = 10 // 重复发起请求最大次数
let recallApiCount = 0
export function getWxworkCodeFromUrl() {
// 第一次从企业微信跳转过来必须带上此 redirect 参数,其中藏了code,且此code仅可使用一次,否则将无法获取token
const routeQuery = readRouteQuery()
// 若路由参数中没有code参数,再尝试找是否存在 redirect 参数,若有则从此参数中尝试获取 code来调用接口获取token,若还是没有则直接打印错误信息
let wxworkCode = routeQuery.code
if(!wxworkCode) {
if(!!routeQuery.redirect) {
const redirectJson = getUrlParams(decodeURIComponent(redirect))
wxworkCode = redirectJson.code
}
}
return wxworkCode
}
export function stroeWxworkToken({params={},callback=(_params)=>{}}) {
let token = getWxworkToken()
if(!!token) { // 若已存在token,则不再重复执行
callback(params)
return
}
// 第一次从企业微信跳转过来必须带上此 redirect 参数,其中藏了code,且此code仅可使用一次,否则将无法获取token
const wxworkCode = getWxworkCodeFromUrl()
if(!wxworkCode) {
uni.showModal({
title: '提示',
content: 'URL中缺少code参数,无法获取token',
showCancel: false
})
console.error('** 获取 token 发生异常: 【WxworkHttp】redirect 中缺少必要的 code 参数,无法获取token,目前收到的请求参数是 => ',params)
return
}
uni.request({
url: BASE_URL + '/wxworkSign/' + wxworkCode,
method: 'POST',
timeout: 60000,
success:(res)=>{
console.log('----- wxlogin res = ', res)
if(res.data.code !== 200) {
uni.showModal({
title: '提示',
content: res.data.msg,
showCancel: false
})
return
}
// 获取的用户信息存入,以便全局使用
setWxworkUserInfo(res.data.user)
// 存入token
setWxworkToken(res.data.token)
// 获取token后,回调新参数
const newParams = {...params, token: res.data.token}
// --
callback(newParams)
},
fail: (_err) => {
uni.showModal({
title: '提示',
content: '令牌获取出错',
showCancel: false
})
}
})
}
// 专用于企业微信跳转指定路由时使用的 http 请求
function WxworkHttp (params) {
let url = BASE_URL + params.url;
let method = params.method;
let data = params.data || {};
let header = { 'content-type': 'application/json' }
let token = params.token || getWxworkToken()
if(!!token) {
header['Authorization'] = token
// 重制重发请求次数
recallApiCount = 0
} else {
// 控制尝试重复发起请求的次数,避免死循环
if(recallApiCount > RECALL_API_MAX) return
recallApiCount += 1
// 第一次从企业微信跳转过来必须带上此 redirect 参数,其中藏了code,且此code仅可使用一次,否则将无法获取token
stroeWxworkToken({
params,
callback:(newParams)=>{
WxworkHttp(newParams)
}
})
return
}
// 发起网络请求
return uni.request({
url: url,
method: method || "GET",
data: data,
timeout: 60000,
header,
sslVerify: false, // 是否验证ssl证书
success: res => {
if (res.data.code == 401) { // 401 = token 过期,刷新接口后在重新发起请求
// 控制尝试重复发起请求的次数,避免死循环
if(recallApiCount > RECALL_API_MAX) return
recallApiCount += 1
// 刷新token的接口
const userInfo = getWxworkUserInfo()
uni.request({
url: BASE_URL.BASE_URL() + '/qyWxRefresh',
method: 'POST',
timeout: 60000,
data: {
wxUserId: userInfo.wxUserId,
uuid: '1'
},
success:(refreshRes)=>{
// 存入新token
setWxworkToken(refreshRes.data.token)
// 获取token后,再重新请求接口
const newParams = {...params, token: refreshRes.data.token}
WxworkHttp(newParams)
},
fail: (_err) => {
uni.showModal({
title: '提示',
content: '令牌更新出错',
showCancel: false
})
}
})
return;
}
// 重制重发请求次数
recallApiCount = 0
typeof params.success == "function" && params.success(res.data);
},
fail: err => {
uni.showModal({
title: '提示',
content: '网络请求出错',
showCancel: false
})
typeof params.fail == "function" && params.fail(err.data);
},
complete: (e) => {
typeof params.complete == "function" && params.complete(e.data);
return;
}
})
}
export default WxworkHttp
import { BASE_URL } from './requestConfig.js'
module.exports = (params) => {
let url = BASE_URL + params.url;
let method = params.method;
let header = params.header || {};
let data = params.data || {};
// 请求方式 GET POST
// 发起网络请求
uni.request({
url: url,
method: method || "GET",
data: data,
timeout: 60000,
header: {
'content-type': 'application/json',
'Authorization': uni.getStorageSync('token')
},
sslVerify: false, // 是否验证ssl证书
success: res => {
uni.hideLoading();
if (res.data.code == 401) {
uni.removeStorageSync('token')
uni.removeStorageSync('user');
uni.reLaunch({
url: '/pages/login'
});
return;
}
if (res.data.status == 403) {
uni.removeStorageSync('token')
return;
}
if(res.data.code ==500){
uni.showToast({
title:res.data.msg,
icon:'none'
})
uni.hideLoading();
return
}
typeof params.success == "function" && params.success(res.data);
},
fail: err => {
uni.showModal({
title: '提示',
content: '请求失败',
success: (res) => {
uni.removeStorageSync('token')
uni.reLaunch({
url: '/pages/login'
})
}
})
uni.hideLoading();
typeof params.fail == "function" && params.fail(err.data);
},
complete: (e) => {
typeof params.complete == "function" && params.complete(e.data);
return;
}
})
}
- main.js 中把请求封装到全局,组件中根据情况选用相对应的http请求函数
// main.js 关键代码如下
// #ifdef H5 || APP-PLUS
...其他代码
import Vue from 'vue'
// 组件中根据自身情况选用请求函数即可
import request from './common/request.js'
Vue.prototype.$request = request;
import requestWebWxwork from './common/requestWebWxwork.js'
Vue.prototype.$requestWebWxwork = requestWebWxwork;
import requestApp from './common/requestApp.js'
Vue.prototype.$requestApp = requestApp;
...其他代码
// #endif
...其他代码
- 页面组件代码案例

-
- pages/demoA/wxwork.vue (针对企业微信通过url链接跳转到指定页的 组件代码 案例展示)
<template>
<view class="content">
<fullscreenLoading v-if="tokenLoading"></fullscreenLoading>
<view v-else>
<image class="logo" src="/static/logo.png"></image>
<u-button type="primary">uview ui h5 组件使用测试</u-button>
<view class="text-area">
<text class="title">{{title}}</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// 企业微信直接跳转到该页面,需要优先检查并处理token问题,再挂载业务组件
tokenLoading: true,
title: 'Hello'
}
},
onLoad(_options) {
// 由于企业微信跳转的url带参数可能出现幺蛾子,所以这里通过自己写的路由解析函数来处理
const routeQuery = this.$readRouteQuery()
console.log('routeQuery = ', routeQuery)
this.initWxworkToken()
},
methods: {
initWxworkToken() {
this.$stroeWxworkToken({
callback:(_newParams)=>{
// 这里自定义一份
this.onComponentCreate()
}
})
},
onComponentCreate(){
// 至于这个 this.tokenLoading 什么时候解除可以根据情况而定
this.tokenLoading = false
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
源码链接在此!!!