Merge branch 'release-20241113' into 'master'
Release 20241113 See merge request universal/infrastructure/backend/riven!67
This commit is contained in:
commit
c856816b56
1
.reviewboardrc
Normal file
1
.reviewboardrc
Normal file
@ -0,0 +1 @@
|
||||
REPOSITORY = 'riven'
|
||||
15
pom.xml
15
pom.xml
@ -21,13 +21,16 @@
|
||||
<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>
|
||||
<dingtalk.version>2.1.42</dingtalk.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>riven-server</module>
|
||||
<module>riven-api</module>
|
||||
<module>integration-test</module>
|
||||
<!--<module>integration-test</module>-->
|
||||
<module>riven-third</module>
|
||||
<module>riven-dingtalk</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -62,6 +65,16 @@
|
||||
<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>
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dingtalk</artifactId>
|
||||
<version>${dingtalk.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@ -18,5 +18,9 @@
|
||||
<groupId>cn.axzo.framework</groupId>
|
||||
<artifactId>axzo-consumer-spring-cloud-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.axzo.framework.rocketmq</groupId>
|
||||
<artifactId>axzo-common-rocketmq</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@ -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,26 @@
|
||||
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,
|
||||
// 非钉钉官方
|
||||
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 {
|
||||
send("riven-dingtalk", "riven-dingtalk-send", "发送钉钉消息"),
|
||||
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,42 @@
|
||||
package cn.axzo.riven.client.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-28 14:48
|
||||
*/
|
||||
public interface CommandParser<T> {
|
||||
|
||||
default Map<String, String> parseStringToKeyValuePairs(String input) {
|
||||
Map<String, String> keyValueMap = new HashMap<>();
|
||||
StringBuilder currentValue = new StringBuilder();
|
||||
String currentKey = null;
|
||||
|
||||
String[] parts = input.split(" ");
|
||||
for (String part : parts) {
|
||||
if (part.startsWith("-")) {
|
||||
// 如果当前有键值对需要保存,先保存上一个键值对
|
||||
if (currentKey!= null && currentValue.length() > 0) {
|
||||
keyValueMap.put(currentKey, currentValue.toString());
|
||||
currentValue.setLength(0);
|
||||
}
|
||||
currentKey = part;
|
||||
} else {
|
||||
currentValue.append(part).append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后一个键值对
|
||||
if (currentKey!= null && currentValue.length() > 0) {
|
||||
keyValueMap.put(currentKey, currentValue.toString().trim());
|
||||
}
|
||||
|
||||
return keyValueMap;
|
||||
}
|
||||
|
||||
T transferToModel(String content);
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package cn.axzo.riven.client.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 企业内部应用注册
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-28 18:31
|
||||
*/
|
||||
@Data
|
||||
public class DingtalkAppRegModel implements CommandParser<DingtalkAppRegModel> {
|
||||
private String name;
|
||||
private String description;
|
||||
private String appId;
|
||||
private String agentId;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private String robotCode;
|
||||
|
||||
@Override
|
||||
public DingtalkAppRegModel transferToModel(String content) {
|
||||
DingtalkAppRegModel model = new DingtalkAppRegModel();
|
||||
|
||||
Map<String, String> map = parseStringToKeyValuePairs(content);
|
||||
map.forEach((k, v) -> {
|
||||
switch (k) {
|
||||
case "-n":
|
||||
model.setName(v.trim());
|
||||
break;
|
||||
case "-d":
|
||||
model.setDescription(v.trim());
|
||||
break;
|
||||
case "-a":
|
||||
model.setAppId(v.trim());
|
||||
break;
|
||||
case "-A":
|
||||
model.setAgentId(v.trim());
|
||||
break;
|
||||
case "-ci":
|
||||
model.setClientId(v.trim());
|
||||
break;
|
||||
case "-cs":
|
||||
model.setClientSecret(v.trim());
|
||||
break;
|
||||
case "-r":
|
||||
model.setRobotCode(v.trim());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package cn.axzo.riven.client.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 站在使用者角度的命名
|
||||
* 对应 Riven 服务来说,是广播 MQ 用到的模型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-24 18:17
|
||||
*/
|
||||
@Data
|
||||
public class DingtalkReceiveMqModel implements Serializable {
|
||||
private final static long serialVersionUID = 1L;
|
||||
private String traceId;
|
||||
private String activeProfile;
|
||||
|
||||
/**
|
||||
* 会话 ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 机器人 Code
|
||||
*/
|
||||
private String robotCode;
|
||||
|
||||
/**
|
||||
* 消息 ID
|
||||
*/
|
||||
private String msgId;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package cn.axzo.riven.client.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 站在使用者角度的命名
|
||||
* 对应 Riven 服务来说,是监听 MQ 用到的模型
|
||||
* <p>
|
||||
* 由 riven 转发的钉钉群消息到 MQ 的模型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-24 18:17
|
||||
*/
|
||||
@Data
|
||||
public class DingtalkSendMqModel<T extends ReplyMessage<T>> implements Serializable {
|
||||
private final static long serialVersionUID = 1L;
|
||||
private String traceId;
|
||||
|
||||
/**
|
||||
* 会话 ID
|
||||
* 如果是无上下文的发送时,必传
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 机器人的 code,从应用中查看,仅自定义机器人有
|
||||
* 如果是无上下文的发送时,必传
|
||||
*/
|
||||
private String robotCode;
|
||||
|
||||
/**
|
||||
* 消息 ID
|
||||
*/
|
||||
private String msgId;
|
||||
|
||||
/**
|
||||
* 额外的,需要被@的用户
|
||||
*/
|
||||
private List<String> atUserIds;
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private ReplyMessage<T> message;
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package cn.axzo.riven.client.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 创建的待办的对话模型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-28 14:14
|
||||
*/
|
||||
@Data
|
||||
public class DingtalkTodoModel implements CommandParser<DingtalkTodoModel> {
|
||||
private String title;
|
||||
private String description;
|
||||
private Long dueDate;
|
||||
private Integer priority = 20;
|
||||
|
||||
@SneakyThrows
|
||||
public DingtalkTodoModel transferToModel(String content) {
|
||||
DingtalkTodoModel model = new DingtalkTodoModel();
|
||||
|
||||
Map<String, String> map = parseStringToKeyValuePairs(content);
|
||||
map.forEach((k, v) -> {
|
||||
switch (k) {
|
||||
case "-t":
|
||||
model.setTitle(v.trim());
|
||||
break;
|
||||
case "-d":
|
||||
model.setDescription(v.trim());
|
||||
break;
|
||||
case "-D":
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
try {
|
||||
model.setDueDate(sdf.parse(v.trim()).getTime());
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
break;
|
||||
case "-p":
|
||||
model.setPriority(Integer.parseInt(v.trim()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package cn.axzo.riven.client.model;
|
||||
|
||||
import cn.axzo.framework.jackson.utility.JSON;
|
||||
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 回复消息 POJO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-24 13:43
|
||||
*/
|
||||
public interface ReplyMessage<T> extends Serializable {
|
||||
default String getClz() {
|
||||
return this.getClass().getName();
|
||||
}
|
||||
|
||||
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,60 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 机器人 Code
|
||||
*/
|
||||
private String robotCode;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private CommonStatusEnum status = CommonStatusEnum.ENABLED;
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package cn.axzo.riven.client.req;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 创建待办入参模型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-31 10:34
|
||||
*/
|
||||
@Data
|
||||
public class ThirdCreateTodoReq {
|
||||
|
||||
/**
|
||||
* 应用的 ClientId
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 机器人 code
|
||||
*/
|
||||
private String robotCode;
|
||||
|
||||
/**
|
||||
* 待办的创建人
|
||||
*/
|
||||
private String operatorId;
|
||||
|
||||
/**
|
||||
* 待办的执行人
|
||||
*/
|
||||
@NotEmpty(message = "执行人不能为空")
|
||||
private List<String> executorIds;
|
||||
|
||||
/**
|
||||
* 待办的标题
|
||||
*/
|
||||
@NotBlank(message = "标题不能为空")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 待办的描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 待办的截止时间
|
||||
*/
|
||||
private Long dueDate;
|
||||
|
||||
/**
|
||||
* 优先级
|
||||
*/
|
||||
private Integer priority = 20;
|
||||
|
||||
public String toCommandStr() {
|
||||
String command = " createTodo ";
|
||||
if (StringUtils.hasText(title)) {
|
||||
command += " -t " + title;
|
||||
}
|
||||
if (StringUtils.hasText(description)) {
|
||||
command += " -d " + description;
|
||||
}
|
||||
if (Objects.nonNull(dueDate)) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
command += " -d " + sdf.format(dueDate);
|
||||
}
|
||||
command += " -p " + priority;
|
||||
return command;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package cn.axzo.riven.client.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 通用的钉钉消息记录查询入参
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 10:51
|
||||
*/
|
||||
@Data
|
||||
public class ThirdDingtalkMessageRecordReq {
|
||||
|
||||
private String conversationId;
|
||||
|
||||
private String conversationTitle;
|
||||
|
||||
private String conversationType;
|
||||
|
||||
private String msgId;
|
||||
|
||||
private String senderNick;
|
||||
|
||||
private String senderStaffId;
|
||||
|
||||
private String senderId;
|
||||
|
||||
private String robotCode;
|
||||
}
|
||||
@ -5,6 +5,8 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ -21,4 +23,6 @@ public class ThirdPartyUserReq {
|
||||
* 三方用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
private List<String> userIds;
|
||||
}
|
||||
|
||||
45
riven-dingtalk/pom.xml
Normal file
45
riven-dingtalk/pom.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<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>com.aliyun</groupId>
|
||||
<artifactId>dingtalk</artifactId>
|
||||
</dependency>
|
||||
<!-- 钉钉旧版sdk,不建议使用,单独放到本 pom 中 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.axzo.framework.data</groupId>
|
||||
<artifactId>axzo-data-mybatis-plus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.axzo.framework.rocketmq</groupId>
|
||||
<artifactId>axzo-common-rocketmq</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,51 @@
|
||||
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.callback.robot.model.ChatbotMessageWrapper;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 09:39
|
||||
*/
|
||||
public abstract class AbstractKeywordProcessor implements KeywordProcessor {
|
||||
private ChatbotMessageWrapper chatbotMessage;
|
||||
|
||||
@Override
|
||||
public void setChatbotMessage(ChatbotMessageWrapper chatbotMessage) {
|
||||
this.chatbotMessage = chatbotMessage;
|
||||
}
|
||||
|
||||
protected final ChatbotMessageWrapper getChatbotMessage() {
|
||||
return chatbotMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ReplyMessage process(String content) {
|
||||
String originalContent = content.trim();
|
||||
for (String s : getKeywords()) {
|
||||
content = content.trim().replaceFirst(s, "");
|
||||
}
|
||||
if (!StringUtils.hasText(content)) {
|
||||
ReplyMessage help = help();
|
||||
if (Objects.isNull(help) && Objects.equals(content, "")) {
|
||||
help = SampleText.from("当前命令(" + originalContent + ")未配置帮助信息哦");
|
||||
}
|
||||
return help;
|
||||
}
|
||||
return doProcess(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这里的 content 已经移除了命令字符串,仅需对内容或者参数做解析处理即可
|
||||
*
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
protected abstract ReplyMessage doProcess(String content);
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package cn.axzo.riven.dingtalk.callback.keyword;
|
||||
|
||||
import cn.axzo.riven.client.model.ReplyMessage;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.model.ChatbotMessageWrapper;
|
||||
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* 机器人支持的关键词
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-24 16:29
|
||||
*/
|
||||
public interface KeywordProcessor extends Ordered {
|
||||
default int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 透传钉钉机器人回调的消息
|
||||
*/
|
||||
void setChatbotMessage(ChatbotMessageWrapper chatbotMessage);
|
||||
|
||||
String[] getKeywords();
|
||||
|
||||
/**
|
||||
* 当使用了关键词,但是内容为空时,提出响应的使用提示
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
ReplyMessage help();
|
||||
|
||||
/**
|
||||
* 根据关键词的内容进行处理
|
||||
*
|
||||
* @param content 含有关键词的完整消息
|
||||
* @return
|
||||
*/
|
||||
ReplyMessage process(String content);
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package cn.axzo.riven.dingtalk.callback.keyword.impl;
|
||||
|
||||
import cn.axzo.riven.client.common.enums.sync.Channel;
|
||||
import cn.axzo.riven.client.model.DingtalkAppRegModel;
|
||||
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.callback.keyword.AbstractKeywordProcessor;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdApplicationService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 应用注册
|
||||
* <p>
|
||||
* 该关键词是为了通过现有的群中的机器人注册新的“机器人企业应用”
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 09:33
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class ApplicationRegisterKeywordProcessor extends AbstractKeywordProcessor {
|
||||
private final static String[] KEYWORD = {"appReg", "应用注册"};
|
||||
@Resource
|
||||
private ThirdApplicationService thirdApplicationService;
|
||||
|
||||
@Override
|
||||
public String[] getKeywords() {
|
||||
return KEYWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage help() {
|
||||
return SampleMarkdown.from("使用示例", "> ### 请按以下帮助信息发送消息哦 \n " +
|
||||
"> appReg ([option] text)* \n " +
|
||||
"> \n " +
|
||||
"> **完整语法**:appReg -n 应用名称 -d 应用描述 -a appId -A agentId -ci clientId -cs clientSecret -r 机器人Code \n " +
|
||||
"> \n " +
|
||||
" \n " +
|
||||
"> 以上参数信息请从[钉钉后台](https://open-dev.dingtalk.com/)获取!\n" +
|
||||
" \n " +
|
||||
"#### 使用示例: \n " +
|
||||
"appReg -n 测试应用 -d 测试应用描述 -a 123 -A 123 -ci 123 -cs 123 -r robotCode123");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage doProcess(String content) {
|
||||
DingtalkAppRegModel model = new DingtalkAppRegModel().transferToModel(content);
|
||||
|
||||
ThirdApplication application = new ThirdApplication();
|
||||
application.setChannel(Channel.DingTalk.name());
|
||||
application.setName(model.getName());
|
||||
application.setDescription(model.getDescription());
|
||||
application.setAppId(model.getAppId());
|
||||
application.setAgentId(model.getAgentId());
|
||||
application.setClientId(model.getClientId());
|
||||
application.setClientSecret(model.getClientSecret());
|
||||
application.setRobotCode(model.getRobotCode());
|
||||
try {
|
||||
thirdApplicationService.insert(application);
|
||||
} catch (Exception e) {
|
||||
return SampleText.from(String.format("注册应用发生异常:%s", e.getMessage()));
|
||||
}
|
||||
return SampleText.from("注册应用成功");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,200 @@
|
||||
package cn.axzo.riven.dingtalk.callback.keyword.impl;
|
||||
|
||||
import cn.axzo.framework.jackson.utility.JSON;
|
||||
import cn.axzo.riven.client.model.DingtalkTodoModel;
|
||||
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.callback.keyword.AbstractKeywordProcessor;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.model.ChatbotMessageWrapper;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdPartyUserV2;
|
||||
import cn.axzo.riven.dingtalk.robot.basic.ApplicationAccessTokenService;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdPartyUserService;
|
||||
import com.aliyun.dingtalktodo_1_0.Client;
|
||||
import com.aliyun.dingtalktodo_1_0.models.CreateTodoTaskHeaders;
|
||||
import com.aliyun.dingtalktodo_1_0.models.CreateTodoTaskRequest;
|
||||
import com.aliyun.dingtalktodo_1_0.models.CreateTodoTaskResponse;
|
||||
import com.aliyun.tea.TeaException;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyun.teautil.Common;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import com.dingtalk.api.DefaultDingTalkClient;
|
||||
import com.dingtalk.api.DingTalkClient;
|
||||
import com.dingtalk.api.request.OapiV2UserGetRequest;
|
||||
import com.dingtalk.api.response.OapiV2UserGetResponse;
|
||||
import com.dingtalk.open.app.api.models.bot.MentionUser;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.taobao.api.ApiException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 创建待办关键词的处理器
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 17:36
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class CreateTodoKeywordProcessor extends AbstractKeywordProcessor {
|
||||
@Resource
|
||||
private ThirdPartyUserService thirdPartyUserService;
|
||||
private final static String[] KEYWORD = {"createTodo", "创建待办"};
|
||||
|
||||
@Override
|
||||
public String[] getKeywords() {
|
||||
return KEYWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当使用了关键词,但是内容为空时,提出响应的使用提示
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public ReplyMessage help() {
|
||||
return SampleMarkdown.from("使用示例", "> ### 请按以下帮助信息发送消息哦 \n " +
|
||||
"> createTodo <@用户...> ([option] text)* \n " +
|
||||
"> \n " +
|
||||
"> **完整语法**:createTodo @用户 -d 标题 -d 描述 -D 截止时间 -p 优先级 \n " +
|
||||
" \n " +
|
||||
"> **参数解析**:用户:最少需要@一个用户,可以是多个 \n " +
|
||||
"> \n " +
|
||||
"> **优先级**:10(较低),20(普通),30(紧急),40(非常紧急) \n " +
|
||||
" \n " +
|
||||
" \n " +
|
||||
"#### 使用示例: \n " +
|
||||
" \n " +
|
||||
"createTodo @张三 -t 请完成REQ-3115的状态 -d 需求已经提测 -D 2024-10-28 17:00:00 -p 30");
|
||||
}
|
||||
|
||||
/**
|
||||
* https://open.dingtalk.com/document/orgapp/event-todo-task-create
|
||||
*
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
protected ReplyMessage doProcess(String content) {
|
||||
DingtalkTodoModel model = new DingtalkTodoModel().transferToModel(content);
|
||||
String accessToken = ApplicationAccessTokenService.getAccessToken(getChatbotMessage().getRobotCode());
|
||||
if (!StringUtils.hasText(accessToken)) {
|
||||
return SampleText.from("未成功获取 AccessToken");
|
||||
}
|
||||
return createTodoTask(accessToken, getChatbotMessage(), model);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* https://open.dingtalk.com/document/orgapp/add-dingtalk-to-do-task
|
||||
* <p>
|
||||
* 测试使用的数据
|
||||
* senderId: vgmlrP0L0iiTiP0Y6lVhiiarQiEiE
|
||||
* executorId: rcnSH26ZiPevENaCkHgFHwQiEiE
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private ReplyMessage createTodoTask(String accessToken, ChatbotMessageWrapper chatbotMessage, DingtalkTodoModel model) throws Exception {
|
||||
if (CollectionUtils.isEmpty(chatbotMessage.getAtUsers()) || chatbotMessage.getAtUsers().size() <= 1) {
|
||||
return SampleText.from("创建不成功:请在对话中@需要被创建待办的用户");
|
||||
}
|
||||
|
||||
Set<String> users = new HashSet<>();
|
||||
users.add(chatbotMessage.getSenderStaffId());
|
||||
users.addAll(chatbotMessage.getAtUsers().stream().map(MentionUser::getStaffId).filter(StringUtils::hasText).distinct().collect(Collectors.toList()));
|
||||
if (log.isDebugEnabled()) {
|
||||
log.info("使用 userId 查询三方用户信息: {}", JSON.toJSONString(users));
|
||||
}
|
||||
|
||||
Map<String, ThirdPartyUserV2> userMap = thirdPartyUserService.getUserInfos(Lists.newArrayList(users))
|
||||
.stream().collect(Collectors.toMap(ThirdPartyUserV2::getUserId, Function.identity(), (s, t) -> s));
|
||||
|
||||
if (users.size() != userMap.size()) {
|
||||
return SampleText.from("创建不成功:ThirdPartyUser 的用户信息不完整");
|
||||
}
|
||||
|
||||
String senderUnionId = userMap.getOrDefault(chatbotMessage.getSenderStaffId(), new ThirdPartyUserV2()).getUnionId();
|
||||
userMap.remove(chatbotMessage.getSenderStaffId());
|
||||
|
||||
List<String> executorUnionIds = userMap.values().stream().map(ThirdPartyUserV2::getUnionId).distinct().collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(executorUnionIds)) {
|
||||
return SampleText.from("创建不成功:执行人为空,请确认组织人员基础数据是否完整");
|
||||
}
|
||||
|
||||
Client client = CreateTodoKeywordProcessor.createClient();
|
||||
CreateTodoTaskHeaders createTodoTaskHeaders = new CreateTodoTaskHeaders();
|
||||
createTodoTaskHeaders.xAcsDingtalkAccessToken = accessToken;
|
||||
|
||||
CreateTodoTaskRequest.CreateTodoTaskRequestNotifyConfigs notifyConfigs = new CreateTodoTaskRequest.CreateTodoTaskRequestNotifyConfigs()
|
||||
.setDingNotify("1");
|
||||
CreateTodoTaskRequest createTodoTaskRequest = new CreateTodoTaskRequest()
|
||||
.setOperatorId(senderUnionId)
|
||||
.setSubject(model.getTitle())
|
||||
.setCreatorId(senderUnionId)
|
||||
.setDescription(model.getDescription())
|
||||
.setDueTime(model.getDueDate())
|
||||
.setExecutorIds(executorUnionIds)
|
||||
.setIsOnlyShowExecutor(true)
|
||||
.setPriority(model.getPriority())
|
||||
.setNotifyConfigs(notifyConfigs);
|
||||
|
||||
try {
|
||||
CreateTodoTaskResponse response = client.createTodoTaskWithOptions(senderUnionId, createTodoTaskRequest, createTodoTaskHeaders, new RuntimeOptions());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.info("钉钉响应:{}", JSON.toJSONString(response));
|
||||
}
|
||||
} catch (TeaException err) {
|
||||
if (!Common.empty(err.code) && !Common.empty(err.message)) {
|
||||
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||
return SampleText.from(String.format("调用钉钉 OAPI 异常:code:%s, message: %s", err.getCode(), err.getMessage()));
|
||||
}
|
||||
} catch (Exception _err) {
|
||||
TeaException err = new TeaException(_err.getMessage(), _err);
|
||||
if (!Common.empty(err.code) && !Common.empty(err.message)) {
|
||||
// err 中含有 code 和 message 属性,可帮助开发定位问题
|
||||
return SampleText.from(String.format("调用钉钉 OAPI 异常:code:%s, message: %s", err.getCode(), err.getMessage()));
|
||||
}
|
||||
}
|
||||
return SampleText.from("创建钉钉待办成功");
|
||||
}
|
||||
|
||||
public static Client createClient() throws Exception {
|
||||
com.aliyun.teaopenapi.models.Config config = new Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
return new Client(config);
|
||||
}
|
||||
|
||||
|
||||
//不推荐使用该方法,会消耗API 使用量
|
||||
public static OapiV2UserGetResponse getUserInfo(String accessToken, String userId) {
|
||||
|
||||
try {
|
||||
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
|
||||
OapiV2UserGetRequest req = new OapiV2UserGetRequest();
|
||||
req.setUserid(userId);
|
||||
req.setLanguage("zh_CN");
|
||||
OapiV2UserGetResponse rsp = client.execute(req, accessToken);
|
||||
return rsp;
|
||||
} catch (ApiException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package cn.axzo.riven.dingtalk.callback.keyword.impl;
|
||||
|
||||
import cn.axzo.riven.client.model.ReplyMessage;
|
||||
import cn.axzo.riven.client.model.SampleText;
|
||||
import cn.axzo.riven.dingtalk.callback.keyword.AbstractKeywordProcessor;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.strategy.impl.TransferToMQStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 兜底的关键词
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-24 17:01
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DefaultKeywordProcessor extends AbstractKeywordProcessor {
|
||||
@Resource
|
||||
private TransferToMQStrategy transferToMQStrategy;
|
||||
private final static String[] KEYWORD = {""};
|
||||
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getKeywords() {
|
||||
return KEYWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage help() {
|
||||
return SampleText.from("需要我帮你做什么呢?不妨@我发送“menu”或者“菜单”试试吧!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage doProcess(String content) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("不是内置的关键词,将转发给其他后端服务");
|
||||
}
|
||||
return transferToMQStrategy.doHandle(getChatbotMessage());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package cn.axzo.riven.dingtalk.callback.keyword.impl;
|
||||
|
||||
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.callback.keyword.AbstractKeywordProcessor;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 群注册关键词的处理器
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-24 16:31
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class GroupRegisterKeywordProcessor extends AbstractKeywordProcessor {
|
||||
private final static String[] KEYWORD = {"groupReg", "群注册"};
|
||||
private final ThirdDingtalkConversationService thirdDingtalkConversationService;
|
||||
|
||||
@Override
|
||||
public String[] getKeywords() {
|
||||
return KEYWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage help() {
|
||||
return SampleMarkdown.from("使用示例", "> ### 请按以下帮助信息发送消息哦 \n " +
|
||||
"> groupReg (applicationName) \n " +
|
||||
"> \n " +
|
||||
"> **完整语法**:groupReg 应用名称 \n " +
|
||||
" \n " +
|
||||
"> **参数描述** \n " +
|
||||
"> 应用名称一般建议使用后端服务名,方便后续管理 \n " +
|
||||
" \n " +
|
||||
" \n " +
|
||||
"### 使用示例: \n " +
|
||||
"groupReg workflowEngine");
|
||||
}
|
||||
|
||||
/**
|
||||
* 主要功能如下:
|
||||
* 1. 单纯注册会话
|
||||
* 2. 注册会话并绑定后端服务
|
||||
* 3. 会话重绑定后端服务
|
||||
*
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public ReplyMessage doProcess(String content) {
|
||||
ThirdDingtalkConversation conversation = new ThirdDingtalkConversation();
|
||||
BeanUtil.copyProperties(getChatbotMessage(), conversation);
|
||||
|
||||
conversation.setApplicationName(content.trim());
|
||||
|
||||
try {
|
||||
// 进行机器人和会话的绑定注册
|
||||
thirdDingtalkConversationService.upsert(conversation);
|
||||
} catch (Exception e) {
|
||||
return SampleText.from(String.format("群注册发送异常:%s", e.getMessage()));
|
||||
}
|
||||
return SampleText.from(String.format("群注册成功,并已关联[%s]后端服务!", conversation.getApplicationName()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package cn.axzo.riven.dingtalk.callback.keyword.impl;
|
||||
|
||||
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.callback.keyword.AbstractKeywordProcessor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 主菜单
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-24 16:28
|
||||
*/
|
||||
@Component
|
||||
public class MenuKeywordProcessor extends AbstractKeywordProcessor {
|
||||
private final static String[] KEYWORD = {"menu", "菜单"};
|
||||
|
||||
@Override
|
||||
public String[] getKeywords() {
|
||||
return KEYWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage help() {
|
||||
String content = "### 目前支持的命令如下哦 \n " +
|
||||
" \n " +
|
||||
"> 可以@我并发送命令获取特定命令的帮助 \n " +
|
||||
" \n " +
|
||||
" **命令列表**: \n " +
|
||||
"1. 帮助(*menu*):展示本信息 \n " +
|
||||
"2. 群注册(*groupReg*):将当前群与某个后端应用绑定 \n " +
|
||||
"3. 创建待办(*createTodo*):为指定用户创建钉钉待办 \n " +
|
||||
"4. 应用注册(*aggReg*):利用该机器人,直接注册本公司下的‘其他企业内部应用’ \n " +
|
||||
"99. 其他非以上关键词的消息,将转发给群关联的后端服务。 \n ";
|
||||
|
||||
return doProcess(content);
|
||||
}
|
||||
|
||||
public ReplyMessage doProcess(String content) {
|
||||
return SampleMarkdown.from("菜单", content);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package cn.axzo.riven.dingtalk.callback.robot;
|
||||
|
||||
import cn.axzo.framework.jackson.utility.JSON;
|
||||
import cn.axzo.riven.client.model.ReplyMessage;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.model.ChatbotMessageWrapper;
|
||||
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.hutool.core.date.DateUtil;
|
||||
import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
|
||||
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<ChatbotMessageWrapper, Void> {
|
||||
@Resource
|
||||
private List<RobotHandleStrategy<? extends ReplyMessage>> robotHandleStrategies;
|
||||
@Resource
|
||||
private RobotReply robotReply;
|
||||
|
||||
/**
|
||||
* 执行回调
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Void execute(ChatbotMessageWrapper 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 -> {
|
||||
ReplyContext context = ReplyContext.builder()
|
||||
.conversationId(message.getConversationId())
|
||||
.sessionWebhook(message.getSessionWebhook())
|
||||
.sessionWebhookExpiredDate(DateUtil.date(message.getSessionWebhookExpiredTime()))
|
||||
.replyMessage(handle.handle(message))
|
||||
.build();
|
||||
robotReply.reply(context);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package cn.axzo.riven.dingtalk.callback.robot.model;
|
||||
|
||||
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
|
||||
|
||||
/**
|
||||
* 包装后的 Stream 回调对象
|
||||
*
|
||||
* 由于 ChatbotMessage 类型可能较老,经过与钉钉技术支持沟通,通过 JSONObject 能完整返回数据,但使用不友好
|
||||
* 所以本来扩展包装
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 17:21
|
||||
*/
|
||||
public class ChatbotMessageWrapper extends ChatbotMessage {
|
||||
private String senderPlatform;
|
||||
|
||||
private String robotCode;
|
||||
|
||||
public String getSenderPlatform() {
|
||||
return senderPlatform;
|
||||
}
|
||||
|
||||
public void setSenderPlatform(String senderPlatform) {
|
||||
this.senderPlatform = senderPlatform;
|
||||
}
|
||||
|
||||
public String getRobotCode() {
|
||||
return robotCode;
|
||||
}
|
||||
|
||||
public void setRobotCode(String robotCode) {
|
||||
this.robotCode = robotCode;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
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.common.enums.DingTalkMsgTypeEnum;
|
||||
import cn.axzo.riven.client.model.ReplyMessage;
|
||||
import cn.axzo.riven.client.model.SampleText;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.model.ChatbotMessageWrapper;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 抽象的机器人监听消息的处理策略
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:35
|
||||
*/
|
||||
public abstract class AbstractRobotHandleStrategy implements RobotHandleStrategy<ReplyMessage> {
|
||||
@Resource
|
||||
private ThirdDingtalkMessageRecordService thirdDingtalkMessageRecordService;
|
||||
|
||||
protected final String getContent(ChatbotMessageWrapper chatbotMessage) {
|
||||
if (Objects.nonNull(chatbotMessage)) {
|
||||
return chatbotMessage.getText().getContent();
|
||||
}
|
||||
throw new ServiceException("获取用户发送的消息内容失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage handle(ChatbotMessageWrapper 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) {
|
||||
record.setResponseMsgType(replyMessage.getClz());
|
||||
record.setResponseContent(replyMessage.toJson());
|
||||
thirdDingtalkMessageRecordService.update(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志
|
||||
*
|
||||
* @param chatbotMessage
|
||||
* @return
|
||||
*/
|
||||
private ThirdDingtalkMessageRecord insertLog(ChatbotMessageWrapper 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.setSenderStaffId(chatbotMessage.getSenderStaffId());
|
||||
record.setSenderId(chatbotMessage.getSenderId());
|
||||
record.setSessionWebhook(chatbotMessage.getSessionWebhook());
|
||||
record.setSessionWebhookExpiredTime(DateUtil.date(chatbotMessage.getSessionWebhookExpiredTime()));
|
||||
record.setRobotCode(record.getRobotCode());
|
||||
record.setHandleType(this.getClass().getSimpleName());
|
||||
record.setRequestContent(JSON.toJSONString(chatbotMessage));
|
||||
return thirdDingtalkMessageRecordService.insert(record);
|
||||
}
|
||||
|
||||
protected abstract ReplyMessage doHandle(ChatbotMessageWrapper chatbotMessage);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package cn.axzo.riven.dingtalk.callback.robot.strategy;
|
||||
|
||||
import cn.axzo.riven.dingtalk.callback.robot.model.ChatbotMessageWrapper;
|
||||
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(ChatbotMessageWrapper chatbotMessage);
|
||||
}
|
||||
@ -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.impl.DefaultKeywordProcessor;
|
||||
import cn.axzo.riven.dingtalk.callback.keyword.KeywordProcessor;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.model.ChatbotMessageWrapper;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.strategy.AbstractRobotHandleStrategy;
|
||||
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;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 注册会话
|
||||
* <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 Set<String> words;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
keywordProcessorHandles.forEach(bean -> {
|
||||
words.addAll(Lists.newArrayList(bean.getKeywords()));
|
||||
for (String keyword : bean.getKeywords()) {
|
||||
keywordMap.put(keyword, bean);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean support(String keyword) {
|
||||
for (String word : words) {
|
||||
if (keyword.trim().startsWith(word)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplyMessage doHandle(ChatbotMessageWrapper chatbotMessage) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(" KeywordProcessorStrategy Entrance ");
|
||||
}
|
||||
String content = getContent(chatbotMessage);
|
||||
|
||||
KeywordProcessor keywordProcessor = getKeywordHandle(content);
|
||||
keywordProcessor.setChatbotMessage(chatbotMessage);
|
||||
|
||||
return keywordProcessor.process(content);
|
||||
}
|
||||
|
||||
private KeywordProcessor getKeywordHandle(String content) {
|
||||
content = content.trim();
|
||||
if (StringUtils.hasText(content)) {
|
||||
for (String word : words) {
|
||||
if (content.startsWith(word)) {
|
||||
return keywordMap.get(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DefaultKeywordProcessor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE + 2;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
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.model.ChatbotMessageWrapper;
|
||||
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.beans.factory.annotation.Value;
|
||||
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;
|
||||
@Value("${spring.profiles.active}")
|
||||
private String activeProfile;
|
||||
@Resource
|
||||
private ThirdDingtalkConversationService thirdDingtalkConversationService;
|
||||
public static 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(ChatbotMessageWrapper chatbotMessage) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(" DefaultTransferToMQStrategy Entrance ");
|
||||
}
|
||||
|
||||
ThirdDingtalkConversation conversation = checkConversationExists(chatbotMessage);
|
||||
if (Objects.isNull(conversation)) {
|
||||
return SampleText.from("当前群未关联后端服务,请@机器人发送“groupReg”或“群注册”查看注册帮助!");
|
||||
}
|
||||
|
||||
SampleMessageQueue messageQueue = SampleMessageQueue.from(conversation.getApplicationName(), getContent(chatbotMessage));
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("发送 MQ 的消息数据:{}", messageQueue.toJson());
|
||||
}
|
||||
|
||||
DingtalkReceiveMqModel model = new DingtalkReceiveMqModel();
|
||||
model.setActiveProfile(activeProfile);
|
||||
model.setTraceId(messageQueue.getTraceId());
|
||||
model.setConversationId(chatbotMessage.getConversationId());
|
||||
model.setMsgId(chatbotMessage.getMsgId());
|
||||
model.setContent(getContent(chatbotMessage));
|
||||
model.setRobotCode(chatbotMessage.getRobotCode());
|
||||
|
||||
producer.send(Event.builder()
|
||||
.shardingKey(chatbotMessage.getConversationId())
|
||||
.eventCode(DingtalkEventEnum.receive.getEventCode())
|
||||
.targetId(messageQueue.getTraceId())// traceId
|
||||
.targetType(conversation.getApplicationName())
|
||||
.data(model)
|
||||
.build());
|
||||
return messageQueue;
|
||||
}
|
||||
|
||||
private ThirdDingtalkConversation checkConversationExists(ChatbotMessageWrapper chatbotMessage) {
|
||||
return conversationMap.getOrDefault(chatbotMessage.getConversationId(), null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package cn.axzo.riven.dingtalk.config;
|
||||
|
||||
import cn.axzo.framework.rocketmq.BaseListener;
|
||||
import cn.axzo.framework.rocketmq.EventConsumer;
|
||||
import org.apache.rocketmq.common.message.MessageExt;
|
||||
import org.apache.rocketmq.spring.annotation.ConsumeMode;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.annotation.SelectorType;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 监听所有业务方需要发送的钉钉消息的 MQ 消费组
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 10:32
|
||||
*/
|
||||
@Configuration
|
||||
public class DingTalkRocketConfiguration {
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "rocketmq.name-server")
|
||||
@RocketMQMessageListener(topic = "${rocketmq.topic.sync:topic_third_party_sync_event}_" + "${spring.profiles.active}",
|
||||
consumerGroup = "GID_${spring.application.name}_riven_consumer",
|
||||
consumeMode = ConsumeMode.CONCURRENTLY,
|
||||
maxReconsumeTimes = 0,
|
||||
selectorType = SelectorType.TAG,
|
||||
selectorExpression = "riven-dingtalk-send",
|
||||
nameServer = "${rocketmq.name-server}"
|
||||
)
|
||||
public static class ReplyMessageRocketConsumer extends BaseListener implements RocketMQListener<MessageExt> {
|
||||
@Resource
|
||||
private EventConsumer eventConsumer;
|
||||
|
||||
@Override
|
||||
public void onMessage(MessageExt message) {
|
||||
super.onEvent(message, eventConsumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package cn.axzo.riven.dingtalk.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 支持刷新的配置
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-28 19:32
|
||||
*/
|
||||
@Component
|
||||
@RefreshScope
|
||||
@Data
|
||||
public class RefreshableConfiguration {
|
||||
|
||||
@Value("${dingtalk.stream.enabled:false}")
|
||||
private Boolean enableDingtalkStream;
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package cn.axzo.riven.dingtalk.constant;
|
||||
|
||||
/**
|
||||
* dingtalk 相关的常量
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:44
|
||||
*/
|
||||
public interface DingTalkConstant {
|
||||
String REGISTER = "注册";
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
package cn.axzo.riven.dingtalk.controller;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdApplicationReq;
|
||||
import cn.axzo.riven.client.req.ThirdCreateTodoReq;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.RobotMsgCallbackConsumer;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.model.ChatbotMessageWrapper;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
import cn.axzo.riven.dingtalk.robot.connection.AutoConnector;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdApplicationService;
|
||||
import cn.azxo.framework.common.model.CommonResponse;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import com.dingtalk.open.app.api.OpenDingTalkClient;
|
||||
import com.dingtalk.open.app.api.models.bot.MentionUser;
|
||||
import com.dingtalk.open.app.api.models.bot.MessageContent;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import shade.com.alibaba.fastjson2.JSON;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static cn.axzo.riven.dingtalk.robot.connection.AutoConnector.clientMap;
|
||||
|
||||
/**
|
||||
* 三方应用管理
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 09:24
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/webApi/dingtalk")
|
||||
public class DingtalkController {
|
||||
@Value("${spring.profiles.active}")
|
||||
private String activeProfile;
|
||||
@Resource
|
||||
private ThirdApplicationService thirdApplicationService;
|
||||
@Resource
|
||||
private AutoConnector autoConnector;
|
||||
@Resource
|
||||
private RobotMsgCallbackConsumer robotMsgCallbackConsumer;
|
||||
|
||||
@PostMapping("/enabled")
|
||||
public CommonResponse<String> changeStream(@RequestParam(required = false) String appId, @RequestParam Boolean enable) {
|
||||
List<String> envs = Lists.newArrayList("dev", "local");
|
||||
if (!envs.contains(activeProfile)) {
|
||||
return CommonResponse.success("非 dev/local 环境不可用");
|
||||
}
|
||||
OpenDingTalkClient client = clientMap.getOrDefault(appId, null);
|
||||
try {
|
||||
if (enable && Objects.isNull(client)) {
|
||||
ThirdApplicationReq query = new ThirdApplicationReq();
|
||||
query.setAppId(appId);
|
||||
List<ThirdApplication> applications = thirdApplicationService.genericQuery(query);
|
||||
autoConnector.startStream(applications);
|
||||
} else {
|
||||
if (Objects.nonNull(client)) {
|
||||
client.stop();
|
||||
clientMap.remove(appId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return CommonResponse.error(e.getMessage());
|
||||
}
|
||||
return CommonResponse.success("调整成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用注册
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/application/register")
|
||||
public CommonResponse appRegister(@Validated @RequestBody ThirdApplicationReq req) {
|
||||
ThirdApplication application = new ThirdApplication();
|
||||
BeanUtil.copyProperties(req, application);
|
||||
thirdApplicationService.insert(application);
|
||||
return CommonResponse.success("应用注册成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建待办
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("todo/create")
|
||||
public CommonResponse<Boolean> createTodo(@Validated @RequestBody ThirdCreateTodoReq req) {
|
||||
ChatbotMessageWrapper messageWrapper = new ChatbotMessageWrapper();
|
||||
messageWrapper.setConversationId("mock-"+ UUID.fastUUID());
|
||||
messageWrapper.setConversationTitle("mock-title");
|
||||
messageWrapper.setConversationType("2");
|
||||
messageWrapper.setMsgId("mock-msgid-"+ UUID.fastUUID());
|
||||
messageWrapper.setSenderNick("mock-name");
|
||||
messageWrapper.setSenderStaffId(req.getOperatorId());
|
||||
messageWrapper.setSenderId("mock-sender-id");
|
||||
messageWrapper.setSessionWebhook("");
|
||||
List<MentionUser> atUsers = new ArrayList<>();
|
||||
MentionUser robotUser = new MentionUser();
|
||||
robotUser.setDingtalkId("$:LWCP_v1:$BQYU6rJ24ZSqNq30n47vbAdAwuFkEDtq");
|
||||
atUsers.add(robotUser);
|
||||
|
||||
List<MentionUser> executors = req.getExecutorIds().stream().map(userId -> {
|
||||
MentionUser mentionUser = new MentionUser();
|
||||
mentionUser.setDingtalkId(userId);
|
||||
mentionUser.setStaffId(userId);
|
||||
return mentionUser;
|
||||
}).collect(Collectors.toList());
|
||||
atUsers.addAll(executors);
|
||||
messageWrapper.setAtUsers(atUsers);
|
||||
messageWrapper.setSessionWebhookExpiredTime(req.getDueDate());
|
||||
|
||||
messageWrapper.setMsgtype("text");
|
||||
MessageContent text = new MessageContent();
|
||||
text.setContent(req.toCommandStr());
|
||||
messageWrapper.setText(text);
|
||||
messageWrapper.setRobotCode(StringUtils.hasText(req.getRobotCode())? req.getRobotCode() : req.getClientId());
|
||||
robotMsgCallbackConsumer.execute(messageWrapper);
|
||||
return CommonResponse.success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/test/consumer")
|
||||
public CommonResponse test() {
|
||||
String json = "{\"conversationId\":\"cidJaQrl1/gNuYwUdwnxXv9rw==\",\"atUsers\":[{\"dingtalkId\":\"$:LWCP_v1:$V3m0NHKO2lgEZLHNkq3kCQ==\",\"staffId\":\"17189335664858211\"},{\"dingtalkId\":\"$:LWCP_v1:$BQYU6rJ24ZSqNq30n47vbAdAwuFkEDtq\"}],\"chatbotCorpId\":\"ding509fc72d6685d56d4ac5d6980864d335\",\"chatbotUserId\":\"$:LWCP_v1:$BQYU6rJ24ZSqNq30n47vbAdAwuFkEDtq\",\"msgId\":\"msgle3bAxyvcgljY51KWNg2jA==\",\"senderNick\":\"张勇杰\",\"senderStaffId\":\"16643308030554669\",\"sessionWebhookExpiredTime\":1730346122013,\"createAt\":1730340721715,\"senderCorpId\":\"ding509fc72d6685d56d4ac5d6980864d335\",\"conversationType\":\"2\",\"senderId\":\"$:LWCP_v1:$7CYT5pCj4s4mkQK7Y3n12A==\",\"conversationTitle\":\"通知\",\"sessionWebhook\":\"https://oapi.dingtalk.com/robot/sendBySession?session=225ea1735196265565ee379c85081a0b\",\"msgtype\":\"text\",\"text\":{\"content\":\" createTodo -t 请完成REQ-3115的状态 -d 需求已经提测 -D 2024-10-31 17:00:00 -p 30\"},\"senderPlatform\":\"Win\",\"robotCode\":\"dingx9pejjkh8whnaqkw\"}";
|
||||
ChatbotMessageWrapper messageWrapper = JSON.parseObject(json, ChatbotMessageWrapper.class);
|
||||
robotMsgCallbackConsumer.execute(messageWrapper);
|
||||
return CommonResponse.success(true);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package cn.axzo.riven.dingtalk.listener.dd;
|
||||
|
||||
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,133 @@
|
||||
package cn.axzo.riven.dingtalk.listener.mq;
|
||||
|
||||
import cn.axzo.framework.jackson.utility.JSON;
|
||||
import cn.axzo.framework.rocketmq.Event;
|
||||
import cn.axzo.framework.rocketmq.EventConsumer;
|
||||
import cn.axzo.framework.rocketmq.EventHandler;
|
||||
import cn.axzo.riven.client.common.enums.DingtalkEventEnum;
|
||||
import cn.axzo.riven.client.model.DingtalkSendMqModel;
|
||||
import cn.axzo.riven.client.model.ReplyMessage;
|
||||
import cn.axzo.riven.client.req.ThirdDingtalkMessageRecordReq;
|
||||
import cn.axzo.riven.dingtalk.reply.RobotReply;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 监听所有业务需要回复或发送的消息事件
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 10:31
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RocketReplyMessageEventListener implements EventHandler, InitializingBean {
|
||||
@Resource
|
||||
private EventConsumer eventConsumer;
|
||||
@Resource
|
||||
private ThirdDingtalkMessageRecordService thirdDingtalkMessageRecordService;
|
||||
@Resource
|
||||
private RobotReply robotReply;
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event, EventConsumer.Context context) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("RocketReplyMessageEventListener receive key: {}", event.getTargetId());
|
||||
}
|
||||
|
||||
DingtalkSendMqModel receiveModel = event.normalizedData(DingtalkSendMqModel.class);
|
||||
receiveModel.setMessage(buildReplyMessage(event));
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("receive message model: {}", JSON.toJSONString(receiveModel));
|
||||
}
|
||||
|
||||
Optional<ThirdDingtalkMessageRecord> optRecord = getUniqueRecord(receiveModel);
|
||||
if (!optRecord.isPresent()) {
|
||||
log.info("未获取到消息记录,将使用 HTTP API 的方式协助回复/发送钉钉消息,receive mq key:{}", event.getTargetId());
|
||||
}
|
||||
|
||||
ReplyContext replyContext = new ReplyContext();
|
||||
replyContext.setConversationId(receiveModel.getConversationId());
|
||||
replyContext.setRobotCode(receiveModel.getRobotCode());
|
||||
|
||||
// 处理需要@的用户
|
||||
List<String> atUserIds = Lists.newArrayList(receiveModel.getAtUserIds());
|
||||
replyContext.setAtUserId(atUserIds);
|
||||
|
||||
replyContext.setReplyMessage(receiveModel.getMessage());
|
||||
|
||||
optRecord.ifPresent(record -> {
|
||||
// 通过群对话回复消息,对话信息一定会有记录
|
||||
replyContext.setSessionWebhook(record.getSessionWebhook());
|
||||
replyContext.setSessionWebhookExpiredDate(record.getSessionWebhookExpiredTime());
|
||||
|
||||
atUserIds.add(record.getSenderId());
|
||||
});
|
||||
|
||||
updateLog(receiveModel);
|
||||
|
||||
robotReply.reply(replyContext);
|
||||
}
|
||||
|
||||
private void updateLog(DingtalkSendMqModel receiveModel) {
|
||||
ThirdDingtalkMessageRecordReq recordQuery = new ThirdDingtalkMessageRecordReq();
|
||||
recordQuery.setMsgId(receiveModel.getMsgId());
|
||||
recordQuery.setConversationId(receiveModel.getConversationId());
|
||||
List<ThirdDingtalkMessageRecord> records = thirdDingtalkMessageRecordService.genericQuery(recordQuery);
|
||||
if (!CollectionUtils.isEmpty(records) && records.size() == 1) {
|
||||
ThirdDingtalkMessageRecord record = records.get(0);
|
||||
record.setResponseMsgType(receiveModel.getMessage().getClz());
|
||||
record.setResponseContent(receiveModel.getMessage().toJson());
|
||||
thirdDingtalkMessageRecordService.update(record);
|
||||
}
|
||||
}
|
||||
|
||||
private ReplyMessage buildReplyMessage(Event event) {
|
||||
if (Objects.nonNull(event.getData())) {
|
||||
JSONObject data = (JSONObject) event.getData();
|
||||
JSONObject message = data.getJSONObject("message");
|
||||
String classStr = message.getString("clz");
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName(classStr);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return message.toJavaObject(new TypeReference<Object>() {
|
||||
@Override
|
||||
public Class<Object> getType() {
|
||||
return (Class<Object>) clazz;
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
eventConsumer.registerHandler(DingtalkEventEnum.send.getEventCode(), this);
|
||||
}
|
||||
|
||||
private Optional<ThirdDingtalkMessageRecord> getUniqueRecord(DingtalkSendMqModel receiveModel) {
|
||||
ThirdDingtalkMessageRecordReq req = new ThirdDingtalkMessageRecordReq();
|
||||
req.setConversationId(receiveModel.getConversationId());
|
||||
req.setMsgId(receiveModel.getMsgId());
|
||||
List<ThirdDingtalkMessageRecord> records = thirdDingtalkMessageRecordService.genericQuery(req);
|
||||
if (!CollectionUtils.isEmpty(records) && records.size() == 1) {
|
||||
return Optional.of(records.get(0));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
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.nonNull(replyContext.getReplyMessage())
|
||||
&& 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,29 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 真实的执行回复, replyContext 中的 replyMessage 可能为空, 需要实现中处理这情况
|
||||
*
|
||||
* @param replyContext
|
||||
*/
|
||||
abstract void doReply(ReplyContext replyContext);
|
||||
|
||||
abstract boolean doFilter(ReplyContext replyContext);
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||
|
||||
import cn.axzo.riven.dingtalk.robot.basic.ApplicationAccessTokenService;
|
||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||
import com.aliyun.dingtalkrobot_1_0.Client;
|
||||
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendHeaders;
|
||||
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendRequest;
|
||||
import com.aliyun.dingtalkrobot_1_0.models.OrgGroupSendResponse;
|
||||
import com.aliyun.tea.TeaException;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import lombok.SneakyThrows;
|
||||
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.Objects;
|
||||
|
||||
/**
|
||||
* 基于 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
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class BasedHttpReply extends AbstractRobotReply {
|
||||
@Resource
|
||||
private ApplicationAccessTokenService applicationAccessTokenService;
|
||||
private Client robotClient;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE + 1;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws Exception {
|
||||
Config config = new Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
robotClient = new Client(config);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
void doReply(ReplyContext replyContext) {
|
||||
if(!StringUtils.hasText(replyContext.getRobotCode())) {
|
||||
log.error("没有机器人 Code,无法使用 Http API 发送消息");
|
||||
}
|
||||
// 通过 HTTP API 发送消息
|
||||
OrgGroupSendHeaders orgGroupSendHeaders = new OrgGroupSendHeaders();
|
||||
orgGroupSendHeaders.setXAcsDingtalkAccessToken(applicationAccessTokenService.getAccessToken(replyContext.getRobotCode()));
|
||||
|
||||
OrgGroupSendRequest orgGroupSendRequest = new OrgGroupSendRequest();
|
||||
String clz = replyContext.getReplyMessage().getClz();
|
||||
orgGroupSendRequest.setMsgKey(clz.substring(clz.lastIndexOf(".") + 1));
|
||||
orgGroupSendRequest.setRobotCode(replyContext.getRobotCode());
|
||||
orgGroupSendRequest.setOpenConversationId(replyContext.getConversationId());
|
||||
orgGroupSendRequest.setMsgParam(replyContext.getReplyMessage().toJson());
|
||||
try {
|
||||
OrgGroupSendResponse orgGroupSendResponse = robotClient.orgGroupSendWithOptions(orgGroupSendRequest,
|
||||
orgGroupSendHeaders, new RuntimeOptions());
|
||||
if (Objects.isNull(orgGroupSendResponse) || Objects.isNull(orgGroupSendResponse.getBody())) {
|
||||
log.error("RobotGroupMessagesService_send orgGroupSendWithOptions return error, response={}",
|
||||
orgGroupSendResponse);
|
||||
return;
|
||||
}
|
||||
String processQueryKey = orgGroupSendResponse.getBody().getProcessQueryKey();
|
||||
log.info("通过 HTTP 发送的消息后,返回的消息 ID:{}", processQueryKey);
|
||||
} catch (TeaException e) {
|
||||
log.error("RobotGroupMessagesService_send orgGroupSendWithOptions throw TeaException, errCode={}, " +
|
||||
"errorMessage={}", e.getCode(), e.getMessage(), e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("RobotGroupMessagesService_send orgGroupSendWithOptions throw Exception", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean doFilter(ReplyContext replyContext) {
|
||||
return StringUtils.hasText(replyContext.getRobotCode())
|
||||
&& StringUtils.hasText(replyContext.getConversationId())
|
||||
&& !StringUtils.hasText(replyContext.getSessionWebhook());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
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 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.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基于 Session Webhook 的回复
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 17:28
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class BasedSessionWebhookReply extends AbstractRobotReply {
|
||||
@Resource
|
||||
private BasedHttpReply basedHttpReply;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
void doReply(ReplyContext replyContext) {
|
||||
try {
|
||||
// just reply generic text
|
||||
BotReplier botReplier = BotReplier.fromWebhook(replyContext.getSessionWebhook());
|
||||
|
||||
ReplyMessage replyMessage = replyContext.getReplyMessage();
|
||||
if (Objects.isNull(replyMessage)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("需要回复的消息会空,直接跳过发送");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package cn.axzo.riven.dingtalk.reply.strategy;
|
||||
|
||||
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* 回复机器人的策略
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 20:37
|
||||
*/
|
||||
public interface RobotReplyStrategy extends Ordered {
|
||||
|
||||
void reply(ReplyContext replyContext);
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 机器人Code
|
||||
*/
|
||||
private String robotCode;
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
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 String conversationType;
|
||||
|
||||
/**
|
||||
* 机器人所属企业
|
||||
*/
|
||||
private String chatbotCorpId;
|
||||
|
||||
/**
|
||||
* 机器人用户 ID(加密数据)
|
||||
*/
|
||||
private String chatbotUserId;
|
||||
|
||||
/**
|
||||
* 会话关联的应用名称
|
||||
*/
|
||||
private String applicationName;
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 会话类型:
|
||||
* 1:单聊
|
||||
* 2:群聊
|
||||
*/
|
||||
private String conversationType;
|
||||
/**
|
||||
* 这条消息的 ID
|
||||
*/
|
||||
private String msgId;
|
||||
|
||||
/**
|
||||
* 谁@的机器人发的消息
|
||||
*/
|
||||
private String senderNick;
|
||||
|
||||
/**
|
||||
* 企业内部群中@该机器人的成员 userId
|
||||
*/
|
||||
private String senderStaffId;
|
||||
|
||||
/**
|
||||
* 加密的发送者ID
|
||||
*/
|
||||
private String senderId;
|
||||
|
||||
|
||||
/**
|
||||
* 会话级的 Webhook
|
||||
*/
|
||||
private String sessionWebhook;
|
||||
|
||||
/**
|
||||
* 会话级的 Webhook 过期时间
|
||||
*/
|
||||
private Date sessionWebhookExpiredTime;
|
||||
|
||||
/**
|
||||
* 机器人编码
|
||||
*/
|
||||
private String robotCode;
|
||||
|
||||
/**
|
||||
* 该消息是怎么处理的,处理策略的类型
|
||||
*/
|
||||
private String handleType;
|
||||
|
||||
/**
|
||||
* 请求的内容
|
||||
*/
|
||||
private String requestContent;
|
||||
|
||||
/**
|
||||
* 响应的内容
|
||||
*/
|
||||
private String responseContent;
|
||||
/**
|
||||
* 响应的消息类型
|
||||
*/
|
||||
private String responseMsgType;
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package cn.axzo.riven.dingtalk.repository.entity;
|
||||
|
||||
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 三方人员同步表
|
||||
* </p>
|
||||
*
|
||||
* @author ZhanSiHu
|
||||
* @since 2023-09-25
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("third_party_user")
|
||||
public class ThirdPartyUserV2 extends BaseEntity<ThirdPartyUserV2> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
/**
|
||||
* 系统企业ID
|
||||
*/
|
||||
private Long ouId;
|
||||
|
||||
/**
|
||||
* 三方平台渠道,钉钉: DING 企微: QW
|
||||
*/
|
||||
private String channel;
|
||||
|
||||
/**
|
||||
* 三方平台UID
|
||||
*/
|
||||
private String unionId;
|
||||
|
||||
/**
|
||||
* 三方用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 三方用户姓名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 三方用户手机号
|
||||
*/
|
||||
private String userPhone;
|
||||
|
||||
/** 三方用户邮箱 **/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 工号
|
||||
*/
|
||||
private String jobNumber;
|
||||
|
||||
/** 职位 **/
|
||||
private String title;
|
||||
|
||||
/** 直属主管ID **/
|
||||
private String managerId;
|
||||
|
||||
/**
|
||||
* 三方用户所在部门ID,多部门多记录
|
||||
*/
|
||||
private String departmentId;
|
||||
|
||||
/** 内部部门ID **/
|
||||
private Long innerDeptId;
|
||||
|
||||
|
||||
public boolean sameWith(ThirdPartyUserV2 newUser) {
|
||||
//暂时只比较必要的
|
||||
return this.userName.equals(newUser.getUserName())
|
||||
&& this.userPhone.equals(newUser.getUserPhone())
|
||||
&& StrUtil.equals(this.jobNumber, newUser.getJobNumber())
|
||||
&& StrUtil.equals(this.email, newUser.getEmail())
|
||||
&& StrUtil.equals(this.title, newUser.title)
|
||||
&& StrUtil.equals(this.managerId, newUser.managerId)
|
||||
&& StrUtil.equals(this.departmentId, newUser.departmentId);
|
||||
}
|
||||
}
|
||||
@ -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,15 @@
|
||||
package cn.axzo.riven.dingtalk.repository.mapper;
|
||||
|
||||
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,17 @@
|
||||
package cn.axzo.riven.dingtalk.repository.mapper;
|
||||
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdPartyUserV2;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 三方人员同步表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author ZhanSiHu
|
||||
* @since 2023-09-25
|
||||
*/
|
||||
@Mapper
|
||||
public interface ThirdPartyUserMapperV2 extends BaseMapper<ThirdPartyUserV2> {
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
package cn.axzo.riven.dingtalk.robot.basic;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdApplicationReq;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdApplicationService;
|
||||
import com.aliyun.dingtalkoauth2_1_0.Client;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponseBody;
|
||||
import com.aliyun.tea.TeaException;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 企业内部应用的 AccessToken
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-25 15:55
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ApplicationAccessTokenService {
|
||||
@Resource
|
||||
private ThirdApplicationService thirdApplicationService;
|
||||
private Client auth2Client;
|
||||
|
||||
public static final ConcurrentHashMap<String/*robotCode*/, AccessToken> accessTokenMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
static class AccessToken {
|
||||
private String accessToken = "";
|
||||
private Long expireTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* init for first accessToken
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() throws Exception {
|
||||
Config config = new Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
auth2Client = new Client(config);
|
||||
|
||||
List<ThirdApplication> applications = thirdApplicationService.genericQuery(new ThirdApplicationReq());
|
||||
for (ThirdApplication application : applications) {
|
||||
int maxTryTimes = 3;
|
||||
while (maxTryTimes-- > 0) {
|
||||
if (refreshAccessToken(application)) {
|
||||
break;
|
||||
}
|
||||
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
if (maxTryTimes <= 0) {
|
||||
throw new RuntimeException("fail to get accessToken from remote, try 3 times, please check your appKey" +
|
||||
" and appSecret");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* schedule for refresh token when expired
|
||||
*/
|
||||
@Scheduled(fixedRate = 60 * 1000)
|
||||
public void checkAccessToken() {
|
||||
if(accessTokenMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
accessTokenMap.forEach((k,v)-> {
|
||||
// check before expired in 10 minutes
|
||||
long advanceCheckTime = 10 * 60L;
|
||||
if (v.expireTimestamp - System.currentTimeMillis() > advanceCheckTime * 1000L) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThirdApplicationReq req = new ThirdApplicationReq();
|
||||
req.setRobotCode(req.getRobotCode());
|
||||
List<ThirdApplication> applications = thirdApplicationService.genericQuery(req);
|
||||
for (ThirdApplication application : applications) {
|
||||
refreshAccessToken(application);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private Boolean refreshAccessToken(ThirdApplication application) {
|
||||
GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest()
|
||||
.setAppKey(application.getClientId())
|
||||
.setAppSecret(application.getClientSecret());
|
||||
|
||||
try {
|
||||
GetAccessTokenResponse getAccessTokenResponse = auth2Client.getAccessToken(getAccessTokenRequest);
|
||||
if (Objects.isNull(getAccessTokenResponse) || Objects.isNull(getAccessTokenResponse.body)) {
|
||||
log.error("AccessTokenService_getTokenFromRemoteServer getAccessToken return error," +
|
||||
" response={}", getAccessTokenResponse);
|
||||
return false;
|
||||
}
|
||||
|
||||
GetAccessTokenResponseBody body = getAccessTokenResponse.body;
|
||||
if (Objects.isNull(body.accessToken) || Objects.isNull(body.expireIn)) {
|
||||
log.error("AccessTokenService_getTokenFromRemoteServer getAccessToken invalid token, token or expireIn" +
|
||||
" maybe null, accessToken={}, expireIn={}", body.accessToken, body.expireIn);
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessToken accessToken = new AccessToken();
|
||||
accessToken.setAccessToken(body.accessToken);
|
||||
accessToken.setExpireTimestamp(System.currentTimeMillis() + body.expireIn * 1000);
|
||||
this.accessTokenMap.put(application.getRobotCode(), accessToken);
|
||||
log.info("refresh access token success, expireIn={}", body.expireIn);
|
||||
return true;
|
||||
} catch (TeaException e) {
|
||||
log.error("AccessTokenService_getTokenFromRemoteServer getAccessToken throw " +
|
||||
"TeaException, errCode={}, errorMessage={}", e.getCode(), e.getMessage(), e);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("AccessTokenService_getTokenFromRemoteServer getAccessToken throw Exception", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static String getAccessToken(String robotCode) {
|
||||
return accessTokenMap.getOrDefault(robotCode, new AccessToken()).getAccessToken();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
package cn.axzo.riven.dingtalk.robot.connection;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdApplicationReq;
|
||||
import cn.axzo.riven.dingtalk.callback.robot.RobotMsgCallbackConsumer;
|
||||
import cn.axzo.riven.dingtalk.config.RefreshableConfiguration;
|
||||
import cn.axzo.riven.dingtalk.listener.dd.DingTalkAllEventListener;
|
||||
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 com.google.common.collect.Lists;
|
||||
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;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
/**
|
||||
* 机器人应用自动连接
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 14:18
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AutoConnector {
|
||||
@Value("${spring.profiles.active}")
|
||||
private String activeProfile;
|
||||
public static final Map<String, OpenDingTalkClient> clientMap = new ConcurrentHashMap<>();
|
||||
@Resource
|
||||
private RefreshableConfiguration refreshableConfiguration;
|
||||
@Resource
|
||||
private ThirdApplicationService applicationService;
|
||||
@Resource
|
||||
private RobotMsgCallbackConsumer robotMsgCallbackConsumer;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
List<String> envs = Lists.newArrayList("dev", "local");
|
||||
if (envs.contains(activeProfile)) {
|
||||
doConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private void doConnection() {
|
||||
List<ThirdApplication> thirdApplications = applicationService.genericQuery(new ThirdApplicationReq());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("application query result count: {}", thirdApplications.size());
|
||||
}
|
||||
|
||||
if (refreshableConfiguration.getEnableDingtalkStream()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("auto connection robot by stream model");
|
||||
}
|
||||
}
|
||||
|
||||
startStream(thirdApplications);
|
||||
|
||||
}
|
||||
|
||||
public void startStream(List<ThirdApplication> thirdApplications) {
|
||||
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();
|
||||
clientMap.put(application.getAppId(), client);
|
||||
if (refreshableConfiguration.getEnableDingtalkStream()) {
|
||||
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,56 @@
|
||||
package cn.axzo.riven.dingtalk.robot.connection.model;
|
||||
|
||||
import cn.axzo.riven.client.model.ReplyMessage;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 统一的回复上下文模型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 20:11
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Accessors(chain = true)
|
||||
public class ReplyContext {
|
||||
|
||||
/**
|
||||
* 会话级的 Webhook
|
||||
*/
|
||||
private String sessionWebhook;
|
||||
/**
|
||||
* 会话级的 Webhook 有效期
|
||||
*/
|
||||
private Date sessionWebhookExpiredDate;
|
||||
|
||||
/**
|
||||
* 会话 ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 自定义机器人的 code
|
||||
*/
|
||||
private String robotCode;
|
||||
|
||||
/**
|
||||
* 回复的消息
|
||||
*/
|
||||
private ReplyMessage replyMessage;
|
||||
|
||||
/**
|
||||
* 回复消息时需要被@的用户
|
||||
*/
|
||||
private List<String> atUserId;
|
||||
|
||||
|
||||
}
|
||||
@ -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,23 @@
|
||||
package cn.axzo.riven.dingtalk.service;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdDingtalkConversationReq;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 三方钉钉群会话信息
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:48
|
||||
*/
|
||||
public interface ThirdDingtalkConversationService {
|
||||
|
||||
/**
|
||||
* insert or update
|
||||
* @param conversation
|
||||
*/
|
||||
void upsert(ThirdDingtalkConversation conversation);
|
||||
|
||||
List<ThirdDingtalkConversation> genericQuery(ThirdDingtalkConversationReq req);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package cn.axzo.riven.dingtalk.service;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdDingtalkMessageRecordReq;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 三方钉钉群消息记录
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:48
|
||||
*/
|
||||
public interface ThirdDingtalkMessageRecordService {
|
||||
ThirdDingtalkMessageRecord insert(ThirdDingtalkMessageRecord record);
|
||||
|
||||
List<ThirdDingtalkMessageRecord> genericQuery(ThirdDingtalkMessageRecordReq req);
|
||||
|
||||
void update(ThirdDingtalkMessageRecord record);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package cn.axzo.riven.dingtalk.service;
|
||||
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdPartyUserV2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-28 10:38
|
||||
*/
|
||||
public interface ThirdPartyUserService {
|
||||
Optional<ThirdPartyUserV2> getUserInfo(String userId);
|
||||
|
||||
List<ThirdPartyUserV2> getUserInfos(List<String> userIds);
|
||||
}
|
||||
@ -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,64 @@
|
||||
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.mapper.ThirdDingtalkConversationMapper;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.axzo.riven.dingtalk.callback.robot.strategy.impl.TransferToMQStrategy.conversationMap;
|
||||
|
||||
/**
|
||||
* 三方钉钉群会话信息
|
||||
*
|
||||
* @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) {
|
||||
ThirdDingtalkConversation oldEntity = thirdDingtalkConversationMapper
|
||||
.selectOne(new LambdaQueryWrapper<ThirdDingtalkConversation>()
|
||||
.eq(ThirdDingtalkConversation::getConversationId, conversation.getConversationId()));
|
||||
if (Objects.nonNull(oldEntity)) {
|
||||
BeanUtil.copyProperties(conversation, oldEntity, "id", "idDelete", "createAt", "updateAt");
|
||||
thirdDingtalkConversationMapper.updateById(oldEntity);
|
||||
} else {
|
||||
thirdDingtalkConversationMapper.insert(conversation);
|
||||
conversationMap.put(conversation.getConversationId(), 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())
|
||||
;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package cn.axzo.riven.dingtalk.service.impl;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdDingtalkMessageRecordReq;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkMessageRecord;
|
||||
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkMessageRecordMapper;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 三方钉钉群消息记录
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-23 19:48
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ThirdDingtalkMessageRecordServiceImpl implements ThirdDingtalkMessageRecordService {
|
||||
private final ThirdDingtalkMessageRecordMapper thirdDingtalkMessageRecordMapper;
|
||||
|
||||
@Override
|
||||
public ThirdDingtalkMessageRecord insert(ThirdDingtalkMessageRecord record) {
|
||||
thirdDingtalkMessageRecordMapper.insert(record);
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ThirdDingtalkMessageRecord record) {
|
||||
thirdDingtalkMessageRecordMapper.updateById(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThirdDingtalkMessageRecord> genericQuery(ThirdDingtalkMessageRecordReq req) {
|
||||
return thirdDingtalkMessageRecordMapper.selectList(buildQueryWrapper(req));
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<ThirdDingtalkMessageRecord> buildQueryWrapper(ThirdDingtalkMessageRecordReq req) {
|
||||
return new LambdaQueryWrapper<ThirdDingtalkMessageRecord>()
|
||||
.eq(StringUtils.hasText(req.getConversationId()), ThirdDingtalkMessageRecord::getConversationId, req.getConversationId())
|
||||
.like(StringUtils.hasText(req.getConversationTitle()), ThirdDingtalkMessageRecord::getConversationTitle, req.getConversationTitle())
|
||||
.eq(StringUtils.hasText(req.getConversationType()), ThirdDingtalkMessageRecord::getConversationType, req.getConversationType())
|
||||
.eq(StringUtils.hasText(req.getMsgId()), ThirdDingtalkMessageRecord::getMsgId, req.getMsgId())
|
||||
.like(StringUtils.hasText(req.getSenderNick()), ThirdDingtalkMessageRecord::getSenderNick, req.getSenderNick())
|
||||
.eq(StringUtils.hasText(req.getSenderStaffId()), ThirdDingtalkMessageRecord::getSenderStaffId, req.getSenderStaffId())
|
||||
.eq(StringUtils.hasText(req.getSenderId()), ThirdDingtalkMessageRecord::getSenderId, req.getSenderId())
|
||||
.eq(StringUtils.hasText(req.getRobotCode()), ThirdDingtalkMessageRecord::getRobotCode, req.getRobotCode())
|
||||
;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package cn.axzo.riven.dingtalk.service.impl;
|
||||
|
||||
import cn.axzo.riven.client.req.ThirdPartyUserReq;
|
||||
import cn.axzo.riven.dingtalk.repository.entity.ThirdPartyUserV2;
|
||||
import cn.axzo.riven.dingtalk.repository.mapper.ThirdPartyUserMapperV2;
|
||||
import cn.axzo.riven.dingtalk.service.ThirdPartyUserService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 三方用户操作类型
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2024-10-28 10:38
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class ThirdPartyUserServiceImpl implements ThirdPartyUserService {
|
||||
@Resource
|
||||
private ThirdPartyUserMapperV2 thirdPartyUserMapperV2;
|
||||
|
||||
@Override
|
||||
public Optional<ThirdPartyUserV2> getUserInfo(String userId) {
|
||||
return Optional.ofNullable(thirdPartyUserMapperV2.selectOne(buildQueryWrapper(ThirdPartyUserReq.builder()
|
||||
.userId(userId)
|
||||
.build())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThirdPartyUserV2> getUserInfos(List<String> userIds) {
|
||||
return thirdPartyUserMapperV2.selectList(buildQueryWrapper(ThirdPartyUserReq.builder()
|
||||
.userIds(userIds).build()));
|
||||
}
|
||||
|
||||
|
||||
private LambdaQueryWrapper<ThirdPartyUserV2> buildQueryWrapper(ThirdPartyUserReq req) {
|
||||
return new LambdaQueryWrapper<ThirdPartyUserV2>()
|
||||
.eq(StringUtils.hasText(req.getUserId()), ThirdPartyUserV2::getUserId, req.getUserId())
|
||||
.in(!CollectionUtils.isEmpty(req.getUserIds()), ThirdPartyUserV2::getUserId, req.getUserIds())
|
||||
.eq(StringUtils.hasText(req.getUnionId()), ThirdPartyUserV2::getUnionId, req.getUnionId())
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
61
riven-dingtalk/src/main/resources/sql/DDL.sql
Normal file
61
riven-dingtalk/src/main/resources/sql/DDL.sql
Normal file
@ -0,0 +1,61 @@
|
||||
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',
|
||||
robot_code varchar(255) not null comment '机器人编码',
|
||||
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',
|
||||
'dingxga1f6ghiefbzovb', 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 varchar(1) not null comment '会话类型 1:单聊 2:群聊',
|
||||
chatbot_corp_id varchar(255) not null comment '机器人所属企业',
|
||||
chatbot_user_id varchar(255) not null comment '机器人用户ID',
|
||||
application_name varchar(255) 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 '会话信息';
|
||||
|
||||
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 varchar(1) not null comment '会话类型 1:单聊 2:群聊',
|
||||
msg_id varchar(255) not null comment '消息ID',
|
||||
sender_nick varchar(64) not null comment '发送人昵称',
|
||||
sender_staff_id varchar(64) not null comment '企业内部群中@该机器人的成员 userId',
|
||||
sender_id varchar(64) not null comment '加密的发送者ID',
|
||||
session_webhook varchar(512) not null comment '会话webhook url',
|
||||
session_webhook_expired_time datetime not null comment '会话webhook url过期时间',
|
||||
robot_code varchar(64) default '' not null comment '机器人编码',
|
||||
handle_type varchar(64) not null comment '处理类型',
|
||||
request_content text null comment '请求内容',
|
||||
response_content text null comment '响应内容',
|
||||
response_msg_type varchar(64) 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 '钉钉群消息记录';
|
||||
@ -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,12 +115,10 @@
|
||||
<dependency>
|
||||
<groupId>com.dingtalk.open</groupId>
|
||||
<artifactId>app-stream-client</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dingtalk</artifactId>
|
||||
<version>2.0.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.axzo.maokai</groupId>
|
||||
|
||||
@ -11,6 +11,7 @@ import org.springframework.core.env.Environment;
|
||||
@Slf4j
|
||||
@SpringBootApplication(scanBasePackages = {"cn.axzo", "com.axzo.framework"})
|
||||
@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 {
|
||||
|
||||
|
||||
@ -139,9 +139,9 @@ public class DingtalkTest {
|
||||
//事件产生时间
|
||||
Long bornTime = event.getEventBornTime();
|
||||
//获取事件体
|
||||
JSONObject bizData = event.getData();
|
||||
// JSONObject bizData = event.getData();
|
||||
//处理事件
|
||||
System.out.println(bizData.toJSONString());
|
||||
// System.out.println(bizData.toJSONString());
|
||||
//消费成功
|
||||
return EventAckStatus.SUCCESS;
|
||||
} catch (Exception e) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user