微信JS-SDK之图像接口开发详解

  由于现在手头的项目中有一个上传证件照认证的功能(手机端),之前的思路是直接点击上传,然后直接将图片上传到服务器去,这篇文章有讲到(http://www.cnblogs.com/it-cen/p/4535219.html),但在微信里打开网页去上传,速度并不快,而且,假如我上传一张2M大的图片,也没有对其进行压缩处理,这样很影响上传和下载的速度。

  所以,我这里借助微信JSSDK的图像接口对其进行开发实现图片上传的功能,为何我选择此接口?第一,目前的项目是在微信中打开的网页,利用此接口,性能肯定是好一点的啦,毕竟是微信自己的东西;第二,用此接口,开发效率更高嘛;第三,最重要的一点,就是它能对图片进行压缩,假如一张2M的图片,通过微信图片上传接口可以将图片压缩成几百K的大小,这对网站的性能是很有帮助的。

  一、我的思路是:

先调用“拍照或从手机相册选择图片接口”—>选择成功图片后—>调用“上传图片接口”—>上传成功后(也就是图片上传到了微信服务器上)—>调用“下载图片接口”—>将图片下载到自己的服务器存储。

 

  二、JSSDK的使用步骤

  1、概述 

  微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包。

  通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力,为微信用户提供更优质的网页体验。

  2、使用步骤

  步骤一:绑定域名

  先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。

  备注:登录后可在“开发者中心”查看对应的接口权限。

  步骤二:引入JS文件

  在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js

  步骤三:通过config接口注入权限验证配置

  所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用)

1 wx.config({
2     debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
3     appId: '', // 必填,公众号的唯一标识
4     timestamp: , // 必填,生成签名的时间戳
5     nonceStr: '', // 必填,生成签名的随机串
6     signature: '',// 必填,签名,见附录1
7     jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
8 });

  步骤四:通过ready接口处理成功验证

  

1 wx.ready(function(){
2 
3     // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
4 });

  步骤五:通过error接口处理失败验证

