feat(REQ-3114) - 新增钉钉群会话,与机器人对话和回复的基础能力

This commit is contained in:
wangli 2024-10-23 20:44:23 +08:00
parent 85425c2741
commit 68cac160e4
33 changed files with 996 additions and 1 deletions

View File

@ -21,6 +21,7 @@
<mapstruct.version>1.4.2.Final</mapstruct.version>
<revision>2.0.0-SNAPSHOT</revision>
<feign-httpclient.version>11.8</feign-httpclient.version>
<dingtalk.stream.version>1.3.7</dingtalk.stream.version>
</properties>
<modules>
@ -28,6 +29,7 @@
<module>riven-api</module>
<module>integration-test</module>
<module>riven-third</module>
<module>riven-dingtalk</module>
</modules>
<dependencyManagement>
@ -52,6 +54,11 @@
<artifactId>feign-httpclient</artifactId>
<version>${feign-httpclient.version}</version>
</dependency>
<dependency>
<groupId>com.dingtalk.open</groupId>
<artifactId>app-stream-client</artifactId>
<version>${dingtalk.stream.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -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;
}
}

View File

@ -0,0 +1,24 @@
package cn.axzo.riven.client.common.enums;
/**
* 本类的信息来自于 {@see https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots}
*
* @author wangli
* @since 2024-10-23 20:13
*/
public enum DingTalkMsgTypeEnum {
sampleText,
sampleMarkdown,
sampleImageMsg,
sampleLink,
sampleActionCard,
sampleActionCard2,
sampleActionCard3,
sampleActionCard4,
sampleActionCard5,
sampleActionCard6,
sampleAudio,
sampleFile,
sampleVideo,
;
}

View File

@ -0,0 +1,43 @@
package cn.axzo.riven.client.req;
import cn.axzo.riven.client.common.enums.CommonStatusEnum;
import cn.axzo.riven.client.common.enums.sync.Channel;
import lombok.Data;
/**
* 三方应用通用查询入参
*
* @author wangli
* @since 2024-10-23 14:30
*/
@Data
public class ThirdApplicationReq {
/**
* 渠道
*/
private Channel channel = Channel.DingTalk;
/**
* 应用名称
*/
private String name;
/**
* 应用描述
*/
private String description;
private String appId;
private String agentId;
private String clientId;
private String clientSecret;
/**
* 状态
*/
private CommonStatusEnum status = CommonStatusEnum.ENABLED;
}

27
riven-dingtalk/pom.xml Normal file
View File

@ -0,0 +1,27 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>riven</artifactId>
<groupId>cn.axzo</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>riven-dingtalk</artifactId>
<packaging>jar</packaging>
<name>riven-dingtalk</name>
<dependencies>
<dependency>
<groupId>cn.axzo</groupId>
<artifactId>riven-api</artifactId>
</dependency>
<dependency>
<groupId>com.dingtalk.open</groupId>
<artifactId>app-stream-client</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework.data</groupId>
<artifactId>axzo-data-mybatis-plus</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,50 @@
package cn.axzo.riven.dingtalk.callback.robot;
import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.riven.dingtalk.callback.robot.strategy.RobotHandleStrategy;
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 机器人回调处理
*
* @author wangli
* @since 2024-10-23 15:33
*/
@Slf4j
@Component
public class RobotMsgCallbackConsumer implements OpenDingTalkCallbackListener<ChatbotMessage, Void> {
@Resource
private List<RobotHandleStrategy> robotHandleStrategies;
/**
* 执行回调
*
* @param message
* @return
*/
@Override
public Void execute(ChatbotMessage message) {
if (log.isDebugEnabled()) {
log.debug("receive message : {}", JSON.toJSONString(message));
}
String keyword = message.getText().getContent().trim();
robotHandleStrategies.stream()
.filter(s -> s.support(keyword))
.findFirst()
.ifPresent(handle -> {
Object result = handle.handle(message);
if(result instanceof ReplyContext) {
ReplyContext replyContext = (ReplyContext) result;
}
});
return null;
}
}

