TMaize Blog

Java微信开发基本配置

填写服务器配置,验证服务器地址的有效性

开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,注意只有检验服务器的是Get请求,其他的消息都是Post请求

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

  1. 将token、timestamp、nonce三个参数进行字典序排序

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

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

在绑定的url上转发消息

public class Main extends HttpServlet{

	private static final long serialVersionUID = 1L;
	
	//转发服务器验证信息
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		RequestDispatcher requestDispatcher = this.getServletContext().getRequestDispatcher("/verify");
		requestDispatcher.forward(request, response);
	}
	
	//转发交互信息
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		RequestDispatcher requestDispatcher = this.getServletContext().getRequestDispatcher("/message");
		requestDispatcher.forward(request, response);;
	}
}

验证服务器地址的有效性

//微信公众号的验证
//只有这个是get方式,其他的都是post方式
public class VerifyServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	// 微信公众号上面设置的
	private final String token = "tmaize_dev";

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");

		String signature = request.getParameter("signature");
		String timestamp = request.getParameter("timestamp");
		String nonce = request.getParameter("nonce");
		String echostr = request.getParameter("echostr");

		if(signature!=null && signature!=null && signature!=null && signature!=null){
			ArrayList<String> array = new ArrayList<String>();
			array.add(signature);
			array.add(timestamp);
			array.add(nonce);
			//1.排序
			String sortString = sort(token, timestamp, nonce);
			//2.加密
			String mytoken = SHA1(sortString);
			//3.校验签名
			//如果检验成功输出echostr,微信服务器接收到此输出,才会确认检验完成。
			if(mytoken.equals(signature)){
				response.getWriter().println(echostr);
			}
			else{
				response.getWriter().println("验证失败");
			}
			
		}
		else{
			response.getWriter().println("微信公众号token测试地址,这是后台程序没有界面!");
		}
	}

	//排序
	public static String sort(String token, String timestamp, String nonce) {
		String[] strArray = { token, timestamp, nonce };
		Arrays.sort(strArray);
		StringBuilder sbuilder = new StringBuilder();
		for (String str : strArray) {
			sbuilder.append(str);
		}

		return sbuilder.toString();
	}

	//加密
	public static String SHA1(String decript) {
		try {
			MessageDigest digest = MessageDigest.getInstance("SHA-1");
			digest.update(decript.getBytes());
			byte messageDigest[] = digest.digest();
			StringBuffer hexString = new StringBuffer();
			for (int i = 0; i < messageDigest.length; i++) {
				String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
				if (shaHex.length() < 2) {
					hexString.append(0);
				}
				hexString.append(shaHex);
			}
			return hexString.toString();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return "";
	}
}

获取access_token

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效

首先要做到每隔半小时获取一次access_token,可以在web.xml文件中为获取access_token 的Servlet做<load-on-startup>0</load-on-startup>配置,让他在服务器启动时启动,然后再在init方法里面开一个线程, 在线程中循环获取access_token,如果获取到则休眠半小时

通过http请求方式: GET调用接口来获取access_token

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

/**
 * 定义一个默认启动的servlet,在init方法中启动一个Thread,并在web.xml中将这个servlet设置为默认自启动的
 * 在进程中定义一个无限循环的方法,用来获取access_token,当获取成功后,此进程休眠半小时,否则休眠3秒钟继续获取
 */
public class Access_tokenServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	public void init() throws ServletException {
		Get_access_token.appId = getInitParameter("appid");
		Get_access_token.appSecret = getInitParameter("appsecret");
		new Thread(new Get_access_token()).start();
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.getWriter().println(Get_access_token.access_token.getAccess_token());
	}
}

在该线程中循环获取access_token

/**
 * 每半小时获取一次access_token
 */
public class Get_access_token implements Runnable {

	public static String appId = null;
	public static String appSecret = null;
	public static Access_token access_token = null;

	@Override
	public void run() {
		while (true) {
			try {
				access_token = this.getAccessToken();
				if (access_token == null) {
					System.out.println("appid失效");
					Thread.sleep(1000 * 3); // 获取的access_token为空 休眠3秒
					continue;
				}
				else{
					Thread.sleep(1000*60*30); // 获取到access_token 休眠半小时
					System.out.println(access_token.getAccess_token());
				}
			} catch (IOException e) {
				System.out.println("服务器请求超时!!!");
			} catch (JSONException e) {
				System.out.println("数据解析失败!!!");
			} catch (InterruptedException e) {
				System.out.println("线程错误!!!");
			}
		}
	}

	/**
	 * http请求方式: GET
	 * https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
	 * 正常情况下,微信会返回下述JSON数据包给公众号
	 * {"access_token":"ACCESS_TOKEN","expires_in":7200}
	 * 错误时微信会返回错误码等信息
	 * {"errcode":40013,"errmsg":"invalid appid"}
	 */
	private Access_token getAccessToken() throws IOException, JSONException {
		
		String result = HttpUtil.GetHttpContext("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId+"&secret="+appSecret, "get");
		if(result.startsWith("{\"err")){
			return null;
		}
		else{
			JSONObject jsonObject = new JSONObject(result);
			Access_token access_token = new Access_token();
			access_token.setAccess_token(jsonObject.getString("access_token"));
			access_token.setExpiresin(jsonObject.getInt("expires_in"));
			return access_token;
		}
	}

}

处理消息

当启用并设置服务器配置后,用户发给公众号的消息以及开发者需要的事件推送,将被微信以Post方式转发到该URL中。 所以需要在response给出响应,不过响应的类型时要符合微信标准的xml格式

public class MessageServlet extends HttpServlet{
	
	private static final long serialVersionUID = 1L;
	
	//响应交互信息
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
	
		try {
			HashMap<String, String> map = MessageUtil.parseXml(request);
			TextMessage textMessage = MessageUtil.hashMapToTextMessage(map);
			String ToUserName = textMessage.getToUserName();
			String FromUserName = textMessage.getFromUserName();
			textMessage.setFromUserName(ToUserName);
			textMessage.setToUserName(FromUserName);
			
            //逻辑处理

            //返回xml格式的结果,微信服务器会转给用户
	        PrintWriter out = response.getWriter();
	        out.print(MessageUtil.textMessageToXml(textMessage));  
	        out.close(); 
		} catch (DocumentException e) {
			System.out.println("解析数据错误");
		}
	}
}

总结

注意: 公众平台接口调用仅支持80端口

至此可以实现微信公众号简单的文本消息的发送于接收处理,更多的功能请参考文档

微信公众平台 官方文档