关于微信开发:微信浏览器内打开自己的APPwxopenlaunchapp填坑记录

需要:微信浏览器内关上app分享的文章,要呈现“用app关上”的按钮,点击即跳到本人的APP,并依据参数执行相应的操作。上面开始踩坑之旅行:首先须要有一个微信公众号(类型必须是服务号,订阅号不行,解决办法:再注册一个服务号)注册并审核通过后,进入https://open.weixin.qq.com/ 微信官网示例代码:https://www.weixinsxy.com/jss...备注:链接中蕴含php、java、nodejs以及 python 的示例代码供第三方参考,第三方切记要对获取的 accesstoken 以及jsapi_ticket进行缓存以确保不会触发频率限度 我的案例: <?phprequire_once "jssdk.php"; //从下载的官网示例代码获取// 留神,因为curl版本的不同,下面这个jssdk.php的某一句要改一下://原句curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, true);//改成curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);否则可能不工作 //留神:这里要填公众服务号的appID以及appSecret //在https://mp.weixin.qq.com/ 性能设置、根本配置-公众号开发信息中获取$jssdk = new JSSDK("wxxxxxxxbb5", "xxxxxxxxxxx");$signPackage = $jssdk->GetSignPackage();?><script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <script> wx.config({ debug: false,appId: '<?php echo $signPackage["appId"];?>',timestamp: <?php echo $signPackage["timestamp"];?>,nonceStr: '<?php echo $signPackage["nonceStr"];?>',signature: '<?php echo $signPackage["signature"];?>',jsApiList: [ // 所有要调用的 API 都要加到这个列表中 //我没用到 所以留空],openTagList: ['wx-open-launch-app']}); wx.ready(function () { // 在这里调用 API 可留空}); </script><!--留神:wx-open-launch-app标签里的appid是所需跳转到的你的挪动利用的appid,不是公众号服务号的--><wx-open-launch-app style="position:fixed; z-index: 999; top:10em; right:3px; " id="launch-btn" appid="wxxxxxxxxxx52" extinfo="works_id=<?php echo $works_id ?>"> <script type="text/wxtag-template"> <style>.btn {width:100%; height:1.5em; padding: 3px; border-radius: 0.6em; color:#fff; background: #dc143c; }</style><div class="btn">在App内关上</div></script></wx-open-launch-app> ...

December 22, 2022 · 1 min · jiezi

关于微信开发:CRM客户关系管理系统有哪些类型

