robot/src/main/java/com/itranswarp/wxapi/sample/robot/Robot.java
@Component
public class Robot {

	static final String TULING_URL = "http://www.tuling123.com/openapi/api";

	static final Map<String, String> HEADERS = MapUtil.createMap("Content-Type", "application/json", "Authorization",
			"token");

	@Value("${robot.app.key}")
	String apiKey;

	@Value("${robot.app.secret}")
	String apiSecret;

	public RobotResponse talk(String userId, String text) throws Exception {
		return talk(userId, text, null);
	}

	public RobotResponse talk(String userId, String text, String location) throws Exception {
		System.out.println("Key: " + apiKey + ", Secret: " + apiSecret);
		Map<String, String> params = MapUtil.createMap("key", apiKey, "userid", HashUtil.md5(userId), "info", text);
		if (location != null && !location.isEmpty()) {
			params.put("loc", location);
		}
		String timestamp = String.valueOf(System.currentTimeMillis());
		String jsonData = JsonUtil.toJson(params);
		String aesKey = HashUtil.md5(apiSecret + timestamp + apiKey);
		String encryptedData = aesEncrypt(aesKey, jsonData);
		Map<String, String> postData = MapUtil.createMap("key", apiKey, "timestamp", timestamp, "data", encryptedData);
		String json = HttpUtil.httpPost(TULING_URL, JsonUtil.toJson(postData), HEADERS);
		RobotResponse resp = JsonUtil.fromJson(RobotResponse.class, json);
		if (resp.code >= 40000 && resp.code <= 49999) {
			throw new RobotException(resp.code, resp.text);
		}
		return resp;
	}

