关于hybrid-app:一个-Hybrid-SDK-设计与实现

随着挪动浪潮的衰亡,各种 App 层出不穷,极速倒退的业务拓展晋升了团队对开发效率的要求,这个时候纯正应用 Native 开发技术老本难免会更高一点。而 H5 的低成本、高效率、跨平台等个性马上被利用起来了,造成一种新的开发模式: Hybrid App 作为一种混合开发的模式,Hybrid App 底层依赖于 Native 提供的容器(Webview),下层应用各种前端技术实现业务开发(当初三足鼎立的 Vue、React、Angular),底层透明化、下层多样化。这种场景十分有利于前端染指,非常适合业务的疾速迭代。于是 Hybrid 火了。 大道理谁都懂,然而依照我晓得的状况,还是有十分多的人和公司在 Hybrid 这一块并没有做的很好,所以我将我的教训做一个总结,心愿能够帮忙宽广开发者的技术选型有所帮忙 Hybrid 的一个现状可能晚期都是 PC 端的网页开发,随着挪动互联网的倒退,iOS、Android 智能手机的遍及,十分多的业务和场景都从 PC 端转移到挪动端。开始有前端开发者为挪动端开发网页。这样子晚期资源打包到 Native App 中会造成利用包体积的增大。越来越多的业务开始用 H5 尝试,这样子难免会须要一个须要拜访 Native 性能的中央,这样子可能晚期就是懂点前端技术的 Native 开发者本人封装或者裸露 Native 能力给 JS 端,等业务较多的时候者样子很显著不事实,就须要专门的 Hybrid 团队做这个事件;量大了,就须要规矩,就须要标准。 总结: Hybrid 开发效率高、跨平台、低成本Hybrid 从业务上讲,没有版本问题,有 Bug 能够及时修复Hybrid 在大量利用的时候就须要肯定的标准,那么本文将探讨一个 Hybrid 的设计常识。 Hybrid 、Native、前端各自的工作是什么Hybrid 交互接口如何设计Hybrid 的 Header 如何设计Hybrid 的如何设计目录构造以及增量机制如何实现资源缓存策略,白屏问题...Native 与前端分工在做 Hybird 架构设计之前咱们须要分清 Native 与前端的界线。首先 Native 提供的是宿主环境,要正当利用 Native 提供的能力,要实现通用的 Hybrid 架构,站在大前端的视觉,我感觉须要思考以下外围设计问题。 ...

June 28, 2021 · 8 min · jiezi

ionic初体验

搞了一波cordova后,算是对Hybrid有了一点点微小的认知。为了快速开发,ionic无疑是更好的选择,它底层的打包和通信机制基于cordova实现,在上层实现了自己的UI组件,可以结合Angular或React使用,并且宣称将在未来支持Vue。 环境准备如果已经安装了cordova,则单独安装ionic即可,否则需要一并安装。 npm install -g ionic cordova创建项目通过start命令来新建一个ionic项目。 ionic start my-app并且可以支持传入模板,以及项目类型,具体参考ionic start。 我们在这里创建一个基于angular的tabs导航的app。 ionic start myapp tabs --type=ionic-angular当然也可以直接从一个更完善的模板开始。 ionic start myapp super --type=ionic-angular这几种方式可以都试试看。 运行项目在浏览器运行web版在尝试npm start调用ionic-app-scripts serve启动项目时,发现报错找不到@ionic/app-scripts模块,尝试重新安装该模块,node-gyp模块又报了这个错: Error: Can't find Python executable "python", you can set the PYTHON env variable.查询node-gyp后,官方提供了两种解决方案 我采用了第一种方案: npm install --global --production windows-build-toolsps: 必须以系统管理员方式运行命令行。 接着重新安装一遍@ionic/app-scripts,然后重新运行项目,冇问题啦。 npm uninstall @ionic/app-scriptsnpm install --save-dev @ionic/app-scriptsnpm start 支持android和iosionic cordova platform add iosionic cordova platform add androidandroid调试首先检查下设备连接是否正常 D:\robin\frontend\hybrid\ionic\ionic-blog> adb devicesList of devices attached5fdba1e7 device使用ionic cli提供的命令运行app ...

November 5, 2019 · 1 min · jiezi

支付宝移动端-Hybrid-解决方案探索与实践

