微信服务号开发指南(PHP)

By admin, 2 十一月, 2015

微信的订阅号和服务号开发差别不大,本文同样适用于订阅号的开发。

1. 申请服务号

到微信公众平台申请服务号:https://mp.weixin.qq.com/

微信的权限不时会调整,网上一些文章的描述会过时,最好是登录服务号的后台,查看有哪些权限。很多高级的权限都需要认证的服务号的,认证需要提供公司营业执照等,并每年缴纳一笔费用(印象中是300元/年)。

2. 开发者中心

登录公众平台,做好基本设置(我也不记得有什么东西需要设置了),然后就点击公众平台左下角的“开发者中心”链接查看和设置权限等信息。

微信开发者中心截图

其中 开发者文档 会是需要经常查阅的文档,可以收藏到浏览器书签。

3. 接入指南

首先,我们要搭一个PHP的后台,设定一个微信的入口地址,这个地址需要填到公众平台里。微信服务器根据我们填的接口地址,会把服务号的各种请求发向这个地址,PHP程序根据请求的参数返回不同的信息。

这里是官方的接入指南文档,我们要写的第一段代码是响应“第二步:验证服务器地址的有效性”,通过了这个验证,就算完成了PHP接口的雏形。之后就是扩展这段PHP代码,让其可以处理更复杂的请求。官方的文档有一小段PHP代码示例,我这里再提供一段示例(但**不能运行**,因为依赖的相关函数和数据表实现没有提供)。如果你觉得看代码头痛(太多和接口定义相关的逻辑,我自己看也头痛),可以略过。当实现到相关接口的时候再回来看,也许有帮助。

 

  public function index() {
    include_once 'weixin.php';
    if (!Weixin::checkSignature()) {
      return 'Signature validation fail.';
    }
    
    // API verificaiton for weixin
    if (isset($_GET['echostr']))
      return $_GET["echostr"];

    // get post data, May be due to the different environments
    $postStr = file_get_contents("php://input");
    if (empty($postStr)) {
      exit;
      //AccessLog::createLog(0, 'weixin', 0, 'no post data');
    }

    // extract post data

    $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
    // 可以var_dump($postObj),这样就知道微信请求的所有参数了
    $openid = $postObj->FromUserName;
    $type = $postObj->MsgType;

    $user = WeixinUser::ofOpenid($openid)->first(); // 查找用户,我们可以设计一个用户数据表,以用户的openid作为识别用户的标记

    if (!$user) {
      $ret = Weixin::getUserInfo($openid);
      $user = WeixinUser::createUser($ret);
    }

    if ($type == 'event') {
      $event = $postObj->Event;
      $key = $postObj->EventKey;

      if ($event == 'subscribe') {
        // update subscribe status
        $user->subscribe = 1;
        $user->save();
                Weixin::reply($openid, $ret);  
      } elseif ($event == 'unsubscribe') {
        // update subscribe status
        $user->subscribe = 0;
        $user->save();

      } elseif ($type == 'text') {

        Weixin::reply($openid, '你输入了' . $postObj->Content);
      }
    }
  }

上面的代码引用了一个weixin.php的文件,我简单列一下里面几个函数的实现,完整的实现应该参考开发文档里描述的数据格式。

<?php

class Weixin {

  static function checkSignature() {
    $signature = filter_input(INPUT_GET, 'signature');
    $timestamp = filter_input(INPUT_GET, 'timestamp');
    $nonce = filter_input(INPUT_GET, 'nonce');

    $token = Config::get('app.weixin_token');
    $tmpArr = array($token, $timestamp, $nonce);
    sort($tmpArr, SORT_STRING);
    $tmpStr = implode($tmpArr);
    $tmpStr = sha1($tmpStr);

    return ($tmpStr == $signature);
  }

