|
@@ -5,6 +5,10 @@ import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.fs.common.exception.CustomException;
|
|
import com.fs.common.exception.CustomException;
|
|
|
|
|
+import com.fs.qw.domain.QwConversationMessage;
|
|
|
|
|
+import com.fs.qw.domain.QwConversationParticipant;
|
|
|
|
|
+import com.fs.qw.mapper.QwConversationMessageMapper;
|
|
|
|
|
+import com.fs.qw.mapper.QwConversationParticipantMapper;
|
|
|
import com.fs.qw.service.ICorporateWeChatSpaceService;
|
|
import com.fs.qw.service.ICorporateWeChatSpaceService;
|
|
|
import com.fs.qw.utils.WeChatSpaceDecryptUtil;
|
|
import com.fs.qw.utils.WeChatSpaceDecryptUtil;
|
|
|
import com.fs.qw.utils.WeChatSpaceUtil;
|
|
import com.fs.qw.utils.WeChatSpaceUtil;
|
|
@@ -20,6 +24,7 @@ import lombok.RequiredArgsConstructor;
|
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.util.List;
|
|
|
import java.util.Objects;
|
|
import java.util.Objects;
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
@@ -32,6 +37,14 @@ public class ICorporateWeChatSpaceServiceImpl implements ICorporateWeChatSpaceSe
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private ISysConfigService sysConfigService;
|
|
private ISysConfigService sysConfigService;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private QwConversationMessageMapper messageMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private QwConversationParticipantMapper participantMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private WeChatSpaceUtil weChatSpaceUtil;
|
|
|
|
|
+
|
|
|
private final RestTemplate restTemplate = new RestTemplate();
|
|
private final RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
|
|
|
private final ConcurrentHashMap<String, String> consumedCodes = new ConcurrentHashMap<>();
|
|
private final ConcurrentHashMap<String, String> consumedCodes = new ConcurrentHashMap<>();
|
|
@@ -39,93 +52,61 @@ public class ICorporateWeChatSpaceServiceImpl implements ICorporateWeChatSpaceSe
|
|
|
// 系统配置缓存前缀
|
|
// 系统配置缓存前缀
|
|
|
private final static String CONFIG_KEY = "qw.sessionConfig";
|
|
private final static String CONFIG_KEY = "qw.sessionConfig";
|
|
|
|
|
|
|
|
- //获取会话记录Key
|
|
|
|
|
- private final static String targetKey = "invokeSyncMsg";
|
|
|
|
|
-
|
|
|
|
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
|
|
|
- // =============== 核心:通过专区中转拉取会话 ===============
|
|
|
|
|
|
|
+ // =============== 查询数据库保存的会话 ===============
|
|
|
@Override
|
|
@Override
|
|
|
- public JSONObject fetchConversations(long limit,long timeout,String cursor,
|
|
|
|
|
- String customerId, String staffUserId) {
|
|
|
|
|
|
|
+ public JSONObject fetchConversations(long limit, long timeout, String cursor,
|
|
|
|
|
+ String customerId, String staffUserId, String corpid) {
|
|
|
JSONObject result = new JSONObject();
|
|
JSONObject result = new JSONObject();
|
|
|
try {
|
|
try {
|
|
|
- QwSessionConfigVo qwConfig = getQwSessionConfig();
|
|
|
|
|
- String corpid = qwConfig.getCorpid();
|
|
|
|
|
- String agentSecret = qwConfig.getAgentSecret();
|
|
|
|
|
- String accessToken = WeChatSpaceUtil.getAccessToken(corpid, agentSecret);
|
|
|
|
|
-
|
|
|
|
|
- // 构建 request_data(invoke_sync_msg 能力要求)
|
|
|
|
|
- JSONObject requestData = new JSONObject();
|
|
|
|
|
- if (StringUtils.isNotBlank(cursor)){// 首次为空不传
|
|
|
|
|
- requestData.put("cursor", cursor);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- requestData.put("limit", limit > 0 ? limit : 200); // 限制 1-1000
|
|
|
|
|
- //requestData.put("token", ""); // 暂不传 token,有频率限制但测试够用
|
|
|
|
|
-
|
|
|
|
|
- String abilityId = null;
|
|
|
|
|
-
|
|
|
|
|
- if (qwConfig.getAbilityIds() != null) {
|
|
|
|
|
- //根据配置的能力Key找到对应的 abilityId
|
|
|
|
|
- abilityId = qwConfig.getAbilityIds().stream()
|
|
|
|
|
- .filter(item -> targetKey.equals(item.getKey()))
|
|
|
|
|
- .map(QwSessionConfigVo.AbilityItem::getValue)
|
|
|
|
|
- .findFirst()
|
|
|
|
|
- .orElse(null);
|
|
|
|
|
- }
|
|
|
|
|
- if (abilityId == null) {
|
|
|
|
|
- throw new CustomException("未配置获取会话记录的能力ID");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 调用 sync_call_program
|
|
|
|
|
- String url = "https://qyapi.weixin.qq.com/cgi-bin/chatdata/sync_call_program?access_token=" + accessToken;
|
|
|
|
|
- JSONObject requestBody = new JSONObject();
|
|
|
|
|
- requestBody.put("program_id", qwConfig.getProgramId());
|
|
|
|
|
- requestBody.put("ability_id", abilityId);
|
|
|
|
|
- requestBody.put("request_data", JSON.toJSONString(requestData));
|
|
|
|
|
-
|
|
|
|
|
- log.info("调用专区接口: ability_id={}, request_data={}", abilityId, requestData);
|
|
|
|
|
- JSONObject response = restTemplate.postForObject(url, requestBody, JSONObject.class);
|
|
|
|
|
- //log.info("专区响应: {}", response);
|
|
|
|
|
-
|
|
|
|
|
- if (response != null && response.getInteger("errcode") == 0) {
|
|
|
|
|
- String responseDataStr = response.getString("response_data");
|
|
|
|
|
- if (responseDataStr != null) {
|
|
|
|
|
- JSONObject responseData = JSON.parseObject(responseDataStr);
|
|
|
|
|
- Integer innerErrCode = responseData.getInteger("errcode");
|
|
|
|
|
- if (innerErrCode != null && innerErrCode == 0) {
|
|
|
|
|
- //获取 cursor用于下次拉取更多数据
|
|
|
|
|
- String nextCursor = responseData.getString("next_cursor");
|
|
|
|
|
- // 获取消息列表并处理
|
|
|
|
|
- JSONArray msgList = responseData.getJSONArray("msg_list");
|
|
|
|
|
- if (msgList != null && !msgList.isEmpty()) {
|
|
|
|
|
- // 解密 + 过滤 + 格式化
|
|
|
|
|
- JSONArray processedList = processMessages(msgList, customerId, staffUserId, qwConfig);
|
|
|
|
|
- result.put("data", processedList);
|
|
|
|
|
- } else {
|
|
|
|
|
- result.put("data", new JSONArray());
|
|
|
|
|
- }
|
|
|
|
|
- result.put("errcode", 0);
|
|
|
|
|
- result.put("errmsg", "ok");
|
|
|
|
|
- //返回 has_more 和 next_cursor 给前端,当没有更多数据时,返回 has_more 为 0
|
|
|
|
|
- result.put("has_more", responseData.getInteger("has_more"));
|
|
|
|
|
- result.put("next_cursor", nextCursor);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 专区内部错误
|
|
|
|
|
- result.put("errcode", innerErrCode);
|
|
|
|
|
- result.put("errmsg", responseData.getString("errmsg"));
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- result.put("errcode", -1);
|
|
|
|
|
- result.put("errmsg", "专区返回数据格式错误");
|
|
|
|
|
|
|
+ // 从数据库查询两个参与者之间的消息
|
|
|
|
|
+ List<QwConversationMessage> messages = messageMapper.selectMessagesBetweenUsers(
|
|
|
|
|
+ corpid, staffUserId, customerId, limit);
|
|
|
|
|
+
|
|
|
|
|
+ JSONArray dataArray = new JSONArray();
|
|
|
|
|
+ for (QwConversationMessage msg : messages) {
|
|
|
|
|
+ JSONObject item = new JSONObject();
|
|
|
|
|
+ item.put("msgid", msg.getMsgid());
|
|
|
|
|
+ item.put("secretKey", msg.getSecretKey()); // 已解密存储
|
|
|
|
|
+
|
|
|
|
|
+ // sender 对象
|
|
|
|
|
+ JSONObject sender = new JSONObject();
|
|
|
|
|
+ sender.put("type", msg.getSenderType());
|
|
|
|
|
+ sender.put("id", msg.getSenderId());
|
|
|
|
|
+ item.put("sender", sender);
|
|
|
|
|
+
|
|
|
|
|
+ // receiver_list:从参与者表获取 type=2 的参与者(接收者)
|
|
|
|
|
+ List<QwConversationParticipant> receivers = participantMapper.selectByMsgidAndType(
|
|
|
|
|
+ corpid, msg.getMsgid(), 2);
|
|
|
|
|
+ JSONArray receiverList = new JSONArray();
|
|
|
|
|
+ for (QwConversationParticipant p : receivers) {
|
|
|
|
|
+ JSONObject recv = new JSONObject();
|
|
|
|
|
+ recv.put("type", p.getUserType());
|
|
|
|
|
+ recv.put("id", p.getUserId());
|
|
|
|
|
+ receiverList.add(recv);
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- result.put("errcode", response != null ? response.getInteger("errcode") : -1);
|
|
|
|
|
- result.put("errmsg", response != null ? response.getString("errmsg") : "调用专区失败");
|
|
|
|
|
|
|
+ item.put("receiver_list", receiverList);
|
|
|
|
|
+ item.put("msgtype", msg.getMsgtype());
|
|
|
|
|
+
|
|
|
|
|
+ // 时间格式化
|
|
|
|
|
+ if (msg.getSendTime() != null) {
|
|
|
|
|
+ String formattedTime = Instant.ofEpochSecond(msg.getSendTime())
|
|
|
|
|
+ .atZone(ZoneId.systemDefault())
|
|
|
|
|
+ .toLocalDateTime()
|
|
|
|
|
+ .format(DATE_TIME_FORMATTER);
|
|
|
|
|
+ item.put("send_time_str", formattedTime);
|
|
|
|
|
+ item.put("send_time", msg.getSendTime());
|
|
|
|
|
+ }
|
|
|
|
|
+ dataArray.add(item);
|
|
|
}
|
|
}
|
|
|
|
|
+ result.put("data", dataArray);
|
|
|
|
|
+ result.put("errcode", 0);
|
|
|
|
|
+ result.put("errmsg", "ok");
|
|
|
|
|
+ result.put("has_more", 0);
|
|
|
|
|
+ result.put("next_cursor", "");
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.error("获取会话记录失败", e);
|
|
|
|
|
|
|
+ log.error("查询会话记录失败", e);
|
|
|
result.put("errcode", -1);
|
|
result.put("errcode", -1);
|
|
|
result.put("errmsg", "内部错误:" + e.getMessage());
|
|
result.put("errmsg", "内部错误:" + e.getMessage());
|
|
|
}
|
|
}
|
|
@@ -135,13 +116,13 @@ public class ICorporateWeChatSpaceServiceImpl implements ICorporateWeChatSpaceSe
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public JSONObject getAgentConfigSignature(String url) {
|
|
|
|
|
- QwSessionConfigVo qwSessionConfig = getQwSessionConfig();
|
|
|
|
|
- return WeChatSpaceUtil.generateAgentConfigSignature(qwSessionConfig.getCorpid(), qwSessionConfig.getAgentSecret(), qwSessionConfig.getAgentid(), url);
|
|
|
|
|
|
|
+ public JSONObject getAgentConfigSignature(String url,String corpid) {
|
|
|
|
|
+ QwSessionConfigVo qwSessionConfig = getQwSessionConfigByCorpid(corpid);
|
|
|
|
|
+ return weChatSpaceUtil.generateAgentConfigSignature(qwSessionConfig.getCorpid(), qwSessionConfig.getAgentSecret(), qwSessionConfig.getAgentid(), url);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
- public JSONObject login(String code) {
|
|
|
|
|
|
|
+ public JSONObject login(String code,String corpid) {
|
|
|
JSONObject result = new JSONObject();
|
|
JSONObject result = new JSONObject();
|
|
|
if (code == null || code.isEmpty()) {
|
|
if (code == null || code.isEmpty()) {
|
|
|
result.put("errcode", -1);
|
|
result.put("errcode", -1);
|
|
@@ -153,9 +134,9 @@ public class ICorporateWeChatSpaceServiceImpl implements ICorporateWeChatSpaceSe
|
|
|
result.put("errmsg", "code already used");
|
|
result.put("errmsg", "code already used");
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
- QwSessionConfigVo qwSessionConfig = getQwSessionConfig();
|
|
|
|
|
|
|
+ QwSessionConfigVo qwSessionConfig = getQwSessionConfigByCorpid(corpid);
|
|
|
try {
|
|
try {
|
|
|
- String accessToken = WeChatSpaceUtil.getAccessToken(qwSessionConfig.getCorpid(), qwSessionConfig.getAgentSecret());
|
|
|
|
|
|
|
+ String accessToken = weChatSpaceUtil.getAccessToken(qwSessionConfig.getCorpid(), qwSessionConfig.getAgentSecret());
|
|
|
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token="
|
|
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token="
|
|
|
+ accessToken + "&code=" + code;
|
|
+ accessToken + "&code=" + code;
|
|
|
JSONObject resp = restTemplate.getForObject(url, JSONObject.class);
|
|
JSONObject resp = restTemplate.getForObject(url, JSONObject.class);
|
|
@@ -181,16 +162,34 @@ public class ICorporateWeChatSpaceServiceImpl implements ICorporateWeChatSpaceSe
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 获取企业微信专区会话配置
|
|
|
|
|
|
|
+ * 获取所有企业微信专区会话配置列表
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
- public QwSessionConfigVo getQwSessionConfig() {
|
|
|
|
|
- QwSessionConfigVo qwSessionConfig = sysConfigService.getConfig(CONFIG_KEY, QwSessionConfigVo.class);
|
|
|
|
|
- if (qwSessionConfig == null){
|
|
|
|
|
- log.error("未找到企微专区配置,key:{}",CONFIG_KEY);
|
|
|
|
|
|
|
+ public List<QwSessionConfigVo> getQwSessionConfigList() {
|
|
|
|
|
+ String json = sysConfigService.selectConfigByKey(CONFIG_KEY);
|
|
|
|
|
+ if (StringUtils.isBlank(json)) {
|
|
|
|
|
+ log.error("未找到企微专区配置, key:{}", CONFIG_KEY);
|
|
|
throw new CustomException("未找到企微专区配置");
|
|
throw new CustomException("未找到企微专区配置");
|
|
|
}
|
|
}
|
|
|
- return qwSessionConfig;
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 直接解析为 List
|
|
|
|
|
+ return JSON.parseArray(json, QwSessionConfigVo.class);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("解析企微专区配置失败", e);
|
|
|
|
|
+ throw new CustomException("企微专区配置格式错误");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据企业ID获取单个配置
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public QwSessionConfigVo getQwSessionConfigByCorpid(String corpid) {
|
|
|
|
|
+ List<QwSessionConfigVo> all = getQwSessionConfigList();
|
|
|
|
|
+ return all.stream()
|
|
|
|
|
+ .filter(config -> config.getCorpid().equals(corpid))
|
|
|
|
|
+ .findFirst()
|
|
|
|
|
+ .orElseThrow(() -> new CustomException("未找到corpid为 " + corpid + " 的配置"));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|