h5企微插件之WeixinJSBridge、iframe通过cors-anywhere加载微信文章实践

项目背景

公司有一个运营同事使用的素材库,是h5开发的企微插件,原本素材主要是公司域名的文章,点击文章可以进入详情页查看文章内容,并且可以转发客户、群发客户、发朋友圈等。现在希望可以支持非公司域名的文章,主要以微信公众号的文章为主,兼顾一些其他文章,还需要统计分享出去的链接访问量,根据分享OA人员进行统计

原本设计方案

详情页使用iframe展示文章,底部是功能按钮,相当简单

开搞

需求到手后,想着这不很简单吗,拿着键盘就要开撸,先让后端配几个微信公众号文章,结果一试傻眼了,居然报了iframe的错误
在这里插入图片描述

百度之后发现,原来这是微信防盗链导致的

但是,虽说微信的防盗链很牛逼,但是也是有解决方案的,那就是cors-anywhere,但是目前该项目是停运的了,需要手动下载代码,起一下服务即可

https://github.com/Rob–W/cors-anywhere

在这里插入图片描述

接下来就可以通过代理和一系列字符串操作加载微信公众号文章了

主要就几个点

  1. 设置X-Requested-With为XMLHttpRequest,否则会请求报错
  2. 将图片的data-src属性转成src属性
  3. 将css文件中的href="//res.wx.qq.com xxx"改成带href="https://res.wx.qq.com xxx"前缀的,不然会请求相对路径
  4. 微信拦截所有图片访问受限,会出现图片加载不了问题:添加一下代码解决
  5. 解决背景图片不展示问题
  6. 主体内容不可见:获取rich_media_content元素,将元素的visibility设置为可见

