Commit fd722a05 authored by shiyu's avatar shiyu

公众号

parent 1223084e
package com.wwdz.ch.core.api;
import com.wwdz.ch.core.entity.ConsignSaleSubscribeMsg;
import com.wwdz.ch.core.type.Result;
import com.wwdz.ch.core.util.OkHttpUtil;
import com.wwdz.ch.core.util.RedisUtils;
import com.xxdxxs.utils.JsonUtils;
import com.xxdxxs.utils.StringUtils;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.MessageDigest;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 微信公众号接口
*/
@Component
public class QdOfficialAccountApi {
private static final Logger logger = LoggerFactory.getLogger(QdOfficialAccountApi.class);
private final static String GET_USER_BASE_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?lang=zh_CN";
private final static String SEND_MSG_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send";
private final static String GET_JSAPI_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
/**
* 测试环境和正式环境的url不同,相互隔离不会冲突
*/
@Value("${dts.qdwx.get-officialaccount-accesstoken-url}")
private String GET_OFFICIAL_ACCOUNT_ACCESSTOKEN_URL;
@Value("${dts.qdwx.officialaccount-appid}")
private String OFFICIAL_ACCOUNT_APPID;
@Value("${dts.qdwx.officialaccount-secret}")
private String OFFICIAL_ACCOUNT_APPSECRET;
@Value("${dts.qdwx.officialaccount-templetId}")
private String OFFICIALACCOUNT_TEMPLETID;
@Value("${dts.qdwx.app-id}")
private String APPLET_APPID;
@Value("${dts.qdwx.msg-url}")
private String MSG_URL;
@Autowired
RedisUtils redisUtils;
/* @Autowired
WxLoginManager wxLoginManager;*/
public String getAccessToken() {
//测试环境
if (!GET_OFFICIAL_ACCOUNT_ACCESSTOKEN_URL.contains("stable_token")) {
String accessTokenUrl = String.format(GET_OFFICIAL_ACCOUNT_ACCESSTOKEN_URL, OFFICIAL_ACCOUNT_APPID, OFFICIAL_ACCOUNT_APPSECRET);
String res = OkHttpUtil.builder().url(accessTokenUrl)
.get().sync();
logger.info("测试环境获取token返回结果: {}", res);
String token = JsonUtils.getValueByPath(res, "access_token");
return token;
}
//正式环境先从缓存中获取,没有再刷新token
//根据商户的appid从redis中获取商户的token
String token = redisUtils.hasKey(OFFICIAL_ACCOUNT_APPID)? redisUtils.get(OFFICIAL_ACCOUNT_APPID).toString() : null;
if (StringUtils.isEmpty(token)) {
token = refreshAccessToken();
}
return token;
}
public String refreshAccessToken() {
try {
String accessTokenUrl = null;
String responseStr = null;
//正式环境的url中包含了"stable_token", 要使用post请求, 测试环境是get请求
if (!GET_OFFICIAL_ACCOUNT_ACCESSTOKEN_URL.contains("stable_token")) {
accessTokenUrl = String.format(GET_OFFICIAL_ACCOUNT_ACCESSTOKEN_URL, OFFICIAL_ACCOUNT_APPID, OFFICIAL_ACCOUNT_APPSECRET);
logger.info("测试环境获取公众号access_token url :{}", accessTokenUrl);
responseStr = OkHttpUtil.builder().url(accessTokenUrl)
.get().sync();
} else {
accessTokenUrl = GET_OFFICIAL_ACCOUNT_ACCESSTOKEN_URL;
logger.info("正式环境获取公众号access_token url :{}", accessTokenUrl);
Map<String, Object> map = new HashMap<>();
map.put("grant_type", "client_credential");
map.put("appid", OFFICIAL_ACCOUNT_APPID);
map.put("secret", OFFICIAL_ACCOUNT_APPSECRET);
responseStr = OkHttpUtil.builder().addParams(map).url(accessTokenUrl)
.post(true).sync();
}
logger.info("公众号刷新access_token response :{}", responseStr);
if (!responseStr.contains("access_token")) {
String errorCode = JsonUtils.getValueByPath(responseStr, "errcode");
String errmsg = JsonUtils.getValueByPath(responseStr, "errmsg");
logger.error("appid : {}, 公众号获取token出错, errorCode : {}, errmsg : {} ", OFFICIAL_ACCOUNT_APPID, errorCode, errmsg);
return null;
}
String token = JsonUtils.getValueByPath(responseStr, "access_token");
String expires_in = JsonUtils.getValueByPath(responseStr, "expires_in");
redisUtils.set(OFFICIAL_ACCOUNT_APPID, token, Long.valueOf(expires_in) - 10, TimeUnit.SECONDS);
return token;
} catch (Exception e) {
logger.error("获取公众号access_token失败", e);
return null;
}
}
/**
* 返回unionid
* @param openid
* @return
*/
public String getUnionidByOpenid(String openid) {
String token = getAccessToken();
String url = GET_USER_BASE_INFO_URL + "&access_token=" + token + "&openid=" + openid;
OkHttpUtil okHttpUtil = OkHttpUtil.builder().url(url);
okHttpUtil.get();
String responseStr = okHttpUtil.async();
logger.info("openid : {}, 获取用户基本信息 response :{}", openid, responseStr);
if (!responseStr.contains("unionid")) {
String errorCode = JsonUtils.getValueByPath(responseStr, "errcode");
String errmsg = JsonUtils.getValueByPath(responseStr, "errmsg");
//token失效
if (("40001").equals(errorCode)) {
refreshAccessToken();
}
logger.error("openid : {}, 获取用户基本信息, errorCode : {}, errmsg : {} ", openid, errorCode, errmsg);
return null;
}
String unionid = JsonUtils.getValueByPath(responseStr, "unionid");
logger.info("openid : {}, 对应的 unionid:{}", openid, unionid);
return unionid;
}
public String getJsapiTicket() {
String key = OFFICIAL_ACCOUNT_APPID + "_jsapi_ticket";
String ticket = redisUtils.hasKey(key)? redisUtils.get(key).toString() : null;
if (StringUtils.isEmpty(ticket)) {
String token = getAccessToken();
String url = GET_JSAPI_TICKET + "?access_token=" + token + "&type=jsapi";
OkHttpUtil okHttpUtil = OkHttpUtil.builder().url(url);
okHttpUtil.get();
String responseStr = okHttpUtil.async();
logger.info("获取JsapiTicket接口返回原文 :{}", responseStr);
if (!responseStr.contains("ticket")) {
String errorCode = JsonUtils.getValueByPath(responseStr, "errcode");
String errmsg = JsonUtils.getValueByPath(responseStr, "errmsg");
//token失效
if (("40001").equals(errorCode)) {
refreshAccessToken();
}
logger.error(">>>>>>>> 获取JsapiTicket失败: {} ", errmsg);
} else {
ticket = JsonUtils.getValueByPath(responseStr, "ticket");
String expires_in = JsonUtils.getValueByPath(responseStr, "expires_in");
redisUtils.set(key, ticket, Long.valueOf(expires_in) - 10, TimeUnit.SECONDS);
}
}
return ticket;
}
/**
* 随机加密
* @param hash
* @return
*/
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
/**
* 产生随机串--由程序自己随机产生
* @return
*/
private String create_nonce_str() {
return UUID.randomUUID().toString();
}
/**
* 由程序自己获取当前时间
* @return
*/
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
......@@ -9,6 +9,14 @@ dts:
apiV3Key: 1231234
pay-notify-url: 12334
#乾岛公众号配置
officialaccount—appid: wx481eb2d1500b8d0c
officialaccount-secret: 91d904dbe7cf56d3d716f46ed9743d8e
officialaccount-token: aabbcceeffqqtt
officialaccount-key: lkPUPk84L0EfqdUjTepT7p9TFWVxcfln3chgr8ywSV6
get-officialaccount-accesstoken-url: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s
# 开发者应该设置成自己的wx相关信息
# 更换主体要更换id和secret
wx:
......
......@@ -10,6 +10,14 @@ dts:
apiV3Key: 123123
pay-notify-url: 123
#乾岛公众号配置
officialaccount—appid: wx481eb2d1500b8d0c
officialaccount-secret: 91d904dbe7cf56d3d716f46ed9743d8e
officialaccount-token: aabbcceeffqqtt
officialaccount-key: lkPUPk84L0EfqdUjTepT7p9TFWVxcfln3chgr8ywSV6
get-officialaccount-accesstoken-url: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s
wx:
app-id: wx5b4409448bf2c72b
app-secret: 6fee0d7b2197845fed0a3fe8f5a9bd66
......
......@@ -42,7 +42,6 @@ public class OfficialAccountCallbackController {
//一次性订阅消息
private final static String SUBSCRIBE_MSG_POPUP_EVENT = "subscribe_msg_popup_event";
@Value("${dts.wx.officialaccount-token}")
private String OFFICIALACCOUNTTOKEN;
......
package com.wwdz.ch.wx.api;
import com.wwdz.ch.core.api.QdOfficialAccountApi;
import com.wwdz.ch.core.consts.OfficalAccountEnum;
import com.wwdz.ch.core.entity.ConsignSaleSubscribeMsg;
import com.wwdz.ch.core.type.Result;
import com.wwdz.ch.db.dao.qiandao.QdUserDao;
import com.wwdz.ch.db.domain.OfficialAccountSubscribeRecord;
import com.wwdz.ch.db.domain.qiandao.QdUser;
import com.wwdz.ch.wx.api.aes.AesException;
import com.wwdz.ch.wx.api.aes.WXBizMsgCrypt;
import com.wwdz.ch.wx.service.OfficialAccountSubscribeRecordService;
import com.xxdxxs.utils.StringUtils;
import com.xxdxxs.utils.XmlUtils;
import io.swagger.annotations.ApiOperation;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/QdOfficialAccountCallback")
public class QdOfficialAccountCallbackController {
private static final Logger logger = LoggerFactory.getLogger(QdOfficialAccountCallbackController.class);
private final static String SUBSCRIBE_EVENT = "subscribe";
private final static String UNSUBSCRIBE_EVENT = "unsubscribe";
//一次性订阅消息
private final static String SUBSCRIBE_MSG_POPUP_EVENT = "subscribe_msg_popup_event";
@Value("${dts.qdwx.officialaccount-token}")
private String OFFICIALACCOUNTTOKEN;
@Value("${dts.qdwx.officialaccount-key}")
private String OFFICIALACCOUNTKEY;
@Value("${dts.qdwx.officialaccount-appid}")
private String APPID;
@Value("${dts.qdwx.officialaccount-secret}")
private String APPSECRET;
@Autowired
QdOfficialAccountApi qdOfficialAccountApi;
@Autowired
OfficialAccountSubscribeRecordService officialAccountSubscribeRecordService;
@Autowired
QdUserDao qdUserDao;
@RequestMapping(value ="/getNotice")
public Object getNotice(@RequestParam(required = false, name = "signature") String signature,
@RequestParam(required = false, name = "timestamp") String timestamp,
@RequestParam(required = false, name = "nonce") String nonce,
@RequestParam(required = false, name = "echostr") String echostr,
HttpServletRequest request, HttpServletResponse response){
if (StringUtils.hasLength(echostr)) {
return Long.valueOf(echostr.trim());
}
ByteArrayOutputStream output = null;
InputStream input = null;
try {
logger.info("接收公众号推送信息校验 -> appid: {}, signature:{}, timestamp:{}, nonce:{}, echostr : {}", APPID, signature, timestamp, nonce, echostr);
output = new ByteArrayOutputStream();
input = request.getInputStream();
byte[] by = new byte[1024];
int length = 0;
while((length = input.read(by)) != -1){
output.write(by, 0, length);
}
String xmlInfo = new String(output.toByteArray(), "UTF-8");
logger.info("微信公众号通知回调接口原文:{}", xmlInfo);
WXBizMsgCrypt wxBizMsgCrypt = new WXBizMsgCrypt(OFFICIALACCOUNTTOKEN, OFFICIALACCOUNTKEY, APPID);
String xmlStr = wxBizMsgCrypt.decryptXmlMsg(signature, timestamp, nonce, xmlInfo);
logger.info("微信公众号通知回调接口解密后内容:{}", xmlInfo);
List<Map<String, String>> list = XmlUtils.fromXml(xmlStr, "xml");
Map<String, String> contentMap = list.get(0);
String event = contentMap.get("Event");
if (SUBSCRIBE_EVENT.equals(event) || UNSUBSCRIBE_EVENT.equals(event)) {
String userOpenId = contentMap.get("FromUserName");
String createTime = contentMap.get("CreateTime");
Date date = new Date(Long.valueOf(createTime) * 1000L);
logger.info("解析xml得到 openId:{}, event :{}, time:{}", userOpenId, event, date);
//新增或更新订阅记录
OfficialAccountSubscribeRecord officialAccountSubscribeRecord = new OfficialAccountSubscribeRecord();
officialAccountSubscribeRecord.setOpenid(userOpenId);
if (SUBSCRIBE_EVENT.equals(event)) {
//只有关注了公众号,才可以通过根据openid获取unionid
String unionid = qdOfficialAccountApi.getUnionidByOpenid(userOpenId);
//根据uniodid查询手机号
QdUser user = qdUserDao.getUserByUnionId(unionid);
if (user != null) {
logger.info("根据unionid查询手机号, unionid :{}, phone : {}", unionid, user.getMobile());
officialAccountSubscribeRecord.setPhone(user.getMobile());
}
officialAccountSubscribeRecord.setUnionid(unionid);
officialAccountSubscribeRecord.setEnabled(1);
} else {
officialAccountSubscribeRecord.setEnabled(0);
}
//查询有无历史订阅记录
boolean isExisted = officialAccountSubscribeRecordService.isExisted(userOpenId);
if (isExisted) {
officialAccountSubscribeRecord.setUpdateTime(date);
officialAccountSubscribeRecordService.update(officialAccountSubscribeRecord);
} else {
//没有历史订阅记录
officialAccountSubscribeRecord.setCreateTime(date);
officialAccountSubscribeRecord.setUpdateTime(date);
officialAccountSubscribeRecord.setPlatformType(OfficalAccountEnum.PlatformTypeEnum.WECHAT.getCode());
officialAccountSubscribeRecordService.insert(officialAccountSubscribeRecord);
}
logger.info("=============== 更新订阅公众号记录成功 ==============");
} else if (SUBSCRIBE_MSG_POPUP_EVENT.equals(event)) {
//一次性订阅消息
}
} catch (Exception e) {
logger.error("微信公众号通知回调接口 error : {}", e);
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return "success";
}
public static void main(String[] args) throws AesException, DocumentException {
String xml = "<xml>\n" +
"<ToUserName><![CDATA[gh_075dfbc935ef]]></ToUserName>\n" +
"<Encrypt><![CDATA[tTfKN0RMPKKG1mvpwYS6aEt7FkHk+wgknt/y8ePwEwkKKuRxe9RV0ClGVtxDrJ35Ws12VGzAoQV3UB6oCQX+4NfjZBp2x/SRimCZrb9UVjL14PfMLEahELFFXgX7xvZP3RWJFSZHVXM1cizIhOGsffz0L9G0ohxmGx8NjZZZyJodocsThzdspiWHeQzsOOKB5g6czU4RdYWkrzVMGo/haPLHnxPps+ozBYaPp2Atqm8iFWRjs377FIWFjLbwnVUwdFE7PLkNi9JBsQzBakfRMui6IcjjStM3zyZQXRIpnGory5aaEbPts2iDaYCI+Pf7WPS+bo4Jp04KyDmxMOEf6iWP43VJ5tsrtGKpP2pFmnwZ9agTSNn6EPZocnLBIX6PUWBJSMi3j2PxJ93I+8rA2F+d63CmhNIEi9CXLFMZSSg=]]></Encrypt>\n" +
"</xml>";
WXBizMsgCrypt wxBizMsgCrypt = new WXBizMsgCrypt("Uu6GHYOiP1xJ6GaBMyXfnITufAGJTe3Q", "noeN1amYRvzrVmE2GnC5VnsIfgPEmv6bhX54wJOARlM", "wx7db8eb146a194083");
String xmlStr = wxBizMsgCrypt.decryptXmlMsg("150532dfbcfdaace820c0e98e34e919193fcca87", "1697175880", "2012082450", xml);
System.out.println(">>>>" +xmlStr);
List<Map<String, String>> list = XmlUtils.fromXml(xmlStr, "xml");
Map<String, String> contentMap = list.get(0);
System.out.println(contentMap.get("Event") + ">>" + contentMap.get("FromUserName") + ">>" + contentMap.get("CreateTime"));
String event = contentMap.get("Event");
if (SUBSCRIBE_EVENT.equals(event) || UNSUBSCRIBE_EVENT.equals(event)) {
String userOpenId = contentMap.get("FromUserName");
String createTime = contentMap.get("CreateTime");
Date date = new Date(Long.valueOf(createTime) * 1000L);
System.out.println(userOpenId + ">>" + event + ">>" + date);
}
}
@RequestMapping(value ="/getToken")
public String getToken(){
String token = null;
try {
token = qdOfficialAccountApi.getAccessToken();
} catch (Exception e) {
logger.error("微信通知回调接口 error : {}", e);
}
return token;
}
}
......@@ -16,7 +16,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment