feat(REQ-3114) - 创建待办的事件处理
This commit is contained in:
parent
0243e7b7ac
commit
cd7584e0b5
2
pom.xml
2
pom.xml
@ -22,7 +22,7 @@
|
||||
<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.0.14</dingtalk.version>
|
||||
<dingtalk.version>2.1.42</dingtalk.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
|
||||
@ -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 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;
|
||||
}
|
||||
if (Objects.equals("-t", k)) {
|
||||
model.setTitle(v.trim());
|
||||
}
|
||||
});
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -23,6 +23,12 @@
|
||||
<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>
|
||||
|
||||
@ -1,9 +1,42 @@
|
||||
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.teaopenapi.models.Config;
|
||||
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.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@ -11,13 +44,17 @@ import org.springframework.stereotype.Component;
|
||||
* @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 new String[0];
|
||||
return KEYWORD;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,16 +64,129 @@ public class CreateTodoKeywordProcessor extends AbstractKeywordProcessor {
|
||||
*/
|
||||
@Override
|
||||
public ReplyMessage help() {
|
||||
return null;
|
||||
return SampleMarkdown.from("使用示例", "> ### 请按以下说明发送信息\n" +
|
||||
"> createTodo <@用户...> ([option] text)*\n" +
|
||||
"> \n" +
|
||||
"> 完整语法:\n" +
|
||||
"> <font color=green>createTodo @用户 -d 标题 -d 描述 -D 截止时间 -p 优先级</font>\n" +
|
||||
" \n" +
|
||||
"> 命令:createTodo/创建待办\n" +
|
||||
"> \n" +
|
||||
"> 用户:最少需要@一个用户,可以是多个\n" +
|
||||
"> \n" +
|
||||
"> 优先级:10(较低),20(普通),30(紧急),40(非常紧急)\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) {
|
||||
return null;
|
||||
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
|
||||
*
|
||||
* @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());
|
||||
if (chatbotMessage.getAtUsers().size() == 2) {
|
||||
// 为单人创建
|
||||
users.add(chatbotMessage.getAtUsers().get(1).getStaffId());
|
||||
} else {
|
||||
// 批量创建
|
||||
users.addAll(chatbotMessage.getAtUsers().stream().map(MentionUser::getStaffId).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");
|
||||
|
||||
// FIXME just for testing
|
||||
// String senderUnionId = "B1iPAiSviiFn9iSgLBXtiPNMqMQiEiE";
|
||||
CreateTodoTaskRequest createTodoTaskRequest = new CreateTodoTaskRequest()
|
||||
.setOperatorId(senderUnionId)
|
||||
.setSubject(model.getTitle())
|
||||
.setCreatorId(senderUnionId)
|
||||
.setDescription(model.getDescription())
|
||||
.setDueTime(model.getDueDate())
|
||||
.setExecutorIds(executorUnionIds)
|
||||
// FIXME just for testing
|
||||
// .setExecutorIds(Lists.newArrayList("rcnSH26ZiPevENaCkHgFHwQiEiE"))
|
||||
.setIsOnlyShowExecutor(true)
|
||||
.setPriority(model.getPriority())
|
||||
.setNotifyConfigs(notifyConfigs);
|
||||
|
||||
CreateTodoTaskResponse response = client.createTodoTaskWithOptions(senderUnionId, createTodoTaskRequest, createTodoTaskHeaders, new RuntimeOptions());
|
||||
log.info("钉钉响应:{}",JSON.toJSONString(response));
|
||||
|
||||
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,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,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> {
|
||||
}
|
||||
@ -15,6 +15,7 @@ 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;
|
||||
@ -33,7 +34,7 @@ public class ApplicationAccessTokenService {
|
||||
private ThirdApplicationService thirdApplicationService;
|
||||
private Client auth2Client;
|
||||
|
||||
private ConcurrentHashMap<String/*robotCode*/, AccessToken> accessTokenMap = new ConcurrentHashMap<>();
|
||||
public static final ConcurrentHashMap<String/*robotCode*/, AccessToken> accessTokenMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ -45,6 +46,7 @@ public class ApplicationAccessTokenService {
|
||||
/**
|
||||
* init for first accessToken
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() throws Exception {
|
||||
Config config = new Config();
|
||||
config.protocol = "https";
|
||||
@ -132,7 +134,7 @@ public class ApplicationAccessTokenService {
|
||||
|
||||
}
|
||||
|
||||
public String getAccessToken(String robotCode) {
|
||||
public static String getAccessToken(String robotCode) {
|
||||
return accessTokenMap.getOrDefault(robotCode, new AccessToken()).getAccessToken();
|
||||
}
|
||||
|
||||
|
||||
@ -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,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())
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user