SpringBoot和微信二维码相关的部分

来源:互联网 发布:苹果mac原装壁纸下载 编辑:程序博客网 时间:2024/05/17 04:00

水平有限 部分代码还是参考了网上大神的代码 主要是为了纪念一下自己的码代码过程 不足之处忘多多包涵。


1.springboot项目就不多说了,然后配置一下server.port=80,因为微信公众平台的服务器配置要求支持80或43端口,然后映射一下自己的127.0.0.1:80一下,就可以在本地调试事件推送了。映射方法:http://ngrok.ciqiuwl.cn/ 这是一位前辈提供的服务器,这样很方便的能在本地进行各种debug,大大方便了开发;



2.微信公众号的服务器配置要验证token的代码如下:因为这是一个get方法,后面的事件推送则是post,所以随便新建个controller 再在里面新建2个方法一个处理get用于token验证 一个用于post负责事件推送;
get部分代码如下:@api以及@apiOperation是swagger2,有了它就可以不用写开发文档了,很棒的发明!再推荐一个好用的 http://www.fangbei.org/tool/message 微信调试器 能方便调试各种接口。


@Api(tags = "1")@RestController@RequestMapping(value = "/wx")public class WXController extends BaseController {Logger logger = LoggerFactory.getLogger(WXController.class);@ApiOperation("get用于微信配置服务器的验证")@RequestMapping(value = "security", method = RequestMethod.GET)public void doGet(HttpServletRequest request, HttpServletResponse response,@RequestParam(value = "signature", required = true) String signature,@RequestParam(value = "timestamp", required = true) String timestamp,@RequestParam(value = "nonce", required = true) String nonce,@RequestParam(value = "echostr", required = true) String echostr) {try {if (SignUtil.checkSignature(signature, timestamp, nonce)) {PrintWriter out = response.getWriter();out.print(echostr);out.close();} else {logger.info("这里存在非法请求!");}} catch (Exception e) {logger.error("" + e.getMessage());}


下面是验证签名的类,这是网上直接拷下来的,没有细看,然后微信平台测试账号那里填上你之前用ngrok映射的域名/wx/security    token填你在下面接口里面设置的 看能否设置成功 不成功的话看127.0.0.1/wx/security 这个你能否成功访问 可能需要加上你的项目名称什么的 反正我是可以的。

public class SignUtil {// 与接口配置信息中的 Token 要一致private static String token = "XXXX";//自己填写/** * 验证签名 * @param signature * @param timestamp * @param nonce * @return */public static boolean checkSignature(String signature, String timestamp, String nonce) {String[] arr = new String[] { token, timestamp, nonce }; // 将token、timestamp、nonce三个参数进行字典序排序Arrays.sort(arr);StringBuilder content = new StringBuilder();for (int i = 0; i < arr.length; i++) {content.append(arr[i]);}MessageDigest md = null;String tmpStr = null;try {md = MessageDigest.getInstance("SHA-1");// 将三个参数字符串拼接成一个字符串进行 sha1 加密 byte[]byte[] digest = md.digest(content.toString().getBytes());tmpStr = byteToStr(digest);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}content = null;// 将 sha1 加密后的字符串可与 signature 对比,标识该请求来源于微信return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;}/** * 将字节数组转换为十六进制字符串 * @param byteArray * @return */private static String byteToStr(byte[] byteArray) {String strDigest = "";for (int i = 0; i < byteArray.length; i++) {strDigest += byteToHexStr(byteArray[i]);}return strDigest;}/** * 将字节转换为十六进制字符串 * @param mByte * @return */private static String byteToHexStr(byte mByte) {char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };char[] tempArr = new char[2];tempArr[0] = Digit[(mByte >>> 4) & 0X0F];tempArr[1] = Digit[m```Byte & 0X0F];String s = new String(tempArr);return s;}}


服务器配置完就可以进行推送事件处理了。。。第一次关注,再次关注,发送各种消息都会触发里面的各种条件,可以在里面进行各种逻辑处理,另外注意返回的hml格式的响应中createTime是和其余的返回字段不一样,所以需要进行额外的处理,这部分的代码也参考了网上前辈的;


private String processRequest(HttpServletRequest request) {String respMessage = "";try {HttpSession session = request.getSession();// 默认返回的文本消息内容String respContent = "";// xml请求解析Map<String, String> requestMap = WeixinUtil.parseXml(request);// 发送方帐号(open_id)String fromUserName = requestMap.get("FromUserName");// 公众帐号String toUserName = requestMap.get("ToUserName");// 消息类型String msgType = requestMap.get("MsgType");// 回复文本消息TextMessage textMessage = new TextMessage();textMessage.setToUserName(fromUserName);// FromUserName 发送方帐号textMessage.setFromUserName(toUserName);// ToUserName 开发者微信号textMessage.setMsgType(WeixinUtil.RESP_MESSAGE_TYPE_TEXT);// MsgType-消息类型textMessage.setCreateTime(System.currentTimeMillis());// CreateTime-消息创建时间-(整型)// 文本消息if (msgType.equals(WeixinUtil.REQ_MESSAGE_TYPE_TEXT)) {// respContent = "您发送的是文本消息!";}// 图片消息else if (msgType.equals(WeixinUtil.REQ_MESSAGE_TYPE_IMAGE)) {// respContent = "您发送的是图片消息!";}// 地理位置消息else if (msgType.equals(WeixinUtil.REQ_MESSAGE_TYPE_LOCATION)) {// respContent = "您发送的是地理位置消息!";}// 链接消息else if (msgType.equals(WeixinUtil.REQ_MESSAGE_TYPE_LINK)) {// respContent = "您发送的是链接消息!";}// 音频消息else if (msgType.equals(WeixinUtil.REQ_MESSAGE_TYPE_VOICE)) {// respContent = "您发送的是音频消息!";}// 事件推送else if (msgType.equals(WeixinUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件类型String eventType = requestMap.get("Event");// 订阅if (eventType.equals(WeixinUtil.EVENT_TYPE_SUBSCRIBE)) {respContent = "感谢您的关注";String EventKey = requestMap.get("EventKey");if (EventKey != null) {// 说明是带参数的二维码进入的// 第一次关注前面有这个需要截一下String id = requestMap.get("EventKey").replace("qrscene_", "");
//这里写逻辑}} else if (eventType.equals(WeixinUtil.EVENT_TYPE_SCAN)) {// 二次订阅System.err.println("EventKey Is " + requestMap.get("EventKey"));String id = requestMap.get("EventKey");//这里写逻辑}// 取消订阅else if (eventType.equals(WeixinUtil.EVENT_TYPE_UNSUBSCRIBE)) {// TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息}// 自定义菜单点击事件else if (eventType.equals(WeixinUtil.EVENT_TYPE_CLICK)) {// TODO 自定义菜单权没有开放,暂不处理该类消息}}if (respContent != null && respContent != " " && respContent != "") {textMessage.setContent(respContent);respMessage = WeixinUtil.textMessageToXml(textMessage);}System.err.println(respMessage);} catch (Exception e) {e.printStackTrace();}return respMessage;}



下面的对xml的处理内容 解析微信传过来的xml内容 以及把自己的内容转换成xml文件传过去 以及一些基本常量和一些基本方法的配置(比如结合里面的获取二维码地址和ticket可以获取带参数的二维码 然后扫描就会触发post事件 会接收到传过去的secen_id 然后就可以对这个id进行惨无人道的操作)


public class WeixinUtil { private static String appID = "XXXX";//自己的appId和appSecret; private static String appsecret = "XXXX";/** 去获得二维码的地址 */public static final String qrcode_url = "https://mp.weixin.qq.com/cgi-bin/showqrcode";/** * 返回消息类型:文本 */public static final String RESP_MESSAGE_TYPE_TEXT = "text";/** * 返回消息类型:音乐 */public static final String RESP_MESSAGE_TYPE_MUSIC = "music";/** * 返回消息类型:图文 */public static final String RESP_MESSAGE_TYPE_NEWS = "news";/** * 请求消息类型:文本 */public static final String REQ_MESSAGE_TYPE_TEXT = "text";/** * 请求消息类型:图片 */public static final String REQ_MESSAGE_TYPE_IMAGE = "image";/** * 请求消息类型:链接 */public static final String REQ_MESSAGE_TYPE_LINK = "link";/** * 请求消息类型:地理位置 */public static final String REQ_MESSAGE_TYPE_LOCATION = "location";/** * 请求消息类型:音频 */public static final String REQ_MESSAGE_TYPE_VOICE = "voice";/** * 请求消息类型:推送 */public static final String REQ_MESSAGE_TYPE_EVENT = "event";/** * 事件类型:subscribe(订阅) */public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";/** * 事件类型:SCAN(二次订阅) */public static final String EVENT_TYPE_SCAN = "SCAN";/** * 事件类型:unsubscribe(取消订阅) */public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";/** * 事件类型:CLICK(自定义菜单点击事件) */public static final String EVENT_TYPE_CLICK = "CLICK";/** 获取微信的access_token **/public static String getAccessToken(HttpSession session) {String access_token = (String) session.getAttribute("access_token");if (access_token != null) {System.err.println("access_token是从sesssion里面取得");return access_token;}String aString = GetPostUtil.sendGet("https://api.weixin.qq.com/cgi-bin/token","grant_type=client_credential&appid=" + appID + "&secret=" + appsecret);JSONObject jsonObject = JSONObject.parseObject(aString); access_token = (String) jsonObject.get("access_token");session.setAttribute("access_token", access_token);return access_token;}/** * 获取生成永久二维码需要的ticket */public static String getTicket(HttpSession session, Integer scene_id) {String access_token = getAccessToken(session);JSONObject jsonObject = JSONObject.parseObject("{\"action_name\": \"QR_LIMIT_SCENE\", \"action_info\": {\"scene\": {\"scene_id\":" + scene_id + "}}}");String aString2 = GetPostUtil.post("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token, jsonObject);JSONObject jsonObject2 = JSONObject.parseObject(aString2);String ticket = (String) jsonObject2.get("ticket");return ticket;}/** * 解析微信发来的请求(XML) *  * @param request * @return * @throws Exception */public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {Map<String, String> map = new HashMap<String, String>();// 将解析结果存储在HashMap中InputStream inputStream = request.getInputStream(); // 从request中取得输入流SAXReader reader = new SAXReader();// 读取输入流Document document = reader.read(inputStream);//Document document=reader.read("C:\\Users\\1.xml");//从文档里面读取xmlElement root = document.getRootElement();// 得到xml根元素List<Element> elementList = root.elements();// 得到根元素的所有子节点for (Element e : elementList) {// 遍历所有子节点map.put(e.getName(), e.getText());}inputStream.close();// 释放资源inputStream = null;return map;}/** *根据access和OPenId获取用户基本信息  */public static JSONObject getUserInfoFromWX(HttpSession session,String openid){String url="https://api.weixin.qq.com/cgi-bin/user/info";String access_token=getAccessToken(session);String info=GetPostUtil.sendGet(url, "access_token="+access_token+"&openid="+openid+"&lang=zh_CN");JSONObject jsonObject=JSONObject.parseObject(info);return jsonObject;}/** * 文本消息对象转换成xml *  * @param textMessage 文本消息对象 * @return xml */public static String textMessageToXml(TextMessage textMessage) {XStream xstream=createXstream();xstream.alias("xml", textMessage.getClass());return xstream.toXML(textMessage);}///**// * 音乐消息对象转换成xml// * // * @param musicMessage 音乐消息对象// * @return xml// *///public static String musicMessageToXml(MusicMessage musicMessage) {//xstream.alias("xml", musicMessage.getClass());//return xstream.toXML(musicMessage);//}/////**// * 图文消息对象转换成xml// * // * @param newsMessage 图文消息对象// * @return xml// *///public static String newsMessageToXml(NewsMessage newsMessage) {//xstream.alias("xml", newsMessage.getClass());//xstream.alias("item", new Article().getClass());//return xstream.toXML(newsMessage);//}//private static XStream xstream = new XStream(new XppDriver(){////public HierarchicalStreamWriter createWriter(Writer out) {//return new PrettyPrintWriter(out) {//// 对所有xml节点的转换都增加CDATA标记//boolean cdata = true;////@SuppressWarnings("rawtypes")//public void startNode(String name, Class clazz) {//super.startNode(name, clazz);//}////protected void writeText(QuickWriter writer, String text) {//if (cdata) {//writer.write("<![CDATA[");//writer.write(text);//writer.write("]]>");//} else {//writer.write(text);//}//}//};//}//});public static XStream createXstream() {return new XStream(new XppDriver() {@Overridepublic HierarchicalStreamWriter createWriter(Writer out) {return new PrettyPrintWriter(out) {boolean cdata = false;Class<?> targetClass = null;@Overridepublic void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {super.startNode(name, clazz);// 业务处理,对于用XStreamCDATA标记的Field,需要加上CDATA标签if (!name.equals("xml")) {cdata = needCDATA(targetClass, name);} else {targetClass = clazz;}}@Overrideprotected void writeText(QuickWriter writer, String text) {if (cdata) {writer.write("<![CDATA[");writer.write(text);writer.write("]]>");} else {writer.write(text);}}};}});}private static boolean needCDATA(Class<?> targetClass, String fieldAlias) {boolean cdata = false;// first, scan selfcdata = existsCDATA(targetClass, fieldAlias);if (cdata)return cdata;// if cdata is false, scan supperClass until java.lang.ObjectClass<?> superClass = targetClass.getSuperclass();while (!superClass.equals(Object.class)) {cdata = existsCDATA(superClass, fieldAlias);if (cdata)return cdata;superClass = superClass.getClass().getSuperclass();}return false;}private static boolean existsCDATA(Class<?> clazz, String fieldAlias) {if ("MediaId".equals(fieldAlias)) {return true; // 特例添加 morning99}// scan fieldsField[] fields = clazz.getDeclaredFields();for (Field field : fields) {// 1. exists XStreamCDATAif (field.getAnnotation(XStreamCDATA.class) != null) {XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);// 2. exists XStreamAliasif (null != xStreamAlias) {if (fieldAlias.equals(xStreamAlias.value()))// matchedreturn true;} else {// not exists XStreamAliasif (fieldAlias.equals(field.getName()))return true;}}}return false;}public static void main(String[] args) throws Exception{Map<String, String> map = new HashMap<String, String>();map=parseXml(null);System.err.println(map.get("Event"));}}

还有一个关于注解的类和文本信息类,注解主要是为了有的字段返回去的xml不加上><![CDATA[]]>的标识,文本信息类用于返回文本信息;

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.FIELD })public @interface XStreamCDATA {}
import com.thoughtworks.xstream.annotations.XStreamAlias;public class TextMessage {// 接收方帐号(收到的OpenID)@XStreamAlias("ToUserName")private String ToUserName;// 开发者微信号@XStreamAlias("FromUserName")private String FromUserName;// 消息创建时间 (整型)private long CreateTime;// 消息类型(text/music/news)@XStreamAlias("MsgType")private String MsgType;// 位0x0001被标志时,星标刚收到的消息@XStreamAlias("Context")private String Content;public String getToUserName() {return ToUserName;}public void setToUserName(String toUserName) {ToUserName = toUserName;}public String getFromUserName() {return FromUserName;}public void setFromUserName(String fromUserName) {FromUserName = fromUserName;}public long getCreateTime() {return CreateTime;}public void setCreateTime(long createTime) {CreateTime = createTime;}public String getMsgType() {return MsgType;}public void setMsgType(String msgType) {MsgType = msgType;}public String getContent() {return Content;}public void setContent(String content) {Content = content;}}



顺便加上几个需要的注释(关于xml的),顺便吐槽一下:没有认证的话获取不到unionId,很坑,scene_str的话EventKey获取不到:因为有些代码是项目里面的逻辑调用,所以手动删了一点,希望不会报错哈哈。。。。无聊的话可以随便接个机器人来玩玩。。。。

<dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.0.1</version></dependency><dependency><groupId>com.thoughtworks.xstream</groupId><artifactId>xstream</artifactId><version>1.4.10</version></dependency>

原创粉丝点击