SpringBoot接入微信JSSDK,看这篇妥妥的

Posted javadog-net

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot接入微信JSSDK,看这篇妥妥的相关的知识,希望对你有一定的参考价值。

先给猴急的客官上干货代码

GitHub

接入微信JSSDK GitHub地址

Gitee

接入微信JSSDK GitHub地址

前言

事情的起因是因为疫情严重,领导要求做一个专题页,能够尽可能帮助所需要的人。
于是乎本狗与同事挑灯奋战,加班加点赶工出来。
部署上线完成,用微信内置浏览器分享后,理想状态应该是这样的,如下图⬇️


但是,结果却不是理想的这样,默默地留下了没有技术的泪水,如下图⬇️

竟然没有关键字和展示图片,在本菜狗的不懈努力下,终于承认技术不行,去请教了大佬,得出如下结论。

  • 1.微信内置浏览器分享若需要自定义展示描述及右侧封面图,必须接入微信JSSDK,并且一定需要有配合本站的微信公众号(appId和appSecret)才可自定义分享,切记小程序(appId和appSecret)的不可以
  • 2.非微信分享,如QQ浏览器,UC浏览器等各大厂商,会根据自身定义获取HTML页面中的TDK(title,description,keyword),举例UC浏览器分享⬇️


    所以,对接微信JSSDK,势在必行!

Tip

史上最详细的接入微信JSSDK菜鸟教程,本文全面的记录了接入微信JSSDK的步骤,具体的代码及遇到的坑,并且展示发布最终效果,并将代码发布GitHub。随篇幅较长,但史上最全。大佬勿喷,新手入门,亲测可用!!!

本文试用人群

  • 需要接入微信JSSDK却看不懂文档的同学
  • 看懂文档但是实操不知如何下手的同学
  • 下了手但是出错不知道如何调试修改的同学
  • 成功接入过但是想重温具体流程的同学

本文目标

  • 实战进行H5网站微信自定义分享
  • 实战进行H5网站调取相册选取图片

放松心态,慢慢来看


正文

官方文档

任何平台接入,官方文档是标杆,虽有些关键点一笔带过,我们也要通读有个印象,点击微信官方文档打开文档,如下⬇️

总览
  • 1.x是接入关键步骤,需仔细品读,与接入有关
  • 2.x - 12.x 具体接口接入,需要对接时具体参考
  • 13.x 需要注意下,api_ticket 微信临时票据,与接入有关
  • 16-22 均是附录,可查阅错误列表对应含义,及接口菜单列表等描述

实操步骤

使用IDEA工具,新建SpringBoot项目,项目名为springboot-wexin,目录结构如下

AjaxJson.java - 自定义接口返回前台数据格式的封装类

/**
 * Copyright &copy; 2005-2020 <a href="http://www.jhmis.com/">jhmis</a> All rights reserved.
 */
package net.javadog.springbootwexin.common;

import com.fasterxml.jackson.annotation.JsonIgnore;

import java.util.LinkedHashMap;
import java.util.List;


/**
 * $.ajax后需要接受的JSON
 *
 */
public class AjaxJson 

	private boolean success = true;// 是否成功
	private String errorCode = "-1";//错误代码
	private String msg = "操作成功";// 提示信息
    private Long count;             //返回表格记录数量
    private List<?> data;           //返回表格数据
	private LinkedHashMap<String, Object> body = new LinkedHashMap<String, Object>();//封装json的map

	public static AjaxJson ok()
		AjaxJson j = new AjaxJson();
		return j;
	

	public static AjaxJson ok(String msg)
		AjaxJson j = new AjaxJson();
		j.setMsg(msg);
		return j;
	

	public static AjaxJson ok(String msg, Object object)
		AjaxJson j = new AjaxJson();
		j.setMsg(msg);
		j.setResult(object);
		return j;
	

	public static AjaxJson ok(Object object)
		AjaxJson j = new AjaxJson();
		j.setResult(object);
		return j;
	

	public static AjaxJson fail(String errorMsg)
		AjaxJson j = new AjaxJson();
		j.setSuccess(false);
		j.setErrorCode("999");//默认错误码
		j.setMsg(errorMsg);
		return j;
	

	public static AjaxJson fail(String errorCode,String errorMsg)
		AjaxJson j = new AjaxJson();
		j.setSuccess(false);
		j.setErrorCode(errorCode);
		j.setMsg(errorMsg);
		return j;
	
	//返回不分页的layui表数据
    public static AjaxJson layuiTable(List<?> list)
        AjaxJson j = new AjaxJson();
        j.setSuccess(true);
        j.setCount(Long.valueOf(list.size()));
        j.setData(list);
        return j;
    
	public LinkedHashMap<String, Object> getBody() 
		return body;
	

	public void setBody(LinkedHashMap<String, Object> body) 
		this.body = body;
	

	public void put(String key, Object value)//向json中添加属性,在js中访问,请调用data.map.key
		body.put(key, value);
	
	
	public void remove(String key)
		body.remove(key);
	

	/**
	 * 直接设置result内容
	 * @param result
	 */
	public void setResult(Object result)
		body.put("result", result);
	
	@JsonIgnore//返回对象时忽略此属性
	public Object getResult()
		return body.get("result");
	

	public String getMsg() 
		return msg;
	

	public void setMsg(String msg) //向json中添加属性,在js中访问,请调用data.msg
		this.msg = msg;
	


	public boolean isSuccess() 
		return success;
	

	public void setSuccess(boolean success) 
		this.success = success;
	
	

	public void setErrorCode(String errorCode) 
		this.errorCode = errorCode;
	

	public String getErrorCode() 
		return errorCode;
	

    public Long getCount() 
        return count;
    

    public void setCount(Long count) 
        this.count = count;
    

    public List<?> getData() 
        return data;
    

    public void setData(List<?> data) 
        this.data = data;
    