View File

@ -0,0 +1,10 @@
package cn.axzo.riven.dingtalk.callback.robot.strategy;
/**
* 抽象的机器人监听消息的处理策略
*
* @author wangli
* @since 2024-10-23 17:35
*/
public abstract class AbstractRobotHandleStrategy<T> implements RobotHandleStrategy<T> {
}

View File

@ -0,0 +1,21 @@
package cn.axzo.riven.dingtalk.callback.robot.strategy;
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
import org.springframework.core.Ordered;
/**
* 机器人处理消息的策略
*
* @author wangli
* @since 2024-10-23 17:37
*/
public interface RobotHandleStrategy<T> extends Ordered {
@Override
default int getOrder() {
return 0;
}
boolean support(String keyword);
T handle(ChatbotMessage chatbotMessage);
}

View File

@ -0,0 +1,28 @@
package cn.axzo.riven.dingtalk.callback.robot.strategy.impl;
import cn.axzo.riven.dingtalk.callback.robot.strategy.AbstractRobotHandleStrategy;
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Objects;
/**
* 默认的将群消息通过 MQ 广播也所有业务方
*
* @author wangli
* @since 2024-10-23 18:13
*/
@Component
public class DefaultTransferToMQStrategy extends AbstractRobotHandleStrategy<String> {
@Override
public boolean support(String keyword) {
return StringUtils.hasText(keyword);
}
@Override
public String handle(ChatbotMessage chatbotMessage) {
// TODO 转发给 MQ 中去
return "";
}
}

View File

@ -0,0 +1,55 @@
package cn.axzo.riven.dingtalk.callback.robot.strategy.impl;
import cn.axzo.riven.dingtalk.callback.robot.strategy.AbstractRobotHandleStrategy;
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
import com.dingtalk.open.app.api.models.bot.ChatbotMessage;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import java.util.Objects;
import static cn.axzo.riven.dingtalk.constant.DingTalkConstant.REGISTER;
/**
* 注册会话
* <p>
* 新创建的群添加机器人后在群内@机器人并发送注册两个字即可完成注册
*
* @author wangli
* @since 2024-10-23 17:30
*/
@Slf4j
@Component
@AllArgsConstructor
public class RegisterConversationStrategy extends AbstractRobotHandleStrategy<ReplyContext> {
private final ThirdDingtalkConversationService thirdDingtalkConversationService;
@Override
public boolean support(String keyword) {
return Objects.equals(keyword, REGISTER);
}
@Override
public ReplyContext handle(ChatbotMessage chatbotMessage) {
if (log.isDebugEnabled()) {
log.debug(" RegisterConversationStrategy Entrance ");
}
ThirdDingtalkConversation conversation = new ThirdDingtalkConversation();
BeanUtils.copyProperties(chatbotMessage, conversation);
// 进行机器人和会话的绑定注册
thirdDingtalkConversationService.upsert(conversation);
return ReplyContext.from(chatbotMessage.getSessionWebhook(), String.format("注册成功,当前的会话 ID%s", conversation.getConversationId()));
}
@Override
public int getOrder() {
return Integer.MIN_VALUE + 2;
}
}

View File

@ -0,0 +1,12 @@
package cn.axzo.riven.dingtalk.constant;
/**
* dingtalk 相关的常量
*
* @author wangli
* @since 2024-10-23 17:44
*/
public interface DingTalkConstant {
String REGISTER = "注册";
}

View File

@ -0,0 +1,53 @@
package cn.axzo.riven.dingtalk.listener;
import com.dingtalk.open.app.api.GenericEventListener;
import com.dingtalk.open.app.api.message.GenericOpenDingTalkEvent;
import com.dingtalk.open.app.stream.protocol.event.EventAckStatus;
import lombok.extern.slf4j.Slf4j;
import shade.com.alibaba.fastjson2.JSON;
import shade.com.alibaba.fastjson2.JSONObject;
/**
* Dingtalk 全量的事件监听
*
* @author wangli
* @since 2024-10-23 15:04
*/
@Slf4j
public class DingTalkAllEventListener implements GenericEventListener {
/**
* 收到事件
*
* @param event
* @return
*/
@Override
public EventAckStatus onEvent(GenericOpenDingTalkEvent event) {
if (log.isDebugEnabled()) {
log.debug("receive dingtalk event: {}", JSON.toJSONString(event));
}
try {
//事件唯一Id
String eventId = event.getEventId();
//事件类型
String eventType = event.getEventType();
//事件产生时间
Long bornTime = event.getEventBornTime();
//获取事件体
JSONObject bizData = event.getData();
//处理事件
process(bizData);
//消费成功
return EventAckStatus.SUCCESS;
} catch (Exception e) {
//消费失败
return EventAckStatus.LATER;
}
}
private void process(JSONObject bizData) {
}
}

View File

@ -0,0 +1,14 @@
package cn.axzo.riven.dingtalk.reply;
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
/**
* 回复机器人
*
* @author wangli
* @since 2024-10-23 20:37
*/
public interface RobotReplyStrategy {
void reply(ReplyContext replyContext);
}

View File

@ -0,0 +1,12 @@
package cn.axzo.riven.dingtalk.reply.strategy;
/**
* 基于 Http Api 的回复
* https://github.com/open-dingtalk/dingtalk-stream-sdk-java-quick-start/blob/main/src/main/java/org/example/service/RobotGroupMessagesService.java
* https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots
*
* @author wangli
* @since 2024-10-23 17:28
*/
public class BasedHttpReply {
}

View File

