zoujiajian 1 rok pred
rodič
commit
a3d0d69e11

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/corp/kf/CorpKfWaitingQueueUserMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.corp.kf;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CorpKfWaitingQueueUser;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpKfWaitingQueueUserMapper
+ * 创建者: JavaZou
+ * 创建时间:2024/12/31 16:54
+ */
+public interface CorpKfWaitingQueueUserMapper extends BaseMapper<CorpKfWaitingQueueUser> {
+}

+ 18 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CorpKfWaitingQueueUser.java

@@ -0,0 +1,18 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpKfWaitingQueueUser
+ * 创建者: JavaZou
+ * 创建时间:2024/12/31 16:53
+ */
+@Getter
+@Setter
+public class CorpKfWaitingQueueUser extends BaseEntity{
+	private String openKfId;
+
+	private String externalUserid;
+}

+ 33 - 0
netflix-dao/src/main/java/com/cyksj/model/kf/CorpKfServicerResp.java

@@ -0,0 +1,33 @@
+package com.cyksj.model.kf;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpKfServicerList
+ * 创建者: JavaZou
+ * 创建时间:2024/12/31 16:32
+ */
+@Getter
+@Setter
+public class CorpKfServicerResp {
+	private Long errcode;
+
+	private String errmsg;
+
+	@JsonProperty("servicer_list")
+	private List<CorpKfServicer> servicerList;
+
+	@Getter
+	@Setter
+	public static class CorpKfServicer{
+		private String userid;
+
+		//	接待人员的接待状态。0:接待中,1:停止接待。
+		private Integer status;
+	}
+}

+ 21 - 0
netflix-dao/src/main/java/com/cyksj/model/kf/CorpKfSessionResp.java

@@ -0,0 +1,21 @@
+package com.cyksj.model.kf;
+
+import com.cyksj.model.entity.BaseEntity;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpKfSessionResp
+ * 创建者: JavaZou
+ * 创建时间:2024/12/31 17:00
+ */
+@Getter
+@Setter
+public class CorpKfSessionResp extends BaseEntity {
+	private Long errcode;
+
+	private String errmsg;
+
+	private String msg_code;
+}

+ 7 - 0
netflix-service/src/main/java/com/cyksj/service/corp/WxCorpOps.java

@@ -218,4 +218,11 @@ public interface WxCorpOps {
 	 * 客服发送消息
 	 */
 	WxCpKfMsgSendResp sendMsgOnEvent(String corpId, WxCorpKfMsgSendRequest request) throws Exception;
+
+	/**
+	 * 获取客服账号接待人员
+	 */
+	CorpKfServicerResp getKfServicerList(String corpId,String openKfId);
+
+	void moveWaitingQueue(String corpId, String openKfId, String externalUserId) throws Exception;
 }

+ 90 - 26
netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpServiceImpl.java

@@ -2,7 +2,9 @@ package com.cyksj.service.corp.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateTime;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpUtil;
 import cn.hutool.json.JSONUtil;
@@ -18,10 +20,13 @@ import com.cyksj.config.corp.YHLXWxCorpConfig;
 import com.cyksj.dto.CorpOauth2UserInfo;
 import com.cyksj.mapper.OrderDonMapper;
 import com.cyksj.mapper.corp.*;
+import com.cyksj.mapper.corp.kf.CorpKfAccountFollowMapper;
 import com.cyksj.mapper.corp.kf.CorpKfAccountMapper;
 import com.cyksj.mapper.corp.kf.CorpKfAccountSessionConfigMapper;
+import com.cyksj.mapper.corp.kf.CorpKfWaitingQueueUserMapper;
 import com.cyksj.model.dto.WxCorpEncryptDto;
 import com.cyksj.model.entity.*;
+import com.cyksj.model.kf.CorpKfServicerResp;
 import com.cyksj.model.kf.WxCorpKfMsgSendRequest;
 import com.cyksj.model.kf.WxCpKfEventMsg;
 import com.cyksj.model.kf.WxCpKfMsgListResp;