  static function getUserInfo($openid) {
    $access_token = Weixin::getAccessToken();
    if (!$access_token)
      return FALSE;

    $url = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=' .
      $access_token . '&openid=' . $openid . '&lang=zh_CN';
    
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($curl, CURLOPT_TIMEOUT, 30);
    $response = curl_exec($curl);
    $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);

    // Check if login was successful
    if ($http_code != 200) {
      print curl_error($curl);
      return FALSE;
    }

    curl_close($curl);
    return json_decode($response);
  }

  
  static function getAccessToken($nocache = FALSE) {
    // try to get from cache first
    $cache_file = '/tmp/weixin_access_token';
    if (!$nocache && file_exists($cache_file) &&
        $_SERVER['REQUEST_TIME'] - filemtime($cache_file) < 7000) {
      static $retry = 0;
      $retry++;
      if ($retry > 3) {
        print "Fail to get access token for 3 times.\n";
        return FALSE;
      }
      $content = file($cache_file);
      $token = $content[0];
      return rtrim($token);
    }

    $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&' .
        'appid=' . Config::get('app.weixin_appid') .
        '&secret=' . Config::get('app.weixin_secret');

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($curl, CURLOPT_TIMEOUT, 30);
    $response = curl_exec($curl);
    $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);

    // Check if login was successful
    if ($http_code != 200) {
      print curl_error($curl);
      return FALSE;
    }

    curl_close($curl);

    $ret = json_decode($response);
    if (isset($ret->access_token)) {
      // save cache
      $fp = fopen($cache_file, "w"); 
      fwrite($fp, $ret->access_token);
      fclose($fp);

      return $ret->access_token;
    } else {
      print $response;
      return FALSE;
    }
  }

  static function reply($openid, $content) {
    if (is_array($content)) {
      // news type
      $ret = '<xml>
        <ToUserName><![CDATA[' . $openid . ']]></ToUserName>
        <FromUserName><![CDATA[' . Config::get('app.weixin_account') . ']]></FromUserName>
        <CreateTime>' . $_SERVER['REQUEST_TIME'] . '</CreateTime>
        <MsgType><![CDATA[news]]></MsgType>
        <ArticleCount>' . count($content) . '</ArticleCount>
        <Articles>';

      foreach ($content as $item) {
        $ret .= '<item>
        <Title><![CDATA[' . $item['title'] . ']]></Title>
        <Description><![CDATA[' . $item['description'] . ']]></Description>
        <PicUrl><![CDATA[' . $item['picurl'] . ']]></PicUrl>
        <Url><![CDATA[' . $item['url'] . ']]></Url>
        </item>';
      }

      $ret .= '</Articles></xml>';
      echo $ret;
    } else {
      // text type
      $textTpl = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime>%s</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[%s]]></Content>
        </xml>";

      $ret = sprintf($textTpl, $openid, Config::get('app.weixin_account'),
          $_SERVER['REQUEST_TIME'], $content);
      echo $ret;
    }
  }
}

4. 调试

由于没有界面,PHP代码如果出错,需要在web服务器(例如apache)的错误日志(error_log)里找。公众平台提供在线接口调试工具,也可以帮助调查一些问题。

5. 网页授权获取用户基本信息

通常,我们的应用场景是这样的:搞一个服务号,里面有几个按钮,点击部分按钮会回复一些内容,点击部分按钮会跳到网页。有些网页是需要识别用户身份的,就好像用户通过服务号点过来就会自动登录一样。这是怎样做到的呢?认证的微信服务号有一个获取用户信息的功能,比如openid、昵称等。注意,同一个用户访问不同服务号openid是不同的,但访问同一个服务号是唯一的。我们可以把openid映射到网站里的用户(可能需要用户做些操作来关联)。以下是该API的官方文档

6. 开发手机网站

当获取了用户的ID后,剩下的工作就是纯网站开发了。可以使用jQuery Mobile,做出来的网站体验很像App。网站开发技术参考PHP网站开发指引

标签

评论

Restricted HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id> <img src>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。
验证码
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
请输入"Drupal10"