李建华 夏 汛 罗明全
(泸州职业技术学院信息工程系 四川 泸州 646600)
随着信息技术的发展,互联网在给我们提供丰富信息的同时也给我们生活带来了巨大改变,信息时代下人们已经离不开互联网。与此同时,类似于QQ、微信、微博等社交网络应用充斥着整个互联网,其中以微信应用最为典型。微信公众号是微信公众平台的简称,利用微信公众号可以进行自媒体活动,用户可在微信平台上和目标受众用户用语言、文字、视频等多种方式进行交流,形成一种微营销方式,使得微信公众号在消息互动的同时也在传递着商业价值。
据《2017 年微信经济数据报告》可知,截至2017年底微信公众号已超过1 000万个[1],微信公众号已经成为了公司对外宣传的标配。公司通过微信公众号向受众用户及时推送消息和业务,极大地拉近了企业和用户之间的距离;用户通过关注企业微信公众号,可以充分了解企业业务动向和产品文化。
微信公众号基础平台已经为用户提供了一些基本的功能,如文章推送、粉丝管理等。但是对于部分特殊功能,则需要开发者自行扩展。微信公众号平台已将自带的资源服务封装成接口,这就使得二次开发成为可能。同时随着粉丝数量越来越庞大,一些类似抽奖、抢红包等高并发功能的二次开发成为必要。
本文以某企业为例,为了有效地对外宣传其企业文化,扩大市场知名度,需要每位员工发现身边的新鲜事,并在信息平台上发布文章,经过领导审核之后,通过微信公众号推送出去。其他员工对该文章进行转发,本公司员工以外的粉丝阅读/转发之后,该员工获得相应的积分(本公司员工之间相互阅读/转发不积分)。同时系统需要具有统计功能,管理员能够按照时间段、部门、转发文章数量进行统计,以作为季度考核的指标。同时该公司员工规模庞大(几千人),可能会出现同一时刻多人阅读/转发同一篇文章,并发度要求高。通过分析,发现微信公众号平台自带的功能并不能满足用户需求,需要对微信公众号平台进行扩展。本文针对用户需求,采用开源框架ThinkPHP和Redis缓存技术,快速搭建一套高并发的微信公众号二次开发平台。测试结果表明,采用ThinkPHP框架能够减少开发难度和缩减开发周期,采用Redis缓存技术能够实现1 000人左右的并发数,基本上能满足用户需求。本文介绍的开发技术具有通用性,在快速搭建高并发的微信公众号二次开发方面有一定的借鉴意义,能够节约开发时间和成本。
ThinkPHP是一款基于Apache2开源协议的Web应用程序框架,自从2006年诞生以来,就受到了开发者的极大关注[2]。其作为轻量级的PHP框架,目前发展迅速,已经是三大主流框架之一(Laravel、Yii和ThinkPHP)。ThinkPHP采用MVC(Model-View- Controller)开发模式思想,将传统的混杂开发模式转变成界面显示,逻辑控制和数据处理三层分离,每层专注于自己的开发,并形成模块化的程序块,代码重用度较高。各层之间耦合度较低,仅仅通过简单的接口进行数据流通。MVC也是现在较为主流的开发模式,它使得Web开发更简单快捷,能够实现一处开发,多处使用的效果,在各个开发语言中都能见到其身影,比较适合大中型项目应用的开发。
Redis缓存也是缓存的一种形式,它是一个开源的基于内存存储的数据库,可支持如链表、集合等多种数据类型[3]。可以使用简单的Redis命令快速处理大量的数据,实现对数据高并发读写。传统的Web数据库数据一般存储在硬盘中,当同时有大量用户操作数据库时,数据库的连接池会承受较大的压力,并且读写硬盘需要较大的IO开销,带来较大的访问延迟,用户的体验感会大打折扣。Redis是一个内存数据库,数据存储在内存,所以相比传统的数据库技术,内存数据库在读写速度方面更有优势。根据官网测试数据显示:在Linux2.6下,50个并发进程执行100 000次请求,其读取速度在十万次/秒左右,写入速度在八万转/秒左右。由此可见,Redis读写效率较高。
考虑到建站的快速性和访问的并发性,本文采用ThinkPHP和Redis技术相结合,实现一个高并发的微信公众号二次开发框架,供读者参考。本系统根据用户角色划分不同权限控制,主要分为前台模块和后台两个模块。后端模块主要采用开源框架Hui-admin[4]实现对用户、文章、组织架构等相关数据管理,所有的操作都在PC端完成。同时实现对关系数据库MySQL和内存数据Redis的管理。前端主要实现文章管理和用户个人信息管理,通过与企业服务号对接,实现微信公众号平台和第三方服务器之间数据流通。前端和后端通过Redis实现快速交互,整个架构如图1所示。
图1 系统架构图
2.2.1 微信公众号与第三方系统对接
在微信公众号二次开发过程中,微信官方平台服务器其实就是一个消息转发器,所有的用户请求和第三方服务器的响应都经过微信服务器进行转发[5],数据流如图2所示。
图2 微信公众号二次开发数据流
(1) 用户在微信终端向微信公众号发送一条消息(发起请求)。
(2) 微信公众号服务器接收到该消息之后,将此消息转发给第三方服务器(转发请求)。
(3) 第三方服务器解析公众号服务器发来的请求,并将响应数据打包成消息,返回给公众号服务器(响应请求)。
(4) 用户收到响应数据之后,按照既定格式展现到页面上(转发响应)。
首先在通过认证的微信公众号(服务号)页面添加菜单项,然后将该菜单和本平台入口文件(URL)进行绑定。在公共配置文件/Thinkphp/Application/Common/Config下的config.php文件中配置微信公众号相关参数。其中WX_APPID是微信公众号中第三方用户唯一凭证,相当于第三方系统接入的微信平台的登录用户名。
return array(
//mysql数据库定义
′DB_TYPE′=> ′mysql′,
′DB_HOST′=> ′localhost′,
′DB_NAME′=> ′db_xxxx′,
′DB_USER′=> ′root′,
′DB_PWD′=> ′123456′,
′DB_PORT′=> 3306,
//端口号一般3306
′DB_PREFIX′=> ′tb_′,
//表前缀
…
′WX_APPID′=> ′xxxxxx′,
//微信公众号ID
′WX_SECRET′=>′xxxxxx′,
//应用密钥
′WEB_URL′=> ′http://xxx.com′,
//第三方系统
…
);
?>
2.2.2 获取微信用户相关信息
微信公众号平台使用OAuth 2.0开放协议进行身份认证,该协议允许第三方应用获取该用户在某一网站上存储的私密资源,无需使用用户名和密码进行身份验证,微信公众号使用OAuth 2.0进行验证一般需要三步(类似于三次握手协议):
第一步:用户经过第三方服务器向微信公众号服务器发送一个访问授权请求(该请求附带WX_APPID、重定向地址、响应消息类型等参数),该请求到达微信服务器之后,微信服务器解析该请求,将当前的URL重定向到第三方服务器,并向第三方服务器返回该用户的CODE值。
第二步:第三方服务器收到该CODE值之后,将CODE、WX_SECRET和WX_APPID等作为参数,再次向微信公众号服务器发起请求,请求返回access token参数。
第三步:第三方服务器收到该access token参数之后,将access token和WX_APPID等作为参数,最后一次向微信公众号平台发起请求,请求该用户在微信平台上存储的私密信息(如微信号、头像、性别等)。整个过程如图3所示。
图3 OAuth 2.0验证流程图
在OAuth 2.0验证过程中,使用到了curl_post()方法。该方法是一个访问HTTP协议接口,用来取得某URL对应的页面内容。核心代码实现如下:
$ch=curl_init();//初始化CURL
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
//屏蔽检测
curl_setopt ($ch, CURLOPT_URL, $url);
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 120);
//设置请求超期时间
file_contents=curl_exec($ch);
//获取URL内容
curl_close($ch);
//关闭连接
file_contents变量中存储的就是ch变量(URL)对应页面内容打包的JSON数据,再经过json_encode方法将JSON数据转化,即可得到用户信息数组。
第三方服务器经过授权之后,能够得到用户的OpenID和相关的信息,将OpenID值写进Redis缓存数据和MySQL数据库的字段,表明该用户已经和第三方系统进行了绑定,可以直接和第三方服务器进行数据交换。
2.2.3 Redis安装与配置
Windows环境下,安装Redis后,需要手动添加php的redis拓展。首先到Redis官网下载php_igbinary.dll和php_redis.dll两个库文件,然后在php.ini文件中新增 extension=php_igbinary.dll和extension= php_redis.dll两个扩展,实现PHP解析器对Redis的支持。
Redis一般是通过命令来操作数据,为了简化缓存读写操作,ThinkPHP把所有的缓存机制封装成了一个S方法,该方法使用简单,跟Session使用类似。通过封装成方法,用户不必关注数据操作的具体细节,而重在注重业务处理逻辑,大大提高开发效率。安装好Redis之后,在/Application/ Common/Common/ function.php中配置Redis数据库,如下所示:
function REDISCONFIG(){
//数据缓存配置
REDIS=S(array(′type′=>′redis′,′host′=> ′127.0.0.1′, ′port′=>′6379′,′prefix′=>′lz_′,));
return $ REDIS;
}
2.2.4 文章转发操作
本系统需要记录用户文章转发之后的阅读量,该阅读量需要记录在首次转发者的记录中。所以需要在文章内容页面记录转发数据(文章ID以及转发者的OpenID),这里使用微信JS-SDK提供的接口。
为了能准确统计文章转发数量,转发时需要使用微信JS-SDK获取signature签名。需要注意的是,这里要求提前得到jsapi_ticket,它是公众号调用微信JS接口时使用的一种网络凭证[6]。正常情况下,jsapi_ticket的有效期为7 200秒,通过access_token来获取。
if(S(′ticketStr′)){//如果ticketStr存在
$ticketStr=S(′ticketStr′);
}else{
$accessToken=$this->curl_post(″https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=″.C(′WX_APPID′).″&secret=″.C(′WX_SECRET′));
$accessTokenArr=json_decode(accessToken);
$accessTokenStr=$accessTokenArr->access_token;
$ticketJson=$this->curl_post(″https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=″.$accessTokenStr.″&type=jsapi″);
$ticketArr=json_decode($ticketJson);
$ticketStr=$ticketArr->ticket;
S(′ticketStr′,$ticketStr,7100);
}
$str=″jsapi_ticket=″.$ticketStr.″&noncestr=XXXXXXX
XXXXXXXX×tamp=1421142450&url=http://xxxx.xxxx.com″.$_SERVER[″REQUEST_URI″];
$signature=sha1($str);
$this->assign(′signature′,$signature);
接下来,在文章详情页面使用signature签名,进行文章转发操作步骤。
(1) 在文章内容页面引入jweixin-1.2.0.js文件。
(2) 所有需要使用JS-SDK的页面必须先进行配置,配置文件如下所示:
wx.config({
debug: true,
appId: ′{:C(′WX_APPID′′)}′,
timestamp: 1421142450,
nonceStr: ′XXXXXXXXXXXXXXXX′,
// 必填,生成签名的随机串
signature: ′ {$signature} ′,
// 必填,微信加密签名
jsApiList: [
′onMenuShareTimeline′,
′onMenuShareAppMessage′,
′onMenuShareQQ′,
]
});
(3) 分享接口实现:在这里我们使用三种分享方式:“分享到朋友圈”、“分享给朋友”和“分享到QQ”,这三种分享方式在实现上异曲同工,其中“分享到朋友圈”接口实现逻辑如表下所示。分享之后第三方服务器需要拿到被分享文章的id和被分享人的OpenID。
wx.onMenuShareTimeline({
title: ′{$info.title}′,
link: ″,
imgUrl:′{:C(′WEB_URL′)}/Public/{$info.cover}′,
success: function () {
var $arid=$.trim($′#ar_id′).val());
var $weid=$.trim($′#we_id′).val());
$.post(′{:U(′Article/forwarded′)}′,
{arId:$arid,weId:$weid},function(){});
},
cancel: function () {
}
});
2.2.5 Redis数据流的控制
(1) Redis数据流 首先在PC后台,用户发布文章,管理员进行审核,审核通过之后将文章ID和文章内容分别写进articleID和articleContent_ar_id表中。articleContent_ar_id表在高并发环境下用户方便快速读取文章内容并呈现,articleID表方便在文章修改时判断文章是否已存在。
在微信前端,首先判断用户是否已绑定自己的微信号,如果没有绑定,则通过注册方式绑定。在注册的同时将自己的工号和OpenID写进num-openid表和staffArray表,主要用来判断访问者是否是本公司员工。如果已经绑定,则阅读文章(本公司员工互相阅读文章,文章阅读数不变)并将文章转发到微信好友、微信朋友圈或者QQ中。如果自己的好友(非本公司员工)阅读文章之后,则该员工的文章阅读数增加1,同时将访问者的OpenID写进文章id为ar_id的缓存表中,防止重复阅读增加阅读数。文章的访问数和自己的阅读数是数据统计的依据,同时也是评优评奖的重要指标,整个流程如图4所示。
图4 Redis数据流图
(2) 数据备份 Redis是缓存数据库,具有一定的生命期,为了保证数据安全,本项目采用定时备份的方式将Redis数据库的关键数据及时备份到MySQL数据库中。
软件测试是软件开发过程中不可缺少的环节,通过测试,目的是为了发现系统与用户需求不符或矛盾的地方,进而进行更改和完善[7]。功能测试和性能测试是一般软件项目需要关注的两个测试方面。功能测试是软件测试中最基本的测试,主要验证系统是否符合需求说明。性能测试主要测试系统在某些极端条件下是否满足需求,其中压力测试是一种典型的性能测试。在Web系统中,压力测试主要是在系统同时接收大量用户和请求时,检测Web系统的响应。
本系统测试环境:ubuntu1204虚拟机,200 GB硬盘,8 GB内存,2 Gbit/s带宽。Apache2.2,PHP5.3.10,MySQL5.5。
按照用户的需求文档,设计了30个测试用例,主要对文章审核流程和文章转发统计这两个业务逻辑复杂点进行测试。测试结果良好,测试结果和预期效果一致,测试通过率100%。
对于Web服务器来说,用户请求响应时间是一个重要指标,它是评价系统性能的关键参数。本系统的性能关键点(并发点)主要是文章阅读和转发操作,使用Apache自带的ab并发测试命令对该并发点进行测试,测试请求数为10 000,结果发现在并发数1 000左右的时候,系统平均响应时间在16.7 ms左右。也就是说1 000人同时阅读文章或者转发文章的时候,平均等待时间在16.7 ms左右。目前该公司人数在4 000左右,该并发数和响应时间基本上能够满足性能要求。
随着“互联网+”的发展,微信公众号已经成了一种大众化的沟通工具。为了满足用户的特殊需求,微信公众号二次开发成为必要。同时随着粉丝数量越来越庞大,一些类似抽奖、抢红包等高并发的二次开发也迫在眉睫。本文以某企业为例,采用开源框架ThinkPHP框架和Redis缓存技术,搭建一套基于微信公众号的高并发二次开发通用平台,该开发技术具有通用性,在快速开发高并发的微信公众号方面有一定的借鉴意义,能够节约时间和经济成本。