java使用struts2框架开发微信服务号

一、前言

此文章仅代表个人的见解

二、微信接入

1、登录微信公众平台官网后,在公众平台后台管理页面 -开发者中心页,点击“修改配置”按钮,填写服务器地址(URL)、TokenEncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URLToken可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。

同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。模式的选择与服务器配置在提交后都会立即生效,请开发者谨慎填写及选择。加解密方式的默认状态为明文模式,选择兼容模式和安全模式需要提前配置好相关加解密代码,详情请参考消息体签名及加解密部分的文档

2、开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带四个参数:signature(微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数),timestamp(时间戳),nonce(随机数),echostr(随机字符串)。

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。

    加密/校验流程如下:

    1. tokentimestampnonce三个参数进行字典序排序

    2. 将三个参数字符串拼接成一个字符串进行sha1加密

    3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

参考api地址http://mp.weixin.qq.com/wiki/17/2d4265491f12608cd170a95559800f2d.html

3、根据api第三步:依据接口文档实现业务逻辑

验证URL有效性成功后即接入生效,成为开发者。如果公众号类型为服务号(订阅号只能使用普通消息接口),可以在公众平台网站中申请认证,认证成功的服务号将获得众多接口权限,以满足开发者需求。

当你前面两部都搞定了的话,第三步就容易许多,基本根据api接口来就行了

三、进入代码开发

1、在上述的3步中,最为开始的疑问想必就是第二步了

当你在修改配置跟填写你自定义的token后,你所提交的地址必须就已经存在,并且能够接收微信推送过去的数据,同时进行验证,通过检验signature对请求进行校验,若校验成功则原样返回echostr,否则连提交保存都无法成功。

那么具体流程是这样子的,当你提交的时候,微信就发送了4个字段到你填写的url

下面是我接收这四个字段的代码,由于微信二次开发在网上对struts的框架介绍比较少,故此处就介绍struts2跟微信对接,而不使用servletservlet虽然是个好东西,笔者比较少用,其实struts2也是基于servlet的,所以基本都看得懂。简便写上接收代码,假设下面代码是填写url访问后的处理逻辑

  //打印接收的四个字段

 System.out.println("signature:" + request.getParameter("signature"));
 System.out.println("timestamp:" + request.getParameter("timestamp"));
 System.out.println("nonce:" + request.getParameter("nonce"));
 System.out.println("echostr:" + request.getParameter("echostr"));

// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败

  if (SignUtil.checkSignature(signature,timestamp,nonce)) {

         response.setContentType("text/html;charset=UTF-8");

         response.getWriter().write(echostr);

  }

 return null;

SignUtil工具类如下,主要改了token,与公众平台上填写的要一致

package cn.com.aguang.weixin.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 请求校验工具类
 * 
 * @author aguang
 * @Email [email protected]
 * 
 */
public class SignUtil {
	// 与接口配置信息中的Token要一致
	private static String token = "aguang";

