微信公众号开发,微信服务需要认证服务,官方文档如下:
引入jar包如下:
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>com.github.liyiorg</groupId>
<artifactId>weixin-popular</artifactId>
<version>2.8.24</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
代码如下:
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.cetcnav.lbs.pis.util.DateUtil;
import com.cetcnav.operations.Attention.service.AttentionService;
import com.cetcnav.operations.cache.CacheManger;
import com.cetcnav.operations.entity.UserApp;
import com.cetcnav.operations.user.service.UserAppService;
import com.cetcnav.operations.util.WXUtil;
import com.cetcnav.operations.util.WeixinUtil;
import com.cetcnav.operations.util.constant.OperationsConstant;
import com.cetcnav.operations.util.constant.WXConstant;
import com.cetcnav.operations.util.properties.RouteUrlPropertiesUtils;
import weixin.popular.bean.xmlmessage.XMLImageMessage;
import weixin.popular.bean.xmlmessage.XMLMessage;
import weixin.popular.support.ExpireKey;
import weixin.popular.support.expirekey.DefaultExpireKey;
/**
* 1、验证消息的确来自微信服务器 2、接收微信服务器推送的时间信息
* @author 2020年10月14日
*/
@RestController
@RequestMapping("/rest/wxauth")
public class WXAuthWebServiceController {
private static Logger log = LoggerFactory.getLogger(WXAuthWebServiceController.class);
@Autowired
private UserAppService userAppService;
// 重复通知过滤
private static ExpireKey expireKey = new DefaultExpireKey();
@RequestMapping(value = "", method = RequestMethod.GET)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1、验证消息的确来自微信服务器
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
String token = OperationsConstant.WECHAT_TOKEN;
String jiami = "";
try {
// 这里是对三个参数进行加密
jiami = SHA1.getSHA1(token, timestamp, nonce, "");
} catch (AesException e) {
e.printStackTrace();
}
log.info("验证消息的确来自微信服务器 :加密" + jiami);
log.info("验证消息的确来自微信服务器 :本身" + signature);
if (jiami.equals(signature)) {
// 返回echostr给微信服务器
OutputStream os = response.getOutputStream();
os.write(URLEncoder.encode(echostr, "UTF-8").getBytes());
os.flush();
os.close();
}
}
@RequestMapping(value = "", method = RequestMethod.POST)
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
// 2、接收微信服务器推送的时间信息
// xml请求解析
Map<String, String> requestMap = WXUtil.parseXml(request);
if (requestMap.get("MsgType") != null) {
// 发送方帐号(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公众帐号
String toUserName = requestMap.get("ToUserName");
// 消息类型
String msgType = requestMap.get("MsgType");
// 消息创建时间 (整型)
String createTime = requestMap.get("CreateTime");
String key = fromUserName + "-" + toUserName + "-" + createTime;
// 重复通知不作处理
if (expireKey.exists(key)) {
log.info("重复通知了");
return;
}
// 事件处理开始
if (msgType.equals(WXConstant.REQ_MESSAGE_TYPE_EVENT)) {
// 事件类型
String eventType = requestMap.get("Event");
// 微信关注事件
if (eventType.equals(WXConstant.EVENT_TYPE_SUBSCRIBE)) {
String unionid = CacheManger.getUnionId(fromUserName);
if (StringUtils.isBlank(unionid)) {
log.error("公众号关注,unionid获取为空。");
return;
}
// 关注时触发 * 累加关注积分
attentionService.attentionInterface(unionid);
UserApp userApp = userAppService.getUserAppByUnionIdAndType(unionid,
OperationsConstant.APP_TYPE_OPERATIONS);
if (userApp == null) {
// userapp里存储公众号账号信息
UserApp ua = new UserApp();
ua.setApptype(OperationsConstant.APP_TYPE_OPERATIONS);
ua.setOpenid(fromUserName);
ua.setUnionId(unionid);
ua.setUpdatetime(DateUtil.getCurrentTime());
ua.setDeleted(false);
userAppService.add(ua);
}
// XMLMessage xmlTextMessage = new
// XMLTextMessage(fromUserName,toUserName,"积分兑换流程如下:\n");
// String textMessage = xmlTextMessage.toXML();
// out.print(textMessage);
// 调用客服接口 客服接口-发消息
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN"
.replace("ACCESS_TOKEN", CacheManger.ACCESS_TOKEN);
String json = "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"DATA\"}}"
.replace("OPENID", fromUserName)
.replace("DATA", "欢迎关注图:\n");
JSONObject jsonStr = WeixinUtil.httpRequest(url, "POST", json);
log.info(jsonStr.toString());
XMLMessage xmlImageMessage = new XMLImageMessage(fromUserName, toUserName,
RouteUrlPropertiesUtils.getString("attention.image.id"));
String imageMessage = xmlImageMessage.toXML();
out.print(imageMessage);
} else if (eventType.equals(WXConstant.EVENT_TYPE_UNSUBSCRIBE)) {
// 取消关注,用户接受不到我们发送的消息了,可以在这里记录用户取消关注的日志信息
}
}
}
out.close();
}
}
微信推送信息类型:
public class WXConstant {
/**
* 请求消息类型:事件
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
* 请求消息类型:事件
*/
public static final String REQ_MESSAGE_TYPE_TEXT = "text";
/**
* 事件类型:subscribe(关注)
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
* 事件类型:unsubscribe(取消关注)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
* 事件类型:CLICK(自定义菜单点击事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
public static final String MESSAGE_SCAN="SCAN";//未关注公众号扫描二维码
public static final String MESSAGE_TEXT="text";//文本
public static final String MESSAGE_IMAGE="image";//图片
public static final String MESSAGE_VOICE="voice";//语音
public static final String MESSAGE_VIDEO="video";//视频
public static final String MESSAGE_LINK="link";//连接
public static final String MESSAGE_LOCATION="location";//地理位置事件
public static final String MESSAGE_EVENT="event";//事件
public static final String MESSAGE_SUBSCRIBE="subscribe";//关注
public static final String MESSAGE_UNSUBSCRIBE="unsubscribe";//取消关注
public static final String MESSAGE_CLICK="CLICK";//点击
public static final String MESSAGE_VIEW="VIEW";//t跳转链接url
}
SHA1加密方法:
import java.security.MessageDigest;
import java.util.Arrays;
/**
* SHA1 class
*
* 计算公众平台的消息签名接口.
*/
public class SHA1 {
/**
* 用SHA1算法生成安全签名
*
* @param token
* 票据
* @param timestamp
* 时间戳
* @param nonce
* 随机字符串
* @param encrypt
* 密文
* @return 安全签名
* @throws AesException
*/
public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException {
try {
String[] array = new String[] { token, timestamp, nonce, encrypt };
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < 4; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.ComputeSignatureError);
}
}
}
AesException
@SuppressWarnings("serial")
public class AesException extends Exception {
public final static int OK = 0;
public final static int ValidateSignatureError = -40001;
public final static int ParseXmlError = -40002;
public final static int ComputeSignatureError = -40003;
public final static int IllegalAesKey = -40004;
public final static int ValidateAppidError = -40005;
public final static int EncryptAESError = -40006;
public final static int DecryptAESError = -40007;
public final static int IllegalBuffer = -40008;
// public final static int EncodeBase64Error = -40009;
// public final static int DecodeBase64Error = -40010;
// public final static int GenReturnXmlError = -40011;
private int code;
private static String getMessage(int code) {
switch (code) {
case ValidateSignatureError:
return "签名验证错误";
case ParseXmlError:
return "xml解析失败";
case ComputeSignatureError:
return "sha加密生成签名失败";
case IllegalAesKey:
return "SymmetricKey非法";
case ValidateAppidError:
return "appid校验失败";
case EncryptAESError:
return "aes加密失败";
case DecryptAESError:
return "aes解密失败";
case IllegalBuffer:
return "解密后得到的buffer非法";
// case EncodeBase64Error:
// return "base64加密错误";
// case DecodeBase64Error:
// return "base64解密错误";
// case GenReturnXmlError:
// return "xml生成失败";
default:
return null; // cannot be
}
}
public int getCode() {
return code;
}
AesException(int code) {
super(getMessage(code));
this.code = code;
}
}