feat(REQ-3114) - 新增钉钉群会话,与机器人对话和回复的基础能力
This commit is contained in:
parent
85425c2741
commit
68cac160e4
7
pom.xml
7
pom.xml
@ -21,6 +21,7 @@
|
||||
<mapstruct.version>1.4.2.Final</mapstruct.version>
|
||||
<revision>2.0.0-SNAPSHOT</revision>
|
||||
<feign-httpclient.version>11.8</feign-httpclient.version>
|
||||
<dingtalk.stream.version>1.3.7</dingtalk.stream.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
@ -28,6 +29,7 @@
|
||||
<module>riven-api</module>
|
||||
<module>integration-test</module>
|
||||
<module>riven-third</module>
|
||||
<module>riven-dingtalk</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -52,6 +54,11 @@
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
<version>${feign-httpclient.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.dingtalk.open</groupId>
|
||||
<artifactId>app-stream-client</artifactId>
|
||||
<version>${dingtalk.stream.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package cn.axzo.riven.client.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 通用的状态枚举
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:33
|
||||
*/
|
||||
@Getter
|
||||
public enum CommonStatusEnum {
|
||||
DISABLED(0, "DISABLED", "停用"),
|
||||
ENABLED(1, "ENABLED", "启用"),
|
||||
SUSPENDED(2, "SUSPENDED", "挂起")
|
||||
;
|
||||
|
||||
private final int code;
|
||||
private final String type;
|
||||
private final String desc;
|
||||
|
||||
CommonStatusEnum(int code, String type, String description) {
|
||||
this.code = code;
|
||||
this.type = type;
|
||||
this.desc = description;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package cn.axzo.riven.client.common.enums;
|
||||
|
||||
/**
|
||||
* 本类的信息来自于 {@see https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots}
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 20:13
|
||||
*/
|
||||
public enum DingTalkMsgTypeEnum {
|
||||
sampleText,
|
||||
sampleMarkdown,
|
||||
sampleImageMsg,
|
||||
sampleLink,
|
||||
sampleActionCard,
|
||||
sampleActionCard2,
|
||||
sampleActionCard3,
|
||||
sampleActionCard4,
|
||||
sampleActionCard5,
|
||||
sampleActionCard6,
|
||||
sampleAudio,
|
||||
sampleFile,
|
||||
sampleVideo,
|
||||
;
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package cn.axzo.riven.client.req;
|
||||
|
||||
import cn.axzo.riven.client.common.enums.CommonStatusEnum;
|
||||
import cn.axzo.riven.client.common.enums.sync.Channel;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 三方应用通用查询入参
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:30
|
||||
*/
|
||||
@Data
|
||||
public class ThirdApplicationReq {
|
||||
|
||||
/**
|
||||
* 渠道
|
||||
*/
|
||||
private Channel channel = Channel.DingTalk;
|
||||
|
||||
/**
|
||||
* 应用名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 应用描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
private String appId;
|
||||
|
||||
private String agentId;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String clientSecret;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private CommonStatusEnum status = CommonStatusEnum.ENABLED;
|
||||
}
|
||||
27
riven-dingtalk/pom.xml
Normal file
27
riven-dingtalk/pom.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>riven</artifactId>
|
||||
<groupId>cn.axzo</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>riven-dingtalk</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>riven-dingtalk</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.axzo</groupId>
|
||||
<artifactId>riven-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.dingtalk.open</groupId>
|
||||
<artifactId>app-stream-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.axzo.framework.data</groupId>
|
||||
<artifactId>axzo-data-mybatis-plus</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,50 @@
|
||||
package cn.axzo.riven.dingtalk.callback.robot;
|
||||
|
||||
import cn.axzo.framework.jackson.utility.JSON;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.strategy.RobotHandleStrategy;
|
||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||
import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
|
||||
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 机器人回调处理
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 15:33
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RobotMsgCallbackConsumer implements OpenDingTalkCallbackListener<ChatbotMessage, Void> {
|
||||
@Resource
|
||||
private List<RobotHandleStrategy> robotHandleStrategies;
|
||||
|
||||
/**
|
||||
* 执行回调
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Void execute(ChatbotMessage message) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("receive message : {}", JSON.toJSONString(message));
|
||||
}
|
||||
String keyword = message.getText().getContent().trim();
|
||||
robotHandleStrategies.stream()
|
||||
.filter(s -> s.support(keyword))
|
||||
.findFirst()
|
||||
.ifPresent(handle -> {
|
||||
Object result = handle.handle(message);
|
||||
if(result instanceof ReplyContext) {
|
||||
ReplyContext replyContext = (ReplyContext) result;
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package cn.axzo.riven.dingtalk.callback.robot.strategy;
|
||||
|
||||
/**
|
||||
* 抽象的机器人监听消息的处理策略
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:35
|
||||
*/
|
||||
public abstract class AbstractRobotHandleStrategy<T> implements RobotHandleStrategy<T> {
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package cn.axzo.riven.dingtalk.callback.robot.strategy;
|
||||
|
||||
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* 机器人处理消息的策略
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:37
|
||||
*/
|
||||
public interface RobotHandleStrategy<T> extends Ordered {
|
||||
@Override
|
||||
default int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean support(String keyword);
|
||||
|
||||
T handle(ChatbotMessage chatbotMessage);
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
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,55 @@
|
||||
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,12 @@
|
||||
package cn.axzo.riven.dingtalk.constant;
|
||||
|
||||
/**
|
||||
* dingtalk 相关的常量
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:44
|
||||
*/
|
||||
public interface DingTalkConstant {
|
||||
|
||||
String REGISTER = "注册";
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package cn.axzo.riven.dingtalk.listener;
|
||||
|
||||
import com.dingtalk.open.app.api.GenericEventListener;
|
||||
import com.dingtalk.open.app.api.message.GenericOpenDingTalkEvent;
|
||||
import com.dingtalk.open.app.stream.protocol.event.EventAckStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import shade.com.alibaba.fastjson2.JSON;
|
||||
import shade.com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
/**
|
||||
* Dingtalk 全量的事件监听
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 15:04
|
||||
*/
|
||||
@Slf4j
|
||||
public class DingTalkAllEventListener implements GenericEventListener {
|
||||
/**
|
||||
* 收到事件
|
||||
*
|
||||
* @param event
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public EventAckStatus onEvent(GenericOpenDingTalkEvent event) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("receive dingtalk event: {}", JSON.toJSONString(event));
|
||||
}
|
||||
try {
|
||||
//事件唯一Id
|
||||
String eventId = event.getEventId();
|
||||
//事件类型
|
||||
String eventType = event.getEventType();
|
||||
//事件产生时间
|
||||
Long bornTime = event.getEventBornTime();
|
||||
//获取事件体
|
||||
JSONObject bizData = event.getData();
|
||||
//处理事件
|
||||
process(bizData);
|
||||
//消费成功
|
||||
return EventAckStatus.SUCCESS;
|
||||
} catch (Exception e) {
|
||||
//消费失败
|
||||
return EventAckStatus.LATER;
|
||||
}
|
||||
}
|
||||
|
||||
private void process(JSONObject bizData) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package cn.axzo.riven.dingtalk.reply;
|
||||
|
||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||
|
||||
/**
|
||||
* 回复机器人
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 20:37
|
||||
*/
|
||||
public interface RobotReplyStrategy {
|
||||
|
||||
void reply(ReplyContext replyContext);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||
|
||||
/**
|
||||
* 基于 Http Api 的回复
|
||||
* https://github.com/open-dingtalk/dingtalk-stream-sdk-java-quick-start/blob/main/src/main/java/org/example/service/RobotGroupMessagesService.java
|
||||
* https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:28
|
||||
*/
|
||||
public class BasedHttpReply {
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||
|
||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||
import com.dingtalk.open.app.api.chatbot.BotReplier;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 基于 Session Webhook 的回复
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:28
|
||||
*/
|
||||
public class BasedSessionWebhookReply {
|
||||
|
||||
public void reply(ReplyContext replyContext) {
|
||||
try {
|
||||
// just reply generic text
|
||||
BotReplier.fromWebhook(replyContext.getSessionWebhook()).replyText(replyContext.getMsgContent());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package cn.axzo.riven.dingtalk.repository.entity;
|
||||
|
||||
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 应用基本信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:03
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName(value = "third_application", autoResultMap = true)
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class ThirdApplication extends BaseEntity<ThirdApplication> {
|
||||
/**
|
||||
* 应用所属渠道
|
||||
*/
|
||||
private String channel;
|
||||
|
||||
/**
|
||||
* 应用名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 应用的 AppId
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 应用的 AgentId
|
||||
*/
|
||||
private String agentId;
|
||||
|
||||
/**
|
||||
* ClientId
|
||||
* (原 AppKey 和 SuiteKey)
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* Client Secret
|
||||
* (原 AppSecret 和 SuiteSecret)
|
||||
*/
|
||||
private String clientSecret;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package cn.axzo.riven.dingtalk.repository.entity;
|
||||
|
||||
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 三方钉钉群会话信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:44
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName(value = "third_dingtalk_conversation", autoResultMap = true)
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class ThirdDingtalkConversation extends BaseEntity<ThirdDingtalkConversation> {
|
||||
/**
|
||||
* 会话 ID=群 ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 会话名称=群名称
|
||||
*/
|
||||
private String conversationTitle;
|
||||
/**
|
||||
* 会话类型:
|
||||
* 1:单聊
|
||||
* 2:群聊
|
||||
*/
|
||||
private Integer conversationType;
|
||||
|
||||
/**
|
||||
* 机器人所属企业
|
||||
*/
|
||||
private String chatbotCorpId;
|
||||
|
||||
/**
|
||||
* 机器人用户 ID(加密数据)
|
||||
*/
|
||||
private String chatbotUserId;
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package cn.axzo.riven.dingtalk.repository.entity;
|
||||
|
||||
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 三方钉钉群消息记录
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 18:18
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName(value = "third_dingtalk_msg_record", autoResultMap = true)
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class ThirdDingtalkMessageRecord extends BaseEntity<ThirdDingtalkMessageRecord> {
|
||||
/**
|
||||
* 会话 ID=群 ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 会话名称=群名称
|
||||
*/
|
||||
private String conversationTitle;
|
||||
|
||||
/**
|
||||
* 这条消息的 ID
|
||||
*/
|
||||
private String msgId;
|
||||
|
||||
/**
|
||||
* 谁@的机器人发的消息
|
||||
*/
|
||||
private String senderNick;
|
||||
|
||||
/**
|
||||
* 会话类型:
|
||||
* 1:单聊
|
||||
* 2:群聊
|
||||
*/
|
||||
private Integer conversationType;
|
||||
|
||||
/**
|
||||
* 会话级的 Webhook
|
||||
*/
|
||||
private String sessionWebhook;
|
||||
|
||||
/**
|
||||
* 会话级的 Webhook 过期时间
|
||||
*/
|
||||
private Date sessionWebhookExpiredTime;
|
||||
|
||||
/**
|
||||
* 请求的内容
|
||||
*/
|
||||
private String requestContext;
|
||||
|
||||
/**
|
||||
* 响应的内容
|
||||
*/
|
||||
private String responseContext;
|
||||
/**
|
||||
* 响应的消息类型
|
||||
*/
|
||||
private String responseMsgType;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package cn.axzo.riven.dingtalk.repository.mapper;
|
||||
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 应用基本信息 Mapper
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:03
|
||||
*/
|
||||
@Mapper
|
||||
public interface ThirdApplicationMapper extends BaseMapper<ThirdApplication> {
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package cn.axzo.riven.dingtalk.repository.mapper;
|
||||
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 应用基本信息 Mapper
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:03
|
||||
*/
|
||||
@Mapper
|
||||
public interface ThirdDingtalkConversationMapper extends BaseMapper<ThirdDingtalkConversation> {
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package cn.axzo.riven.dingtalk.repository.mapper;
|
||||
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 应用基本信息 Mapper
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:03
|
||||
*/
|
||||
@Mapper
|
||||
public interface ThirdDingtalkMessageRecordMapper extends BaseMapper<ThirdDingtalkMessageRecord> {
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package cn.axzo.riven.dingtalk.robot.connection;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdApplicationReq;
|
||||
import cn.axzo.riven.dingtalk.listener.DingTalkAllEventListener;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.RobotMsgCallbackConsumer;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdApplicationService;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.dingtalk.open.app.api.OpenDingTalkClient;
|
||||
import com.dingtalk.open.app.api.OpenDingTalkStreamClientBuilder;
|
||||
import com.dingtalk.open.app.api.callback.DingTalkStreamTopics;
|
||||
import com.dingtalk.open.app.api.security.AuthClientCredential;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 机器人应用自动连接
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:18
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AutoConnector {
|
||||
@Value("${third.dingtalk.auto-connect: true}")
|
||||
private Boolean autoConnect;
|
||||
@Resource
|
||||
private ThirdApplicationService applicationService;
|
||||
@Resource
|
||||
private RobotMsgCallbackConsumer robotMsgCallbackConsumer;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (autoConnect) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("auto connection robot by stream model");
|
||||
}
|
||||
doConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private void doConnection() {
|
||||
List<ThirdApplication> thirdApplications = applicationService.genericQuery(new ThirdApplicationReq());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("application query result count: {}", thirdApplications.size());
|
||||
}
|
||||
thirdApplications.forEach(application -> {
|
||||
try {
|
||||
OpenDingTalkClient client = OpenDingTalkStreamClientBuilder
|
||||
.custom()
|
||||
.credential(new AuthClientCredential(application.getClientId(), application.getClientSecret()))
|
||||
// 注册机器人回调,固定值
|
||||
.registerCallbackListener(DingTalkStreamTopics.BOT_MESSAGE_TOPIC, robotMsgCallbackConsumer)
|
||||
//注册事件监听
|
||||
.registerAllEventListener(new DingTalkAllEventListener())
|
||||
.build();
|
||||
client.start();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("robot connect success, channel: {}, name: {}, other: {}",
|
||||
application.getChannel(), application.getName(), JSON.toJSONString(application));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("robot connect error, channel: {}, name: {}, other: {}, errorInfo: {}",
|
||||
application.getChannel(), application.getName(), JSON.toJSONString(application), e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package cn.axzo.riven.dingtalk.robot.connection.model;
|
||||
|
||||
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 统一的回复上下文模型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 20:11
|
||||
*/
|
||||
@Data
|
||||
public class ReplyContext {
|
||||
|
||||
/**
|
||||
* 会话级的 Webhook
|
||||
*/
|
||||
private String sessionWebhook;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private DingTalkMsgTypeEnum msgType = DingTalkMsgTypeEnum.sampleText;
|
||||
|
||||
/**
|
||||
* 默认的消息类型下,可直接传入要发送的文本
|
||||
* 如果要使用其他 msgType,则用 data 属性进行传值
|
||||
*/
|
||||
private String msgContent;
|
||||
|
||||
/**
|
||||
* 根据不同的 msgType 传入不同请求模型
|
||||
* 参考:{@see https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots}
|
||||
*/
|
||||
private JSONObject data;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package cn.axzo.riven.dingtalk.service;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdApplicationReq;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 三方应用信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:15
|
||||
*/
|
||||
public interface ThirdApplicationService {
|
||||
|
||||
List<ThirdApplication> genericQuery(ThirdApplicationReq req);
|
||||
|
||||
ThirdApplication insert(ThirdApplication req);
|
||||
|
||||
ThirdApplication update(ThirdApplication req);
|
||||
|
||||
Integer delete(Long id);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package cn.axzo.riven.dingtalk.service;
|
||||
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||
|
||||
/**
|
||||
* 三方钉钉群会话信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:48
|
||||
*/
|
||||
public interface ThirdDingtalkConversationService {
|
||||
|
||||
/**
|
||||
* insert or update
|
||||
* @param conversation
|
||||
*/
|
||||
void upsert(ThirdDingtalkConversation conversation);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package cn.axzo.riven.dingtalk.service;
|
||||
|
||||
/**
|
||||
* 三方钉钉群消息记录
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:48
|
||||
*/
|
||||
public interface ThirdDingtalkMessageRecordService {
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
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.dingtalk.repository.entity.ThirdApplication;
|
||||
import cn.axzo.riven.dingtalk.repository.mapper.ThirdApplicationMapper;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdApplicationService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ThirdParty Application Service Implementation
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:16
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ThirdApplicationServiceImpl implements ThirdApplicationService {
|
||||
private final ThirdApplicationMapper thirdApplicationMapper;
|
||||
|
||||
@Override
|
||||
public List<ThirdApplication> genericQuery(ThirdApplicationReq req) {
|
||||
return thirdApplicationMapper.selectList(buildQueryWrapper(req));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThirdApplication insert(ThirdApplication entity) {
|
||||
thirdApplicationMapper.insert(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThirdApplication update(ThirdApplication entity) {
|
||||
thirdApplicationMapper.updateById(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer delete(Long id) {
|
||||
return thirdApplicationMapper.deleteById(id);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<ThirdApplication> buildQueryWrapper(ThirdApplicationReq req) {
|
||||
return new LambdaQueryWrapper<ThirdApplication>()
|
||||
.eq(ThirdApplication::getChannel, Objects.nonNull(req.getChannel()) ? req.getChannel() : Channel.DingTalk)
|
||||
.like(StringUtils.hasText(req.getName()), ThirdApplication::getName, req.getName())
|
||||
.like(StringUtils.hasText(req.getDescription()), ThirdApplication::getDescription, req.getDescription())
|
||||
.eq(StringUtils.hasText(req.getAppId()), ThirdApplication::getAppId, req.getAppId())
|
||||
.eq(StringUtils.hasText(req.getAgentId()), ThirdApplication::getAgentId, req.getAgentId())
|
||||
.eq(StringUtils.hasText(req.getClientId()), ThirdApplication::getClientId, req.getClientId())
|
||||
.eq(StringUtils.hasText(req.getClientSecret()), ThirdApplication::getClientSecret, req.getClientSecret())
|
||||
.eq(ThirdApplication::getStatus, Objects.nonNull(req.getStatus()) ? req.getStatus().getCode() : CommonStatusEnum.ENABLED.getCode())
|
||||
;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package cn.axzo.riven.dingtalk.service.impl;
|
||||
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkConversationMapper;
|
||||
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkMessageRecordMapper;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 三方钉钉群会话信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:48
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ThirdDingtalkConversationServiceImpl implements ThirdDingtalkConversationService {
|
||||
private final ThirdDingtalkConversationMapper thirdDingtalkConversationMapper;
|
||||
|
||||
@Override
|
||||
public void upsert(ThirdDingtalkConversation conversation) {
|
||||
if (Objects.nonNull(conversation.getConversationId())) {
|
||||
ThirdDingtalkConversation oldEntity = thirdDingtalkConversationMapper
|
||||
.selectOne(new LambdaQueryWrapper<ThirdDingtalkConversation>()
|
||||
.eq(ThirdDingtalkConversation::getConversationId, conversation.getConversationId()));
|
||||
BeanUtils.copyProperties(conversation, oldEntity);
|
||||
thirdDingtalkConversationMapper.updateById(oldEntity);
|
||||
} else {
|
||||
thirdDingtalkConversationMapper.insert(conversation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package cn.axzo.riven.dingtalk.service.impl;
|
||||
|
||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 三方钉钉群消息记录
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:48
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ThirdDingtalkMessageRecordServiceImpl implements ThirdDingtalkMessageRecordService {
|
||||
}
|
||||
52
riven-dingtalk/src/main/resources/sql/DDL.sql
Normal file
52
riven-dingtalk/src/main/resources/sql/DDL.sql
Normal file
@ -0,0 +1,52 @@
|
||||
create table third_application
|
||||
(
|
||||
id bigint not null auto_increment comment '主键'
|
||||
primary key,
|
||||
channel varchar(255) not null comment '渠道',
|
||||
name varchar(255) not null comment '应用名称',
|
||||
description varchar(255) not null comment '应用描述',
|
||||
app_id varchar(255) not null comment 'appId',
|
||||
agent_id varchar(255) not null comment 'agentId',
|
||||
client_id varchar(255) not null comment 'clientId',
|
||||
client_secret varchar(255) not null comment 'clientSecret',
|
||||
status tinyint(1) default 1 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 '第三方应用';
|
||||
|
||||
insert into third_application value (1, 'dingtalk', '多群公共机器人', '多群公共机器人',
|
||||
'c26a6e00-dd35-4bb5-a68a-36455ce1c001', '3282101532', 'dingxga1f6ghiefbzovb',
|
||||
'b-SSalEZAoRCWodldDtrdHMKbtJVQpVWP8-x1FYgdSTzPKaGJCDT7RH1j_0d-IKO', 1, now(),
|
||||
now(), 0);
|
||||
|
||||
create table third_dingtalk_conversation
|
||||
(
|
||||
id bigint not null auto_increment comment '主键'
|
||||
primary key,
|
||||
conversation_id varchar(255) not null comment '会话ID',
|
||||
conversation_title varchar(255) not null comment '会话名称',
|
||||
conversation_type tinyint(1) not null comment '会话类型 1:单聊 2:群聊',
|
||||
chatbot_corp_id varchar(255) not null comment '机器人所属企业',
|
||||
chatbot_user_id varchar(255) not null comment '机器人用户ID',
|
||||
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 '会话信息';
|
||||
|
||||
create table third_dingtalk_msg_record
|
||||
(
|
||||
id bigint auto_increment comment '主键'
|
||||
primary key,
|
||||
conversation_id varchar(255) not null comment '会话ID',
|
||||
conversation_title varchar(255) not null comment '会话名称',
|
||||
conversation_type tinyint(1) not null comment '会话类型 1:单聊 2:群聊',
|
||||
msg_id varchar(255) not null comment '消息ID',
|
||||
sender_nick varchar(255) not null comment '发送人昵称',
|
||||
session_webhook varchar(255) not null comment '会话webhook url',
|
||||
session_webhook_expire_time datetime not null comment '会话webhook url过期时间',
|
||||
request_content varchar(255) not null comment '请求内容',
|
||||
handle_type varchar(64) not null comment '处理类型',
|
||||
response_content varchar(4000) default '' not null comment '响应内容',
|
||||
response_msg_type varchar(255) default 'sampleText' not null comment '响应消息类型'
|
||||
) comment '钉钉群消息记录';
|
||||
@ -36,6 +36,12 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.axzo</groupId>
|
||||
<artifactId>riven-dingtalk</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
@ -109,7 +115,6 @@
|
||||
<dependency>
|
||||
<groupId>com.dingtalk.open</groupId>
|
||||
<artifactId>app-stream-client</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
|
||||
@ -11,6 +11,7 @@ import org.springframework.core.env.Environment;
|
||||
@Slf4j
|
||||
@SpringBootApplication(scanBasePackages = "cn.axzo")
|
||||
@EnableFeignClients(basePackages = {"cn.axzo"})
|
||||
@MapperScan({"cn.axzo.riven.dingtalk.**.mapper"})
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package cn.axzo.riven.config;
|
||||
|
||||
import cn.azxo.framework.common.annotation.OnlyPodsEnvironment;
|
||||
import cn.azxo.framework.common.logger.JobLoggerTemplate;
|
||||
import cn.azxo.framework.common.service.JobParamResolver;
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
@ -13,6 +14,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
* xxl-job config
|
||||
*
|
||||
*/
|
||||
@OnlyPodsEnvironment
|
||||
@Configuration
|
||||
public class XxlJobConfig {
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user