CRM作为企业信息化管理系统的一种,类型也是很多的。CRM零碎指个别指透过软件、硬件和网络技术,为你建设一个客户信息收集、治理、剖析和利用的信息系统,从而CRM零碎能够一站式整合与客户相干的各种资讯,实时追踪和残缺保留客户资料档案,再从销售、行销和客服三个方面将流程自动化,强化你与客户之间的互动,并加重不必要的人手劳动。而从不同的角度理解CRM零碎有哪些类型,能够帮忙企业从不同的角度理解企业对于CRM零碎的需要,帮忙企业更好的抉择CRM零碎类型。上面依据企业需要不同,对CRM零碎进行了分类介绍。一、以营销作用的类型1、经营型CRM经营类CRM软件善于整顿客户信息——联系方式、购买记录、拜访记录、购买偏好、询问纪录、往来邮件、沟通历史等,为客户建设客户档件,存于数据库中,必要时能够通过一键分享的性能让所有部门的人员都能够看到信息内容。这类CRM有助于向上销售、进步反复购买率。如果您想晓得何时何地利用何种渠道向何客户群体发送何种产品信息,这种类型的CRM软件是您的不二抉择。2、销售自动化CRM销售自动化CRM可全程跟踪销售流程,性能包含并不仅限于初步筛选潜在客户、安顿预约等,为销售人员、管理人员提供先进的销售治理自动化工具,例如,线索生成与验证、商机获取渠道剖析、销售阶段与可能性剖析、竞争对手剖析、实时预测以及其余性能。这些性能让企业齐全专一于客户生命周期(线索获取-甄别-转换-保留-进步忠诚度、,从而增加收入。3、剖析型CRM剖析型CRM所波及的技术内容比拟多其中次要包含数据库,数据挖掘等。比方该类软件能够从销售自动化CRM中提取销售报告,将其与经营型CRM中的客户信息进行比对,从而得出性别、年龄、地理位置可能对销售造成的影响。此类CR有利于设计长期营销策略,促成企业倒退。4、营销型CRM营销型CRM重视纪录、跟踪、剖析营销流动,是经营型CRM和剖析型CRM的结合体,经常和这二者捆绑在一起。5、合作型CRM合作型CRM用于实现多种渠道和客户进行交换,以及用于公司外部多渠道进行工作交换,也就是协同工作。有了这种CRM零碎,员工不必头脑风暴也能够取得多方位思考。打个比方,若客户反映某个电子游戏存在问题,客 服团队收到反馈后提交至CRM零碎,零碎可将信息发送给游戏开发团队,立刻修复问题。以上介绍的CRM系统分类其实更加侧重于CRM零碎性能,企业只有理解了这些,能力更好的抉择零碎。数夫家具CRM,利用信息技术实现客户关系高效治理,以消费者为外围,全周期精准客户营销,全渠道订单治理和高效供应链协同,反对新批发转型的门店管理系统。二、以客户为核心的类型B2C CRM : 面向消费者B2B CRM:面对管道或终端客户,并非间接购买企业产品或服务的间接消费者三、以零碎架构设计类型CRM业务架构零碎:针对行业的通用根本业务架构CRM平台化架构零碎:针对企业的个性化需要CRM系统对企业业务的帮忙1. 多维度建设残缺客户档案有条理地汇合网站、短讯、电邮、即时沟通软件的客户资料,对立格局贮存交易、服务资讯,缩短员工后续追踪的工夫;这有助剖析客户心理个性、行为模式,辨别顾客群,进行针对性营销。2. 可永恒保留客户资料 同步云端永恒保留客户资料,就算销售员到职,也能留住新旧客户资源。3. 第三方集成性能(企业最放心)能够晦涩与其他软件集成,数据互通;CRM零碎的二次开发成本低,节俭你的人力、工夫老本、资金。4. 疾速给予回应即时网络聊天人可疾速提供客户声援,提供客户时间轴,记录会议工夫,并会定时在零碎介面显示揭示。客户跟进不及时也是导致客户散失的一大起因。5. 提供个性化服务能够依据客户信息的,提供个人化服务,比方节假日问候,生日优惠、订单确认等讯息,6. 晋升成交率帮你追踪机会和流动,疾速对商机评分并加以调配,由此,你能够疾速理解潜在和现有客户的交易阶段与业务健全状态。同时,CRM零碎的安顿通话揭示、触发电子邮件等行政性能,也缩短了交易周期。微盛CRM基于企业微信的营销和服务平台,集引流获客、销售转化、经营治理和会话存档等性能于一体,帮忙企业解决获客难、经营效率低和客户资产散失等问题,助力用好企业微信做增长。

March 28, 2022 · 1 min · jiezi

关于微信开发:微信扫码登录

微信扫码登录1. 应用背景现在开发业务零碎,已不是一个独自的零碎。往往须要同多个不同零碎互相调用,甚至有时还须要跟微信,钉钉,飞书这样平台对接。目前我开发的局部业务零碎,曾经实现微信公众平台对接。作为常识总结,接下来,咱们探讨下对接微信公众平台的一小部分性能,微信扫码登录。其中的关键点是获取openid。我认真查找了微信提供的开发文档,次要有以下三个形式可实现。 通过微信公众平台生成带参数的二维通过微信公众平台微信网页受权登录通过微信开发平台微信登录性能2. 开发环境搭建2.1 内网穿透微信所有的接口拜访,都要求应用域名。但少数开发者是没有域名,给很多开发者测试带来了麻烦。不过有以下两种计划能够尝试: 应用公司域名,让公司管理员配置一个子域名指向你公司公网的一个ip的80端口。而后通过Nginx或者通过nat命令,将改域名定位到您的开发环境应用内网穿透工具,目前市面有很多都能够应用收费的隧道。不过就是不稳固,不反对指定固定子域名或者曾经被微信限度拜访。通过我大量收集材料,发现钉钉开发平台提供的内网穿透工具,比拟不错。用阿里的货色来对接微信货色,想想都为微信感到羞耻。你微信不为开发者提供便当,就让对手来实现。那钉钉的内网穿透工具具体怎么应用用的呢? 首先应用git下载钉钉内网穿透工具,下载好后找到windows_64目录,在这里新建一个start.bat文件,内容为 ding -config=ding.cfg -subdomain=pro 8080其中-subdomain 是用来生成子域名8080示意隐射本地8080端口双击start.bat文件,最终启动胜利界面如下 通过我测试,这个相当稳固,并且能够指定动态子域名。几乎就是业界良心 2.2 公众号测试环境拜访公众平台测试账号零碎,能够通过微信登录,可疾速失去一个测试账号。而后咱们须要以下两个配置 接口配置信息 在点击提交按钮时,微信服务器会验证咱们配置的这个URL是否无效。这个URL有两个用处 通过签名验证地址是否无效接管微信推送的信息,比方用户扫码后告诉签名生成逻辑是用配置的token联合微信回传的timestamp,nonce,通过字符串数组排序造成新的字符串,做SHA签名,再将签名后的二进制数组转换成十六进制字符串。最终的内容就是具体的签名信息。对应的java代码如下 // author: herbert 公众号:小院不小 20210424 public static String getSignature(String token, String timestamp, String nonce) { String[] array = new String[] { token, timestamp, nonce }; Arrays.sort(array); StringBuffer sb = new StringBuffer(); for (String str : array) { sb.append(str); } try { MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(sb.toString().getBytes()); byte[] digest = md.digest(); StringBuffer hexStr = new StringBuffer(); String shaHex = ""; for (int i = 0; i < digest.length; i++) { shaHex = Integer.toHexString(digest[i] & 0xFF); if (shaHex.length() < 2) { hexStr.append(0); } hexStr.append(shaHex); } return hexStr.toString(); } catch (NoSuchAlgorithmException e) { logger.error("获取签名信息失败", e.getCause()); } return ""; }对应GET申请代码如下 ...

April 26, 2021 · 6 min · jiezi

微信小程序授权登录取消授权重新授权处理方法-附可用代码

微信小程序授权登录基本是小程序的标配了,但是官方的demo,取消授权后,就不能再重新点击登录,除非重新加载小程序才可以,这下怎么办? 我们可以先在首页引导用户点击,然后跳转到一个新的页面,在新的页面进行授权,然后新的页面授权成功,立马跳回首页,显示用户信息。 话不多说,直接上代码代码结构: index是首页login是授权页 首页代码index.wxml <!-- 未授权,只显示一个授权按钮 --><view wx:if="{{result==false}}"> <button bindtap="getinfo" class="loginbtn"> 授权登录 </button></view><!-- 授权后只显示头像和昵称 --><view elif="{{result==true}}" class="info"> <image src="{{head}}" class="headimg"></image> <text class="nickname">{{name}}</text></view>index.wxss /**index.wxss**/.loginbtn{ width: 150px; height: 45px; background: #06C05F; margin:100px auto 0; line-height: 45px; font-size: 15px; color: #fff;}.info{ width: 80px; height: 100px; margin:50px auto 0;}.info .headimg{ width: 80px; height: 80px; border-radius: 100%;}.info .nickname{ text-align: center;}index.js //index.jsPage({ data: { userInfo: {}, hasUserInfo: false }, //事件处理函数 getinfo: function () { wx.navigateTo({ url: '../login/index' }) }, onLoad: function (e) { let that = this; // 获取用户信息 wx.getSetting({ success(res) { // console.log("res", res) if (res.authSetting['scope.userInfo']) { console.log("已授权") // 已经授权,可以直接调用 getUserInfo 获取头像昵称 wx.getUserInfo({ success(res) { console.log("获取用户信息成功", res) that.setData({ name: res.userInfo.nickName, head: res.userInfo.avatarUrl, result: true }) }, fail(res) { console.log("获取用户信息失败", res) that.setData({ result: "取消授权" }) } }) } else { console.log("未授权") that.setData({ result: false }) } } }) }})授权页代码index.wxml ...

October 17, 2019 · 1 min · jiezi

3分钟教你学会使用路线规划小程序插件

以下内容转载自微信开放社区腾讯位置服务官方文章《3分钟教你学会使用路线规划小程序插件》作者:腾讯位置服务链接: https://developers.weixin.qq....来源:微信开放社区著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。路线规划小程序插件是腾讯位置服务开发的一款为用户规划驾车、公交、步行路线方案的插件。开发者可以直接在小程序内使用这个插件,从而为自己的用户提供多种出行方案选择。 路线规划插件的功能路线规划插件能为用户规划驾车出行路线(如下图1所示),并且当行车起点和行车终点之间可以规划出多个方案时会展示多个方案及方案耗时。这些不同方案体现了不同的策略,例如根据实时路况时间最短、红绿灯数较少、少收费等策略。 同时驾车路线在地图中会通过不同路线的颜色直观反映道路的拥堵情况,例如红色路线表示那段道路拥堵,这就能够让用户提前规避拥堵路段。 路线规划插件也能为用户规划步行出行路线(如下图2所示),不仅显示了步行路线距离和耗时信息,还显示了用户步行过程中,走过的天桥、人行横道数量,更人性化的显示了步行消耗了多少卡路里。 路线规划插件还能为用户规划公交出行路线(如下图所示),提供多种公交和地铁出行方案,并且用户可以根据自己的实际情况进行方案排序,例如时间短优先排序、少步行优先排序、少换乘优先排序。出行方案上也会有时间短这样的标志信息说明方案特点。 路线规划插件的应用场景路线规划插件应用场景非常丰富,可以直接接入到餐饮、电影等各种类型的小程序中,让消费者在小程序中就能获得到达门店的路线规划方案,方便去门店消费。 设想一个场景,小王周末想要吃一顿大餐,于是打开了某家餐厅小程序,当小王决定去这家餐厅时,不需要再打开地图软件去规划出行路线,通过我们的路线规划插件,在这家餐厅的小程序中就能直接规划小王目前的位置到餐厅的出行路线。小王可以选择开车去餐厅,如果今天车牌号限行,那么小王也可以选择公共交通出行,如果到餐厅的距离很近,那么小王可以选择步行方式到达餐厅。 小程序只需要使用路线规划插件就能拥有这些全面精准规划路线能力。看了这些功能,是不是想马上体验呢?别急!接下来就介绍路线规划插件的使用方法。 路线规划插件的使用方法 1、申请路线规划插件在微信公众平台中, “微信小程序官方后台-设置-第三方设置-插件管理” 里点击 “添加插件”(如下图所示),搜索 “腾讯位置服务路线规划” ,选择添加插件,小程序开发者就可以在小程序内使用该插件了。 2、申请key调用路线规划插件需要申请腾讯位置服务的服务账号,key是开发者的唯一标识,申请key请点击这里。申请key的具体步骤如下: 2.1 填写申请信息 2.2 创建key成功 2.3 授权小程序appid开通微信小程序服务:控制台 -> key管理 -> 设置(使用该功能的key)-> 勾选“微信小程序” -> 填写“授权 APP ID” ->保存。 2.4 勾选“WebService API”及“白名单”微信小程序插件需要使用WebService API的部分服务,所以使用该功能的key需要具备相应的权限。 如果开发者之前是腾讯位置服务的用户并申请过key,则可以跳过上面2.1、2.2的步骤,直接进行2.3、2.4步骤的设置。 3、在小程序中引入路线规划插件只需要在小程序的app.json文件做如下配置就可以在小程序中引入路线规划插件: // app.json{ "plugins": { "routePlan": { "version": "1.0.0", "provider": "wx50b5593e81dd937a" } }, "permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序定位" } }}4、在小程序中调用路线规划插件在小程序中调用路线规划插件也非常简单: let plugin = requirePlugin('routePlan');let key = ''; //使用在腾讯位置服务申请的keylet referer = ''; //调用插件的小程序的名称let startPoint = JSON.stringify({ //起点 'name': '中国技术交易大厦', 'latitude': 39.984154, 'longitude': 116.30749});let endPoint = JSON.stringify({ //终点 'name': '北京西站', 'latitude': 39.894806, 'longitude': 116.321592});wx.navigateTo({ url: 'plugin://routePlan/route-plan?key=' + key + '&referer=' + referer + '&endPoint=' + endPoint});如以上示例代码所示,只需要传4个参数,就能为小程序用户提供驾车、公交、步行路线规划信息了。这4个参数含义如下: ...

October 15, 2019 · 1 min · jiezi

小程序模板消息能力调整新的订阅消息将不受时间限制

昨日,微信在小程序模块消息能力方面公布了一项重大调整。原有的模块消息将升级为「订阅消息」,支持一次性和长期性订阅消息。而模块消息将于2020年1月10日下线,小程序将无法再使用原接口推送模板消息,因此开发者需要及时进行调整。 以往,模块消息作为服务进度提醒和召回用户的重要入口,受到了不少小程序运营者的青睐。但是,部分小程序利用「模板消息」推送营销广告甚至是虚假信息诱导点击。为用户带来骚扰,也影响了微信小程序生态的良好运行。 另一方面,模块消息需要在用户点击触发之后的7天内进行推送,但对于服务周期超过7天的小程序,「模板消息」无法满足它们的需求。 因此,微信对模板消息下发条件进行了调整,将小程序的推送进行场景化细分,每个推送模块都需要经过用户主动授权。用户只有根据需求进行订阅,才会收到小程序的服务通知,并且消息推送没有时间限制,部分公共服务场景还提供了长期订阅功能。 「订阅消息」消息类型新上线的「订阅消息」消息类型分为两种: 一次性消息推送 :用户订阅一次后,小程序可不限时间地推送一条订阅消息。如果用户想避免重复授权,可以勾选“总是保持以上选择,不再询问”并点击允许,以后便默认同意订阅这类消息,无须做多次选择。 长期性消息推送:用户订阅一次后,小程序可长期推送多条消息。目前长期性订阅消息仅面向政务、医疗、交通、金融、教育等线下公共服务小程序开放,后续将根据行业需求和用户体验不断进行完善。 「订阅消息」会带来什么影响?「订阅消息」的更新,意味着用户不再被动地接收信息,小程序消息推送的选择权回到用户手中。以往,当用户选择接收推送后,小程序便不加区分地将服务信息、营销信息统统推送给用户。现在,用户能够自主选择小程序的消息,也可以随时拒收该小程序的服务通知。 对于开发者而言,由于用户主动订阅才可以推送消息,之前通过不断收集formid来发送消息模板的操作将会失效,小程序开始走向人性化、精细化运营。 另一方面,「订阅消息」取消了7天内推送消息的限制,推送时间更加灵活并且能够契合多样化的服务需求。只要用户没有主动拒收,开发者就可以随时推送服务通知。使消息触达更加高效,也使用户唤醒更加方便。 「订阅消息」能用在什么地方· 用户召回对于低频、长线服务的小程序而言,订阅消息在用户召回方面显得更为重要。小程序提供实用的服务功能,让用户主动授权。在退出小程序后,小程序依然能结合场景因素,在后续选择合适的时间为用户提供服务消息,再次唤醒沉默的用户。 但是在召回用户同时,开发者需要顾及用户体验。根据订阅消息运营规范,使用订阅消息能力进行诱导订阅、诱导点击、内容与用户预期不符都会被视为违规。因此,订阅后解锁某个操作,或者订阅后获得奖励都是不被允许的。详见==小程序订阅消息接口运营规范==:https://developers.weixin.qq.... · 刚需服务既然订阅消息的选择权在用户手里,那么小程序能否提供用户需要的服务则成为关键。订阅消息可以更精准化、个性化,引导用户在不同场景内去订阅。例如生活类小程序的服务进度提醒、电商小程序的降价通知、内容小程序的话题推送等等。 针对不同行业的小程序,微信提供了各种消息模板,开发者可在后台选择相应的模板使用。比如,针对小游戏,微信就提供了排行榜、新功能发布、活动结束、版本更新、道具领取等14种通用模板。 在原来的模块消息下线之前,开发者们注意对接口进行及时调整。接入「订阅消息」能力,==可参考接口文档==:https://developers.weixin.qq.... 我们最近新建了个WeGeek技术交流群,欢迎小程序开发同好者进群交流,调戏勾搭群里的云开发大神~扫码添加Wegeek小助手即可获取进群方式。

October 14, 2019 · 1 min · jiezi

好快-1分钟开发好一个下拉刷新滚动加载列表

好快, 1分钟写好下拉刷新,滚动加载自动分页列表前言欢迎关注BUI Webapp专栏 或者 bui神速微信公众号. 以往文章: 2019开发最快的Webapp框架--BUI交互框架微信Webapp开发的各种变态路由需求及解决办法!【BUI实战篇】BUI数据驱动做的拼图游戏 Webapp移动适配版,基于vuejs拼图游戏改造webapp结合Dcloud平台打包图文教程一张脑图看懂BUI Webapp移动快速开发框架【上】--框架与工具、资源一、观看实操视频 点击观看视频实录 安装完以下环境后, 从0到1, 手把手教, 你也可以做到!二、开发准备安装buijs cli命令行工具(需要先安装node环境, 建议使用node 8.x);如何安装使用buijs?安装bui-fast 快速编辑器插件(推荐vscode);如何安装使用bui-fast?打开跨域的chrome浏览器;如何打开跨域的Chrome浏览器?三、开发过程使用 buijs 构建工程 1.创建Webapp工程buijs create demo2.安装依赖cd demo/npm installwindows 推荐使用淘宝的 cnpm install3.运行预览npm run dev使用bui-fast编辑器插件生成控件视频里使用的是vscode 可以在安装插件那里找到 bui-fast. xxx 假设为控件名生成规则1: 在html里, 使用 ui-xxx 生成控件结构ui-list生成以下结构 <div id="uiList" class="bui-scroll"> <div class="bui-scroll-head"></div> <div class="bui-scroll-main"> <ul class="bui-list"> </ul> </div> <div class="bui-scroll-foot"></div></div>在js里, 使用 bui-xxx 生成控件的初始化代码bui-list生成以下初始化代码 // 列表控件 js 初始化:var uiList = bui.list({ id: "#uiList", url: "http://rap2api.taobao.org/app/mock/84605/example/getNews", pageSize: 5, data: {}, //如果分页的字段名不一样,通过field重新定义 field: { page: "page", size: "pageSize", data: "data" }, callback: function(e) {}, template: function(data) { var html = ""; data.forEach(function(el, index) { html += `<li class="bui-btn bui-box"> <div class="bui-thumbnail"><img src="${el.image}" alt=""></div> <div class="span1"> <h3 class="item-title">${el.name}</h3> <p class="item-text">${el.address}</p> <p class="item-text">${el.distance}公里</p> </div> <span class="price"><i>¥</i>${el.price}</span> </li>` }); return html; }});保存就会自动预览四、从bui.list看自动分页设计原理 ...

August 27, 2019 · 2 min · jiezi

小程序实时音视频实践

微信小程序成为当下热门话题,下面从多个方面来谈谈小程序直播功能开发过程详解 。 2017年下半年,微信6.5.21版本支持在线音视频功能。开发者可以通过两个音视频组件 <live-pusher> 和 <live-player> 实现实时地在线直播、视频通话、语音通话等功能。 本期小程序课,微信开发哥将详细为大家介绍一下音视频组件在线直播和视频通话这两个应用场景。 在线直播该怎么做? 在线直播的应用场景有哪些? 在游戏直播、远程授课、以及企业内部的培训分享等场景中,都可能会用到在线直播功能,直播的应用场景可以遍及各行各业。 比如微信电竞是一款游戏直播产品,以小程序为产品呈现方式。 比如在医疗行业,专家医师往往需要全国各地飞进行学术交流和培训,出差本身耽误了医生大量时间,在线远程授课能大大减少这里的时间耗用。 小程序中的 <live-pusher> 和 <live-player> 两个组件 ,都有一个叫做live ( <live-pusher> 中对应 mode 属性为 SD, HD, FHD)的模式,专门为在线直播而设计,通过小程序的音视频接口的live 模式,可以实现上述应用场景。 02在线直播的内部原理是什么? 主播端使用 <live-pusher> ,它在小程序的内部是一个推流引擎,它负责对手机摄像头和麦克风的数据进行采集和编码,并通过 url 参数指定的 rtmp 推流地址上传到云端。 云端的作用类似信号放大器,它负责将来自主播端的一路音视频流数据进行放大,将数据实时并且无差异的负责并扩散到全国各地,从而解决主播和观众端之间距离太远(比如,跨地区和跨运营商)的问题。 观众端使用 <live-player> 进行播放,它在小程序的内部是一个在线播放器,负责从云端实时拉取音视频数据并进行解码和渲染。由于云端的放大效应,每一个观众都能在离自己比较近的云服务器上拉取到实时且流畅的音视频流。   下面从多个方面来谈谈 微信小程序怎么做直播。     怎么用小程序实现在线直播? step1:开通一个云直播服务(比如 腾讯云 ),或者自己搭建一个rtmp服务器(例如 nginx-rtmp 服务)。step2:生成推流 url ,推流地址一般以 “rtmp://” 打头,比如  rtmp://8888.livepush.myqcloud.com/live/8888_test 就是一个典型 rtmp 推流 Url。step3:为你的小程序增加一个 <live-pusher> 标签,并将 url 参数指定为你在 step2 中生成的推流 url。同时, <live-pusher> 的 mode 参数可以指定为 HD 或者 FHD,这是在线直播场景中比较推荐的画质。 同时,你还可以通过 <live-pusher> 的 beauty 和 whiteness 等参数设定美颜和美白等级。 step4:生成推流 url 和播放地址,推流一般都是 rtmp:// 打头的 url,而播放地址则有两种选择,分别是 “rtmp://” 开头的 rtmp 播放协议,“http://” 打头和“.flv”结尾的的 http-flv 播放协议,推荐使用后者,因为这种播放地址各个云厂商都优化的比较好。step5:为你的小程序增加一个 <live-player> 标签 ,并将 src 参数指定为你在 step4 中生成的播放 url。同时, <live-player> 的 mode 参数请指定为 live, orientation  和 object-fit 属性可以用于调整画面布局, min-cache 和 max-cache 则可以用于控制观众跟主播之间的延时大小,推荐的设置是 min-cache = 2, max-cache = 5。关于在线直播 ...

August 21, 2019 · 2 min · jiezi

微信公众号生成新浪短网址-快速生成

有没有想过,向一个公众号发送长链接,然后公众号给你回复一个短网址? 其实很简单: <?phpdefine("TOKEN", "xiao");//用于回复用户消息function responseMsg(){ $postStr = $GLOBALS["HTTP_RAW_POST_DATA"]; if (!empty($postStr)){ $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); $fromUsername = $postObj->FromUserName; $toUsername = $postObj->ToUserName; $MsgT = $postObj->MsgType; $time = time(); //如果用户发的text类型 if($MsgT=="text"){ $key = trim($postObj->Content); $textTpl = "<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Content><![CDATA[%s]]></Content> </xml>"; $msgType = "text"; //生成短网址 $dwzapi = "http://api.t.sina.com.cn/short_url/shorten.json?source=3271760578&url_long=".$key; $dwzpost = file_get_contents($dwzapi); $dwzjsondecode = json_decode($dwzpost,true); //发送 $contentStr = $dwzjsondecode[0]['url_short']; $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr); echo $resultStr; exit; }else{ $textTpl = "<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Content><![CDATA[%s]]></Content> </xml>"; $msgType = "text"; //发送 $contentStr = "请发送链接"; $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr); echo $resultStr; exit; } //如果用户发的event(事件)类型 if($MsgT=="event"){ $Event = $postObj->Event; if ($Event==subscribe) { $contentStr = "欢迎关注"; }else{ $contentStr = "希望您下次关注,但您收不到此条消息了"; } $textTpl = "<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Content><![CDATA[%s]]></Content> </xml>"; $Title = $postObj->Title; $Description = $postObj->Description; $Url = $postObj->Url; $msgType = 'text'; $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr); echo $resultStr; exit; } }else{ echo "不对"; exit; }} $echoStr = $_GET["echostr"]; //如果有$echoStr说明是对接 if (!empty($echoStr)) { //对接规则 $signature = $_GET["signature"]; $timestamp = $_GET["timestamp"]; $nonce = $_GET["nonce"]; $token = TOKEN; $tmpArr = array($token, $timestamp, $nonce); sort($tmpArr, SORT_STRING); $tmpStr = implode( $tmpArr ); $tmpStr = sha1( $tmpStr ); if( $tmpStr == $signature ){ echo $echoStr; }else{ echo ""; exit; } }else{ responseMsg(); }?>配置方法:1、登录公众号2、打开基本配置 ...

August 17, 2019 · 1 min · jiezi

基于SpringBoot的开源微信开发平台JeewxBoot-10-版本发布

项目介绍Jeewx-Boot 是一款基于SpringBoot的免费微信开发平台。支持微信公众号、微信活动、小程序官网。Jeewx-Boot实现了微信公众号管理、小程序CMS、微信活动等基础功能,便于用户二次开发,快速搭建微信应用。源码下载https://github.com/zhangdaisc...https://gitee.com/jeecg/jeewx...技术交流QQ交流群 : 97460170在线文档:http://jeewx-boot.mydoc.io小程序文档: http://shop.jeewx.com/#/doc/r...技术论坛 :www.jeecg.org演示地址:https://app.jeewx.com/jeewx项目说明项目名中文名备注jeewx-boot-base-system系统用户管理含项目启动类jeewx-boot-module-cmsCMS管理后台 jeewx-boot-module-weixin微信公众号管理 jeewx-app-cms小程序官网源码采用wepy语言系统模块├─系统管理│ ├─用户管理│ ├─角色管理│ ├─菜单管理│ └─首页设置│ └─项目管理(插件)├─公众号运营│ ├─基础配置│ │ ├─公众号管理│ │ ├─关注欢迎语│ │ ├─未识别回复语│ │ ├─关键字设置│ │ ├─自定义菜单│ │ ├─菜单支持小程序链接│ │ ├─Oauth2.0链接机制│ ├─素材管理│ │ ├─文本素材│ │ ├─图文素材│ │ ├─超强图文编辑器│ │ ├─图文预览功能│ ├─用户管理│ │ ├─粉丝管理│ │ ├─粉丝标签管理│ │ ├─超强图文编辑器│ │ ├─接受消息管理│ │ ├─粉丝消息回复├─小程序官网│ ├─站点管理│ ├─广告管理│ ├─栏目管理│ ├─文章管理│ ├─后台管理代码│ ├─小程序前端代码├─微信抽奖活动(即将开源)│ ├─砸金蛋│ ├─摇一摇│ ├─微信砍价├─高级功能(尚未开源)│ ├─小程序商城│ ├─微信投票│ ├─分销商城│ ├─。。。└─其他插件 └─更多功能陆续开源。。系统特点采用最新主流技术架构(Springboot+Mybatis+Velicity)强大的代码生成器,代码一键生成微信公众号管理,基础功能无需开发,直接使用支持微信小程序,已经提供小程序CMS供大家学习和使用采用插件开发机制,后续更多插件提供系统截图PC端 小程序端 手机端 欢迎吐槽,欢迎star~

July 15, 2019 · 1 min · jiezi

微信开发SDK支持小程序让你拥有更简易的API-JeewxApi-131-版本发布

JEEWX-API 是一款JAVA版的微信开发SDK,支持微信公众号、小程序、微信企业号、支付宝生活号SDK和微博SDK。你可以基于她 快速的傻瓜化的进行微信开发、支付窗开发和微博开发。 基于jeewx-api开发,可以立即拥有简单易用的API,让开发更加轻松自如,节省更多时间。一、JEEWX-API 快速集成 引入 Maven 依赖, 在pom.xml 添加jeewx-api 依赖 <dependency> <groupId>org.jeecgframework</groupId> <artifactId>jeewx-api</artifactId> <version>1.3.1</version></dependency>二、升级日志 增加微信图文评论接口增加微信获取接口分析数据接口微信第三方平台接口URL去除空格微信上传其他媒体素材接口修改,兼容缩略图重构maven pom依赖,精简依赖引用Logger日志统一改成slf4j升级依赖版本号 jdom、junit、alipay-sdk-java三、源码下载 https://gitee.com/jeecg/jeewx...https://github.com/zhangdaisc...四、技术交流 快速文档详细API技术官网

July 3, 2019 · 1 min · jiezi

微信小程序之列表左滑删除功能

介绍第一次写小程序,记录一下遇到的需求以及解决方法。可能功能不是很难,主要是做下记录。为以后遇到相同的需求做铺垫。什么是左滑删除用过QQ的人都知道,消息列表内,左滑单个聊天可以删除、置顶的操作。对于移动端窄小的屏幕来说,这种交互可以说是非常的节省地方。故受到了众多产品狗的喜爱。 实现原理最外层一个view水平方向排列,里面包含一个内容区view,一个操作区view让你要展示的布局充满屏幕,通过css样式让超出的删除按钮隐藏监听touch事件,平移布局显示和隐藏删除按钮(列表每一项中有一个isTouchMove属性,通过监听touch改变该属性给列表不同的样式将隐藏的按钮显示出来) 直接上代码wxml<view class="list-page"> <view class="list-item {{user.isTouchMove?'list-item-touch-active':''}}" wx:for="{{list}}" wx:for-item="user" wx:for-index="index" wx:key="user.id" bindtouchstart="touchstart" bindtouchmove="touchmove" data-id="{{user.id}}"> <view class="item-content"> <view class="content-name">{{user.name}}</view> <view class="content-info"> <text>{{user.phone}}</text> <text>{{user.sex}}</text> </view> </view> <view class="item-delete">删除</view> </view></view>wxss.list-page{ display: flex; flex-direction: column; border-top: 2rpx solid #f0f0f0}.list-item{ height: 160rpx; width: 100%; display: flex; justify-content: space-between; align-items: center; border-bottom: 2rpx solid #f0f0f0;}.item-content{ width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; padding: 0 20rpx 0 20rpx; -webkit-transition: all 0.4s; transition: all 0.4s; -webkit-transform: translateX(180rpx); transform: translateX(180rpx); margin-left: -200rpx;}.content-info{ display: flex; width: 100%; justify-content: space-between; font-size: 32rpx; color: #999}.content-name{ width: 100%;}.list-item-touch-active .item-content{ margin-left: -100rpx;}.list-item-touch-active .item-content, .list-item-touch-active .item-delete { -webkit-transform: translateX(0) !important; transform: translateX(0) !important;}.item-delete{ width: 100rpx; height: 160rpx; display: flex; justify-content: center; align-items: center; background: red; color: #fff; font-size: 32rpx; -webkit-transform: translateX(180rpx); transform: translateX(180rpx); -webkit-transition: all 0.4s; transition: all 0.4s;}js// pages/list/list.jsconst App = getApp()Page({ /** * 页面的初始数据 */ data: { list:[ { id:1, name:'张三', phone:'15955040222', sex:'男', isTouchMove:false, }, { id: 2, name: '张三', phone: '15955040222', sex: '男', isTouchMove: false, }, { id: 3, name: '张三', phone: '15955040222', sex: '男', isTouchMove: false, }, { id: 4, name: '张三', phone: '15955040222', sex: '男', isTouchMove: false, }, { id: 5, name: '张三', phone: '15955040222', sex: '男', isTouchMove: false, }, { id: 6, name: '张三', phone: '15955040222', sex: '男', isTouchMove: false, }, { id: 7, name: '张三', phone: '15955040222', sex: '男', isTouchMove: false, }, ] }, touchstart: function (e) { //开始触摸时 重置所有删除 let data = App.touch._touchstart(e, this.data.list) //将修改过的list setData this.setData({ list: data }) }, //滑动事件处理 touchmove: function (e) { let data = App.touch._touchmove(e, this.data.list,'id')//将修改过的list setData this.setData({ list: data }) },})对于滑动事件的处理专门封装了一个.js文件,防止以后还会用到。var startXvar startYclass touch { constructor() { } _touchstart(e, items) { //开始触摸时 重置所有删除 items.forEach(function (v, i) { if (v.isTouchMove) //只操作为true的 v.isTouchMove = false; }) startX = e.changedTouches[0].clientX startY = e.changedTouches[0].clientY return items } _touchmove(e, items,key) { const id = e.currentTarget.dataset.id, //获取列表中每一项的唯一值,可以取id touchMoveX = e.changedTouches[0].clientX, //滑动变化坐标 touchMoveY = e.changedTouches[0].clientY, //滑动变化坐标 //获取滑动角度 angle = this._angle({ X: startX, Y: startY }, { X: touchMoveX, Y: touchMoveY }); items.forEach(function (v, i) { v.isTouchMove = false //滑动超过30度角 return if (Math.abs(angle) > 30) return; if (v[key] == id) { //判断滑动的id与列表中的id是否一致,如果是的话,改变滑动这一项的isTouchMove属性 if (touchMoveX > startX) //右滑 v.isTouchMove = false else //左滑 v.isTouchMove = true } }) return items } _angle(start, end) { var _X = end.X - start.X, _Y = end.Y - start.Y //返回角度 /Math.atan()返回数字的反正切值 return 360 * Math.atan(_Y / _X) / (2 * Math.PI); }}export default touch然后去引用这个touch.js文件,在app.js文件中//app.jsimport touch from './utils/touch.js'//新加App({ globalData: { userInfo: null }, touch: new touch() //实例化这个touch对象})末尾到这里左滑删除就告一段落了,主要就是先使用css将删除按钮隐藏起来,然后通过监听touch事件去改变列表中每一项的一个属性,间接修改这个条目的样式将删除按钮显示出来源码上传至github 微信小程序之列表左滑删除功能原文地址 微信小程序之列表左滑删除功能

July 1, 2019 · 2 min · jiezi

开发了一个微信群付费进群功能

准备本案例使用的支付接口是PayJs微信支付商户,没有商户号可以自己申请:https://payjs.cn/ref/DNXBJD 演示 1分钱体验 流程1、写出进群界面2、对接PayJs的JSAPI支付接口3、支付成功后跳转进群二维码 代码jiaqun-login.php这个代码主要是授权登录,获取用户的openid <!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0,viewport-fit=cover"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="format-detection" content="telephone=no"> <link rel="stylesheet" href="https://weui.io/weui.css"/> <link rel="stylesheet" href="https://weui.io/example.css"/> <title>正在登录</title></head><body></body></html><?phpheader("Content-type:text/html;charset=utf8");//发送请求$url = 'https://payjs.cn/api/openid?callback_url=你的域名/你的支付目录/jiaqun-getopenid.php';function post($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); $rst = curl_exec($ch); curl_close($ch); return $rst;}//发送$result = post($url);//返回接口的数据echo "<p style=\"display:none;\">".$result."</p>";?>jiaqun-getopenid.php这个主要是把获取到的openid传到支付订单页面 <?phpheader("Content-type:text/html;charset=utf8");$openid = $_GET["openid"];//浏览器跳转echo "<script>location.href=\"你的域名/你的支付目录/jiaqun-pay.php?openid=$openid\";</script>";?><title>请稍候</title>jiaqun-pay.php这个主要是构建订单发起请求参数,创建支付订单 <?php//引入配置文件require_once("config.php");//获得OPENID$openid = $_GET["openid"];//定义金额$total_fee = 1;$length = strlen($total_fee);if ($length == 1) { $money = "0.0".$total_fee;}else if ($length == 2) { $money = "0.".substr($total_fee, 0,1).substr($total_fee, 1,1);}else if ($length == 3) { $money = substr($total_fee, 0,1).".".substr($total_fee, 1,1).substr($total_fee, 2,3);}else if ($length == 4) { $money = substr($total_fee, 0,1).substr($total_fee, 1,1).".".substr($total_fee, 2,3).substr($total_fee, 4,4);}else if ($length == 5) { $money = substr($total_fee, 0,1).substr($total_fee, 1,1).substr($total_fee, 2,1).".".substr($total_fee, 3,2);}// 构造订单参数$data = [ 'mchid' => $mchid, 'body' => '付费进群', 'total_fee' => $total_fee, 'openid' => $openid, 'out_trade_no' => 'likeyunkeji.' . time(),];// 添加数据签名$data['sign'] = sign($data, $key);//发送请求$url = 'https://payjs.cn/api/jsapi?' . http_build_query($data);function post($data, $url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); $rst = curl_exec($ch); curl_close($ch); return $rst;}//发送$result = post($data, $url);//返回接口的数据$results = json_decode($result);//返回字符串$appId = $results->jsapi->appId;$timeStamp = $results->jsapi->timeStamp;$nonceStr = $results->jsapi->nonceStr;$package = $results->jsapi->package;$signType = $results->jsapi->signType;$paySign = $results->jsapi->paySign;// 获取签名function sign($data, $key){ array_filter($data); ksort($data); return strtoupper(md5(urldecode(http_build_query($data) . '&key=' . $key)));}?><html><head> <title>群聊邀请</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="email=no"> <meta name="apple-mobile-web-app-capable" content="yes"> <link href="https://cdn.bootcss.com/weui/1.1.2/style/weui.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/zepto/1.2.0/zepto.min.js"></script> <link rel="stylesheet" type="text/css" href="css/style.css"></head><body> <!-- 顶部区域 --> <div id="logo_con"> <div class="logoimg"><img src="css/qunlogo.png"/></div> <p class="qunname">里客云科技</p> <p class="qunrenshu">100人</p> </div> <!-- 空白部分 --> <p id="yqjrql">TANKING邀请你加入群聊</p> <!-- 支付按钮 --> <a href="javascript:;" id="jrql">加入群聊</a> <!-- 进群说明 --> <p id="jqsm">1. 该群聊人数较多,为减少新消息给你带来的打扰,建议谨慎加入。 <p id="jqsm">2. 你需要实名验证后才能接受邀请,可绑定银行卡进行验证。</p></body><script> if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } } function onBridgeReady() { WeixinJSBridge.call('hideOptionMenu'); } $('#jrql').on('click', function () { WeixinJSBridge.invoke( //构造发起支付参数,请在接口服务端生成 'getBrandWCPayRequest', { "appId": "<?php echo $appId ?>", "timeStamp": "<?php echo $timeStamp ?>", "nonceStr": "<?php echo $nonceStr ?>", "package": "<?php echo $package ?>", "signType": "<?php echo $signType ?>", "paySign": "<?php echo $paySign ?>" }, function (res) { if (res.err_msg == "get_brand_wcpay_request:ok") { // 支付成功后跳转页面 location.href="https://www.liketube.cn/payjs/css/wxqunqrcode.jpg"; } } ); });</script></html>config.php支付接口的一些参数 ...

June 29, 2019 · 3 min · jiezi

监控微信小程序中的慢HTTP请求

摘要: 请求时间太长,影响用户体验,使用 Fundebug 监控慢请求。 Fundebug 的微信小程序监控插件在 0.5.0 版本已经支持监控 HTTP 请求错误,在小程序中通过wx.request发起 HTTP 请求,如果请求失败,会被捕获并上报。时隔一年,微信小程序插件已经更新到 1.3.1, 而且提供了一个非常有用的功能,支持监控 HTTP 慢请求。对于轻量级的性能分析,可以说已经够用。 本文我们以一个天气微信小程序为例(由bodekjan开发),来演示如何监控慢请求。bmap-wx.js中的weather()函数调用百度地图小程序 api 提供的接口来获取天气预报信息。 接入监控由于使用百度的 api,我们无法确认该接口的稳定性,可能有时候会特别慢,导致天气信息显示不出来。于是,我们使用 Fundebug 来监控请求过慢的情况。接下来,我们来演示如何监控慢请求。注册账户后,记得要在创建项目是选择“微信小程序”这一项目类型。 根据指示完成接入流程: 在app.js顶部加入下面的代码(记得将 apikey 替换成你自己的): var fundebug = require("./utils/fundebug.1.3.1.min.js");fundebug.init({ apikey: "YOUR-API-KEY", monitorMethodCall: true, monitorMethodArguments: true, monitorHttpData: true, setSystemInfo: true, setUserInfo: true, setLocation: true, httpTimeout: 200});虽然init()函数只要设置apikey即可使用,但是为了最大程度发挥监控的威力,我们不妨多设置一些监控选项。微信小程序插件有很多的可配置项,由于涉及到数据,默认处于关闭状态。我们可以监控函数调用(monitorMethodCall),以及函数调用的参数(monitorMethodArguments),监控 HTTP 请求的 Body 中的数据(monitorHttpData),获取系统信息(setSystemInfo)、用户信息(setUserInfo)、地理位置(setLocation)。 监控慢请求最后,最重要的一步,配置httpTimeout来监控超过特定时长的请求,httpTimeout 类型为 Number,单位为毫秒(ms)。演示起见,我们将时间设置为 200 毫秒。 在微信开发者工具内运行代码,Fundebug 立马收到报错。小程序发往https://api.map.baidu.com/telematics/v3/weather接口的请求时长为 571ms,超过预设时间 200ms。 错误详情该请求返回代码 200,表明能够正常获取数据。点击该条错误,查看错误详情: ...

June 27, 2019 · 1 min · jiezi

Yii使用easywechat-实现授权登录

在微信端当我们需要获取到用户的基本信息的时候,我们这时候需要使用到微信的授权登录,这里我来记录下在Yii框架中使用easywechat 实现授权登录 一:前提: 首先我们需要easywechat在Yii中的拓展和进行easywechat配置,具体实现可以参考:https://www.wj0511.com/site/d... 二:实现授权登录 实现代码如下: if (!Yii::$app->wechat->isWechat) { //判断是否使用微信客户端访问}//授权登录if (!Yii::$app->wechat->isAuthorized()) { return Yii::$app->wechat->authorizeRequired()->send();}//获取授权登录的用户信息$user = Yii::$app->wechat->user;根据如上代码就可以实现授权登录实现获取用户信息

June 12, 2019 · 1 min · jiezi

php实现微信公众号创建自定义菜单

目的创建自定义菜单,实现菜单事件。 首先获取Access_Token接口: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET我用的是测试号,修改APPID和APPSECRET,然后浏览器访问上面这个Url即可生成Access_Token 然后配置菜单的事件,caidan.php<?phpheader("Content-type: text/html; charset=utf-8");define("ACCESS_TOKEN", "生成的Access_Token");//创建菜单function createMenu($data){$ch = curl_init();curl_setopt($ch, CURLOPT_URL, "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=".ACCESS_TOKEN);curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)');curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);curl_setopt($ch, CURLOPT_AUTOREFERER, 1);curl_setopt($ch, CURLOPT_POSTFIELDS, $data);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);$tmpInfo = curl_exec($ch);if (curl_errno($ch)) { return curl_error($ch);}curl_close($ch);return $tmpInfo;}//获取菜单function getMenu(){return file_get_contents("https://api.weixin.qq.com/cgi-bin/menu/get?access_token=".ACCESS_TOKEN);}//删除菜单function deleteMenu(){return file_get_contents("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=".ACCESS_TOKEN);}$data = '{ "button":[ { "type":"click", "name":"首页", "key":"home" }, { "type":"click", "name":"简介", "key":"introduct" }, { "name":"菜单", "sub_button":[ { "type":"click", "name":"hello word", "key":"V1001_HELLO_WORLD" }, { "type":"click", "name":"赞一下我们", "key":"V1001_GOOD" }] }]}';echo createMenu($data);浏览器访问caidan.php正确时的返回JSON数据包如下: ...

June 10, 2019 · 1 min · jiezi

在本地测试微信登录

在做微信登录的时候,希望能在本地能够测试登录。这里使用的是微信开放平台的网站应用微信登录, 与微信公众平台不同,微信开放平台并没有提供测试号的服务,因此在调试起来会十分的麻烦。比较传统的方法就是部署上测试服务器上专门先测试这一个接口。但从流程上来说,会比较麻烦,不够高效。下文将分享另一种测试的过程。 代码编写首先在页面引入微信的js文件: <script src="//res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>然后在登录页上实例化登录对象: <!-- login.vuw --><template> <div class="container"> <div id="login_container"></div> </div></template><script>// other code ...export default { // other code ... mounted() { APIs.login({ redirect_uri: Base64.encode('http://apitest.anran758.com') }).then(res => { /* eslint-disable no-new */ new WxLogin({ id: 'login_container', // appid: "", // scope: "", // redirect_uri: "", // state: "", // style: "", // href: "" ...res.data }); }) },};</script>WxLogin接收一个对象,对象属性如下所示。其中对象里的appid, scope,redirect_uri, state四个属性是由后端控制返回的。 参数是否必须说明self_redirect否true:手机点击确认登录后可以在 iframe 内跳转到 redirect_uri,false:手机点击确认登录后可以在 top window 跳转到 redirect_uri。默认为 false。id是第三方页面显示二维码的容器idappid是应用唯一标识,在微信开放平台提交应用审核通过后获得scope是应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可redirect_uri是重定向地址,需要进行UrlEncodestate否用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验style否提供"black"、"white"可选,默认为黑色文字描述。详见文档底部FAQhref否自定义样式链接,第三方可根据实际需求覆盖默认样式。详见文档底部FAQ该方法会生成一个二维码,并挂载到指定的容器 ID 上。用户扫描二维码时,页面会向微信服务端发送一个请求等待用户确认,用户确认完成后,就会将页面重定向至指定的路径(redirect_uri)。 ...

May 19, 2019 · 1 min · jiezi

微信获取用户地理位置经纬度和百度获取实际地址的经纬度之间相差较大解决

前提了解: 坐标系分类(经纬度): WGS84美国GPS,国际通用,如谷歌国外地图、osm地图 火星系GCJ-02国测局制定的标准,国内地图必须至少使用此对位置进行首次加密,高德地图、腾讯搜搜地图、阿里云地图、灵图51ditu地图、谷歌中国地图 BD-09百度在GCJ-02标准基础上进行二次加密 ,百度地图 这两天一直在研究经纬度的问题,最后发现在微信上获取用户的地理位置(经纬度)和在百度上获取实际地址的经纬度之间的距离相差较大,整整相差了5000多米的距离,这到底是怎么回事呢?最后发现原来微信端获取的经纬度使用的是WGS84(大地坐标系),然而在百度上获取的经纬度使用的是百度自己定义的BD-09(百度坐标系),百度对外接口的坐标系并不是GPS采集的真实经纬度,所以导致了两个经纬度之间的差距过大,那么解决方式是什么呢? 既然两个经纬度的坐标系标准不同,那么将其中一个坐标系的转换成另一个坐标系类型不久可以了吗?根据这个思路解决方法如下: GPS84转换为BD-09: 在百度api中,提供了将WGS84(大地坐标系)和GCJ02(国测局坐标系)转化为BD-09(百度坐标系)的接口 百度坐标转换api地址:http://lbsyun.baidu.com/index... 百度坐标转换接口地址如下: http://api.map.baidu.com/geoc... 参数说明: coords需转换的源坐标,多组坐标以“;”分隔,(经度,纬度) ak开发者密钥 from源坐标类型:1:GPS设备获取的角度坐标,WGS84坐标; 2:GPS获取的米制坐标、sogou地图所用坐标; 3:google地图、soso地图、aliyun地图、mapabc地图和amap地图所用坐标,国测局(GCJ02)坐标; 4:3中列表地图坐标对应的米制坐标; 5:百度地图采用的经纬度坐标; 6:百度地图采用的米制坐标; 7:mapbar地图坐标; 8:51地图坐标 to目标坐标类型:5:bd09ll(百度经纬度坐标);6:bd09mc(百度米制经纬度坐标) 根据如上就可以实现将我们微信端获取的经纬度转化成百度自己加密过后的经纬度 当时如何将BD-09转换成GPS84百度没有提供接口,具体如何转换目前不清楚,有知道的,欢迎评论,谢谢

May 15, 2019 · 1 min · jiezi

微信JSSDK获取用户地理位置经纬度

最近一直在做公众号开发,这两天公司让我做一个类似钉钉打卡的功能,这时候我需要获取到用户的经纬度, 但是在这之前我只知道在关注微信公众号的时候获取用户地理位置:https://mp.weixin.qq.com/wiki... 但是这时候在我这里无法使用,我需要在微信公众号的网页中获取用户的地理位置(经纬度) 最后发现,原来微信公众号提供了现成的获取用户地理位置的JS-SDK:https://mp.weixin.qq.com/wiki... 一:绑定域名 登录微信公众号绑定JS接口安全域名(这里我使用的是测试号) 二:引入JS文件 在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/... 如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js... (支持https)。 三:通过config接口注入权限验证配置 wx.config(<?php echo Yii::$app->wechat->js->config([ 'getLocation', 'openLocation' ]) ?> );'getLocation','openLocation'表示的是需要使用的JS接口列表,所有JS接口列表参考上面JS-JDK文档中的附录二 这里我是在Yii框架中使用的是easywechat,原生写法可以参考上面的JS-SDK文档 三:获取用户的地理位置(经纬度) 查看文档会找到如上代码,这时候你将代码直接赋复制执行的话,这时候获取地理位置的接口时不会触发的,我们必须要将回去地理位置的接口放到wx.ready方法内部,如: wx.ready(function(){ // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。 wx.getLocation({ type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02' success: function (res) { var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90 var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。 var speed = res.speed; // 速度,以米/每秒计 var accuracy = res.accuracy; // 位置精度 } }); });这时候我们就可以获取到用户的地理位置信息了 ...

May 15, 2019 · 1 min · jiezi

微信网页登录逻辑与实现

现在的网站开发,都绕不开微信登录(毕竟微信已经成为国民工具)。虽然文档已经写得很详细,但是对于没有经验的开发者还是容易踩坑。 所以,专门记录一下微信网页认证的交互逻辑,也方便自己日后回查: 加载微信网页sdk绘制登陆二维码:新tab页面绘制 / 本页面iframe绘制用户扫码登陆,前端跳入回调网址回调网址进一步做逻辑处理,如果是页内iframe绘制二维码,需要通知顶级页????阅读更多系列文章 / 阅读原文???? 微信网页SDK加载在多人团队协作中,加载资源的代码需要格外小心。因为可能会有多个开发者在同一业务逻辑下调用,这会造成资源的重复加载。 处理方法有两种,第一种是对外暴露多余接口,专门check是否重复加载。但是考虑到调用者每次在加载前,都需要显式调用check()方法进行检查,难免会有遗漏。 所以采用第二种方法--设计模式中的缓存模式,代码如下: // 备忘录模式: 防止重复加载export const loadWeChatJs = (() => { let exists = false; // 打点 const src = '//res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'; // 微信sdk网址 return () => new Promise((resolve, reject) => { // 防止重复加载 if(exists) return resolve(window.WxLogin); let script = document.createElement('script'); script.src = src; script.type = 'text/javascript'; script.onerror = reject; // TODO: 失败时候, 可以移除script标签 script.onload = () => { exists = true; resolve(window.WxLogin); }; document.body.appendChild(script); });})();绘制登陆二维码根据《微信登陆开发指南》,将参数传递给window.WxLogin()即可。 ...

April 29, 2019 · 2 min · jiezi

微信小程序-unionid-登录解决方案

第三方登录模块使开发者能快捷灵活的拥有自己的用户系统,是 LeanCloud 最受欢迎的功能之一。随着第三方平台的演化,特别是微信小程序的流行,LeanCloud 第三方登录模块也一直在改进: v2.0*:增加微信小程序一键登录功能。支持开发者不写任何后端代码实现微信小程序用户系统与 LeanCloud 用户系统的关联。v3.6:增加 unionid 登录接口。支持开发者使用 unionid 关联一个微信开发者帐号下的多个应用从而共享一套 LeanCloud 用户系统。这两个功能各自都非常简单可靠,但是其中重叠的部分需求却是一个难题:「如何在小程序中支持 unionid 登录,既能得到 unionid 登录机制的灵活性,又保留一键登录功能的便利性」。 在最近发布的 JavaScript SDK v3.13 中包含了微信小程序 unionid 登录支持。我们根据不同的需求设计了不同的解决方案。 *这里的版本指开始支持该功能的 JavaScript SDK 版本。 一键登录LeanCloud 的用户系统支持一键使用微信用户身份登录。要使用一键登录功能,需要先设置小程序的 AppID 与 AppSecret: 1.登录 微信公众平台,在 设置 > 开发设置 中获得 AppID 与 AppSecret。前往 LeanCloud 控制台 > 组件 > 社交,保存「微信小程序」的 AppID 与 AppSecret。这样你就可以在应用中使用 AV.User.loginWithWeapp() 方法来使用当前用户身份登录了。 AV.User.loginWithWeapp().then(user => { this.globalData.user = user;}).catch(console.error);使用一键登录方式登录时,LeanCloud 会将该用户的小程序 openid 与 session_key 等信息保存在对应的 user.authData.lc_weapp 属性中,你可以在控制台的 _User 表中看到: ...

April 26, 2019 · 3 min · jiezi

模版消息智能推送!我们教你发得更快更多更省

「模板消息」能力,几乎是小程序触达用户的唯一渠道。有了它,运营人员才有足够的发挥空间,来提高用户的留存,转化。微信为了防止有人用模板消息频繁骚扰用户,推出了一套严格的监督机制。一旦发现有人滥用,立刻对模板消息进行封禁处理,这将直接影响线上业务。然而受人为因素影响,模板消息的审查有很大的不确定性,运营者即使遵照微信运营规范执行也有可能被误伤封禁。以下是模版消息被封的一些常见原因。模版消息被封的常见原因:同一时间段大批量发送,被系统判定为行为异常。推送内容违规或推送内容与用户需求不符,导致用户举报。推送次数过于频繁,一天内向同一个用户推送多次模板消息,导致用户举报。简单总结,主要还是因为模板消息推给了不该推的人,被人当垃圾邮件一样处理,自然容易被封。「知晓推送」就针对以上场景做了功能优化,其新推出的智能推送服务可以助力小程序高效匹配目标用户,极大的降低模板消息被封的概率,为小程序业务的稳定运行再上一层保险。除此之外,「知晓推送」还解决了你的难题:运营灵活性差,每次推送内容变更都得通过修改代码来实现。formid 限制太大,收集麻烦,每一次业务变更都需要重新埋点。推送成本高,后端服务需要单独开发,还不能保证消息推送的稳定性与效率。「知晓推送」提前尝鲜5 月 8 日,我们将正式推出「知晓推送」服务,帮助运营者处理好粉丝转化、消息推送、数据分析等多个层面的麻烦事,真正解决复购难、留存差的问题。四大核心功能助力实现小程序用户的高效转化:1.在线发送完全可视化操作,提前预览发送效果。让模板消息推送变得和发微博一样简单。使用在线推送服务,运营人员不用在对着代码一脸懵逼了。2.自动收集 formid 组件让 formid 不足导致无法推送模板消息的状况成为过去式,两行代码即可完成接入,业务变更也无需重复埋点。3.智能推送支持根据自定义规则过滤出与小程序匹配的目标用户,同时可以设置同一批模板消息的推送次数与单次发送数量,避免骚扰用户,实现精准推送,高效转化。4.便宜限时福利大放送「知晓推送」正式上线前完全免费,服务上线当天还有额外福利。5.8 日前注册的用户都可获赠价值不少于 198 元的资源包(详询,微信:minsupport3)哦!抓紧时间注册吧~

April 19, 2019 · 1 min · jiezi

PC端解析微信发送过来的emoji和在光标处插入emoji

最近公司的一个需求,需要在PC端接收并展示微信发送的消息,那么如何解析微信发送过来的表情?如何在编辑框光标出插入表情?本文将会详细的介绍如何解决这两个问题。一、PC端解析微信发送过来的emoji首先,我们知道,emoji的展示实际是图片的展示,input、textarea是没法展示图片的,所以我们用div里contenteditable=“true"属性,即可编辑。其次我们要了解,微信发送一个表情过来,后台数据库接收到的是/::-O这种特殊字符串,当然,如果是[微笑]这种字符串处理方式也一样,对照表可以参考微信默认表情代码和图片包,下载里面的图标到本地,并处理成如下格式页面展示表情,只需要遍历一下emoji数组,拼接一下图片的url即可。当要处理某个字符串里面的emoji时候,可遍历emoji数组,将后台接收的特殊字符串通过replace接口全局替换成对应的图片,反之,同样的方法将图片转化成微信可以解析成emoji的特殊字符,下面即是转换的函数 // 将特殊符号转成对应表情 config.imgUrl图片存放的url imgChangeEmoji(str) { emoji.forEach(element => { if (str && str.indexOf(element.code) > -1) { const effectCode = element.code.replace(/[.\[]{}()|^$?+]/g, ‘\$&’); // 转义字符串中的元字符 const pattern = new RegExp(effectCode, ‘g’); const imgUrl = &lt;img src='${config.imgUrl}/${element.img}'&gt;; str = str.replace(pattern, imgUrl); } }); return str; } // 将对话中的表情图片替换成特殊字符 config.imgUrl图片存放的url emojiChangeImg(str) { emoji.map(element => { if (str && str.indexOf(element.img) > -1) { const imgUrl = &lt;img src="${config.imgUrl}/${element.img}"&gt;; const pattern = new RegExp(imgUrl, ‘g’); str = str.replace(pattern, element.code); } }); return str; }需要特别注意的是,将特殊字符转成RegExp的时候,必须先用replace进行转义,即const effectCode = element.code.replace(/[.[]{}()|^$?+]/g, ‘\$&’); 手写转义是无效的。有了上面的解析函数,你只需在发送消息前执行emojiChangeImg(str)函数即可,同样的,如果想展示接收回来的消息,可以写个指令运行imgChangeEmoji(str)函数。二、光标处插入emoji在div添加keyup和click事件,如下代码(此处用的是angualr6) <div #editBox contenteditable=“true” (keyup)=“handleInputChange()” (click)=“handleClick()"></div>在handleInputChange()和handleClick()和函数里面设置最后光标对象 // 获取选定对象 const selection = getSelection(); // 设置最后光标对象 this.lastEditRange = selection.getRangeAt(0);选择emoji图片时创建一个img节点,并插入到最后光标位置,如下代码const img = new Image();img.src = ${config.imgUrl}/${emoji.img};const selection = getSelection();if (this.lastEditRange) { // 存在最后光标对象,选定对象清除所有光标并添加最后光标还原之前的状态 selection.removeAllRanges(); selection.addRange(this.lastEditRange);}// 选择第一选区const range = selection.getRangeAt(0);range.insertNode(img);range.collapse(false);即可在光标处插入img。想了解更多光标对象可参考html元素contenteditable属性如何定位光标和设置光标 ...

April 18, 2019 · 1 min · jiezi

微信消息管理之被动回复用户消息

当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。首先处理用户发过来的消息,然后根据消息服务器做出相应的响应。public function responseMsg(){ //get post data, May be due to the different environments $postStr = $GLOBALS[“HTTP_RAW_POST_DATA”]; //用户端发来的数据 //if (!empty($postStr)){ /* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection, the best way is to check the validity of xml by yourself */ libxml_disable_entity_loader(true); //通过simplexml进行数据解析,转换为对象 $postObj = simplexml_load_string($postStr, ‘SimpleXMLElement’, LIBXML_NOCDATA); switch($postObj->MsgType){ //用户发送的消息类型 case “event”: $this->_doEvent($postObj); break; case “text”: $this->_doText($postObj); break; case “image”: $this->_doImage($postObj); break; case “voice”: $this->_doVoice($postObj); break; case “music”: $this->_doMusic($postObj); break; case “location”: $this->_doLocation($postObj); break; default: break; } }1.文本消息//文本消息private function _doText($postObj){ $fromUsername = $postObj->FromUserName; $toUsername = $postObj->ToUserName; $keyword = trim($postObj->Content); $time = time(); $textTpl = “<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Content><![CDATA[%s]]></Content> <FuncFlag>0</FuncFlag> </xml>”; $msgType = “text”; switch($keyword){ case ‘初音科技’: $contentStr = “提供一流的技术支持!!”; break; case ‘PHP’: $contentStr = “最美的语言!!”; break; case ‘图文’: $newsArr = array( array( ‘Title’=>‘初音科技,专为高端网络定制’, ‘Description’=>‘初音科技,做一个品牌影响一个行业!’, ‘PicUrl’=>‘http://edu.zbxiaochengxu.com/o_1b9pjfv6i1111gp69pp1m45sd7q.jpg', ‘Url’=>‘http://mp.weixin.qq.com/s?__biz=MzA4NTkzMzI0Ng==&mid=2652036751&idx=2&sn=641c21e3831c094cdaf40e115c9eaee6&scene=0#wechat_redirect', ), array( ‘Title’=>‘初音科技,专为高端网络定制’, ‘Description’=>‘初音科技,做一个品牌影响一个行业!’, ‘PicUrl’=>‘http://qfenxiang.chuyin.net.cn/weixinopen/images/0.jpg', ‘Url’=>‘http://mp.weixin.qq.com/s?__biz=MzA4NTkzMzI0Ng==&mid=2652036751&idx=2&sn=641c21e3831c094cdaf40e115c9eaee6&scene=0#wechat_redirect', ) ); $this->_replayNews($postObj,$newsArr); break; default: $contentStr = “启军测试微信公众号响应!!”; break; } $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr); echo $resultStr; }2.图片消息//回复图片消息private function _doImage($postObj,$mediaId){ //$contentStr = “您发送的是图片信息!消息类型为:”.$postObj->PicUrl."\nMediaId为:".$postObj->MediaId; if( $postObj->MsgType == ‘image’){ $mediaId = $postObj->MediaId; } $fromUsername = $postObj->FromUserName; $toUsername = $postObj->ToUserName; $time = time(); $imageTpl = “<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Image> <MediaId><![CDATA[%s]]></MediaId> </Image> </xml>”; $msgType = “image”; //$this->_replayText($postObj,$contentStr); $resultStr = sprintf($imageTpl,$fromUsername,$toUsername,$time,$msgType,$mediaId); echo $resultStr;}3.语音消息//回复语音消息private function _doVoice($postObj,$mediaId){ if( $postObj->MsgType == ‘voice’){ //$mediaId = $postObj->MediaId; $contentStr = “您发送的是语音信息!\n你说的是:”.$postObj->Recognition; $this->_replayText($postObj,$contentStr); exit(); } $fromUsername = $postObj->FromUserName; $toUsername = $postObj->ToUserName; $time = time(); $voiceTpl = “<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Voice> <MediaId><![CDATA[%s]]></MediaId> </Voice> </xml>”; $msgType = “voice”; $resultStr = sprintf($voiceTpl,$fromUsername,$toUsername,$time,$msgType,$mediaId); echo $resultStr; }4.音乐消息//音乐消息private function _doMusic($postObj){$fromUsername = $postObj->FromUserName;$toUsername = $postObj->ToUserName;$time = time();$musicTpl = “<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <Music> <Title><![CDATA[%s]]></Title> <Description><![CDATA[%s]]></Description> <MusicUrl><![CDATA[%s]]></MusicUrl> <HQMusicUrl><![CDATA[%s]]></HQMusicUrl> </Music> </xml>”; $msgType = “music”;$title = “还魂门”;$desc = “电视剧<老九门>主题曲”;$url = “http://qfenxiang.chuyin.net.cn/weixinopen/musics/music.mp3";$hqurl = “http://qfenxiang.chuyin.net.cn/weixinopen/musics/music.mp3"; $resultStr = sprintf($musicTpl,$fromUsername,$toUsername,$time,$msgType,$title,$desc,$url,$hqurl,$contentStr);echo $resultStr;}5.图文消息//[封装]图文回复private function _replayNews($postObj,$newsArr){ if( !is_array($newsArr) || sizeof($newsArr)<1 ){ $newsArr = array( array( ‘Title’=>‘全民创业蒙的就是你,来一盆冷水吧!’, ‘Description’=>‘全民创业已经如火如荼,然而创业是一个非常自我的过程,它是一种生活方式的选择。从外部的推动有助于提高创业的存活率,但是未必能够提高创新的成功率。’, ‘PicUrl’=>‘http://yun.topthink.com/Uploads/Editor/2015-07-30/55b991cad4c48.jpg', ‘Url’=>‘http://www.topthink.com/topic/11991.html', ) ); } $itemStr = ‘’; foreach($newsArr as $item){ $newsData = ‘<item> <Title><![CDATA[%s]]></Title> <Description><![CDATA[%s]]></Description> <PicUrl><![CDATA[%s]]></PicUrl> <Url><![CDATA[%s]]></Url> </item>’; $itemStr .= sprintf($newsData,$item[‘Title’],$item[‘Description’],$item[‘PicUrl’],$item[‘Url’]); } $fromUsername = $postObj->FromUserName; $toUsername = $postObj->ToUserName; $time = time(); $newsTpl = ‘<xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[%s]]></MsgType> <ArticleCount>%s</ArticleCount> <Articles>’.$itemStr.’</Articles> </xml>’; $msgType = ’news’; $count = count($newsArr); $resultStr = sprintf($newsTpl,$fromUsername,$toUsername,$time,$msgType,$count,$str); echo $resultStr; } ...

April 17, 2019 · 2 min · jiezi

连小白都能看懂的微信开发之 微信自定义菜单 + 获取网页授权 + 获取用户信息

微信自定义菜单+获取网页授权+获取用户信息今天项目需要一个需求,就是添加一个菜单接口,并且还可以获取用于的信息,从而根据用户的信息去做一些业务的查询。通过百度和自己查看文档大致的解决办法如下:注意在自定义自己菜单前,若跳转自己服务器里面的url时候,首先得设置自己点击菜单时的回调域名不能以http或者https开头必须是www.xx.com类似于这样子的字符串形式1 首先来自定义菜单创建接口按照文档的要求一步一步来:1.1 首选是自定义接口的一些限制和说明,这里就不多说了,看文档就可以明白。这直接看文档即可 https://mp.weixin.qq.com/wiki...1.2 然后是发送接口请求,自定义自己的菜单。接口调用请求说明http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi…详情看文档即可,没什么好说的。发送请求和json即可。本人的请求如下:url : https://api.weixin.qq.com/cgi… json数据{ “menu”: { “button”: [ { “type”: “scancode_push”, “name”: “扫1111111”, “key”: “rselfmenu_0_1”, “sub_button”: [] }, { “type”: “view”, “name”: “我的1111111”, “url”: “http://www.xxxxx.com/cc”, “sub_button”: [] } ] }}注意:不要直接复制官网的json数据,应当去掉 “menu”: 这一层json , 可能会出现 以下错误:{ “errcode”: 40016, “errmsg”: “invalid button size hint: [hTpqGa02101977]"}成功返回: {“errcode”:0,“errmsg”:“ok”}错误时的返回JSON数据包如下(示例为无效菜单名长度): {“errcode”:40018,“errmsg”:“invalid button name size”} 或者其他错误等。2 自定义菜单查询接口请求说明http请求方式:GEThttps://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN返回说明(无个性化菜单时)菜单界面3 自定义菜单删除接口https://mp.weixin.qq.com/wiki…4 controller@GetMapping(“cc”)public String cc(HttpServletRequest request) throws IOException { return “suceess”; //我自己的页面}启动运行但是我们这样子仅仅只是单独的挑了个页面无法获取到用户的一些信息,如debug所示!无法获取到code所以,在跳转第三方网页的时候需要授权后才能获取到用户的一些基本信息。 获取用户信息的前提是网页授权这里没有进行网页授权,从而获取code值也为null5 微信网页授权利用微信网页授权机制来进行重定向到自定义的一个url如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。关于网页授权回调域名的说明1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面<http://www.qq.com/music.html> 、 <http://www.qq.com/login.html> 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com无法进行OAuth2.0鉴权3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可关于网页授权的两种scope的区别说明1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。3、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。然后根据自己的场景选择授权并且获取用户的信息关于特殊场景下的静默授权1、上面已经提到,对于以snsapi_base为scope的网页授权,就静默授权的,用户无感知;2、对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。具体而言,网页授权流程分为四步:1、引导用户进入授权页面同意授权,获取code2、通过code换取网页授权access_token(与基础支持中的access_token不同)3、如果需要,开发者可以刷新网页授权access_token,避免过期4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)具体代码以及步骤如下:#### 第一步 点击菜单的url直接重定向到下面这个url即可,目的 “引导用户进入授权页面同意授权,获取code ”https://open.weixin.qq.com/co… + “自己的appid” + &redirect_uri= +“ 自己的url ”+ &response_type=code&scope=snsapi_base&state=1#wechat_redirect”@GetMapping(“cc”)public String cc(HttpServletRequest request) throws IOException { String code = request.getParameter(“code”); return “redirect:/https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxx&redirect_uri=http://www.xxx.com/aaa&response_type=code&scope=snsapi_base&state=1#wechat_redirect”;}#### 第二步获取到code之后,就可以根据获取用户信息(这里详细见微信网页授权四步骤文档)然后在controller 根据自己的需求执行其他逻辑。@GetMapping(“aaa”)//重定向的url 也就是上面controller重定向的微信url里面重定向自己的url(http://www.xxx.com/aaa)public String aa(HttpServletRequest request) throws IOException { String code = request.getParameter(“code”); return “success”;}debug 结果 #### 第三步获取到授权后code,然后可以根据 网页授权(https://mp.weixin.qq.com/wiki…)第2.3.4步步骤获取用户的信息,从而自己的实现业务逻辑。注:另外看到网上一部分大佬们直接将 将链接的url直接作为微信自定义菜单中view类型中的url 中。结果我在postman中发了一次请求,直接报url长度错误,所以放弃了这种方案!想了解更多请关注微信公众号 ...

April 8, 2019 · 1 min · jiezi

连小白都能看懂的微信开发之自定义菜单以及自定义菜单推送事件

微信开发之自定义菜单以及自定义菜单推送事件自定义菜单创建接口微信提供2种机制生成菜单机制一是在公众平台官网通过网站功能发布菜单 ,这种方式接入后台服务器之后菜单会失效。机制二是通过API调用设置的菜单微信官方文档是这样子描述:1、自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。2、一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“…”代替。3、创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。自定义菜单接口类型按钮,如下10种:1、click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;2、view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。3、scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。5、pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。6、pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。7、pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。8、location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。9、media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。10、view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。请注意,3到8的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。9和10,是专门给第三方平台旗下未微信认证(具体而言,是资质认证未通过)的订阅号准备的事件类型,它们是没有事件推送的,能力相对受限,其他类型的公众号不必使用。接口调用请求说明http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi...click和view的请求示例 { “button”:[ { “type”:“click”, “name”:“今日歌曲”, “key”:“V1001_TODAY_MUSIC” }, { “name”:“菜单”, “sub_button”:[ { “type”:“view”, “name”:“搜索”, “url”:“http://www.soso.com/” }, { “type”:“miniprogram”, “name”:“wxa”, “url”:“http://mp.weixin.qq.com”, “appid”:“wx286b93c14bbf93aa”, “pagepath”:“pages/lunar/index” }, { “type”:“click”, “name”:“赞一下我们”, “key”:“V1001_GOOD” }] }] }其他新增按钮类型的请求示例{ “button”: [ { “name”: “扫码”, “sub_button”: [ { “type”: “scancode_waitmsg”, “name”: “扫码带提示”, “key”: “rselfmenu_0_0”, “sub_button”: [ ] }, { “type”: “scancode_push”, “name”: “扫码推事件”, “key”: “rselfmenu_0_1”, “sub_button”: [ ] } ] }, { “name”: “发图”, “sub_button”: [ { “type”: “pic_sysphoto”, “name”: “系统拍照发图”, “key”: “rselfmenu_1_0”, “sub_button”: [ ] }, { “type”: “pic_photo_or_album”, “name”: “拍照或者相册发图”, “key”: “rselfmenu_1_1”, “sub_button”: [ ] }, { “type”: “pic_weixin”, “name”: “微信相册发图”, “key”: “rselfmenu_1_2”, “sub_button”: [ ] } ] }, { “name”: “发送位置”, “type”: “location_select”, “key”: “rselfmenu_2_0” }, { “type”: “media_id”, “name”: “图片”, “media_id”: “MEDIA_ID1” }, { “type”: “view_limited”, “name”: “图文消息”, “media_id”: “MEDIA_ID2” } ]}参数说明参数是否必须说明button是一级菜单数组,个数应为13个sub_button否二级菜单数组,个数应为15个type是菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型name是菜单标题,不超过16个字节,子菜单不超过60个字节keyclick等点击类型必须菜单KEY值,用于消息接口推送,不超过128字节urlview、miniprogram类型必须网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。media_idmedia_id类型和view_limited类型必须调用新增永久素材接口返回的合法media_idappidminiprogram类型必须小程序的appid(仅认证公众号可配置)pagepathminiprogram类型必须小程序的页面路径返回结果正确时的返回JSON数据包如下:{“errcode”:0,“errmsg”:“ok”}错误时的返回JSON数据包如下(示例为无效菜单名长度):{“errcode”:40018,“errmsg”:“invalid button name size”}代码示例:1 简单的一级菜单可以直接发送json字符串 ,调用接口即可。可以查看此博客 https://www.jianshu.com/p/6ee...2 复杂的二级菜单顶级菜单基类@Datapublic class BasicButton { private String name; private String type; /** * 二级菜单的数组标签 为 sub_button / private BasicButton []sub_button;}点击事件菜单实体@Datapublic class ClickButton extends BasicButton { private String key;}跳转链接菜单实体@Datapublic class ViewButton extends BasicButton { private String url ;}设置菜单public final static String CREAT_OPTION_URL = “https://api.weixin.qq.com/cgi-bin/menu/create?access_token="; public static void creatOption() { String url = CREAT_OPTION_URL + AccessTokenTool.getToken(); String data = JSON.toJSONString(getMenu()); log.info(“发送的菜单json数据为: " + data); String s = HttpUtil.sendHttpByPost(url, data); log.info(“返回的菜单json数据为: " + s); JSONObject jsonObject = JSONObject.parseObject(s); if (jsonObject.getInteger(“errcode”) == 0) { log.info(“设置自定义菜单成功。”); } else { log.error(“设置自定义菜单失败。”); } } /* * 组装菜单数据 * * @return / private static Menu getMenu() { ClickButton btn11 = new ClickButton(); btn11.setName(“点击事件11”); btn11.setType(“click”); btn11.setKey(“11”); ClickButton btn12 = new ClickButton(); btn12.setName(“点击事件12”); btn12.setType(“click”); btn12.setKey(“12”); ClickButton btn13 = new ClickButton(); btn13.setName(“点击事件13”); btn13.setType(“click”); btn13.setKey(“13”); ViewButton btn14 = new ViewButton(); btn14.setName(“view类型事件14”); btn14.setType(“view”); btn14.setUrl(“https://www.baidu.com”); //需要跳转的url ViewButton btn21 = new ViewButton(); btn21.setName(“view类型事件21”); btn21.setType(“view”); btn21.setUrl(“需要跳转的url”); //需要跳转的url ViewButton btn22 = new ViewButton(); btn22.setName(“view类型事件22”); btn22.setType(“view”); btn22.setUrl(“需要跳转的url”); //需要跳转的url ClickButton btn31 = new ClickButton(); btn31.setName(“点击事件31”); btn31.setType(“click”); btn31.setKey(“31”); ViewButton btn32 = new ViewButton(); btn32.setName(“view类型事件32”); btn32.setType(“view”); btn32.setUrl("/find”); //需要跳转的url ClickButton btn33 = new ClickButton(); btn33.setName(“点击事件33”); btn33.setType(“click”); btn33.setKey(“33”); ViewButton btn34 = new ViewButton(); btn34.setName(“view类型事件34”); btn34.setType(“view”); btn34.setUrl( “https://www.baidu.com”); //需要跳转的url BasicButton mainBtn1 = new BasicButton(); mainBtn1.setName(“一级菜单1”); mainBtn1.setSub_button(new BasicButton[]{ btn11, btn12, btn13 ,btn14}); BasicButton mainBtn2 = new BasicButton(); mainBtn2.setName(“一级菜单2”); mainBtn2.setSub_button(new BasicButton[] { btn21, btn22}); BasicButton mainBtn3 = new BasicButton(); mainBtn3.setName(“一级菜单3”); mainBtn3.setSub_button(new BasicButton[] { btn31, btn32, btn33,btn34 }); /* * 这是公众号 “ 程序员日常锦集 ” 目前的菜单结构 ,每个一级菜单都有二级菜单项<br> * * 在某个一级菜单下没有二级菜单的情况,menu该如何定义呢?<br> * 比如,第三个一级菜单项不是“点击事件31”,而直接是“view类型事件32”,那么menu应该这样定义:<br> * menu.setButton(new Button[] { mainBtn1, mainBtn2, btn32 }); / Menu menu = new Menu(); menu.setButton(new BasicButton[] { mainBtn1, mainBtn2, mainBtn3 }); return menu; }自定义菜单事件推送微信会将点击事件推送给开发者,也就是我们填写的服务器地址!这里请注意:请注意,点击菜单弹出子菜单,不会产生上报。请注意,第3个到第8个的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。点击菜单拉取消息时的事件推送推送XML数据包示例:<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[CLICK]]></Event><EventKey><![CDATA[EVENTKEY]]></EventKey></xml>参数说明:事件类型Event为:CLICKEventKey 就是我们自定义菜单时候所填写的key值,根据这个key值区分不同的菜单。点击菜单,解析微信推送给我们的xml数据:判断菜单: //这里xml转为 map类型了 public static BaseMsg handleClick(Map<String, String> xmlData) { String eventKey = xmlData.get(“EventKey”); BaseMsg bm = null; switch (eventKey) { case “11”: bm = new TextMsg(xmlData, “菜单11”); break; case “12”: bm = new TextMsg(xmlData,“菜单11” );; break; case “13”: bm = new TextMsg(xmlData, “”); break; case “31”: bm = new TextMsg(xmlData, “更多信息,敬请期待!”); break; case “33”: //返回图文消息 bm = ArticlesMessageTool.getAiticlesMessage(xmlData, “url”) //其他的消息类型自己定义即可 break; default: bm = new TextMsg(xmlData, " 欢迎。。。。 “); } return bm; }消息基类@XStreamAlias(“xml”) //设置根节点名@Datapublic class BaseMsg { //置顶别名首字母大写 @XStreamAlias(“ToUserName”) private String toUserName;//开发者微信号 private String FromUserName;//发送方帐号(一个OpenID) private String CreateTime;//消息创建时间 (整型) private String MsgType;//MsgType 文本类型 public BaseMsg(Map<String, String> map) { this.CreateTime = System.currentTimeMillis() / 1000 + “”; this.FromUserName = map.get(“ToUserName”); this.toUserName = map.get(“FromUserName”); }}文本消息类:@XStreamAlias(“xml”)@Datapublic class TextMsg extends BaseMsg { private String Content;//文本消息内容 public TextMsg(Map<String, String> map, String Content) { super(map); this.Content = Content; this.setMsgType(“text”); }}图文消息类:@XStreamAlias(“xml”) //设置根节点名@Datapublic class ImageMsg extends BaseMsg { private String ArticleCount;// 是 图文消息个数;当用户发送文本、图片、视频、图文、地理位置这五种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息 private List<ArticlesItem> Articles;// 是 图文消息信息,注意,如果图文数超过限制,则将只发限制内的 public ImageMsg() { } public ImageMsg(Map<String, String> map) { super(map); this.setMsgType(“news”); }获取图文消息工具: /* * 获取图文消息 * * @param custermName * @param serverName * @param createTime * @param xmlData * @return */ public static ImageMsg getAiticlesMessage(Map<String, String> xmlData, String url) { ImageMsg imageMsg = new ImageMsg(xmlData); List<ArticlesItem> list = new ArrayList<ArticlesItem>(); ArticlesItem item = new ArticlesItem(); String title = “欢迎使用公众号!”; String description = “点击图文进入”; //图片路径 String picurl = “自己的服务器地址” + “/img/008.jpg”; item.setDescription(AirPortConfig.description); item.setTitle(AirPortConfig.title); item.setPicUrl(picurl); item.setUrl(url); list.add(item); // 多个可以继续设置….. imageMsg.setArticleCount(“1”); imageMsg.setMsgType(“news”); imageMsg.setArticles(list); return imageMsg; }xml转为mappublic static Map<String, String> getXmlData(InputStream inputStream) { Map<String, String> map = new HashMap<>(); //截取xml SAXReader reader = new SAXReader(); try { Document document = reader.read(inputStream); Element rootElement = document.getRootElement(); //获取根节点 List<Element> elements = rootElement.elements(); // h获取所有的节点 for (Element e : elements) { map.put(e.getName(), e.getStringValue()); } } catch (DocumentException e) { e.printStackTrace(); } return map;}实体对象输出xml public static String bean2Xml(BaseMsg baseMsg) { XStream xStream = new XStream(); //若没有这句,xml中的根元素会是<包.类名>;或者说:注解根本就没生效,所以的元素名就是类的属性 xStream.processAnnotations(BaseMsg.class); xStream.processAnnotations(TextMsg.class); xStream.processAnnotations(ImageMsg.class); String xml = xStream.toXML(baseMsg); log.info(“返回的xml = " + xml); return xml; }maven依赖 <!– https://mvnrepository.com/artifact/dom4j/dom4j –> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!– https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream –> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.11.1</version> </dependency>更多信息关注微信公众号! ...

April 8, 2019 · 4 min · jiezi

连小白都能看懂的微信开发之获取access_token

获取access_tokenaccess_token 作为微信接口全局访问的唯一调用凭据 ,公众号调用各个接口时候都需要使用access_token 。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。 注意:access_token 尽量获取一次,然后各个地方调用即可,切勿多次请求接口获取,防止造成冲突。文档原文如下:获取方式接口调用请求说明https请求方式: GEThttps://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET参数说明参数是否必须说明grant_type是获取access_token填写client_credentialappid是第三方用户唯一凭证secret是第三方用户唯一凭证密钥,即appsecret返回说明正常情况下,微信会返回下述JSON数据包给公众号:{“access_token”:“ACCESS_TOKEN”,“expires_in”:7200}参数说明参数说明access_token获取到的凭证expires_in凭证有效时间,单位:秒错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):{“errcode”:40013,“errmsg”:“invalid appid”}返回码说明返回码说明-1系统繁忙,此时请开发者稍候再试0请求成功40001AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性40002请确保grant_type字段值为client_credential40164调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。(小程序及小游戏调用不要求IP地址在白名单内。)代码实现实体类public class AccessToken { private String expires_in; //成功有效时间 private String access_token; // 普通Token private String errcode; //失败ID private String errmsg; //失败消息 private long expiresIn; //过期时间 , 默认2小时 public long getExpiresIn() { return expiresIn; } public void setExpiresIn(long expiresIn) { this.expiresIn = expiresIn; } public String getExpires_in() { return expires_in; } public void setExpires_in(String expires_in) { this.expires_in = expires_in; } public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public String getErrcode() { return errcode; } public void setErrcode(String errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } /** * * @param expires_in 从微信服务器获取到的过期时间 * @param access_token 从微信服务器获取到的过期时间access-token / public AccessToken(String expires_in, String access_token) { this.access_token = access_token; //当前系统时间+上过期时间 expiresIn = System.currentTimeMillis() + Integer.parseInt(expires_in) * 1000; } //判断token是否过期 public boolean isExpired() { return System.currentTimeMillis() > expiresIn; }}HttpUtil工具 /* * @param url 发送get请求方法 * @return / public static String sendHttpByGet(String url){ try { URL urlGet = new URL(url); URLConnection urlConnection = urlGet.openConnection(); InputStream is = urlConnection.getInputStream(); String inputStreamData = getInputStreamData(is); return inputStreamData; } catch (Exception e) { logger.info(“发送get请求方法异常! " + e); e.printStackTrace(); } return null; }请求获取public class AccessTokenTool { static Logger logger = LoggerFactory.getLogger(AccessTokenTool.class); //自己的appid 和 appsecret private static final String APPID = “wxb24662xxxx”; private static final String APPSECRET = “1864f91ecdd3xxxxxxxxxxx”; //用于存储token private static AccessToken at; /* * @return * @throws Exception / private static com.example.bwjf.demo.pojo.AccessToken getAccessToken(){ String accessTokenUrl = “https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; accessTokenUrl = accessTokenUrl.replace(“APPID”,APPID).replace(“APPSECRET”,APPSECRET); logger.info(” 微信全局token的url " + accessTokenUrl ); String message = HttpUtil.sendHttpByGet(accessTokenUrl); /* * 获取access_token * 成功返回的json : {“access_token”:“ACCESS_TOKEN”,“expires_in”:7200} * 失败放的json : {“errcode”:40013,“errmsg”:“invalid appid”} * / JSONObject jsonObject = JSONObject.parseObject(message); String accessToken = jsonObject.getString(“access_token”); String expires_in = jsonObject.getString(“expires_in”); String errcode = jsonObject.getString(“errcode”); logger.info(“accessToken = " + accessToken); logger.info(“expires_in = “+ expires_in); if(!jsonObject.containsKey(errcode)){ //创建token,并且存起来 at = new AccessToken(expires_in,accessToken); return at; } return null; } /* * 对外提供获取 AccessTokenTool * @return */ public static String getToken(){ if(at==null || at.isExpired()){ //logger.info(“过期”); try { getAccessToken(); } catch (Exception e) { e.printStackTrace(); } } return at.getAccess_token(); } }注:认证后公众号,可能出现的问题调用Api无权限或者为空 (开发者测试账号除外 )请在微信公众平台,安全中心设置ip白名单即可! ...

April 8, 2019 · 2 min · jiezi

连小白都能看懂的微信开发之接收服务器推送消息

接收服务器推送普通消息前言微信接受消息流程机制图接收普通消息当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。 服务器介入获取微信推送消息代码: /** * 此方法用于微信回复消息 * @param request * @return / @PostMapping(“getToken”) @ResponseBody public String getUserMessage(HttpServletRequest request){ String responseString = “”; try { //处理消息与事件推送 Map<String, String> xmlData = RequestParamType.getXmlData(request.getInputStream()); logger.info(xmlData.toString());//接受到的消息 responseString = RequestParamType.dealBean2Xml(xmlData); logger.info(“消息回复ing…” + responseString); return responseString; } catch (IOException e) { e.printStackTrace(); } return null; }解析xml工具类 /* * 解析xml 获取key-value * @param inputStream * @return / public static Map<String,String> getXmlData(InputStream inputStream){ Map<String,String> map = new HashMap<>(); //截取xml SAXReader reader = new SAXReader(); try { Document document = reader.read(inputStream); Element rootElement = document.getRootElement(); //获取根节点 List<Element> elements = rootElement.elements(); // h获取所有的节点 for (Element e: elements) { map.put(e.getName(),e.getStringValue()); } } catch (DocumentException e) { e.printStackTrace(); } return map; }处理xml消息 /* * 处理文本消息 / public static String dealBean2Xml(Map<String,String> xmlData ){ //获取 消息类型 String msgType = xmlData.get(“MsgType”); BaseMsg baseMsg = null; switch (msgType){ //普通文本消息 case “text”: baseMsg = dealText(xmlData); break; //图片消息 case “image”: break; //语音消息 case “voice”: break; //视频消息 case “video”: break; //小视频消息 case “shortvideo”: break; //地理位置消息 case “location”: break; //链接消息 case “link”: break; case “event”: baseMsg = dealTicket(xmlData); break; default: break; } //把消息对象处理为xml数据包 String xml = bean2Xml(baseMsg); if(xml!=null){ return xml; } return null; }处理文本工具类 /* * 处理文本消息 * @param xmlData * @return TextMsg / private static BaseMsg dealText(Map<String, String> xmlData) { //获取请求聊天信息 String content = xmlData.get(“Content”); //回复文本消息 BaseMsg bm = new TextMsg(xmlData,“欢迎访问公众号哟!”); return bm; }将对象转为xml /* * javaBean 转xml * @param baseMsg * @return xml / public static String bean2Xml(BaseMsg baseMsg){ XStream xStream = new XStream(); //若没有这句,xml中的根元素会是<包.类名>;或者说:注解根本就没生效,所以的元素名就是类的属性 xStream.processAnnotations(BaseMsg.class); xStream.processAnnotations(ImageMsg.class); xStream.processAnnotations(LinkMsg.class); xStream.processAnnotations(LocationMsg.class); xStream.processAnnotations(SmalleVideoMsg.class); xStream.processAnnotations(TextMsg.class); xStream.processAnnotations(VideoMsg.class); xStream.processAnnotations(VoiceMsg.class); xStream.processAnnotations(ScanTicket.class); String xml = xStream.toXML(baseMsg); return xml; }消息对象实体消息实体基类/* * base模板实体类 /@XStreamAlias(“xml”) //设置根节点名public class BaseMsg { //置顶别名首字母大写 @XStreamAlias(“ToUserName”) private String toUserName;//开发者微信号 private String FromUserName;//发送方帐号(一个OpenID) private String CreateTime;//消息创建时间 (整型) private String MsgType;//MsgType 文本类型 public String getToUserName() { return toUserName; } public void setToUserName(String toUserName) { toUserName = toUserName; } public String getFromUserName() { return FromUserName; } public void setFromUserName(String fromUserName) { FromUserName = fromUserName; } public String getCreateTime() { return CreateTime; } public void setCreateTime(String createTime) { CreateTime = createTime; } public String getMsgType() { return MsgType; } public void setMsgType(String msgType) { MsgType = msgType; } public BaseMsg(){} public BaseMsg(Map<String,String> map){ this.CreateTime=System.currentTimeMillis()/1000+""; this.FromUserName=map.get(“ToUserName”); this.toUserName=map.get(“FromUserName”); }}消息文本模板实体类/* * 消息文本模板实体类 */@XStreamAlias(“xml”)public class TextMsg extends BaseMsg { private String Content;//文本消息内容 public String getContent() { return Content; } public void setContent(String content) { Content = content; } public TextMsg(Map<String,String> map, String Content){ super(map); this.Content=Content; this.setMsgType(“text”); }}maven 依赖 <!– https://mvnrepository.com/artifact/dom4j/dom4j –> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!– https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream –> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.11.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.53</version> </dependency> ...

April 8, 2019 · 2 min · jiezi

连小白都能看懂的微信入门环境搭建

微信开发之环境搭建接入指南接入概述接入微信公众平台开发,开发者需要按照如下步骤完成:1、填写服务器配置2、验证服务器地址的有效性3、依据接口文档实现业务逻辑下面详细介绍这3个步骤。第一步:填写服务器配置登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。 消息解密类型选择推荐兼容,方便调试和开发。如果选择安全模式,返回的xml是进行加密,解密方式参考 文档如果没有域名 推荐使用内网穿透工具 小花生或者 ngrok 等详细 请看 ngrok 教程: https://blog.csdn.net/weixin_… 官网 : https://www.ngrok.cc第二步:验证消息的确来自微信服务器开发者通过检验signature对请求进行校验(下面有校验方式)。 若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。 signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 timestamp 时间戳 nonce 随机数 echostr 随机字符串代码如下: @GetMapping(“getToken”) @ResponseBody public String getToken(HttpServletRequest request) { String signature = request.getParameter(“signature”); String timestamp= request.getParameter(“timestamp”); String nonce= request.getParameter(“nonce”); String echostr = request.getParameter(“echostr”); logger.info(signature); logger.info(timestamp); logger.info(nonce); logger.info(echostr); //校验请求 boolean check = RequestParamType.check(timestamp, nonce ,signature); if (check) { logger.info(“接入成功。。”); return echostr; }else{ logger.info(“接入失败。。”); return null; } }check工具类: //token private final static String token = “test”; //获取token验证 public static boolean check( String timestamp, String nonce,String signature){ /** * 1)将token、timestamp、nonce三个参数进行字典序排序 2)将三个参数字符串拼接成一个字符串进行sha1加密 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 / logger.info(“token = “+token); String [] strings = new String[] {token,timestamp,nonce}; Arrays.sort(strings); String sha1Str = strings[0] + strings[1] + strings[2]; //sha1加密 String string2Sha1 = Sha1Util.getSha1(sha1Str); logger.info(“加密前:” + signature); logger.info(“加密后:"+string2Sha1); //signature 与生成的signature 对比 if (string2Sha1 != null && string2Sha1.equalsIgnoreCase(signature)){ return true; } return false; }sha1加密 工具类 : /* * sha1加密 * @param str 返回加密字符串 * @return */ public static String getSha1(String str) { if (str == null || str.length() == 0) { return null; } char hexDigits[] = {‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘a’, ‘b’, ‘c’, ’d’, ’e’, ‘f’}; try { MessageDigest mdTemp = MessageDigest.getInstance(“sha1”); mdTemp.update(str.getBytes(“UTF-8”)); byte[] md = mdTemp.digest(); int j = md.length; char buf[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; buf[k++] = hexDigits[byte0 & 0xf]; } return new String(buf); } catch (Exception e) { logger.error(” sha1 加密异常!” + e); e.getStackTrace(); return null; } }第三步:依据接口文档实现业务逻辑验证URL有效性成功后即接入生效,成为开发者。你可以在公众平台网站中申请微信认证,认证成功后,将获得更多接口权限,满足更多业务需求。成为开发者后,用户每次向公众号发送消息、或者产生自定义菜单、或产生微信支付订单等情况时,开发者填写的服务器配置URL将得到微信服务器推送过来的消息和事件,开发者可以依据自身业务逻辑进行响应,如回复消息。公众号调用各接口时,一般会获得正确的结果,具体结果可见对应接口的说明。返回错误时,可根据返回码来查询错误原因。全局返回码说明用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。此外,由于开发者经常有需在多个平台(移动应用、网站、公众帐号)之间共通用户帐号,统一帐号体系的需求,微信开放平台(open.weixin.qq.com)提供了UnionID机制。开发者可通过OpenID来获取用户基本信息,而如果开发者拥有多个应用(移动应用、网站应用和公众帐号,公众帐号只有在被绑定到微信开放平台帐号下后,才会获取UnionID),可通过获取用户基本信息中的UnionID来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的UnionID是唯一的。换句话说,同一用户,对同一个微信开放平台帐号下的不同应用,UnionID是相同的。详情请在微信开放平台的资源中心-移动应用开发-微信登录-授权关系接口调用指引-获取用户个人信息(UnionID机制)中查看。另请注意,微信公众号接口必须以http://或https://开头,分别支持80端口和443端口。关注我 ...

April 6, 2019 · 2 min · jiezi

连小白都能看懂的微信入门开发之内网穿透

Ngrok还算一款免费比较稳定的内网穿透工具这几天在微信开发,一找了个还几个工具老掉线,这个还不错。1. 下载https://www.ngrok.cc 这个没得说我用的是这个下载完后后解压,cmd进入 然后输入启动:sunny.exe clientid 隧道id多个隧道同时启动sunny.exe clientid 隧道id,隧道id隧道id如何获得?2. 注册登录走起!然后进入:点击开通即可:然后填写:然后这里可以查看自己添加的配置 然后获取到 隧道id 这个玩意! 还有自己的域名!3. 然后启动cmd输入 命令sunny.exe clientid 隧道id启动即可,然后启动本地服务,访问 域名 就可以在外网任何地方访问这个域名来访问你的项目了。配置正确的情况下,多试几次,也许就好了,使用过程中可能会有配置不生效的情况。总体来说免费还算稳定!分享关于程序员的一切小生活,不定期分享福利,如果你是程序员或者你想了解程序员,请关注我吧。

April 5, 2019 · 1 min · jiezi

想要更精准的小程序模版消息推送?我们来帮你实现

两年多前,为了让更多的人找到好玩、好用的小程序,我们成立了「知晓程序」。再后来,我们推出了后端云服务平台——知晓云,帮助大家降低创业成本,提升开发效率。「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。随着小程序生态的不断发展、用户规模的不断扩大,开发者的项目也在高速增长。他们不再满足于现有的后端服务支持,开始需要更多开箱即用的功能。为此,我们围绕「简化开发流程,让用户仅需专注于业务」这一目标,相继研发了多个好用的功能。在用户喜爱的众多功能中,使用率最高的是模版消息推送。这一功能自 2018 年 4 月上线后,使用率不断提升,完美的达成了我们设计此功能时的初衷——使用户获得【开箱即用,无需编码】的推送能力,并将其用于小规模的消息通知及用户管理。一年来,知晓云平台上有越来越多的小程序,从数百用户的小项目成长到 UV 过百万级,甚至超过千万的明星项目。模版消息推送数的量级也由早期每天几百条,变为后来的每天数百万条。客户的项目逐渐成熟,其需求也从简单的模版消息推送,逐渐转变为基于推送的转化、留存全链路触达与数据分析服务。因此,我们决定再往前迈进一步。5 月 8 日,我们将正式推出「知晓推送」服务,帮助运营者处理好粉丝转化、消息推送、数据分析等多个层面的麻烦事,真正解决复购难、留存差的问题。决定推出「知晓推送」服务前我们考察了市场上现在服务存在的问题,主要有这几条:平台支持少:只做了微信小程序平台支持,无法实现在支付宝小程序及其他平台发推送的需求。服务价格贵:按年收费且价格高昂,发送一千条推送就需要付出 10 块钱的成本。效果追踪差:模板消息发完就完事儿了,推送效果如何、用户是否点击,点击的用户特征,这些数据都无法追踪与分析。这些问题,我们来逐一解决。平台支持少?知晓云已经支持包括微信小程序和支付宝小程序在内的各大小程序平台的消息推送,对 Android、iOS 平台的支持也将在近期上线。大家的业务版图拓展到哪儿,我们支持到哪儿!服务价格贵?不得不说,友商的定价确实高得离谱。我们来算一笔账:假设小王有一个 2 万用户的小程序,日常运营每周一次推送,一年要发 100 万条模板消息。如用 x 神推的服务,要花 2 万块。2 万块啊!痛大家所痛,我们决定把它除以 200——只要 100 块。效果追踪差?经过长期的实践总结,我们推出 3 大重磅特性:过滤低价值用户,提升回访转化率大数据精准过滤有恶意举报行为或被标记为机器人的用户,有效减少无效发送,提升模板消息的回访转化率。一键埋点,自动收集 formid收集用户的 formid 是发送模板消息的前提条件,知晓云 SDK 提供透明页面自动收集 formid 功能,在不打扰用户的前提下即可收集大量 formid。智能发送规则,实现精准推送开发者可以自行定义用户行为,为满足特定条件的用户发送对应的模板消息。实现消息的精准推送。知晓云结合不同行业的海量行业案例,挖掘了一套专有的用户过滤机制,祝你实现业务量的翻倍增长。「知晓推送」活动预告「知晓推送」的主要特性说完了。考虑到 5 月还比较遥远,我们先预告一下福利吧:如你还不是知晓云用户,赶紧注册用起来吧~「知晓推送」正式上线前完全免费,服务上线当天还有额外福利~如你已经是知晓云用户了,也请您放心。5.8 日前注册的用户都可获赠价值不少于 198 元的资源包哦~支持 iOS、Android 的公测福利(4/2 - 4/10)「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是以小程序开发为起点的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。另外,知晓云支持移动端(iOS、Android)的公测活动即将开启:即日起至 4 月 10 日,通过扫描下方的二维码填写表单报名参与公测活动的用户,在成功接入移动端(iOS、Android)应用后将获得 100 元无门槛优惠券???? 报名点这里????知晓云是国内首家专注于小程序开发的后端云服务。使用知晓云,小程序开发快人一步。

April 2, 2019 · 1 min · jiezi

Python微信防撤回,基于itchat模块

有时候,女神发来一条消息,说约你看电影,她考虑了一下,又撤回了,不约你了…而你又想知道她究竟发了什么,该怎么办?微信防撤回了解一下。环境要求Python3 电脑安装itchatpip install itchat使用代码新建chehui.py,拷贝以下代码#!/usr/bin/env python3# -- coding: utf-8 --author = ‘jiangwenwen’import itchatfrom itchat.content import import timeimport reimport osprint(“该程序由里客云资源站开发,网址:likeyunba.com”)print(“作者:TANKING”)print(“打开程序会弹出一个二维码,微信扫码”)print(“如果二维码弹不出,那就在你这个程序的同一个目录下找到QR.png双击打开扫码”)print(“扫码后,出现Start auto replying就可以实时监控消息了…")msg_information = {}# 针对表情包的内容face_bug = None@itchat.msg_register([TEXT, PICTURE, FRIENDS, CARD, MAP, SHARING, RECORDING, ATTACHMENT, VIDEO], isFriendChat=True, isMpChat=True)def handle_receive_msg(msg): global face_bug # 接收消息的时间 msg_time_rec = time.strftime("%Y-%m-%d %H:%M:%S”, time.localtime()) # 在好友列表列表中查询发送信息的好友昵称 msg_from = itchat.search_friends(userName=msg[‘FromUserName’])[‘NickName’] # 信息发送的时间 msg_time = msg[‘CreateTime’] # 每条信息的ID msg_id = msg[‘MsgId’] # 储存信息的内容 msg_content = None # 储存分享的连接,比如分享的文章和音乐 msg_share_url = None # 如果发送的消息是文本或者好友推荐 if msg[‘Type’] == ‘Text’ or msg[‘Type’] == ‘Friends’: msg_content = msg[‘Text’] print(msg_content) # 如果发送的消息是附件,视频,图片,语音 elif msg[‘Type’] == ‘Attachment’ or msg[‘Type’] == ‘Video’ \ or msg[‘Type’] == ‘Picture’\ or msg[‘Type’] == ‘Recording’: # 内容为下载文件名 msg_content = msg[‘FileName’] msg‘Text’ # 如果消息是推荐的名片 elif msg[‘Type’] == ‘Card’: # 内容是推荐人的昵称和性别 msg_content = msg[‘RecommendInfo’][‘NickName’] + ‘的名片’ if msg[‘RecommendInfo’][‘Sex’] == 1: msg_content += ‘性别为男’ else: msg_content += ‘性别为女’ print(msg_content) # 如果消息为分享的位置信息 elif msg[‘Type’] == ‘Map’: x, y, location = re.search( “<location x="(.?)" y="(.?)".label="(.?)".”, msg[‘OriContent’]).group(1, 2, 3) if location is None: # 内容为详细地址 msg_content = r’纬度->’ + x.str() + “经度->” + y.str() else: msg_content = r"" + location # 如果消息是分享的音乐或者文章,详细的内容为文章的标题或者分享的名字 elif msg[‘Type’] == ‘Sharing’: msg_content = msg[‘Text’] msg_share_url = msg[‘Url’] print(msg_share_url) face_bug = msg_content # 将信息存储在字典中,每一个msg_id对应一条消息 msg_information.update( { msg_id: { “msg_from”: msg_from, “msg_time”: msg_time, “msg_time_rec”: msg_time_rec, “msg_type”: msg[‘Type’], “msg_content”: msg_content, “msg_share_url”: msg_share_url } })#这个是用于监听是否有friend消息撤回@itchat.msg_register(NOTE, isFriendChat=True, isGroupChat=True, isMpChat=True)def information(msg): # 这里如果这里的msg[‘Content’]中包含消息撤回和id,就执行下面的语句 if ‘撤回了一条消息’ in msg[‘Content’]: old_msg_id = re.search("&lt;msgid&gt;(.*?)&lt;/msgid&gt;", msg[‘Content’]).group(1) # 得到消息 old_msg = msg_information.get(old_msg_id) print(old_msg) # 如果发送的是表情 if len(old_msg_id)<11: itchat.send_file(face_bug, toUserName=‘filehelper’) # 发送撤回的提示给文件助手 else: msg_body = “【”\ + old_msg.get(‘msg_from’) + “撤回了】\n”\ + old_msg.get(“msg_type”) + “消息:” + “\n”\ + old_msg.get(“msg_time_rec”) + “\n”\ + r"" + old_msg.get(“msg_content”) # 如果分享的文件被撤回了,那么就将分享的url加在msg_body中发送给文件助手 if old_msg[‘msg_type’] == “Sharing”: msg_body += “\n就是这个链接>” + old_msg.get(‘msg_share_url’) # 将撤回消息发送到文件助手 itchat.send_msg(msg_body, toUserName=“filehelper”) # 有文件的话也要将文件发送回去 if old_msg[“msg_type”] == “Picture”\ or old_msg[“msg_type”] == “Recording”\ or old_msg[“msg_type”] == “Video”\ or old_msg[“msg_type”] == “Attachment”: file = “@fil@%s” % (old_msg[‘msg_content’]) itchat.send(msg=file, toUserName=‘filehelper’) os.remove(old_msg[‘msg_content’]) # 删除字典旧信息 msg_information.pop(old_msg_id)itchat.auto_login(hotReload=True)itchat.run() CMD运行即可。考虑到有一些人没有Python环境,我已经打包成可执行文件了,直接双击exe就可以在电脑运行。微信扫码:TANKINGHTTP://LIKEYUNBA.COM2019-3-28 ...

March 28, 2019 · 2 min · jiezi

开发支付宝小程序无从下手?我们给你创造了一条捷径

作为国内首家专注于小程序领域的后端云服务,知晓云正式开启 3.0 计划——全平台 Serverless 服务。「知晓云」cloud.minapp.com,诞生于 2017 年 8 月 8 日,是国内第一个专注于小程序开发的后端云服务,它免去了小程序开发中服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程,让开发者更快、更低成本地做出优质的小程序。即日起至 3 月 1 日公测活动期间,成功参与新版公测活动并接入支付宝小程序的用户,可获得个人版套餐 6 个月的免费使用资格。计划的第一站我们选择了支付宝小程序。在接下来的时间里,我们还将逐步完成对其他各大平台的支持:(百度、头条等)小程序、Web 端、iOS、Android ,一步步完成全平台后端云服务建设。知晓云能带给你什么?如果想做一个应用,基本上一定要有用户系统、数据存储、网络接口、管理后台等配套,这都是后端的工作。而通过我们多年实践,在大多数应用场景里,这类配套的定制(需要开发)的部分,均发生在应用层,也即,这类业务模块完全可以做成通用的服务。将后端的用户系统、数据存储、网络接口、服务运维甚至支付网关等通用业务做成服务提供给用户,通过管理后台控制,就可以做到无需后端工程师参与开发而完成项目。因此我们在微信小程序上线后的半年里,倾入全部力量,打造了知晓云——一个为小程序设计的后端云服务。17 年以来,我们花了一年半反复打磨产品,与广大的微信小程序开发者共同成长。在刚过去的 2018 年 ,我们累计服务了 40000+ 开发者,实现了服务规模同比 1000% 的增长。各大厂纷纷入局重注小程序,标志着移动互联网正式进入新时代——一个强调快速上线、专注服务本身的时代。支付宝以及其他平台的小程序,在这个时代里,更加需要无服务器(Serverless)的开发方式。通过接入 SDK 就可以使用后端服务,开发者可以快速构建一个完整产品,上线推广。我们希望将从服务数万开发者的过程中总结出的,各个应用场景的经验推广到各大平台去,帮助更多的开发者。我们相信,小程序和后端云服务结合,将使开发过程变得更加简单、迅速和高效,让更多富有创造力的开发者创造更多的价值。好用、省心的后端云服务有了一个好的想法,再花上几分钟在小程序中接入知晓云的 SDK,你就不再需要去管什么 PHP、数据库等后端逻辑,也无需管理服务器或维护后端服务,更不用担心自己服务器的负载和运维……与此同时,你还将获得:成本降低 100%数百元的服务器及数据库费用,一天 3 毛钱就能搞定,大大节约开发成本。运维时间节省 100%24 小时待命的线上运维、峰值动态扩容,服务器运维再无后顾之忧。效率提升 200%2 秒即可授权登录,一键部署新体验,你的开发效率将至少提升一倍!一句话,有了「知晓云」,核心业务逻辑以外的事情都不需要你操心。即便你已经拥有了自己的服务器,仍然可以来体验我们的产品——「知晓云」还开放了运营后台的 API 支持、第三方服务器的 OpenAPI 支持,方便开发者快速将小程序业务和其他业务联合起来。从「晓」开始,再无后顾之忧我们提供的不仅仅是后端云服务。在历经多年的「移动生态」构建过程后,我们深知一款产品的研发上线只是第一步。上线了,一切才算正式开始。因此,我们在小程序推广环节铺了一条「高速轨道」,使你的产品借此触达到更多的用户。我们拥有:知晓程序公众号:小程序生态第一大号知晓程序商店:首家小程序商店知晓市场:小程序定制对接平台作为小程序生态里唯一一家,技术服务与推广服务都做到了行业领先的团队。我们将为用户提供免费的冷启动资源及推广支持。真正实现:「从『晓』开始,让您再无后顾之忧」2019 年,知晓云迈出了新的一步,希望大家也能顺利的迈出自己的每一步。本文首发于「知晓程序」公众号:https://mp.weixin.qq.com/s/rJ…知晓云是国内首家专注于小程序开发的后端云服务。使用知晓云,小程序开发快人一步。

February 20, 2019 · 1 min · jiezi

在微信小程序中保存网络图片

微信代码片段点这里, 该功能需要添加appid才能进行正常的测试。在小程序的文档中我们得知,wx.saveImageToPhotosAlbum 是用来保存图片到相册的。但是仔细一看会发现这个接口的filePath参数只接受临时文件路径或永久文件路径,不支持网络图片路径,意味着我们不能直接调用这个接口。。因此先需要把该文件下载至本地,使用 wx.downloadFile 。但值得注意的是小程序只可以跟指定的域名与进行网络通信,也就是说下载图片之前,我们需要先去微信公众者平台的开发设置里设置uploadFile合法域名。示例代码如下:<!– index.wxml –><image class=“qr-code” src="{{url}}" mode=“aspectFill” /><button class=“text” bindtap=“saveImage”>保存图片</button>// index.jsconst app = getApp()Page({ data: { url: ‘https://avatars3.githubusercontent.com/u/23024075?s=460&v=4' }, // 保存图片 saveImage() { this.wxToPromise(‘downloadFile’, { url: this.data.url }) .then(res => this.wxToPromise(‘saveImageToPhotosAlbum’, { filePath: res.tempFilePath })) .then(res => { // do something wx.showToast({ title: ‘保存成功~’,icon: ’none’ }); }) .catch(err) => { console.log(err); // 如果是用户自己取消的话保存图片的话 // if (err.errMsg.indexOf(‘cancel’)) return; }) }, /** * 将 callback 转为易读的 promise * @returns [promise] */ wxToPromise(method, opt) { return new Promise((resolve, reject) => { wx[method]({ …opt, success(res) { opt.success && opt.success(); resolve(res) }, fail(err) { opt.fail && opt.fail(); reject(err) } }) }); },})然后理论上就可以保存图片了… 用户第一次在我们的小程序使用保存图片这个功能是会弹出一个授权弹框,如果用户手滑点了拒绝授权后再点一次保存图片,然后就会发现什么反应都没有了。。。出现这样的原因是因为这个授权弹框只会出现一次,所以我们得想办法再让用户重新授权一次。这时就想到使用 wx.authorize .但是经过测试后发现,使用 wx.authorize 后,会报 authorize:fail auth deny 的错误。然后经过查阅资料得知:如果用户未接受或拒绝过此权限,会弹窗询问用户,用户点击同意后方可调用接口;如果用户已授权,可以直接调用接口;如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调。请开发者兼容用户拒绝授权的场景。emmm… 那这样效果当然不符合我们预期,只能在换一种方式。这时就想到了使用<button open-type=“openSetting”/>,在交互上做一个提示弹框,引导用户重新授权:<image class=“qr-code” src="{{url}}" mode=“aspectFill” /><button class=“text” bindtap=“saveImage”>保存图片</button><!– 简陋版提示 –><view wx:if="{{showDialog}}" class=“dialog-wrap”> <view class=“dialog”> 这是一段提示用户授权的提示语 <view class=“dialog-footer”> <button class=“btn” open-type=“openSetting” bindtap=“confirm” > 授权 </button> <button class=“btn” bindtap=“cancel”>取消</button> </view> </view></view>const app = getApp()Page({ data: { url: ‘https://avatars3.githubusercontent.com/u/23024075?s=460&v=4', showDialog: false, }, saveImage() { this.wxToPromise(‘downloadFile’, { url: this.data.url }) .then(res => this.wxToPromise(‘saveImageToPhotosAlbum’, { filePath: res.tempFilePath })) .then(res => { console.log(res); // this.hide(); wx.showToast({ title: ‘保存成功’, icon: ’none’, }); }) .catch(({ errMsg }) => { console.log(errMsg) // if (~errMsg.indexOf(‘cancel’)) return; if (!errMsg.indexOf(‘auth’)) { wx.showToast({ title: ‘图片保存失败,稍后再试’, icon: ’none’ }); } else { // 调用授权提示弹框 this.setData({ showDialog: true }) }; }) }, // callback to promise wxToPromise(method, opt) { return new Promise((resolve, reject) => { wx[method]({ …opt, success(res) { opt.success && opt.success(); resolve(res) }, fail(err) { opt.fail && opt.fail(); reject(err) } }) }); }, confirm() { this.setData({ showDialog:false }) }, cancel() { this.setData({ showDialog: false }) }})最后这样就完成啦https://anran758.github.io/bl… ...

February 11, 2019 · 2 min · jiezi

公测招募!知晓云已支持支付宝小程序

2017 年 8 月 8 日,知晓云正式上线。在这一年多的时间里,随着微信小程序的蓬勃发展,知晓云取得了一些成绩——累计服务 40000+ 开发者,18 年更是实现了服务规模同比增长 1000%。对于用户来说,省去服务器搭建、域名备案、数据接口开发、线上运维等繁琐流程与不到 3 毛钱一天的价格,是选择我们重要因素。我们在做哪些努力这一年多来,我们聚焦在 Serverless(无服务器架构)= BaaS(后端即服务) + FaaS(函数即服务)两大基础能力的同时,也针对微信小程序平台的各项能力做了深度支持,提供了大量开箱即用的功能,如——最好用的模板消息推送一行代码接入微信支付最省事的小程序码生成器一键开通智能客服消息小程序富文本 CMS 和排版渲染组件等等紧接着,随着开发者规模的进一步扩大,各式各样的小程序,也对平台提出了更多的服务需求,如短信能力、邮件能力、错误监测能力等等,我们一一满足。在这个过程中,我们明白,仅在深度上继续深挖是不够的,还需要在服务的广度上多思考。「如何给予小程序开发者更多的支持?」2018 年,对于这个问题,我们的答案是: 除了「想法」(业务逻辑)本身,其他事情都可以交给我们。我们提供的,从来不止是后端云服务,而是一整个服务体系——小程序开发入门培训平台:「知晓课堂」小程序插件开发者的交易平台:「插件市场」小程序定制服务平台:「知晓市场」最好的微信新商业服务平台:「知晓程序」在 2019 年,我们将继续探索答案,希望能和知晓云的用户一起成长。更好地服务于用户这半年,小程序生态也发生了翻天覆地的变化,各大厂商争先恐后接连重注小程序:百度智能小程序:千亿流量、开源、90%广告分成头条小程序:打通抖音,还有八大流量入口支付宝小程序:3年砸10亿激励小程序创业者……这一现象,既反映了大厂们对小程序这一应用形态的看好,也象征着一种趋势。在见过更多的用户后,我们对「愿景」一词有了更深刻的理解:以微信小程序作为起点,随着用户量增加和模式验证的完成 ——> 经历更多的挫折并成长 ——> 去到更多更大的舞台。这个起点,不止是你(小程序)的,也是我们(服务商)的。至此,我们决定开始知晓云的 3.0 计划——全平台 Serverless 服务。希望能让用户得以验证的想法,在全世界开花。在 19 年伊始,我们选择了支付宝小程序作为 3.0 计划的第一站。而在接下来的时间里,我们将一步步对其他各大平台:(百度、头条等)小程序、Web 端、iOS、Android 进行支持,逐步完成全平台后端云服务建设,这一切会发生得非常快。招募公测用户现在,知晓云对支付宝的支持已初步完成。我们将开放公测并招募 50 位用户免费参与。我们希望你是这样的:有小程序开发的经验对开发支付宝小程序感兴趣希望有一个后端云服务平台帮助更快、更省地上线业务公测期间,你将能直接与知晓云的核心工程师深度交流,一起探索小程序领域中更多的可能性。公测开启时间:2019 年 2 月 13 日报名截止时间:2019 年 2 月 18 日期待你的参与,快提交申请吧????本文首发于「知晓云」公众号:https://mp.weixin.qq.com/s/DH…知晓云是国内首家专注于小程序开发的后端云服务。使用知晓云,小程序开发快人一步。

January 29, 2019 · 1 min · jiezi

【实战教程】使用知晓云完成小程序客服消息的自动回复

在上次推送完「卡券核销消息推送」的教程后,我们决定再多出点教程。此次教程将再次带领大家体验「消息推送」,实现另一个「微信消息推送」的触发器,关于自动回复小程序客服消息的。 详细步骤如下:开通知晓云消息推送,并开通小程序消息推送功能,将知晓云消息推送配置同步到小程序创建 sendCustomMsg 触发器,触发器类型为“微信消息推送”,动作类型为云函数创建 sendCustomMsg 云函数总体流程如下流程图:开通消息推送进入知晓云,选择 控制台 -> 设置 -> 小程序 ,拉到最底找到「消息推送」,点击「立即开通」,即可开通消息推送功能。开通之后需要将消息推送的相关配置项配置到小程序或者公众号后台。由于此次我们要实现的是小程序的客服消息回复,所以需要在知晓云的小程序后台开通消息推送(设置->开发设置->消息推送 )并完成相关配置的填写。▲ 知晓云配置项配置项填写完成后还需要将消息推送的相关配置同步到微信小程序后台,两部分内容均填写完成后微信消息推送类型的触发器才可以正常使用。▲ 小程序消息推送配置项创建触发器由于该步骤中在创建触发器时的动作类型需要选择「云函数」,且需要选定对应的云函数 js 文件,所以我们需要在创建触发器前先创建(在控制台点击 引擎 -> 云函数 -> 添加)一个空的云函数,以便顺利执行后续步骤。现在我们创建一个名为 sendCustomMsg 的云函数:如上图点击所示确定即可,云函数的代码等会再写,先回到本小节的重点上来。我们需要(通过控制台 引擎 -> 触发器 -> 添加)创建一个触发器,这个触发器将帮助我们在设置好的条件被触发的情况下运行 sendCustomMsg 云函数来实现用户消息的自动回复。第一步,配置触发器。记得触发类型一定要选择「微信消息推送」。第二步,设置触发条件。这一步需要填写小程序的 appID (小程序 appID 可以在微信小程序后台获取) ,其他部分的设置如下图。第三步,设置动作。按照下图所示配置后点击完成即可成功创建「微信消息推送」触发器。编辑云函数回到我们刚才创建的云函数(名为 sendCustomMsg 的空的云函数), 本次教程中作为示例的云函数代码写的比较简单,只是将用户发送的内容以原样再发回给用户。代码里用到了给用户回复消息的小程序客服消息接口,该接口需要的 access_token 和 open_id 可以分别从云函数的 API 和 云函数的参数 event.data.FromUserName 获取。(标灰色两个地址见置顶留言 )全部代码如下:测试最后我们可以通过小程序客服按钮(contact-button)进入到客服对话框进行测试。在下图中可以看到,我们发送什么给客服,客服都会按原样发回给我们。这意味着,我们的想要的功能实现了 :)本文首发于「知晓云」公众号:https://mp.weixin.qq.com/s/VZ…如果你还想了解 更多小程序开发技巧,快速掌握小程序开发能力。欢迎扫描下方二维码关注「知晓云」,我们会持续为更新与小程序有关的实战教程哦~

January 29, 2019 · 1 min · jiezi

微信开放平台扫码登录获取用户基本信息!附可用demo

微信开放平台提供了网站扫码登录的接口,用于获取用户基本信息(头像,昵称)方便网站快速接入微信登录,快捷登录。需要使用登录接口,需要成为微信开放平台认证开发者(300元)才可以获得这个接口权限。准备工作:1、准备APPID、APPSECRET2、准备接口地址3、准备REDIRECT_URI获取code接口https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect获取acess_token、openid接口https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code获取用户信息接口:https://api.weixin.qq.com/sns/userinfo?access_token=access_token&openid=openid流程:1、获取CODE2、获取access_token、openid3、获取用户信息操作:1、请求CODE参数说明通过接口地址,拼接以上参数进行访问即可https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=这里填写redirect_uri&response_type=code&scope=SCOPE&state=STATE#wechat_redirectredirect_uri说明这是点击上面地址扫码后跳转的地址,跳转的地址回给你带上两个参数,code和state参数。state说明用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验。可以自己生成随机字符串,为了简单学习,我这里用时间戳进行MD5加密简单生成<?php$data = time();$state = MD5($data);?>例如你的redirect_uri是http://www.baidu.com/login.php,那么扫码后,跳转的地址会是这样的。http://www.baidu.com/login.php?code=生成的code&state=生成的state当然redirect_uri需要进行urlEncode编码。<?php$redirect_uri = urlEncode(“http://www.baidu.com/login.php");?>最终获取CODE的访问链接就是这样的:<?php$appid = “填写你的APPID”;$redirect_uri = UrlEncode(“http://www.baidu.com/login.php");$data = time();$state = MD5($data);//跳转页面echo “<script>location.href="https://open.weixin.qq.com/connect/qrconnect?appid=$appid&redirect_uri=$redirect_uri&response_type=code&scope=snsapi_login&state=$state#wechat_redirect";</script>”;?>然后就跳转到了一个扫码的页面了:2、获取access_token和openid通过curl向接口发起请求即可<?php//从redirect_uri得到code$code = $_GET[“code”];$appid = “填写你的”;$secret = “填写你的”;//获取access_token和openid$url = “https://api.weixin.qq.com/sns/oauth2/access_token?appid=$appid&secret=$secret&code=$code&grant_type=authorization_code";function post($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); $rst = curl_exec($ch); curl_close($ch); return $rst;}//发送请求$result = post($url);//返回接口的数据$arr = json_decode($result,true);//解析json,单独把openid和access_token取出来待会用$openid = $arr[‘openid’];$token = $arr[‘access_token’];?>3、获取用户信息<?php//这里是接着上面的代码的//获取用户信息需要openid 和 access_token//获取用户信息$getinfourl = “https://api.weixin.qq.com/sns/userinfo?access_token=$token&openid=$openid";function getinfo($getinfourl) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $getinfourl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); $rst = curl_exec($ch); curl_close($ch); return $rst;}//发送请求获取用户信息$info_result = getinfo($getinfourl);//返回接口的数据// echo $info_result;$info_arr = json_decode($info_result,true);$nickname = $info_arr[’nickname’];$headimgurl = $info_arr[‘headimgurl’];//显示头像和昵称echo “<img src="$headimgurl"/>";echo “<h2>$nickname<h2>”;?>完整代码code.php<?php$appid = “填写你的”;$redirect_uri = UrlEncode(“http://www.baidu.com/login.php");$data = time();$state = MD5($data);echo “<script>location.href="https://open.weixin.qq.com/connect/qrconnect?appid=$appid&redirect_uri=$redirect_uri&response_type=code&scope=snsapi_login&state=$state#wechat_redirect";</script>”;?>login.php<!DOCTYPE html><html><head> <title>登录成功!</title> <style type=“text/css”> *{margin:0px;padding: 0px;} #headimg{ width: 180px; height: 180px; margin:100px auto 10px; border-radius: 100%; } #headimg img{ width: 180px; height: 180px; border-radius: 100%; } h2{ text-align: center; } p{ text-align: center; font-size: 38px; font-weight: bold; margin-top: 20px; } </style></head><body></body></html><?php$code = $_GET[“code”];$appid = “填写你的”;$secret = “填写你的”;//获取access_token和openid$url = “https://api.weixin.qq.com/sns/oauth2/access_token?appid=$appid&secret=$secret&code=$code&grant_type=authorization_code";function post($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); $rst = curl_exec($ch); curl_close($ch); return $rst;}//发送请求$result = post($url);//返回接口的数据$arr = json_decode($result,true);$openid = $arr[‘openid’];$token = $arr[‘access_token’];//获取用户信息$getinfourl = “https://api.weixin.qq.com/sns/userinfo?access_token=$token&openid=$openid";function getinfo($getinfourl) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $getinfourl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); $rst = curl_exec($ch); curl_close($ch); return $rst;}//发送请求获取用户信息$info_result = getinfo($getinfourl);//返回接口的数据// echo $info_result;$info_arr = json_decode($info_result,true);$nickname = $info_arr[’nickname’];$headimgurl = $info_arr[‘headimgurl’];$errcode = $info_arr[’errcode’];if ($errcode == “41001”) { echo “<p>登录失效,请重新扫码登录<p>”; echo “<p><a href="code.php">登录</a><p>”;}else{ echo “<div id="headimg"><img src="$headimgurl"/></div>”; echo “<h2>$nickname<h2>”; echo “<p>登录成功<p>”;}?>DEMO:http://www.likeyunba.com/code…时间:2018-1-26作者:TANKING网站:http://likeyunba.com ...

January 26, 2019 · 2 min · jiezi

【实战教程】使用知晓云完成微信卡券消息的处理

知晓云新推出「消息推送」功能,该功能通过「消息推送」类型的触发器接收微信推送过来的消息,开发者可以对此消息实行相关操作。此教程将带领大家体验这个新功能,实现一个「微信消息推送」类型的触发器。该触发器将会在消费者核销微信优惠券的时候触发,并调用一个云函数。调用的云函数会将核销的相关信息保存在一个特定的数据表中,由此来记录公众号卡券的使用情况。一、开通知晓云消息推送功能知晓云控制台 -> 设置 -> 小程序,拉到最底找到「消息推送」,点击 立即开通,即可开通消息推送功能:开通之后需要将消息推送的相关配置项配置到小程序或者公众号后台,此教程为实现接收微信卡券核销事件的消息的功能,所以需要将配置项配置到公众号后台。进入公众号后台 点击开发->基本配置->服务器配置,同步配置成知晓云消息推送的相关配置,完成配置后,微信消息推送类型的触发器方可使用。▲ 知晓云配置项▲ 微信公众平台配置项二、创建云函数后面我们会创建一个动作类型为「云函数」的触发器,所以我们需要先创建一个云函数,供触发器使用。现在我们创建一个云函数,用于用户在核销卡券的事件触发时调用该云函数操作数据表,将微信推送过来的核销的相关数据保存到相关到表中。云函数 user_consume_card.js,tableID 必须是已存在 table (需要提前在知晓云控制台创建表),并且有 data 中相应字段的列,否则将创建不成功。微信消息推送触发器返回的数据可在云函数的第一个参数 event.data 获取到,具体返回的数据有哪些可以查看微信开发者文档。例如卡券核销的返回数据如下,只是触发器将微信的 XML 格式的数据转换成了 JSON 格式。三、创建触发器添加一个「微信消息推送」类型的触发器,该触发器会在指定的条件触发时触发该触发器,点击 引擎 -> 触发器 -> 添加 。1.触发器配置,触发器类型一定要选择「微信消息推送」2.触发条件设置这一步需要填写公众号的 AppID ,ID 的获取需要进入「公众号平台」,点击 开发->基本配置->服务器配置 即可看到 AppID。参数规则: 触发触发器的参数规则可以填写微信事件名称,如果需要查看相关事件名称可以查看微信卡券事件推送教程。此教程为卡券核销事件推送,因此触发条件就为 evnet = user_consume_card 的事件:3.动作 动作类型选择云函数,当然你也可以选择其他的动作类型,选中我们创建云函数步骤中创建的云函数,配置好之后,当用户核销卡券时即会触发该触发器,并调用对应的云函数。至此,我们的微信消息推送触发器的 Demo 就完成了。后续需要通过创建卡券,并核销卡券触发该触发器:四、创建优惠券接下来我们来制作微信卡券,制券成功并核销后,将触发我们上面创建的触发器。1.制券 微信公众平台 -> 卡券功能 -> 优惠券,点击「新建优惠券」,并选择卡券类型填写卡券详细信息注意选择合适的「核销方式」,这关联到后面的核销可以采用那种方式进行核销。填写好相关信息点击下一步进入到「使用设置」,填写相关信息后提交审核。2.投放 审核通过后即可投放,支持二维码、公众号图文、群发、摇周边等多种形式投放,这里选择下载二维码进行投放消费者扫码领取优惠券,点击「立即使用」显示优惠券码,如果在第三步中「使用设置」的「核销方式」选择了二维码或者条形码,则会以优惠券的二维码或者条形码 + 卡券号的形式显示,本教程选择了「仅卡券号」,所以只显示了卡券号。核销(三种方式)手机核销网页核销自助核销本教程采用手机核销,其他核销方式可在「微信公众平台」查看。添加核销员之前,店员需要先关注「卡券商户助手」公众号,才能配置核销权限:添加核销员 添加核销员后,店员可在「卡券商户助手」中选择「扫一扫核销」|者「卡券号核销」五、查看触发器触发日志卡券核销之后,可以在知晓云控制台 -> 引擎 -> 触发器,找到之前创建的触发器( user_consume_card ),查看日志。触发器调用云函数,并且创建了一条核销记录,可在数据表中找到该表进行查看。本文首发于「知晓云」公众号:https://mp.weixin.qq.com/s/_w…如果你还想了解 更多小程序开发技巧,快速掌握小程序开发能力。欢迎扫描下方二维码关注「知晓云」,我们会持续为更新与小程序有关的实战教程哦~

December 29, 2018 · 1 min · jiezi

Flutter 插件开发:以微信SDK为例

就像 React Native 一样,在 Flutter 应用中,如果需要调用第三方库的方法或者有一些功能需要使用原生的开发来提供,使用 Flutter Plugin 是一种不错的方式,它本质上就是一个 Dart Package,但与其它的 package 不同点在于,Flutter 插件中一般都存在两个特殊的文件夹:android 与 ios,如果需要编写Java、Kotlin或者 Object-C 以及 Swift 代码,我们就需要在这两个文件夹项目中进行,然后通过相应的方法将原生代码中开发的方法映射到 dart 中。本文以开发一个微信插件为例,为Flutter应用提供微信分享、登录、支付等功能,项目代码可以直接在下方找到,也已经提交至Pub库:原文地址:https://pantao.onmr.com/press/flutter-wechat-plugin.htmlPub库:https://pub.dartlang.org/packages/wechat项目地址:https://github.com/pantao/flutter-wechat创建插件目录要开发插件,可以使用下面的代码快速基于 plugin 模板开始:flutter create –template=plugin wechat上面的代码中,表示以 plugin 模板创建一个名为 wechat 的 package,创建完成之后,整个项目的目录结构就都提供好了,并且官方还提供了一些基本开发示例。目录结构- android // Android 相关原生代码目录- ios // ios 相关原生代码目录- lib // Dart 代码目录- example // 一个完整的调用了我们正在开发的插件的 Flutter App- pubspec.yaml // 项目配置文件从 example/lib/main.dart 开始在开发我们的应用之后,先来了解一下 flutter 为我们生成的文件们,打开 example/lib/main.dart,代码如下:import ‘package:flutter/material.dart’;import ‘dart:async’;import ‘package:flutter/services.dart’;import ‘package:wechat/wechat.dart’;void main() => runApp(MyApp());class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState();}class _MyAppState extends State<MyApp> { String _platformVersion = ‘Unknown’; @override void initState() { super.initState(); initPlatformState(); } // Platform messages are asynchronous, so we initialize in an async method. Future<void> initPlatformState() async { String platformVersion; // Platform messages may fail, so we use a try/catch PlatformException. try { platformVersion = await Wechat.platformVersion; } on PlatformException { platformVersion = ‘Failed to get platform version.’; } // If the widget was removed from the tree while the asynchronous platform // message was in flight, we want to discard the reply rather than calling // setState to update our non-existent appearance. if (!mounted) return; setState(() { _platformVersion = platformVersion; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text(‘Plugin example app’), ), body: Center( child: Text(‘Running on: $_platformVersion\n’), ), ), ); }}这里需要特别注意的就是 initPlatformState() 方法中对 Wechat.platformVersion 的调用,这里面的 Wechat 就是我们的插件,platformVersion 就是插件提供的 get 方法,跟着这个文件,找到 lib/wechat.dart 文件,代码如下:import ‘dart:async’;import ‘package:flutter/services.dart’;class Wechat { static const MethodChannel _channel = const MethodChannel(‘wechat’); static Future<String> get platformVersion async { final String version = await _channel.invokeMethod(‘getPlatformVersion’); return version; }}在该文件中,可以看到 class Wechat 定义了一个 get 方法 platformVersion,它的函数体有点特别:final String version = await _channel.invokeMethod(‘getPlatformVersion’);return version;我们的 version 是通过 _channel.invokeMethod(‘getPlatformVersion’) 方法的调用得到的,这个 _channel 就是我们 Dart 代码与 原生代码进行通信的桥了,也是 Flutter 原生插件的核心(当然,如果你编写的插件并不需要原生代码相关的功能,那么,_channel 就是可有可无的了,比如我们可以写一个下面这样的方法,返回 两个数字 a 与 b 的和:class Wechat { … static int calculate (int a, int b) { return a + b; }}之后,修改 example/lib/main.dart 代码:class _MyAppState extends State<MyApp> { String _platformVersion = ‘Unknown’; // 定义一个 int 型变量,用于保存计算结果 int _calculateResult; @override void initState() { super.initState(); initPlatformState(); } Future<void> initPlatformState() async { String platformVersion; try { platformVersion = await Wechat.platformVersion; } on PlatformException { platformVersion = ‘Failed to get platform version.’; } if (!mounted) return; // init 的时候,计算一下 10 + 10 的结果 _calculateResult = Wechat.calculate(10, 10); setState(() { _platformVersion = platformVersion; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text(‘Plugin example app’), ), body: Container( padding: EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( children: <Widget>[ Text(‘Running on: $_platformVersion\n’), // 输出该结果 Text(‘Calculate Result: $_calculateResult\n’), ], ), ), ), ), ); }}支持原生编码提供的方法很多时候,写插件,更多的是因为我们需要让应用能够调用原生代码提供的方法,怎么做呢?Android 系统打开 android/src/main/java/com/example/wechat/WechatPlugin.java 文件,看如下代码:package com.example.wechat;import io.flutter.plugin.common.MethodCall;import io.flutter.plugin.common.MethodChannel;import io.flutter.plugin.common.MethodChannel.MethodCallHandler;import io.flutter.plugin.common.MethodChannel.Result;import io.flutter.plugin.common.PluginRegistry.Registrar;/** WechatPlugin /public class WechatPlugin implements MethodCallHandler { /* Plugin registration. / public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), “wechat”); channel.setMethodCallHandler(new WechatPlugin()); } @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals(“getPlatformVersion”)) { result.success(“Android " + android.os.Build.VERSION.RELEASE); } else { result.notImplemented(); } }}还记得上面提到的 getPlatformVersion 吗?还记得 _channel 那么,是不是在这里面也看到的对应的存在?没错, dart 中的 getPlatformVersion 通过 _channel.invokeMethod 发起一次请求,然后,Java 代码中的 onMethodCall 方法回被调用,该方法有两个参数:MethodCall call:请求本身Result result:结果处理方法然后通过 call.method 可以知到 _channel.invokeMethod 中的方法名,然后通过 result.success 回调返回成功结果响应。registerWith在上面还有一小段代码 registerWith,可以看到里面有一个调用:final MethodChannel channel = new MethodChannel(registrar.messenger(), “wechat”);channel.setMethodCallHandler(new WechatPlugin());这里就是在注册我们的插件,将 wechat 注册成为我们的 channel 名,这样,才不会调用 alipay 插件的调用最后到了 wechat 插件这里。iOS 系统同样的,这次我们打开 ios/Classes/WechatPlugin.m 文件:#import “WechatPlugin.h”@implementation WechatPlugin+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>)registrar { FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@“wechat” binaryMessenger:[registrar messenger]]; WechatPlugin* instance = [[WechatPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel];}- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@“getPlatformVersion” isEqualToString:call.method]) { result([@“iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); } else { result(FlutterMethodNotImplemented); }}@end虽然语法有所不同,但是,可以看到,跟 android 的 Java 代码结构上几乎是一模一样的,首先 register 一个名为 wechat 的 channel,然后去 handleMethodCall,同样的通过 call.method 拿到方法名,通过 result 做出响应。小试牛刀接下来,我们将前面的 caculate 方法,移到原生代码中来提供(虽然这很没必要,但毕竟,只是为了演示嘛)。Android在前面打开的 android/src/main/java/com/example/wechat/WechatPlugin.java 文件中,修改 onMethodCall 方法: @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals(“getPlatformVersion”)) { result.success(“Android " + android.os.Build.VERSION.RELEASE); } else if (call.method.equals(“calculate”)) { int a = call.argument(“a”); int b = call.argument(“b”); int r = a + b; result.success(”” + r); } else { result.notImplemented(); } }添加了 call.method.equals(“calculate”) 判断,这里面具体的过程是:调用 call.argument() 方法,可以取得由 wechat.dart 传递过来的参数计算结果调用 result.success() 响应结果然后,我们需要在 lib/wechat.dart 中修改 calculate 方法的实现,代码如下: static Future<int> calculate (int a, int b) async { final String result = await _channel.invokeMethod(‘calculate’, { ‘a’: a, ‘b’: b }); return int.parse(result); }由于 _channel.invokeMethod 是一个异步操作,所以,我们需要将 calculate 的返回类型修改为 Future,同时加上 async,此时我们就可以直接使用 await 关键字了,跟 JavaScript 中的 await 一样,让我们用同步的方式编写异步代码,在新版的 calculate 代码中,我们并没有直接计算 a+b 的结果,而是调用 _channel.invokeMethod 方法,将 a 与 b 传递给了 Java 端的 onMethodCall 方法,然后返回该方法返回的结果。_channel.invokeMethod该方法接受两个参数,第一个定义一个方法名,它是一个标识,简单来说,它告诉原生端的代码,我们这次是要干什么,第二个参数是一个 Map<String, dynamic> 型数据,是参数列表,我们可以在原生代码中获取到。接着,我们需要更新一下对该方法的调用了,回到 example/lib/main.dart 中,修改成如下调用:_calculateResult = await Wechat.calculate(10, 10);因为我们现在的 calculate 方法已经是一个异步方法了。iOS如果我们的插件需要支持 Android 与 IOS 两端,那么需要同步的在 ios 中实现上面的方法,打开 ios/Classes/WechatPlugin.m 文件,作如下修改:- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSDictionary arguments = [call arguments]; if ([@“getPlatformVersion” isEqualToString:call.method]) { result([@“iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); } else if ([@“calculate” isEqualToString:call.method]) { NSInteger a = [arguments[@“a”] intValue]; NSInteger b = [arguments[@“b”] intValue]; result([NSString stringWithFormat:@"%d”, a + b]); } else { result(FlutterMethodNotImplemented); }}实现过程与 java 端保持一致即可。添加第三方 SDK我们的插件是可以提供微信的分享相关功能的,所以,肯定需要用到第三方SDK,还是从 Android 开始。Android 端 WechatSDK按 官方接入指南 所述,我们需要添加依赖:dependencies { compile ‘com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+’}或dependencies { compile ‘com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+’}前者带有统计功能,这很简单,打开 android/build.gradle 文件 ,在最下方粘贴以上片段即可:…android { compileSdkVersion 27 defaultConfig { minSdkVersion 16 testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner” } lintOptions { disable ‘InvalidPackage’ }}dependencies { compile ‘com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+’}然后,回到 WechatPlugin.java 文件,先添加一个 register 方法,它将我们的Appid 注册给微信,还是接着前面的 onMethodCall 中的 if 判断:…import com.tencent.mm.opensdk.openapi.WXAPIFactory;… else if (call.method.equals(“register”)) { appid = call.argument(“appid”); api = WXAPIFactory.createWXAPI(context, appid, true); result.success(api.registerApp(appid)); }…然后回到 lib/wechat.dart 添加相应调用:… /// Register app to Wechat with [appid] static Future<dynamic> register(String appid) async { var result = await _channel.invokeMethod( ‘register’, { ‘appid’: appid } ); return result; }…此时,在我们的 example 应该中,就可以调用 Wechat.register 方法,来注册应用了ios按照官方 ios 接入指南所述,我们可以通过 pod 添加依赖:pod ‘WechatOpenSDK’打开 ios/wechat.podspec ,可以看到如下内容:## To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html#Pod::Spec.new do |s| s.name = ‘wechat’ s.version = ‘0.0.1’ s.summary = ‘A new flutter plugin project.’ s.description = <<-DESCA new flutter plugin project. DESC s.homepage = ‘http://example.com’ s.license = { :file => ‘../LICENSE’ } s.author = { ‘Your Company’ => ’email@example.com’ } s.source = { :path => ‘.’ } s.source_files = ‘Classes/**/’ s.public_header_files = ‘Classes//*.h’ s.dependency ‘Flutter’ s.ios.deployment_target = ‘8.0’end留意到数第三行的 s.dependency,这就是在指定我们依赖 Flutter,如果有其它依赖在这里添加一行即可:… s.public_header_files = ‘Classes//*.h’ s.dependency ‘Flutter’ s.dependency ‘WechatOpenSDK’ s.ios.deployment_target = ‘8.0’end然后打开 ios/Classes/WechatPlugin.h 文件,修改如下:#import <Flutter/Flutter.h>#include “WXApi.h”@interface WechatPlugin : NSObject<FlutterPlugin, WXApiDelegate>@end再回到 ios/Classes/WechatPlugin.m,接着前面的 if 条件继续添加判断:… // Register app to Wechat with appid else if ([@“register” isEqualToString:call.method]) { [WXApi registerApp:arguments[@“appid”]]; result(nil); }…此时,我们的插件已经支持微信 SDK 的 注册至微信 功能了,更多实现,本文就不再讨论,有兴趣,可以直接下载完整项目,后面都是大同小异的实现,唯一需要的是,你需要有一定的 Java 编码与 Objective-C 编码能力。 ...

December 20, 2018 · 5 min · jiezi

一张图清晰解释微信三方平台获取授权流程

背景微信公众平台体系,大家最为熟悉的,一个是公众号,另一个就是小程序。如果需要使用公众号的高级功能,那么大家首先想到的就是自组开发团队来干这些活儿。绝大多数情况下,公众号运营方会把这些工作外包出去;但是外包出去之后会有一个风险:账号的所有权总不能外包出去吧,万一有风险,我能够把外包工作收回。微信公众号体系原生支持这种 “外包” 思路,那就是微信第三方平台,公众号能够通过这套体系,将公众号的部分功能和权限开放给第三方。这在微信开放平台中可以找到相应的文档。与自有公众号开发不同,微信三方平台的授权体系是慎之又慎,笔者第一次接触的时候,被文档中提及的各种 token、各种 ticket 搞晕了,于是特意整理了这个流程,以图表的方式将授权流程说明下来,便于查阅。已经学习了授权流程的同学,可以直接将本文拉到最后面查看完整图。本文按照授权顺序,一步一步地说明。图中关键的 token 或 ticket 数据,均用彩色标出并一一对应。希望本文对微信三方平台开发者能够有所帮助。授权步骤三方平台数据准备微信三方平台也是在微信开放平台上的账号,也有 appid 的概念。在微信的文档中,三方平台称为 “component”。三方平台需要实现一个供微信回调的 URL,在平台中称为 “授权事件接收URL”(以下简称 “通知回调”),在应用详情页中进行配置。不要被这个名称误导了,其实所有和三方平台直接相关的事件都会经过这个 URL 通知。获取 component_access_token这里涉及流程中的两个术语:component_verify_ticket 和 component_access_token。微信会每十分钟往通知回调中发送一个消息,将参数 component_verify_ticket 告知三方平台后台。三方平台拿到这个消息后,则需要使用自己的 app_secret 和 appid 信息,加上微信推送的这个 ticket,通过微信三方平台的 api_component_token 接口,向微信平台换取 component_access_token。生成授权注册页面 URL让公众号点击授权有两种模式,一种是引导公众号所有者扫码进入一个授权页;另一种范式是在移动端点击链接来授权。两种方式对后台而言大同小异,本文讲解第一种。这里其实包含了两个小步骤:首先是三方平台后台向微信请求获得预授权码 pre_auth_code;第二步是使用这个预授权码,来组合成一个 URL 给公众号所有者扫码。关键的参数如下(componentloginpage 是用于扫码的 URL):公众号授权获取授权的公众号公众号扫码授权后,微信会向通知回调发送消息,除了告知授权的公众号(称为 “authorizer”)的 appid 之外,最重要的是推送一个新的票据字段 authorization_code,这个 code 是与授予权限的公众号绑定的:拉取公众号信息及其授予的权限这分别是两个 API,其中比较重要的是拉取公众号授予的权限范围,调用了接口 “api_query_auth”。除了获得授权范围之外,最重要的,是再引入两个新参数:authorizer_access_token:用在后文 “代公众号调用接口” 中,替代微信公众平台的 access_token 参数。authorizer_refresh_token:用于定时刷新 access_token两个 API 的调用图如下:代公众号实现业务刷新 authorizer_access_token前文提到,通过 API:api_query_auth 可以获得用于替代公众号的authorizer_access_token。有了这个之后,就可以代公众号中使用 access_token 的调用。同样地,这个 token 也有过期时间,因此三方平台需要调用 API,在 token 即将失效时刷新。使用这个 API 循环刷新即可:获取微信 JS-SDK 的 ticket微信 JS-SDK 接口使用的不是 access_token,而是被称为 jsapi_ticket的一个票据。普通的公众号使用 access_token 来换取,三方平台则使用 component_access_token 来换取:其实这个接口已经不是微信三方应用的范围了,只是普通的微信公众平台接口。但是因为非常常用,所以还是在这里说明了一下。授权流程总览<span id=‘总览’>上面所提及的各个分步骤,组合成一览图如下(图片比较宽,推荐大屏幕查看或者放大查看):<>图中各个调用过程的标题,是 API 的名称,可以作为关键字在微信文档中搜索。参考资料第三方平台概述JS-SDK使用权限签名算法,搜索 “JS-SDK使用权限签名算法”本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。本文地址:https://segmentfault.com/a/1190000017402102。原文发布于:https://cloud.tencent.com/developer/article/1368038,也是本人的专栏。 ...

December 17, 2018 · 1 min · jiezi

微信小程序解除 10 个请求并发限制了?!

这可能是一个冷消息,所以标题比较劲爆。小程序并发限制由来已久,从刚发布时的 5 并发,到后来的 10 并发,同时发出的请求数若超出这个限制则将被残忍抛弃,由此催生了很多开发者在自己的项目中造了「请求排队」的轮子。然而事实上,早在一年半以前,该限制就被微信官方取消。10 个请求的并发限制关于并发限制,微信开发者文档中是这么写的:这一限制的意思是在同一时刻, wx.request、wx.uploadFile、wx.downloadFile 加起来的并发总数不能超出 10 个。至今,仍有很多开发者一直遵守着这个规则。许多人在写业务的时候小心翼翼地维护着请求数。为了将请求数控制好,特地将一些并行请求改为串行,或者引入请求队列来维护小程序请求。这部分资深开发者为了遵守这一规则所花的功夫,多少反映出了早年他们在面对数额超出后请求被残忍抛弃时的无奈。附小程序基础库版本 1.3.0 的控制台报错:时至今日,仍有开发者在讨论解决小程序并发限制的方法:被忽略的消息实际上,微信在 2017 年 7 月的基础库 1.4.0 版本升级中就做了优化,对超过并发限制的请求做了队列处理,只是还有很多开发者并不知道这一消息。从严格意义上来说,此次优化并没有完全解除原有的并发限制。目前同时处理请求的上限仍是 10 个,但在 10 个以外的请求会排队,当前面有请求完成的时候,队列中的请求按顺序发送并处理,*不会像之前那样直接将超出 10 个的请求丢弃。附件小程序基础库 1.4.0 更新日志(部分):现在,我们终于可以忽略请求并发限制,愉快地发送请求了。毕竟请求都是可以都发送出去的,只不过在效率上会比无并发限制的情况慢一些。发送请求的正确姿势如上文所说,微信小程序是在基础库 1.4.0 版本中加入对超过并发限制的请求做队列处理优化的,在 1.4.0 以下的版本中超出并发部分的请求会被丢弃。据微信官方数据,截止到 2018 年 12 月,1.4.0 版本以下用户占比大约是 0.04%,虽然目前小程序很少会兼容到这么低的版本,但是对一些有特殊需要的小程序也要注意基础库的差异。另外要注意的是小程序并发请求的排队机制。当同时调用的请求超过 10 个时,小程序会先发起 10 个并发请求,超过 10 个的部分按调用顺序进行排队,当前一个请求完成时,再发送队列中的下一个请求。附 20 个请求并发测试:测试结果:从图中可以看到,前 10 个请求同时发出,而后面的请求的起始点,对应了前面某个请求的结束点,可以反映出请求的排队行为。这意味着,在并发请求很多的时候应该做好排队策略,按请求的重要程度和响应时间调整调用顺序,如果遇到请求的响应很慢的情况,可以考虑做 timeout 处理,以免大量等待,影响用户体验。搜索关注公众号「知晓云」,让你的小程序开发快人一步。

December 12, 2018 · 1 min · jiezi

微信小程序之登录态的探索

上一篇:开发微信小程序必须要知道的事登录,几乎什么项目都会用到,其重要性不言而喻,而小程序的登录却一直是为人头疼的一件事,这里我分享下我们在小程序登录上的探索通常的登录都是通过一个表单,这很正常,但如果在小程序里你也这么做那就有点不可思议了,微信的一键登录对用户体验有多好你难道不知道?不用是不是脑子有坑?最主要——你要利用微信的生态必须需要用微信的登录,以获取相关信息来和微信交互,OK,我们进入正题用户在小程序、小游戏中需要点击组件后,才可以触发登录授权弹窗、授权自己的昵称头像等数据友情提示一下:wx.login并不需要点击组件,需要的是wx.getUserInfo,但通常我们都会用到UnionID、encryptedData、iv等信息完成完整的登录流程,本文主要聚焦的也是这种场景所以之前直接通过调用API的方式就行不通了,那么问题来了——这个点击按钮要放到哪里?放到首页,一进小程序就必须先登录。这样显然很粗暴,而且问题并没有解决,反而会把用户直接拒之门外,毕竟你不是用小程序做后台系统,什么场景都需要授权,先授权也是必须的在需要授权的时候跳到登陆页面。这样就解决了上面遇到的不需要授权的时候也被强制授权,可是这样好吗?体验上不好,操作被打断,尤其整个页面都不需要授权只有在一个地方需要授权的,例如:你正在读一篇文章,读罢深有感触,想评论一番,洋洋洒洒几十字写完正准备点击呢,他妈的跳转了!跳转了!又一个漏斗,增加用户流失率。还TM要登录!很多用户心里一定这么想那就直接放在需要登录的页面上(这不是漏斗吗?很多读者一定这么想。但想想看上面那个场景,点评论时只是需要点击下弹出的登录按钮,而且还假模假样的以微信的口吻提醒你需要登录,那你会不会登录?最起码你很愿意登录,而且来的很突然,我控几不住自己的手就点了!点了!) 可是这种方式有一个问题怎么在需要的页面都能弹出登录按钮应该很多人都能想到:抽离出组件,那怎么保证在需要的页面都有这个组件呢?错杀一千也不能放过一个!把登录组件集成到共用的父组件,然后在每个页面都使用。我也建议这么做,因为这个共用的父组件其实又很多用处,例如iPhoneX适配等 等等,什么都准备好了,什么时候需要登录呢?XX,这个肯定是你自己控制的啦。嗯~好吧,我们来理一理在哪里校验是否需要鉴权请求接口的时候,嗯~这是大家的共识BOSS来了怎么鉴权官方的这张图已经做了很详尽的说明,这里不做赘述 但是看到session_key了吗?看到官方同时说的了吗所以问题又来了怎么保证session_key的有效性诚如上图要保证调用接口时后端session_key不失效,只能在每次调用前先使用wx.checkSession检查是否有效实践中也发现wx.checkSeesion非常耗时,大约200ms,所以也不能每次接口调用前都使用wx.checkSession检查是否有效同时要注意⚠️前端不能随便重新执行wx.login,因为可能导致正在进行的其它后端任务session_key失效天啦噜,怎么办?! 通过实践和偶然的发现——官方的示例代码 得知:在使用小程序期间session_key是不会失效的,so,你想到了什么?在每个请求前去校验有效性将校验有效性的结果存储起来通过async/await和刚才存储起来的结果来保证不过多调用wx.checkSession先问个问题:你准备用什么方式来存储校验的结果?。。。让思考先飞一会。。。。。。。。。。。。。。。。。。。。。。。。。。。storage吗?当然可以,不过不够完美,为什么?因为storage是永久的存储,而session_key的有效期却只是在使用小程序期间,所以你需要在小程序结束后手动重置该状态以重新校验其有效性,那是不是在app的onUnload里重置呢?不是!开发过小程序的应该都知道,那就是结束使用小程序的方式太多,不能保证每种方式都会触发onUnload,例如用户直接销毁了微信进程????(其实你也可以在app的onShow里搞)那用什么呢?直接用内存啊,借助内存的自动管理来智能管理,所以最终代码应该是这样的// doRequest.jslet wxSessionValid = null // 微信session_key的有效性// 带鉴权的请求封装async function doRequestWithCheckAuth() { … if (typeof wxSessionValid !== ‘boolean’) { wxSessionValid = await checkWxSession() // 检查微信session是否有效 } if (!wxSessionValid) { await reLogin() // 重新登录 } wxSessionValid = true // 重新登陆后session_key一定有效 …}这样是不是看起来比较完美了?嗯~不知道有没有同学着急问业务侧的session(自定义的登录态)怎么没讲?嗯,那现在讲吧怎么校验完整的认证体系其实很简单,都不想把它作为一部分来讲,但既然讲了就必然有我想强调的校验微信端的session_key略有麻烦,但不应该把它抛给服务端服务端不能直接校验session_key的有效性而是通过调用接口发现错误了才知道失效了,这是被动的服务端需要同时维护两个session而放在前端我们只需要校验两个session的有效性即可,任何一个失效就重新登录,这是积极主动有效的操作,应该被提倡贯通OK,基本上梳理的差不多了,就差弹登录按钮了,这个简单,调用刚才封装的组件的方法就行了嘛,bingo,可是,点完允许后呢?怎么继续用户的操作呢?怎么能让用户的体验不被打断呢?先回放下刚才reLogin的代码async function reLogin() { // 确保有用户信息 await new Promise(resolve => { // ⚠️注意开头有await!!! wx.getSetting({ success: (res) => { // 如果用户没有授权或者没有必要的用户信息 if (!res.authSetting[‘scope.userInfo’] || !_.isRealTrue(wx.getStorageSync(‘userInfoRes’).userInfo)) { navToLogin(resolve) // 去提示用户点击登录按钮,⚠️注意:并把当前的resolve带过去 } else { resolve() // 静默登录 } } }) }) return new Promise((resolve) => { wx.login({ success: res => { login(res.code).then((jwt) => { resolve(jwt) // resolve jwt }) // 通过code进行登录 }, fail(err) { wx.showToast({ title: err.errMsg, icon: ’none’, duration: 2000 }) } }) })}function navToLogin(resolve) { /* eslint-disable no-undef */ const pages = getCurrentPages() const page = pages[pages.length - 1] // 当前page page.openLoginModal(resolve) // 打开登录按钮弹框,并把当前的resolve带过去}上面的代码注释里有两个⚠️注意看到没?是的,通过回调的方式????当用户同意授权了就继续余下的逻辑,如果被拒绝了,则安利他,再拒绝就终止操作,下次需要授权也会继续弹出授权 有不明白欢迎评论留言指出,我再做说明修改完整源码以后会放出的,通过wepy搭建的一个框架 下一篇会讲api的封装谢谢 ...

November 16, 2018 · 1 min · jiezi

开发微信小程序必须要知道的事

为什么是小程序?为什么我们会开发小程序呢?或许是因为工作需要,或许是源于自己的追求(来自名利的欲望),但我要说——这是一种缘分,很美好的缘分,很多年后还值得庆幸的缘分小程序目前可以分为三个阶段一是语音和摇一摇(还有yue pao利器的传说)二是公众号,也就是这时注定了小程序的出现是历史的必然选择(shihouzhuge),为什么这么说呢?因为微信在开放了webview的同时加入了js-sdk的开发工具包,而这就是小程序的前身三就是当下的微信os,能跑小程序的微信我们继续来说说第二点,有了js-sdk不就可以了吗?不就可以打通微信了吗?还要什么小程序?!可是人家是有梦想的鹅厂啊!!?先从技术上说,js-sdk只是为传统网页提供包含微信api的开发工具包,并没有解决移动网页遇到的体验不良问题,所以小程序就做了资源离线存储,提高加载速度提供更强的开放能力通过构建组件系统实现对安全性的管控通过内置实现的组件提高开发速度和降低开发成本开放入口啊,不像网页只能通过链接打开加上其他XXX就搞出了比拟原生的体验(chadebushao)微信的梦想上说作为一个月活超10亿的超级app,人口红利已达天花板,所以现在开始打时长红利的主意,拓展微信的使用场景,拓宽微信的边界马化腾亲口说过:从消费互联网到产业互联网,随着产业互联网时代的到来,我们也在“连接产业”上寻求突破,而小程序就是连接产业互联网的“利器”(听不懂什么玩意)赚钱啊!鹅厂的梦想啊!做什么样的小程序?回到上面的引子,为什么值得庆幸?因为上面说了——能降低开发成本,能提高用户体验,能褥流量!能让每个人都有机会搞事情!!!搞什么呢?我也不知道,如果你有好的想法欢迎联系我????不过,这里分享下我的想法战线不能长!张小龙说过——“用完即走”,其实这不是原因,只是我调个书袋而已????但微信确实是这样啊,聊天、朋友圈,好像确实没什么太丧心病狂的一直拉着你不走(好像现在还有看一看?始终记住——鹅厂有梦想!)所以你做的东西要符合人家产品的思想!好吧,说个实际的场景,如果你要在小程序里的创作文章,那别人来一条信息你回不回?不回?你的思绪回不回被打乱?不会?你的心情会不会很烦?不会?????你赢了,来生再见,所以权衡好你的功能设计,战线越长越容易死短!平!快! 对应上面。就是快速进入主题,功能点一目了然,功能也尽量单一,该干什么尽快干什么!例如让用户分享,让用户pay,快!快!快!不犹豫,让用户尽快上车做矩阵,对应上面,如果你想把app的功能都搬到小程序上,那一定要做功能拆分,做多个小程序形成矩阵小程序os长什么样?想种一个小程序总先知道这片土地什么样吧?OK,欢迎来到小程序黑土地。。。首先我们先说说小程序用到的两个线程——渲染线程、脚本线程,与网页开发不同,这两个线程是分开的,分别运行在不同的线程中,而网页则是互斥的,也就是说视图和脚本被分开了,在不同的线程里,这就导致了和普通网页开发一个很大的不同——没有DOM API,而且也不是运行在浏览器里所以也没有BOM API我们再看图还发现小程序总共有三种运行环境,并且!每个环境两个线程还都不一样!所以同学们啊,在开发工具上行的在真机上不一定行啊!可别太天真了,一定要在真机上验证功能的可行性!我们有没有想过小程序为什么要费那么大功夫重造轮子?不直接用成熟的web技术?说是体验,其实我觉得最主要的是微信想管控一切,不是你想怎样就可以怎样,而是我让你怎样,你才能怎样(至今我还对此很不爽)所以就干掉了灵活的web,别被我带沟里——小程序并不是完全没有了web,实际上你看到的就是web,只是没有暴露出来,而是微信直接通过编译小程序来替你操作了既然是两个线程,那必然要通信啊,要协作完成任务,那怎么实现的呢?看下面的通信模型 没看到图也能先想到是这个样式????,这里提示几点上图中的Native是指微信客户端逻辑层发送网络请求也经由Native转发渲染层是由多个webview组成的,为什么?为了提供更好的交互体验呀,这样也更贴近原生体验,同时避免了单个WebView的任务过于繁重,同时导致了小程序的生命周期不容易被理解(下面带你理解)通信是有时间成本的,所以在开发中我们最好使用异步接口来看下生命周期 其实了解了渲染层是由多个webview组成的就很容易理解生命周期了navigatebBack是返回上一个webview,销毁当前的webviewnavigateTo是打开承载新页面的webview,同时保留老的webviewredirectTo是在当前webview里打开新的页面左下角有两张拼一起的图是switchTab的Tabbar页面初始化之后不会被销毁!所以Tabbar页面不会unLoad,更多请参阅图片有用请点赞????下一篇文章将讲讲小程序的登录态????

November 15, 2018 · 1 min · jiezi