vue对接微信JSSDK实现微信登录、修改发送到朋友圈内容、微信支付

  前提是了解微信JSSDK: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

  接口只有认证公众号才能使用,域名必须备案且在微信后台设置。先确认已经满足使用jssdk的要求再进行开发。

0.JSSDK使用步骤

1.绑定域名

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。备注:登录后可在“开发者中心”查看对应的接口权限。

2.引入JS文件

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

如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js/jweixin-1.4.0.js (支持https)。

备注:支持使用 AMD/CMD 标准模块加载方法加载

3.通过config接口注入权限验证配置

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

  签名signature在后台生成、nonceStr采用uuid生成唯一标识,timestamp是签名时候的时间戳

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

4.通过ready接口处理成功验证

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

5.通过error接口处理失败验证

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

1. Vue中引入

1. 在 main.js 中全局引入:

// 引入微信对接模块
import { WechatPlugin } from 'vux'
Vue.use(WechatPlugin)
console.log(Vue.wechat) // 可以直接访问 wx 对象,wx对象是微信jssdk的入口

结果:

2.组件外使用

在引入插件后调用config方法进行配置,你可以通过 Vue.wechat 在组件外部访问wx对象。jssdk需要请求签名配置接口,你可以直接使用 VUX 基于 Axios 封装的 AjaxPlugin。

import { WechatPlugin, AjaxPlugin } from 'vux'
Vue.use(WechatPlugin)
Vue.use(AjaxPlugin)

Vue.http.get('/api', ({data}) => {
  Vue.wechat.config(data.data)
})

3.组件中使用

之后任何组件中都可以通过 this.$wechat 访问到 wx 对象。

export default {
  created () {
    this.$wechat.onMenuShareTimeline({
      title: 'hello VUX'
    })
  }
}

 2.实战对接微信jssdk进行分享朋友圈内容修改

  虽然微信提供了JSSDK,但是这不意味着你可以用自定义的按钮来直接打开微信的分享界面,这套JSSDK只是把微信分享接口的内容定义好了,实际还是需要用户点击右上角的菜单按钮进行主动的分享,用户点开分享界面之后,出现的内容就会是你定义的分享标题、图片和链接。

(1)main.js引入wechat模块:

// 引入微信对接模块
import { WechatPlugin } from 'vux'
Vue.use(WechatPlugin)
console.log(Vue.wechat) // 可以直接访问 wx 对象,wx对象是微信jssdk的入口

(2)模块中使用:

  wxShare方法,第一个参数用于封装title、link、imgUrl等参数;第二个是封装的成功回调,下面的例子没有用到,以后可以用这两个参数封装。

<script>
    import axios from "@/axios";
    import Vue from 'vue';

    export default {
        name: 'Constants',
        // 项目的根路径(加api的会被代理请求,用于处理ajax请求)
        projectBaseAddress: '/api',
        // 微信授权后台地址,这里手动加api是用window.location.href跳转
        weixinAuthAddress: '/api/weixin/auth/login.html',
        async wxShare(obj, callback) {
            alert(1);

            function getUrl() {
                var url = window.location.href;
                var locationurl = url.split('#')[0];

                return locationurl;
            }

            alert(2);

            // wx.config的参数
            var wxdata = {
                "url": getUrl()
            };

            //微信分享(向后台请求数据)
            var data = await axios.post("/weixin/auth/getJsapiSigner.html", wxdata);
            alert(JSON.stringify(data));

            var wxdata = data.data;
            // 向后端返回的签名信息添加前端处理的东西
            wxdata.debug = true;
            wxdata.jsApiList = [
                // 所有要调用的 API 都要加到这个列表中
                'onMenuShareTimeline' //分享到朋友圈
            ];
            alert(JSON.stringify(wxdata));
            Vue.wechat.config(wxdata);
            alert(4);

            Vue.wechat.ready(function() {
                alert(5);

                // 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)
                Vue.wechat.onMenuShareTimeline({
                    title: '这是分享标题', // 分享标题
                    link: "http://ynpxwl.cn/api/login.html", // 分享链接
                    imgUrl: "http://ynpxwl.cn/api/static/x-admin/images/bg.png", // 分享图标
                    success: function() {
                        // 用户确认分享后执行的回调函数
                        alert('用户已分享');
                    },
                    cancel: function(res) {
                        alert('用户已取消');
                    },
                    fail: function(res) {
                        alert(JSON.stringify(res));
                    }
                });

                alert(6);
            })
        }
    };
</script>

我打印的alert信息是为了测试;注意连接的link为实际的url,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致

(3)后台代码getJsapiSigner与签名算法如下

接收前台传的url,调用工具类进行签名,最后将appId传回去:

    @RequestMapping("/getJsapiSigner")
    @ResponseBody
    public JSONResultUtil<Map<String, String>> getJsapiSigner(
            @RequestBody(required = false) Map<String, Object> condition) {

        String url = MapUtils.getString(condition, "url");
        Map<String, String> signers = WeixinJSAPISignUtils.sign(WeixinInterfaceUtils.getJsapiTicket(), url);

        signers.put("appId", WeixinConstants.APPID);
        logger.info("signers: {}", signers);

        return new JSONResultUtil<Map<String, String>>(true, "ok", signers);
    }

 

签名算法:

