Grails框架实现微信自定义分享页
2016-09-27 14:06
381 查看
微信分享说明文档
官方实例
微信web开发者工具可以在pc端进行测试开发
下面我来说说具体来实现这个自定义分享
需要在官方申请一个公众号
在Grails 2.4.4中需要安装rest-client-builder 2.1.1 插件
下面由代码来讲解这个分享过程
WeixinpayConfig.groovy 配置微信相关参数信息
WinxinUtils.groovy 请求微信工具类
WeixinsignUtils.groovy 生成随机字符串和SHA1加密
MapUtil.groovy Map工具类
SHA1.groovy 把字符串进行SHA1加密
SharedController.groovy 这个就是请求分享的入口controller
下面的js代码是在分享页面增加的微信
shared.js 分享出去时配置相关自定义信息
这样就完成了,微信页面的分享了,其中包含了后台生成的分享配置信息,和页面的自定义的信息。
分享成功的效果图:
希望这个能够帮助到你。
官方实例
微信web开发者工具可以在pc端进行测试开发
下面我来说说具体来实现这个自定义分享
需要在官方申请一个公众号
在Grails 2.4.4中需要安装rest-client-builder 2.1.1 插件
下面由代码来讲解这个分享过程
WeixinpayConfig.groovy 配置微信相关参数信息
package com.weixin.config; /** * * @author lvbaolin * @date 2016年5月27日上午11:10:50 * @company 北京远中和科技 * * 配置微信参数 */ public class WeixinpayConfig { // 配置基本信息 //微信分享使用 //公众号appId public static String SHAREDAPPID = "*****"; //微信分享使用 //公众号secret public static String SHAREDSECRET = "****"; //获取access_token 路径 public static String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token"; //获得jsapi_ticket 路径 public static String TICKENT = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"; // 基本信息结束 }
WinxinUtils.groovy 请求微信工具类
package com.weixinpay.util import grails.plugins.rest.client.RestBuilder import org.springframework.http.HttpStatus import business.BusinessException import com.weixin.config.WeixinpayConfig class WinxinUtils { /** * 对微信发起get请求获取相关数据 * @param * @return */ private static getWeixinRequest(def path){ RestBuilder rest = new RestBuilder() def resp = rest.get(path) if(resp.statusCode == HttpStatus.OK) { //println resp.body return resp.json } else throw new Exception(resp.text) return null } }
WeixinsignUtils.groovy 生成随机字符串和SHA1加密
package com.weixin.sign; import java.util.Map; import com.weixin.config.WeixinpayConfig; import com.weixin.util.MapUtil; /** * * @author lvbaolin * @date 2016年5月27日上午11:45:29 * @company 北京远中和科技 * * */ public class WeixinsignUtils { /** * 随机字符串,不长于32位。推荐随机数生成算法 * * @return */ public static String createStr() { // TODO Auto-generated method stub String str = ""; char[] ch = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p','q','r','s','t','u','v','w','x','y','z'}; // int n = 1 + (int) (Math.random() * 25) ; int n = 20; for (int i = 0; i < n; i++) { int x = 1 + (int) (Math.random() * 33); str += ch[x]; } return str; } /** * create sign * * @param map * @return */ public static String createSHA1_Sign(Map<String, String> map) { String result = MapUtil.mapJoin(map, true, false); //System.out.println(result); try { result = SHA1.getSHA1HexString(result); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("sign create error"); } return result; } }
MapUtil.groovy Map工具类
package com.weixin.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * * @author lvbaolin * @date 2016年5月30日下午2:55:35 * @company 北京远中和科技 * */ public class MapUtil { public static Map<String,Object> toMap(Object object){ Map<String,Object> map = new HashMap<String, Object>(); Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { Object obj; try { obj = field.get(object); if(obj!=null){ map.put(field.getName(), obj); } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return map; } /** * Map key 排序(ASCII字典序排序) * @param map * @return */ public static Map<String,String> order(Map<String, String> map){ HashMap<String, String> tempMap = new LinkedHashMap<String, String>(); List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>( map.entrySet()); Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1,Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); for (int i = 0; i < infoIds.size(); i++) { Map.Entry<String, String> item = infoIds.get(i); tempMap.put(item.getKey(), item.getValue()); } return tempMap; } /** * 转换对象为map * @param object * @param ignore * @return */ public static Map<String,String> objectToMap(Object object,String... ignore){ Map<String,String> tempMap = new LinkedHashMap<String, String>(); for(Field f : object.getClass().getDeclaredFields()){ if(!f.isAccessible()){ f.setAccessible(true); } boolean ig = false; if(ignore!=null&&ignore.length>0){ for(String i : ignore){ if(i.equals(f.getName())){ ig = true; break; } } } if(ig){ continue; }else{ Object o = null; try { o = f.get(object); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } tempMap.put(f.getName(), o==null?"":o.toString()); } } return tempMap; } /** * url 参数串连 * @param map * @param keyLower * @param valueUrlencode * @return */ public static String mapJoin(Map<String, String> map,boolean keyLower,boolean valueUrlencode){ StringBuilder stringBuilder = new StringBuilder(); for(String key :map.keySet()){ if(map.get(key)!=null&&!"".equals(map.get(key))){ try { String temp = (key.endsWith("_")&&key.length()>1)?key.substring(0,key.length()-1):key; stringBuilder.append(keyLower?temp.toLowerCase():temp) .append("=") .append(valueUrlencode?URLEncoder.encode(map.get(key),"utf-8").replace("+", "%20"):map.get(key)) .append("&"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } if(stringBuilder.length()>0){ stringBuilder.deleteCharAt(stringBuilder.length()-1); } return stringBuilder.toString(); } /** * 简单 xml 转换为 Map * @param reader * @return */ public static Map<String,String> xmlToMap(String xml){ try { DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document document = documentBuilder.parse(new ByteArrayInputStream(xml.getBytes())); Element element = document.getDocumentElement(); NodeList nodeList = element.getChildNodes(); Map<String, String> map = new LinkedHashMap<String, String>(); for(int i=0;i<nodeList.getLength();i++){ Element e = (Element) nodeList.item(i); map.put(e.getNodeName(),e.getTextContent()); } return map; } catch (DOMException e) { e.printStackTrace(); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } }
SHA1.groovy 把字符串进行SHA1加密
package com.weixin.sign; import java.security.MessageDigest; public class SHA1 { public static String getSHA1HexString(String str) throws Exception { // SHA1签名生成 MessageDigest md = MessageDigest.getInstance("SHA-1"); md.update(str.getBytes()); byte[] digest = md.digest(); StringBuffer hexstr = new StringBuffer(); String shaHex = ""; for (int i = 0; i < digest.length; i++) { shaHex = Integer.toHexString(digest[i] & 0xFF); if (shaHex.length() < 2) { hexstr.append(0); } hexstr.append(shaHex); } return hexstr.toString(); } private final int[] abcde = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }; // 摘要数据存储数组 private int[] digestInt = new int[5]; // 计算过程中的临时数据存储数组 private int[] tmpData = new int[80]; // 计算sha-1摘要 private int process_input_bytes(byte[] bytedata) { // 初试化常量 System.arraycopy(abcde, 0, digestInt, 0, abcde.length); // 格式化输入字节数组,补10及长度数据 byte[] newbyte = byteArrayFormatData(bytedata); // 获取数据摘要计算的数据单元个数 int MCount = newbyte.length / 64; // 循环对每个数据单元进行摘要计算 for (int pos = 0; pos < MCount; pos++) { // 将每个单元的数据转换成16个整型数据,并保存到tmpData的前16个数组元素中 for (int j = 0; j < 16; j++) { tmpData[j] = byteArrayToInt(newbyte, (pos * 64) + (j * 4)); } // 摘要计算函数 encrypt(); } return 20; } // 格式化输入字节数组格式 private byte[] byteArrayFormatData(byte[] bytedata) { // 补0数量 int zeros = 0; // 补位后总位数 int size = 0; // 原始数据长度 int n = bytedata.length; // 模64后的剩余位数 int m = n % 64; // 计算添加0的个数以及添加10后的总长度 if (m < 56) { zeros = 55 - m; size = n - m + 64; } else if (m == 56) { zeros = 63; size = n + 8 + 64; } else { zeros = 63 - m + 56; size = (n + 64) - m + 64; } // 补位后生成的新数组内容 byte[] newbyte = new byte[size]; // 复制数组的前面部分 System.arraycopy(bytedata, 0, newbyte, 0, n); // 获得数组Append数据元素的位置 int l = n; // 补1操作 newbyte[l++] = (byte) 0x80; // 补0操作 for (int i = 0; i < zeros; i++) { newbyte[l++] = (byte) 0x00; } // 计算数据长度,补数据长度位共8字节,长整型 long N = (long) n * 8; byte h8 = (byte) (N & 0xFF); byte h7 = (byte) ((N >> 8) & 0xFF); byte h6 = (byte) ((N >> 16) & 0xFF); byte h5 = (byte) ((N >> 24) & 0xFF); byte h4 = (byte) ((N >> 32) & 0xFF); byte h3 = (byte) ((N >> 40) & 0xFF); byte h2 = (byte) ((N >> 48) & 0xFF); byte h1 = (byte) (N >> 56); newbyte[l++] = h1; newbyte[l++] = h2; newbyte[l++] = h3; newbyte[l++] = h4; newbyte[l++] = h5; newbyte[l++] = h6; newbyte[l++] = h7; newbyte[l++] = h8; return newbyte; } private int f1(int x, int y, int z) { return (x & y) | (~x & z); } private int f2(int x, int y, int z) { return x ^ y ^ z; } private int f3(int x, int y, int z) { return (x & y) | (x & z) | (y & z); } private int f4(int x, int y) { return (x << y) | x >>> (32 - y); } // 单元摘要计算函数 private void encrypt() { for (int i = 16; i <= 79; i++) { tmpData[i] = f4(tmpData[i - 3] ^ tmpData[i - 8] ^ tmpData[i - 14] ^ tmpData[i - 16], 1); } int[] tmpabcde = new int[5]; for (int i1 = 0; i1 < tmpabcde.length; i1++) { tmpabcde[i1] = digestInt[i1]; } for (int j = 0; j <= 19; j++) { int tmp = f4(tmpabcde[0], 5) + f1(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[j] + 0x5a827999; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1], 30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int k = 20; k <= 39; k++) { int tmp = f4(tmpabcde[0], 5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[k] + 0x6ed9eba1; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1], 30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int l = 40; l <= 59; l++) { int tmp = f4(tmpabcde[0], 5) + f3(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[l] + 0x8f1bbcdc; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1], 30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int m = 60; m <= 79; m++) { int tmp = f4(tmpabcde[0], 5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[m] + 0xca62c1d6; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1], 30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int i2 = 0; i2 < tmpabcde.length; i2++) { digestInt[i2] = digestInt[i2] + tmpabcde[i2]; } for (int n = 0; n < tmpData.length; n++) { tmpData = 0; } } // 4字节数组转换为整数 private int byteArrayToInt(byte[] bytedata, int i) { return ((bytedata[i] & 0xff) << 24) | ((bytedata[i + 1] & 0xff) << 16) | ((bytedata[i + 2] & 0xff) << 8) | (bytedata[i + 3] & 0xff); } // 整数转换为4字节数组 private void intToByteArray(int intValue, byte[] byteData, int i) { byteData[i] = (byte) (intValue >>> 24); byteData[i + 1] = (byte) (intValue >>> 16); byteData[i + 2] = (byte) (intValue >>> 8); byteData[i + 3] = (byte) intValue; } // 将字节转换为十六进制字符串 private static String byteToHexString(byte ib) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] ob = new char[2]; ob[0] = Digit[(ib >>> 4) & 0X0F]; ob[1] = Digit[ib & 0X0F]; String s = new String(ob); return s; } // 将字节数组转换为十六进制字符串 private static String byteArrayToHexString(byte[] bytearray) { String strDigest = ""; for (int i = 0; i < bytearray.length; i++) { strDigest += byteToHexString(bytearray[i]); } return strDigest; } // 计算sha-1摘要,返回相应的字节数组 public byte[] getDigestOfBytes(byte[] byteData) { process_input_bytes(byteData); byte[] digest = new byte[20]; for (int i = 0; i < digestInt.length; i++) { intToByteArray(digestInt[i], digest, i * 4); } return digest; } // 计算sha-1摘要,返回相应的十六进制字符串 public String getDigestOfString(byte[] byteData) { return byteArrayToHexString(getDigestOfBytes(byteData)); } /**/ public static void main(String[] args) { String data = "123456"; System.out.println(data); String digest = new SHA1().getDigestOfString(data.getBytes()); System.out.println(digest); // System.out.println( ToMD5.convertSHA1(data).toUpperCase()); } }
SharedController.groovy 这个就是请求分享的入口controller
package business import grails.converters.JSON import com.weixin.config.WeixinpayConfig import com.weixin.sign.WeixinsignUtils import com.weixin.util.MapUtil import com.weixinpay.util.WinxinUtils class TopicController { def grailsApplication def show(){ def result = [:] //分享出去的数据 result.data = [data:data] //获取access_token 路径 def access_token_path = "${WeixinpayConfig.ACCESS_TOKEN }?appid=${WeixinpayConfig.SHAREDAPPID}&grant_type=client_credential&secret=${WeixinpayConfig.SHAREDSECRET }" def token_data = WinxinUtils.getWeixinRequest(access_token_path) //print "token_data=$token_data" //获取jsapi_ticket 路径 def ticket_path = "${WeixinpayConfig.TICKENT }?access_token=${token_data.access_token }&type=jsapi" def ticket_data = WinxinUtils.getWeixinRequest(ticket_path) //print "ticket_data=$ticket_data" // def requestMap = [:] //有效的jsapi_ticket requestMap.jsapi_ticket = ticket_data.ticket //随机字符串 requestMap.nonceStr = WeixinsignUtils.createStr() //timestamp生成签名的时间戳 requestMap.timestamp = new Date().time + "" //当前网页的url requestMap.url = grailsApplication.config.topic.shareUrl+"/topic/show?tid="+params.tid //请求map进行排序 Map<String, String> map = MapUtil.order(requestMap) //生成签名 requestMap.signature = WeixinsignUtils.createSHA1_Sign(map).toLowerCase() //必填,公众号的唯一标识 requestMap.appId=WeixinpayConfig.SHAREDAPPID //print requestMap result.weixin = requestMap //render result as JSON [result: result] }
下面的js代码是在分享页面增加的微信
<script> /* * 注意: * 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 * 2. 如果发现在 Android 不能分享自定义内容,请到官网下载最新的包覆盖安装,Android 自定义分享接口需升级至 6.0.2.58 版本及以上。 * 3. 常见问题及完整 JS-SDK 文档地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html * * 开发中遇到问题详见文档“附录5-常见错误及解决办法”解决,如仍未能解决可通过以下渠道反馈: * 邮箱地址:weixin-open@qq.com * 邮件主题:【微信JS-SDK反馈】具体问题 * 邮件内容说明:用简明的语言描述问题所在,并交代清楚遇到该问题的场景,可附上截屏图片,微信团队会尽快处理你的反馈。 */ wx.config({ debug: false, appId: '${result?.weixin?.appId}', timestamp: ${result?.weixin?.timestamp}, nonceStr: '${result?.weixin?.nonceStr}', signature: '${result?.weixin?.signature}', jsApiList: [ 'checkJsApi', 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone' ] }); </script>
shared.js 分享出去时配置相关自定义信息
/* * 注意: * 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 * 2. 如果发现在 Android 不能分享自定义内容,请到官网下载最新的包覆盖安装,Android 自定义分享接口需升级至 6.0.2.58 版本及以上。 * 3. 完整 JS-SDK 文档地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html * * 如有问题请通过以下渠道反馈: * 邮箱地址:weixin-open@qq.com * 邮件主题:【微信JS-SDK反馈】具体问题 * 邮件内容说明:用简明的语言描述问题所在,并交代清楚遇到该问题的场景,可附上截屏图片,微信团队会尽快处理你的反馈。 */ function divHide(){ $(".yes_box").hide(); } function sharedSuccess(){ $(".yes_box").show(); window.setTimeout("divHide()",1000);//使用字符串执行方法 } //sharedSuccess(); wx.ready(function() { // 2. 分享接口 // 2.1 自定义分享内容及分享结果接口 wx.onMenuShareAppMessage({ title : $("#title").val(), // 分享标题 desc : $("#desc").val(), // 分享描述 link : $("#link").val(), // 分享链接 imgUrl : $("#imgUrl").val(), // 分享图标 // type: '', // 分享类型,music、video或link,不填默认为link // dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空 trigger : function(res) { //alert('用户点击分享给朋友'); }, complete : function(res) { //alert(JSON.stringify(res)); }, success : function(res) { sharedSuccess(); //alert('已分享'); }, cancel : function(res) { //alert('已取消'); }, fail : function(res) { alert(JSON.stringify(res)); } }); // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口 wx.onMenuShareTimeline({ title: $("#title").val(), link: $("#link").val(), imgUrl: $("#imgUrl").val(), trigger: function (res) { // 不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回 //alert('用户点击分享到朋友圈'); }, success: function (res) { sharedSuccess(); //alert('已分享'); }, cancel: function (res) { //alert('已取消'); }, fail: function (res) { alert(JSON.stringify(res)); } }); // 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口 wx.onMenuShareQQ({ title: $("#title").val(), desc: $("#desc").val(), link: $("#link").val(), imgUrl: $("#imgUrl").val(), trigger: function (res) { //alert('用户点击分享到QQ'); }, complete: function (res) { //alert(JSON.stringify(res)); }, success: function (res) { sharedSuccess(); //alert('已分享'); }, cancel: function (res) { //alert('已取消'); }, fail: function (res) { alert(JSON.stringify(res)); } }); // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口 wx.onMenuShareWeibo({ title: $("#title").val(), desc: $("#desc").val(), link: $("#link").val(), imgUrl: $("#imgUrl").val(), trigger: function (res) { //alert('用户点击分享到微博'); }, complete: function (res) { //alert(JSON.stringify(res)); }, success: function (res) { sharedSuccess(); //alert('已分享'); }, cancel: function (res) { //alert('已取消'); }, fail: function (res) { alert(JSON.stringify(res)); } }); // 2.5 监听“分享到QZone”按钮点击、自定义分享内容及分享接口 wx.onMenuShareQZone({ title: $("#title").val(), desc: $("#desc").val(), link: $("#link").val(), imgUrl: $("#imgUrl").val(), trigger: function (res) { //alert('用户点击分享到QZone'); }, complete: function (res) { //alert(JSON.stringify(res)); }, success: function (res) { sharedSuccess(); //alert('已分享'); }, cancel: function (res) { //alert('已取消'); }, fail: function (res) { alert(JSON.stringify(res)); } }); }); wx.error(function(res) { // alert("验证失败。"); alert(res.errMsg); });
这样就完成了,微信页面的分享了,其中包含了后台生成的分享配置信息,和页面的自定义的信息。
分享成功的效果图:
希望这个能够帮助到你。
相关文章推荐
- 实现微信JS-SDK分享自定义标题和图片
- 微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈
- tp3.2结合mysql实现微信自定义分享链接和图文
- 外贸多语言网站SEO设计,如何实现微信自定义分享?
- PHP自定义实现微信分享朋友圈源码演示下载
- kphp框架中实现自定义session会话处理方法的php和msyql代码分享
- 关于微信分享到朋友圈(Thinkphp框架下实现)
- JAVA WEB项目在微信浏览器下实现自定义分享源码示例
- 在Unity3D项目中接入ShareSDK实现安卓平台微信分享功能(可使用ShareSDK默认UI或自定义UI)
- 自定义Dialog对话框(实现微信分享对话框)
- 微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈
- jssdk实现自定义分享到微信好友和微信朋友圈
- 微信JS-SDK实现自定义分享功能,分享给朋友,分享到QQ,分享到微博
- 如何实现微信自定义分享标题、图片、描述等信息
- 整合Spring MVC,mybatis,hibernate,freemarker框架实现的自定义注解Validator验证机制实现对敏感词过滤的代码分享
- 整合Spring MVC,mybatis,hibernate,freemarker框架实现的自定义注解Validator验证机制实现对敏感词过滤的代码分享
- 微信分享自定义内容实现
- 基于thinkPHP实现的微信自定义分享功能示例
- 实现微信自定义分享网页(java)
- 微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