diff --git a/contact-center/app/pom.xml b/contact-center/app/pom.xml index 01da5fe9..df97d49d 100644 --- a/contact-center/app/pom.xml +++ b/contact-center/app/pom.xml @@ -88,6 +88,7 @@ org.springframework.boot spring-boot-maven-plugin + 3.1.3 diff --git a/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java b/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java index 6df47781..85ca0eb4 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java @@ -1,14 +1,14 @@ -/* - * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. - * , Licensed under the Chunsong Public +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html * 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. - * Copyright (C) 2019-2022 Chatopera Inc, , - * Licensed under the Apache License, Version 2.0, + * Copyright (C) 2019-2022 Chatopera Inc, , + * Licensed under the Apache License, Version 2.0, * http://www.apache.org/licenses/LICENSE-2.0 */ package com.cskefu.cc.basic; @@ -217,4 +217,13 @@ public class Constants { public static final String AUTH_TOKEN_TYPE_BEARER = "Bearer"; public static final String AUTH_TOKEN_TYPE_BASIC = "Basic"; + /** + * License + */ + public static final String LICENSE_SERVER_INST_ID = "SERVERINSTID"; + public static final String LICENSE_SERVICE_NAME = "SERVICENAME"; + public static final String LICENSE_SERVICE_NAME_PREFIX = "春松客服"; + public static final String LICENSES = "LICENSES"; + public static final String METAKV_DATATYPE_STRING = "string"; + } diff --git a/contact-center/app/src/main/java/com/cskefu/cc/config/AppCtxRefreshEventListener.java b/contact-center/app/src/main/java/com/cskefu/cc/config/AppCtxRefreshEventListener.java index 234abb1a..51d4a6a8 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/config/AppCtxRefreshEventListener.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/config/AppCtxRefreshEventListener.java @@ -24,6 +24,7 @@ import com.cskefu.cc.model.BlackEntity; import com.cskefu.cc.model.SysDic; import com.cskefu.cc.model.SystemConfig; import com.cskefu.cc.persistence.repository.*; +import com.cskefu.cc.proxy.LicenseProxy; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,7 +37,6 @@ public class AppCtxRefreshEventListener implements ApplicationListener, Licensed under the Chunsong Public - * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html - * 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. - * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, - * http://www.apache.org/licenses/LICENSE-2.0 - * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.cskefu.cc.persistence.repository; - -import com.cskefu.cc.model.MetadataTable; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface MetadataRepository extends JpaRepository{ - - MetadataTable findByTablename(String tablename); - - Page findAll(Pageable paramPageable); - - int countByTablename(String tableName) ; - - List findAll(); -} +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * 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. + * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, + * http://www.apache.org/licenses/LICENSE-2.0 + * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.cskefu.cc.persistence.repository; + +import com.cskefu.cc.model.MetadataTable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface MetadataRepository extends JpaRepository{ + + MetadataTable findByTablename(String tablename); + + Page findAll(Pageable paramPageable); + + int countByTablename(String tableName) ; + + List findAll(); +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/persistence/repository/MetakvRepository.java b/contact-center/app/src/main/java/com/cskefu/cc/persistence/repository/MetakvRepository.java new file mode 100644 index 00000000..717c697f --- /dev/null +++ b/contact-center/app/src/main/java/com/cskefu/cc/persistence/repository/MetakvRepository.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * 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.cskefu.cc.persistence.repository; + +import com.cskefu.cc.model.Metakv; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MetakvRepository extends JpaRepository { + + Optional findFirstByMetakey(final String p1); + +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/plugins/chatbot/ChatbotEventHandler.java b/contact-center/app/src/main/java/com/cskefu/cc/plugins/chatbot/ChatbotEventHandler.java index ef54e3a2..1ef1de9d 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/plugins/chatbot/ChatbotEventHandler.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/plugins/chatbot/ChatbotEventHandler.java @@ -1,371 +1,371 @@ -/* - * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. - * , Licensed under the Chunsong Public - * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html - * 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. - * Copyright (C) 2019-Jun. 2023 Chatopera Inc, , - * Licensed under the Apache License, Version 2.0, - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.cskefu.cc.plugins.chatbot; - -import com.chatopera.bot.sdk.Response; -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 com.cskefu.cc.acd.ACDServiceRouter; -import com.cskefu.cc.basic.Constants; -import com.cskefu.cc.basic.MainContext; -import com.cskefu.cc.basic.MainUtils; -import com.cskefu.cc.model.*; -import com.cskefu.cc.persistence.repository.AgentUserRepository; -import com.cskefu.cc.persistence.repository.ChatbotRepository; -import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository; -import com.cskefu.cc.proxy.OnlineUserProxy; -import com.cskefu.cc.socketio.client.NettyClients; -import com.cskefu.cc.socketio.message.AgentStatusMessage; -import com.cskefu.cc.socketio.message.ChatMessage; -import com.cskefu.cc.socketio.message.Message; -import com.cskefu.cc.socketio.util.IMServiceUtils; -import com.cskefu.cc.util.IP; -import com.cskefu.cc.util.IPTools; -import org.apache.commons.lang3.StringUtils; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -import java.net.InetSocketAddress; -import java.util.Date; - -public class ChatbotEventHandler { - private static final Logger logger = LoggerFactory.getLogger(ChatbotEventHandler.class); - - protected final SocketIOServer server; - - private static AgentUserRepository agentUserRes; - private static PassportWebIMUserRepository onlineUserRes; - private static ChatbotRepository chatbotRes; - private static ChatbotProxy chatbotProxy; - - @Autowired - public ChatbotEventHandler(SocketIOServer server) { - this.server = server; - } - - @OnConnect - public void onConnect(SocketIOClient client) { - try { - String user = client.getHandshakeData().getSingleUrlParam("userid"); - String nickname = client.getHandshakeData().getSingleUrlParam("nickname"); - String session = MainUtils.getContextID(client.getHandshakeData().getSingleUrlParam("session")); - String appid = client.getHandshakeData().getSingleUrlParam("appid"); - String aiid = client.getHandshakeData().getSingleUrlParam("aiid"); - logger.info( - "[onConnect] userid {}, nickname {}, session {}, appid {}, aiid {}", user, nickname, session, appid, - aiid); - - client.set("aiid", aiid); - client.set("session", session); - client.set("userid", user); - client.set("appid", appid); - - Date now = new Date(); - - if (StringUtils.isNotBlank(user)) { - /** - * 加入到 缓存列表 - */ - NettyClients.getInstance().putChatbotEventClient(user, client); - CousultInvite invite = OnlineUserProxy.consult(appid); - - /** - * 更新坐席服务类型 - */ - IMServiceUtils.shiftOpsType(user, MainContext.OptType.CHATBOT); - - // send out tip - Message tip = new Message(); - tip.setMessage("您正在使用机器人客服!"); - tip.setMessageType(MainContext.MessageType.MESSAGE.toString()); - tip.setCalltype(MainContext.CallType.IN.toString()); - tip.setCreatetime(MainUtils.dateFormate.format(now)); - - client.sendEvent(MainContext.MessageType.STATUS.toString(), tip); - - // send out welcome message - if (invite != null) { - Chatbot chatbot = getChatbotRes().findById(invite.getAiid()).orElse(null); - com.chatopera.bot.sdk.Chatbot bot = new com.chatopera.bot.sdk.Chatbot( - chatbot.getClientId(), chatbot.getSecret(), chatbot.getBaseUrl()); - Response result = bot.command("GET", "/"); - - // 发送欢迎语 - if (result.getRc() == 0) { - JSONObject details = (JSONObject) result.getData(); - ChatMessage welcome = new ChatMessage(); - String welcomeTextMessage = details.getString("welcome"); - if (StringUtils.isNotBlank(welcomeTextMessage)) { - welcome.setCalltype(MainContext.CallType.OUT.toString()); - welcome.setAppid(appid); - welcome.setAiid(aiid); - welcome.setMessage(welcomeTextMessage); - welcome.setTouser(user); - welcome.setMsgtype(MainContext.MessageType.MESSAGE.toString()); - welcome.setUserid(user); - welcome.setUsername(invite.getAiname()); - welcome.setUpdatetime(System.currentTimeMillis()); - client.sendEvent(MainContext.MessageType.MESSAGE.toString(), welcome); - } - - // 发送常见问题列表 - JSONObject faqhotresp = bot.conversation(user, "__faq_hot_list"); - logger.info("faqhot {}", faqhotresp.toString()); - if (faqhotresp.getInt("rc") == 0) { - JSONObject faqhotdata = faqhotresp.getJSONObject("data"); - if ((!faqhotdata.getBoolean("logic_is_fallback")) && - faqhotdata.has("string") && - faqhotdata.has("params")) { - ChatMessage faqhotmsg = new ChatMessage(); - faqhotmsg.setCalltype(MainContext.CallType.OUT.toString()); - faqhotmsg.setAppid(appid); - faqhotmsg.setAiid(aiid); - faqhotmsg.setMessage(faqhotdata.getString("string")); - faqhotmsg.setExpmsg(faqhotdata.getJSONArray("params").toString()); - faqhotmsg.setTouser(user); - faqhotmsg.setMsgtype(MainContext.MessageType.MESSAGE.toString()); - faqhotmsg.setUserid(user); - faqhotmsg.setUsername(invite.getAiname()); - faqhotmsg.setUpdatetime(System.currentTimeMillis()); - client.sendEvent(MainContext.MessageType.MESSAGE.toString(), faqhotmsg); - } - } - } else if (result.getRc() == 999 || result.getRc() == 998) { - logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError()); - } else { - logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError()); - } - } - - InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress(); - String ip = MainUtils.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString()); - PassportWebIMUser passportWebIMUser = getOnlineUserRes().findById(user).orElse(null); - - if (passportWebIMUser == null) { - passportWebIMUser = new PassportWebIMUser(); - passportWebIMUser.setAppid(appid); - if (StringUtils.isNotBlank(nickname)) { - passportWebIMUser.setUsername(nickname); - } else { - passportWebIMUser.setUsername(Constants.GUEST_USER + "_" + MainUtils.genIDByKey(user)); - } - - passportWebIMUser.setSessionid(session); - passportWebIMUser.setOptype(MainContext.OptType.CHATBOT.toString()); - passportWebIMUser.setUserid(user); - passportWebIMUser.setId(user); - passportWebIMUser.setChannel(MainContext.ChannelType.WEBIM.toString()); - passportWebIMUser.setIp(ip); - passportWebIMUser.setUpdatetime(now); - passportWebIMUser.setLogintime(now); - passportWebIMUser.setCreatetime(now); - IP ipdata = IPTools.getInstance().findGeography(ip); - passportWebIMUser.setCity(ipdata.getCity()); - passportWebIMUser.setCountry(ipdata.getCountry()); - passportWebIMUser.setProvince(ipdata.getProvince()); - passportWebIMUser.setIsp(ipdata.getIsp()); - passportWebIMUser.setRegion(ipdata.getRegion()); - passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.ONLINE.toString()); - } - - // 在线客服访客咨询记录 - AgentUser agentUser = new AgentUser( - passportWebIMUser.getId(), - MainContext.ChannelType.WEBIM.toString(), // callout - passportWebIMUser.getId(), - passportWebIMUser.getUsername(), - appid); - - agentUser.setServicetime(now); - agentUser.setCreatetime(now); - agentUser.setUpdatetime(now); - agentUser.setSessionid(session); - agentUser.setRegion(passportWebIMUser.getRegion()); - - // 聊天机器人处理的请求 - agentUser.setOpttype(MainContext.OptType.CHATBOT.toString()); - agentUser.setAgentno(aiid); // 聊天机器人ID - agentUser.setAgentname(invite != null ? invite.getAiname() : "机器人客服"); - agentUser.setCity(passportWebIMUser.getCity()); - agentUser.setProvince(passportWebIMUser.getProvince()); - agentUser.setCountry(passportWebIMUser.getCountry()); - AgentService agentService = ACDServiceRouter.getAcdChatbotService().processChatbotService( - invite != null ? invite.getAiname() : "机器人客服", agentUser); - agentUser.setAgentserviceid(agentService.getId()); - - // 标记为机器人坐席 - agentUser.setChatbotops(true); - - // 保存到MySQL - getAgentUserRes().save(agentUser); - getOnlineUserRes().save(passportWebIMUser); - } - } catch (Exception e) { - logger.info("[onConnect] error", e); - } - } - - // 添加 @OnDisconnect 事件,客户端断开连接时调用,刷新客户端信息 - @OnDisconnect - public void onDisconnect(SocketIOClient client) { - String user = client.getHandshakeData().getSingleUrlParam("userid"); - if (StringUtils.isNotBlank(user)) { - NettyClients.getInstance().removeChatbotEventClient( - user, MainUtils.getContextID(client.getSessionId().toString())); - PassportWebIMUser passportWebIMUser = MainContext.getCache().findOneOnlineUserByUserId(user); - - MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> { - ACDServiceRouter.getAcdChatbotService().processChatbotService(null, p); - - MainContext.getCache().deleteAgentUserByUserId(p); - MainContext.getCache().deleteOnlineUserById(user); - - p.setStatus(MainContext.AgentUserStatusEnum.END.toString()); - passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.OFFLINE.toString()); - - getAgentUserRes().save(p); - getOnlineUserRes().save(passportWebIMUser); - }); - } - client.disconnect(); - } - - // 消息接收入口,网站有新用户接入对话 - @OnEvent(value = "new") - public void onEvent(SocketIOClient client, AckRequest request, Message data) { - - } - - // 消息接收入口,坐席状态更新 - @OnEvent(value = "agentstatus") - public void onEvent(SocketIOClient client, AckRequest request, AgentStatusMessage data) { - logger.info("[onEvent] agentstatus: ", data.getMessage()); - } - - // 消息接收入口,收发消息,用户向机器人发送消息 - @OnEvent(value = "message") - public void onEvent(SocketIOClient client, AckRequest request, ChatMessage data) { - String aiid = client.get("aiid"); - String user = client.get("userid"); - String sessionid = client.get("session"); - String appid = client.get("appid"); - logger.info( - "[onEvent] message: session {}, aiid {}, userid {}, dataType {}, appid {}", sessionid, aiid, - user, data.getType(), appid); - - // ignore event if dataType is not message. - if (!StringUtils.equals(data.getType(), Constants.IM_MESSAGE_TYPE_MESSAGE)) { - return; - } - - MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> { - /** - * 以下代码主要用于检查 访客端的字数限制 - */ - CousultInvite invite = OnlineUserProxy.consult(data.getAppid()); - // ignore event if no invite found. - if (invite == null) { - return; - } - - // ignore if Chatbot is turnoff. - if (!invite.isAi()) { - return; - } - - Date now = new Date(); - if (invite.getMaxwordsnum() > 0) { - if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > invite.getMaxwordsnum()) { - data.setMessage(data.getMessage().substring(0, invite.getMaxwordsnum())); - } - } else if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > 300) { - data.setMessage(data.getMessage().substring(0, 300)); - } - - data.setUsession(user); // 绑定唯一用户 - data.setSessionid(sessionid); - data.setMessage(MainUtils.processEmoti(data.getMessage())); // 处理表情 - data.setTouser(aiid); - data.setUsername(p.getUsername()); - data.setAiid(aiid); - data.setAgentserviceid(p.getAgentserviceid()); - data.setChannel(p.getChanneltype()); - data.setContextid(p.getAgentserviceid()); // 一定要设置 ContextID - data.setCalltype(MainContext.CallType.IN.toString()); - - // 保存并发送消息给访客 - getChatbotProxy().createTextMessage( - data, - MainContext.CallType.IN.toString()); - - // 更新访客咨询记录 - p.setUpdatetime(now); - p.setLastmessage(now); - p.setLastmsg(data.getMessage()); - getAgentUserRes().save(p); - - // 发送消息给Bot - getChatbotProxy().publishMessage(data, Constants.CHATBOT_EVENT_TYPE_CHAT); - }); - - - } - - /** - * Lazy load - * - * @return - */ - private AgentUserRepository getAgentUserRes() { - if (agentUserRes == null) { - agentUserRes = MainContext.getContext().getBean(AgentUserRepository.class); - } - - return agentUserRes; - } - - /** - * Lazy load - * - * @return - */ - private ChatbotProxy getChatbotProxy() { - if (chatbotProxy == null) { - chatbotProxy = MainContext.getContext().getBean(ChatbotProxy.class); - } - return chatbotProxy; - } - - private PassportWebIMUserRepository getOnlineUserRes() { - if (onlineUserRes == null) { - onlineUserRes = MainContext.getContext().getBean(PassportWebIMUserRepository.class); - } - - return onlineUserRes; - } - - private ChatbotRepository getChatbotRes() { - if (chatbotRes == null) { - chatbotRes = MainContext.getContext().getBean(ChatbotRepository.class); - } - - return chatbotRes; - } -} +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * 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. + * Copyright (C) 2019-Jun. 2023 Chatopera Inc, , + * Licensed under the Apache License, Version 2.0, + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.cskefu.cc.plugins.chatbot; + +import com.chatopera.bot.sdk.Response; +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 com.cskefu.cc.acd.ACDServiceRouter; +import com.cskefu.cc.basic.Constants; +import com.cskefu.cc.basic.MainContext; +import com.cskefu.cc.basic.MainUtils; +import com.cskefu.cc.model.*; +import com.cskefu.cc.persistence.repository.AgentUserRepository; +import com.cskefu.cc.persistence.repository.ChatbotRepository; +import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository; +import com.cskefu.cc.proxy.OnlineUserProxy; +import com.cskefu.cc.socketio.client.NettyClients; +import com.cskefu.cc.socketio.message.AgentStatusMessage; +import com.cskefu.cc.socketio.message.ChatMessage; +import com.cskefu.cc.socketio.message.Message; +import com.cskefu.cc.socketio.util.IMServiceUtils; +import com.cskefu.cc.util.IP; +import com.cskefu.cc.util.IPTools; +import org.apache.commons.lang3.StringUtils; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.net.InetSocketAddress; +import java.util.Date; + +public class ChatbotEventHandler { + private static final Logger logger = LoggerFactory.getLogger(ChatbotEventHandler.class); + + protected final SocketIOServer server; + + private static AgentUserRepository agentUserRes; + private static PassportWebIMUserRepository onlineUserRes; + private static ChatbotRepository chatbotRes; + private static ChatbotProxy chatbotProxy; + + @Autowired + public ChatbotEventHandler(SocketIOServer server) { + this.server = server; + } + + @OnConnect + public void onConnect(SocketIOClient client) { + try { + String user = client.getHandshakeData().getSingleUrlParam("userid"); + String nickname = client.getHandshakeData().getSingleUrlParam("nickname"); + String session = MainUtils.getContextID(client.getHandshakeData().getSingleUrlParam("session")); + String appid = client.getHandshakeData().getSingleUrlParam("appid"); + String aiid = client.getHandshakeData().getSingleUrlParam("aiid"); + logger.info( + "[onConnect] userid {}, nickname {}, session {}, appid {}, aiid {}", user, nickname, session, appid, + aiid); + + client.set("aiid", aiid); + client.set("session", session); + client.set("userid", user); + client.set("appid", appid); + + Date now = new Date(); + + if (StringUtils.isNotBlank(user)) { + /** + * 加入到 缓存列表 + */ + NettyClients.getInstance().putChatbotEventClient(user, client); + CousultInvite invite = OnlineUserProxy.consult(appid); + + /** + * 更新坐席服务类型 + */ + IMServiceUtils.shiftOpsType(user, MainContext.OptType.CHATBOT); + + // send out tip + Message tip = new Message(); + tip.setMessage("您正在使用机器人客服!"); + tip.setMessageType(MainContext.MessageType.MESSAGE.toString()); + tip.setCalltype(MainContext.CallType.IN.toString()); + tip.setCreatetime(MainUtils.dateFormate.format(now)); + + client.sendEvent(MainContext.MessageType.STATUS.toString(), tip); + + // send out welcome message + if (invite != null) { + Chatbot chatbot = getChatbotRes().findById(invite.getAiid()).orElse(null); + com.chatopera.bot.sdk.Chatbot bot = new com.chatopera.bot.sdk.Chatbot( + chatbot.getClientId(), chatbot.getSecret(), chatbot.getBaseUrl()); + Response result = bot.command("GET", "/"); + + // 发送欢迎语 + if (result.getRc() == 0) { + JSONObject details = (JSONObject) result.getData(); + ChatMessage welcome = new ChatMessage(); + String welcomeTextMessage = details.getString("welcome"); + if (StringUtils.isNotBlank(welcomeTextMessage)) { + welcome.setCalltype(MainContext.CallType.OUT.toString()); + welcome.setAppid(appid); + welcome.setAiid(aiid); + welcome.setMessage(welcomeTextMessage); + welcome.setTouser(user); + welcome.setMsgtype(MainContext.MessageType.MESSAGE.toString()); + welcome.setUserid(user); + welcome.setUsername(invite.getAiname()); + welcome.setUpdatetime(System.currentTimeMillis()); + client.sendEvent(MainContext.MessageType.MESSAGE.toString(), welcome); + } + + // 发送常见问题列表 + JSONObject faqhotresp = bot.conversation(user, "__faq_hot_list"); + logger.info("faqhot {}", faqhotresp.toString()); + if (faqhotresp.getInt("rc") == 0) { + JSONObject faqhotdata = faqhotresp.getJSONObject("data"); + if ((!faqhotdata.getBoolean("logic_is_fallback")) && + faqhotdata.has("string") && + faqhotdata.has("params")) { + ChatMessage faqhotmsg = new ChatMessage(); + faqhotmsg.setCalltype(MainContext.CallType.OUT.toString()); + faqhotmsg.setAppid(appid); + faqhotmsg.setAiid(aiid); + faqhotmsg.setMessage(faqhotdata.getString("string")); + faqhotmsg.setExpmsg(faqhotdata.getJSONArray("params").toString()); + faqhotmsg.setTouser(user); + faqhotmsg.setMsgtype(MainContext.MessageType.MESSAGE.toString()); + faqhotmsg.setUserid(user); + faqhotmsg.setUsername(invite.getAiname()); + faqhotmsg.setUpdatetime(System.currentTimeMillis()); + client.sendEvent(MainContext.MessageType.MESSAGE.toString(), faqhotmsg); + } + } + } else if (result.getRc() == 999 || result.getRc() == 998) { + logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError()); + } else { + logger.error("[chat] chatbot agent response rc {}, error {}", result.getRc(), result.getError()); + } + } + + InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress(); + String ip = MainUtils.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString()); + PassportWebIMUser passportWebIMUser = getOnlineUserRes().findById(user).orElse(null); + + if (passportWebIMUser == null) { + passportWebIMUser = new PassportWebIMUser(); + passportWebIMUser.setAppid(appid); + if (StringUtils.isNotBlank(nickname)) { + passportWebIMUser.setUsername(nickname); + } else { + passportWebIMUser.setUsername(Constants.GUEST_USER + "_" + MainUtils.genIDByKey(user)); + } + + passportWebIMUser.setSessionid(session); + passportWebIMUser.setOptype(MainContext.OptType.CHATBOT.toString()); + passportWebIMUser.setUserid(user); + passportWebIMUser.setId(user); + passportWebIMUser.setChannel(MainContext.ChannelType.WEBIM.toString()); + passportWebIMUser.setIp(ip); + passportWebIMUser.setUpdatetime(now); + passportWebIMUser.setLogintime(now); + passportWebIMUser.setCreatetime(now); + IP ipdata = IPTools.getInstance().findGeography(ip); + passportWebIMUser.setCity(ipdata.getCity()); + passportWebIMUser.setCountry(ipdata.getCountry()); + passportWebIMUser.setProvince(ipdata.getProvince()); + passportWebIMUser.setIsp(ipdata.getIsp()); + passportWebIMUser.setRegion(ipdata.getRegion()); + passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.ONLINE.toString()); + } + + // 在线客服访客咨询记录 + AgentUser agentUser = new AgentUser( + passportWebIMUser.getId(), + MainContext.ChannelType.WEBIM.toString(), // callout + passportWebIMUser.getId(), + passportWebIMUser.getUsername(), + appid); + + agentUser.setServicetime(now); + agentUser.setCreatetime(now); + agentUser.setUpdatetime(now); + agentUser.setSessionid(session); + agentUser.setRegion(passportWebIMUser.getRegion()); + + // 聊天机器人处理的请求 + agentUser.setOpttype(MainContext.OptType.CHATBOT.toString()); + agentUser.setAgentno(aiid); // 聊天机器人ID + agentUser.setAgentname(invite != null ? invite.getAiname() : "机器人客服"); + agentUser.setCity(passportWebIMUser.getCity()); + agentUser.setProvince(passportWebIMUser.getProvince()); + agentUser.setCountry(passportWebIMUser.getCountry()); + AgentService agentService = ACDServiceRouter.getAcdChatbotService().processChatbotService( + invite != null ? invite.getAiname() : "机器人客服", agentUser); + agentUser.setAgentserviceid(agentService.getId()); + + // 标记为机器人坐席 + agentUser.setChatbotops(true); + + // 保存到MySQL + getAgentUserRes().save(agentUser); + getOnlineUserRes().save(passportWebIMUser); + } + } catch (Exception e) { + logger.info("[onConnect] error", e); + } + } + + // 添加 @OnDisconnect 事件,客户端断开连接时调用,刷新客户端信息 + @OnDisconnect + public void onDisconnect(SocketIOClient client) { + String user = client.getHandshakeData().getSingleUrlParam("userid"); + if (StringUtils.isNotBlank(user)) { + NettyClients.getInstance().removeChatbotEventClient( + user, MainUtils.getContextID(client.getSessionId().toString())); + PassportWebIMUser passportWebIMUser = MainContext.getCache().findOneOnlineUserByUserId(user); + + MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> { + ACDServiceRouter.getAcdChatbotService().processChatbotService(null, p); + + MainContext.getCache().deleteAgentUserByUserId(p); + MainContext.getCache().deleteOnlineUserById(user); + + p.setStatus(MainContext.AgentUserStatusEnum.END.toString()); + passportWebIMUser.setStatus(MainContext.OnlineUserStatusEnum.OFFLINE.toString()); + + getAgentUserRes().save(p); + getOnlineUserRes().save(passportWebIMUser); + }); + } + client.disconnect(); + } + + // 消息接收入口,网站有新用户接入对话 + @OnEvent(value = "new") + public void onEvent(SocketIOClient client, AckRequest request, Message data) { + + } + + // 消息接收入口,坐席状态更新 + @OnEvent(value = "agentstatus") + public void onEvent(SocketIOClient client, AckRequest request, AgentStatusMessage data) { + logger.info("[onEvent] agentstatus: ", data.getMessage()); + } + + // 消息接收入口,收发消息,用户向机器人发送消息 + @OnEvent(value = "message") + public void onEvent(SocketIOClient client, AckRequest request, ChatMessage data) { + String aiid = client.get("aiid"); + String user = client.get("userid"); + String sessionid = client.get("session"); + String appid = client.get("appid"); + logger.info( + "[onEvent] message: session {}, aiid {}, userid {}, dataType {}, appid {}", sessionid, aiid, + user, data.getType(), appid); + + // ignore event if dataType is not message. + if (!StringUtils.equals(data.getType(), Constants.IM_MESSAGE_TYPE_MESSAGE)) { + return; + } + + MainContext.getCache().findOneAgentUserByUserId(user).ifPresent(p -> { + /** + * 以下代码主要用于检查 访客端的字数限制 + */ + CousultInvite invite = OnlineUserProxy.consult(data.getAppid()); + // ignore event if no invite found. + if (invite == null) { + return; + } + + // ignore if Chatbot is turnoff. + if (!invite.isAi()) { + return; + } + + Date now = new Date(); + if (invite.getMaxwordsnum() > 0) { + if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > invite.getMaxwordsnum()) { + data.setMessage(data.getMessage().substring(0, invite.getMaxwordsnum())); + } + } else if (StringUtils.isNotBlank(data.getMessage()) && data.getMessage().length() > 300) { + data.setMessage(data.getMessage().substring(0, 300)); + } + + data.setUsession(user); // 绑定唯一用户 + data.setSessionid(sessionid); + data.setMessage(MainUtils.processEmoti(data.getMessage())); // 处理表情 + data.setTouser(aiid); + data.setUsername(p.getUsername()); + data.setAiid(aiid); + data.setAgentserviceid(p.getAgentserviceid()); + data.setChannel(p.getChanneltype()); + data.setContextid(p.getAgentserviceid()); // 一定要设置 ContextID + data.setCalltype(MainContext.CallType.IN.toString()); + + // 保存并发送消息给访客 + getChatbotProxy().createTextMessage( + data, + MainContext.CallType.IN.toString()); + + // 更新访客咨询记录 + p.setUpdatetime(now); + p.setLastmessage(now); + p.setLastmsg(data.getMessage()); + getAgentUserRes().save(p); + + // 发送消息给Bot + getChatbotProxy().publishMessage(data, Constants.CHATBOT_EVENT_TYPE_CHAT); + }); + + + } + + /** + * Lazy load + * + * @return + */ + private AgentUserRepository getAgentUserRes() { + if (agentUserRes == null) { + agentUserRes = MainContext.getContext().getBean(AgentUserRepository.class); + } + + return agentUserRes; + } + + /** + * Lazy load + * + * @return + */ + private ChatbotProxy getChatbotProxy() { + if (chatbotProxy == null) { + chatbotProxy = MainContext.getContext().getBean(ChatbotProxy.class); + } + return chatbotProxy; + } + + private PassportWebIMUserRepository getOnlineUserRes() { + if (onlineUserRes == null) { + onlineUserRes = MainContext.getContext().getBean(PassportWebIMUserRepository.class); + } + + return onlineUserRes; + } + + private ChatbotRepository getChatbotRes() { + if (chatbotRes == null) { + chatbotRes = MainContext.getContext().getBean(ChatbotRepository.class); + } + + return chatbotRes; + } +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/proxy/LicenseProxy.java b/contact-center/app/src/main/java/com/cskefu/cc/proxy/LicenseProxy.java new file mode 100644 index 00000000..eb9c00f6 --- /dev/null +++ b/contact-center/app/src/main/java/com/cskefu/cc/proxy/LicenseProxy.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * 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.cskefu.cc.proxy; + +import com.cskefu.cc.basic.Constants; +import com.cskefu.cc.basic.MainUtils; +import com.cskefu.cc.model.Metakv; +import com.cskefu.cc.persistence.repository.MetakvRepository; +import com.cskefu.cc.util.Base62; +import org.json.JSONArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.Optional; + +@Service +public class LicenseProxy { + private final static Logger logger = LoggerFactory.getLogger(LicenseProxy.class); + + @Autowired + private MetakvRepository metkvRes; + + /** + * 初始化 serverinstId + * serverinstId 作为服务唯一的实例ID + */ + public void checkOnStartup() { + Optional metaServerinstIdOpt = metkvRes.findFirstByMetakey(Constants.LICENSE_SERVER_INST_ID); + if (metaServerinstIdOpt.isEmpty()) { + // 没有 serverinstId 信息,初始化 + final String serverinstId = MainUtils.getUUID(); + createMetakv(Constants.LICENSE_SERVER_INST_ID, serverinstId, Constants.METAKV_DATATYPE_STRING); + } + + Optional metaServicenameOpt = metkvRes.findFirstByMetakey(Constants.LICENSE_SERVICE_NAME); + if (metaServicenameOpt.isEmpty()) { + // 没有 Service Name 信息,初始化 + final String serviceName = generateLicenseServiceName(); + createMetakv(Constants.LICENSE_SERVICE_NAME, serviceName, Constants.METAKV_DATATYPE_STRING); + } + + Optional metaLicensesOpt = metkvRes.findFirstByMetakey(Constants.LICENSES); + if (metaLicensesOpt.isEmpty()) { + // 没有 license 信息,初始化 + createMetakv(Constants.LICENSES, (new JSONArray()).toString(), Constants.METAKV_DATATYPE_STRING); + } + } + + + /** + * 建立 Metakv 数据 + * + * @param key + * @param value + * @param datatype + */ + public void createMetakv(final String key, final String value, final String datatype) { + Date now = new Date(); + Metakv metakv = new Metakv(); + metakv.setCreatetime(now); + metakv.setUpdatetime(now); + metakv.setMetakey(key); + metakv.setMetavalue(value); + metakv.setDatatype(datatype); + + metkvRes.save(metakv); + } + + /** + * 生成随机字符串,作为服务名称 + * + * @return + */ + private String generateLicenseServiceName() { + StringBuffer sb = new StringBuffer(); + + sb.append(Constants.LICENSE_SERVICE_NAME_PREFIX); + sb.append(Base62.generatingRandomAlphanumericString(5)); + + return sb.toString(); + } +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/util/Base62.java b/contact-center/app/src/main/java/com/cskefu/cc/util/Base62.java index 597c9150..64cd5722 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/util/Base62.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/util/Base62.java @@ -1,15 +1,15 @@ /* - * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. - * , Licensed under the Chunsong Public + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html * 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. - * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, + * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, * http://www.apache.org/licenses/LICENSE-2.0 - * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, + * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, * http://www.apache.org/licenses/LICENSE-2.0 */ package com.cskefu.cc.util; @@ -17,47 +17,68 @@ package com.cskefu.cc.util; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; +import java.util.Random; + public class Base62 { - private static final int BINARY = 0x2; + private static final int BINARY = 0x2; - private static final int NUMBER_61 = 0x0000003d; - - static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', - 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z' }; + private static final int NUMBER_61 = 0x0000003d; + + static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z'}; - public static String encode(long value){ - return encode(String.valueOf(value)).toLowerCase() ; - } - - public static String encode(String str){ - String md5Hex = DigestUtils.md5Hex(str); - // 6 digit binary can indicate 62 letter & number from 0-9a-zA-Z - int binaryLength = 6 * 6; - long binaryLengthFixer = Long.valueOf(StringUtils.repeat("1", binaryLength), BINARY); - for (int i = 0; i < 4;) { - String subString = StringUtils.substring(md5Hex, i * 8, (i + 1) * 8); - subString = Long.toBinaryString(Long.valueOf(subString, 16) & binaryLengthFixer); - subString = StringUtils.leftPad(subString, binaryLength, "0"); - StringBuilder sbBuilder = new StringBuilder(); - for (int j = 0; j < 6; j++) { - String subString2 = StringUtils.substring(subString, j * 6, (j + 1) * 6); - int charIndex = Integer.valueOf(subString2, BINARY) & NUMBER_61; - sbBuilder.append(DIGITS[charIndex]); - } - String shortUrl = sbBuilder.toString(); - if(shortUrl!=null){ - return shortUrl; - } - } - // if all 4 possibilities are already exists - return null; - } - + public static String encode(long value) { + return encode(String.valueOf(value)).toLowerCase(); + } + + public static String encode(String str) { + String md5Hex = DigestUtils.md5Hex(str); + // 6 digit binary can indicate 62 letter & number from 0-9a-zA-Z + int binaryLength = 6 * 6; + long binaryLengthFixer = Long.valueOf(StringUtils.repeat("1", binaryLength), BINARY); + for (int i = 0; i < 4; ) { + String subString = StringUtils.substring(md5Hex, i * 8, (i + 1) * 8); + subString = Long.toBinaryString(Long.valueOf(subString, 16) & binaryLengthFixer); + subString = StringUtils.leftPad(subString, binaryLength, "0"); + StringBuilder sbBuilder = new StringBuilder(); + for (int j = 0; j < 6; j++) { + String subString2 = StringUtils.substring(subString, j * 6, (j + 1) * 6); + int charIndex = Integer.valueOf(subString2, BINARY) & NUMBER_61; + sbBuilder.append(DIGITS[charIndex]); + } + String shortUrl = sbBuilder.toString(); + if (shortUrl != null) { + return shortUrl; + } + } + // if all 4 possibilities are already exists + return null; + } + @SuppressWarnings("unused") - private static void print(Object messagr){ - System.out.println(messagr); - } + private static void print(Object messagr) { + System.out.println(messagr); + } + + /** + * 生成随机字符串 + * @param targetStringLength + * @return + */ + public static String generatingRandomAlphanumericString(final int targetStringLength) { + int leftLimit = 48; // numeral '0' + int rightLimit = 122; // letter 'z' + Random random = new Random(); + + String generatedString = random.ints(leftLimit, rightLimit + 1) + .filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) + .limit(targetStringLength) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + + return generatedString; + } } \ No newline at end of file diff --git a/contact-center/config/sql/002.mysql-create-schemas.sql b/contact-center/config/sql/002.mysql-create-schemas.sql index 350fa3ac..2b8774b2 100644 --- a/contact-center/config/sql/002.mysql-create-schemas.sql +++ b/contact-center/config/sql/002.mysql-create-schemas.sql @@ -138,6 +138,24 @@ CREATE TABLE `cs_fb_otn_follow` ( -- Records of cs_fb_otn_follow -- ---------------------------- +-- ---------------------------- +-- Table structure for cs_metakv +-- ---------------------------- +DROP TABLE IF EXISTS `cs_metakv`; +CREATE TABLE `cs_metakv` ( + `metakey` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '元数据字段名,唯一标识', + `metavalue` text COLLATE utf8mb4_unicode_ci COMMENT '元数据值', + `createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updatetime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `datatype` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'string' COMMENT '数据类型', + `comment` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '字段备注描述', + PRIMARY KEY (`metakey`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统内置元数据'; + +-- ---------------------------- +-- Records of cs_metakv +-- ---------------------------- + -- ---------------------------- -- Table structure for cs_organ_user -- ---------------------------- diff --git a/contact-center/config/sql/upgrade/001.add_table_cs_metakey.sql b/contact-center/config/sql/upgrade/001.add_table_cs_metakey.sql new file mode 100644 index 00000000..9a99355c --- /dev/null +++ b/contact-center/config/sql/upgrade/001.add_table_cs_metakey.sql @@ -0,0 +1,14 @@ +USE `cosinee`; +-- ----------------- +-- prepare variables +-- ----------------- + +CREATE TABLE IF NOT EXISTS `cs_metakey` ( + `metakey` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '元数据字段名,唯一标识', + `metavalue` text COLLATE utf8mb4_unicode_ci COMMENT '元数据值', + `createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updatetime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `datatype` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'string' COMMENT '数据类型', + `comment` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '字段备注描述', + PRIMARY KEY (`metakey`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统内置元数据'; diff --git a/contact-center/root/pom.xml b/contact-center/root/pom.xml index 81dc1ff9..c1e1b160 100644 --- a/contact-center/root/pom.xml +++ b/contact-center/root/pom.xml @@ -400,11 +400,18 @@ compose4j 1.0.0 + com.chatopera.bot sdk 3.5.1 + + + com.chatopera.store + store-sdk + 1.0-SNAPSHOT +