package cn.qs.utils.weixin;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class WeixinJSAPISignUtils {

    public static void main(String[] args) {
        // 注意 URL 一定要动态获取,不能 hardcode
        String url = "http://8fbb6757.ngrok.io/weixinauth/index.html";
        Map<String, String> ret = sign(WeixinInterfaceUtils.getJsapiTicket(), url);
        for (Map.Entry entry : ret.entrySet()) {
            System.out.println(entry.getKey() + ", " + entry.getValue());
        }
    }

    /**
     * 签名
     * 
     * @param jsapiTicket
     *            jsapiTicket
     * @param url
     *            调用接口的当前URL(不包含#以及后面部分)
     * @return
     */
    public static Map<String, String> sign(String jsapiTicket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String signatureString;
        String signature = "";

        // 注意这里参数名必须全部小写,且必须有序(必须这样签名)==签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。签名用的url必须是调用JS接口页面的完整URL。
        signatureString = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url="
                + url;

        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(signatureString.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapiTicket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

获取JsapiTicket 和 accessToken 的工具类

package cn.qs.utils.weixin;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

import cn.qs.utils.HttpUtils;

public class WeixinInterfaceUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(WeixinInterfaceUtils.class);

    /**
     * 获取ACCESS_TOKEN
     */
    public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";

    /**
     * 获取JSAPI_TICKET
     */
    public static final String JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";

    // 用于管理token
    /**
     * 获取到的accessToken
     */
    private static String accessToken;

    /**
     * 最后一次获取Access_Token的时间
     */
    private static Date lastGetAccessTokenTime;

    public static String getAccessToken() {
        if (StringUtils.isBlank(accessToken) || isExpiredAccessToken()) {
            accessToken = null;
            lastGetAccessTokenTime = null;

            Map<String, String> param = new HashMap<>();
            param.put("grant_type", "client_credential");
            param.put("appid", WeixinConstants.APPID);
            param.put("secret", WeixinConstants.APP_SECRET);

            String responseStr = HttpUtils.doGetWithParams(ACCESS_TOKEN_URL, param);
            if (StringUtils.isNotBlank(responseStr)) {
                JSONObject parseObject = JSONObject.parseObject(responseStr);
                if (parseObject != null && parseObject.containsKey("access_token")) {
                    accessToken = parseObject.getString("access_token");
                    lastGetAccessTokenTime = new Date();
                    LOGGER.debug("调用接口获取accessToken,获取到的信息为: {}", parseObject.toString());
                }
            }
        } else {
            LOGGER.debug("使用未过时的accessToken: {}", accessToken);
        }

        return accessToken;
    }

    private static boolean isExpiredAccessToken() {
        if (lastGetAccessTokenTime == null) {
            return true;
        }

        // 1.5小时以后的就算失效
        long existTime = 5400000L;
        long now = System.currentTimeMillis();
        if (now - lastGetAccessTokenTime.getTime() > existTime) {
            return true;
        }

        return false;
    }

    /**
     * 获取到的jsapiTicket
     */
    private static String jsapiTicket;

    /**
     * 最后一次获取JsapiTicket的时间
     */
    private static Date lastGetJsapiTicketTime;

    public static String getJsapiTicket() {
        if (StringUtils.isBlank(jsapiTicket) || isExpiredJsapiTicket()) {
            jsapiTicket = null;
            lastGetJsapiTicketTime = null;

            String tmpUrl = JSAPI_TICKET_URL.replaceAll("ACCESS_TOKEN", getAccessToken());
            String responseStr = HttpUtils.doGet(tmpUrl);
            if (StringUtils.isNotBlank(responseStr)) {
                JSONObject parseObject = JSONObject.parseObject(responseStr);
                if (parseObject != null && parseObject.containsKey("ticket")) {
                    jsapiTicket = parseObject.getString("ticket");
                    lastGetJsapiTicketTime = new Date();
                    LOGGER.debug("调用接口获取jsapiTicket,获取到的信息为: {}", parseObject.toString());
                }
            }
        } else {
            LOGGER.debug("使用未过时的jsapiTicket: {}", jsapiTicket);
        }

        return jsapiTicket;
    }

    private static boolean isExpiredJsapiTicket() {
        if (lastGetJsapiTicketTime == null) {
            return true;
        }

        // 1.5小时以后的就算失效
        long existTime = 5400000L;
        long now = System.currentTimeMillis();
        if (now - lastGetJsapiTicketTime.getTime() > existTime) {
            return true;
        }

        return false;
    }
}

  这里只是简单的进行修改分享朋友圈的信息,实际中可以修改方法进一步封装。

 

3. 对接微信支付

微信支付相关文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

我们需要基本的参数有 appId、商号mch_id、商号的api_key。appId和商户号从公众号可以直接查看,apiKey需要从公众号-》微信支付-》商号  登录之后进行设置。

 

我们查看微信JSSDK支付接口如下:

wx.chooseWXPay({
  timestamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
  nonceStr: '', // 支付签名随机串,不长于 32 位
  package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
  signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
  paySign: '', // 支付签名
  success: function (res) {
    // 支付成功后的回调函数
  }
});

  备注:prepay_id 通过微信支付统一下单接口拿到,paySign 采用统一的微信支付 Sign 签名生成方法,注意这里 appId 也要参与签名,appId 与 config 中传入的 appId 一致,即最后参与签名的参数有appId, timeStamp, nonceStr, package, signType。

  可以看到,需要从后台统一生成订单,也就是说所有的订单处理都需要先从后端通过http接口进行订单处理,最后通过返回的标识从前台进行处理。

  测试的时候我们微信提供了沙箱测试。仿真系统与生产环境完全独立,包括存储层。商户在仿真系统所做的所有交易(如下单、支付、查询)均为无资金流的假数据,即:用户无需真实扣款,商户也不会有资金入账。在所有请求的URL前面加上/sandboxnew 就是沙箱测试。

 

我们下载文档的demo之后进行简单的封装以及测是,其实其SDK已经封装好了,包括沙箱测试环境等环境。我们获取到SDK之后可以利用SDK现有的工具类。

1.利用沙箱测试环境进行测试

  查看文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_1&index=1

沙箱测试的key与真实环境的key有所区别,所以需要先获取沙箱测试idkey。

1.获取沙箱测试环境key的正确步骤

    public static void main(String[] args) throws Exception {
        // 构造配置信息
        WXPayConfig wxPayConfig = new MyWxPayConfig();

        // 参与sign的字段包括mch_id、nonce_str、真实环境的key
        Map<String, String> param = new LinkedHashMap<>();
        param.put("mch_id", wxPayConfig.getMchID());
        param.put("nonce_str", WXPayUtil.generateNonceStr());
        String generateSignature = WXPayUtil.generateSignature(param, wxPayConfig.getKey(), SignType.MD5); // wxPayConfig.getKey()是真实环境的key值
        param.put("sign", generateSignature);

        // 转为XML
        String mapToXml = WXPayUtil.mapToXml(param);
        String url = "https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey";
        // 发送请求获取XML数据
        String doPost = HttpUtils.doPost(url, mapToXml);
        Map<String, String> xmlToMap = WXPayUtil.xmlToMap(doPost);
        System.out.println(xmlToMap);
    }

结果:

{return_msg=ok, sandbox_signkey=XXXXXXXXX, return_code=SUCCESS}

 

HttpUtils是自己封装的工具类,如下:

package cn.qs.utils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * http工具类的使用
 * 
 * @author Administrator
 *
 */
public class HttpUtils {

    private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);

    /**
     * get请求
     * 
     * @return
     */
    public static String doGet(String url) {
        CloseableHttpClient client = null;
        CloseableHttpResponse response = null;
        try {
            // 定义HttpClient
            client = HttpClientBuilder.create().build();
            // 发送get请求
            HttpGet request = new HttpGet(url);
            // 执行请求
            response = client.execute(request);

            return getResponseResult(response);
        } catch (Exception e) {
            logger.error("execute error,url: {}", url, e);
        } finally {
            IOUtils.closeQuietly(response);
            IOUtils.closeQuietly(client);
        }

        return "";
    }

    /**
     * get请求携带参数
     * 
     * @return
     */
    public static String doGetWithParams(String url, Map<String, String> params) {
        CloseableHttpClient client = null;
        CloseableHttpResponse response = null;
        try {
            // 定义HttpClient
            client = HttpClientBuilder.create().build();

            // 1.转化参数
            if (params != null && params.size() > 0) {
                List<NameValuePair> nvps = new ArrayList<NameValuePair>();
                for (Iterator<String> iter = params.keySet().iterator(); iter.hasNext();) {
                    String name = iter.next();
                    String value = params.get(name);
                    nvps.add(new BasicNameValuePair(name, value));
                }
                String paramsStr = EntityUtils.toString(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
                url += "?" + paramsStr;
            }

            HttpGet request = new HttpGet(url);
            response = client.execute(request);

            return getResponseResult(response);
        } catch (IOException e) {
            logger.error("execute error,url: {}", url, e);
        } finally {
            IOUtils.closeQuietly(response);
            IOUtils.closeQuietly(client);
        }

        return "";
    }

    public static String doPost(String url, Map<String, String> params) {
        CloseableHttpClient client = null;
        CloseableHttpResponse response = null;
        try {
            // 定义HttpClient
            client = HttpClientBuilder.create().build();
            HttpPost request = new HttpPost(url);

            // 1.转化参数
            if (params != null && params.size() > 0) {
                List<NameValuePair> nvps = new ArrayList<NameValuePair>();
                for (Iterator<String> iter = params.keySet().iterator(); iter.hasNext();) {
                    String name = iter.next();
                    String value = params.get(name);
                    nvps.add(new BasicNameValuePair(name, value));
                }
                request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
            }

            response = client.execute(request);
            return getResponseResult(response);
        } catch (IOException e) {
            logger.error("execute error,url: {}", url, e);
        } finally {
            IOUtils.closeQuietly(response);
            IOUtils.closeQuietly(client);
        }

        return "";
    }

    public static String doPost(String url, String params) {
        return doPost(url, params, false);
    }

    /**
     * post请求(用于请求json格式的参数)
     * 
     * @param url
     * @param params
     * @param isJsonData
     * @return
     */
    public static String doPost(String url, String params, boolean isJsonData) {
        CloseableHttpClient client = null;
        CloseableHttpResponse response = null;
        try {
            // 定义HttpClient
            client = HttpClientBuilder.create().build();

            HttpPost request = new HttpPost(url);
            StringEntity entity = new StringEntity(params, HTTP.UTF_8);
            request.setEntity(entity);

            if (isJsonData) {
                request.setHeader("Accept", "application/json");
                request.setHeader("Content-Type", "application/json");
            }

            response = client.execute(request);

            return getResponseResult(response);
        } catch (IOException e) {
            logger.error("execute error,url: {}", url, e);
        } finally {
            IOUtils.closeQuietly(response);
            IOUtils.closeQuietly(client);
        }

        return "";
    }

    /**
     * 上传文件携带参数发送请求
     * 
     * @param url
     *            URL
     * @param fileName
     *            neme,相当于input的name
     * @param filePath
     *            本地路径
     * @param params
     *            参数
     * @return
     */
    public static String doPostWithFile(String url, String fileName, String filePath, Map<String, String> params) {
        CloseableHttpClient httpclient = HttpClientBuilder.create().build();
        CloseableHttpResponse response = null;
        try {
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();

            // 上传文件,如果不需要上传文件注掉此行
            multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE).addPart(fileName,
                    new FileBody(new File(filePath)));

            if (params != null && params.size() > 0) {
                Set<Entry<String, String>> entrySet = params.entrySet();
                for (Entry<String, String> entry : entrySet) {
                    multipartEntityBuilder.addTextBody(entry.getKey(), entry.getValue(),
                            ContentType.create(HTTP.PLAIN_TEXT_TYPE, StandardCharsets.UTF_8));
                }
            }

            HttpEntity httpEntity = multipartEntityBuilder.build();

            HttpPost httppost = new HttpPost(url);
            httppost.setEntity(httpEntity);

            response = httpclient.execute(httppost);
            return getResponseResult(response);
        } catch (Exception e) {
            logger.error("execute error,url: {}", url, e);
        } finally {
            IOUtils.closeQuietly(response);
            IOUtils.closeQuietly(httpclient);
        }

        return "";
    }

    private static String getResponseResult(CloseableHttpResponse response) throws ParseException, IOException {
        /** 请求发送成功,并得到响应 **/
        if (response != null) {
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                return EntityUtils.toString(response.getEntity(), "utf-8");
            } else {
                logger.error("getResponseResult code error, code: {}", response.getStatusLine().getStatusCode());
            }
        }

        return "";
    }

}