	String aesEncrypt(String aesKey, String data) throws GeneralSecurityException {
		Key key = new SecretKeySpec(getHash(aesKey), "AES");
		IvParameterSpec iv = new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 });
		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		cipher.init(Cipher.ENCRYPT_MODE, key, iv);
		byte[] encryptData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
		return Base64.getEncoder().encodeToString(encryptData);
	}

	byte[] getHash(String s) throws GeneralSecurityException {
		byte[] data = s.getBytes(StandardCharsets.UTF_8);
		MessageDigest digest = MessageDigest.getInstance("MD5");
		digest.update(data);
		return digest.digest();
	}

	public static class RobotResponse {

		public static int CODE_TEXT = 100000;
		public static int CODE_LINK = 200000;
		public static int CODE_NEWS = 302000;
		public static int CODE_COOK = 308000;
		public static int CODE_SONG = 313000;
		public static int CODE_POEM = 314000;

		public int code;
		public String text;
		public String url;
		public Item[] list;
		public Func function;
	}

	public static class Item {
		public String article;
		public String source;
		public String icon;
		public String info;
		public String detailurl;
	}

	public static class Func {
		public String song;
		public String singer;
		public String author;
		public String name;
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/ReceivedTextMessage.java
public class ReceivedTextMessage extends AbstractReceivedMessage {

	public final String MsgType = Message.TYPE_TEXT;

	public final String Content;

	ReceivedTextMessage(long CreateTime, String FromUserName, String ToUserName, long MsgId, String Content) {
		super(CreateTime, FromUserName, ToUserName, MsgId);
		this.Content = Content;
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/util/XmlUtil.java
public class XmlUtil {

	public static String toXml(Object o) {
		XmlMapper mapper = new XmlMapper();
		try {
			return mapper.writeValueAsString(o);
		} catch (JsonProcessingException e) {
			throw new RuntimeException(e);
		}
	}

	public static String toXml(Object o, String rootName) {
		XmlMapper mapper = new XmlMapper();
		try {
			return mapper.writer().withRootName(rootName).writeValueAsString(o);
		} catch (JsonProcessingException e) {
			throw new RuntimeException(e);
		}
	}

	public static <T> T fromXml(Class<T> clazz, String s) {
		XmlMapper mapper = new XmlMapper();
		mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		try {
			return mapper.readValue(s, clazz);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
}
wxapi/src/main/java/com/itranswarp/wxapi/message/ReceivedVideoMessage.java
public class ReceivedVideoMessage extends AbstractReceivedMessage {

	public final String MsgType = Message.TYPE_VIDEO;

	public final String MediaId;

	public final String ThumbMediaId;

	ReceivedVideoMessage(long CreateTime, String FromUserName, String ToUserName, long MsgId, String MediaId,
			String ThumbMediaId) {
		super(CreateTime, FromUserName, ToUserName, MsgId);
		this.MediaId = MediaId;
		this.ThumbMediaId = ThumbMediaId;
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/ReceivedShortVideoMessage.java
public class ReceivedShortVideoMessage extends AbstractReceivedMessage {

	public final String MsgType = Message.TYPE_SHORT_VIDEO;

	public final String MediaId;

	public final String ThumbMediaId;

	ReceivedShortVideoMessage(long CreateTime, String FromUserName, String ToUserName, long MsgId, String MediaId,
			String ThumbMediaId) {
		super(CreateTime, FromUserName, ToUserName, MsgId);
		this.MediaId = MediaId;
		this.ThumbMediaId = ThumbMediaId;
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/RepliedImageMessage.java
public class RepliedImageMessage extends AbstractRepliedMessage {

	public final Image Image;

	public RepliedImageMessage(String FromUserName, String ToUserName, String MediaId) {
		super(Message.TYPE_IMAGE, FromUserName, ToUserName);
		this.Image = new Image(MediaId);
	}

	public static class Image {

		public final String MediaId;

		public Image(String mediaId) {
			MediaId = mediaId;
		}
	}
}
wxapi/src/main/java/com/itranswarp/wxapi/message/ReceivedImageMessage.java
public class ReceivedImageMessage extends AbstractReceivedMessage {

	public final String MsgType = Message.TYPE_IMAGE;

	public final String PicUrl;

	public final String MediaId;

	ReceivedImageMessage(long CreateTime, String FromUserName, String ToUserName, long MsgId, String PicUrl,
			String MediaId) {
		super(CreateTime, FromUserName, ToUserName, MsgId);
		this.PicUrl = PicUrl;
		this.MediaId = MediaId;
	}

}
robot/src/main/java/com/itranswarp/wxapi/sample/robot/Robot.java
public static class RobotResponse {

	public static int CODE_TEXT = 100000;
	public static int CODE_LINK = 200000;
	public static int CODE_NEWS = 302000;
	public static int CODE_COOK = 308000;
	public static int CODE_SONG = 313000;
	public static int CODE_POEM = 314000;

	public int code;
	public String text;
	public String url;
	public Item[] list;
	public Func function;
}
wxapi/src/main/java/com/itranswarp/wxapi/message/RepliedTextMessage.java
public class RepliedTextMessage extends AbstractRepliedMessage {

	public final String Content;

	public RepliedTextMessage(String FromUserName, String ToUserName, String Content) {
		super(Message.TYPE_TEXT, FromUserName, ToUserName);
		this.Content = Content;
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/ReceivedLinkMessage.java
public class ReceivedLinkMessage extends AbstractReceivedMessage {

	public final String MsgType = Message.TYPE_LINK;

	public final String Url;

	public final String Title;

	public final String Description;

	ReceivedLinkMessage(long CreateTime, String FromUserName, String ToUserName, long MsgId, String Url, String Title,
			String Description) {
		super(CreateTime, FromUserName, ToUserName, MsgId);
		this.Url = Url;
		this.Title = Title;
		this.Description = Description;
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/RepliedVoiceMessage.java
public class RepliedVoiceMessage extends AbstractRepliedMessage {

	public final Voice Voice;

	public RepliedVoiceMessage(String FromUserName, String ToUserName, String MediaId) {
		super(Message.TYPE_VOICE, FromUserName, ToUserName);
		this.Voice = new Voice(MediaId);
	}

	public static class Voice {

		public final String MediaId;

		public Voice(String mediaId) {
			MediaId = mediaId;
		}
	}
}
wxapi/src/main/java/com/itranswarp/wxapi/event/AbstractEvent.java
public abstract class AbstractEvent {

	public static final String EVENT_SUBSCRIBE = "subscribe";
	public static final String EVENT_UNSUBSCRIBE = "unsubscribe";
	public static final String EVENT_SCAN = "SCAN";

	public final long CreateTime;

	public final String FromUserName;

	public final String ToUserName;

	public final String Event;

	AbstractEvent(long CreateTime, String FromUserName, String ToUserName, String Event) {
		this.CreateTime = CreateTime;
		this.FromUserName = FromUserName;
		this.ToUserName = ToUserName;
		this.Event = Event;
	}
}
wxapi/src/main/java/com/itranswarp/wxapi/message/Message.java
public static class MessageBuilder {
	final String FromUserName;
	final String ToUserName;

	MessageBuilder(String FromUserName, String ToUserName) {
		this.FromUserName = FromUserName;
		this.ToUserName = ToUserName;
	}

	public RepliedTextMessage toTextMessage(String Content) {
		return new RepliedTextMessage(FromUserName, ToUserName, Content);
	}

	public RepliedImageMessage toImageMessage(String MediaId) {
		return new RepliedImageMessage(FromUserName, ToUserName, MediaId);
	}

	public RepliedVoiceMessage toVoiceMessage(String MediaId) {
		return new RepliedVoiceMessage(FromUserName, ToUserName, MediaId);
	}

	public RepliedVideoMessage toVideoMessage(String MediaId, String Title, String Description) {
		return new RepliedVideoMessage(FromUserName, ToUserName, MediaId, Title, Description);
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/ReceivedLocationMessage.java
public class ReceivedLocationMessage extends AbstractReceivedMessage {

	public final String MsgType = Message.TYPE_LOCATION;

	public final String Label;

	public final double Location_X;

	public final double Location_Y;

	public final long Scale;

	ReceivedLocationMessage(long CreateTime, String FromUserName, String ToUserName, long MsgId, String Label,
			double Location_X, double Location_Y, long Scale) {
		super(CreateTime, FromUserName, ToUserName, MsgId);
		this.Label = Label;
		this.Location_X = Location_X;
		this.Location_Y = Location_Y;
		this.Scale = Scale;
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/Message.java
public class Message {

	static final String TYPE_TEXT = "text";

	static final String TYPE_IMAGE = "image";

	static final String TYPE_VOICE = "voice";

	static final String TYPE_VIDEO = "video";

	static final String TYPE_SHORT_VIDEO = "shortvideo";

	static final String TYPE_LOCATION = "location";

	static final String TYPE_LINK = "link";

	static final String TYPE_EVENT = "event";

	static final String EVENT_SUBSCRIBE = "subscribe";

	public String ToUserName;

	public String FromUserName;

	public long CreateTime;

	public String MsgType;

	public long MsgId;

	// for text message:
	public String Content;

	// for image message:
	public String PicUrl;

	// for image / voice / video / short video message:
	public String MediaId;

	// for voice message:
	public String Format;

	public String Recognition;

	// for video / short video message:
	public String ThumbMediaId;

	// for location message:
	public String Label;

	public double Location_X;

	public double Location_Y;

	public long Scale;

	// for link message:
	public String Url;

	public String Title;

	public String Description;

	// for event:
	public String Event;

	public String EventKey;

	public String Ticket;

	public double Latitude;

	public double Longitude;

	public double Precision;

	public boolean isTextMessage() {
		return TYPE_TEXT.equals(this.MsgType);
	}

	public boolean isImageMessage() {
		return TYPE_IMAGE.equals(this.MsgType);
	}

	public boolean isVoiceMessage() {
		return TYPE_VOICE.equals(this.MsgType);
	}

	public boolean isVideoMessage() {
		return TYPE_VIDEO.equals(this.MsgType);
	}

	public boolean isShortVideoMessage() {
		return TYPE_SHORT_VIDEO.equals(this.MsgType);
	}

	public boolean isLocationMessage() {
		return TYPE_LOCATION.equals(this.MsgType);
	}

	public boolean isLinkMessage() {
		return TYPE_LINK.equals(this.MsgType);
	}

	public boolean isEvent() {
		return TYPE_EVENT.equals(this.MsgType);
	}

	/**
	 * 把消息转换为相应的事件类型
	 * 
	 * @return AbstractEvent的子类
	 */
	public AbstractEvent asEvent() {
		if (isEvent()) {
			switch (this.Event) {
			case AbstractEvent.EVENT_SUBSCRIBE:
				return new SubscribeEvent(CreateTime, FromUserName, ToUserName, Event, EventKey, Ticket);
			case AbstractEvent.EVENT_UNSUBSCRIBE:
				return new UnsubscribeEvent(CreateTime, FromUserName, ToUserName, Event, EventKey, Ticket);
			case AbstractEvent.EVENT_SCAN:
				return new ScanEvent(CreateTime, FromUserName, ToUserName, Event, EventKey, Ticket);
			default:
				throw new WeixinMessageException("Cannot handle new type of event from " + this);
			}
		}
		throw new WeixinMessageException("Cannot cast to event from " + this);
	}

	/**
	 * 把消息转换为文本消息
	 * 
	 * @return ReceivedTextMessage
	 */
	public ReceivedTextMessage asTextMessage() {
		if (isTextMessage()) {
			return new ReceivedTextMessage(CreateTime, FromUserName, ToUserName, MsgId, Content);
		}
		throw new WeixinMessageException("Cannot cast to text message from " + this);
	}

	/**
	 * 把消息转换为图片消息
	 * 
	 * @return ReceivedImageMessage
	 */
	public ReceivedImageMessage asImageMessage() {
		if (isImageMessage()) {
			return new ReceivedImageMessage(CreateTime, FromUserName, ToUserName, MsgId, PicUrl, MediaId);
		}
		throw new WeixinMessageException("Cannot cast to image message from " + this);
	}

	/**
	 * 把消息转换为语音消息
	 * 
	 * @return ReceivedVoiceMessage
	 */
	public ReceivedVoiceMessage asVoiceMessage() {
		if (isVoiceMessage()) {
			return new ReceivedVoiceMessage(CreateTime, FromUserName, ToUserName, MsgId, MediaId, Format, Recognition);
		}
		throw new WeixinMessageException("Cannot cast to voice message from " + this);
	}

	/**
	 * 把消息转换为视频消息
	 * 
	 * @return ReceivedVideoMessage
	 */
	public ReceivedVideoMessage asVideoMessage() {
		if (isVideoMessage()) {
			return new ReceivedVideoMessage(CreateTime, FromUserName, ToUserName, MsgId, MediaId, ThumbMediaId);
		}
		throw new WeixinMessageException("Cannot cast to video message from " + this);
	}

	/**
	 * 把消息转换为小视频消息
	 * 
	 * @return ReceivedShortVideoMessage
	 */
	public ReceivedShortVideoMessage asShortVideoMessage() {
		if (isShortVideoMessage()) {
			return new ReceivedShortVideoMessage(CreateTime, FromUserName, ToUserName, MsgId, MediaId, ThumbMediaId);
		}
		throw new WeixinMessageException("Cannot cast to short video message from " + this);
	}

	/**
	 * 把消息转换为位置消息
	 * 
	 * @return ReceivedLocationMessage
	 */
	public ReceivedLocationMessage asLocationMessage() {
		if (isLocationMessage()) {
			return new ReceivedLocationMessage(CreateTime, FromUserName, ToUserName, MsgId, Label, Location_X,
					Location_Y, Scale);
		}
		throw new WeixinMessageException("Cannot cast to location message from " + this);
	}

	/**
	 * 把消息转换为链接消息
	 * 
	 * @return ReceivedLinkMessage
	 */
	public ReceivedLinkMessage asLinkMessage() {
		if (isLinkMessage()) {
			return new ReceivedLinkMessage(CreateTime, FromUserName, ToUserName, MsgId, Url, Title, Description);
		}
		throw new WeixinMessageException("Cannot cast to link message from " + this);
	}

	/**
	 * 创建一个MessageBuilder对象
	 * 
	 * @param FromUserName
	 *            发送方
	 * @param ToUserName
	 *            接收方
	 * @return MessageBuilder
	 */
	public static MessageBuilder buildMessage(String FromUserName, String ToUserName) {
		return new MessageBuilder(FromUserName, ToUserName);
	}

	public String toString() {
		return "<Message: MsgType=" + this.MsgType + ", MsgId=" + this.MsgId + ">";
	}

	public static class MessageBuilder {
		final String FromUserName;
		final String ToUserName;

		MessageBuilder(String FromUserName, String ToUserName) {
			this.FromUserName = FromUserName;
			this.ToUserName = ToUserName;
		}

		public RepliedTextMessage toTextMessage(String Content) {
			return new RepliedTextMessage(FromUserName, ToUserName, Content);
		}

		public RepliedImageMessage toImageMessage(String MediaId) {
			return new RepliedImageMessage(FromUserName, ToUserName, MediaId);
		}

		public RepliedVoiceMessage toVoiceMessage(String MediaId) {
			return new RepliedVoiceMessage(FromUserName, ToUserName, MediaId);
		}

		public RepliedVideoMessage toVideoMessage(String MediaId, String Title, String Description) {
			return new RepliedVideoMessage(FromUserName, ToUserName, MediaId, Title, Description);
		}

	}
}
wxapi/src/main/java/com/itranswarp/wxapi/message/ReceivedVoiceMessage.java
public class ReceivedVoiceMessage extends AbstractReceivedMessage {

	public final String MsgType = Message.TYPE_VOICE;

	public final String MediaId;

	public final String Format;

	public final String Recognition;

	public ReceivedVoiceMessage(long CreateTime, String FromUserName, String ToUserName, long MsgId, String MediaId,
			String Format, String Recognition) {
		super(CreateTime, FromUserName, ToUserName, MsgId);
		this.MediaId = MediaId;
		this.Format = Format;
		this.Recognition = Recognition;
	}

}
robot/src/main/java/com/itranswarp/wxapi/sample/robot/RobotException.java
public class RobotException extends RuntimeException {

	private int code = 0;

	public int getCode() {
		return code;
	}

	public RobotException() {
		super();
	}

	public RobotException(int code) {
		super();
		this.code = code;
	}

	public RobotException(int code, String message, Throwable cause) {
		super(message, cause);
		this.code = code;
	}

	public RobotException(int code, String message) {
		super(message);
		this.code = code;
	}

	public RobotException(int code, Throwable cause) {
		super(cause);
		this.code = code;
	}

	public RobotException(String message, Throwable cause) {
		super(message, cause);
	}

	public RobotException(String message) {
		super(message);
	}

	public RobotException(Throwable cause) {
		super(cause);
	}

}
wxapi/src/main/java/com/itranswarp/wxapi/message/RepliedVideoMessage.java
public class RepliedVideoMessage extends AbstractRepliedMessage {

	public final Video Video;

	public RepliedVideoMessage(String FromUserName, String ToUserName, String MediaId, String Title,
			String Description) {
		super(Message.TYPE_VIDEO, FromUserName, ToUserName);
		this.Video = new Video(MediaId, Title, Description);
	}

	public static class Video {

		public final String MediaId;
		public final String Title;
		public final String Description;

		public Video(String MediaId, String Title, String Description) {
			this.MediaId = MediaId;
			this.Title = Title;
			this.Description = Description;
		}
	}
}


Question: package com.itranswarp.wxapi.sample.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.itranswarp.wxapi.event.AbstractEvent;
import com.itranswarp.wxapi.message.Message;
import com.itranswarp.wxapi.message.Message.MessageBuilder;
import com.itranswarp.wxapi.sample.robot.Robot;
import com.itranswarp.wxapi.sample.robot.Robot.RobotResponse;
import com.itranswarp.wxapi.sample.robot.RobotException;
import com.itranswarp.wxapi.message.ReceivedImageMessage;
import com.itranswarp.wxapi.message.ReceivedLinkMessage;
import com.itranswarp.wxapi.message.ReceivedLocationMessage;
import com.itranswarp.wxapi.message.ReceivedShortVideoMessage;
import com.itranswarp.wxapi.message.ReceivedTextMessage;
import com.itranswarp.wxapi.message.ReceivedVideoMessage;
import com.itranswarp.wxapi.message.ReceivedVoiceMessage;
import com.itranswarp.wxapi.message.RepliedImageMessage;
import com.itranswarp.wxapi.message.RepliedTextMessage;
import com.itranswarp.wxapi.message.RepliedVideoMessage;
import com.itranswarp.wxapi.message.RepliedVoiceMessage;
import com.itranswarp.wxapi.util.XmlUtil;





@Controller
public class MessageController extends AbstractController {

	@Autowired
	Robot robot;

	/**
	 * Response echo message.
	 * 
	 * @param echoStr
	 *            Random string sent from weixin.
	 * @param request
	 *            The HttpServletRequest object.
	 * @return The echo string.
	 */
	@RequestMapping(value = "/wxapi/message", method = RequestMethod.GET)
	@ResponseBody
	String token(@RequestParam(value = "echostr") String echoStr, HttpServletRequest request) {
		client.validateSignature(request);
		return echoStr;
	}

	String talk(String userId, String text) throws Exception {
		try {
			RobotResponse resp = robot.talk(userId, text);
			if (resp.code == RobotResponse.CODE_TEXT) {
				return resp.text;
			}
		} catch (RobotException e) {
			log.warn("RobotException: " + e.getCode(), e);
		}
		return "亲，听不懂你在说什么 :(";
	}

	/**
	 * Receive XML messages sent from weixin.
	 * 
	 * @param request
	 * @param response
	 * @return
	 * @throws IOException
	 */
	@RequestMapping(value = "/wxapi/message", method = RequestMethod.POST, consumes = "text/xml", produces = "text/xml;charset=utf-8")
	@ResponseBody
	String onMessageReceived(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String xml = client.readXml(request);
		log.info("weixin >>> " + xml);
Answer: 		Message msg = XmlUtil.fromXml(Message.class, xml);