微信参考文档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 ,防止超过调用频率。