关于沙盒测试的坑:

(1)沙盒测试不能支付,也无需支付 ,下的订单每个都是已经支付过的订单。 

 (2)沙箱支付环境只能付款101分

2.真实环境

  最后真实环境后台统一下单用的是 Git上的项目 best-pay-sdk

  项目中只用到了微信的JSAPI方式支付,但是best-pay-sdk里面集成了微信支付、支付宝支付等方式。pom地址如下:

        <dependency>
            <groupId>cn.springboot</groupId>
            <artifactId>best-pay-sdk</artifactId>
            <version>1.3.0</version>
        </dependency>

 

统一下单逻辑如下:

action层代码: 

    /**
     * 统一下订单
     * 
     * @param user
     * @param request
     * @return
     */
    @RequestMapping("unifiedOrder")
    @ResponseBody
    public JSONResultUtil<Map<String, String>> unifiedOrder(@RequestBody Pay pay) {
        // 1.创建系统信息
        pay.setPayDate(new Date());
        pay.setUserId(MySystemUtils.getLoginUser().getId());
        pay.setUsername(MySystemUtils.getLoginUser().getUsername());

        String loginUsername = MySystemUtils.getLoginUsername();
        User findUserByUsername = userService.findUserByUsername(loginUsername);
        Float coupon = ArithUtils.format(findUserByUsername.getCoupon(), 2);
        Float actuallyPay = pay.getPayAmount();
        if (coupon != null && coupon != 0 && coupon < pay.getPayAmount()) {
            Float shouldPay = ArithUtils.format(pay.getPayAmount(), 2);
            actuallyPay = ArithUtils.sub(shouldPay, coupon);
            pay.setPayAmount(actuallyPay);
            pay.setRemark1("应收金额: " + shouldPay + ",实收金额: " + actuallyPay + ", 第一次付费减金额: " + coupon);

            // 去掉优惠券
            findUserByUsername.setCoupon(0F);
            userService.update(findUserByUsername);

            logger.info("{}使用第一次赠送金额{}", findUserByUsername.getFullname(), coupon);
        } else {
            logger.info("没有优惠金额");
        }

        String orderId = UUIDUtils.getUUID2();
        pay.setOrderId(orderId);
        pay.setOrderStatus("未支付");

        payService.add(pay);

        // 普通用户登录支付订单无需拉起支付
        if (!MySystemUtils.isWXLogin()) {
            return new JSONResultUtil<Map<String, String>>(false, "您不是微信账号登录,订单无法支付");
        }

        // 2.创建订单==用于JSAPI发起支付
        String orderName = pay.getChildrenName() + "在幼儿园 " + pay.getKindergartenName() + "支付学费";
        Map<String, String> unifiedOrder = WeixinPayUtils.unifiedOrder(orderId, orderName, actuallyPay,
                MySystemUtils.getLoginUser().getUsername());
        unifiedOrder.put("payId", pay.getId() + "");

        return new JSONResultUtil<Map<String, String>>(true, "ok", unifiedOrder);
    }

 

