主题:

通过微信公众号API接口实现微信的关注、取消关注、定位、JSSDK定位。

实现效果:

用户关注后,将关注者信息存入数据库:

QQ截图20180608165141.png

取消关注后,软删除(sub_status值变为0):

2.png

定位(关注时即可获取定位信息并存入数据库):

3.png

JSSDK定位:

Screenshot_2018-06-08-16-57-38-232_com.tencent.mm.png

控制器Wechat.php实例

<?php
namespace app\index\controller;
use think\Controller;

class Wechat extends Controller
{
	// 微信signature签名校验
	public function __construct() {
		parent::__construct();
		$this->model = model('Wechat');
	}
	public function index() {
		$check = $this->model->check();
		// 判断模型中返回值
		if(!$check) {
			exit('signature error');
		}
		// 接收微信请求数据流对象
		$xmldata = file_get_contents("php://input");
		// 转换获取到的xml字符串为SimpleXMLElement对象,然后输出对象的键和元素
		$postObj = simplexml_load_string($xmldata, 'SimpleXMLElement', LIBXML_NOCDATA);
		// 将对xml象转化为一个数组
		$data = (array)$postObj;

		// 事件推送,企业级开发中必须做数据真实性判断再执行下一步
		if(isset($data['MsgType']) && $data['MsgType'] == 'event') {
			// 关注
			if($data['Event'] == 'subscribe') {
				$this->model->subscribe($data);
			}
			// 取消关注
			if($data['Event'] == 'unsubscribe') {
				$this->model->unsubscribe($data);
			}
			// 定位
			if($data['Event'] == 'LOCATION') {
				$this->model->location($data);
				// file_put_contents('D://location.txt', var_export($data, true));
				exit('success');
			}
		}

		// 消息推送
		if(isset($data['MsgType']) && $data['MsgType'] == 'text') {
			$this->robot($data);
			exit('success');
		}
		exit(input('get.echostr'));
		
	}
	
	// 获取校验access_token(区别于网页授权access_token)
	public function get_access_token() {
		$access_token = $this->model->access_token();
		return $access_token;
	}


	// 微信网页授权
	public function auth() {
		// 如果正式公众号必须使用备案域名,并配置到公众号回调域名中
		$redirect_uri = config('app.myurl'). 'index.php/index/wechat/userinfo';
		// 第一步:用户同意授权,获取code
		$url_code = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='. config('app.appid'). '&redirect_uri='. urlEncode($redirect_uri).'&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect';
		// 获取$url的Loation请求头信息
		header('Location:'. $url_code);
	}

	// 显示用户信息
	public function userinfo() {
		// 获取code
		$code = input('get.code');
		// 第二步:通过code换取网页授权access_token(注意这个和之前的普通access_token完全不一样)
		$res = $this->model->auth_access_token($code, false);
		$auth_access_token = $res['access_token'];
		$openid = $res['openid'];
		// 第三步:拉取用户信息(需要scope为snsapi_userinfo)
		$userinfo = $this->model->get_userinfo($auth_access_token, $openid);
		dump($userinfo);
	}

	// 获取用户地理位置,JSSDK方式
	public function location() {
		$data['appid'] = config('app.appid');
		$data['timestamp'] = time();
		$data['nonceStr'] = md5(time().rand(1,999));
		// 生成签名
		// 1.获取jsapi_ticket
		$access_token = $this->model->access_token();
		$jsapi_ticket = $this->model->jsapi_ticket($access_token);
		// 2.构造签名字符串
		$params['noncestr'] = $data['nonceStr'];
		$params['jsapi_ticket'] = $jsapi_ticket;
		$params['timestamp'] = $data['timestamp'];
		$params['url'] = config('app.myurl'). 'index.php/index/wechat/location';
		ksort($params);
		// 防止url字符转义
		$str = urldecode(http_build_query($params));
		// 3.生成签名
		$data['signature'] = sha1($str);
		
		// 将所有数据打包成一个数组传入视图,默认传入方法名同名视图
		return $this->fetch('', $data);
	}
}

运行实例 »

点击 "运行实例" 按钮查看在线实例

模型Wechat.php实例

<?php
namespace app\index\model;
use think\Model;
use think\facade\Cache;
use think\Db;

class Wechat extends Model
{
	// 微信signature签名校验
    public function check() {
    	// 将微信服务器的请求数据分别存为变量
    	$signature = input('get.signature');
    	$timestamp = input('get.timestamp');
    	$nonce = input('get.nonce');
    	$echostr = input('get.echostr');
    	// 在框架配置文件中设置微信的token,并读取
    	$token = config('app.wechattoken');
    	// 将获取的数据存到一个数组中
		$tmpArr = array($timestamp, $nonce, $token);
		// 排序数据数组
		sort($tmpArr, SORT_STRING);
		// 将排序后的数组数据拼接成一个字符串
		$str = implode($tmpArr);
		// 判断加密后的字符串与微信请求中的signature是否一致
		if(sha1($str) != $signature) {
			return false;
		}
		return true;
	}

