微信參考文檔https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
微信官方文檔
步驟一:綁定域名
先登錄微信公衆平臺進入“公衆號設置”的“功能設置”裏填寫“JS接口安全域名”。
備註:登錄後可在“開發者中心”查看對應的接口權限。
添加步驟:
1.下載txt文件(MP_verify_8W1dJO7OTGYxxxx.txt),放到項目根目錄下;
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 標準模塊加載方法加載
步驟三:通過config接口注入權限驗證配置
所有需要使用JS-SDK的頁面必須先注入配置信息,否則將無法調用(同一個url僅需調用一次,對於變化url的SPA的web app可在每次url變化時進行調用,目前Android微信客戶端不支持pushState的H5新特性,所以使用pushState來實現web app的頁面會導致簽名失敗,此問題會在Android6.2中修復)。
wx.config({
debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: '', // 必填,公衆號的唯一標識
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名
jsApiList: [] // 必填,需要使用的JS接口列表
});
步驟四:通過ready接口處理成功驗證
wx.ready(function(){
// config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
});
步驟五:通過error接口處理失敗驗證
wx.error(function(res){
// config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這裏更新簽名。
});
代碼:
前端頁面
需要引入http://res.wx.qq.com/open/js/jweixin-1.4.0.js
<script>
$(function(){
$.ajax({
type : "post",
url : "/wx/wxShare",
dataType : "json",
async : true,
data:{url:'需要分享頁面的url'},
success : function(data) {
wx.config({
debug: false,////生產環境需要關閉debug模式
appId: data.appid,//appId通過微信服務號後臺查看
timestamp: data.timestamp,//生成簽名的時間戳
nonceStr: data.nonceStr,//生成簽名的隨機字符串
signature: data.signature,//簽名
jsApiList: [//需要調用的JS接口列表
'checkJsApi',//判斷當前客戶端版本是否支持指定JS接口
'onMenuShareTimeline',//分享給好友
'onMenuShareAppMessage'//分享到朋友圈
]
});
},
error: function(xhr, status, error) {
// alert(status);
//alert(xhr.responseText);
}
})
wx.ready(function () {
wx.onMenuShareTimeline({
title: '',//分享的標題
desc: '朋友圈都被這個刷屏了,你也來曬一曬吧~', // 分享描述
link:'', //注意這裏最好是http訪問全路徑 要不容易出問題
imgUrl: '', // 分享圖標 http訪問全路徑
success: function () {
// 用戶確認分享後執行的回調函數
//alert("分享成功");
}
cancel: function () {
// 用戶取消分享後執行的回調函數
//alert("取消分享");
}
});
//分享給朋友
wx.onMenuShareAppMessage({
title: '',
desc: '朋友圈都被這個刷屏了,你也來曬一曬吧~', // 分享描述
link:'',
imgUrl: '', // 分享圖標 // 分享圖標
type: '', // 分享類型,music、video或link,不填默認爲link
dataUrl: '', // 如果type是music或video,則要提供數據鏈接,默認爲空
success: function () {
// 用戶確認分享後執行的回調函數
},
cancel: function () {
// 用戶取消分享後執行的回調函數
}
});
wx.error(function (res) {
// alert(res.errMsg);
});
});
});
</script>
後臺
import com.ljzforum.base.enums.APIResponseCode;
import com.ljzforum.base.vo.APIResultVo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author : 恆果果
* create at: 2019-12-09 10:48
* @description: 微信分享等
*/
@RestController
public class WxController {
@Resource
private WXIConfig wxiConfig;
@RequestMapping("/wxShare")
public ModelAndView wxShare(String url){
ModelAndView view = new ModelAndView();
view.addObject("data",wxShareN(url))
return view ;
}
}
/**
* 微信分享
*/
public LinkedHashMap<String,Object> wxShareN(String url) {
//微信分享
LinkedHashMap<String,Object> map = new LinkedHashMap<>();
try {
String ticket = wxService.getJssdkTicketN();
String shareUrl = url;
map.put("appId", wxiConfig.getAppId());
Map<String, String> signMap = WxJSSDKSign.sign(ticket, shareUrl);
map.put("signMap",signMap);
} catch (Exception e) {
log.error("獲取微信的JSSDK接口失敗........", e);
}
return map;
}
WXIConfig
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ljzforum.common.constant.RedisContent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 微信接口的服務信息
*
* @author
*/
@Service
@Slf4j
public class WxService {
@Resource
private RedisTemplate redisTemplate;
@Resource
private WXIConfig wxiConfig;
/**
* 獲取JSSDK的祕鑰
*/
public String getJssdkTicket(String access_token) {
String ticket = (String) redisTemplate.opsForValue().get(RedisContent.WX_OPEN_TOKEN_TICKET + access_token);
if (ticket == null) {
Map<String, Object> params = new LinkedHashMap<String, Object>();
params.put("access_token", access_token);
params.put("type", "jsapi");
JSONObject result = JSONUtil.parseObj(HttpUtil.get(WXIConfig.WX_JS_SDK_GET_TICKET, params));
if (result.containsKey("errcode") && !result.getStr("errmsg").equals("ok")) {
log.error("獲取ticket出錯:" + result.toString());
return null;
}
ticket = result.getStr("ticket");
redisTemplate.opsForValue().set(RedisContent.WX_OPEN_TOKEN_TICKET + access_token, ticket, 30, TimeUnit.MINUTES);
}
return ticket;
}
/**
* 獲取JSSDK的祕鑰
*/
public String getJssdkTicket() {
String access_token = getCgiBinTokenN();
if (access_token == null) {
return null;
}
String ticket;
try {
ticket = getJssdkTicket(access_token);
} catch (Exception e) {
log.error("redisL裏的的token不正確,重新從接口獲取token");
access_token = getCgiBinTokenN();
ticket = getJssdkTicket(access_token);
}
return ticket;
}
/**
* 獲取Token數據
*/
public String getCgiBinTokenN() {
String access_token = (String) redisTemplate.opsForValue().get(RedisContent.WX_CGI_BIN_TOKEN + wxiConfig.getAppId());
if (access_token == null) {
Map<String, Object> params = new LinkedHashMap<String, Object>();
params.put("grant_type", "client_credential");
params.put("appid", wxiConfig.getAppId());
params.put("secret", wxiConfig.getAppSecret());
JSONObject result = JSONUtil.parseObj(HttpUtil.get(WXIConfig.WX_CGI_BIN_GET_TOKEN, params));
if (result.containsKey("errcode") && result.getStr("errcode") != null) {
result = JSONUtil.parseObj(HttpUtil.get(WXIConfig.WX_CGI_BIN_GET_TOKEN, params));
}
access_token = result.getStr("access_token");
redisTemplate.opsForValue().set(RedisContent.WX_CGI_BIN_TOKEN + wxiConfig.getAppId(), access_token, 30, TimeUnit.MINUTES);
}
return access_token;
}
}
WXIConfig
import com.ljzforum.common.util.CustomizedPropertyConfigurer;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class WXIConfig {
//微信支付分配的商戶號
@Value("${mch_id}")
public String mchId;
//支付的私人祕鑰
@Value("${app_pay_key}")
public String appPayKey;
//獲取APPID
@Value("${app_id}")
public String appId;
//獲取微信APP_SECRET
@Value("${app_secret}")
public String appSecret;
//獲取小程序APPID
@Value("${mini_appid}")
public String miniAppid;
//獲取小程序SECRET
@Value("${mini_secret}")
public String miniSecret;
//臨時的整型參數值
public static String QR_SCENE = "QR_SCENE";
//臨時的字符串參數值
public static String QR_STR_SCENE = "QR_STR_SCENE";
//永久的整型參數值
public static String QR_LIMIT_SCENE = "QR_LIMIT_SCENE";
//永久的字符串參數值
public static String QR_LIMIT_STR_SCENE = "QR_LIMIT_STR_SCENE";
//統一下單
public static String WX_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//獲取access token
public static String WX_TOKEN = "https://api.weixin.qq.com/cgi-bin/token";
//獲取微信服務器IP地址
public static String WX_TOKEN_CALLBACK_IP = "https://api.weixin.qq.com/cgi-bin/getcallbackip";
//用戶同意授權,獲取code
public static String WX_GET_USERINFO_AUTHORIZE = "https://open.weixin.qq.com/connect/oauth2/authorize";
//通過code換取網頁授權access_token
public static String WX_GET_USERINFO_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
//刷新access_token(如果需要)
public static String WX_GET_USERINFO_REFRESH_TOKEN = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
//刷新拉取用戶信息(需scope爲 snsapi_userinfo)
public static String WX_GET_USERINFO = "https://api.weixin.qq.com/sns/userinfo";
//通過access_token、openid獲取用戶信息
public static String WX_GET_INFO = "https://api.weixin.qq.com/cgi-bin/user/info";
//獲取微信用戶素材
public static String WX_BATCH_GET_MATERIAL = "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=";
//獲取微信用戶素材總數
public static String WX_GET_MATERIAL_COUNT = "https://api.weixin.qq.com/cgi-bin/material/get_materialcount?access_token=";
//獲取微信永久素材
public static String WX_GET_MATERIAL = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=";
//創建微信菜單
public static String WX_CREATE_MENU = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";
//查詢微信菜單
public static String WX_GET_MENU = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=";
//刪除微信菜單
public static String WX_DELETE_MENU = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=";
//微信退款
public static String WX_REFUND_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=";
//獲取access_toke
public static String WX_CGI_BIN_GET_TOKEN = "https://api.weixin.qq.com/cgi-bin/token";
//長鏈接轉短鏈接接口
public static String WX_GET_SHORT_URL = "https://api.weixin.qq.com/cgi-bin/shorturl?access_token=";
//新增其他類型永久素材
public static String WX_MATERIAL_ADD_MATERIAL = "http://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%1s&type=%2s";
/**
* 獲取JSSDK調研說需要的鑰匙
*/
public static String WX_JS_SDK_GET_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
//微信開放平臺公衆號消息加解密Key
public static String WX_PLATFORM_ENCODING_AES_KEY = (String) CustomizedPropertyConfigurer.getContextProperty("wx_platform_encoding_aes_key");
//微信開放平臺公衆號消息校驗Token
public static String WX_PLATFORM_TOKEN = (String) CustomizedPropertyConfigurer.getContextProperty("wx_platform_token");
//微信開放平臺APPID
public static String WX_PLATFORM_APP_ID = (String) CustomizedPropertyConfigurer.getContextProperty("wx_platform_app_id");
//微信開放平臺AppSecret
public static String WX_PLATFORM_APP_SECRET = (String) CustomizedPropertyConfigurer.getContextProperty("wx_platform_app_secret");
//獲取第三方平臺component_access_token
public static String WX_GET_COMPONENT_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/component/api_component_token";
//獲取預授權碼pre_auth_code
public static String WX_GET_PRE_AUTH_CODE = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=";
//獲取公衆號的接口調用憑據和授權信息
public static String WX_API_QUERY_AUTH = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=";
public static String WX_API_REFRESH_TOKEN = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=";
public static String WX_GET_AUTHORIZER_INFO = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=";
/**
* 微信通知信息推送模版
*/
//模版id
public static String WX_TEMPLATE_ID_SUCCESS_SIGNUP = "w64cYpbQWD65KR6cziKipXNnBowEOSheqc7uoKUTR70";
public static String WX_TEMPLATE_ID_ACTIVITY_MESSAGE = "jVXOi_SxSpGA9t9dHCHA7Ss50a4Y8KPLFVnCas5EaIc";
//發送模版信息
public static String WX_SEND_TEMPLATE = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
//微信短鏈生成接口
public static String WX_SHORT_URL = "https://api.weixin.qq.com/cgi-bin/shorturl?access_token=";
public static String USER_LIST_URL = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=";
//生成帶參數的二維碼
public static String WX_QRCODE_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";
//通過ticket換取二維碼
public static String WX_CGI_BIN_QRCODE = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
//客服接口-發消息
public static String WX_SEND_MSG_CUSTOM = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=";
//圖片
public static String WX_MATERIAL_IMAGE = "image";
//語音
public static String WX_MATERIAL_VOICE = "voice";
//視頻
public static String WX_MATERIAL_VIDEO = "video";
//縮略圖
public static String WX_MATERIAL_THUMB = "thumb";
}
WxJSSDKSign
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 WxJSSDKSign {
public static void main(String[] args) {
String jsapi_ticket = "jsapi_ticket";
// 注意 URL 一定要動態獲取,不能 hardcode
String url = "http://example.com";
Map<String, String> ret = sign(jsapi_ticket, url);
for (Map.Entry entry : ret.entrySet()) {
System.out.println(entry.getKey() + ", " + entry.getValue());
}
};
public static Map<String, String> sign(String jsapi_ticket, String url) {
Map<String, String> ret = new HashMap<String, String>();
String nonce_str = create_nonce_str();
String timestamp = create_timestamp();
String string1;
String signature = "";
// 注意這裏參數名必須全部小寫,且必須有序
string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url;
// System.out.println(string1);
try {
//生成一個 MessageDigest 對象
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
// 復位摘要
crypt.reset();
//處理數據
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ret.put("url", url);
ret.put("ticket", jsapi_ticket);
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);
}
}
注意事項:
JAVA, Node, Python 部分代碼只實現了簽名算法,需要開發者傳入 jsapi_ticket 和 url ,其中 jsapi_ticket 需要通過 http://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=ACCESS_TOKEN 接口獲取 url 爲調用頁面的完整 url 。
PHP 部分代碼包括了獲取 access_token 和 jsapi_ticket 的操作,只需傳入 appid 和 appsecret 即可,但要注意如果已有其他業務需要使用 access_token 的話,應修改獲取 access_token 部分代碼從全局緩存中獲取,防止重複獲取 access_token ,超過調用頻率。
注意事項:
1. jsapi_ticket 的有效期爲 7200 秒,開發者必須全局緩存 jsapi_ticket ,防止超過調用頻率。