前面处理一些系统内部逻辑之后调用工具类生成订单,同时将二次签名信息返回到前台。

统一下单工具类:

package cn.qs.utils.weixin.pay;

import java.util.LinkedHashMap;
import java.util.Map;

import com.lly835.bestpay.config.WxPayConfig;
import com.lly835.bestpay.enums.BestPayTypeEnum;
import com.lly835.bestpay.model.PayRequest;
import com.lly835.bestpay.model.PayResponse;
import com.lly835.bestpay.service.impl.BestPayServiceImpl;

import cn.qs.utils.weixin.WeixinConstants;
import cn.qs.utils.weixin.auth.WeixinJSAPISignUtils;

public class WeixinPayUtils {

    private static final WxPayConfig wxPayConfig = new WxPayConfig();

    static {
        // 公众号支付,设置公众号Id
        wxPayConfig.setAppId(WeixinConstants.APPID);
        wxPayConfig.setMchId(WeixinConstants.MCHID);
        wxPayConfig.setMchKey(WeixinConstants.API_KEY);
        wxPayConfig.setNotifyUrl(WeixinConstants.PAY_SUCCESS_NOTIFY_URL);
    }

    /**
     * 统一下单
     * 
     * @return
     */
    public static Map<String, String> unifiedOrder(String orderId, String orderName, double amount, String openId) {
        // 支付类, 所有方法都在这个类里
        BestPayServiceImpl bestPayService = new BestPayServiceImpl();
        bestPayService.setWxPayConfig(wxPayConfig);

        PayRequest payRequest = new PayRequest();
        payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_MP);
        payRequest.setOrderId(orderId);
        payRequest.setOrderName(orderName);
        payRequest.setOrderAmount(amount);
        payRequest.setOpenid(openId);
        PayResponse pay = bestPayService.pay(payRequest);
        Map<String, String> result = new LinkedHashMap<>();
        result.put("appId", pay.getAppId());
        result.put("nonceStr", pay.getNonceStr());
        result.put("timeStamp", WeixinJSAPISignUtils.getTimestamp());
        result.put("package", pay.getPackAge());
        result.put("signType", pay.getSignType());
        result.put("paySign", pay.getPaySign());

