Java微信公众平台开发之获取地理位置


本部分需要用到微信的JS-SDK,微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包。
通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力,为微信用户提供更优质的网页体验。

官方文档

一、JS-SDK引入

1.先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”,和网页授权一样只是个域名。

2.在需要调用JS接口的页面引入如下JS文件之一

<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js">script>
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js">script>

二、通过config接口注入权限验证配置

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

首先生成这个signature之前需要获取到一个临时票据jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,同样也需要个中控服务器控制刷新。

1、获取临时票据

封装返回结果
package com.phil.wechatauth.model.resp;

import com.phil.common.result.ResultState;

/**
 * jsapi_ticket是公众号用于调用微信JS接口的临时票据
 * @author phil
 * @date  2017年8月21日
 *
 */
public class JsapiTicket extends ResultState {

    /**
     * 
     */
    private static final long serialVersionUID = -357009110782376503L;
    
    private String ticket; //jsapi_ticket
    
    private String expires_in;

    public String getTicket() {
        return ticket;
    }

    public void setTicket(String ticket) {
        this.ticket = ticket;
    }

    public String getExpires_in() {
        return expires_in;
    }

    public void setExpires_in(String expires_in) {
        this.expires_in = expires_in;
    }
}

获取方法

    /**
     * 获取jsapi_ticket 调用微信JS接口的临时票据
     * @return
     */
    public String getTicket(String accessToken) {
        JsapiTicket jsapiTicket = null;
        Map params = new TreeMap();
        params.put("access_token",accessToken);
        params.put("type", "jsapi");
        String result = HttpReqUtil.HttpDefaultExecute(HttpReqUtil.GET_METHOD, WechatConfig.GET_TICKET_URL, params,"");
        if(StringUtils.isNotBlank(result)){
            jsapiTicket = JsonUtil.fromJson(result, JsapiTicket.class);
        }
        if(jsapiTicket.getErrcode()==0){
            return jsapiTicket.getTicket();
        }
        return null;
    }

2、生成签名并返回参数

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

string1示例如下

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

这里有个坑,页面是nonceStr,但是签名的字段是noncestr,注意大小写

简单封装下JS-SDK config配置信息

package com.phil.wechatauth.model.resp;

/**
 * JS-SDK的页面配置信息
 * @author phil
 * @date 2017年8月22日
 *
 */
public class JsWechatConfig {

    private String appId;

    private long timestamp;

    private String noncestr;

    private String signature;

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public String getNoncestr() {
        return noncestr;
    }

    public void setNoncestr(String noncestr) {
        this.noncestr = noncestr;
    }

    public String getSignature() {
        return signature;
    }

    public void setSignature(String signature) {
        this.signature = signature;
    }
}

添加配置信息到页面

/**
 * 
 */
package com.phil.wechatauth.controller;

import java.util.SortedMap;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.phil.common.config.SystemConfig;
import com.phil.common.config.WechatConfig;
import com.phil.common.util.DateTimeUtil;
import com.phil.common.util.PayUtil;
import com.phil.common.util.SignatureUtil;
import com.phil.wechatauth.model.resp.JsWechatConfig;
import com.phil.wechatauth.service.WechatAuthService;

/**
 * JS-SDK
 * @author phil
 * @date  2017年8月21日
 *
 */
@Controller
@RequestMapping("/auth")
public class WechatAuthController {
    
    @Autowired
    private WechatAuthService wechatAuthService;
    
    /**
     * 获取地理位置
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping("/getLocation")
    public String getLocation(HttpServletRequest request) throws Exception{
        JsWechatConfig jsWechatConfig = new JsWechatConfig();
        jsWechatConfig.setAppId(WechatConfig.APP_ID);
        jsWechatConfig.setTimestamp(DateTimeUtil.currentTime());
        jsWechatConfig.setNoncestr(PayUtil.createNonceStr());
        SortedMap map = new TreeMap();
        map.put("jsapi_ticket", wechatAuthService.getTicket(wechatAuthService.findlastestToken()));
        map.put("noncestr", jsWechatConfig.getNoncestr());
        map.put("timestamp", jsWechatConfig.getTimestamp());
        map.put("url", request.getRequestURL().toString());
        String signature = SignatureUtil.createSha1Sign(map, null, SystemConfig.CHARACTER_ENCODING);
        jsWechatConfig.setSignature(signature);
        request.setAttribute("jsWechatConfig", jsWechatConfig);
        return "wechatauth/getLocation";
    }
}

签名方法

    /**
     * 通过Map中的所有元素参与签名
     * 
     * @param map 待参与签名的map集合
     * @params apikey apikey中 如果为空则不参与签名,如果不为空则参与签名
     * @return
     */
    public static String createSha1Sign(SortedMap map, String apiKey, String characterEncoding) {
        String result = notSignParams(map, apiKey);
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(result.getBytes());
            result = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return result;
    }

其他的签名方法点击查看

三、通过ready接口处理成功验证

以上执行完成,进入的完整的页面

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>获取地理位置title>

<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js">script>
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="http://cdn.bootcss.com/jquery/1.11.3/jquery.min.js">script>
head>
<body>
    <br>
    <div class="container">
        <div class="form-group">
            <label for="firstname" class="col-sm-2 control-label">地址:label>
            <div class="col-sm-10" id="item-ifo">
                <input type="text" value="" class="form-control"
                    name="location.address" id="address" placeholder="正在获取地理位置" tabindex="1" autocomplete="off" />
                <div class="i-name ico" id="i-name">div>
            div>
        div>
    div>

body>
<script type="text/javascript">
    wx.config({
        debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '${jsWechatConfig.appId}', // 必填,公众号的唯一标识
        timestamp: '${jsWechatConfig.timestamp}' , // 必填,生成签名的时间戳
        nonceStr: '${jsWechatConfig.noncestr}', // 必填,生成签名的随机串
        signature: '${jsWechatConfig.signature}',// 必填,签名,见附录1
        jsApiList: [ 'checkJsApi', 'openLocation', 'getLocation'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
    });
    
    wx.checkJsApi({
        jsApiList: ['getLocation'], // 需要检测的JS接口列表,所有JS接口列表见附录2,
        success: function(res) {
            if (res.checkResult.getLocation == false) {  
                alert('你的微信版本太低,不支持微信JS接口,请升级到最新的微信版本!');  
                return;  
         }
        }
    });
    var latitude;  
    var longitude;  
    var speed;  
    var accuracy; 
    wx.ready(function(){
        // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
        wx.getLocation({  
            success : function(res) {  
                latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90  
                longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。  
                speed = res.speed; // 速度,以米/每秒计  
                accuracy = res.accuracy; // 位置精度  
                alert(latitude);  
                alert(accuracy);
            },  
            cancel : function(res) {  
                alert('未能获取地理位置');  
            }
        });  
    });
    
    wx.error(function(res){
        // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
        alert("验证出错");  
    });
script>
html>

可以通过微信官方提供的微信web开发者工具调试,如果提示无效的签名的话检查下签名字段是否是小写

工具类我已经上传到Github,点击参考