diff --git a/contact-center/app/src/main/java/com/chatopera/cc/core/UKDataContext.java b/contact-center/app/src/main/java/com/chatopera/cc/core/UKDataContext.java index 6df22979..62ef391f 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/core/UKDataContext.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/core/UKDataContext.java @@ -335,6 +335,28 @@ public class UKDataContext { } } + // 服务处理类型 + public enum OptTypeEnum { + CHATBOT("机器人客服", 1), + HUMAN("人工客服", 2); + + private final String name; + private final int index; + + private OptTypeEnum(String name, int index) { + this.name = name; + this.index = index; + } + + public String toLetters() { + return super.toString().toLowerCase(); + } + + public String toString() { + return this.name; + } + } + // 外呼计划状态 public enum CallOutDialplanStatusEnum { RUNNING("执行中", 1), diff --git a/contact-center/app/src/main/java/com/chatopera/cc/util/Constants.java b/contact-center/app/src/main/java/com/chatopera/cc/util/Constants.java index fe5e6e41..e695417a 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/util/Constants.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/util/Constants.java @@ -25,7 +25,6 @@ import java.util.Set; public class Constants { - public final static String OPT_TYPE = "webim"; public final static String MINIO_BUCKET = "chatopera"; /** diff --git a/contact-center/app/src/main/java/com/chatopera/cc/webim/service/acd/ServiceQuene.java b/contact-center/app/src/main/java/com/chatopera/cc/webim/service/acd/ServiceQuene.java index 8503c961..792729e6 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/webim/service/acd/ServiceQuene.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/webim/service/acd/ServiceQuene.java @@ -523,47 +523,44 @@ public class ServiceQuene { /** * 为访客 分配坐席, ACD策略,此处 AgentStatus 是建议 的 坐席, 如果启用了 历史服务坐席 优先策略, 则会默认检查历史坐席是否空闲,如果空闲,则分配,如果不空闲,则 分配当前建议的坐席 * - * @param agentStatus * @param agentUser * @param orgi * @return * @throws Exception */ - public static AgentService processAiService(AiUser aiUser, String orgi) throws Exception { + public static AgentService processChatbotService(final AgentUser agentUser, final String orgi) { AgentService agentService = new AgentService(); //放入缓存的对象 AgentServiceRepository agentServiceRes = UKDataContext.getContext().getBean(AgentServiceRepository.class); - if (!StringUtils.isBlank(aiUser.getAgentserviceid())) { - agentService = agentServiceRes.findByIdAndOrgi(aiUser.getAgentserviceid(), orgi); - agentService.setEndtime(new Date()); + 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(UKDataContext.AgentUserStatusEnum.END.toString()); } else { - agentService.setServicetime(new Date()); - agentService.setLogindate(new Date()); + agentService.setServicetime(now); + agentService.setLogindate(now); agentService.setOrgi(orgi); - agentService.setOwner(aiUser.getContextid()); - agentService.setSessionid(aiUser.getSessionid()); - if (aiUser.getIpdata() != null) { - agentService.setRegion(aiUser.getIpdata().getRegion()); - } + agentService.setOwner(agentUser.getContextid()); + agentService.setSessionid(agentUser.getSessionid()); + agentService.setRegion(agentUser.getRegion()); + agentService.setUsername(agentUser.getUsername()); + agentService.setChannel(agentUser.getChannel()); - agentService.setUsername(aiUser.getUsername()); - agentService.setChannel(aiUser.getChannel()); - - if (!StringUtils.isBlank(aiUser.getContextid())) { - agentService.setContextid(aiUser.getContextid()); + if (StringUtils.isNotBlank(agentUser.getContextid())) { + agentService.setContextid(agentUser.getContextid()); } else { - agentService.setContextid(aiUser.getSessionid()); + agentService.setContextid(agentUser.getSessionid()); } - agentService.setUserid(aiUser.getUserid()); - agentService.setAiid(aiUser.getAiid()); + agentService.setUserid(agentUser.getUserid()); + agentService.setAiid(agentUser.getAgentno()); agentService.setAiservice(true); agentService.setStatus(UKDataContext.AgentUserStatusEnum.INSERVICE.toString()); - agentService.setAppid(aiUser.getAppid()); + agentService.setAppid(agentUser.getAppid()); agentService.setLeavemsg(false); } diff --git a/contact-center/app/src/main/java/com/chatopera/cc/webim/service/task/CallOutWireTask.java b/contact-center/app/src/main/java/com/chatopera/cc/webim/service/task/CallOutWireTask.java index b98843ab..9be84dd2 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/webim/service/task/CallOutWireTask.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/webim/service/task/CallOutWireTask.java @@ -258,7 +258,7 @@ public class CallOutWireTask implements MessageListener { onlineUser.setCreater(onlineUser.getId()); onlineUser.setCreatetime(createtime); onlineUser.setUpdatetime(createtime); - onlineUser.setOptype(Constants.OPT_TYPE); + onlineUser.setOptype(UKDataContext.OptTypeEnum.HUMAN.toString()); onlineUser.setAppid(channel); } else { onlineUser.setOlduser("1"); // 不是 老访客 diff --git a/contact-center/app/src/main/java/com/chatopera/cc/webim/util/OnlineUserUtils.java b/contact-center/app/src/main/java/com/chatopera/cc/webim/util/OnlineUserUtils.java index 861e4917..2da7a330 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/webim/util/OnlineUserUtils.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/webim/util/OnlineUserUtils.java @@ -599,39 +599,33 @@ public class OnlineUserUtils { */ public static void offline(String user, String orgi) throws Exception { if (UKDataContext.getContext() != null) { - try { - OnlineUser onlineUser = (OnlineUser) CacheHelper.getOnlineUserCacheBean().getCacheObject(user, orgi); - if (onlineUser != null) { - CousultInvite invite = OnlineUserUtils.cousult(onlineUser.getAppid(), onlineUser.getOrgi(), UKDataContext.getContext().getBean(ConsultInviteRepository.class)); - if (invite.isTraceuser()) { - onlineUser.setStatus(UKDataContext.OnlineUserOperatorStatus.OFFLINE.toString()); - onlineUser.setInvitestatus(UKDataContext.OnlineUserInviteStatus.DEFAULT.toString()); - onlineUser.setBetweentime((int) (new Date().getTime() - onlineUser.getLogintime().getTime())); - onlineUser.setUpdatetime(new Date()); - OnlineUserRepository service = UKDataContext.getContext().getBean( - OnlineUserRepository.class); - service.save(onlineUser); + OnlineUser onlineUser = (OnlineUser) CacheHelper.getOnlineUserCacheBean().getCacheObject(user, orgi); + if (onlineUser != null) { + CousultInvite invite = OnlineUserUtils.cousult(onlineUser.getAppid(), onlineUser.getOrgi(), UKDataContext.getContext().getBean(ConsultInviteRepository.class)); + if (invite.isTraceuser()) { + onlineUser.setStatus(UKDataContext.OnlineUserOperatorStatus.OFFLINE.toString()); + onlineUser.setInvitestatus(UKDataContext.OnlineUserInviteStatus.DEFAULT.toString()); + onlineUser.setBetweentime((int) (new Date().getTime() - onlineUser.getLogintime().getTime())); + onlineUser.setUpdatetime(new Date()); + OnlineUserRepository service = UKDataContext.getContext().getBean( + OnlineUserRepository.class); + service.save(onlineUser); - OnlineUserHisRepository onlineHisUserRes = UKDataContext.getContext().getBean(OnlineUserHisRepository.class); - { - List hisList = onlineHisUserRes.findBySessionidAndOrgi(onlineUser.getSessionid(), orgi); - OnlineUserHis his = null; - if (hisList.size() > 0) { - his = hisList.get(0); - } else { - his = new OnlineUserHis(); - } - - UKTools.copyProperties(onlineUser, his); - his.setDataid(onlineUser.getId()); - onlineHisUserRes.save(his); + OnlineUserHisRepository onlineHisUserRes = UKDataContext.getContext().getBean(OnlineUserHisRepository.class); + { + List hisList = onlineHisUserRes.findBySessionidAndOrgi(onlineUser.getSessionid(), orgi); + OnlineUserHis his = null; + if (hisList.size() > 0) { + his = hisList.get(0); + } else { + his = new OnlineUserHis(); } + + UKTools.copyProperties(onlineUser, his); + his.setDataid(onlineUser.getId()); + onlineHisUserRes.save(his); } } - } catch (ClassCastException e) { - // #TODO workaround for - // https://github.com/chatopera/cosin/issues/75 - // AiUser is not saved, just remove from cache. } CacheHelper.getOnlineUserCacheBean().delete(user, orgi); } diff --git a/contact-center/app/src/main/java/com/chatopera/cc/webim/util/server/handler/ChatbotEventHandler.java b/contact-center/app/src/main/java/com/chatopera/cc/webim/util/server/handler/ChatbotEventHandler.java index e3967678..599a3989 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/webim/util/server/handler/ChatbotEventHandler.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/webim/util/server/handler/ChatbotEventHandler.java @@ -16,12 +16,15 @@ package com.chatopera.cc.webim.util.server.handler; import com.chatopera.cc.core.UKDataContext; +import com.chatopera.cc.util.IP; import com.chatopera.cc.util.IPTools; import com.chatopera.cc.util.UKTools; import com.chatopera.cc.util.client.NettyClients; import com.chatopera.cc.webim.service.acd.ServiceQuene; import com.chatopera.cc.webim.service.cache.CacheHelper; +import com.chatopera.cc.webim.service.repository.AgentUserRepository; import com.chatopera.cc.webim.service.repository.ConsultInviteRepository; +import com.chatopera.cc.webim.service.repository.OnlineUserRepository; import com.chatopera.cc.webim.util.MessageUtils; import com.chatopera.cc.webim.util.OnlineUserUtils; import com.chatopera.cc.webim.util.router.OutMessageRouter; @@ -48,6 +51,9 @@ public class ChatbotEventHandler { protected SocketIOServer server; + private AgentUserRepository agentUserRes; + private OnlineUserRepository onlineUserRes; + @Autowired public ChatbotEventHandler(SocketIOServer server) { this.server = server; @@ -57,22 +63,23 @@ public class ChatbotEventHandler { public void onConnect(SocketIOClient client) { try { String user = client.getHandshakeData().getSingleUrlParam("userid"); + String nickname = client.getHandshakeData().getSingleUrlParam("nickname"); String orgi = client.getHandshakeData().getSingleUrlParam("orgi"); -// String session = client.getHandshakeData().getSingleUrlParam("session") ; + String session = client.getHandshakeData().getSingleUrlParam("session"); String appid = client.getHandshakeData().getSingleUrlParam("appid"); String aiid = client.getHandshakeData().getSingleUrlParam("aiid"); // String agent = client.getHandshakeData().getSingleUrlParam("agent") ; // String skill = client.getHandshakeData().getSingleUrlParam("skill") ; - logger.info("[chatbot socket.io] onConnect user {}, orgi {}, appid {}, aiid {}", user, orgi, appid, aiid); + Date now = new Date(); if (StringUtils.isNotBlank(user)) { // /** // * 加入到 缓存列表 // */ - NettyClients.getInstance().putIMEventClient(user, client); + NettyClients.getInstance().putChatbotEventClient(user, client); MessageOutContent outMessage = new MessageOutContent(); CousultInvite invite = OnlineUserUtils.cousult(appid, orgi, UKDataContext.getContext().getBean(ConsultInviteRepository.class)); - if (invite != null && !StringUtils.isBlank(invite.getAisuccesstip())) { + if (invite != null && StringUtils.isNotBlank(invite.getAisuccesstip())) { outMessage.setMessage(invite.getAisuccesstip()); } else { outMessage.setMessage("欢迎使用华夏春松机器人客服!"); @@ -80,43 +87,91 @@ public class ChatbotEventHandler { outMessage.setMessageType(UKDataContext.MessageTypeEnum.MESSAGE.toString()); outMessage.setCalltype(UKDataContext.CallTypeEnum.IN.toString()); - outMessage.setNickName("AI"); - outMessage.setCreatetime(UKTools.dateFormate.format(new Date())); + outMessage.setNickName(invite.getAiname()); + outMessage.setCreatetime(UKTools.dateFormate.format(now)); client.sendEvent(UKDataContext.MessageTypeEnum.STATUS.toString(), outMessage); InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress(); String ip = UKTools.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString()); - AiUser aiUser = new AiUser(user, user, System.currentTimeMillis(), orgi, IPTools.getInstance().findGeography(ip)); - aiUser.setSessionid(UKTools.getContextID(client.getSessionId().toString())); - aiUser.setAppid(appid); - aiUser.setAiid(aiid); - aiUser.setUsername(UKDataContext.GUEST_USER + "_" + UKTools.genIDByKey(aiUser.getId())); - aiUser.setChannel(UKDataContext.ChannelTypeEnum.WEBIM.toString()); + OnlineUser onlineUser = getOnlineUserRes().findOne(user); - AgentService agentService = ServiceQuene.processAiService(aiUser, orgi); - aiUser.setAgentserviceid(agentService.getId()); + if (onlineUser == null) { + onlineUser = new OnlineUser(); + onlineUser.setAppid(appid); + if (StringUtils.isNotBlank(nickname)) { + onlineUser.setUsername(nickname); + } else { + onlineUser.setUsername(UKDataContext.GUEST_USER + "_" + UKTools.genIDByKey(user)); + } - CacheHelper.getOnlineUserCacheBean().put(user, aiUser, UKDataContext.SYSTEM_ORGI); + onlineUser.setSessionid(session); + onlineUser.setOptype(UKDataContext.OptTypeEnum.CHATBOT.toString()); + onlineUser.setUserid(user); + onlineUser.setId(user); + onlineUser.setOrgi(orgi); + onlineUser.setChannel(UKDataContext.ChannelTypeEnum.WEBIM.toString()); + onlineUser.setIp(ip); + onlineUser.setUpdatetime(now); + onlineUser.setLogintime(now); + onlineUser.setCreatetime(now); + IP ipdata = IPTools.getInstance().findGeography(ip); + onlineUser.setCity(ipdata.getCity()); + onlineUser.setCountry(ipdata.getCountry()); + onlineUser.setProvince(ipdata.getProvince()); + onlineUser.setIsp(ipdata.getIsp()); + onlineUser.setRegion(ipdata.getRegion()); + onlineUser.setStatus(UKDataContext.OnlineUserOperatorStatus.ONLINE.toString()); + } + // 在线客服访客咨询记录 + AgentUser agentUser = new AgentUser(onlineUser.getId(), + UKDataContext.ChannelTypeEnum.WEBIM.toString(), // callout + onlineUser.getId(), + onlineUser.getUsername(), + UKDataContext.SYSTEM_ORGI, + appid); + + agentUser.setServicetime(now); + agentUser.setCreatetime(now); + agentUser.setUpdatetime(now); + agentUser.setSessionid(session); + // 聊天机器人处理的请求 + agentUser.setOpttype(UKDataContext.OptTypeEnum.CHATBOT.toString()); + agentUser.setAgentno(aiid); // 聊天机器人ID + agentUser.setCity(onlineUser.getCity()); + agentUser.setProvince(onlineUser.getProvince()); + agentUser.setCountry(onlineUser.getCountry()); + AgentService agentService = ServiceQuene.processChatbotService(agentUser, orgi); + agentUser.setAgentserviceid(agentService.getId()); + + getAgentUserRes().save(agentUser); + getOnlineUserRes().save(onlineUser); + CacheHelper.getAgentUserCacheBean().put(user, agentUser, orgi); + CacheHelper.getOnlineUserCacheBean().put(user, onlineUser, orgi); } } catch (Exception e) { e.printStackTrace(); } } - //添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息 @OnDisconnect public void onDisconnect(SocketIOClient client) throws Exception { String user = client.getHandshakeData().getSingleUrlParam("userid"); String orgi = client.getHandshakeData().getSingleUrlParam("orgi"); - if (!StringUtils.isBlank(user)) { - NettyClients.getInstance().removeIMEventClient(user, UKTools.getContextID(client.getSessionId().toString())); - AiUser aiUser = (AiUser) CacheHelper.getOnlineUserCacheBean().getCacheObject(user, orgi); - if (aiUser != null) { - ServiceQuene.processAiService(aiUser, orgi); - CacheHelper.getOnlineUserCacheBean().delete(user, UKDataContext.SYSTEM_ORGI); + if (StringUtils.isNotBlank(user)) { + NettyClients.getInstance().removeChatbotEventClient(user, UKTools.getContextID(client.getSessionId().toString())); + AgentUser agentUser = (AgentUser) CacheHelper.getAgentUserCacheBean().getCacheObject(user, orgi); + OnlineUser onlineUser = (OnlineUser) CacheHelper.getOnlineUserCacheBean().getCacheObject(user, orgi); + if (agentUser != null) { + ServiceQuene.processChatbotService(agentUser, orgi); + CacheHelper.getAgentUserCacheBean().delete(user, UKDataContext.SYSTEM_ORGI); + CacheHelper.getOnlineUserCacheBean().delete(user, orgi); + agentUser.setStatus(UKDataContext.OnlineUserOperatorStatus.OFFLINE.toString()); + onlineUser.setStatus(UKDataContext.OnlineUserOperatorStatus.OFFLINE.toString()); + getAgentUserRes().save(agentUser); + getOnlineUserRes().save(onlineUser); } } client.disconnect(); @@ -159,22 +214,21 @@ public class ChatbotEventHandler { * 处理表情 */ data.setMessage(UKTools.processEmoti(data.getMessage())); - data.setTousername(UKDataContext.ChannelTypeEnum.AI.toString()); + data.setTousername(invite.getAiname()); data.setAiid(aiid); - Object cacheData = (AiUser) CacheHelper.getOnlineUserCacheBean().getCacheObject(user, orgi); - if (cacheData != null && cacheData instanceof AiUser) { - AiUser aiUser = (AiUser) cacheData; - data.setAgentserviceid(aiUser.getAgentserviceid()); - data.setChannel(aiUser.getChannel()); + AgentUser agentUser = (AgentUser) CacheHelper.getAgentUserCacheBean().getCacheObject(user, orgi); + if (agentUser != null) { + data.setAgentserviceid(agentUser.getAgentserviceid()); + data.setChannel(agentUser.getChannel()); /** * 一定要设置 ContextID */ - data.setContextid(aiUser.getAgentserviceid()); + data.setContextid(agentUser.getAgentserviceid()); } MessageOutContent outMessage = MessageUtils.createAiMessage(data, data.getAppid(), data.getChannel(), UKDataContext.CallTypeEnum.IN.toString(), UKDataContext.AiItemType.USERINPUT.toString(), UKDataContext.MediaTypeEnum.TEXT.toString(), data.getUserid()); - if (!StringUtils.isBlank(data.getUserid()) && UKDataContext.MessageTypeEnum.MESSAGE.toString().equals(data.getType())) { + if (StringUtils.isNotBlank(data.getUserid()) && UKDataContext.MessageTypeEnum.MESSAGE.toString().equals(data.getType())) { if (!StringUtils.isBlank(data.getTouser())) { OutMessageRouter router = null; router = (OutMessageRouter) UKDataContext.getContext().getBean(data.getChannel()); @@ -182,11 +236,33 @@ public class ChatbotEventHandler { router.handler(data.getTouser(), UKDataContext.MessageTypeEnum.MESSAGE.toString(), data.getAppid(), outMessage); } } - if (cacheData != null && cacheData instanceof AiUser) { - AiUser aiUser = (AiUser) cacheData; - aiUser.setTime(System.currentTimeMillis()); - CacheHelper.getOnlineUserCacheBean().put(user, aiUser, UKDataContext.SYSTEM_ORGI); + if (agentUser != null) { + Date now = new Date(); + agentUser.setUpdatetime(now); + agentUser.setLastmessage(now); + agentUser.setLastmsg(data.getMessage()); + CacheHelper.getAgentUserCacheBean().put(user, agentUser, UKDataContext.SYSTEM_ORGI); } } } -} \ No newline at end of file + + /** + * Lazy load + * @return + */ + public AgentUserRepository getAgentUserRes() { + if (agentUserRes == null) + agentUserRes = UKDataContext.getContext().getBean(AgentUserRepository.class); + return agentUserRes; + } + + /** + * Lazy load + * @return + */ + public OnlineUserRepository getOnlineUserRes() { + if (onlineUserRes == null) + onlineUserRes = UKDataContext.getContext().getBean(OnlineUserRepository.class); + return onlineUserRes; + } +} \ No newline at end of file diff --git a/contact-center/app/src/main/java/com/chatopera/cc/webim/web/handler/apps/internet/IMController.java b/contact-center/app/src/main/java/com/chatopera/cc/webim/web/handler/apps/internet/IMController.java index 2362342e..7adfa149 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/webim/web/handler/apps/internet/IMController.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/webim/web/handler/apps/internet/IMController.java @@ -298,20 +298,16 @@ public class IMController extends Handler { contacts = processContacts(orgi, contacts, appid, userid); } if (StringUtils.isNotBlank(sign)) { - try { - OnlineUserUtils.online(super.getIMUser(request, sign, contacts != null ? contacts.getName() : null), - orgi, - sessionid, - UKDataContext.OnlineUserTypeStatus.WEBIM.toString(), - request, - UKDataContext.ChannelTypeEnum.WEBIM.toString(), - appid, - contacts, - invite); - } catch (java.lang.ClassCastException e) { - // #TODO workaround for - // https://github.com/chatopera/cosin/issues/75 - } + OnlineUserUtils.online(super.getIMUser(request, sign, contacts != null ? contacts.getName() : null), + orgi, + sessionid, + UKDataContext.OnlineUserTypeStatus.WEBIM.toString(), + request, + UKDataContext.ChannelTypeEnum.WEBIM.toString(), + appid, + contacts, + invite); + } OnlineUserUtils.webIMClients.putClient(userid, new WebIMClient(userid, client, emitter)); } diff --git a/contact-center/app/src/main/java/com/chatopera/cc/webim/web/model/AgentUser.java b/contact-center/app/src/main/java/com/chatopera/cc/webim/web/model/AgentUser.java index f0b31998..d06c3121 100644 --- a/contact-center/app/src/main/java/com/chatopera/cc/webim/web/model/AgentUser.java +++ b/contact-center/app/src/main/java/com/chatopera/cc/webim/web/model/AgentUser.java @@ -79,7 +79,8 @@ public class AgentUser implements Serializable, Comparable { private Date waittingtimestart = new Date(); private Date lastgetmessage = new Date(); private String lastmsg; - + private String opttype; + private String skill ; //请求的技能组 private String agent ; //请求的坐席 @@ -565,6 +566,14 @@ public class AgentUser implements Serializable, Comparable { this.skillname = skillname; } + public String getOpttype() { + return opttype; + } + + public void setOpttype(String opttype) { + this.opttype = opttype; + } + @Override public int compareTo(AgentUser o) { int ret = 0 ; diff --git a/contact-center/config/sql/cskefu-MySQL-slim.sql b/contact-center/config/sql/cskefu-MySQL-slim.sql index 7425c7f7..b2ba8da3 100644 --- a/contact-center/config/sql/cskefu-MySQL-slim.sql +++ b/contact-center/config/sql/cskefu-MySQL-slim.sql @@ -650,6 +650,7 @@ CREATE TABLE `uk_agentuser` ( `datadept` varchar(255) DEFAULT NULL COMMENT '创建人部门', `intime` int(32) DEFAULT NULL COMMENT '接入时间', `batid` varchar(32) DEFAULT NULL COMMENT '批次ID', + `opttype` varchar(32) DEFAULT NULL COMMENT '服务处理类型', `ipaddr` varchar(50) DEFAULT NULL COMMENT 'IP地址', `osname` varchar(100) DEFAULT NULL COMMENT '操作系统名称', `browser` varchar(100) DEFAULT NULL COMMENT '浏览器',