        return result;
    }

}

 

wxPayConfig  是配置工具类,里面包含微信支付需要的基本参数:公众号ID、商户号、商户号的API_key、以及订单支付成功的回调地址。

回调地址不携带参数,如下:当支付成功微信会将订单信息以及支付信息返回到该地址,可以根据订单号进行处理,我的处理是将订单状态改为已支付。

    /**
     * 微信成功回调地址
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    @RequestMapping("/paySuccess")
    public void paySuccess(HttpServletRequest request, HttpServletResponse response) throws IOException {
        try {
            InputStream inStream = request.getInputStream();
            int _buffer_size = 1024;
            if (inStream != null) {
                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                byte[] tempBytes = new byte[_buffer_size];
                int count = -1;
                while ((count = inStream.read(tempBytes, 0, _buffer_size)) != -1) {
                    outStream.write(tempBytes, 0, count);
                }
                tempBytes = null;
                outStream.flush();
                // 将流转换成字符串
                String result = new String(outStream.toByteArray(), "UTF-8");

                // 转换为Map处理自己的业务逻辑,这里将订单状态改为已支付
                if (StringUtils.isNotBlank(result)) {
                    Map<String, String> xmlToMap = WxPayXmlUtil.xmlToMap(result);
                    if ("SUCCESS".equals(MapUtils.getString(xmlToMap, "result_code", ""))) {
                        String orderId = MapUtils.getString(xmlToMap, "out_trade_no", "");
                        Pay systemPay = payService.findByOrderId(orderId);
                        if (systemPay != null && systemPay.getOrderStatus() != "已支付") {
                            systemPay.setOrderStatus("已支付");
                            logger.info("修改订单状态为已支付, orderId: {} ", orderId);
                            payService.update(systemPay);
                        }
                    }
                }
            }

            // 通知微信支付系统接收到信息
            response.getWriter().write(
                    "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
        } catch (Exception e) {
            logger.error("paySuccess error", e);

            // 如果失败返回错误,微信会再次发送支付信息
            response.getWriter().write("fail");
        }
    }

 

WxPayXmlUtil工具类如下:

package cn.qs.utils.weixin.pay;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 2018/7/3
 */
public final class WxPayXmlUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(WxPayXmlUtil.class);

    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML
     *            XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WxPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            LOGGER.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(),
                    strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data
     *            Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = WxPayXmlUtil.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key : data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); // .replaceAll("\n|\r",
                                                        // "");
        try {
            writer.close();
        } catch (Exception ex) {
        }
        return output;
    }
}

 

