• 所以我才打算写一篇全面的制作教程,当然了,最好的教程微信工作平台的文档。我这里只是讲述一下我的工作中的制作流程。所有相关文章的源码,我托管在我自己的github上面,欢迎关注:地址点击打开链接。接下来开始...

    因为工作的需要,这一两年对微信公众号和小程序,项目制作的比较多。所以我才打算写一篇全面的制作教程,当然了,最好的教程是微信工作平台的文档。我这里只是讲述一下我的工作中的制作流程。所有相关文章的源码,我托管在我自己的github上面,欢迎关注:地址点击打开链接。接下来开始我们的教程。

    1.微信与公众平台的区别:

    微信:即时聊天的软件,属于一对一的关系

    微信公众平台:属于一对多的关系。

    2.订阅号与服务号的区别:

    订阅号:针对个人或媒体每天可以群发1条信息,默认不具有自定义菜单。服务器号:针对企业或银行每月可以群发4条信息,默认具有自定义菜单。运营主体是组织(比如企业、媒体、公益组织)的,可以申请服务号。运营主体是组织和个人的可以申请订阅号,但是个人不能申请服务号。

    3.公众平台的两种模式:

    1、编辑模式:直接使用微信公众平台所提供的后台操作进行用户交互。编辑模式可以使用在如下的场景:不具备开发能力的运营者,主要是进行品牌宣传、新闻媒体、自助客服的公众账号,运营初期,不需要特别多的功能,开发模式系统升级,故障等特殊情况

    2、开发者模式:直接使用接口代码实现用户的交流

    4.微信公众平台前期准备:

    注册公众平台,拥有线上服务器

    5.认识一下编辑模式:

    原理:

    5.1消息群发:

    选择对象选择素材进行群发:


    5.2:自定义菜单:

    启用自定义菜单,并开启:


    点击查看,进入设置界面:



    5.3自动回复:


    上述讲述的主要是编辑模式的使用,我想很多人应该都会使用,其实和我们平常在论坛编辑文章和帖子差不多。

    6.开发者模式

    原理:


    6.1第一步我们要开启开发者模式:

    注意:在开启开发者模式之后编辑模式中部分功能不能正常使用。即开发者模式跟编辑模式存在冲突。进入设置界面:


    修改配置:


    设置结果如下图:填写上你的服务器文件地址,token,点击提交,验证即可。如果提交并验证通过,就进入到开发者模式



    6.2我线上的验证代码如下:

    public function valid(){
    //获取随机字符串
    $echoStr = input("echostr");
    if($echoStr){
    // 验证接口的有效性,由于接口有效性的验证必定会传递echostr 参数
    if($this ->checkSignature()){
    echo $echoStr;
    exit;
    }
    }else{
    $this->responseMsg();
    }
    }
    protected function checkSignature()
    {
    // 微信加密签名
    $signature = input("signature");
    $timestamp = input("timestamp");//时间戳
    $nonce =input("nonce");//随机数
    $token = "weixin"; //token值,必须和你设置的一样
    $tmpArr =array($token,$timestamp,$nonce);
    sort($tmpArr,SORT_STRING);
    $tmpStr = implode($tmpArr);
    $tmpStr =sha1($tmpStr);
    if($tmpStr == $signature){
    return true;
    }else{
    return false;
    }
    }

    关于上述代码中的变量来源,微信都有详细的说明,我搭建的公众号使用的是PHP7.0版本,TP5.0框架。在这里说明一下。

    上述代码只会执行一次,开启开发者模式之后,就不会在执行,只会执行上述代码中的

    responseMsg

    方法。

    我们先上传一段测试代码试试,回复文本消息,是否有回复,代码如下:

    public function responseMsg()
    {
            //get post data, May be due to the different environments
            $postStr = file_get_contents('php://input');    
      //extract 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);
      $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
    $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>";
                    if(!empty( $keyword ))
    {
          $msgType = "text";
        $contentStr = "Welcome to wechat world!";
        $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
        echo $resultStr;
    }else{
        echo "Input something...";
    }

    }else {
        echo "";
        exit;
    }
    }

    结果如下:

    ,由于微信的网页授权的限制,所以我们可以申请测试账号,我这里使用的就是测试账号。上述的开启开发者模式是一样的。只需要去申请一个测试账号就好。申请的地方:



    如果出现上述的代码,说明我们已经测试成功。接下来,我们开始获取access_token的值,这个参数对于我们来说很重要。因为

    access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。access_token是身份认证 其他接口基本上都需要使用该值进行验证。

    7.access_token获取:(可以在本地测试)

    7.1查看接口说明:


    获取access_token方式一:


    结果如下:


    获取access_token方式二:


    结果如下:


    我们开始对上述的代码进行封装,因为access_token,每天只能调用2000次,所以我们要缓存起来,这样才能达到复用的效果,

    7.2:curl封装发送请求和获取access_token封装:

    // 获取请求的地址的方法
    if(!function_exists("http_curl")){
    function http_curl($url,$data =array(),$method ="get",$returnType ="json")
    {
    //1.开启会话
    $ch = curl_init();
    //2.设置参数
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);
    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
    if($method!="get"){
    curl_setopt($ch,CURLOPT_POST,TRUE);
    curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
    }
    curl_setopt($ch,CURLOPT_URL,$url);
    //执行会话
    $json = curl_exec($ch);
    curl_close($ch);
    if($returnType == "json"){
    return json_decode($json,true);
    }
    return $json;
    }
    }
    if(!function_exists('get_access_token')){
    function get_access_token()
    {
    $appid = "wx1ba8f59d9e2c0be0"; //微信的appid
    $secret ="9e65155599fb9ec047455e197ff6e121"; //微信的开发者密钥
    // 读取缓存中的内容
    include_once "MyMemcache.php"; //引入缓存方法文件
    $obj = new \MyMemcache("47.104.71.253");
    $value = $obj ->get($appid);
    if(!$value){
    $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appid."&secret=".$secret;
    $result = http_curl($url);
    $value = $result['access_token'];
    $obj->set($appid,$value,7000);
    }
    return $value;
    }
    }

    上述代码就是我对这两个方法的封装,其中我们用到了缓存技术:缓存的方法如下:

    // memcache操作类
    class MyMemcache{
    public $conn;
    public $isMemcache =true;
    public function __construct($host="127.0.0.1",$port='11211')
    {
    // 建立连接
    if(class_exists('MyMemcache')){
    $obj =new \Memcache();
    }else{
    $this ->isMemcache =false;
    $obj =new \Memcached();
    }
    $obj ->addServer($host,$port);
    $this ->conn =$obj;
    }
    //获取数据
    public function get($key)
    {
    return $this->conn->get($key);
    }
    //设置数据
    public function set($key,$value,$expire=0)
    {
    if($this->isMemcache){
    $this->conn->set($key,$value,0,$expire);
    }else{
    // Memcached扩展的操作方式
    $this->conn->set($key,$value,$expire);
    }
    }
    }

    结合上述的三个方法,我们就可以实现获取access_token的值,并保存在缓存系统,7000s去重新获取一次。

    上述的步骤完成,我们就算是对微信公众号的开发的基本准备全部准备完毕,接下来就开始对着微信开发者文档进行开发和数据的替换了。第一节先讲述到这里.....

    展开全文
  • 微信开发教程 手把手实战教程 有代码注释 微信开发 php微信 java微信 微信实战教程 微信教程
  • 微信开发之入门教程

    2016-01-12 10:55:26
    微信开发也是有了一定的认识。在此,小宝鸽再次无私地分享给大家啦。其实微信开发跟web开发没有多大的区别,只是经过了微信,然后再由浏览器打开。因为经过微信,因此你的web会附加一些微信平台所提供的一些功能,...

    时间葱葱,小宝鸽入职也有半年了,刚入的时候刚好有负责开发一个微信企业号的新项目。从项目的一无所有到第一版上线,再一步步完善升级。期间学到了许多东西。对微信开发也是有了一定的认识。在此,小宝鸽再次无私地分享给大家啦。

    其实微信开发跟web开发没有多大的区别,只是经过了微信,然后再由浏览器打开。因为经过微信,因此你的web会附加一些微信平台所提供的一些功能,如获取用户地理位置、获取微信用户头像、拍照上传、发送微信消息等等,通过微信接口即可调用。要将web项目挂靠在微信公众平台上是需要一个帐号的。微信公众平号分为服务号、订阅号、企业号。这三种帐号有一些小区别,但是开发流程都是差不多的,只是开放的功能上有些区别,知道其中一种开发,其他的也差不多。关于具体区别先不作过多介绍,后面的文章会讲到。接下来我们以企业号为例带大家进入微信开发之旅。

    接下来将从下面几个角度带大家了解微信开发:

    (1)申请企业号体验号
    (2)企业号的一些配置
    (3)微信JS接口调用

    好了现在马上开始:

    一、申请企业号体验号

    1.1、首先来到微信企业号的网址 https://qy.weixin.qq.com/

    这里写图片描述

    1.2、可以看到“开发者中心”字眼,点击进入相应页面

    这里写图片描述

    1.3、进入后可以看到“欢迎你,开发者”的公告,右侧有个“申请体验号”,点击进入相应页面。

    这里写图片描述

    1.4、进入“申请体验号”后,可以看到注册流程,按照注册流程填写相应资料并申请,验证邮箱绑定微信号后体验号就申请成功啦。

    这里写图片描述

    1.5、申请成功之后,回到https://qy.weixin.qq.com/,用微信扫一扫扫描登录下方的二维码,输入对应密码即可登录成功,来到你的微信企业号首页啦。

    这里写图片描述

    二、企业号的一些配置

    2.1、添加子部门,如下图,将鼠标放到“企业号体验43560625”就会出现小下标,然后点击添加子部门,填写好信息保存,然后刷新页面即可。

    这里写图片描述
    这里写图片描述

    2.2、添加成员,点击通讯录,然后如下图进行操作,即可添加成员。部门选择刚刚添加的部门

    这里写图片描述

    2.3、然成员关注该企业号,如果添加成员的时候有输入邮箱,可以在通讯录的成员管理那里给成员发送关注邀请,邀请会将企业号二维码发送到对应成员邮箱。另外一种方法直接点击“设置”即可看到体验号二维码。让成员扫这个二维码关注也是可以的(需要注意的是,体验号只能最多关注10个成员哦)。另外下图中的CorpID (wx7099477f2de8aded)非常重要的,先记录起来,下面接口微信JS调用的时候会用到。

    这里写图片描述

    2.4、添加管理组,点击“设置”–>”权限管理”,就会跳转到下图页面,然后“新建管理组”,选择管理员的时候,如果提示该成员已在其他管理组,那么估计需要添加成员了。小宝鸽添加了一个管理组“测试”,添加成功后如下图。其中Secret也是非常重要的东西,之后JS接口调用获取签名需要用到。

    这里写图片描述

    2.5、应用管理。猿友们可以看到左侧菜单中有个”应用中心”。点击应用中心将来到下图页面。”企业小助手”就是本企业号默认存在的一个应用。猿友们可以自行创建更多的应用。

    这里写图片描述

    点击”企业小助手”将会来到下面的界面,默认是回调模式,我们需要设置成普通模式。

    这里写图片描述

    点击”普通模式”,启用模式,然后启用”自定义菜单”。

    这里写图片描述

    自定义菜单启用完成之后,点击自定义菜单中的设置,将会跳转到如下页面:

    这里写图片描述

    添加菜单”测试”,然后设置”微信信息”,内容为”测试啦啦啦”,保存–>发布,然后右边有个预览,点击菜单”测试”,就会自动回复消息,如下图:

    这里写图片描述

    菜单响应除了发送微信消息也可以是跳转到某个链接,因为跳转链接是需要配置可信域名的,因此先介绍如何配置可信域名
    应用管理还有一个地方需要设置的,那就可信域名,如下图,回到”企业小助手”应用的详情页面,添加可信域名,可信域名是有一些要求的(1. 设置的应用域名须通过ICP备案的验证,2. 请使用二级或二级以上域名),这里小宝鸽网上找了一个”yo.bbdfun.com”,猿友们也可以使用这个

    这里写图片描述

    配置了可信域名之后呢,猿友们可以配置跳转到链接的菜单啦,注意配置的url必须是已可信域名作为域名哈,例如:

    这里写图片描述

    三、微信JS接口调用

    3.1、微信提供了一系列的JS接口,使得公众号企开发十分快捷高效,微信JS-SDK接口:
    http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS-SDK%E6%8E%A5%E5%8F%A3
    各位猿友们可粗略看一下上面文档,便可知道大概提供的一些功能。

    3.2、各位猿友们粗略看完”微信JS-SDK接口”,应该有看到下图的说明吧,接口的使用是需要注入权限验证配置的,现在我们上面的体验号等等的一系列操作就派上用场啦。

    这里写图片描述

    3.3、接下来将会一点点向大家介绍怎么调用微信接口啦

    微信接口文档之后的猿友们应该都知道微信接口的调用步骤如下:

    这里写图片描述

    最重要的还是步骤二:权限验证配置。里面有几个参数,小宝鸽将会为猿友们一一介绍:
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来
    appId: ”, // 必填,企业号的唯一标识,此处填写企业号corpid
    timestamp: , // 必填,生成签名的时间戳
    nonceStr: ”, // 必填,生成签名的随机串
    signature: ”,// 必填,签名,见附录1
    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2

    debug、appId和jsApiList相信各位猿友们应该都知道大概是什么东西。那么现在给各位猿友们重点介绍:timestamp、nonceStr、signature。
    其实timestamp、nonceStr是用来生成signature的。
    js生成时间戳方法:timestamp = Date.parse(new Date()); //1414587457
    另外,nonceStr也是一串随机串,我们也用时间戳就好了nonceStr=Date.parse(new Date()); //1414587457

    剩下的就是最关键的signature生成方法,这里需要引入access_token概念
    生成signature签名第一步获得access_token:
    浏览器输入:https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wx5f24fa0db1819ea2&corpsecret=uQtWzF0bQtl2KRHX0amekjpq8L0aO96LSpSNfctOBLRbuYPO4DUBhMn0_v2jHS-9
    即可得到access_token:YoxXjnJS57r8gk5Nf-Ki_mSvn98fILxv56EE7NFWE3qQNOH3OaW4iDWwLc05g1mdbuNhipK8fgy-q-pA93DqFw(其有效期为7200秒,即两个小时)

    这里写图片描述

    生成signature签名第二步通过access_token获得ticket:
    浏览器输入:https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=YoxXjnJS57r8gk5Nf-Ki_mSvn98fILxv56EE7NFWE3qQNOH3OaW4iDWwLc05g1mdbuNhipK8fgy-q-pA93DqFw
    即可得到ticket:”sM4AOVdWfPE4DxkXGEs8VLMMSNOZxIv5IhnWCyv5sA4UgJuWuMQdfMCeyC5kSL_c7OIMGeETC2y9PXfLbFIFNw(其有效期也是7200秒,即两个小时)

    这里写图片描述

    生成signature签名第三步通过ticket以及下面参数拼成字符串:
    noncestr=1414587457
    jsapi_ticket(即上面的ticket)=sM4AOVdWfPE4DxkXGEs8VLMMSNOZxIv5IhnWCyv5sA5kumyWTQ2VcKEcphBAW62J_HUgmaiKEQ3qhwj5Vlqq7g
    timestamp=1414587457
    url=http://mp.weixin.qq.com

    通过上面的参数拼成(注意参数顺序必须一样):jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VLMMSNOZxIv5IhnWCyv5sA5kumyW
    TQ2VcKEcphBAW62J_HUgmaiKEQ3qhwj5Vlqq7g
    &noncestr=1414587457&timestamp=1414587457&url=http://mp.weixin.qq.com

    最后利用上面的字符串进行sha1加密,有在线的校验工具http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign。但是真正开发的时候肯定是需要写代码的,下面附上sha1加密的java算法:`

    jdk也有提供这个java.security这个包,里面封装好了sha1加密算法。使用方法可参考博主的另外一篇博客AES加密解密 SHA1、SHA加密 MD5加密

    注意真正获取access_token、ticket的时候是需要通过代码实现的,上面在浏览器输入对应地址获取只是为了理顺流程。下面是通过java代码获取。

    3.4、java代码获取签名

    关于使用java代码获取签名的详细过程请参考博主的另外一篇文章 微信开发之使用java获取签名signature(贴源码,附工程)

    该文章有详细的代码,而且附工程下载。

    获取到了签名之后就可以调用微信js接口了,例子后面的文章将会讲到。

    展开全文
  • 微信开发之素材管理是子恒老师《微信公众平台开发》视频教程的第三部。详细讲解了用php开发微信,对微信公众平台中的素材管理开发。内容包含微信临时素材,永久素材的上传,删除,获取素材的media_id等等。欢迎反馈...
  • 本课程是基于ThinkPHP5.0.1的微信开发教程,在整个课程中全部采用原理讲解和实践开发配套的方式。在该教程中还包含了源码供大家下载。代码下载地址:https://code.csdn.net/hayixia606/wechat/tree/master
  • java微信开发教程

    2014-08-17 23:25:47
    微信公众平台开发教程Java版(1)环境准备篇

    微信公众平台开发教程Java版(1)环境准备篇

    微信公众平台开发教程Java版(一)环境准备篇

    准备写系列博客,记录下我的微信公众平台学习记录,也为那些摸索中的开发者提供点参考。

    希望与大家共同进步。

    微信3.0的时候我开始做微信公众账号,那时候没时间研究开发,先用的是编辑者模式,后用开发者模式,托管于第三方。

    一直想自己写个服务端来实现个人定制化的需求。

    废话不多说,进入正题。

    想要开发微信公众平台需要一些环境

    一、申请微信公众账号

           这个就不用废话了。附上地址:
           https://mp.weixin.qq.com/cgi-bin/readtemplate?t=wxm2-realname-reg_tmpl&lang=zh_CN
           现在申请好严格的说,3.0的时候申请都不需要拍照什么的。

           友情提示:
                     1、微信公众账号的名字一旦申请,则不能更改。取名请慎重!

                     2、一个身份证只能申请两个公众号

                     3、公众号分两种:订阅号和服务号

                     订阅号可一天群发一次消息,目前不能申请自定义菜单。发送的消息将显示在“订阅号”文件夹中,适合媒体等提供咨询服务的公众号。

                     服务号一个月只能群发一条消息,能申请自定义菜单,发送的消息会显示在用户的聊天列表中,并会提醒用户新消息。适合为用户提供服务的公众号

     

    二、外网服务器

           你需要一台外网服务器,来发布你的代码,用于接收处理用户发送的请求。

           如果没有的话,也不用担心。可以使用百度BAE,或者是sina sae,国外比较多的是用google的gae。

           google gae支持的语言很多。但是在国内经常访问不了,不推荐使用。

           百度Bae 支持java和php(完全免费,百度对于资源方面还是一向很大方的,赞一个,哈哈)

           sina sae 支持java,php,python(可免费使用半年,收费的,但很便宜)

    三、至少会一种语言
            java,php,asp,python等,至少得会一样!

    微信公众平台开发教程Java版(2)接口配置

    微信公众账号申请完成后,默认开启的是编辑模式。

    我们需要修改为开发模式。

     

    登陆微信公众平台》功能》高级功能

    先关闭 编辑模式,再开启 开发模式。

     

    申请成为开发者,如果是服务号,需要则会有开发者凭证信息

    如图


     

    如果是订阅号,则只显示服务器配置。

     

    下一步就是配置接口服务器了。

    在公众平台网站的高级功能 – 开发模式页,点击“成为开发者”按钮,填写URL和Token,其中URL是开发者用来接收微信服务器数据的接口URL。(这就是我们开发的程序,并部署到公网上了)

    Token 官网描述:可由开发者任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。

    总之就是你的程序里面写的token和这里填入的token要一致。

     



     

     还没有url和token?

     

    首先需要新建一个java web工程。

     

     接下来就要看看验证url和token了。

     下面是官网的描述,已经写的很清楚了



     核心实现方式就是将三个参数排序,拼接成字符串进行sha1加密,然后与signature比较

     官网也给了实例,是php的,我们只需要装换成java就可以了。

    private function checkSignature()
    {
            $signature = $_GET["signature"];
            $timestamp = $_GET["timestamp"];
            $nonce = $_GET["nonce"];	
            		
    	$token = TOKEN;
    	$tmpArr = array($token, $timestamp, $nonce);
    	sort($tmpArr);
    	$tmpStr = implode( $tmpArr );
    	$tmpStr = sha1( $tmpStr );
    	
    	if( $tmpStr == $signature ){
    		return true;
    	}else{
    		return false;
    	}
    }

     

     

    java代码 我的 WeixinController 类

        我的项目架构是基于spring3.0的,用到了注解。当get请求的时候会执行get方法,post请求的时候会执行post方法,分别来处理不同的请求,各位也可用servlet等去实现,原理都一样

    package com.ifp.weixin.controller;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.io.UnsupportedEncodingException;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import com.ifp.weixin.biz.core.CoreService;
    import com.ifp.weixin.util.SignUtil;
    
    @Controller
    @RequestMapping("/weixinCore")
    public class WeixinController {
    
    	@Resource(name="coreService")
    	private CoreService coreService;
    	
    	@RequestMapping(method = RequestMethod.GET)
    	public void get(HttpServletRequest request, HttpServletResponse response) {
    		// 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
    		String signature = request.getParameter("signature");
    		// 时间戳
    		String timestamp = request.getParameter("timestamp");
    		// 随机数
    		String nonce = request.getParameter("nonce");
    		// 随机字符串
    		String echostr = request.getParameter("echostr");
    
    		PrintWriter out = null;
    		try {
    			out = response.getWriter();
    			// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,否则接入失败
    			if (SignUtil.checkSignature(signature, timestamp, nonce)) {
    				out.print(echostr);
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			out.close();
    			out = null;
    		}
    	}
    
    	@RequestMapping(method = RequestMethod.POST)
    	public void post(HttpServletRequest request, HttpServletResponse response) {
    		//暂时空着,在这里可处理用户请求
    	}
    
    }
    

     

     上面类中用到了SignUtil 类

    package com.ifp.weixin.util;
    
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    
    import com.ifp.weixin.constant.Constant;
    /**
     * 验证签名
     *
     */
    public class SignUtil {
    	
    
    	/**
    	 * 验证签名
    	 * @param signature
    	 * @param timestamp
    	 * @param nonce
    	 * @return
    	 */
    	public static boolean checkSignature(String signature, String timestamp, String nonce) {
    		String[] arr = new String[] { Constant.TOKEN, timestamp, nonce };
    		// 将token、timestamp、nonce三个参数进行字典排序
    		Arrays.sort(arr);
    		StringBuilder content = new StringBuilder();
    		for (int i = 0; i < arr.length; i++) {
    			content.append(arr[i]);
    		}
    		MessageDigest md = null;
    		String tmpStr = null;
    
    		try {
    			md = MessageDigest.getInstance("SHA-1");
    			// 将三个参数字符串拼接成一个字符串进行sha1加密
    			byte[] digest = md.digest(content.toString().getBytes());
    			tmpStr = byteToStr(digest);
    		} catch (NoSuchAlgorithmException e) {
    			e.printStackTrace();
    		}
    
    		content = null;
    		// 将sha1加密后的字符串可与signature对比
    		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    	}
    
    	/**
    	 * 将字节数组转换为十六进制字符串
    	 * 
    	 * @param byteArray
    	 * @return
    	 */
    	private static String byteToStr(byte[] byteArray) {
    		String strDigest = "";
    		for (int i = 0; i < byteArray.length; i++) {
    			strDigest += byteToHexStr(byteArray[i]);
    		}
    		return strDigest;
    	}
    
    	/**
    	 * 将字节转换为十六进制字符串
    	 * 
    	 * @param mByte
    	 * @return
    	 */
    	private static String byteToHexStr(byte mByte) {
    		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    		char[] tempArr = new char[2];
    		tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
    		tempArr[1] = Digit[mByte & 0X0F];
    
    		String s = new String(tempArr);
    		return s;
    	}
    }
    

     
     我们看到 checkSignature 这个方法里使用到了Constant.TOKEN ,这个token,我声明的一个常量。

     

     要与微信配置接口里面的token值一样

    /**
    * 与接口配置信息中的Token要一致
    */
    public static String TOKEN = "infopower";

    也贴上web.xml的配置,我的后缀是.html 的请求都交给DispatcherServlet了。

     

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    	http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    	<display-name>weixinHelp</display-name>
    	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:/applicationContext.xml</param-value>
    	</context-param>
    
    	<context-param>
    		<param-name>log4jConfigLocation</param-name>
    		<param-value>classpath:/properties/log4j.properties</param-value>
    	</context-param>
    	<listener>
    		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    	</listener>
    
    	<filter>
    		<filter-name>encodingFilter</filter-name>
    		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    		<init-param>
    			<param-name>encoding</param-name>
    			<param-value>UTF-8</param-value>
    		</init-param>
    	</filter>
    	<filter-mapping>
    		<filter-name>encodingFilter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    	<listener>
    		<description>spring 容器的监听器</description>
    		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    	</listener>
    	<servlet>
    		<servlet-name>action</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>action</servlet-name>
    		<url-pattern>*.html</url-pattern>
    	</servlet-mapping>
    
    	<welcome-file-list>
    		<welcome-file>index.jsp</welcome-file>
    	</welcome-file-list>
    </web-app>
    

      

     

     我们的代码已经写完了,访问请求地址试试



     

    什么都没有显示,看看后台



     

    报空指针异常

     

    别担心,我们的代码没问题。

    因为直接访问地址,默认是get请求,而什么参数都没有传给后台,当然会报空指针

    前台没有异常,是因为我做了异常处理。

    ok

     

    接下来就是把代码打成war包发布到外网。

    然后填入相应的url和token,接口的配置就完成了。

     

    注意1:一定要发布war包到外网,配置外网的url,有些开发者配置的是ip是localhost,那肯定是不行的啦。

               如果没有外网环境,请看我的第一篇,环境准备,里面有介绍可以使用百度bae

               http://tuposky.iteye.com/blog/2008583

      注意2:开发模式一定要开启,不然配置了url和token也没用,我犯过这个错,嘿嘿。

    微信公众平台开发教程Java版(3) 消息接收和发送

    微信公众平台开发教程Java版(三) 消息接收和发送

    前面两章已经介绍了如何接入微信公众平台,这一章说说消息的接收和发送

    可以先了解公众平台的消息api接口(接收消息,发送消息)

    http://mp.weixin.qq.com/wiki/index.php


     

    接收消息

    当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。

     

     http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF

     

    接收的消息类型有6种,分别为:

    • 1 文本消息
    • 2 图片消息
    • 3 语音消息
    • 4 视频消息
    • 5 地理位置消息
    • 6 链接消息

    可以根据官方的api提供的字段建立对应的实体类

    如:文本消息

     

    有很多属性是所有消息类型都需要的,可以把这些信息提取出来建立一个基类

     

    package com.ifp.weixin.entity.Message.req;
    
    /**
     * 消息基类(用户 -> 公众帐号)
     * 
     */
    public class BaseMessage {
    	/**
    	 * 开发者微信号
    	 */
    	private String ToUserName;
    	/**
    	 * 发送方帐号(一个OpenID)
    	 */
    	private String FromUserName;
    	/**
    	 * 消息创建时间 (整型)
    	 */
    	private long CreateTime;
    
    	/**
    	 * 消息类型 text、image、location、link
    	 */
    	private String MsgType;
    
    	/**
    	 * 消息id,64位整型
    	 */
    	private long MsgId;
    
    	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 long getCreateTime() {
    		return CreateTime;
    	}
    
    	public void setCreateTime(long createTime) {
    		CreateTime = createTime;
    	}
    
    	public String getMsgType() {
    		return MsgType;
    	}
    
    	public void setMsgType(String msgType) {
    		MsgType = msgType;
    	}
    
    	public long getMsgId() {
    		return MsgId;
    	}
    
    	public void setMsgId(long msgId) {
    		MsgId = msgId;
    	}
    
    }
    

     接收的文本消息

     

    package com.ifp.weixin.entity.Message.req;
    
    /**
     * 文本消息
     */
    public class TextMessage extends BaseMessage {
    	/**
    	 * 回复的消息内容
    	 */
    	private String Content;
    
    	public String getContent() {
    		return Content;
    	}
    
    	public void setContent(String content) {
    		Content = content;
    	}
    }

     接收的图片消息

    package com.ifp.weixin.entity.Message.req;
    
    public class ImageMessage extends BaseMessage{
    
    	private String picUrl;
    
    	public String getPicUrl() {
    		return picUrl;
    	}
    
    	public void setPicUrl(String picUrl) {
    		this.picUrl = picUrl;
    	}
    	
    }
    

     

     

    接收的链接消息

    package com.ifp.weixin.entity.Message.req;
    
    
    public class LinkMessage extends BaseMessage {
    	/**
    	 * 消息标题
    	 */
    	private String Title;
    	/**
    	 * 消息描述
    	 */
    	private String Description;
    	/**
    	 * 消息链接
    	 */
    	private String Url;
    
    	public String getTitle() {
    		return Title;
    	}
    
    	public void setTitle(String title) {
    		Title = title;
    	}
    
    	public String getDescription() {
    		return Description;
    	}
    
    	public void setDescription(String description) {
    		Description = description;
    	}
    
    	public String getUrl() {
    		return Url;
    	}
    
    	public void setUrl(String url) {
    		Url = url;
    	}
    
    }
    

     

     接收的语音消息

     

    package com.ifp.weixin.entity.Message.req;
    
    /**
     * 语音消息
     * 
     * @author Caspar
     * 
     */
    public class VoiceMessage extends BaseMessage {
    	/**
    	 * 媒体ID
    	 */
    	private String MediaId;
    	/**
    	 * 语音格式
    	 */
    	private String Format;
    
    	public String getMediaId() {
    		return MediaId;
    	}
    
    	public void setMediaId(String mediaId) {
    		MediaId = mediaId;
    	}
    
    	public String getFormat() {
    		return Format;
    	}
    
    	public void setFormat(String format) {
    		Format = format;
    	}
    
    }
    

     接收的地理位置消息

     

    package com.ifp.weixin.entity.Message.req;
    
    
    /**
     * 位置消息
     * 
     * @author caspar
     * 
     */
    public class LocationMessage extends BaseMessage {
    	/**
    	 * 地理位置维度
    	 */
    	private String Location_X;
    	/**
    	 * 地理位置经度
    	 */
    	private String Location_Y;
    
    	/**
    	 * 地图缩放大小
    	 */
    	private String Scale;
    
    	/**
    	 * 地理位置信息
    	 */
    	private String Label;
    
    	public String getLocation_X() {
    		return Location_X;
    	}
    
    	public void setLocation_X(String location_X) {
    		Location_X = location_X;
    	}
    
    	public String getLocation_Y() {
    		return Location_Y;
    	}
    
    	public void setLocation_Y(String location_Y) {
    		Location_Y = location_Y;
    	}
    
    	public String getScale() {
    		return Scale;
    	}
    
    	public void setScale(String scale) {
    		Scale = scale;
    	}
    
    	public String getLabel() {
    		return Label;
    	}
    
    	public void setLabel(String label) {
    		Label = label;
    	}
    
    }
    

     

     

    发送被动响应消息

        对于每一个POST请求,开发者在响应包(Get)中返回特定XML结构,对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。请注意,回复图片等多媒体消息时需要预先上传多媒体文件到微信服务器,只支持认证服务号。

     

        同样,建立响应消息的对应实体类

        也把公共的属性提取出来,封装成基类

     

         响应消息的基类

    package com.ifp.weixin.entity.Message.resp;
    
    /**
     * 消息基类(公众帐号 -> 用户)
     */
    public class BaseMessage {
    	
    	/**
    	 * 接收方帐号(收到的OpenID)
    	 */
    	private String ToUserName;
    	/**
    	 * 开发者微信号
    	 */
    	private String FromUserName;
    	/**
    	 * 消息创建时间 (整型)
    	 */
    	private long CreateTime;
    	
    	/**
    	 * 消息类型
    	 */
    	private String MsgType;
    	
    	/**
    	 * 位0x0001被标志时,星标刚收到的消息
    	 */
    	private int FuncFlag;
    
    	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 long getCreateTime() {
    		return CreateTime;
    	}
    
    	public void setCreateTime(long createTime) {
    		CreateTime = createTime;
    	}
    
    	public String getMsgType() {
    		return MsgType;
    	}
    
    	public void setMsgType(String msgType) {
    		MsgType = msgType;
    	}
    
    	public int getFuncFlag() {
    		return FuncFlag;
    	}
    
    	public void setFuncFlag(int funcFlag) {
    		FuncFlag = funcFlag;
    	}
    }

     

     

        响应文本消息

       

    package com.ifp.weixin.entity.Message.resp;
    
    
    /**
     * 文本消息
     */
    public class TextMessage extends BaseMessage {
    	/**
    	 * 回复的消息内容
    	 */
    	private String Content;
    
    	public String getContent() {
    		return Content;
    	}
    
    	public void setContent(String content) {
    		Content = content;
    	}
    }

     

     

    响应图文消息

       

    package com.ifp.weixin.entity.Message.resp;
    
    import java.util.List;
    
    /**
     * 多图文消息,
     * 单图文的时候 Articles 只放一个就行了
     * @author Caspar.chen
     */
    public class NewsMessage extends BaseMessage {
    	/**
    	 * 图文消息个数,限制为10条以内
    	 */
    	private int ArticleCount;
    	/**
    	 * 多条图文消息信息,默认第一个item为大图
    	 */
    	private List<Article> Articles;
    
    	public int getArticleCount() {
    		return ArticleCount;
    	}
    
    	public void setArticleCount(int articleCount) {
    		ArticleCount = articleCount;
    	}
    
    	public List<Article> getArticles() {
    		return Articles;
    	}
    
    	public void setArticles(List<Article> articles) {
    		Articles = articles;
    	}
    }

     图文消息的定义

     

     

    package com.ifp.weixin.entity.Message.resp;
    
    /**
     * 图文消息
     * 
     */
    public class Article {
    	/**
    	 * 图文消息名称
    	 */
    	private String Title;
    
    	/**
    	 * 图文消息描述
    	 */
    	private String Description;
    
    	/**
    	 * 图片链接,支持JPG、PNG格式,<br>
    	 * 较好的效果为大图640*320,小图80*80
    	 */
    	private String PicUrl;
    
    	/**
    	 * 点击图文消息跳转链接
    	 */
    	private String Url;
    
    	public String getTitle() {
    		return Title;
    	}
    
    	public void setTitle(String title) {
    		Title = title;
    	}
    
    	public String getDescription() {
    		return null == Description ? "" : Description;
    	}
    
    	public void setDescription(String description) {
    		Description = description;
    	}
    
    	public String getPicUrl() {
    		return null == PicUrl ? "" : PicUrl;
    	}
    
    	public void setPicUrl(String picUrl) {
    		PicUrl = picUrl;
    	}
    
    	public String getUrl() {
    		return null == Url ? "" : Url;
    	}
    
    	public void setUrl(String url) {
    		Url = url;
    	}
    
    }

     

     

    响应音乐消息

     

    package com.ifp.weixin.entity.Message.resp;
    
    
    
    /**
     * 音乐消息
     */
    public class MusicMessage extends BaseMessage {
    	/**
    	 * 音乐
    	 */
    	private Music Music;
    
    	public Music getMusic() {
    		return Music;
    	}
    
    	public void setMusic(Music music) {
    		Music = music;
    	}
    }

     

     

    音乐消息的定义

    package com.ifp.weixin.entity.Message.resp;
    
    /**
     * 音乐消息
     */
    public class Music {
    	/**
    	 * 音乐名称
    	 */
    	private String Title;
    	
    	/**
    	 * 音乐描述
    	 */
    	private String Description;
    	
    	/**
    	 * 音乐链接
    	 */
    	private String MusicUrl;
    	
    	/**
    	 * 高质量音乐链接,WIFI环境优先使用该链接播放音乐
    	 */
    	private String HQMusicUrl;
    
    	public String getTitle() {
    		return Title;
    	}
    
    	public void setTitle(String title) {
    		Title = title;
    	}
    
    	public String getDescription() {
    		return Description;
    	}
    
    	public void setDescription(String description) {
    		Description = description;
    	}
    
    	public String getMusicUrl() {
    		return MusicUrl;
    	}
    
    	public void setMusicUrl(String musicUrl) {
    		MusicUrl = musicUrl;
    	}
    
    	public String getHQMusicUrl() {
    		return HQMusicUrl;
    	}
    
    	public void setHQMusicUrl(String musicUrl) {
    		HQMusicUrl = musicUrl;
    	}
    
    }

     
     构建好之后的项目结构图为

     

     

    到这里,请求消息和响应消息的实体类都定义好了

     

    解析请求消息

     

    用户向微信公众平台发送消息后,微信公众平台会通过post请求发送给我们。

    上一章中WeixinController 类的post方法我们空着

     

     现在我们要在这里处理用户请求了。

     

    因为微信的发送和接收都是用xml格式的,所以我们需要处理请求过来的xml格式。

    发送的时候也需要转化成xml格式再发送给微信,所以封装了消息处理的工具类,用到dome4j和xstream两个jar包

    package com.ifp.weixin.util;
    
    import java.io.InputStream;
    import java.io.Writer;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import com.ifp.weixin.entity.Message.resp.Article;
    import com.ifp.weixin.entity.Message.resp.MusicMessage;
    import com.ifp.weixin.entity.Message.resp.NewsMessage;
    import com.ifp.weixin.entity.Message.resp.TextMessage;
    import com.thoughtworks.xstream.XStream;
    import com.thoughtworks.xstream.core.util.QuickWriter;
    import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
    import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
    import com.thoughtworks.xstream.io.xml.XppDriver;
    
    /**
     * 消息工具类
     * 
     */
    public class MessageUtil {
    
    	/**
    	 * 解析微信发来的请求(XML)
    	 * 
    	 * @param request
    	 * @return
    	 * @throws Exception
    	 */
    	public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
    		// 将解析结果存储在HashMap中
    		Map<String, String> map = new HashMap<String, String>();
    
    		// 从request中取得输入流
    		InputStream inputStream = request.getInputStream();
    		// 读取输入流
    		SAXReader reader = new SAXReader();
    		Document document = reader.read(inputStream);
    		// 得到xml根元素
    		Element root = document.getRootElement();
    		// 得到根元素的所有子节点
    		
    		@SuppressWarnings("unchecked")
    		List<Element> elementList = root.elements();
    
    		// 遍历所有子节点
    		for (Element e : elementList)
    			map.put(e.getName(), e.getText());
    
    		// 释放资源
    		inputStream.close();
    		inputStream = null;
    
    		return map;
    	}
    
    	/**
    	 * 文本消息对象转换成xml
    	 * 
    	 * @param textMessage 文本消息对象
    	 * @return xml
    	 */
    	public static String textMessageToXml(TextMessage textMessage) {
    		xstream.alias("xml", textMessage.getClass());
    		return xstream.toXML(textMessage);
    	}
    
    	/**
    	 * 音乐消息对象转换成xml
    	 * 
    	 * @param musicMessage 音乐消息对象
    	 * @return xml
    	 */
    	public static String musicMessageToXml(MusicMessage musicMessage) {
    		xstream.alias("xml", musicMessage.getClass());
    		return xstream.toXML(musicMessage);
    	}
    
    	/**
    	 * 图文消息对象转换成xml
    	 * 
    	 * @param newsMessage 图文消息对象
    	 * @return xml
    	 */
    	public static String newsMessageToXml(NewsMessage newsMessage) {
    		xstream.alias("xml", newsMessage.getClass());
    		xstream.alias("item", new Article().getClass());
    		return xstream.toXML(newsMessage);
    	}
    
    	/**
    	 * 扩展xstream,使其支持CDATA块
    	 * 
    	 */
    	private static XStream xstream = new XStream(new XppDriver() {
    		public HierarchicalStreamWriter createWriter(Writer out) {
    			return new PrettyPrintWriter(out) {
    				// 对所有xml节点的转换都增加CDATA标记
    				boolean cdata = true;
    				protected void writeText(QuickWriter writer, String text) {
    					if (cdata) {
    						writer.write("<![CDATA[");
    						writer.write(text);
    						writer.write("]]>");
    					} else {
    						writer.write(text);
    					}
    				}
    			};
    		}
    	});
    	
    	
    }

     接下来在处理业务逻辑,建立一个接收并响应消息的service类,并针对用户输入的1或2回复不同的信息给用户

     

    package com.ifp.weixin.biz.core.impl;
    
    import java.util.Date;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.log4j.Logger;
    import org.springframework.stereotype.Service;
    
    import com.ifp.weixin.biz.core.CoreService;
    import com.ifp.weixin.constant.Constant;
    import com.ifp.weixin.entity.Message.resp.TextMessage;
    import com.ifp.weixin.util.MessageUtil;
    
    @Service("coreService")
    public class CoreServiceImpl implements CoreService{
    
    	public static Logger log = Logger.getLogger(CoreServiceImpl.class);
    	
    	
    	@Override
    	public String processRequest(HttpServletRequest request) {
    		String respMessage = null;
    		try {
    			// xml请求解析
    			Map<String, String> requestMap = MessageUtil.parseXml(request);
    
    			// 发送方帐号(open_id)
    			String fromUserName = requestMap.get("FromUserName");
    			// 公众帐号
    			String toUserName = requestMap.get("ToUserName");
    			// 消息类型
    			String msgType = requestMap.get("MsgType");
    
    			TextMessage textMessage = new TextMessage();
    			textMessage.setToUserName(fromUserName);
    			textMessage.setFromUserName(toUserName);
    			textMessage.setCreateTime(new Date().getTime());
    			textMessage.setMsgType(Constant.RESP_MESSAGE_TYPE_TEXT);
    			textMessage.setFuncFlag(0);
    			// 文本消息
    			if (msgType.equals(Constant.REQ_MESSAGE_TYPE_TEXT)) {
    				// 接收用户发送的文本消息内容
    				String content = requestMap.get("Content");
    
    				if ("1".equals(content)) {
    					textMessage.setContent("1是很好的");
    					// 将文本消息对象转换成xml字符串
    					respMessage = MessageUtil.textMessageToXml(textMessage);
    				}else if ("2".equals(content)) {
    					textMessage.setContent("我不是2货");
    					// 将文本消息对象转换成xml字符串
    					respMessage = MessageUtil.textMessageToXml(textMessage);
    				}
    			} 
    			
    			
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return respMessage;
    	}
    
    
    }
    

     接下来在controller里面的post方法里面调用即可

     

    WeixinController类的完整代码

    package com.ifp.weixin.controller;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.io.UnsupportedEncodingException;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import com.ifp.weixin.biz.core.CoreService;
    import com.ifp.weixin.util.SignUtil;
    
    @Controller
    @RequestMapping("/weixinCore")
    public class WeixinController {
    
    	@Resource(name="coreService")
    	private CoreService coreService;
    	
    	@RequestMapping(method = RequestMethod.GET)
    	public void get(HttpServletRequest request, HttpServletResponse response) {
    		// 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
    		String signature = request.getParameter("signature");
    		// 时间戳
    		String timestamp = request.getParameter("timestamp");
    		// 随机数
    		String nonce = request.getParameter("nonce");
    		// 随机字符串
    		String echostr = request.getParameter("echostr");
    
    		PrintWriter out = null;
    		try {
    			out = response.getWriter();
    			// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,否则接入失败
    			if (SignUtil.checkSignature(signature, timestamp, nonce)) {
    				out.print(echostr);
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			out.close();
    			out = null;
    		}
    	}
    
    	@RequestMapping(method = RequestMethod.POST)
    	public void post(HttpServletRequest request, HttpServletResponse response) {
    		try {
    			request.setCharacterEncoding("UTF-8");
    		} catch (UnsupportedEncodingException e) {
    			e.printStackTrace();
    		}
    		response.setCharacterEncoding("UTF-8");
    
    		// 调用核心业务类接收消息、处理消息
    		String respMessage = coreService.processRequest(request);
    
    		// 响应消息
    		PrintWriter out = null;
    		try {
    			out = response.getWriter();
    			out.print(respMessage);
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			out.close();
    			out = null;
    		}
    	}
    
    }
    

     

     效果如下:

     

     ok,大功告成,消息的接收和发送就写完了。

    微信公众平台开发教程Java版(4) 图文消息

    微信公众平台开发教程Java版(四) 图文消息

    引言:

    上一章讲到了消息的接收和发送,但是讲的是最简单的文本信息。

    在微信中用的最多的信息还是图文消息,本章就为大家讲解下微信图文消息是如何实现的。

    包括单图文和多图文消息。

    图文消息的XML数据包结构:

    <xml>
    <ToUserName><![CDATA[toUser]]></ToUserName>
    <FromUserName><![CDATA[fromUser]]></FromUserName>
    <CreateTime>12345678</CreateTime>
    <MsgType><![CDATA[news]]></MsgType>
    <ArticleCount>2</ArticleCount>
    <Articles>
    <item>
    <Title><![CDATA[title1]]></Title> 
    <Description><![CDATA[description1]]></Description>
    <PicUrl><![CDATA[picurl]]></PicUrl>
    <Url><![CDATA[url]]></Url>
    </item>
    <item>
    <Title><![CDATA[title]]></Title>
    <Description><![CDATA[description]]></Description>
    <PicUrl><![CDATA[picurl]]></PicUrl>
    <Url><![CDATA[url]]></Url>
    </item>
    </Articles>
    </xml>

       

     

    从上面结构图中可以看出要注意的几点

    1、图文消息的条数最大限制为10,

    2、多图文中列表中的第一个为大图,其余为小图

    注意:在多图文模式下只有第一个可以显示描述信息,其余的都不显示

    了解了图文消息的结构后,要发送图文消息就简单了。

    我们之前已经封装过消息处理的代码和图文消息的实体类,这里就不啰嗦了,不知道的可以看上一章

    微信公众平台开发教程Java版(三) 消息接收和发送

     

    下面我就上单图文和多图文消息的源代码

     

    package com.ifp.weixin.biz.core.impl;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.log4j.Logger;
    import org.springframework.stereotype.Service;
    
    import com.ifp.weixin.biz.core.CoreService;
    import com.ifp.weixin.constant.Constant;
    import com.ifp.weixin.entity.Message.resp.Article;
    import com.ifp.weixin.entity.Message.resp.NewsMessage;
    import com.ifp.weixin.entity.Message.resp.TextMessage;
    import com.ifp.weixin.util.MessageUtil;
    
    @Service("coreService")
    public class CoreServiceImpl implements CoreService {
    
    	public static Logger log = Logger.getLogger(CoreServiceImpl.class);
    
    	@Override
    	public String processRequest(HttpServletRequest request) {
    		String respMessage = null;
    		try {
    			// xml请求解析
    			Map<String, String> requestMap = MessageUtil.parseXml(request);
    
    			// 发送方帐号(open_id)
    			String fromUserName = requestMap.get("FromUserName");
    			// 公众帐号
    			String toUserName = requestMap.get("ToUserName");
    			// 消息类型
    			String msgType = requestMap.get("MsgType");
    
    			TextMessage textMessage = new TextMessage();
    			textMessage.setToUserName(fromUserName);
    			textMessage.setFromUserName(toUserName);
    			textMessage.setCreateTime(new Date().getTime());
    			textMessage.setMsgType(Constant.RESP_MESSAGE_TYPE_TEXT);
    			textMessage.setFuncFlag(0);
    			
    			// 文本消息
    			if (msgType.equals(Constant.REQ_MESSAGE_TYPE_TEXT)) {
    				// 接收用户发送的文本消息内容
    				String content = requestMap.get("Content");
    
    				// 创建图文消息
    				NewsMessage newsMessage = new NewsMessage();
    				newsMessage.setToUserName(fromUserName);
    				newsMessage.setFromUserName(toUserName);
    				newsMessage.setCreateTime(new Date().getTime());
    				newsMessage.setMsgType(Constant.RESP_MESSAGE_TYPE_NEWS);
    				newsMessage.setFuncFlag(0);
    
    				List<Article> articleList = new ArrayList<Article>();
    				// 单图文消息
    				if ("1".equals(content)) {
    					Article article = new Article();
    					article.setTitle("我是一条单图文消息");
    					article.setDescription("我是描述信息,哈哈哈哈哈哈哈。。。");
    					article.setPicUrl("http://www.iteye.com/upload/logo/user/603624/2dc5ec35-073c-35e7-9b88-274d6b39d560.jpg");
    					article.setUrl("http://tuposky.iteye.com");
    					articleList.add(article);
    					// 设置图文消息个数
    					newsMessage.setArticleCount(articleList.size());
    					// 设置图文消息包含的图文集合
    					newsMessage.setArticles(articleList);
    					// 将图文消息对象转换成xml字符串
    					respMessage = MessageUtil.newsMessageToXml(newsMessage);
    				}
    				// 多图文消息
    				else if ("3".equals(content)) {
    					Article article1 = new Article();
    					article1.setTitle("我是一条多图文消息");
    					article1.setDescription("");
    					article1.setPicUrl("http://www.isic.cn/viewResourcesAction//logo/20130913/2013091314543416032.jpg");
    					article1.setUrl("http://tuposky.iteye.com/blog/2008583");
    
    					Article article2 = new Article();
    					article2.setTitle("微信公众平台开发教程Java版(二)接口配置 ");
    					article2.setDescription("");
    					article2.setPicUrl("http://www.isic.cn/viewResourcesAction//logo/20131021/2013102111243367254.jpg");
    					article2.setUrl("http://tuposky.iteye.com/blog/2008655");
    
    					Article article3 = new Article();
    					article3.setTitle("微信公众平台开发教程Java版(三) 消息接收和发送");
    					article3.setDescription("");
    					article3.setPicUrl("http://www.isic.cn/viewResourcesAction//logo/20131021/2013102111291287031.jpg");
    					article3.setUrl("http://tuposky.iteye.com/blog/2017429");
    
    					articleList.add(article1);
    					articleList.add(article2);
    					articleList.add(article3);
    					newsMessage.setArticleCount(articleList.size());
    					newsMessage.setArticles(articleList);
    					respMessage = MessageUtil.newsMessageToXml(newsMessage);
    				} 
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return respMessage;
    	}
    
    }
    

     

    单个图文和多图文的处理方式其实是一样的

    单图文的时候articleList 的size为1

    多图文的时候为多个。

     

    运行的效果截图如下:

    用户发送1,单图文消息

     

    用户发送3 多图文消息

     

     Ps: 图文消息中的图片是可以引用外部资源的!



    展开全文
  • 微信公众平台开发微信用户开发管理是子恒老师《微信公众平台开发》视频教程的第7部。详细讲解了用php开发微信,对微信公众平台中的粉丝用户管理开发。内容包含微信公众平台用户分组,获取微信用户列表,查询用户...
  • 微信公众平台开发之消息管理是子恒老师《微信公众平台开发》视频教程的第4部。详细讲解了用php开发微信,对微信公众平台中的消息管理开发。内容包含微信关键字回复,多图文消息回复开发,接收图片消息,视频,小视频...
  • 微信公众号开发教程(一)验证接入本篇文章主要介绍了微信公众号开发接入详细流程,希望对刚接触公众号开发的同学有所帮助,有兴趣的同学可多多关注叩丁狼公众号,后续会更新不同的公众号小案例。公众号的分类我们平常...

    作者:陈惠,叩丁狼教育高级讲师。原创文章,转载请注明出处。

    微信公众号开发教程(一)验证接入

    本篇文章主要介绍了微信公众号开发接入详细流程,希望对刚接触公众号开发的同学有所帮助,有兴趣的同学可多多关注叩丁狼公众号,后续会更新不同的公众号小案例。

    公众号的分类

    我们平常在微信应用上会看到有很多的公众号,但是各自并不一样,公众号也分很多种类型,不过最常见的就是服务号和订阅号了。下面我们来看一下他们的区别:

    1、订阅号
    为媒体和个人提供一种信息传播方式,主要偏于为用户传达资讯(类似报纸杂志),主要的定位是阅读,每天可以群发1条消息;

    2、服务号
    为企业,政府或组织提供对用户进行服务,主要偏于服务交互(类似银行提供服务查询),每个月只可群发4条消息;

    3、企业微信(企业号)
    为企业,政府,事业单位,实现生产管理和协作运营的移动化,主要用于公司内部通讯使用,旨在为用户提供移动办公,需要先有成员的通讯信息验证才可以关注成功企业微信;

    通过以下图片我们可以更清晰的看出不同公众号的区别:

    分类.jpg

    区别.jpg

    还有一个比较明显的区别就是,订阅号都是存放在一个名叫订阅号的文件夹中,点开才能看到所有关注过的订阅号,但是服务号却和好友一样直接就显示在聊天列表中。这个大家打开微信客户端便能看到。

    如果大家有需要申请公众号的,要根据实际需求考虑清楚应该申请哪一种公众号
    以下是官方给出的建议,大家可以多参考参考

    1)如果想简单的发送消息,达到宣传效果,建议可选择订阅号;
    2)如果想用公众号获得更多的功能,例如开通微信支付,建议可以选择服务号;
    3)如果想用来管理内部企业员工、团队,对内使用,可申请企业号;
    4)订阅号可通过微信认证资质审核通过后有一次升级为服务号的入口,升级成功后类型不可再变;
    5)服务号不可变更成订阅号。

    4.测试号(用于开发测试及功能体验)
    这种是平常普通用户是涉及不到的,是专门为开发人员准备的一种仅用于测试的公众号。

    由于用户体验和安全性方面的考虑,微信公众号的注册有一定门槛,某些高级接口的权限需要微信认证后才可以获取。所以,为了帮助开发者快速了解和上手微信公众号开发,熟悉各个接口的调用,微信推出了公众帐号测试号,无需公众帐号、快速申请接口测试号,通过手机微信扫描二维码即可获得,利用测试号我们可以体验和测试更多高级功能。

    申请地址:
    http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

    但测试号也不是万能的,部分高级功能,如微信支付,卡券功能等也是不开放的。
    如果要实现支付功能还是得去注册个正式的公众号。

    微信公众平台:

    我们注册的公众号都可以在这个平台上进行管理,比如想弄一些自定义菜单,或者自动回复,或者平常推送的文章,都可以在这上面操作。

    平台地址:
    http://mp.weixin.qq.com

    平台中分为两种管理模式:

    平台提供了两种管理模式给我们,应该根据我们的需求来选择使用不同的模式。

    下面我们来了解一下,两种模式的区别:

    编辑模式

    主要针对非编程人员及信息发布类公众帐号使用。
    开启该模式后,可以方便地通过界面配置“自定义菜单”和“自动回复的消息”。
    好处是可视化界面配置,操作简单,快捷,但是功能有限。

    开发模式

    主要针对具备开发能力的人使用。
    开启该模式后,能够使用微信公众平台开放的接口,但是编辑模式的设置会失效,比如“自定义菜单”和“自动回复的消息”功能。通过编程方式可以实现更多复杂的功能,提供个性化服务。

    总的来说,编辑模式就是为所有人提供的,如果你的需求仅仅只是最常见的菜单,自动回复等,使用编辑模式已经满足,但是如果你需求的功能比较复杂,有很多很多的想法,就需要使用到开发模式。

    下面我们来学习一下,怎么使用这个开发模式。

    开发模式配置流程

    1.登录测试号页面,可以看到该测试号相关的信息

    就是使用电脑打开http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
    使用微信扫一扫二维码,即可在打开的页面中看见微信给你分配的测试号的相关信息。

    下面两个属性是唯一的标识,每个测试号都会有自己的appid与appsecret ,是比较重要的信息,不要随意发给别人。

    appid:是公众号开发识别码,配合开发者密码可调用公众号的接口能力。
    appsecret:是校验公众号开发者身份的密码,具有极高的安全性。

    测试号信息.png

    如果是正式的公众号,登录公众平台官网之后,找到“基本配置”菜单栏也可以获取上公众号相关的信息。

    2.配置参数介绍

    再往下看,我们会看到URL和Token这两个属性,和上面appid/appsecret不同的是,上面的是微信分配给我们的,但是下面这两个是需要我们填进去的。

    我们先来了解一下,这两个属性有什么作用。

    URL:就是指我们自己的服务器地址
    该URL是开发者用来接收和响应微信消息和事件的接口URL
    (必须以http://或https://开头,分别支持80端口和443端口)

    Token:可任意填写,用作生成签名(必须为英文或数字,长度为3-32字符)
    该签名在后边会用到,这里暂时随便填个内容也可以

    接口信息配置.png

    接下来我们需要了解的是微信与我们的服务器交互的过程:

    当我们在微信app上,给公众号发送一条内容的时候,实际会发送到微信的服务器上,此时微信的服务器就会对内容进行封装成某种格式的数据比如xml格式,再转发到我们配置好的URL上,所以该URL实际就是我们处理数据的一个请求路径。所以该URL必须是能暴露给外界访问的一个公网地址,不能使用内网地址,生产环境可以申请腾讯云,阿里云服务器等,但是在开发环境中可以暂时利用一些软件来完成内网穿透,便于修改和测试,如NATAPP,花生壳等软件,使用起来也很方便,在本地安装对应的软件,配置运行后,直接使用软件分配的临时域名来访问本地应用即可,只是偶尔会存在网络不稳定的情况。这里不详细介绍如何使用了,具体教程可参考软件官网。

    交互流程.png

    在开发的过程中,我们会经常使用到微信公众号提供给开发者的开发文档
    具体地址:https://mp.weixin.qq.com/wiki
    大家打开后可以选择”接入指南”,参考微信提供的一些帮助信息。

    3.搭建本地应用(java/python/php等语言均可)

    基本概念都了解过后,我们马上就要开始开发了,因此需要先提供好一个应用,接下来就可以在该应用上添加公众号相关的功能了。

    本文项目使用java语言,SpringMVC+Spring+MyBatis框架

    4.URL接入验证

    我们需要先来了解一下接入的过程是怎么样的。下图是微信官方对接入过程的介绍。

    图片.png

    由以上介绍可知,当我们填入url与token的值,并提交后,微信会发送一个get请求到我们填写的url上,并且携带4个参数,而signature参数结合了开发者填写的token参数和请求中的timestamp参数、nonce参数来做的加密签名,我们在后台需要对该签名进行校验,看是否合法。实际上,我们发现微信带过来的4个参数中并没有带token参数,仅有signature是和token有关的,所以我们应该在本地应用中也准备一个和填入的token相同的参数,再通过微信传入的timestamp与nonce做相同算法的加密操作,若结果与微信传入的signature相同,即为合法,则原样返回echostr参数,代表接入成功,否则不做处理,则接入失败。

    详细流程可参考微信官方提供的逻辑流程图,我们的应用需要以该流程图的步骤来实现。

    图片.png

    代码示例:

    @Controller
    public class WeChatController {
            /**
             * 微信URL接入验证
             * @param signature
             * @param timestamp
             * @param nonce
             * @param echostr
             * @return
             */
            @RequestMapping(value="/weChat",method= RequestMethod.GET)
            @ResponseBody
            public String validate(String signature,String timestamp,String nonce,String echostr){
                //1. 将token、timestamp、nonce三个参数进行字典序排序
                String[] arr = {timestamp,nonce,WeChatUtil.TOKEN};
                Arrays.sort(arr);
                //2. 将三个参数字符串拼接成一个字符串进行sha1加密
                StringBuilder sb = new StringBuilder();
                for (String temp : arr) {
                   sb.append(temp);
                }
                //3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
                if(SecurityUtil.SHA1(sb.toString()).equals(signature)){
                    //接入成功
                    return echostr;
                }
                //接入失败
                return null;
            }
    }
    

    备注:

    1. 代码中WeChatUtil.TOKEN是一个常量,常量值必须要和我们在页面上填入的token值相同,通常可以抽取到配置文件中来注入值比较灵活。
      2.SecurityUtil是一个工具类,提供了sha1加密的方法。

    接入成功后即可根据开发文档实现一些小案例了。文章暂时使用的是测试号来做开发,后续有些高级功能权限不够可能会改成正式公众号来进行开发。


    展开全文
  • 自己开发一套微信支付接口(SDK)是子恒老师《子恒说微信开发》视频教程的第15部。详细讲解了用php开发一套自己的微信支付接口。内容包含微信支付开发思路,基础类开发,微信支付接口应用,公众号发红包,企业付款等等...
  • 微信公众平台开发微信支付开发是子恒老师《微信公众平台开发》视频教程的第12部。详细讲解了用php进行微信支付的开发。内容包含获取支付密钥,微信公众号支付开发,扫码支付,微信刷卡支付,异步处理支付结果等等...
  • 微信开发视频教程-深入浅出微信公众平台实战开发(微网站、LBS云、Api接口调用、服务号高级接口) 一、微信开发实例视频教程总目录: 微信开发实例视频教程 讲师介绍: 易伟,现广东合桓律师事务所专职律师。...
  • php开发微信公众号视频教程,该资源设计微信开发的各个环节,都有视频和源码,很适合初学者入门和学习。
  • 微信群发多图文消息开发是子恒老师《子恒说微信开发》视频教程的第16部。详细讲解了用php开发微信群发图文消息。内容包含图文消息前端页面,一个页面插入多个百度UEditor,群发多图文,查询和删除图文消息等等。欢迎...
  • 微信公众平台开发之公众号JSSDK开发是子恒老师《微信公众平台开发》视频教程的第9部。详细讲解了用php开发微信公众号,对微信公众平台中的JSSDK开发。内容包含用JSSDK获取网络状态,地理位置,分享到朋友圈,QQ,...
  • 微信公众平台开发之自定义菜单管理是子恒老师《微信公众平台开发》视频教程的第5部。详细讲解了用php开发微信,对微信公众平台中的自定义管理开发。内容包含微信添加菜单,获取自定义菜单配置,删除菜单等等。欢迎...
  • 笔者在CSDN博客频道推出微信公众平台开发教程之后,接触了许多公众平台开发爱好者,也帮助他们解决了许多实际的问题,当然这其中有很多问题都是重复的,因此,笔者将这些问题及解答整理出来,以帮助更多初学者少走...
  • 微信公众平台开发微信编辑器开发是子恒老师《微信公众平台开发》视频教程的第11部。详细讲解了用php进行微信编辑器的开发。内容包含微信公众号编辑器开发思路,编辑器前端页面,功能实现,添加新样式等等。欢迎...
  • 李纲明老师鼎力之作,是微信公众号开发类课程,本课程按照腾讯最新技术要求, 于2019年发布的全新微信公众号开发视频教学课程。 课程特点: 1.紧贴最新官方文档 2.课程包含实战案例 3.课程提供源程序,提供课程...
1 2 3 4 5 ... 20
收藏数 9,666
精华内容 3,866