From e463139cc2293db99ba2335a4f4cc8bef3fc2bed Mon Sep 17 00:00:00 2001 From: "dengchao@xgtl" <2325690622@qq.com> Date: Fri, 17 Apr 2020 15:03:47 +0800 Subject: [PATCH] Fix AgentServiceRepository related class --- .../chatopera/cc/acd/ACDChatbotService.java | 34 +- .../controller/api/ApiLeavemsgController.java | 34 +- .../controller/api/ApiQualityController.java | 32 +- .../apps/service/CommentController.java | 16 +- .../cc/socketio/handler/IMEventHandler.java | 536 +++++++++--------- .../cc/socketio/util/HumanUtils.java | 301 +++++----- 6 files changed, 461 insertions(+), 492 deletions(-) diff --git a/contact-center/app/src/main/java/com/chatopera/cc/acd/ACDChatbotService.java b/contact-center/app/src/main/java/com/chatopera/cc/acd/ACDChatbotService.java index ec941f68..e43cb09e 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/acd/ACDChatbotService.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/acd/ACDChatbotService.java @@ -20,39 +20,39 @@ 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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import java.util.Date; +@Slf4j @Component +@RequiredArgsConstructor public class ACDChatbotService { - private final static Logger logger = LoggerFactory.getLogger(ACDChatbotService.class); - @Autowired - private AgentServiceRepository agentServiceRes; + @NonNull + private final AgentServiceRepository agentServiceRes; /** * 为访客分配机器人客服, ACD策略,此处 AgentStatus 是建议 的 坐席, 如果启用了 历史服务坐席 优先策略, 则会默认检查历史坐席是否空闲,如果空闲,则分配,如果不空闲,则 分配当前建议的坐席 - * - * @param agentUser - * @param orgi - * @return - * @throws Exception */ + @Nullable 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()); + if (agentService != null) { + agentService.setEndtime(now); + if (agentService.getServicetime() != null) { + agentService.setSessiontimes(System.currentTimeMillis() - agentService.getServicetime().getTime()); + } + agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString()); } - agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString()); } else { agentService.setServicetime(now); agentService.setLogindate(now); @@ -81,7 +81,9 @@ public class ACDChatbotService { agentService.setLeavemsg(false); } - agentServiceRes.save(agentService); + if (agentService != null) { + agentServiceRes.save(agentService); + } return agentService; } diff --git a/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiLeavemsgController.java b/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiLeavemsgController.java index e34e66e8..3572f84e 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiLeavemsgController.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiLeavemsgController.java @@ -23,22 +23,19 @@ import com.chatopera.cc.persistence.repository.AgentServiceRepository; import com.chatopera.cc.util.Menu; import com.chatopera.cc.util.RestResult; import com.chatopera.cc.util.RestResultType; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; -import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; @@ -47,33 +44,26 @@ import java.util.List; */ @RestController @RequestMapping("/api/leavemsg") +@RequiredArgsConstructor public class ApiLeavemsgController extends Handler { - @Autowired - private AgentServiceRepository agentServiceRepository; + @NonNull + private final AgentServiceRepository agentServiceRepository; /** * 获取留言列表 - * - * @param request - * @param username 搜索用户名,精确搜索 - * @return */ @RequestMapping("/list") @Menu(type = "apps", subtype = "app", access = true) - public ResponseEntity list(HttpServletRequest request, @RequestBody RequestValues values) { - Page page = agentServiceRepository.findAll(new Specification() { - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, - CriteriaBuilder cb) { - List list = new ArrayList(); - list.add(cb.equal(root.get("leavemsg").as(Boolean.class), true)); + public ResponseEntity list(@RequestBody RequestValues values) { + Page page = agentServiceRepository.findAll((Specification) (root, query, cb) -> { + List list = new ArrayList<>(); + list.add(cb.equal(root.get("leavemsg").as(Boolean.class), true)); - list.add(cb.equal(root.get("leavemsgstatus").as(String.class), MainContext.LeaveMsgStatus.NOTPROCESS.toString())); + list.add(cb.equal(root.get("leavemsgstatus").as(String.class), MainContext.LeaveMsgStatus.NOTPROCESS.toString())); - Predicate[] p = new Predicate[list.size()]; - return cb.and(list.toArray(p)); - } + Predicate[] p = new Predicate[list.size()]; + return cb.and(list.toArray(p)); }, PageRequest.of(super.getP(values.getQuery()), super.getPs(values.getQuery()), Sort.Direction.DESC, "createtime")); return new ResponseEntity<>(new RestResult(RestResultType.OK, page), HttpStatus.OK); } diff --git a/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiQualityController.java b/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiQualityController.java index e5c793d6..c4355101 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiQualityController.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/controller/api/ApiQualityController.java @@ -23,23 +23,20 @@ import com.chatopera.cc.persistence.repository.AgentServiceRepository; import com.chatopera.cc.util.Menu; import com.chatopera.cc.util.RestResult; import com.chatopera.cc.util.RestResultType; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; import java.util.ArrayList; import java.util.List; @@ -49,31 +46,24 @@ import java.util.List; */ @RestController @RequestMapping("/api/quality") +@RequiredArgsConstructor public class ApiQualityController extends Handler { - @Autowired - private AgentServiceRepository agentServiceRepository; + @NonNull + private final AgentServiceRepository agentServiceRepository; /** * 获取质检列表 - * - * @param request - * @param username 搜索用户名,精确搜索 - * @return */ @RequestMapping(method = RequestMethod.GET) @Menu(type = "apps", subtype = "app", access = true) - public ResponseEntity list(HttpServletRequest request, @Valid AgentService agentService, @Valid String begin, @Valid String end) { - Page page = agentServiceRepository.findAll(new Specification() { - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, - CriteriaBuilder cb) { - List list = new ArrayList(); - list.add((cb.equal(root.get("qualitystatus").as(String.class), MainContext.QualityStatusEnum.NODIS.toString()))); + public ResponseEntity list(HttpServletRequest request) { + Page page = agentServiceRepository.findAll((Specification) (root, query, cb) -> { + List list = new ArrayList<>(); + list.add((cb.equal(root.get("qualitystatus").as(String.class), MainContext.QualityStatusEnum.NODIS.toString()))); - Predicate[] p = new Predicate[list.size()]; - return cb.and(list.toArray(p)); - } + Predicate[] p = new Predicate[list.size()]; + return cb.and(list.toArray(p)); }, PageRequest.of(super.getP(request), super.getPs(request), Sort.Direction.DESC, "createtime")); return new ResponseEntity<>(new RestResult(RestResultType.OK, page), HttpStatus.OK); } diff --git a/contact-center/app/src/main/java/com/chatopera/cc/controller/apps/service/CommentController.java b/contact-center/app/src/main/java/com/chatopera/cc/controller/apps/service/CommentController.java index 64486378..68f43231 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/controller/apps/service/CommentController.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/controller/apps/service/CommentController.java @@ -20,27 +20,27 @@ import com.chatopera.cc.controller.Handler; import com.chatopera.cc.model.AgentService; import com.chatopera.cc.persistence.repository.AgentServiceRepository; import com.chatopera.cc.util.Menu; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; -import javax.validation.Valid; @Controller @RequestMapping("/service") -public class CommentController extends Handler{ - @Autowired - private AgentServiceRepository agentServiceRes ; - +@RequiredArgsConstructor +public class CommentController extends Handler { + @NonNull + private final AgentServiceRepository agentServiceRes; @RequestMapping("/comment/index") - @Menu(type = "service" , subtype = "comment" , admin= true) - public ModelAndView index(ModelMap map , HttpServletRequest request , String userid , String agentservice , @Valid String channel) { + @Menu(type = "service", subtype = "comment", admin = true) + public ModelAndView index(ModelMap map, HttpServletRequest request) { Page agentServiceList = agentServiceRes.findByOrgiAndSatisfaction(super.getOrgi(request), true, PageRequest.of(super.getP(request), super.getPs(request))); map.addAttribute("serviceList", agentServiceList); return request(super.createAppsTempletResponse("/apps/service/comment/index")); diff --git a/contact-center/app/src/main/java/com/chatopera/cc/socketio/handler/IMEventHandler.java b/contact-center/app/src/main/java/com/chatopera/cc/socketio/handler/IMEventHandler.java index 1226195a..b42a2b5c 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/socketio/handler/IMEventHandler.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/socketio/handler/IMEventHandler.java @@ -1,273 +1,263 @@ -/* - * Copyright (C) 2017 优客服-多渠道客服系统 - * Modifications copyright (C) 2018-2019 Chatopera Inc, - * - * 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.socketio.handler; - -import com.chatopera.cc.acd.ACDServiceRouter; -import com.chatopera.cc.acd.ACDVisitorDispatcher; -import com.chatopera.cc.acd.basic.ACDComposeContext; -import com.chatopera.cc.acd.basic.ACDMessageHelper; -import com.chatopera.cc.basic.MainContext; -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.Contacts; -import com.chatopera.cc.model.CousultInvite; -import com.chatopera.cc.persistence.repository.AgentServiceRepository; -import com.chatopera.cc.proxy.AgentUserProxy; -import com.chatopera.cc.proxy.OnlineUserProxy; -import com.chatopera.cc.socketio.client.NettyClients; -import com.chatopera.cc.socketio.message.AgentStatusMessage; -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; -import com.corundumstudio.socketio.annotation.OnConnect; -import com.corundumstudio.socketio.annotation.OnDisconnect; -import com.corundumstudio.socketio.annotation.OnEvent; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; -import java.util.Date; - -public class IMEventHandler { - private final static Logger logger = LoggerFactory.getLogger(IMEventHandler.class); - protected SocketIOServer server; - - public IMEventHandler(SocketIOServer server) { - this.server = server; - } - - static private AgentUserProxy agentUserProxy; - static private AgentServiceRepository agentServiceRepository; - static private ACDVisitorDispatcher acdVisitorDispatcher; - - /** - * 接入访客并未访客寻找坐席服务人员 - * - * @param client - */ - @OnConnect - public void onConnect(SocketIOClient client) { - try { - 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"); - - 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 {}, 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(); - String ip = MainUtils.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString()); - - /** - * 加入到 缓存列表 - */ - NettyClients.getInstance().putIMEventClient(user, client); - - /** - * 更新坐席服务类型 - */ - IMServiceUtils.shiftOpsType(user, orgi, MainContext.OptType.HUMAN); - - IP ipdata = null; - if ((StringUtils.isNotBlank(ip))) { - ipdata = IPTools.getInstance().findGeography(ip); - } - - /** - * 用户进入到对话连接 , 排队用户请求 , 如果返回失败, - * 表示当前坐席全忙,用户进入排队状态,当前提示信息 显示 当前排队的队列位置, - * 不可进行对话,用户发送的消息作为留言处理 - */ - final ACDComposeContext ctx = ACDMessageHelper.getWebIMComposeContext( - user, - nickname, - orgi, - session, - appid, - ip, - osname, - browser, - "", - ipdata, - MainContext.ChannelType.WEBIM.toString(), - skill, - agent, - title, - url, - traceid, - user, - isInvite, - MainContext.ChatInitiatorType.USER.toString()); - getAcdVisitorDispatcher().enqueue(ctx); - ACDServiceRouter.getAcdAgentService().notifyAgentUserProcessResult(ctx); - } else { - logger.warn("[onConnect] invalid connection, no user present."); - //非法链接 - client.disconnect(); - } - } catch (Exception e) { - logger.error("[onConnect] error", e); - client.disconnect(); - } - } - - //添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息 - @OnDisconnect - public void onDisconnect(SocketIOClient client) { - final String user = client.getHandshakeData().getSingleUrlParam("userid"); - final String orgi = client.getHandshakeData().getSingleUrlParam("orgi"); - logger.info("[onDisconnect] user {}, orgi {}", user, orgi); - if (user != null) { - try { - /** - * 用户主动断开服务 - */ - MainContext.getCache().findOneAgentUserByUserIdAndOrgi(user, orgi).ifPresent(p -> { - ACDServiceRouter.getAcdAgentService().finishAgentService(p - , orgi); - }); - } catch (Exception e) { - logger.warn("[onDisconnect] error", e); - } - NettyClients.getInstance().removeIMEventClient( - user, MainUtils.getContextID(client.getSessionId().toString())); - } - } - - // 消息接收入口,用于接受网站资源用户传入的 个人信息 - @OnEvent(value = "new") - public void onNewEvent(SocketIOClient client, AckRequest request, Contacts contacts) { - String user = client.getHandshakeData().getSingleUrlParam("userid"); - String orgi = client.getHandshakeData().getSingleUrlParam("orgi"); - - MainContext.getCache().findOneAgentUserByUserIdAndOrgi(user, orgi).ifPresent(p -> { - p.setName(contacts.getName()); - p.setPhone(contacts.getPhone()); - p.setEmail(contacts.getEmail()); - p.setResion(contacts.getMemo()); - p.setChatbotops(false); // 非机器人客服 - p.setOpttype(MainContext.OptType.HUMAN.toString()); - getAgentUserProxy().save(p); - }); - - getAgentServiceRepository().findOneByUseridAndOrgiOrderByLogindateDesc( - user, orgi).ifPresent(p -> { - p.setName(contacts.getName()); - p.setPhone(contacts.getPhone()); - p.setEmail(contacts.getEmail()); - p.setResion(contacts.getMemo()); - agentServiceRepository.save(p); - }); - } - - // 消息接收入口,坐席状态更新 - @OnEvent(value = "agentstatus") - public void onAgentStatusEvent(SocketIOClient client, AckRequest request, AgentStatusMessage data) { - logger.info("[onEvent] {}", data.getMessage()); - } - - // 消息接收入口,收发消息,用户向 坐席发送消息 和 向用户发送消息 - @OnEvent(value = "message") - public void onMessageEvent(SocketIOClient client, AckRequest request, ChatMessage data) { - if (data.getType() == null) { - data.setType("message"); - } - /** - * 以下代码主要用于检查 访客端的字数限制 - */ - CousultInvite invite = OnlineUserProxy.consult(data.getAppid(), data.getOrgi()); - - int dataLength = data.getMessage().length(); - if (invite != null && invite.getMaxwordsnum() > 0) { - if (StringUtils.isNotBlank(data.getMessage()) && dataLength > invite.getMaxwordsnum()) { - data.setMessage(data.getMessage().substring(0, invite.getMaxwordsnum())); - } - } -// else if (StringUtils.isNotBlank(data.getMessage()) && dataLength > 600) { -// data.setMessage(data.getMessage().substring(0, 600)); -// } - /** - * 处理表情 - */ - data.setMessage(MainUtils.processEmoti(data.getMessage())); - HumanUtils.processMessage(data, data.getUserid()); - } - - private static AgentUserProxy getAgentUserProxy() { - if (agentUserProxy == null) { - agentUserProxy = MainContext.getContext().getBean(AgentUserProxy.class); - } - return agentUserProxy; - } - - private static AgentServiceRepository getAgentServiceRepository() { - if (agentServiceRepository == null) { - agentServiceRepository = MainContext.getContext().getBean(AgentServiceRepository.class); - } - return agentServiceRepository; - } - - private static ACDVisitorDispatcher getAcdVisitorDispatcher() { - if (acdVisitorDispatcher == null) { - acdVisitorDispatcher = MainContext.getContext().getBean(ACDVisitorDispatcher.class); - } - return acdVisitorDispatcher; - } - -} +/* + * Copyright (C) 2017 优客服-多渠道客服系统 + * Modifications copyright (C) 2018-2019 Chatopera Inc, + * + * 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.socketio.handler; + +import com.chatopera.cc.acd.ACDServiceRouter; +import com.chatopera.cc.acd.ACDVisitorDispatcher; +import com.chatopera.cc.acd.basic.ACDComposeContext; +import com.chatopera.cc.acd.basic.ACDMessageHelper; +import com.chatopera.cc.basic.MainContext; +import com.chatopera.cc.basic.MainUtils; +import com.chatopera.cc.model.Contacts; +import com.chatopera.cc.model.CousultInvite; +import com.chatopera.cc.persistence.repository.AgentServiceRepository; +import com.chatopera.cc.proxy.AgentUserProxy; +import com.chatopera.cc.proxy.OnlineUserProxy; +import com.chatopera.cc.socketio.client.NettyClients; +import com.chatopera.cc.socketio.message.AgentStatusMessage; +import com.chatopera.cc.socketio.message.ChatMessage; +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; +import com.corundumstudio.socketio.annotation.OnConnect; +import com.corundumstudio.socketio.annotation.OnDisconnect; +import com.corundumstudio.socketio.annotation.OnEvent; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; + +@SuppressWarnings("unused") +public class IMEventHandler { + private static final Logger logger = LoggerFactory.getLogger(IMEventHandler.class); + private static AgentUserProxy agentUserProxy; + private static AgentServiceRepository agentServiceRepository; + private static ACDVisitorDispatcher acdVisitorDispatcher; + protected SocketIOServer server; + + public IMEventHandler(SocketIOServer server) { + this.server = server; + } + + private static AgentUserProxy getAgentUserProxy() { + if (agentUserProxy == null) { + agentUserProxy = MainContext.getContext().getBean(AgentUserProxy.class); + } + return agentUserProxy; + } + + private static AgentServiceRepository getAgentServiceRepository() { + if (agentServiceRepository == null) { + agentServiceRepository = MainContext.getContext().getBean(AgentServiceRepository.class); + } + return agentServiceRepository; + } + + private static ACDVisitorDispatcher getAcdVisitorDispatcher() { + if (acdVisitorDispatcher == null) { + acdVisitorDispatcher = MainContext.getContext().getBean(ACDVisitorDispatcher.class); + } + return acdVisitorDispatcher; + } + + /** + * 接入访客并未访客寻找坐席服务人员 + */ + @OnConnect + public void onConnect(SocketIOClient client) { + try { + 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"); + + 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 {}, 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(); + String ip = MainUtils.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString()); + + /* + * 加入到 缓存列表 + */ + NettyClients.getInstance().putIMEventClient(user, client); + + /* + * 更新坐席服务类型 + */ + IMServiceUtils.shiftOpsType(user, orgi, MainContext.OptType.HUMAN); + + IP ipdata = null; + if ((StringUtils.isNotBlank(ip))) { + ipdata = IPTools.getInstance().findGeography(ip); + } + + /* + * 用户进入到对话连接 , 排队用户请求 , 如果返回失败, + * 表示当前坐席全忙,用户进入排队状态,当前提示信息 显示 当前排队的队列位置, + * 不可进行对话,用户发送的消息作为留言处理 + */ + final ACDComposeContext ctx = ACDMessageHelper.getWebIMComposeContext( + user, + nickname, + orgi, + session, + appid, + ip, + osname, + browser, + "", + ipdata, + MainContext.ChannelType.WEBIM.toString(), + skill, + agent, + title, + url, + traceid, + user, + isInvite, + MainContext.ChatInitiatorType.USER.toString()); + getAcdVisitorDispatcher().enqueue(ctx); + ACDServiceRouter.getAcdAgentService().notifyAgentUserProcessResult(ctx); + } else { + logger.warn("[onConnect] invalid connection, no user present."); + //非法链接 + client.disconnect(); + } + } catch (Exception e) { + logger.error("[onConnect] error", e); + client.disconnect(); + } + } + + //添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息 + @OnDisconnect + public void onDisconnect(SocketIOClient client) { + final String user = client.getHandshakeData().getSingleUrlParam("userid"); + final String orgi = client.getHandshakeData().getSingleUrlParam("orgi"); + logger.info("[onDisconnect] user {}, orgi {}", user, orgi); + if (user != null) { + try { + /* + * 用户主动断开服务 + */ + MainContext.getCache().findOneAgentUserByUserIdAndOrgi(user, orgi) + .ifPresent(p -> ACDServiceRouter.getAcdAgentService().finishAgentService(p, orgi)); + } catch (Exception e) { + logger.warn("[onDisconnect] error", e); + } + NettyClients.getInstance().removeIMEventClient( + user, MainUtils.getContextID(client.getSessionId().toString())); + } + } + + // 消息接收入口,用于接受网站资源用户传入的 个人信息 + @OnEvent(value = "new") + public void onNewEvent(SocketIOClient client, AckRequest request, Contacts contacts) { + String user = client.getHandshakeData().getSingleUrlParam("userid"); + String orgi = client.getHandshakeData().getSingleUrlParam("orgi"); + + MainContext.getCache().findOneAgentUserByUserIdAndOrgi(user, orgi).ifPresent(p -> { + p.setName(contacts.getName()); + p.setPhone(contacts.getPhone()); + p.setEmail(contacts.getEmail()); + p.setResion(contacts.getMemo()); + p.setChatbotops(false); // 非机器人客服 + p.setOpttype(MainContext.OptType.HUMAN.toString()); + getAgentUserProxy().save(p); + }); + + getAgentServiceRepository().findOneByUseridAndOrgiOrderByLogindateDesc( + user, orgi).ifPresent(p -> { + p.setName(contacts.getName()); + p.setPhone(contacts.getPhone()); + p.setEmail(contacts.getEmail()); + p.setResion(contacts.getMemo()); + agentServiceRepository.save(p); + }); + } + + // 消息接收入口,坐席状态更新 + @OnEvent(value = "agentstatus") + public void onAgentStatusEvent(SocketIOClient client, AckRequest request, AgentStatusMessage data) { + logger.info("[onEvent] {}", data.getMessage()); + } + + // 消息接收入口,收发消息,用户向 坐席发送消息 和 向用户发送消息 + @OnEvent(value = "message") + public void onMessageEvent(SocketIOClient client, AckRequest request, ChatMessage data) { + if (data.getType() == null) { + data.setType("message"); + } + /* + * 以下代码主要用于检查 访客端的字数限制 + */ + CousultInvite invite = OnlineUserProxy.consult(data.getAppid(), data.getOrgi()); + + int dataLength = data.getMessage().length(); + if (invite != null && invite.getMaxwordsnum() > 0) { + if (StringUtils.isNotBlank(data.getMessage()) && dataLength > invite.getMaxwordsnum()) { + data.setMessage(data.getMessage().substring(0, invite.getMaxwordsnum())); + } + } +// else if (StringUtils.isNotBlank(data.getMessage()) && dataLength > 600) { +// data.setMessage(data.getMessage().substring(0, 600)); +// } + /* + * 处理表情 + */ + data.setMessage(MainUtils.processEmoti(data.getMessage())); + HumanUtils.processMessage(data, data.getUserid()); + } + +} diff --git a/contact-center/app/src/main/java/com/chatopera/cc/socketio/util/HumanUtils.java b/contact-center/app/src/main/java/com/chatopera/cc/socketio/util/HumanUtils.java index ffe6c1e3..432d60f4 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/socketio/util/HumanUtils.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/socketio/util/HumanUtils.java @@ -1,152 +1,149 @@ -/* - * Copyright (C) 2017 优客服-多渠道客服系统 - * Modifications copyright (C) 2018-2019 Chatopera Inc, - * - * 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.socketio.util; - -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.model.AgentService; -import com.chatopera.cc.model.AgentUser; -import com.chatopera.cc.persistence.repository.AgentServiceRepository; -import com.chatopera.cc.socketio.message.ChatMessage; -import com.chatopera.cc.socketio.message.Message; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HumanUtils { - private final static Logger logger = LoggerFactory.getLogger(HumanUtils.class); - private static AgentServiceRepository agentServiceRes; - - - /** - * 发送文本消息 - * - * @param data - * @param userid - */ - public static void processMessage(ChatMessage data, String userid) { - processMessage(data, MainContext.MediaType.TEXT.toString(), userid); - } - - /** - * 发送各种消息的底层方法 - * - * @param chatMessage - * @param msgtype - * @param userid - */ - 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).orElse(null); - - Message outMessage = new Message(); - - /** - * 访客的昵称 - */ - // TODO 确定该值代表访客昵称 - String userNickName = (agentUser != null) && StringUtils.isNotBlank( - agentUser.getNickname()) ? agentUser.getNickname() : ""; - - if (agentUser != null && StringUtils.isNotBlank(agentUser.getAgentserviceid())) { - AgentService agentService = getAgentServiceRes().findOne( - agentUser.getAgentserviceid()); - if (StringUtils.isNotBlank(agentService.getUsername())) { - userNickName = agentService.getUsername(); - } - } - - // 将访客名称修改为关联联系人的名称 - chatMessage.setUsername(userNickName); - - outMessage.setMessage(chatMessage.getMessage()); - outMessage.setFilename(chatMessage.getFilename()); - outMessage.setFilesize(chatMessage.getFilesize()); - - outMessage.setMessageType(msgtype); - outMessage.setCalltype(MainContext.CallType.IN.toString()); - outMessage.setAgentUser(agentUser); - outMessage.setSnsAccount(null); - - Message statusMessage = null; - if (agentUser == null) { - statusMessage = new Message(); - statusMessage.setMessage(chatMessage.getMessage()); - statusMessage.setMessageType(MainContext.MessageType.STATUS.toString()); - statusMessage.setCalltype(MainContext.CallType.OUT.toString()); - statusMessage.setMessage("当前坐席全忙,请稍候"); - } else { - chatMessage.setUserid(agentUser.getUserid()); - chatMessage.setTouser(agentUser.getAgentno()); - chatMessage.setAgentuser(agentUser.getId()); - chatMessage.setAgentserviceid(agentUser.getAgentserviceid()); - chatMessage.setAppid(agentUser.getAppid()); - chatMessage.setOrgi(agentUser.getOrgi()); - chatMessage.setMsgtype(msgtype); - // agentUser作为 session id - chatMessage.setUsession(agentUser.getUserid()); - chatMessage.setContextid(agentUser.getContextid()); - chatMessage.setCalltype(MainContext.CallType.IN.toString()); - if (StringUtils.isNotBlank(agentUser.getAgentno())) { - chatMessage.setTouser(agentUser.getAgentno()); - } - chatMessage.setChannel(agentUser.getChannel()); - outMessage.setContextid(agentUser.getContextid()); - outMessage.setChannel(agentUser.getChannel()); - outMessage.setAgentUser(agentUser); - outMessage.setCreatetime(Constants.DISPLAY_DATE_FORMATTER.format(chatMessage.getCreatetime())); - } - - outMessage.setChannelMessage(chatMessage); - - // 将消息返送给 访客 - if (StringUtils.isNotBlank(chatMessage.getUserid()) && MainContext.MessageType.MESSAGE.toString().equals( - chatMessage.getType())) { - MainContext.getPeerSyncIM().send(ReceiverType.VISITOR, ChannelType.toValue(outMessage.getChannel()), - outMessage.getAppid(), MessageType.MESSAGE, chatMessage.getUserid(), - outMessage, true); - if (statusMessage != null) { - MainContext.getPeerSyncIM().send(ReceiverType.VISITOR, ChannelType.toValue(outMessage.getChannel()), - outMessage.getAppid(), MessageType.STATUS, chatMessage.getUserid(), - statusMessage, true); - } - } - - // 将消息发送给 坐席 - if (agentUser != null && StringUtils.isNotBlank(agentUser.getAgentno())) { - MainContext.getPeerSyncIM().send(ReceiverType.AGENT, - ChannelType.WEBIM, - agentUser.getAppid(), - MessageType.MESSAGE, - agentUser.getAgentno(), - outMessage, true); - } - } - - private static AgentServiceRepository getAgentServiceRes() { - if (agentServiceRes == null) { - agentServiceRes = MainContext.getContext().getBean(AgentServiceRepository.class); - } - return agentServiceRes; - } - - -} +/* + * Copyright (C) 2017 优客服-多渠道客服系统 + * Modifications copyright (C) 2018-2019 Chatopera Inc, + * + * 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.socketio.util; + +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.model.AgentService; +import com.chatopera.cc.model.AgentUser; +import com.chatopera.cc.persistence.repository.AgentServiceRepository; +import com.chatopera.cc.socketio.message.ChatMessage; +import com.chatopera.cc.socketio.message.Message; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public class HumanUtils { + private final static Logger logger = LoggerFactory.getLogger(HumanUtils.class); + private static AgentServiceRepository agentServiceRes; + + + /** + * 发送文本消息 + */ + public static void processMessage(ChatMessage data, String userid) { + processMessage(data, MainContext.MediaType.TEXT.toString(), userid); + } + + /** + * 发送各种消息的底层方法 + */ + 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).orElse(null); + + Message outMessage = new Message(); + + /* + * 访客的昵称 + */ + // TODO 确定该值代表访客昵称 + String userNickName = (agentUser != null) && StringUtils.isNotBlank( + agentUser.getNickname()) ? agentUser.getNickname() : ""; + + if (agentUser != null && StringUtils.isNotBlank(agentUser.getAgentserviceid())) { + Optional optional = getAgentServiceRes().findById(agentUser.getAgentserviceid()); + if (optional.isPresent()) { + AgentService agentService = optional.get(); + if (StringUtils.isNotBlank(agentService.getUsername())) { + userNickName = agentService.getUsername(); + } + } + } + + // 将访客名称修改为关联联系人的名称 + chatMessage.setUsername(userNickName); + + outMessage.setMessage(chatMessage.getMessage()); + outMessage.setFilename(chatMessage.getFilename()); + outMessage.setFilesize(chatMessage.getFilesize()); + + outMessage.setMessageType(msgtype); + outMessage.setCalltype(MainContext.CallType.IN.toString()); + outMessage.setAgentUser(agentUser); + outMessage.setSnsAccount(null); + + Message statusMessage = null; + if (agentUser == null) { + statusMessage = new Message(); + statusMessage.setMessage(chatMessage.getMessage()); + statusMessage.setMessageType(MainContext.MessageType.STATUS.toString()); + statusMessage.setCalltype(MainContext.CallType.OUT.toString()); + statusMessage.setMessage("当前坐席全忙,请稍候"); + } else { + chatMessage.setUserid(agentUser.getUserid()); + chatMessage.setTouser(agentUser.getAgentno()); + chatMessage.setAgentuser(agentUser.getId()); + chatMessage.setAgentserviceid(agentUser.getAgentserviceid()); + chatMessage.setAppid(agentUser.getAppid()); + chatMessage.setOrgi(agentUser.getOrgi()); + chatMessage.setMsgtype(msgtype); + // agentUser作为 session id + chatMessage.setUsession(agentUser.getUserid()); + chatMessage.setContextid(agentUser.getContextid()); + chatMessage.setCalltype(MainContext.CallType.IN.toString()); + if (StringUtils.isNotBlank(agentUser.getAgentno())) { + chatMessage.setTouser(agentUser.getAgentno()); + } + chatMessage.setChannel(agentUser.getChannel()); + outMessage.setContextid(agentUser.getContextid()); + outMessage.setChannel(agentUser.getChannel()); + outMessage.setAgentUser(agentUser); + outMessage.setCreatetime(Constants.DISPLAY_DATE_FORMATTER.format(chatMessage.getCreatetime())); + } + + outMessage.setChannelMessage(chatMessage); + + // 将消息返送给 访客 + if (StringUtils.isNotBlank(chatMessage.getUserid()) && MainContext.MessageType.MESSAGE.toString().equals( + chatMessage.getType())) { + MainContext.getPeerSyncIM().send(ReceiverType.VISITOR, ChannelType.toValue(outMessage.getChannel()), + outMessage.getAppid(), MessageType.MESSAGE, chatMessage.getUserid(), + outMessage, true); + if (statusMessage != null) { + MainContext.getPeerSyncIM().send(ReceiverType.VISITOR, ChannelType.toValue(outMessage.getChannel()), + outMessage.getAppid(), MessageType.STATUS, chatMessage.getUserid(), + statusMessage, true); + } + } + + // 将消息发送给 坐席 + if (agentUser != null && StringUtils.isNotBlank(agentUser.getAgentno())) { + MainContext.getPeerSyncIM().send(ReceiverType.AGENT, + ChannelType.WEBIM, + agentUser.getAppid(), + MessageType.MESSAGE, + agentUser.getAgentno(), + outMessage, true); + } + } + + private static AgentServiceRepository getAgentServiceRes() { + if (agentServiceRes == null) { + agentServiceRes = MainContext.getContext().getBean(AgentServiceRepository.class); + } + return agentServiceRes; + } + + +}