	/**
	 * 验证签名
	 * 
	 * @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三个参数进行字典序排序
		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[] 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[mByte & 0X0F];

		String s = new String(tempArr);
		return s;
	}

	public static void sort(String a[]) {
		for (int i = 0; i < a.length - 1; i++) {
			for (int j = i + 1; j < a.length; j++) {
				if (a[j].compareTo(a[i]) < 0) {
					String temp = a[i];
					a[i] = a[j];
					a[j] = temp;
				}
			}
		}
	}
}
2、上述搭建后便可以进行提交了,如果保存成功,那么恭喜你,现在你可以进行第三步接口的开发了。

     废话我就不多说了直接上代码,大家可以根据api文档,还有我以下附上的几个封装好的处理工具,以及对接的代码和测试类代码,基本根据参考这几个,也可以进行微信接口开发了,我推荐大家先自己摸索一下,如果有什么地方卡住了可以下面的代码,如果有什么不规范的地方或者写得不好的地方,可以留言说一下,下面的例子只针对几个接口做开发而已,具体后面的功能再加上。不对的地方请指出

3、笔者填写的地址处理类(框架s2sh,大家可以根据各自不同情况进行修改)

package cn.com.aguang.weixin.action.weixin;

import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.struts2.ServletActionContext;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

import cn.com.aguang.weixin.bussiness.WeixinOpenIdBus;
import cn.com.aguang.weixin.bussiness.WeixinUtilBus;
import cn.com.aguang.weixin.dao.WeixinOpenIdDAO;
import cn.com.aguang.weixin.po.AgWeixinOpenId;
import cn.com.aguang.weixin.utils.HttpUtils;
import cn.com.aguang.weixin.utils.SignUtil;
import cn.com.aguang.weixin.utils.WeiXinContants;

import com.opensymphony.xwork2.ActionSupport;

@Controller
@Scope("prototype")
public class AguangWeiXinSoftAction extends ActionSupport {

	// 微信opendId bus 类
	private WeixinOpenIdBus weixinOpenIdBus;

	// 为了维护行更高,觉得抽取业务逻辑的工具类,将业务一块一块分开
	private WeixinUtilBus weixinUtilBus;

	@Resource
	public void setWeixinUtilBus(WeixinUtilBus weixinUtilBus) {
		this.weixinUtilBus = weixinUtilBus;
	}

	@Resource
	public void setWeixinOpenIdBus(WeixinOpenIdBus weixinOpenIdBus) {
		this.weixinOpenIdBus = weixinOpenIdBus;
	}

	// 微信对接参数
	private String signature;

	// 微信对接参数
	private String timestamp;

	// 微信对接参数
	private String nonce;

	// 微信对接参数
	private String echostr;

	public String getSignature() {
		return signature;
	}

	public void setSignature(String signature) {
		this.signature = signature;
	}

	public String getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(String timestamp) {
		this.timestamp = timestamp;
	}

	public String getNonce() {
		return nonce;
	}

	public void setNonce(String nonce) {
		this.nonce = nonce;
	}

	public String getEchostr() {
		return echostr;
	}

	public void setEchostr(String echostr) {
		this.echostr = echostr;
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 3674789318078769142L;

	/**
	 * 

* 重大发现,经过api多个接口的测试研究,发现很多接口都通过这里来认证,并且对接 *

* * 微信接入,token暗号对接,对应公众号微信平台管理开发填写的地址 */ @Override public String execute() throws Exception { HttpServletResponse response = ServletActionContext.getResponse(); // JSONArray array = new JSONArray(); HttpServletRequest request = ServletActionContext.getRequest(); // 验证消息的真实性接收的参数,即填写url时点击保存提交的时候,能接收到下面4个参数 // System.out.println("signature:" + request.getParameter("signature")); // System.out.println("timestamp:" + request.getParameter("timestamp")); // System.out.println("nonce:" + request.getParameter("nonce")); // System.out.println("echostr:" + request.getParameter("echostr")); // 获取腾讯推送的数据流 经测试成功 InputStream is = request.getInputStream(); // 获取io流数据,如果是验证消息真实性即token,则不会有io流数据,否则一般为微信推送数据给我们,即有人关注了我们的公众号或者发送信息等推送事件 String pushData = this.weixinUtilBus.getInfoFromIoStream(is); if (pushData != null && !"".equals(pushData)) { Document document = DocumentHelper.parseText(pushData); Element root = document.getRootElement(); String msgType = root.element("MsgType").getText(); if ("event".equals(msgType)) { // 事件 click、unSubscribe、subscribe String event = root.element("Event").getText(); if ("click".equals(event.toLowerCase())) { // 点击按钮事件 String eventKey = root.element("EventKey").getText(); if ("aGuangSoftTest".equals(eventKey)) { // 点击者openId String fromUserName = root.element("FromUserName") .getText(); // 接收的时间 int createTime = Integer.parseInt(root .element("CreateTime").getText().toString()); // 回复内容 String content = WeiXinContants.AGUANG_SOFT_TEST_CLICK_CONTENT; // 生成微信接收格式的XML字符串 String replyAguangSoftTestContentXMLStr = this.weixinUtilBus .getReplyConentXmlStr(content, fromUserName, createTime); // 响应回去,注意是以text/xml的形式返回 response.setContentType("text/xml;charset=UTF-8"); response.getWriter().write( replyAguangSoftTestContentXMLStr); } } else if ("unsubscribe".equals(event.toLowerCase())) { // 取消关注事件 // 取消者openId String fromUserName = root.element("FromUserName") .getText(); // 将该记录查出,然后删除 List list = this.weixinOpenIdBus .findByProperty(WeixinOpenIdDAO.OPEN_ID, fromUserName); if (list != null && list.size() > 0) { this.weixinOpenIdBus.delete(list.get(0).getId()); } } else if ("subscribe".equals(event.toLowerCase())) { // 关注事件 // 关注者openId String fromUserName = root.element("FromUserName") .getText(); AgWeixinOpenId woi = new AgWeixinOpenId(); woi.setCreateTime(new Date()); woi.setOpenId(fromUserName); this.weixinOpenIdBus.save(woi); } else if ("scancode_waitmsg".equals(event)) { String scanResult = root.element("ScanCodeInfo") .element("ScanResult").getText(); int createTime = Integer.parseInt(root .element("CreateTime").getText()); // 点击扫描者 String fromUserName = root.element("FromUserName") .getText(); // 生成XML字符串数据 String scancodeResultXMLStr = this.weixinUtilBus .scancodeResultXMLStr(scanResult, fromUserName, createTime); // 响应回去 response.setContentType("text/xml;charset=UTF-8"); response.getWriter().write(scancodeResultXMLStr); } } else if ("text".equals(msgType)) { // 接收普通消息信息内容 // 发送消息者openId String fromUserName = root.element("FromUserName").getText(); // 接收的时间 int createTime = Integer.parseInt(root.element("CreateTime") .getText().toString()); // 接收的消息 String content = root.element("Content").getText(); Element weather = this.getWeatherInfoByCity(content); if (weather == null) { System.out.println("输入城市名可以查询当天的天气预报情况,如:广州"); // 生成微信接收格式的XML字符串 String replyAguangSoftTestContentXMLStr = this.weixinUtilBus .getReplyConentXmlStr("输入城市名可以查询当天的天气预报情况,如:广州", fromUserName, createTime); // 响应回去,注意是以text/xml的形式返回 response.setContentType("text/xml;charset=UTF-8"); response.getWriter() .write(replyAguangSoftTestContentXMLStr); } else { // 回复内容 String weatherInfo = "您输入的城市为:" + weather.element("city").getText() + ",天气:" + weather.element("status1").getText() + "->" + weather.element("status2").getText() + "。风向:" + weather.element("direction1").getText() + "。紫外线指数:" + weather.element("pollution").getText() + "。温度:" + weather.element("ssd_l").getText() + "。注意事项:" + weather.element("gm_s").getText() + "。" + weather.element("yd_s").getText() + "。" + weather.element("xcz_s").getText() + "。" + weather.element("pollution_s").getText(); System.out.println(weatherInfo); // 生成微信接收格式的XML字符串 String replyAguangSoftTestContentXMLStr = this.weixinUtilBus .getReplyConentXmlStr(weatherInfo, fromUserName, createTime); // 响应回去,注意是以text/xml的形式返回 response.setContentType("text/xml;charset=UTF-8"); response.getWriter() .write(replyAguangSoftTestContentXMLStr); } } } is.close(); // 如果这3个参数有为空的,即不是验证消息的真实性,那么就返回空,因为是处理上面的其他时间,下面的验证无需执行 if (signature == null || timestamp == null || nonce == null) { return null; } // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败 if (SignUtil.checkSignature(signature, timestamp, nonce)) { response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(echostr); } return null; } /** * 获取最新accessToken * * @return String */ public String getAccessToken() { String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=" + WeiXinContants.GRANT_TYPE + "&appid=" + WeiXinContants.APP_ID + "&secret=" + WeiXinContants.APP_SECRET; JSONObject json = JSONObject.fromObject(HttpUtils.getInstance() .getJsonObjectByUrl(apiUrl)); String access_token = json.get("access_token") + ""; System.out.println("access_token:" + access_token); return access_token; } /** * 获取微信服务ip 列表 */ public String getWeiXinServiceIp() { String apiUrl = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=" + WeiXinContants.ACCESS_TOKEN; JSONObject json = JSONObject.fromObject(HttpUtils.getInstance() .getJsonObjectByUrl(apiUrl)); String ipStr = json.get("ip_list") + ""; System.out.println(ipStr); return ipStr; } /** * 创建菜单 */ public String createMenu() throws Exception { String apiUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + new AguangWeiXinSoftAction().getAccessToken(); // 推送给微信服务器的json数据 JSONObject data = new JSONObject(); // 按钮json数组数据 JSONArray buttonJsonArray = new JSONArray(); // 菜单按钮1 JSONObject button1 = new JSONObject(); button1.put("type", "click"); button1.put("name", "普通按钮"); button1.put("key", "aGuangSoftTest"); // 添加按钮1 buttonJsonArray.add(button1); // 菜单按钮2 JSONObject button2 = new JSONObject(); button2.put("name", "多级菜单"); JSONArray sub_button2 = new JSONArray(); // 菜单按钮2中的子按钮1 JSONObject button2Sub1 = new JSONObject(); // view表示点击后跳转链接 button2Sub1.put("type", "view"); button2Sub1.put("name", "谁的服务"); button2Sub1.put("url", "http://www.aguang.cc/weixin/"); // 菜单按钮2中的子按钮2 JSONObject button2Sub2 = new JSONObject(); // view表示点击后跳转链接 button2Sub2.put("type", "view"); button2Sub2.put("name", "我的信息"); button2Sub2.put("url", "http://www.aguang.cc/weixin/wx/"); // 菜单按钮2中的子按钮3 JSONObject button2Sub3 = new JSONObject(); // 表示点击后跳转链接 button2Sub3.put("type", "view"); button2Sub3.put("name", "赞一个"); button2Sub3.put("url", "http://www.aguang.cc/weixin/wx/"); sub_button2.add(button2Sub1); sub_button2.add(button2Sub2); sub_button2.add(button2Sub3); // 将子菜单绑定到菜单2按钮中 button2.put("sub_button", sub_button2.toString()); // 添加菜单按钮2 buttonJsonArray.add(button2); // 菜单按钮3 JSONObject button3 = new JSONObject(); button3.put("name", "最新按钮"); // 定义菜单按钮3的子按钮json数组 JSONArray sub_button3 = new JSONArray(); // 菜单按钮3中的子按钮1 JSONObject button3Sub1 = new JSONObject(); button3Sub1.put("type", "scancode_waitmsg"); button3Sub1.put("name", "扫码带提示"); button3Sub1.put("key", "searchCode"); button3Sub1.put("sub_button", "[]"); // 将子按钮绑定到菜单按钮3中 sub_button3.add(button3Sub1.toString()); // 菜单按钮3中的子按钮2 JSONObject button3Sub2 = new JSONObject(); button3Sub2.put("type", "scancode_push"); button3Sub2.put("name", "扫码推事件"); button3Sub2.put("key", "searchCodePushEvent"); button3Sub2.put("sub_button", "[]"); sub_button3.add(button3Sub2.toString()); button3.put("sub_button", sub_button3.toString()); buttonJsonArray.add(button3); // 最终的按钮自定义菜单数据 data.put("button", buttonJsonArray.toString()); try { System.out.println(data.toString()); HttpUtils.getInstance().createPostHttpRequest(apiUrl, data.toString()); } catch (Exception e) { e.printStackTrace(); } return null; } /** * * 微信菜单2的多级菜单1 * * @return String */ public String menu2Button1() { return SUCCESS; } /** * * 微信菜单2的多级菜单2 * * @return String */ public String menu2Button2() { return SUCCESS; } /** * * 微信菜单2的多级菜单1 * * @return String */ public String menu2Button3() { return SUCCESS; } /** * 根据openId获取用户信息 * * @return String */ public String getUserInfoByOpenId() { String openId = ""; String apiUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + getAccessToken() + "&openid=" + openId + "&lang=zh_CN"; try { HttpUtils.getInstance().createPostHttpRequest(apiUrl, ""); } catch (Exception e) { e.printStackTrace(); } return SUCCESS; } /** * * 根据城市名获取该城市的天气预报 * * @return String */ private Element getWeatherInfoByCity(String city) { try { String apiUrl = "http://php.weather.sina.com.cn/xml.php?city=" + URLEncoder.encode(city, "gbk") + "&password=DJOYnieT8234jlsK&day=0"; String data = HttpUtils.getInstance().getXmlDataByUrl(apiUrl); System.out.println(data); Document document = DocumentHelper.parseText(data); Element root = document.getRootElement(); // 事件 click、unSubscribe、subscribe Element weather = root.element("Weather"); if (weather == null) { return null; } else { return weather; } } catch (Exception e) { // e.printStackTrace(); System.out.println("转码失败"); return null; } } }

4、action的测试类(自定义菜单主要是在测试类运行)

package cn.com.aguang.weixin.action.weixin;

import java.net.URLEncoder;
import java.util.Date;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.com.aguang.commons.business.BusinessException;
import cn.com.aguang.weixin.bussiness.WeixinOpenIdBus;
import cn.com.aguang.weixin.po.AgWeixinOpenId;
import cn.com.aguang.weixin.utils.HttpUtils;
import cn.com.aguang.weixin.utils.WeiXinContants;

import com.opensymphony.xwork2.interceptor.annotations.After;
import com.opensymphony.xwork2.interceptor.annotations.Before;

public class AguangWeiXinSoftTest {

	private static WeixinOpenIdBus weixinOpenIdBus;

	/**
	 * @throws java.lang.Exception
	 */
	@BeforeClass
	public static void setUpBeforeClass() {
		ApplicationContext ctx = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		weixinOpenIdBus = (WeixinOpenIdBus) ctx.getBean("weixinOpenIdBus");
	}

	/**
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
		System.out.println("---------------Begin");
	}

	/**
	 * @throws java.lang.Exception
	 */
	@After
	public void tearDown() throws Exception {
		System.out.println("---------------End");
	}

	/**
	 * 更新access_token
	 */
	public void updateAccessToken() {
		String access_token = getAccessToken();
		System.out.println("更新之前的access_token:" + WeiXinContants.ACCESS_TOKEN);
		if (access_token != null && "".equals(access_token)) {
			WeiXinContants.setAccessToken(access_token);
		}
		System.out.println("更新之后的access_token:" + access_token);
	}

	/**
	 * 获取微信服务ip 列表
	 */
	public void getWeiXinServiceIp() {
		String apiUrl = "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token="
				+ WeiXinContants.ACCESS_TOKEN;

		JSONObject json = JSONObject.fromObject(HttpUtils.getInstance()
				.getJsonObjectByUrl(apiUrl));
		String ipStr = json.get("ip_list") + "";
		System.out.println(ipStr);
	}

	/**
	 * 创建自定义菜单
	 */
	public void createMenu() {
		String apiUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token="
				+ getAccessToken();
		// 推送给微信服务器的json数据
		JSONObject data = new JSONObject();

		// 按钮json数组数据
		JSONArray buttonJsonArray = new JSONArray();
		// 菜单按钮1
		JSONObject button1 = new JSONObject();
		button1.put("type", "click");
		button1.put("name", "普通按钮");
		button1.put("key", "aGuangSoftTest");
		// 添加按钮1
		buttonJsonArray.add(button1);

		// 菜单按钮2
		JSONObject button2 = new JSONObject();
		button2.put("name", "多级菜单");
		JSONArray sub_button2 = new JSONArray();
		// 菜单按钮2中的子按钮1
		JSONObject button2Sub1 = new JSONObject();
		// view表示点击后跳转链接
		button2Sub1.put("type", "view");
		button2Sub1.put("name", "谁的服务");
		button2Sub1.put("url",
				"http://www.aguang.cc/weixin/wx/menu2Button1.action");

		// 菜单按钮2中的子按钮2
		JSONObject button2Sub2 = new JSONObject();
		// view表示点击后跳转链接
		button2Sub2.put("type", "view");
		button2Sub2.put("name", "我的信息");
		button2Sub2.put("url",
				"http://www.aguang.cc/weixin/wx/menu2Button2.action");

		// 菜单按钮2中的子按钮3
		JSONObject button2Sub3 = new JSONObject();
		// 表示点击后跳转链接
		button2Sub3.put("type", "view");
		button2Sub3.put("name", "赞一个");
		button2Sub3.put("url",
				"http://www.aguang.cc/weixin/wx/menu2Button3.action");

		sub_button2.add(button2Sub1);
		sub_button2.add(button2Sub2);
		sub_button2.add(button2Sub3);

		// 将子菜单绑定到菜单2按钮中
		button2.put("sub_button", sub_button2.toString());

		// 添加菜单按钮2
		buttonJsonArray.add(button2);

		// 菜单按钮3
		JSONObject button3 = new JSONObject();
		button3.put("name", "最新按钮");

		// 定义菜单按钮3的子按钮json数组
		JSONArray sub_button3 = new JSONArray();
		// 菜单按钮3中的子按钮1
		JSONObject button3Sub1 = new JSONObject();
		button3Sub1.put("type", "scancode_waitmsg");
		button3Sub1.put("name", "扫码带提示");
		button3Sub1.put("key", "searchCode");
		button3Sub1.put("sub_button", "[]");
		// 将子按钮绑定到菜单按钮3中
		sub_button3.add(button3Sub1.toString());

		// 菜单按钮3中的子按钮2
		JSONObject button3Sub2 = new JSONObject();
		button3Sub2.put("type", "scancode_push");
		button3Sub2.put("name", "扫码推事件");
		button3Sub2.put("key", "searchCodePushEvent");
		button3Sub2.put("sub_button", "[]");
		sub_button3.add(button3Sub2.toString());

		button3.put("sub_button", sub_button3.toString());
		buttonJsonArray.add(button3);

		// 最终的按钮自定义菜单数据
		data.put("button", buttonJsonArray.toString());

		try {
			System.out.println(data.toString());
			HttpUtils.getInstance().createPostHttpRequest(apiUrl,
					data.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 根据openId获取用户信息,需要认证才可以,暂时无法使用,还没有认证
	 * 
	 * @return String
	 */
	public void getUserInfoByOpenId() {
		String openId = "cJCc8GeFCJu8kHocFLqg";
		String apiUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="
				+ getAccessToken() + "&openid=" + openId + "&lang=zh_CN";

		try {
			JSONObject json = JSONObject.fromObject(HttpUtils.getInstance()
					.createPostHttpRequest(apiUrl, ""));
			System.out.println("国家" + json.get("country"));
			System.out.println("省份" + json.get("province"));
			System.out.println("城市" + json.get("city"));
			System.out.println("昵称" + json.get("nickname"));
			System.out.println("头像地址" + json.get("headimgurl"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 获取最新accessToken
	 * 
	 * @return String
	 */
	private String getAccessToken() {
		String apiUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type="
				+ WeiXinContants.GRANT_TYPE + "&appid=" + WeiXinContants.APP_ID
				+ "&secret=" + WeiXinContants.APP_SECRET;

		JSONObject json = JSONObject.fromObject(HttpUtils.getInstance()
				.getJsonObjectByUrl(apiUrl));

		String access_token = json.get("access_token") + "";
		System.out.println("access_token:" + access_token);
		return access_token;
	}

	/**
	 * 添加关注记录
	 */
	public void addWeixinOpenId() {
		AgWeixinOpenId woi = new AgWeixinOpenId();
		woi.setOpenId("123");
		woi.setCreateTime(new Date());

		try {
			weixinOpenIdBus.save(woi);
		} catch (BusinessException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * 根据城市获取天气预报信息
	 * 
	 * @param city
	 */
	@Test
	public void getWeatherInfoByCity() {
		String city = "广州";
		try {
			String apiUrl = "http://php.weather.sina.com.cn/xml.php?city="
					+ URLEncoder.encode(city, "gbk")
					+ "&password=DJOYnieT8234jlsK&day=0";
			String data = HttpUtils.getInstance().getXmlDataByUrl(apiUrl);
			System.out.println(data);
			Document document = DocumentHelper.parseText(data);
			Element root = document.getRootElement();
			// 事件 click、unSubscribe、subscribe
			Element weather = root.element("Weather");
			if (weather == null) {
				System.out.println("无数据");
			} else {
				// 回复内容
				String weatherInfo = "您输入的城市为:"
						+ weather.element("city").getText() + "。天气:"
						+ weather.element("status1").getText() + "->"
						+ weather.element("status2").getText() + "。风向:"
						+ weather.element("direction1").getText() + "。紫外线指数:"
						+ weather.element("pollution").getText() + "。温度:"
						+ weather.element("ssd_l").getText() + "。注意事项:"
						+ weather.element("gm_s").getText() + "。"
						+ weather.element("yd_s").getText() + "。"
						+ weather.element("xcz_s").getText() + "。"
						+ weather.element("pollution_s").getText();
				System.out.println(weatherInfo);
			}
		} catch (Exception e) {
			// e.printStackTrace();
			System.out.println("转码失败");
		}
	}
}

5、几个相关的bus类,直接上实现类就行了

package cn.com.aguang.weixin.bussiness.impl;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import cn.com.aguang.commons.business.AbstractBaseBus;
import cn.com.aguang.commons.dao.BaseDAO;
import cn.com.aguang.weixin.bussiness.WeixinOpenIdBus;
import cn.com.aguang.weixin.dao.WeixinOpenIdDAO;
import cn.com.aguang.weixin.po.AgWeixinOpenId;

@Service("weixinOpenIdBus")
public class WeixinOpenIdBusImpl extends
		AbstractBaseBus implements WeixinOpenIdBus {

	private WeixinOpenIdDAO weixinOpenIdDAO;

	@Resource
	public void setWeixinOpenIdDAO(WeixinOpenIdDAO weixinOpenIdDAO) {
		this.weixinOpenIdDAO = weixinOpenIdDAO;
	}

	@Override
	protected BaseDAO getDAO() {
		return weixinOpenIdDAO;
	}

	@Override
	protected void checkRequires(AgWeixinOpenId paramT) {
		// do nothing
	}

}

package cn.com.aguang.weixin.bussiness.impl;

import java.io.IOException;
import java.io.InputStream;

import org.springframework.stereotype.Service;

import cn.com.aguang.weixin.bussiness.WeixinUtilBus;
import cn.com.aguang.weixin.utils.WeiXinContants;

@Service("weixinUtilBus")
public class WeixinUtilBusImpl implements WeixinUtilBus {

	/**
	 * 根据IO流获取IO流的数据
	 */
	@Override
	public String getInfoFromIoStream(InputStream is) {
		String pushData = "";
		try {
			if (is != null) {
				String charSet = null;
				charSet = "utf-8";
				byte b[] = new byte[200];
				int numRead = is.read(b);
				if (numRead != -1) {
					String content = new String(b, 0, numRead);
					while (numRead != -1) {
						numRead = is.read(b);
						if (numRead != -1) {
							String newContent = new String(b, 0, numRead,
									charSet);
							content += newContent;
						}
					}
					pushData = content;
					System.out.println("接收到推送的信息:" + content);
				}
			}
			return pushData;
		} catch (IOException e) {
			return "";
		}
	}

	/**
	 * 根据内容,接收者openId获取应该相应的XML字符串
	 */
	@Override
	public String getReplyConentXmlStr(String content, String toUserName,
			int createTime) {
		String replyAguangSoftTestContentXMLStr = ""
				+ createTime
				+ "";
		return replyAguangSoftTestContentXMLStr;
	}

	/**
	 * 根据扫描内容,接收者openId获取应该相应的XML字符串
	 */
	@Override
	public String scancodeResultXMLStr(String scanResult, String toUserName,
			int createTime) {
		String scancodeResultXMLStr = ""
				+ createTime
				+ "";
		return scancodeResultXMLStr;
	}

}

6、相关的工具类,主要是请求封装的工具类,微信公众平台的appid等参数定义

package cn.com.aguang.weixin.utils;

public class WeiXinContants {

	public static final String WEI_XIN_NO = "AGuangSoftTest"; //公众平台设置的微信号,发送信息时需要

	public static final String APP_ID = "**************";//根据自己的替换

	public static final String APP_SECRET = "*************************"; 根据自己的替换

	public static final String TOKEN = "***";  //根据自己的替换

	public static final String GRANT_TYPE = "client_credential";

	public static String ACCESS_TOKEN = "Cx_JPR-Cs791HRhDcA2jjm9U_UtHEPaOXT3q-LL7six96i9u0_-olxXoaHUbhGz3Cal7sp2C2KM0qerk4fpPnU7_iZCKhi42nOaKE2SA-Os";

	public static String AGUANG_SOFT_TEST_CLICK_CONTENT = "接口点击事件回复调试成功,这只是一小步";

	public static void setAccessToken(String access_token) {
		ACCESS_TOKEN = access_token;
	}
}
封装的请求工具类

package cn.com.aguang.weixin.utils;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
 * 
 * 饿汉试 单例 直接实例化一个,线程安全(以前一直用懒汉式,第一次调用实例化,但是有可能会出现多个实例,非常小的概率)
 * 
 * @author aGuang
 *
 */
public class HttpUtils {

	private static HttpUtils instance = new HttpUtils();

	/**
	 * 私有构造方法
	 */
	private HttpUtils() {

	}

	public static HttpUtils getInstance() {
		return instance;
	}

	/**
	 * 根据api访问地址获取地址返回的json数据
	 * 
	 * @param apiUrl
	 * 
	 * @return json
	 */
	public String getJsonObjectByUrl(String apiUrl) {
		try {
			URL url = new URL(apiUrl);
			HttpURLConnection con = (HttpURLConnection) url.openConnection();
			con.setAllowUserInteraction(false);
			InputStream urlStream = url.openStream();
			String charSet = null;
			charSet = "utf-8";
			byte b[] = new byte[200];
			int numRead = urlStream.read(b);
			String content = new String(b, 0, numRead);
			while (numRead != -1) {
				numRead = urlStream.read(b);
				if (numRead != -1) {
					String newContent = new String(b, 0, numRead, charSet);
					content += newContent;
				}
			}
			// System.out.println("content:" + content);
			JSONObject json = JSONObject.fromObject(new String(content
					.getBytes(), "utf-8"));

			urlStream.close();
			return json.toString();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 根据api访问地址获取地址返回的json数据
	 * 
	 * @param apiUrl
	 * 
	 * @return
	 */
	public String getJsonArrayByUrl(String apiUrl) {
		try {
			URL url = new URL(apiUrl);
			HttpURLConnection con = (HttpURLConnection) url.openConnection();
			con.setAllowUserInteraction(false);
			InputStream urlStream = url.openStream();
			String charSet = null;
			charSet = "utf-8";
			byte b[] = new byte[200];
			int numRead = urlStream.read(b);
			String content = new String(b, 0, numRead);
			while (numRead != -1) {
				numRead = urlStream.read(b);
				if (numRead != -1) {
					String newContent = new String(b, 0, numRead, charSet);
					content += newContent;
				}
			}
			// System.out.println("content:" + content);
			JSONArray array = JSONArray.fromObject(new String(content
					.getBytes(), "utf-8"));

			urlStream.close();
			return array.toString();
		} catch (Exception e) {
			// e.printStackTrace();
			return null;
		}
	}

	/**
	 * 根据api访问地址获取地址返回的XML数据
	 * 
	 * @param apiUrl
	 * 
	 * @return
	 */
	public String getXmlDataByUrl(String apiUrl) {
		try {
			URL url = new URL(apiUrl);
			HttpURLConnection con = (HttpURLConnection) url.openConnection();
			con.setAllowUserInteraction(false);
			InputStream urlStream = url.openStream();
			String charSet = null;
			charSet = "utf-8";
			byte b[] = new byte[6000];
			int numRead = urlStream.read(b);
			String content = new String(b, 0, numRead);
			while (numRead != -1) {
				numRead = urlStream.read(b);
				if (numRead != -1) {
					String newContent = new String(b, 0, numRead, charSet);
					content += newContent;
				}
			}
			// System.out.println("content:" + content);
			String xmlString = new String(content.getBytes(), "utf-8");

			urlStream.close();
			return xmlString;
		} catch (Exception e) {
			// e.printStackTrace();
			return null;
		}
	}

	/**
	 * 用post请求访问一个http
	 * 
	 * @param apiUrl
	 *            请求地址
	 * @return
	 */
	public String createPostHttpRequest(String apiUrl, String data)
			throws Exception {
		// Post请求的url,与get不同的是不需要带参数
		URL postUrl = new URL(apiUrl);
		// 打开连接
		HttpURLConnection connection = (HttpURLConnection) postUrl
				.openConnection();
		// 25秒连接超时
		connection.setConnectTimeout(25000);
		// 读取超时 --服务器响应比较慢,增大时间
		connection.setReadTimeout(25000);

		HttpURLConnection.setFollowRedirects(true);

		// 设置是否向connection输出,因为这个是post请求,参数要放在
		// http正文内,因此需要设为true
		connection.setDoOutput(true);
		// Read from the connection. Default is true.
		connection.setDoInput(true);
		// Set the post method. Default is GET
		connection.setRequestMethod("POST");
		// Post cannot use caches
		// Post 请求不能使用缓存
		connection.setUseCaches(false);
		// URLConnection.setFollowRedirects是static函数,作用于所有的URLConnection对象。
		// connection.setFollowRedirects(true);

		// URLConnection.setInstanceFollowRedirects是成员函数,仅作用于当前函数
		connection.setInstanceFollowRedirects(true);
		// 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
		// 意思是正文是urlencoded编码过的form参数,下面我们可以看到我们对正文内容使用URLEncoder.encode
		// 进行编码
		// connection.setRequestProperty("Content-Type",
		// "application/x-www-form-urlencoded");
		connection
				.setRequestProperty("User-Agent",
						"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0");
		connection.setRequestProperty("Referer", "https://api.weixin.qq.com/");
		// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
		// 要注意的是connection.getOutputStream会隐含的进行connect。
		connection.connect();
		OutputStreamWriter out = new OutputStreamWriter(
				connection.getOutputStream());
		// The URL-encoded contend
		// 正文,正文内容其实跟get的URL中'?'后的参数字符串一致
		// String content = URLEncoder.encode("中国聚龙", "utf-8");
		String content = data;
		// DataOutputStream.writeBytes将字符串中的16位的unicode字符以8位的字符形式写道流里面
		out.write(content);
		out.flush();
		out.close(); // flush and close
		BufferedReader reader = new BufferedReader(new InputStreamReader(
				connection.getInputStream(), "utf-8"));// 设置编码,否则中文乱码
		String line = "";
		System.out.println("=============================");
		System.out.println("Contents of post request");
		System.out.println("=============================");
		while ((line = reader.readLine()) != null) {
			// line = new String(line.getBytes(), "utf-8");
			System.out.println("返回的信息:" + line);
		}
		System.out.println("=============================");
		System.out.println("Contents of post request ends");
		System.out.println("=============================");
		reader.close();
		connection.disconnect();
		return "";
	}
}

上面的几个代码就是我的公众号测试开发上写的,如果有什么不对的,请指出


有什么好的建议也可以给我,对于更多要了解微信的,我建议还是多看微信提供的api文档,本文只供参考,希望对大家有所帮助(其实主要是代码,我个人觉得新手来说,代码就是王道,但是后面还有多数的接口使用,大家可以自己摸索使用)

你可能感兴趣的:(微信开发)