补充:实际best-pat-sdk在微信下单之后已经帮我们二次签名了,源码如下:

    @Override
    public PayResponse pay(PayRequest request) {
        WxPayUnifiedorderRequest wxRequest = new WxPayUnifiedorderRequest();
        wxRequest.setOutTradeNo(request.getOrderId());
        wxRequest.setTotalFee(MoneyUtil.Yuan2Fen(request.getOrderAmount()));
        wxRequest.setBody(request.getOrderName());
        wxRequest.setOpenid(request.getOpenid());
        wxRequest.setTradeType(request.getPayTypeEnum().getCode());

        //小程序和app支付有独立的appid,公众号、h5、native都是公众号的appid
        if (request.getPayTypeEnum() == BestPayTypeEnum.WXPAY_MINI){
            wxRequest.setAppid(wxPayConfig.getMiniAppId());
        }else if (request.getPayTypeEnum() == BestPayTypeEnum.WXPAY_APP){
            wxRequest.setAppid(wxPayConfig.getAppAppId());
        }else {
            wxRequest.setAppid(wxPayConfig.getAppId());
        }
        wxRequest.setMchId(wxPayConfig.getMchId());
        wxRequest.setNotifyUrl(wxPayConfig.getNotifyUrl());
        wxRequest.setNonceStr(RandomUtil.getRandomStr());
        wxRequest.setSpbillCreateIp(StringUtils.isEmpty(request.getSpbillCreateIp()) ? "8.8.8.8" : request.getSpbillCreateIp());
        wxRequest.setAttach(request.getAttach());
        wxRequest.setSign(WxPaySignature.sign(MapUtil.buildMap(wxRequest), wxPayConfig.getMchKey()));

        RequestBody body = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), XmlUtil.toString(wxRequest));
        Call<WxPaySyncResponse> call = retrofit.create(WxPayApi.class).unifiedorder(body);
        Response<WxPaySyncResponse> retrofitResponse  = null;
        try{
            retrofitResponse = call.execute();
        }catch (IOException e) {
            e.printStackTrace();
        }
        assert retrofitResponse != null;
        if (!retrofitResponse.isSuccessful()) {
            throw new RuntimeException("【微信统一支付】发起支付, 网络异常");
        }
        WxPaySyncResponse response = retrofitResponse.body();

        assert response != null;
        if(!response.getReturnCode().equals(WxPayConstants.SUCCESS)) {
            throw new RuntimeException("【微信统一支付】发起支付, returnCode != SUCCESS, returnMsg = " + response.getReturnMsg());
        }
        if (!response.getResultCode().equals(WxPayConstants.SUCCESS)) {
            throw new RuntimeException("【微信统一支付】发起支付, resultCode != SUCCESS, err_code = " + response.getErrCode() + " err_code_des=" + response.getErrCodeDes());
        }

        return buildPayResponse(response);
    }
    /**
     * 返回给h5的参数
     * @param response
     * @return
     */
    private PayResponse buildPayResponse(WxPaySyncResponse response) {
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = RandomUtil.getRandomStr();
        String packAge = "prepay_id=" + response.getPrepayId();
        String signType = "MD5";

        //先构造要签名的map
        Map<String, String> map = new HashMap<>();
        map.put("appId", response.getAppid());
        map.put("timeStamp", timeStamp);
        map.put("nonceStr", nonceStr);
        map.put("package", packAge);
        map.put("signType", signType);

        PayResponse payResponse = new PayResponse();
        payResponse.setAppId(response.getAppid());
        payResponse.setTimeStamp(timeStamp);
        payResponse.setNonceStr(nonceStr);
        payResponse.setPackAge(packAge);
        payResponse.setSignType(signType);
        payResponse.setPaySign(WxPaySignature.sign(map, wxPayConfig.getMchKey()));
        payResponse.setMwebUrl(response.getMwebUrl());
        payResponse.setCodeUrl(response.getCodeUrl());

        return payResponse;
    }

 

前台代码:

下单的vue页面:

            async doPay() {
                if(!Constants.isNotBlank(this.kindergartenId, "幼儿园") || !Constants.isNotBlank(this.semester, "学期") || !Constants.isNotBlank(this.grade, "年级") ||
                    !Constants.isNotBlank(this.classNum, "班级") || !Constants.isNotBlank(this.parentPhone, "家长电话") || !Constants.isNotBlank(this.childrenName, "学生姓名")) {
                    return;
                }

                var response = await axios.post('/pay/unifiedOrder.html', {
                    kindergartenId: this.kindergartenId,
                    kindergartenName: this.kindergartenName,
                    version: this.version,
                    server: this.server,
                    semester: this.semester,
                    grade: this.grade,
                    classNum: this.classNum,
                    parentName: this.parentName,
                    parentPhone: this.parentPhone,
                    childrenName: this.childrenName,
                    payAmount: this.payAmount
                });

                if(response.success) {
                    // 统一下订单
                    Constants.wxSPay(response.data);
                }
            }

Constant.vue

