步骤一:绑定域名
先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
备注:登录后可在“开发者中心”查看对应的接口权限。
步骤二:引入JS文件
在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js
备注:支持使用 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接口列表
});
签名算法见文末的附录1,所有JS接口列表见文末的附录2
步骤四:通过ready接口处理成功验证
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
步骤五:通过error接口处理失败验证
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
所有接口通过wx对象(也可使用jWeixin对象)来调用,参数是一个对象,除了每个接口本身需要传的参数之外,还有以下通用参数:
1.success:接口调用成功时执行的回调函数。
2.fail:接口调用失败时执行的回调函数。
3.complete:接口调用完成时执行的回调函数,无论成功或失败都会执行。
4.cancel:用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
5.trigger: 监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。
备注:不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回。
以上几个函数都带有一个参数,类型为对象,其中除了每个接口本身返回的数据之外,还有一个通用属性errMsg,其值格式如下:
调用成功时:"xxx:ok" ,其中xxx为调用的接口名
用户取消时:"xxx:cancel",其中xxx为调用的接口名
调用失败时:其值为具体错误信息
wx.startRecord();
wx.stopRecord({
success: function (res) {
var localId = res.localId;
}
});
wx.onVoiceRecordEnd({
// 录音时间超过一分钟没有停止的时候会执行 complete 回调
complete: function (res) {
var localId = res.localId;
}
});
wx.playVoice({
localId: '' // 需要播放的音频的本地ID,由stopRecord接口获得
});
wx.pauseVoice({
localId: '' // 需要暂停的音频的本地ID,由stopRecord接口获得
});
wx.stopVoice({
localId: '' // 需要停止的音频的本地ID,由stopRecord接口获得
});
wx.onVoicePlayEnd({
success: function (res) {
var localId = res.localId; // 返回音频的本地ID
}
});
wx.uploadVoice({
localId: '', // 需要上传的音频的本地ID,由stopRecord接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
var serverId = res.serverId; // 返回音频的服务器端ID
}
});
备注:上传语音有效期3天,可用微信多媒体接口下载语音到自己的服务器,此处获得的 serverId 即 media_id,参考文档 .目前多媒体文件下载接口的频率限制为10000次/天,如需要调高频率,请登录微信公众平台,在开发 - 接口权限的列表中,申请提高临时上限。
下面开始码代码:
voice.js
var URIstring=location.href.split('#')[0];//获取当前页面的全路径 var url="/zf/ConfigParam?Rurl="+encodeURIComponent(URIstring);//将路径传入后台加密时使用 $(document).ready(function(){ $.ajax({ url :encodeURI(url),// encodeURI(encodeURI(url)), type : 'POST', async: false, dataType : 'json', timeout : 5000, error: function(XMLHttpRequest, textStatus, errorThrown) { alert("不好意思,出了点小问题"); }, success : function(req) { wx.config({ debug: false,//开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: req.appid, // 必填,公众号的唯一标识 timestamp:req.timestamp, // 必填,生成签名的时间戳 nonceStr: req.nonceStr, // 必填,生成签名的随机串 signature:req.signature,// 必填,签名,见附录1 jsApiList:['chooseImage','previewImage','uploadImage','downloadImage',]// 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); } }); var voice = { localId: '', serverId: '' };//全局变量 var END; });
//松手结束录音 }); //上传录音 } // 4.4 监听录音自动停止 });
document.querySelector('#playVoice').onclick = function () { };
// 暂停播放音频 |
---|
/*使用微信jssdk接口录音,在同一个域只需要授权一次,即第一次使用录音的时候,微信自己会弹出对话框询问是否允许录音,用户点击允许后,之后再使用录音时,便不会再咨询用户是否允许。
在第一次按住录音后,由于用户未曾允许录音,微信会提示用户授权允许在本页面使用微信录音功能,这时用户会放开录音按钮转而去点击允许,在用户允许后,才真正会开始录音,而此时用户早已放开录音按钮,那么录音按钮上便不会再有touchend事件,录音便会一直进行。
解决策略:使用localStorage记录用户是否曾授权,并以此来判断是否需要在刚进入页面是自动录一段录音来触发用户授权*/
if(!localStorage.rainAllowRecord || localStorage.rainAllowRecord !== 'true'){
wx.startRecord({
success: function(){
localStorage.rainAllowRecord = 'true';
setTimeout(function(){
wx.stopRecord({
fail:function(res){
//alert("停止失败");
},
success: function (res) {
var localId = res.localId;
}
});
},800);//这里设置800毫秒,是因为如果用户录音之后马上松开按钮,会成 wx.stopRecord不起作用的情况,然后会一直录音,所以时间设置长一点
//clearTimeout(t);
},
cancel: function () {
alert('用户拒绝授权录音');
}
});
}
wx.error(function (res) {
wx.stopRecord();
alert(res.errMsg);
});
以上是前端js,具体业务具体操作
后台代码
ConfigParam.java
import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONObject; public class ConfigParam extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); String Rurl=java.net.URLDecoder.decode(request.getParameter("Rurl"),"utf-8"); //System.out.println(Rurl); Map JSONObject json=JSONObject.fromObject(ret); //System.out.println(json.toString()); out.print(json); } } |
---|
WeixinUtil.java 和AccessToken.java
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * 公众平台通用接口工具类 * * * */ public class WeixinUtil { private static Log log = LogFactory.getLog(WeixinUtil.class); // 获取access_token的接口地址(GET) 限200(次/天) public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token? grant_type=client_credential&appid=APPID&secret=APPSECRET"; private static Map /** * 获取access_token * * @param appid 凭证 * @param appsecret 密钥 * @param flag 是否重新获取 * @return */ public static AccessToken getAccessToken(String appid, String appsecret,boolean flag) { AccessToken accessToken = null; log.info("=================getAccessToken-start"); if(!flag){ accessToken = tokenMap.get(appid); } long now = System.currentTimeMillis(); if(accessToken != null && !flag){ long lastTime = accessToken.getCreateTime(); //当accessToken还在有效期内 if(now <= (lastTime + accessToken.getExpiresIn() * 1000) ){ log.info("读取缓存accessToken=="+accessToken); return accessToken; } } String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret); JSONObject jsonObject = httpRequest(requestUrl, "GET", null); // 如果请求成功 if (null != jsonObject) { try { accessToken = new AccessToken(); accessToken.setCreateTime(now); accessToken.setToken(jsonObject.getString("access_token")); accessToken.setExpiresIn(jsonObject.getInt("expires_in") - 300); log.info("重新获取accessToken=="+accessToken); } catch (JSONException e) { accessToken = null; // 获取token失败 log.error("获取token失败 errcode:"+jsonObject.getInt("errcode")+" errmsg:{"+ jsonObject.getString("errmsg")+"}" ); } } log.info("=================getAccessToken-end"); tokenMap.put(appid, accessToken); return accessToken; } /** * 获取access_token * * @param appid 凭证 * @param appsecret 密钥 * @return */ public static AccessToken getAccessToken(String appid, String appsecret ) { log.info("getAccessToken:重新获取AccessToken"); return getAccessToken(appid, appsecret, true); } public static String JS_API_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; /** * * @param accessToken * @return */ public static String getJsapi_ticket(String accessToken){ log.info("=====================getJsapi_ticket:start"); String url = JS_API_URL.replace("ACCESS_TOKEN", accessToken); JSONObject json = httpRequest(url, "GET", null); log.info("getJsapi_ticket:"+json); String jsapi_ticket = ""; try { jsapi_ticket = json.getString("ticket"); } catch (Exception e) { log.error("=====================getJsapi_ticket:error"+json); e.printStackTrace(); } log.info("=====================getJsapi_ticket:end"); return jsapi_ticket; } //JS SDK 中需要的相关的数据 private static Map public static String getJsapi_ticket_cache(String accessToken ){ String jsapi = JsSDKMap.get(accessToken); if(jsapi != null){ log.info("读取jsapi_ticket缓存"+jsapi ); return jsapi; }else{ jsapi = getJsapi_ticket(accessToken); log.info("刷新Jsapi_ticket:"+jsapi+"===="+accessToken ); JsSDKMap.put(accessToken, jsapi); } return jsapi; } //获得jsapi_ticket,noncestr,timestamp public static Map log.info("=================getJSSDKData-start"); //1)获取access_token AccessToken accessToken = getAccessToken(appid, appsecret,false); String token = accessToken.getToken(); log.info("accessToken:"+accessToken); //2)获取jsapi_ticket(有效期7200秒 String jsapi_ticket = getJsapi_ticket_cache(token); log.info("jsapi_ticket:"+jsapi_ticket); //3)获取//随机字符串 String noncestr = Double.toString(Math.random()).substring(2, 15);//随机字符串 //4)获取 随机时间戳 String timeStamp =((int)(new Date().getTime()/1000))+"";//随机时间戳 //5)获取签名 String str = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + noncestr + "×tamp="+ timeStamp +"&url=" + requestUrl; log.info("str:" + str); String signature = SHA1Util.Sha1(str); Map map.put("ticket", jsapi_ticket); map.put("noncestr", noncestr); map.put("timestamp", timeStamp); map.put("signature", signature); map.put("url", requestUrl); map.put("appid", appid); log.info("=================getJSSDKData-end"); return map; } /** * 发起https请求并获取结果 * * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) */ public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; StringBuffer buffer = new StringBuffer(); try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); // 设置请求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); // 当有数据需要提交时 if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意编码格式,防止中文乱码 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 将返回的输入流转换成字符串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 释放资源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { log.error("Weixin server connection timed out."); } catch (Exception e) { log.error("https request error:{}", e); } return jsonObject; } }
/** |
---|
uploadVoice
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); //String mediaId=request.getParameter("mediaId"); Map String mediaId = request.getParameter("mediaId"); String folderName="/app/ss/"+new SimpleDateFormat("yyyy/MMdd/").format(new Date()); try { File dir = new File(folderName); if (!dir.exists()) dir.mkdirs(); } catch (Exception ex) { } String Str=""; String randstr = UUID.randomUUID().toString().replaceAll("-", ""); String path=folderName+randstr;//绝对路径 String frontName = "" + randstr;//相对路径 String absFileName=path+".jpg";//"D:\\img\\a.jpg";// String str= DloadImgUtil.downloadMedia(mediaId, absFileName,path+"_240.mp3","2");//调用下载接口 map.put("status", "1"); map.put("msg",Str); map.put("path", ""); map.put("photoUrl",""); JSONObject json = JSONObject.fromObject(map); out.print(json); } |
---|
记住,大坑来了,由于从微信服务器下载下来的音频格式是amr格式的,为了实现使用h5标签能够播放,需要转成mp3格式
我是使用ffmpeg进行转换的
使用ffmpeg将amr格式转为mp3格式时需注意:windows系统使用ffmpeg.exe,linux系统使用ffmpeg(去官网下载对应版本http://ffmpeg.org/download.html)
DloadVoicUtil.java
public class DloadImgUtil { private final static String FFMPEG_PATH; static { FFMPEG_PATH =DloadImgUtil.class.getResource("ffmpeg.exe").getFile(); //windows系统使用, FFmpeg文件和DloadImgUtil 同一目录下 } // static { String filePath = null; AccessToken accessToken = WeixinUtil.getAccessToken(WechatUtil.appId, WechatUtil.appSecret, false); // 拼接请求地址 String requestUrl = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID"; requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken.getToken()) .replace("MEDIA_ID", mediaId); String returnstr="0"; BufferedInputStream bis = null; FileOutputStream fos = null; HttpsURLConnection conn = null; try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); // URL url = new URL(null,requestUrl,new // sun.net.www.protocol.https.Handler()); // URL url = new URL(requestUrl); URL url = new URL(requestUrl); conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(ssf); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 设置请求方式(GET/POST) conn.setRequestMethod("GET"); // 根据内容类型获取扩展名 // String fileExt = DloadImgUtil // .getFileexpandedName(conn.getHeaderField("Content-Type")); // 将mediaId作为文件名 filePath = savePath; bis = new BufferedInputStream(conn.getInputStream()); fos = new FileOutputStream(new File(filePath)); byte[] buf = new byte[8096]; int size = 0; while ((size = bis.read(buf)) != -1) fos.write(buf, 0, size); amr2mp3(savePath,out); } catch (Exception e) { returnstr="-1"; e.printStackTrace(); } finally { // 释放资源 conn.disconnect(); try { // 关闭流,释放资源 if (fos != null) { fos.close(); } if (bis != null) { bis.close(); } } catch (Exception e) { e.getStackTrace(); } } return returnstr; } /** |
---|