WxInitController.java - 微信初始化接入Controller控制器

package net.javadog.springbootwexin.controller;
import net.javadog.springbootwexin.common.AjaxJson;
import net.javadog.springbootwexin.service.WxService;
import net.javadog.springbootwexin.utils.WxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

/**
 * 一个低端小气没档次的程序狗 JavaDog
 * blog.javadog.net
 *
 * @BelongsProject: springboot-wexin
 * @BelongsPackage: net.javadog.springbootwexin.controller
 * @Author: hdx
 * @CreateTime: 2020-02-14 14:52
 * @Description: 微信初始化接入Controller控制器
 */
@RestController
@RequestMapping("/weixin")
public class WxInitController 
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private WxService wxService;

    /**
    *@Author: hdx
    *@CreateTime: 20:39 2020/2/14
    *@param:  shareUrl 分享url地址
    *@Description: 初始化微信JSSDK Config信息
     1.先通过appId和appSecret参数请求指定微信地址 获取AccessToken
     2.在通过第一步中的AccessToken作为参数请求微信地址 获取jsapi_ticket临时票据(此处不考虑调用频率,使用者根据情况放入缓存或定时任务)
     3.通过第二步的JssdkGetticket和timestamp,nonceStr,url作为参数请求微信地址,获取签名signature
     4.将第三步获得的signature和jsapi_ticket,nonceStr,timestamp,url返回给前端,作为Config初始化验证的信息
    */
    @RequestMapping("/initWXJSSDKConfigInfo")
    public AjaxJson initWXJSConfig (@RequestParam(required = false) String url) throws Exception
        logger.info("url=" + url);
        String json = "";
        try 
            Map map = wxService.initJSSDKConfig(url);
            json = WxUtil.mapToJson(map);
        catch (Exception e)
            AjaxJson.fail(e.getMessage());
        
        return AjaxJson.ok(json);
    



WxService.java - 初始化JSSDKConfig

package net.javadog.springbootwexin.service;

import lombok.Getter;
import net.javadog.springbootwexin.utils.WxUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 一个低端小气没档次的程序狗 JavaDog
 * blog.javadog.net
 *
 * @BelongsProject: springboot-wexin
 * @BelongsPackage: net.javadog.springbootwexin.service
 * @Author: hdx
 * @CreateTime: 2020-02-14 20:43
 * @Description: 微信相关service
 */
@Service
public class WxService 
    @Getter
    private static String AppId;
    @Value("$wx.appId")
    public void setAppId(String appId) 
        AppId = appId;
    
    /**
    *@Author: hdx
    *@CreateTime: 20:46 2020/2/14
    *@param:  shareUrl 分享的url
    *@Description: 初始化JSSDKConfig
    */
    public Map initJSSDKConfig(String url) throws Exception 
        //获取AccessToken
        String accessToken = WxUtil.getJSSDKAccessToken();
        //获取JssdkGetticket
        String jsapiTicket = WxUtil.getJssdkGetticket(accessToken);
        String timestamp = Long.toString(System.currentTimeMillis() / 1000);
        String nonceStr = UUID.randomUUID().toString();
        String signature = WxUtil.buildJSSDKSignature(jsapiTicket,timestamp,nonceStr,url);
        Map<String,String> map = new HashMap<String,String>();
        map.put("url", url);
        map.put("jsapi_ticket", jsapiTicket);
        map.put("nonceStr", nonceStr);
        map.put("timestamp", timestamp);
        map.put("signature", signature);
        map.put("appid", AppId);
        return map;
    


