zoukankan      html  css  js  c++  java
  • 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("
    |
    ",
                                                            // "");
            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地址:后端地址  前端地址 

  • 相关阅读:
    jQuery插件 -- Form表单插件jquery.form.js
    Ajax发送GET、POST请求和响应XML数据案例
    Ajax知识点复习
    Tomcat配置连接池的java实现
    dbcp数据库连接池的java实现
    C3P0数据库连接池的java实现
    推荐几款基于Bootstrap的响应式后台管理模板
    Java复习第四天
    Java复习第三天
    Java复习第二天
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/12079741.html
Copyright © 2011-2022 走看看