@@ -50,6 +55,7 @@ import java.io.File;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
@@ -106,9 +112,15 @@ public class CorpServiceImpl implements CorpService {
 	@Autowired
 	private CorpKfAccountMapper corpKfAccountMapper;
 
+	@Autowired
+	private CorpKfAccountFollowMapper corpKfAccountFollowMapper;
+
 	@Autowired
 	private CorpKfAccountSessionConfigMapper corpKfAccountSessionConfigMapper;
 
+	@Autowired
+	private CorpKfWaitingQueueUserMapper corpKfWaitingQueueUserMapper;
+
 	@PostConstruct
 	private void init() {
 		corpMessageRouter = new CorpMessageRouter(this)
@@ -521,7 +533,7 @@ public class CorpServiceImpl implements CorpService {
 			WxCpKfMsgListResp wxCpKfMsgListResp = wxCorpOps.syncMsg(message.getToUserName(), null, message.getToken(), null, openKfId);
 			List<WxCpKfMsgListResp.WxCpKfMsgItem> msgList = wxCpKfMsgListResp.getMsgList();
 			//欢迎语
-			sendWelcomeMsg(message, msgList);
+			enterSessionEvent(message, msgList);
 			//消息发送失败事件
 			//拒收客户消息变更事件
 			//同步客服聊天记录
@@ -537,44 +549,96 @@ public class CorpServiceImpl implements CorpService {
 	/**
 	 * 微信客服欢迎语
 	 */
-	public void sendWelcomeMsg(CorpXmlMessage message, List<WxCpKfMsgListResp.WxCpKfMsgItem> msgList) throws Exception {
+	public void enterSessionEvent(CorpXmlMessage message, List<WxCpKfMsgListResp.WxCpKfMsgItem> msgList) throws Exception {
 		String openKfId = message.getOpenKfId();
 		List<WxCpKfMsgListResp.WxCpKfMsgItem> enterSessionList = msgList.stream()
 				.filter((item) -> item.getEvent() != null && item.getEvent().getEventType().equals("enter_session")).collect(Collectors.toList());
+		CorpKfAccount corpKfAccount = corpKfAccountMapper.selectOne(Wrappers.lambdaQuery(CorpKfAccount.class)
+				.eq(CorpKfAccount::getOpenKfId, openKfId)
+				.last("limit 1"));
+		if (corpKfAccount == null) {
+			log.info("客服账号:{}已被删除或不存在", openKfId);
+			return;
+		}
+		//账号会话设置
+		CorpKfAccountSessionConfig corpKfAccountSessionConfig = corpKfAccountSessionConfigMapper.selectOne(Wrappers.lambdaQuery(CorpKfAccountSessionConfig.class)
+				.eq(CorpKfAccountSessionConfig::getOpenKfId, openKfId)
+				.last("limit 1"));
 		//欢迎语
 		if (enterSessionList.size() > 0) {
 			WxCpKfMsgListResp.WxCpKfMsgItem wxCpKfMsgItem = enterSessionList.get(enterSessionList.size() - 1);
 			WxCpKfEventMsg event = wxCpKfMsgItem.getEvent();
 			String welcomeCode = event.getWelcomeCode();
-			CorpKfAccount corpKfAccount = corpKfAccountMapper.selectOne(Wrappers.lambdaQuery(CorpKfAccount.class)
-					.eq(CorpKfAccount::getOpenKfId, openKfId)
-					.last("limit 1"));
-			if (corpKfAccount == null) {
-				return;
-			}
-			WxCorpKfMsgSendRequest wxCpKfMsgSendRequest = new WxCorpKfMsgSendRequest();
-			wxCpKfMsgSendRequest.setMsgType("text");
-			wxCpKfMsgSendRequest.setCode(welcomeCode);
-			WxCorpKfMsgSendRequest.WxCpKfTextMsg textMsg = new WxCorpKfMsgSendRequest.WxCpKfTextMsg();
-			//账号会话设置
-			CorpKfAccountSessionConfig corpKfAccountSessionConfig = corpKfAccountSessionConfigMapper.selectOne(Wrappers.lambdaQuery(CorpKfAccountSessionConfig.class)
-					.eq(CorpKfAccountSessionConfig::getOpenKfId, openKfId)
-					.last("limit 1"));
+			//如果满足发送欢迎语条件(条件为:用户在过去48小时里未收过欢迎语,且未向客服发过消息),会返回该字段。
+			//可用该welcome_code调用发送事件响应消息接口给客户发送欢迎语。
 			if (corpKfAccountSessionConfig == null) {
 				log.info("openKfId:{}未设置会话设置", openKfId);
 				return;
 			}
-			if (StrUtil.isEmpty(corpKfAccountSessionConfig.getWelcomeContent())) {
-				log.info("openKfId:{}未设置会话欢迎语设置", openKfId);
-				return;
+			if (StrUtil.isNotEmpty(welcomeCode)) {
+				WxCorpKfMsgSendRequest wxCpKfMsgSendRequest = new WxCorpKfMsgSendRequest();
+				wxCpKfMsgSendRequest.setMsgType("text");
+				wxCpKfMsgSendRequest.setCode(welcomeCode);
+				WxCorpKfMsgSendRequest.WxCpKfTextMsg textMsg = new WxCorpKfMsgSendRequest.WxCpKfTextMsg();
+				if (StrUtil.isEmpty(corpKfAccountSessionConfig.getWelcomeContent())) {
+					log.info("openKfId:{}未设置会话欢迎语设置", openKfId);
+					return;
+				}
+				textMsg.setContent(corpKfAccountSessionConfig.getWelcomeContent());
+				wxCpKfMsgSendRequest.setText(textMsg);
+				wxCpKfMsgSendRequest.setToUser(welcomeCode);
+				wxCpKfMsgSendRequest.setOpenKfid(event.getOpenKfid());
+				wxCorpOps.sendMsgOnEvent(message.getToUserName(), wxCpKfMsgSendRequest);
+			} else {
+				//配置接待人员
+				//是否有空闲人员
+				int hour = DateTime.now().hour(true);
+				corpKfAccountFollowMapper.selectList(Wrappers.lambdaQuery(CorpKfAccountFollow.class)
+						.eq(CorpKfAccountFollow::getOpenKfId, openKfId)
+						.le(CorpKfAccountFollow::getStartTime, hour)
+						.ge(CorpKfAccountFollow::getEndTime, hour));
+				//获取接待人员列表
+				CorpKfServicerResp corpKfServicerResp = wxCorpOps.getKfServicerList(message.getToUserName(), openKfId);
+				List<CorpKfServicerResp.CorpKfServicer> servicerList = corpKfServicerResp.getServicerList();
+				List<CorpKfServicerResp.CorpKfServicer> corpKfServicers = servicerList.stream().filter(servicer -> servicer.getUserid() != null && servicer.getStatus() == 0).collect(Collectors.toList());
+				if (CollUtil.isEmpty(corpKfServicers)) {
+					//非值班 移入等待池并发送非值班提示消息
+					moveWaitingQueue(message.getToUserName(), openKfId, event.getExternalUserId());
+					String noDutyTips = corpKfAccountSessionConfig.getNoDutyTips();
+					if (StrUtil.isNotEmpty(noDutyTips)) {
+						//发送提示
+						sendCorpKfTextTips(message.getToUserName(), event.getExternalUserId(), openKfId, noDutyTips);
+					}
+					return;
+				}
+				//未有可接待的客服 移入等待池并发送进入等待池消息
+				moveWaitingQueue(message.getToUserName(), openKfId, event.getExternalUserId());
+				String queueTips = corpKfAccountSessionConfig.getQueueTips();
+				sendCorpKfTextTips(message.getToUserName(), event.getExternalUserId(), openKfId, queueTips);
 			}
-			textMsg.setContent(corpKfAccountSessionConfig.getWelcomeContent());
-			wxCpKfMsgSendRequest.setText(textMsg);
-			wxCpKfMsgSendRequest.setToUser(welcomeCode);
-			wxCpKfMsgSendRequest.setOpenKfid(event.getOpenKfid());
-			wxCorpOps.sendMsgOnEvent(message.getToUserName(), wxCpKfMsgSendRequest);
-		} else {
-			log.error("企业微信客服 事件消息回调 时间内容为空.");
 		}
 	}
+
+	/**
+	 * 发送微信客服会话文本消息
+	 */
+	public void sendCorpKfTextTips(String corpId, String externalUserid, String openKfId, String content) throws Exception {
+		WxCorpKfMsgSendRequest wxCpKfMsgSendRequest = new WxCorpKfMsgSendRequest();
+		wxCpKfMsgSendRequest.setMsgType("text");
+		wxCpKfMsgSendRequest.setToUser(externalUserid);
+		wxCpKfMsgSendRequest.setOpenKfid(openKfId);
+		WxCorpKfMsgSendRequest.WxCpKfTextMsg textMsg = new WxCorpKfMsgSendRequest.WxCpKfTextMsg();
+		textMsg.setContent(content);
+		wxCpKfMsgSendRequest.setText(textMsg);
+		wxCorpOps.sendMsgOnEvent(corpId, wxCpKfMsgSendRequest);
+	}
+
+	private void moveWaitingQueue(String corpId, String openKfId, String externalUserId) throws Exception {
+		CorpKfWaitingQueueUser corpKfWaitingQueueUser = new CorpKfWaitingQueueUser();
+		corpKfWaitingQueueUser.setOpenKfId(openKfId);
+		corpKfWaitingQueueUser.setExternalUserid(externalUserId);
+		corpKfWaitingQueueUserMapper.insert(corpKfWaitingQueueUser);
+		//移入等待池
+		wxCorpOps.moveWaitingQueue(corpId, openKfId, externalUserId);
+	}
 }

+ 47 - 1
netflix-service/src/main/java/com/cyksj/service/corp/impl/WxCorpOpsImpl.java

@@ -894,6 +894,7 @@ public class WxCorpOpsImpl implements WxCorpOps {
 		String url = "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=" + getKfAccessToken(corpId);
 
 		JSONObject json = new JSONObject();
+		json.putOpt("open_kfid", openKfId);
 		if (cursor != null) {
 			json.putOpt("cursor", cursor);
 		}
@@ -933,7 +934,7 @@ public class WxCorpOpsImpl implements WxCorpOps {
 						.body(HttpRequest.BodyPublishers.ofString(Jsons.toJson(request), IoKit.Charsets.UTF_8.getCharset()))
 						.send(HttpResponse.BodyHandlers.ofString());
 		if (200 != res.statusCode()) {
-			throw BusinessRuntimeException.getInstance("发送欢迎语等事件响应消息: " + res.body());
+			throw BusinessRuntimeException.getInstance("发送客服事件响应消息: " + res.body());
 		}
 
 		WxCpKfMsgSendResp resp = Jsons.parseObject(res.body(), WxCpKfMsgSendResp.class);
@@ -942,4 +943,49 @@ public class WxCorpOpsImpl implements WxCorpOps {
 		}
 		return resp;
 	}
+
+	@Override
+	public CorpKfServicerResp getKfServicerList(String corpId,String openKfId) throws Exception {
+		String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/kf/servicer/list?access_token=%s&open_kfid=%s", getKfAccessToken(corpId), openKfId);
+		HttpResponse<String> res =
+				J11HttpC.custom()
+						.ofPost()
+						.url(url)
+						.headers(J11HttpC.ReqType.raw_json)
+						.send(HttpResponse.BodyHandlers.ofString());
+		if (200 != res.statusCode()) {
+			throw BusinessRuntimeException.getInstance("获取微信客服接待人员响应消息: " + res.body());
+		}
+
+		CorpKfServicerResp resp = Jsons.parseObject(res.body(), CorpKfServicerResp.class);
+		if (resp.getErrcode() != 0) {
+			throw BusinessRuntimeException.getInstance("获取微信客服接待人员响应消息: " + resp.getErrmsg());
+		}
+		return resp;
+	}
+
+	@Override
+	public void moveWaitingQueue(String corpId, String openKfId, String externalUserId) throws Exception {
+		String url = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/trans?access_token=" + getKfAccessToken(corpId);
+		JSONObject request = new JSONObject();
+		request.putOpt("open_kfid", openKfId);
+		request.putOpt("external_userid", externalUserId);
+		//待接入池排队中
+		request.putOpt("service_state", 2);
+		HttpResponse<String> res =
+				J11HttpC.custom()
+						.ofPost()
+						.url(url)
+						.headers(J11HttpC.ReqType.raw_json)
+						.body(HttpRequest.BodyPublishers.ofString(Jsons.toJson(request), IoKit.Charsets.UTF_8.getCharset()))
+						.send(HttpResponse.BodyHandlers.ofString());
+		if (200 != res.statusCode()) {
+			throw BusinessRuntimeException.getInstance("移入等待池响应消息: " + res.body());
+		}
+
+		CorpKfSessionResp resp = Jsons.parseObject(res.body(), CorpKfSessionResp.class);
+		if (resp.getErrcode() != 0) {
+			throw BusinessRuntimeException.getInstance("移入等待池响应消息: " + resp.getErrmsg());
+		}
+	}
 }