企业微信 应用消息 增加撤回功能;

pull/57/head
3y 7 months ago
parent 5e22aad354
commit 857a798657

@ -55,9 +55,11 @@ public class CommonConstant {
public static final String EMPTY_JSON_OBJECT = "{}";
public static final String EMPTY_VALUE_JSON_ARRAY = "[]";
/**
* cron
*
*/
public static final String CRON_FORMAT = "ss mm HH dd MM ? yyyy-yyyy";
public static final Long ONE_DAY_SECOND = 86400L;
/**
*
*/

@ -24,6 +24,7 @@ public class RecallTaskInfo {
/**
* ids
* idsdis
*/
private List<String> recallMessageId;

@ -1,5 +1,6 @@
package com.java3y.austin.handler.handler;
import cn.hutool.core.date.DateUtil;
import com.java3y.austin.common.domain.AnchorInfo;
import com.java3y.austin.common.domain.TaskInfo;
import com.java3y.austin.common.enums.AnchorState;
@ -7,9 +8,11 @@ import com.java3y.austin.handler.flowcontrol.FlowControlFactory;
import com.java3y.austin.handler.flowcontrol.FlowControlParam;
import com.java3y.austin.support.utils.LogUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.PostConstruct;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author 3y
@ -32,6 +35,8 @@ public abstract class BaseHandler implements Handler {
private LogUtils logUtils;
@Autowired
private FlowControlFactory flowControlFactory;
@Autowired
private StringRedisTemplate redisTemplate;
/**
* Handler
@ -65,4 +70,20 @@ public abstract class BaseHandler implements Handler {
public abstract boolean handler(TaskInfo taskInfo);
/**
* redis
*
* @param prefix redis
* @param messageTemplateId id
* @param taskId taskId
* @param expireTime redis)
*/
protected void saveRecallInfo(String prefix, Long messageTemplateId, String taskId, Long expireTime) {
redisTemplate.opsForList().leftPush(prefix + messageTemplateId, taskId);
redisTemplate.opsForValue().set(prefix + taskId, taskId);
redisTemplate.expire(prefix + messageTemplateId, expireTime, TimeUnit.SECONDS);
redisTemplate.expire(prefix + taskId, expireTime, TimeUnit.SECONDS);
}
}

@ -32,7 +32,7 @@ import java.util.List;
/**
*
*
* https://open.dingtalk.com/document/group/custom-robot-access
* @author 3y
*/
@Slf4j
@ -137,6 +137,11 @@ public class DingDingRobotHandler extends BaseHandler implements Handler {
}
/**
*
* https://open.dingtalk.com/document/group/custom-robot-access
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {

@ -1,7 +1,6 @@
package com.java3y.austin.handler.handler.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.StrPool;
import com.alibaba.fastjson.JSON;
import com.dingtalk.api.DefaultDingTalkClient;
@ -14,6 +13,7 @@ import com.dingtalk.api.response.OapiMessageCorpconversationGetsendresultRespons
import com.dingtalk.api.response.OapiMessageCorpconversationRecallResponse;
import com.google.common.base.Throwables;
import com.java3y.austin.common.constant.AustinConstant;
import com.java3y.austin.common.constant.CommonConstant;
import com.java3y.austin.common.constant.SendChanelUrlConstant;
import com.java3y.austin.common.domain.LogParam;
import com.java3y.austin.common.domain.RecallTaskInfo;
@ -35,13 +35,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
*
*
* <p>
* https://open.dingtalk.com/document/group/custom-robot-access
* https://open.dingtalk.com/document/orgapp/asynchronous-sending-of-enterprise-session-messages
*
* @author 3y
*/
@ -50,8 +47,8 @@ import java.util.concurrent.TimeUnit;
public class DingDingWorkNoticeHandler extends BaseHandler implements Handler {
private static final String DING_DING_RECALL_KEY_PREFIX = "RECALL_";
private static final String RECALL_BIZ_TYPE = "DingDingWorkNoticeHandler#recall";
private static final String DING_DING_RECALL_KEY_PREFIX = "DING_RECALL_";
private static final String DING_DING_RECALL_BIZ_TYPE = "DingDingWorkNoticeHandler#recall";
@Autowired
private AccountUtils accountUtils;
@Autowired
@ -74,16 +71,11 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler {
OapiMessageCorpconversationAsyncsendV2Request request = assembleParam(account, taskInfo);
OapiMessageCorpconversationAsyncsendV2Response response = new DefaultDingTalkClient(SendChanelUrlConstant.DING_DING_SEND_URL).execute(request, accessToken);
// 发送成功后记录TaskId用于消息撤回(支持当天的)
if (response.getErrcode() == 0) {
redisTemplate.opsForList().leftPush(DING_DING_RECALL_KEY_PREFIX + taskInfo.getMessageTemplateId(), String.valueOf(response.getTaskId()));
redisTemplate.opsForValue().set(DING_DING_RECALL_KEY_PREFIX + taskInfo.getMessageId(), String.valueOf(response.getTaskId()));
redisTemplate.expire(DING_DING_RECALL_KEY_PREFIX + taskInfo.getMessageTemplateId(), (DateUtil.endOfDay(new Date()).getTime() - DateUtil.current()) / 1000, TimeUnit.SECONDS);
redisTemplate.expire(DING_DING_RECALL_KEY_PREFIX + taskInfo.getMessageId(), (DateUtil.endOfDay(new Date()).getTime() - DateUtil.current()) / 1000, TimeUnit.SECONDS);
// 发送成功后记录TaskId用于消息撤回(支持24小时之内)
if (response.isSuccess()) {
saveRecallInfo(DING_DING_RECALL_KEY_PREFIX, taskInfo.getMessageTemplateId(), String.valueOf(response.getTaskId()), CommonConstant.ONE_DAY_SECOND);
return true;
}
// 常见的错误 应当 关联至 AnchorState,由austin后台统一透出失败原因
log.error("DingDingWorkNoticeHandler#handler fail!result:{},params:{}", JSON.toJSONString(response), JSON.toJSONString(taskInfo));
} catch (Exception e) {
log.error("DingDingWorkNoticeHandler#handler fail!{},params:{}", Throwables.getStackTraceAsString(e), taskInfo);
@ -176,6 +168,9 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler {
/**
*
*
*
* @param accountId
*/
public void pull(Long accountId) {
try {
@ -205,19 +200,20 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler {
public void recall(RecallTaskInfo recallTaskInfo) {
SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> {
try {
DingTalkClient client = new DefaultDingTalkClient(SendChanelUrlConstant.DING_DING_RECALL_URL);
DingDingWorkNoticeAccount account = accountUtils.getAccountById(recallTaskInfo.getSendAccount(), DingDingWorkNoticeAccount.class);
String accessToken = accessTokenUtils.getAccessToken(recallTaskInfo.getSendChannel(), recallTaskInfo.getSendAccount(), account, false);
// 优先去除messageId如果未传入messageId则按照模板id去除
// 优先撤回messageId如果未传入messageId则按照模板id撤回
if (CollUtil.isNotEmpty(recallTaskInfo.getRecallMessageId())) {
for (String messageId : recallTaskInfo.getRecallMessageId()) {
String taskId = redisTemplate.opsForValue().get(DING_DING_RECALL_KEY_PREFIX + messageId);
recallBiz(account, accessToken, taskId);
recallBiz(client, account, accessToken, taskId);
}
} else {
while (redisTemplate.opsForList().size(DING_DING_RECALL_KEY_PREFIX + recallTaskInfo.getMessageTemplateId()) > 0) {
String taskId = redisTemplate.opsForList().leftPop(DING_DING_RECALL_KEY_PREFIX + recallTaskInfo.getMessageTemplateId());
recallBiz(account, accessToken, taskId);
recallBiz(client, account, accessToken, taskId);
}
}
} catch (Exception e) {
@ -229,18 +225,18 @@ public class DingDingWorkNoticeHandler extends BaseHandler implements Handler {
/**
* api
*
* @param client
* @param account
* @param accessToken
* @param taskId
* @throws ApiException
*/
private void recallBiz(DingDingWorkNoticeAccount account, String accessToken, String taskId) throws ApiException {
DingTalkClient client = new DefaultDingTalkClient(SendChanelUrlConstant.DING_DING_RECALL_URL);
private void recallBiz(DingTalkClient client, DingDingWorkNoticeAccount account, String accessToken, String taskId) throws ApiException {
OapiMessageCorpconversationRecallRequest req = new OapiMessageCorpconversationRecallRequest();
req.setAgentId(Long.valueOf(account.getAgentId()));
req.setMsgTaskId(Long.valueOf(taskId));
OapiMessageCorpconversationRecallResponse rsp = client.execute(req, accessToken);
logUtils.print(LogParam.builder().bizType(RECALL_BIZ_TYPE).object(JSON.toJSONString(rsp)).build());
logUtils.print(LogParam.builder().bizType(DING_DING_RECALL_BIZ_TYPE).object(JSON.toJSONString(rsp)).build());
}
}

@ -91,7 +91,10 @@ public class EmailHandler extends BaseHandler implements Handler {
return account;
}
/**
* api
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {

@ -13,6 +13,7 @@ import com.java3y.austin.common.enums.ChannelType;
import com.java3y.austin.common.enums.SendMessageType;
import com.java3y.austin.handler.handler.BaseHandler;
import com.java3y.austin.handler.handler.Handler;
import com.java3y.austin.support.config.SupportThreadPoolConfig;
import com.java3y.austin.support.utils.AccountUtils;
import com.java3y.austin.support.utils.LogUtils;
import lombok.extern.slf4j.Slf4j;
@ -27,6 +28,7 @@ import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@ -35,15 +37,20 @@ import java.util.Map;
/**
* @author 3y
*
* https://developer.work.weixin.qq.com/document/path/90235
*/
@Component
@Slf4j
public class EnterpriseWeChatHandler extends BaseHandler implements Handler {
private static final String WE_CHAT_RECALL_KEY_PREFIX = "WECHAT_RECALL_";
private static final String WE_CHAT_RECALL_BIZ_TYPE = "EnterpriseWeChatHandler#recall";
@Autowired
private AccountUtils accountUtils;
@Autowired
private LogUtils logUtils;
@Autowired
private StringRedisTemplate redisTemplate;
public EnterpriseWeChatHandler() {
channelCode = ChannelType.ENTERPRISE_WE_CHAT.getCode();
@ -55,11 +62,13 @@ public class EnterpriseWeChatHandler extends BaseHandler implements Handler {
WxCpDefaultConfigImpl accountConfig = accountUtils.getAccountById(taskInfo.getSendAccount(), WxCpDefaultConfigImpl.class);
WxCpMessageServiceImpl messageService = new WxCpMessageServiceImpl(initService(accountConfig));
WxCpMessageSendResult result = messageService.send(buildWxCpMessage(taskInfo, accountConfig.getAgentId()));
// 发送成功后记录TaskId用于消息撤回(支持24小时之内)
if (Integer.valueOf(WxCpErrorMsgEnum.CODE_0.getCode()).equals(result.getErrCode())) {
saveRecallInfo(WE_CHAT_RECALL_KEY_PREFIX, taskInfo.getMessageTemplateId(), String.valueOf(result.getMsgId()), CommonConstant.ONE_DAY_SECOND);
return true;
}
logUtils.print(AnchorInfo.builder().bizId(taskInfo.getBizId()).messageId(taskInfo.getMessageId()).businessId(taskInfo.getBusinessId())
.ids(taskInfo.getReceiver()).state(result.getErrCode()).build());
logUtils.print(AnchorInfo.builder().bizId(taskInfo.getBizId()).messageId(taskInfo.getMessageId()).businessId(taskInfo.getBusinessId()).ids(taskInfo.getReceiver()).state(result.getErrCode()).build());
} catch (Exception e) {
log.error("EnterpriseWeChatHandler#handler fail:{},params:{}",
Throwables.getStackTraceAsString(e), JSON.toJSONString(taskInfo));
@ -98,7 +107,6 @@ public class EnterpriseWeChatHandler extends BaseHandler implements Handler {
// 通用配置
WxCpMessage wxCpMessage = new WxCpMessage();
if (SendMessageType.TEXT.getCode().equals(contentModel.getSendType())) {
wxCpMessage = WxCpMessage.TEXT().content(contentModel.getContent()).build();
} else if (SendMessageType.IMAGE.getCode().equals(contentModel.getSendType())) {
@ -123,7 +131,7 @@ public class EnterpriseWeChatHandler extends BaseHandler implements Handler {
Map contentItems = JSON.parseObject(contentModel.getContentItems(), Map.class);
wxCpMessage = WxCpMessage.newMiniProgramNoticeBuilder().appId(contentModel.getAppId()).page(contentModel.getPage()).emphasisFirstItem(contentModel.getEmphasisFirstItem()).contentItems(contentItems).title(contentModel.getTitle()).description(contentModel.getDescription()).build();
} else if (SendMessageType.TEMPLATE_CARD.getCode().equals(contentModel.getSendType())) {
// WxJava 未支持
}
wxCpMessage.setAgentId(agentId);
@ -132,9 +140,35 @@ public class EnterpriseWeChatHandler extends BaseHandler implements Handler {
}
/**
*
* https://developer.work.weixin.qq.com/document/path/94867
*
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {
SupportThreadPoolConfig.getPendingSingleThreadPool().execute(() -> {
try {
WxCpDefaultConfigImpl accountConfig = accountUtils.getAccountById(recallTaskInfo.getSendAccount(), WxCpDefaultConfigImpl.class);
WxCpMessageServiceImpl messageService = new WxCpMessageServiceImpl(initService(accountConfig));
// 优先撤回messageId如果未传入messageId则按照模板id撤回
if (CollUtil.isNotEmpty(recallTaskInfo.getRecallMessageId())) {
for (String messageId : recallTaskInfo.getRecallMessageId()) {
String msgId = redisTemplate.opsForValue().get(WE_CHAT_RECALL_KEY_PREFIX + messageId);
messageService.recall(msgId);
}
} else {
while (redisTemplate.opsForList().size(WE_CHAT_RECALL_KEY_PREFIX + recallTaskInfo.getMessageTemplateId()) > 0) {
String msgId = redisTemplate.opsForList().leftPop(WE_CHAT_RECALL_KEY_PREFIX + recallTaskInfo.getMessageTemplateId());
messageService.recall(msgId);
}
}
} catch (Exception e) {
log.error("EnterpriseWeChatHandler#recall fail:{}", Throwables.getStackTraceAsString(e));
}
});
}
}

@ -27,6 +27,7 @@ import java.util.List;
/**
*
* https://developer.work.weixin.qq.com/document/path/91770
*
* @author 3y
*/
@ -92,6 +93,11 @@ public class EnterpriseWeChatRobotHandler extends BaseHandler implements Handler
}
/**
*
* https://developer.work.weixin.qq.com/document/path/91770
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {

@ -24,8 +24,8 @@ import java.util.ArrayList;
import java.util.List;
/**
*
*
*
* https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
* @author 3y
*/
@Slf4j
@ -60,6 +60,12 @@ public class FeiShuRobotHandler extends BaseHandler implements Handler {
return false;
}
/**
*
*
* @param taskInfo
* @return
*/
private FeiShuRobotParam assembleParam(TaskInfo taskInfo) {
FeiShuRobotContentModel contentModel = (FeiShuRobotContentModel) taskInfo.getContentModel();
@ -91,9 +97,13 @@ public class FeiShuRobotHandler extends BaseHandler implements Handler {
}
/**
*
* https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {
// 不支持或未实现
}
}

@ -27,6 +27,7 @@ import java.util.Set;
/**
* @author sunql
*
* https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/deleteMessageTemplate.html
*/
@Component
@Slf4j
@ -61,6 +62,9 @@ public class MiniProgramAccountHandler extends BaseHandler implements Handler {
/**
*
* @param receiver
* @param contentModel
* @return
*/
private WxMaSubscribeMessage assembleReq(Set<String> receiver, MiniProgramContentModel contentModel) {
return WxMaSubscribeMessage.builder()
@ -73,7 +77,7 @@ public class MiniProgramAccountHandler extends BaseHandler implements Handler {
/**
*
*
* @param data
* @returnp
*/
private List<WxMaSubscribeMessage.MsgData> getWxMaTemplateData(Map<String, String> data) {
@ -83,6 +87,11 @@ public class MiniProgramAccountHandler extends BaseHandler implements Handler {
}
/**
*
* https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/deleteMessageTemplate.html
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {

@ -28,6 +28,7 @@ import java.util.Set;
/**
* @author zyg
*
* https://developers.weixin.qq.com/doc/offiaccount/Subscription_Messages/api.html
*/
@Component
@Slf4j
@ -63,6 +64,8 @@ public class OfficialAccountHandler extends BaseHandler implements Handler {
/**
*
* @param receiver
* @param contentModel
*/
private WxMpTemplateMessage assembleReq(Set<String> receiver, OfficialAccountsContentModel contentModel) {
return WxMpTemplateMessage.builder()
@ -76,7 +79,7 @@ public class OfficialAccountHandler extends BaseHandler implements Handler {
/**
*
*
* @param data
* @return
*/
private List<WxMpTemplateData> getWxMpTemplateData(Map<String, String> data) {
@ -86,6 +89,11 @@ public class OfficialAccountHandler extends BaseHandler implements Handler {
}
/**
*
* https://developers.weixin.qq.com/doc/offiaccount/Subscription_Messages/api.html
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {

@ -31,6 +31,7 @@ import java.util.Set;
*
* <p>
* ()
* https://docs.getui.com/getui/start/devcenter/
*
* @author 3y
*/
@ -156,6 +157,12 @@ public class PushHandler extends BaseHandler implements Handler {
}
/**
*
*
* https://docs.getui.com/getui/server/rest_v2/push/
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {

@ -175,6 +175,11 @@ public class SmsHandler extends BaseHandler implements Handler {
}
}
/**
*
* eghttps://cloud.tencent.com/document/product/382/52077
* @param recallTaskInfo
*/
@Override
public void recall(RecallTaskInfo recallTaskInfo) {

@ -27,6 +27,7 @@ import com.java3y.austin.web.vo.UploadResponseVo;
import com.taobao.api.FileItem;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
@ -59,7 +60,7 @@ public class MaterialServiceImpl implements MaterialService {
req.setMedia(item);
req.setType(EnumUtil.getDescriptionByCode(Integer.valueOf(fileType), FileType.class));
rsp = client.execute(req, accessToken);
if (rsp.getErrcode() == 0L) {
if (rsp.isSuccess()) {
return new BasicResultVO(RespStatusEnum.SUCCESS, UploadResponseVo.builder().id(rsp.getMediaId()).build());
}
log.error("MaterialService#dingDingMaterialUpload fail:{}", rsp.getErrmsg());
@ -81,7 +82,7 @@ public class MaterialServiceImpl implements MaterialService {
.form(IdUtil.fastSimpleUUID(), SpringFileUtils.getFile(multipartFile))
.execute().body();
EnterpriseWeChatRootResult result = JSON.parseObject(response, EnterpriseWeChatRootResult.class);
if (result.getErrcode() == 0) {
if (Integer.valueOf(WxCpErrorMsgEnum.CODE_0.getCode()).equals(result.getErrcode())) {
return new BasicResultVO(RespStatusEnum.SUCCESS, UploadResponseVo.builder().id(result.getMediaId()).build());
}
log.error("MaterialService#enterpriseWeChatRootMaterialUpload fail:{}", result.getErrmsg());

@ -49,9 +49,11 @@ public class LoginUtils {
*/
public boolean needLogin() {
try {
WeChatLoginConfig bean = applicationContext.getBean(OfficialAccountParamConstant.WE_CHAT_LOGIN_CONFIG, WeChatLoginConfig.class);
if (CommonConstant.ENV_TEST.equals(env) && Objects.nonNull(bean)) {
return true;
if (CommonConstant.ENV_TEST.equals(env)) {
WeChatLoginConfig bean = applicationContext.getBean(OfficialAccountParamConstant.WE_CHAT_LOGIN_CONFIG, WeChatLoginConfig.class);
if (Objects.nonNull(bean)) {
return true;
}
}
} catch (Exception e) {
log.error("LoginUtils#needLogin fail:{}", Throwables.getStackTraceAsString(e));

Loading…
Cancel
Save