具体代码如下:

   function processLoadWeChatUrl(url) {
    const instance = Axios.create({
      headers: {
        "X-Requested-With": "XMLHttpRequest",
      },
    })
    url = `http://127.0.0.1:8090/${url}`
    instance.get(url).then(response => {
      let html = response.data
      // 将data-src属性转成src
      html = html.replace(/data-src/g, "src")
        .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/g, '')
        .replace(/https/g, 'http')
        .replace(/href="\/\/res.wx.qq.com\//g, 'href="https://res.wx.qq.com/')
      //根据微信传回的html中的特殊路径data-src转为src等
      let html_src = html
      let iframe = document.getElementById('iFrame-id');
      iframe.src = html_src
      //这时候就成功拿到了微信公众号,这个html_src是个静态网页
      let doc = iframe.contentDocument || iframe.document
      //添加meta展示图片不加这个图片出不来
      let htmlArr = html_src.split('</head>')
      let html_src_add = htmlArr[0] + '<meta name="referrer" content="never"></head>' + htmlArr[1]
      let backgroundUrlReg = /url[(]&quot;(\S*)&quot;/g
      //获取背景图
      let backgroundImgs = html_src_add.match(backgroundUrlReg)
      if (backgroundImgs && backgroundImgs.length) {
        backgroundImgs.forEach(item => {
          let url = item.replace(/url[(]&quot;/g, '').replace(/&quot;/g, '')
          let img = document.createElement('img')
          img.src = url
          doc.querySelector("body").appendChild(img)
        })
      }
      //配置无效img绕过背景图片路径限制
      doc.write(html_src_add)
      setLoadWeChatSuccess(true)
      // 设置主体内容为可见
      const rich_media_content = doc.querySelector(".rich_media_content")
      rich_media_content.style.visibility = 'unset'
      const images = doc.querySelectorAll('img')
      images.forEach(function (image) {
        image.style.maxWidth = '100%'
      })
    }).catch(function (error) { // 请求失败处理
      setLoadWeChatSuccess(false)
      console.log(error)
    })
  }

<Iframe url={webViewUrl}
  width="100%"
  id="iFrame-id"
  className="myClassname"
  height="87%"
  styles={{height: "87%"}}
  security="restricted"
  sandbox=""
/>

至此,已经可以实现在iframe绕过微信防盗链加载微信文章了
在这里插入图片描述

到这里以为就很简单了,剩下的就是埋点统计访问量的问题了,写一个中间页,转发分享时候把中间页地址带上要跳转的url、分享用户信息等分享出去,在中间页埋点,埋点成功后通过重定向到文章地址就解决问题了

然后就写下了这样几行代码,其中神策的pageview事件在setTitle完成,url的公共参数在神策js已经处理,这里省略

function UrlMiddlePage() {
  useEffect(() => {
    const search = new URLSearchParams(window.location.search)
    const url = search.get('jumpUrl')
    const title = search.get('title')
    setTitle(title)
    window.location.replace(url)
  }, [])

  return <Loading key='loading' show />
}

本以为这样已经没问题了,其实在浏览器上确实没有问题了

然后,坑来了

当我使用微信打开中间页时候,可以成功跳转微信公众号文章,但是当我返回上一页的时候,居然是展示了中间页,微信浏览器重定向居然不起作用??重定向之后的中间页的页面栈还在,所以导致重定向到微信文章后,有两个页面,这样就会出现死循环,看了文章返回中间页,然后触发useEffect,又跳转微信公众号文章

解决方案

既然已经知道是微信浏览器不支持重定向,那就想加个状态判断,如果是已经跳转过微信文章,就关闭微信浏览器退出即可,这时候首先想到的是加个state存一下状态即可,但是后来发现,在微信文章返回中间页,中间页是重新加载的,所以不能用state保存状态了,只能用sessionStorage进行状态存储,跳转之前设置sessionStorage,后续判断sessionStorage有值的话,直接关闭微信浏览器即可,其中使用window.WeixinJSBridge进行浏览器的关闭

注意:本来关闭微信浏览器是想用wx.closeWindow api的,但是发现该api只能是手动触发,否则不起作用,所以转成使用window.WeixinJSBridge.call(“closeWindow”)

function UrlMiddlePage() {

  useEffect(() => {
    if (sessionStorage.getItem(KEY)) {
      closeWindow()
      return
    } else if (isInWechatBrowser() || isInWeComBrowser()) {
      sessionStorage.setItem(KEY, KEY)
    }
    const search = new URLSearchParams(window.location.search)
    const url = search.get('jumpUrl')
    const title = search.get('title')
    setTitle(title)
    window.location.replace(url)
  }, [])

  // 兼容微信,微信执行window.location.replace,页面栈还是存在的,关闭微信的页面,退回聊天页面
  const closeWindow = () => {
    if (window.WeixinJSBridge) {
      window.WeixinJSBridge.call("closeWindow")
    } else if (document.addEventListener) {
      document.addEventListener("WeixinJSBridgeReady", () => {
        window.WeixinJSBridge && window.WeixinJSBridge.call("closeWindow")
      })
    } else if (document.attachEvent) {
      document.attachEvent("onWeixinJSBridgeReady", () => {
        // 关闭ios的页面
        window.WeixinJSBridge && window.WeixinJSBridge.call("closeWindow")
      })
    }
  }

  return <Loading key='loading' show />
}

还有高手

到这里,本以为已经完美解决问题了(测试机是安卓手机),上到ios测试的时候,又出现问题了,在ios的微信浏览器中,在微信文章返回中间页时候,是不会触发页面的重新加载、也不会触发useEffect的(企微对微信聊天框也会这样),导致没有触发判断sessionStorage的时机,最终,发现在返回中间页的时候,会触发window.onpageshow,只要判断是ios并且是微信的情况下,在window.onpageshow判断sessionStorage即可

最终代码如下:

function UrlMiddlePage() {

  useEffect(() => {
    processWeChatSpecialScenario()
    // 安卓微信,在其他域名页面返回本页面时候会触发useEffect
    if (sessionStorage.getItem(KEY)) {
      closeWindow()
      return
    } else if (isInWechatBrowser() || isInWeComBrowser()) {
      sessionStorage.setItem(KEY, KEY)
    }
    const search = new URLSearchParams(window.location.search)
    const url = search.get('jumpUrl')
    const title = search.get('title')
    setTitle(title)
    window.location.replace(url)
  }, [])

  /**
   * 处理微信浏览器、企微浏览器的特定场景
   * IOS微信或者企微对微信聊天框 ,在window.location.replace跳转页面后,返回不会触发useEffect,但是会触发window.onpageshow
  */
  const processWeChatSpecialScenario = () => {
    if (!isInWechatBrowser() && !isInWeComBrowser()) { return }
    window.onpageshow = (event) => {
      if (!event.persisted || !sessionStorage.getItem(KEY)) { return }
      closeWindow()
    }
  }

  // 兼容微信,微信执行window.location.replace,页面栈还是存在的,关闭微信的页面,退回聊天页面
  const closeWindow = () => {
    if (window.WeixinJSBridge) {
      window.WeixinJSBridge.call("closeWindow")
    } else if (document.addEventListener) {
      document.addEventListener("WeixinJSBridgeReady", () => {
        window.WeixinJSBridge && window.WeixinJSBridge.call("closeWindow")
      })
    } else if (document.attachEvent) {
      document.attachEvent("onWeixinJSBridgeReady", () => {
        // 关闭ios的页面
        window.WeixinJSBridge && window.WeixinJSBridge.call("closeWindow")
      })
    }
  }

  return <Loading key='loading' show />
}

总结

到这里,已经成功完整需求,包括:

  1. 通过cors-anywhere实现在iframe展示微信文章
  2. 兼容微信浏览器不能重定向问题,使用WeixinJSBridge.call(“closeWindow”)关闭微信浏览器
  3. 兼容ios、android的微信返回中间页的触发事件和机制问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/449116.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud微服务全家桶学习笔记-GateWay网关(微服务入口)】

Gateway服务网关 API网关为微服务架构中的服务提供了统一的访问入口&#xff0c;客户端通过API网关访问相关服务。API网关的定义类似于设计模式中的门面模式&#xff0c;它相当于整个微服务架构中的门面&#xff0c;所有客户端的访问都通过它来进行路由及过滤。它实现了请求路…

ElasticSearch深度分页问题如何解决

文章目录 概述解决方法深度分页方式from size深度分页之scrollsearch_after 三种分页方式比较 概述 Elasticsearch 的深度分页问题是指在大数据集上进行大量分页查询时可能导致的性能下降和资源消耗增加的情况。这种情况通常发生在需要访问大量数据的情形下&#xff0c;比如用…

二,几何相交---4,BO算法---(2)比较和排序

在某一时刻xt&#xff0c;扫描线从左到右时&#xff0c;一部分线段会与扫描线相交&#xff0c;此时此刻&#xff0c;线段可以分成高低顺序&#xff0c; 那么对于给定两条线段&#xff0c;是如何变化的呢&#xff1f;有两个端点&#xff0c;左端点和右端点&#xff0c; 三种情况…

OpenAI:ChatGPT API 文档之 Embedding

在自然语言处理和机器学习领域&#xff0c;"embeddings" 是指将单词、短语或文本转换成连续向量空间的过程。这个向量空间通常被称为嵌入空间&#xff08;embedding space&#xff09;&#xff0c;而生成的向量则称为嵌入向量&#xff08;embedding vector&#xff0…

Java项目:47 ssm007亚盛汽车配件销售业绩管理统+jsp(含文档)

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 亚盛汽车配件销售业绩管理系统根据调研&#xff0c;确定管理员管理客户&#xff0c;供应商&#xff0c;员工。 管理配件和配件的进货以及出售…

S5PV210_视频编解码项目_裸机开发:实现按键的外部中断处理

加粗样式本文所作内容&#xff1a; 基于S5PV210芯片实现按键的外部中断处理程序&#xff0c;搭建中断处理流程框架 S5PV210对于中断处理的操作流程 1 外部中断得到触发&#xff1a; 1&#xff09;外部中断在初始化阶段得到使能 2&#xff09;外界达到了外部中断的触发条件 …

(4)可执行文件

我们把.o文件链接起来得到可执行文件&#xff0c;然后一开始没有指定函数执行入口&#xff0c;连接器显示如下 这时候我们看最终的可执行文件会发现这个位置是main。也就是说连接器自动帮我们把入口识别为main 所以我们重新用-e main来指定连接器入口为main&#xff0c;再看看 …

李彦宏:程序员职业将不复存在,会说话就能当程序员;ChatGPT 日耗电超 50 万度丨 RTE 开发者日报 Vol.161

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、…

springcloud2022 feign超时时间配置

spring:application:name: order-webcloud:openfeign:client:config:default:connectTimeout: 60000readTimeout: 60000 默认connection10秒,readTimeout 60秒

nacos服务中心和注册中心

前言 Nacos 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理 平台。它是使用 java 编写。所以也是需要依赖 java 环境的&#xff1a; Java环境变量配置详解-CSDN博客 Nacos 文档地址&#xff1a; https://nacos.io/zh-cn/docs/quick-star…

力扣刷题日志-Day2 (力扣151、43、14)

151. 反转字符串中的单词 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开 思路&#xff1a;根据题目大意&#xff0c;空格之间的就是一个单词&#xff0c;所以我们需要利用…

JMeter 简介及安装详细教程(全网独家)

JMeter 简介 全名为 Apache JMeter JMeter 是一个软件&#xff0c;使负载测试或业绩为导向的业务&#xff08;功能&#xff09;测试不同的协议或技术。 它是 Apache 软件基金会的Stefano Mazzocchi JMeter 最初开发的。 它主要对 Apache JServ&#xff08;现在称为如 Apache T…

Git版本工具学习

目录 版本控制git配置工作区域文件状态git对象模型基础命令.gitignore忽略文件IDEA集成Git 版本控制 本地版本控制&#xff1a;在本地记录每一次版本更新。 集中版本控制&#xff1a;版本数据都保存在单一服务器&#xff0c;不联网就看不到版本信息。SVN 分布式版本控制&…

flink的分组聚合、over聚合、窗口聚合对比

【背景】 flink有几种聚合&#xff0c;使用上是有一些不同&#xff0c;需要加以区分&#xff1a; 分组聚合&#xff1a;group agg over聚合&#xff1a;over agg 窗口聚合&#xff1a;window agg 省流版&#xff1a; 触发计算时机 结果流类型 状态大小 分组聚合group ag…

MongoDB的count() 统计文档数量非常慢

在MongoDB中&#xff0c;count()函数用于统计文档的数量。但是count()函数通常不会使用索引来计算文档数量&#xff0c;而是扫描集合中的文档来计数。当数据量较大的时候&#xff0c;就不适合使用了。 解决方案&#xff1a; 1、使用聚合框架&#xff08;aggregation framewor…

EasyNVR级联EasyCVR,在EasyCVR播放视频会导致EasyNVR崩溃的原因排查与解决

视频综合管理平台EasyCVR视频监控系统支持多协议接入、兼容多类型设备&#xff0c;平台可以将监控区域内所有部署的监控设备进行统一接入与集中汇聚管理&#xff0c;实现对监控区域的实时视频监控、录像与存储、设备管理、云台控制、语音对讲、级联共享等&#xff0c;在监控中心…

从零搭建NodeJS项目(小白教程)

这边文章将介绍如何从零开始创建一个基于Express框架的Node.js项目。Express是一个快速、无拘束且极简的Node.js web应用框架&#xff0c;它提供了一系列强大的功能&#xff0c;使得web开发变得更加高效。 目录 1. 环境准备 2. 安装Express脚手架 3. 创建项目 4. 初始化项…

Clearview X for mac v3.5.0 电子书阅读器 兼容 M1/M2/M3

应用介绍 Clearview X 是 macOS 上的一款简洁易用且美观大方的电子书阅读器。直观好用的图书管理功能&#xff0c;支持 PDF, Epub, MOBI, CHM, FB2, CBR, CBZ 等流行的电子书格式&#xff0c;可以方便地添加注解&#xff0c;插入书签&#xff0c;及迅速的搜索查找。支持在不同…

git init 执行后发生了什么?

首先在磁盘中创建一个新目录 Git&#xff0c;进入该目录后执行 git init 初始化。这个时候目录下会创建一个隐藏目录 ./git&#xff0c;这个./git 目录叫做 Git 版本库或者仓库 $ git init Initialized empty Git repository in D:/Git/.git/ 在讲解.git 目录内容前&#xff0…

【C++】关联式容器

目录 前言&#xff1a; 一&#xff0c;set容器 二&#xff0c;multiset容器 三&#xff0c;map容器 四&#xff0c;multimap容器 前言&#xff1a; 在C中&#xff0c;STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、 forward_list(C11)等&#xff0c;这…