本文内容主要分为以下三个部分:• 移动互联网背景下的高可用性能挑战主要给大家介绍支付宝 APP 在这几年移动互联网快速发展的阶段,其自身的一个变化与遇到的性能挑战。• 支付宝 Hybrid 方案建设与演进 (H5 容器 & 小程序)为了应对前面提到的这些挑战,支付宝逐步沉淀出 2 套 Hybri 方案,分别是 H5 容器与小程序。• Hybrid 方案借助移动开发平台 mPaaS 对外输出通过 mPaaS 平台,让大家也可以去接触使用到支付宝的 Hybrid 技术。 移动互联网背景下的高可用性能挑战根据公开的 2018 年移动互联网行业分析报告,目前支付宝的月活跃用户已经超过 QQ ,成为国内第二大 App。 支付宝一开始仅仅只是一个单体应用的工具型 App,让用户可以在手机完成支付宝相关的业务查询和操作。2013 年后,支付宝逐步转型为平台型 App, 平台型 App 具有服务化、模块化、工具组件化的特点,这个时候支付宝的业务不仅仅是支付,还需要给客户提供了很多生活相关的服务,例如余额宝、缴电费等。2015 年后支付宝成长为超级 App,超级 App 会面临开放、动态化、高可用的挑战,此时支付宝里面需要支持大量复杂的业务,同时开放自己的商业能力,用自己流量助力合作伙伴。 从单体应用到超级 App 的转变,其实体现了一个用户对 App 需求的变化,移动互联网用户需求的本质是服务,而不是 App,用户高频使用的 App 是少数。在 超级 App 时代,支付宝主要面临的挑战是: 1. 支持复杂业务App 的业务越来越复杂,不仅仅是内部业务,还包含了大量外部的合作伙伴。如果采用传统的 App 开发方式很难应对日趋复杂的业务场景。2. 满足业务快速迭代的需求当前业务的另外一个特点就是需要快速迭代,变化的政策、突发事件都需要我们可以快速把新的业务需求触达给用户。但是 App 开发一个不容忽视的问题,就是应用商店审核。由于审核的存在,App 上开发的业务会有一个统一排期,比如说月底会有新版本,那么所有的业务进度都得考虑 App 的排期计划。3. 开放平台作为超级 App,一个最主要的特征就是开放。开放就是共享 App 的流量,让外部伙伴的业务可以通过支付宝触达用户,这就面临一个质量管控的问题。支付宝需要保证这些业务是合法合规的,保障用户的财产安全。 ...

August 19, 2019 · 3 min · jiezi

Hybrid-App-应用-开发中-9-个必备知识点复习WebView-调试-等

前言我们大前端团队内部 ????每周一练 的知识复习计划继续加油,本篇文章是 《Hybrid APP 混合应用专题》 主题的第二期和第三期的合集。 这一期共整理了 10 个问题,和相应的参考答案,文字和图片较多,建议大家可以收藏,根据文章目录来阅读。 之前分享的每周内容,我都整理到掘金收藏集 ????《EFT每周一练》 上啦,欢迎点赞收藏咯????????。 内容回顾: 《EFT 每周分享 —— Hybrid App 应用开发中 5 个必备知识点复习》《EFT 每周分享 —— HTTP 的15个常见知识点复习》《EFT 每周分享 —— 数据结构与算法合集》文章收录: 本系列所有文章,都将收录在 Github 上,欢迎点击查阅。 注:本文整理部分资料来源网络,有些图片/段落找不到原文出处,如有侵权,联系删除。一、iOS 平台中 UIWebView 与 WKWebView 有什么区别?参考文章:《UIWebView与WKWebView》UIWebView 是苹果继承于 UIView 封装的一个加载 web 内容的类,它可以加载任何远端的web数据展示在你的页面上,你可以像浏览器一样前进后退刷新等操作。不过苹果在 iOS8 以后推出了 WKWebView 来加载 Web,并应用于 iOS 和 OSX 中,它取代了 UIWebView 和 WebView ,在两个平台上支持同一套 API。 它脱离于 UIWebView 的设计,将原本的设计拆分成14个类,和3个代理协议,虽然是这样但是了解之后其实用法比较简单,依照职责单一的原则,每个协议做的事情根据功能分类。 WKWebView 与 UIWebView 的区别: ...

July 13, 2019 · 6 min · jiezi

Hybird-App-应用开发中5个必备知识点复习

前言我们大前端团队内部 ????每周一练 的知识复习计划还在继续,本周主题是 《Hybird APP 混合应用专题》 ,这期内容比较多,篇幅也相对较长,每个知识点内容也比较多。 之前分享的每周内容,我都整理到掘金收藏集 ????《EFT每周一练》 上啦,欢迎点赞收藏咯????????。 注:本文整理资料来源网络,有些图片/段落找不到原文出处,如有侵权,联系删除。一、什么是 Hybird App,与 Native App 及 Web App 有什么区别参考文章: 《Web App Hybrid App和 Native App的区别》《Hybrid APP基础篇(二) -> Native、Hybrid、React Native、Web App方案的分析比较》1.1 主流应用类型随着现在移动互联网的快速发展,市面上目前主流移动应用程序主要分三类:Web App、 Native App 和 Hybrid App。 三者大致关系如下: 1.2 Web AppWeb App,即移动端网站,一般指的是基于 Web 的应用,基于浏览器运行,无需下载安装,基本上可以说是触屏版的网页应用。这类应用基本上是一个网页或一系列网页,旨在在移动屏幕上工作。 Web 网站一般分为两种: MPA(Multi-page Application)SPA(Single-page Application)一般的 Web App 是指 SPA 形式开发的网站。 优点: 开发和维护成本低,可以跨平台,调试方便;前端人员开发的代码,可应用于各大主流浏览器(特殊情况可以代码进行下兼容),没有新的学习成本,而且可以直接在浏览器中调试。 更新最为快速;由于web app资源是直接部署在服务器端的,所以只需替换服务器端文件,用户访问是就已经更新了(当然需要解决一些缓存问题)。 无需安装App,不会占用手机内存;通过浏览器即可访问,无需安装,用户使用成本更低。 缺点: 性能低,用户体验差;由于是直接通过的浏览器访问,所以无法使用原生的API,操作体验不好。 依赖于网络,页面访问速度慢,耗费流量;Web App每次访问都必须依赖网络,从服务端加载资源,当网速慢时访问速度很不理想,特别是在移动端,对网站性能优化要求比较高。 功能受限,大量功能无法实现;只能使用 HTML5 的一些特殊 API ,无法调用原生 API ,所以很多功能存在无法实现情况。 ...