	// 获取校验access_token(区别于网页授权access_token)
	public function access_token($iscache = true) {
		// 如果某个参数使用较多,放到一个变量中,方便更改
		$cache_key = 'access_token';
		// 默认不用删除缓存
		if(!$iscache) {
			Cache::rm($cache_key);
		}
		// 获取缓存中的access_token值
		$access_token = Cache::get($cache_key);
		if($access_token && $iscache) {
			return $access_token;
		}
		// 将appid和appsecret(微信公众号中获取)的值保存至config/app.php中,并调取
		$appid = config('app.appid');
		$appsecret = config('app.appsecret');
		// 拼接url
		$url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='. $appid. '&secret='. $appsecret;
		// 获取access_token的值,返回一个json数据
		$res = http_Get($url);
		// 将json数据转换成数组
		$res = json_decode($res, true);
		// 如果没有拿到access_token的值,返回false
		if(!isset($res['access_token'])) {
			return false;
		}
		// 拿到数据后进行缓存,使用facade中的Cache
		Cache::set($cache_key, $res['access_token'], $res['expires_in']-300);
		return $res['access_token'];
	}

	// 换取网页授权access_token
	public function auth_access_token($code) {
		$appid = config('app.appid');
		$appsecret = config('app.appsecret');
		$url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='. $appid. '&secret='. $appsecret. '&code='. $code. '&grant_type=authorization_code';
		$res = http_Get($url);
		$res = json_decode($res, true);
		if(!isset($res['access_token'])) {
			return false;
		}
		return $res;
	}

	// 获取jsapi_ticket
	public function jsapi_ticket($access_token, $iscache = true) {
		$key = 'jsapi_ticket';
		if(!$iscache) {
			Cache::rm($key);
		}
		$data = Cache::get($key);
		if($data && $iscache) {
			return $data;
		}
		$appid = config('app.appid');
		$appsecret = config('app.appsecret');
		$url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='. $access_token. '&type=jsapi';
		$res = http_Get($url);
		$res = json_decode($res,true);
		if(!isset($res['ticket'])) {
			return false;
		}
		Cache::set($key,$res['ticket'],($res['expires_in']-300));
		return $res['ticket'];
	}

	// 拉取用户信息
	public function get_userinfo($auth_access_token, $openid) {
		$url = 'https://api.weixin.qq.com/sns/userinfo?access_token='. $auth_access_token. '&openid='. $openid. '&lang=zh_CN';
		$res = http_Get($url);
		$res = json_decode($res, true);
		return $res;
	}

	// 关注
	public function subscribe($data) {
		// 检查用户是否已存在
		$user = Db::name('user')->where(array('openid' => $data['FromUserName']))->find();
		// 判断是否有历史关注记录,软删除
		if(!$user) {
			Db::name('user')->insertGetId(array('openid' => $data['FromUserName'],'sub_status' => 1,'add_time' => time()));
		} else {
			Db::name('user')->where(array('openid' => $data['FromUserName']))->update(array('sub_status' => 1));
		}
	}

	// 取消关注
	public function unsubscribe($data) {
		// 软删除
		Db::name('user')
		->where(array('openid' => $data['FromUserName']))
		->update(array('sub_status' => 0));
	}

	// 定位
	public function location($data) {
		// 更新数据库中经纬度数据
		Db::name('user')
		->where(array('openid' => $data['FromUserName']))
		->update(array('lat' => $data['Latitude'],'lng' => $data['Longitude']));
	}

}

运行实例 »

点击 "运行实例" 按钮查看在线实例

地图显示视图location.html实例

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>微信定位</title>	
</head>
<body>
	
</body>
<!-- 这里src中前面可不加http/https -->
<script type="text/javascript" src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script type="text/javascript">
	// 此方法只有在微信中有效
	wx.config({
	    debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
	    appId: '{$appid}', // 必填,公众号的唯一标识
	    timestamp: {$timestamp}, // 必填,生成签名的时间戳
	    nonceStr: '{$nonceStr}', // 必填,生成签名的随机串
	    signature: '{$signature}',// 必填,签名
	    jsApiList: ['getLocation', 'openLocation'] // 必填,需要使用的JS接口列表
	});
	// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
	wx.ready(function(){
    	getLocation(openLocation);
	});
	// 获取地理位置接口
	function getLocation(callback) {
		wx.getLocation({
			type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
			success: function (res) {
				var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
				var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
				var speed = res.speed; // 速度,以米/每秒计
				var accuracy = res.accuracy; // 位置精度
				// 判断返回值是否定义
				if(callback != undefined) {
					callback(res);
				}
			}
		});
	}
	
	// 使用微信内置地图查看位置接口,创建一个方法
	function openLocation(res) {
		wx.openLocation({
			latitude: res.latitude, // 纬度,浮点数,范围为90 ~ -90
			longitude: res.longitude, // 经度,浮点数,范围为180 ~ -180。
			name: '', // 位置名
			address: '', // 地址详情说明
			scale: 15, // 地图缩放级别,整形值,范围从1~28。默认为最大
			infoUrl: '' // 在查看位置界面底部显示的超链接,可点击跳转
		});
	}	
</script>
</html>

运行实例 »

点击 "运行实例" 按钮查看在线实例