@ -0,0 +1,25 @@
package cn.axzo.riven.dingtalk.reply.strategy;
import cn.axzo.riven.dingtalk.robot.connection.model.ReplyContext;
import com.dingtalk.open.app.api.chatbot.BotReplier;
import java.io.IOException;
/**
* 基于 Session Webhook 的回复
*
* @author wangli
* @since 2024-10-23 17:28
*/
public class BasedSessionWebhookReply {
public void reply(ReplyContext replyContext) {
try {
// just reply generic text
BotReplier.fromWebhook(replyContext.getSessionWebhook()).replyText(replyContext.getMsgContent());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,61 @@
package cn.axzo.riven.dingtalk.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* 应用基本信息
*
* @author wangli
* @since 2024-10-23 14:03
*/
@EqualsAndHashCode(callSuper = true)
@TableName(value = "third_application", autoResultMap = true)
@Data
@ToString(callSuper = true)
public class ThirdApplication extends BaseEntity<ThirdApplication> {
/**
* 应用所属渠道
*/
private String channel;
/**
* 应用名称
*/
private String name;
/**
* 描述信息
*/
private String description;
/**
* 应用的 AppId
*/
private String appId;
/**
* 应用的 AgentId
*/
private String agentId;
/**
* ClientId
* ( AppKey SuiteKey)
*/
private String clientId;
/**
* Client Secret
* ( AppSecret SuiteSecret)
*/
private String clientSecret;
/**
* 状态
*/
private Integer status;
}

View File

@ -0,0 +1,45 @@
package cn.axzo.riven.dingtalk.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* 三方钉钉群会话信息
*
* @author wangli
* @since 2024-10-23 19:44
*/
@EqualsAndHashCode(callSuper = true)
@TableName(value = "third_dingtalk_conversation", autoResultMap = true)
@Data
@ToString(callSuper = true)
public class ThirdDingtalkConversation extends BaseEntity<ThirdDingtalkConversation> {
/**
* 会话 ID= ID
*/
private String conversationId;
/**
* 会话名称=群名称
*/
private String conversationTitle;
/**
* 会话类型
* 1单聊
* 2群聊
*/
private Integer conversationType;
/**
* 机器人所属企业
*/
private String chatbotCorpId;
/**
* 机器人用户 ID加密数据
*/
private String chatbotUserId;
}

View File

@ -0,0 +1,72 @@
package cn.axzo.riven.dingtalk.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
/**
* 三方钉钉群消息记录
*
* @author wangli
* @since 2024-10-23 18:18
*/
@EqualsAndHashCode(callSuper = true)
@TableName(value = "third_dingtalk_msg_record", autoResultMap = true)
@Data
@ToString(callSuper = true)
public class ThirdDingtalkMessageRecord extends BaseEntity<ThirdDingtalkMessageRecord> {
/**
* 会话 ID= ID
*/
private String conversationId;
/**
* 会话名称=群名称
*/
private String conversationTitle;
/**
* 这条消息的 ID
*/
private String msgId;
/**
* @的机器人发的消息
*/
private String senderNick;
/**
* 会话类型
* 1单聊
* 2群聊
*/
private Integer conversationType;
/**
* 会话级的 Webhook
*/
private String sessionWebhook;
/**
* 会话级的 Webhook 过期时间
*/
private Date sessionWebhookExpiredTime;
/**
* 请求的内容
*/
private String requestContext;
/**
* 响应的内容
*/
private String responseContext;
/**
* 响应的消息类型
*/
private String responseMsgType;
}

View File

@ -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> {
}

View File

@ -0,0 +1,16 @@
package cn.axzo.riven.dingtalk.repository.mapper;
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 应用基本信息 Mapper
*
* @author wangli
* @since 2024-10-23 14:03
*/
@Mapper
public interface ThirdDingtalkConversationMapper extends BaseMapper<ThirdDingtalkConversation> {
}

View File

@ -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> {
}

View File

@ -0,0 +1,75 @@
package cn.axzo.riven.dingtalk.robot.connection;
import cn.axzo.riven.client.req.ThirdApplicationReq;
import cn.axzo.riven.dingtalk.listener.DingTalkAllEventListener;
import cn.axzo.riven.dingtalk.callback.robot.RobotMsgCallbackConsumer;
import cn.axzo.riven.dingtalk.repository.entity.ThirdApplication;
import cn.axzo.riven.dingtalk.service.ThirdApplicationService;
import com.alibaba.fastjson.JSON;
import com.dingtalk.open.app.api.OpenDingTalkClient;
import com.dingtalk.open.app.api.OpenDingTalkStreamClientBuilder;
import com.dingtalk.open.app.api.callback.DingTalkStreamTopics;
import com.dingtalk.open.app.api.security.AuthClientCredential;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
/**
* 机器人应用自动连接
*
* @author wangli
* @since 2024-10-23 14:18
*/
@Slf4j
@Component
public class AutoConnector {
@Value("${third.dingtalk.auto-connect: true}")
private Boolean autoConnect;
@Resource
private ThirdApplicationService applicationService;
@Resource
private RobotMsgCallbackConsumer robotMsgCallbackConsumer;
@PostConstruct
public void init() {
if (autoConnect) {
if (log.isDebugEnabled()) {
log.debug("auto connection robot by stream model");
}
doConnection();
}
}
private void doConnection() {
List<ThirdApplication> thirdApplications = applicationService.genericQuery(new ThirdApplicationReq());
if (log.isDebugEnabled()) {
log.debug("application query result count: {}", thirdApplications.size());
}
thirdApplications.forEach(application -> {
try {
OpenDingTalkClient client = OpenDingTalkStreamClientBuilder
.custom()
.credential(new AuthClientCredential(application.getClientId(), application.getClientSecret()))
// 注册机器人回调固定值
.registerCallbackListener(DingTalkStreamTopics.BOT_MESSAGE_TOPIC, robotMsgCallbackConsumer)
//注册事件监听
.registerAllEventListener(new DingTalkAllEventListener())
.build();
client.start();
if (log.isDebugEnabled()) {
log.debug("robot connect success, channel: {}, name: {}, other: {}",
application.getChannel(), application.getName(), JSON.toJSONString(application));
}
} catch (Exception e) {
log.error("robot connect error, channel: {}, name: {}, other: {}, errorInfo: {}",
application.getChannel(), application.getName(), JSON.toJSONString(application), e.getMessage(), e);
}
});
}
}

View File

@ -0,0 +1,60 @@
package cn.axzo.riven.dingtalk.robot.connection.model;
import cn.axzo.riven.client.common.enums.DingTalkMsgTypeEnum;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
/**
* 统一的回复上下文模型
*
* @author wangli
* @since 2024-10-23 20:11
*/
@Data
public class ReplyContext {
/**
* 会话级的 Webhook
*/
private String sessionWebhook;
/**
* 消息类型
*/
private DingTalkMsgTypeEnum msgType = DingTalkMsgTypeEnum.sampleText;
/**
* 默认的消息类型下可直接传入要发送的文本
* 如果要使用其他 msgType则用 data 属性进行传值
*/
private String msgContent;
/**
* 根据不同的 msgType 传入不同请求模型
* 参考{@see https://open.dingtalk.com/document/orgapp/types-of-messages-sent-by-robots}
*/
private JSONObject data;
private ReplyContext(String sessionWebhook, DingTalkMsgTypeEnum msgType, String msgContent, JSONObject data) {
this.sessionWebhook = sessionWebhook;
this.msgType = msgType;
this.msgContent = msgContent;
this.data = data;
}
public static ReplyContext from(String msgContent) {
return new ReplyContext(null, DingTalkMsgTypeEnum.sampleText, msgContent, null);
}
public static ReplyContext from(String sessionWebhook, String msgContent) {
return new ReplyContext(sessionWebhook, DingTalkMsgTypeEnum.sampleText, msgContent, null);
}
public static ReplyContext from(DingTalkMsgTypeEnum msgType, JSONObject data) {
return new ReplyContext(null, msgType, null, data);
}
public static ReplyContext from(String sessionWebhook, DingTalkMsgTypeEnum msgType, JSONObject data) {
return new ReplyContext(sessionWebhook, msgType, null, data);
}
}

View File

@ -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);
}

View File

@ -0,0 +1,18 @@
package cn.axzo.riven.dingtalk.service;
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
/**
* 三方钉钉群会话信息
*
* @author wangli
* @since 2024-10-23 19:48
*/
public interface ThirdDingtalkConversationService {
/**
* insert or update
* @param conversation
*/
void upsert(ThirdDingtalkConversation conversation);
}

View File

@ -0,0 +1,10 @@
package cn.axzo.riven.dingtalk.service;
/**
* 三方钉钉群消息记录
*
* @author wangli
* @since 2024-10-23 19:48
*/
public interface ThirdDingtalkMessageRecordService {
}

View File

@ -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())
;
}
}

