微信消息推送
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

409 lines
18 KiB

4 years ago
package com.ynxbd.push.helper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ynxbd.push.config.IniConfig;
import com.ynxbd.push.entity.*;
import com.ynxbd.push.entity.tencent.*;
import com.ynxbd.push.httpRequest.xbd.wx.WxCacheRequest;
import com.ynxbd.push.thread.ThreadConfig;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.springframework.beans.BeanUtils;
import weixin.popular.api.API;
import weixin.popular.api.MessageAPI;
import weixin.popular.api.UserAPI;
import weixin.popular.bean.message.MessageSendResult;
import weixin.popular.bean.message.massmessage.Filter;
import weixin.popular.bean.message.massmessage.MassTextMessage;
import weixin.popular.bean.user.Group;
import weixin.popular.client.LocalHttpClient;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 所有消息发送的工厂类
*
* @Project WebApi
* @Name MessageApiHelper
* @Author 李进才
* @Date 2020/5/25
* @Version v1.0.0
* @Copyright @ 2017 云南新八达科技有限公司 All rights reserved.
*/
@Slf4j
public class MessageApiHelper {
protected static Header jsonHeader;
public static final Integer EVERY_OPENID_COUNT = 10000;
public static final String CODE_INVALIDATION = "40001";
public static final String CODE_EXPIRED = "42001";
public static final String MESSAGE_RESULT_ERROR_CODE = "4002";
/**
* 三层调用,第一层,传输SendMessageTemplate对象(减少controller代码量)
*
* @param template
* @return
*/
public static MessageSendResult templateMessageSend(SendMessageTemplate template) {
MessageSendResult messageSendResult = templateMessage(template);
log.info("TenCent Return Message" + messageSendResult.getErrcode());
return messageSendResult;
}
/**
* 三层调用第二层,转换实体对象为json对象
*
* @param sendMessageTemplate 模版数据
* @return 请求接口返回的状态码等信息
*/
public static MessageSendResult templateMessage(SendMessageTemplate sendMessageTemplate) {
try {
String jsonMessage = TemplateSelectHelper.select(sendMessageTemplate);
log.info("SendTenCentJson:{}", jsonMessage);
return templateMessageSend(jsonMessage);
} catch (Exception e) {
MessageSendResult messageSendResult = new MessageSendResult();
messageSendResult.setErrcode(MESSAGE_RESULT_ERROR_CODE);
return messageSendResult;
}
}
/**
* 三层调用最后一层,调起http发送模版消息
*
* @param messageJson json字符串
*/
private static MessageSendResult templateMessageSend(String messageJson) {
HttpUriRequest httpUriRequest = RequestBuilder.post().setHeader(jsonHeader).setUri("https://api.weixin.qq.com/cgi-bin/message/template/send").addParameter("access_token", API.accessToken(WxCacheRequest.getAccessToken())).setEntity(new StringEntity(messageJson, StandardCharsets.UTF_8)).build();
MessageSendResult result = LocalHttpClient.executeJsonResult(httpUriRequest, MessageSendResult.class);
String errCode = result.getErrcode();
if (CODE_INVALIDATION.equals(errCode) || CODE_EXPIRED.equals(errCode)) {
String errMsg = result.getErrmsg();
log.error("[推送]请求失败 errCode={}, errMsg={}", errCode, errMsg);
}
return result;
}
/**
* 群发消息给用户
*
* @param content 发送消息的内容
* @return 请求接口返回的状态码等信息
*/
public static MessageSendResult group(String content) {
try {
String accessToken = WxCacheRequest.getAccessToken();
Group group = UserAPI.groupsGet(accessToken);
String gid = group.getGroups().get(0).getId();
MassTextMessage textMessage = new MassTextMessage(content);
textMessage.setFilter(new Filter(false, gid));
return MessageAPI.messageMassSendall(accessToken, textMessage);
} catch (Exception e) {
log.info(e.getMessage());
MessageSendResult messageSendResult = new MessageSendResult();
messageSendResult.setErrcode(MESSAGE_RESULT_ERROR_CODE);
return messageSendResult;
}
}
/**
* 根据openid用客服接口发送信息,最多每次发送二十条,每当用户回复自己可以再发送二十条
*
* @param openid 用户在该微信公众号的唯一id,也就是需要发送给哪个用户
* @param content 需要发送给用户的内容
* @return 请求接口返回的状态码等信息
*/
public static MessageSendResult sendTextForOpenId(String openid, String content) {
try {
TestMessage testMessage = new TestMessage();
testMessage.setMsgtype("text");
testMessage.setTouser(openid);
Map<String, Object> map = new HashMap<>();
map.put("content", content);
testMessage.setText(map);
String jsonTestMessage = new ObjectMapper().writeValueAsString(testMessage);
return messageMassSend(jsonTestMessage);
} catch (Exception e) {
MessageSendResult messageSendResult = new MessageSendResult();
messageSendResult.setErrcode(MESSAGE_RESULT_ERROR_CODE);
return messageSendResult;
}
}
/**
* 通过客服的方式发送链接信息给用户
*
* @param openid 用户在该微信公众号的唯一id,也就是需要发送给哪个用户
* @param title 标题内容
* @param description 描述
* @param url 跳转链接
* @param thumb_url 图片链接
* @return 请求接口返回的状态码等信息
*/
public static MessageSendResult sendLinkForOpenId(String openid, String title, String description, String url, String thumb_url) {
try {
LinkMessage linkMessage = new LinkMessage();
linkMessage.setMsgtype("link");
linkMessage.setTouser(openid);
Map<String, Object> map = new HashMap<>();
map.put("title", title);
map.put("description", description);
map.put("url", url);
map.put("thumb_url", thumb_url);
linkMessage.setLink(map);
String jsonTestMessage = new ObjectMapper().writeValueAsString(linkMessage);
return messageMassSend(jsonTestMessage);
} catch (Exception e) {
MessageSendResult messageSendResult = new MessageSendResult();
messageSendResult.setErrcode(MESSAGE_RESULT_ERROR_CODE);
return messageSendResult;
}
}
/**
* 一次性订阅消息发送,因为每家都一样全部都在一个类封装好了,不像模版消息调了好多层解偶
*
* @param subscribe
* @return
* @throws Exception
*/
public static MessageSendResult subscribeMessage(Subscribe subscribe) throws JsonProcessingException {
Map<String, Object> miniProgram = new HashMap<>();
miniProgram.put("appid", subscribe.getAppid());
miniProgram.put("pagepath", subscribe.getPagepath());
Map<String, Object> data = new HashMap<>();
data.put("content", TemplateJsonValue.builder().value(subscribe.getContent()).color("#173177").build());
TemplateJson templateJson = TemplateJson.builder()
.template_id(subscribe.getTemplate_id())
.miniprogram(miniProgram)
.url(subscribe.getUrl())
.scene(subscribe.getScene())
.title(subscribe.getTitle())
.data(data)
.touser(subscribe.getTouser())
.build();
ObjectMapper objectMapper = new ObjectMapper();
String messageJson = objectMapper.writeValueAsString(templateJson);
HttpUriRequest httpUriRequest = RequestBuilder.post().setHeader(jsonHeader).setUri("https://api.weixin.qq.com/cgi-bin/message/template/subscribe").addParameter("access_token", API.accessToken(WxCacheRequest.getAccessToken())).setEntity(new StringEntity(messageJson, StandardCharsets.UTF_8)).build();
return LocalHttpClient.executeJsonResult(httpUriRequest, MessageSendResult.class);
}
/**
* 发送文本消息
*
* @param messageJson messageJson
* @return MessageSendResult
*/
private static MessageSendResult messageMassSend(String messageJson) {
HttpUriRequest httpUriRequest = RequestBuilder.post().setHeader(jsonHeader).setUri("https://api.weixin.qq.com/cgi-bin/message/custom/send").addParameter("access_token", API.accessToken(WxCacheRequest.getAccessToken())).setEntity(new StringEntity(messageJson, StandardCharsets.UTF_8)).build();
return LocalHttpClient.executeJsonResult(httpUriRequest, MessageSendResult.class);
}
/**
* 获取该公众号下所有用户的openId
*
* @param next_openid next_openid
* @return
*/
public static AllUser getAllUser(String next_openid) {
try {
HttpUriRequest httpUriRequest = RequestBuilder.get().setHeader(jsonHeader).setUri("https://api.weixin.qq.com/cgi-bin/user/get").addParameter("access_token", API.accessToken(WxCacheRequest.getAccessToken())).addParameter("next_openid", next_openid).build();
AllUser messageSendResult = LocalHttpClient.executeJsonResult(httpUriRequest, AllUser.class);
messageSendResult.setErrcode("0");
return messageSendResult;
} catch (Exception e) {
AllUser allUser = new AllUser();
allUser.setErrcode(MESSAGE_RESULT_ERROR_CODE);
return allUser;
}
}
/**
* 群发模版消息入口(包括多线程组装)
*
* @param sendMessageTemplate
* @return
* @throws Exception
*/
public static CompleteThreadCount groupTemplateSend(SendMessageTemplate sendMessageTemplate) throws IOException, InterruptedException {
final CompleteThreadCount completeThreadCount = CompleteThreadCount.builder().completed(0).failed(0).cancelled(0).build();
final OpenIdList openIdList = getAllUserList();
ThreadPoolExecutor threadPoolExecutor = ThreadConfig.getExecutor(15, 20);
log.info("所有用户总数:" + openIdList.getCount());
final CloseableHttpAsyncClient httpClient = HttpAsyncClientHelper.getHttpAsyncClient();
final CountDownLatch latch = new CountDownLatch(openIdList.getCount());
httpClient.start();
for (List<String> openIds : openIdList.getOpenIds()) {
try {
threadPoolExecutor.execute(() -> groupSendTemplateMessage(httpClient, openIds, sendMessageTemplate, completeThreadCount, latch));
} catch (Exception e) {
log.error("主线程循环过程中出现错误:{}", e.getMessage());
}
}
log.info("---执行完成-线程未停止!----完成数:{}, 失败数{}", completeThreadCount.getCompleted(), completeThreadCount.getFailed() + "取消数:" + completeThreadCount.getCancelled());
latch.await();
httpClient.close();
List<Runnable> notStopRunnable = threadPoolExecutor.shutdownNow();
for (Runnable runnable : notStopRunnable) {
log.error("未停止任务:{}", runnable.toString());
}
log.info("线程是否结束:{}, 异步是否结束:{}", threadPoolExecutor.isShutdown(), httpClient.isRunning());
log.info("---执行完成-线程已停止!!---完成数:{}, 失败数:{}", completeThreadCount.getCompleted(), completeThreadCount.getFailed() + "取消数:" + completeThreadCount.getCancelled());
return completeThreadCount;
}
/**
* 发送一组openid的模版消息(任务分包,将openid分散然后由异步发送)
*
* @param completeThreadCount
* @param openIds
* @throws Exception
*/
private static void groupSendTemplateMessage(CloseableHttpAsyncClient httpClient, List<String> openIds, SendMessageTemplate sendMessageTemplate, CompleteThreadCount completeThreadCount, CountDownLatch latch) {
//将一万条请求任务逐次加入线程
for (String openId : openIds) {
try {
SendMessageTemplate singleSendMessageTemplate = SendMessageTemplate.builder().build();
BeanUtils.copyProperties(sendMessageTemplate, singleSendMessageTemplate);
singleSendMessageTemplate.setOpenId(openId);
Thread.sleep(Integer.parseInt(IniConfig.getInstance("speed")));
asyncTemplateMessageSend(httpClient, completeThreadCount, RequestBuilder
.post()
.setHeader(jsonHeader)
.setUri("https://api.weixin.qq.com/cgi-bin/message/template/send")
.addParameter("access_token", API.accessToken(Objects.requireNonNull(WxCacheRequest.getAccessToken())))
.addParameter("time_stamp", String.valueOf(System.currentTimeMillis()))
.setEntity(new StringEntity(TemplateSelectHelper.select(singleSendMessageTemplate), StandardCharsets.UTF_8))
.build(), latch);
} catch (Exception e) {
log.error("httpClient异步线程执行中出现错误:{}", e.toString());
}
}
}
/**
* 异步发送网络请求(实际调起http的地方,异步调用)
*
* @param httpclient
* @param request
*/
private static void asyncTemplateMessageSend(final CloseableHttpAsyncClient httpclient, final CompleteThreadCount completeThreadCount, final HttpUriRequest request, final CountDownLatch latch) throws IOException {
httpclient.execute(request, new FutureCallback<HttpResponse>() {
@SneakyThrows
@Override
public void completed(HttpResponse response) {
completeThreadCount.setCompleted(completeThreadCount.getCompleted() + 1);
latch.countDown();
}
@SneakyThrows
@Override
public void failed(Exception ex) {
log.error("回调过程中出现错误:" + ex.toString());
completeThreadCount.setFailed(completeThreadCount.getFailed() + 1);
latch.countDown();
}
@SneakyThrows
@Override
public void cancelled() {
completeThreadCount.setCancelled(completeThreadCount.getCancelled() + 1);
latch.countDown();
}
});
}
/**
* 获取所有用户列表的集合(1W个用户为一组)
*
* @return
*/
private static OpenIdList getAllUserList() {
List<List<String>> openIds = new ArrayList<>();
List<String> nextOpenIds = new ArrayList<>();
AllUser userList = getAllUser(null);
String nextOpenId = userList.getNext_openid();
nextOpenIds.add(nextOpenId);
Integer count = ((List<String>) userList.getData().get("openid")).size();
openIds.add((List<String>) userList.getData().get("openid"));
for (int i = 0; i < Math.floor(userList.getTotal() / EVERY_OPENID_COUNT); i++) {
try {
if (nextOpenId == null || nextOpenId.isEmpty()) {
break;
}
userList = getAllUser(nextOpenId);
if (userList.getData().get("openid") == null) {
break;
}
nextOpenId = userList.getNext_openid();
nextOpenIds.add(nextOpenId);
count += ((List<String>) userList.getData().get("openid")).size();
openIds.add((List<String>) userList.getData().get("openid"));
} catch (Exception e) {
log.error("请求列表过程中出现错误:next_openid:" + nextOpenId + "错误:" + e.toString());
}
}
return OpenIdList.builder().openIds(openIds).count(count).build();
}
//
// /**
// * 将所有openId组装成SendMessageTemplate集合
// * @param openIdList
// * @param sendMessageTemplate
// * @return
// */
// private List<List<SendMessageTemplate>> GetAllSendMessageTemplate(OpenIdList openIdList, SendMessageTemplate sendMessageTemplate){
// List<List<SendMessageTemplate>> allSendMessageTemplate = new ArrayList<>();
// for (List<String> openList : openIdList.getOpenIds()) {
// allSendMessageTemplate.add(GetGroupSendMessageTemplate(openList,sendMessageTemplate));
// }
// return allSendMessageTemplate;
// }
//
// /**
// * 单组openId组装成SendMessageTemplate哈希集合,防止重复
// * @param groupOpenId
// * @param sendMessageTemplate
// * @return
// */
// private List<SendMessageTemplate> GetGroupSendMessageTemplate(List<String> groupOpenId, SendMessageTemplate sendMessageTemplate){
// List<SendMessageTemplate> groupSendMessageTemplate = new ArrayList<>();
// for(String openId: groupOpenId){
// SendMessageTemplate newSendMessageTemplate =SendMessageTemplate.builder().build();
// BeanUtils.copyProperties(sendMessageTemplate,newSendMessageTemplate);
// newSendMessageTemplate.setOpenId(openId);
// groupSendMessageTemplate.add(newSendMessageTemplate);
// }
// return groupSendMessageTemplate;
// }
}