WxUtil.java - 微信工具类

package net.javadog.springbootwexin.utils;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.security.MessageDigest;
import java.util.Map;

/**
 * 一个低端小气没档次的程序狗 JavaDog
 * blog.javadog.net
 *
 * @BelongsProject: springboot-wexin
 * @BelongsPackage: net.javadog.springbootwexin.utils
 * @Author: hdx
 * @CreateTime: 2020-02-14 21:19
 * @Description: 微信工具类
 */
@Component
public class WxUtil 
    @Getter
    protected static String AppId;
    @Getter
    protected static String AppSecret;
    @Getter
    protected static String JssdkAccesstokenUrl;
    @Getter
    protected static String JssdkGetticketUrl;

    @Value("$wx.appId")
    public void setAppId(String appId) 
        AppId = appId;
    

    @Value("$wx.appSecret")
    public void setAppSecret(String appSecret) 
        AppSecret = appSecret;
    

    @Value("$wx.jssdk_accesstoken_url")
    public void setJssdkAccesstokenUrl(String jssdkAccesstokenUrl) 
        JssdkAccesstokenUrl = jssdkAccesstokenUrl;
    

    @Value("$wx.jssdk_getticket_url")
    public void setJssdkGetticketUrl(String jssdkGetticketUrl) 
        JssdkGetticketUrl = jssdkGetticketUrl;
    

    /**
    *@Author: hdx
    *@CreateTime: 21:31 2020/2/14
    *@param:  * @param null
    *@Description:
    
    */
    public static String getJSSDKAccessToken() 
        String token = null;
        String url = JssdkAccesstokenUrl.replaceAll("APPID",
                AppId).replaceAll("APPSECRET",
                AppSecret);
        String json = postRequestForWeiXinService(url);
        Map map = jsonToMap(json);
        if (map != null) 
            token = (String) map.get("access_token");
        
        return token;
    

    /**
    *@Author: hdx
    *@CreateTime: 21:40 2020/2/14
    *@param:  * @param null
    *@Description: 根据accessToken获取JssdkGetticket

    */
    public static String getJssdkGetticket(String accessToken) 
        String url = JssdkGetticketUrl.replaceAll("ACCESS_TOKEN", accessToken);
        String json = postRequestForWeiXinService(url);
        Map map = jsonToMap(json);
        String jsapi_ticket = null;
        if (map != null) 
            jsapi_ticket = (String) map.get("ticket");
        
        return jsapi_ticket;
    

    /**
    *@Author: hdx
    *@CreateTime: 21:41 2020/2/14
    *@param:ticket 根据accessToken生成的JssdkGetticket
    *@param:timestamp 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
    *@param:nonceStr 随机字符串
    *@param:url 当前网页的URL
    *@Description: 构建分享链接的签名

    */
    public static String buildJSSDKSignature(String ticket,String timestamp,String nonceStr ,String url) throws Exception 
        String orderedString = "jsapi_ticket=" + ticket
                + "&noncestr=" + nonceStr + "&timestamp=" + timestamp
                + "&url=" + url;

        return sha1(orderedString);
    

    /**
     * sha1 加密JSSDK微信配置参数获取签名。
     *
     * @return
     */
    public static String sha1(String orderedString) throws Exception 
        String ciphertext = null;
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] digest = md.digest(orderedString.getBytes());
        ciphertext = byteToStr(digest);
        return ciphertext.toLowerCase();
    
    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) 
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) 
            strDigest += byteToHexStr(byteArray[i]);
        
        return strDigest;
    
    /**
     * 将字节转换为十六进制字符串
     *
     * @param mByte
     * @return
     */
    private static String byteToHexStr(byte mByte) 
        char[] Digit =  \'0\', \'1\', \'2\', \'3\', \'4\', \'5\', \'6\', \'7\', \'8\', \'9\', \'A\', \'B\', \'C\', \'D\', \'E\', \'F\' ;
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];

        String s = new String(tempArr);
        return s;
    
    /**
    *@Author: hdx
    *@CreateTime: 21:49 2020/2/14
    *@param:  map
    *@Description:  mapToJson

    */
    public static String mapToJson(Map map)
        Gson gson = new Gson();
        String json = gson.toJson(map);
        return json;
    

    /**
    *@Author: hdx
    *@CreateTime: 21:37 2020/2/14
    *@param:  json
    *@Description: jsonToMap

    */
    private static Map jsonToMap(String json) 
        Gson gons = new Gson();
        Map map = gons.fromJson(json, new TypeToken<Map>().getType());
        return map;
    

    /**
    *@Author: hdx
    *@CreateTime: 21:36 2020/2/14
    *@param:  * @param null
    *@Description: 调取微信接口

    */
    private static String postRequestForWeiXinService(String getAccessTokenUrl) 
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> postForEntity = restTemplate.postForEntity(getAccessTokenUrl, null, String.class);
        String json = postForEntity.getBody();
        return json;
    