June 27, 2019 · 5 min · jiezi

移动端跨平台方案如何选择

May 31, 2019 · 0 min · jiezi

下篇中高级前端大厂面试秘籍寒冬中为您保驾护航直通大厂

引言本篇文章会继续沿着前面两篇的脚步,继续梳理前端领域一些比较主流的进阶知识点,力求能让大家在横向层面有个全面的概念。能在面试时有限的时间里,能够快速抓住重点与面试官交流。这些知识点属于加分项,如果能在面试时从容侃侃而谈,想必面试官会记忆深刻,为你折服的~???? 另外有许多童鞋提到: 面试造火箭,实践全不会,对这种应试策略表达一些担忧。其实我是觉得面试或者这些知识点,也仅仅是个初级的 开始。能帮助在初期的快速成长,但这种策略并没办法让你达到更高的水平,只有后续不断地真正实践和深入研究,才能突破自己的瓶颈,继续成长。面试,不也只是一个开始而已嘛。~???? 建议各位小伙从基础入手,先看 (上篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂(中篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂小菜鸡博客求赞 ???? blog进阶知识Hybrid随着 Web技术 和 移动设备 的快速发展,在各家大厂中,Hybrid 技术已经成为一种最主流最不可取代的架构方案之一。一套好的 Hybrid 架构方案能让 App 既能拥有 极致的体验和性能,同时也能拥有 Web技术 灵活的开发模式、跨平台能力以及热更新机制。因此,相关的 Hybrid 领域人才也是十分的吃香,精通Hybrid 技术和相关的实战经验,也是面试中一项大大的加分项。 1. 混合方案简析Hybrid App,俗称 混合应用,即混合了 Native技术 与 Web技术 进行开发的移动应用。现在比较流行的混合方案主要有三种,主要是在UI渲染机制上的不同: Webview UI: 通过 JSBridge 完成 H5 与 Native 的双向通讯,并 基于 Webview 进行页面的渲染;优势: 简单易用,架构门槛/成本较低,适用性与灵活性极强;劣势: Webview 性能局限,在复杂页面中,表现远不如原生页面;Native UI: 通过 JSBridge 赋予 H5 原生能力,并进一步将 JS 生成的虚拟节点树(Virtual DOM)传递至 Native 层,并使用 原生系统渲染。优势: 用户体验基本接近原生,且能发挥 Web技术 开发灵活与易更新的特性;劣势: 上手/改造门槛较高,最好需要掌握一定程度的客户端技术。相比于常规 Web开发,需要更高的开发调试、问题排查成本;小程序 通过更加定制化的 JSBridge,赋予了 Web 更大的权限,并使用双 WebView 双线程的模式隔离了 JS逻辑 与 UI渲染,形成了特殊的开发模式,加强了 H5 与 Native 混合程度,属于第一种方案的优化版本;优势: 用户体验好于常规 Webview 方案,且通常依托的平台也能提供更为友好的开发调试体验以及功能;劣势: 需要依托于特定的平台的规范限定2. WebvievWebview 是 Native App 中内置的一款基于 Webkit内核 的浏览器,主要由两部分组成: ...

April 26, 2019 · 7 min · jiezi

Vue与原生APP的相互交互

现在好多APP都采用了Hybrid的开发模式,这种模式特别适合那些内容变动更新较大的APP,从而使得开发和日常维护过程变得集中式、更简短、更经济高效,不需要纯原生频繁发布。但有利肯定有弊咯,性能方面能稍微差一点,还有就是两者之间的交互问题。美团、爱奇艺、微信等知名移动应用,都是采用Hybrid App开发模式。Hybrid开发已成为未来的一种发展趋势。1、原生APP与Vue交互场景:原生的头部标题栏,内容为H5页面。现在需要在原生头部增加一个时间帅选功能,原生筛选好时间后调用H5的查询方法。mounted () { /* 将nativeCallToSearch方法绑定到window下面,提供给外部调用 / window.nativeCallToSearch = (res) => { this.nativeCallToSearch(res) }}methods () { /* * 原生时间筛选 * @param {string} searchDate 查询的时间 / nativeCallToSearch (searchDate) { // do something… }}2、Vue与原生APP交互场景:H5页面中涉及分享功能(用H5来做分享坑太多),H5实现具体分享的内容,原生只负责分享操作。methods () { /* * 分享微信好友 / goWXFriend () { this.$loading.show() if (this.androidOrIos === ‘android’) { / eslint-disable / / 安卓识别不了js对象 / javascript: share.shareWX(this.shareObj.link, this.shareObj.linkTitle, this.shareObj.linkContent, this.shareObj.linkImgUrl) / eslint-enable / this.$loading.hide() } else if (this.androidOrIos === ‘ios’) { / 将对象转为字符串 */ window.webkit.messageHandlers.shareWX.postMessage(JSON.stringify(this.shareObj)) this.$loading.hide() } }} ...

April 17, 2019 · 1 min · jiezi