1 wx.error(function(res){
2 
3     // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
4 
5 });

  接口调用说明

  所有接口通过wx对象(也可使用jWeixin对象)来调用,参数是一个对象,除了每个接口本身需要传的参数之外,还有以下通用参数:

  1. success:接口调用成功时执行的回调函数。
  2. fail:接口调用失败时执行的回调函数。
  3. complete:接口调用完成时执行的回调函数,无论成功或失败都会执行。
  4. cancel:用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
  5. trigger: 监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。

  备注:不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回


  以上几个函数都带有一个参数,类型为对象,其中除了每个接口本身返回的数据之外,还有一个通用属性errMsg,其值格式如下:

    1. 调用成功时:"xxx:ok" ,其中xxx为调用的接口名
    2. 用户取消时:"xxx:cancel",其中xxx为调用的接口名
    3. 调用失败时:其值为具体错误信息

  

  三、开发及代码分析详解(用的是CI框架,只要是MVC模式都可以)

  1、先在服务器端取到:公众号的唯一标识appId、生成签名的时间戳timestamp、生成签名的随机串nonceStr、签名signature。

  wx_upload.php

 1 <?php
 2 class wx_upload extends xx_Controller {
 3     public function __construct() {
 4         parent::__construct();
 5     }
 6  
 7     public function wxUploadImg() {
 8         //在模板里引入jssdk的js文件
 9         $this->addResLink('http://res.wx.qq.com/open/js/jweixin-1.0.0.js');
10         //取得:公众号的唯一标识appId、生成签名的时间戳timestamp、生成签名的随机串nonceStr、签名signature这些值,并以json形式传到模板页面
11         $this->smartyData['wxJsApi'] = json_encode(array('signPackage' => $this->model->weixin->signPackage()));
12     }
图片上传控制器

   WeixinModel.php

  1 <?php
  2     class WxModel extends ModelBase{
  3         public $appId;
  4         public $appSecret;
  5         public $token;
  6 
  7         public function __construct() {
  8             parent::__construct();
  9 
 10             //审核通过的移动应用所给的AppID和AppSecret
 11             $this->appId = 'wx0000000000000000';
 12             $this->appSecret = '00000000000000000000000000000';
 13             $this->token = '00000000';
 14         }
 15 
 16         /**
 17          * 获取jssdk所需参数的所有值
 18          * @return array
 19          */
 20         public function signPackage() {
 21             $protocol = (!empty($_SERVER['HTTPS'] && $_SERVER['HTTPS'] == 'off' || $_SERVER['port'] == 443)) ? 'https://' : 'http://';
 22             //当前网页的URL
 23             $url = "$protocol$_SERVER['host']$_SERVER['REQUEST_URI']";
 24             //生成签名的时间戳
 25             $timestamp = time();
 26             //生成签名的随机串
 27             $nonceStr = $this->createNonceStr();
 28             //获取公众号用于调用微信JS接口的临时票据
 29             $jsApiTicket = $this->getJsApiTicket();
 30             //对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,
 31             //使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串$str。
 32             //这里需要注意的是所有参数名均为小写字符
 33             $str = "jsapi_ticket=$jsApiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
 34             //对$str进行sha1签名,得到signature:
 35             $signature = sha1($str);
 36             $signPackage = array(
 37                 "appId"     => $this->AppId,
 38                 "nonceStr"  => $nonceStr,
 39                 "timestamp" => $timestamp,
 40                 "url"       => $url,
 41                 "signature" => $signature,
 42                 "rawString" => $string
 43                 );
 44             return $signPackage;
 45         }
 46 
 47         /**
 48          * 创建签名的随机字符串
 49          * @param  int $length 字符串长度
 50          * @return string      随机字符串
 51          */
 52         private function createNonceStr($length == 16) {
 53             $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 54             $str = '';
 55             for ($i=0; $i < $length; $i++) { 
 56                 $str .= substr(mt_rand(0, strlen($chars)), 1);
 57             }
 58             return $str;
 59         }
 60 
 61         /**
 62          * 获取公众号用于调用微信JS接口的临时票据
 63          * @return string 
 64          */
 65         private function getJsApiTicket() {
 66             //先查看redis里是否存了jsapi_ticket此值,假如有,就直接返回
 67             $jsApiTicket = $this->library->redisCache->get('weixin:ticket');
 68             if (!$jsApiTicket) {
 69                 //先获取access_token(公众号的全局唯一票据)
 70                 $accessToken = $this->getApiToken();
 71                 //通过access_token 采用http GET方式请求获得jsapi_ticket
 72                 $result = $this->callApi("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=$accessToken&type=jsapi");
 73                 //得到了jsapi_ticket
 74                 $jsApiTicket = $result['ticket'];
 75                 //将jsapi_ticket缓存到redis里面,下次就不用再请求去取了
 76                 $expire = max(1, intval($result['expire']) - 60);
 77                 $this->library->redisCache->set('weixin:ticket', $jsApiTicket, $expire);
 78             }
 79             return $jsApiTicket;
 80         }
 81 
 82         /**
 83          * 获取众号的全局唯一票据access_token
 84          * @param  boolean $forceRefresh 是否强制刷新
 85          * @return string                返回access_token
 86          */
 87         private function getApiToken($forceRefresh = false) {
 88             //先查看redis是否存了accessToken,如果有了,就不用再去微信server去请求了(提高效率)
 89             $accessToken = $this->library->redisCache->get('weixin:accessToken');
 90             //强制刷新accessToken或者accessToken为空时就去请求accessToken
 91             if ($forceRefresh || empty($accessToken)) {
 92                 //请求得到accessToken
 93                 $result = $this->callApi("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}");
 94                 $accessToken = $result['access_token'];
 95                 $expire = max(1, intval($result['expire']) - 60);
 96                 //将其存进redis里面去
 97                 $this->library->redisCache->set('weixin:accessToken', $accessToken, $expire);
 98             }
 99             return $accessToken;
100         }
获取到appId、nonceStr、timestamp、signature模型

  这里要补充一些 JS-SDK使用权限签名算法 的思路和注意点(这里我直接复制官网文档给大家看看)

    jsapi_ticket

    生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。

    1、获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token

    2、用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket

      https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

    成功返回如下JSON:

1 {
2 "errcode":0,
3 "errmsg":"ok",
4 "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
5 "expires_in":7200
6 }

    获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。

    

    签名算法

    签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。
    即signature=sha1(string1)。 示例:

    • noncestr=Wm3WZYTPz0wzccnW
    • jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
    • timestamp=1414587457
    • url=http://mp.weixin.qq.com?params=value

    步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:

      jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

    步骤2. 对string1进行sha1签名,得到signature:

      0f9de62fce790f9a083d5c99e95740ceb90c27ed

    注意事项

      1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。

      2.签名用的url必须是调用JS接口页面的完整URL。

      3.出于安全考虑,开发者必须在服务器端实现签名的逻辑

 

   2、取到我们所需要的值后,就在js文件里面开始使用了

  uploadImg.tpl

<script>
        $(function(){
            $.util.wxMenuImage('{$wxJsApi|default:""}')
        });
</script>

   uploadImg.js

 1 if(typeof($util)=='undefined')$util={};
 2 
 3 $.util.wxMenuImage = function(json) {
 4     if (json.length == 0) return; 
 5     //解析json变成js对象
 6     wxJsApi = JSON.parse(json);
 7 
 8     //通过config接口注入权限验证配置
 9     wx.config({
10         debug: false,   //开启调试模式,调用的所有api的返回值会在客户端alert出来
11         appId: wxJsApi.signPackage.appId,   //公众号的唯一标识
12         timestamp: wxJsApi.signPackage.timestamp,   //生成签名的时间戳
13         nonceStr: wxJsApi.signPackage.nonceStr, //生成签名的随机串
14         signature: wxJsApi.signPackage.signature,   //签名
15         jsApiList: ['chooseImage', 'uploadImage']   //需要使用的JS接口列表 这里我用了选择图片和上传图片接口
16     });
17 
18     //通过ready接口处理成功验证,config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后
19     wx.ready(function(){
20         //得到上传图片按钮
21         document.querySelector('#uploadImage').onclick = function() {
22             var images = {localId:[],serverId:[]};
23             //调用 拍照或从手机相册中选图接口
24             wx.chooseImage({
25                 success: function(res) {
26                     if (res.localIds.length != 1) {
27                         alert('只能上传一张图片');
28                         return;
29                     }
30                     //返回选定照片的本地ID列表
31                     iamges.localId = res.localIds;
32                     images.serverId = [];
33                     //上传图片函数
34                     function upload() {
35                         //调用上传图片接口
36                         wx.uploadImage({
37                             localId: images.localId[0], // 需要上传的图片的本地ID,由chooseImage接口获得
38                             isShowProcess: 1,   // 默认为1,显示进度提示
39                             success: function(res) {
40                                 //返回图片的服务器端ID res.serverId,然后调用wxImgCallback函数进行下载图片操作
41                                 wxImgCallback(res.serverId);
42                             },
43                             fail: function(res) {
44                                 alert('上传失败');
45                             }
46                         });
47                     }
48                     upload();
49                 }
50             });
51         }
52     });
53 }
54 
55 
56 function wxImgCallback(serverId) {
57     //将serverId传给wx_upload.php的upload方法
58     var url = 'wx_upload/upload/'+serverId;
59     $.getJSON(url, function(data){
60         if (data.code == 0) {
61             alert(data.msg);
62         } else if (data.code == 1) {
63             //存储到服务器成功后的处理
64             //
65         }
66     });
67 }
图片选择和图片上传接口调用

   

  3、图片上传完成后会返回一个serverId,然后通过这个来下载图片到本地服务器

  这里先补充下如何调用下载图片接口(我直接复制官方文档的说明了)

    公众号可调用本接口来获取多媒体文件。请注意,视频文件不支持下载,调用该接口需http协议。

    接口调用请求说明

    http请求方式: GET
    http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID

    参数说明

参数是否必须说明
access_token 调用接口凭证
media_id 媒体文件ID

    返回说明

    正确情况下的返回HTTP头如下:

      HTTP/1.1 200 OK
      Connection: close
      Content-Type: image/jpeg 
      Content-disposition: attachment; filename="MEDIA_ID.jpg"
      Date: Sun, 06 Jan 2013 10:20:18 GMT
      Cache-Control: no-cache, must-revalidate
      Content-Length: 339721
      curl -G "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID"

    错误情况下的返回JSON数据包示例如下(示例为无效媒体ID错误)::

      {"errcode":40007,"errmsg":"invalid media_id"}


 接下来看自己写的代码
 wx_upload.php
 1     /*********************图片下载到本地服务器****************************************/
 2     //从微信服务器读取图片,然后下载到本地服务器
 3     public function upload($media_id) {
 4         //图片文件名
 5         $fileName = md5($this->wxId."/$media_id");
 6         //调用下载图片接口,返回路径
 7         $path = $this->weixin->wxDownImg($media_id, sys_get_temp_dir()."$fileName");
 8         if ($path != false) {
 9             //将图片的路径插入数据库去存储
10             if ($this->model->weixin->updateByWxid($this->wxId, array('img_path'=>$path))) {
11                 $this->output->_display(json_encode(
12                     array(
13                             'code'=>1,
14                             'msg'=>'上传成功',
15                             'fileUrl' =>$path;
16                         )
17                 ));
18             } else {
19                 $this->output->_display(json_encode2(array('code'=>0,'msg' => '上传失败','err'=>'1')));
20             }
21         } else {
22             $this->output->_display(json_encode2(array('code'=>0,'msg' => '上传失败','err'=>'2')));
23         }
24         
25     }
从微信服务器下载图片到本地存储

  WeixinModel.php

 1 //从微信服务器端下载图片到本地服务器
 2         public function wxDownImg($media_id, $path) {
 3             //调用 多媒体文件下载接口
 4             $url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token={$this->model->weixin->_getApiToken()}&media_id=$media_id";
 5             //用curl请求,返回文件资源和curl句柄的信息
 6             $info = $this->curl_request($url);
 7             //文件类型
 8             $types = array('image/bmp'=>'.bmp', 'image/gif'=>'.gif', 'image/jpeg'=>'.jpg', 'image/png'=>'.png');
 9             //判断响应首部里的的content-type的值是否是这四种图片类型
10             if (isset($types[$info['header']['content_type']])) {
11                 //文件的uri
12                 $path = $path.$types[$info['header']['content_type']];
13             } else {
14                 return false;
15             }
16 
17             //将资源写入文件里
18             if ($this->saveFile($path, $info['body'])) {
19                 //将文件保存在本地目录
20                 $imgPath = rtrim(base_url(), '/').'/img'.date('Ymd').'/'.md5($this->controller->wxId.$media_id).$types[$info['header'['content_type']]];
21                 if (!is_dir($imgPath)) {
22                     if(mkdir($imgPath)) {
23                         if (false !== rename($path, $imgPath) {
24                             return $imgPath;
25                         }
26                     }
27                 }
28                 return $path;
29             }
30 
31             return false;
32 
33         }
34 
35         /**
36          * curl请求资源
37          * @param  string $url 请求url
38          * @return array 
39          */
40         private function curl_request($url = '') {
41             if ($url == '') return;
42             $ch = curl_init();
43             //这里返回响应报文时,只要body的内容,其他的都不要
44             curl_setopt($ch, CURLOPT_HEADER, 0);
45             curl_setopt($ch, CURLOPT_NOBODY, 0);
46             curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
47             curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
48             curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
49             $package = curl_exec($ch);
50             //获取curl连接句柄的信息
51             $httpInfo = curl_getinfo($ch);
52             curl_close($ch);
53 
54             $info = array_merge(array($package), array($httpInfo));
55 
56             return $info;
57 
58         }
59 
60         /**
61          * 将资源写入文件
62          * @param  string 资源uri
63          * @param  source 资源
64          * @return boolean 
65          */
66         private function saveFile($path, $fileContent) {
67             $fp = fopen($path, 'w');
68             if (false !== $localFile) {
69                 if (false !== fwrite($fp, $fileContent)) {
70                     fclose($fp);
71                     return true;
72                 }
73             }
74             return false;
75         }
从微信服务器下载图片到本地存储接口

  到这里,已经完成了:

    先调用“拍照或从手机相册选择图片接口”—>选择成功图片后—>调用“上传图片接口”—>上传成功后(也就是图片上传到了微信服务器上)—>调用“下载图片接口”—>将图片下载到自己的服务器存储。

  这一思路的实现。我们用到了微信的选择图片接口、上传图片接口和下载媒体资源接口。

  下面我附上这一接口开发的全部代码:

 1 <?php
 2 class wx_upload extends xx_Controller {
 3     public function __construct() {
 4         parent::__construct();
 5     }
 6  
 7     public function wxUploadImg() {
 8         //在模板里引入jssdk的js文件
 9         $this->addResLink('http://res.wx.qq.com/open/js/jweixin-1.0.0.js');
10         //取得:公众号的唯一标识appId、生成签名的时间戳timestamp、生成签名的随机串nonceStr、签名signature这些值,并以json形式传到模板页面
11         $this->smartyData['wxJsApi'] = json_encode(array('signPackage' => $this->model->weixin->signPackage()));
12     }
13 
14     /*********************图片下载到本地服务器****************************************/
15     //从微信服务器读取图片,然后下载到本地服务器
16     public function upload($media_id) {
17         //图片文件名
18         $fileName = md5($this->wxId."/$media_id");
19         //调用下载图片接口,返回路径
20         $path = $this->weixin->wxDownImg($media_id, sys_get_temp_dir()."$fileName");
21         if ($path != false) {
22             //将图片的路径插入数据库去存储
23             if ($this->model->weixin->updateByWxid($this->wxId, array('img_path'=>$path))) {
24                 $this->output->_display(json_encode(
25                     array(
26                             'code'=>1,
27                             'msg'=>'上传成功',
28                             'fileUrl' =>$path;
29                         )
30                 ));
31             } else {
32                 $this->output->_display(json_encode2(array('code'=>0,'msg' => '上传失败','err'=>'1')));
33             }
34         } else {
35             $this->output->_display(json_encode2(array('code'=>0,'msg' => '上传失败','err'=>'2')));
36         }
37         
38     }
39 }
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 ?>
wx_upload.php
  1 <?php
  2     class WxModel extends ModelBase{
  3         public $appId;
  4         public $appSecret;
  5         public $token;
  6 
  7         public function __construct() {
  8             parent::__construct();
  9 
 10             //审核通过的移动应用所给的AppID和AppSecret
 11             $this->appId = 'wx0000000000000000';
 12             $this->appSecret = '00000000000000000000000000000';
 13             $this->token = '00000000';
 14         }
 15 
 16         /**
 17          * 获取jssdk所需参数的所有值
 18          * @return array
 19          */
 20         public function signPackage() {
 21             $protocol = (!empty($_SERVER['HTTPS'] && $_SERVER['HTTPS'] == 'off' || $_SERVER['port'] == 443)) ? 'https://' : 'http://';
 22             //当前网页的URL
 23             $url = "$protocol$_SERVER['host']$_SERVER['REQUEST_URI']";
 24             //生成签名的时间戳
 25             $timestamp = time();
 26             //生成签名的随机串
 27             $nonceStr = $this->createNonceStr();
 28             //获取公众号用于调用微信JS接口的临时票据
 29             $jsApiTicket = $this->getJsApiTicket();
 30             //对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,
 31             //使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串$str。
 32             //这里需要注意的是所有参数名均为小写字符
 33             $str = "jsapi_ticket=$jsApiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
 34             //对$str进行sha1签名,得到signature:
 35             $signature = sha1($str);
 36             $signPackage = array(
 37                 "appId"     => $this->AppId,
 38                 "nonceStr"  => $nonceStr,
 39                 "timestamp" => $timestamp,
 40                 "url"       => $url,
 41                 "signature" => $signature,
 42                 "rawString" => $string
 43                 );
 44             return $signPackage;
 45         }
 46 
 47         /**
 48          * 创建签名的随机字符串
 49          * @param  int $length 字符串长度
 50          * @return string      随机字符串
 51          */
 52         private function createNonceStr($length == 16) {
 53             $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 54             $str = '';
 55             for ($i=0; $i < $length; $i++) { 
 56                 $str .= substr(mt_rand(0, strlen($chars)), 1);
 57             }
 58             return $str;
 59         }
 60 
 61         /**
 62          * 获取公众号用于调用微信JS接口的临时票据
 63          * @return string 
 64          */
 65         private function getJsApiTicket() {
 66             //先查看redis里是否存了jsapi_ticket此值,假如有,就直接返回
 67             $jsApiTicket = $this->library->redisCache->get('weixin:ticket');
 68             if (!$jsApiTicket) {
 69                 //先获取access_token(公众号的全局唯一票据)
 70                 $accessToken = $this->getApiToken();
 71                 //通过access_token 采用http GET方式请求获得jsapi_ticket
 72                 $result = $this->callApi("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=$accessToken&type=jsapi");
 73                 //得到了jsapi_ticket
 74                 $jsApiTicket = $result['ticket'];
 75                 //将jsapi_ticket缓存到redis里面,下次就不用再请求去取了
 76                 $expire = max(1, intval($result['expire']) - 60);
 77                 $this->library->redisCache->set('weixin:ticket', $jsApiTicket, $expire);
 78             }
 79             return $jsApiTicket;
 80         }
 81 
 82         /**
 83          * 获取众号的全局唯一票据access_token
 84          * @param  boolean $forceRefresh 是否强制刷新
 85          * @return string                返回access_token
 86          */
 87         private function getApiToken($forceRefresh = false) {
 88             //先查看redis是否存了accessToken,如果有了,就不用再去微信server去请求了(提高效率)
 89             $accessToken = $this->library->redisCache->get('weixin:accessToken');
 90             //强制刷新accessToken或者accessToken为空时就去请求accessToken
 91             if ($forceRefresh || empty($accessToken)) {
 92                 //请求得到accessToken
 93                 $result = $this->callApi("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}");
 94                 $accessToken = $result['access_token'];
 95                 $expire = max(1, intval($result['expire']) - 60);
 96                 //将其存进redis里面去
 97                 $this->library->redisCache->set('weixin:accessToken', $accessToken, $expire);
 98             }
 99             return $accessToken;
100         }
101 
102         //从微信服务器端下载图片到本地服务器
103         public function wxDownImg($media_id, $path) {
104             //调用 多媒体文件下载接口
105             $url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token={$this->model->weixin->_getApiToken()}&media_id=$media_id";
106             //用curl请求,返回文件资源和curl句柄的信息
107             $info = $this->curl_request($url);
108             //文件类型
109             $types = array('image/bmp'=>'.bmp', 'image/gif'=>'.gif', 'image/jpeg'=>'.jpg', 'image/png'=>'.png');
110             //判断响应首部里的的content-type的值是否是这四种图片类型
111             if (isset($types[$info['header']['content_type']])) {
112                 //文件的uri
113                 $path = $path.$types[$info['header']['content_type']];
114             } else {
115                 return false;
116             }
117 
118             //将资源写入文件里
119             if ($this->saveFile($path, $info['body'])) {
120                 //将文件保存在本地目录
121                 $imgPath = rtrim(base_url(), '/').'/img'.date('Ymd').'/'.md5($this->controller->wxId.$media_id).$types[$info['header'['content_type']]];
122                 if (!is_dir($imgPath)) {
123                     if(mkdir($imgPath)) {
124                         if (false !== rename($path, $imgPath) {
125                             return $imgPath;
126                         }
127                     }
128                 }
129                 return $path;
130             }
131 
132             return false;
133 
134         }
135 
136         /**
137          * curl请求资源
138          * @param  string $url 请求url
139          * @return array 
140          */
141         private function curl_request($url = '') {
142             if ($url == '') return;
143             $ch = curl_init();
144             //这里返回响应报文时,只要body的内容,其他的都不要
145             curl_setopt($ch, CURLOPT_HEADER, 0);
146             curl_setopt($ch, CURLOPT_NOBODY, 0);
147             curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
148             curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
149             curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
150             $package = curl_exec($ch);
151             //获取curl连接句柄的信息
152             $httpInfo = curl_getinfo($ch);
153             curl_close($ch);
154 
155             $info = array_merge(array($package), array($httpInfo));
156 
157             return $info;
158 
159         }
160 
161         /**
162          * 将资源写入文件
163          * @param  string 资源uri
164          * @param  source 资源
165          * @return boolean 
166          */
167         private function saveFile($path, $fileContent) {
168             $fp = fopen($path, 'w');
169             if (false !== $localFile) {
170                 if (false !== fwrite($fp, $fileContent)) {
171                     fclose($fp);
172                     return true;
173                 }
174             }
175             return false;
176         }
177 
178     }
179 
180 
181 
182 
183 
184 
185 
186 
187 ?>
WeixinModel.php
 1 <html>
 2     <head>
 3         
 4     </head>
 5     <body>
 6         <button id="uploadImage">点击上传图片</button>
 7         <script>
 8         $(function(){
 9             $.util.wxMenuImage('{$wxJsApi|default:""}')
10         });
11         </script>
12     </body>
13 </html>
uploadImg.tpl
 1 if(typeof($util)=='undefined')$util={};
 2 
 3 $.util.wxMenuImage = function(json) {
 4     if (json.length == 0) return; 
 5     //解析json变成js对象
 6     wxJsApi = JSON.parse(json);
 7 
 8     //通过config接口注入权限验证配置
 9     wx.config({
10         debug: false,   //开启调试模式,调用的所有api的返回值会在客户端alert出来
11         appId: wxJsApi.signPackage.appId,   //公众号的唯一标识
12         timestamp: wxJsApi.signPackage.timestamp,   //生成签名的时间戳
13         nonceStr: wxJsApi.signPackage.nonceStr, //生成签名的随机串
14         signature: wxJsApi.signPackage.signature,   //签名
15         jsApiList: ['chooseImage', 'uploadImage']   //需要使用的JS接口列表 这里我用了选择图片和上传图片接口
16     });
17 
18     //通过ready接口处理成功验证,config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后
19     wx.ready(function(){
20         //得到上传图片按钮
21         document.querySelector('#uploadImage').onclick = function() {
22             var images = {localId:[],serverId:[]};
23             //调用 拍照或从手机相册中选图接口
24             wx.chooseImage({
25                 success: function(res) {
26                     if (res.localIds.length != 1) {
27                         alert('只能上传一张图片');
28                         return;
29                     }
30                     //返回选定照片的本地ID列表
31                     iamges.localId = res.localIds;
32                     images.serverId = [];
33                     //上传图片函数
34                     function upload() {
35                         //调用上传图片接口
36                         wx.uploadImage({
37                             localId: images.localId[0], // 需要上传的图片的本地ID,由chooseImage接口获得
38                             isShowProcess: 1,   // 默认为1,显示进度提示
39                             success: function(res) {
40                                 //返回图片的服务器端ID res.serverId,然后调用wxImgCallback函数进行下载图片操作
41                                 wxImgCallback(res.serverId);
42                             },
43                             fail: function(res) {
44                                 alert('上传失败');
45                             }
46                         });
47                     }
48                     upload();
49                 }
50             });
51         }
52     });
53 }
54 
55 
56 function wxImgCallback(serverId) {
57     //将serverId传给wx_upload.php的upload方法
58     var url = 'wx_upload/upload/'+serverId;
59     $.getJSON(url, function(data){
60         if (data.code == 0) {
61             alert(data.msg);
62         } else if (data.code == 1) {
63             //存储到服务器成功后的处理
64             //
65         }
66     });
67 }
uploadImg.js

  代码中有些方法没有贴出来,大家要是想看看,可以到http://www.cnblogs.com/it-cen/p/4535219.html 这篇博文去看。

  本次讲解就到此,这篇博文是给对微信接口开发有兴趣的朋友参考,如果你是高手,完全可以绕道。

 

 

  如果此博文中有哪里讲得让人难以理解,欢迎留言交流,若有讲解错的地方欢迎指出。

  如果您觉得您能在此博文学到了新知识,请为我顶一个,如文章中有解释错的地方,欢迎指出。

  互相学习,共同进步!

 

posted @ 2015-06-19 12:38  __kelly_  阅读(9626)  评论(8编辑  收藏  举报