1
0
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:
Hai Liang Wang 2019-11-19 16:19:20 +08:00
parent bd0c322450
commit 20287ead02
49 changed files with 3034 additions and 1915 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),

View File

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

View File

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

View File

@ -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共享用户

View File

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

View File

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

View File

@ -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查询
*

View File

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

View File

@ -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()),

View File

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

View File

@ -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; // 租户

View File

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

View File

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

View File

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

View File

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

View File

@ -43,4 +43,5 @@ public class MainContextTest extends TestCase {
assertEquals(MainContext.ChannelType.WEBIM.toString(), "webim");
assertEquals(MainContext.ChannelType.toValue("webim"), MainContext.ChannelType.WEBIM);
}
}

View File

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

View File

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

View 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