View File

@ -0,0 +1,37 @@
package cn.axzo.riven.dingtalk.service.impl;
import cn.axzo.riven.dingtalk.repository.entity.ThirdDingtalkConversation;
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkConversationMapper;
import cn.axzo.riven.dingtalk.repository.mapper.ThirdDingtalkMessageRecordMapper;
import cn.axzo.riven.dingtalk.service.ThirdDingtalkConversationService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 三方钉钉群会话信息
*
* @author wangli
* @since 2024-10-23 19:48
*/
@Service
@AllArgsConstructor
public class ThirdDingtalkConversationServiceImpl implements ThirdDingtalkConversationService {
private final ThirdDingtalkConversationMapper thirdDingtalkConversationMapper;
@Override
public void upsert(ThirdDingtalkConversation conversation) {
if (Objects.nonNull(conversation.getConversationId())) {
ThirdDingtalkConversation oldEntity = thirdDingtalkConversationMapper
.selectOne(new LambdaQueryWrapper<ThirdDingtalkConversation>()
.eq(ThirdDingtalkConversation::getConversationId, conversation.getConversationId()));
BeanUtils.copyProperties(conversation, oldEntity);
thirdDingtalkConversationMapper.updateById(oldEntity);
} else {
thirdDingtalkConversationMapper.insert(conversation);
}
}
}