<script>
    import axios from "@/axios";
    import Vue from 'vue';
    import { AlertModule } from 'vux';
    import store from '@/store';

    export default {
        store,
        // 是否是开发模式
        devModel: true,
        name: 'Constants',
        // 项目的根路径(加api的会被代理请求,用于处理ajax请求)
        projectBaseAddress: '/api',
        // 微信授权后台地址,这里手动加api是用window.location.href跳转
        weixinAuthAddress: '/api/weixin/auth/login.html',

        /**
         * 获取协议 + IP + 端口
         */
        getBasePath() {
            // 获取当前网址,如:http://localhost:8080/MyWeb/index.html
            //            var curWwwPath = window.document.location.href;
            // 获取主机地址之后的目录,如: MyWeb/index.html
            //            var pathName = window.document.location.pathname;

            // window.location.protocol(网站协议:https、http)
            // window.location.host    (端口号+域名;注意:80端口,只显示域名)
            // 返回:https://www.domain.com:8080
            var path = window.location.protocol + '//' + window.location.host
            return path;
        },
        async wxConfig() {
            function getUrl() {
                var url = window.location.href;
                var locationurl = url.split('#')[0];

                return locationurl;
            }

            // wx.config的参数
            var wxdata = {
                "url": getUrl()
            };

            //微信分享(向后台请求数据)
            var data = await axios.post("/weixin/auth/getJsapiSigner.html", wxdata);

            var wxdata = data.data;
            // 向后端返回的签名信息添加前端处理的东西
            wxdata.debug = false;
            // 所有要调用的 API 都要加到这个列表中
            wxdata.jsApiList = ['onMenuShareTimeline', 'chooseWXPay'];

            Vue.wechat.config(wxdata);
        },
        async wxShare(obj) {
            // 先config
            await this.wxConfig();

            var titleValue = "测试标题";
            var linkValue = "http://ynpxwl.cn/api/login.html";
            var imgUrlValue = "http://ynpxwl.cn/api/static/image/0.png";
            if(obj) {
                if(obj.title) {
                    titleValue = obj.title;
                }
                if(obj.link) {
                    linkValue = obj.link;
                }
                if(obj.imgUrl) {
                    imgUrlValue = obj.imgUrl;
                }
            }

            Vue.wechat.ready(function() {
                Vue.wechat.onMenuShareTimeline({
                    title: titleValue, // 分享标题
                    link: linkValue, // 分享链接
                    imgUrl: imgUrlValue, // 分享图标
                    success: function() {
                        // 用户确认分享后执行的回调函数(这里需要记录后台)
                        alert('用户已分享');
                    },
                    cancel: function(res) {
                        alert('用户已取消');
                    },
                    fail: function(res) {
                        alert(JSON.stringify(res));
                    }
                });
            })
        },
        async wxSPay(data) {
            // 先config
            await this.wxConfig();

            // 将_this指向当前vm对象
            const _this = this;

            Vue.wechat.chooseWXPay({
                appId: data.appId,
                timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
                nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
                package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
                signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
                paySign: data.paySign, // 支付签名
                success: function(res) {
                    // 支付成功跳转路由(路由push无效)
                    window.location.href = "http://xxxxx.cn/#/plain/pays";
                },
                fail: function(res) {
                    alert("支付失败")
                }
            });
        },
        isNotBlank(value, fieldRemark) {
            if(!value) {
                AlertModule.show({
                    title: "提示信息",
                    content: fieldRemark + "不能为空"
                });
                return false;
            }

            return true;
        }
    };
</script>

注意:微信支付成功之后如果需要跳转页面用改变页面地址的方法,路由push无效。

 

4.微信登录 

  之前在学习公众号的时候就已经学习过微信登录了。

(1)前台

            async wxLogin() {
                //访问微信登陆,跳转的地址由后台处理
                window.location.replace(Constants.weixinAuthAddress);
            }

weixinAuthAddress值如下:

        // 微信授权后台地址,这里手动加api是用window.location.href跳转
        weixinAuthAddress: '/api/weixin/auth/login.html',

 

  实际上就是访问后台的一个地址。

 

(2)后台 (用户先从前台访问到authorize方法,方法重定向到微信授权页面,微信同意之后会重定向携带参数code和state定位到calback方法),callback可以用code获取用户信息进行登录或者进行其他操作

package cn.qs.controller.weixin;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;

import cn.qs.bean.user.User;
import cn.qs.bean.user.WechatUser;
import cn.qs.service.user.UserService;
import cn.qs.utils.DefaultValue;
import cn.qs.utils.HttpUtils;
import cn.qs.utils.JSONResultUtil;
import cn.qs.utils.securty.MD5Utils;
import cn.qs.utils.system.MySystemUtils;
import cn.qs.utils.weixin.WeixinConstants;
import cn.qs.utils.weixin.WeixinInterfaceUtils;
import cn.qs.utils.weixin.auth.WeixinJSAPISignUtils;

@Controller
@RequestMapping("weixin/auth")
public class WeixinAuthController {

    private static final Logger logger = org.slf4j.LoggerFactory.getLogger(WeixinAuthController.class);

    @Autowired
    private UserService userService;

    /**
     * 首页,跳转到index.html,index.html有一个连接会访问下面的login方法
     * 
     * @return
     */
    @RequestMapping("/index")
    public String index(ModelMap map) {
        // 注意 URL 一定要动态获取,不能 hardcode
        String url = "http://4de70c98.ngrok.io/weixin/auth/index.html";
        Map<String, String> signers = WeixinJSAPISignUtils.sign(WeixinInterfaceUtils.getJsapiTicket(), url);

        map.put("signers", signers);
        return "weixinauth/index";
    }

    @RequestMapping("/getJsapiSigner")
    @ResponseBody
    public JSONResultUtil<Map<String, String>> getJsapiSigner(
            @RequestBody(required = false) Map<String, Object> condition) {

        String url = MapUtils.getString(condition, "url");
        Map<String, String> signers = WeixinJSAPISignUtils.sign(WeixinInterfaceUtils.getJsapiTicket(), url);

        signers.put("appId", WeixinConstants.APPID);
        logger.info("signers: {}", signers);

        return new JSONResultUtil<Map<String, String>>(true, "ok", signers);
    }

