1
0
mirror of https://github.com/chatopera/cosin.git synced 2025-06-16 18:30:03 +08:00

Closed #227 enable ACD Policies with configuration

This commit is contained in:
Hai Liang Wang 2019-11-20 07:09:20 +08:00
parent 26baf7a5d7
commit f57bd54f56
26 changed files with 285 additions and 242 deletions

View File

@ -56,14 +56,15 @@ public class ACDAgentService {
/**
* 查询条件当前在线的 坐席并且 未达到最大 服务人数的坐席
*/
List<AgentStatus> agentStatusList = acdAgentAllocatorMw.filterOutAvailableAgentStatus(agentUser, orgi);
final SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
List<AgentStatus> agentStatusList = acdAgentAllocatorMw.filterOutAvailableAgentStatus(
agentUser, orgi, sessionConfig);
/**
* 处理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()) {

View File

@ -16,6 +16,7 @@
package com.chatopera.cc.acd;
import com.chatopera.cc.basic.Constants;
import com.chatopera.cc.basic.MainContext;
import com.chatopera.cc.cache.Cache;
import com.chatopera.cc.model.AgentStatus;
@ -74,6 +75,7 @@ public class ACDPolicyService {
cache.putSessionConfigByOrgi(sessionConfig, orgi);
}
}
return sessionConfig;
}
@ -104,7 +106,7 @@ public class ACDPolicyService {
AgentStatus x = null;
int min = 0;
for (final AgentStatus o : agentStatuses) {
if (o.getUsers() <= min) {
if ((x == null) || (o.getUsers() < min)) {
x = o;
min = o.getUsers();
}

View File

@ -39,7 +39,7 @@ public class ACDQueueService {
int queneUsers = 0;
Map<String, AgentUser> map = cache.getAgentUsersInQueByOrgi(orgi);
for (Map.Entry<String, AgentUser> entry : map.entrySet()) {
for (final Map.Entry<String, AgentUser> entry : map.entrySet()) {
if (StringUtils.isNotBlank(skill)) {
if (StringUtils.equals(entry.getValue().getSkill(), skill)) {
queneUsers++;

View File

@ -30,6 +30,7 @@ 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.HashMapUtils;
import com.chatopera.cc.util.IP;
import com.chatopera.cc.util.SerializeUtil;
import com.chatopera.compose4j.Composer;
@ -197,17 +198,31 @@ public class ACDServiceRouter {
*/
@SuppressWarnings("unchecked")
public void allotVisitors(String agentno, String orgi) {
logger.info("[allotVisitors] agentno {}, orgi {}", agentno, orgi);
// 获得目标坐席的状态
AgentStatus agentStatus = SerializeUtil.deserialize(
redisCommand.getHashKV(RedisKey.getAgentStatusReadyHashKey(orgi), agentno));
if (agentStatus == null) {
logger.warn("[allotAgent] can not find AgentStatus for agentno {}", agentno);
logger.warn("[allotVisitors] can not find AgentStatus for agentno {}", agentno);
return;
}
logger.info("[allotVisitors] agentStatus id {}, status {}, service {}/{}, skills {}, busy {}",
agentStatus.getId(), agentStatus.getStatus(), agentStatus.getUsers(), agentStatus.getMaxusers(),
HashMapUtils.concatKeys(agentStatus.getSkills(), "|"), agentStatus.isBusy());
if ((!StringUtils.equals(
MainContext.AgentStatusEnum.READY.toString(), agentStatus.getStatus())) || agentStatus.isBusy()) {
// 该坐席处于非就绪状态或该坐席处于置忙
// 不分配坐席
return;
}
// 获得所有待服务访客的列表
Map<String, AgentUser> pendingAgentUsers = cache.getAgentUsersInQueByOrgi(orgi);
final SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
// 本次批量分配访客数目
int assigned = 0;
for (Map.Entry<String, AgentUser> entry : pendingAgentUsers.entrySet()) {
AgentUser agentUser = entry.getValue();
@ -216,39 +231,37 @@ public class ACDServiceRouter {
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 (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()) &&
StringUtils.isBlank(agentUser.getSkill())) {
// 待服务访客没有指定坐席并且没有指定技能组
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) { //坐席未达到最大咨询访客数量
// 坐席未达到最大咨询访客数量并且单次批量分配小于坐席就绪时分配最大访客数量(initMaxuser)
if (((agentStatus.getUsers() + assigned) < sessionConfig.getMaxuser()) && (assigned < sessionConfig.getInitmaxuser())) {
assigned++;
// 从排队队列移除
cache.deleteAgentUserInqueByAgentUserIdAndOrgi(agentUser.getUserid(), orgi);
@ -284,10 +297,13 @@ public class ACDServiceRouter {
MainContext.MessageType.NEW, agentUser.getAgentno(), outMessage, true);
}
} catch (Exception ex) {
logger.warn("[allotAgent] fail to process service", ex);
logger.warn("[allotVisitors] fail to process service", ex);
}
} else {
logger.info("[allotAgent] agentno {} reach the max users limit", agentno);
logger.info(
"[allotVisitors] agentno {} reach the max users limit {}/{} or batch assign limit {}/{}", agentno,
(agentStatus.getUsers() + assigned),
sessionConfig.getMaxuser(), assigned, sessionConfig.getInitmaxuser());
break;
}
}
@ -303,6 +319,9 @@ public class ACDServiceRouter {
*/
public void serviceFinish(final AgentUser agentUser, final String orgi) {
if (agentUser != null) {
/**
* 设置AgentUser
*/
// 获得坐席状态
AgentStatus agentStatus = null;
if (StringUtils.equals(MainContext.AgentUserStatusEnum.INSERVICE.toString(), agentUser.getStatus()) &&
@ -321,7 +340,9 @@ public class ACDServiceRouter {
final SessionConfig sessionConfig = acdPolicyService.initSessionConfig(orgi);
// 坐席服务
/**
* 坐席服务
*/
AgentService service = null;
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
service = agentServiceRes.findByIdAndOrgi(agentUser.getAgentserviceid(), agentUser.getOrgi());
@ -338,10 +359,9 @@ public class ACDServiceRouter {
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);
final AgentUserTask agentUserTask = agentUserTaskRes.findOne(
agentUser.getId());
if (agentUserTask != null) {
service.setAgentreplyinterval(agentUserTask.getAgentreplyinterval());
service.setAgentreplytime(agentUserTask.getAgentreplytime());
service.setAvgreplyinterval(agentUserTask.getAvgreplyinterval());
@ -367,6 +387,15 @@ public class ACDServiceRouter {
agentServiceRes.save(service);
}
/**
* 更新AgentStatus
*/
if (agentStatus != null) {
agentStatus.setUsers(
cache.getInservAgentUsersSizeByAgentnoAndOrgi(agentStatus.getAgentno(), agentStatus.getOrgi()));
agentStatusRes.save(agentStatus);
}
/**
* 发送到访客端的通知
*/
@ -423,8 +452,7 @@ public class ACDServiceRouter {
// 当前访客服务已经结束为坐席寻找新访客
if (agentStatus != null) {
long maxusers = sessionConfig != null ? sessionConfig.getMaxuser() : Constants.AGENT_STATUS_MAX_USER;
if ((agentStatus.getUsers() - 1) < maxusers) {
if ((agentStatus.getUsers() - 1) < sessionConfig.getMaxuser()) {
allotVisitors(agentStatus.getAgentno(), orgi);
}
}
@ -513,6 +541,8 @@ public class ACDServiceRouter {
Message outMessage = new Message();
outMessage.setAgentUser(agentUser);
outMessage.setChannelMessage(agentUser);
logger.info("[allotAgentForInvite] agentno {}, agentuser agentno {}", agentno, agentUser.getAgentno());
peerSyncIM.send(MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
agentUser.getAppid(),
MainContext.MessageType.NEW, agentUser.getAgentno(), outMessage, true);

View File

@ -67,6 +67,7 @@ public class ACDWorkMonitor {
AgentReport report = new AgentReport();
Map<String, AgentStatus> readys = cache.getAgentStatusReadyByOrig(orgi);
int readyNum = 0;
int busyNum = 0;
@ -101,10 +102,10 @@ public class ACDWorkMonitor {
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()
// );
logger.info(
"[getAgentReport] orgi {}, organ {}, agents {}, busy {}, users {}, inqueue {}", orgi, organ,
report.getAgents(), report.getBusy(), report.getUsers(), report.getInquene()
);
return report;
}

View File

@ -74,7 +74,7 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
* 查询条件当前在线的 坐席并且 未达到最大 服务人数的坐席
*/
final List<AgentStatus> agentStatuses = filterOutAvailableAgentStatus(
ctx.getAgentUser(), ctx.getOrgi());
ctx.getAgentUser(), ctx.getOrgi(), ctx.getSessionConfig());
/**
* 处理ACD 技能组请求和 坐席请求
@ -108,6 +108,11 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
final boolean isInvite) {
AgentStatus agentStatus = null;
// 过滤后没有就绪的满足条件的坐席
if (agentStatuses.size() == 0) {
return agentStatus;
}
// 邀请功能
if (isInvite) {
logger.info("[filterOutAgentStatusWithPolicies] is invited onlineUser.");
@ -131,10 +136,10 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
for (final AgentStatus o : agentStatuses) {
if (StringUtils.equals(
o.getAgentno(), report.getData()) && o.getUsers() < sessionConfig.getMaxuser()) {
agentStatus = o;
logger.info(
"[filterOutAgentStatusWithPolicies] choose agentno {} by chat history.",
agentStatus.getAgentno());
agentStatus = o;
break;
}
}
@ -193,8 +198,8 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
*/
public List<AgentStatus> filterOutAvailableAgentStatus(
final AgentUser agentUser,
final String orgi
) {
final String orgi,
final SessionConfig sessionConfig) {
logger.info(
"[filterOutAvailableAgentStatus] pre-conditions: agentUser.agentno {}, orgi {}, skill {}, onlineUser {}",
agentUser.getAgentno(), orgi, agentUser.getSkill(), agentUser.getUserid()
@ -222,6 +227,7 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
if (agentUser != null && StringUtils.isNotBlank(agentUser.getAgentno())) {
// 指定坐席
for (final Map.Entry<String, AgentStatus> entry : map.entrySet()) {
// 被指定的坐席不检查是否忙是否达到最大接待数量
if (StringUtils.equals(
entry.getValue().getAgentno(), agentUser.getAgentno())) {
agentStatuses.add(entry.getValue());
@ -251,6 +257,7 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
// 指定技能组
for (final Map.Entry<String, AgentStatus> entry : map.entrySet()) {
if ((!entry.getValue().isBusy()) &&
(entry.getValue().getUsers() < sessionConfig.getMaxuser()) &&
(entry.getValue().getSkills() != null &&
entry.getValue().getSkills().containsKey(agentUser.getSkill()))) {
logger.info(
@ -282,13 +289,21 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
*/
// 对于该租户的所有客服
for (final Map.Entry<String, AgentStatus> entry : map.entrySet()) {
if (!entry.getValue().isBusy()) {
if ((!entry.getValue().isBusy()) && (entry.getValue().getUsers() < sessionConfig.getMaxuser())) {
agentStatuses.add(entry.getValue());
logger.info(
"[filterOutAvailableAgentStatus] <Redundance> find ready agent {}, agentname {}, status {}, service {}/{}",
"[filterOutAvailableAgentStatus] <Redundance> find ready agent {}, agentname {}, status {}, service {}/{}, skills {}",
entry.getValue().getAgentno(), entry.getValue().getUsername(), entry.getValue().getStatus(),
entry.getValue().getUsers(),
entry.getValue().getMaxusers());
entry.getValue().getMaxusers(),
HashMapUtils.concatKeys(entry.getValue().getSkills(), "|"));
} else {
logger.info(
"[filterOutAvailableAgentStatus] <Redundance> skip ready agent {}, name {}, status {}, service {}/{}, skills {}",
entry.getValue().getAgentno(), entry.getValue().getUsername(), entry.getValue().getStatus(),
entry.getValue().getUsers(),
entry.getValue().getMaxusers(),
HashMapUtils.concatKeys(entry.getValue().getSkills(), "|"));
}
}
}
@ -303,11 +318,10 @@ public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
* 1. 在AgentUser服务结束并且还没有对应的AgentService
* 2. 在新服务开始安排坐席
*
* @param agentStatus 坐席状态
* @param agentUser 坐席访客会话
* @param orgi 租户ID
* @param finished 结束服务
* @param sessionConfig 坐席配置
* @param agentStatus 坐席状态
* @param agentUser 坐席访客会话
* @param orgi 租户ID
* @param finished 结束服务
* @return
*/
public AgentService processAgentService(

View File

@ -17,7 +17,6 @@
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;
@ -41,9 +40,6 @@ public class ACDVisBindingMw implements Middleware<ACDComposeContext> {
@Autowired
private OrganRepository organRes;
@Autowired
private Cache cache;
/**
* 绑定技能组或坐席
*
@ -72,7 +68,7 @@ public class ACDVisBindingMw implements Middleware<ACDComposeContext> {
}
if (StringUtils.isNotBlank(ctx.getAgentno())) {
logger.info("[apply] bind agentno {}", ctx.getAgentno());
logger.info("[apply] bind agentno {}, isInvite {}", ctx.getAgentno(), ctx.isInvite());
// 绑定坐席
// 绑定坐席有可能是因为前端展示了技能组和坐席
// 也有可能是坐席发送了邀请该访客接收邀请

View File

@ -182,6 +182,7 @@ public class ACDVisBodyParserMw implements Middleware<ACDComposeContext> {
} else {
// TODO 什么是否返回 noAgentMessage, 是否在是 INQUENE getQueneindex == 0
// 当前没有坐席要留言
ctx.setNoagent(true);
ctx.setMessage(acdMessageHelper.getNoAgentMessage(
ctx.getAgentService().getQueneindex(),
ctx.getChannel(),

View File

@ -0,0 +1,50 @@
/*
* 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.aspect;
import com.chatopera.cc.cache.Cache;
import com.chatopera.cc.model.AgentStatus;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
*/
@Aspect
@Component
public class AgentStatusAspect {
private final static Logger logger = LoggerFactory.getLogger(AgentStatusAspect.class);
@Autowired
private Cache cache;
@After("execution(* com.chatopera.cc.persistence.repository.AgentStatusRepository.save(..))")
public void save(final JoinPoint joinPoint) {
final AgentStatus agentStatus = (AgentStatus) joinPoint.getArgs()[0];
cache.putAgentStatusByOrgi(agentStatus, agentStatus.getOrgi());
}
@After("execution(* com.chatopera.cc.persistence.repository.AgentStatusRepository.delete(..))")
public void delete(final JoinPoint joinPoint) {
final AgentStatus agentStatus = (AgentStatus) joinPoint.getArgs()[0];
cache.deleteAgentStatusByAgentnoAndOrgi(agentStatus.getAgentno(), agentStatus.getOrgi());
}
}

View File

@ -59,12 +59,11 @@ public class AgentUserAspect {
public void save(final JoinPoint joinPoint) {
final AgentUser agentUser = (AgentUser) joinPoint.getArgs()[0];
logger.info(
"[save] agentUser id {}, agentno {}, userId {}", agentUser.getId(), agentUser.getAgentno(),
agentUser.getUserid());
"[save] agentUser id {}, agentno {}, userId {}, status {}", agentUser.getId(), agentUser.getAgentno(),
agentUser.getUserid(), agentUser.getStatus());
if (StringUtils.isBlank(agentUser.getId())
|| StringUtils.isBlank(agentUser.getUserid())
|| StringUtils.isBlank(agentUser.getAgentno())) {
|| StringUtils.isBlank(agentUser.getUserid())) {
return;
}
@ -82,7 +81,7 @@ public class AgentUserAspect {
"[delete] agentUser id {}, agentno {}, userId {}", agentUser.getId(), agentUser.getAgentno(),
agentUser.getUserid());
cache.deleteAgentUserAuditByOrgiAndId(agentUser.getOrgi(), agentUser.getId());
cache.deleteAgentUserByUserIdAndOrgi(agentUser.getUserid(), agentUser.getOrgi());
cache.deleteAgentUserByUserIdAndOrgi(agentUser, agentUser.getOrgi());
}
/**

View File

@ -65,8 +65,6 @@ public class Constants {
public static final String CSKEFU_SYSTEM_ADV = "cskefu_system_adv"; // 系统广告位
public static final int AGENT_STATUS_MAX_USER = 20; // 每个坐席 最大接待的 咨询数量
public static final String SYSTEM_CACHE_CALLOUT_CONFIG = "callout_config";
/**

View File

@ -57,37 +57,6 @@ public class Cache {
return convertFromStringToAgentStatus(agentStatuses);
}
/**
* 获得未就绪的坐席状态列表
*
* @param orgi
* @return
*/
public Map<String, AgentStatus> getAgentStatusNotReadyByOrgi(final String orgi) {
Map<String, String> agentStatuses = redisCommand.getHash(RedisKey.getAgentStatusNotReadyHashKey(orgi));
return convertFromStringToAgentStatus(agentStatuses);
}
/**
* 返回置忙的客服列表
*
* @param orgi
* @return
*/
public Map<String, AgentStatus> getAgentStatusBusyByOrig(final String orgi) {
Map<String, String> agentStatuses = redisCommand.getHash(RedisKey.getAgentStatusReadyHashKey(orgi));
Map<String, AgentStatus> result = new HashMap<>();
Map<String, AgentStatus> map = convertFromStringToAgentStatus(agentStatuses);
for (Map.Entry<String, AgentStatus> entry : map.entrySet()) {
if (entry.getValue().isBusy()) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
/**
* 通过访客ID和ORGI获得访客坐席关联关系
*
@ -124,8 +93,8 @@ public class Cache {
public Map<String, AgentUser> getAgentUsersInQueByOrgi(final String orgi) {
Map<String, String> agentUsers = redisCommand.getHash(RedisKey.getAgentUserInQueHashKey(orgi));
Map<String, AgentUser> map = new HashMap<>();
for (Map.Entry<String, String> entry : agentUsers.entrySet()) {
AgentUser obj = SerializeUtil.deserialize(entry.getValue());
for (final Map.Entry<String, String> entry : agentUsers.entrySet()) {
final AgentUser obj = SerializeUtil.deserialize(entry.getValue());
map.put(obj.getId(), obj);
}
return map;
@ -421,17 +390,18 @@ public class Cache {
/**
* Delete agentUser
*
* @param userId
* @param agentUser
* @param orgi
*/
public void deleteAgentUserByUserIdAndOrgi(String userId, String orgi) {
if (redisCommand.hasHashKV(RedisKey.getAgentUserInQueHashKey(orgi), userId)) {
@AgentUserAspect.LinkAgentUser
public void deleteAgentUserByUserIdAndOrgi(final AgentUser agentUser, String orgi) {
if (redisCommand.hasHashKV(RedisKey.getAgentUserInQueHashKey(orgi), agentUser.getUserid())) {
// 排队等待中
redisCommand.delHashKV(RedisKey.getAgentUserInQueHashKey(orgi), userId);
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserInServHashKey(orgi), userId)) {
redisCommand.delHashKV(RedisKey.getAgentUserInServHashKey(orgi), userId);
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserEndHashKey(orgi), userId)) {
redisCommand.delHashKV(RedisKey.getAgentUserEndHashKey(orgi), userId);
redisCommand.delHashKV(RedisKey.getAgentUserInQueHashKey(orgi), agentUser.getUserid());
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserInServHashKey(orgi), agentUser.getUserid())) {
redisCommand.delHashKV(RedisKey.getAgentUserInServHashKey(orgi), agentUser.getUserid());
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserEndHashKey(orgi), agentUser.getUserid())) {
redisCommand.delHashKV(RedisKey.getAgentUserEndHashKey(orgi), agentUser.getUserid());
} else {
// TODO 考虑是否有其他状态保存
}
@ -539,7 +509,6 @@ public class Cache {
}
/******************************
* Callcenter Agent 相关
******************************/

View File

@ -207,11 +207,7 @@ public class LoginController extends Handler {
final AgentStatus agentStatus = agentProxy.resolveAgentStatusByAgentnoAndOrgi(
loginUser.getId(), orgi, loginUser.getSkills());
agentStatus.setBusy(false);
agentProxy.ready(loginUser, agentStatus);
// 更新缓存和数据库
cache.putAgentStatusByOrgi(agentStatus, loginUser.getOrgi());
agentStatusRes.save(agentStatus);
agentProxy.ready(loginUser, agentStatus, false);
// 工作状态记录
acdWorkMonitor.recordAgentStatus(agentStatus.getAgentno(),

View File

@ -174,7 +174,9 @@ public class ApiAgentUserController extends Handler {
// 当前访客的ID
final String userId = agentUser.getUserid();
logger.info("[transout] agentuserid {} \n target agent id {}, \n current agent id {}, onlineuserid {}", agentUserId, transAgentId, currentAgentno, userId);
logger.info(
"[transout] agentuserid {} \n target agent id {}, \n current agent id {}, onlineuserid {}",
agentUserId, transAgentId, currentAgentno, userId);
// 检查权限
@ -281,14 +283,15 @@ public class ApiAgentUserController extends Handler {
}
/**
* 结束坐席会话
* 结束对话
* 如果当前对话属于登录用户或登录用户为超级用户则可以结束这个对话
*
* @param request
* @param payload
* @return
*/
private JsonObject end(final HttpServletRequest request, final JsonObject payload) {
logger.info("[end] payload ", payload.toString());
logger.info("[end] payload {}", payload.toString());
final String orgi = super.getOrgi(request);
final User logined = super.getUser(request);
JsonObject resp = new JsonObject();

View File

@ -26,7 +26,6 @@ import com.chatopera.cc.model.AgentStatus;
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.proxy.AgentUserProxy;
import com.chatopera.cc.util.Menu;
import com.chatopera.cc.util.RestResult;
@ -41,7 +40,6 @@ import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.nio.charset.CharacterCodingException;
import java.util.Date;
import java.util.List;
@ -63,14 +61,11 @@ public class ApiServiceQueneController extends Handler {
private ACDPolicyService acdPolicyService;
@Autowired
private AgentStatusRepository agentStatusRepository;
private AgentStatusRepository agentStatusRes;
@Autowired
private ACDServiceRouter acdServiceRouter;
@Autowired
private AgentUserRepository agentUserRepository;
@Autowired
private Cache cache;
@ -98,104 +93,86 @@ public class ApiServiceQueneController extends Handler {
@Menu(type = "apps", subtype = "user", access = true)
public ResponseEntity<RestResult> agentStatus(
HttpServletRequest request,
@Valid String status) throws CharacterCodingException {
@Valid String status) {
User logined = super.getUser(request);
AgentStatus agentStatus = null;
if (StringUtils.isNotBlank(status) && status.equals(MainContext.AgentStatusEnum.READY.toString())) {
List<AgentStatus> agentStatusList = agentStatusRepository.findByAgentnoAndOrgi(
logined.getId(), super.getOrgi(request));
if (agentStatusList.size() > 0) {
agentStatus = agentStatusList.get(0);
agentStatus.setSkills(logined.getSkills());
} else {
agentStatus = new AgentStatus();
agentStatus.setUserid(logined.getId());
agentStatus.setUsername(logined.getUname());
agentStatus.setAgentno(logined.getId());
agentStatus.setLogindate(new Date());
agentStatus.setSkills(logined.getSkills());
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(super.getOrgi(request));
agentStatus = agentStatusRes.findOneByAgentnoAndOrgi(logined.getId(), logined.getOrgi()).orElseGet(() -> {
AgentStatus p = new AgentStatus();
p.setUserid(logined.getId());
p.setUsername(logined.getUname());
p.setAgentno(logined.getId());
p.setLogindate(new Date());
agentStatus.setUsers(agentUserRepository.countByAgentnoAndStatusAndOrgi(
logined.getId(),
MainContext.AgentUserStatusEnum.INSERVICE.toString(),
super.getOrgi(request)));
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(logined.getOrgi());
p.setUpdatetime(new Date());
p.setOrgi(super.getOrgi(request));
p.setMaxusers(sessionConfig.getMaxuser());
return p;
});
agentStatus.setUpdatetime(new Date());
/**
* 设置技能组
*/
agentStatus.setSkills(logined.getSkills());
agentStatus.setOrgi(super.getOrgi(request));
agentStatus.setMaxusers(sessionConfig.getMaxuser());
agentStatusRepository.save(agentStatus);
/**
* 更新当前用户状态
*/
agentStatus.setUsers(cache.getInservAgentUsersSizeByAgentnoAndOrgi(
agentStatus.getAgentno(),
super.getOrgi(request)));
agentStatus.setStatus(MainContext.AgentStatusEnum.READY.toString());
agentStatusRes.save(agentStatus);
}
if (agentStatus != null) {
/**
* 更新当前用户状态
*/
agentStatus.setUsers(cache.getInservAgentUsersSizeByAgentnoAndOrgi(
agentStatus.getAgentno(),
super.getOrgi(request)));
agentStatus.setStatus(MainContext.AgentStatusEnum.READY.toString());
cache.putAgentStatusByOrgi(agentStatus, super.getOrgi(request));
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);
acdServiceRouter.allotVisitors(agentStatus.getAgentno(), super.getOrgi(request));
}
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);
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) {
agentStatusRes.findOneByAgentnoAndOrgi(
logined.getId(), super.getOrgi(request)).ifPresent(p -> {
acdWorkMonitor.recordAgentStatus(
temp.getAgentno(), temp.getUsername(), temp.getAgentno(),
p.getAgentno(), p.getUsername(), p.getAgentno(),
logined.isAdmin(),
temp.getAgentno(),
temp.isBusy() ? MainContext.AgentStatusEnum.BUSY.toString() : MainContext.AgentStatusEnum.READY.toString(),
p.getAgentno(),
p.isBusy() ? MainContext.AgentStatusEnum.BUSY.toString() : MainContext.AgentStatusEnum.READY.toString(),
MainContext.AgentStatusEnum.NOTREADY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(), temp.getOrgi(), temp.getUpdatetime());
agentStatusRepository.delete(temp);
}
cache.deleteAgentStatusByAgentnoAndOrgi(super.getUser(request).getId(), super.getOrgi(request));
MainContext.AgentWorkType.MEIDIACHAT.toString(), p.getOrgi(), p.getUpdatetime());
agentStatusRes.delete(p);
});
} else if (StringUtils.isNotBlank(status) && status.equals(MainContext.AgentStatusEnum.BUSY.toString())) {
List<AgentStatus> agentStatusList = agentStatusRepository.findByAgentnoAndOrgi(
logined.getId(), super.getOrgi(request));
if (agentStatusList.size() > 0) {
agentStatus = agentStatusList.get(0);
agentStatus.setBusy(true);
agentStatusRes.findOneByAgentnoAndOrgi(
logined.getId(), logined.getOrgi()).ifPresent(p -> {
p.setBusy(true);
acdWorkMonitor.recordAgentStatus(
agentStatus.getAgentno(), agentStatus.getUsername(), agentStatus.getAgentno(),
logined.isAdmin(), agentStatus.getAgentno(),
p.getAgentno(), p.getUsername(), p.getAgentno(),
logined.isAdmin(), p.getAgentno(),
MainContext.AgentStatusEnum.READY.toString(), MainContext.AgentStatusEnum.BUSY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(), agentStatus.getOrgi(),
agentStatus.getUpdatetime());
agentStatus.setUpdatetime(new Date());
agentStatusRepository.save(agentStatus);
cache.putAgentStatusByOrgi(agentStatus, super.getOrgi(request));
}
MainContext.AgentWorkType.MEIDIACHAT.toString(), p.getOrgi(),
p.getUpdatetime());
p.setUpdatetime(new Date());
agentStatusRes.save(p);
});
} else if (StringUtils.isNotBlank(status) && status.equals(
MainContext.AgentStatusEnum.NOTBUSY.toString())) {
List<AgentStatus> agentStatusList = agentStatusRepository.findByAgentnoAndOrgi(
logined.getId(), super.getOrgi(request));
if (agentStatusList.size() > 0) {
agentStatus = agentStatusList.get(0);
agentStatus.setBusy(false);
agentStatusRes.findOneByAgentnoAndOrgi(
logined.getId(), logined.getOrgi()).ifPresent(p -> {
p.setBusy(false);
acdWorkMonitor.recordAgentStatus(
agentStatus.getAgentno(), agentStatus.getUsername(), agentStatus.getAgentno(),
logined.isAdmin(), agentStatus.getAgentno(),
p.getAgentno(), p.getUsername(), p.getAgentno(),
logined.isAdmin(), p.getAgentno(),
MainContext.AgentStatusEnum.BUSY.toString(), MainContext.AgentStatusEnum.READY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(), agentStatus.getOrgi(),
agentStatus.getUpdatetime());
MainContext.AgentWorkType.MEIDIACHAT.toString(), p.getOrgi(),
p.getUpdatetime());
agentStatus.setUpdatetime(new Date());
agentStatusRepository.save(agentStatus);
cache.putAgentStatusByOrgi(agentStatus, super.getOrgi(request));
}
p.setUpdatetime(new Date());
agentStatusRes.save(p);
});
acdServiceRouter.allotVisitors(agentStatus.getAgentno(), super.getOrgi(request));
}
agentUserProxy.broadcastAgentsStatus(

View File

@ -17,17 +17,16 @@
package com.chatopera.cc.controller.apps;
import com.alibaba.fastjson.JSONObject;
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.acd.ACDWorkMonitor;
import com.chatopera.cc.activemq.BrokerPublisher;
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.exception.CSKefuException;
import com.chatopera.cc.controller.Handler;
import com.chatopera.cc.exception.CSKefuException;
import com.chatopera.cc.model.*;
import com.chatopera.cc.peer.PeerSyncIM;
import com.chatopera.cc.persistence.blob.JpaBlobHelper;
@ -81,9 +80,6 @@
@Autowired
private ACDPolicyService acdPolicyService;
@Autowired
private ACDAgentService acdAgentService;
@Autowired
private ACDServiceRouter acdServiceRouter;
@ -543,14 +539,7 @@
logined.getId(), orgi, logined.getSkills());
// 缓存就绪状态
agentProxy.ready(logined, agentStatus);
// TODO 对于busy的判断其实可以和AgentStatus maxuser以及users结合
// 现在为了配合前端的行为从未就绪到就绪设置为置闲
agentStatus.setBusy(false);
// 更新数据库
cache.putAgentStatusByOrgi(agentStatus, agentStatus.getOrgi());
agentStatusRes.save(agentStatus);
agentProxy.ready(logined, agentStatus, false);
// 为该坐席分配访客
acdServiceRouter.allotVisitors(agentStatus.getAgentno(), orgi);
@ -648,6 +637,7 @@
@Menu(type = "apps", subtype = "agent")
public ModelAndView notbusy(HttpServletRequest request) {
final User logined = super.getUser(request);
// 组织结构和权限数据
logger.info("[notbusy] set user {} as not busy", logined.getId());
AgentStatus agentStatus = agentProxy.resolveAgentStatusByAgentnoAndOrgi(
@ -717,6 +707,7 @@
@RequestMapping({"/end"})
@Menu(type = "apps", subtype = "agent")
public ModelAndView end(HttpServletRequest request, @Valid String id) {
logger.info("[end] end id {}", id);
final String orgi = super.getOrgi(request);
final User logined = super.getUser(request);

View File

@ -160,16 +160,24 @@ public class AgentStatus implements java.io.Serializable, Comparable<AgentStatus
this.createtime = createtime;
}
/**
* 同时服务最多的访客数
* @return
*/
@Transient
public int getMaxusers() {
SessionConfig sessionConfig = MainContext.getACDServiceRouter().getAcdPolicyService().initSessionConfig(this.orgi);
return sessionConfig != null ? sessionConfig.getMaxuser() : Constants.AGENT_STATUS_MAX_USER;
return sessionConfig.getMaxuser();
}
public void setMaxusers(int maxusers) {
this.maxusers = maxusers;
}
/**
* 单次批量分配最大访客数目
* @return
*/
@Transient
public int getInitmaxusers() {
SessionConfig sessionConfig = MainContext.getACDServiceRouter().getAcdPolicyService().initSessionConfig(this.orgi);

View File

@ -17,6 +17,7 @@
package com.chatopera.cc.persistence.repository;
import java.util.List;
import java.util.Optional;
import com.chatopera.cc.model.AgentStatus;
import org.springframework.data.domain.Page;
@ -28,10 +29,8 @@ public interface AgentStatusRepository extends
JpaRepository<AgentStatus, String> {
AgentStatus findByIdAndOrgi(String paramString, String orgi);
List<AgentStatus> findByAgentnoAndOrgi(String agentid, String orgi);
@Query(value = "SELECT * FROM uk_agentstatus WHERE agentno = ?1 AND orgi = ?2 ORDER BY createtime DESC LIMIT 1", nativeQuery = true)
AgentStatus findOneByAgentnoAndOrgi(final String agentid, final String orgi);
Optional<AgentStatus> findOneByAgentnoAndOrgi(final String agentid, final String orgi);
AgentStatus findByAgentno(String agentid);

View File

@ -15,6 +15,7 @@ import com.chatopera.cc.persistence.repository.SNSAccountRepository;
import com.chatopera.cc.persistence.repository.StreamingFileRepository;
import com.chatopera.cc.socketio.message.ChatMessage;
import com.chatopera.cc.socketio.message.Message;
import com.chatopera.cc.util.HashMapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
@ -70,7 +71,7 @@ public class AgentProxy {
* @param user
* @param agentStatus
*/
public void ready(final User user, final AgentStatus agentStatus) {
public void ready(final User user, final AgentStatus agentStatus, final boolean busy) {
agentStatus.setOrgi(user.getOrgi());
agentStatus.setUserid(user.getId());
agentStatus.setUsername(user.getUname());
@ -79,6 +80,9 @@ public class AgentProxy {
agentStatus.setOrgi(agentStatus.getOrgi());
agentStatus.setUpdatetime(new Date());
agentStatus.setSkills(user.getSkills());
// TODO 对于busy的判断其实可以和AgentStatus maxuser以及users结合
// 现在为了配合前端的行为从未就绪到就绪设置为置闲
agentStatus.setBusy(busy);
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(agentStatus.getOrgi());
agentStatus.setMaxusers(sessionConfig.getMaxuser());
@ -92,6 +96,9 @@ public class AgentProxy {
logger.info(
"[ready] set agent {}, status {}", agentStatus.getAgentno(),
MainContext.AgentStatusEnum.READY.toString());
// 更新数据库
agentStatusRes.save(agentStatus);
}
@ -325,15 +332,13 @@ public class AgentProxy {
* @return
*/
public AgentStatus resolveAgentStatusByAgentnoAndOrgi(final String agentno, final String orgi, final HashMap<String, String> skills) {
logger.info(
"[resolveAgentStatusByAgentnoAndOrgi] agentno {}, skills {}", agentno,
HashMapUtils.concatKeys(skills, "|"));
AgentStatus agentStatus = cache.findOneAgentStatusByAgentnoAndOrig(agentno, orgi);
if (agentStatus == null) {
final List<AgentStatus> ass = agentStatusRes.findByAgentnoAndOrgi(agentno, orgi);
if (ass.size() > 0) {
agentStatus = ass.get(0);
} else {
agentStatus = new AgentStatus();
}
agentStatus = agentStatusRes.findOneByAgentnoAndOrgi(agentno, orgi).orElseGet(AgentStatus::new);
}
if (skills != null) {

View File

@ -105,6 +105,8 @@ public class AgentUserProxy {
@Autowired
private Cache cache;
@Autowired
private AgentStatusRepository agentStatusRes;
@Autowired
private PeerSyncIM peerSyncIM;
@ -488,7 +490,7 @@ public class AgentUserProxy {
int users = cache.getInservAgentUsersSizeByAgentnoAndOrgi(agentStatus.getAgentno(), orgi);
agentStatus.setUsers(users);
agentStatus.setUpdatetime(new Date());
cache.putAgentStatusByOrgi(agentStatus, orgi);
agentStatusRes.save(agentStatus);
}
/**

View File

@ -536,6 +536,8 @@ public class UserProxy {
public void attachOrgansPropertiesForUser(final User user) {
List<OrganUser> organs = organUserRes.findByUserid(user.getId());
user.setOrgans(new HashMap<>());
user.setAffiliates(new HashSet<>());
final HashMap<String, String> skills = new HashMap<>();
for (final OrganUser organ : organs) {

View File

@ -92,18 +92,13 @@ public class AgentEventHandler {
client.set("connectid", connectid);
// 更新AgentStatus到数据库
List<AgentStatus> agentStatusList = getAgentStatusRes().findByAgentnoAndOrgi(userid, orgi);
if (agentStatusList.size() > 0) {
AgentStatus agentStatus = agentStatusList.get(0);
agentStatus.setUpdatetime(new Date());
agentStatus.setConnected(true);
getAgentStatusRes().findOneByAgentnoAndOrgi(userid, orgi).ifPresent(p -> {
p.setUpdatetime(new Date());
p.setConnected(true);
// 设置agentSkills
agentStatus.setSkills(getUserProxy().getSkillsMapByAgentno(userid));
getAgentStatusRes().save(agentStatus);
MainContext.getCache().putAgentStatusByOrgi(agentStatus, orgi);
}
p.setSkills(getUserProxy().getSkillsMapByAgentno(userid));
getAgentStatusRes().save(p);
});
// 工作工作效率
InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress();
@ -190,7 +185,8 @@ public class AgentEventHandler {
final String session = client.get("session");
final String connectid = client.get("connectid");
logger.info(
"[onIntervetionEvent] intervention: agentno {}, session {}, connectid {}, payload {}", agentno, session, connectid,
"[onIntervetionEvent] intervention: agentno {}, session {}, connectid {}, payload {}", agentno, session,
connectid,
received.toJsonObject());
if (received.valid()) {

View File

@ -156,8 +156,11 @@ public class IMEventHandler {
/**
* 发送消息给坐席
* 如果没有AgentService或该AgentService没有坐席或AgentService在排队中则不发送
*/
if (agentServiceMessage.getAgentService() != null) {
if (agentServiceMessage.getAgentService() != null && (!agentServiceMessage.isNoagent()) && !StringUtils.equals(
MainContext.AgentUserStatusEnum.INQUENE.toString(),
agentServiceMessage.getAgentService().getStatus())) {
// 通知消息到坐席
MainContext.getPeerSyncIM().send(ReceiverType.AGENT,
ChannelType.WEBIM,

View File

@ -60,7 +60,7 @@ public class HumanUtils {
protected static void processMessage(final ChatMessage chatMessage, final String msgtype, final String userid) {
logger.info("[processMessage] userid {}, msgtype {}", userid, msgtype);
AgentUser agentUser = MainContext.getCache().findOneAgentUserByUserIdAndOrgi(
userid, MainContext.SYSTEM_ORGI).orElseGet(null);
userid, MainContext.SYSTEM_ORGI).orElse(null);
Message outMessage = new Message();

View File

@ -88,11 +88,11 @@
</div>
<div class="ukefu-webim-prop">
<div class="ukefu-webim-tl" style="clear:both;">坐席分配最大访客数量</div>
<div class="ukefu-webim-tl" style="clear:both;">坐席同时服务最多访客数</div>
<div class="box-item">
<div class="row">
<div class="col-lg-8">
<p>每个人工坐席分配访客的最大数量</p>
<p>坐席分配的同时服务访客数量的最大限制</p>
<p style="color:#888888;font-size:13px;margin-top:10px;">分配过多用户会导致客户满意度降低</p>
</div>
<div class="col-lg-4">
@ -114,11 +114,11 @@
</div>
<div class="ukefu-webim-prop">
<div class="ukefu-webim-tl" style="clear:both;">坐席就绪时分配最大访客数量</div>
<div class="ukefu-webim-tl" style="clear:both;">坐席批量分配最大访客数</div>
<div class="box-item">
<div class="row">
<div class="col-lg-8">
<p>为每个人工坐席分配访客的最大数量</p>
<p>单次分配访客的最大数量</p>
<p style="color:#888888;font-size:13px;margin-top:10px;">为避免坐席就绪的时候突然涌入大量的咨询客户,设置此参数</p>
</div>
<div class="col-lg-4">

View File

@ -237,7 +237,7 @@ public class ChatbotEventHandler {
MainContext.getCache().findOneAgentUserByUserIdAndOrgi(user, orgi).ifPresent(p -> {
MainContext.getACDServiceRouter().getAcdChatbotService().processChatbotService(null, p, orgi);
MainContext.getCache().deleteAgentUserByUserIdAndOrgi(user, orgi);
MainContext.getCache().deleteAgentUserByUserIdAndOrgi(p, orgi);
MainContext.getCache().deleteOnlineUserByIdAndOrgi(user, orgi);
p.setStatus(MainContext.OnlineUserStatusEnum.OFFLINE.toString());