JS高级面试之hybrid部分

该文章主要记录hybrid前端客户端混合开发的一些知识点Hybrid1. hybrid是什么,为何用hybrid?hybrid是客户端与前端的混合开发hybrid存在的核心意义在意快速迭代,无需审核hybrid实现流程,以及webview和file协议2. 介绍一下hybrid更新和上线的流程?掌握流程图要点一: 服务端的版本和zip包维护要点二: 更新zip包之前,先对比版本号要点三: zip下载解压和覆盖hybrid和h5的主要区别?优点: 体验好,可快速迭代缺点: 开发成本高,运维成本高适合的场景: hybrid适合产品型,H5适合运营型前端JS和客户端如何通讯?通讯的基本形式: 前端调用能力,传递参数,监听回调对schema协议的理解和使用定义了前端与客户端的约定可以通过ifream使用调用schema代码的封装放在客户端内置上线的好处: 更快,更安全其余内容后续继续补充…

April 7, 2019 · 1 min · jiezi

App常用开发模式

App常用开发模式Native App传统的原生App开发模式,有iOS和安卓两大系统,需要各自语言开发各自App。优点:性能和体验都是最好。缺点:开发和发布成本高, 维持多个版本的更新升级比较麻烦,用户的安装门槛也高WebApp移动端的网站, h5开发和发布成本最低, 性能和体验较差,受到浏览器处理能力的限制Hybrid App混合模式移动应用,介于Web App、Native App这两之间App开发技术, 兼具“Native App良好交互体验的优势”和“Web App跨平台开发的优势”原理: 由Native通过JSBridge等方法提供统一的API,用html,css实现界面,JS写逻辑调用API,最终页面在Webview中显示,这种模式下,Android、iOS的API一般有一致性,HybridApp所以有跨平台效果开发者可以像开发WebApp一样开发app的视觉UI,当需要使用原生功能(如摄像头,陀螺仪等功能)时,只需要调用官方的API就可以实现Native的效果。至于JS和Native的通信,常用的有URL监听和Hybrid厂商使用的JSBridge通信,两者原理相近。Hybird App 的常见跨平台开发工具有PhoneGap,Ionic, 国内有AppCan缺点:Hybird严重受限于WebView的解析渲染效率,需要原生配合。JSBridge调用方式React Native AppFacebook发现Hybrid存在很多缺陷和不足,然后自己开发了一套RN, 使用JSX写原生界面,js通过JSBridge调用原生API渲染UI交互通信。支持flexBox布局, 采用DOM 结构;优点:效率体验接近Native App,发布和开发成本低于Native App。缺点:新东西,更新迭代快,api后期同早期变化很大,需要踩坑。。Weex App阿里开发团队在RN的成功案例上,设计的一套开发模式,2016年4月正式开源,并在v2.0版本官方支持Vue.js优点:单页开发模式效率极高,热更新发包体积小,并且跨平台性强。缺点:同RN, 且社区没有RN活跃,已捐献给 Apache 基金会孵化管理。。。奔溃。。。

January 28, 2019 · 1 min · jiezi

WeexBox 1.2.0 新增 Lottie 动画,妈妈再也不用担心我加班写动画了!

背景weex官方提供了animation模块可以用来在组件上执行动画,但是它的功能有限还容易造成卡顿。所以WeexBox从一开始就支持了BindingX,丰富的API和流畅的性能可以支撑复杂的动画。可是这样就满足了吗?致力于解放开发的WeexBox,怎么忍心看着你们写大坨大坨的动画代码呢,如果可以不写代码,那就太好了~LottieLottie是Airbnb开源的动画库。它通过AE做成动画导出JSON文件,然后前端使用Lottie直接加载JSON文件生成动画,不需要前端进行复杂的绘制等操作。而且它还具有占用内存少,加载流畅等特点。N多现成可用的优秀动画在这里WeexBox中使用Lottie因为太简单,我就直接贴代码了。<template> <div class=“wrap”> <wb-lottie class=“happyBirthday” :sourceJson=sourceJson :loop=loop ref=“lottie”></wb-lottie> <text class=“title”>播放动画</text> <div class=“button” @click=“play”> <text class=“button-text”>播放</text> </div> <div class=“empty”></div> <text class=“title”>暂停动画</text> <div class=“button” @click=“pause”> <text class=“button-text”>暂停</text> </div> <div class=“empty”></div> <text class=“title”>停止动画</text> <div class=“button” @click=“stop”> <text class=“button-text”>停止</text> </div> <div class=“empty”></div> </div></template><script>// 这个就是设计师给你的json文件const happyBirthday = require(’./happyBirthday.json’)export default { components: { }, data() { return { sourceJson: happyBirthday, loop: false, } }, created() { }, methods: { // 播放动画 play() { this.$refs.lottie.play((result) => { console.log(JSON.stringify(result)) }) }, // 暂停动画 pause() { this.$refs.lottie.pause() }, // 停止动画 stop() { this.$refs.lottie.stop() }, },}</script><style lang=“scss” scoped>@import ‘../../style/global’;.happyBirthday { width: 750px; height: 750px;}</style>以上只演示了部分功能,详细文档在此总结我们终于找到了能让设计师、产品都对动画满意的方法,那就是设计师设计好了直接导出动画啊哈哈,妈妈再也不用担心我加班写动画了! ...

