- 微信 schema 跳转之非官方文档
微信“应该”是最近开放了 schema 跳转小程序 的能力,大大方便了短信、邮件、外部网页等唤起微信小程序。 schema 链接格式大体是这样:[代码]weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e[代码],但是这段文本在安卓端无法识别。小规模测试结果如下: [图片] 后来想到用一个正常能够识别的网页地址,内容是重定向到指定的 schema 链接。这就是擅长的领域了,query 参数上带上 schema 链接,location.href 一下不就行了。这里就不 show 代码了,能看到文章的你一定行。 但是,发现在部分安卓手机下(如小米)还是没反应,原来简单的 schema 跳转水这么深的,于是百度谷歌了一下,找到了下面两份关键材料: H5唤起APP进行分享的尝试 AlanZhang001/H5CallUpNative: H5端唤醒移动客户端程序 看源码也不多,总结下来,因不同系统和浏览器对 schema 规范的理解不同,还有一些商业因素,不同环境下面需要用不同的方式进行跳转,甚至有的环境你根本就跳不了。 时间紧,任务重。简单处理吧,不同方式都来一遍,谁好使就用谁。所以简单总结了下,能用的几种方式: location 跳转 a 链接跳转 iframe 跳转 以上三种方式,逐一试用,最后实在不行就不行吧,简单处理,看有没有大神补充的。 相关代码如下: location [代码]location.href = "weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e"; [代码] a 链接跳转 [代码]var aLink = document.createElement("a"); aLink.className = 'call_up_a_link'; aLink.href = "weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e"; aLink.style.cssText = "display:none;width:0px;height:0px;"; document.body.appendChild(aLink); aLink.click(); [代码] iframe [代码]var iframe = document.createElement('iframe'); iframe.className = 'call_up_iframe'; iframe.src = "weixin://dl/business/?ticket=l69894d682fa8dbafe724a0ca3950741e"; iframe.style.cssText = "display:none;width:0px;height:0px;"; document.body.appendChild(iframe); [代码] 以上代码均可从参考资料中找到出处,感谢 是直接一进来就执行,还是事件触发,都可以。或者是一开始进来就执行,失败了显示几个可选跳转按钮让用户手动触发跳转。 但是关键问题还有一个,如何判断是可以成功唤起了呢?上述 github 代码里提到了一个根据页面 hidden 状态,但不够精准,如果用户没有选择跳转到微信呢?这是另一个需要深究的问题。 出于时间考虑,先以业务交付优先,如果有朋友知道的也可以一起讨论下。 另行文时间短,以技术交流为主,若有瑕疵,欢迎指出。 附上 vue 版本源码:微信 schema 跳转 参考链接: 微信官方文档:urlscheme.generate H5唤起APP进行分享的尝试 AlanZhang001/H5CallUpNative: H5端唤醒移动客户端程序 安卓端,微信schema无法跳转微信小程序?
2021-01-04 - 云函数调用公众号api需要将ip加入白名单,但云函数ip不固定,如何解决?
需完成功能:在云函数中获取公众号图文列表,并保存数据库 问题:获取公众号图文列表api 需要将ip加入到白名单中,但云函数ip不固定,如何解决?
2020-02-11 - 云开发怎么设置公众号的ip白名单?
小程序是用的云开发来做的, 新需求是希望能通过公众号给用户推送提醒消息,我在云函数上使用公众号的appid和secret来获取access_token,结果是失败,因为没有加ip白名单, 但是云开发后台的ip地址一直在变化,怎么办?
2019-08-07 - 云开发如何对外提供回调 URL
在云开发的使用过程中,有不少的用户会提出自己的疑问,我如何才能在自己的云开发应用中加入一个回调地址? 在实际开发过程中,不少涉及到支付、订单等异步操作的场景,会需要提供一个回调 URL,以确保在用户完成自己的支付过程后,由支付服务器对回调的 URL 发起请求,确认调用成功。因此,不少用户在使用时提出了自己的疑问,应该如何实现这样的功能? 由于目前云开发云函数尚未对外提供 HTTP 调用的能力,因此,我们可以借助腾讯云提供的云函数和 API 网关,来实现类似的功能。 架构介绍 [图片] 在进行调用时开发者所设置的回调地址是一个 API 网关的地址,微信支付等服务的服务器在完成操作以后,会执行 HTTP 请求,请求 API 网关。 API 网关对应的是腾讯云云函数,我们可以在腾讯云云函数中调用云开发的 SDK,或者是云开发的 HTTP API,完成对云开发数据库的操作。 具体实现 依赖 腾讯云账号 小程序的 AppID 和 App Secret 实现 1. 创建云函数 访问 https://console.cloud.tencent.com/ ,使用你的腾讯云账号登陆,在顶部菜单栏中找到「云产品」— 「Serverless」—「云函数」 [图片] 在云函数中,选择「函数服务」,并在函数服务页面点击「新建」 [图片] 创建一个新的云函数,其中函数名称根据你的需要填写 [图片] 运行环境选择 Node.js 8.9,创建方式选择空白函数,完成后点击下一步。 在下一页不需要做修改,直接点击完成。 2. 创建 API 网关调用 在创建完成云函数以后,我们会进入到这样的界面,在这个界面中选择「触发方式」 [图片] 在触发方式页面新增一个触发方式,使用 「API 网关触发」,请求方法选择「ANY」,发布环境选择「发布」,鉴权方法选择「免鉴权」。 [图片] 填写完成后,点击保存,然后你会获得一个地址,这个地址就是你后续的回调地址。 [图片] 你可以将这个地址在浏览器中打开,可以看到这样的数据 [图片] 则说明我们的云函数完成了配置。 3. 编写程序调用 当我们完成了云函数的配置以后,接下来我们可以修改云函数,使其完成我们自己想要的功能。 我们希望云函数可以从外部发来的请求中获取到参数,并借助云开发提供的 API,对云开发数据库中的数据进行更新。则我们可以这样操作 我们在本地新建一个目录,并在其中执行如下命令(需要你提前安装了 N ode.js 环境) [代码]cd 新建文件夹 npm init -y npm install -y got [代码] 然后创建一个文件[代码]index.js[代码],并在其中加入如下代码,并设置其中的 APPID、SECRET、ENV_ID 等字段。 [代码]'use strict'; const got = require('got'); const APPID = ''; // 小程序 APPID const SECRET = ''; // 小程序 Secret const ENV_ID = '' const TOKEN_URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${SECRET}` const QUERY_URL = 'https://api.weixin.qq.com/tcb/databasecollectionget?access_token=' exports.main_handler = async (event, context, callback) => { // 1. 提取参数 可以从 URL?id=1&status=2 中提取出 1 和 2 const id = event.queryString.id; const status = event.queryString.status; // 2. 获取 Token,用于后续的调用 let token_resp = await got(TOKEN_URL); let token = JSON.parse(token_resp.body).access_token const url = QUERY_URL + token; // 执行云开发命令 let result = await got(url, { method: 'POST', json: true, headers: { 'content-type': 'applicaiton/json', 'accept-encoding':'gzip' }, body: { "env": ENV_ID } }) return result.body }; [代码] 配置完成后,保存文件。 4. 上传代码 打包完成后,重新访问腾讯云控制台,找到刚刚创建的云函数,进入到「函数代码」中,选择其中的「本地上传文件夹」,选择你刚刚创建的文件夹,等待其自动压缩完成后,点击保存上传。 [图片] 上传完成后,会自动进行部署。 你可以重新访问刚刚生成的 API 网关地址,并在其后加入 id 和 status 参数,比如我的最后的地址是 [代码]https://service-51pn7koc-1251337088.gz.apigw.tencentcs.com/release/myFunction?id=1&status=2[代码] [图片] 访问以后,得到的结果是这样的 [图片] 这样就说明我成功的从云函数中访问了云数据库,并且通过一个无需鉴权的 API 地址获取到了信息。 总结 实际上,借助于 API 网关、云函数以及云开发的 API & SDK,你可以实现非常多的功能,能不能实现,就看你的想象力有没有达到啦。
2019-09-18 - 干货满满:个人小程序广告日入1k实战操作
众所周知,个人小程序受限于微信的政策,很多内容或者类目无法去做,对于变现,大家的目光基本也就聚焦在广告变现这一条路上。 随着小程序各种广告的开放,个人小程序的变现之路似乎也越来越明朗。然而,现实的情况却是,月底结算的时候,流量主们看着结算单上的两位数,欲哭无泪。 那么,对于个人小程序,广告变现的路子该如何去走? 如何才能实现收益最大化? 如何才能广告月入30k,当上CEO,赢取白富美,走上人生巅峰? **下面是满满的干货,一定要看到最后一行!** 1. 做什么方向?做什么内容?起什么名字? 以变现为目的的小程序,名字很重要,一定要和其他热门小程序相似、相近、相仿! 内容? 内容从来不是重点,只需要记住一点, 什么火做什么,什么话题热做什么! 以这个小程序为例: [图片] 看明白了吧,短视频现在多火啊,做他准没错! 说到这里,有人发现问题了,视频类目明明是个人不允许做的啊,这怎么办? 这个问题待会说。 2. 善用广告 目前可用的广告基本有banner,开屏,激励视频,贴片,这几种广告形式一定要在小程序中运用的淋漓尽致! 仍以此小程序为例: [图片] [图片] [图片] a. 打开小程序就开屏广告,切换tab就开屏广告,没事滚滚屏也给你来一个,不怕你不点,反正不小心也会点到 b. 视频流里面能放多少个就放多少个,万一不小心点到了呢? c. 视频的贴片广告一定要加的,不然就浪费了 d. 视频下方的相关视频一定要做,想看可以,先看一下激励视频再说。反正客户群体主要是大爷大妈,有的是时间,不在乎这15秒到30秒。 3. 善用分享 凡是目所能及的地方,一定要加上分享,分享朋友圈这种不仅要加还要做动态效,让用户记得去点。 [图片] 4. 善于召回用户 一定要用好小程序的下发通知功能,收集formid,没事就给用户发个通知什么的,不愁他不回头(至于formid怎么收集,不在本教程讨论范畴之内,自行搜索)。 [图片] 6. 绕审(敲黑板!划重点!) 说了这么多,一定有人要说了,你这小程序类目不对,广告乱搞,怎么可能过审啊? 重点来了,绕审,这个一定要做。 众所周知,微信的审核是 有人工环节的,只需要在提审期间,让审核人员看到你想让他看到的,不就行了么? 这个也很简单,审核期间,接口返回的数据做点手脚,页面上加个判断不就是了。 [图片] 看到了吧,审核期间所有视频都变成了图片,点击查看大图而已。 完美解决!! 好了说了这么多,对于月入30K是不是有点心得了???如果有兴趣,继续往下看 -------------------- -------------------- -------------------- 以上内容均属严重违规行为,请大家引以为鉴!!! 以上内容均属严重违规行为,请大家引以为鉴!!! 以上内容均属严重违规行为,请大家引以为鉴!!! 以上内容均属严重违规行为,请大家引以为鉴!!! 以上内容均属严重违规行为,请大家引以为鉴!!!
2019-09-10 - 小程序流量主运营技巧
前言(写给入坑的小白) 本文不涉及任何需要资质的小程序(如:视频类目)。小程序流量主是个人和小微企业主要变现途径之一,满1000人即可开通流量主(登录mp.weixin.qq.com,左侧边栏-推广-流量主-开通即可)。开通后,开发者可从流量主-广告位管理添加广告位,目前有6种广告位。 [图片] 正文(本文约很多字,分为四大主类,手里有1-10个小程序建议全部看完;手里有10个以上小程序,可跳过1、2、3,均为个人观点,不喜使劲喷) 一、小程序定位 小程序定位目前有以下四种,均不需要任何资质,个人(商城除外)/小微企业都可以做,由于本人不擅长文字表达,每个类型只选择一个做分析,谅解。 1、工具类 工具类有很多可以做:题库、技术文档、教程、去水印等。目前最火爆的应该属于疫情相关类的工具,关于疫情数据类小程序不做分析,没资质也没权利,主要说疫情周边可运营的工具。头像口罩,代表小程序:头像加口罩、戴个口罩吧、戴上口罩(每日搜索量约等于2000),可参考以下做法: [图片] 以下为近7日访问数据量 [图片] 盈利方式:流量主 延伸参考:如果仅做头像加口罩的话,那么疫情过后,这个小程序会直线下降,将无任何作用。如果开发者手里目前有类似小程序,可参考“头像加口罩”做法,逐渐去延伸头像周边功能,例: ①、头像加字:头像+数字、头像加V、头像加字、头像加圣诞帽、新年头像边框、头像加福、头像加明星等 ②、聊天背景图、壁纸:武汉加油、卡通、美女(不要漏点太多)、二次元、跑车、科技等 ③、趣味九宫格配图:类似朋友圈9张图,中间获取用户自己头像,周围8张图弄点能吸引用户的等 ④、文字秀:微信昵称下标、上标、个性昵称等 运营分析:如果参考以上4点做法,首先你的程序再疫情结束后,不至于直线下滑,最起码能留住一些用户(UI很重要) 个人建议:工具类的好处就是不需要去长时间盯着后台,建议有想法的开发者,可以入门5-10个左右工具类小程序(功能不要相同)。 推广方式:参考本文第四大板块内容 2、返利类 主流返利平台:淘宝、天猫、拼多多、京东、蘑菇街、唯品会、网易考拉,以下参考 [图片] 盈利方式:返利(主)+流量主(辅) [图片] 基础分析:每个人微信里都会有一个或多个微信群是给你们购物优惠券链接的,他们盈利方式主要是靠每个平台的返利,比如淘宝天猫的叫“阿里妈妈”、拼多多的叫“多多进宝”等 运营分析: ①、平台功能:提供所有优惠券、商品返利、代理入驻、提现(个人可做收款码、企业可对接微信支付到零钱) ②、招代理商、可以给代理商(兼职、宝妈)50%以上的返利 ③、除了商品优惠券之外,可以把返利分给一部分给到用户。首先,用户会花更少的钱买到商品;其次,用户买完东西还会赚点小钱,每个月可提现到微信零钱。这样用户会发生裂变,省钱+赚钱。 个人建议:开发者至少有一个类似的返利小程序,每个月只需运营一天,工作内容一是把用户的返利发给用户&代理商,二是自己去各大平台领取每个月的“工资” 推广方式:参考本文第四大板块内容 3、商城类(个人开发者可跳过) 商城类,本人运营的比较少,每天就10-20单左右,卖啥就不做广告了 盈利方式:差价 基础分析:如果自己手里有一些商品低价资源,可以做一个“综合服务商城类目”,然后去试着用广告主去推一下 运营分析: ①、平台功能:砍价、返利、拼团、回购、入驻、积分、抽奖、游戏营销 ②、广告主曝光&点击报价不要最低,也不要最高,理由就是最低的话,80%的钱会给你推到一些质量很差的微信用户,比如我。 ③、对接圈子,虽然圈子刚起步,不确定能不能做大,万一呢? 个人建议:企业一定要有一个自己的商城,哪怕没人买。这种东西怎么说呢,就好比一个企业站,虽然没什么用,但是得放那儿,万一客户要看呢? 推广方式:参考本文第四大板块内容 4、游戏类(非小游戏) 答题、成语、找茬等类似运营的比较多,可自行搜索,不要认为这是游戏,开发者就望而却步,在线教育类目是可以通过的,这个开发者很多都不知道。以下可参考: [图片] 盈利方式:流量主 基础分析:基本所有的模式都是闯关类型,这种类型的小程序,基本都是用户消磨时间用 运营分析:关卡尽量多,入门、初级、中级、高级,高级模式可以做类比循环,形成无限关卡模式,闯关奖励机制,签到机制等。这种类型的小程序比较方便运营,裂变起来也快。 个人建议:裂变模式一定要有,虽然微信会严格把控这方面功能,但是开发者可以做一些技巧,不要让用户强制或者主动去触发,这样微信对开发者还是很友好的。 推广方式:参考本文第四大板块内容 二、小程序开发 有实力的开发者,自己开发,云开发很快,会前端就可以了,没实力的去正规平台买源码,论坛源码也很多,有部分论坛还是嵌入了比特币勒索,自己做好防护。个人建议:开发者能开发尽量自己开发,后期迭代方便,不要像我一样,50多个小程序80%是买现成去运营的。反正各有各的好处,开发者可自行决定,运营者可选择直接购买源码直接上线运营,前提是自己看好功能是不是和自己要的一样。有些SAAS平台的开发者实力还是可以的,支持定制功能。此处不做广告,自行搜索或者询问朋友。 三、广告位位置及利润 开发者的每个页面广告位一定要分开!一定要分开!一定要分开!这样做的目的是为了分析每个广告位的利润,好去做调整,把收益最大化。 失败案例举例:小程序的主页、个人中心页用同一个banner广告位,这样做出来一点好处都没有,后台只能看到banner收益是多少,看不到是哪个页面收益。极端情况,收益全部再首页,个人中心页没有广告收益,这种情况开发者是不知道的,如果把广告位分开,这种情况可以去优化个人页面,或者主页面换成视频banner。广告位分析页面:流量主--数据统计--广告数据--广告指标明细--细分数据 [图片] [图片] 1、很多人表示,疫情期间流量主收入下滑。这个原因不是因为微信调整流量主收益,根本问题是自己的用户质量。举个例子,当你开通流量主之后,你的用户还是这1000个,假如你第一天收益为100,你很开心,1000用户就能赚100,你第二天就放弃推广了,这样的话,你的用户质量是会逐渐下滑,微信方完全可以认定为你这1000人都是自己的号,去刷广告费的。长此以往下去,你的流量主利润会无限趋向于0。举个栗子: [图片] 2、广告位位置一定要合理好看,但是不代表“流氓”,比如全明星代言的某游戏“元宝无限收一刀999”点哪儿哪充值。开发者需要注意的是小程序的质量,需要用户在每个页面停留的时长最起码30秒,这样一个完整的视频广告才能曝光完。 3、banner广告收益是按有效点击计算的。很多人好几千曝光,但是点击只有几个、十几个,这种情况需要不断去优化接入的场景/位置,提高用户点击意愿。个人技巧:banner广告位尽量不要太多,1-2个就可以。尽量多放几个视频广告位,这样曝光也有收益。格子广告没试过,用过都说不好~ 4、激励广告作为流量主最高收益是有一定道理的,用户为了获取某些奖励是必须观看完整的,所以给开发者建议:用户如果可以获得小程序内某些奖励,可以适当多放一些激励广告位。 5、所有的广告位都是根据用户年龄、爱好等参数去调取相应的广告,开发者不需要去考虑 6、广告收益个人认为:激励》视频》插屏》前贴》banner》格子(格子没试过,暂放倒数第一) 四、小程序推广 尽量做成年人主打的小程序,有些开发者觉得好玩儿,做一些儿童益智类的小程序,你是认为儿童有手机,还是认为家长愿意让孩子玩儿手机呢?这个很不解。没有鄙视的意思,也许是情怀吧~~毕竟我做小程序比较俗,就是为了赚钱。 主流推广方式:公众号引流、截流,由于涉及一些不合常规的内容,本文只说常规操作,剩下的自己领悟,或者可以联系我~ 首先小程序的名字至关重要,一个好的名字可以带来无限的流量,再加上裂变功能(邪恶的微笑)。起名字的时候可以用到的工具:搜索小程序-微信指数,查询关键字,尽量找稳定再1000万以上的搜索量,从关键字中摸索自己的小程序名字。这样用户搜索到你的小程序几率会很高~ 1、工具类核心玩儿法(适用于所有小程序推广):文章引流,截取关键字,火爆主题,比如2019年12月19日庆余年全集泄露、2020年疫情(不要发疫情数据内容,要发一些正能量的有内容文章去引流),我阅读过的文章最低的阅读量8000左右,最高的10万+,据说有好几百万的阅读量。如果你的文章写的好,结尾放一个小广告:为防止疫情蔓延,请给您的头像带上口罩~,啪,一个卡片小程序(或二维码),流量自己想~ 推广对象:18-30岁 2、返利类核心玩儿法: ①、可以参考工具类玩儿法 ②、各大微信群、QQ群,去推广,招代理等方式,或者去买一些基础流量,进行裂变,实际运营看下效果,好继续针对用户群体去推广,建立自己的群体系,群内发商品返利链接。微信好友没人?给你举个例子,我这篇文章发完,如果加个我的二维码,最起码能有100人加我,不是我文章写的有多好,是你永远不知道用户有什么样的目的和需求~ 推广对象:18-60岁 3、商城类核心玩儿法 ①、可参考返利类核心玩儿法,拥有自己的客户群体系,发一些自己的商品还是可以的,一定要带分销体系,你懂得~(最高3级,再高就是传销了) ②、广告主、目前效益个人感觉不明显,每次花1000块钱做广告,利润基本没有,和发广告的钱持平,而且用户留存也不是很高,可能是我的商品比较单一等各方面因素吧,不过赚流量还是不错的。 推广对象:18-30岁(以我的商城为例,还需看商城出售的内容) 4、游戏类核心玩儿法(非小游戏) ①、一个好的名字就够了。举例:精选商品橱窗(腾讯官方),微橱窗(我朋友的)。不得不说,这波流量很高,遗憾的是,他不是火爆的游戏类小程序~ [图片] ②、参考工具类玩儿法,文章引流截流 推广对象:18-40岁 五、小程序矩阵 矩阵一定要有,矩阵一定要有,矩阵一定要有,防截流,底配10个小程序。不是纯矩阵,是微信开发规定,每个小程序可以跳转10个小程序,开发者可以利用这个功能去添加自己的矩阵来获取更多的流量收益,保证自己的用户在自己的矩阵圈活动。 [图片] 写这篇文章主要是给大家传授经验,希望小白能学到点东西,入门后的朋友可领悟到更多运营方法,江湖之大,附月账单有缘再见 [图片]
2020-05-25 - 【必收】精心整理!小程序开发资源汇总(附带源码)
很多小伙伴想在春节放假期间学小程序,但是小程序学习的资源和教程可能不太好找。所以小助手精心整理了一期,全是干货!认真学,开启美妙的小程序开发之旅,做一个属于自己的微信小程序。有需要的小伙伴收藏好这期文章哦~ 本文收集整理了微信小程序开发资源,包括官方文档,云开发训练营文档,视频教程以及实战源码推荐,会不间断更新。。 欢迎添加云开发小助手CloudBase微信:Tcloudedu1 ,一起加入技术交流群~ 小程序云开发官方公众号 [图片] 目录 官方文档 云开发训练营 视频教程 小程序·云开发Demo 技术交流群 官方文档 小程序开发者工具 小程序设计指南 小程序开发教程 小程序框架 小程序组件 小程序API 小程序开发者工具 小程序云开发文档 云开发训练营 小程序开发入门 小程序与JavaScript 云开发快速入门 [图片] 视频教程 腾讯云云开发B站:https://space.bilibili.com/447496276 [图片] 小程序·云开发Demo 技术博客小程序 包括文章的发布及浏览、评论、点赞、浏览历史、分类、排行榜、分享、生成海报图等。 网盘小程序 兼具文件存储与分享功能的专属网盘小程序。 教务助手小程序 用完即走,查个成绩和课表,无需下载app或去翻看公众号内的历史内容。 功能日历小程序 既能查看日历又能备注事项,看云开发如何支持功能性日历小程序的快速开发。 客户业务需求收集小程序 用云开发快速制作客户业务需求收集小程序,教你用云开发实现小程序版“朋友圈”的发布与展示。 小程序朋友圈 把朋友圈装进小程序需要几步?借助云开发实现小程序朋友圈的发布与展示。 南苑导览 一款由学生独立开发的以地图为载体,提供中山大学南方学院具体地点的位置信息、导航、校园历史及文化介绍的小程序。 互动打卡小程序 用云开发轻松构建精美互动打卡小程序,交互式双人打卡,快乐加倍。 个性头像小程序 别再@官方啦!云开发教你轻松制作个性头像小程序,趣味挂件、个性icon。 二手书商城小程序 云开发轻松制作二手书交易商城小程序,让智慧延续,让温暖传递。 后台数据批量导出 小程序开发过程中如何将云数据库中的数据批量导出至excel。 发送邮件 初学者福音,手把手教你用小程序云开发实现邮件发送功能。 高考查分小程序 实现高考分数轻松查,小程序源码。 mini论坛 仅需两天轻松搭建mini论坛小程序。 运动圈小程序 打造运动圈小程序(以乒乓球为例),实现球友间高效互动。 心情日记小程序 我能想到最浪漫的事,可能就是“你的心事我全知晓”。 最美恋爱小程序 小程序前端用的是taro框架写的,后台用的云开发。教你用云开发为心爱的人做个小程。 校园约拍小程序 校园场景下,小程序·云开发大显身手,校园约拍小程序源码。 体重记录小程序 只想记录每日体重还得下个APP,不用那么麻烦!用云开发做个专属体重记录小程序,看看你每天瘦了多少。 口袋工具 口袋工具之历史上的今天。一个基于云开发的小程序,看看历史上的今天都发生了啥。 迷你微博 独立做个精简版微博出来让你刷刷刷吗?而且,它还兼具搜索、点赞、主页的功能 多媒体小程序 使用小程序·云开发构建多媒体小程序。 技术交流群 交流技术为主,开发学习工作中遇到问题可以在群内交流,欢迎有需要的朋友加群。 添加小助手微信(Tcloudedu1),回复“技术群”,即可加入云开发技术群。 最后 如果你有关于使用腾讯云云开发相关的技术故事/技术实战经验想要跟大家分享,欢迎留言联系我们~ 关注腾讯云云开发,后台回复【源码】,获取更多微信小程序云开发实战源码。 [图片] [图片] [图片] 关注「腾讯云云开发」,后台回复【 源码 】,获取更多微信小程序云开发实战源码。 持续更新中… [图片]
2020-01-16 - 十几套小程序视频教程免费分享
[图片] [图片] 闲来无事收集并整理了十几套小程序的视频教程和几百个小程序源码分享给大家,用于帮助大家更好地学习小程序,如果链接失效了可以给我留言。 下载地址: 点击下载 更多资源软件教程下载地址: https://www.90pan.com/n29950
2019-12-11 - 小程序云开发 如何调用第三方接口
小程序云开发 如何调用第三方接口
2018-12-24 - 云开发云函数无法获取get请求的返回值
AppID:wx05b683ac5e374894 问题类型:(Bug ) 框架类型:(小程序) 终端类型:(工具) 基础库版本:(2.2.3) 代码片段:() [图片] 云函数端 调用nodejs 的 request库 无法获取get 请求的返回值 加载成功 但是无法获取返回值 [图片] 我在本地测试是成功 可以返回OK
2018-08-22 - 云开发的云函数调用时能否固定IP?
现在需要用一个第三方接口,要求设置ip白名单,可是云函数没有固定ip呀?这个怎么解决
2019-10-11 - 一次性订阅消息接口不支持在表单提交事件中调用?
form的bindsubmit事件中调用wx.requestSubscribeMessage报requestSubscribeMessage:fail can only be invoked by user TAP gesture.这个难道不属于用户点击行为。手机型号是:华为荣耀20,Android版本:9,微信版本号:7.0.7,基础库版本号:2.8.3
2019-10-18 - 涉嫌诱导用户点击广告,我好郁闷!被封原因是什么?
[图片] 无缘无故被投诉说我诱导用户点击广告,我可从来没有过啊,而且本身底部广告也没多少钱,我根本就没管过。 而且我到底怎么诱导的,哪里出错了,我哪个地方明示暗示诱导了,起码得有个提示吧…… 我现在是一脸懵逼啊……我写申诉我都不知道怎么写。 你起码得说哪篇文章哪句话不对吧,我现在申诉都不知道咋回复…… 我郁闷…… 公号ID:chaojimeigong
2019-10-18 - base64加密工具类 es6写法
一个ES6写法的Base64加密工具类 [代码]class Base64 {[代码][代码] [代码][代码]constructor() {[代码] [代码] [代码][代码]}[代码][代码] [代码][代码]_keyStr = [代码][代码]"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[代码][代码];[代码] [代码] [代码][代码]encode(input) {[代码][代码] [代码][代码]var[代码] [代码]output = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]var[代码] [代码]chr1, chr2, chr3, enc1, enc2, enc3, enc4;[代码][代码] [代码][代码]var[代码] [代码]i = 0;[代码][代码] [代码][代码]input = [代码][代码]this[代码][代码]._utf8_encode(input);[代码] [代码] [代码][代码]while[代码] [代码](i < input.length) {[代码] [代码] [代码][代码]chr1 = input.charCodeAt(i++);[代码][代码] [代码][代码]chr2 = input.charCodeAt(i++);[代码][代码] [代码][代码]chr3 = input.charCodeAt(i++);[代码] [代码] [代码][代码]enc1 = chr1 >> 2;[代码][代码] [代码][代码]enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);[代码][代码] [代码][代码]enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);[代码][代码] [代码][代码]enc4 = chr3 & 63;[代码] [代码] [代码][代码]if[代码] [代码](isNaN(chr2)) {[代码][代码] [代码][代码]enc3 = enc4 = 64;[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]if[代码] [代码](isNaN(chr3)) {[代码][代码] [代码][代码]enc4 = 64;[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]output = output + [代码][代码]this[代码][代码]._keyStr.charAt(enc1) + [代码][代码]this[代码][代码]._keyStr.charAt(enc2) + [代码][代码]this[代码][代码]._keyStr.charAt(enc3) + [代码][代码]this[代码][代码]._keyStr.charAt(enc4);[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]return[代码] [代码]output;[代码][代码] [代码][代码]};[代码][代码] [代码][代码]// public method for decoding[代码][代码] [代码][代码]decode(input) {[代码][代码] [代码][代码]var[代码] [代码]output = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]var[代码] [代码]chr1, chr2, chr3;[代码][代码] [代码][代码]var[代码] [代码]enc1, enc2, enc3, enc4;[代码][代码] [代码][代码]var[代码] [代码]i = 0;[代码] [代码] [代码][代码]input = input.replace(/[^A-Za-z0-9\+\/\=]/g, [代码][代码]""[代码][代码]);[代码] [代码] [代码][代码]while[代码] [代码](i < input.length) {[代码] [代码] [代码][代码]enc1 = [代码][代码]this[代码][代码]._keyStr.indexOf(input.charAt(i++));[代码][代码] [代码][代码]enc2 = [代码][代码]this[代码][代码]._keyStr.indexOf(input.charAt(i++));[代码][代码] [代码][代码]enc3 = [代码][代码]this[代码][代码]._keyStr.indexOf(input.charAt(i++));[代码][代码] [代码][代码]enc4 = [代码][代码]this[代码][代码]._keyStr.indexOf(input.charAt(i++));[代码] [代码] [代码][代码]chr1 = (enc1 << 2) | (enc2 >> 4);[代码][代码] [代码][代码]chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);[代码][代码] [代码][代码]chr3 = ((enc3 & 3) << 6) | enc4;[代码] [代码] [代码][代码]output = output + String.fromCharCode(chr1);[代码] [代码] [代码][代码]if[代码] [代码](enc3 != 64) {[代码][代码] [代码][代码]output = output + String.fromCharCode(chr2);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]if[代码] [代码](enc4 != 64) {[代码][代码] [代码][代码]output = output + String.fromCharCode(chr3);[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]output = [代码][代码]this[代码][代码]._utf8_decode(output);[代码] [代码] [代码][代码]return[代码] [代码]output;[代码][代码] [代码][代码]};[代码] [代码] [代码][代码]// private method for UTF-8 encoding[代码][代码] [代码][代码]_utf8_encode(string) {[代码][代码] [代码][代码]string = string.replace(/\r\n/g, [代码][代码]"\n"[代码][代码]);[代码][代码] [代码][代码]var[代码] [代码]utftext = [代码][代码]""[代码][代码];[代码] [代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]n = 0; n < string.length; n++) {[代码] [代码] [代码][代码]var[代码] [代码]c = string.charCodeAt(n);[代码] [代码] [代码][代码]if[代码] [代码](c < 128) {[代码][代码] [代码][代码]utftext += String.fromCharCode(c);[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]if[代码] [代码]((c > 127) && (c < 2048)) {[代码][代码] [代码][代码]utftext += String.fromCharCode((c >> 6) | 192);[代码][代码] [代码][代码]utftext += String.fromCharCode((c & 63) | 128);[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]utftext += String.fromCharCode((c >> 12) | 224);[代码][代码] [代码][代码]utftext += String.fromCharCode(((c >> 6) & 63) | 128);[代码][代码] [代码][代码]utftext += String.fromCharCode((c & 63) | 128);[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]return[代码] [代码]utftext;[代码][代码] [代码][代码]};[代码] [代码] [代码][代码]// private method for UTF-8 decoding[代码][代码] [代码][代码]_utf8_decode(utftext) {[代码][代码] [代码][代码]var[代码] [代码]string = [代码][代码]""[代码][代码];[代码][代码] [代码][代码]var[代码] [代码]i = 0;[代码][代码] [代码][代码]var[代码] [代码]c = 0;[代码][代码] [代码][代码]var[代码] [代码]c1 = 0;[代码][代码] [代码][代码]var[代码] [代码]c2 = 0;[代码] [代码] [代码][代码]while[代码] [代码](i < utftext.length) {[代码] [代码] [代码][代码]c = utftext.charCodeAt(i);[代码] [代码] [代码][代码]if[代码] [代码](c < 128) {[代码][代码] [代码][代码]string += String.fromCharCode(c);[代码][代码] [代码][代码]i++;[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]if[代码] [代码]((c > 191) && (c < 224)) {[代码][代码] [代码][代码]c2 = utftext.charCodeAt(i + 1);[代码][代码] [代码][代码]string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));[代码][代码] [代码][代码]i += 2;[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]c2 = utftext.charCodeAt(i + 1);[代码][代码] [代码][代码]c3 = utftext.charCodeAt(i + 2);[代码][代码] [代码][代码]string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));[代码][代码] [代码][代码]i += 3;[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]}[代码] [代码] [代码][代码]return[代码] [代码]string;[代码][代码] [代码][代码]}[代码][代码]}[代码] [代码]export {[代码][代码] [代码][代码]Base64[代码][代码]}[代码] 使用的话在要使用的js中 [代码]import{[代码][代码] [代码][代码]Base64[代码][代码]} from [代码][代码]'utils/base64.js'[代码][代码];[代码][代码]const base64 = [代码][代码]new[代码] [代码]Base64();[代码]既可以
2019-07-13 - 小程序请求数据双向混合加密和防篡改+防重放攻击的实现
前言 大家好,借着中秋放假明日又要上班的这个晚上,平常又没空,趁这个时间点就决定来一篇。 [图片] 我们都知道微信小程序的服务端API上,官方可谓是做足了心思,对用户的数据进行加密,虽然对我们开发者来说似乎是一种麻烦,但是从长远角度来看,是十分有必要的,用户隐私高于一切。 那么在小程序的开发过程中与后端对接接口时是否有想过这样的问题呢? HTTPS真的百分百安全吗? 数据被成功抓包后安全吗? 数据被篡改后还有效吗? 请求被重放后安全吗? 世界上没有绝对安全的系统,但我们可以让它被破解的成本变高。 本篇文章专业性并不高,如果存在错误请大家为我指出来,以免误导别人,谢谢! 以下我们将小程序端称为C端,服务端称为S端,服务端代码是Node.js,仅供参考,但原理都一样,后端可以是其它语言。 思考 围绕着以上的问题,探究一下问题的答案? 本部分只对问题做思考,具体实现请参考下面的实现部分。 HTTPS真的百分百安全吗?只能说是相对安全,当然,在微信小程序的沙箱环境里,HTTPS通信会更加安全,否则官方可能会要求我们对请求加密了对吧。但百密一疏,C端是否存在漏洞,假设C端安全了难道S端就安全嘛?细思恐极,退一万步讲,百分百安全是不存在的,由于篇幅问题相关漏洞大家可以搜索探究。 假设数据被中间人成功抓包,如果数据是明文传输,那么将导致数据泄露,因此对数据进行加密是必要的,但应该如何加密呢?如何做到密钥安全?C端和S端如何进行数据的加解密?RSA和AES加密应该使用哪种加密明文?如何充分发挥RSA和AES两个加密算法的特长? 假设数据被篡改,如果因为请求数据被篡改而导致严重后果,那么很大程度上其实是代码设计有问题,正常设计中安全应被摆在重要的位置,至少不应该出现购买某样商品时是通过C端发送商品价格给S端调起支付那样(只需篡改商品价格即可支付极少的钱购买商品),如果代码设计并不存在严重问题,那么数据被篡改也是不可忽视的,我们需要进行数据签名,让不同的数据拥有唯一的MD5哈希值,如果数据被篡改,通过哈希值即可判断数据是否被篡改,当然也可能会有人问,既然数据被篡改,那攻击者会不会重新生成一个哈希值代替?是的。但是我们有接下来会说到的key,key会作为签名的数据变量之一,由于攻击者并不知道key值因此无法重新签名,key值建议不是固定值而是周期性更换的随机值,例如随着用户的登录态而产生并随之抹除。 假设请求被重放,大部分时候由于数据被加密和防篡改处理过,攻击者并无法直接获得数据或篡改,但如果通过劫持到的登录认证请求的原始数据并重新发起该请求,则攻击者将可能获得重要的认证数据,使得系统将攻击者作为正常用户处理,后续的请求攻击者仍能伪装成正常的用户进行后续攻击。 期望效果 在开始实现之前先看一下完成后的效果,以下是截取了Network中其中一个GET请求发送的数据: [图片] 实际上发送的数据是如下图所示: [图片] 然后是该请求返回的数据: [图片] 实际上返回的数据是如下图所示: [图片] 整个流程: [图片] 实现 由于整个流程在C端的实现上顺序反过来的,因此下面的步骤也将是反向而行。 工具库下载 工欲善其事,必先利其器!这三个库是经过修改压缩的,支持在小程序上使用并且体积可观(总共69.1KB,如果不涉及密码哈希处理只需要前两个库,体积只有65.3KB),接下来的实现操作将会使用到,建议大家可以根据实际情况对功能进行二次封装: CryptoJS.js:点击下载 RSA.js:点击下载 SHA256.js(可选):点击下载 在线的各类加解密工具(可选,可以收藏起来,平时测试挺有用):点击访问 请求数据防重放 要防范请求重放攻击,首先需要了解Unix时间戳 timestamp概念,和时间戳不一样的是它的单位是秒,事实上这个需求也只需要秒级即可。除此之外还将用到另一个值:nonce,它是一个随机产生并只能被使用一次的值,长度自定,请求越频繁长度需要越长(降低同一时间产生相同nonce的几率),C端发送请求时需要将timestamp和生成的nonce加入发送的参数中。那么,如何将两者结合呢?我这为防重放的目标下了一个简单的定义。 同样的请求只能发生一次,且请求必须在规定时间内发出。 何为同样的请求?不是指两次发送的参数一致就是一样的,而是连timestamp和nonce也一样才算是同样的请求。 那么S端如何确认其是同样的请求呢? S端每次接收到一个请求,都会将该请求的nonce存入缓存并保持60秒(这个阈值不一定是60秒,可以根据实际需要定义),时间过后该值将被移除,建议S端采用Redis存储nonce,这样可省去检测和移除nonce的代码。如果S端发现当前请求的nonce存在于已存储的nonce之中,则此请求发生重复,那么timestamp有何用? 如果只使用nonce我们只能保证该请求60秒内不会重复,但60秒后依然任人宰割,这不是要的结果。所以timestamp将用来限制时间,S端时间戳减去C端发送请求的时间戳,得到的差值为N秒,如果N秒大于60秒则此请求过期,那么则可以保证,60秒内因为nonce相同而被判为请求重放,60秒后因为时间差超过而被判为请求已过期,因此确保了请求不会被重放。。 以下展示三种情况: [代码]C端时间戳:1568487720 //2019/9/15 03:02:00 C端NONCE:5rKbMs2Fm3 C端发送请求 -> S端接收请求 S端时间戳:1568487722 //2019/9/15 03:02:02 CS端时间差:1568487722 - 1568487720 = 2 C端NONCE是否存在于缓存:false 【重放校验通过】 [代码] [代码]C端时间戳:1568487722 //2019/9/15 03:05:22 C端NONCE:IzFEs52bAC C端发送请求 -> S端接收请求 S端时间戳:1568487922 //2019/9/15 03:02:00 CS端时间差:1568487922 - 1568487722 = 200 C端NONCE是否存在于缓存:false 【重放校验不通过,请求已过期,因为时间差超过60秒】 [代码] [代码]C端时间戳:1568487720 //2019/9/15 03:02:00 C端NONCE:IxwPHQU0nA C端发送请求 -> S端接收请求 S端时间戳:1568487722 //2019/9/15 03:02:02 CS端时间差:1568487722 - 1568487720 = 2 C端NONCE是否存在于缓存:true 【重放校验不通过,此请求为重放请求,因为nonce已经存在,此请求已经完成,不可重复】 [代码] timestamp和nonce将作为参数参与下面部分的签名。 请求数据防篡改 C端数据签名 首先通过对参数按照参数名进行字典排序(调过一些第三方API的朋友应该明白),假设当前需要传输的参数如下: [代码]{ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3" } [代码] 进行字典排序,参数名顺序应为: [代码]const keys = Object.keys(data); //获得参数名数组 keys.sort(); //字典排序 console.log(key); //["a", "b", "c", "nonce", "timestamp"]; [代码] 参数字典排序后应和参数一起拼接为字符串,至于使用什么拼接符就要与S端商量了,如果参数值是一个数组或一个对象(如c为[1,2,3])那么可以将数据值转为JSON字符串再拼接。以上参数拼接后字符串如下: [代码]a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720 [代码] 下一步是计算拼接字符串的MD5哈希值了嘛? 不是。因为这样拼接的字符串很容易被攻击者伪造签名并篡改数据,这样就失去了签名的意义了。也就是说缺了一个key值。key值又从何而来?上面思考部分有提到建议从登录后发放,并且这个key与该用户的登录态绑定,登录态有效期间,将使用这个key进行请求的签名与验签,至于如何鉴别用户,我们会在最终发送给S端的参数加入一个sessionId作为登录态唯一标识,这里不加是因为这部分数据是需要参与后续的加密的,而sessionId不参与加密。 但是可能又会有一个问题,登录前没有key怎么实现的登录请求?事实上,登录请求并不怕篡改,因为攻击者自己也不知道账号密码,所以无需提供key用于登录请求的签名。 提到登录密码这个需要注意一点,密码不能明文传输,请计算哈希值后传输,S端比对账户密码哈希值即可确认是否正确,同样S端非特殊情况也不能明文存储密码,建议SHA-1或更高级的SHA-256计算后的值,MD5值可能被使用彩虹表(一种为各种常见密码建立的MD5映射表)破解。SHA256计算的库已在上面工具库下载提供。 拼接上我们登录时随机生成的key: [代码]a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720&key=gUelv79KTcFaCkVB [代码] 接下来计算32位MD5哈希值 为什么上面说MD5会被破解而这里却用MD5计算?因为此处计算MD5的目的并不是为了隐藏明文数据,而只是用于数据校验 此处引入了CryptoJS.js [代码]const CryptoJS = require('./CryptoJS'); const signStr = "a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720&key=gUelv79KTcFaCkVB"; const sign = CryptoJS.MD5(signStr).toString(); //a42af0962de99e698d27030c5c9d3b0e [代码] 这么一来我们的数据签名阶段就完成了,然后需要把签名加入参数之中,将和参数一起传输,需要注意的是传输参数无需在意参数名排序。以下是当前的参数处理结果: [代码]{ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" } [代码] 其实从上面这两部分内容来看,会发现防重放和防篡改是相辅相成的,就像两兄弟一样,少了谁都干不好这件事。 S端验证签名 既然有签名那必定也有验签,验签流程其实就是重复C端的前面流程并比对CS两端得出的签名值是否一致。S端取得请求数据后(假设数据未加密,暂时不讨论解密),将除了sign之外的参数名进行字典排序,sign用一个临时变量存下,然后排好序的参数和C端一样拼接得到字符串。 接下来根据上面部分提到的未加密参数sessionId获得用户登录态并获取到该状态的临时key拼接到字符串末尾,接下来进行MD5计算即可得到S端方获得的签名,此时与请求中携带的sign比较是否一致则可确定签名是否有效,如果不一致返回签名错误。具体流程请参考以下: [代码]C端发送请求 -> S端接收请求 //请求参数为data const _sign = data.sign; //排序并拼接除sign外的参数 let signStr = a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720 const sessionId = ...; //用户的sessionId const sessionData = getUserSessionData(sessionId);//根据sessionId查询用户登录态sessionData并取出key,并拼接到字符串尾部 const key = sessionData.key signStr += key; //MD5计算并比对,代码仅供参考 const sign = crypto.md5(signStr); if(sign !== _sign) { //签名错误! } [代码] 请求数据加解密 有了上面部分的参数处理铺垫后,接下来就该开始本文章最核心的加解密,在这之前我们先了解AES和RSA两种加密算法。了解概念后我们再来思考一下两者的一些特性。 AES加解密需要密钥,除某些模式外还需要提供初始化向量,可使用密钥解出明文,是对称加密算法。 RSA加解密需要一对密钥,分别为公钥和私钥,公钥加密,私钥解密,是非对称加密算法。 两者在加密长文本性能上AES占优势。 根据这些让我们发现,他们可以形成互补关系,RSA加解密安全性高但长文本处理性能不及AES。AES加解密长文本性能优于RSA但需要明文密钥和向量加解密,密钥的安全性成问题。 那么在C端生成随机的AES密钥和向量,使用密钥和向量使用aes-128-cbc加密模式(也可以根据实际需要采用其它的模式)加密真正需要传输的参数(参数则是经过防重放+防篡改处理的参数)得到encryptedData,然后将该密钥使用RSA公钥加密得到encryptedKey,下面我顺便把AES加密的向量也一起加密了得到encryptedIV,这样就完美的互补了对方的缺点,既能够较快的完成数据加密又能保证密钥安全性,两全其美。 整个个加解密流程如下图所示: [图片] 下面的四个小章节将逐一描述流程实现: C端加密数据 C端加密后的数据应如下(并非固定格式,根据自己需要定制): [代码]{ "sessionId": "xxxxxxxx", "encryptedData": "xxxxxx", "encryptedKey": "xxxxxx", "encryptedIV": "xxxxxxx" } [代码] 但RSA公钥又是怎么发放到C端的呢?答案是在登录认证的时候服务器下发的,登录成功时服务器会创建RSA密钥对并把公钥发放给C端,私钥存在服务器上该用户的登录态数据中。流程如下所示: [代码]C端发起登录请求 -> S端接收登录请求 S端登录认证是否通过 true S端生成RSA密钥对 - publicKey , privateKey S端查询相关用户信息 S端生成登录态信息 -> 向登录态信息存入privateKey私钥,登录态信息类似如下 "xxxxxx": { id: "xxxxx", authData: { key: "xxxxxx", privateKey: "xxxxxx" } } S端返回登录态唯一标识sessionId和publicKey公钥以及相关用户信息 -> C端 C端存储登录态信息于本地,后续请求将使用服务器提供的公钥进行加密 [代码] 其中privateKey就是该用户当前登录态所使用的解密私钥,C端通过公钥加密后的AES密钥数据只能用该私钥解密。 如果希望登录阶段的请求也加密,那么可以手动生成一个RSA密钥对,然后客户端放置一个固定的公钥,服务器也使用一个固定的私钥进行登录阶段的加解密。 具体实现如下: 此处引入了CryptoJS.js和RSA.js [代码]const CryptoJS = require('./CryptoJS'); const RSA = require('./RSA.js'); //假设当前已登录成功并获得S端下发的RSA公钥且已存入本地存储 //createRandomStr为生成随机大小写英文和数字的字符串 const aesKey = createRandomStr(16); //生成AES128位密钥 16字节=128位 const aesIV = createRandomStr(16); //生成初始化向量IV const raw = JSON.stringfly({ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" }); const encryptedData = CryptoJS.AES.encrypt(data: raw, key: aesKey, { iv: aesIV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); //使用CBC模式和Pkcs7填充加密 const authData = wx.getStorageSync("authData"); //读取本地存的RSA公钥 RSA.setPublicKey(authData.publicKey); //设置RSA公钥 const encryptedKey = RSA.encrypt(aesKey); //RSA加密AES加密密钥 const encryptedIV = RSA.encrypt(aesIV); //RSA加密AES加密初始化向量,是否加密向量可由自己决定 //最后的处理结果 const result = { sessionId: authData.sessionId, encryptedData, encryptedKey, encryptedIV }; [代码] S端解密数据 [代码]const crypto = require('crypto'); const cryptojs = require('crypto-js'); const { sessionId, encryptedData, encryptedKey, encryptedIV } = requestData; const authData = getUserSessionData(sessionId); //获取用户登录态数据 const privateKey = authData.privateKey; const aesKey = crypto.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, encryptedKey); //使用私钥解密得到AES密钥 const aesIV = crypto.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, encryptedIV); //使用私钥解密得到AES iv向量 const key = cryptojs.enc.Base64.parse(aesKey); const iv = cryptojs.enc.Utf8.parse(aesIV); const decryptedData = cryptojs.AES.decrypt(encryptedData, key, { iv, mode: cryptojs.mode.CBC, padding: cryptojs.pad.Pkcs7 }); //采用与加密统一的模式和填充进行解密 const { "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" } = decryptedData; //至此解密得到C端传来的数据 [代码] S端加密数据 当S端处理完C端的请求后应加密响应数据,那么加密响应数据应该使用什么密钥呢?既然C端已经将加密的密钥发送过来了,那么干脆将C端使用的AES密钥拿来加密响应数据就可以了。加密的数据传回C端后,C端只需使用该请求所使用的AES加密密钥进行解密即可。响应的加密数据如下: [代码]const cryptojs = require('crypto-js'); const responseData = JSON.stringify(...); //此为S端需要返回给C端的数据 const aesKey = ...; //此为之前C端用来加密数据的AES密钥 const aesIV = createRandomStr(16); //生成初始化向量IV const encryptedData = cryptojs.AES.encrypt(data, aesKey, { iv: aesIV, mode: cryptojs.mode.CBC, padding: cryptojs.pad.Pkcs7 }); //加密响应数据 const encryptedResponse = { "encryptedData": encryptedData, "iv": aesIV }; //得到加密后的响应数据并返回给C端 [代码] C端解密数据 C端接收到S端的响应数据后应对加密的数据进行解密,此次解密就是单纯的AES解密了,使用发起请求时用于加密数据的AES密钥配合响应数据的iv向量对encryptedData进行解密,得到解密后的数据即为S端真正的响应数据。实现过程如下: 此处引入了CryptoJS.js [代码]const CryptoJS = require('./CryptoJS'); const key = ...; //之前用于加密请求参数的AES加密密钥 const { encryptedData, iv } = responseData; const aesIV = CryptoJS.enc.Utf8.parse(iv); const aesKey = CryptoJS.enc.Utf8.parse(key); const decryptedData = CryptoJS.AES.decrypt(encryptedData, aesKey, { iv: aesIV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); //AES解密响应数据 const { ... } = decryptedData; //得到解密后的响应数据 [代码] 结语 第一次写这么长的文章,可能存在大量纰漏,如果大佬发现问题欢迎指出(`・ω・´)我会马上修改。 也许小程序的运行环境会比我想象中的更安全 也许HTTPS也会比我想象中的更安全 也许Web服务器引擎也比我想象中的更安全 但,安全不总是先行一步的吗?
2019-10-24 - 小程序构建骨架屏的探索
首屏 一般情况下,在首屏数据未拿到之前,为了提升用户的体验,会在页面上展示一个loading的图层,类似下面这个 [图片] 其中除了菊花图以外网上还流传这各种各样的loading动画,在PC端上几乎要统一江湖了,不过最近在移动端上面看到不同于菊花图的加载方式,就是这篇文章需要分享的Skeleton Screen,中文称之为"骨架屏" 概念 A skeleton screen is essentially a blank version of a page into which information is gradually loaded. 在H5中,骨架屏其实已经不是什么新奇的概念了,网上也有各种方案生成对应的骨架屏,包括我们经常使用的知乎、饿了么、美团等APP都有应用骨架屏这个概念 图片来源网络,侵删 [图片] 方案 先从H5生成骨架屏方案开始说起,总的来说H5生成骨架屏的方案有2种 完全靠手写HTML和CSS方式给每个页面定制一套骨架屏 利用预渲染的方式生成静态骨架屏 第一套方案,毫无疑问是最简单最直白的方式,缺点也很明显,假如页面布局有修改的话,那么除了修改业务代码之外还需要额外修改骨架屏,增加了维护的成本。 第二套方案,一定程度上改善了第一套方案带来的维护成本增加的缺点,主要还是使用工具预渲染页面,获取到DOM节点和样式,保留页面结构,覆盖样式,生成灰色块盖在原有文本、图片或者是canvas等节点上面,最后将生成的HTML和CSS打包出来,就是一个带有骨架屏的页面。最后再利用webpack工具将生成的骨架屏插入到HTML里面,详细的话可以看看饿了么的分享,这里就不多描述了。 调研了下H5生成骨架屏的方案,对于小程序生成骨架屏的方案也有了一个大致的想法,主要有2个难点需要实现 预渲染 获取节点 预渲染 再说回饿了么提供的骨架屏的方案,使用 puppeteer 渲染页面(或者使用服务端渲染,vue或者react都有提供相应的方案),拿到DOM节点和样式,这里有一点需要注意的是,页面的渲染是需要初始化的数据,数据的来源可以是初始化的data(vue)或者mock数据,当然小程序是无法直接使用 puppeteer 来做预渲染(有另外的方案可以实现),需要利用小程序初始化的 data + template 渲染之后得到一个初始化结构作为骨架屏的结构 [代码]//index.js Page({ data: { motto: 'Hello World', userInfo: { avatarUrl: 'https://wx.qlogo.cn/mmopen/vi_32/SYiaiba5faeraYBoQCWdsBX4hSjFKiawzhIpnXjejDtjmiaFqMqhIlRBqR7IVdbKE51npeF6X1cXxtDQD2bzehgqMA/132', nickName: 'jay' }, lists: [ 'aslkdnoakjbsnfkajbfk', 'qwrwfhbfdvndgndghndeghsdfh', 'qweqwtefhfhgmjfgjdfghaefdhsdfgdfh', ], showSkeleton: true }, onLoad: function () { const that = this; setTimeout(() => { that.setData({ showSkeleton: false }) }, 3000) } }) //index.wxml <view class="container"> <view class="userinfo"> <block> <image class="userinfo-avatar skeleton-radius" src="{{userInfo.avatarUrl}}" mode="cover"></image> <text class="userinfo-nickname skeleton-rect">{{userInfo.nickName}}</text> </block> </view> <view style="margin: 20px 0"> <view wx:for="{{lists}}" class="lists"> <icon type="success" size="20" class="list skeleton-radius"/> <text class="skeleton-rect">{{item}}</text> </view> </view> <view class="usermotto"> <text class="user-motto skeleton-rect">{{motto}}</text> </view> <view style="margin-top: 200px;"> aaaaaaaaaaa </view> </view> [代码] 有了上面的 data + template 之后,就有了一个初始化的页面结构,接下来就需要拿到节点信息 节点 小程序基础库1.4.0之后小程序基础库提供了一组新的API,可用于获取节点信息,具体API戳这里。 跟H5方式一样,根据class或者id获取节点信息,不同的是只能获取到当前的节点信息,无法获取到其父或者子节点信息,所以只能手动给需要渲染骨架屏的节点添加相应的class或者id [代码]<view class="container"> <view class="userinfo"> <block> <image class="userinfo-avatar skeleton-radius" src="{{userInfo.avatarUrl}}" mode="cover"></image> <text class="userinfo-nickname skeleton-rect">{{userInfo.nickName}}</text> </block> </view> <view style="margin: 20px 0"> <view wx:for="{{lists}}" class="lists"> <icon type="success" size="20" class="list skeleton-radius"/> <text class="skeleton-rect">{{item}}</text> </view> </view> <view class="usermotto"> <text class="user-motto skeleton-rect">{{motto}}</text> </view> <view style="margin-top: 200px;"> aaaaaaaaaaa </view> </view> [代码] 约定2个特殊的class作为获取节点信息的标记[代码]skeleton-rect[代码]和[代码]skeleton-radius[代码],在页面中获取相应的[代码]top[代码]、[代码]left[代码]、[代码]width[代码]、[代码]height[代码]进行骨架屏的绘制 结果 [图片] 具体的调用方式和源码,请看 github ,最后求start 总结 上文有说到小程序也可以使用 page-skeleton-webpack-plugin 方式一样生成骨架屏,最重要的一点就是需要将小程序跑在chrome上面,后面的流程就一样了,至于怎么将小程序跑在chrome上面呢?可以利用 wept ,缺点就是目前作者已经停止维护这个工具了,不支持新版小程序的API。 说回来我这个生成骨架屏的方案,其实跟 page-skeleton-webpack-plugin 有点相似,不同的是,page-skeleton-webpack-plugin 采用离线渲染的方式生成静态骨架屏插入路由中,而我采用运行时先渲染页面默认结构,然后根据默认结构再绘制骨架屏。从性能角度出发确实不如 page-skeleton-webpack-plugin,但是也差不了多少了,主要还是小程序并没有提供类似服务端渲染的方案。目前从使用上来讲,还是有点小麻烦,需要默认数据撑开页面结构,需要给相应的节点添加class,后面有时间再研究下有没有更好的方案吧~~~
2019-02-20