feat(REQ-3114) - 完善钉钉消息的处理,增加关键词以及转发 MQ 的实现
This commit is contained in:
parent
f956028378
commit
a17a046e83
@ -18,5 +18,9 @@
|
|||||||
<groupId>cn.axzo.framework</groupId>
|
<groupId>cn.axzo.framework</groupId>
|
||||||
<artifactId>axzo-consumer-spring-cloud-starter</artifactId>
|
<artifactId>axzo-consumer-spring-cloud-starter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.axzo.framework.rocketmq</groupId>
|
||||||
|
<artifactId>axzo-common-rocketmq</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@ -20,5 +20,7 @@ public enum DingTalkMsgTypeEnum {
|
|||||||
sampleAudio,
|
sampleAudio,
|
||||||
sampleFile,
|
sampleFile,
|
||||||
sampleVideo,
|
sampleVideo,
|
||||||
|
// 非钉钉官方
|
||||||
|
messageQueue,
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
package cn.axzo.riven.client.common.enums;
|
||||||
|
|
||||||
|
import cn.axzo.framework.rocketmq.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程实例相关的 MQ 事件枚举定义
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2023/9/25 11:47
|
||||||
|
*/
|
||||||
|
public enum DingtalkEventEnum {
|
||||||
|
|
||||||
|
receive("riven-dingtalk", "riven-dingtalk-receive", "钉钉消息"),
|
||||||
|
;
|
||||||
|
private final String module;
|
||||||
|
private final String tag;
|
||||||
|
private final String desc;
|
||||||
|
private Event.EventCode eventCode;
|
||||||
|
|
||||||
|
DingtalkEventEnum(String module, String tag, String desc) {
|
||||||
|
this.eventCode = Event.EventCode.builder()
|
||||||
|
.module(module)
|
||||||
|
.name(tag)
|
||||||
|
.build();
|
||||||
|
this.module = module;
|
||||||
|
this.tag = tag;
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModule() {
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTag() {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event.EventCode getEventCode() {
|
||||||
|
return eventCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package cn.axzo.riven.client.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听由 riven 转发的钉钉群消息模型
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 18:17
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DingtalkReceiveMqModel implements Serializable {
|
||||||
|
private final static long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package cn.axzo.riven.client.model;
|
||||||
|
|
||||||
|
import cn.axzo.framework.jackson.utility.JSON;
|
||||||
|
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复消息 POJO
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 13:43
|
||||||
|
*/
|
||||||
|
public interface ReplyMessage<T> {
|
||||||
|
|
||||||
|
DingTalkMsgTypeEnum msgType();
|
||||||
|
|
||||||
|
T messageBody();
|
||||||
|
|
||||||
|
default String toJson() {
|
||||||
|
return JSON.toJSONString(messageBody());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package cn.axzo.riven.client.model;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单的 MD 内容
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 14:01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SampleMarkdown implements ReplyMessage<SampleMarkdown> {
|
||||||
|
private final String title;
|
||||||
|
private final String text;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DingTalkMsgTypeEnum msgType() {
|
||||||
|
return DingTalkMsgTypeEnum.sampleMarkdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleMarkdown messageBody() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SampleMarkdown from(String title, String text) {
|
||||||
|
return new SampleMarkdown(title, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package cn.axzo.riven.client.model;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static cn.azxo.framework.common.constatns.Constants.CTX_LOG_ID_MDC;
|
||||||
|
import static com.google.common.net.HttpHeaders.X_REQUEST_ID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发消息内容到后端服务
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 17:14
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SampleMessageQueue implements ReplyMessage<SampleMessageQueue> {
|
||||||
|
private String applicationName;
|
||||||
|
private String traceId;
|
||||||
|
private String messageContent;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DingTalkMsgTypeEnum msgType() {
|
||||||
|
return DingTalkMsgTypeEnum.messageQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleMessageQueue messageBody() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SampleMessageQueue from(String applicationName, String messageContent) {
|
||||||
|
String traceId = MDC.get(X_REQUEST_ID);
|
||||||
|
if (Strings.isNullOrEmpty(traceId)) {
|
||||||
|
MDC.put(CTX_LOG_ID_MDC, traceId());
|
||||||
|
}
|
||||||
|
return new SampleMessageQueue(applicationName, traceId, messageContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String traceId() {
|
||||||
|
return UUID.randomUUID().toString().replaceAll("-", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package cn.axzo.riven.client.model;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单文本
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 13:45
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SampleText implements ReplyMessage<SampleText> {
|
||||||
|
private final String content;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DingTalkMsgTypeEnum msgType() {
|
||||||
|
return DingTalkMsgTypeEnum.sampleText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SampleText messageBody() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SampleText from(String content) {
|
||||||
|
return new SampleText(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package cn.axzo.riven.client.req;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 钉钉会话的通用查询入参
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 17:47
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ThirdDingtalkConversationReq {
|
||||||
|
|
||||||
|
private String conversationId;
|
||||||
|
|
||||||
|
private String conversationTitle;
|
||||||
|
|
||||||
|
private String conversationType;
|
||||||
|
|
||||||
|
private String chatbotCorpId;
|
||||||
|
|
||||||
|
private String chatbotUserId;
|
||||||
|
|
||||||
|
private String applicationName;
|
||||||
|
}
|
||||||
@ -19,9 +19,17 @@
|
|||||||
<groupId>com.dingtalk.open</groupId>
|
<groupId>com.dingtalk.open</groupId>
|
||||||
<artifactId>app-stream-client</artifactId>
|
<artifactId>app-stream-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.axzo.framework.data</groupId>
|
<groupId>cn.axzo.framework.data</groupId>
|
||||||
<artifactId>axzo-data-mybatis-plus</artifactId>
|
<artifactId>axzo-data-mybatis-plus</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.axzo.framework.rocketmq</groupId>
|
||||||
|
<artifactId>axzo-common-rocketmq</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.callback.keyword;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import cn.axzo.riven.client.model.SampleText;
|
||||||
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兜底的关键词,不用注册为 Spring 的 Bean
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 17:01
|
||||||
|
*/
|
||||||
|
public class DefaultKeywordProcessor implements KeywordProcessor {
|
||||||
|
@Override
|
||||||
|
public String[] getKeywords() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReplyMessage process(ChatbotMessage chatbotMessage) {
|
||||||
|
return SampleText.from("不能理解你说的话!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.callback.keyword;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人支持的关键词
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 16:29
|
||||||
|
*/
|
||||||
|
public interface KeywordProcessor {
|
||||||
|
String[] getKeywords();
|
||||||
|
|
||||||
|
ReplyMessage process(ChatbotMessage chatbotMessage);
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.callback.keyword;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import cn.axzo.riven.client.model.SampleText;
|
||||||
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主菜单
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 16:28
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class MenuKeywordProcessor implements KeywordProcessor {
|
||||||
|
public final static String[] KEYWORD = {"menu", "菜单"};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getKeywords() {
|
||||||
|
return KEYWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReplyMessage process(ChatbotMessage chatbotMessage) {
|
||||||
|
return SampleText.from("这里是菜单");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.callback.keyword;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import cn.axzo.riven.client.model.SampleText;
|
||||||
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||||
|
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import static cn.axzo.riven.dingtalk.constant.DingTalkConstant.REGISTER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 16:31
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RegisterKeywordProcessor implements KeywordProcessor {
|
||||||
|
public final static String[] KEYWORD = {"register", "注册"};
|
||||||
|
private final ThirdDingtalkConversationService thirdDingtalkConversationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getKeywords() {
|
||||||
|
return KEYWORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要功能如下:
|
||||||
|
* 1. 单纯注册会话
|
||||||
|
* 2. 注册会话并绑定后端服务
|
||||||
|
* 3. 会话重绑定后端服务
|
||||||
|
*
|
||||||
|
* @param chatbotMessage
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ReplyMessage process(ChatbotMessage chatbotMessage) {
|
||||||
|
ThirdDingtalkConversation conversation = new ThirdDingtalkConversation();
|
||||||
|
BeanUtil.copyProperties(chatbotMessage, conversation);
|
||||||
|
|
||||||
|
String replyText = String.format("注册成功,但未关联服务。\r\n如需请@机器发送“注册 [服务名]”即可(示例:注册 pudge)。\r\n当前的会话 ID:%s", conversation.getConversationId());
|
||||||
|
String content = chatbotMessage.getText().getContent();
|
||||||
|
String applicationName;
|
||||||
|
if (StringUtils.hasText(applicationName = content.replaceAll(REGISTER, "").trim())) {
|
||||||
|
replyText = String.format("本群于" + applicationName + "服务关联成功。 \r\n本群的会话 ID:%s", conversation.getConversationId());
|
||||||
|
conversation.setApplicationName(applicationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进行机器人和会话的绑定注册
|
||||||
|
thirdDingtalkConversationService.upsert(conversation);
|
||||||
|
return SampleText.from(replyText);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,11 @@
|
|||||||
package cn.axzo.riven.dingtalk.callback.robot;
|
package cn.axzo.riven.dingtalk.callback.robot;
|
||||||
|
|
||||||
import cn.axzo.framework.jackson.utility.JSON;
|
import cn.axzo.framework.jackson.utility.JSON;
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
import cn.axzo.riven.dingtalk.callback.robot.strategy.RobotHandleStrategy;
|
import cn.axzo.riven.dingtalk.callback.robot.strategy.RobotHandleStrategy;
|
||||||
|
import cn.axzo.riven.dingtalk.reply.RobotReply;
|
||||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
|
import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
|
||||||
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -21,7 +24,9 @@ import java.util.List;
|
|||||||
@Component
|
@Component
|
||||||
public class RobotMsgCallbackConsumer implements OpenDingTalkCallbackListener<ChatbotMessage, Void> {
|
public class RobotMsgCallbackConsumer implements OpenDingTalkCallbackListener<ChatbotMessage, Void> {
|
||||||
@Resource
|
@Resource
|
||||||
private List<RobotHandleStrategy> robotHandleStrategies;
|
private List<RobotHandleStrategy<? extends ReplyMessage>> robotHandleStrategies;
|
||||||
|
@Resource
|
||||||
|
private RobotReply robotReply;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行回调
|
* 执行回调
|
||||||
@ -34,15 +39,20 @@ public class RobotMsgCallbackConsumer implements OpenDingTalkCallbackListener<Ch
|
|||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("receive message : {}", JSON.toJSONString(message));
|
log.debug("receive message : {}", JSON.toJSONString(message));
|
||||||
}
|
}
|
||||||
|
// 用户输入的信息
|
||||||
String keyword = message.getText().getContent().trim();
|
String keyword = message.getText().getContent().trim();
|
||||||
|
|
||||||
robotHandleStrategies.stream()
|
robotHandleStrategies.stream()
|
||||||
.filter(s -> s.support(keyword))
|
.filter(s -> s.support(keyword))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.ifPresent(handle -> {
|
.ifPresent(handle -> {
|
||||||
Object result = handle.handle(message);
|
ReplyContext context = ReplyContext.builder()
|
||||||
if(result instanceof ReplyContext) {
|
.conversationId(message.getConversationId())
|
||||||
ReplyContext replyContext = (ReplyContext) result;
|
.sessionWebhook(message.getSessionWebhook())
|
||||||
}
|
.sessionWebhookExpiredDate(DateUtil.date(message.getSessionWebhookExpiredTime()))
|
||||||
|
.replyMessage(handle.handle(message))
|
||||||
|
.build();
|
||||||
|
robotReply.reply(context);
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,75 @@
|
|||||||
package cn.axzo.riven.dingtalk.callback.robot.strategy;
|
package cn.axzo.riven.dingtalk.callback.robot.strategy;
|
||||||
|
|
||||||
|
import cn.axzo.framework.domain.ServiceException;
|
||||||
|
import cn.axzo.framework.jackson.utility.JSON;
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||||
|
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 抽象的机器人监听消息的处理策略
|
* 抽象的机器人监听消息的处理策略
|
||||||
*
|
*
|
||||||
* @author wangli
|
* @author wangli
|
||||||
* @since 2024-10-23 17:35
|
* @since 2024-10-23 17:35
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractRobotHandleStrategy<T> implements RobotHandleStrategy<T> {
|
public abstract class AbstractRobotHandleStrategy implements RobotHandleStrategy<ReplyMessage> {
|
||||||
|
@Resource
|
||||||
|
private ThirdDingtalkMessageRecordService thirdDingtalkMessageRecordService;
|
||||||
|
|
||||||
|
protected final String getContent(ChatbotMessage chatbotMessage) {
|
||||||
|
if (Objects.nonNull(chatbotMessage)) {
|
||||||
|
return chatbotMessage.getText().getContent();
|
||||||
|
}
|
||||||
|
throw new ServiceException("获取用户发送的消息内容失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReplyMessage handle(ChatbotMessage chatbotMessage) {
|
||||||
|
// 记录日志
|
||||||
|
ThirdDingtalkMessageRecord record = insertLog(chatbotMessage);
|
||||||
|
|
||||||
|
// 后续处理
|
||||||
|
ReplyMessage replyMessage = doHandle(chatbotMessage);
|
||||||
|
|
||||||
|
// 更新日志
|
||||||
|
updateLog(record, replyMessage);
|
||||||
|
return replyMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新日志的响应信息
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
* @param replyMessage
|
||||||
|
*/
|
||||||
|
private void updateLog(ThirdDingtalkMessageRecord record, ReplyMessage replyMessage) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录日志
|
||||||
|
*
|
||||||
|
* @param chatbotMessage
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ThirdDingtalkMessageRecord insertLog(ChatbotMessage chatbotMessage) {
|
||||||
|
ThirdDingtalkMessageRecord record = new ThirdDingtalkMessageRecord();
|
||||||
|
record.setConversationId(chatbotMessage.getConversationId());
|
||||||
|
record.setConversationTitle(chatbotMessage.getConversationTitle());
|
||||||
|
record.setConversationType(chatbotMessage.getConversationType());
|
||||||
|
record.setMsgId(chatbotMessage.getMsgId());
|
||||||
|
record.setSenderNick(chatbotMessage.getSenderNick());
|
||||||
|
record.setSessionWebhook(chatbotMessage.getSessionWebhook());
|
||||||
|
record.setSessionWebhookExpiredTime(DateUtil.date(chatbotMessage.getSessionWebhookExpiredTime()));
|
||||||
|
record.setHandleType(this.getClass().getSimpleName());
|
||||||
|
record.setRequestContent(JSON.toJSONString(chatbotMessage));
|
||||||
|
return thirdDingtalkMessageRecordService.insert(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ReplyMessage doHandle(ChatbotMessage chatbotMessage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
package cn.axzo.riven.dingtalk.callback.robot.strategy.impl;
|
|
||||||
|
|
||||||
import cn.axzo.riven.dingtalk.callback.robot.strategy.AbstractRobotHandleStrategy;
|
|
||||||
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认的将群消息通过 MQ 广播也所有业务方
|
|
||||||
*
|
|
||||||
* @author wangli
|
|
||||||
* @since 2024-10-23 18:13
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class DefaultTransferToMQStrategy extends AbstractRobotHandleStrategy<String> {
|
|
||||||
@Override
|
|
||||||
public boolean support(String keyword) {
|
|
||||||
return StringUtils.hasText(keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String handle(ChatbotMessage chatbotMessage) {
|
|
||||||
// TODO 转发给 MQ 中去
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.callback.robot.strategy.impl;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import cn.axzo.riven.dingtalk.callback.keyword.DefaultKeywordProcessor;
|
||||||
|
import cn.axzo.riven.dingtalk.callback.keyword.KeywordProcessor;
|
||||||
|
import cn.axzo.riven.dingtalk.callback.robot.strategy.AbstractRobotHandleStrategy;
|
||||||
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册会话
|
||||||
|
* <p>
|
||||||
|
* 新创建的群,添加机器人后,在群内@机器人并发送【注册】两个字,即可完成注册。
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-23 17:30
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class KeywordProcessorStrategy extends AbstractRobotHandleStrategy {
|
||||||
|
|
||||||
|
// 容器中所有实现了的关键词 Spring's Bean
|
||||||
|
private final List<KeywordProcessor> keywordProcessorHandles;
|
||||||
|
|
||||||
|
// 关键词对应的处理 Bean Map
|
||||||
|
private Map<String, KeywordProcessor> keywordMap;
|
||||||
|
// 具体的关键词
|
||||||
|
private List<String> words;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
keywordProcessorHandles.forEach(bean -> {
|
||||||
|
for (String keyword : bean.getKeywords()) {
|
||||||
|
words.addAll(Lists.newArrayList(bean.getKeywords()));
|
||||||
|
keywordMap.put(keyword, bean);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean support(String keyword) {
|
||||||
|
if (!StringUtils.hasText(keyword)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String word : words) {
|
||||||
|
if (keyword.trim().startsWith(word)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReplyMessage doHandle(ChatbotMessage chatbotMessage) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(" KeywordProcessorStrategy Entrance ");
|
||||||
|
}
|
||||||
|
|
||||||
|
KeywordProcessor keywordProcessor = getKeywordHandle(chatbotMessage);
|
||||||
|
|
||||||
|
return keywordProcessor.process(chatbotMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeywordProcessor getKeywordHandle(ChatbotMessage chatbotMessage) {
|
||||||
|
String command = chatbotMessage.getText().getContent().trim();
|
||||||
|
for (String word : words) {
|
||||||
|
if (command.startsWith(word)) {
|
||||||
|
return keywordMap.get(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new DefaultKeywordProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return Integer.MIN_VALUE + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,55 +0,0 @@
|
|||||||
package cn.axzo.riven.dingtalk.callback.robot.strategy.impl;
|
|
||||||
|
|
||||||
import cn.axzo.riven.dingtalk.callback.robot.strategy.AbstractRobotHandleStrategy;
|
|
||||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
|
||||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
|
||||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
|
||||||
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static cn.axzo.riven.dingtalk.constant.DingTalkConstant.REGISTER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册会话
|
|
||||||
* <p>
|
|
||||||
* 新创建的群,添加机器人后,在群内@机器人并发送【注册】两个字,即可完成注册。
|
|
||||||
*
|
|
||||||
* @author wangli
|
|
||||||
* @since 2024-10-23 17:30
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class RegisterConversationStrategy extends AbstractRobotHandleStrategy<ReplyContext> {
|
|
||||||
|
|
||||||
private final ThirdDingtalkConversationService thirdDingtalkConversationService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean support(String keyword) {
|
|
||||||
return Objects.equals(keyword, REGISTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ReplyContext handle(ChatbotMessage chatbotMessage) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug(" RegisterConversationStrategy Entrance ");
|
|
||||||
}
|
|
||||||
|
|
||||||
ThirdDingtalkConversation conversation = new ThirdDingtalkConversation();
|
|
||||||
BeanUtils.copyProperties(chatbotMessage, conversation);
|
|
||||||
|
|
||||||
// 进行机器人和会话的绑定注册
|
|
||||||
thirdDingtalkConversationService.upsert(conversation);
|
|
||||||
return ReplyContext.from(chatbotMessage.getSessionWebhook(), String.format("注册成功,当前的会话 ID:%s", conversation.getConversationId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return Integer.MIN_VALUE + 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.callback.robot.strategy.impl;
|
||||||
|
|
||||||
|
import cn.axzo.framework.rocketmq.Event;
|
||||||
|
import cn.axzo.framework.rocketmq.RocketMQEventProducer;
|
||||||
|
import cn.axzo.riven.client.common.enums.DingtalkEventEnum;
|
||||||
|
import cn.axzo.riven.client.model.DingtalkReceiveMqModel;
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import cn.axzo.riven.client.model.SampleMessageQueue;
|
||||||
|
import cn.axzo.riven.client.model.SampleText;
|
||||||
|
import cn.axzo.riven.client.req.ThirdDingtalkConversationReq;
|
||||||
|
import cn.axzo.riven.dingtalk.callback.robot.strategy.AbstractRobotHandleStrategy;
|
||||||
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||||
|
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
||||||
|
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认的将群消息通过 MQ 广播也所有业务方
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-23 18:13
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class TransferToMQStrategy extends AbstractRobotHandleStrategy {
|
||||||
|
@Resource
|
||||||
|
private RocketMQEventProducer producer;
|
||||||
|
@Resource
|
||||||
|
private ThirdDingtalkConversationService thirdDingtalkConversationService;
|
||||||
|
private final Map</*会话 ID*/String, ThirdDingtalkConversation> conversationMap = new HashMap<>();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
conversationMap.putAll(thirdDingtalkConversationService.genericQuery(new ThirdDingtalkConversationReq())
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(ThirdDingtalkConversation::getConversationId, Function.identity(), (s, t) -> s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean support(String keyword) {
|
||||||
|
return StringUtils.hasText(keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplyMessage doHandle(ChatbotMessage chatbotMessage) {
|
||||||
|
// TODO 转发给 MQ 中去
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(" DefaultTransferToMQStrategy Entrance ");
|
||||||
|
}
|
||||||
|
|
||||||
|
ThirdDingtalkConversation conversation = checkConversationExists(chatbotMessage);
|
||||||
|
if (Objects.isNull(conversation)) {
|
||||||
|
return SampleText.from("这里应该提示用法");
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleMessageQueue messageQueue = SampleMessageQueue.from(conversation.getApplicationName(), getContent(chatbotMessage));
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("发送 MQ 的消息数据:{}", messageQueue.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
DingtalkReceiveMqModel model = new DingtalkReceiveMqModel();
|
||||||
|
|
||||||
|
producer.send(Event.builder()
|
||||||
|
.shardingKey(chatbotMessage.getConversationId())
|
||||||
|
.eventCode(DingtalkEventEnum.receive.getEventCode())
|
||||||
|
.targetId(messageQueue.getTraceId())// traceId
|
||||||
|
.targetType(chatbotMessage.getConversationId())
|
||||||
|
.data(model)
|
||||||
|
.build());
|
||||||
|
return messageQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThirdDingtalkConversation checkConversationExists(ChatbotMessage chatbotMessage) {
|
||||||
|
return conversationMap.getOrDefault(chatbotMessage.getConversationId(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.reply;
|
||||||
|
|
||||||
|
import cn.axzo.framework.domain.ServiceException;
|
||||||
|
import cn.axzo.framework.jackson.utility.JSON;
|
||||||
|
import cn.axzo.riven.dingtalk.reply.strategy.RobotReplyStrategy;
|
||||||
|
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum.messageQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器人回复的入口
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 10:11
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class RobotReply {
|
||||||
|
@Resource
|
||||||
|
private List<RobotReplyStrategy> robotReplyStrategies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回复消息
|
||||||
|
*
|
||||||
|
* @param replyContext
|
||||||
|
*/
|
||||||
|
public void reply(ReplyContext replyContext) {
|
||||||
|
if (Objects.isNull(replyContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Objects.equals(messageQueue, replyContext.getReplyMessage().msgType())) {
|
||||||
|
// 通过 MQ 转发给其他后端,这里统一不在回复
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beforeCheck(replyContext);
|
||||||
|
robotReplyStrategies.forEach(strategy -> strategy.reply(replyContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beforeCheck(ReplyContext replyContext) {
|
||||||
|
boolean useConversation = StringUtils.hasText(replyContext.getConversationId());
|
||||||
|
boolean useWebhook = StringUtils.hasText(replyContext.getSessionWebhook()) && Objects.nonNull(replyContext.getSessionWebhookExpiredDate());
|
||||||
|
if (!useConversation && !useWebhook) {
|
||||||
|
throw new ServiceException(String.format("构建的回复上下文缺失参数:%s", JSON.toJSONString(replyContext)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||||
|
|
||||||
|
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽象的机器人回复
|
||||||
|
*
|
||||||
|
* @author wangli
|
||||||
|
* @since 2024-10-24 10:21
|
||||||
|
*/
|
||||||
|
public abstract class AbstractRobotReply implements RobotReplyStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reply(ReplyContext replyContext) {
|
||||||
|
if(doFilter(replyContext)) {
|
||||||
|
doReply(replyContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract void doReply(ReplyContext replyContext);
|
||||||
|
|
||||||
|
abstract boolean doFilter(ReplyContext replyContext);
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
package cn.axzo.riven.dingtalk.reply.strategy;
|
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||||
|
|
||||||
|
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于 Http Api 的回复
|
* 基于 Http Api 的回复
|
||||||
* https://github.com/open-dingtalk/dingtalk-stream-sdk-java-quick-start/blob/main/src/main/java/org/example/service/RobotGroupMessagesService.java
|
* https://github.com/open-dingtalk/dingtalk-stream-sdk-java-quick-start/blob/main/src/main/java/org/example/service/RobotGroupMessagesService.java
|
||||||
@ -8,5 +11,20 @@ package cn.axzo.riven.dingtalk.reply.strategy;
|
|||||||
* @author wangli
|
* @author wangli
|
||||||
* @since 2024-10-23 17:28
|
* @since 2024-10-23 17:28
|
||||||
*/
|
*/
|
||||||
public class BasedHttpReply {
|
@Component
|
||||||
|
public class BasedHttpReply extends AbstractRobotReply {
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return Integer.MIN_VALUE + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doReply(ReplyContext replyContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean doFilter(ReplyContext replyContext) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,18 @@
|
|||||||
package cn.axzo.riven.dingtalk.reply.strategy;
|
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
|
import cn.axzo.riven.client.model.SampleMarkdown;
|
||||||
|
import cn.axzo.riven.client.model.SampleText;
|
||||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||||
import com.dingtalk.open.app.api.chatbot.BotReplier;
|
import com.dingtalk.open.app.api.chatbot.BotReplier;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于 Session Webhook 的回复
|
* 基于 Session Webhook 的回复
|
||||||
@ -11,15 +20,49 @@ import java.io.IOException;
|
|||||||
* @author wangli
|
* @author wangli
|
||||||
* @since 2024-10-23 17:28
|
* @since 2024-10-23 17:28
|
||||||
*/
|
*/
|
||||||
public class BasedSessionWebhookReply {
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class BasedSessionWebhookReply extends AbstractRobotReply {
|
||||||
|
@Resource
|
||||||
|
private BasedHttpReply basedHttpReply;
|
||||||
|
|
||||||
public void reply(ReplyContext replyContext) {
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doReply(ReplyContext replyContext) {
|
||||||
try {
|
try {
|
||||||
// just reply generic text
|
// just reply generic text
|
||||||
BotReplier.fromWebhook(replyContext.getSessionWebhook()).replyText(replyContext.getMsgContent());
|
BotReplier botReplier = BotReplier.fromWebhook(replyContext.getSessionWebhook());
|
||||||
|
|
||||||
|
ReplyMessage replyMessage = replyContext.getReplyMessage();
|
||||||
|
switch (replyMessage.msgType()) {
|
||||||
|
case sampleText:
|
||||||
|
SampleText text = (SampleText) replyMessage.messageBody();
|
||||||
|
botReplier.replyText(text.getContent(), replyContext.getAtUserId());
|
||||||
|
break;
|
||||||
|
case sampleMarkdown:
|
||||||
|
SampleMarkdown markdown = (SampleMarkdown) replyMessage.messageBody();
|
||||||
|
botReplier.replyMarkdown(markdown.getTitle(), markdown.getText(), replyContext.getAtUserId());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// current reply just supported text and markdown
|
||||||
|
botReplier.replyText(replyMessage.toJson(), replyContext.getAtUserId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
log.warn("通过 sessionWebhook 回复发生异常,将通过 HTTP API 回复");
|
||||||
|
basedHttpReply.doReply(replyContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean doFilter(ReplyContext replyContext) {
|
||||||
|
return Objects.nonNull(replyContext.getSessionWebhook()) && Objects.nonNull(replyContext.getSessionWebhookExpiredDate())
|
||||||
|
&& StringUtils.hasText(replyContext.getSessionWebhook())
|
||||||
|
&& new Date().before(replyContext.getSessionWebhookExpiredDate());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
package cn.axzo.riven.dingtalk.reply;
|
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||||
|
|
||||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回复机器人
|
* 回复机器人的策略
|
||||||
*
|
*
|
||||||
* @author wangli
|
* @author wangli
|
||||||
* @since 2024-10-23 20:37
|
* @since 2024-10-23 20:37
|
||||||
*/
|
*/
|
||||||
public interface RobotReplyStrategy {
|
public interface RobotReplyStrategy extends Ordered {
|
||||||
|
|
||||||
void reply(ReplyContext replyContext);
|
void reply(ReplyContext replyContext);
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ public class ThirdDingtalkConversation extends BaseEntity<ThirdDingtalkConversat
|
|||||||
* 1:单聊
|
* 1:单聊
|
||||||
* 2:群聊
|
* 2:群聊
|
||||||
*/
|
*/
|
||||||
private Integer conversationType;
|
private String conversationType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 机器人所属企业
|
* 机器人所属企业
|
||||||
@ -42,4 +42,9 @@ public class ThirdDingtalkConversation extends BaseEntity<ThirdDingtalkConversat
|
|||||||
* 机器人用户 ID(加密数据)
|
* 机器人用户 ID(加密数据)
|
||||||
*/
|
*/
|
||||||
private String chatbotUserId;
|
private String chatbotUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话关联的应用名称
|
||||||
|
*/
|
||||||
|
private String applicationName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ public class ThirdDingtalkMessageRecord extends BaseEntity<ThirdDingtalkMessageR
|
|||||||
* 1:单聊
|
* 1:单聊
|
||||||
* 2:群聊
|
* 2:群聊
|
||||||
*/
|
*/
|
||||||
private Integer conversationType;
|
private String conversationType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会话级的 Webhook
|
* 会话级的 Webhook
|
||||||
@ -56,15 +56,20 @@ public class ThirdDingtalkMessageRecord extends BaseEntity<ThirdDingtalkMessageR
|
|||||||
*/
|
*/
|
||||||
private Date sessionWebhookExpiredTime;
|
private Date sessionWebhookExpiredTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该消息是怎么处理的,处理策略的类型
|
||||||
|
*/
|
||||||
|
private String handleType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求的内容
|
* 请求的内容
|
||||||
*/
|
*/
|
||||||
private String requestContext;
|
private String requestContent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应的内容
|
* 响应的内容
|
||||||
*/
|
*/
|
||||||
private String responseContext;
|
private String responseContent;
|
||||||
/**
|
/**
|
||||||
* 响应的消息类型
|
* 响应的消息类型
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package cn.axzo.riven.dingtalk.repository.mapper;
|
package cn.axzo.riven.dingtalk.repository.mapper;
|
||||||
|
|
||||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
|
||||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
package cn.axzo.riven.dingtalk.robot.connection.model;
|
package cn.axzo.riven.dingtalk.robot.connection.model;
|
||||||
|
|
||||||
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
|
import cn.axzo.riven.client.model.ReplyMessage;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一的回复上下文模型
|
* 统一的回复上下文模型
|
||||||
@ -11,50 +17,35 @@ import lombok.Data;
|
|||||||
* @since 2024-10-23 20:11
|
* @since 2024-10-23 20:11
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Accessors(chain = true)
|
||||||
public class ReplyContext {
|
public class ReplyContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会话级的 Webhook
|
* 会话级的 Webhook
|
||||||
*/
|
*/
|
||||||
private String sessionWebhook;
|
private String sessionWebhook;
|
||||||
|
/**
|
||||||
|
* 会话级的 Webhook 有效期
|
||||||
|
*/
|
||||||
|
private Date sessionWebhookExpiredDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息类型
|
* 会话 ID
|
||||||
*/
|
*/
|
||||||
private DingTalkMsgTypeEnum msgType = DingTalkMsgTypeEnum.sampleText;
|
private String conversationId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认的消息类型下,可直接传入要发送的文本
|
* 回复的消息
|
||||||
* 如果要使用其他 msgType,则用 data 属性进行传值
|
|
||||||
*/
|
*/
|
||||||
private String msgContent;
|
private ReplyMessage replyMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据不同的 msgType 传入不同请求模型
|
* 回复消息时需要被@的用户
|
||||||
* 参考:{@see https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots}
|
|
||||||
*/
|
*/
|
||||||
private JSONObject data;
|
private List<String> atUserId;
|
||||||
|
|
||||||
private ReplyContext(String sessionWebhook, DingTalkMsgTypeEnum msgType, String msgContent, JSONObject data) {
|
|
||||||
this.sessionWebhook = sessionWebhook;
|
|
||||||
this.msgType = msgType;
|
|
||||||
this.msgContent = msgContent;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReplyContext from(String msgContent) {
|
|
||||||
return new ReplyContext(null, DingTalkMsgTypeEnum.sampleText, msgContent, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReplyContext from(String sessionWebhook, String msgContent) {
|
|
||||||
return new ReplyContext(sessionWebhook, DingTalkMsgTypeEnum.sampleText, msgContent, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReplyContext from(DingTalkMsgTypeEnum msgType, JSONObject data) {
|
|
||||||
return new ReplyContext(null, msgType, null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReplyContext from(String sessionWebhook, DingTalkMsgTypeEnum msgType, JSONObject data) {
|
|
||||||
return new ReplyContext(sessionWebhook, msgType, null, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
package cn.axzo.riven.dingtalk.service;
|
package cn.axzo.riven.dingtalk.service;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.req.ThirdDingtalkConversationReq;
|
||||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 三方钉钉群会话信息
|
* 三方钉钉群会话信息
|
||||||
*
|
*
|
||||||
@ -15,4 +18,6 @@ public interface ThirdDingtalkConversationService {
|
|||||||
* @param conversation
|
* @param conversation
|
||||||
*/
|
*/
|
||||||
void upsert(ThirdDingtalkConversation conversation);
|
void upsert(ThirdDingtalkConversation conversation);
|
||||||
|
|
||||||
|
List<ThirdDingtalkConversation> genericQuery(ThirdDingtalkConversationReq req);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package cn.axzo.riven.dingtalk.service;
|
package cn.axzo.riven.dingtalk.service;
|
||||||
|
|
||||||
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 三方钉钉群消息记录
|
* 三方钉钉群消息记录
|
||||||
*
|
*
|
||||||
@ -7,4 +9,5 @@ package cn.axzo.riven.dingtalk.service;
|
|||||||
* @since 2024-10-23 19:48
|
* @since 2024-10-23 19:48
|
||||||
*/
|
*/
|
||||||
public interface ThirdDingtalkMessageRecordService {
|
public interface ThirdDingtalkMessageRecordService {
|
||||||
|
ThirdDingtalkMessageRecord insert(ThirdDingtalkMessageRecord record);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,21 @@
|
|||||||
package cn.axzo.riven.dingtalk.service.impl;
|
package cn.axzo.riven.dingtalk.service.impl;
|
||||||
|
|
||||||
|
import cn.axzo.riven.client.common.enums.CommonStatusEnum;
|
||||||
|
import cn.axzo.riven.client.common.enums.sync.Channel;
|
||||||
|
import cn.axzo.riven.client.req.ThirdApplicationReq;
|
||||||
|
import cn.axzo.riven.client.req.ThirdDingtalkConversationReq;
|
||||||
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||||
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkConversationMapper;
|
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkConversationMapper;
|
||||||
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkMessageRecordMapper;
|
|
||||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,14 +31,30 @@ public class ThirdDingtalkConversationServiceImpl implements ThirdDingtalkConver
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void upsert(ThirdDingtalkConversation conversation) {
|
public void upsert(ThirdDingtalkConversation conversation) {
|
||||||
if (Objects.nonNull(conversation.getConversationId())) {
|
ThirdDingtalkConversation oldEntity = thirdDingtalkConversationMapper
|
||||||
ThirdDingtalkConversation oldEntity = thirdDingtalkConversationMapper
|
.selectOne(new LambdaQueryWrapper<ThirdDingtalkConversation>()
|
||||||
.selectOne(new LambdaQueryWrapper<ThirdDingtalkConversation>()
|
.eq(ThirdDingtalkConversation::getConversationId, conversation.getConversationId()));
|
||||||
.eq(ThirdDingtalkConversation::getConversationId, conversation.getConversationId()));
|
if (Objects.nonNull(oldEntity)) {
|
||||||
BeanUtils.copyProperties(conversation, oldEntity);
|
BeanUtil.copyProperties(conversation, oldEntity, "id", "idDelete", "createAt", "updateAt");
|
||||||
thirdDingtalkConversationMapper.updateById(oldEntity);
|
thirdDingtalkConversationMapper.updateById(oldEntity);
|
||||||
} else {
|
} else {
|
||||||
thirdDingtalkConversationMapper.insert(conversation);
|
thirdDingtalkConversationMapper.insert(conversation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThirdDingtalkConversation> genericQuery(ThirdDingtalkConversationReq req) {
|
||||||
|
return thirdDingtalkConversationMapper.selectList(buildQueryWrapper(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
private LambdaQueryWrapper<ThirdDingtalkConversation> buildQueryWrapper(ThirdDingtalkConversationReq req) {
|
||||||
|
return new LambdaQueryWrapper<ThirdDingtalkConversation>()
|
||||||
|
.eq(StringUtils.hasText(req.getConversationId()), ThirdDingtalkConversation::getConversationId, req.getConversationId())
|
||||||
|
.like(StringUtils.hasText(req.getConversationTitle()), ThirdDingtalkConversation::getConversationTitle, req.getConversationTitle())
|
||||||
|
.eq(StringUtils.hasText(req.getConversationType()), ThirdDingtalkConversation::getConversationType, req.getConversationType())
|
||||||
|
.eq(StringUtils.hasText(req.getChatbotCorpId()), ThirdDingtalkConversation::getChatbotCorpId, req.getChatbotCorpId())
|
||||||
|
.eq(StringUtils.hasText(req.getChatbotUserId()), ThirdDingtalkConversation::getChatbotUserId, req.getChatbotUserId())
|
||||||
|
.eq(StringUtils.hasText(req.getApplicationName()), ThirdDingtalkConversation::getApplicationName, req.getApplicationName())
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package cn.axzo.riven.dingtalk.service.impl;
|
package cn.axzo.riven.dingtalk.service.impl;
|
||||||
|
|
||||||
|
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||||
|
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkMessageRecordMapper;
|
||||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
|
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -13,4 +15,11 @@ import org.springframework.stereotype.Service;
|
|||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class ThirdDingtalkMessageRecordServiceImpl implements ThirdDingtalkMessageRecordService {
|
public class ThirdDingtalkMessageRecordServiceImpl implements ThirdDingtalkMessageRecordService {
|
||||||
|
private final ThirdDingtalkMessageRecordMapper thirdDingtalkMessageRecordMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThirdDingtalkMessageRecord insert(ThirdDingtalkMessageRecord record) {
|
||||||
|
thirdDingtalkMessageRecordMapper.insert(record);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,31 +22,35 @@ insert into third_application value (1, 'dingtalk', '多群公共机器人', '
|
|||||||
|
|
||||||
create table third_dingtalk_conversation
|
create table third_dingtalk_conversation
|
||||||
(
|
(
|
||||||
id bigint not null auto_increment comment '主键'
|
id bigint not null auto_increment comment '主键'
|
||||||
primary key,
|
primary key,
|
||||||
conversation_id varchar(255) not null comment '会话ID',
|
conversation_id varchar(255) not null comment '会话ID',
|
||||||
conversation_title varchar(255) not null comment '会话名称',
|
conversation_title varchar(255) not null comment '会话名称',
|
||||||
conversation_type tinyint(1) not null comment '会话类型 1:单聊 2:群聊',
|
conversation_type varchar(1) not null comment '会话类型 1:单聊 2:群聊',
|
||||||
chatbot_corp_id varchar(255) not null comment '机器人所属企业',
|
chatbot_corp_id varchar(255) not null comment '机器人所属企业',
|
||||||
chatbot_user_id varchar(255) not null comment '机器人用户ID',
|
chatbot_user_id varchar(255) not null comment '机器人用户ID',
|
||||||
create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间',
|
application_name varchar(255) default '' not null comment '会话关联的应用名称',
|
||||||
update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
|
create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间',
|
||||||
is_delete bigint default 0 not null comment '删除标志'
|
update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
|
||||||
|
is_delete bigint default 0 not null comment '删除标志'
|
||||||
) comment '会话信息';
|
) comment '会话信息';
|
||||||
|
|
||||||
create table third_dingtalk_msg_record
|
create table third_dingtalk_msg_record
|
||||||
(
|
(
|
||||||
id bigint auto_increment comment '主键'
|
id bigint auto_increment comment '主键'
|
||||||
primary key,
|
primary key,
|
||||||
conversation_id varchar(255) not null comment '会话ID',
|
conversation_id varchar(255) not null comment '会话ID',
|
||||||
conversation_title varchar(255) not null comment '会话名称',
|
conversation_title varchar(255) not null comment '会话名称',
|
||||||
conversation_type tinyint(1) not null comment '会话类型 1:单聊 2:群聊',
|
conversation_type varchar(1) not null comment '会话类型 1:单聊 2:群聊',
|
||||||
msg_id varchar(255) not null comment '消息ID',
|
msg_id varchar(255) not null comment '消息ID',
|
||||||
sender_nick varchar(255) not null comment '发送人昵称',
|
sender_nick varchar(64) not null comment '发送人昵称',
|
||||||
session_webhook varchar(255) not null comment '会话webhook url',
|
session_webhook varchar(512) not null comment '会话webhook url',
|
||||||
session_webhook_expire_time datetime not null comment '会话webhook url过期时间',
|
session_webhook_expired_time datetime not null comment '会话webhook url过期时间',
|
||||||
request_content varchar(255) not null comment '请求内容',
|
request_content varchar(4000) not null comment '请求内容',
|
||||||
handle_type varchar(64) not null comment '处理类型',
|
handle_type varchar(64) not null comment '处理类型',
|
||||||
response_content varchar(4000) default '' not null comment '响应内容',
|
response_content varchar(4000) default '' not null comment '响应内容',
|
||||||
response_msg_type varchar(255) default 'sampleText' not null comment '响应消息类型'
|
response_msg_type varchar(32) default '' not null comment '响应消息类型',
|
||||||
|
create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间',
|
||||||
|
update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
|
||||||
|
is_delete bigint default 0 not null comment '删除标志'
|
||||||
) comment '钉钉群消息记录';
|
) comment '钉钉群消息记录';
|
||||||
Loading…
Reference in New Issue
Block a user