    /**
     * (一)微信授权:重定向到授权页面
     * 
     * @return
     * @throws UnsupportedEncodingException
     */
    @RequestMapping("/login")
    public String authorize() throws UnsupportedEncodingException {
        // 回调地址必须在公网可以访问
        String recirectUrl = URLEncoder.encode(WeixinConstants.AUTH_REDIRECT_URL, "UTF-8");

        // 授权地址
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
        url = url.replace("APPID", WeixinConstants.APPID).replace("REDIRECT_URI", recirectUrl);
        logger.info("url: {}", url);

        // 参数替换之后重定向到授权地址
        return "redirect:" + url;
    }

    /**
     * (二)用户同意授权; (三)微信会自动重定向到该页面并携带参数code和state用于换取access_token和openid; (四)
     * 用access_token和openid获取用户信息(五)如果有必要可以进行登录,两种:第一种是直接拿微信号登录;
     * 第二种是根据openid和nickname获取账号进行登录
     * 
     * @param code
     * @param state
     * @return
     * @throws UnsupportedEncodingException
     */
    @RequestMapping("/calback")
    public String calback(String code, String state) throws UnsupportedEncodingException {
        // 获取access_token和openid
        try {
            String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
            url = url.replace("APPID", WeixinConstants.APPID).replace("SECRET", WeixinConstants.APP_SECRET)
                    .replace("CODE", code);
            String doGet = HttpUtils.doGet(url);

            if (StringUtils.isNotBlank(doGet)) {
                JSONObject parseObject = JSONObject.parseObject(doGet);

                // 获取两个参数之后获取用户信息
                String accessToken = parseObject.getString("access_token");
                String openid = parseObject.getString("openid");
                String getUserInfoURL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
                getUserInfoURL = getUserInfoURL.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openid);
                String doGet2 = HttpUtils.doGet(getUserInfoURL);
                logger.debug("userInfo: {}", doGet2);

                // 用获取到的用户信息进行自己体系的登录
                if (StringUtils.isNotBlank(doGet2)) {
                    WechatUser user = JSONObject.parseObject(doGet2, WechatUser.class);
                    logger.debug("user: {}", user);

                    return doLoginWithWechatUser(user);
                }
            }
        } catch (Exception e) {
            logger.error("登录错误", e);
        }

        logger.info("登录失败了");
        return "error";
    }

    private String doLoginWithWechatUser(WechatUser wechatUser) {
        if (wechatUser == null || StringUtils.isBlank(wechatUser.getOpenid())) {
            return "获取信息错误";
        }

        String openid = wechatUser.getOpenid();
        User findUserByUsername = userService.findUserByUsername(openid);
        if (findUserByUsername == null) {
            User user = new User();
            user.setUsername(openid);
            user.setPassword(MD5Utils.md5(openid));
            user.setRoles(DefaultValue.ROLE_PLAIN_USER);
            user.setSex("1".equals(wechatUser.getSex()) ? "男" : "女");
            user.setProperty("from", "wechat");

            String address = "";
            if (StringUtils.isNotBlank(wechatUser.getCountry())) {
                address += wechatUser.getCountry();
            }
            if (StringUtils.isNotBlank(wechatUser.getProvince())) {
                address += wechatUser.getProvince();
            }
            if (StringUtils.isNotBlank(wechatUser.getCity())) {
                address += wechatUser.getCity();
            }
            user.setWechataddress(address);
            user.setWechatnickname(wechatUser.getNickname());
            user.setWechatphoto(wechatUser.getHeadimgurl());

            // 设置第一次登陆的优惠金额
            user.setCoupon(NumberUtils.toFloat(MySystemUtils.getProperty("coupon", "0")));

            logger.debug("create user", user);
            userService.add(user);
            findUserByUsername = userService.findUserByUsername(openid);
        } else {
            logger.debug("已经存在的账户, {}", findUserByUsername);
        }

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        HttpSession session = request.getSession();
        session.setAttribute("user", findUserByUsername);

        // 登录成功之后后台进行跳转
        String redirectUrl = "";
        if (DefaultValue.ROLE_SYSYEM.equals(findUserByUsername.getUsername())) {
            redirectUrl = "redirect:" + WeixinConstants.ROLE_ADMIN_REDIRECTURL;
        } else {
            redirectUrl = "redirect:" + WeixinConstants.ROLE_PLAIN_REDIRECTURL;
        }

        return redirectUrl;
    }
}

 

总结:

1.关于H5调用手机发起拨打电话

<a href='tel:18008426772'>18008426772</a>

  亲测在苹果手机和安卓手机都有效。

 2.快速清空微信浏览器中的缓存

(1)在微信聊天框中输入debugx5.qq.com 并发送

(2)点击该网址进入,在新页面下拉菜单至最底部。

(3)选中Cookie、文件缓存、广告过滤缓存和DNS缓存,点击“清除”即可清除完成。

 

git地址:后端地址  前端地址 

posted @ 2019-12-22 14:27  QiaoZhi  阅读(2683)  评论(0编辑  收藏  举报