mirror of
https://github.com/chatopera/cosin.git
synced 2025-06-16 18:30:03 +08:00
#227 enhance automatic service dist
This commit is contained in:
parent
bd0c322450
commit
20287ead02
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.acd.visitor.ACDVisAllocatorMw;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.proxy.AgentUserProxy;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class ACDAgentService {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDAgentService.class);
|
||||
|
||||
@Autowired
|
||||
private ACDVisAllocatorMw acdAgentAllocatorMw;
|
||||
|
||||
@Autowired
|
||||
private AgentUserProxy agentUserProxy;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private ACDQueueService acdQueueService;
|
||||
|
||||
/**
|
||||
* 为访客分配坐席
|
||||
*
|
||||
* @param agentUser
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public AgentService allotAgent(
|
||||
final AgentUser agentUser,
|
||||
final String orgi) {
|
||||
/**
|
||||
* 查询条件,当前在线的 坐席,并且 未达到最大 服务人数的坐席
|
||||
*/
|
||||
List<AgentStatus> agentStatusList = acdAgentAllocatorMw.filterOutAvailableAgentStatus(agentUser, orgi);
|
||||
|
||||
/**
|
||||
* 处理ACD 的 技能组请求和 坐席请求
|
||||
*/
|
||||
AgentStatus agentStatus = null;
|
||||
AgentService agentService = null; //放入缓存的对象
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
if (agentStatusList.size() > 0) {
|
||||
agentStatus = agentStatusList.get(0);
|
||||
if (agentStatus.getUsers() >= sessionConfig.getMaxuser()) {
|
||||
agentStatus = null;
|
||||
/**
|
||||
* 判断当前有多少人排队中 , 分三种情况:1、请求技能组的,2、请求坐席的,3,默认请求的
|
||||
*
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
agentService = acdAgentAllocatorMw.processAgentService(agentStatus, agentUser, orgi, false, sessionConfig);
|
||||
// 处理结果:进入排队队列
|
||||
if (StringUtils.equals(MainContext.AgentUserStatusEnum.INQUENE.toString(), agentService.getStatus())) {
|
||||
agentService.setQueneindex(
|
||||
acdQueueService.getQueueIndex(agentUser.getAgentno(), orgi, agentUser.getSkill()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.warn("[allotAgent] exception: ", ex);
|
||||
}
|
||||
agentUserProxy.broadcastAgentsStatus(
|
||||
orgi, "user", agentService != null && agentService.getStatus().equals(
|
||||
MainContext.AgentUserStatusEnum.INSERVICE.toString()) ? "inservice" : "inquene",
|
||||
agentUser.getId()
|
||||
);
|
||||
return agentService;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.model.AgentService;
|
||||
import com.chatopera.cc.model.AgentUser;
|
||||
import com.chatopera.cc.persistence.repository.AgentServiceRepository;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
public class ACDChatbotService {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDChatbotService.class);
|
||||
|
||||
@Autowired
|
||||
private AgentServiceRepository agentServiceRes;
|
||||
|
||||
/**
|
||||
* 为访客分配机器人客服, ACD策略,此处 AgentStatus 是建议 的 坐席, 如果启用了 历史服务坐席 优先策略, 则会默认检查历史坐席是否空闲,如果空闲,则分配,如果不空闲,则 分配当前建议的坐席
|
||||
*
|
||||
* @param agentUser
|
||||
* @param orgi
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public AgentService processChatbotService(final String botName, final AgentUser agentUser, final String orgi) {
|
||||
AgentService agentService = new AgentService(); //放入缓存的对象
|
||||
Date now = new Date();
|
||||
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
|
||||
agentService = agentServiceRes.findByIdAndOrgi(agentUser.getAgentserviceid(), orgi);
|
||||
agentService.setEndtime(now);
|
||||
if (agentService.getServicetime() != null) {
|
||||
agentService.setSessiontimes(System.currentTimeMillis() - agentService.getServicetime().getTime());
|
||||
}
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
} else {
|
||||
agentService.setServicetime(now);
|
||||
agentService.setLogindate(now);
|
||||
agentService.setOrgi(orgi);
|
||||
agentService.setOwner(agentUser.getContextid());
|
||||
agentService.setSessionid(agentUser.getSessionid());
|
||||
agentService.setRegion(agentUser.getRegion());
|
||||
agentService.setUsername(agentUser.getUsername());
|
||||
agentService.setChannel(agentUser.getChannel());
|
||||
if (botName != null) {
|
||||
agentService.setAgentusername(botName);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(agentUser.getContextid())) {
|
||||
agentService.setContextid(agentUser.getContextid());
|
||||
} else {
|
||||
agentService.setContextid(agentUser.getSessionid());
|
||||
}
|
||||
|
||||
agentService.setUserid(agentUser.getUserid());
|
||||
agentService.setAiid(agentUser.getAgentno());
|
||||
agentService.setAiservice(true);
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
|
||||
|
||||
agentService.setAppid(agentUser.getAppid());
|
||||
agentService.setLeavemsg(false);
|
||||
}
|
||||
|
||||
agentServiceRes.save(agentService);
|
||||
return agentService;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.socketio.message.Message;
|
||||
import com.chatopera.cc.util.IP;
|
||||
|
||||
public class ACDComposeContext extends Message {
|
||||
|
||||
// 技能组及渠道
|
||||
private String orgi;
|
||||
private String organid;
|
||||
private Organ organ;
|
||||
private String appid;
|
||||
private String channel;
|
||||
private SNSAccount snsAccount;
|
||||
private String sessionid;
|
||||
|
||||
// 策略
|
||||
private SessionConfig sessionConfig;
|
||||
// 坐席报告
|
||||
private AgentReport agentReport;
|
||||
|
||||
// 机器人客服
|
||||
private String aiid;
|
||||
private boolean isAi;
|
||||
|
||||
// 是否是邀请
|
||||
private boolean isInvite;
|
||||
|
||||
private User agent;
|
||||
private String agentno;
|
||||
private String agentUserId;
|
||||
|
||||
private String agentServiceId;
|
||||
|
||||
private AgentUser agentUser;
|
||||
|
||||
private AgentService agentService;
|
||||
|
||||
// 访客
|
||||
private String onlineUserId;
|
||||
private OnlineUser onlineUser;
|
||||
private String onlineUserNickname;
|
||||
private String onlineUserHeadimgUrl;
|
||||
|
||||
// 其它信息
|
||||
private IP ipdata;
|
||||
private String initiator;
|
||||
private String title;
|
||||
private String url;
|
||||
private String browser;
|
||||
private String osname;
|
||||
private String traceid;
|
||||
private String ownerid;
|
||||
private String ip;
|
||||
|
||||
public String getOrganid() {
|
||||
return organid;
|
||||
}
|
||||
|
||||
public void setOrganid(String organid) {
|
||||
this.organid = organid;
|
||||
}
|
||||
|
||||
public Organ getOrgan() {
|
||||
return organ;
|
||||
}
|
||||
|
||||
public void setOrgan(Organ organ) {
|
||||
this.organ = organ;
|
||||
}
|
||||
|
||||
public String getAppid() {
|
||||
return appid;
|
||||
}
|
||||
|
||||
public void setAppid(String appid) {
|
||||
this.appid = appid;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public SNSAccount getSnsAccount() {
|
||||
return snsAccount;
|
||||
}
|
||||
|
||||
public void setSnsAccount(SNSAccount snsAccount) {
|
||||
this.snsAccount = snsAccount;
|
||||
}
|
||||
|
||||
public SessionConfig getSessionConfig() {
|
||||
return sessionConfig;
|
||||
}
|
||||
|
||||
public void setSessionConfig(SessionConfig sessionConfig) {
|
||||
this.sessionConfig = sessionConfig;
|
||||
}
|
||||
|
||||
public String getAiid() {
|
||||
return aiid;
|
||||
}
|
||||
|
||||
public void setAiid(String aiid) {
|
||||
this.aiid = aiid;
|
||||
}
|
||||
|
||||
public boolean isAi() {
|
||||
return isAi;
|
||||
}
|
||||
|
||||
public void setAi(boolean ai) {
|
||||
isAi = ai;
|
||||
}
|
||||
|
||||
public boolean isInvite() {
|
||||
return isInvite;
|
||||
}
|
||||
|
||||
public void setInvite(boolean invite) {
|
||||
isInvite = invite;
|
||||
}
|
||||
|
||||
public User getAgent() {
|
||||
return agent;
|
||||
}
|
||||
|
||||
public void setAgent(User agent) {
|
||||
this.agent = agent;
|
||||
}
|
||||
|
||||
public String getAgentno() {
|
||||
return agentno;
|
||||
}
|
||||
|
||||
public void setAgentno(String agentno) {
|
||||
this.agentno = agentno;
|
||||
}
|
||||
|
||||
public String getAgentUserId() {
|
||||
return agentUserId;
|
||||
}
|
||||
|
||||
public void setAgentUserId(String agentUserId) {
|
||||
this.agentUserId = agentUserId;
|
||||
}
|
||||
|
||||
public String getOnlineUserId() {
|
||||
return onlineUserId;
|
||||
}
|
||||
|
||||
public void setOnlineUserId(String onlineUserId) {
|
||||
this.onlineUserId = onlineUserId;
|
||||
}
|
||||
|
||||
public String getAgentServiceId() {
|
||||
return agentServiceId;
|
||||
}
|
||||
|
||||
public void setAgentServiceId(String agentServiceId) {
|
||||
this.agentServiceId = agentServiceId;
|
||||
}
|
||||
|
||||
public AgentUser getAgentUser() {
|
||||
return agentUser;
|
||||
}
|
||||
|
||||
public void setAgentUser(AgentUser agentUser) {
|
||||
this.agentUser = agentUser;
|
||||
}
|
||||
|
||||
public OnlineUser getOnlineUser() {
|
||||
return onlineUser;
|
||||
}
|
||||
|
||||
public void setOnlineUser(OnlineUser onlineUser) {
|
||||
this.onlineUser = onlineUser;
|
||||
}
|
||||
|
||||
public AgentService getAgentService() {
|
||||
return agentService;
|
||||
}
|
||||
|
||||
public void setAgentService(AgentService agentService) {
|
||||
this.agentService = agentService;
|
||||
}
|
||||
|
||||
public String getSessionid() {
|
||||
return sessionid;
|
||||
}
|
||||
|
||||
public void setSessionid(String sessionid) {
|
||||
this.sessionid = sessionid;
|
||||
}
|
||||
|
||||
public String getOnlineUserNickname() {
|
||||
return onlineUserNickname;
|
||||
}
|
||||
|
||||
public void setOnlineUserNickname(String onlineUserNickname) {
|
||||
this.onlineUserNickname = onlineUserNickname;
|
||||
}
|
||||
|
||||
public IP getIpdata() {
|
||||
return ipdata;
|
||||
}
|
||||
|
||||
public void setIpdata(IP ipdata) {
|
||||
this.ipdata = ipdata;
|
||||
}
|
||||
|
||||
public String getInitiator() {
|
||||
return initiator;
|
||||
}
|
||||
|
||||
public void setInitiator(String initiator) {
|
||||
this.initiator = initiator;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getBrowser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
public void setBrowser(String browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
public String getOsname() {
|
||||
return osname;
|
||||
}
|
||||
|
||||
public void setOsname(String osname) {
|
||||
this.osname = osname;
|
||||
}
|
||||
|
||||
public String getTraceid() {
|
||||
return traceid;
|
||||
}
|
||||
|
||||
public void setTraceid(String traceid) {
|
||||
this.traceid = traceid;
|
||||
}
|
||||
|
||||
public String getOwnerid() {
|
||||
return ownerid;
|
||||
}
|
||||
|
||||
public void setOwnerid(String ownerid) {
|
||||
this.ownerid = ownerid;
|
||||
}
|
||||
|
||||
public String getOrgi() {
|
||||
return orgi;
|
||||
}
|
||||
|
||||
public void setOrgi(String orgi) {
|
||||
this.orgi = orgi;
|
||||
}
|
||||
|
||||
public String getOnlineUserHeadimgUrl() {
|
||||
return onlineUserHeadimgUrl;
|
||||
}
|
||||
|
||||
public void setOnlineUserHeadimgUrl(String onlineUserHeadimgUrl) {
|
||||
this.onlineUserHeadimgUrl = onlineUserHeadimgUrl;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public AgentReport getAgentReport() {
|
||||
return agentReport;
|
||||
}
|
||||
|
||||
public void setAgentReport(AgentReport agentReport) {
|
||||
this.agentReport = agentReport;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.model.AgentService;
|
||||
import com.chatopera.cc.model.SessionConfig;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ACDMessageHelper {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDMessageHelper.class);
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
/**
|
||||
* 通知消息内容:分配到坐席
|
||||
*
|
||||
* @param agentService
|
||||
* @param channel
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
public String getSuccessMessage(AgentService agentService, String channel, String orgi) {
|
||||
String queneTip = "<span id='agentno'>" + agentService.getAgentusername() + "</span>";
|
||||
if (!MainContext.ChannelType.WEBIM.toString().equals(channel)) {
|
||||
queneTip = agentService.getAgentusername();
|
||||
}
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
String successMsg = "坐席分配成功," + queneTip + "为您服务。";
|
||||
if (StringUtils.isNotBlank(sessionConfig.getSuccessmsg())) {
|
||||
successMsg = sessionConfig.getSuccessmsg().replaceAll("\\{agent\\}", queneTip);
|
||||
}
|
||||
return successMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知消息内容:和坐席断开
|
||||
*
|
||||
* @param channel
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
public String getServiceFinishMessage(String channel, String orgi) {
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
String queneTip = "坐席已断开和您的对话";
|
||||
if (StringUtils.isNotBlank(sessionConfig.getFinessmsg())) {
|
||||
queneTip = sessionConfig.getFinessmsg();
|
||||
}
|
||||
return queneTip;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通知消息内容:和坐席断开,刷新页面
|
||||
*
|
||||
* @param channel
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
public String getServiceOffMessage(String channel, String orgi) {
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
String queneTip = "坐席已断开和您的对话,刷新页面为您分配新的坐席";
|
||||
if (StringUtils.isNotBlank(sessionConfig.getFinessmsg())) {
|
||||
queneTip = sessionConfig.getFinessmsg();
|
||||
}
|
||||
return queneTip;
|
||||
}
|
||||
|
||||
public String getNoAgentMessage(int queneIndex, String channel, String orgi) {
|
||||
if (queneIndex < 0) {
|
||||
queneIndex = 0;
|
||||
}
|
||||
String queneTip = "<span id='queneindex'>" + queneIndex + "</span>";
|
||||
if (!MainContext.ChannelType.WEBIM.toString().equals(channel)) {
|
||||
queneTip = String.valueOf(queneIndex);
|
||||
}
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
String noAgentTipMsg = "坐席全忙,已进入等待队列,您也可以在其他时间再来咨询。";
|
||||
if (StringUtils.isNotBlank(sessionConfig.getNoagentmsg())) {
|
||||
noAgentTipMsg = sessionConfig.getNoagentmsg().replaceAll("\\{num\\}", queneTip);
|
||||
}
|
||||
return noAgentTipMsg;
|
||||
}
|
||||
|
||||
public String getQueneMessage(int queneIndex, String channel, String orgi) {
|
||||
|
||||
String queneTip = "<span id='queneindex'>" + queneIndex + "</span>";
|
||||
if (!MainContext.ChannelType.WEBIM.toString().equals(channel)) {
|
||||
queneTip = String.valueOf(queneIndex);
|
||||
}
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
String agentBusyTipMsg = "正在排队,请稍候,在您之前,还有 " + queneTip + " 位等待用户。";
|
||||
if (StringUtils.isNotBlank(sessionConfig.getAgentbusymsg())) {
|
||||
agentBusyTipMsg = sessionConfig.getAgentbusymsg().replaceAll("\\{num\\}", queneTip);
|
||||
}
|
||||
return agentBusyTipMsg;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.SessionConfig;
|
||||
import com.chatopera.cc.persistence.repository.SessionConfigRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 坐席自动分配策略集
|
||||
*/
|
||||
@Component
|
||||
public class ACDPolicyService {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDPolicyService.class);
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
@Autowired
|
||||
private SessionConfigRepository sessionConfigRes;
|
||||
|
||||
/**
|
||||
* 载入坐席 ACD策略配置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<SessionConfig> initSessionConfigList() {
|
||||
List<SessionConfig> sessionConfigList;
|
||||
if ((sessionConfigList = cache.findOneSessionConfigListByOrgi(MainContext.SYSTEM_ORGI)) == null) {
|
||||
sessionConfigList = sessionConfigRes.findAll();
|
||||
if (sessionConfigList != null && sessionConfigList.size() > 0) {
|
||||
cache.putSessionConfigListByOrgi(sessionConfigList, MainContext.SYSTEM_ORGI);
|
||||
}
|
||||
}
|
||||
return sessionConfigList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 载入坐席 ACD策略配置
|
||||
*
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
public SessionConfig initSessionConfig(final String orgi) {
|
||||
SessionConfig sessionConfig;
|
||||
if ((sessionConfig = cache.findOneSessionConfigByOrgi(orgi)) == null) {
|
||||
sessionConfig = sessionConfigRes.findByOrgi(orgi);
|
||||
if (sessionConfig == null) {
|
||||
sessionConfig = new SessionConfig();
|
||||
} else {
|
||||
cache.putSessionConfigByOrgi(sessionConfig, orgi);
|
||||
}
|
||||
}
|
||||
return sessionConfig;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.AgentUser;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class ACDQueueService {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDQueueService.class);
|
||||
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public int getQueueIndex(String agent, String orgi, String skill) {
|
||||
int queneUsers = 0;
|
||||
Map<String, AgentUser> map = cache.getAgentUsersInQueByOrgi(orgi);
|
||||
|
||||
for (Map.Entry<String, AgentUser> entry : map.entrySet()) {
|
||||
if (StringUtils.isNotBlank(skill)) {
|
||||
if (StringUtils.equals(entry.getValue().getSkill(), skill)) {
|
||||
queneUsers++;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
if (StringUtils.isNotBlank(agent)) {
|
||||
if (StringUtils.equals(entry.getValue().getAgentno(), agent)) {
|
||||
queneUsers++;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
queneUsers++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return queneUsers;
|
||||
}
|
||||
}
|
@ -0,0 +1,665 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.acd.agent.ACDAgentMw1;
|
||||
import com.chatopera.cc.acd.visitor.*;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.cache.RedisCommand;
|
||||
import com.chatopera.cc.cache.RedisKey;
|
||||
import com.chatopera.cc.exception.CSKefuException;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.peer.PeerSyncIM;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.proxy.AgentUserProxy;
|
||||
import com.chatopera.cc.socketio.client.NettyClients;
|
||||
import com.chatopera.cc.socketio.message.Message;
|
||||
import com.chatopera.cc.util.IP;
|
||||
import com.chatopera.cc.util.SerializeUtil;
|
||||
import com.chatopera.compose4j.Composer;
|
||||
import com.chatopera.compose4j.exception.Compose4jRuntimeException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Automatic Call Distribution Main Entry
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Component
|
||||
public class ACDServiceRouter {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDServiceRouter.class);
|
||||
|
||||
// Redis缓存: 缓存的底层实现接口
|
||||
@Autowired
|
||||
private RedisCommand redisCommand;
|
||||
|
||||
// 缓存管理:高级缓存实现接口
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
// 在线访客与坐席关联表
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRes;
|
||||
|
||||
// 在线访客
|
||||
@Autowired
|
||||
private OnlineUserRepository onlineUserRes;
|
||||
|
||||
// 坐席服务记录
|
||||
@Autowired
|
||||
private AgentServiceRepository agentServiceRes;
|
||||
|
||||
|
||||
@Autowired
|
||||
private AgentUserProxy agentUserProxy;
|
||||
|
||||
// 坐席服务任务
|
||||
@Autowired
|
||||
private AgentUserTaskRepository agentUserTaskRes;
|
||||
|
||||
// 机器人坐席
|
||||
@Autowired
|
||||
private ACDChatbotService acdChatbotService;
|
||||
|
||||
// 坐席状态
|
||||
@Autowired
|
||||
private AgentStatusRepository agentStatusRes;
|
||||
|
||||
// 消息工厂
|
||||
@Autowired
|
||||
private ACDMessageHelper acdMessageHelper;
|
||||
|
||||
// 坐席服务
|
||||
@Autowired
|
||||
private ACDAgentService acdAgentService;
|
||||
|
||||
// 消息分发
|
||||
@Autowired
|
||||
private PeerSyncIM peerSyncIM;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private ACDQueueService acdQueueService;
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
/**
|
||||
* 为坐席安排访客
|
||||
*/
|
||||
private Composer<ACDComposeContext> agentPipeline;
|
||||
|
||||
@Autowired
|
||||
private ACDAgentMw1 acdAgentMw1;
|
||||
|
||||
/**
|
||||
* 为访客安排坐席
|
||||
*/
|
||||
private Composer<ACDComposeContext> visitorPipeline;
|
||||
|
||||
@Autowired
|
||||
private ACDVisBodyParserMw acdVisBodyParserMw;
|
||||
|
||||
@Autowired
|
||||
private ACDVisBindingMw acdVisBindingMw;
|
||||
|
||||
@Autowired
|
||||
private ACDVisSessionCfgMw acdVisSessionCfgMw;
|
||||
|
||||
@Autowired
|
||||
private ACDVisServiceMw acdVisServiceMw;
|
||||
|
||||
@Autowired
|
||||
private ACDVisAllocatorMw acdVisAllocatorMw;
|
||||
|
||||
@PostConstruct
|
||||
private void setup() {
|
||||
logger.info("[setup] setup ACD Algorithm Service ...");
|
||||
|
||||
setUpAgentPipeline();
|
||||
setUpVisitorPipeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立坐席处理管道
|
||||
*/
|
||||
private void setUpAgentPipeline() {
|
||||
agentPipeline = new Composer<>();
|
||||
agentPipeline.use(acdAgentMw1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 建立访客处理管道
|
||||
*/
|
||||
private void setUpVisitorPipeline() {
|
||||
visitorPipeline = new Composer<>();
|
||||
|
||||
/**
|
||||
* 1) 设置基本信息
|
||||
*/
|
||||
visitorPipeline.use(acdVisBodyParserMw);
|
||||
|
||||
/**
|
||||
* 1) 绑定技能组或坐席(包括邀请时的坐席)
|
||||
*/
|
||||
visitorPipeline.use(acdVisBindingMw);
|
||||
|
||||
/**
|
||||
* 1) 坐席配置:工作时间段,有无就绪在线坐席
|
||||
*
|
||||
*/
|
||||
visitorPipeline.use(acdVisSessionCfgMw);
|
||||
|
||||
/**
|
||||
* 1)选择坐席,确定AgentService
|
||||
*/
|
||||
visitorPipeline.use(acdVisServiceMw);
|
||||
|
||||
/**
|
||||
* 1)根据策略筛选坐席
|
||||
*/
|
||||
visitorPipeline.use(acdVisAllocatorMw);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为坐席批量分配用户
|
||||
*
|
||||
* @param agentno
|
||||
* @param orgi
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void allotVisitors(String agentno, String orgi) {
|
||||
// 获得目标坐席的状态
|
||||
AgentStatus agentStatus = SerializeUtil.deserialize(
|
||||
redisCommand.getHashKV(RedisKey.getAgentStatusReadyHashKey(orgi), agentno));
|
||||
|
||||
if (agentStatus == null) {
|
||||
logger.warn("[allotAgent] can not find AgentStatus for agentno {}", agentno);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获得所有待服务访客的列表
|
||||
Map<String, AgentUser> pendingAgentUsers = cache.getAgentUsersInQueByOrgi(orgi);
|
||||
|
||||
for (Map.Entry<String, AgentUser> entry : pendingAgentUsers.entrySet()) {
|
||||
AgentUser agentUser = entry.getValue();
|
||||
boolean process = false;
|
||||
|
||||
if ((StringUtils.equals(agentUser.getAgentno(), agentno))) {
|
||||
// 待服务的访客指定了该坐席
|
||||
process = true;
|
||||
} else {
|
||||
if (agentStatus != null &&
|
||||
agentStatus.getSkills() != null &&
|
||||
agentStatus.getSkills().size() > 0) {
|
||||
// 目标坐席有状态,并且坐席属于某技能组
|
||||
if ((StringUtils.isBlank(agentUser.getAgentno()) &&
|
||||
StringUtils.isBlank(agentUser.getSkill()))) {
|
||||
// 待服务的访客还没有指定坐席,并且也没有绑定技能组
|
||||
process = true;
|
||||
} else {
|
||||
if (StringUtils.isBlank(agentUser.getAgentno()) &&
|
||||
agentStatus.getSkills().containsKey(agentUser.getSkill())) {
|
||||
// 待服务的访客还没有指定坐席,并且指定的技能组和该坐席的技能组一致
|
||||
process = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 目标坐席没有状态,或该目标坐席有状态但是没有属于任何一个技能组
|
||||
if (StringUtils.isBlank(agentUser.getAgentno()) &&
|
||||
StringUtils.isBlank(agentUser.getSkill())) {
|
||||
// 待服务访客没有指定坐席,并且没有指定技能组
|
||||
process = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!process) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
long maxusers = sessionConfig == null ? Constants.AGENT_STATUS_MAX_USER : sessionConfig.getMaxuser();
|
||||
if (agentStatus.getUsers() < maxusers) { //坐席未达到最大咨询访客数量
|
||||
// 从排队队列移除
|
||||
cache.deleteAgentUserInqueByAgentUserIdAndOrgi(agentUser.getUserid(), orgi);
|
||||
|
||||
// 下面开始处理其加入到服务中的队列
|
||||
try {
|
||||
AgentService agentService = acdVisAllocatorMw.processAgentService(
|
||||
agentStatus, agentUser, orgi, false, sessionConfig);
|
||||
|
||||
// 处理完成得到 agentService
|
||||
Message outMessage = new Message();
|
||||
outMessage.setMessage(acdMessageHelper.getSuccessMessage(
|
||||
agentService,
|
||||
agentUser.getChannel(),
|
||||
orgi));
|
||||
outMessage.setMessageType(MainContext.MediaType.TEXT.toString());
|
||||
outMessage.setCalltype(MainContext.CallType.IN.toString());
|
||||
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
|
||||
|
||||
if (StringUtils.isNotBlank(agentUser.getUserid())) {
|
||||
outMessage.setAgentUser(agentUser);
|
||||
outMessage.setChannelMessage(agentUser);
|
||||
|
||||
// 向访客推送消息
|
||||
peerSyncIM.send(
|
||||
MainContext.ReceiverType.VISITOR,
|
||||
MainContext.ChannelType.toValue(agentUser.getChannel()), agentUser.getAppid(),
|
||||
MainContext.MessageType.STATUS, agentUser.getUserid(), outMessage, true
|
||||
);
|
||||
|
||||
// 向坐席推送消息
|
||||
peerSyncIM.send(MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
|
||||
agentUser.getAppid(),
|
||||
MainContext.MessageType.NEW, agentUser.getAgentno(), outMessage, true);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.warn("[allotAgent] fail to process service", ex);
|
||||
}
|
||||
} else {
|
||||
logger.info("[allotAgent] agentno {} reach the max users limit", agentno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
agentUserProxy.broadcastAgentsStatus(orgi, "agent", "success", agentno);
|
||||
}
|
||||
|
||||
/**
|
||||
* 访客服务结束
|
||||
*
|
||||
* @param agentUser
|
||||
* @param orgi
|
||||
* @throws Exception
|
||||
*/
|
||||
public void serviceFinish(final AgentUser agentUser, final String orgi) {
|
||||
if (agentUser != null) {
|
||||
// 获得坐席状态
|
||||
AgentStatus agentStatus = null;
|
||||
if (StringUtils.equals(MainContext.AgentUserStatusEnum.INSERVICE.toString(), agentUser.getStatus()) &&
|
||||
agentUser.getAgentno() != null) {
|
||||
agentStatus = cache.findOneAgentStatusByAgentnoAndOrig(agentUser.getAgentno(), orgi);
|
||||
}
|
||||
|
||||
// 设置新AgentUser的状态
|
||||
agentUser.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
if (agentUser.getServicetime() != null) {
|
||||
agentUser.setSessiontimes(System.currentTimeMillis() - agentUser.getServicetime().getTime());
|
||||
}
|
||||
|
||||
// 从缓存中删除agentUser缓存
|
||||
agentUserRes.save(agentUser);
|
||||
|
||||
final SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
|
||||
// 坐席服务
|
||||
AgentService service = null;
|
||||
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
|
||||
service = agentServiceRes.findByIdAndOrgi(agentUser.getAgentserviceid(), agentUser.getOrgi());
|
||||
} else if (agentStatus != null) {
|
||||
// 该访客没有和坐席对话,因此没有 AgentService
|
||||
// 当做留言处理,创建一个新的 AgentService
|
||||
service = acdVisAllocatorMw.processAgentService(agentStatus, agentUser, orgi, true, sessionConfig);
|
||||
}
|
||||
|
||||
if (service != null) {
|
||||
service.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
service.setEndtime(new Date());
|
||||
if (service.getServicetime() != null) {
|
||||
service.setSessiontimes(System.currentTimeMillis() - service.getServicetime().getTime());
|
||||
}
|
||||
|
||||
final List<AgentUserTask> agentUserTaskList = agentUserTaskRes.findByIdAndOrgi(
|
||||
agentUser.getId(), agentUser.getOrgi());
|
||||
if (agentUserTaskList.size() > 0) {
|
||||
final AgentUserTask agentUserTask = agentUserTaskList.get(0);
|
||||
service.setAgentreplyinterval(agentUserTask.getAgentreplyinterval());
|
||||
service.setAgentreplytime(agentUserTask.getAgentreplytime());
|
||||
service.setAvgreplyinterval(agentUserTask.getAvgreplyinterval());
|
||||
service.setAvgreplytime(agentUserTask.getAvgreplytime());
|
||||
|
||||
service.setUserasks(agentUserTask.getUserasks());
|
||||
service.setAgentreplys(agentUserTask.getAgentreplys());
|
||||
|
||||
// 开启了质检,并且是有效对话
|
||||
if (sessionConfig.isQuality()) {
|
||||
// 未分配质检任务
|
||||
service.setQualitystatus(MainContext.QualityStatusEnum.NODIS.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用了质检任务,开启质检
|
||||
*/
|
||||
if ((!sessionConfig.isQuality()) || service.getUserasks() == 0) {
|
||||
// 未开启质检 或无效对话无需质检
|
||||
service.setQualitystatus(MainContext.QualityStatusEnum.NO.toString());
|
||||
}
|
||||
agentServiceRes.save(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送到访客端的通知
|
||||
*/
|
||||
switch (MainContext.ChannelType.toValue(agentUser.getChannel())) {
|
||||
case WEBIM:
|
||||
// WebIM 发送对话结束事件
|
||||
// 向访客发送消息
|
||||
Message outMessage = new Message();
|
||||
outMessage.setAgentStatus(agentStatus);
|
||||
outMessage.setMessage(acdMessageHelper.getServiceFinishMessage(agentUser.getChannel(), orgi));
|
||||
outMessage.setMessageType(MainContext.AgentUserStatusEnum.END.toString());
|
||||
outMessage.setCalltype(MainContext.CallType.IN.toString());
|
||||
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
|
||||
outMessage.setAgentUser(agentUser);
|
||||
|
||||
// 向访客发送消息
|
||||
peerSyncIM.send(
|
||||
MainContext.ReceiverType.VISITOR,
|
||||
MainContext.ChannelType.toValue(agentUser.getChannel()), agentUser.getAppid(),
|
||||
MainContext.MessageType.STATUS, agentUser.getUserid(), outMessage, true
|
||||
);
|
||||
|
||||
if (agentStatus != null) {
|
||||
// 坐席在线,通知结束会话
|
||||
outMessage.setChannelMessage(agentUser);
|
||||
outMessage.setAgentUser(agentUser);
|
||||
peerSyncIM.send(MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
|
||||
agentUser.getAppid(),
|
||||
MainContext.MessageType.END, agentUser.getAgentno(), outMessage, true);
|
||||
}
|
||||
break;
|
||||
case PHONE:
|
||||
// 语音渠道,强制发送
|
||||
logger.info("[serviceFinish] send notify to callout channel agentno {}", agentUser.getAgentno());
|
||||
NettyClients.getInstance().sendCalloutEventMessage(
|
||||
agentUser.getAgentno(), MainContext.MessageType.END.toString(), agentUser);
|
||||
break;
|
||||
default:
|
||||
logger.info(
|
||||
"[serviceFinish] ignore notify agent service end for channel {}, agent user id {}",
|
||||
agentUser.getChannel(), agentUser.getId());
|
||||
}
|
||||
|
||||
// 更新访客的状态为可以接收邀请
|
||||
final OnlineUser onlineUser = onlineUserRes.findOneByUseridAndOrgi(
|
||||
agentUser.getUserid(), agentUser.getOrgi());
|
||||
if (onlineUser != null) {
|
||||
onlineUser.setInvitestatus(MainContext.OnlineUserInviteStatus.DEFAULT.toString());
|
||||
onlineUserRes.save(onlineUser);
|
||||
logger.info(
|
||||
"[online] onlineUser id {}, status {}, invite status {}", onlineUser.getId(),
|
||||
onlineUser.getStatus(), onlineUser.getInvitestatus());
|
||||
}
|
||||
|
||||
// 当前访客服务已经结束,为坐席寻找新访客
|
||||
if (agentStatus != null) {
|
||||
long maxusers = sessionConfig != null ? sessionConfig.getMaxuser() : Constants.AGENT_STATUS_MAX_USER;
|
||||
if ((agentStatus.getUsers() - 1) < maxusers) {
|
||||
allotVisitors(agentStatus.getAgentno(), orgi);
|
||||
}
|
||||
}
|
||||
agentUserProxy.broadcastAgentsStatus(orgi, "end", "success", agentUser != null ? agentUser.getId() : null);
|
||||
} else {
|
||||
logger.info("[serviceFinish] orgi {}, invalid agent user, should not be null", orgi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 撤退一个坐席
|
||||
* 1)将该坐席状态置为"非就绪"
|
||||
* 2) 将该坐席的访客重新分配给其它坐席
|
||||
*
|
||||
* @param orgi
|
||||
* @param agentno
|
||||
* @return 有没有成功将所有其服务的访客都分配出去
|
||||
*/
|
||||
public boolean withdrawAgent(final String orgi, final String agentno) {
|
||||
// 先将该客服切换到非就绪状态
|
||||
final AgentStatus agentStatus = cache.findOneAgentStatusByAgentnoAndOrig(agentno, orgi);
|
||||
if (agentStatus != null) {
|
||||
agentStatus.setBusy(false);
|
||||
agentStatus.setUpdatetime(new Date());
|
||||
agentStatus.setStatus(MainContext.AgentStatusEnum.NOTREADY.toString());
|
||||
agentStatusRes.save(agentStatus);
|
||||
cache.putAgentStatusByOrgi(agentStatus, orgi);
|
||||
}
|
||||
|
||||
// 然后将该坐席的访客分配给其它坐席
|
||||
// 获得该租户在线的客服的多少
|
||||
// TODO 对于agentUser的技能组过滤,在下面再逐个考虑?
|
||||
// 该信息同样也包括当前用户
|
||||
List<AgentUser> agentUsers = cache.findInservAgentUsersByAgentnoAndOrgi(agentno, orgi);
|
||||
int sz = agentUsers.size();
|
||||
for (final AgentUser x : agentUsers) {
|
||||
try {
|
||||
// TODO 此处没有考虑遍历过程中,系统中坐席的服务访客的信息实际上是变化的
|
||||
// 可能会发生maxusers超过设置的情况,如果做很多检查,会带来一定一系统开销
|
||||
// 因为影响不大,放弃实时的检查
|
||||
acdAgentService.allotAgent(x, x.getOrgi());
|
||||
// 因为重新分配该访客,将其从撤离的坐席中服务集合中删除
|
||||
// 此处类似于 Transfer
|
||||
redisCommand.removeSetVal(
|
||||
RedisKey.getInServAgentUsersByAgentnoAndOrgi(agentno, orgi), x.getUserid());
|
||||
sz--;
|
||||
} catch (Exception e) {
|
||||
logger.warn("[withdrawAgent] throw error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (sz == 0) {
|
||||
logger.info("[withdrawAgent] after re-allotAgent, the agentUsers size is {} for agentno {}", sz, agentno);
|
||||
} else {
|
||||
logger.warn("[withdrawAgent] after re-allotAgent, the agentUsers size is {} for agentno {}", sz, agentno);
|
||||
}
|
||||
|
||||
return sz == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请访客进入当前对话,如果当前操作的 坐席是已就绪状态,则直接加入到当前坐席的
|
||||
* 对话列表中,如果未登录,则分配给其他坐席
|
||||
*
|
||||
* @param agentno
|
||||
* @param agentUser
|
||||
* @param orgi
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public AgentService allotAgentForInvite(
|
||||
final String agentno,
|
||||
final AgentUser agentUser,
|
||||
final String orgi
|
||||
) throws Exception {
|
||||
AgentStatus agentStatus = cache.findOneAgentStatusByAgentnoAndOrig(agentno, orgi);
|
||||
AgentService agentService;
|
||||
if (agentStatus != null) {
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
agentService = acdVisAllocatorMw.processAgentService(agentStatus, agentUser, orgi, false, sessionConfig);
|
||||
agentUserProxy.broadcastAgentsStatus(orgi, "invite", "success", agentno);
|
||||
|
||||
/**
|
||||
* 通知坐席新的访客邀请成功
|
||||
*/
|
||||
Message outMessage = new Message();
|
||||
outMessage.setAgentUser(agentUser);
|
||||
outMessage.setChannelMessage(agentUser);
|
||||
peerSyncIM.send(MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
|
||||
agentUser.getAppid(),
|
||||
MainContext.MessageType.NEW, agentUser.getAgentno(), outMessage, true);
|
||||
} else {
|
||||
agentService = acdAgentService.allotAgent(agentUser, orgi);
|
||||
}
|
||||
return agentService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除AgentUser
|
||||
* 包括数据库记录及缓存信息
|
||||
*
|
||||
* @param agentUser
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
public void deleteAgentUser(final AgentUser agentUser, final String orgi) throws CSKefuException {
|
||||
logger.info("[deleteAgentUser] userId {}, orgi {}", agentUser.getUserid(), orgi);
|
||||
|
||||
if (agentUser == null || agentUser.getId() == null) {
|
||||
throw new CSKefuException("Invalid agentUser info");
|
||||
}
|
||||
|
||||
if (!StringUtils.equals(MainContext.AgentUserStatusEnum.END.toString(), agentUser.getStatus())) {
|
||||
/**
|
||||
* 未结束聊天,先结束对话,然后删除记录
|
||||
*/
|
||||
// 删除缓存
|
||||
serviceFinish(agentUser, orgi);
|
||||
}
|
||||
|
||||
// 删除数据库里的AgentUser记录
|
||||
agentUserRes.delete(agentUser);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为新增加的访客会话分配坐席和开启访客与坐席的对话
|
||||
*
|
||||
* @param onlineUserId
|
||||
* @param nickname
|
||||
* @param orgi
|
||||
* @param session
|
||||
* @param appid
|
||||
* @param ip
|
||||
* @param osname
|
||||
* @param browser
|
||||
* @param headimg
|
||||
* @param ipdata
|
||||
* @param channel
|
||||
* @param skill
|
||||
* @param agent
|
||||
* @param title
|
||||
* @param url
|
||||
* @param traceid
|
||||
* @param ownerid
|
||||
* @param initiator
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public Message allocateAgentService(
|
||||
final String onlineUserId,
|
||||
final String nickname,
|
||||
final String orgi,
|
||||
final String session,
|
||||
final String appid,
|
||||
final String ip,
|
||||
final String osname,
|
||||
final String browser,
|
||||
final String headimg,
|
||||
final IP ipdata,
|
||||
final String channel,
|
||||
final String skill,
|
||||
final String agent,
|
||||
final String title,
|
||||
final String url,
|
||||
final String traceid,
|
||||
final String ownerid,
|
||||
final boolean isInvite,
|
||||
final String initiator) {
|
||||
logger.info(
|
||||
"[allocateAgentService] user {}, appid {}, agent {}, skill {}, nickname {}, initiator {}, isInvite {}",
|
||||
onlineUserId,
|
||||
appid,
|
||||
agent,
|
||||
skill,
|
||||
nickname, initiator, isInvite);
|
||||
|
||||
// 坐席服务请求,分配 坐席
|
||||
Message result = new Message();
|
||||
|
||||
final ACDComposeContext ctx = new ACDComposeContext();
|
||||
ctx.setOnlineUserId(onlineUserId);
|
||||
ctx.setOnlineUserNickname(nickname);
|
||||
ctx.setOrganid(skill);
|
||||
ctx.setOrgi(orgi);
|
||||
ctx.setChannel(channel);
|
||||
ctx.setAgentno(agent);
|
||||
ctx.setBrowser(browser);
|
||||
ctx.setOsname(osname);
|
||||
ctx.setAppid(appid);
|
||||
ctx.setTitle(title);
|
||||
ctx.setSessionid(session);
|
||||
ctx.setUrl(url);
|
||||
ctx.setOnlineUserHeadimgUrl(headimg);
|
||||
ctx.setTraceid(traceid);
|
||||
ctx.setOwnerid(ownerid);
|
||||
ctx.setInitiator(initiator);
|
||||
ctx.setIpdata(ipdata);
|
||||
ctx.setIp(ip);
|
||||
ctx.setInvite(isInvite);
|
||||
|
||||
try {
|
||||
visitorPipeline.handle(ctx);
|
||||
result = (Message) ctx;
|
||||
} catch (Compose4jRuntimeException e) {
|
||||
logger.error("[allocateAgentService] error", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public ACDPolicyService getAcdPolicyService() {
|
||||
return acdPolicyService;
|
||||
}
|
||||
|
||||
public ACDMessageHelper getAcdMessageHelper() {
|
||||
return acdMessageHelper;
|
||||
}
|
||||
|
||||
public ACDAgentService getAcdAgentService() {
|
||||
return acdAgentService;
|
||||
}
|
||||
|
||||
public ACDChatbotService getAcdChatbotService() {
|
||||
return acdChatbotService;
|
||||
}
|
||||
|
||||
public ACDQueueService getAcdQueueService() {
|
||||
return acdQueueService;
|
||||
}
|
||||
|
||||
public ACDWorkMonitor getAcdWorkMonitor() {
|
||||
return acdWorkMonitor;
|
||||
}
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.AgentReport;
|
||||
import com.chatopera.cc.model.AgentStatus;
|
||||
import com.chatopera.cc.model.WorkMonitor;
|
||||
import com.chatopera.cc.persistence.repository.WorkMonitorRepository;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class ACDWorkMonitor {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDWorkMonitor.class);
|
||||
|
||||
@Autowired
|
||||
private WorkMonitorRepository workMonitorRes;
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
/**
|
||||
* 获得 当前服务状态
|
||||
*
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public AgentReport getAgentReport(String orgi) {
|
||||
return getAgentReport(null, orgi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得一个技能组的坐席状态
|
||||
*
|
||||
* @param organ
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
public AgentReport getAgentReport(String organ, String orgi) {
|
||||
/**
|
||||
* 统计当前在线的坐席数量
|
||||
*/
|
||||
AgentReport report = new AgentReport();
|
||||
|
||||
Map<String, AgentStatus> readys = cache.getAgentStatusReadyByOrig(orgi);
|
||||
int readyNum = 0;
|
||||
int busyNum = 0;
|
||||
|
||||
for (Map.Entry<String, AgentStatus> entry : readys.entrySet()) {
|
||||
if (organ == null) {
|
||||
readyNum++;
|
||||
if (entry.getValue().isBusy()) {
|
||||
busyNum++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.getValue().getSkills() != null &&
|
||||
entry.getValue().getSkills().containsKey(organ)) {
|
||||
readyNum++;
|
||||
if (entry.getValue().isBusy()) {
|
||||
busyNum++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
report.setAgents(readyNum);
|
||||
report.setBusy(busyNum);
|
||||
report.setOrgi(orgi);
|
||||
|
||||
/**
|
||||
* 统计当前服务中的用户数量
|
||||
*/
|
||||
// 服务中
|
||||
report.setUsers(cache.getInservAgentUsersSizeByOrgi(orgi));
|
||||
// 等待中
|
||||
report.setInquene(cache.getInqueAgentUsersSizeByOrgi(orgi));
|
||||
|
||||
// DEBUG
|
||||
// logger.info(
|
||||
// "[getAgentReport] orgi {}, organ {}, agents {}, busy {}, users {}, inqueue {}", orgi, organ,
|
||||
// report.getAgents(), report.getBusy(), report.getUsers(), report.getInquene()
|
||||
// );
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param agent 坐席
|
||||
* @param userid 用户ID
|
||||
* @param status 工作状态,也就是上一个状态
|
||||
* @param current 下一个工作状态
|
||||
* @param worktype 类型 : 语音OR 文本
|
||||
* @param orgi
|
||||
* @param lasttime
|
||||
*/
|
||||
public void recordAgentStatus(
|
||||
String agent,
|
||||
String username,
|
||||
String extno,
|
||||
boolean admin,
|
||||
String userid,
|
||||
String status,
|
||||
String current,
|
||||
String worktype,
|
||||
String orgi,
|
||||
Date lasttime
|
||||
) {
|
||||
WorkMonitor workMonitor = new WorkMonitor();
|
||||
if (StringUtils.isNotBlank(agent) && StringUtils.isNotBlank(status)) {
|
||||
workMonitor.setAgent(agent);
|
||||
workMonitor.setAgentno(agent);
|
||||
workMonitor.setStatus(status);
|
||||
workMonitor.setAdmin(admin);
|
||||
workMonitor.setUsername(username);
|
||||
workMonitor.setExtno(extno);
|
||||
workMonitor.setWorktype(worktype);
|
||||
if (lasttime != null) {
|
||||
workMonitor.setDuration((int) (System.currentTimeMillis() - lasttime.getTime()) / 1000);
|
||||
}
|
||||
if (status.equals(MainContext.AgentStatusEnum.BUSY.toString())) {
|
||||
workMonitor.setBusy(true);
|
||||
}
|
||||
if (status.equals(MainContext.AgentStatusEnum.READY.toString())) {
|
||||
int count = workMonitorRes.countByAgentAndDatestrAndStatusAndOrgi(
|
||||
agent, MainUtils.simpleDateFormat.format(new Date()),
|
||||
MainContext.AgentStatusEnum.READY.toString(), orgi
|
||||
);
|
||||
if (count == 0) {
|
||||
workMonitor.setFirsttime(true);
|
||||
}
|
||||
}
|
||||
if (current.equals(MainContext.AgentStatusEnum.NOTREADY.toString())) {
|
||||
List<WorkMonitor> workMonitorList = workMonitorRes.findByOrgiAndAgentAndDatestrAndFirsttime(
|
||||
orgi, agent, MainUtils.simpleDateFormat.format(new Date()), true);
|
||||
if (workMonitorList.size() > 0) {
|
||||
WorkMonitor firstWorkMonitor = workMonitorList.get(0);
|
||||
if (firstWorkMonitor.getFirsttimes() == 0) {
|
||||
firstWorkMonitor.setFirsttimes(
|
||||
(int) (System.currentTimeMillis() - firstWorkMonitor.getCreatetime().getTime()));
|
||||
workMonitorRes.save(firstWorkMonitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
workMonitor.setCreatetime(new Date());
|
||||
workMonitor.setDatestr(MainUtils.simpleDateFormat.format(new Date()));
|
||||
|
||||
workMonitor.setName(agent);
|
||||
workMonitor.setOrgi(orgi);
|
||||
workMonitor.setUserid(userid);
|
||||
|
||||
workMonitorRes.save(workMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd.agent;
|
||||
|
||||
import com.chatopera.cc.acd.ACDComposeContext;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 为坐席分配访客
|
||||
*/
|
||||
@Component
|
||||
public class ACDAgentMw1 implements Middleware<ACDComposeContext> {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDAgentMw1.class);
|
||||
|
||||
@Override
|
||||
public void apply(final ACDComposeContext ctx, final Functional next) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd.visitor;
|
||||
|
||||
import com.chatopera.cc.acd.ACDComposeContext;
|
||||
import com.chatopera.cc.acd.ACDQueueService;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.persistence.repository.AgentServiceRepository;
|
||||
import com.chatopera.cc.persistence.repository.AgentUserRepository;
|
||||
import com.chatopera.cc.persistence.repository.OnlineUserRepository;
|
||||
import com.chatopera.cc.persistence.repository.UserRepository;
|
||||
import com.chatopera.cc.proxy.AgentUserProxy;
|
||||
import com.chatopera.cc.util.WebIMReport;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDVisAllocatorMw.class);
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
@Autowired
|
||||
private ACDQueueService acdQueueService;
|
||||
|
||||
@Autowired
|
||||
private AgentServiceRepository agentServiceRes;
|
||||
|
||||
@Autowired
|
||||
private OnlineUserRepository onlineUserRes;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRes;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRes;
|
||||
|
||||
@Autowired
|
||||
private AgentUserProxy agentUserProxy;
|
||||
|
||||
@Override
|
||||
public void apply(final ACDComposeContext ctx, final Functional next) {
|
||||
|
||||
/**
|
||||
* 查询条件,当前在线的 坐席,并且 未达到最大 服务人数的坐席
|
||||
*/
|
||||
List<AgentStatus> agentStatuses = filterOutAvailableAgentStatus(
|
||||
ctx.getAgentUser(), ctx.getOrgi());
|
||||
/**
|
||||
* 处理ACD 的 技能组请求和 坐席请求
|
||||
*/
|
||||
AgentStatus agentStatus = null;
|
||||
AgentService agentService = null; //放入缓存的对象
|
||||
|
||||
if (agentStatuses.size() > 0) {
|
||||
agentStatus = agentStatuses.get(0);
|
||||
if (agentStatus.getUsers() >= ctx.getSessionConfig().getMaxuser()) {
|
||||
agentStatus = null;
|
||||
/**
|
||||
* 判断当前有多少人排队中 , 分三种情况:1、请求技能组的,2、请求坐席的,3,默认请求的
|
||||
*
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
agentService = processAgentService(
|
||||
agentStatus, ctx.getAgentUser(), ctx.getOrgi(), false, ctx.getSessionConfig());
|
||||
// 处理结果:进入排队队列
|
||||
if (StringUtils.equals(MainContext.AgentUserStatusEnum.INQUENE.toString(), agentService.getStatus())) {
|
||||
agentService.setQueneindex(
|
||||
acdQueueService.getQueueIndex(
|
||||
ctx.getAgentUser().getAgentno(), ctx.getOrgi(), ctx.getAgentUser().getSkill()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.warn("[allotAgent] exception: ", ex);
|
||||
}
|
||||
|
||||
agentUserProxy.broadcastAgentsStatus(
|
||||
ctx.getOrgi(), "user", agentService != null && agentService.getStatus().equals(
|
||||
MainContext.AgentUserStatusEnum.INSERVICE.toString()) ? "inservice" : "inquene",
|
||||
ctx.getAgentUser().getId());
|
||||
ctx.setAgentService(agentService);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤在线客服
|
||||
* 优先级: 1. 指定坐席;2. 指定技能组; 3. 租户所有的坐席
|
||||
*
|
||||
* @param agentUser
|
||||
* @param orgi
|
||||
* @return
|
||||
*/
|
||||
public List<AgentStatus> filterOutAvailableAgentStatus(
|
||||
final AgentUser agentUser,
|
||||
final String orgi
|
||||
) {
|
||||
logger.info(
|
||||
"[filterOutAvailableAgentStatus] agentUser {}, orgi {}, skill {}, onlineUser {}",
|
||||
agentUser.getAgentno(), orgi, agentUser.getSkill(), agentUser.getUserid()
|
||||
);
|
||||
List<AgentStatus> agentStatuses = new ArrayList<>();
|
||||
Map<String, AgentStatus> map = cache.findAllReadyAgentStatusByOrgi(orgi);
|
||||
|
||||
if (agentUser != null && StringUtils.isNotBlank(agentUser.getAgentno())) {
|
||||
// 指定坐席
|
||||
for (Map.Entry<String, AgentStatus> entry : map.entrySet()) {
|
||||
if ((!entry.getValue().isBusy()) && (StringUtils.equals(
|
||||
entry.getValue().getAgentno(), agentUser.getAgentno()))) {
|
||||
agentStatuses.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定坐席未查询到就绪的
|
||||
*/
|
||||
if (agentStatuses.size() == 0) {
|
||||
if (StringUtils.isNotBlank(agentUser.getSkill())) {
|
||||
// 指定技能组
|
||||
for (Map.Entry<String, AgentStatus> entry : map.entrySet()) {
|
||||
if ((!entry.getValue().isBusy()) &&
|
||||
(entry.getValue().getSkills() != null &&
|
||||
entry.getValue().getSkills().containsKey(agentUser.getSkill()))) {
|
||||
agentStatuses.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定的坐席和技能组中未查到坐席
|
||||
* 接下来进行无差别查询
|
||||
*/
|
||||
if (agentStatuses.size() == 0) {
|
||||
// 对于该租户的所有客服
|
||||
for (Map.Entry<String, AgentStatus> entry : map.entrySet()) {
|
||||
if (!entry.getValue().isBusy()) {
|
||||
agentStatuses.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("[filterOutAvailableAgentStatus] agent status list size: {}", agentStatuses.size());
|
||||
return agentStatuses;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为agentUser生成对应的AgentService
|
||||
* 使用场景:
|
||||
* 1. 在AgentUser服务结束并且还没有对应的AgentService
|
||||
* 2. 在新服务开始,安排坐席
|
||||
*
|
||||
* @param agentStatus 坐席状态
|
||||
* @param agentUser 坐席访客会话
|
||||
* @param orgi 租户ID
|
||||
* @param finished 结束服务
|
||||
* @param sessionConfig 坐席配置
|
||||
* @return
|
||||
*/
|
||||
public AgentService processAgentService(
|
||||
AgentStatus agentStatus,
|
||||
final AgentUser agentUser,
|
||||
final String orgi,
|
||||
final boolean finished,
|
||||
final SessionConfig sessionConfig) {
|
||||
AgentService agentService = new AgentService();
|
||||
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
|
||||
agentService.setId(agentUser.getAgentserviceid());
|
||||
}
|
||||
agentService.setOrgi(orgi);
|
||||
|
||||
final Date now = new Date();
|
||||
// 批量复制属性
|
||||
MainUtils.copyProperties(agentUser, agentService);
|
||||
agentService.setChannel(agentUser.getChannel());
|
||||
agentService.setSessionid(agentUser.getSessionid());
|
||||
|
||||
// 此处为何设置loginDate为现在
|
||||
agentUser.setLogindate(now);
|
||||
OnlineUser onlineUser = onlineUserRes.findOneByUseridAndOrgi(agentUser.getUserid(), orgi);
|
||||
|
||||
if (finished == true) {
|
||||
// 服务结束
|
||||
agentUser.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
agentService.setSessiontype(MainContext.AgentUserStatusEnum.END.toString());
|
||||
if (agentStatus == null) {
|
||||
// 没有满足条件的坐席,留言
|
||||
agentService.setLeavemsg(true);
|
||||
agentService.setLeavemsgstatus(MainContext.LeaveMsgStatus.NOTPROCESS.toString()); //未处理的留言
|
||||
}
|
||||
|
||||
if (onlineUser != null) {
|
||||
// 更新OnlineUser对象,变更为默认状态,可以接受邀请
|
||||
onlineUser.setInvitestatus(MainContext.OnlineUserInviteStatus.DEFAULT.toString());
|
||||
}
|
||||
} else if (agentStatus != null) {
|
||||
agentService.setAgent(agentStatus.getAgentno());
|
||||
agentService.setSkill(agentUser.getSkill());
|
||||
|
||||
if (sessionConfig.isLastagent()) {
|
||||
// 启用了历史坐席优先 , 查找 历史服务坐席
|
||||
List<com.chatopera.cc.util.WebIMReport> webIMaggList = MainUtils.getWebIMDataAgg(
|
||||
onlineUserRes.findByOrgiForDistinctAgent(orgi, agentUser.getUserid()));
|
||||
if (webIMaggList.size() > 0) {
|
||||
for (WebIMReport report : webIMaggList) {
|
||||
if (report.getData().equals(agentStatus.getAgentno())) {
|
||||
break;
|
||||
} else {
|
||||
AgentStatus hisAgentStatus = cache.findOneAgentStatusByAgentnoAndOrig(
|
||||
report.getData(), orgi);
|
||||
if (hisAgentStatus != null && hisAgentStatus.getUsers() < hisAgentStatus.getMaxusers()) {
|
||||
// 变更为 历史服务坐席
|
||||
agentStatus = hisAgentStatus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
agentUser.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
|
||||
agentService.setSessiontype(MainContext.AgentUserStatusEnum.INSERVICE.toString());
|
||||
|
||||
// 设置坐席名字
|
||||
agentService.setAgentno(agentStatus.getUserid());
|
||||
agentService.setAgentusername(agentStatus.getUsername());
|
||||
} else {
|
||||
// 不是服务结束,但是没有满足条件的坐席
|
||||
// 加入到排队中
|
||||
agentUser.setStatus(MainContext.AgentUserStatusEnum.INQUENE.toString());
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.INQUENE.toString());
|
||||
agentService.setSessiontype(MainContext.AgentUserStatusEnum.INQUENE.toString());
|
||||
}
|
||||
|
||||
if (finished || agentStatus != null) {
|
||||
agentService.setAgentuserid(agentUser.getId());
|
||||
agentService.setInitiator(MainContext.ChatInitiatorType.USER.toString());
|
||||
|
||||
long waittingtime = 0;
|
||||
if (agentUser.getWaittingtimestart() != null) {
|
||||
waittingtime = System.currentTimeMillis() - agentUser.getWaittingtimestart().getTime();
|
||||
} else {
|
||||
if (agentUser.getCreatetime() != null) {
|
||||
waittingtime = System.currentTimeMillis() - agentUser.getCreatetime().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
agentUser.setWaittingtime((int) waittingtime);
|
||||
agentUser.setServicetime(now);
|
||||
agentService.setOwner(agentUser.getOwner());
|
||||
agentService.setTimes(0);
|
||||
|
||||
final User agent = userRes.findOne(agentService.getAgentno());
|
||||
agentUser.setAgentname(agent.getUname());
|
||||
agentUser.setAgentno(agentService.getAgentno());
|
||||
|
||||
if (StringUtils.isNotBlank(agentUser.getName())) {
|
||||
agentService.setName(agentUser.getName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(agentUser.getPhone())) {
|
||||
agentService.setPhone(agentUser.getPhone());
|
||||
}
|
||||
if (StringUtils.isNotBlank(agentUser.getEmail())) {
|
||||
agentService.setEmail(agentUser.getEmail());
|
||||
}
|
||||
if (StringUtils.isNotBlank(agentUser.getResion())) {
|
||||
agentService.setResion(agentUser.getResion());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(agentUser.getSkill())) {
|
||||
agentService.setAgentskill(agentUser.getSkill());
|
||||
}
|
||||
|
||||
agentService.setServicetime(now);
|
||||
|
||||
if (agentUser.getCreatetime() != null) {
|
||||
agentService.setWaittingtime((int) (System.currentTimeMillis() - agentUser.getCreatetime().getTime()));
|
||||
agentUser.setWaittingtime(agentService.getWaittingtime());
|
||||
}
|
||||
if (onlineUser != null) {
|
||||
agentService.setOsname(onlineUser.getOpersystem());
|
||||
agentService.setBrowser(onlineUser.getBrowser());
|
||||
// 记录onlineUser的id
|
||||
agentService.setDataid(onlineUser.getId());
|
||||
}
|
||||
|
||||
agentService.setLogindate(agentUser.getCreatetime());
|
||||
agentServiceRes.save(agentService);
|
||||
|
||||
agentUser.setAgentserviceid(agentService.getId());
|
||||
agentUser.setLastgetmessage(now);
|
||||
agentUser.setLastmessage(now);
|
||||
}
|
||||
|
||||
agentService.setDataid(agentUser.getId());
|
||||
|
||||
/**
|
||||
* 分配成功以后, 将用户和坐席的对应关系放入到缓存
|
||||
* 将 AgentUser 放入到当前坐席的服务队列
|
||||
*/
|
||||
agentUserRes.save(agentUser);
|
||||
|
||||
/**
|
||||
* 更新OnlineUser对象,变更为服务中,不可邀请
|
||||
*/
|
||||
if (onlineUser != null && !finished) {
|
||||
onlineUser.setInvitestatus(MainContext.OnlineUserInviteStatus.INSERV.toString());
|
||||
onlineUserRes.save(onlineUser);
|
||||
}
|
||||
|
||||
// 更新坐席服务人数,坐席更新时间到缓存
|
||||
if (agentStatus != null) {
|
||||
agentUserProxy.updateAgentStatus(agentStatus, orgi);
|
||||
}
|
||||
return agentService;
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd.visitor;
|
||||
|
||||
import com.chatopera.cc.acd.ACDComposeContext;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.Organ;
|
||||
import com.chatopera.cc.model.User;
|
||||
import com.chatopera.cc.persistence.repository.OrganRepository;
|
||||
import com.chatopera.cc.persistence.repository.UserRepository;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ACDVisBindingMw implements Middleware<ACDComposeContext> {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDVisBindingMw.class);
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRes;
|
||||
|
||||
@Autowired
|
||||
private OrganRepository organRes;
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
/**
|
||||
* 绑定技能组或坐席
|
||||
*
|
||||
* @param ctx
|
||||
* @param next
|
||||
*/
|
||||
@Override
|
||||
public void apply(final ACDComposeContext ctx, final Functional next) {
|
||||
/**
|
||||
* 访客新上线的请求
|
||||
*/
|
||||
/**
|
||||
* 技能组 和 坐席
|
||||
*/
|
||||
if (StringUtils.isNotBlank(ctx.getOrganid())) {
|
||||
logger.info("[apply] bind skill {}", ctx.getOrganid());
|
||||
// 绑定技能组
|
||||
Organ organ = organRes.findOne(ctx.getOrganid());
|
||||
if (organ != null) {
|
||||
ctx.getAgentUser().setSkill(organ.getId());
|
||||
ctx.setOrgan(organ);
|
||||
}
|
||||
} else {
|
||||
// 如果没有绑定技能组,则清除之前的标记
|
||||
ctx.getAgentUser().setSkill(null);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(ctx.getAgentno())) {
|
||||
logger.info("[apply] bind agentno {}", ctx.getAgentno());
|
||||
// 绑定坐席
|
||||
// 绑定坐席有可能是因为前端展示了技能组和坐席
|
||||
// 也有可能是坐席发送了邀请,该访客接收邀请
|
||||
ctx.getAgentUser().setAgentno(ctx.getAgentno());
|
||||
User agent = userRes.findOne(ctx.getAgentno());
|
||||
ctx.setAgent(agent);
|
||||
ctx.getAgentUser().setAgentname(agent.getUname());
|
||||
} else {
|
||||
// 如果没有绑定坐席,则清除之前的标记
|
||||
ctx.getAgentUser().setAgentno(null);
|
||||
ctx.getAgentUser().setAgentname(null);
|
||||
ctx.setAgent(null);
|
||||
}
|
||||
|
||||
next.apply();
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd.visitor;
|
||||
|
||||
import com.chatopera.cc.acd.ACDComposeContext;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.AgentUser;
|
||||
import com.chatopera.cc.model.AgentUserContacts;
|
||||
import com.chatopera.cc.model.Contacts;
|
||||
import com.chatopera.cc.persistence.es.ContactsRepository;
|
||||
import com.chatopera.cc.persistence.repository.AgentUserContactsRepository;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Resolve AgentUser
|
||||
*/
|
||||
@Component
|
||||
public class ACDVisBodyParserMw implements Middleware<ACDComposeContext> {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDVisBodyParserMw.class);
|
||||
|
||||
@Autowired
|
||||
private AgentUserContactsRepository agentUserContactsRes;
|
||||
|
||||
@Autowired
|
||||
private ContactsRepository contactsRes;
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
/**
|
||||
* 设置AgentUser基本信息
|
||||
*
|
||||
* @param ctx
|
||||
* @param next
|
||||
*/
|
||||
@Override
|
||||
public void apply(final ACDComposeContext ctx, final Functional next) {
|
||||
|
||||
/**
|
||||
* NOTE AgentUser代表一次会话记录,在上一个会话结束,并且由坐席人员点击"清除"后,会从数据库中删除
|
||||
* 此处查询到的,可能是之前的会话。其状态需要验证,所以不一定是由TA来服务本次会话。
|
||||
*/
|
||||
AgentUser agentUser = cache.findOneAgentUserByUserIdAndOrgi(ctx.getOnlineUserId(), ctx.getOrgi()).orElseGet(
|
||||
() -> {
|
||||
/**
|
||||
* NOTE 新创建的AgentUser不需要设置Status和Agentno
|
||||
* 因为两个值在后面会检查,如果存在则不会申请新的Agent
|
||||
*/
|
||||
AgentUser p = new AgentUser(
|
||||
ctx.getOnlineUserId(),
|
||||
ctx.getChannel(),
|
||||
ctx.getOnlineUserId(),
|
||||
ctx.getOnlineUserNickname(),
|
||||
ctx.getOrgi(),
|
||||
ctx.getAppid());
|
||||
logger.info("[apply] create new agent user id {}", p.getId());
|
||||
return p;
|
||||
});
|
||||
|
||||
|
||||
logger.info("[apply] resolve agent user id {}", agentUser.getId());
|
||||
|
||||
agentUser.setOrgi(ctx.getOrgi());
|
||||
agentUser.setUsername(resolveAgentUsername(agentUser, ctx.getOnlineUserNickname()));
|
||||
agentUser.setOsname(ctx.getOsname());
|
||||
agentUser.setBrowser(ctx.getBrowser());
|
||||
agentUser.setAppid(ctx.getAppid());
|
||||
agentUser.setSessionid(ctx.getSessionid());
|
||||
|
||||
if (ctx.getIpdata() != null) {
|
||||
logger.info("[apply] set IP data for agentUser {}", agentUser.getId());
|
||||
agentUser.setCountry(ctx.getIpdata().getCountry());
|
||||
agentUser.setProvince(ctx.getIpdata().getProvince());
|
||||
agentUser.setCity(ctx.getIpdata().getCity());
|
||||
if (StringUtils.isNotBlank(ctx.getIp())) {
|
||||
agentUser.setRegion(ctx.getIpdata().toString() + "[" + ctx.getIp() + "]");
|
||||
} else {
|
||||
agentUser.setRegion(ctx.getIpdata().toString());
|
||||
}
|
||||
}
|
||||
|
||||
agentUser.setOwner(ctx.getOwnerid()); // 智能IVR的 EventID
|
||||
agentUser.setHeadimgurl(ctx.getOnlineUserHeadimgUrl());
|
||||
agentUser.setTitle(ctx.getTitle());
|
||||
agentUser.setUrl(ctx.getUrl());
|
||||
agentUser.setTraceid(ctx.getTraceid());
|
||||
|
||||
ctx.setAgentUser(agentUser);
|
||||
next.apply();
|
||||
|
||||
logger.info(
|
||||
"[apply] message text: {}, noagent {}", ctx.getMessage(), ctx.isNoagent());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 确定该访客的名字,优先级
|
||||
* 1. 如果AgentUser username 与 nickName 不一致,则用 agentUser username
|
||||
* 2. 如果AgentUser username 与 nickName 一致,则查找 AgentUserContact对应的联系人
|
||||
* 2.1 如果联系人存在,则用联系人的名字
|
||||
* 2.2 如果联系人不存在,则使用 nickName
|
||||
* <p>
|
||||
* TODO 此处有一些问题:如果联系人更新了名字,那么么后面TA的会话用的还是旧的名字,
|
||||
* 所以,在更新联系人名字的时候,也应更新其对应的AgentUser里面的名字
|
||||
*
|
||||
* @param agentUser
|
||||
* @param nickname
|
||||
* @return
|
||||
*/
|
||||
private String resolveAgentUsername(final AgentUser agentUser, final String nickname) {
|
||||
if (!StringUtils.equals(agentUser.getUsername(), nickname)) {
|
||||
return agentUser.getUsername();
|
||||
}
|
||||
|
||||
// 查找会话联系人关联表
|
||||
AgentUserContacts agentUserContact = agentUserContactsRes.findOneByUseridAndOrgi(
|
||||
agentUser.getUserid(), agentUser.getOrgi()).orElse(null);
|
||||
if (agentUserContact != null) {
|
||||
Contacts contact = contactsRes.findOneById(agentUserContact.getContactsid()).orElseGet(null);
|
||||
if (contact != null) {
|
||||
return contact.getName();
|
||||
}
|
||||
}
|
||||
|
||||
return nickname;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd.visitor;
|
||||
|
||||
import com.chatopera.cc.acd.ACDComposeContext;
|
||||
import com.chatopera.cc.acd.ACDMessageHelper;
|
||||
import com.chatopera.cc.acd.ACDQueueService;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.proxy.AgentUserProxy;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 寻找或为绑定服务访客的坐席,建立双方通话
|
||||
*/
|
||||
@Component
|
||||
public class ACDVisServiceMw implements Middleware<ACDComposeContext> {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDVisServiceMw.class);
|
||||
|
||||
@Autowired
|
||||
private ACDQueueService acdQueueService;
|
||||
|
||||
@Autowired
|
||||
private ACDMessageHelper acdMessageHelper;
|
||||
|
||||
@Autowired
|
||||
private AgentUserProxy agentUserProxy;
|
||||
|
||||
@Override
|
||||
public void apply(final ACDComposeContext ctx, final Functional next) {
|
||||
ctx.setMessageType(MainContext.MessageType.STATUS.toString());
|
||||
/**
|
||||
* 首先交由 IMR处理 MESSAGE指令 , 如果当前用户是在 坐席对话列表中, 则直接推送给坐席,如果不在,则执行 IMR
|
||||
*/
|
||||
if (StringUtils.isNotBlank(ctx.getAgentUser().getStatus())) {
|
||||
// 该AgentUser已经在数据库中
|
||||
switch (MainContext.AgentUserStatusEnum.toValue(ctx.getAgentUser().getStatus())) {
|
||||
case INQUENE:
|
||||
logger.info("[apply] agent user is in queue");
|
||||
int queueIndex = acdQueueService.getQueueIndex(
|
||||
ctx.getAgentUser().getAgentno(), ctx.getOrgi(),
|
||||
ctx.getOrganid());
|
||||
ctx.setMessage(
|
||||
acdMessageHelper.getQueneMessage(
|
||||
queueIndex,
|
||||
ctx.getChannel(),
|
||||
ctx.getOrgi()));
|
||||
break;
|
||||
case INSERVICE:
|
||||
// 该访客与坐席正在服务中,忽略新的连接
|
||||
logger.info(
|
||||
"[apply] agent user {} is in service, userid {}, agentno {}", ctx.getAgentUser().getId(),
|
||||
ctx.getAgentUser().getUserid(), ctx.getAgentUser().getAgentno());
|
||||
break;
|
||||
case END:
|
||||
logger.info("[apply] agent user is null or END");
|
||||
// 过滤坐席,获得 Agent Service
|
||||
next.apply();
|
||||
if (ctx.getAgentService() != null) {
|
||||
// 没有得到agent service
|
||||
postResolveAgentService(ctx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 该AgentUser为新建
|
||||
// 过滤坐席,获得 Agent Service
|
||||
next.apply();
|
||||
if (ctx.getAgentService() != null) {
|
||||
// 没有得到agent service
|
||||
postResolveAgentService(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据AgentService,按照逻辑继续执行
|
||||
*
|
||||
* @param ctx
|
||||
*/
|
||||
private void postResolveAgentService(final ACDComposeContext ctx) {
|
||||
/**
|
||||
* 找到空闲坐席,如果未找到坐席,则将该用户放入到 排队队列
|
||||
*/
|
||||
switch (MainContext.AgentUserStatusEnum.toValue(ctx.getAgentService().getStatus())) {
|
||||
case INSERVICE:
|
||||
ctx.setMessage(
|
||||
acdMessageHelper.getSuccessMessage(
|
||||
ctx.getAgentService(),
|
||||
ctx.getChannel(),
|
||||
ctx.getOrgi()));
|
||||
|
||||
// TODO 判断 INSERVICE 时,agentService 对应的 agentUser
|
||||
logger.info(
|
||||
"[apply] agent service: agentno {}, \n agentuser id {} \n user {} \n channel {} \n status {} \n queue index {}",
|
||||
ctx.getAgentService().getAgentno(), ctx.getAgentService().getAgentuserid(),
|
||||
ctx.getAgentService().getUserid(),
|
||||
ctx.getAgentService().getChannel(),
|
||||
ctx.getAgentService().getStatus(),
|
||||
ctx.getAgentService().getQueneindex());
|
||||
|
||||
if (StringUtils.isNotBlank(ctx.getAgentService().getAgentuserid())) {
|
||||
agentUserProxy.findOne(ctx.getAgentService().getAgentuserid()).ifPresent(p -> {
|
||||
ctx.setAgentUser(p);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO 如果是 INSERVICE 那么 agentService.getAgentuserid 就一定不能为空?
|
||||
// // TODO 此处需要考虑 agentService.getAgentuserid 为空的情况
|
||||
// // 那么什么情况下,agentService.getAgentuserid为空?
|
||||
// if (StringUtils.isNotBlank(agentService.getAgentuserid())) {
|
||||
// logger.info("[handle] set Agent User with agentUser Id {}", agentService.getAgentuserid());
|
||||
// getAgentUserProxy().findOne(agentService.getAgentuserid()).ifPresent(p -> {
|
||||
// outMessage.setChannelMessage(p);
|
||||
// });
|
||||
// } else {
|
||||
// logger.info("[handle] agent user id is null.");
|
||||
// }
|
||||
break;
|
||||
case INQUENE:
|
||||
if (ctx.getAgentService().getQueneindex() > 0) {
|
||||
// 当前有坐席,要排队
|
||||
ctx.setMessage(acdMessageHelper.getQueneMessage(
|
||||
ctx.getAgentService().getQueneindex(),
|
||||
ctx.getAgentUser().getChannel(),
|
||||
ctx.getOrgi()));
|
||||
} else {
|
||||
// TODO 什么是否返回 noAgentMessage, 是否在是 INQUENE 时 getQueneindex == 0
|
||||
// 当前没有坐席,要留言
|
||||
ctx.setMessage(acdMessageHelper.getNoAgentMessage(
|
||||
ctx.getAgentService().getQueneindex(),
|
||||
ctx.getChannel(),
|
||||
ctx.getOrgi()));
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
logger.info("[handler] should not happen for new onlineUser service request.");
|
||||
default:
|
||||
}
|
||||
ctx.setChannelMessage(ctx.getAgentUser());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.chatopera.cc.acd.visitor;
|
||||
|
||||
import com.chatopera.cc.acd.ACDComposeContext;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.model.AgentReport;
|
||||
import com.chatopera.cc.model.SessionConfig;
|
||||
import com.chatopera.compose4j.Functional;
|
||||
import com.chatopera.compose4j.Middleware;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class ACDVisSessionCfgMw implements Middleware<ACDComposeContext> {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDVisSessionCfgMw.class);
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
|
||||
@Override
|
||||
public void apply(final ACDComposeContext ctx, final Functional next) {
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(
|
||||
ctx.getOrgi());
|
||||
|
||||
ctx.setSessionConfig(sessionConfig);
|
||||
|
||||
// 查询就绪的坐席,如果指定技能组则按照技能组查询
|
||||
AgentReport report;
|
||||
if (StringUtils.isNotBlank(ctx.getOrganid())) {
|
||||
report = acdWorkMonitor.getAgentReport(ctx.getOrganid(), ctx.getOrgi());
|
||||
} else {
|
||||
report = acdWorkMonitor.getAgentReport(ctx.getOrgi());
|
||||
}
|
||||
|
||||
ctx.setAgentReport(report);
|
||||
|
||||
// 不在工作时间段
|
||||
if (sessionConfig.isHourcheck() && !MainUtils.isInWorkingHours(sessionConfig.getWorkinghours())) {
|
||||
logger.info("[apply] not in working hours");
|
||||
ctx.setMessage(sessionConfig.getNotinwhmsg());
|
||||
} else if (report.getAgents() == 0) {
|
||||
// 没有就绪的坐席
|
||||
logger.info("[apply] find no agents, redirect to leave a message.");
|
||||
ctx.setNoagent(true);
|
||||
} else {
|
||||
logger.info("[apply] find agents size {}, allocate agent in next.", report.getAgents());
|
||||
// 具备工作中的就绪坐席,进入筛选坐席
|
||||
next.apply();
|
||||
}
|
||||
}
|
||||
}
|
@ -10,12 +10,13 @@
|
||||
*/
|
||||
package com.chatopera.cc.activemq;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.AgentStatus;
|
||||
import com.chatopera.cc.persistence.repository.AgentStatusRepository;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.slf4j.Logger;
|
||||
@ -36,6 +37,12 @@ public class SocketioConnEventSubscription {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(SocketioConnEventSubscription.class);
|
||||
|
||||
@Autowired
|
||||
private ACDServiceRouter acdServiceRouter;
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Autowired
|
||||
private AgentStatusRepository agentStatusRes;
|
||||
|
||||
@ -58,14 +65,15 @@ public class SocketioConnEventSubscription {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject j = parser.parse(payload).getAsJsonObject();
|
||||
if (j.has("userId") && j.has("orgi") && j.has("isAdmin")) {
|
||||
final AgentStatus agentStatus = cache.findOneAgentStatusByAgentnoAndOrig(j.get("userId").getAsString(),
|
||||
final AgentStatus agentStatus = cache.findOneAgentStatusByAgentnoAndOrig(
|
||||
j.get("userId").getAsString(),
|
||||
j.get("orgi").getAsString());
|
||||
if (agentStatus != null && (!agentStatus.isConnected())) {
|
||||
/**
|
||||
* 处理该坐席为离线
|
||||
*/
|
||||
// 重分配坐席
|
||||
if (AutomaticServiceDist.withdrawAgent(agentStatus.getOrgi(), agentStatus.getAgentno())) {
|
||||
if (acdServiceRouter.withdrawAgent(agentStatus.getOrgi(), agentStatus.getAgentno())) {
|
||||
logger.info("[onMessage] re-allotAgent for user's visitors successfully.");
|
||||
} else {
|
||||
logger.info("[onMessage] re-allotAgent, error happens.");
|
||||
@ -81,15 +89,15 @@ public class SocketioConnEventSubscription {
|
||||
agentStatusRes.save(agentStatus);
|
||||
|
||||
// 记录坐席工作日志
|
||||
AutomaticServiceDist.recordAgentStatus(agentStatus.getAgentno(),
|
||||
agentStatus.getUsername(),
|
||||
agentStatus.getAgentno(),
|
||||
j.get("isAdmin").getAsBoolean(),
|
||||
agentStatus.getAgentno(),
|
||||
agentStatus.getStatus(),
|
||||
MainContext.AgentStatusEnum.OFFLINE.toString(),
|
||||
MainContext.AgentWorkType.MEIDIACHAT.toString(),
|
||||
agentStatus.getOrgi(), null);
|
||||
acdWorkMonitor.recordAgentStatus(agentStatus.getAgentno(),
|
||||
agentStatus.getUsername(),
|
||||
agentStatus.getAgentno(),
|
||||
j.get("isAdmin").getAsBoolean(),
|
||||
agentStatus.getAgentno(),
|
||||
agentStatus.getStatus(),
|
||||
MainContext.AgentStatusEnum.OFFLINE.toString(),
|
||||
MainContext.AgentWorkType.MEIDIACHAT.toString(),
|
||||
agentStatus.getOrgi(), null);
|
||||
} else if (agentStatus == null) {
|
||||
// 该坐席已经完成离线设置
|
||||
logger.info("[onMessage] agent is already offline, skip any further operations");
|
||||
|
@ -43,9 +43,9 @@ public class OnlineUserAspect {
|
||||
@Before("execution(* com.chatopera.cc.persistence.repository.OnlineUserRepository.save(..))")
|
||||
public void save(final JoinPoint joinPoint) {
|
||||
final OnlineUser onlineUser = (OnlineUser) joinPoint.getArgs()[0];
|
||||
logger.info(
|
||||
"[save] put onlineUser id {}, status {}, invite status {}", onlineUser.getId(), onlineUser.getStatus(),
|
||||
onlineUser.getInvitestatus());
|
||||
// logger.info(
|
||||
// "[save] put onlineUser id {}, status {}, invite status {}", onlineUser.getId(), onlineUser.getStatus(),
|
||||
// onlineUser.getInvitestatus());
|
||||
if (StringUtils.isNotBlank(onlineUser.getStatus())) {
|
||||
switch (MainContext.OnlineUserStatusEnum.toValue(onlineUser.getStatus())) {
|
||||
case OFFLINE:
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package com.chatopera.cc.basic;
|
||||
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.basic.resource.ActivityResource;
|
||||
import com.chatopera.cc.basic.resource.BatchResource;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
@ -59,6 +60,8 @@ public class MainContext {
|
||||
|
||||
private static PeerSyncIM peerSyncIM;
|
||||
|
||||
private static ACDServiceRouter acdServiceRouter;
|
||||
|
||||
static {
|
||||
ConvertUtils.register(new DateConverter(), java.util.Date.class);
|
||||
enableModule("report");
|
||||
@ -594,8 +597,6 @@ public class MainContext {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 会话监控消息类型
|
||||
*/
|
||||
@ -1088,4 +1089,13 @@ public class MainContext {
|
||||
return modules;
|
||||
}
|
||||
|
||||
public static ACDServiceRouter getACDServiceRouter() {
|
||||
if (acdServiceRouter == null) {
|
||||
acdServiceRouter = getContext().getBean(ACDServiceRouter.class);
|
||||
}
|
||||
return acdServiceRouter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1396,4 +1396,34 @@ public class MainUtils {
|
||||
}
|
||||
return strb.toString();
|
||||
}
|
||||
|
||||
public static void putMapEntry(
|
||||
Map<String, String[]> map, String name,
|
||||
String value) {
|
||||
String[] newValues = null;
|
||||
String[] oldValues = (String[]) (String[]) map.get(name);
|
||||
if (oldValues == null) {
|
||||
newValues = new String[1];
|
||||
newValues[0] = value;
|
||||
} else {
|
||||
newValues = new String[oldValues.length + 1];
|
||||
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
|
||||
newValues[oldValues.length] = value;
|
||||
}
|
||||
map.put(name, newValues);
|
||||
}
|
||||
|
||||
public static byte convertHexDigit(byte b) {
|
||||
if ((b >= 48) && (b <= 57)) {
|
||||
return (byte) (b - 48);
|
||||
}
|
||||
if ((b >= 97) && (b <= 102)) {
|
||||
return (byte) (b - 97 + 10);
|
||||
}
|
||||
if ((b >= 65) && (b <= 70)) {
|
||||
return (byte) (b - 65 + 10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,9 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDAgentService;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.User;
|
||||
@ -36,6 +38,9 @@ import java.util.TimeZone;
|
||||
public class ApplicationController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ApplicationController.class);
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Value("${git.build.version}")
|
||||
private String appVersionNumber;
|
||||
|
||||
@ -58,7 +63,7 @@ public class ApplicationController extends Handler {
|
||||
User logined = super.getUser(request);
|
||||
TimeZone timezone = TimeZone.getDefault();
|
||||
|
||||
view.addObject("agentStatusReport", AutomaticServiceDist.getAgentReport(logined.getOrgi()));
|
||||
view.addObject("agentStatusReport", acdWorkMonitor.getAgentReport(logined.getOrgi()));
|
||||
view.addObject("tenant", super.getTenant(request));
|
||||
view.addObject("istenantshare", super.isEnabletneant());
|
||||
view.addObject("timeDifference", timezone.getRawOffset());
|
||||
|
@ -16,13 +16,16 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.basic.auth.AuthToken;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.model.AgentStatus;
|
||||
import com.chatopera.cc.model.SystemConfig;
|
||||
import com.chatopera.cc.model.User;
|
||||
import com.chatopera.cc.model.UserRole;
|
||||
import com.chatopera.cc.persistence.repository.AgentStatusRepository;
|
||||
import com.chatopera.cc.persistence.repository.UserRepository;
|
||||
import com.chatopera.cc.persistence.repository.UserRoleRepository;
|
||||
@ -84,6 +87,9 @@ public class LoginController extends Handler {
|
||||
@Autowired
|
||||
private UserProxy userProxy;
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
/**
|
||||
* 登录页面
|
||||
*
|
||||
@ -208,7 +214,7 @@ public class LoginController extends Handler {
|
||||
agentStatusRes.save(agentStatus);
|
||||
|
||||
// 工作状态记录
|
||||
AutomaticServiceDist.recordAgentStatus(agentStatus.getAgentno(),
|
||||
acdWorkMonitor.recordAgentStatus(agentStatus.getAgentno(),
|
||||
agentStatus.getUsername(),
|
||||
agentStatus.getAgentno(),
|
||||
user.isAdmin(), // 0代表admin
|
||||
|
@ -16,21 +16,23 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller.admin;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDAgentService;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.socketio.client.NettyClients;
|
||||
import com.chatopera.cc.model.SysDic;
|
||||
import com.chatopera.cc.model.User;
|
||||
import com.chatopera.cc.persistence.repository.OnlineUserRepository;
|
||||
import com.chatopera.cc.persistence.repository.SysDicRepository;
|
||||
import com.chatopera.cc.persistence.repository.UserEventRepository;
|
||||
import com.chatopera.cc.persistence.repository.UserRepository;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.util.Menu;
|
||||
import com.chatopera.cc.proxy.OnlineUserProxy;
|
||||
import com.chatopera.cc.socketio.client.NettyClients;
|
||||
import com.chatopera.cc.util.Menu;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@ -47,6 +49,9 @@ import java.util.List;
|
||||
@Controller
|
||||
public class AdminController extends Handler {
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRes;
|
||||
|
||||
@ -66,7 +71,7 @@ public class AdminController extends Handler {
|
||||
public ModelAndView index(ModelMap map, HttpServletRequest request) {
|
||||
ModelAndView view = request(super.createRequestPageTempletResponse("redirect:/"));
|
||||
User user = super.getUser(request);
|
||||
view.addObject("agentStatusReport", AutomaticServiceDist.getAgentReport(user.getOrgi()));
|
||||
view.addObject("agentStatusReport", acdWorkMonitor.getAgentReport(user.getOrgi()));
|
||||
view.addObject("agentStatus", cache.findOneAgentStatusByAgentnoAndOrig(user.getId(), user.getOrgi()));
|
||||
return view;
|
||||
}
|
||||
@ -78,7 +83,7 @@ public class AdminController extends Handler {
|
||||
map.put("chatClients", NettyClients.getInstance().size());
|
||||
map.put("systemCaches", cache.getSystemSizeByOrgi(MainContext.SYSTEM_ORGI));
|
||||
|
||||
map.put("agentReport", AutomaticServiceDist.getAgentReport(orgi));
|
||||
map.put("agentReport", acdWorkMonitor.getAgentReport(orgi));
|
||||
map.put("webIMReport", MainUtils.getWebIMReport(userEventRes.findByOrgiAndCreatetimeRange(super.getOrgi(request), MainUtils.getStartTime(), MainUtils.getEndTime())));
|
||||
|
||||
map.put("agents", getAgent(request).size());
|
||||
|
@ -16,13 +16,14 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller.api;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDMessageHelper;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.basic.MainContext.*;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.exception.CSKefuException;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.controller.api.request.RestUtils;
|
||||
import com.chatopera.cc.exception.CSKefuException;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.peer.PeerSyncIM;
|
||||
import com.chatopera.cc.persistence.repository.AgentServiceRepository;
|
||||
@ -61,6 +62,12 @@ public class ApiAgentUserController extends Handler {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ApiAgentUserController.class);
|
||||
|
||||
@Autowired
|
||||
private ACDMessageHelper acdMessageHelper;
|
||||
|
||||
@Autowired
|
||||
private ACDServiceRouter acdServiceRouter;
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
@ -201,7 +208,7 @@ public class ApiAgentUserController extends Handler {
|
||||
// 更新当前坐席的服务访客列表
|
||||
if (currentAgentStatus != null) {
|
||||
cache.deleteOnlineUserIdFromAgentStatusByUseridAndAgentnoAndOrgi(userId, currentAgentno, orgi);
|
||||
AutomaticServiceDist.updateAgentStatus(currentAgentStatus, orgi);
|
||||
agentUserProxy.updateAgentStatus(currentAgentStatus, orgi);
|
||||
}
|
||||
|
||||
if (transAgentStatus != null) {
|
||||
@ -212,7 +219,7 @@ public class ApiAgentUserController extends Handler {
|
||||
// 转接坐席提示消息
|
||||
Message outMessage = new Message();
|
||||
outMessage.setMessage(
|
||||
AutomaticServiceDist.getSuccessMessage(agentService, agentUser.getChannel(), orgi));
|
||||
acdMessageHelper.getSuccessMessage(agentService, agentUser.getChannel(), orgi));
|
||||
outMessage.setMessageType(MediaType.TEXT.toString());
|
||||
outMessage.setCalltype(CallType.IN.toString());
|
||||
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
|
||||
@ -292,7 +299,7 @@ public class ApiAgentUserController extends Handler {
|
||||
logined.getId(), agentUser.getAgentno()) || logined.isAdmin())) {
|
||||
// 删除访客-坐席关联关系,包括缓存
|
||||
try {
|
||||
AutomaticServiceDist.deleteAgentUser(agentUser, orgi);
|
||||
acdServiceRouter.deleteAgentUser(agentUser, orgi);
|
||||
} catch (CSKefuException e) {
|
||||
// 未能删除成功
|
||||
logger.error("[end]", e);
|
||||
@ -322,7 +329,7 @@ public class ApiAgentUserController extends Handler {
|
||||
*/
|
||||
private JsonObject withdraw(final HttpServletRequest request, final JsonObject j) {
|
||||
JsonObject resp = new JsonObject();
|
||||
AutomaticServiceDist.withdrawAgent(super.getOrgi(request), super.getUser(request).getId());
|
||||
acdServiceRouter.withdrawAgent(super.getOrgi(request), super.getUser(request).getId());
|
||||
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
|
||||
return resp;
|
||||
}
|
||||
|
@ -55,7 +55,8 @@ public class ApiAppsController extends Handler {
|
||||
@Menu(type = "apps", subtype = "apps", access = true)
|
||||
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body, @Valid String q) {
|
||||
logger.info("[operations] body {}, q {}", body, q);
|
||||
final JsonObject j = StringUtils.isBlank(body) ? (new JsonObject()) : (new JsonParser()).parse(body).getAsJsonObject();
|
||||
final JsonObject j = StringUtils.isBlank(body) ? (new JsonObject()) : (new JsonParser()).parse(
|
||||
body).getAsJsonObject();
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
HttpHeaders headers = RestUtils.header();
|
||||
@ -111,7 +112,7 @@ public class ApiAppsController extends Handler {
|
||||
logger.info("[invite] new invite record {} of onlineUser id {} saved.", record.getId(), onlineUser.getId());
|
||||
|
||||
try {
|
||||
OnlineUserProxy.sendWebIMClients(onlineUser.getUserid(), "invite");
|
||||
OnlineUserProxy.sendWebIMClients(onlineUser.getUserid(), "invite:" + agentno);
|
||||
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
|
||||
} catch (Exception e) {
|
||||
logger.error("[invite] error", e);
|
||||
|
@ -16,7 +16,9 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller.api;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
@ -25,7 +27,7 @@ import com.chatopera.cc.model.SessionConfig;
|
||||
import com.chatopera.cc.model.User;
|
||||
import com.chatopera.cc.persistence.repository.AgentStatusRepository;
|
||||
import com.chatopera.cc.persistence.repository.AgentUserRepository;
|
||||
import com.chatopera.cc.persistence.repository.OrganRepository;
|
||||
import com.chatopera.cc.proxy.AgentUserProxy;
|
||||
import com.chatopera.cc.util.Menu;
|
||||
import com.chatopera.cc.util.RestResult;
|
||||
import com.chatopera.cc.util.RestResultType;
|
||||
@ -51,17 +53,24 @@ import java.util.List;
|
||||
@RequestMapping("/api/servicequene")
|
||||
public class ApiServiceQueneController extends Handler {
|
||||
|
||||
@Autowired
|
||||
private AgentUserProxy agentUserProxy;
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private AgentStatusRepository agentStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private ACDServiceRouter acdServiceRouter;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRepository;
|
||||
|
||||
@Autowired
|
||||
private OrganRepository organRes;
|
||||
|
||||
|
||||
@Autowired
|
||||
private Cache cache;
|
||||
|
||||
@ -75,7 +84,7 @@ public class ApiServiceQueneController extends Handler {
|
||||
@Menu(type = "apps", subtype = "user", access = true)
|
||||
public ResponseEntity<RestResult> list(HttpServletRequest request) {
|
||||
return new ResponseEntity<>(
|
||||
new RestResult(RestResultType.OK, AutomaticServiceDist.getAgentReport(super.getOrgi(request))),
|
||||
new RestResult(RestResultType.OK, acdWorkMonitor.getAgentReport(super.getOrgi(request))),
|
||||
HttpStatus.OK);
|
||||
}
|
||||
|
||||
@ -106,7 +115,7 @@ public class ApiServiceQueneController extends Handler {
|
||||
agentStatus.setLogindate(new Date());
|
||||
agentStatus.setSkills(logined.getSkills());
|
||||
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(super.getOrgi(request));
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(super.getOrgi(request));
|
||||
|
||||
agentStatus.setUsers(agentUserRepository.countByAgentnoAndStatusAndOrgi(
|
||||
logined.getId(),
|
||||
@ -130,19 +139,19 @@ public class ApiServiceQueneController extends Handler {
|
||||
agentStatus.setStatus(MainContext.AgentStatusEnum.READY.toString());
|
||||
cache.putAgentStatusByOrgi(agentStatus, super.getOrgi(request));
|
||||
|
||||
AutomaticServiceDist.recordAgentStatus(
|
||||
acdWorkMonitor.recordAgentStatus(
|
||||
agentStatus.getAgentno(), agentStatus.getUsername(), agentStatus.getAgentno(),
|
||||
logined.isAdmin(), agentStatus.getAgentno(),
|
||||
MainContext.AgentStatusEnum.OFFLINE.toString(), MainContext.AgentStatusEnum.READY.toString(),
|
||||
MainContext.AgentWorkType.MEIDIACHAT.toString(), agentStatus.getOrgi(), null);
|
||||
AutomaticServiceDist.allotAgent(agentStatus.getAgentno(), super.getOrgi(request));
|
||||
acdServiceRouter.allotVisitors(agentStatus.getAgentno(), super.getOrgi(request));
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(status)) {
|
||||
if (status.equals(MainContext.AgentStatusEnum.NOTREADY.toString())) {
|
||||
List<AgentStatus> agentStatusList = agentStatusRepository.findByAgentnoAndOrgi(
|
||||
logined.getId(), super.getOrgi(request));
|
||||
for (AgentStatus temp : agentStatusList) {
|
||||
AutomaticServiceDist.recordAgentStatus(
|
||||
acdWorkMonitor.recordAgentStatus(
|
||||
temp.getAgentno(), temp.getUsername(), temp.getAgentno(),
|
||||
logined.isAdmin(),
|
||||
temp.getAgentno(),
|
||||
@ -158,7 +167,7 @@ public class ApiServiceQueneController extends Handler {
|
||||
if (agentStatusList.size() > 0) {
|
||||
agentStatus = agentStatusList.get(0);
|
||||
agentStatus.setBusy(true);
|
||||
AutomaticServiceDist.recordAgentStatus(
|
||||
acdWorkMonitor.recordAgentStatus(
|
||||
agentStatus.getAgentno(), agentStatus.getUsername(), agentStatus.getAgentno(),
|
||||
logined.isAdmin(), agentStatus.getAgentno(),
|
||||
MainContext.AgentStatusEnum.READY.toString(), MainContext.AgentStatusEnum.BUSY.toString(),
|
||||
@ -176,7 +185,7 @@ public class ApiServiceQueneController extends Handler {
|
||||
if (agentStatusList.size() > 0) {
|
||||
agentStatus = agentStatusList.get(0);
|
||||
agentStatus.setBusy(false);
|
||||
AutomaticServiceDist.recordAgentStatus(
|
||||
acdWorkMonitor.recordAgentStatus(
|
||||
agentStatus.getAgentno(), agentStatus.getUsername(), agentStatus.getAgentno(),
|
||||
logined.isAdmin(), agentStatus.getAgentno(),
|
||||
MainContext.AgentStatusEnum.BUSY.toString(), MainContext.AgentStatusEnum.READY.toString(),
|
||||
@ -187,9 +196,9 @@ public class ApiServiceQueneController extends Handler {
|
||||
agentStatusRepository.save(agentStatus);
|
||||
cache.putAgentStatusByOrgi(agentStatus, super.getOrgi(request));
|
||||
}
|
||||
AutomaticServiceDist.allotAgent(agentStatus.getAgentno(), super.getOrgi(request));
|
||||
acdServiceRouter.allotVisitors(agentStatus.getAgentno(), super.getOrgi(request));
|
||||
}
|
||||
AutomaticServiceDist.broadcastAgentsStatus(
|
||||
agentUserProxy.broadcastAgentsStatus(
|
||||
super.getOrgi(request), "agent", "api", super.getUser(request).getId());
|
||||
}
|
||||
return new ResponseEntity<>(new RestResult(RestResultType.OK, agentStatus), HttpStatus.OK);
|
||||
|
@ -17,7 +17,10 @@
|
||||
package com.chatopera.cc.controller.apps;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDAgentService;
|
||||
import com.chatopera.cc.acd.ACDMessageHelper;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.activemq.BrokerPublisher;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
@ -57,6 +60,18 @@ import java.util.*;
|
||||
public class AgentAuditController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(AgentAuditController.class);
|
||||
|
||||
@Autowired
|
||||
private ACDAgentService acdAgentService;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private ACDMessageHelper acdMessageHelper;
|
||||
|
||||
@Autowired
|
||||
private ACDServiceRouter acdServiceRouter;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRes;
|
||||
|
||||
@ -128,7 +143,7 @@ public class AgentAuditController extends Handler {
|
||||
@Valid final String skill,
|
||||
@Valid final String agentno,
|
||||
@Valid String sort
|
||||
) {
|
||||
) {
|
||||
final String orgi = super.getOrgi(request);
|
||||
final User logined = super.getUser(request);
|
||||
logger.info("[index] skill {}, agentno {}, logined {}", skill, agentno, logined.getId());
|
||||
@ -256,7 +271,7 @@ public class AgentAuditController extends Handler {
|
||||
HttpServletRequest request,
|
||||
String id,
|
||||
String channel
|
||||
) throws IOException, TemplateException {
|
||||
) throws IOException, TemplateException {
|
||||
String mainagentuser = "/apps/cca/mainagentuser";
|
||||
if (channel.equals("phone")) {
|
||||
mainagentuser = "/apps/cca/mainagentuser_callout";
|
||||
@ -287,11 +302,11 @@ public class AgentAuditController extends Handler {
|
||||
view.addObject(
|
||||
"agentUserMessageList",
|
||||
this.chatMessageRepository.findByUsessionAndOrgi(agentUser.getUserid(), orgi,
|
||||
new PageRequest(0, 20, Sort.Direction.DESC,
|
||||
"updatetime"
|
||||
)
|
||||
)
|
||||
);
|
||||
new PageRequest(0, 20, Sort.Direction.DESC,
|
||||
"updatetime"
|
||||
)
|
||||
)
|
||||
);
|
||||
AgentService agentService = null;
|
||||
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
|
||||
agentService = this.agentServiceRes.findOne(agentUser.getAgentserviceid());
|
||||
@ -320,20 +335,20 @@ public class AgentAuditController extends Handler {
|
||||
}
|
||||
view.addObject("serviceCount", Integer
|
||||
.valueOf(this.agentServiceRes
|
||||
.countByUseridAndOrgiAndStatus(agentUser
|
||||
.getUserid(), orgi,
|
||||
MainContext.AgentUserStatusEnum.END
|
||||
.toString())));
|
||||
.countByUseridAndOrgiAndStatus(agentUser
|
||||
.getUserid(), orgi,
|
||||
MainContext.AgentUserStatusEnum.END
|
||||
.toString())));
|
||||
view.addObject("tagRelationList", tagRelationRes.findByUserid(agentUser.getUserid()));
|
||||
}
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(super.getOrgi(request));
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(super.getOrgi(request));
|
||||
|
||||
view.addObject("sessionConfig", sessionConfig);
|
||||
if (sessionConfig.isOtherquickplay()) {
|
||||
view.addObject("topicList", OnlineUserProxy.search(null, orgi, super.getUser(request)));
|
||||
}
|
||||
view.addObject("sessionConfig", sessionConfig);
|
||||
if (sessionConfig.isOtherquickplay()) {
|
||||
view.addObject("topicList", OnlineUserProxy.search(null, orgi, super.getUser(request)));
|
||||
}
|
||||
|
||||
view.addObject("tags", tagRes.findByOrgiAndTagtype(orgi, MainContext.ModelType.USER.toString()));
|
||||
view.addObject("tags", tagRes.findByOrgiAndTagtype(orgi, MainContext.ModelType.USER.toString()));
|
||||
return view;
|
||||
}
|
||||
|
||||
@ -357,7 +372,7 @@ public class AgentAuditController extends Handler {
|
||||
final @Valid String agentserviceid,
|
||||
final @Valid String agentnoid,
|
||||
final @Valid String agentuserid
|
||||
) {
|
||||
) {
|
||||
logger.info("[transfer] userId {}, agentUser {}", userid, agentuserid);
|
||||
final String orgi = super.getOrgi(request);
|
||||
final User logined = super.getUser(request);
|
||||
@ -422,7 +437,7 @@ public class AgentAuditController extends Handler {
|
||||
HttpServletRequest request,
|
||||
@Valid String agentnoid,
|
||||
@Valid String organ
|
||||
) {
|
||||
) {
|
||||
final String orgi = super.getOrgi(request);
|
||||
if (StringUtils.isNotBlank(organ)) {
|
||||
List<String> usersids = new ArrayList<String>();
|
||||
@ -473,7 +488,7 @@ public class AgentAuditController extends Handler {
|
||||
@Valid final String currentAgentnoid,
|
||||
@Valid final String agentno, // 会话转接给下一个坐席
|
||||
@Valid final String memo
|
||||
) throws CSKefuException {
|
||||
) throws CSKefuException {
|
||||
final String currentAgentno = currentAgentnoid; // 当前会话坐席的agentno
|
||||
|
||||
final String orgi = super.getOrgi(request);
|
||||
@ -506,7 +521,7 @@ public class AgentAuditController extends Handler {
|
||||
// 更新当前坐席的服务访客列表
|
||||
if (currentAgentStatus != null) {
|
||||
cache.deleteOnlineUserIdFromAgentStatusByUseridAndAgentnoAndOrgi(userid, currentAgentno, orgi);
|
||||
AutomaticServiceDist.updateAgentStatus(currentAgentStatus, super.getOrgi(request));
|
||||
agentUserProxy.updateAgentStatus(currentAgentStatus, super.getOrgi(request));
|
||||
}
|
||||
|
||||
if (transAgentStatus != null) {
|
||||
@ -518,7 +533,7 @@ public class AgentAuditController extends Handler {
|
||||
try {
|
||||
Message outMessage = new Message();
|
||||
outMessage.setMessage(
|
||||
AutomaticServiceDist.getSuccessMessage(agentService, agentUser.getChannel(), orgi));
|
||||
acdMessageHelper.getSuccessMessage(agentService, agentUser.getChannel(), orgi));
|
||||
outMessage.setMessageType(MainContext.MediaType.TEXT.toString());
|
||||
outMessage.setCalltype(MainContext.CallType.IN.toString());
|
||||
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
|
||||
@ -534,7 +549,7 @@ public class AgentAuditController extends Handler {
|
||||
agentUser.getUserid(),
|
||||
outMessage,
|
||||
true
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
// 通知转接消息给新坐席
|
||||
@ -544,7 +559,7 @@ public class AgentAuditController extends Handler {
|
||||
MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
|
||||
agentUser.getAppid(), MainContext.MessageType.NEW, agentService.getAgentno(),
|
||||
outMessage, true
|
||||
);
|
||||
);
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("[transfersave]", ex);
|
||||
@ -588,7 +603,7 @@ public class AgentAuditController extends Handler {
|
||||
logined.getId(), agentUser.getAgentno()) || logined.isAdmin())) {
|
||||
// 删除访客-坐席关联关系,包括缓存
|
||||
try {
|
||||
AutomaticServiceDist.deleteAgentUser(agentUser, orgi);
|
||||
acdServiceRouter.deleteAgentUser(agentUser, orgi);
|
||||
} catch (CSKefuException e) {
|
||||
// 未能删除成功
|
||||
logger.error("[end]", e);
|
||||
|
@ -17,7 +17,10 @@
|
||||
package com.chatopera.cc.controller.apps;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDAgentService;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.activemq.BrokerPublisher;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
@ -72,6 +75,18 @@
|
||||
|
||||
static final Logger logger = LoggerFactory.getLogger(AgentController.class);
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private ACDAgentService acdAgentService;
|
||||
|
||||
@Autowired
|
||||
private ACDServiceRouter acdServiceRouter;
|
||||
|
||||
@Autowired
|
||||
private ContactsRepository contactsRes;
|
||||
|
||||
@ -448,7 +463,7 @@
|
||||
view.addObject("tagRelationList", tagRelationRes.findByUserid(agentUser.getUserid()));
|
||||
}
|
||||
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(super.getOrgi(request));
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(super.getOrgi(request));
|
||||
|
||||
view.addObject("sessionConfig", sessionConfig);
|
||||
if (sessionConfig.isOtherquickplay()) {
|
||||
@ -472,7 +487,7 @@
|
||||
@RequestMapping("/other/topic")
|
||||
@Menu(type = "apps", subtype = "othertopic")
|
||||
public ModelAndView othertopic(ModelMap map, HttpServletRequest request, String q) throws IOException, TemplateException {
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(super.getOrgi(request));
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(super.getOrgi(request));
|
||||
|
||||
map.put("sessionConfig", sessionConfig);
|
||||
if (sessionConfig.isOtherquickplay()) {
|
||||
@ -485,7 +500,7 @@
|
||||
@RequestMapping("/other/topic/detail")
|
||||
@Menu(type = "apps", subtype = "othertopicdetail")
|
||||
public ModelAndView othertopicdetail(ModelMap map, HttpServletRequest request, String id) throws IOException, TemplateException {
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(super.getOrgi(request));
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(super.getOrgi(request));
|
||||
|
||||
map.put("sessionConfig", sessionConfig);
|
||||
if (sessionConfig.isOtherquickplay()) {
|
||||
@ -538,8 +553,8 @@
|
||||
agentStatusRes.save(agentStatus);
|
||||
|
||||
// 为该坐席分配访客
|
||||
AutomaticServiceDist.allotAgent(agentStatus.getAgentno(), orgi);
|
||||
AutomaticServiceDist.recordAgentStatus(agentStatus.getAgentno(),
|
||||
acdServiceRouter.allotVisitors(agentStatus.getAgentno(), orgi);
|
||||
acdWorkMonitor.recordAgentStatus(agentStatus.getAgentno(),
|
||||
agentStatus.getUsername(),
|
||||
agentStatus.getAgentno(),
|
||||
logined.isAdmin(), // 0代表admin
|
||||
@ -575,7 +590,7 @@
|
||||
cache.putAgentStatusByOrgi(agentStatus, orgi);
|
||||
agentStatusRes.save(agentStatus);
|
||||
|
||||
AutomaticServiceDist.recordAgentStatus(agentStatus.getAgentno(),
|
||||
acdWorkMonitor.recordAgentStatus(agentStatus.getAgentno(),
|
||||
agentStatus.getUsername(),
|
||||
agentStatus.getAgentno(),
|
||||
logined.isAdmin(), // 0代表admin
|
||||
@ -603,7 +618,7 @@
|
||||
logined.getId(), logined.getOrgi(), logined.getSkills());
|
||||
|
||||
agentStatus.setBusy(true);
|
||||
AutomaticServiceDist.recordAgentStatus(
|
||||
acdWorkMonitor.recordAgentStatus(
|
||||
agentStatus.getAgentno(),
|
||||
agentStatus.getUsername(),
|
||||
agentStatus.getAgentno(),
|
||||
@ -618,7 +633,7 @@
|
||||
cache.putAgentStatusByOrgi(agentStatus, super.getOrgi(request));
|
||||
agentStatusRes.save(agentStatus);
|
||||
|
||||
AutomaticServiceDist.broadcastAgentsStatus(super.getOrgi(request), "agent", "busy", logined.getId());
|
||||
agentUserProxy.broadcastAgentsStatus(super.getOrgi(request), "agent", "busy", logined.getId());
|
||||
|
||||
return request(super.createRequestPageTempletResponse("/public/success"));
|
||||
}
|
||||
@ -644,7 +659,7 @@
|
||||
agentStatus.setStatus(MainContext.AgentStatusEnum.READY.toString());
|
||||
|
||||
// 更新工作记录
|
||||
AutomaticServiceDist.recordAgentStatus(
|
||||
acdWorkMonitor.recordAgentStatus(
|
||||
agentStatus.getAgentno(),
|
||||
agentStatus.getUsername(),
|
||||
agentStatus.getAgentno(),
|
||||
@ -661,7 +676,7 @@
|
||||
agentStatusRes.save(agentStatus);
|
||||
|
||||
// 重新分配访客给坐席
|
||||
AutomaticServiceDist.allotAgent(agentStatus.getAgentno(), super.getOrgi(request));
|
||||
acdServiceRouter.allotVisitors(agentStatus.getAgentno(), super.getOrgi(request));
|
||||
|
||||
return request(super.createRequestPageTempletResponse("/public/success"));
|
||||
}
|
||||
@ -676,7 +691,7 @@
|
||||
List<AgentService> agentServiceList = new ArrayList<AgentService>();
|
||||
for (AgentUser agentUser : agentUserList) {
|
||||
if (agentUser != null && super.getUser(request).getId().equals(agentUser.getAgentno())) {
|
||||
AutomaticServiceDist.deleteAgentUser(agentUser, orgi);
|
||||
acdServiceRouter.deleteAgentUser(agentUser, orgi);
|
||||
AgentService agentService = agentServiceRes.findByIdAndOrgi(agentUser.getAgentserviceid(), orgi);
|
||||
if (agentService != null) {
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
@ -712,7 +727,7 @@
|
||||
logined.getId(), agentUser.getAgentno()) || logined.isAdmin())) {
|
||||
// 删除访客-坐席关联关系,包括缓存
|
||||
try {
|
||||
AutomaticServiceDist.deleteAgentUser(agentUser, orgi);
|
||||
acdServiceRouter.deleteAgentUser(agentUser, orgi);
|
||||
} catch (CSKefuException e) {
|
||||
// 未能删除成功
|
||||
logger.error("[end]", e);
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
package com.chatopera.cc.controller.apps;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
@ -44,6 +44,8 @@ import java.util.List;
|
||||
@RequestMapping("/apps/quality")
|
||||
public class AgentQualityController extends Handler {
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private QualityRepository qualityRes;
|
||||
@ -60,7 +62,7 @@ public class AgentQualityController extends Handler {
|
||||
@RequestMapping(value = "/index")
|
||||
@Menu(type = "agent", subtype = "quality", access = false)
|
||||
public ModelAndView index(ModelMap map, HttpServletRequest request) {
|
||||
map.addAttribute("sessionConfig", AutomaticServiceDist.initSessionConfig(super.getOrgi(request)));
|
||||
map.addAttribute("sessionConfig", acdPolicyService.initSessionConfig(super.getOrgi(request)));
|
||||
map.addAttribute("qualityList", qualityRes.findByQualitytypeAndOrgi(MainContext.QualityType.CHAT.toString(), super.getOrgi(request)));
|
||||
map.addAttribute("tagList", tagRes.findByOrgiAndTagtype(super.getOrgi(request), MainContext.TagType.QUALITY.toString()));
|
||||
return request(super.createAppsTempletResponse("/apps/quality/index"));
|
||||
@ -92,7 +94,7 @@ public class AgentQualityController extends Handler {
|
||||
if (tempList.size() > 0) {
|
||||
qualityRes.save(tempList);
|
||||
}
|
||||
SessionConfig config = AutomaticServiceDist.initSessionConfig(super.getOrgi(request));
|
||||
SessionConfig config = acdPolicyService.initSessionConfig(super.getOrgi(request));
|
||||
if (config != null) {
|
||||
if ("points".equals(request.getParameter("qualityscore"))) {
|
||||
config.setQualityscore("points");
|
||||
|
@ -16,14 +16,14 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller.apps;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.util.Menu;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -47,6 +47,9 @@ import java.util.List;
|
||||
@RequestMapping("/setting")
|
||||
public class AgentSettingsController extends Handler {
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private SessionConfigRepository sessionConfigRes;
|
||||
|
||||
@ -117,7 +120,7 @@ public class AgentSettingsController extends Handler {
|
||||
cache.putSessionConfigByOrgi(tempSessionConfig, orgi);
|
||||
cache.deleteSessionConfigListByOrgi(orgi);
|
||||
|
||||
AutomaticServiceDist.initSessionConfigList();
|
||||
acdPolicyService.initSessionConfigList();
|
||||
map.put("sessionConfig", tempSessionConfig);
|
||||
|
||||
return request(super.createRequestPageTempletResponse("redirect:/setting/agent/index.html"));
|
||||
|
@ -16,7 +16,7 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller.apps;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
@ -33,7 +33,6 @@ import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@ -52,6 +51,9 @@ import java.util.List;
|
||||
public class AppsController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(AppsController.class);
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRes;
|
||||
|
||||
@ -137,7 +139,7 @@ public class AppsController extends Handler {
|
||||
}
|
||||
|
||||
private void aggValues(ModelMap map, HttpServletRequest request) {
|
||||
map.put("agentReport", AutomaticServiceDist.getAgentReport(super.getOrgi(request)));
|
||||
map.put("agentReport", acdWorkMonitor.getAgentReport(super.getOrgi(request)));
|
||||
map.put(
|
||||
"webIMReport", MainUtils.getWebIMReport(
|
||||
userEventRes.findByOrgiAndCreatetimeRange(super.getOrgi(request), MainUtils.getStartTime(),
|
||||
|
@ -17,18 +17,20 @@
|
||||
|
||||
package com.chatopera.cc.controller.apps;
|
||||
|
||||
import com.chatopera.cc.proxy.OnlineUserProxy;
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDAgentService;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.socketio.util.RichMediaUtils;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.persistence.blob.JpaBlobHelper;
|
||||
import com.chatopera.cc.persistence.es.ContactsRepository;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.proxy.OnlineUserProxy;
|
||||
import com.chatopera.cc.socketio.util.RichMediaUtils;
|
||||
import com.chatopera.cc.util.*;
|
||||
import freemarker.template.TemplateException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@ -68,6 +70,12 @@ import java.util.*;
|
||||
public class IMController extends Handler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(IMController.class);
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private OnlineUserRepository onlineUserRes;
|
||||
|
||||
@ -539,8 +547,9 @@ public class IMController extends Handler {
|
||||
@Valid final String description,
|
||||
@Valid final String imgurl,
|
||||
@Valid final String pid,
|
||||
@Valid final String purl) throws Exception {
|
||||
logger.info("[index] orgi {}, skill {}, agent {}, traceid {}", orgi, skill, agent, traceid);
|
||||
@Valid final String purl,
|
||||
@Valid final boolean isInvite) throws Exception {
|
||||
logger.info("[index] orgi {}, skill {}, agent {}, traceid {}, isInvite {}", orgi, skill, agent, traceid, isInvite);
|
||||
Map<String, String> sessionMessageObj = cache.findOneSystemMapByIdAndOrgi(sessionid, orgi);
|
||||
|
||||
if (sessionMessageObj != null) {
|
||||
@ -581,7 +590,7 @@ public class IMController extends Handler {
|
||||
view.addObject("nickname", nickname);
|
||||
|
||||
boolean consult = true; //是否已收集用户信息
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(orgi);
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
|
||||
|
||||
// 强制开启满意调查问卷
|
||||
sessionConfig.setSatisfaction(true);
|
||||
@ -599,6 +608,8 @@ public class IMController extends Handler {
|
||||
map.addAttribute("userid", userid);
|
||||
map.addAttribute("schema", request.getScheme());
|
||||
map.addAttribute("sessionid", sessionid);
|
||||
map.addAttribute("isInvite", isInvite);
|
||||
|
||||
|
||||
view.addObject("product", product);
|
||||
view.addObject("description", description);
|
||||
@ -640,9 +651,9 @@ public class IMController extends Handler {
|
||||
AgentReport report;
|
||||
|
||||
if (invite.isSkill() && invite.isConsult_skill_fixed()) { // 绑定技能组
|
||||
report = AutomaticServiceDist.getAgentReport(invite.getConsult_skill_fixed_id(), invite.getOrgi());
|
||||
report = acdWorkMonitor.getAgentReport(invite.getConsult_skill_fixed_id(), invite.getOrgi());
|
||||
} else {
|
||||
report = AutomaticServiceDist.getAgentReport(invite.getOrgi());
|
||||
report = acdWorkMonitor.getAgentReport(invite.getOrgi());
|
||||
}
|
||||
|
||||
if (report.getAgents() == 0 ||
|
||||
|
@ -16,7 +16,8 @@
|
||||
*/
|
||||
package com.chatopera.cc.controller.apps.service;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDAgentService;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
@ -24,6 +25,7 @@ import com.chatopera.cc.controller.Handler;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.peer.PeerSyncIM;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.proxy.AgentUserProxy;
|
||||
import com.chatopera.cc.proxy.OnlineUserProxy;
|
||||
import com.chatopera.cc.proxy.UserProxy;
|
||||
import com.chatopera.cc.socketio.message.Message;
|
||||
@ -59,6 +61,15 @@ public class ChatServiceController extends Handler {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ChatServiceController.class);
|
||||
|
||||
@Autowired
|
||||
private AgentUserProxy agentUserProxy;
|
||||
|
||||
@Autowired
|
||||
private ACDAgentService acdAgentService;
|
||||
|
||||
@Autowired
|
||||
private ACDServiceRouter acdServiceRouter;
|
||||
|
||||
@Autowired
|
||||
private AgentServiceRepository agentServiceRes;
|
||||
|
||||
@ -228,13 +239,13 @@ public class ChatServiceController extends Handler {
|
||||
super.getUser(request).getId(), super.getOrgi(request));
|
||||
|
||||
if (agentStatus != null) {
|
||||
AutomaticServiceDist.updateAgentStatus(agentStatus, super.getOrgi(request));
|
||||
agentUserProxy.updateAgentStatus(agentStatus, super.getOrgi(request));
|
||||
}
|
||||
|
||||
AgentStatus transAgentStatus = cache.findOneAgentStatusByAgentnoAndOrig(
|
||||
agentno, super.getOrgi(request));
|
||||
if (transAgentStatus != null) {
|
||||
AutomaticServiceDist.updateAgentStatus(transAgentStatus, super.getOrgi(request));
|
||||
agentUserProxy.updateAgentStatus(transAgentStatus, super.getOrgi(request));
|
||||
agentService.setAgentno(agentno);
|
||||
agentService.setAgentusername(transAgentStatus.getUsername());
|
||||
}
|
||||
@ -279,7 +290,7 @@ public class ChatServiceController extends Handler {
|
||||
AgentUser agentUser = agentUserRepository.findByIdAndOrgi(
|
||||
agentService.getAgentuserid(), super.getOrgi(request));
|
||||
if (agentUser != null) {
|
||||
AutomaticServiceDist.deleteAgentUser(agentUser, user.getOrgi());
|
||||
acdServiceRouter.deleteAgentUser(agentUser, user.getOrgi());
|
||||
}
|
||||
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
|
||||
agentServiceRes.save(agentService);
|
||||
@ -320,7 +331,7 @@ public class ChatServiceController extends Handler {
|
||||
|
||||
if (onlineUser != null) {
|
||||
IP ipdata = IPTools.getInstance().findGeography(onlineUser.getIp());
|
||||
OnlineUserProxy.allocateAgentService(
|
||||
acdServiceRouter.allocateAgentService(
|
||||
onlineUser.getUserid(),
|
||||
onlineUser.getUsername(),
|
||||
user.getOrgi(),
|
||||
@ -337,7 +348,9 @@ public class ChatServiceController extends Handler {
|
||||
null,
|
||||
null,
|
||||
agentService.getContactsid(),
|
||||
onlineUser.getOwner());
|
||||
onlineUser.getOwner(),
|
||||
true,
|
||||
MainContext.ChatInitiatorType.AGENT.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -384,7 +397,7 @@ public class ChatServiceController extends Handler {
|
||||
agentUser.setAgentno(null);
|
||||
agentUser.setSkill(null);
|
||||
agentUserRes.save(agentUser);
|
||||
AutomaticServiceDist.allotAgent(agentUser, super.getOrgi(request));
|
||||
acdAgentService.allotAgent(agentUser, super.getOrgi(request));
|
||||
}
|
||||
return request(super.createRequestPageTempletResponse("redirect:/service/quene/index.html"));
|
||||
}
|
||||
@ -394,7 +407,7 @@ public class ChatServiceController extends Handler {
|
||||
public ModelAndView invite(ModelMap map, HttpServletRequest request, @Valid String id) throws Exception {
|
||||
AgentUser agentUser = agentUserRes.findByIdAndOrgi(id, super.getOrgi(request));
|
||||
if (agentUser != null && agentUser.getStatus().equals(MainContext.AgentUserStatusEnum.INQUENE.toString())) {
|
||||
AutomaticServiceDist.allotAgentForInvite(super.getUser(request).getId(), agentUser, super.getOrgi(request));
|
||||
acdServiceRouter.allotAgentForInvite(super.getUser(request).getId(), agentUser, super.getOrgi(request));
|
||||
}
|
||||
return request(super.createRequestPageTempletResponse("redirect:/service/quene/index.html"));
|
||||
}
|
||||
@ -438,7 +451,8 @@ public class ChatServiceController extends Handler {
|
||||
}
|
||||
cache.deleteAgentStatusByAgentnoAndOrgi(agentStatus.getAgentno(), super.getOrgi(request));
|
||||
|
||||
AutomaticServiceDist.broadcastAgentsStatus(super.getOrgi(request), "agent", "offline", super.getUser(request).getId());
|
||||
agentUserProxy.broadcastAgentsStatus(
|
||||
super.getOrgi(request), "agent", "offline", super.getUser(request).getId());
|
||||
|
||||
return request(super.createRequestPageTempletResponse("redirect:/service/agent/index.html"));
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
package com.chatopera.cc.interceptor;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
@ -125,7 +124,9 @@ public class UserInterceptorHandler extends HandlerInterceptorAdapter {
|
||||
view.addObject("models", MainContext.getModules());
|
||||
|
||||
if (user != null) {
|
||||
view.addObject("agentStatusReport", AutomaticServiceDist.getAgentReport(user.getOrgi()));
|
||||
view.addObject(
|
||||
"agentStatusReport",
|
||||
MainContext.getACDServiceRouter().getAcdWorkMonitor().getAgentReport(user.getOrgi()));
|
||||
}
|
||||
/**
|
||||
* WebIM共享用户
|
||||
|
@ -16,11 +16,12 @@
|
||||
*/
|
||||
package com.chatopera.cc.model;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
@ -161,7 +162,7 @@ public class AgentStatus implements java.io.Serializable, Comparable<AgentStatus
|
||||
|
||||
@Transient
|
||||
public int getMaxusers() {
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(this.orgi);
|
||||
SessionConfig sessionConfig = MainContext.getACDServiceRouter().getAcdPolicyService().initSessionConfig(this.orgi);
|
||||
return sessionConfig != null ? sessionConfig.getMaxuser() : Constants.AGENT_STATUS_MAX_USER;
|
||||
}
|
||||
|
||||
@ -171,7 +172,7 @@ public class AgentStatus implements java.io.Serializable, Comparable<AgentStatus
|
||||
|
||||
@Transient
|
||||
public int getInitmaxusers() {
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(this.orgi);
|
||||
SessionConfig sessionConfig = MainContext.getACDServiceRouter().getAcdPolicyService().initSessionConfig(this.orgi);
|
||||
return sessionConfig != null ? sessionConfig.getInitmaxuser() : getMaxusers();
|
||||
}
|
||||
|
||||
@ -214,7 +215,7 @@ public class AgentStatus implements java.io.Serializable, Comparable<AgentStatus
|
||||
@Override
|
||||
public int compareTo(AgentStatus o) {
|
||||
int retValue = 0;
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(this.orgi);
|
||||
SessionConfig sessionConfig = MainContext.getACDServiceRouter().getAcdPolicyService().initSessionConfig(this.orgi);
|
||||
if (sessionConfig != null && !StringUtils.isBlank(sessionConfig.getDistribution()) && sessionConfig.getDistribution().equals("0")) {
|
||||
if (this.getUpdatetime() != null && o.getUpdatetime() != null) {
|
||||
retValue = (int) (this.getUpdatetime().getTime() - o.getUpdatetime().getTime());
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.chatopera.cc.proxy;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
@ -33,12 +33,14 @@ import java.util.List;
|
||||
|
||||
@Component
|
||||
public class AgentProxy {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(AgentProxy.class);
|
||||
|
||||
@Value("${web.upload-path}")
|
||||
private String webUploadPath;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private AttachmentRepository attachementRes;
|
||||
|
||||
@ -77,7 +79,7 @@ public class AgentProxy {
|
||||
agentStatus.setOrgi(agentStatus.getOrgi());
|
||||
agentStatus.setUpdatetime(new Date());
|
||||
agentStatus.setSkills(user.getSkills());
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(agentStatus.getOrgi());
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(agentStatus.getOrgi());
|
||||
agentStatus.setMaxusers(sessionConfig.getMaxuser());
|
||||
|
||||
/**
|
||||
|
@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.chatopera.cc.proxy;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDWorkMonitor;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
@ -25,6 +26,7 @@ import com.chatopera.cc.peer.PeerSyncIM;
|
||||
import com.chatopera.cc.persistence.es.ContactsRepository;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.socketio.message.Message;
|
||||
import com.corundumstudio.socketio.SocketIONamespace;
|
||||
import freemarker.template.TemplateException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
@ -60,6 +62,16 @@ public class AgentUserProxy {
|
||||
// 转接聊天
|
||||
private final static String AUTH_KEY_AUDIT_TRANS = "A13_A01_A03";
|
||||
|
||||
|
||||
@Autowired
|
||||
private ACDWorkMonitor acdWorkMonitor;
|
||||
|
||||
@Autowired
|
||||
private AgentReportRepository agentReportRes;
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private AgentUserRepository agentUserRes;
|
||||
|
||||
@ -246,7 +258,7 @@ public class AgentUserProxy {
|
||||
agentUserList.add(agentUser);
|
||||
}
|
||||
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(logined.getOrgi());
|
||||
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(logined.getOrgi());
|
||||
|
||||
view.addObject("sessionConfig", sessionConfig);
|
||||
if (sessionConfig.isOtherquickplay()) {
|
||||
@ -369,14 +381,18 @@ public class AgentUserProxy {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建AgentUser
|
||||
*
|
||||
* @param contact 联系人
|
||||
* @param agent 坐席
|
||||
* @param onlineUser
|
||||
* @param contact
|
||||
* @param agent
|
||||
* @param channel
|
||||
* @param logined
|
||||
* @param status
|
||||
* @param creator
|
||||
* @return
|
||||
* @throws CSKefuException
|
||||
*/
|
||||
public AgentUser createAgentUserWithContactAndAgentAndChannelAndStatus(
|
||||
final OnlineUser onlineUser,
|
||||
@ -461,6 +477,42 @@ public class AgentUserProxy {
|
||||
return opt.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新坐席当前服务中的用户状态
|
||||
* #TODO 需要分布式锁
|
||||
*
|
||||
* @param agentStatus
|
||||
* @param orgi
|
||||
*/
|
||||
public synchronized void updateAgentStatus(AgentStatus agentStatus, String orgi) {
|
||||
int users = cache.getInservAgentUsersSizeByAgentnoAndOrgi(agentStatus.getAgentno(), orgi);
|
||||
agentStatus.setUsers(users);
|
||||
agentStatus.setUpdatetime(new Date());
|
||||
cache.putAgentStatusByOrgi(agentStatus, orgi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向所有坐席client通知坐席状态变化
|
||||
*
|
||||
* @param orgi
|
||||
* @param worktype
|
||||
* @param workresult
|
||||
* @param dataid
|
||||
*/
|
||||
public void broadcastAgentsStatus(final String orgi, final String worktype, final String workresult, final String dataid) {
|
||||
/**
|
||||
* 坐席状态改变,通知监测服务
|
||||
*/
|
||||
AgentReport agentReport = acdWorkMonitor.getAgentReport(orgi);
|
||||
agentReport.setOrgi(orgi);
|
||||
agentReport.setWorktype(worktype);
|
||||
agentReport.setWorkresult(workresult);
|
||||
agentReport.setDataid(dataid);
|
||||
agentReportRes.save(agentReport);
|
||||
MainContext.getContext().getBean("agentNamespace", SocketIONamespace.class).getBroadcastOperations().sendEvent(
|
||||
"status", agentReport);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用AgentUser查询
|
||||
*
|
||||
|
@ -16,19 +16,14 @@
|
||||
*/
|
||||
package com.chatopera.cc.proxy;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainContext.ChannelType;
|
||||
import com.chatopera.cc.basic.MainContext.MessageType;
|
||||
import com.chatopera.cc.basic.MainContext.ReceiverType;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.cache.Cache;
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.cc.persistence.es.ContactsRepository;
|
||||
import com.chatopera.cc.persistence.interfaces.DataExchangeInterface;
|
||||
import com.chatopera.cc.persistence.repository.*;
|
||||
import com.chatopera.cc.socketio.message.Message;
|
||||
import com.chatopera.cc.socketio.message.OtherMessageItem;
|
||||
import com.chatopera.cc.util.*;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
@ -39,7 +34,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
@ -62,19 +56,13 @@ public class OnlineUserProxy {
|
||||
private static UserRepository userRes;
|
||||
private static Cache cache;
|
||||
private static ConsultInviteRepository consultInviteRes;
|
||||
private static InviteRecordRepository inviteRecordRes;
|
||||
private static OnlineUserHisRepository onlineUserHisRes;
|
||||
private static UserTraceRepository userTraceRes;
|
||||
private static OrgiSkillRelRepository orgiSkillRelRes;
|
||||
private static AgentUserProxy agentUserProxy;
|
||||
private static AgentUserContactsRepository agentUserContactsRes;
|
||||
private static ContactsRepository contactsRes;
|
||||
private static UserProxy userProxy;
|
||||
|
||||
// Compare two onlineUser by createtime
|
||||
public final static Comparator<OnlineUser> compareByCreateTime = (OnlineUser o1, OnlineUser o2) -> o1.getCreatetime().compareTo(
|
||||
o2.getCreatetime());
|
||||
|
||||
/**
|
||||
* @param id
|
||||
* @return
|
||||
@ -725,7 +713,7 @@ public class OnlineUserProxy {
|
||||
public static String getKeyword(String url) {
|
||||
Map<String, String[]> values = new HashMap<String, String[]>();
|
||||
try {
|
||||
parseParameters(values, url, "UTF-8");
|
||||
OnlineUserUtils.parseParameters(values, url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -750,505 +738,6 @@ public class OnlineUserProxy {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在访客启动聊天窗口后,建立访客和坐席的连接关系
|
||||
* 1)在inMessage中如果绑定了坐席,就联系该坐席服务这个访客
|
||||
* 2)在inMessage中如果没有绑定坐席,就寻找一个符合要求的坐席(比如技能组)
|
||||
* 找不到坐席时进入排队,找到坐席通知双方加入会话
|
||||
*
|
||||
* @param agentUser 预连接的坐席人员
|
||||
* @return
|
||||
*/
|
||||
private static Optional<Message> dispatchAgentService(final AgentUser agentUser) {
|
||||
Message result = new Message();
|
||||
AgentService agentService = null;
|
||||
result.setOrgi(agentUser.getOrgi());
|
||||
result.setMessageType(MainContext.MessageType.STATUS.toString());
|
||||
result.setAgentUser(agentUser);
|
||||
|
||||
/**
|
||||
* 首先交由 IMR处理 MESSAGE指令 , 如果当前用户是在 坐席对话列表中, 则直接推送给坐席,如果不在,则执行 IMR
|
||||
*/
|
||||
if (agentUser != null && StringUtils.isNotBlank(agentUser.getStatus())) {
|
||||
switch (MainContext.AgentUserStatusEnum.toValue(agentUser.getStatus())) {
|
||||
case INQUENE:
|
||||
int queueIndex = AutomaticServiceDist.getQueueIndex(
|
||||
agentUser.getAgentno(), agentUser.getOrgi(),
|
||||
agentUser.getSkill());
|
||||
result.setMessage(
|
||||
AutomaticServiceDist.getQueneMessage(queueIndex, agentUser.getChannel(),
|
||||
agentUser.getOrgi()));
|
||||
break;
|
||||
case INSERVICE:
|
||||
// 该访客与坐席正在服务中,忽略新的连接
|
||||
logger.info(
|
||||
"[handler] agent user {} is in service, userid {}, agentno {}", agentUser.getId(),
|
||||
agentUser.getUserid(), agentUser.getAgentno());
|
||||
break;
|
||||
}
|
||||
} else if ((agentService = AutomaticServiceDist.allotAgent(
|
||||
agentUser, agentUser.getOrgi())) != null) {
|
||||
/**
|
||||
* 找到空闲坐席,如果未找到坐席,则将该用户放入到 排队队列
|
||||
*/
|
||||
switch (MainContext.AgentUserStatusEnum.toValue(agentService.getStatus())) {
|
||||
case INSERVICE:
|
||||
result.setMessage(
|
||||
AutomaticServiceDist.getSuccessMessage(agentService, agentUser.getChannel(),
|
||||
agentUser.getOrgi()));
|
||||
|
||||
// TODO 判断 INSERVICE 时,agentService 对应的 agentUser
|
||||
logger.info("[handle] agent service: agentno {}", agentService.getAgentno());
|
||||
logger.info("[handle] agent service: agentuser id {}", agentService.getAgentuserid());
|
||||
logger.info(
|
||||
"[handle] agent service: user {}, channle {}", agentService.getUserid(),
|
||||
agentService.getChannel());
|
||||
logger.info("[handle] agent service: status {}, queue index {}", agentService.getStatus(),
|
||||
agentService.getQueneindex());
|
||||
|
||||
if (StringUtils.isNotBlank(agentService.getAgentuserid())) {
|
||||
getAgentUserProxy().findOne(agentService.getAgentuserid()).ifPresent(p -> {
|
||||
result.setAgentUser(p);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO 如果是 INSERVICE 那么 agentService.getAgentuserid 就一定不能为空?
|
||||
// // TODO 此处需要考虑 agentService.getAgentuserid 为空的情况
|
||||
// // 那么什么情况下,agentService.getAgentuserid为空?
|
||||
// if (StringUtils.isNotBlank(agentService.getAgentuserid())) {
|
||||
// logger.info("[handle] set Agent User with agentUser Id {}", agentService.getAgentuserid());
|
||||
// getAgentUserProxy().findOne(agentService.getAgentuserid()).ifPresent(p -> {
|
||||
// outMessage.setChannelMessage(p);
|
||||
// });
|
||||
// } else {
|
||||
// logger.info("[handle] agent user id is null.");
|
||||
// }
|
||||
break;
|
||||
case INQUENE:
|
||||
if (agentService.getQueneindex() > 0) {
|
||||
// 当前有坐席,要排队
|
||||
result.setMessage(AutomaticServiceDist.getQueneMessage(
|
||||
agentService.getQueneindex(),
|
||||
agentUser.getChannel(),
|
||||
agentUser.getOrgi()));
|
||||
} else {
|
||||
// TODO 什么是否返回 noAgentMessage, 是否在是 INQUENE 时 getQueneindex == 0
|
||||
// 当前没有坐席,要留言
|
||||
result.setMessage(AutomaticServiceDist.getNoAgentMessage(
|
||||
agentService.getQueneindex(),
|
||||
agentUser.getChannel(),
|
||||
agentUser.getOrgi()));
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
logger.info("[handler] should not happen for new onlineUser service request.");
|
||||
default:
|
||||
}
|
||||
|
||||
result.setAgentService(agentService);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为新增加的访客会话分配坐席和开启访客与坐席的对话
|
||||
*
|
||||
* @param onlineUserId
|
||||
* @param nickname
|
||||
* @param orgi
|
||||
* @param session
|
||||
* @param appid
|
||||
* @param ip
|
||||
* @param osname
|
||||
* @param browser
|
||||
* @param headimg
|
||||
* @param ipdata
|
||||
* @param channel
|
||||
* @param skill
|
||||
* @param agent
|
||||
* @param title
|
||||
* @param url
|
||||
* @param traceid
|
||||
* @param eventid
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Message allocateAgentService(
|
||||
final String onlineUserId,
|
||||
final String nickname,
|
||||
final String orgi,
|
||||
final String session,
|
||||
final String appid,
|
||||
final String ip,
|
||||
final String osname,
|
||||
final String browser,
|
||||
final String headimg,
|
||||
final IP ipdata,
|
||||
final String channel,
|
||||
final String skill,
|
||||
final String agent,
|
||||
final String title,
|
||||
final String url,
|
||||
final String traceid,
|
||||
final String eventid) {
|
||||
logger.info(
|
||||
"[allocateAgentService] user {}, appid {}, agent {}, skill {}, nickname {}", onlineUserId, appid,
|
||||
agent,
|
||||
skill,
|
||||
nickname);
|
||||
// 坐席服务请求,分配 坐席
|
||||
final Message result = new Message();
|
||||
|
||||
/**
|
||||
* NOTE AgentUser代表一次会话记录,在上一个会话结束,并且由坐席人员点击"清除"后,会从数据库中删除
|
||||
* 此处查询到的,可能是之前的会话。其状态需要验证,所以不一定是由TA来服务本次会话。
|
||||
*/
|
||||
AgentUser agentUser = getCache().findOneAgentUserByUserIdAndOrgi(onlineUserId, orgi).orElseGet(() -> {
|
||||
/**
|
||||
* NOTE 新创建的AgentUser不需要设置Status和Agentno
|
||||
* 因为两个值在后面会检查,如果存在则不会申请新的Agent
|
||||
*/
|
||||
AgentUser p = new AgentUser(
|
||||
onlineUserId,
|
||||
channel,
|
||||
onlineUserId,
|
||||
nickname,
|
||||
orgi,
|
||||
appid);
|
||||
logger.info("[allocateAgentService] create new agent user id {}", p.getId());
|
||||
return p;
|
||||
});
|
||||
|
||||
logger.info("[allocateAgentService] resolve agent user id {}", agentUser.getId());
|
||||
|
||||
agentUser.setUsername(resolveAgentUsername(agentUser, nickname));
|
||||
|
||||
agentUser.setOsname(osname);
|
||||
agentUser.setBrowser(browser);
|
||||
agentUser.setAppid(appid);
|
||||
agentUser.setSessionid(session);
|
||||
|
||||
if (ipdata != null) {
|
||||
logger.info("[allocateAgentService] set IP data for agentUser {}", agentUser.getId());
|
||||
agentUser.setCountry(ipdata.getCountry());
|
||||
agentUser.setProvince(ipdata.getProvince());
|
||||
agentUser.setCity(ipdata.getCity());
|
||||
if (StringUtils.isNotBlank(ip)) {
|
||||
agentUser.setRegion(ipdata.toString() + "[" + ip + "]");
|
||||
} else {
|
||||
agentUser.setRegion(ipdata.toString());
|
||||
}
|
||||
}
|
||||
|
||||
agentUser.setOwner(eventid); // 智能IVR的 EventID
|
||||
agentUser.setHeadimgurl(headimg);
|
||||
agentUser.setStatus(null); // 修改状态
|
||||
agentUser.setTitle(title);
|
||||
agentUser.setUrl(url);
|
||||
agentUser.setTraceid(traceid);
|
||||
|
||||
/**
|
||||
* 访客新上线的请求
|
||||
*/
|
||||
/**
|
||||
* 技能组 和 坐席
|
||||
*/
|
||||
if (StringUtils.isNotBlank(skill)) {
|
||||
// 绑定技能组
|
||||
agentUser.setSkill(skill);
|
||||
} else if (StringUtils.isNotBlank(agent)) {
|
||||
// 绑定坐席
|
||||
agentUser.setAgentno(agent);
|
||||
agentUser.setAgentname(getUserRes().findOne(agent).getUname());
|
||||
} else {
|
||||
/**
|
||||
* NOTE 处理和"邀请"的关联
|
||||
* 要关联访客与发出邀请的坐席
|
||||
* 当访客接受邀请后,让该坐席与之对话
|
||||
* 方案是从数据库InviteRecord查询最近10条,然后时间降序匹配在线客服进行发送
|
||||
*/
|
||||
// 根据邀请信息锁定目标坐席
|
||||
// 从邀请信息中查看,是否有Agent
|
||||
// 增加时间校验,如果这个邀请是很久之前的,就忽略
|
||||
logger.info("[allocateAgentService] process invite events");
|
||||
final Date threshold = new Date(System.currentTimeMillis() - Constants.WEBIM_AGENT_INVITE_TIMEOUT);
|
||||
Page<InviteRecord> inviteRecords = getInviteRecordRes().findByUseridAndOrgiAndResultAndCreatetimeGreaterThan(
|
||||
onlineUserId,
|
||||
orgi,
|
||||
MainContext.OnlineUserInviteStatus.ACCEPT.toString(),
|
||||
threshold,
|
||||
new PageRequest(0, 10, Sort.Direction.DESC, "createtime"));
|
||||
logger.info("[allocateAgentService] get inviteRecords size {}", inviteRecords.getContent().size());
|
||||
|
||||
for (final InviteRecord inviteRecord : inviteRecords.getContent()) {
|
||||
// most recent invite
|
||||
// 判断该坐席是否在线,就绪
|
||||
// TODO 此处还需要限制技能组,即在有请求技能组的前提下,确认该坐席属于这个技能组
|
||||
final AgentStatus as = cache.findOneAgentStatusByAgentnoAndOrig(
|
||||
inviteRecord.getAgentno(), inviteRecord.getOrgi());
|
||||
if (as != null &&
|
||||
StringUtils.equals(MainContext.AgentStatusEnum.READY.toString(), as.getStatus()) &&
|
||||
(!as.isBusy())) { // 该坐席就绪且置闲
|
||||
logger.info(
|
||||
"[allocateAgentService] find an agent {} for user {} with InviteRecord {}",
|
||||
inviteRecord.getAgentno(), inviteRecord.getUserid(), inviteRecord.getId());
|
||||
agentUser.setAgentno(inviteRecord.getAgentno());
|
||||
agentUser.setAgentname(getUserRes().findOne(inviteRecord.getAgentno()).getUname());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(orgi);
|
||||
AgentReport report;
|
||||
if (StringUtils.isNotBlank(skill)) {
|
||||
report = AutomaticServiceDist.getAgentReport(skill, orgi);
|
||||
} else {
|
||||
report = AutomaticServiceDist.getAgentReport(orgi);
|
||||
}
|
||||
|
||||
if (sessionConfig.isHourcheck() && !MainUtils.isInWorkingHours(sessionConfig.getWorkinghours())) {
|
||||
result.setMessage(sessionConfig.getNotinwhmsg());
|
||||
} else {
|
||||
if (report.getAgents() == 0) {
|
||||
result.setNoagent(true);
|
||||
}
|
||||
// 寻找或为绑定服务访客的坐席,建立双方通话
|
||||
dispatchAgentService(agentUser).ifPresent(p -> {
|
||||
result.setMessage(p.getMessage());
|
||||
// 为新访客找到了服务坐席
|
||||
result.setAgentService(p.getAgentService());
|
||||
result.setChannelMessage(p.getAgentUser());
|
||||
result.setAgentUser(p.getAgentUser());
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定该访客的名字,优先级
|
||||
* 1. 如果AgentUser username 与 nickName 不一致,则用 agentUser username
|
||||
* 2. 如果AgentUser username 与 nickName 一致,则查找 AgentUserContact对应的联系人
|
||||
* 2.1 如果联系人存在,则用联系人的名字
|
||||
* 2.2 如果联系人不存在,则使用 nickName
|
||||
*
|
||||
* TODO 此处有一些问题:如果联系人更新了名字,那么么后面TA的会话用的还是旧的名字,
|
||||
* 所以,在更新联系人名字的时候,也应更新其对应的AgentUser里面的名字
|
||||
* @param agentUser
|
||||
* @param nickname
|
||||
* @return
|
||||
*/
|
||||
private static String resolveAgentUsername(final AgentUser agentUser, final String nickname) {
|
||||
if (!StringUtils.equals(agentUser.getUsername(), nickname)) {
|
||||
return agentUser.getUsername();
|
||||
}
|
||||
|
||||
// 查找会话联系人关联表
|
||||
AgentUserContacts agentUserContact = getAgentUserContactsRes().findOneByUseridAndOrgi(
|
||||
agentUser.getUserid(), agentUser.getOrgi()).orElse(null);
|
||||
if (agentUserContact != null) {
|
||||
Contacts contact = getContactsRes().findOneById(agentUserContact.getContactsid()).orElseGet(null);
|
||||
if (contact != null) {
|
||||
return contact.getName();
|
||||
}
|
||||
}
|
||||
|
||||
return nickname;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userid
|
||||
* @param orgi
|
||||
* @param session
|
||||
* @param appid
|
||||
* @param ip
|
||||
* @param osname
|
||||
* @param browser
|
||||
* @param channel
|
||||
* @param skill
|
||||
* @param agent
|
||||
* @param nickname
|
||||
* @param title
|
||||
* @param url
|
||||
* @param traceid
|
||||
* @param initiator
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
|
||||
public static Message allocateAgentService(
|
||||
String userid,
|
||||
String orgi,
|
||||
String session,
|
||||
String appid,
|
||||
String ip,
|
||||
String osname,
|
||||
String browser,
|
||||
String channel,
|
||||
String skill,
|
||||
String agent,
|
||||
String nickname,
|
||||
String title,
|
||||
String url,
|
||||
String traceid,
|
||||
String initiator) {
|
||||
IP ipdata = null;
|
||||
if (StringUtils.isNotBlank(ip)) {
|
||||
ipdata = IPTools.getInstance().findGeography(ip);
|
||||
logger.info("[allocateAgentService] find ipdata {}", ipdata.toString());
|
||||
} else {
|
||||
logger.info("[allocateAgentService] no IP present");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(nickname)) {
|
||||
logger.info("[allocateAgentService] reset nickname as it does not present.");
|
||||
nickname = "Guest_" + userid;
|
||||
}
|
||||
|
||||
return allocateAgentService(
|
||||
userid, nickname, orgi, session, appid, ip, osname, browser, "", ipdata, channel, skill, agent, title,
|
||||
url, traceid, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create agentuser object for Wechat Channel
|
||||
*
|
||||
* @param openid
|
||||
* @param nickname
|
||||
* @param orgi
|
||||
* @param session
|
||||
* @param appid
|
||||
* @param headimg
|
||||
* @param country
|
||||
* @param province
|
||||
* @param city
|
||||
* @param channel
|
||||
* @param skill
|
||||
* @param agent
|
||||
* @param initiator
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Message allocateAgentService(
|
||||
String openid,
|
||||
String nickname,
|
||||
String orgi,
|
||||
String session,
|
||||
String appid,
|
||||
String headimg,
|
||||
String country,
|
||||
String province,
|
||||
String city,
|
||||
String channel,
|
||||
String skill,
|
||||
String agent,
|
||||
String initiator) throws Exception {
|
||||
IP ipdata = new IP();
|
||||
ipdata.setCountry(country);
|
||||
ipdata.setProvince(province);
|
||||
ipdata.setCity(city);
|
||||
return allocateAgentService(
|
||||
openid, nickname, orgi, session, appid, null, null, null, headimg, ipdata, channel, skill, agent, null,
|
||||
null, null, session);
|
||||
}
|
||||
|
||||
public static void parseParameters(
|
||||
Map<String, String[]> map, String data,
|
||||
String encoding) throws UnsupportedEncodingException {
|
||||
if ((data == null) || (data.length() <= 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
if (encoding == null) {
|
||||
bytes = data.getBytes();
|
||||
} else {
|
||||
bytes = data.getBytes(encoding);
|
||||
}
|
||||
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
}
|
||||
parseParameters(map, bytes, encoding);
|
||||
}
|
||||
|
||||
public static void parseParameters(
|
||||
Map<String, String[]> map, byte[] data,
|
||||
String encoding) throws UnsupportedEncodingException {
|
||||
if ((data != null) && (data.length > 0)) {
|
||||
int ix = 0;
|
||||
int ox = 0;
|
||||
String key = null;
|
||||
String value = null;
|
||||
while (ix < data.length) {
|
||||
byte c = data[(ix++)];
|
||||
switch ((char) c) {
|
||||
case '&':
|
||||
value = new String(data, 0, ox, encoding);
|
||||
if (key != null) {
|
||||
putMapEntry(map, key, value);
|
||||
key = null;
|
||||
}
|
||||
ox = 0;
|
||||
break;
|
||||
case '=':
|
||||
if (key == null) {
|
||||
key = new String(data, 0, ox, encoding);
|
||||
ox = 0;
|
||||
} else {
|
||||
data[(ox++)] = c;
|
||||
}
|
||||
break;
|
||||
case '+':
|
||||
data[(ox++)] = 32;
|
||||
break;
|
||||
case '%':
|
||||
data[(ox++)] = (byte) ((convertHexDigit(data[(ix++)]) << 4) + convertHexDigit(data[(ix++)]));
|
||||
|
||||
break;
|
||||
default:
|
||||
data[(ox++)] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
value = new String(data, 0, ox, encoding);
|
||||
putMapEntry(map, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void putMapEntry(
|
||||
Map<String, String[]> map, String name,
|
||||
String value) {
|
||||
String[] newValues = null;
|
||||
String[] oldValues = (String[]) (String[]) map.get(name);
|
||||
if (oldValues == null) {
|
||||
newValues = new String[1];
|
||||
newValues[0] = value;
|
||||
} else {
|
||||
newValues = new String[oldValues.length + 1];
|
||||
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
|
||||
newValues[oldValues.length] = value;
|
||||
}
|
||||
map.put(name, newValues);
|
||||
}
|
||||
|
||||
private static byte convertHexDigit(byte b) {
|
||||
if ((b >= 48) && (b <= 57)) {
|
||||
return (byte) (b - 48);
|
||||
}
|
||||
if ((b >= 97) && (b <= 102)) {
|
||||
return (byte) (b - 97 + 10);
|
||||
}
|
||||
if ((b >= 65) && (b <= 70)) {
|
||||
return (byte) (b - 65 + 10);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邀请
|
||||
*
|
||||
@ -1357,7 +846,8 @@ public class OnlineUserProxy {
|
||||
public static List<OtherMessageItem> search(String q, String orgi, User user) throws IOException, TemplateException {
|
||||
List<OtherMessageItem> otherMessageItemList = null;
|
||||
String param = "";
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(orgi);
|
||||
SessionConfig sessionConfig = MainContext.getACDServiceRouter().getAcdPolicyService().initSessionConfig(
|
||||
orgi);
|
||||
if (StringUtils.isNotBlank(sessionConfig.getOqrsearchurl())) {
|
||||
Template templet = MainUtils.getTemplate(sessionConfig.getOqrsearchinput());
|
||||
Map<String, Object> values = new HashMap<String, Object>();
|
||||
@ -1416,7 +906,8 @@ public class OnlineUserProxy {
|
||||
public static OtherMessageItem detail(String id, String orgi, User user) throws IOException, TemplateException {
|
||||
OtherMessageItem otherMessageItem = null;
|
||||
String param = "";
|
||||
SessionConfig sessionConfig = AutomaticServiceDist.initSessionConfig(orgi);
|
||||
SessionConfig sessionConfig = MainContext.getACDServiceRouter().getAcdPolicyService().initSessionConfig(
|
||||
orgi);
|
||||
if (StringUtils.isNotBlank(sessionConfig.getOqrdetailinput())) {
|
||||
Template templet = MainUtils.getTemplate(sessionConfig.getOqrdetailinput());
|
||||
Map<String, Object> values = new HashMap<String, Object>();
|
||||
@ -1510,13 +1001,6 @@ public class OnlineUserProxy {
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static AgentUserProxy getAgentUserProxy() {
|
||||
if (agentUserProxy == null) {
|
||||
agentUserProxy = MainContext.getContext().getBean(AgentUserProxy.class);
|
||||
}
|
||||
return agentUserProxy;
|
||||
}
|
||||
|
||||
private static ConsultInviteRepository getConsultInviteRes() {
|
||||
if (consultInviteRes == null) {
|
||||
consultInviteRes = MainContext.getContext().getBean(ConsultInviteRepository.class);
|
||||
@ -1524,13 +1008,6 @@ public class OnlineUserProxy {
|
||||
return consultInviteRes;
|
||||
}
|
||||
|
||||
private static InviteRecordRepository getInviteRecordRes() {
|
||||
if (inviteRecordRes == null) {
|
||||
inviteRecordRes = MainContext.getContext().getBean(InviteRecordRepository.class);
|
||||
}
|
||||
return inviteRecordRes;
|
||||
}
|
||||
|
||||
private static OnlineUserHisRepository getOnlineUserHisRes() {
|
||||
if (onlineUserHisRes == null) {
|
||||
onlineUserHisRes = MainContext.getContext().getBean(OnlineUserHisRepository.class);
|
||||
|
@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.chatopera.cc.schedule;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.acd.ACDPolicyService;
|
||||
import com.chatopera.cc.acd.ACDServiceRouter;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
@ -42,7 +43,6 @@ import org.springframework.scheduling.annotation.Scheduled;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@ -50,6 +50,12 @@ public class WebIMTask {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(WebIMTask.class);
|
||||
|
||||
@Autowired
|
||||
private ACDPolicyService acdPolicyService;
|
||||
|
||||
@Autowired
|
||||
private ACDServiceRouter acdServiceRouter;
|
||||
|
||||
@Autowired
|
||||
private AgentUserTaskRepository agentUserTaskRes;
|
||||
|
||||
@ -70,7 +76,7 @@ public class WebIMTask {
|
||||
|
||||
@Scheduled(fixedDelay = 5000, initialDelay = 20000) // 处理超时消息,每5秒执行一次
|
||||
public void task() {
|
||||
final List<SessionConfig> sessionConfigList = AutomaticServiceDist.initSessionConfigList();
|
||||
final List<SessionConfig> sessionConfigList = acdPolicyService.initSessionConfigList();
|
||||
if (sessionConfigList != null && sessionConfigList.size() > 0 && MainContext.getContext() != null) {
|
||||
for (final SessionConfig sessionConfig : sessionConfigList) {
|
||||
if (sessionConfig.isSessiontimeout()) { //设置了启用 超时提醒
|
||||
@ -104,7 +110,7 @@ public class WebIMTask {
|
||||
sessionConfig.getServicename(),
|
||||
p, agentStatus, task);
|
||||
try {
|
||||
AutomaticServiceDist.serviceFinish(p, task.getOrgi());
|
||||
acdServiceRouter.serviceFinish(p, task.getOrgi());
|
||||
} catch (Exception e) {
|
||||
logger.warn("[task] exception: ", e);
|
||||
}
|
||||
@ -130,7 +136,7 @@ public class WebIMTask {
|
||||
sessionConfig, sessionConfig.getRetimeoutmsg(), agentStatus.getUsername(),
|
||||
p, agentStatus, task);
|
||||
try {
|
||||
AutomaticServiceDist.serviceFinish(p, task.getOrgi());
|
||||
acdServiceRouter.serviceFinish(p, task.getOrgi());
|
||||
} catch (Exception e) {
|
||||
logger.warn("[task] exception: ", e);
|
||||
}
|
||||
@ -152,7 +158,7 @@ public class WebIMTask {
|
||||
sessionConfig, sessionConfig.getQuenetimeoutmsg(), sessionConfig.getServicename(),
|
||||
p, null, task);
|
||||
try {
|
||||
AutomaticServiceDist.serviceFinish(p, task.getOrgi());
|
||||
acdServiceRouter.serviceFinish(p, task.getOrgi());
|
||||
} catch (Exception e) {
|
||||
logger.warn("[task] exception: ", e);
|
||||
}
|
||||
@ -165,11 +171,11 @@ public class WebIMTask {
|
||||
|
||||
@Scheduled(fixedDelay = 5000, initialDelay = 20000) // 每5秒执行一次
|
||||
public void agent() {
|
||||
List<SessionConfig> sessionConfigList = AutomaticServiceDist.initSessionConfigList();
|
||||
List<SessionConfig> sessionConfigList = acdPolicyService.initSessionConfigList();
|
||||
if (sessionConfigList != null && sessionConfigList.size() > 0) {
|
||||
for (final SessionConfig sessionConfig : sessionConfigList) {
|
||||
// ? 为什么还要重新取一次?
|
||||
// sessionConfig = AutomaticServiceDist.initSessionConfig(sessionConfig.getOrgi());
|
||||
// sessionConfig = automaticServiceDist.initSessionConfig(sessionConfig.getOrgi());
|
||||
if (sessionConfig != null && MainContext.getContext() != null && sessionConfig.isAgentreplaytimeout()) {
|
||||
List<AgentUserTask> agentUserTask = agentUserTaskRes.findByLastgetmessageLessThanAndStatusAndOrgi(
|
||||
MainUtils.getLastTime(sessionConfig.getAgenttimeout()),
|
||||
|
@ -16,14 +16,12 @@
|
||||
*/
|
||||
package com.chatopera.cc.socketio.handler;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainContext.ReceiverType;
|
||||
import com.chatopera.cc.basic.MainContext.MessageType;
|
||||
import com.chatopera.cc.basic.MainContext.ChannelType;
|
||||
import com.chatopera.cc.basic.MainContext.CallType;
|
||||
import com.chatopera.cc.basic.MainContext.ChannelType;
|
||||
import com.chatopera.cc.basic.MainContext.MessageType;
|
||||
import com.chatopera.cc.basic.MainContext.ReceiverType;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
import com.chatopera.cc.model.AgentService;
|
||||
import com.chatopera.cc.model.Contacts;
|
||||
import com.chatopera.cc.model.CousultInvite;
|
||||
import com.chatopera.cc.persistence.repository.AgentServiceRepository;
|
||||
@ -35,6 +33,8 @@ import com.chatopera.cc.socketio.message.ChatMessage;
|
||||
import com.chatopera.cc.socketio.message.Message;
|
||||
import com.chatopera.cc.socketio.util.HumanUtils;
|
||||
import com.chatopera.cc.socketio.util.IMServiceUtils;
|
||||
import com.chatopera.cc.util.IP;
|
||||
import com.chatopera.cc.util.IPTools;
|
||||
import com.corundumstudio.socketio.AckRequest;
|
||||
import com.corundumstudio.socketio.SocketIOClient;
|
||||
import com.corundumstudio.socketio.SocketIOServer;
|
||||
@ -47,7 +47,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class IMEventHandler {
|
||||
private final static Logger logger = LoggerFactory.getLogger(IMEventHandler.class);
|
||||
@ -71,27 +70,40 @@ public class IMEventHandler {
|
||||
final String user = client.getHandshakeData().getSingleUrlParam("userid");
|
||||
final String orgi = client.getHandshakeData().getSingleUrlParam("orgi");
|
||||
final String session = MainUtils.getContextID(client.getHandshakeData().getSingleUrlParam("session"));
|
||||
// 渠道标识
|
||||
final String appid = client.getHandshakeData().getSingleUrlParam("appid");
|
||||
// 要求目标坐席服务
|
||||
final String agent = client.getHandshakeData().getSingleUrlParam("agent");
|
||||
// 要求目标技能组服务
|
||||
final String skill = client.getHandshakeData().getSingleUrlParam("skill");
|
||||
// 是否是邀请后加入会话
|
||||
final boolean isInvite = StringUtils.equalsIgnoreCase(
|
||||
client.getHandshakeData().getSingleUrlParam("isInvite"), "true");
|
||||
|
||||
final String title = client.getHandshakeData().getSingleUrlParam("title");
|
||||
final String url = client.getHandshakeData().getSingleUrlParam("url");
|
||||
final String traceid = client.getHandshakeData().getSingleUrlParam("traceid");
|
||||
|
||||
final String nickname = client.getHandshakeData().getSingleUrlParam("nickname");
|
||||
String nickname = client.getHandshakeData().getSingleUrlParam("nickname");
|
||||
|
||||
final String osname = client.getHandshakeData().getSingleUrlParam("osname");
|
||||
final String browser = client.getHandshakeData().getSingleUrlParam("browser");
|
||||
|
||||
logger.info(
|
||||
"[onConnect] user {}, orgi {}, session {}, appid {}, agent {}, skill {}, title {}, url {}, traceid {}, nickname {}",
|
||||
user, orgi, session, appid, agent, skill, title, url, traceid, nickname);
|
||||
"[onConnect] user {}, orgi {}, session {}, appid {}, agent {}, skill {}, title {}, url {}, traceid {}, nickname {}, isInvite {}",
|
||||
user, orgi, session, appid, agent, skill, title, url, traceid, nickname, isInvite);
|
||||
|
||||
// save connection info
|
||||
client.set("session", session);
|
||||
client.set("userid", user);
|
||||
client.set("appid", appid);
|
||||
client.set("isInvite", isInvite);
|
||||
|
||||
// 保证传入的Nickname不是null
|
||||
if (StringUtils.isBlank(nickname)) {
|
||||
logger.info("[onConnect] reset nickname as it does not present.");
|
||||
nickname = "Guest_" + user;
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(user)) {
|
||||
InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress();
|
||||
@ -107,26 +119,35 @@ public class IMEventHandler {
|
||||
*/
|
||||
IMServiceUtils.shiftOpsType(user, orgi, MainContext.OptType.HUMAN);
|
||||
|
||||
IP ipdata = null;
|
||||
if ((StringUtils.isNotBlank(ip))) {
|
||||
ipdata = IPTools.getInstance().findGeography(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户进入到对话连接 , 排队用户请求 , 如果返回失败,
|
||||
* 表示当前坐席全忙,用户进入排队状态,当前提示信息 显示 当前排队的队列位置,
|
||||
* 不可进行对话,用户发送的消息作为留言处理
|
||||
*/
|
||||
Message agentServiceMessage = OnlineUserProxy.allocateAgentService(
|
||||
Message agentServiceMessage = MainContext.getACDServiceRouter().allocateAgentService(
|
||||
user,
|
||||
nickname,
|
||||
orgi,
|
||||
session,
|
||||
appid,
|
||||
ip,
|
||||
osname,
|
||||
browser,
|
||||
"",
|
||||
ipdata,
|
||||
MainContext.ChannelType.WEBIM.toString(),
|
||||
skill,
|
||||
agent,
|
||||
nickname,
|
||||
title,
|
||||
url,
|
||||
traceid,
|
||||
user,
|
||||
isInvite,
|
||||
MainContext.ChatInitiatorType.USER.toString());
|
||||
|
||||
if (agentServiceMessage != null && StringUtils.isNotBlank(
|
||||
@ -190,7 +211,7 @@ public class IMEventHandler {
|
||||
* 用户主动断开服务
|
||||
*/
|
||||
MainContext.getCache().findOneAgentUserByUserIdAndOrgi(user, orgi).ifPresent(p -> {
|
||||
AutomaticServiceDist.serviceFinish(p
|
||||
MainContext.getACDServiceRouter().serviceFinish(p
|
||||
, orgi);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
package com.chatopera.cc.socketio.message;
|
||||
|
||||
import com.chatopera.cc.model.*;
|
||||
import com.chatopera.compose4j.AbstractContext;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
@ -25,7 +26,7 @@ import java.util.List;
|
||||
/**
|
||||
* 发送消息的高级封装
|
||||
*/
|
||||
public class Message implements java.io.Serializable {
|
||||
public class Message extends AbstractContext {
|
||||
|
||||
public String id;
|
||||
private String orgi; // 租户
|
||||
|
@ -0,0 +1,77 @@
|
||||
package com.chatopera.cc.util;
|
||||
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
|
||||
public class OnlineUserUtils {
|
||||
|
||||
public static void parseParameters(
|
||||
Map<String, String[]> map, String data,
|
||||
String encoding) throws UnsupportedEncodingException {
|
||||
if ((data == null) || (data.length() <= 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
if (encoding == null) {
|
||||
bytes = data.getBytes();
|
||||
} else {
|
||||
bytes = data.getBytes(encoding);
|
||||
}
|
||||
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
}
|
||||
parseParameters(map, bytes, encoding);
|
||||
}
|
||||
|
||||
|
||||
public static void parseParameters(
|
||||
Map<String, String[]> map, byte[] data,
|
||||
String encoding) throws UnsupportedEncodingException {
|
||||
if ((data != null) && (data.length > 0)) {
|
||||
int ix = 0;
|
||||
int ox = 0;
|
||||
String key = null;
|
||||
String value = null;
|
||||
while (ix < data.length) {
|
||||
byte c = data[(ix++)];
|
||||
switch ((char) c) {
|
||||
case '&':
|
||||
value = new String(data, 0, ox, encoding);
|
||||
if (key != null) {
|
||||
MainUtils.putMapEntry(map, key, value);
|
||||
key = null;
|
||||
}
|
||||
ox = 0;
|
||||
break;
|
||||
case '=':
|
||||
if (key == null) {
|
||||
key = new String(data, 0, ox, encoding);
|
||||
ox = 0;
|
||||
} else {
|
||||
data[(ox++)] = c;
|
||||
}
|
||||
break;
|
||||
case '+':
|
||||
data[(ox++)] = 32;
|
||||
break;
|
||||
case '%':
|
||||
data[(ox++)] = (byte) ((MainUtils.convertHexDigit(
|
||||
data[(ix++)]) << 4) + MainUtils.convertHexDigit(data[(ix++)]));
|
||||
|
||||
break;
|
||||
default:
|
||||
data[(ox++)] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
value = new String(data, 0, ox, encoding);
|
||||
MainUtils.putMapEntry(map, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -563,7 +563,7 @@
|
||||
var hostname = location.hostname ;
|
||||
var protocol = window.location.protocol.replace(/:/g,'');
|
||||
var username = encodeURIComponent("${username}");
|
||||
var socket = io(protocol + '://'+hostname+':${port}/im/user?userid=${userid!''}&orgi=${orgi!''}&session=${sessionid!''}&appid=${appid!''}&osname=${(osname!'')?url}&browser=${(browser!'')?url}<#if skill??>&skill=${skill}</#if><#if username??>&nickname='+username+'</#if><#if agent??>&agent=${agent}</#if><#if title??>&title=${title?url}</#if><#if traceid??>&url=${url?url}</#if><#if traceid??>&traceid=${traceid}</#if>', {transports: ['websocket', 'polling']});
|
||||
var socket = io(protocol + '://'+hostname+':${port}/im/user?userid=${userid!''}&orgi=${orgi!''}&session=${sessionid!''}&appid=${appid!''}&osname=${(osname!'')?url}&browser=${(browser!'')?url}<#if skill??>&skill=${skill}</#if><#if username??>&nickname='+username+'</#if><#if agent??>&agent=${agent}</#if><#if title??>&title=${title?url}</#if><#if traceid??>&url=${url?url}</#if><#if traceid??>&traceid=${traceid}</#if><#if isInvite??>&isInvite=${isInvite}</#if>', {transports: ['websocket', 'polling']});
|
||||
|
||||
console.log('connect debug', protocol, hostname);
|
||||
|
||||
|
@ -146,6 +146,7 @@ location.reload();
|
||||
var protocol = window.location.protocol.replace(/:/g,'');
|
||||
<!--var protocol = "$!''}"-->
|
||||
var cskefu = {
|
||||
service: {agentno: null},
|
||||
time : new Date().getTime(),
|
||||
in: protocol + "://${hostname!''}<#if port?? && port != 80>:${port!''}</#if>/im/${appid!''}/userlist.html?appid=${appid!''}<#if aiid??>&aiid=${aiid}</#if>&orgi=${orgi!''}&client=${client}" ,
|
||||
url: protocol + "://${hostname!''}<#if port?? && port != 80>:${port!''}</#if>/im/online?appid=${appid!''}&orgi=${orgi!''}<#if aiid??>&aiid=${aiid}</#if>&client=${client}" ,
|
||||
@ -176,13 +177,15 @@ var cskefu = {
|
||||
if (xhr.readyState == 4) {
|
||||
var status = xhr.status;
|
||||
if (status >= 200 && status < 300) {
|
||||
var event = xhr.responseText ;
|
||||
var event = xhr.responseText;
|
||||
if(event && event.indexOf('invite') >= 0){
|
||||
cskefu.writeinvite() ;
|
||||
var agentno = event.substring(event.lastIndexOf(":") + 1).trim();
|
||||
cskefu.service.agentno = agentno;
|
||||
cskefu.writeinvite();
|
||||
}else if(event && event.indexOf('refuse') >= 0){
|
||||
cskefu.refuseInvite() ;
|
||||
}else if(event && event.indexOf('accept') >= 0){
|
||||
cskefu.acceptInvite() ;
|
||||
cskefu.acceptInvite();
|
||||
}
|
||||
if(success){
|
||||
success(event);
|
||||
@ -281,7 +284,7 @@ var cskefu = {
|
||||
"</div></div></#if>" ;
|
||||
<#if webimexist == true >
|
||||
append(document.body, '<div class="ukefu-im-point" id="ukefu-point" style="display:none;z-index:100000;font-family:14px \5FAE\8F6F\96C5\9ED1,Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;position: fixed;${position};<#if inviteData?? && inviteData.skill == true && inviteData.consult_skill_fixed == false>cursor:default;<#else>cursor: pointer;</#if>"> <div class="ukefu-point-theme${inviteData.consult_vsitorbtn_model!'1'} ukefu-theme-color theme1" style="${style}${theme}" id="ukefu-point-theme"><div class="ukefu-im-point-text" id="ukefu-im-point-text" style="cursor: pointer;${text};line-height: 23px;font-size: 15px;text-align: center;margin: 0 auto;"><i style="width:24px;height:24px;display: inline-block;font: normal normal normal 14px/1 FontAwesome;font-size: inherit;text-rendering: auto;-webkit-font-smoothing: antialiased;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAB60lEQVRIS7WV4TVsQRCEqyJABkSACBABLwJE8IjAigARIAMieCsCREAGjwja+eb03TO7O9ddrPm1Z29PV3dXdY31y8eL5I+ILUmbktYz/lXSs+2nofu9ABFBsjNJB5JWJT3MJNuR9CbpTtK5bUDnThMgIkaZnKSXtkkydyIC8BNJgI1sn88GzQFExI2kXUlHtsdDI+B7RBDPvUfbf+o7UwCZnHnv2qb9hU9EMEYKAuS4uzgByHapYqtvnkNoyRvE030Zaw0AScz7cijRZ9+Tv33b2xOAlOGjpLWvjmYWLEf1X9I2Mi4dRMRRtgVZ5WTgtaRX26cVmUj3lMtVzEPdeUTARVFfB4AsIbYG4Pe/0qbdxXERSaL7UapnKiYLIW5MTG8HGYjG6aAQlqNE+1RXVBYRUzH5H0SzF5MO2NqXJXHQ5dpAjbWKQL2jrSWo6MA2+zQlU2bOKOBi0MRaRVRq3OtcYHaT2YFDSQR8CSSTQ/itbXgpp+VFgCDbi5Z59ej+L6RKuqqTNwEqvWMbK2li97XxpSXwPhRTlPSOq7Zc99MHJxeQdt+6HcklYhdICmfonWKaZwgAh8RC8HmSXUjCjtcXtZQhAKrHGqiQUTynpTRfr1YLQwBomcq7HekdxbdG9JOF65XpMpLWOT4AygTtGd0Q7EsAAAAASUVORK5CYII=);"></i><br>${(inviteData.consult_vsitorbtn_content!'在线客服')?no_esc}</div></div><#if inviteData?? && inviteData.skill == true && inviteData.consult_skill_fixed == false>'+skillHtml+'</#if></div>');
|
||||
append(document.body, "<div class='ukefu-im-preview' id='ukefu-invite-dialog' style='z-index: 2147483648;display:none;height:177px;position: fixed;z-index:10000;<#if phone?? && phone == true>bottom:0px;width:100%;<#else>top:"+top+";width: 420px;left:"+left+";</#if>border-radius: 2px;padding: 0px;overflow: hidden;margin: 0px;${invitetheme!''}'> <div id='ukefu-cousult-invite-dialog' class='ukefu-im-preview-bar ukefu-cousult-invite-dialog ukefu-theme-color theme1' style='padding:0px;height:100%;width:100%;background-size: cover;background:url(${schema!'http'}://${hostname!''}<#if port?? && port != 80>:${port!''}</#if>/res/image.html?id=${inviteData.consult_invite_bg}) no-repeat'><a href='javascript:void(0)' onclick='cskefu.refuseInvite();'><span style='float: right;width: 20px;height: 20px;color: #ffffff;font-size: 20px;' >×</span></a><div class='ukefu-consult-body' id='ukefu_consult_body' style='width:100%;height:100%;'><div class='ukefu-cousult-invite-content' id='ukefu-cousult-invite-content' style='color: #FFFFFF;width: 70%;height: 70px;line-height: 35px;font-size: 13pt;font-weight: 200;word-wrap: break-word;word-break: break-all;position: absolute;top: 30px;left: 25%;'>${inviteData.consult_invite_content!'欢迎来到本网站,请问有什么可以帮您?'}</div></div><div class='ukefu-cousult-invite-btn' style='position: absolute;bottom: 0px;right: 0px;margin: 0px 10px 10px 0px;'><button class='theme1' id='invite-btn' style='border-color:#FFFFFF !important;color:#FFFFFF;display: inline-block;height: 38px;line-height: 38px;padding: 0 18px;background-color: #009688;color: #fff;white-space: nowrap;text-align: center;font-size: 14px;margin-right:10px;border: none;border-radius: 2px;cursor: pointer;opacity: .9;filter: alpha(opacity=90);${invitetheme!''}border:1px solid #FFFFFF;' onclick='cskefu.refuseInvite();' ><#if inviteData.consult_invite_later?? && inviteData.consult_invite_later != ''>${inviteData.consult_invite_later}<#else>稍后再说</#if></button><button class='' style='display: inline-block;height: 38px;line-height: 38px;padding: 0 18px;background-color: #009688;color: #fff;white-space: nowrap;text-align: center;font-size: 14px;border: none;border-radius: 2px;cursor: pointer;opacity: .9;filter: alpha(opacity=90);background-color:#FFFFFF;color:#333333;' onclick='cskefu.openChatDialog();'><#if inviteData.consult_invite_accept?? && inviteData.consult_invite_accept!=''>${inviteData.consult_invite_accept}<#else>现在咨询</#if></button></div></div></div>");
|
||||
append(document.body, "<div class='ukefu-im-preview' id='ukefu-invite-dialog' style='z-index: 2147483648;display:none;height:177px;position: fixed;z-index:10000;<#if phone?? && phone == true>bottom:0px;width:100%;<#else>top:"+top+";width: 420px;left:"+left+";</#if>border-radius: 2px;padding: 0px;overflow: hidden;margin: 0px;${invitetheme!''}'> <div id='ukefu-cousult-invite-dialog' class='ukefu-im-preview-bar ukefu-cousult-invite-dialog ukefu-theme-color theme1' style='padding:0px;height:100%;width:100%;background-size: cover;background:url(${schema!'http'}://${hostname!''}<#if port?? && port != 80>:${port!''}</#if>/res/image.html?id=${inviteData.consult_invite_bg}) no-repeat'><a href='javascript:void(0)' onclick='cskefu.refuseInvite();'><span style='float: right;width: 20px;height: 20px;color: #ffffff;font-size: 20px;' >×</span></a><div class='ukefu-consult-body' id='ukefu_consult_body' style='width:100%;height:100%;'><div class='ukefu-cousult-invite-content' id='ukefu-cousult-invite-content' style='color: #FFFFFF;width: 70%;height: 70px;line-height: 35px;font-size: 13pt;font-weight: 200;word-wrap: break-word;word-break: break-all;position: absolute;top: 30px;left: 25%;'>${inviteData.consult_invite_content!'欢迎来到本网站,请问有什么可以帮您?'}</div></div><div class='ukefu-cousult-invite-btn' style='position: absolute;bottom: 0px;right: 0px;margin: 0px 10px 10px 0px;'><button class='theme1' id='invite-btn' style='border-color:#FFFFFF !important;color:#FFFFFF;display: inline-block;height: 38px;line-height: 38px;padding: 0 18px;background-color: #009688;color: #fff;white-space: nowrap;text-align: center;font-size: 14px;margin-right:10px;border: none;border-radius: 2px;cursor: pointer;opacity: .9;filter: alpha(opacity=90);${invitetheme!''}border:1px solid #FFFFFF;' onclick='cskefu.refuseInvite();' ><#if inviteData.consult_invite_later?? && inviteData.consult_invite_later != ''>${inviteData.consult_invite_later}<#else>稍后再说</#if></button><button class='' style='display: inline-block;height: 38px;line-height: 38px;padding: 0 18px;background-color: #009688;color: #fff;white-space: nowrap;text-align: center;font-size: 14px;border: none;border-radius: 2px;cursor: pointer;opacity: .9;filter: alpha(opacity=90);background-color:#FFFFFF;color:#333333;' onclick='openInviteChatDialog();'><#if inviteData.consult_invite_accept?? && inviteData.consult_invite_accept!=''>${inviteData.consult_invite_accept}<#else>现在咨询</#if></button></div></div></div>");
|
||||
</#if>
|
||||
<#if inviteData?? && inviteData.skill == true && inviteData.consult_skill_fixed == false>
|
||||
document.getElementById("ukefu-im-point-text").onclick=function(){
|
||||
@ -419,9 +422,17 @@ document.getElementById("ukefu-point").style.display = "block" ;
|
||||
|
||||
}
|
||||
|
||||
function openAgentChatDialog(url){
|
||||
return cskefu.openChatDialogWithURL(url) ;
|
||||
// 邀请聊天
|
||||
function openInviteChatDialog(){
|
||||
var url = cskefu.chat + "&agent=" + cskefu.service.agentno + "&isInvite=true";
|
||||
return cskefu.openChatDialogWithURL(url);
|
||||
}
|
||||
|
||||
// 技能组或坐席聊天
|
||||
function openAgentChatDialog(url){
|
||||
return cskefu.openChatDialogWithURL(url);
|
||||
}
|
||||
|
||||
Fingerprint2.get({}, function(components){
|
||||
var glue = components.map(function (component) { return component.value })
|
||||
cskefuOnlineUserId = Fingerprint2.x64hash128(glue.join(''), 31)
|
||||
|
@ -0,0 +1,32 @@
|
||||
package com.chatopera.cc.acd;
|
||||
|
||||
import com.chatopera.cc.model.AgentUser;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.TestSuite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ACDComposeContextTest extends TestCase {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ACDComposeContextTest.class);
|
||||
|
||||
public ACDComposeContextTest(String testName) {
|
||||
super(testName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the suite of tests being tested
|
||||
*/
|
||||
public static Test suite() {
|
||||
return new TestSuite(ACDComposeContextTest.class);
|
||||
}
|
||||
|
||||
public void testSetAndGet(){
|
||||
ACDComposeContext ctx = new ACDComposeContext();
|
||||
ctx.setAgentUser(new AgentUser());
|
||||
ctx.getAgentUser().setOrgi("foo");
|
||||
assertEquals(ctx.getAgentUser().getOrgi(), "foo");
|
||||
}
|
||||
|
||||
}
|
@ -43,4 +43,5 @@ public class MainContextTest extends TestCase {
|
||||
assertEquals(MainContext.ChannelType.WEBIM.toString(), "webim");
|
||||
assertEquals(MainContext.ChannelType.toValue("webim"), MainContext.ChannelType.WEBIM);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ services:
|
||||
restart: always
|
||||
ports:
|
||||
- "${CC_WEB_PORT:-8035}:8035"
|
||||
- "${CC_SOCKET_PORT:-8036}:8036"
|
||||
- "8036:8036"
|
||||
volumes:
|
||||
- ./contact-center/data:/data
|
||||
- ./contact-center/logs:/logs
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.chatopera.cc.plugins.chatbot;
|
||||
|
||||
import com.chatopera.cc.acd.AutomaticServiceDist;
|
||||
import com.chatopera.cc.basic.Constants;
|
||||
import com.chatopera.cc.basic.MainContext;
|
||||
import com.chatopera.cc.basic.MainUtils;
|
||||
@ -209,7 +208,7 @@ public class ChatbotEventHandler {
|
||||
agentUser.setCity(onlineUser.getCity());
|
||||
agentUser.setProvince(onlineUser.getProvince());
|
||||
agentUser.setCountry(onlineUser.getCountry());
|
||||
AgentService agentService = AutomaticServiceDist.processChatbotService(
|
||||
AgentService agentService = MainContext.getACDServiceRouter().getAcdChatbotService().processChatbotService(
|
||||
invite != null ? invite.getAiname() : "机器人客服", agentUser, orgi);
|
||||
agentUser.setAgentserviceid(agentService.getId());
|
||||
|
||||
@ -236,7 +235,7 @@ public class ChatbotEventHandler {
|
||||
OnlineUser onlineUser = MainContext.getCache().findOneOnlineUserByUserIdAndOrgi(user, orgi);
|
||||
|
||||
MainContext.getCache().findOneAgentUserByUserIdAndOrgi(user, orgi).ifPresent(p -> {
|
||||
AutomaticServiceDist.processChatbotService(null, p, orgi);
|
||||
MainContext.getACDServiceRouter().getAcdChatbotService().processChatbotService(null, p, orgi);
|
||||
|
||||
MainContext.getCache().deleteAgentUserByUserIdAndOrgi(user, orgi);
|
||||
MainContext.getCache().deleteOnlineUserByIdAndOrgi(user, orgi);
|
||||
@ -271,7 +270,9 @@ public class ChatbotEventHandler {
|
||||
String user = client.get("userid");
|
||||
String sessionid = client.get("session");
|
||||
String appid = client.get("appid");
|
||||
logger.info("[onEvent] message: session {}, aiid {}, userid {}, dataType {}, appid {}, orgi {}", sessionid, aiid, user, data.getType(), appid, orgi);
|
||||
logger.info(
|
||||
"[onEvent] message: session {}, aiid {}, userid {}, dataType {}, appid {}, orgi {}", sessionid, aiid,
|
||||
user, data.getType(), appid, orgi);
|
||||
|
||||
// ignore event if dataType is not message.
|
||||
if (!StringUtils.equals(data.getType(), Constants.IM_MESSAGE_TYPE_MESSAGE)) {
|
||||
|
20
scripts/plugins.install.all.sh
Executable file
20
scripts/plugins.install.all.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#! /bin/bash
|
||||
###########################################
|
||||
#
|
||||
###########################################
|
||||
|
||||
# constants
|
||||
baseDir=$(cd `dirname "$0"`;pwd)
|
||||
# functions
|
||||
|
||||
# main
|
||||
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
|
||||
cd $baseDir/..
|
||||
|
||||
if [ -d ./private/plugins ]; then
|
||||
./private/plugins/scripts/install-all.sh
|
||||
fi
|
||||
|
||||
if [ -d ./public/plugins ]; then
|
||||
./public/plugins/scripts/install-all.sh
|
||||
fi
|
20
scripts/plugins.uninstall.all.sh
Executable file
20
scripts/plugins.uninstall.all.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#! /bin/bash
|
||||
###########################################
|
||||
#
|
||||
###########################################
|
||||
|
||||
# constants
|
||||
baseDir=$(cd `dirname "$0"`;pwd)
|
||||
# functions
|
||||
|
||||
# main
|
||||
[ -z "${BASH_SOURCE[0]}" -o "${BASH_SOURCE[0]}" = "$0" ] || return
|
||||
cd $baseDir/..
|
||||
|
||||
if [ -d ./private/plugins ]; then
|
||||
./private/plugins/scripts/uninstall-all.sh
|
||||
fi
|
||||
|
||||
if [ -d ./public/plugins ]; then
|
||||
./public/plugins/scripts/uninstall-all.sh
|
||||
fi
|
Loading…
x
Reference in New Issue
Block a user