package com.github.sd4324530.fastweixin.servlet;
import com.github.sd4324530.fastweixin.company.handle.QYEventHandle;
import com.github.sd4324530.fastweixin.company.handle.QYMessageHandle;
import com.github.sd4324530.fastweixin.company.message.req.*;
import com.github.sd4324530.fastweixin.company.message.resp.QYBaseRespMsg;
import com.github.sd4324530.fastweixin.company.message.resp.QYTextRespMsg;
import com.github.sd4324530.fastweixin.message.aes.AesException;
import com.github.sd4324530.fastweixin.message.aes.WXBizMsgCrypt;
import com.github.sd4324530.fastweixin.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
/**
* ====================================================================
* 上海聚攒软件开发有限公司
* --------------------------------------------------------------------
*
* @author Nottyjay
* @version 1.0.beta
* ====================================================================
*/
public abstract class QYWeixinSupport{
private static final Logger LOG = LoggerFactory.getLogger(QYWeixinSupport.class);
private static final Object LOCK = new Object();
// protected String fromUserName, toUserName;
// private WXBizMsgCrypt wxcpt;
/**
* 子类提供token用于绑定微信企业平台
*
* @return token
*/
protected abstract String getToken();
/**
* 子类提供cropId用于绑定微信企业平台
*
* @return cropId
*/
protected abstract String getCropId();
/**
* 加密的密钥,消息解密时需要
*
* @return 密钥
*/
protected abstract String getAESKey();
/**
* 微信消息处理器列表
*/
private static List<QYMessageHandle> messageHandles;
/**
* 微信事件处理器列表
*/
private static List<QYEventHandle> eventHandles;
/**
* 子类重写,加入自定义的微信消息处理器,细化消息的处理
*
* @return 微信消息处理器列表
*/
protected List<QYMessageHandle> initMessageHandles(){
return null;
}
/**
* 子类重写,加入自定义的微信事件处理器,细化消息的处理
*
* @return 微信事件处理器列表
*/
protected List<QYEventHandle> initEventHandles(){
return null;
}
/**
* 绑定服务器的方法
* @param request 请求
* @param response 响应
*/
public void bindServer(HttpServletRequest request, HttpServletResponse response){
PrintWriter pw = null;
try {
pw = response.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
if(StrUtil.isBlank(getToken()) || StrUtil.isBlank(getAESKey()) || StrUtil.isBlank(getCropId())){
pw.write("");
pw.flush();
pw.close();
}
try {
WXBizMsgCrypt pc = new WXBizMsgCrypt(getToken(), getAESKey(), getCropId());
String echoStr = pc.verifyUrl(request.getParameter("msg_signature"), request.getParameter("timestamp"), request.getParameter("nonce"), request.getParameter("echostr"));
pw.write(echoStr);
pw.flush();
pw.close();
} catch (AesException e) {
e.printStackTrace();
pw.write("");
pw.flush();
pw.close();
}
}
/**
* 处理微信服务器发来的请求方法
*
* @param request http请求对象
* @return 处理消息的结果,已经是接口要求的XML的报文了
*/
public String processRequest(HttpServletRequest request){
Map<String, Object> reqMap = MessageUtil.parseXml(request, getToken(), getCropId(), getAESKey());
String fromUserName = (String)reqMap.get("FromUserName");
String toUserName = (String)reqMap.get("ToUserName");
String msgType = (String)reqMap.get("MsgType");
LOG.debug("收到消息,消息类型:{}", msgType);
QYBaseRespMsg msg = null;
if(msgType.equals(QYReqType.EVENT)){
String eventType = (String)reqMap.get("Event");
LOG.debug("收到消息,事件类型:{} " , eventType);
if(QYEventType.SUBSCRIBE.equalsIgnoreCase(eventType)){
QYBaseEvent event = new QYBaseEvent();
buildBasicEvent(reqMap, event);
msg = handleSubScribe(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.UNSUBSCRIBE.equalsIgnoreCase(eventType)){
QYBaseEvent event = new QYBaseEvent();
buildBasicEvent(reqMap, event);
msg = handleUnsubscribe(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.CLICK.equalsIgnoreCase(eventType)){
String eventKey = (String)reqMap.get("EventKey");
LOG.debug("eventKey:{}", eventKey);
QYMenuEvent event = new QYMenuEvent(eventKey);
buildBasicEvent(reqMap, event);
msg = handleMenuClickEvent(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.VIEW.equalsIgnoreCase(eventType)){
String eventKey = (String)reqMap.get("EventKey");
LOG.debug("eventKey:{}", eventKey);
QYMenuEvent event = new QYMenuEvent(eventKey);
buildBasicEvent(reqMap, event);
msg = handleMenuViewEvent(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.LOCATION.equalsIgnoreCase(eventType)){
double latitude = Double.parseDouble((String)reqMap.get("Latitude"));
double longitude = Double.parseDouble((String)reqMap.get("Longitude"));
double precision = Double.parseDouble((String)reqMap.get("Precision"));
QYLocationEvent event = new QYLocationEvent(latitude, longitude, precision);
buildBasicEvent(reqMap, event);
msg = handleLocationEvent(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.SCANCODEPUSH.equalsIgnoreCase(eventType) || QYEventType.SCANCODEWAITMSG.equalsIgnoreCase(eventType)){
String eventKey = (String)reqMap.get("EventKey");
Map<String, Object> scanCodeInfo = (Map<String, Object>)reqMap.get("ScanCodeInfo");
String scanType = (String)scanCodeInfo.get("ScanType");
String scanResult = (String)scanCodeInfo.get("ScanResult");
QYScanCodeEvent event = new QYScanCodeEvent(eventKey, scanType, scanResult);
buildBasicEvent(reqMap, event);
msg = handleScanCodeEvent(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.PICPHOTOORALBUM.equalsIgnoreCase(eventType) || QYEventType.PICSYSPHOTO.equalsIgnoreCase(eventType) || QYEventType.PICWEIXIN.equalsIgnoreCase(eventType)){
String eventKey = (String)reqMap.get("EventKey");
Map<String, Object> sendPicsInfo = (Map<String, Object>)reqMap.get("SendPicsInfo");
int count = Integer.parseInt((String)sendPicsInfo.get("Count"));
List<Map> picList = (List)sendPicsInfo.get("PicList");
QYSendPicInfoEvent event = new QYSendPicInfoEvent(eventKey, count, picList);
buildBasicEvent(reqMap, event);
msg = handleSendPicsInfoEvent(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.ENTERAGENT.equalsIgnoreCase(eventType)){
QYEnterAgentEvent event = new QYEnterAgentEvent();
buildBasicEvent(reqMap, event);
msg = handleEnterAgentEvent(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}else if(QYEventType.BATCHJOBRESULT.equalsIgnoreCase(eventType)){
Map<String, Object> batchJob = (Map<String, Object>)reqMap.get("BatchJob");
String jobId = (String)batchJob.get("JobId");
String jobType = (String)batchJob.get("JobType");
int errCode = (Integer)batchJob.get("ErrCode");
String errMsg = (String)batchJob.get("ErrMsg");
QYBatchJobEvent event = new QYBatchJobEvent(jobId, jobType, errCode, errMsg);
buildBasicEvent(reqMap, event);
msg = handleBatchJobEvent(event);
if(BeanUtil.isNull(msg)){
msg = processEventHandle(event);
}
}
}else{
if(QYReqType.TEXT.equalsIgnoreCase(msgType)){
String content = (String)reqMap.get("Content");
LOG.debug("文本消息内容:{}", content);
QYTextReqMsg textReqMsg = new QYTextReqMsg(content);
buildBasicReqMsg(reqMap, textReqMsg);
msg = handleTextMsg(textReqMsg);
if(BeanUtil.isNull(msg)){
msg = processMessageHandle(textReqMsg);
}
}else if(QYReqType.IMAGE.equalsIgnoreCase(msgType)){
String picUrl = (String)reqMap.get("PicUrl");
String mediaId = (String)reqMap.get("MediaId");
QYImageReqMsg imageReqMsg = new QYImageReqMsg(picUrl, mediaId);
buildBasicReqMsg(reqMap, imageReqMsg);
msg = handleImageMsg(imageReqMsg);
if(BeanUtil.isNull(msg)){
msg = processMessageHandle(imageReqMsg);
}
}else if(QYReqType.VOICE.equalsIgnoreCase(msgType)){
String format = (String)reqMap.get("Format");
String mediaId = (String)reqMap.get("MediaId");
QYVoiceReqMsg voiceReqMsg = new QYVoiceReqMsg(mediaId, format);
buildBasicReqMsg(reqMap, voiceReqMsg);
msg = handleVoiceMsg(voiceReqMsg);
if(BeanUtil.isNull(msg)){
msg = processMessageHandle(voiceReqMsg);
}
}else if(QYReqType.VIDEO.equalsIgnoreCase(msgType)){
String thumbMediaId = (String)reqMap.get("ThumbMediaId");
String mediaId = (String)reqMap.get("MediaId");
QYVideoReqMsg videoReqMsg = new QYVideoReqMsg(mediaId, thumbMediaId);
buildBasicReqMsg(reqMap, videoReqMsg);
msg = handleVideoMsg(videoReqMsg);
if(BeanUtil.isNull(msg)){
msg = processMessageHandle(videoReqMsg);
}
}else if(QYReqType.SHORT_VIDEO.equalsIgnoreCase(msgType)){
String thumbMediaId = (String)reqMap.get("ThumbMediaId");
String mediaId = (String)reqMap.get("MediaId");
QYVideoReqMsg videoReqMsg = new QYVideoReqMsg(mediaId, thumbMediaId);
buildBasicReqMsg(reqMap, videoReqMsg);
msg = handleShortVideoMsg(videoReqMsg);
if(BeanUtil.isNull(msg)){
msg = processMessageHandle(videoReqMsg);
}
}else if(QYReqType.LOCATION.equalsIgnoreCase(msgType)){
double locationX = Double.parseDouble((String) reqMap.get("Location_X"));
double locationY = Double.parseDouble((String)reqMap.get("Location_Y"));
int scale = Integer.parseInt((String)reqMap.get("Scale"));
String label = (String)reqMap.get("Label");
QYLocationReqMsg locationReqMsg = new QYLocationReqMsg(locationX, locationY, scale, label);
buildBasicReqMsg(reqMap, locationReqMsg);
msg = handleLocationMsg(locationReqMsg);
if(BeanUtil.isNull(msg)){
msg = processMessageHandle(locationReqMsg);
}
}
}
String result = "";
if(BeanUtil.nonNull(msg)){
msg.setFromUserName(toUserName);
msg.setToUserName(fromUserName);
result = msg.toXml();
try{
WXBizMsgCrypt pc = new WXBizMsgCrypt(getToken(), getAESKey(), getCropId());
result = pc.encryptMsg(result, request.getParameter("timestamp"), request.getParameter("nonce"));
LOG.debug("加密后密文:{}", result);
}catch (AesException e){
LOG.error("加密异常", e);
}
}
return result;
}
/**
* 自定义的消息事件处理
* @param msg 微信消息
* @return 响应结果
*/
private QYBaseRespMsg processMessageHandle(QYBaseReqMsg msg){
if(CollectionUtil.isEmpty(messageHandles)){
synchronized (LOCK){
messageHandles = this.initMessageHandles();
}
}
if(CollectionUtil.isNotEmpty(messageHandles)){
for(QYMessageHandle messageHandle : messageHandles){
QYBaseRespMsg resultMsg = null;
boolean result;
try{
result = messageHandle.beforeHandle(msg);
}catch (Exception e){
result = false;
}
if(result){
resultMsg = messageHandle.handle(msg);
}
if(BeanUtil.nonNull(resultMsg)){
return resultMsg;
}
}
}
return null;
}
/**
* 自定义的消息事件处理
* @param event 事件
* @return 响应结果
*/
private QYBaseRespMsg processEventHandle(QYBaseEvent event){
if(CollectionUtil.isEmpty(eventHandles)){
synchronized (LOCK){
eventHandles = this.initEventHandles();
}
}
if(CollectionUtil.isNotEmpty(eventHandles)){
for(QYEventHandle eventHandle : eventHandles){
QYBaseRespMsg resultMsg = null;
boolean result;
try{
result = eventHandle.beforeHandle(event);
}catch (Exception e){
result = false;
}
if(result){
resultMsg = eventHandle.handle(event);
}
if(BeanUtil.nonNull(resultMsg)){
return resultMsg;
}
}
}
return null;
}
/**
* 处理文本消息,有需要时子类重写
*
* @param msg 请求消息对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleTextMsg(QYTextReqMsg msg) {
return handleDefaultMsg(msg);
}
/**
* 处理图片消息,有需要时子类重写
*
* @param msg 请求消息对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleImageMsg(QYImageReqMsg msg) {
return handleDefaultMsg(msg);
}
/**
* 处理语音消息,有需要时子类重写
*
* @param msg 请求消息对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleVoiceMsg(QYVoiceReqMsg msg) {
return handleDefaultMsg(msg);
}
/**
* 处理视频消息,有需要时子类重写
*
* @param msg 请求消息对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleVideoMsg(QYVideoReqMsg msg) {
return handleDefaultMsg(msg);
}
/**
* 处理小视频消息,有需要时子类重写
*
* @param msg 请求消息对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleShortVideoMsg(QYVideoReqMsg msg) {
return handleDefaultMsg(msg);
}
/**
* 处理地理位置消息,有需要时子类重写
*
* @param msg 请求消息对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleLocationMsg(QYLocationReqMsg msg) {
return handleDefaultMsg(msg);
}
/**
* 处理地理位置事件,有需要时子类重写
*
* @param event 地理位置事件对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleLocationEvent(QYLocationEvent event) {
return handleDefaultEvent(event);
}
/**
* 处理菜单点击事件,有需要时子类重写
*
* @param event 菜单点击事件对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleMenuClickEvent(QYMenuEvent event) {
return handleDefaultEvent(event);
}
/**
* 处理菜单跳转事件,有需要时子类重写
*
* @param event 菜单跳转事件对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleMenuViewEvent(QYMenuEvent event){
return handleDefaultEvent(event);
}
/**
* 处理菜单扫描推事件,有需要时子类重写
* @param event
* @return
*/
protected QYBaseRespMsg handleScanCodeEvent(QYScanCodeEvent event){
return handleDefaultEvent(event);
}
/**
* 处理菜单弹出相册事件,有需要时子类重写
*
* @param event 菜单弹出相册事件
* @return 响应的消息对象
*/
protected QYBaseRespMsg handleSendPicsInfoEvent(QYSendPicInfoEvent event){
return handleDefaultEvent(event);
}
/**
* 处理用户进入应用事件,有需要时子类重写
*
* @param event 添加应用事件对象
* @return 响应的消息对象
*/
protected QYBaseRespMsg handleEnterAgentEvent(QYEnterAgentEvent event){
return handleDefaultEvent(event);
}
/**
* 处理异步任务通知事件,有需要时子类重写
*
* @param event 添加通知事件对象
* @return 响应的消息对象
*/
protected QYBaseRespMsg handleBatchJobEvent(QYBatchJobEvent event){
return handleDefaultEvent(event);
}
/**
* 处理添加关注事件,有需要时子类重写
*
* @param event 添加关注事件对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleSubScribe(QYBaseEvent event){
return new QYTextRespMsg("感谢您的关注");
}
/**
* 处理取消关注事件,有需要时子类重写
*
* @param event 取消关注事件对象
* @return 响应消息对象
*/
protected QYBaseRespMsg handleUnsubscribe(QYBaseEvent event){
return null;
}
protected QYBaseRespMsg handleDefaultMsg(QYBaseReqMsg msg) {
return null;
}
protected QYBaseRespMsg handleDefaultEvent(QYBaseEvent event) {
return null;
}
private void buildBasicReqMsg(Map<String, Object> reqMap, QYBaseReqMsg reqMsg) {
addBasicReqParams(reqMap, reqMsg);
reqMsg.setMsgId((String) reqMap.get("MsgId"));
}
private void buildBasicEvent(Map<String, Object> reqMap, QYBaseEvent event) {
addBasicReqParams(reqMap, event);
event.setEvent((String) reqMap.get("Event"));
}
private void addBasicReqParams(Map<String, Object> reqMap, QYBaseReq req) {
req.setMsgType((String) reqMap.get("MsgType"));
req.setFromUserName((String) reqMap.get("FromUserName"));
req.setToUserName((String) reqMap.get("ToUserName"));
req.setCreateTime(Long.parseLong((String) reqMap.get("CreateTime")));
req.setAgentId((String) reqMap.get("AgentID"));
}
}