View File

@ -0,0 +1,16 @@
package cn.axzo.riven.dingtalk.service.impl;
import cn.axzo.riven.dingtalk.service.ThirdDingtalkMessageRecordService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 三方钉钉群消息记录
*
* @author wangli
* @since 2024-10-23 19:48
*/
@Service
@AllArgsConstructor
public class ThirdDingtalkMessageRecordServiceImpl implements ThirdDingtalkMessageRecordService {
}

View File

@ -0,0 +1,52 @@
create table third_application
(
id bigint not null auto_increment comment '主键'
primary key,
channel varchar(255) not null comment '渠道',
name varchar(255) not null comment '应用名称',
description varchar(255) not null comment '应用描述',
app_id varchar(255) not null comment 'appId',
agent_id varchar(255) not null comment 'agentId',
client_id varchar(255) not null comment 'clientId',
client_secret varchar(255) not null comment 'clientSecret',
status tinyint(1) default 1 not null comment '状态',
create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间',
update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
is_delete bigint default 0 not null comment '删除标志'
) comment '第三方应用';
insert into third_application value (1, 'dingtalk', '多群公共机器人', '多群公共机器人',
'c26a6e00-dd35-4bb5-a68a-36455ce1c001', '3282101532', 'dingxga1f6ghiefbzovb',
'b-SSalEZAoRCWodldDtrdHMKbtJVQpVWP8-x1FYgdSTzPKaGJCDT7RH1j_0d-IKO', 1, now(),
now(), 0);
create table third_dingtalk_conversation
(
id bigint not null auto_increment comment '主键'
primary key,
conversation_id varchar(255) not null comment '会话ID',
conversation_title varchar(255) not null comment '会话名称',
conversation_type tinyint(1) not null comment '会话类型 1:单聊 2:群聊',
chatbot_corp_id varchar(255) not null comment '机器人所属企业',
chatbot_user_id varchar(255) not null comment '机器人用户ID',
create_at datetime default CURRENT_TIMESTAMP not null comment '创建时间',
update_at datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
is_delete bigint default 0 not null comment '删除标志'
) comment '会话信息';
create table third_dingtalk_msg_record
(
id bigint auto_increment comment '主键'
primary key,
conversation_id varchar(255) not null comment '会话ID',
conversation_title varchar(255) not null comment '会话名称',
conversation_type tinyint(1) not null comment '会话类型 1:单聊 2:群聊',
msg_id varchar(255) not null comment '消息ID',
sender_nick varchar(255) not null comment '发送人昵称',
session_webhook varchar(255) not null comment '会话webhook url',
session_webhook_expire_time datetime not null comment '会话webhook url过期时间',
request_content varchar(255) not null comment '请求内容',
handle_type varchar(64) not null comment '处理类型',
response_content varchar(4000) default '' not null comment '响应内容',
response_msg_type varchar(255) default 'sampleText' not null comment '响应消息类型'
) comment '钉钉群消息记录';

View File

@ -36,6 +36,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.axzo</groupId>
<artifactId>riven-dingtalk</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
@ -109,7 +115,6 @@
<dependency>
<groupId>com.dingtalk.open</groupId>
<artifactId>app-stream-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>

View File

@ -11,6 +11,7 @@ import org.springframework.core.env.Environment;
@Slf4j
@SpringBootApplication(scanBasePackages = "cn.axzo")
@EnableFeignClients(basePackages = {"cn.axzo"})
@MapperScan({"cn.axzo.riven.dingtalk.**.mapper"})
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);

View File

@ -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 {