[置顶] 微信JSSDK开发(分享接口和上传图片接口)
2016-07-26 13:58
701 查看
JSSDK使用步骤
1:绑定域名
先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。如果你使用了支付类接口,请确保支付目录在该安全域名下,否则将无法完成支付。备注:登录后可在“开发者中心”查看对应的接口权限。(注意:一个公众号只能设置三个域名,设置好了每个月只有三次修改的机会,而且还需要审核,所以慎重。重点是微信接口只能在这三个域名下测试,本地无法测试)
2:引入JS文件
在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js请注意,如果你的页面启用了https,务必引入https://res.wx.qq.com/open/js/jweixin-1.0.0.js
,否则将无法在iOS9.0以上系统中成功使用JSSDK
如需使用摇一摇周边功能,请引入 jweixin-1.1.0.js
备注:支持使用 AMD/CMD 标准模块加载方法加载
3.把ID和密码存到application.properties,方便以后替换
#超级合伙人联盟服务号 WEIXIN_APPID=xxxxxx WEIXIN_APP_SECRET=xxxxxx OSS_ACCESS_ID=xxxxx OSS_ACCESS_KEY=xxxxx OSS_URL=http://hhr360oss.oss-cn-hangzhou.aliyuncs.com/
4.JS-SDK使用权限签名算法(获取[b]jsapi_ticket)[/b]
package com.hongwei.futures.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.util.Date; import java.util.Map; import javax.imageio.stream.FileImageInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONObject; import com.opensymphony.xwork2.ActionContext; public class WeiXinUtil { private static final Log logger = LogFactory.getLog(WeiXinUtil.class); /** * * @param httpSession * @param request * @param actionContext * @param from 第一次分享出去后 后面微信会自己拼接 from 和 isappinstalled(这是个坑人的点) * @param isappinstalled * @throws Exception */ public static void getToken(HttpSession httpSession, HttpServletRequest request, ActionContext actionContext, String from, String isappinstalled) throws Exception { String access_token = null; if (null == httpSession.getAttribute("access_token")) { // 第一步获取token存入全局缓存, String result1 = HttpClientUtil.getHTTP("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + Property.getProperty("WEIXIN_APPID") + "&secret=" + Property.getProperty("WEIXIN_APP_SECRET")); org.json.JSONObject obj1 = new JSONObject(result1); access_token = obj1.get("access_token").toString(); httpSession.setAttribute("access_token", access_token); } else { access_token = httpSession.getAttribute("access_token").toString(); } String jsapi_ticket = null; if (null == httpSession.getAttribute("jsapi_ticket")) { // 第二步根据token得到jsapi_ticket存入全局缓存 String result2 = HttpClientUtil.getHTTP("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi"); JSONObject obj2 = new JSONObject(result2); jsapi_ticket = obj2.get("ticket").toString(); httpSession.setAttribute("jsapi_ticket", jsapi_ticket); } else { jsapi_ticket = httpSession.getAttribute("jsapi_ticket").toString(); } // 获取请求的地址 StringBuffer url = request.getRequestURL(); String contextUrl = url.delete(url.length() - request.getRequestURI().length(), url.length()).toString(); String httpUrl = contextUrl + request.getRequestURI(); if (from != null && isappinstalled != null) { httpUrl = httpUrl + "?from=" + from + "&isappinstalled=" + isappinstalled; } // 签名算法 Map<String, String> map = Sign.sign(jsapi_ticket, httpUrl); actionContext.put("appId", Property.getProperty("WEIXIN_APPID")); actionContext.put("timestamp", map.get("timestamp")); actionContext.put("nonceStr", map.get("nonceStr")); actionContext.put("signature", map.get("signature")); actionContext.put("newDate", new Date().getTime()); } public static String getOnlyToken(HttpSession httpSession) throws Exception { String access_token = null; if (null == httpSession.getAttribute("access_token")) { // 第一步获取token存入全局缓存, String result1 = HttpClientUtil.getHTTP("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + Property.getProperty("WEIXIN_APPID") + "&secret=" + Property.getProperty("WEIXIN_APP_SECRET")); org.json.JSONObject obj1 = new JSONObject(result1); access_token = obj1.get("access_token").toString(); httpSession.setAttribute("access_token", access_token); } else { access_token = httpSession.getAttribute("access_token").toString(); } return access_token; } /** * 从微信上获取图片并且上传到OSS服务器上 * @param httpSession * @param serverId * @return * @throws Exception */ public static String downloadPicAndUploadOSS(HttpSession httpSession, String serverId) throws Exception { String token = getOnlyToken(httpSession); String downloadUrl = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token="+token+"&media_id="+serverId; byte[] buffer = null; BufferedInputStream in = new BufferedInputStream(new URL(downloadUrl).openStream()); String url = WeiXinUtil.class.getResource("/").getPath(); String filePath = url+"cc.jpg"; File file = new File(filePath); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); buffer = new byte[2048]; int length = in.read(buffer); while (length != -1) { out.write(buffer, 0, length); length = in.read(buffer); } in.close(); out.close(); byte[] imageToArray = imageToArray(filePath); String stockImage = OSSUploadUtil.imageFileUpload(imageToArray, "cc.jpg", "_stock"); logger.info("stock_image_url====>" + stockImage); return stockImage; } public static byte[] imageToArray(String filePath) throws FileNotFoundException, IOException { byte[] data = null; FileImageInputStream input = null; try { input = new FileImageInputStream(new File(filePath)); ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int numBytesRead = 0; while ((numBytesRead = input.read(buf)) != -1) { output.write(buf, 0, numBytesRead); } data = output.toByteArray(); output.close(); input.close(); } catch (FileNotFoundException ex1) { ex1.printStackTrace(); } catch (IOException ex1) { ex1.printStackTrace(); } return data; } }
相关工具类
package com.hongwei.futures.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import net.sf.json.JSONObject; import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class HttpClientUtil { private static final Log logger = LogFactory.getLog(HttpClientUtil.class); /** * http get 方式交互 */ public static String getHTTP(String URL) { String responseMsg = ""; // 构造HTTPClient的实例 HttpClient httpClient = new HttpClient(); GetMethod getmethod = new GetMethod(URL); // 使用系统系统的默认的恢复策略 getmethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler()); try { // ִ执行 HTTPClient方法,调用HTTP接口 httpClient.executeMethod(getmethod); // 读取返回的内容 byte[] responseBody = getmethod.getResponseBody(); // 处理返回内容 responseMsg = new String(responseBody); // 返回结果显示 // System.out.println("HTTP GET 方式执行结果:"+responseMsg); } catch (HttpException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 释放操作 getmethod.releaseConnection(); } return responseMsg; } public static String getHTTP_New(String URL) { String responseMsg = ""; HttpClient httpClient = new HttpClient(); // 创建GET方法的实例 GetMethod getMethod = new GetMethod(URL); // 此处可以在getMethod上添加请求参数 try { // 执行getMethod int statusCode = httpClient.executeMethod(getMethod); if (statusCode != HttpStatus.SC_OK) { System.err.println("Method failed: " + getMethod.getStatusLine()); } // 读取内容 byte[] responseBody = getMethod.getResponseBody(); // 处理内容 responseMsg = new String(responseBody); } catch (HttpException e) { // 发生致命的异常,可能是协议不对或者返回的内容有问题 System.out.println("Please check your provided http address!"); e.printStackTrace(); } catch (IOException e) { // 发生网络异常 e.printStackTrace(); } finally { // 释放连接 getMethod.releaseConnection(); } return responseMsg; } // HTTP 通过POST方式交互 /** * http post 方式交互 */ public static String postHTTP(String URL, String uid, String pwd, String tos, String content, String otime) { String ResultStrMsg = ""; // 1.构造HttpClient的实例 HttpClient httpClient = new HttpClient(); httpClient.getParams().setContentCharset("GB2312"); PostMethod method = new PostMethod(URL); // 把参数值放入到PostMethod对象中 // 方式一 NameValuePair[] dataparam = { new NameValuePair("id", uid), new NameValuePair("pwd", pwd), new NameValuePair("to", tos), new NameValuePair("content", content), new NameValuePair("time", otime) }; method.addParameters(dataparam); // 方式二 // method.addParameter("", ""); // method.addParameter("", ""); try { // 执行接口方法,调用接口方法 httpClient.executeMethod(method); // 读取返回的值ֵ ResultStrMsg = method.getResponseBodyAsString().trim(); // System.out.println("HTTP POST 方式执行结果:"+ResultStrMsg); } catch (HttpException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { method.releaseConnection(); } return ResultStrMsg; } public static String postHTTP_new(String URL, String uid, String pwd, String tos, String content, String otime) { String ResultStrMsg = ""; // 1.构造HttpClient的实例 HttpClient httpClient = new HttpClient(); httpClient.getParams().setContentCharset("GB2312"); PostMethod method = new PostMethod(URL); // 把参数值放入到PostMethod对象中 // 方式一 NameValuePair[] dataparam = { new NameValuePair("account", uid), new NameValuePair("pswd", pwd), new NameValuePair("mobile", tos), new NameValuePair("msg", content), new NameValuePair("needstatus", otime) }; method.addParameters(dataparam); // 方式二 // method.addParameter("", ""); // method.addParameter("", ""); try { // 执行接口方法,调用接口方法 httpClient.executeMethod(method); // 读取返回的值ֵ ResultStrMsg = method.getResponseBodyAsString().trim(); // System.out.println("HTTP POST 方式执行结果:"+ResultStrMsg); } catch (HttpException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { method.releaseConnection(); } return ResultStrMsg; } 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) { logger.error("Weixin server connection timed out."); } catch (Exception e) { logger.error("https request error:{}", e); } return jsonObject; } }
package com.hongwei.futures.util; import java.util.Map; import java.util.HashMap; import java.util.Formatter; import java.util.UUID; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.io.UnsupportedEncodingException; /** * 微信分享接口的SHA1签名 * @author Administrator * */ public class Sign { 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; try { 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("jsapi_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); } }
package com.hongwei.futures.util; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Random; import com.aliyun.openservices.ClientException; import com.aliyun.openservices.ServiceException; import com.aliyun.openservices.oss.OSSClient; import com.aliyun.openservices.oss.OSSErrorCode; import com.aliyun.openservices.oss.OSSException; import com.aliyun.openservices.oss.model.CannedAccessControlList; import com.aliyun.openservices.oss.model.ObjectMetadata; public class OSSUploadUtil { private static String[] types = new String[] { ".bmp", ".png", ".gif", ".jpeg", ".pjpeg", ".jpg" }; public static String imageFileUpload(byte[] fileByte, String user_avatar, String suffix) throws Exception { String fileType = ".jpg"; for (int i = 0; i < types.length; i++) { if (types[i].equalsIgnoreCase(user_avatar.substring(user_avatar.lastIndexOf(".")))) { if (types[i].endsWith(".gif")) fileType = ".gif"; if (types[i].endsWith(".png")) fileType = ".png"; } } String fileName = (System.currentTimeMillis() + (new Random(999999).nextLong())) + suffix + fileType; try { InputStream input = new ByteArrayInputStream(fileByte); String bucketName = "whkzdd"; // 使用默认的OSS服务器地址创建OSSClient对象。 OSSClient client = new OSSClient(Property.getProperty("OSS_ACCESS_ID"), Property.getProperty("OSS_ACCESS_KEY")); ensureBucket(client, bucketName); ObjectMetadata objectMeta = new ObjectMetadata(); objectMeta.setContentLength(fileByte.length); client.putObject(bucketName, fileName, input, objectMeta); String saveUrl = Property.getProperty("OSS_URL") + fileName; return saveUrl; } catch (Exception e) { e.printStackTrace(); return null; } } public static void ensureBucket(OSSClient client, String bucketName)throws OSSException, ClientException { try { // 创建bucket client.createBucket(bucketName); client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead); } catch (ServiceException e) { if (!OSSErrorCode.BUCKES_ALREADY_EXISTS.equals(e.getErrorCode())) { // 如果Bucket已经存在,则忽略 throw e; } } } }
5.后台action(请求使用微信接口的页面,我们需要的主要是这几个参数appId,timestamp,nonceStr,signature,newDate)
// 个人信息 @Action("userInfo") public String userInfo() { FuUser fuUser = (FuUser) this.getHttpServletRequest().getSession().getAttribute("fuUser"); this.getActionContext().put("fuUser", fuUser); try { WeiXinUtil.getToken(this.getHttpSession(), this.getHttpServletRequest(), this.getActionContext(), from, isappinstalled); } catch (Exception e) { e.printStackTrace(); } return SUCCESS; }
6.前台分享接口和上传图片接口
<div class="userTx"> <a href="javascript:void();" id="imgUpload"> <c:if test="${empty fuUser.userAvatar}"> <img src="../images_yqb/meTouX.png"/> </c:if> <c:if test="${!empty fuUser.userAvatar}"> <img src="${fuUser.userAvatar}"/> </c:if> </a> </div> <input id="serverId" type="hidden"/> <input id="appId" type="hidden" value="${appId}"/> <input id="timestamp" type="hidden" value="${timestamp}"/> <input id="nonceStr" type="hidden" value="${nonceStr}"/> <input id="signature" type="hidden" value="${signature}"/> </body> </html> <script src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <link href="${ctx}/js/uploadify-v2.1.4/uploadify.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="${ctx}/js/uploadify-v2.1.4/swfobject.js"></script> <script type="text/javascript" src="${ctx}/js/uploadify-v2.1.4/jquery.uploadify.v2.1.4.min.js"></script> <script language="javascript" type="text/javascript"> //通过config接口注入权限验证配置 var appId=$("#appId").val(); var timestamp=$("#timestamp").val(); var nonceStr=$("#nonceStr").val(); var signature=$("#signature").val(); wx.config({ debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: appId, // 必填,公众号的唯一标识 timestamp: timestamp, // 必填,生成签名的时间戳 nonceStr: nonceStr, // 必填,生成签名的随机串 signature: signature,// 必填,签名,见附录1 jsApiList: ['checkJsApi','onMenuShareTimeline','onMenuShareAppMessage','chooseImage', 'uploadImage'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //判断当前客户端版本是否支持指定JS接口 wx.checkJsApi({ jsApiList: ['chooseImage', 'uploadImage'], // 需要检测的JS接口列表,所有JS接口列表见附录2, success: function(res) { // 以键值对的形式返回,可用的api值true,不可用为false // 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"} if(!res["checkResult"]["chooseImage"]) { alert("当前客户端不支持上传图片"); } } }); //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: '10元抢iPhone6s猜股市涨跌', // 分享标题 desc: '', // 分享描述 link: 'https://www.hhr360.com/user_options/wqqIndex.htm', // 分享链接 imgUrl: 'https://www.hhr360.com/images_czt/wei.png', // 分享图标 success: function () { // 用户确认分享后执行的回调函数 }, cancel: function () { // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: '10元抢iPhone6s猜股市涨跌', // 分享标题 desc: '', // 分享描述 link: 'https://www.hhr360.com/user_options/wqqIndex.htm', // 分享链接 imgUrl: 'https://www.hhr360.com/images_czt/wei.png', // 分享图标 type: '', // 分享类型,music、video或link,不填默认为link dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空 success: function () { // 用户确认分享后执行的回调函数 }, cancel: function () { // 用户取消分享后执行的回调函数 } }); }); //图片div点击事件 $("#imgUpload").click(function() { wx.chooseImage({ count: 1, // 默认9 sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 success: function (res) { var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片 if(localIds == null || localIds == ""){ alert("请选择头像图片!"); return null; } $("#imgUpload").find("img").attr("src", localIds); $.post("${ctx}/wxyqb/setImageSrcId.htm", {"srcId": localIds.toString()}, function(d) {}); wx.uploadImage({ localId: localIds.toString(), // 需要上传的图片的本地ID,由chooseImage接口获得 isShowProgressTips: 1, // 默认为1,显示进度提示 success: function (res) { var serverId = res.serverId; // 返回图片的服务器端ID $("#serverId").val(serverId); $.post('${ctx}/wxyqb/saveUserAvatar.htm', {"serverId" : serverId, "userId" : ${fuUser.id}}, function(d){ if(d == 1){ alert("头像上传失败!") return false; } if(d == 2){ alert("设置成功!"); location.href = "${ctx}/wxyqb/userInfo.htm"; } }); }, fail: function (res) { alert(res); } }); } }); }); </script>
6.对图片处理的action
//把图片ID放到session @Action("setImageSrcId") public String setImageSrcId() { if (srcId != null && !"".equals(srcId)) { this.getHttpServletRequest().getSession().setAttribute("srcId", srcId); } return null; } // 保存用户头像 @Action("saveUserAvatar") public String saveUserAvatar() { try { if (serverId == null || "".equals(serverId)) { write("1"); // 上传图片失败 return null; } FuUser fuUser = fuUserService.get(userId); String userAvatar = WeiXinUtil.downloadPicAndUploadOSS(this.getHttpSession(), serverId); fuUser.setUserAvatar(userAvatar); fuUserService.save(fuUser); this.getHttpServletRequest().getSession().setAttribute("srcId", null); this.getHttpServletRequest().getSession().setAttribute("fuUser", fuUser);//刷新用户session,显示最新的图片 write("2"); } catch (Exception e) { e.printStackTrace(); } return null; }
微信JSSDK还有个最坑爹的地方就是,一旦那个页面用到了微信接口,那么你请求到那个页面的地址就不能用get方式传参,否则就调用不了它的接口。另外因为必须在绑定的域名下使用接口,所以不能在本地调式,也给开发带了很大的麻烦。
相关文章推荐
- 像微信的底部菜单栏被键盘挤上去
- 支付宝支付与微信支付的集成
- 转 -- 微信读书iOS性能优化
- 微信中使用支付宝支付
- Android 模仿微信长按录音功能
- 获取微信公众号一键关注链接
- 微信红包海量运营-----“海量之道2.0”(春节发红包如何保证服务器正常运行)
- C#实现微信开发
- 如何运营微信公众号?
- 微信敏感字
- PHP微信开发入门(三)
- 模拟微信登录
- 微信支付WxpayAPI_php_v3(二)支付功能开发
- 微信支付WxpayAPI_php_v3(一)sdk简介与错误修改
- 修改源码自定义SwipeRefreshLayout样式——高仿微信朋友圈下拉刷新
- 关于java中map存储多行的小程序(一键对多值)
- Android集成微信支付SDK
- 前端开发,怎样禁止微信内置浏览器的缓存?
- 微信的演进和未来
- 仿微信支付宝输入密码框