January 19, 2019 · 1 min · jiezi

大前端时代安全性如何做

之前在上家公司的时候做过一些爬虫的工作,也帮助爬虫工程师解决过一些问题。然后我写过一些文章发布到网上,之后有一些人就找我做一些爬虫的外包,内容大概是爬取小红书的用户数据和商品数据,但是我没做。我觉得对于国内的大数据公司没几家是有真正的大数据量,而是通过爬虫工程师团队不断的去各地爬取数据,因此不要以为我们的数据没价值,对于内容型的公司来说,数据是可信竞争力。那么我接下来想说的就是网络和数据的安全性问题。对于内容型的公司,数据的安全性很重要。对于内容公司来说,数据的重要性不言而喻。比如你一个做在线教育的平台,题目的数据很重要吧,但是被别人通过爬虫技术全部爬走了?如果核心竞争力都被拿走了,那就是凉凉。再比说有个独立开发者想抄袭你的产品,通过抓包和爬虫手段将你核心的数据拿走,然后短期内做个网站和 App,短期内成为你的劲敌。背景目前通过 App 中的 网页分析后,我们的数据安全性做的较差,有以下几个点存在问题:网站的数据通过最早期的前后端分离来实现。稍微学过 Web 前端的工程师都可以通过神器 Chrome 分析网站,进而爬取需要的数据。打开 「Network」就可以看到网站的所有网络请求了,哎呀,不小心我看到了什么?没错就是网站的接口信息都可以看到了。比如 “detail.json?itemId=141529859”。或者你的网站接口有些特殊的判断处理,将一些信息存储到 sessionStorage、cookie、localStorage 里面,有点前端经验的爬虫工程师心想”嘿嘿嘿,这不是在裸奔数据么“。或者有些参数是通过 JavaScript 临时通过函数生成的。问题不大,工程师也可以对网页元素进行查找,找到关键的 id、或者 css 类名,然后在 “Search“ 可以进行查找,找到对应的代码 JS 代码,点击查看代码,如果是早期前端开发模式那么代码就是裸奔的,跟开发者在自己的 IDE 里面看到的内容一样,有经验的爬虫就可以拿这个做事情,因此安全性问题亟待解决。想知道 Chrome 更多的调试使用技巧,看看这篇文章App 的数据即使采用了 HTTPS,但是对于专业的抓包工具也是可以直接拿到数据的,因此 App 的安全问题也可以做一些提高,具体的策略下文会讲到。想知道 Charles 的更多使用技巧,可以看看这篇文章爬虫手段目前爬虫技术都是从渲染好的 html 页面直接找到感兴趣的节点,然后获取对应的文本有些网站安全性做的好,比如列表页可能好获取,但是详情页就需要从列表页点击对应的 item,将 itemId 通过 form 表单提交,服务端生成对应的参数,然后重定向到详情页(重定向过来的地址后才带有详情页的参数 detailID),这个步骤就可以拦截掉一部分的爬虫开发者解决方案制定出Web 端反爬技术方案本人从这2个角度(网页所见非所得、查接口请求没用)出发,制定了下面的反爬方案。使用HTTPS 协议单位时间内限制掉请求次数过多,则封锁该账号前端技术限制 (接下来是核心技术)# 比如需要正确显示的数据为“19950220”1. 先按照自己需求利用相应的规则(数字乱序映射,比如正常的0对应还是0,但是乱序就是 0 <-> 1,1 <-> 9,3 <-> 8,…)制作自定义字体(ttf)2. 根据上面的乱序映射规律,求得到需要返回的数据 19950220 -> 177302203. 对于第一步得到的字符串,依次遍历每个字符,将每个字符根据按照线性变换(y=kx+b)。线性方程的系数和常数项是根据当前的日期计算得到的。比如当前的日期为“2018-07-24”,那么线性变换的 k 为 7,b 为 24。4. 然后将变换后的每个字符串用“3.1415926”拼接返回给接口调用者。(为什么是3.1415926,因为对数字伪造反爬,所以拼接的文本肯定是数字的话不太会引起研究者的注意,但是数字长度太短会误伤正常的数据,所以用所熟悉的 )1773 -&gt; “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -&gt; 313.1415926733.1415926733.14159264502 -&gt; "0*7+24" + "3.1415926" + "2*7+24" -&gt; 243.14159263820 -&gt; "2*7+24" + "3.1415926" + "0*7+24" -&gt; 383.141592624# 前端拿到数据后再解密,解密后根据自定义的字体 Render 页面1. 先将拿到的字符串按照“3.1415926”拆分为数组2. 对数组的每1个数据,按照“线性变换”(y=kx+b,k和b同样按照当前的日期求解得到),逆向求解到原本的值。3. 将步骤2的的到的数据依次拼接,再根据 ttf 文件 Render 页面上。后端需要根据上一步设计的协议将数据进行加密处理下面以 Node.js 为例讲解后端需要做的事情首先后端设置接口路由获取路由后面的参数根据业务需要根据 SQL 语句生成对应的数据。如果是数字部分,则需要按照上面约定的方法加以转换。将生成数据转换成 JSON 返回给调用者// jsonvar JoinOparatorSymbol = “3.1415926”;function encode(rawData, ruleType) { if (!isNotEmptyStr(rawData)) { return “”; } var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var encodeData = “”; for (var index = 0; index < rawData.length; index++) { var datacomponent = rawData[index]; if (!isNaN(datacomponent)) { if (ruleType < 3) { var currentNumber = rawDataMap(String(datacomponent), ruleType); encodeData += (currentNumber * month + day) + JoinOparatorSymbol; } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } else { encodeData += rawDataMap(String(datacomponent), ruleType) + JoinOparatorSymbol; } } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } } if (encodeData.length >= JoinOparatorSymbol.length) { var lastTwoString = encodeData.substring(encodeData.length - JoinOparatorSymbol.length, encodeData.length); if (lastTwoString == JoinOparatorSymbol) { encodeData = encodeData.substring(0, encodeData.length - JoinOparatorSymbol.length); } }//字体映射处理function rawDataMap(rawData, ruleType) { if (!isNotEmptyStr(rawData) || !isNotEmptyStr(ruleType)) { return; } var mapData; var rawNumber = parseInt(rawData); var ruleTypeNumber = parseInt(ruleType); if (!isNaN(rawData)) { lastNumberCategory = ruleTypeNumber; //字体文件1下的数据加密规则 if (ruleTypeNumber == 1) { if (rawNumber == 1) { mapData = 1; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 4; } else if (rawNumber == 4) { mapData = 5; } else if (rawNumber == 5) { mapData = 3; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 9; } else if (rawNumber == 9) { mapData = 7; } else if (rawNumber == 0) { mapData = 0; } } //字体文件2下的数据加密规则 else if (ruleTypeNumber == 0) { if (rawNumber == 1) { mapData = 4; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 3; } else if (rawNumber == 4) { mapData = 1; } else if (rawNumber == 5) { mapData = 8; } else if (rawNumber == 6) { mapData = 5; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } //字体文件3下的数据加密规则 else if (ruleTypeNumber == 2) { if (rawNumber == 1) { mapData = 6; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 1; } else if (rawNumber == 4) { mapData = 3; } else if (rawNumber == 5) { mapData = 4; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 3; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } else if (ruleTypeNumber == 3) { if (rawNumber == 1) { mapData = “&#xefab;”; } else if (rawNumber == 2) { mapData = “&#xeba3;”; } else if (rawNumber == 3) { mapData = “&#xecfa;”; } else if (rawNumber == 4) { mapData = “&#xedfd;”; } else if (rawNumber == 5) { mapData = “&#xeffa;”; } else if (rawNumber == 6) { mapData = “&#xef3a;”; } else if (rawNumber == 7) { mapData = “&#xe6f5;”; } else if (rawNumber == 8) { mapData = “&#xecb2;”; } else if (rawNumber == 9) { mapData = “&#xe8ae;”; } else if (rawNumber == 0) { mapData = “&#xe1f2;”; } } else{ mapData = rawNumber; } } else if (ruleTypeNumber == 4) { var sources = [“年”, “万”, “业”, “人”, “信”, “元”, “千”, “司”, “州”, “资”, “造”, “钱”]; //判断字符串为汉字 if (/^[\u4e00-\u9fa5]$/.test(rawData)) { if (sources.indexOf(rawData) > -1) { var currentChineseHexcod = rawData.charCodeAt(0).toString(16); var lastCompoent; var mapComponetnt; var numbers = [“0”, “1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”]; var characters = [“a”, “b”, “c”, “d”, “e”, “f”, “g”, “h”, “h”, “i”, “j”, “k”, “l”, “m”, “n”, “o”, “p”, “q”, “r”, “s”, “t”, “u”, “v”, “w”, “x”, “y”, “z”]; if (currentChineseHexcod.length == 4) { lastCompoent = currentChineseHexcod.substr(3, 1); var locationInComponents = 0; if (/[0-9]/.test(lastCompoent)) { locationInComponents = numbers.indexOf(lastCompoent); mapComponetnt = numbers[(locationInComponents + 1) % 10]; } else if (/[a-z]/.test(lastCompoent)) { locationInComponents = characters.indexOf(lastCompoent); mapComponetnt = characters[(locationInComponents + 1) % 26]; } mapData = “&#x” + currentChineseHexcod.substr(0, 3) + mapComponetnt + “;”; } } else { mapData = rawData; } } else if (/[0-9]/.test(rawData)) { mapData = rawDataMap(rawData, 2); } else { mapData = rawData; } } return mapData;}//apimodule.exports = { “GET /api/products”: async (ctx, next) => { ctx.response.type = “application/json”; ctx.response.body = { products: products }; }, “GET /api/solution1”: async (ctx, next) => { try { var data = fs.readFileSync(pathname, “utf-8”); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log(“fail: " + error); } var data = { code: 200, message: “success”, data: { name: “@杭城小刘”, year: LBPEncode(“1995”, rule), month: LBPEncode(“02”, rule), day: LBPEncode(“20”, rule), analysis : rule } } ctx.set(“Access-Control-Allow-Origin”, “”); ctx.response.type = “application/json”; ctx.response.body = data; }, “GET /api/solution2”: async (ctx, next) => { try { var data = fs.readFileSync(pathname, “utf-8”); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log(“fail: " + error); } var data = { code: 200, message: “success”, data: { name: LBPEncode(“建造师”,rule), birthday: LBPEncode(“1995年02月20日”,rule), company: LBPEncode(“中天公司”,rule), address: LBPEncode(“浙江省杭州市拱墅区石祥路”,rule), bidprice: LBPEncode(“2万元”,rule), negative: LBPEncode(“2018年办事效率太高、负面基本没有”,rule), title: LBPEncode(“建造师”,rule), honor: LBPEncode(“最佳奖”,rule), analysis : rule } } ctx.set(“Access-Control-Allow-Origin”, “*”); ctx.response.type = “application/json”; ctx.response.body = data; }, “POST /api/products”: async (ctx, next) => { var p = { name: ctx.request.body.name, price: ctx.request.body.price }; products.push(p); ctx.response.type = “application/json”; ctx.response.body = p; }};//路由const fs = require(“fs”);function addMapping(router, mapping){ for(var url in mapping){ if (url.startsWith(“GET”)) { var path = url.substring(4); router.get(path,mapping[url]); console.log(Register URL mapping: GET: ${path}); }else if (url.startsWith(‘POST ‘)) { var path = url.substring(5); router.post(path, mapping[url]); console.log(Register URL mapping: POST ${path}); } else if (url.startsWith(‘PUT ‘)) { var path = url.substring(4); router.put(path, mapping[url]); console.log(Register URL mapping: PUT ${path}); } else if (url.startsWith(‘DELETE ‘)) { var path = url.substring(7); router.del(path, mapping[url]); console.log(Register URL mapping: DELETE ${path}); } else { console.log(Invalid URL: ${url}); } }}function addControllers(router, dir){ fs.readdirSync(__dirname + “/” + dir).filter( (f) => { return f.endsWith(".js”); }).forEach( (f) => { console.log(Process controllers:${f}...); let mapping = require(__dirname + “/” + dir + “/” + f); addMapping(router,mapping); });}module.exports = function(dir){ let controllers = dir || “controller”; let router = require(“koa-router”)(); addControllers(router,controllers); return router.routes();};前端根据服务端返回的数据逆向解密$("#year”).html(getRawData(data.year,log));// util.jsvar JoinOparatorSymbol = “3.1415926”;function isNotEmptyStr($str) { if (String($str) == "” || $str == undefined || $str == null || $str == “null”) { return false; } return true;}function getRawData($json,analisys) { $json = $json.toString(); if (!isNotEmptyStr($json)) { return; } var date= new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var datacomponents = $json.split(JoinOparatorSymbol); var orginalMessage = “”; for(var index = 0;index < datacomponents.length;index++){ var datacomponent = datacomponents[index]; if (!isNaN(datacomponent) && analisys < 3){ var currentNumber = parseInt(datacomponent); orginalMessage += (currentNumber - day)/month; } else if(analisys == 3){ orginalMessage += datacomponent; } else{ //其他情况待续,本 Demo 根据本人在研究反爬方面的技术并实践后持续更新 } } return orginalMessage;}比如后端返回的是323.14743.14743.1446,根据我们约定的算法,可以的到结果为1773根据 ttf 文件 Render 页面上面计算的到的1773,然后根据ttf文件,页面看到的就是1995然后为了防止爬虫人员查看 JS 研究问题,所以对 JS 的文件进行了加密处理。如果你的技术栈是 Vue 、React 等,webpack 为你提供了 JS 加密的插件,也很方便处理JS混淆工具个人觉得这种方式还不是很安全。于是想到了各种方案的组合拳。比如反爬升级版个人觉得如果一个前端经验丰富的爬虫开发者来说,上面的方案可能还是会存在被破解的可能,所以在之前的基础上做了升级版本组合拳1: 字体文件不要固定,虽然请求的链接是同一个,但是根据当前的时间戳的最后一个数字取模,比如 Demo 中对4取模,有4种值 0、1、2、3。这4种值对应不同的字体文件,所以当爬虫绞尽脑汁爬到1种情况下的字体时,没想到再次请求,字体文件的规则变掉了 ????组合拳2: 前面的规则是字体问题乱序,但是只是数字匹配打乱掉。比如 1 -> 4, 5 -> 8。接下来的套路就是每个数字对应一个 unicode 码 ,然后制作自己需要的字体,可以是 .ttf、.woff 等等。这几种组合拳打下来。对于一般的爬虫就放弃了。反爬手段再升级上面说的方法主要是针对数字做的反爬手段,如果要对汉字进行反爬怎么办?接下来提供几种方案方案1: 对于你站点频率最高的词云,做一个汉字映射,也就是自定义字体文件,步骤跟数字一样。先将常用的汉字生成对应的 ttf 文件;根据下面提供的链接,将 ttf 文件转换为 svg 文件,然后在下面的“字体映射”链接点进去的网站上面选择前面生成的 svg 文件,将svg文件里面的每个汉字做个映射,也就是将汉字专为 unicode 码(注意这里的 unicode 码不要去在线直接生成,因为直接生成的东西也就是有规律的。我给的做法是先用网站生成,然后将得到的结果做个简单的变化,比如将“e342”转换为 “e231”);然后接口返回的数据按照我们的这个字体文件的规则反过去映射出来。方案2: 将网站的重要字体,将 html 部分生成图片,这样子爬虫要识别到需要的内容成本就很高了,需要用到 OCR。效率也很低。所以可以拦截掉一部分的爬虫方案3: 看到携程的技术分享“反爬的最高境界就是 Canvas 的指纹,原理是不同的机器不同的硬件对于 Canvas 画出的图总是存在像素级别的误差,因此我们判断当对于访问来说大量的 canvas 的指纹一致的话,则认为是爬虫,则可以封掉它”。本人将方案1实现到 Demo 中了。关键步骤先根据你们的产品找到常用的关键词,生成词云根据词云,将每个字生成对应的 unicode 码将词云包括的汉字做成一个字体库将字体库 .ttf 做成 svg 格式,然后上传到 icomoon 制作自定义的字体,但是有规则,比如 “年” 对应的 unicode 码是 “u5e74” ,但是我们需要做一个 恺撒加密 ,比如我们设置 偏移量 为1,那么经过恺撒加密 “年”对应的 unicode 码是“u5e75” 。利用这种规则制作我们需要的字体库在每次调用接口的时候服务端做的事情是:服务端封装某个方法,将数据经过方法判断是不是在词云中,如果是词云中的字符,利用规则(找到汉字对应的 unicode 码,再根据凯撒加密,设置对应的偏移量,Demo 中为1,将每个汉字加密处理)加密处理后返回数据客户端做的事情:先引入我们前面制作好的汉字字体库调用接口拿到数据,显示到对应的 Dom 节点上如果是汉字文本,我们将对应节点的 css 类设置成汉字类,该类对应的 font-family 是我们上面引入的汉字字体库//style.css@font-face { font-family: “NumberFont”; src: url(‘http://127.0.0.1:8080/Util/analysis’); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}@font-face { font-family: “CharacterFont”; src: url(‘http://127.0.0.1:8080/Util/map’); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}h2 { font-family: “NumberFont”;}h3,a{ font-family: “CharacterFont”;}传送门字体制作的步骤、ttf转svg、字体映射规则实现的效果页面上看到的数据跟审查元素看到的结果不一致去查看接口数据跟审核元素和界面看到的三者不一致页面每次刷新之前得出的结果更不一致对于数字和汉字的处理手段都不一致这几种组合拳打下来。对于一般的爬虫就放弃了。前面的 ttf 转 svg 网站当 ttf 文件太大会限制转换,让你购买,下面贴出个新的链接。ttf转svgDemo 地址运行步骤//客户端。先查看本机 ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js 里面将接口地址修改为本机 ip$ cd Demo$ lsREST Spider-release file-Server.jsSpider-develop Util rule.json$ node file-Server.js Server is runnig at http://127.0.0.1:8080///服务端 先安装依赖$ cd REST/$ npm install$ node app.js App 端安全的解决方案目前 App 的网络通信基本都是用 HTTPS 的服务,但是随便一个抓包工具都是可以看到 HTTPS 接口的详细数据,为了做到防止抓包和无法模拟接口的情况,我们采取以下措施:中间人盗用数据,我们可以采取 HTTPS 证书的双向认证,这样子实现的效果就是中间人在开启抓包软件分析 App 的网络请求的时候,网络会自动断掉,无法查看分析请求的情况对于防止用户模仿我们的请求再次发起请求,我们可以采用 「防重放策略」,用户再也无法模仿我们的请求,再次去获取数据了。对于 App 内的 H5 资源,反爬虫方案可以采用上面的解决方案,H5 内部的网络请求可以通过 Hybrid 层让 Native 的能力去完成网络请求,完成之后将数据回调给 JS。这么做的目的是往往我们的 Native 层有完善的账号体系和网络层以及良好的安全策略、鉴权体系等等。后期会讨论 App 安全性的更深层次玩法,比如从逆向的角度出发如何保护 App 的安全性。提前给出一篇逆向安全方面的文章关于 Hybrid 的更多内容,可以看看这篇文章 Awesome Hybrid比如 JS 需要发起一个网络请求,那么按照上面将网络请求让 Native 去完成,然后回调给 JSJS 端代码var requestObject = { url: arg.Api + “SearchInfo/getLawsInfo”, params: requestparams, Hybrid_Request_Method: 0};requestHybrid({ tagname: ‘NativeRequest’, param: requestObject, encryption: 1, callback: function (data) { renderUI(data); }})Native 代码(iOS为例)[self.bridge registerHandler:@“NativeRequest” handler:^(id data, WVJBResponseCallback responseCallback) { NSAssert([data isKindOfClass:[NSDictionary class]], @“H5 端不按套路”); if ([data isKindOfClass:[NSDictionary class]]) { NSDictionary *dict = (NSDictionary *)data; RequestModel *requestModel = [RequestModel yy_modelWithJSON:dict]; NSAssert( (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Post) || (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Get ), @“H5 端不按套路”); [HybridRequest requestWithNative:requestModel hybridRequestSuccess:^(id responseObject) { NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil]; responseCallback([self convertToJsonData:@{@“success”:@“1”,@“data”:json}]); } hybridRequestfail:^{ LBPLog(@“H5 call Native`s request failed”); responseCallback([self convertToJsonData:@{@“success”:@“0”,@“data”:@""}]); }]; }}];以上是第一阶段的安全性总结,后期应该会更新(App逆向、防重放、服务端等)。 ...

January 15, 2019 · 8 min · jiezi