先給猴急的客官上乾貨程式碼
事情的起因是因為疫情嚴重,領導要求做一個專題頁,能夠儘可能幫助所需要的人。
於是乎本狗與同事挑燈奮戰,加班加點趕工出來。
部署上線完成,用微信內建瀏覽器分享後,理想狀態應該是這樣的,如下圖⬇️
但是,結果卻不是理想的這樣,默默地留下了沒有技術的淚水,如下圖⬇️
竟然沒有關鍵字和展示圖片,在本菜狗的不懈努力下,終於承認技術不行,去請教了大佬,得出如下結論。
史上最詳細的接入微信JSSDK菜鳥教學,本文全面的記錄了接入微信JSSDK的步驟,具體的程式碼及遇到的坑,並且展示釋出最終效果,並將程式碼釋出GitHub。隨篇幅較長,但史上最全。大佬勿噴,新手入門,親測可用!!!
放鬆心態,慢慢來看
任何平臺接入,官方檔案是標杆,雖有些關鍵點一筆帶過,我們也要通讀有個印象,點選微信官方檔案開啟檔案,如下⬇️
使用IDEA工具,新建SpringBoot專案,專案名為springboot-wexin,目錄結構如下
AjaxJson.java - 自定義介面返回前臺資料格式的封裝類
/**
* Copyright © 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 + "×tamp=" + 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>