SpringbootWexinApplication.java - SpringBoot启动类

package net.javadog.springbootwexin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootWexinApplication 

    public static void main(String[] args) 
        SpringApplication.run(SpringbootWexinApplication.class, args);
    



config/application.yml - 基础配置文件

spring:
  profiles:
    #激活配置文件
    active: prod
  #配置静态资源路径
  resources:
    static-locations: classpath:/static/

#日志相关
logging:
  #配置文件日志路径
  config: classpath:logback-spring.xml

#微信相关配置
wx:
  #appId (到时候换成自己公众号的)
  appId: wx4ad618620f8c3528
  #appSecret(到时候换成自己公众号的)
  appSecret: b772c7863b29e270aa86e40f9b9e6215
  #参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token)
  jssdk_accesstoken_url: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
  #用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket)
  jssdk_getticket_url: https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

application-dev.yml -开发配置文件(可选)

# 开发环境配置
spring:
  profiles: dev

#端口设置
server:
  port: 8000

application-prod.yml -生产配置文件(因JS接口安全域名限制,则采取正式生产配置)

# 生产环境配置
spring:
  profiles: prod

#端口设置
server:
  port: 8002

application-test.yml -测试配置文件(可选)

# 生产环境配置
spring:
  profiles: prod

#端口设置
server:
  port: 8002

**demo.html ** - 测试h5页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>测试jssdk</title>
    <!--引入微信JS文件-->
    <script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js" type="text/javascript"></script>
    <!--引入jquery-->
    <script src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"></script>
<script>
    //获取当前页面地址
    var url = (window.location.href).split(\'#\')[0];
    //调取后台接口获取权限验证配置
    $.ajax(
        type : "get",
        /*!!!切记到时候改成自己的*/
        url : "http://wxjssdk.javadog.net/weixin/initWXJSSDKConfigInfo?url="+url,//替换网址,xxx根据自己jssdk文件位置修改
        success : function(data)
            console.log("返回值为=" + data);
            var msg = "";
            if(data.success)
                msg = JSON.parse(data.msg);
            
            //通过config接口注入权限验证配置
            wx.config(
                debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印
                appId: msg.appid,
                timestamp: msg.timestamp,
                nonceStr: msg.nonceStr,
                signature: msg.signature,
                /*!!!切记到时候按需自己选择,参考文档填写*/
                jsApiList: [
                    "onMenuShareAppMessage",//分享给好友
                    "chooseImage"
                ]
            );
        ,
        error:function(data)
            alert(JSON.stringify(data));
        
    );

    //通过ready接口处理成功验证
    wx.ready(function ()
        wx.checkJsApi(
            jsApiList: [\'chooseImage\',\'onMenuShareAppMessage\'],
            success: function (res) JSON.stringify(res)
        );
        var shareData = 
            title: \'标题\',
            desc: \'简介\',//这里请特别注意是要去除html
            link: url,
            imgUrl: \'http://b2b.haier.com/shop/userfiles/sys/1/files/201912/af656b3a-8c2c-424d-937b-a8035deb78f5.jpg\'
        ;
        wx.onMenuShareAppMessage(shareData);


    );
    //从相册选取图片
    function wxchooseImage()
        wx.chooseImage(
            count: 1, // 默认9
            sizeType: [\'original\', \'compressed\'], // 可以指定是原图还是压缩图,默认二者都有
            sourceType: [\'album\', \'camera\'], // 可以指定来源是相册还是相机,默认二者都有
            success: function (res) 
                var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
            
        );
    
</script>
</head>
<body>
<button onclick="wxchooseImage();">点我选取相册</button>
</body>
</html>

以上是关于SpringBoot接入微信JSSDK,看这篇妥妥的的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud : 接入 微信公众号平台获取JsSDK配置参数

超详细的springBoot学习教程,springboot学习看这篇就够了

不会用SpringBoot连接Redis,那就赶紧看这篇

配置微信jssdk自定义分享

不会用SpringBoot连接Redis,那就赶紧看这篇

微信jssdk图片语音开发记录