1
0
mirror of https://github.com/chatopera/cosin.git synced 2025-08-01 16:38:02 +08:00

#945 V9后端初始化

Signed-off-by: lecjy <565572696@qq.com>
This commit is contained in:
lecjy 2023-09-24 13:15:09 +08:00
parent e91760a560
commit 7c93ac0109
2300 changed files with 4458 additions and 600936 deletions

2
.gitignore vendored
View File

@ -23,3 +23,5 @@ nohup.out
docker-compose.dev.yml
docker-compose.custom.yml
private/
smart-doc/
target/

View File

@ -1,11 +0,0 @@
# dev profile
src/main/resources/application-dev.properties
# ignore plugins: app views, classes
src/main/resources/templates/apps/callout
src/main/resources/templates/apps/callcenter
src/main/java/com/cskefu/cc/plugins/botplt
# ignore logs
logs/
data/

View File

@ -1,141 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cskefu.cc</groupId>
<artifactId>contact-center</artifactId>
<packaging>war</packaging>
<name>cc-core</name>
<description>春松客服:开源客服系统</description>
<licenses>
<license>
<name>Chunsong Public License, version 1.0</name>
<url>https://docs.cskefu.com/licenses/v1.html</url>
<comments>
Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
</comments>
</license>
</licenses>
<parent>
<groupId>com.cskefu.cc</groupId>
<artifactId>cc-root</artifactId>
<version>8.0.0-SNAPSHOT</version>
<!-- for Chatopera Nexus reference if file is available with latest version -->
<!-- <relativePath/> -->
<!-- for local reference if file is available with latest version -->
<relativePath>../root/pom.xml</relativePath>
</parent>
<build>
<finalName>contact-center</finalName>
<plugins>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.2.5</version>
<executions>
<execution>
<id>get-the-git-infos</id>
<goals>
<goal>revision</goal>
</goals>
<!-- *NOTE*: The default phase of revision is initialize, but in case you want to change it, you can do so by adding the phase here -->
<phase>initialize</phase>
</execution>
<execution>
<id>validate-the-git-infos</id>
<goals>
<goal>validateRevision</goal>
</goals>
<!-- *NOTE*: The default phase of validateRevision is verify, but in case you want to change it, you can do so by adding the phase here -->
<phase>package</phase>
</execution>
</executions>
<configuration>
<excludeProperties>
<excludeProperty>git.tags</excludeProperty>
<excludeProperty>git.remote.*</excludeProperty>
<excludeProperty>git.closest.*</excludeProperty>
<excludeProperty>git.total.commit.count</excludeProperty>
</excludeProperties>
<dotGitDirectory>${project.basedir}/../../.git</dotGitDirectory>
<generateGitPropertiesFilename>
${project.build.outputDirectory}/git.properties
</generateGitPropertiesFilename>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<prefix>git</prefix>
<verbose>false</verbose>
<injectAllReactorProjects>true</injectAllReactorProjects>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<attachClasses>true</attachClasses>
<warSourceExcludes>**/WEB-INF</warSourceExcludes>
<packagingExcludes>**/WEB-INF,**/resources</packagingExcludes>
<webResources>
<resource>
<directory>../config/sql/</directory>
<includes>
<include>**/*.sql</include>
</includes>
</resource>
</webResources>
</configuration>
<version>3.3.2</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
<repositories>
<repository>
<id>chatopera</id>
<name>Chatopera Inc.</name>
<url>https://nexus.chatopera.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<developers>
<developer>
<id>hain</id>
<name>Hai Liang Wang</name>
<email>hai@chatopera.com</email>
<url>https://github.com/hailiang-wang</url>
<organization>Chatopera Inc.</organization>
<organizationUrl>https://www.chatopera.com</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
<timezone>Asia/Shanghai</timezone>
</developer>
</developers>
</project>

View File

@ -1,141 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.config.AppCtxRefreshEventListener;
import com.cskefu.cc.util.SystemEnvHelper;
import com.cskefu.cc.util.mobile.MobileNumberUtils;
import jakarta.servlet.MultipartConfigElement;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.JmsPoolConnectionFactoryFactory;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.util.unit.DataSize;
import java.io.IOException;
@SpringBootApplication
@EnableJpaRepositories("com.cskefu.cc.persistence.repository")
@EnableTransactionManagement
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
@Value("${web.upload-path}")
private String uploaddir;
@Value("${spring.servlet.multipart.max-file-size}")
private Long multipartMaxUpload;
@Value("${spring.servlet.multipart.max-request-size}")
private Long multipartMaxRequest;
/**
* 加载模块
*/
static {
// CRM模块
if (StringUtils.equalsIgnoreCase(SystemEnvHelper.parseFromApplicationProps("cskefu.modules.contacts"), "true")) {
MainContext.enableModule(Constants.CSKEFU_MODULE_CONTACTS);
}
// 会话监控模块 Customer Chats Audit
if (StringUtils.equalsIgnoreCase(SystemEnvHelper.parseFromApplicationProps("cskefu.modules.cca"), "true")) {
MainContext.enableModule(Constants.CSKEFU_MODULE_CCA);
}
// 企业聊天模块
if (StringUtils.equalsIgnoreCase(SystemEnvHelper.parseFromApplicationProps("cskefu.modules.entim"), "true")) {
MainContext.enableModule(Constants.CSKEFU_MODULE_ENTIM);
}
// 数据报表
if (StringUtils.equalsIgnoreCase(SystemEnvHelper.parseFromApplicationProps("cskefu.modules.report"), "true")) {
MainContext.enableModule(Constants.CSKEFU_MODULE_REPORT);
}
}
/**
* Init local resources
*/
protected static void serve(final String[] args) {
try {
// Tune druid params, https://github.com/cskefu/cskefu/issues/835
System.setProperty("druid.mysql.usePingMethod", "false");
MobileNumberUtils.init();
/************************
* 该APP中加载多个配置文件
* http://roufid.com/load-multiple-configuration-files-different-directories-spring-boot/
************************/
SpringApplication app = new SpringApplicationBuilder(Application.class)
.properties("spring.config.name:application,git")
.build();
app.setBannerMode(Banner.Mode.CONSOLE);
app.setAddCommandLineProperties(false);
app.addListeners(new AppCtxRefreshEventListener());
MainContext.setApplicationContext(app.run(args));
} catch (IOException e) {
logger.error("Application Startup Error", e);
System.exit(1);
}
}
// TODO lecjy
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(DataSize.ofMegabytes(multipartMaxUpload)); //KB,MB
factory.setMaxRequestSize(DataSize.ofMegabytes(multipartMaxRequest));
factory.setLocation(uploaddir);
return factory.createMultipartConfig();
}
// TODO lecjy
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
return factory -> {
// 定义404错误页
HttpStatus notFound = HttpStatus.NOT_FOUND;
// 定义404错误页
ErrorPage errorPage = new ErrorPage(notFound, "/error.html");
// 追加错误页替换springboot默认的错误页
factory.addErrorPages(errorPage);
};
}
public static void main(String[] args) {
try {
Application.serve(args);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -1,116 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.acd.basic.ACDMessageHelper;
import com.cskefu.cc.acd.basic.IACDDispatcher;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.cache.RedisCommand;
import com.cskefu.cc.cache.RedisKey;
import com.cskefu.cc.model.AgentStatus;
import com.cskefu.cc.model.AgentUser;
import com.cskefu.cc.persistence.repository.AgentStatusRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
@Component
public class ACDAgentDispatcher implements IACDDispatcher {
private final static Logger logger = LoggerFactory.getLogger(ACDAgentDispatcher.class);
@Autowired
private Cache cache;
@Autowired
private AgentStatusRepository agentStatusRes;
@Autowired
private RedisCommand redisCommand;
@Autowired
private ACDVisitorDispatcher acdVisitorDispatcher;
@Autowired
private ACDMessageHelper acdMessageHelper;
@Override
public void enqueue(ACDComposeContext ctx) {
}
/**
* 撤退一个坐席
* 1将该坐席状态置为"非就绪"
* 2) 将该坐席的访客重新分配给其它坐席
*
* @param ctx agentno为必填
* @return 有没有成功将所有其服务的访客都分配出去
*/
@Override
public void dequeue(final ACDComposeContext ctx) {
// 先将该客服切换到非就绪状态
final AgentStatus agentStatus = cache.findOneAgentStatusByAgentno(ctx.getAgentno());
if (agentStatus != null) {
agentStatus.setBusy(false);
agentStatus.setUpdatetime(new Date());
agentStatus.setStatus(MainContext.AgentStatusEnum.NOTREADY.toString());
agentStatusRes.save(agentStatus);
cache.putAgentStatus(agentStatus);
}
// 然后将该坐席的访客分配给其它坐席
// 获得该租户在线的客服的多少
// TODO 对于agentUser的技能组过滤在下面再逐个考虑
// 该信息同样也包括当前用户
List<AgentUser> agentUsers = cache.findInservAgentUsersByAgentno(ctx.getAgentno());
int sz = agentUsers.size();
for (final AgentUser x : agentUsers) {
try {
// TODO 此处没有考虑遍历过程中系统中坐席的服务访客的信息实际上是变化的
// 可能会发生maxusers超过设置的情况如果做很多检查会带来一定一系统开销
// 因为影响不大放弃实时的检查
ACDComposeContext y = acdMessageHelper.getComposeContextWithAgentUser(
x, false, MainContext.ChatInitiatorType.USER.toString());
acdVisitorDispatcher.enqueue(y);
// 因为重新分配该访客将其从撤离的坐席中服务集合中删除
// 此处类似于 Transfer
redisCommand.removeSetVal(
RedisKey.getInServAgentUsersByAgentno(ctx.getAgentno()), x.getUserid());
sz--;
} catch (Exception e) {
logger.warn("[dequeue] throw error:", e);
}
}
if (sz == 0) {
logger.info(
"[dequeue] after re-allotAgent, the agentUsers size is {} for agentno {}", sz,
ctx.getAgentno());
} else {
logger.warn(
"[dequeue] after re-allotAgent, the agentUsers size is {} for agentno {}", sz,
ctx.getAgentno());
}
ctx.setResolved(sz == 0);
}
}

View File

@ -1,649 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.acd.basic.ACDMessageHelper;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.cache.RedisCommand;
import com.cskefu.cc.cache.RedisKey;
import com.cskefu.cc.exception.CSKefuException;
import com.cskefu.cc.model.*;
import com.cskefu.cc.peer.PeerSyncIM;
import com.cskefu.cc.persistence.repository.*;
import com.cskefu.cc.proxy.AgentStatusProxy;
import com.cskefu.cc.proxy.AgentUserProxy;
import com.cskefu.cc.socketio.client.NettyClients;
import com.cskefu.cc.socketio.message.Message;
import com.cskefu.cc.util.HashMapUtils;
import com.cskefu.cc.util.SerializeUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Component
public class ACDAgentService {
private final static Logger logger = LoggerFactory.getLogger(ACDAgentService.class);
@Autowired
private RedisCommand redisCommand;
@Autowired
private ACDMessageHelper acdMessageHelper;
@Autowired
private AgentStatusProxy agentStatusProxy;
@Autowired
private ACDPolicyService acdPolicyService;
@Autowired
@Lazy
private PeerSyncIM peerSyncIM;
@Autowired
private Cache cache;
@Autowired
private AgentUserRepository agentUserRes;
@Autowired
private AgentServiceRepository agentServiceRes;
@Autowired
private AgentUserTaskRepository agentUserTaskRes;
@Autowired
private AgentStatusRepository agentStatusRes;
@Autowired
private PassportWebIMUserRepository onlineUserRes;
@Autowired
private UserRepository userRes;
@Autowired
private AgentUserProxy agentUserProxy;
/**
* ACD结果通知
*
* @param ctx
*/
public void notifyAgentUserProcessResult(final ACDComposeContext ctx) {
Objects.requireNonNull(ctx, "ctx can not be null");
if (StringUtils.isBlank(ctx.getMessage())) {
logger.info("[onConnect] can not find available agent for user {}", ctx.getOnlineUserId());
return;
}
logger.info("[onConnect] find available agent for onlineUser id {}", ctx.getOnlineUserId());
/**
* 发送消息给坐席
* 如果没有AgentService或该AgentService没有坐席或AgentService在排队中则不发送
*/
if (ctx.getAgentService() != null && (!ctx.isNoagent()) && !StringUtils.equals(
MainContext.AgentUserStatusEnum.INQUENE.toString(),
ctx.getAgentService().getStatus())) {
// 通知消息到坐席
MainContext.getPeerSyncIM().send(MainContext.ReceiverType.AGENT,
MainContext.ChannelType.WEBIM,
ctx.getAppid(),
MainContext.MessageType.NEW,
ctx.getAgentService().getAgentno(),
ctx, true);
}
/**
* 发送消息给访客
*/
Message outMessage = new Message();
outMessage.setAgentUser(ctx.getAgentUser());
outMessage.setMessage(ctx.getMessage());
outMessage.setMessageType(MainContext.MessageType.MESSAGE.toString());
outMessage.setCalltype(MainContext.CallType.IN.toString());
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
outMessage.setNoagent(ctx.isNoagent());
if (ctx.getAgentService() != null) {
outMessage.setAgentserviceid(ctx.getAgentService().getId());
}
MainContext.getPeerSyncIM().send(MainContext.ReceiverType.VISITOR,
MainContext.ChannelType.toValue(ctx.getChannelType()),
ctx.getAppid(),
MainContext.MessageType.NEW, ctx.getOnlineUserId(), outMessage, true);
}
/**
* 邀请访客进入当前对话如果当前操作的 坐席是已就绪状态则直接加入到当前坐席的
* 对话列表中如果未登录则分配给其他坐席
*
* @param agentno
* @param agentUser
* @throws Exception
*/
public void assignVisitorAsInvite(
final String agentno,
final AgentUser agentUser
) throws Exception {
final AgentStatus agentStatus = cache.findOneAgentStatusByAgentno(agentno);
pickupAgentUserInQueue(agentUser, agentStatus);
}
/**
* 为坐席批量分配用户
*
* @param agentno
*/
public void assignVisitors(String agentno) {
logger.info("[assignVisitors] agentno {}", agentno);
// 获得目标坐席的状态
AgentStatus agentStatus = SerializeUtil.deserialize(
redisCommand.getHashKV(RedisKey.getAgentStatusReadyHashKey(), agentno));
if (agentStatus == null) {
logger.warn("[assignVisitors] can not find AgentStatus for agentno {}", agentno);
return;
}
logger.info("[assignVisitors] agentStatus id {}, status {}, service {}/{}, skills {}, busy {}",
agentStatus.getId(), agentStatus.getStatus(), agentStatus.getUsers(), agentStatus.getMaxusers(),
HashMapUtils.concatKeys(agentStatus.getSkills(), "|"), agentStatus.isBusy());
if ((!StringUtils.equals(
MainContext.AgentStatusEnum.READY.toString(), agentStatus.getStatus())) || agentStatus.isBusy()) {
// 该坐席处于非就绪状态或该坐席处于置忙
// 不分配坐席
return;
}
// 获得所有待服务访客的列表
final Map<String, AgentUser> pendingAgentUsers = cache.getAgentUsersInQue();
// 本次批量分配访客数目
Map<String, Integer> assigned = new HashMap<>();
int currentAssigned = cache.getInservAgentUsersSizeByAgentno(
agentStatus.getAgentno());
logger.info(
"[assignVisitors] agentno {}, name {}, current assigned {}, batch size in queue {}",
agentStatus.getAgentno(),
agentStatus.getUsername(), currentAssigned, pendingAgentUsers.size());
for (Map.Entry<String, AgentUser> entry : pendingAgentUsers.entrySet()) {
AgentUser agentUser = entry.getValue();
boolean process = false;
if ((StringUtils.equals(agentUser.getAgentno(), agentno))) {
// 待服务的访客指定了该坐席
process = true;
} else if (agentStatus != null &&
agentStatus.getSkills() != null &&
agentStatus.getSkills().size() > 0) {
// 目标坐席有状态并且坐席属于某技能组
if ((StringUtils.isBlank(agentUser.getAgentno()) &&
StringUtils.isBlank(agentUser.getSkill()))) {
// 待服务的访客还没有指定坐席并且也没有绑定技能组
process = true;
} else if (StringUtils.isBlank(agentUser.getAgentno()) &&
agentStatus.getSkills().containsKey(agentUser.getSkill())) {
// 待服务的访客还没有指定坐席并且指定的技能组和该坐席的技能组一致
process = true;
}
} else if (StringUtils.isBlank(agentUser.getAgentno()) &&
StringUtils.isBlank(agentUser.getSkill())) {
// 目标坐席没有状态或该目标坐席有状态但是没有属于任何一个技能组
// 待服务访客没有指定坐席并且没有指定技能组
process = true;
}
if (!process) {
continue;
}
// 坐席未达到最大咨询访客数量并且单次批量分配小于坐席就绪时分配最大访客数量(initMaxuser)
final SessionConfig sessionConfig = acdPolicyService.initSessionConfig(agentUser.getSkill());
if ((ACDServiceRouter.getAcdPolicyService().getAgentUsersBySkill(agentStatus, agentUser.getSkill()) < sessionConfig.getMaxuser()) && (assigned.getOrDefault(agentUser.getSkill(), 0) < sessionConfig.getInitmaxuser())) {
assigned.merge(agentUser.getSkill(), 1, Integer::sum);
pickupAgentUserInQueue(agentUser, agentStatus);
} else {
logger.info(
"[assignVisitors] agentno {} reach the max users limit {}/{} or batch assign limit {}/{}",
agentno,
(currentAssigned + assigned.getOrDefault(agentUser.getSkill(), 0)),
sessionConfig.getMaxuser(), assigned, sessionConfig.getInitmaxuser());
break;
}
}
agentStatusProxy.broadcastAgentsStatus("agent", "success", agentno);
}
/**
* 从队列中选择访客进行会话
*
* @param agentUser
* @param agentStatus
* @return
*/
public AgentService pickupAgentUserInQueue(final AgentUser agentUser, final AgentStatus agentStatus) {
// 从排队队列移除
cache.deleteAgentUserInqueByAgentUserId(agentUser.getUserid());
AgentService agentService = null;
// 下面开始处理其加入到服务中的队列
try {
agentService = resolveAgentService(
agentStatus, agentUser, false);
// 处理完成得到 agentService
Message outMessage = new Message();
outMessage.setMessage(acdMessageHelper.getSuccessMessage(
agentService,
agentUser.getChanneltype()));
outMessage.setMessageType(MainContext.MediaType.TEXT.toString());
outMessage.setCalltype(MainContext.CallType.IN.toString());
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
if (StringUtils.isNotBlank(agentUser.getUserid())) {
outMessage.setAgentUser(agentUser);
outMessage.setChannelMessage(agentUser);
// 向访客推送消息
peerSyncIM.send(
MainContext.ReceiverType.VISITOR,
MainContext.ChannelType.toValue(agentUser.getChanneltype()), agentUser.getAppid(),
MainContext.MessageType.STATUS, agentUser.getUserid(), outMessage, true
);
// 向坐席推送消息
peerSyncIM.send(MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
agentUser.getAppid(),
MainContext.MessageType.NEW, agentUser.getAgentno(), outMessage, true);
// 通知更新在线数据
agentStatusProxy.broadcastAgentsStatus("agent", "pickup", agentStatus.getAgentno());
}
} catch (Exception ex) {
logger.warn("[assignVisitors] fail to process service", ex);
}
return agentService;
}
/**
* 访客服务结束
*
* @param agentUser
* @throws Exception
*/
public void finishAgentService(final AgentUser agentUser) {
if (agentUser != null) {
/**
* 设置AgentUser
*/
// 获得坐席状态
AgentStatus agentStatus = null;
if (StringUtils.equals(MainContext.AgentUserStatusEnum.INSERVICE.toString(), agentUser.getStatus()) &&
agentUser.getAgentno() != null) {
agentStatus = cache.findOneAgentStatusByAgentno(agentUser.getAgentno());
}
// 设置新AgentUser的状态
agentUser.setStatus(MainContext.AgentUserStatusEnum.END.toString());
if (agentUser.getServicetime() != null) {
agentUser.setSessiontimes(System.currentTimeMillis() - agentUser.getServicetime().getTime());
}
// 从缓存中删除agentUser缓存
agentUserRes.save(agentUser);
final SessionConfig sessionConfig = acdPolicyService.initSessionConfig(agentUser.getSkill());
/**
* 坐席服务
*/
AgentService service = null;
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
service = agentServiceRes.findById(agentUser.getAgentserviceid()).orElse(null);
} else if (agentStatus != null) {
// 该访客没有和坐席对话因此没有 AgentService
// 当做留言处理创建一个新的 AgentService
service = resolveAgentService(agentStatus, agentUser, true);
}
if (service != null) {
service.setStatus(MainContext.AgentUserStatusEnum.END.toString());
service.setEndtime(new Date());
if (service.getServicetime() != null) {
service.setSessiontimes(System.currentTimeMillis() - service.getServicetime().getTime());
}
final AgentUserTask agentUserTask = agentUserTaskRes.findById(agentUser.getId()).orElse(null);
if (agentUserTask != null) {
service.setAgentreplyinterval(agentUserTask.getAgentreplyinterval());
service.setAgentreplytime(agentUserTask.getAgentreplytime());
service.setAvgreplyinterval(agentUserTask.getAvgreplyinterval());
service.setAvgreplytime(agentUserTask.getAvgreplytime());
service.setUserasks(agentUserTask.getUserasks());
service.setAgentreplys(agentUserTask.getAgentreplys());
// 开启了质检并且是有效对话
if (sessionConfig.isQuality()) {
// 未分配质检任务
service.setQualitystatus(MainContext.QualityStatusEnum.NODIS.toString());
}
}
/**
* 启用了质检任务开启质检
*/
if ((!sessionConfig.isQuality()) || service.getUserasks() == 0) {
// 未开启质检 或无效对话无需质检
service.setQualitystatus(MainContext.QualityStatusEnum.NO.toString());
}
agentServiceRes.save(service);
}
/**
* 更新AgentStatus
*/
if (agentStatus != null) {
agentStatus.setUsers(
cache.getInservAgentUsersSizeByAgentno(agentStatus.getAgentno()));
agentStatusRes.save(agentStatus);
}
Message outMessage = new Message();
/**
* 发送到访客端的通知
*/
switch (MainContext.ChannelType.toValue(agentUser.getChanneltype())) {
case WEBIM:
// WebIM 发送对话结束事件
// 向访客发送消息
outMessage.setAgentStatus(agentStatus);
outMessage.setMessage(acdMessageHelper.getServiceFinishMessage(agentUser.getChanneltype(), agentUser.getSkill()));
outMessage.setMessageType(MainContext.AgentUserStatusEnum.END.toString());
outMessage.setCalltype(MainContext.CallType.IN.toString());
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
outMessage.setAgentUser(agentUser);
// 向访客发送消息
peerSyncIM.send(
MainContext.ReceiverType.VISITOR,
MainContext.ChannelType.toValue(agentUser.getChanneltype()), agentUser.getAppid(),
MainContext.MessageType.STATUS, agentUser.getUserid(), outMessage, true
);
if (agentStatus != null) {
// 坐席在线通知结束会话
outMessage.setChannelMessage(agentUser);
outMessage.setAgentUser(agentUser);
peerSyncIM.send(MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
agentUser.getAppid(),
MainContext.MessageType.END, agentUser.getAgentno(), outMessage, true);
}
break;
case PHONE:
// 语音渠道强制发送
logger.info(
"[finishAgentService] send notify to callout channel agentno {}", agentUser.getAgentno());
NettyClients.getInstance().sendCalloutEventMessage(
agentUser.getAgentno(), MainContext.MessageType.END.toString(), agentUser);
break;
case MESSENGER:
outMessage.setAgentStatus(agentStatus);
outMessage.setMessage(acdMessageHelper.getServiceFinishMessage(agentUser.getChanneltype(), agentUser.getSkill()));
outMessage.setMessageType(MainContext.AgentUserStatusEnum.END.toString());
outMessage.setCalltype(MainContext.CallType.IN.toString());
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
outMessage.setAgentUser(agentUser);
// 向访客发送消息
peerSyncIM.send(
MainContext.ReceiverType.VISITOR,
MainContext.ChannelType.toValue(agentUser.getChanneltype()), agentUser.getAppid(),
MainContext.MessageType.STATUS, agentUser.getUserid(), outMessage, true
);
if (agentStatus != null) {
// 坐席在线通知结束会话
outMessage.setChannelMessage(agentUser);
outMessage.setAgentUser(agentUser);
peerSyncIM.send(MainContext.ReceiverType.AGENT, MainContext.ChannelType.MESSENGER,
agentUser.getAppid(),
MainContext.MessageType.END, agentUser.getAgentno(), outMessage, true);
}
break;
default:
logger.info(
"[finishAgentService] ignore notify agent service end for channel {}, agent user id {}",
agentUser.getChanneltype(), agentUser.getId());
}
// 更新访客的状态为可以接收邀请
final PassportWebIMUser passportWebIMUser = onlineUserRes.findOneByUserid(
agentUser.getUserid());
if (passportWebIMUser != null) {
passportWebIMUser.setInvitestatus(MainContext.OnlineUserInviteStatus.DEFAULT.toString());
onlineUserRes.save(passportWebIMUser);
logger.info(
"[finishAgentService] onlineUser id {}, status {}, invite status {}", passportWebIMUser.getId(),
passportWebIMUser.getStatus(), passportWebIMUser.getInvitestatus());
}
// 当前访客服务已经结束为坐席寻找新访客
if (agentStatus != null) {
if ((ACDServiceRouter.getAcdPolicyService().getAgentUsersBySkill(agentStatus, agentUser.getSkill()) - 1) < sessionConfig.getMaxuser()) {
assignVisitors(agentStatus.getAgentno());
}
}
agentStatusProxy.broadcastAgentsStatus(
"end", "success", agentUser != null ? agentUser.getId() : null);
} else {
logger.info("[finishAgentService] invalid agent user, should not be null");
}
}
/**
* 删除AgentUser
* 包括数据库记录及缓存信息
*
* @param agentUser
* @return
*/
public void finishAgentUser(final AgentUser agentUser) throws CSKefuException {
logger.info("[finishAgentUser] userId {}", agentUser.getUserid());
if (agentUser == null || agentUser.getId() == null) {
throw new CSKefuException("Invalid agentUser info");
}
if (!StringUtils.equals(MainContext.AgentUserStatusEnum.END.toString(), agentUser.getStatus())) {
/**
* 未结束聊天先结束对话然后删除记录
*/
// 删除缓存
finishAgentService(agentUser);
}
// 删除数据库里的AgentUser记录
agentUserRes.delete(agentUser);
}
/**
* 为agentUser生成对应的AgentService
* 使用场景
* 1. 在AgentUser服务结束并且还没有对应的AgentService
* 2. 在新服务开始安排坐席
*
* @param agentStatus 坐席状态
* @param agentUser 坐席访客会话
* @param finished 结束服务
* @return
*/
public AgentService resolveAgentService(
AgentStatus agentStatus,
final AgentUser agentUser,
final boolean finished) {
AgentService agentService = new AgentService();
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
AgentService existAgentService = agentServiceRes.findById(agentUser.getAgentserviceid()).orElse(null);
if (existAgentService != null) {
agentService = existAgentService;
} else {
agentService.setId(agentUser.getAgentserviceid());
}
}
final Date now = new Date();
// 批量复制属性
MainUtils.copyProperties(agentUser, agentService);
agentService.setChanneltype(agentUser.getChanneltype());
agentService.setSessionid(agentUser.getSessionid());
// 此处为何设置loginDate为现在
agentUser.setLogindate(now);
PassportWebIMUser passportWebIMUser = onlineUserRes.findOneByUserid(agentUser.getUserid());
if (finished == true) {
// 服务结束
agentUser.setStatus(MainContext.AgentUserStatusEnum.END.toString());
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
agentService.setSessiontype(MainContext.AgentUserStatusEnum.END.toString());
if (agentStatus == null) {
// 没有满足条件的坐席留言
agentService.setLeavemsg(true);
agentService.setLeavemsgstatus(MainContext.LeaveMsgStatus.NOTPROCESS.toString()); //未处理的留言
}
if (passportWebIMUser != null) {
// 更新OnlineUser对象变更为默认状态可以接受邀请
passportWebIMUser.setInvitestatus(MainContext.OnlineUserInviteStatus.DEFAULT.toString());
}
} else if (agentStatus != null) {
agentService.setAgent(agentStatus.getAgentno());
agentService.setSkill(agentUser.getSkill());
agentUser.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
agentService.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
agentService.setSessiontype(MainContext.AgentUserStatusEnum.INSERVICE.toString());
// 设置坐席名字
agentService.setAgentno(agentStatus.getUserid());
agentService.setAgentusername(agentStatus.getUsername());
} else {
// 不是服务结束但是没有满足条件的坐席
// 加入到排队中
agentUser.setStatus(MainContext.AgentUserStatusEnum.INQUENE.toString());
agentService.setStatus(MainContext.AgentUserStatusEnum.INQUENE.toString());
agentService.setSessiontype(MainContext.AgentUserStatusEnum.INQUENE.toString());
}
if (finished || agentStatus != null) {
agentService.setAgentuserid(agentUser.getId());
agentService.setInitiator(MainContext.ChatInitiatorType.USER.toString());
long waittingtime = 0;
if (agentUser.getWaittingtimestart() != null) {
waittingtime = System.currentTimeMillis() - agentUser.getWaittingtimestart().getTime();
} else {
if (agentUser.getCreatetime() != null) {
waittingtime = System.currentTimeMillis() - agentUser.getCreatetime().getTime();
}
}
agentUser.setWaittingtime((int) waittingtime);
agentUser.setServicetime(now);
agentService.setOwner(agentUser.getOwner());
agentService.setTimes(0);
final User agent = userRes.findById(agentService.getAgentno()).orElse(null);
agentUser.setAgentname(agent.getUname());
agentUser.setAgentno(agentService.getAgentno());
if (StringUtils.isNotBlank(agentUser.getName())) {
agentService.setName(agentUser.getName());
}
if (StringUtils.isNotBlank(agentUser.getPhone())) {
agentService.setPhone(agentUser.getPhone());
}
if (StringUtils.isNotBlank(agentUser.getEmail())) {
agentService.setEmail(agentUser.getEmail());
}
if (StringUtils.isNotBlank(agentUser.getResion())) {
agentService.setResion(agentUser.getResion());
}
if (StringUtils.isNotBlank(agentUser.getSkill())) {
agentService.setAgentskill(agentUser.getSkill());
}
agentService.setServicetime(now);
if (agentUser.getCreatetime() != null) {
agentService.setWaittingtime((int) (System.currentTimeMillis() - agentUser.getCreatetime().getTime()));
agentUser.setWaittingtime(agentService.getWaittingtime());
}
if (passportWebIMUser != null) {
agentService.setOsname(passportWebIMUser.getOpersystem());
agentService.setBrowser(passportWebIMUser.getBrowser());
// 记录onlineUser的id
agentService.setDataid(passportWebIMUser.getId());
}
agentService.setLogindate(agentUser.getCreatetime());
agentServiceRes.save(agentService);
agentUser.setAgentserviceid(agentService.getId());
agentUser.setLastgetmessage(now);
agentUser.setLastmessage(now);
}
agentService.setDataid(agentUser.getId());
/**
* 分配成功以后 将用户和坐席的对应关系放入到缓存
* AgentUser 放入到当前坐席的服务队列
*/
agentUserRes.save(agentUser);
/**
* 更新OnlineUser对象变更为服务中不可邀请
*/
if (passportWebIMUser != null && !finished) {
passportWebIMUser.setInvitestatus(MainContext.OnlineUserInviteStatus.INSERV.toString());
onlineUserRes.save(passportWebIMUser);
}
// 更新坐席服务人数坐席更新时间到缓存
if (agentStatus != null) {
agentUserProxy.updateAgentStatus(agentStatus);
}
return agentService;
}
}

View File

@ -1,84 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.model.AgentService;
import com.cskefu.cc.model.AgentUser;
import com.cskefu.cc.persistence.repository.AgentServiceRepository;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class ACDChatbotService {
private final static Logger logger = LoggerFactory.getLogger(ACDChatbotService.class);
@Autowired
private AgentServiceRepository agentServiceRes;
/**
* 为访客分配机器人客服 ACD策略此处 AgentStatus 是建议 坐席 如果启用了 历史服务坐席 优先策略 则会默认检查历史坐席是否空闲如果空闲则分配如果不空闲 分配当前建议的坐席
*
* @param agentUser
* @return
* @throws Exception
*/
public AgentService processChatbotService(final String botName, final AgentUser agentUser) {
AgentService agentService = new AgentService(); //放入缓存的对象
Date now = new Date();
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
agentService = agentServiceRes.findById(agentUser.getAgentserviceid()).orElse(null);
agentService.setEndtime(now);
if (agentService.getServicetime() != null) {
agentService.setSessiontimes(System.currentTimeMillis() - agentService.getServicetime().getTime());
}
agentService.setStatus(MainContext.AgentUserStatusEnum.END.toString());
} else {
agentService.setServicetime(now);
agentService.setLogindate(now);
agentService.setOwner(agentUser.getContextid());
agentService.setSessionid(agentUser.getSessionid());
agentService.setRegion(agentUser.getRegion());
agentService.setUsername(agentUser.getUsername());
agentService.setChanneltype(agentUser.getChanneltype());
if (botName != null) {
agentService.setAgentusername(botName);
}
if (StringUtils.isNotBlank(agentUser.getContextid())) {
agentService.setContextid(agentUser.getContextid());
} else {
agentService.setContextid(agentUser.getSessionid());
}
agentService.setUserid(agentUser.getUserid());
agentService.setAiid(agentUser.getAgentno());
agentService.setAiservice(true);
agentService.setStatus(MainContext.AgentUserStatusEnum.INSERVICE.toString());
agentService.setAppid(agentUser.getAppid());
agentService.setLeavemsg(false);
}
agentServiceRes.save(agentService);
return agentService;
}
}

View File

@ -1,397 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.*;
import com.cskefu.cc.proxy.OrganProxy;
import com.cskefu.cc.util.HashMapUtils;
import com.cskefu.cc.util.WebIMReport;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* 坐席自动分配策略集
*/
@Component
public class ACDPolicyService {
private final static Logger logger = LoggerFactory.getLogger(ACDPolicyService.class);
@Autowired
private Cache cache;
@Autowired
private UserRepository userRes;
@Autowired
private SessionConfigRepository sessionConfigRes;
@Autowired
private PassportWebIMUserRepository onlineUserRes;
@Autowired
private AgentUserRepository agentUserRes;
@Autowired
private ChannelRepository snsAccountRes;
@Autowired
private OrganProxy organProxy;
/**
* 载入坐席 ACD策略配置
*
* @return
*/
@SuppressWarnings("unchecked")
public List<SessionConfig> initSessionConfigList() {
List<SessionConfig> sessionConfigList;
if ((sessionConfigList = cache.findOneSessionConfigList()) == null) {
sessionConfigList = sessionConfigRes.findAll();
if (sessionConfigList != null && sessionConfigList.size() > 0) {
cache.putSessionConfigList(sessionConfigList);
}
}
return sessionConfigList;
}
/**
* 载入坐席 ACD策略配置
*
* @return
*/
public SessionConfig initSessionConfig(String organid) {
SessionConfig sessionConfig;
if ((sessionConfig = cache.findOneSessionConfig(organid)) == null) {
sessionConfig = sessionConfigRes.findBySkill(organid);
if (sessionConfig == null) {
sessionConfig = new SessionConfig();
} else {
cache.putSessionConfig(sessionConfig, organid);
}
}
return sessionConfig;
}
/**
* 确定AgentStatus空闲坐席优先
*
* @param agentStatuses
* @return
*/
public AgentStatus decideAgentStatusWithIdleAgent(final List<AgentStatus> agentStatuses) {
for (final AgentStatus o : agentStatuses) {
if (o.getUsers() == 0) {
logger.info("[decideAgentStatusWithIdleAgent] choose agentno {} by idle status.", o.getAgentno());
return o;
}
}
return null;
}
/**
* 确定AgentStatus坐席平均分配
*
* @param agentStatuses
* @return
*/
public AgentStatus decideAgentStatusInAverage(final List<AgentStatus> agentStatuses) {
// 查找最少人数的AgentStatus
AgentStatus x = agentStatuses.stream().min(Comparator.comparingInt(AgentStatus::getUsers)).get();
if (x != null) {
logger.info("[decideAgentStatusWithIdleAgent] choose agentno {} in average.", x.getAgentno());
}
return x;
}
/**
* 过滤就绪坐席
* 优先级: 1. 指定坐席;2. 指定技能组; 3. 租户所有的坐席
*
* @param agentUser
* @return
*/
public List<AgentStatus> filterOutAvailableAgentStatus(
final AgentUser agentUser,
final SessionConfig sessionConfig) {
logger.info(
"[filterOutAvailableAgentStatus] pre-conditions: agentUser.agentno {}, skill {}, onlineUser {}",
agentUser.getAgentno(), agentUser.getSkill(), agentUser.getUserid()
);
List<AgentStatus> agentStatuses = new ArrayList<>();
Map<String, AgentStatus> map = cache.findAllReadyAgentStatus();
// DEBUG
if (map.size() > 0) {
StringBuffer sb = new StringBuffer();
sb.append("[filterOutAvailableAgentStatus] ready agents online: \n");
for (final Map.Entry<String, AgentStatus> f : map.entrySet()) {
sb.append(
String.format(" name %s, agentno %s, service %d/%d, status %s, busy %s, skills %s \n",
f.getValue().getUsername(),
f.getValue().getAgentno(), f.getValue().getUsers(), f.getValue().getMaxusers(),
f.getValue().getStatus(), f.getValue().isBusy(),
HashMapUtils.concatKeys(f.getValue().getSkills(), "|")));
}
logger.info(sb.toString());
} else {
logger.info("[filterOutAvailableAgentStatus] None ready agent found.");
}
if (agentUser != null && StringUtils.isNotBlank(agentUser.getAgentno())) {
User user = userRes.findById(agentUser.getAgentno()).orElse(null);
if (user != null && !user.isSuperadmin()) {
// 用户不为空并且不是超级管理员
// 指定坐席
for (final Map.Entry<String, AgentStatus> entry : map.entrySet()) {
// 被指定的坐席不检查是否忙是否达到最大接待数量
if (StringUtils.equals(
entry.getValue().getAgentno(), agentUser.getAgentno())) {
agentStatuses.add(entry.getValue());
logger.info(
"[filterOutAvailableAgentStatus] <Agent> find ready agent {}, name {}, status {}, service {}/{}",
entry.getValue().getAgentno(), entry.getValue().getUsername(), entry.getValue().getStatus(),
entry.getValue().getUsers(),
entry.getValue().getMaxusers());
break;
}
}
}
}
// 此处size是1或0
if (agentStatuses.size() == 1) {
logger.info("[filterOutAvailableAgentStatus] agent status list size: {}", agentStatuses.size());
// 得到指定的坐席
return filterOutAgentStatusBySkipSuperAdmin(agentStatuses);
}
// Note 如果指定了坐席但是该坐席却不是就绪的那么就根据技能组或其它条件查找
/**
* 指定坐席未查询到就绪的
*/
if (StringUtils.isNotBlank(agentUser.getSkill())) {
// 指定技能组
for (final Map.Entry<String, AgentStatus> entry : map.entrySet()) {
if ((!entry.getValue().isBusy()) &&
(getAgentUsersBySkill(entry.getValue(), agentUser.getSkill()) < sessionConfig.getMaxuser()) &&
(entry.getValue().getSkills() != null &&
entry.getValue().getSkills().containsKey(agentUser.getSkill()))) {
logger.info(
"[filterOutAvailableAgentStatus] <Skill#{}> find ready agent {}, name {}, status {}, service {}/{}, skills {}",
agentUser.getSkill(),
entry.getValue().getAgentno(), entry.getValue().getUsername(), entry.getValue().getStatus(),
entry.getValue().getUsers(),
entry.getValue().getMaxusers(),
HashMapUtils.concatKeys(entry.getValue().getSkills(), "|"));
agentStatuses.add(entry.getValue());
} else {
logger.info(
"[filterOutAvailableAgentStatus] <Skill#{}> skip ready agent {}, name {}, status {}, service {}/{}, skills {}",
agentUser.getSkill(),
entry.getValue().getAgentno(), entry.getValue().getUsername(), entry.getValue().getStatus(),
entry.getValue().getUsers(),
entry.getValue().getMaxusers(),
HashMapUtils.concatKeys(entry.getValue().getSkills(), "|"));
}
}
// 如果绑定了技能组立即返回该技能组的人
// 这时候如果该技能组没有人也不按照其它条件查找
logger.info("[filterOutAvailableAgentStatus] agent status list size: {}", agentStatuses.size());
return filterOutAgentStatusBySkipSuperAdmin(agentStatuses);
} else {
/**
* 在指定的坐席和技能组中未查到坐席
* 接下来进行无差别查询
*
* TODO 指定技能组无用户停止分配
*/
Channel channel = snsAccountRes.findBySnsid(agentUser.getAppid()).get();
Map<String, Organ> allOrgan = organProxy.findAllOrganByParentId(channel.getOrgan());
// allOrgan.keySet().retainAll
// 对于该租户的所有客服
for (final Map.Entry<String, AgentStatus> entry : map.entrySet()) {
Set<String> agentSkills = entry.getValue().getSkills().keySet();
agentSkills.retainAll(allOrgan.keySet());
if ((!entry.getValue().isBusy()) && (entry.getValue().getUsers() < sessionConfig.getMaxuser()) && agentSkills.size() > 0) {
agentStatuses.add(entry.getValue());
logger.info(
"[filterOutAvailableAgentStatus] <Redundance> find ready agent {}, agentname {}, status {}, service {}/{}, skills {}",
entry.getValue().getAgentno(), entry.getValue().getUsername(), entry.getValue().getStatus(),
entry.getValue().getUsers(),
entry.getValue().getMaxusers(),
HashMapUtils.concatKeys(entry.getValue().getSkills(), "|"));
} else {
logger.info(
"[filterOutAvailableAgentStatus] <Redundance> skip ready agent {}, name {}, status {}, service {}/{}, skills {}",
entry.getValue().getAgentno(), entry.getValue().getUsername(), entry.getValue().getStatus(),
entry.getValue().getUsers(),
entry.getValue().getMaxusers(),
HashMapUtils.concatKeys(entry.getValue().getSkills(), "|"));
}
}
}
logger.info("[filterOutAvailableAgentStatus] agent status list size: {}", agentStatuses.size());
return filterOutAgentStatusBySkipSuperAdmin(agentStatuses);
}
/**
* 过滤超级管理员
*
* @param agentStatuses
* @return
*/
private List<AgentStatus> filterOutAgentStatusBySkipSuperAdmin(final List<AgentStatus> agentStatuses) {
List<AgentStatus> result = new ArrayList<>();
List<String> uids = new ArrayList<>();
HashMap<String, User> userMap = new HashMap<>();
for (final AgentStatus as : agentStatuses) {
if (StringUtils.isNotBlank(as.getUserid()))
uids.add(as.getUserid());
}
List<User> users = userRes.findByIdIn(uids);
for (final User u : users) {
userMap.put(u.getId(), u);
}
for (final AgentStatus as : agentStatuses) {
if (userMap.containsKey(as.getUserid())) {
if (!userMap.get(as.getUserid()).isSuperadmin())
result.add(as);
}
}
logger.info("[filterOutAgentStatusBySkipSuperAdmin] agent status list size: {}", agentStatuses.size());
return result;
}
/**
* 根据坐席配置的策略输出符合要求的AgentStatus确定最终的坐席
*
* @param sessionConfig
* @param agentStatuses
* @return
*/
public AgentStatus filterOutAgentStatusWithPolicies(
final SessionConfig sessionConfig,
final List<AgentStatus> agentStatuses,
final String onlineUserId,
final boolean isInvite) {
AgentStatus agentStatus = null;
// 过滤后没有就绪的满足条件的坐席
if (agentStatuses.size() == 0) {
return agentStatus;
}
// 邀请功能
if (isInvite) {
logger.info("[filterOutAgentStatusWithPolicies] is invited onlineUser.");
if (agentStatuses.size() == 1) {
agentStatus = agentStatuses.get(0);
// Note: 如何该邀请人离线了恰巧只有一个其它就绪坐席也会进入这种条件
logger.info(
"[filterOutAgentStatusWithPolicies] resolve agent as the invitee {}.",
agentStatus.getAgentno());
}
// 邀请功能但是agentStatuses大小不是1则进入后续决策
}
// 启用历史坐席优先
if ((agentStatus == null) && sessionConfig.isLastagent()) {
logger.info("[filterOutAgentStatusWithPolicies] check agent against chat history.");
// 启用了历史坐席优先 查找 历史服务坐席
List<WebIMReport> webIMaggs = MainUtils.getWebIMDataAgg(
onlineUserRes.findBySkillForDistinctAgent(sessionConfig.getSkill(), onlineUserId));
for (WebIMReport report : webIMaggs) {
for (final AgentStatus o : agentStatuses) {
if (StringUtils.equals(
o.getAgentno(), report.getData()) && getAgentUsersBySkill(o, sessionConfig.getSkill()) < sessionConfig.getMaxuser()) {
agentStatus = o;
logger.info(
"[filterOutAgentStatusWithPolicies] choose agentno {} by chat history.",
agentStatus.getAgentno());
break;
}
}
if (agentStatus != null) {
break;
}
}
}
// 新客服接入人工坐席分配策略
if (agentStatus == null) {
// 设置默认为空闲坐席优先
if (StringUtils.isBlank(sessionConfig.getDistribution())) {
sessionConfig.setDistribution("0");
}
switch (sessionConfig.getDistribution()) {
case "0":
// 空闲坐席优先
agentStatus = decideAgentStatusWithIdleAgent(agentStatuses);
if (agentStatus == null) {
// 如果没有空闲坐席则按照平均分配
agentStatus = decideAgentStatusInAverage(agentStatuses);
}
break;
case "1":
// 坐席平均分配
agentStatus = decideAgentStatusInAverage(agentStatuses);
break;
default:
logger.warn(
"[filterOutAgentStatusWithPolicies] unexpected Distribution Strategy 【{}】",
sessionConfig.getDistribution());
}
}
if (agentStatus != null) {
logger.info(
"[filterOutAgentStatusWithPolicies] final agentStatus {}, agentno {}", agentStatus.getId(),
agentStatus.getAgentno());
} else {
logger.info("[filterOutAgentStatusWithPolicies] oops, no agent satisfy rules.");
}
return agentStatus;
}
public int getAgentUsersBySkill(AgentStatus agentStatus, String skill) {
return agentUserRes.countByAgentnoAndStatusAndSkill(agentStatus.getAgentno(), MainContext.AgentUserStatusEnum.INSERVICE.toString(), skill);
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.AgentUser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class ACDQueueService {
private final static Logger logger = LoggerFactory.getLogger(ACDQueueService.class);
@Autowired
private Cache cache;
@SuppressWarnings("unchecked")
public int getQueueIndex(String agent, String skill) {
int queneUsers = 0;
Map<String, AgentUser> map = cache.getAgentUsersInQue();
for (final Map.Entry<String, AgentUser> entry : map.entrySet()) {
if (StringUtils.isNotBlank(skill)) {
if (StringUtils.equals(entry.getValue().getSkill(), skill)) {
queneUsers++;
}
continue;
} else {
if (StringUtils.isNotBlank(agent)) {
if (StringUtils.equals(entry.getValue().getAgentno(), agent)) {
queneUsers++;
}
continue;
} else {
queneUsers++;
}
}
}
return queneUsers;
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.cskefu.cc.basic.MainContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Automatic Call Distribution Main Entry
* ACD服务路由得到子服务
*/
public class ACDServiceRouter {
private final static Logger logger = LoggerFactory.getLogger(ACDServiceRouter.class);
private static ACDChatbotService acdChatbotService;
// 坐席服务
private static ACDAgentService acdAgentService;
private static ACDPolicyService acdPolicyService;
private static ACDWorkMonitor acdWorkMonitor;
public static ACDPolicyService getAcdPolicyService() {
if (acdPolicyService == null) {
acdPolicyService = MainContext.getContext().getBean(ACDPolicyService.class);
}
return acdPolicyService;
}
public static ACDAgentService getAcdAgentService() {
if (acdAgentService == null) {
acdAgentService = MainContext.getContext().getBean(ACDAgentService.class);
}
return acdAgentService;
}
public static ACDChatbotService getAcdChatbotService() {
if (acdChatbotService == null) {
acdChatbotService = MainContext.getContext().getBean(ACDChatbotService.class);
}
return acdChatbotService;
}
public static ACDWorkMonitor getAcdWorkMonitor() {
if (acdWorkMonitor == null) {
acdWorkMonitor = MainContext.getContext().getBean(ACDWorkMonitor.class);
}
return acdWorkMonitor;
}
}

View File

@ -1,107 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.chatopera.compose4j.Composer;
import com.chatopera.compose4j.exception.Compose4jRuntimeException;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.acd.basic.IACDDispatcher;
import com.cskefu.cc.acd.middleware.visitor.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
/**
* 处置访客分配
*/
@Component
public class ACDVisitorDispatcher implements IACDDispatcher {
private final static Logger logger = LoggerFactory.getLogger(ACDVisitorDispatcher.class);
/**
* 为访客安排坐席
*/
private Composer<ACDComposeContext> pipleline;
@Autowired
private ACDVisBodyParserMw acdVisBodyParserMw;
@Autowired
private ACDVisBindingMw acdVisBindingMw;
@Autowired
private ACDVisSessionCfgMw acdVisSessionCfgMw;
@Autowired
private ACDVisServiceMw acdVisServiceMw;
@Autowired
private ACDVisAllocatorMw acdVisAllocatorMw;
@PostConstruct
private void setup() {
logger.info("[setup] setup ACD Visitor Dispatch Service ...");
buildPipeline();
}
/**
* 建立访客处理管道
*/
private void buildPipeline() {
pipleline = new Composer<>();
/**
* 1) 设置基本信息
*/
pipleline.use(acdVisBodyParserMw);
/**
* 1) 绑定技能组或坐席(包括邀请时的坐席)
*/
pipleline.use(acdVisBindingMw);
/**
* 1) 坐席配置:工作时间段有无就绪在线坐席
*
*/
pipleline.use(acdVisSessionCfgMw);
/**
* 1选择坐席确定AgentService
*/
pipleline.use(acdVisServiceMw);
/**
* 1根据策略筛选坐席
*/
pipleline.use(acdVisAllocatorMw);
}
@Override
public void enqueue(final ACDComposeContext ctx) {
try {
pipleline.handle(ctx);
} catch (Compose4jRuntimeException e) {
logger.error("[enqueueVisitor] error", e);
}
}
@Override
public void dequeue(ACDComposeContext ctx) {
}
}

View File

@ -1,194 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.AgentReport;
import com.cskefu.cc.model.AgentStatus;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.WorkMonitor;
import com.cskefu.cc.persistence.repository.AgentServiceRepository;
import com.cskefu.cc.persistence.repository.AgentUserRepository;
import com.cskefu.cc.persistence.repository.WorkMonitorRepository;
import com.cskefu.cc.proxy.OrganProxy;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
public class ACDWorkMonitor {
private final static Logger logger = LoggerFactory.getLogger(ACDWorkMonitor.class);
@Autowired
private WorkMonitorRepository workMonitorRes;
@Autowired
private Cache cache;
@Autowired
private OrganProxy organProxy;
@Autowired
private AgentServiceRepository agentServiceRes;
@Autowired
private AgentUserRepository agentUserRes;
/**
* 获得 当前服务状态
*
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public AgentReport getAgentReport() {
return getAgentReport(null);
}
/**
* 获得一个技能组的坐席状态
*
* @param organ
* @return
*/
public AgentReport getAgentReport(String organ) {
/**
* 统计当前在线的坐席数量
*/
AgentReport report = new AgentReport();
Map<String, AgentStatus> readys = cache.getAgentStatusReady();
int readyNum = 0;
int busyNum = 0;
for (Map.Entry<String, AgentStatus> entry : readys.entrySet()) {
if (organ == null) {
readyNum++;
if (entry.getValue().isBusy()) {
busyNum++;
}
continue;
}
if (entry.getValue().getSkills() != null &&
entry.getValue().getSkills().containsKey(organ)) {
readyNum++;
if (entry.getValue().isBusy()) {
busyNum++;
}
}
}
report.setAgents(readyNum);
report.setBusy(busyNum);
/**
* 统计当前服务中的用户数量
*/
if (organ != null) {
Organ currentOrgan = new Organ();
currentOrgan.setId(organ);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
report.setUsers(agentServiceRes.countByStatusAndAgentskillIn(MainContext.AgentUserStatusEnum.INSERVICE.toString(), organs.keySet()));
report.setInquene(agentUserRes.countByStatusAndSkillIn(MainContext.AgentUserStatusEnum.INQUENE.toString(), organs.keySet()));
} else {
// 服务中
report.setUsers(cache.getInservAgentUsersSize());
// 等待中
report.setInquene(cache.getInqueAgentUsersSize());
}
// DEBUG
logger.info(
"[getAgentReport] organ {}, agents {}, busy {}, users {}, inqueue {}", organ,
report.getAgents(), report.getBusy(), report.getUsers(), report.getInquene()
);
return report;
}
/**
* @param agent 坐席
* @param userid 用户ID
* @param status 工作状态也就是上一个状态
* @param current 下一个工作状态
* @param worktype 类型 语音OR 文本
* @param lasttime
*/
public void recordAgentStatus(
String agent,
String username,
String extno,
boolean admin,
String userid,
String status,
String current,
String worktype,
Date lasttime
) {
WorkMonitor workMonitor = new WorkMonitor();
if (StringUtils.isNotBlank(agent) && StringUtils.isNotBlank(status)) {
workMonitor.setAgent(agent);
workMonitor.setAgentno(agent);
workMonitor.setStatus(status);
workMonitor.setAdmin(admin);
workMonitor.setUsername(username);
workMonitor.setExtno(extno);
workMonitor.setWorktype(worktype);
if (lasttime != null) {
workMonitor.setDuration((int) (System.currentTimeMillis() - lasttime.getTime()) / 1000);
}
if (status.equals(MainContext.AgentStatusEnum.BUSY.toString())) {
workMonitor.setBusy(true);
}
if (status.equals(MainContext.AgentStatusEnum.READY.toString())) {
int count = workMonitorRes.countByAgentAndDatestrAndStatus(
agent, MainUtils.simpleDateFormat.format(new Date()),
MainContext.AgentStatusEnum.READY.toString()
);
if (count == 0) {
workMonitor.setFirsttime(true);
}
}
if (current.equals(MainContext.AgentStatusEnum.NOTREADY.toString())) {
List<WorkMonitor> workMonitorList = workMonitorRes.findByAgentAndDatestrAndFirsttime(agent, MainUtils.simpleDateFormat.format(new Date()), true);
if (workMonitorList.size() > 0) {
WorkMonitor firstWorkMonitor = workMonitorList.get(0);
if (firstWorkMonitor.getFirsttimes() == 0) {
firstWorkMonitor.setFirsttimes(
(int) (System.currentTimeMillis() - firstWorkMonitor.getCreatetime().getTime()));
workMonitorRes.save(firstWorkMonitor);
}
}
}
workMonitor.setCreatetime(new Date());
workMonitor.setDatestr(MainUtils.simpleDateFormat.format(new Date()));
workMonitor.setName(agent);
workMonitor.setUserid(userid);
workMonitorRes.save(workMonitor);
}
}
}

View File

@ -1,309 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd.basic;
import com.cskefu.cc.model.*;
import com.cskefu.cc.socketio.message.Message;
import com.cskefu.cc.util.IP;
public class ACDComposeContext extends Message {
// 技能组及渠道
private String organid;
private Organ organ;
private String appid;
private String channeltype;
private Channel channel;
private String sessionid;
// 策略
private SessionConfig sessionConfig;
// 坐席报告
private AgentReport agentReport;
// 机器人客服
private String aiid;
private boolean isAi;
// 是否是邀请
private boolean isInvite;
private User agent;
private String agentno;
private String agentUserId;
private String agentServiceId;
private AgentUser agentUser;
private AgentService agentService;
// 访客
private String onlineUserId;
private PassportWebIMUser passportWebIMUser;
private String onlineUserNickname;
private String onlineUserHeadimgUrl;
// 其它信息
private IP ipdata;
private String initiator;
private String title;
private String url;
private String browser;
private String osname;
private String traceid;
private String ownerid;
private String ip;
public String getOrganid() {
return organid;
}
public void setOrganid(String organid) {
this.organid = organid;
}
public Organ getOrgan() {
return organ;
}
public void setOrgan(Organ organ) {
this.organ = organ;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getChannelType() {
return channeltype;
}
public void setChannelType(String channelType) {
this.channeltype = channelType;
}
public Channel getSnsAccount() {
return channel;
}
public void setSnsAccount(Channel channel) {
this.channel = channel;
}
public SessionConfig getSessionConfig() {
return sessionConfig;
}
public void setSessionConfig(SessionConfig sessionConfig) {
this.sessionConfig = sessionConfig;
}
public String getAiid() {
return aiid;
}
public void setAiid(String aiid) {
this.aiid = aiid;
}
public boolean isAi() {
return isAi;
}
public void setAi(boolean ai) {
isAi = ai;
}
public boolean isInvite() {
return isInvite;
}
public void setInvite(boolean invite) {
isInvite = invite;
}
public User getAgent() {
return agent;
}
public void setAgent(User agent) {
this.agent = agent;
}
public String getAgentno() {
return agentno;
}
public void setAgentno(String agentno) {
this.agentno = agentno;
}
public String getAgentUserId() {
return agentUserId;
}
public void setAgentUserId(String agentUserId) {
this.agentUserId = agentUserId;
}
public String getOnlineUserId() {
return onlineUserId;
}
public void setOnlineUserId(String onlineUserId) {
this.onlineUserId = onlineUserId;
}
public String getAgentServiceId() {
return agentServiceId;
}
public void setAgentServiceId(String agentServiceId) {
this.agentServiceId = agentServiceId;
}
public AgentUser getAgentUser() {
return agentUser;
}
public void setAgentUser(AgentUser agentUser) {
this.agentUser = agentUser;
}
public PassportWebIMUser getOnlineUser() {
return passportWebIMUser;
}
public void setOnlineUser(PassportWebIMUser passportWebIMUser) {
this.passportWebIMUser = passportWebIMUser;
}
public AgentService getAgentService() {
return agentService;
}
public void setAgentService(AgentService agentService) {
this.agentService = agentService;
}
public String getSessionid() {
return sessionid;
}
public void setSessionid(String sessionid) {
this.sessionid = sessionid;
}
public String getOnlineUserNickname() {
return onlineUserNickname;
}
public void setOnlineUserNickname(String onlineUserNickname) {
this.onlineUserNickname = onlineUserNickname;
}
public IP getIpdata() {
return ipdata;
}
public void setIpdata(IP ipdata) {
this.ipdata = ipdata;
}
public String getInitiator() {
return initiator;
}
public void setInitiator(String initiator) {
this.initiator = initiator;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getBrowser() {
return browser;
}
public void setBrowser(String browser) {
this.browser = browser;
}
public String getOsname() {
return osname;
}
public void setOsname(String osname) {
this.osname = osname;
}
public String getTraceid() {
return traceid;
}
public void setTraceid(String traceid) {
this.traceid = traceid;
}
public String getOwnerid() {
return ownerid;
}
public void setOwnerid(String ownerid) {
this.ownerid = ownerid;
}
public String getOnlineUserHeadimgUrl() {
return onlineUserHeadimgUrl;
}
public void setOnlineUserHeadimgUrl(String onlineUserHeadimgUrl) {
this.onlineUserHeadimgUrl = onlineUserHeadimgUrl;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public AgentReport getAgentReport() {
return agentReport;
}
public void setAgentReport(AgentReport agentReport) {
this.agentReport = agentReport;
}
}

View File

@ -1,228 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd.basic;
import com.cskefu.cc.acd.ACDPolicyService;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.model.AgentService;
import com.cskefu.cc.model.AgentUser;
import com.cskefu.cc.model.SessionConfig;
import com.cskefu.cc.util.IP;
import com.cskefu.cc.util.IPTools;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ACDMessageHelper {
private final static Logger logger = LoggerFactory.getLogger(ACDMessageHelper.class);
@Autowired
private ACDPolicyService acdPolicyService;
/**
* 通过 AgentUser获得ComposeContext
*
* @param agentUser
* @param isInvite
* @param initiator
* @return
*/
public ACDComposeContext getComposeContextWithAgentUser(final AgentUser agentUser, final boolean isInvite, final String initiator) {
ACDComposeContext ctx = new ACDComposeContext();
ctx.setOnlineUserId(agentUser.getUserid());
ctx.setOnlineUserNickname(agentUser.getNickname());
ctx.setOrganid(agentUser.getSkill());
ctx.setChannelType(agentUser.getChanneltype());
ctx.setAgentno(agentUser.getAgentno());
ctx.setBrowser(agentUser.getBrowser());
ctx.setOsname(agentUser.getOsname());
ctx.setAppid(agentUser.getAppid());
ctx.setTitle(agentUser.getTitle());
ctx.setSessionid(agentUser.getSessionid());
ctx.setUrl(agentUser.getUrl());
ctx.setOwnerid(agentUser.getOwner());
if (StringUtils.isNotBlank(agentUser.getIpaddr())) {
ctx.setIp(agentUser.getIpaddr());
// TODO set IP Data
ctx.setIpdata(IPTools.getInstance().findGeography(agentUser.getIpaddr()));
}
ctx.setInvite(isInvite);
ctx.setInitiator(initiator);
return ctx;
}
/**
* 通知消息内容分配到坐席
*
* @param agentService
* @param channel
* @return
*/
public String getSuccessMessage(AgentService agentService, String channel) {
String queneTip = "<span id='agentno'>" + agentService.getAgentusername() + "</span>";
if (!MainContext.ChannelType.WEBIM.toString().equals(channel)) {
queneTip = agentService.getAgentusername();
}
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(agentService.getSkill());
String successMsg = "坐席分配成功," + queneTip + "为您服务。";
if (StringUtils.isNotBlank(sessionConfig.getSuccessmsg())) {
successMsg = sessionConfig.getSuccessmsg().replaceAll("\\{agent\\}", queneTip);
}
return successMsg;
}
/**
* 通知消息内容和坐席断开
*
* @param channel
* @return
*/
public String getServiceFinishMessage(String channel, String organid) {
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(organid);
String queneTip = "坐席已断开和您的对话";
if (StringUtils.isNotBlank(sessionConfig.getFinessmsg())) {
queneTip = sessionConfig.getFinessmsg();
}
return queneTip;
}
/**
* 通知消息内容和坐席断开刷新页面
*
* @param channel
* @return
*/
public String getServiceOffMessage(String channel, String organid) {
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(organid);
String queneTip = "坐席已断开和您的对话,刷新页面为您分配新的坐席";
if (StringUtils.isNotBlank(sessionConfig.getFinessmsg())) {
queneTip = sessionConfig.getFinessmsg();
}
return queneTip;
}
public String getNoAgentMessage(int queneIndex, String channel, String organid) {
if (queneIndex < 0) {
queneIndex = 0;
}
String queneTip = "<span id='queneindex'>" + queneIndex + "</span>";
if (!MainContext.ChannelType.WEBIM.toString().equals(channel)) {
queneTip = String.valueOf(queneIndex);
}
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(organid);
String noAgentTipMsg = "坐席全忙,已进入等待队列,您也可以在其他时间再来咨询。";
if (StringUtils.isNotBlank(sessionConfig.getNoagentmsg())) {
noAgentTipMsg = sessionConfig.getNoagentmsg().replaceAll("\\{num\\}", queneTip);
}
return noAgentTipMsg;
}
public String getQueneMessage(int queneIndex, String channel, String organid) {
String queneTip = "<span id='queneindex'>" + queneIndex + "</span>";
if (!MainContext.ChannelType.WEBIM.toString().equals(channel)) {
queneTip = String.valueOf(queneIndex);
}
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(organid);
String agentBusyTipMsg = "正在排队,请稍候,在您之前,还有 " + queneTip + " 位等待用户。";
if (StringUtils.isNotBlank(sessionConfig.getAgentbusymsg())) {
agentBusyTipMsg = sessionConfig.getAgentbusymsg().replaceAll("\\{num\\}", queneTip);
}
return agentBusyTipMsg;
}
/**
* 构建WebIM分发的Context
*
* @param onlineUserId
* @param nickname
* @param session
* @param appid
* @param ip
* @param osname
* @param browser
* @param headimg
* @param ipdata
* @param channel
* @param skill
* @param agent
* @param title
* @param url
* @param traceid
* @param ownerid
* @param isInvite
* @param initiator
* @return
*/
public static ACDComposeContext getWebIMComposeContext(
final String onlineUserId,
final String nickname,
final String session,
final String appid,
final String ip,
final String osname,
final String browser,
final String headimg,
final IP ipdata,
final String channel,
final String skill,
final String agent,
final String title,
final String url,
final String traceid,
final String ownerid,
final boolean isInvite,
final String initiator) {
logger.info(
"[enqueueVisitor] user {}, appid {}, agent {}, skill {}, nickname {}, initiator {}, isInvite {}",
onlineUserId,
appid,
agent,
skill,
nickname, initiator, isInvite);
// 坐席服务请求分配 坐席
final ACDComposeContext ctx = new ACDComposeContext();
ctx.setOnlineUserId(onlineUserId);
ctx.setOnlineUserNickname(nickname);
ctx.setOrganid(skill);
ctx.setChannelType(channel);
ctx.setAgentno(agent);
ctx.setBrowser(browser);
ctx.setOsname(osname);
ctx.setAppid(appid);
ctx.setTitle(title);
ctx.setSessionid(session);
ctx.setUrl(url);
ctx.setOnlineUserHeadimgUrl(headimg);
ctx.setTraceid(traceid);
ctx.setOwnerid(ownerid);
ctx.setInitiator(initiator);
ctx.setIpdata(ipdata);
ctx.setIp(ip);
ctx.setInvite(isInvite);
return ctx;
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.acd.basic;
/**
* 调度类抽象接口
*/
public interface IACDDispatcher {
// 一个目标对象入队
void enqueue(final ACDComposeContext ctx);
// 一个目标对象出队
void dequeue(final ACDComposeContext ctx);
}

View File

@ -1,67 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd.middleware.visitor;
import com.cskefu.cc.acd.ACDAgentService;
import com.cskefu.cc.acd.ACDPolicyService;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.model.AgentService;
import com.cskefu.cc.model.AgentStatus;
import com.chatopera.compose4j.Functional;
import com.chatopera.compose4j.Middleware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ACDVisAllocatorMw implements Middleware<ACDComposeContext> {
private final static Logger logger = LoggerFactory.getLogger(ACDVisAllocatorMw.class);
@Autowired
private ACDAgentService acdAgentService;
@Autowired
private ACDPolicyService acdPolicyService;
@Override
public void apply(final ACDComposeContext ctx, final Functional next) {
/**
* 查询条件当前在线的 坐席并且 未达到最大 服务人数的坐席
*/
final List<AgentStatus> agentStatuses = acdPolicyService.filterOutAvailableAgentStatus(
ctx.getAgentUser(), ctx.getSessionConfig());
/**
* 处理ACD 技能组请求和 坐席请求
*/
AgentStatus agentStatus = acdPolicyService.filterOutAgentStatusWithPolicies(
ctx.getSessionConfig(), agentStatuses, ctx.getOnlineUserId(), ctx.isInvite());
AgentService agentService = null;
try {
agentService = acdAgentService.resolveAgentService(
agentStatus, ctx.getAgentUser(), false);
} catch (Exception ex) {
logger.warn("[allotAgent] exception: ", ex);
}
ctx.setAgentService(agentService);
}
}

View File

@ -1,86 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd.middleware.visitor;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.repository.OrganRepository;
import com.cskefu.cc.persistence.repository.UserRepository;
import com.chatopera.compose4j.Functional;
import com.chatopera.compose4j.Middleware;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ACDVisBindingMw implements Middleware<ACDComposeContext> {
private final static Logger logger = LoggerFactory.getLogger(ACDVisBindingMw.class);
@Autowired
private UserRepository userRes;
@Autowired
private OrganRepository organRes;
/**
* 绑定技能组或坐席
*
* @param ctx
* @param next
*/
@Override
public void apply(final ACDComposeContext ctx, final Functional next) {
/**
* 访客新上线的请求
*/
/**
* 技能组 坐席
*/
if (StringUtils.isNotBlank(ctx.getOrganid())) {
logger.info("[apply] bind skill {}", ctx.getOrganid());
// 绑定技能组
Organ organ = organRes.findById(ctx.getOrganid()).orElse(null);
if (organ != null) {
ctx.getAgentUser().setSkill(organ.getId());
ctx.setOrgan(organ);
}
} else {
// 如果没有绑定技能组则清除之前的标记
ctx.getAgentUser().setSkill(null);
}
if (StringUtils.isNotBlank(ctx.getAgentno()) && (!StringUtils.equalsIgnoreCase(ctx.getAgentno(), "null"))) {
logger.info("[apply] bind agentno {}, isInvite {}", ctx.getAgentno(), ctx.isInvite());
// 绑定坐席
// 绑定坐席有可能是因为前端展示了技能组和坐席
// 也有可能是坐席发送了邀请该访客接收邀请
ctx.getAgentUser().setAgentno(ctx.getAgentno());
User agent = userRes.findById(ctx.getAgentno()).orElse(null);
ctx.setAgent(agent);
ctx.getAgentUser().setAgentname(agent.getUname());
} else {
// 如果没有绑定坐席则清除之前的标记
ctx.getAgentUser().setAgentno(null);
ctx.getAgentUser().setAgentname(null);
ctx.setAgent(null);
}
next.apply();
}
}

View File

@ -1,242 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd.middleware.visitor;
import com.cskefu.cc.acd.ACDQueueService;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.acd.basic.ACDMessageHelper;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.AgentUser;
import com.cskefu.cc.model.AgentUserContacts;
import com.cskefu.cc.model.Contacts;
import com.cskefu.cc.persistence.repository.ContactsRepository;
import com.cskefu.cc.persistence.repository.AgentUserContactsRepository;
import com.cskefu.cc.proxy.AgentStatusProxy;
import com.cskefu.cc.proxy.AgentUserProxy;
import com.chatopera.compose4j.Functional;
import com.chatopera.compose4j.Middleware;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Resolve AgentUser
*/
@Component
public class ACDVisBodyParserMw implements Middleware<ACDComposeContext> {
private final static Logger logger = LoggerFactory.getLogger(ACDVisBodyParserMw.class);
@Autowired
private AgentUserContactsRepository agentUserContactsRes;
@Autowired
private ContactsRepository contactsRes;
@Autowired
private Cache cache;
@Autowired
private AgentUserProxy agentUserProxy;
@Autowired
private AgentStatusProxy agentStatusProxy;
@Autowired
private ACDQueueService acdQueueService;
@Autowired
private ACDMessageHelper acdMessageHelper;
/**
* 设置AgentUser基本信息
*
* @param ctx
* @param next
*/
@Override
public void apply(final ACDComposeContext ctx, final Functional next) {
/**
* NOTE AgentUser代表一次会话记录在上一个会话结束并且由坐席人员点击"清除"会从数据库中删除
* 此处查询到的可能是之前的会话其状态需要验证所以不一定是由TA来服务本次会话
*/
AgentUser agentUser = cache.findOneAgentUserByUserId(ctx.getOnlineUserId()).orElseGet(
() -> {
/**
* NOTE 新创建的AgentUser不需要设置Status和Agentno
* 因为两个值在后面会检查如果存在则不会申请新的Agent
*/
AgentUser p = new AgentUser(
ctx.getOnlineUserId(),
ctx.getChannelType(),
ctx.getOnlineUserId(),
ctx.getOnlineUserNickname(),
ctx.getAppid());
logger.info("[apply] create new agent user id {}", p.getId());
return p;
});
logger.info("[apply] resolve agent user id {}", agentUser.getId());
agentUser.setUsername(resolveAgentUsername(agentUser, ctx.getOnlineUserNickname()));
agentUser.setOsname(ctx.getOsname());
agentUser.setBrowser(ctx.getBrowser());
agentUser.setAppid(ctx.getAppid());
agentUser.setSessionid(ctx.getSessionid());
if (ctx.getIpdata() != null) {
logger.info("[apply] set IP data for agentUser {}", agentUser.getId());
agentUser.setCountry(ctx.getIpdata().getCountry());
agentUser.setProvince(ctx.getIpdata().getProvince());
agentUser.setCity(ctx.getIpdata().getCity());
if (StringUtils.isNotBlank(ctx.getIp())) {
agentUser.setRegion(ctx.getIpdata().toString() + "[" + ctx.getIp() + "]");
} else {
agentUser.setRegion(ctx.getIpdata().toString());
}
}
agentUser.setOwner(ctx.getOwnerid()); // 智能IVR的 EventID
agentUser.setHeadimgurl(ctx.getOnlineUserHeadimgUrl());
agentUser.setTitle(ctx.getTitle());
agentUser.setUrl(ctx.getUrl());
agentUser.setTraceid(ctx.getTraceid());
ctx.setAgentUser(agentUser);
next.apply();
/**
* 发送通知
*/
if (ctx.getAgentService() != null && StringUtils.isNotBlank(ctx.getAgentService().getStatus())) {
/**
* 找到空闲坐席如果未找到坐席则将该用户放入到 排队队列
*/
switch (MainContext.AgentUserStatusEnum.toValue(ctx.getAgentService().getStatus())) {
case INSERVICE:
ctx.setMessage(
acdMessageHelper.getSuccessMessage(
ctx.getAgentService(),
ctx.getChannelType()));
// TODO 判断 INSERVICE agentService 对应的 agentUser
logger.info(
"[apply] agent service: agentno {}, \n agentuser id {} \n user {} \n channel {} \n status {} \n queue index {}",
ctx.getAgentService().getAgentno(), ctx.getAgentService().getAgentuserid(),
ctx.getAgentService().getUserid(),
ctx.getAgentService().getChanneltype(),
ctx.getAgentService().getStatus(),
ctx.getAgentService().getQueneindex());
if (StringUtils.isNotBlank(ctx.getAgentService().getAgentuserid())) {
agentUserProxy.findOne(ctx.getAgentService().getAgentuserid()).ifPresent(ctx::setAgentUser);
}
// TODO 如果是 INSERVICE 那么 agentService.getAgentuserid 就一定不能为空
// // TODO 此处需要考虑 agentService.getAgentuserid 为空的情况
// // 那么什么情况下agentService.getAgentuserid为空
// if (StringUtils.isNotBlank(agentService.getAgentuserid())) {
// logger.info("[handle] set Agent User with agentUser Id {}", agentService.getAgentuserid());
// getAgentUserProxy().findOne(agentService.getAgentuserid()).ifPresent(p -> {
// outMessage.setChannelMessage(p);
// });
// } else {
// logger.info("[handle] agent user id is null.");
// }
agentStatusProxy.broadcastAgentsStatus(
"user", MainContext.AgentUserStatusEnum.INSERVICE.toString(),
ctx.getAgentUser().getId());
break;
case INQUENE:
// 处理结果进入排队队列
ctx.getAgentService().setQueneindex(
acdQueueService.getQueueIndex(
ctx.getAgentUser().getAgentno(), ctx.getAgentUser().getSkill()));
if (ctx.getAgentService().getQueneindex() > 0) {
// 当前有坐席要排队
ctx.setMessage(acdMessageHelper.getQueneMessage(
ctx.getAgentService().getQueneindex(),
ctx.getAgentUser().getChanneltype(),
ctx.getOrganid()));
} else {
// TODO 什么是否返回 noAgentMessage, 是否在是 INQUENE getQueneindex == 0
// 当前没有坐席要留言
ctx.setNoagent(true);
ctx.setMessage(acdMessageHelper.getNoAgentMessage(
ctx.getAgentService().getQueneindex(),
ctx.getChannelType(),
ctx.getOrganid()));
}
agentStatusProxy.broadcastAgentsStatus("user", MainContext.AgentUserStatusEnum.INQUENE.toString(),
ctx.getAgentUser().getId());
break;
case END:
logger.info("[handler] should not happen for new onlineUser service request.");
default:
}
ctx.setChannelMessage(ctx.getAgentUser());
} else {
ctx.setNoagent(true);
ctx.setMessage(acdMessageHelper.getNoAgentMessage(
0,
ctx.getChannelType(),
ctx.getOrganid()));
}
logger.info(
"[apply] message text: {}, noagent {}", ctx.getMessage(), ctx.isNoagent());
}
/**
* 确定该访客的名字优先级
* 1. 如果AgentUser username nickName 不一致则用 agentUser username
* 2. 如果AgentUser username nickName 一致则查找 AgentUserContact对应的联系人
* 2.1 如果联系人存在则用联系人的名字
* 2.2 如果联系人不存在则使用 nickName
* <p>
* TODO 此处有一些问题如果联系人更新了名字那么么后面TA的会话用的还是旧的名字
* 所以在更新联系人名字的时候也应更新其对应的AgentUser里面的名字
*
* @param agentUser
* @param nickname
* @return
*/
private String resolveAgentUsername(final AgentUser agentUser, final String nickname) {
if (!StringUtils.equals(agentUser.getUsername(), nickname)) {
return agentUser.getUsername();
}
// 查找会话联系人关联表
AgentUserContacts agentUserContact = agentUserContactsRes.findOneByUserid(
agentUser.getUserid()).orElse(null);
if (agentUserContact != null) {
Contacts contact = contactsRes.findOneById(agentUserContact.getContactsid()).orElseGet(null);
if (contact != null) {
return contact.getName();
}
}
return nickname;
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd.middleware.visitor;
import com.cskefu.cc.acd.ACDQueueService;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.acd.basic.ACDMessageHelper;
import com.cskefu.cc.basic.MainContext;
import com.chatopera.compose4j.Functional;
import com.chatopera.compose4j.Middleware;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 寻找或为绑定服务访客的坐席建立双方通话
*/
@Component
public class ACDVisServiceMw implements Middleware<ACDComposeContext> {
private final static Logger logger = LoggerFactory.getLogger(ACDVisServiceMw.class);
@Autowired
private ACDQueueService acdQueueService;
@Autowired
private ACDMessageHelper acdMessageHelper;
@Override
public void apply(final ACDComposeContext ctx, final Functional next) {
ctx.setMessageType(MainContext.MessageType.STATUS.toString());
/**
* 首先交由 IMR处理 MESSAGE指令 如果当前用户是在 坐席对话列表中 则直接推送给坐席如果不在则执行 IMR
*/
if (StringUtils.isNotBlank(ctx.getAgentUser().getStatus())) {
// 该AgentUser已经在数据库中
switch (MainContext.AgentUserStatusEnum.toValue(ctx.getAgentUser().getStatus())) {
case INQUENE:
logger.info("[apply] agent user is in queue");
int queueIndex = acdQueueService.getQueueIndex(
ctx.getAgentUser().getAgentno(),
ctx.getOrganid());
ctx.setMessage(
acdMessageHelper.getQueneMessage(
queueIndex,
ctx.getChannelType(),
ctx.getOrganid()));
break;
case INSERVICE:
// 该访客与坐席正在服务中忽略新的连接
logger.info(
"[apply] agent user {} is in service, userid {}, agentno {}", ctx.getAgentUser().getId(),
ctx.getAgentUser().getUserid(), ctx.getAgentUser().getAgentno());
break;
case END:
logger.info("[apply] agent user is null or END");
// 过滤坐席获得 Agent Service
next.apply();
}
} else {
// 该AgentUser为新建
// 过滤坐席获得 Agent Service
next.apply();
}
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.acd.middleware.visitor;
import com.cskefu.cc.acd.ACDPolicyService;
import com.cskefu.cc.acd.ACDWorkMonitor;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.model.AgentReport;
import com.cskefu.cc.model.SessionConfig;
import com.chatopera.compose4j.Functional;
import com.chatopera.compose4j.Middleware;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
*/
@Component
public class ACDVisSessionCfgMw implements Middleware<ACDComposeContext> {
private final static Logger logger = LoggerFactory.getLogger(ACDVisSessionCfgMw.class);
@Autowired
private ACDPolicyService acdPolicyService;
@Autowired
private ACDWorkMonitor acdWorkMonitor;
@Override
public void apply(final ACDComposeContext ctx, final Functional next) {
SessionConfig sessionConfig = acdPolicyService.initSessionConfig(ctx.getOrganid());
ctx.setSessionConfig(sessionConfig);
// 查询就绪的坐席如果指定技能组则按照技能组查询
AgentReport report;
if (StringUtils.isNotBlank(ctx.getOrganid())) {
report = acdWorkMonitor.getAgentReport(ctx.getOrganid());
} else {
report = acdWorkMonitor.getAgentReport();
}
ctx.setAgentReport(report);
// 不在工作时间段
if (sessionConfig.isHourcheck() && !MainUtils.isInWorkingHours(sessionConfig.getWorkinghours())) {
logger.info("[apply] not in working hours");
ctx.setMessage(sessionConfig.getNotinwhmsg());
} else if (report.getAgents() == 0) {
// 没有就绪的坐席
if (ctx.getChannelType().equals(MainContext.ChannelType.MESSENGER.toString())) {
next.apply();
} else {
logger.info("[apply] find no agents, redirect to leave a message.");
ctx.setNoagent(true);
}
} else {
logger.info("[apply] find agents size {}, allocate agent in next.", report.getAgents());
// 具备工作中的就绪坐席进入筛选坐席
next.apply();
}
}
}

View File

@ -1,110 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.activemq;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.exception.CSKefuException;
import com.cskefu.cc.model.AgentUser;
import com.cskefu.cc.model.AgentUserAudit;
import com.cskefu.cc.persistence.repository.AgentUserRepository;
import com.cskefu.cc.proxy.AgentAuditProxy;
import com.cskefu.cc.socketio.client.NettyClients;
import com.cskefu.cc.util.SerializeUtil;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* 会话监控
*/
@Component
public class AgentAuditSubscription {
private final static Logger logger = LoggerFactory.getLogger(AgentAuditSubscription.class);
@Autowired
private Cache cache;
@Autowired
private AgentAuditProxy agentAuditProxy;
@Autowired
private AgentUserRepository agentUserRes;
/**
* 接收坐席会话监控消息
*
* @param msg
*/
@JmsListener(destination = Constants.AUDIT_AGENT_MESSAGE, containerFactory = "jmsListenerContainerTopic")
public void onMessage(final String msg) {
logger.info("[onMessage] payload {}", msg);
try {
final JsonObject json = new JsonParser().parse(msg).getAsJsonObject();
if (json.has("data") &&
json.has("agentUserId") &&
json.has("event") && json.has("agentno")) {
// 查找关联的会话监控信息
final AgentUserAudit agentUserAudit = cache.findOneAgentUserAuditById(
json.get("agentUserId").getAsString()).orElseGet(() -> {
final AgentUser agentUser = agentUserRes.findById(json.get("agentUserId").getAsString()).orElse(null);
if (agentUser != null) {
return agentAuditProxy.updateAgentUserAudits(agentUser);
} else {
logger.warn(
"[onMessage] can not find agent user by id {}", json.get("agentUserId").getAsString());
}
return null;
});
if (agentUserAudit != null) {
final String agentno = json.get("agentno").getAsString();
logger.info(
"[onMessage] agentno {}, subscribers size {}, subscribers {}", agentno,
agentUserAudit.getSubscribers().size(),
StringUtils.join(agentUserAudit.getSubscribers().keySet(), "|"));
// 发送消息给坐席监控不需要分布式因为这条消息已经是从ActiveMQ使用Topic多机广播
for (final String subscriber : agentUserAudit.getSubscribers().keySet()) {
logger.info("[onMessage] process subscriber {}", subscriber);
if (!StringUtils.equals(subscriber, agentno)) {
logger.info("[onMessage] publish event to {}", subscriber);
NettyClients.getInstance().publishAuditEventMessage(
subscriber,
json.get("event").getAsString(),
SerializeUtil.deserialize(json.get("data").getAsString()));
}
}
} else {
logger.warn(
"[onMessage] can not resolve agent user audit object for agent user id {}",
json.get("agentUserId").getAsString());
}
} else {
throw new CSKefuException("Invalid payload.");
}
} catch (Exception e) {
logger.error("[onMessage] error", e);
}
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.activemq;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.socketio.client.NettyClients;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class AgentSessionSubscription {
private final static Logger logger = LoggerFactory.getLogger(AgentSessionSubscription.class);
/**
* 接收坐席会话监控消息
*
* @param msg
*/
@JmsListener(destination = Constants.MQ_TOPIC_WEB_SESSION_SSO, containerFactory = "jmsListenerContainerTopic")
public void onMessage(final String msg) {
logger.info("[onMessage] payload {}", msg);
try {
final JsonObject json = new JsonParser().parse(msg).getAsJsonObject();
// 把登出消息通知给浏览器
NettyClients.getInstance().publishLeaveEventMessage(
json.get("agentno").getAsString(),
json.get("expired").getAsString());
} catch (Exception e) {
logger.warn("[onMessage] error", e);
}
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, All rights reserved.
* <https://www.chatopera.com>
*/
package com.cskefu.cc.activemq;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.socketio.client.NettyClients;
import com.cskefu.cc.util.SerializeUtil;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* WebIM Agent
*/
@Component
public class AgentSubscription {
private final static Logger logger = LoggerFactory.getLogger(AgentSubscription.class);
@Value("${application.node.id}")
private String appNodeId;
@Autowired
private BrokerPublisher brokerPublisher;
/**
* Publish Message into ActiveMQ
*
* @param j
*/
public void publish(JsonObject j) {
j.addProperty("node", appNodeId);
brokerPublisher.send(Constants.INSTANT_MESSAGING_MQ_TOPIC_AGENT, j.toString(), true);
}
@JmsListener(destination = Constants.INSTANT_MESSAGING_MQ_TOPIC_AGENT, containerFactory = "jmsListenerContainerTopic")
public void onMessage(final String payload) {
logger.info("[onMessage] payload {}", payload);
JsonParser parser = new JsonParser();
JsonObject j = parser.parse(payload).getAsJsonObject();
logger.debug("[onMessage] message body {}", j.toString());
try {
if (!j.has("id")) {
logger.warn("[onMessage] Invalid payload, id is null");
return;
}
NettyClients.getInstance().sendAgentEventMessage(
j.get("id").getAsString(),
j.get("event").getAsString(),
SerializeUtil.deserialize(j.get("data").getAsString()));
} catch (Exception e) {
logger.error("onMessage", e);
}
}
}

View File

@ -1,63 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.activemq;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.persistence.repository.BlackListRepository;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* 访客黑名单
*/
@Component
public class BlackListEventSubscription {
private final static Logger logger = LoggerFactory.getLogger(BlackListEventSubscription.class);
@Autowired
private Cache cache;
@Autowired
private BlackListRepository blackListRes;
/**
* 拉黑访客到达拉黑时间后从黑名单中移除
*
* @param payload
*/
@JmsListener(destination = Constants.WEBIM_SOCKETIO_ONLINE_USER_BLACKLIST, containerFactory = "jmsListenerContainerQueue")
public void onMessage(final String payload) {
logger.info("[onMessage] payload {}", payload);
try {
final JSONObject json = JSON.parseObject(payload);
final String userId = json.getString("userId");
if (StringUtils.isNotBlank(userId)) {
cache.findOneBlackEntityByUserId(userId).ifPresent(blackListRes::delete);
} else {
logger.warn("[onMessage] error: invalid payload");
}
} catch (Exception e) {
logger.error("[onMessage] error", e);
}
}
}

View File

@ -1,111 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, All rights reserved.
* <https://www.chatopera.com>
*/
package com.cskefu.cc.activemq;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.PostConstruct;
import org.apache.activemq.ScheduledMessage;
import org.apache.activemq.command.ActiveMQTopic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class BrokerPublisher {
final static private Logger logger = LoggerFactory.getLogger(BrokerPublisher.class);
@Autowired
private JmsTemplate jmsTemplate;
@PostConstruct
public void setup() {
logger.info("[ActiveMQ Publisher] setup successfully.");
}
/**
* 时延消息
*
* @param destination
* @param payload
* @param delay available by delayed seconds
*/
public void send(final String destination, final String payload, final boolean isTopic, final int delay) {
try {
if (isTopic) {
jmsTemplate.convertAndSend(new ActiveMQTopic(destination), payload, m -> {
m.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 1000 * delay);
return m;
});
} else {
// 默认为Queue
jmsTemplate.convertAndSend(destination, payload, m -> {
m.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 1000 * delay);
return m;
});
}
logger.debug("[send] send succ, dest {}, payload {}", destination, payload);
} catch (Exception e) {
logger.warn("[send] error happens.", e);
}
}
/**
* @param destination
* @param payload
* @param isTopic
*/
public void send(final String destination, final String payload, boolean isTopic) {
try {
if (isTopic) {
jmsTemplate.convertAndSend(new ActiveMQTopic(destination), payload);
} else {
// 默认为Queue
jmsTemplate.convertAndSend(destination, payload);
}
logger.debug("[send] send succ, dest {}, payload {}", destination, payload);
} catch (Exception e) {
logger.warn("[send] error happens.", e);
}
}
public void send(final String destination, final String payload) {
send(destination, payload, false);
}
public void send(final String destination, final JSONObject payload) {
send(destination, payload.toJSONString());
}
public void send(final String destination, final org.json.JSONObject payload) {
send(destination, payload.toString());
}
public void send(final String destination, final Map<String, String> payload) {
JSONObject obj = new JSONObject();
for (Map.Entry<String, String> entry : payload.entrySet()) {
obj.put(entry.getKey(), entry.getValue());
}
send(destination, obj.toJSONString());
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, All rights reserved.
* <https://www.chatopera.com>
*/
package com.cskefu.cc.activemq;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.socketio.client.NettyClients;
import com.cskefu.cc.util.SerializeUtil;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
/**
* IM OnlineUser
*/
@Component
public class OnlineUserSubscription {
private final static Logger logger = LoggerFactory.getLogger(OnlineUserSubscription.class);
@Value("${application.node.id}")
private String appNodeId;
@Autowired
private BrokerPublisher brokerPublisher;
@PostConstruct
public void setup() {
logger.info("ActiveMQ Subscription is setup successfully.");
}
/**
* Publish Message into ActiveMQ
*
* @param j
*/
public void publish(final JsonObject j) {
j.addProperty("node", appNodeId);
brokerPublisher.send(Constants.INSTANT_MESSAGING_MQ_TOPIC_ONLINEUSER, j.toString(), true);
}
@JmsListener(destination = Constants.INSTANT_MESSAGING_MQ_TOPIC_ONLINEUSER, containerFactory = "jmsListenerContainerTopic")
public void onMessage(final String payload){
logger.info("[onMessage] payload {}", payload);
JsonParser parser = new JsonParser();
JsonObject j = parser.parse(payload).getAsJsonObject();
logger.debug("[instant messaging] message body {}", j.toString());
try {
NettyClients.getInstance().publishIMEventMessage(j.get("id").getAsString(),
j.get("event").getAsString(),
SerializeUtil.deserialize(j.get("data").getAsString()));
} catch (Exception e) {
logger.error("onMessage", e);
}
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, All rights reserved.
* <https://www.chatopera.com>
*/
package com.cskefu.cc.activemq;
import com.cskefu.cc.acd.ACDAgentDispatcher;
import com.cskefu.cc.acd.ACDWorkMonitor;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.AgentStatus;
import com.cskefu.cc.persistence.repository.AgentStatusRepository;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.util.Date;
/**
* 处理SocketIO的离线事件
*/
@Component
public class SocketioConnEventSubscription {
private final static Logger logger = LoggerFactory.getLogger(SocketioConnEventSubscription.class);
@Autowired
private ACDAgentDispatcher acdAgentDispatcher;
@Autowired
private ACDWorkMonitor acdWorkMonitor;
@Autowired
private AgentStatusRepository agentStatusRes;
@Autowired
private Cache cache;
@Value("${application.node.id}")
private String appNodeId;
@PostConstruct
public void setup() {
logger.info("ActiveMQ Subscription is setup successfully.");
}
@JmsListener(destination = Constants.WEBIM_SOCKETIO_AGENT_DISCONNECT, containerFactory = "jmsListenerContainerQueue")
public void onMessage(final String payload) {
logger.info("[onMessage] payload {}", payload);
try {
JsonParser parser = new JsonParser();
JsonObject j = parser.parse(payload).getAsJsonObject();
if (j.has("userId") && j.has("isAdmin")) {
final AgentStatus agentStatus = cache.findOneAgentStatusByAgentno(
j.get("userId").getAsString());
if (agentStatus != null && (!agentStatus.isConnected())) {
/**
* 处理该坐席为离线
*/
// 重分配坐席
ACDComposeContext ctx = new ACDComposeContext();
ctx.setAgentno(agentStatus.getAgentno());
acdAgentDispatcher.dequeue(ctx);
if (ctx.isResolved()) {
logger.info("[onMessage] re-allotAgent for user's visitors successfully.");
} else {
logger.info("[onMessage] re-allotAgent, error happens.");
}
// 更新数据库
agentStatus.setBusy(false);
agentStatus.setStatus(MainContext.AgentStatusEnum.OFFLINE.toString());
agentStatus.setUpdatetime(new Date());
// 设置该坐席状态为离线
cache.deleteAgentStatusByAgentno(agentStatus.getAgentno());
agentStatusRes.save(agentStatus);
// 记录坐席工作日志
acdWorkMonitor.recordAgentStatus(agentStatus.getAgentno(),
agentStatus.getUsername(),
agentStatus.getAgentno(),
j.get("isAdmin").getAsBoolean(),
agentStatus.getAgentno(),
agentStatus.getStatus(),
MainContext.AgentStatusEnum.OFFLINE.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(),
null);
} else if (agentStatus == null) {
// 该坐席已经完成离线设置
logger.info("[onMessage] agent is already offline, skip any further operations");
} else {
// 该坐席目前在线忽略该延迟事件
logger.info("[onMessage] agent is online now, ignore this message.");
}
}
} catch (Exception e) {
logger.error("onMessage", e);
}
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.aspect;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.AgentStatus;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
*/
@Aspect
@Component
public class AgentStatusAspect {
private final static Logger logger = LoggerFactory.getLogger(AgentStatusAspect.class);
@Autowired
private Cache cache;
@After("execution(* com.cskefu.cc.persistence.repository.AgentStatusRepository.save(..))")
public void save(final JoinPoint joinPoint) {
final AgentStatus agentStatus = (AgentStatus) joinPoint.getArgs()[0];
cache.putAgentStatus(agentStatus);
}
@After("execution(* com.cskefu.cc.persistence.repository.AgentStatusRepository.delete(..))")
public void delete(final JoinPoint joinPoint) {
final AgentStatus agentStatus = (AgentStatus) joinPoint.getArgs()[0];
cache.deleteAgentStatusByAgentno(agentStatus.getAgentno());
}
}

View File

@ -1,116 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.aspect;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.cache.RedisCommand;
import com.cskefu.cc.cache.RedisKey;
import com.cskefu.cc.model.AgentUser;
import com.cskefu.cc.proxy.AgentAuditProxy;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Aspect
@Component
public class AgentUserAspect {
private final static Logger logger = LoggerFactory.getLogger(AgentUserAspect.class);
@Autowired
private Cache cache;
@Autowired
private RedisCommand redisCommand;
@Autowired
private AgentAuditProxy agentAuditProxy;
@After("execution(* com.cskefu.cc.persistence.repository.AgentUserRepository.save(..))")
public void save(final JoinPoint joinPoint) {
final AgentUser agentUser = (AgentUser) joinPoint.getArgs()[0];
logger.info(
"[save] agentUser id {}, agentno {}, userId {}, status {}", agentUser.getId(), agentUser.getAgentno(),
agentUser.getUserid(), agentUser.getStatus());
if (StringUtils.isBlank(agentUser.getId())
|| StringUtils.isBlank(agentUser.getUserid())) {
return;
}
// 更新坐席监控信息
agentAuditProxy.updateAgentUserAudits(agentUser);
// 同步缓存
cache.putAgentUser(agentUser);
}
@After("execution(* com.cskefu.cc.persistence.repository.AgentUserRepository.delete(..))")
public void delete(final JoinPoint joinPoint) {
final AgentUser agentUser = (AgentUser) joinPoint.getArgs()[0];
logger.info(
"[delete] agentUser id {}, agentno {}, userId {}", agentUser.getId(), agentUser.getAgentno(),
agentUser.getUserid());
cache.deleteAgentUserAuditById(agentUser.getId());
cache.deleteAgentUserByUserId(agentUser);
}
/**
* 更新内存中的坐席与其服务的访客的集合
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("@annotation(com.cskefu.cc.aspect.AgentUserAspect.LinkAgentUser)")
public Object LinkAgentUser(ProceedingJoinPoint joinPoint) throws Throwable {
final AgentUser updated = (AgentUser) joinPoint.getArgs()[0];
Object proceed = joinPoint.proceed(); // after things are done.
logger.info(
"[linkAgentUser] agentUser: status {}, userId {}, agentno {}", updated.getStatus(),
updated.getUserid(), updated.getAgentno());
if (StringUtils.equals(updated.getStatus(), MainContext.AgentUserStatusEnum.END.toString())) {
// 从集合中删除
redisCommand.removeSetVal(
RedisKey.getInServAgentUsersByAgentno(updated.getAgentno()), updated.getUserid());
} else if (StringUtils.equals(updated.getStatus(), MainContext.AgentUserStatusEnum.INSERVICE.toString())) {
redisCommand.insertSetVal(
RedisKey.getInServAgentUsersByAgentno(updated.getAgentno()), updated.getUserid());
} else if (StringUtils.equals(updated.getStatus(), MainContext.AgentUserStatusEnum.INQUENE.toString())) {
logger.info("[linkAgentUser] ignored inque agent user, haven't resolve one agent yet.");
} else {
logger.warn("[linkAgentUser] unexpected condition.");
}
return proceed;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LinkAgentUser {
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.aspect;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.BlackEntity;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BlackEntityAspect {
private final static Logger logger = LoggerFactory.getLogger(BlackEntityAspect.class);
@Autowired
private Cache cache;
@After("execution(* com.cskefu.cc.persistence.repository.BlackListRepository.save(..))")
public void save(final JoinPoint joinPoint) {
final BlackEntity blackEntity = (BlackEntity) joinPoint.getArgs()[0];
logger.info("[save] blackEntity userId {}", blackEntity.getUserid());
cache.putBlackEntity(blackEntity);
}
@After("execution(* com.cskefu.cc.persistence.repository.BlackListRepository.delete(..))")
public void delete(final JoinPoint joinPoint) {
final BlackEntity blackEntity = (BlackEntity) joinPoint.getArgs()[0];
logger.info("[delete] blackEntity userId {}", blackEntity.getUserid());
cache.deleteBlackEntityByUserId(blackEntity.getUserid());
}
}

View File

@ -1,58 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.aspect;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.PassportWebIMUser;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class OnlineUserAspect {
private final static Logger logger = LoggerFactory.getLogger(OnlineUserAspect.class);
@Autowired
private Cache cache;
/**
* 因为会定期从缓存序列化到数据库
*
* @param joinPoint
*/
@Before("execution(* com.cskefu.cc.persistence.repository.PassportWebIMUserRepository.save(..))")
public void save(final JoinPoint joinPoint) {
final PassportWebIMUser passportWebIMUser = (PassportWebIMUser) joinPoint.getArgs()[0];
// logger.info(
// "[save] put onlineUser id {}, status {}, invite status {}", onlineUser.getId(), onlineUser.getStatus(),
// onlineUser.getInvitestatus());
if (StringUtils.isNotBlank(passportWebIMUser.getStatus())) {
switch (MainContext.OnlineUserStatusEnum.toValue(passportWebIMUser.getStatus())) {
case OFFLINE:
cache.deleteOnlineUserById(passportWebIMUser.getId());
break;
default:
cache.putOnlineUser(passportWebIMUser);
}
}
}
}

View File

@ -1,93 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.aspect;
import com.cskefu.cc.persistence.hibernate.BaseService;
import com.cskefu.cc.util.CskefuList;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.StaleStateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Aspect
@Component
public class SyncDatabaseAspect {
private final static Logger logger = LoggerFactory.getLogger(SyncDatabaseAspect.class);
@Autowired
private BaseService<?> dbDataRes;
/**
* 定义拦截规则拦截org.springframework.data.elasticsearch.repository
*/
@Pointcut("execution(* org.springframework.data.elasticsearch.repository.*.save(*))")
public void syncSaveEsData() {
}
/**
* 定义拦截规则拦截org.springframework.data.elasticsearch.repository
*/
@Pointcut("execution(* org.springframework.data.elasticsearch.repository.*.delete(*))")
public void syncDeleteEsData() {
}
@SuppressWarnings("unchecked")
@Around("syncSaveEsData()")
public void syncSaveEsData(ProceedingJoinPoint pjp) throws Throwable {
pjp.proceed();
Object[] args = pjp.getArgs();
if (args.length == 1) {
Object data = args[0];
if (data != null) {
if (data instanceof CskefuList) {
/** 只有一个地方用到从ES同步数据到MySQL **/
} else if (data instanceof List) {
// TODO 批量建联系人操作会执行这段代码此处会报错但是批量更新可以通过
dbDataRes.saveOrUpdateAll((List<Object>) data);
} else {
try {
// 更新时执行此代码但是新建时会报错
dbDataRes.saveOrUpdate(data);
} catch (StaleStateException ex) {
// 报错的情况下执行此代码
dbDataRes.save(data);
}
}
}
}
}
@SuppressWarnings("unchecked")
@Around("syncDeleteEsData()")
public void syncDeleteEsData(ProceedingJoinPoint pjp) throws Throwable {
pjp.proceed();
Object[] args = pjp.getArgs();
if (args.length == 1) {
Object data = args[0];
if (data instanceof List) {
dbDataRes.deleteAll((List<Object>) data);
} else {
dbDataRes.delete(data);
}
}
}
}

View File

@ -1,220 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.basic;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 常量
*/
public class Constants {
/**
* 系统配置
*/
public static final String USER_SESSION_NAME = "user";
public static final String ORGAN_SESSION_NAME = "organ";
public static final String GUEST_USER = "guest";
public static final String IM_USER_SESSION_NAME = "im_user";
public static final String CSKEFU_SYSTEM_DIC = "com.dic.system.template";
public static final String CSKEFU_SYSTEM_AUTH_DIC = "com.dic.auth.resource";
public static final String CSKEFU_SYSTEM_AREA_DIC = "com.dic.address.area";
public static final String CSKEFU_SYSTEM_ADPOS_DIC = "com.dic.adv.type";
public static final String CSKEFU_SYSTEM_COMMENT_DIC = "com.dic.webim.comment";
public static final String CSKEFU_SYSTEM_COMMENT_ITEM_DIC = "com.dic.webim.comment.item";
public static final String CSKEFU_SYSTEM_DIS_AI = "ownerai";
public static final String CSKEFU_SYSTEM_DIS_AGENT = "owneruser";
public static final String CSKEFU_SYSTEM_ASSUSER = "assuser";
public static final String CSKEFU_SYSTEM_DIS_ORGAN = "ownerdept";
public static final String CSKEFU_SYSTEM_DIS_TIME = "distime";
public static final String CSKEFU_SYSTEM_COOKIES_FLAG = "uk_flagid";
public static final String CSKEFU_SYSTEM_NO_DAT = "NOTEXIST";
public static final String CSKEFU_SYSTEM_SECFIELD = "cskefu_sec_field";
public static final String CSKEFU_SYSTEM_CALLCENTER = "callcenter";
public static final String CSKEFU_SYSTEM_WORKORDEREMAIL = "workordermail";
public static final String CSKEFU_SYSTEM_SMSEMAIL = "callcenter";
public static final String CSKEFU_SYSTEM_AI_INPUT = "inputparam";
public static final String CSKEFU_SYSTEM_AI_OUTPUT = "outputparam";
public static final String CSKEFU_SYSTEM_INFOACQ = "infoacq"; // 数据采集模式
public static final String DEFAULT_TYPE = "default"; // 默认分类代码
public static final String CACHE_SKILL = "cache_skill_"; // 技能组的缓存
public static final String CACHE_AGENT = "cache_agent_"; // 坐席列表的缓存
public static final String CUBE_TITLE_MEASURE = "指标";
public static final String CSKEFU_SYSTEM_AREA = "cskefu_system_area";
public static final String CSKEFU_SYSTEM_ADV = "cskefu_system_adv"; // 系统广告位
public static final String SYSTEM_CACHE_CALLOUT_CONFIG = "callout_config";
/**
* 分布式存储
*/
public final static String MINIO_BUCKET = "chatopera";
/**
* Channels
*/
public static final String CHANNEL_TYPE_WEBIM = "webim";
public static final String CHANNEL_TYPE_MESSENGER = "messenger";
public final static String IM_MESSAGE_TYPE_MESSAGE = "message";
public final static String IM_MESSAGE_TYPE_WRITING = "writing";
public final static String CHATBOT_EVENT_TYPE_CHAT = "chat";
/**
* Messenger Channels
*/
public static final String MESSENGER_CHANNEL_ENABLED = "enabled";
public static final String MESSENGER_CHANNEL_DISABLED = "disabled";
/**
* Modules
*/
public final static String CSKEFU_MODULE_CALLOUT = "callout";
public final static String CSKEFU_MODULE_CHATBOT = "chatbot";
public final static String CSKEFU_MODULE_CONTACTS = "contacts";
public final static String CSKEFU_MODULE_SKYPE = "skype";
public final static String CSKEFU_MODULE_MESSENGER = "messenger";
public final static String CSKEFU_MODULE_CCA = "cca";
public final static String CSKEFU_MODULE_ENTIM = "entim";
public final static String CSKEFU_MODULE_WORKORDERS = "workorders";
public final static String CSKEFU_MODULE_CALLCENTER = "callcenter";
public final static String CSKEFU_MODULE_REPORT = "report";
/**
* Formatter
*/
// Date Formatter https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
public final static SimpleDateFormat QUERY_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
public final static SimpleDateFormat DISPLAY_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public final static DecimalFormat DURATION_MINS_FORMATTER = new DecimalFormat("0.00");
/**
* Instant Messaging Events
*/
public final static String INSTANT_MESSAGING_MQ_TOPIC_AGENT = "cskefu.webim.agent";
// freeswitch 通知消息
public final static String INSTANT_MESSAGING_MQ_QUEUE_PBX = "pbx.*.events";
public final static String INSTANT_MESSAGING_MQ_TOPIC_ONLINEUSER = "cskefu.webim.onlineuser";
public final static String WEBIM_SOCKETIO_AGENT_DISCONNECT = "cskefu.socketio.agent.disconnect";
// 黑名单
public final static String WEBIM_SOCKETIO_ONLINE_USER_BLACKLIST = "cskefu.im.onlineuser.blacklist";
// 坐席socketio断开到判定为离线的时长
public final static int WEBIM_SOCKETIO_AGENT_OFFLINE_THRESHOLD = 20;
// 发送消息给访客: 接收来自路由的消息并判断渠道
public final static String INSTANT_MESSAGING_MQ_TOPIC_VISITOR = "cskefu.outbound.visitor";
// 发送给聊天机器人并处理返回结果
public final static String INSTANT_MESSAGING_MQ_QUEUE_CHATBOT = "cskefu.outbound.chatbot";
public static final String AUDIT_AGENT_MESSAGE = "cskefu.agent.audit";
// 机器人返回的结果数据来源为faq
public final static String PROVIDER_FAQ = "faq";
public final static String PROVIDER_FEEDBACK = "feedback";
public final static String PROVIDER_FEEDBACK_EVAL_POSITIVE = "positive";
public final static String PROVIDER_FEEDBACK_EVAL_NEGATIVE = "negative";
// Facebook OTN 发送
public final static String INSTANT_MESSAGING_MQ_QUEUE_FACEBOOK_OTN = "cskefu.outbound.faceboot.otn";
/**
* 登录用户的唯一登录会话管理
*/
// web session single sign on
public final static String MQ_TOPIC_WEB_SESSION_SSO = "cskefu.agent.session.retired";
/**
* Attachment File Type
*/
public final static String ATTACHMENT_TYPE_IMAGE = "image";
public final static String ATTACHMENT_TYPE_FILE = "file";
/**
* FreeSwitch Communication
*/
// callcenter
public final static String ACTIVEMQ_QUEUE_SWITCH_SYNC = "cskefu.callcenter.switch.sync";
// callout
public final static String FS_SIP_STATUS = "pbx:%s:sips"; // 查询SIP状态
public final static String FS_CHANNEL_CC_TO_FS = "pbx/%s/execute"; // 发送外呼执行信号
public final static String FS_DIALPLAN_STATUS = "pbx:%s:status"; // 外呼执行状态存储
public final static String FS_DIALPLAN_TARGET = "pbx:%s:targets:%s"; // 外呼计划电话列表
public final static String FS_BRIDGE_CONNECT = "callOutConnect";
public final static String FS_LEG_ANSWER = "answer";
public final static String FS_LEG_HANGUP = "hangup";
public final static String FS_LEG_INCALL_ZH = "通话";
public final static String FS_CALL_TYPE_CALLOUT = "callout";
public final static Set<String> CALL_DIRECTION_TYPES = new HashSet<>(Arrays.asList(
MainContext.CallType.OUT.toString(), MainContext.CallType.IN.toString()));
public final static Set<String> CALL_SERVICE_STAUTS = new HashSet<>(Arrays.asList(MainContext.CallServiceStatus.INQUENE.toString(),
MainContext.CallServiceStatus.RING.toString(),
MainContext.CallServiceStatus.INCALL.toString(),
MainContext.CallServiceStatus.BRIDGE.toString(),
MainContext.CallServiceStatus.HOLD.toString(),
MainContext.CallServiceStatus.HANGUP.toString(),
MainContext.CallServiceStatus.OFFLINE.toString()));
/**
* 缓存管理策略
*/
public final static String cache_setup_strategy_skip = "skip";
/**
* Skype消息路由
* TODO 待优化为Skype渠道暂时使用常量
*/
public final static String CHANNEL_SKYPE_DEST = "skype.{0}.send";
public final static String CHANNEL_SKYPE_RECV = "skype.*.rec";
public static final String SKYPE_PAYLOAD_KEY_CONTENT = "content";
public static final String SKYPE_PAYLOAD_KEY_SKYPEID = "skypeId";
public static final String SKYPE_PAYLOAD_KEY_MSGTYPE = "msgType";
/**
* skype接收图片类型
*/
public final static String SKYPE_MESSAGE_TEXT = "text";
public final static String SKYPE_MESSAGE_PIC = "pic";
public final static String SKYPE_MESSAGE_FILE = "file";
/**
* 坐席邀请访客加入聊天的超时如果访客过了这么长时间还没有接入
* 就忽略该邀请当前设置为 20 分钟如果访客点击该邀请则会随机分配坐席
*/
public final static int WEBIM_AGENT_INVITE_TIMEOUT = 20 * 60 * 1000;
/**
* 聊天机器人
*/
public static final HashSet<String> CHATBOT_VALID_LANGS = new HashSet<>(Arrays.asList("zh_CN", "en_US"));
public static final String CHATBOT_CHATBOT_FIRST = "机器人客服优先";
public static final String CHATBOT_HUMAN_FIRST = "人工客服优先";
public static final String CHATBOT_CHATBOT_ONLY = "仅机器人客服";
public static final HashSet<String> CHATBOT_VALID_WORKMODELS = new HashSet<>(Arrays.asList(CHATBOT_CHATBOT_FIRST, CHATBOT_HUMAN_FIRST, CHATBOT_CHATBOT_ONLY));
/**
* AUTH
*/
public static final String AUTH_TOKEN_TYPE_BEARER = "Bearer";
public static final String AUTH_TOKEN_TYPE_BASIC = "Basic";
}

View File

@ -1,20 +0,0 @@
/**
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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 Jun. 2023 Chatopera Inc. <https://www.chatopera.com>. All rights reserved.
*/
package com.cskefu.cc.basic;
import jakarta.annotation.PreDestroy;
public class TerminateBean {
@PreDestroy
public void onDestroy() throws Exception {
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.basic;
public class Viewport {
private String page;
private String template;
public Viewport(String template, String page) {
this.template = template;
this.page = page;
}
public Viewport(String page) {
this.page = page;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.basic.auth;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 存储Auth Token的Redis连接
*/
public class AuthRedisTemplate extends RedisTemplate<String, String> {
public AuthRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
this.setKeySerializer(stringSerializer);
this.setValueSerializer(stringSerializer);
this.setHashKeySerializer(stringSerializer);
this.setHashValueSerializer(stringSerializer);
}
public AuthRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
this.setConnectionFactory(connectionFactory);
this.afterPropertiesSet();
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}

View File

@ -1,32 +0,0 @@
/**
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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 Jun. 2023 Chatopera Inc. <https://www.chatopera.com>. All rights reserved.
*/
package com.cskefu.cc.basic.auth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class BasicTokenMgr {
final static Logger logger = LoggerFactory.getLogger(BasicTokenMgr.class);
/**
* Generate basic token with username and password
*
* @param username
* @param password
* @return
*/
public String generate(final String username, final String password) {
return null;
}
}

View File

@ -1,132 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.basic.auth;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.cache.RedisKey;
import com.cskefu.cc.model.User;
import com.cskefu.cc.util.SerializeUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
/**
* 认证和授权的API Token管理
*/
@Component
public class BearerTokenMgr {
private final static Logger logger = LoggerFactory.getLogger(BearerTokenMgr.class);
@Value("${server.session-timeout}")
private int timeout;
@Autowired
private AuthRedisTemplate authRedisTemplate;
private ValueOperations<String, String> redisValOps;
@PostConstruct
private void init() {
redisValOps = authRedisTemplate.opsForValue();
}
/**
* Remove token with Bearer prefix
*
* @param token
* @return
*/
private String trimToken(final String token) {
if (token.startsWith(Constants.AUTH_TOKEN_TYPE_BEARER)) {
return StringUtils.substring(token, 7);
}
return token;
}
/**
* 设置一个KEY的过期时间
*
* @param key
* @param seconds
*/
private void expire(final String key, final long seconds) {
authRedisTemplate.expire(key, seconds, TimeUnit.SECONDS);
}
private String resolveTokenKey(final String token) {
return RedisKey.getApiTokenBearerKeyWithValue(trimToken(token));
}
/**********************************
* LOGIN USER API TOKEN 相关
* 认证授权登录用户
**********************************/
/**
* @param token 授权的KEY
* @param user 已经登录的用户
*/
public void update(final String token, final User user) {
if (StringUtils.isNotBlank(token) && user != null) {
String serialized = SerializeUtil.serialize(user);
final String key = resolveTokenKey(token);
redisValOps.set(key, serialized);
expire(key, timeout);
} else {
logger.warn("[putLoginUserByAuth] error Invalid params.");
}
}
/**
* 判断一个Auth是否是有效的
*
* @param token
* @return
*/
public boolean existToken(final String token) {
return authRedisTemplate.hasKey(resolveTokenKey(token));
}
/**
* 根据租户ID和认证Auth获得一个登录用户
*
* @param token
* @return
*/
public User retrieve(final String token) {
String serialized = redisValOps.get(resolveTokenKey(token));
if (StringUtils.isNotBlank(serialized)) {
return (User) SerializeUtil.deserialize(serialized);
}
return null;
}
/**
* 登出已经登录的系统用户
*
* @param token
*/
public void delete(final String token) {
authRedisTemplate.delete(resolveTokenKey(token));
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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.basic.plugins;
import java.util.HashMap;
import java.util.Map;
public abstract class AbstractPluginConfigurer implements IPluginConfigurer {
public abstract String getPluginId();
public abstract String getPluginName();
public abstract String getIOEventHandler();
public Map<String, String> getEnvironmentVariables() {
Map<String, String> env = new HashMap<>();
return env;
}
public boolean isModule() {
return false;
}
public abstract void setup();
}

View File

@ -1,36 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.basic.plugins;
import java.util.Map;
public interface IPluginConfigurer {
// 插件的ID:插件的标识用于区别其它插件[a-z]组成最大32位长度
String getPluginId();
// 插件的名字:最少的概述插件
String getPluginName();
// 即时通信接口
String getIOEventHandler();
// 获得环境变量及默认值
Map<String, String> getEnvironmentVariables();
// 是否是Module(在一级菜单有入口的插件)
boolean isModule();
// 安装插件
public void setup();
}

View File

@ -1,97 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.basic.plugins;
import com.cskefu.cc.basic.MainContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 插件注册表
*/
@Component
public class PluginRegistry {
/**
* Plugins Entry
*/
public final static String PLUGIN_CHANNEL_MESSAGER_SUFFIX = "ChannelMessager";
public final static String PLUGIN_CHATBOT_MESSAGER_SUFFIX = "ChatbotMessager";
// 插件列表
private final List<IPluginConfigurer> plugins = new ArrayList<>();
/**
* 添加插件
*
* @param plugin
*/
public void addPlugin(final IPluginConfigurer plugin) {
for (final IPluginConfigurer x : plugins) {
if (StringUtils.equalsIgnoreCase(x.getPluginId(), plugin.getPluginId())) {
return;
}
}
if (StringUtils.isNotBlank(plugin.getPluginId())) {
MainContext.enableModule(plugin.getPluginId());
}
plugins.add(plugin);
}
/**
* 获得插件列表
*
* @return
*/
public List<IPluginConfigurer> getPlugins() {
return plugins;
}
/**
* 获得一个插件
*
* @param pluginId
* @return
*/
public Optional<IPluginConfigurer> getPlugin(final String pluginId) {
IPluginConfigurer p = null;
for (final IPluginConfigurer plugin : plugins) {
if (StringUtils.equalsIgnoreCase(plugin.getPluginId(), pluginId)) {
p = plugin;
break;
}
}
return Optional.ofNullable(p);
}
/**
* 删除插件
*
* @param pluginId
*/
public void removePlugin(final String pluginId) {
for (final IPluginConfigurer plugin : plugins) {
if (StringUtils.equalsIgnoreCase(plugin.getPluginId(), pluginId)) {
plugins.remove(plugin);
break;
}
}
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.basic.resource;
import com.cskefu.cc.model.JobDetail;
import com.cskefu.cc.util.es.UKDataBean;
import java.util.HashMap;
import java.util.Map;
public class OutputTextFormat {
private String id ;
private String title ;
private String parent ;
private Map<String , Object> data = new HashMap<>();
private JobDetail job ;
private UKDataBean dataBean ;
public OutputTextFormat(JobDetail job){
this.job = job ;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
public JobDetail getJob() {
return job;
}
public void setJob(JobDetail job) {
this.job = job;
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
this.parent = parent;
}
public UKDataBean getDataBean() {
return dataBean;
}
public void setDataBean(UKDataBean dataBean) {
this.dataBean = dataBean;
}
}

View File

@ -1,112 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.basic.resource;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.model.JobDetail;
import java.lang.reflect.InvocationTargetException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaddy0302 Rivulet Resource.java 2010-3-6
*
*/
public abstract class Resource {
public static Logger log = LoggerFactory.getLogger(Resource.class.getName()) ;
public abstract void begin() throws Exception;
public abstract void end(boolean clear) throws Exception;
/**
* Re connection
*/
public abstract JobDetail getJob();
/**
* Re connection
*/
public abstract void process(OutputTextFormat meta , JobDetail job)throws Exception;
/**
* synchronized
* Single-mode single-threaded access to records under a record
*
* @return
*/
public abstract OutputTextFormat next() throws Exception;
/**
*
* @return
*/
public abstract boolean isAvailable() ;
/**
*
* @return
*/
public abstract OutputTextFormat getText(OutputTextFormat object) throws Exception;
/**
*
*/
public abstract void rmResource() ;
/**
*
*/
public abstract void updateTask()throws Exception ;
/**
*
* @param job
* @return
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InvocationTargetException
* @throws IllegalArgumentException
*/
public static Resource getResource(JobDetail job)
throws Exception{
return job != null
&& MainContext.getResource(job.getTasktype()) != null ? (Resource) MainContext
.getResource(job.getTasktype()).getConstructor(
new Class[] { JobDetail.class }).newInstance(
new Object[] { job })
: null;
}
/**
* Filter
* @param file
* @param netFile
* @return
*/
public boolean val(String inputFile , String acceptDocType){
String file = inputFile!=null ? inputFile.toLowerCase() :null ;
return file!=null && acceptDocType!=null && ((acceptDocType.contains(file.substring(file.lastIndexOf(".") + 1)) || acceptDocType.contains("all"))) ;
}
}

View File

@ -1,822 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.cache;
import com.cskefu.cc.aspect.AgentUserAspect;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.exception.CSKefuCacheException;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.AgentUserRepository;
import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository;
import com.cskefu.cc.util.SerializeUtil;
import com.cskefu.cc.util.freeswitch.model.CallCenterAgent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.*;
@Component
public class Cache {
final static private Logger logger = LoggerFactory.getLogger(Cache.class);
@Autowired
private PassportWebIMUserRepository onlineUserRes;
@Autowired
private AgentUserRepository agentUserRes;
@Autowired
private RedisCommand redisCommand;
/**
* 获得就绪的坐席列表
*
* @return
*/
public Map<String, AgentStatus> getAgentStatusReady() {
Map<String, String> agentStatuses = redisCommand.getHash(RedisKey.getAgentStatusReadyHashKey());
return convertFromStringToAgentStatus(agentStatuses);
}
/**
* 通过访客ID获得访客坐席关联关系
*
* @param userId
* @return
*/
public Optional<AgentUser> findOneAgentUserByUserId(final String userId) {
if (redisCommand.hasHashKV(RedisKey.getAgentUserInQueHashKey(), userId)) {
// 排队等待中
return Optional.ofNullable((AgentUser) SerializeUtil.deserialize(
redisCommand.getHashKV(RedisKey.getAgentUserInQueHashKey(), userId)));
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserInServHashKey(), userId)) {
// 服务中
return Optional.ofNullable((AgentUser) SerializeUtil.deserialize(
redisCommand.getHashKV(RedisKey.getAgentUserInServHashKey(), userId)));
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserEndHashKey(), userId)) {
// 已经结束
return Optional.ofNullable((AgentUser) SerializeUtil.deserialize(
redisCommand.getHashKV(RedisKey.getAgentUserEndHashKey(), userId)));
} else {
// 缓存中没有找到继续到数据库查找
return agentUserRes.findOneByUserid(userId);
}
}
/**
* 返回排队中的客服列表
*
* @return
*/
public Map<String, AgentUser> getAgentUsersInQue() {
Map<String, String> agentUsers = redisCommand.getHash(RedisKey.getAgentUserInQueHashKey());
Map<String, AgentUser> map = new HashMap<>();
for (final Map.Entry<String, String> entry : agentUsers.entrySet()) {
final AgentUser obj = SerializeUtil.deserialize(entry.getValue());
map.put(obj.getId(), obj);
}
return map;
}
/**
* 将访客ID从服务中队列中删除
* TODO 将访客对应的客服的服务列表变更
*
* @param userid
*/
public void deleteAgentUserInservByAgentUserId(final String userid) {
redisCommand.delHashKV(RedisKey.getAgentUserInServHashKey(), userid);
}
/**
* 将访客ID从排队队列中删除
*
* @param userid
*/
public void deleteAgentUserInqueByAgentUserId(final String userid) {
redisCommand.delHashKV(RedisKey.getAgentUserInQueHashKey(), userid);
}
/**
* 获得一个坐席的状态
*
* @param agentno 坐席ID
* @return
*/
public AgentStatus findOneAgentStatusByAgentno(final String agentno) {
String status = getAgentStatusStatus(agentno);
logger.debug("[findOneAgentStatusByAgentnoAndOrig] agentno {}, status {}", agentno, status);
// 缓存中没有该坐席状态该坐席目前是离线的
if (StringUtils.equals(status, MainContext.AgentStatusEnum.OFFLINE.toString())) {
return null;
}
String val = redisCommand.getHashKV(RedisKey.getAgentStatusHashKeyByStatusStr(status), agentno);
AgentStatus result = SerializeUtil.deserialize(val);
logger.debug("[findOneAgentStatusByAgentnoAndOrig] result: username {}", result.getUsername());
return result;
}
/**
* 更新坐席状态
*
* @param agentStatus
*/
public void putAgentStatus(AgentStatus agentStatus) {
String pre = getAgentStatusStatus(agentStatus.getAgentno()); // 坐席前状态
if (StringUtils.equals(pre, MainContext.AgentStatusEnum.OFFLINE.toString())) {
// 之前不存在新建缓存
if ((!StringUtils.equals(agentStatus.getStatus(), MainContext.AgentStatusEnum.OFFLINE.toString()))) {
redisCommand.setHashKV(
RedisKey.getAgentStatusHashKeyByStatusStr(agentStatus.getStatus()),
agentStatus.getAgentno(), SerializeUtil.serialize(agentStatus));
}
return;
} else {
// 之前存在与将要更新的状态一致
if (StringUtils.equals(pre, agentStatus.getStatus())) {
redisCommand.setHashKV(
RedisKey.getAgentStatusHashKeyByStatusStr(pre), agentStatus.getAgentno(),
SerializeUtil.serialize(agentStatus));
return;
} else {
// 之前存在而且与新状态不一致
redisCommand.delHashKV(RedisKey.getAgentStatusHashKeyByStatusStr(pre), agentStatus.getAgentno());
if (!StringUtils.equals(agentStatus.getStatus(), MainContext.AgentStatusEnum.OFFLINE.toString())) {
redisCommand.setHashKV(
RedisKey.getAgentStatusHashKeyByStatusStr(agentStatus.getStatus()),
agentStatus.getAgentno(), SerializeUtil.serialize(agentStatus));
}
}
}
}
/**
* 获得一个租户的就绪坐席状态
*
* @return
*/
public Map<String, AgentStatus> findAllReadyAgentStatus() {
List<String> keys = new ArrayList<>();
keys.add(RedisKey.getAgentStatusReadyHashKey());
Map<String, String> map = redisCommand.getAllMembersInMultiHash(keys);
return convertFromStringToAgentStatus(map);
}
/**
* 获得一个租户的所有坐席状态
*
* @return
*/
public Map<String, AgentStatus> findAllAgentStatus() {
List<String> keys = new ArrayList<>();
// TODO 增加支持更多状态
keys.add(RedisKey.getAgentStatusReadyHashKey());
keys.add(RedisKey.getAgentStatusNotReadyHashKey());
Map<String, String> map = redisCommand.getAllMembersInMultiHash(keys);
return convertFromStringToAgentStatus(map);
}
/**
* Inline方法
*/
private static Map<String, AgentStatus> convertFromStringToAgentStatus(final Map<String, String> map) {
Map<String, AgentStatus> result = new HashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
AgentStatus obj = SerializeUtil.deserialize(entry.getValue());
result.put(entry.getKey(), obj);
}
return result;
}
/**
* Delete Agent Status
*
* @param agentno
*/
public void deleteAgentStatusByAgentno(final String agentno) {
String status = getAgentStatusStatus(agentno);
if (!StringUtils.equals(MainContext.AgentStatusEnum.OFFLINE.toString(), status)) {
redisCommand.delHashKV(RedisKey.getAgentStatusHashKeyByStatusStr(status), agentno);
}
}
/**
* 获得一个坐席的状态 agentStatus.status
* 只返回大类状态
*
* @param agentno
* @return
*/
private String getAgentStatusStatus(final String agentno) {
// 首先判断这个坐席的状态是READY还是BUSY再去更新
if (redisCommand.hasHashKV(RedisKey.getAgentStatusReadyHashKey(), agentno)) {
return MainContext.AgentStatusEnum.READY.toString();
} else if (redisCommand.hasHashKV(RedisKey.getAgentStatusNotReadyHashKey(), agentno)) {
return MainContext.AgentStatusEnum.NOTREADY.toString();
} else {
return MainContext.AgentStatusEnum.OFFLINE.toString();
}
}
/**
* 获得技能组的坐席状态
*
* @param skill
* @return
*/
public List<AgentStatus> getAgentStatusBySkill(final String skill) {
Map<String, AgentStatus> map = findAllAgentStatus();
List<AgentStatus> agentList = new ArrayList<>();
for (Map.Entry<String, AgentStatus> entry : map.entrySet()) {
if (StringUtils.isNotBlank(skill)) {
if (entry.getValue().getSkills() != null &&
entry.getValue().getSkills().containsKey(skill)) {
agentList.add(entry.getValue());
continue;
}
} else {
agentList.add(entry.getValue());
}
}
return agentList;
}
/**
* 获得指定租户的就绪的坐席个数
*
* @return
*/
public int getAgentStatusReadySize() {
return Math.toIntExact(redisCommand.getHashSize(RedisKey.getAgentStatusReadyHashKey()));
}
/**************************
* AgentUser相关
**************************/
/**
* 更新坐席访客关联关系
* TODO 更新坐席的访客列表信息增加新的访客信息
* 包括从等待到服务中从等待服务中到删除
* 但是此处并不包含"转接"访客给其它坐席的情况其它坐席的关联此处会完成
* 但是之前那个关联坐席的信息需要删除要另行维护
*
* @param agentUser 最新的agentUser的状态
*/
@AgentUserAspect.LinkAgentUser
public void putAgentUser(AgentUser agentUser) {
if (redisCommand.hasHashKV(RedisKey.getAgentUserInServHashKey(), agentUser.getUserid())) {
// 服务中
if (!StringUtils.equals(
agentUser.getStatus(),
MainContext.AgentUserStatusEnum.INSERVICE.toString())) {
// 删除旧记录
redisCommand.delHashKV(RedisKey.getAgentUserInServHashKey(), agentUser.getUserid());
}
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserInQueHashKey(), agentUser.getUserid())) {
// 等待服务
if (!StringUtils.equals(
agentUser.getStatus(),
MainContext.AgentUserStatusEnum.INQUENE.toString())) {
// 删除旧记录
redisCommand.delHashKV(RedisKey.getAgentUserInQueHashKey(), agentUser.getUserid());
}
}
// 更新新记录忽略状态为END的agentUser已结束的服务不加入缓存
if (!StringUtils.equals(agentUser.getStatus(), MainContext.AgentUserStatusEnum.END.toString())) {
redisCommand.setHashKV(
RedisKey.getAgentUserHashKeyByStatusStr(agentUser.getStatus()), agentUser.getUserid(),
SerializeUtil.serialize(agentUser));
}
}
/**
* 获得一个客服服务中的访客列表
*
* @param agentno
* @return
*/
public List<AgentUser> findInservAgentUsersByAgentno(final String agentno) {
logger.info("[findInservAgentUsersByAgentno] agentno {}", agentno);
List<AgentUser> result = new ArrayList<>();
List<String> ids = redisCommand.getSet(RedisKey.getInServAgentUsersByAgentno(agentno));
if (ids.size() == 0) { // no inserv agentUser
return result;
} else {
result = agentUserRes.findAllByUserids(ids);
}
return result;
}
/**
* 获得一个坐席服务中的访客数量
*
* @param agentno
* @return
*/
public int getInservAgentUsersSizeByAgentno(final String agentno) {
return Math.toIntExact(redisCommand.getSetSize(RedisKey.getInServAgentUsersByAgentno(agentno)));
}
/**
* 获得服务中的访客的数量
*
* @return
*/
public int getInservAgentUsersSize() {
return redisCommand.getHashSize(RedisKey.getAgentUserInServHashKey());
}
/**
* 获得等待中的访客的数量
*
* @return
*/
public int getInqueAgentUsersSize() {
return redisCommand.getHashSize(RedisKey.getAgentUserInQueHashKey());
}
/**
* Delete agentUser
*
* @param agentUser
*/
@AgentUserAspect.LinkAgentUser
public void deleteAgentUserByUserId(final AgentUser agentUser) {
if (redisCommand.hasHashKV(RedisKey.getAgentUserInQueHashKey(), agentUser.getUserid())) {
// 排队等待中
redisCommand.delHashKV(RedisKey.getAgentUserInQueHashKey(), agentUser.getUserid());
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserInServHashKey(), agentUser.getUserid())) {
redisCommand.delHashKV(RedisKey.getAgentUserInServHashKey(), agentUser.getUserid());
} else if (redisCommand.hasHashKV(RedisKey.getAgentUserEndHashKey(), agentUser.getUserid())) {
redisCommand.delHashKV(RedisKey.getAgentUserEndHashKey(), agentUser.getUserid());
} else {
// TODO 考虑是否有其他状态保存
}
}
/***************************
* CousultInvite 相关
***************************/
public void putConsultInvite(final CousultInvite cousultInvite) {
redisCommand.setHashKV(
RedisKey.getConsultInvites(), cousultInvite.getSnsaccountid(),
SerializeUtil.serialize(cousultInvite));
}
public CousultInvite findOneConsultInviteBySnsid(final String snsid) {
String serialized = redisCommand.getHashKV(RedisKey.getConsultInvites(), snsid);
if (StringUtils.isBlank(serialized)) {
return null;
} else {
return (CousultInvite) SerializeUtil.deserialize(serialized);
}
}
public void deleteConsultInviteBySnsid(final String snsid) {
redisCommand.delHashKV(RedisKey.getConsultInvites(), snsid);
}
/****************************
* OnlineUser相关
****************************/
/**
* 更新 onlineUser
*
* @param passportWebIMUser
*/
public void putOnlineUser(final PassportWebIMUser passportWebIMUser) {
// 此处onlineUser的id onlineUser userId相同
redisCommand.setHashKV(
RedisKey.getOnlineUserHashKey(), passportWebIMUser.getId(), SerializeUtil.serialize(passportWebIMUser));
}
/**
* 获得 onlineUser
*
* @param id
* @return
*/
public PassportWebIMUser findOneOnlineUserByUserId(final String id) {
String serialized = redisCommand.getHashKV(RedisKey.getOnlineUserHashKey(), id);
if (StringUtils.isBlank(serialized)) {
// query with MySQL
return onlineUserRes.findOneByUserid(id);
} else {
return convertFromStringToOnlineUser(serialized);
}
}
private static PassportWebIMUser convertFromStringToOnlineUser(final String serialized) {
PassportWebIMUser obj = SerializeUtil.deserialize(serialized);
return obj;
}
/**
* 删除 onlineUser
*
* @param id
*/
public void deleteOnlineUserById(final String id) {
redisCommand.delHashKV(RedisKey.getOnlineUserHashKey(), id);
}
/**
* 根据租户ID获得在线访客的列表大小
*/
public int getOnlineUserSize() {
return redisCommand.getHashSize(RedisKey.getOnlineUserHashKey());
}
/**
* 将在线访客从一个坐席的服务列表中删除
*
* @param userid
* @param agentno
*/
public void deleteOnlineUserIdFromAgentStatusByUseridAndAgentno(final String userid, final String agentno) {
redisCommand.removeSetVal(RedisKey.getInServAgentUsersByAgentno(agentno), userid);
}
private Map<String, PassportWebIMUser> convertFromStringToOnlineUsers(final Map<String, String> map) {
Map<String, PassportWebIMUser> result = new HashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
PassportWebIMUser x = SerializeUtil.deserialize(entry.getValue());
result.put(entry.getKey(), x);
}
return result;
}
/******************************
* Callcenter Agent 相关
******************************/
/**
* 更新CallCenterAgent
*
* @param id
* @param agent
*/
public void putCallCenterAgentById(final String id, final CallCenterAgent agent) {
redisCommand.setHashKV(RedisKey.getCallCenterAgentHashKey(), id, SerializeUtil.serialize(agent));
}
/**
* 根据ID和租户ID获得CallCenterAgent
*
* @param id
* @return
*/
public CallCenterAgent findOneCallCenterAgentById(final String id) {
String serialized = redisCommand.getHashKV(RedisKey.getCallCenterAgentHashKey(), id);
if (StringUtils.isNotBlank(serialized)) {
return (CallCenterAgent) SerializeUtil.deserialize(serialized);
} else {
return null;
}
}
/**
* 删除CallCenterAgent
*
* @param id
*/
public void deleteCallCenterAgentById(final String id) {
redisCommand.delHashKV(RedisKey.getCallCenterAgentHashKey(), id);
}
/**
* 根据租户ID获得所有的CallCenterAgent
*
* @return
*/
public Map<String, CallCenterAgent> findAllCallCenterAgents() {
Map<String, String> map = redisCommand.getHash(RedisKey.getCallCenterAgentHashKey());
Map<String, CallCenterAgent> result = new HashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
result.put(entry.getKey(), SerializeUtil.deserialize(entry.getValue()));
}
return result;
}
/**
* 访客黑名单
*/
// 将访客放在租户的黑名单中
public void putBlackEntity(final BlackEntity blackEntity) {
redisCommand.setHashKV(
RedisKey.getBlackEntityKey(), blackEntity.getUserid(), SerializeUtil.serialize(blackEntity));
}
// 通过指定的访客和租户查找黑名单
public Optional<BlackEntity> findOneBlackEntityByUserId(final String userid) {
String ser = redisCommand.getHashKV(RedisKey.getBlackEntityKey(), userid);
if (StringUtils.isBlank(ser)) {
return Optional.empty();
}
return Optional.ofNullable(SerializeUtil.deserialize(ser));
}
// 将一个访客从黑名单中移除
public void deleteBlackEntityByUserId(final String userid) {
redisCommand.delHashKV(RedisKey.getBlackEntityKey(), userid);
}
// 指定的访客是否在租户的黑名单中
public boolean existBlackEntityByUserId(final String userid) {
return redisCommand.hasHashKV(RedisKey.getBlackEntityKey(), userid);
}
// 根据租户ID获得所有访客的黑名单
public Map<String, BlackEntity> findAllBlackEntity() {
Map<String, BlackEntity> result = new HashMap<>();
for (Map.Entry<String, String> entry : redisCommand.getHash(
RedisKey.getBlackEntityKey()).entrySet()) {
result.put(entry.getKey(), SerializeUtil.deserialize(entry.getValue()));
}
return result;
}
/*****************************
* Job 相关
*****************************/
public void putJobById(final String jobId, final JobDetail job) {
redisCommand.setHashKV(RedisKey.getJobHashKey(), jobId, SerializeUtil.serialize(job));
}
public JobDetail findOneJobById(final String jobId) {
String serialized = redisCommand.getHashKV(RedisKey.getJobHashKey(), jobId);
if (StringUtils.isNotBlank(serialized)) {
return (JobDetail) SerializeUtil.deserialize(serialized);
}
return null;
}
public boolean existJobById(final String jobId) {
return redisCommand.hasHashKV(RedisKey.getJobHashKey(), jobId);
}
public void deleteJobByJobId(final String jobId) {
redisCommand.delHashKV(RedisKey.getJobHashKey(), jobId);
}
/**
* 系统词典相关
*/
// 存储根词典
public void putSysDic(final String id, final SysDic sysDic) {
redisCommand.setHashKV(RedisKey.getSysDicHashKey(), id, SerializeUtil.serialize(sysDic));
}
// 将指定租户的系统词典清空
public void eraseSysDic() {
redisCommand.delete(RedisKey.getSysDicHashKey());
}
// 存储词典子项
public void putSysDic(final String code, final List<SysDic> sysDics) {
redisCommand.setHashKV(RedisKey.getSysDicHashKey(), code, SerializeUtil.serialize(sysDics));
}
// 获得词典的子项列表
public List<SysDic> getSysDicItemsByCode(final String code) {
String serialized = redisCommand.getHashKV(RedisKey.getSysDicHashKey(), code);
if (serialized != null) {
return (List<SysDic>) SerializeUtil.deserialize(serialized);
}
return null;
}
// 获得词典子项
public SysDic findOneSysDicByCode(final String code) {
String serialized = redisCommand.getHashKV(RedisKey.getSysDicHashKey(), code);
if (StringUtils.isBlank(serialized)) {
return null;
}
return (SysDic) SerializeUtil.deserialize(serialized);
}
// 获得词典
public SysDic findOneSysDicById(final String id) {
String serialized = redisCommand.getHashKV(RedisKey.getSysDicHashKey(), id);
if (StringUtils.isBlank(serialized)) {
return null;
}
return (SysDic) SerializeUtil.deserialize(serialized);
}
// 批量存储
public void putSysDic(List<SysDic> vals) {
Map<String, String> map = new HashMap<>();
for (final SysDic dic : vals) {
map.put(dic.getId(), SerializeUtil.serialize(dic));
}
redisCommand.hmset(RedisKey.getSysDicHashKey(), map);
}
public void deleteSysDicById(final String id) {
redisCommand.delHashKV(RedisKey.getSysDicHashKey(), id);
}
public boolean existSysDicById(final String id) {
return redisCommand.hasHashKV(RedisKey.getSysDicHashKey(), id);
}
/**
* System 相关
*/
public <T extends Serializable> void putSystemById(final String id, final T obj) {
redisCommand.setHashKV(RedisKey.getSystemHashKey(), id, SerializeUtil.serialize(obj));
}
public <T extends Serializable> void putSystemListById(final String id, final List<T> obj) {
redisCommand.setHashKV(RedisKey.getSystemHashKey(), id, SerializeUtil.serialize(obj));
}
public <TK, TV extends Serializable> void putSystemMapById(final String id, final Map<TK, TV> obj) {
redisCommand.setHashKV(RedisKey.getSystemHashKey(), id, SerializeUtil.serialize(obj));
}
public boolean existSystemById(final String id) {
return redisCommand.hasHashKV(RedisKey.getSystemHashKey(), id);
}
public void deleteSystembyId(final String id) {
redisCommand.delHashKV(RedisKey.getSystemHashKey(), id);
}
public <T extends Serializable> T findOneSystemById(final String id) {
String serialized = redisCommand.getHashKV(RedisKey.getSystemHashKey(), id);
if (StringUtils.isNotBlank(serialized)) {
return (T) SerializeUtil.deserialize(serialized);
}
return null;
}
public <T extends Serializable> List<T> findOneSystemListById(final String id) {
String serialized = redisCommand.getHashKV(RedisKey.getSystemHashKey(), id);
if (StringUtils.isNotBlank(serialized)) {
return (List<T>) SerializeUtil.deserialize(serialized);
}
return null;
}
public <TK, TV extends Serializable> Map<TK, TV> findOneSystemMapById(final String id) {
String serialized = redisCommand.getHashKV(RedisKey.getSystemHashKey(), id);
if (StringUtils.isNotBlank(serialized)) {
return (Map<TK, TV>) SerializeUtil.deserialize(serialized);
}
return null;
}
// 获得系统cache的列表大小
public int getSystemSize() {
return redisCommand.getHashSize(RedisKey.getSystemHashKey());
}
/**************************
* Session Config 相关
**************************/
public void putSessionConfig(final SessionConfig sessionConfig, String organid) {
redisCommand.put(RedisKey.getSessionConfig(organid), SerializeUtil.serialize(sessionConfig));
}
public SessionConfig findOneSessionConfig(String organid) {
String serialized = redisCommand.get(RedisKey.getSessionConfig(organid));
if (StringUtils.isNotBlank(serialized)) {
return (SessionConfig) SerializeUtil.deserialize(serialized);
}
return null;
}
public void deleteSessionConfig(String organid) {
redisCommand.delete(RedisKey.getSessionConfig(organid));
}
public boolean existSessionConfig(String organid) {
return redisCommand.exists(RedisKey.getSessionConfig(organid));
}
public void putSessionConfigList(final List<SessionConfig> lis) {
redisCommand.put(RedisKey.getSessionConfigList(), SerializeUtil.serialize(lis));
}
public List<SessionConfig> findOneSessionConfigList() {
String serialized = redisCommand.get(RedisKey.getSessionConfigList());
if (StringUtils.isNotBlank(serialized)) {
return (List<SessionConfig>) SerializeUtil.deserialize(serialized);
}
return null;
}
public void deleteSessionConfigList() {
redisCommand.delete(RedisKey.getSessionConfigList());
}
public boolean existSessionConfigList() {
return redisCommand.exists(RedisKey.getSessionConfigList());
}
/******************************************
* Customer Chats Audit 相关
******************************************/
public void putAgentUserAudit(final AgentUserAudit audit) throws CSKefuCacheException {
if (StringUtils.isBlank(audit.getAgentUserId())) {
throw new CSKefuCacheException("agentUserId is required.");
}
redisCommand.setHashKV(
RedisKey.getCustomerChatsAuditKey(), audit.getAgentUserId(), SerializeUtil.serialize(audit));
}
public void deleteAgentUserAuditById(final String agentUserId) {
redisCommand.delHashKV(RedisKey.getCustomerChatsAuditKey(), agentUserId);
}
public Optional<AgentUserAudit> findOneAgentUserAuditById(final String agentUserId) {
logger.info("[findOneAgentUserAuditById] agentUserId {}", agentUserId);
String serialized = redisCommand.getHashKV(RedisKey.getCustomerChatsAuditKey(), agentUserId);
if (StringUtils.isBlank(serialized)) {
return Optional.empty();
}
return Optional.ofNullable((AgentUserAudit) SerializeUtil.deserialize(serialized));
}
public boolean existAgentUserAuditById(final String agentUserId) {
return redisCommand.hasHashKV(RedisKey.getCustomerChatsAuditKey(), agentUserId);
}
/******************************************
* User Session 相关
******************************************/
/**
* 存入user的session存储这组信息是为了让客户的账号只能在一个浏览器内登录使用
* 如果一个用户账号在多个浏览器使用则登出之前的登录只保留最后一个登录正常使用
*
* @param agentno
* @param sessionId
*/
public void putUserSessionByAgentnoAndSessionId(final String agentno, final String sessionId) {
redisCommand.setHashKV(RedisKey.getUserSessionKey(), agentno, sessionId);
}
public boolean existUserSessionByAgentno(final String agentno) {
return redisCommand.hasHashKV(RedisKey.getUserSessionKey(), agentno);
}
public String findOneSessionIdByAgentno(final String agentno) {
return redisCommand.getHashKV(RedisKey.getUserSessionKey(), agentno);
}
public void deleteUserSessionByAgentno(final String agentno) {
redisCommand.delHashKV(RedisKey.getUserSessionKey(), agentno);
}
}

View File

@ -1,304 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import jakarta.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public class RedisCommand {
final static private Logger logger = LoggerFactory.getLogger(RedisCommand.class);
private ListOperations<String, String> redisListOps;
private HashOperations<String, String, String> redisHashOps;
private ValueOperations<String, String> redisValOps;
private SetOperations<String, String> redisSetOps;
/**
* 使用StringRedisTemplate而不是RedisTemplate解决序列化问题
* https://stackoverflow.com/questions/13215024/weird-redis-key-with-spring-data-jedis
*/
@Autowired
private StringRedisTemplate redis;
@PostConstruct
private void init() {
redisListOps = redis.opsForList();
redisHashOps = redis.opsForHash();
redisValOps = redis.opsForValue();
redisSetOps = redis.opsForSet();
}
/*****************************
* String 相关
*****************************/
/**
* 设置一个KEY
*
* @param key
* @param serialized
*/
public void put(final String key, final String serialized) {
boolean result = true;
redisValOps.set(key, serialized);
}
public String get(final String key) {
return redisValOps.get(key);
}
/**
* 删除一个KEY
*
* @param key
*/
public void delete(final String key) {
redis.delete(key);
}
/**
* 设置一个KEY的过期时间
*
* @param key
* @param seconds
*/
public void expire(final String key, final long seconds) {
redis.expire(key, seconds, TimeUnit.SECONDS);
}
/**
* 获得一个KEY的过期时间
*
* @param key
* @return
*/
public long ttl(final String key) {
return redis.getExpire(key);
}
/**
* 是否存在一个KEY
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redis.hasKey(key);
}
/*****************************
* List 相关
*****************************/
/**
* 在列表右侧追加
*
* @param key
* @param val
* @return
*/
public long appendList(final String key, final String val) {
return redisListOps.rightPush(key, val);
}
/**
* 获得一个列表的所有元素
*
* @param key
* @return
*/
public List<String> getList(final String key) {
return redisListOps.range(key, 0, redisListOps.size(key));
}
/**
* 获得一个类标的长度
*
* @param key
* @return
*/
public long listSize(final String key) {
return redisListOps.size(key);
}
/**
* Remove all value = val in key
*
* @param key
* @param val
*/
public void listRemove(final String key, final String val) {
redisListOps.remove(key, 0, val);
}
/*****************************
* Hash 相关
*****************************/
/**
* 获得一个Hash的列表长度
*
* @param hashKey
* @return
*/
public int getHashSize(final String hashKey) {
return Math.toIntExact(redisHashOps.size(hashKey));
}
/**
* 获得多个Hash的全部成员
*
* @param keys
* @return
*/
public Map<String, String> getAllMembersInMultiHash(final List<String> keys) {
return redis.execute((RedisCallback<Map<String, String>>) con -> {
Map<String, String> ans = new HashMap<>();
for (String key : keys) {
Map<byte[], byte[]> result = con.hGetAll(key.getBytes());
if (!CollectionUtils.isEmpty(result)) {
for (Map.Entry<byte[], byte[]> entry : result.entrySet()) {
ans.put(new String(entry.getKey()), new String(entry.getValue()));
}
}
}
return ans;
});
}
/**
* 获得一个Hash中所有的值
* https://juejin.im/post/5c1399a7f265da61764ac526
*
* @param hashKey
* @return
*/
public Map<String, String> getHash(final String hashKey) {
return redis.execute((RedisCallback<Map<String, String>>) con -> {
Map<byte[], byte[]> result = con.hGetAll(hashKey.getBytes());
if (CollectionUtils.isEmpty(result)) {
return new HashMap<>(0);
}
Map<String, String> ans = new HashMap<>(result.size());
for (Map.Entry<byte[], byte[]> entry : result.entrySet()) {
ans.put(new String(entry.getKey()), new String(entry.getValue()));
}
return ans;
});
}
/**
* 设置Hash Map KV
*
* @param hashKey
* @param childKey
* @param childVal
*/
public void setHashKV(final String hashKey, final String childKey, final String childVal) {
redisHashOps.put(hashKey, childKey, childVal);
}
/**
* 指定的Hash实存存在键
*
* @param key
* @param childKey
* @return
*/
public boolean hasHashKV(String key, String childKey) {
return redisHashOps.hasKey(key, childKey);
}
/**
* 获得Hash Map KV的值
*
* @param hashKey
* @param childKey
* @return
*/
public String getHashKV(final String hashKey, final String childKey) {
return redisHashOps.get(hashKey, childKey);
}
/**
* 删除Hash Map KV的值
*
* @param hashKey
* @param childKey
*/
public void delHashKV(final String hashKey, final String childKey) {
redisHashOps.delete(hashKey, childKey);
}
/**
* HashSet
* https://www.cnblogs.com/hongdada/p/9141125.html
* 寻求使用 hmset 提升大量HASH KEY的存储 https://redis.io/commands/hmset
* TODO 查看 putAll源代码确定是用hmset
*
* @param key
* @param map 对应多个键值
*/
public void hmset(final String key, final Map<String, String> map) {
try {
redisHashOps.putAll(key, map);
} catch (Exception e) {
logger.error("hmset bad things happen", e);
}
}
/*****************************
* Set 相关
*****************************/
public void insertSetVal(final String key, final String val) {
redisSetOps.add(key, val);
}
public void removeSetVal(final String key, final String val) {
redisSetOps.remove(key, val);
}
public int getSetSize(final String key) {
return Math.toIntExact(redisSetOps.size(key));
}
public List<String> getSet(final String key) {
Set<String> s = redisSetOps.members(key);
if (CollectionUtils.isEmpty(s)) {
return new ArrayList<>();
}
return new ArrayList<>(s);
}
}

View File

@ -1,258 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.cache;
import com.cskefu.cc.basic.MainContext;
public class RedisKey {
public static final String CACHE_SESSIONS = "sso";
/*********************
*
* 以下为Redis的常用KEY管理
*
*********************/
// AGENT STATUS 相关
/**
* 获得坐席列表指定字符串状态的KEY
*
* @return
*/
public static String getAgentStatusHashKeyByStatusStr(final String status) {
StringBuffer sb = new StringBuffer();
sb.append("agent:status:");
sb.append(status);
return sb.toString();
}
/**
* 就绪的客服列表KEY
*
* @return
*/
public static String getAgentStatusReadyHashKey() {
return getAgentStatusHashKeyByStatusStr(MainContext.AgentStatusEnum.READY.toString());
}
/**
* 未就绪的坐席
*
* @return
*/
public static String getAgentStatusNotReadyHashKey() {
return getAgentStatusHashKeyByStatusStr(MainContext.AgentStatusEnum.NOTREADY.toString());
}
// AGENT USER 相关
/**
* 获得坐席访客关联列表指定字符串状态的KEY
*
* @param status
* @return
*/
public static String getAgentUserHashKeyByStatusStr(final String status) {
StringBuffer sb = new StringBuffer();
sb.append("agent:user:");
sb.append(status);
return sb.toString();
}
/**
* 排队中的访客KEY
*
* @return
*/
public static String getAgentUserInQueHashKey() {
return getAgentUserHashKeyByStatusStr(MainContext.AgentUserStatusEnum.INQUENE.toString());
}
/**
* 服务中的访客
*
* @return
*/
public static String getAgentUserInServHashKey() {
return getAgentUserHashKeyByStatusStr(MainContext.AgentUserStatusEnum.INSERVICE.toString());
}
/**
* 结束服务的访客
*
* @return
*/
public static String getAgentUserEndHashKey() {
return getAgentUserHashKeyByStatusStr(MainContext.AgentUserStatusEnum.END.toString());
}
/**
* 获得一个坐席的服务中的访客列表KEY
*/
public static String getInServAgentUsersByAgentno(final String agentno) {
StringBuffer sb = new StringBuffer();
sb.append("agent:");
sb.append(agentno);
sb.append(":inserv");
return sb.toString();
}
// Customer Chats Audit
/**
* 存储AgentUser监控信息的存储Hash的KEY
*
* @return
*/
public static String getCustomerChatsAuditKey() {
StringBuffer sb = new StringBuffer();
sb.append("audit:customerchats");
return sb.toString();
}
// ONLINE USER 相关
/**
* 获得在线访客列表
*
* @return
*/
public static String getOnlineUserHashKey() {
StringBuffer sb = new StringBuffer();
sb.append("visitor:online");
return sb.toString();
}
// LOGIN USER 相关
/**
* 已经登录的系统用户的API Auth Token
* 包括管理员坐席等访客不在该列
* 在该列表中的用户代表在线的系统用户通过浏览器或API访问了系统
*
* @return
*/
public static String getApiTokenBearerKeyWithValue(final String token) {
StringBuffer sb = new StringBuffer();
sb.append("api:token:bearer:");
sb.append(token);
return sb.toString();
}
/**
* CallCenter Agent 相关
*
* @return
*/
public static String getCallCenterAgentHashKey() {
StringBuffer sb = new StringBuffer();
sb.append("callcenter:agent");
return sb.toString();
}
/**
* Job 相关
*
* @return
*/
public static String getJobHashKey() {
StringBuffer sb = new StringBuffer();
sb.append("job");
return sb.toString();
}
/**
* System 相关
*
* @return
*/
public static String getSystemHashKey() {
StringBuffer sb = new StringBuffer();
sb.append("system");
return sb.toString();
}
/**
* 系统词典
*
* @return
*/
public static String getSysDicHashKey() {
StringBuffer sb = new StringBuffer();
sb.append("sysdic");
return sb.toString();
}
/**
* 坐席会话配置相关
*
* @return
*/
public static String getSessionConfigList() {
StringBuffer sb = new StringBuffer();
sb.append("session:config:list");
return sb.toString();
}
public static String getSessionConfig(String organid) {
StringBuffer sb = new StringBuffer();
sb.append(organid);
sb.append(":session:config");
return sb.toString();
}
/**
* SocketIO连接相关
*/
public static String getWebIMAgentSocketIOByAgentno(final String agentno) {
StringBuffer sb = new StringBuffer();
sb.append("agent:socketio:");
sb.append(agentno);
return sb.toString();
}
/**
* CousultInvite 相关
*/
public static String getConsultInvites() {
StringBuffer sb = new StringBuffer();
sb.append("consultinvite");
return sb.toString();
}
/**
* 和访客黑名单相关
*/
public static String getBlackEntityKey() {
StringBuffer sb = new StringBuffer();
sb.append("visitor:blacklist");
return sb.toString();
}
/**
* 系统登录用户的会话Session信息
*/
public static String getUserSessionKey() {
StringBuffer sb = new StringBuffer();
sb.append("user:session");
return sb.toString();
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, All rights reserved.
* <https://www.chatopera.com>
*/
package com.cskefu.cc.config;
import jakarta.jms.ConnectionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
@EnableJms
@Configuration
public class ActiveMQConfigure {
// topic模式的ListenerContainer
@Bean
@SuppressWarnings("SpringJavaAutowiringInspection")
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
// queue模式的ListenerContainer
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerQueue(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setConnectionFactory(connectionFactory);
return bean;
}
}

View File

@ -1,127 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.auth.BearerTokenMgr;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.util.matcher.RequestMatcher;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import static com.cskefu.cc.basic.Constants.AUTH_TOKEN_TYPE_BASIC;
import static com.cskefu.cc.basic.Constants.AUTH_TOKEN_TYPE_BEARER;
public class ApiRequestMatchingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ApiRequestMatchingFilter.class);
private final RequestMatcher[] ignoredRequests;
private static BearerTokenMgr bearerTokenMgr;
public ApiRequestMatchingFilter(RequestMatcher... matcher) {
this.ignoredRequests = matcher;
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String method = request.getMethod();
if (StringUtils.isNotBlank(method) && method.equalsIgnoreCase("options")) {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,accept,authorization,content-type");
response.setHeader("X-Frame-Options", "SAMEORIGIN");
response.setStatus(HttpStatus.ACCEPTED.value());
} else {
boolean matchAnyRoles = false;
for (RequestMatcher anyRequest : ignoredRequests) {
if (anyRequest.matches(request)) {
matchAnyRoles = true;
}
}
if (matchAnyRoles) {
String authorization = request.getHeader("authorization");
if (StringUtils.isBlank(authorization)) {
authorization = request.getParameter("authorization");
}
if (StringUtils.isNotBlank(authorization)) {
// set the default value for backward compatibility as bear token bare metal
String authorizationTrimed = authorization;
String authorizationTokenType = AUTH_TOKEN_TYPE_BEARER;
if (authorization.startsWith(String.format("%s ", AUTH_TOKEN_TYPE_BEARER))) {
authorizationTrimed = StringUtils.substring(authorization, 7);
authorizationTokenType = AUTH_TOKEN_TYPE_BEARER;
} else if (authorization.startsWith(String.format("%s ", AUTH_TOKEN_TYPE_BASIC))) {
authorizationTrimed = StringUtils.substring(authorization, 6);
authorizationTokenType = AUTH_TOKEN_TYPE_BASIC;
}
if (StringUtils.isNotBlank(authorizationTrimed)) {
switch (authorizationTokenType) {
case AUTH_TOKEN_TYPE_BEARER:
if (getBearerTokenMgr().existToken(authorizationTrimed)) {
chain.doFilter(req, resp);
} else {
response.sendRedirect("/auth/error");
}
break;
case AUTH_TOKEN_TYPE_BASIC:
// TODO
response.sendRedirect("/auth/error");
break;
default:
response.sendRedirect("/auth/error");
}
} else {
response.sendRedirect("/auth/error");
}
} else {
response.sendRedirect("/auth/error");
}
} else {
chain.doFilter(req, resp);
}
}
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
private static BearerTokenMgr getBearerTokenMgr() {
if (bearerTokenMgr == null) {
bearerTokenMgr = MainContext.getContext().getBean(BearerTokenMgr.class);
}
return bearerTokenMgr;
}
}

View File

@ -1,153 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.basic.plugins.IPluginConfigurer;
import com.cskefu.cc.basic.plugins.PluginRegistry;
import com.cskefu.cc.cache.Cache;
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 org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import java.util.*;
public class AppCtxRefreshEventListener implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger logger = LoggerFactory.getLogger(AppCtxRefreshEventListener.class);
private void setupSysdicCacheAndExtras(final ContextRefreshedEvent event, final String cacheSetupStrategy, final Cache cache, final SysDicRepository sysDicRes, final BlackListRepository blackListRes) {
if (!StringUtils.equalsIgnoreCase(cacheSetupStrategy, Constants.cache_setup_strategy_skip)) {
/**************************
* 加载系统到缓存
* 加载系统词典大约只需要5s左右
**************************/
// 首先将之前缓存清空此处使用系统的默认租户信息
cache.eraseSysDic();
List<SysDic> sysDicList = sysDicRes.findAll();
Map<String, List<SysDic>> rootDictItems = new HashMap<>(); // 关联根词典及其子项
Map<String, SysDic> rootDics = new HashMap<>();
Set<String> parents = new HashSet<>();
// 获得所有根词典
for (final SysDic dic : sysDicList) {
if (StringUtils.equals(dic.getParentid(), "0")) {
parents.add(dic.getId());
rootDics.put(dic.getId(), dic);
}
}
// 向根词典中添加子项
for (final SysDic dic : sysDicList) {
if ((!StringUtils.equals(dic.getParentid(), "0")) &&
parents.contains(dic.getDicid())) {
// 不是根词典并且包含在一个根词典内
if (!rootDictItems.containsKey(dic.getDicid())) {
rootDictItems.put(dic.getDicid(), new ArrayList<>());
}
rootDictItems.get(dic.getDicid()).add(dic);
}
}
// 更新缓存
// TODO 集群时注意!!!
// 此处为长时间的操作如果在一个集群中会操作共享内容非常不可靠
// 所以当前代码不支持集群需要解决启动上的这个问题
// 存储根词典 TODO 此处只考虑了系统默认租户
cache.putSysDic(new ArrayList<>(rootDics.values()));
for (final Map.Entry<String, List<SysDic>> entry : rootDictItems.entrySet()) {
SysDic rootDic = rootDics.get(entry.getKey());
// 打印根词典信息
logger.debug("[onApplicationEvent] root dict: {}, code {}, name {}, item size {}", entry.getKey(), rootDics.get(entry.getKey()).getCode(), rootDics.get(entry.getKey()).getName(), entry.getValue().size());
// 存储子项列表
cache.putSysDic(rootDic.getCode(), entry.getValue());
// 存储子项成员
cache.putSysDic(entry.getValue());
}
List<BlackEntity> blackList = blackListRes.findAll();
for (final BlackEntity black : blackList) {
if (StringUtils.isNotBlank(black.getUserid())) {
if (black.getEndtime() == null || black.getEndtime().after(new Date())) {
cache.putSystemById(black.getUserid(), black);
}
}
}
/**
* 加载系统全局配置
*/
SystemConfigRepository systemConfigRes = event.getApplicationContext().getBean(SystemConfigRepository.class);
List<SystemConfig> configs = systemConfigRes.findAll();
SystemConfig config = configs.size() > 0 ? configs.get(0) : null;
if (config != null) {
cache.putSystemById("systemConfig", config);
}
logger.warn("[StartedEventListener] setup Sysdicts in Redis done, strategy {}", cacheSetupStrategy);
} else {
logger.warn("[onApplicationEvent] skip initialize sysdicts.");
}
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (MainContext.getContext() == null) {
logger.info("[onApplicationEvent] set main context and initialize the Cache System.");
MainContext.setApplicationContext(event.getApplicationContext());
SysDicRepository sysDicRes = event.getApplicationContext().getBean(SysDicRepository.class);
BlackListRepository blackListRes = event.getApplicationContext().getBean(BlackListRepository.class);
Cache cache = event.getApplicationContext().getBean(Cache.class);
String cacheSetupStrategy = event.getApplicationContext().getEnvironment().getProperty("cache.setup.strategy");
setupSysdicCacheAndExtras(event, cacheSetupStrategy, cache, sysDicRes, blackListRes);
MainUtils.initSystemArea();
MainUtils.initSystemSecField(event.getApplicationContext().getBean(TablePropertiesRepository.class));
// MainUtils.initAdv();//初始化广告位
// 初始化插件
PluginRegistry pluginRegistry = MainContext.getContext().getBean(PluginRegistry.class);
for (final IPluginConfigurer p : pluginRegistry.getPlugins()) {
logger.info("[Plugins] registered plugin id {}, class {}", p.getPluginId(), p.getClass().getName());
}
} else {
logger.info("[onApplicationEvent] bypass, initialization has been done already.");
}
// Fix SQL init lazy load delay
if (MainContext.getContext() != null) {
UserRepository userRes = MainContext.getContext().getBean(UserRepository.class);
userRes.findByUsername("admin").ifPresent((p) -> {
logger.warn("[onApplicationEvent] inited JPA sql.");
});
}
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import com.cskefu.cc.proxy.UserProxy;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class ApplicationStartupListener implements ApplicationListener<ApplicationReadyEvent> {
final private static Logger logger = LoggerFactory.getLogger(ApplicationStartupListener.class);
@Value("${extras.auth.super-admin.pass}")
private String superAdminPass;
@Autowired
private UserProxy userProxy;
@Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
if (StringUtils.isNotBlank(superAdminPass)) {
logger.warn("Reset Superadmin Password by ENV variable EXTRAS_AUTH_SUPER_ADMIN_PASS=********");
if (!userProxy.resetAccountPasswordByUsername("admin", superAdminPass)) {
logger.error("Reset Superadmin Password failure. Check 1) admin user do exist in DB with username admin.");
}
}
return;
}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import com.lmax.disruptor.ExceptionHandler;
public class CSKeFuExceptionHandler implements ExceptionHandler<Object>{
@Override
public void handleEventException(Throwable ex, long arg1, Object arg2) {
ex.printStackTrace();
}
@Override
public void handleOnShutdownException(Throwable ex) {
}
@Override
public void handleOnStartException(Throwable ex) {
// TODO Auto-generated method stub
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import com.cskefu.cc.interceptor.*;
import com.cskefu.cc.util.SystemEnvHelper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
public class CSKeFuWebAppConfigurer implements WebMvcConfigurer {
private final static Logger logger = LoggerFactory.getLogger(CSKeFuWebAppConfigurer.class);
private final static String ENABLE_LOG_REQUEST = SystemEnvHelper.parseFromApplicationProps("extras.log.request");
/**
* https://www.baeldung.com/spring-cors
*
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// enables CORS requests from any origin to any endpoint in the application.
registry.addMapping("/**").allowedOrigins("*");
}
@Override
@SuppressWarnings("deprecation")
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(Boolean.TRUE);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多个拦截器组成一个拦截器链
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用户排除拦截
registry.addInterceptor(new UserExperiencePlanInterceptorHandler()).addPathPatterns("/**").excludePathPatterns("/im/**", "/res/image*", "/res/file*", "/cs/**", "/messenger/webhook/*");
registry.addInterceptor(new UserInterceptorHandler()).addPathPatterns("/**").excludePathPatterns("/login.html", "/im/**", "/res/image*", "/res/file*", "/cs/**", "/messenger/webhook/*");
registry.addInterceptor(new CrossInterceptorHandler()).addPathPatterns("/**");
if (StringUtils.equalsIgnoreCase(ENABLE_LOG_REQUEST, "on")) {
logger.warn("Logging request into DB as in ENV: ENABLE_LOG_REQUEST=on");
registry.addInterceptor(new RequestLogIntercreptorHandler()).addPathPatterns("/**");
} else {
logger.info("Disable Logging request into DB.");
}
registry.addInterceptor(new ViewsInterceptorHandler()).addPathPatterns("/**");
}
}

View File

@ -1,69 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.model.User;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.security.web.util.matcher.RequestMatcher;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class DelegateRequestMatchingFilter implements Filter {
private final RequestMatcher[] ignoredRequests;
public DelegateRequestMatchingFilter(RequestMatcher... matcher) {
this.ignoredRequests = matcher;
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
boolean matchAnyRoles = false;
for (RequestMatcher anyRequest : ignoredRequests) {
if (anyRequest.matches(request)) {
matchAnyRoles = true;
}
}
User user = (User) request.getSession().getAttribute(Constants.USER_SESSION_NAME);
if (matchAnyRoles) {
if (user != null && (user.isAdmin())) {
chain.doFilter(req, resp);
} else {
// 重定向到 无权限执行操作的页面
HttpServletResponse response = (HttpServletResponse) resp;
response.sendRedirect("/?msg=security");
}
} else {
try {
chain.doFilter(req, resp);
} catch (ClientAbortException ex) {
//Tomcat异常不做处理
}
}
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* 线程池 作业调度平台
* @author iceworld
*
*/
@Configuration
public class ExecutorConfig {
private static final int CORE_POOL_SIZE = 7;
private static final int MAX_POOL_SIZE = 100;
/**
* 作业平台使用的线程池
* @return
*/
@Bean(name = "webimTaskExecutor")
public ThreadPoolTaskExecutor common() {
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
// 线程池维护线程的最少数量
poolTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);
// 线程池维护线程的最大数量
poolTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
// 线程池所使用的缓冲队列
poolTaskExecutor.setQueueCapacity(200);
// 线程池维护线程所允许的空闲时间
poolTaskExecutor.setKeepAliveSeconds(30000);
poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
poolTaskExecutor.setThreadNamePrefix("cs-webim-task-");
return poolTaskExecutor;
}
@Bean(name = "scheduleTaskExecutor")
public ThreadPoolTaskScheduler schedule(){
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(CORE_POOL_SIZE);
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
taskScheduler.setThreadNamePrefix("cs-schedule-");
return taskScheduler;
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import com.cskefu.cc.exception.InstantMessagingExceptionListener;
import jakarta.annotation.PreDestroy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.server.Ssl;
import org.springframework.context.annotation.Bean;
import java.io.InputStream;
@org.springframework.context.annotation.Configuration
public class MessagingServerConfigure {
@Value("${uk.im.server.host}")
private String host;
@Value("${uk.im.server.port}")
private Integer port;
@Value("${cs.im.server.ssl.port}")
private Integer sslPort;
@Value("${uk.im.server.threads}")
private String threads;
private SocketIOServer server;
@Bean(name = "webimport")
public Integer getWebIMPort() {
if (sslPort != null) {
return sslPort;
} else {
return port;
}
}
@Autowired
private ServerProperties serverProperties;
@Bean
public SocketIOServer socketIOServer() {
Configuration config = new Configuration();
//解决对此重启服务时netty端口被占用问题
com.corundumstudio.socketio.SocketConfig tmpConfig = new com.corundumstudio.socketio.SocketConfig();
tmpConfig.setReuseAddress(true);
config.setSocketConfig(tmpConfig);
// config.setHostname(host);
config.setPort(port);
// config.getSocketConfig().setReuseAddress(true);
// config.setSocketConfig(new SocketConfig());
// config.setOrigin("*");
config.setExceptionListener(new InstantMessagingExceptionListener());
// config.setSSLProtocol("https");
int workThreads = StringUtils.isNotBlank(threads) && threads.matches("[\\d]{1,6}") ? Integer.parseInt(threads) : 100;
config.setWorkerThreads(workThreads);
// config.setStoreFactory(new HazelcastStoreFactory());
config.setAuthorizationListener(data -> true);
config.getSocketConfig().setReuseAddress(true);
config.getSocketConfig().setSoLinger(0);
config.getSocketConfig().setTcpNoDelay(true);
config.getSocketConfig().setTcpKeepAlive(true);
// ServerProperties serverProperties = applicationContext.getBean(ServerProperties.class);
Ssl ssl = serverProperties.getSsl();
if (ssl != null) {
String keyStore = ssl.getKeyStore();
String keyStorePassword = ssl.getKeyStorePassword();
if (StringUtils.isNotEmpty(keyStore) && StringUtils.isNotEmpty(keyStorePassword)) {
InputStream keyStoreStream = this.getClass().getResourceAsStream("/" + keyStore.trim().split(":")[1]);
config.setKeyStore(keyStoreStream);
config.setKeyStorePassword(keyStorePassword);
}
}
return server = new SocketIOServer(config);
}
@Bean
public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
return new SpringAnnotationScanner(socketServer);
}
@PreDestroy
public void destory() {
server.stop();
}
}

View File

@ -1,56 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.config;
import org.springframework.beans.factory.annotation.Value;
import de.neuland.pug4j.PugConfiguration;
import de.neuland.pug4j.spring.template.SpringTemplateLoader;
import de.neuland.pug4j.spring.view.PugViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
@Configuration
public class PugConfig {
@Value("${spring.pug4j.cache}")
private Boolean pug4jCache;
@Value("${spring.pug4j.template-loader-path}")
private String templatePath;
@Bean
public SpringTemplateLoader templateLoader() {
SpringTemplateLoader templateLoader = new SpringTemplateLoader();
templateLoader.setTemplateLoaderPath(templatePath);
templateLoader.setEncoding("UTF-8");
templateLoader.setSuffix(".pug");
return templateLoader;
}
@Bean
public PugConfiguration pugConfiguration() {
PugConfiguration configuration = new PugConfiguration();
configuration.setCaching(pug4jCache);
configuration.setTemplateLoader(templateLoader());
return configuration;
}
@Bean
public ViewResolver viewResolver() {
PugViewResolver viewResolver = new PugCskefuViewResolver();
viewResolver.setConfiguration(pugConfiguration());
viewResolver.setOrder(0);
viewResolver.setSuffix(".pug");
return viewResolver;
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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.config;
import de.neuland.pug4j.spring.view.PugView;
import de.neuland.pug4j.spring.view.PugViewResolver;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
public class PugCskefuViewResolver extends PugViewResolver {
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = super.buildView(viewName);
if (viewName.startsWith("/resource/css")) {
PugView pugView = (PugView) view;
pugView.setContentType("text/css; charset=UTF-8");
}
return view;
}
}

View File

@ -1,33 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import java.util.concurrent.Executors;
@Configuration
public class RedisConfigure {
@Bean
RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.setTaskExecutor(Executors.newFixedThreadPool(100));
return container;
}
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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 Jun. 2023 Chatopera Inc. <https://www.chatopera.com>. All rights reserved.
*/
package com.cskefu.cc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.cskefu.cc.basic.TerminateBean;
@Configuration
public class ShutdownConfig {
@Bean
public TerminateBean getTerminateBean() {
return new TerminateBean();
}
}

View File

@ -1,56 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.convert.converter.Converter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringToDateConverter implements Converter<String, Date> {
private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
private static final String shortDateFormat = "yyyy-MM-dd";
/**
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Date convert(String source) {
if (StringUtils.isBlank(source)) {
return null;
}
source = source.trim();
try {
if (source.contains("-")) {
SimpleDateFormat formatter;
if (source.contains(":")) {
formatter = new SimpleDateFormat(dateFormat);
} else {
formatter = new SimpleDateFormat(shortDateFormat);
}
Date dtDate = formatter.parse(source);
return dtDate;
} else if (source.matches("^\\d+$")) {
Long lDate = new Long(source);
return new Date(lDate);
}
} catch (Exception e) {
throw new RuntimeException(String.format("parser %s to Date callOutFail", source));
}
throw new RuntimeException(String.format("parser %s to Date callOutFail", source));
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import jakarta.annotation.PostConstruct;
@Configuration
public class WebConfigBeans {
@Autowired
private RequestMappingHandlerAdapter handlerAdapter;
@Bean
public StandardServletMultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
/**
* 增加字符串转日期的功能
*/
@PostConstruct
public void initEditableValidation() {
ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) handlerAdapter
.getWebBindingInitializer();
if (initializer.getConversionService() != null) {
GenericConversionService genericConversionService = (GenericConversionService) initializer
.getConversionService();
genericConversionService.addConverter(new StringToDateConverter());
}
}
}

View File

@ -1,106 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
import java.io.IOException;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterAfter(tokenInfoTokenFilterSecurityInterceptor(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(authorize -> authorize.requestMatchers("/**").permitAll())
.csrf().disable().headers().frameOptions().sameOrigin()
// .addFilterAfter(csrfHeaderFilter(), BasicAuthenticationFilter.class) // TODO lecjy
.and().addFilterAfter(apiTokenFilterSecurityInterceptor(), BasicAuthenticationFilter.class);
http.headers().contentTypeOptions().disable();
return http.build();
}
@Bean
public Filter tokenInfoTokenFilterSecurityInterceptor() throws Exception {
RequestMatcher autconfig = new AntPathRequestMatcher("/autoconfig/**");
RequestMatcher configprops = new AntPathRequestMatcher("/configprops/**");
RequestMatcher beans = new AntPathRequestMatcher("/beans/**");
RequestMatcher dump = new AntPathRequestMatcher("/dump/**");
RequestMatcher env = new AntPathRequestMatcher("/env/**");
RequestMatcher info = new AntPathRequestMatcher("/info/**");
RequestMatcher mappings = new AntPathRequestMatcher("/mappings/**");
RequestMatcher trace = new AntPathRequestMatcher("/trace/**");
RequestMatcher druid = new AntPathRequestMatcher("/druid/**");
/**
* Bypass actuator api
*/
// RequestMatcher health = new AntPathRequestMatcher("/health/**");
// RequestMatcher metrics = new AntPathRequestMatcher("/metrics/**");
// return new DelegateRequestMatchingFilter(autconfig , configprops , beans , dump , env , health , info , mappings , metrics , trace, druid);
return new DelegateRequestMatchingFilter(autconfig, configprops, beans, dump, env, mappings, trace, druid);
}
@Bean
public Filter apiTokenFilterSecurityInterceptor() throws Exception {
return new ApiRequestMatchingFilter(new AntPathRequestMatcher("/api/**"));
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
// Token is being added to the XSRF-TOKEN cookie.
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
}

View File

@ -1,73 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.config;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.http.Rfc6265CookieProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
@Configuration
public class WebServerContainerConfigure {
@Value("${server.threads.max}")
private Integer maxthread;
@Value("${server.connection.max}")
private Integer maxconnections;
@Value("${web.upload-path}")
private String path;
@Bean
public TomcatServletWebServerFactory createEmbeddedServletContainerFactory() throws IOException, NoSuchAlgorithmException {
TomcatServletWebServerFactory tomcatFactory = new TomcatServletWebServerFactory();
tomcatFactory.addConnectorCustomizers(new CSKeFuTomcatConnectorCustomizer(maxthread, maxconnections));
// Enable cookie value with space
// https://stackoverflow.com/questions/38687210/error-with-cookie-value-when-adding-a-new-spring-session
// TODO lecjy
tomcatFactory.addContextCustomizers(context -> context.setCookieProcessor(new Rfc6265CookieProcessor()));
return tomcatFactory;
}
class CSKeFuTomcatConnectorCustomizer implements TomcatConnectorCustomizer {
private final Integer maxthread;
private final Integer maxconnection;
CSKeFuTomcatConnectorCustomizer(Integer maxthread, Integer maxconnection) {
this.maxthread = maxthread;
this.maxconnection = maxconnection;
}
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
//设置最大连接数
protocol.setMaxConnections(maxthread != null ? maxthread : 2000);
//设置最大线程数
protocol.setMaxThreads(maxconnection != null ? maxconnection : 2000);
protocol.setConnectionTimeout(30000);
}
}
}

View File

@ -1,121 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.config;
import com.cskefu.cc.basic.auth.AuthRedisTemplate;
import com.cskefu.cc.cache.RedisKey;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.FlushMode;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import java.time.Duration;
/**
* maxInactiveIntervalInSeconds: 设置 Session 失效时间
* 使用 Redis Session 之后 Spring Boot server.session.timeout 属性不再生效
* http://www.ityouknow.com/springboot/2016/03/06/spring-boot-redis.html
* 86400 代表一天
* maxInactiveIntervalInSeconds = 86400 * 30
*/
@Configuration
public class WebServerSessionConfigure {
/**
* spring在多长时间后强制使redis中的session失效,默认是1800.(单位/)
*/
@Value("${server.session-timeout}")
private long maxInactiveIntervalInSeconds;
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.password}")
private String pass;
@Value("${spring.redis.session.db}")
private int sessionDb;
@Value("${spring.redis.token.db}")
private int tokenDb;
@Value("${spring.data.redis.timeout}")
private int timeout;
@Primary
@Bean
// TODO lecjy
public RedisSessionRepository sessionRepository(RedisTemplate<String, Object> sessionRedisTemplate) {
RedisSessionRepository sessionRepository = new RedisSessionRepository(sessionRedisTemplate);
sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(maxInactiveIntervalInSeconds));
sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
sessionRepository.setRedisKeyNamespace(RedisKey.CACHE_SESSIONS);
return sessionRepository;
}
@Bean
public RedisTemplate<String, Object> sessionRedisTemplate() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host);
factory.setPort(port);
factory.setDatabase(sessionDb);
if (StringUtils.isNotBlank(pass)) {
factory.setPassword(pass);
}
factory.setTimeout(timeout);
factory.afterPropertiesSet();
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(factory);
return template;
}
/**
* 存储AuthToken
* @return
*/
@Bean
public AuthRedisTemplate authRedisTemplate() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host);
factory.setPort(port);
factory.setDatabase(tokenDb);
if (StringUtils.isNotBlank(pass)) {
factory.setPassword(pass);
}
factory.setTimeout(timeout);
factory.afterPropertiesSet();
AuthRedisTemplate template = new AuthRedisTemplate();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(factory);
return template;
}
}

View File

@ -1,164 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller;
import com.cskefu.cc.acd.ACDWorkMonitor;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.PbxHost;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.repository.ExtensionRepository;
import com.cskefu.cc.persistence.repository.OrganRepository;
import com.cskefu.cc.persistence.repository.PbxHostRepository;
import com.cskefu.cc.proxy.OrganProxy;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.stream.Collectors;
@Controller
public class ApplicationController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(ApplicationController.class);
@Autowired
private ACDWorkMonitor acdWorkMonitor;
@Value("${cskefu.build.version}")
private String appVersionNumber;
@Value("${git.commit.id.abbrev}")
private String appVersionAbbrev;
@Value("${application.build.datestr}")
private String appBuildDate;
@Value("${application.customer.entity}")
private String appCustomerEntity;
@Autowired
private Cache cache;
@Value("${tongji.baidu.sitekey}")
private String tongjiBaiduSiteKey;
@Autowired
private OrganProxy organProxy;
@Autowired
private OrganRepository organRepository;
@Autowired
private PbxHostRepository pbxHostRes;
@Autowired
private ExtensionRepository extensionRes;
@RequestMapping("/")
public ModelAndView admin(HttpServletRequest request) {
// logger.info("[admin] path {} queryString {}", request.getPathInfo(),request.getQueryString());
ModelAndView view = request(super.createView("/apps/index"));
User logined = super.getUser(request);
Organ currentOrgan = super.getOrgan(request);
TimeZone timezone = TimeZone.getDefault();
List<Organ> organs = organProxy.findOrganInIds(logined.getAffiliates());
view.addObject(
"skills",
organProxy.findAllOrganByParent(currentOrgan).keySet().stream().collect(Collectors.joining(","))
);
view.addObject("agentStatusReport", acdWorkMonitor.getAgentReport(currentOrgan != null ? currentOrgan.getId() : null));
view.addObject("istenantshare", false);
view.addObject("timeDifference", timezone.getRawOffset());
view.addObject("organList", organs);
view.addObject("currentOrgan", super.getOrgan(request));
// 增加版本信息
view.addObject("appBuildDate", appBuildDate);
view.addObject("appVersionAbbrev", appVersionAbbrev);
view.addObject("appVersionNumber", appVersionNumber);
view.addObject("appCustomerEntity", appCustomerEntity);
// 在线坐席状态信息
view.addObject("agentStatus", cache.findOneAgentStatusByAgentno(logined.getId()));
// 呼叫中心信息
if (MainContext.hasModule(Constants.CSKEFU_MODULE_CALLCENTER) && logined.isCallcenter()) {
extensionRes.findByAgentno(logined.getId()).ifPresent(ext -> {
PbxHost one = pbxHostRes.findById(ext.getHostid()).orElse(null);
Map<String, Object> webrtcData = new HashMap<>();
webrtcData.put("callCenterWebrtcIP", one.getWebrtcaddress());
webrtcData.put("callCenterWebRtcPort", one.getWebrtcport());
webrtcData.put("callCenterExtensionNum", ext.getExtension());
try {
webrtcData.put("callCenterExtensionPassword", MainUtils.decryption(ext.getPassword()));
} catch (NoSuchAlgorithmException e) {
logger.error("[admin]", e);
webrtcData.put("callCenterError", "Invalid data for callcenter agent.");
}
view.addObject("webrtc", webrtcData);
});
}
if (StringUtils.isNotBlank(tongjiBaiduSiteKey) && !StringUtils.equalsIgnoreCase(tongjiBaiduSiteKey, "placeholder")) {
logger.info("tongjiBaiduSiteKey: {}", tongjiBaiduSiteKey);
view.addObject("tongjiBaiduSiteKey", tongjiBaiduSiteKey);
}
return view;
}
@RequestMapping("/setorgan")
@ResponseBody
public String setOrgan(HttpServletRequest request, @Valid String organ) {
if (StringUtils.isNotBlank(organ)) {
Organ currentOrgan = organRepository.findById(organ).orElse(null);
if (currentOrgan != null) {
request.getSession(true).setAttribute(Constants.ORGAN_SESSION_NAME, currentOrgan);
}
}
return "ok";
}
@RequestMapping("/lazyAgentStatus")
public ModelAndView lazyAgentStatus(HttpServletRequest request) {
ModelAndView view = request(super.createView("/public/agentstatustext"));
Organ currentOrgan = super.getOrgan(request);
view.addObject("agentStatusReport", acdWorkMonitor.getAgentReport(currentOrgan != null ? currentOrgan.getId() : null));
return view;
}
}

View File

@ -1,414 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.basic.Viewport;
import com.cskefu.cc.basic.auth.BearerTokenMgr;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.api.QueryParams;
import com.cskefu.cc.exception.CSKefuException;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.StreamingFile;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.blob.JpaBlobHelper;
import com.cskefu.cc.persistence.repository.StreamingFileRepository;
import com.cskefu.cc.proxy.OrganProxy;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.cskefu.cc.basic.Constants.AUTH_TOKEN_TYPE_BASIC;
import static com.cskefu.cc.basic.Constants.AUTH_TOKEN_TYPE_BEARER;
@Controller
@SessionAttributes
public class Handler {
private static final Logger logger = LoggerFactory.getLogger(Handler.class);
@Autowired
private JpaBlobHelper jpaBlobHelper;
@Autowired
private StreamingFileRepository streamingFileRes;
@Autowired
private Cache cache;
@Autowired
private BearerTokenMgr bearerTokenMgr;
@Autowired
private OrganProxy organProxy;
public final static int PAGE_SIZE_BG = 1;
public final static int PAGE_SIZE_TW = 20;
public final static int PAGE_SIZE_FV = 50;
public final static int PAGE_SIZE_HA = 100;
private long starttime = System.currentTimeMillis();
public User getUser(HttpServletRequest request) {
User user = (User) request.getSession(true).getAttribute(Constants.USER_SESSION_NAME);
if (user == null) {
String authorization = request.getHeader("authorization");
if (StringUtils.isBlank(authorization) && request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals("authorization")) {
authorization = cookie.getValue();
break;
}
}
}
// trim token
if (StringUtils.isNotBlank(authorization)) {
String authorizationTrimed = authorization;
if (authorization.startsWith(String.format("%s ", AUTH_TOKEN_TYPE_BEARER))) {
authorizationTrimed = StringUtils.substring(authorization, 7);
if (StringUtils.isNotBlank(authorizationTrimed)) {
user = bearerTokenMgr.retrieve(authorizationTrimed);
}
} else if (authorization.startsWith(String.format("%s ", AUTH_TOKEN_TYPE_BASIC))) {
authorizationTrimed = StringUtils.substring(authorization, 6);
// TODO https://gitlab.chatopera.com/chatopera/chatopera.bot/issues/1292
// get user with basic token mgr
}
}
if (user == null) {
user = new User();
user.setId(MainUtils.getContextID(request.getSession().getId()));
user.setUsername(Constants.GUEST_USER + "_" + MainUtils.genIDByKey(user.getId()));
user.setSessionid(user.getId());
}
} else {
user.setSessionid(MainUtils.getContextID(request.getSession().getId()));
}
return user;
}
/**
* 获得登录账号的当前导航的组织机构
*
* @param request
* @return
*/
public Organ getOrgan(HttpServletRequest request) {
User user = getUser(request);
if (user.getOrgans() != null) {
Organ organ = (Organ) request.getSession(true).getAttribute(Constants.ORGAN_SESSION_NAME);
if (organ == null) {
organ = organProxy.getDefault(user.getOrgans().values());
if (organ != null) {
request.getSession(true).setAttribute(Constants.ORGAN_SESSION_NAME, organ);
}
}
return organ;
} else {
return null;
}
}
/**
* 获得该用户的组织机构及附属组织机构的数组
*
* @param user
* @return
*/
public List<String> getMyAffiliatesFlat(final User user) {
ArrayList<String> organIds = new ArrayList<>(user.getAffiliates());
return organIds;
}
/**
* 获得当前用户导航的组织机构和附属组织机构的信息
*
* @param user
* @return
*/
public List<String> getMyCurrentAffiliatesFlat(final User user) {
ArrayList<String> organIds = new ArrayList<>(user.getCurrOrganAffiliates());
return organIds;
}
/**
* 构建ElasticSearch基于部门查询的Filter
*
* @param request
* @return
* @throws CSKefuException
*/
public boolean preCheckPermissions(final HttpServletRequest request)
throws CSKefuException {
// 组合部门条件
User u = getUser(request);
if (u == null) {
throw new CSKefuException("[esOrganFilter] 未能获取到登录用户。");
} else if (u.isAdmin()) {
// 管理员, 查看任何数据
return true;
} else {
// 用户在部门中通过部门过滤数据
// String[] values = u.getAffiliates().toArray(new
// String[u.getAffiliates().size()]);
// boolQueryBuilder.filter(termsQuery("organ", values));
// 不对contacts进行过滤普通用户也可以查看该租户的任何数据
// return true;
}
return true;
}
/**
* 创建或从HTTP会话中查找到访客的User对象该对象不在数据库中属于临时会话
* 这个User很可能是打开一个WebIM访客聊天控件随机生成用户名之后和Contact关联
* 这个用户可能关联一个OnlineUser如果开始给TA分配坐席
*
* @param request
* @param userid
* @param nickname
* @return
*/
public User getIMUser(HttpServletRequest request, String userid, String nickname) {
User user = (User) request.getSession(true).getAttribute(Constants.IM_USER_SESSION_NAME);
if (user == null) {
user = new User();
if (StringUtils.isNotBlank(userid)) {
user.setId(userid);
} else {
user.setId(MainUtils.getContextID(request.getSession().getId()));
}
if (StringUtils.isNotBlank(nickname)) {
user.setUsername(nickname);
} else {
Map<String, String> sessionMessage = cache.findOneSystemMapById(request.getSession().getId());
if (sessionMessage != null) {
String struname = sessionMessage.get("username");
String strcname = sessionMessage.get("company_name");
user.setUsername(struname + "@" + strcname);
} else {
user.setUsername(Constants.GUEST_USER + "_" + MainUtils.genIDByKey(user.getId()));
}
}
user.setSessionid(user.getId());
} else {
user.setSessionid(MainUtils.getContextID(request.getSession().getId()));
}
return user;
}
public User getIMUser(HttpServletRequest request, String userid, String nickname, String sessionid) {
User user = (User) request.getSession(true).getAttribute(Constants.IM_USER_SESSION_NAME);
if (user == null) {
user = new User();
if (StringUtils.isNotBlank(userid)) {
user.setId(userid);
} else {
user.setId(MainUtils.getContextID(request.getSession().getId()));
}
if (StringUtils.isNotBlank(nickname)) {
user.setUsername(nickname);
} else {
Map<String, String> sessionMessage = cache.findOneSystemMapById(sessionid);
if (sessionMessage != null) {
String struname = sessionMessage.get("username");
String strcname = sessionMessage.get("company_name");
user.setUsername(struname + "@" + strcname);
} else {
user.setUsername(Constants.GUEST_USER + "_" + MainUtils.genIDByKey(user.getId()));
}
}
user.setSessionid(user.getId());
} else {
user.setSessionid(MainUtils.getContextID(request.getSession().getId()));
}
return user;
}
public void setUser(HttpServletRequest request, User user) {
request.getSession(true).removeAttribute(Constants.USER_SESSION_NAME);
request.getSession(true).setAttribute(Constants.USER_SESSION_NAME, user);
}
/**
* 创建系统监控的 模板页面
*
* @param page
* @return
*/
public Viewport createViewIncludedByFreemarkerTplForAdmin(String page) {
return new Viewport("/admin/include/tpl", page);
}
/**
* 创建系统监控的 模板页面
*
* @param page
* @return
*/
public Viewport createViewIncludedByFreemarkerTpl(String page) {
return new Viewport("/apps/include/tpl", page);
}
/**
* 创建系统监控的 模板页面
*
* @param page
* @return
*/
public Viewport createViewIncludedByFreemarkerTplForEntIM(final String page) {
return new Viewport("/apps/entim/include/tpl", page);
}
public Viewport createView(final String page) {
return new Viewport(page);
}
/**
* @param data
* @return
*/
public ModelAndView request(Viewport data) {
return new ModelAndView(data.getTemplate() != null ? data.getTemplate() : data.getPage(), "data", data);
}
public int getP(HttpServletRequest request) {
int page = 0;
String p = request.getParameter("p");
if (StringUtils.isNotBlank(p) && p.matches("[\\d]*")) {
page = Integer.parseInt(p);
if (page > 0) {
page = page - 1;
}
}
return page;
}
public int getPs(HttpServletRequest request) {
int pagesize = PAGE_SIZE_TW;
String ps = request.getParameter("ps");
if (StringUtils.isNotBlank(ps) && ps.matches("[\\d]*")) {
pagesize = Integer.parseInt(ps);
}
return pagesize;
}
public int getP(QueryParams params) {
int page = 0;
if (params != null && StringUtils.isNotBlank(params.getP()) && params.getP().matches("[\\d]*")) {
page = Integer.parseInt(params.getP());
if (page > 0) {
page = page - 1;
}
}
return page;
}
public int getPs(QueryParams params) {
int pagesize = PAGE_SIZE_TW;
if (params != null && StringUtils.isNotBlank(params.getPs()) && params.getPs().matches("[\\d]*")) {
pagesize = Integer.parseInt(params.getPs());
}
return pagesize;
}
public int get50Ps(HttpServletRequest request) {
int pagesize = PAGE_SIZE_FV;
String ps = request.getParameter("ps");
if (StringUtils.isNotBlank(ps) && ps.matches("[\\d]*")) {
pagesize = Integer.parseInt(ps);
}
return pagesize;
}
public long getStarttime() {
return starttime;
}
public void setStarttime(long starttime) {
this.starttime = starttime;
}
/**
* 使用Blob保存文件
*
* @param multipart
* @return id
* @throws IOException
*/
public String saveImageFileWithMultipart(MultipartFile multipart) throws IOException {
StreamingFile sf = new StreamingFile();
final String fileid = MainUtils.getUUID();
sf.setId(fileid);
sf.setMime(multipart.getContentType());
sf.setData(jpaBlobHelper.createBlob(multipart.getInputStream(), multipart.getSize()));
sf.setName(multipart.getOriginalFilename());
streamingFileRes.save(sf);
return fileid;
}
/**
* 使用Blob保存文件
*
* @param dataStr Data URL 图片数据
* @return id
* @throws IOException
*/
public String saveImageFileWithDataURL(String dataStr) throws IOException {
String[] cell = dataStr.split(";");
String mime = cell[0].substring(5);
String base64Str = cell[1].substring(7);
byte[] buf = Base64.decodeBase64(base64Str);
StreamingFile sf = new StreamingFile();
final String fileid = MainUtils.getUUID();
sf.setId(fileid);
sf.setMime(mime);
sf.setData(jpaBlobHelper.createBlob(new ByteArrayInputStream(buf),
buf.length));
sf.setName(fileid);
streamingFileRes.save(sf);
return fileid;
}
public String getSchema(HttpServletRequest request) {
String schema = request.getScheme();
String headerProto = request.getHeader("X-Forwarded-Proto");
if (StringUtils.isNotBlank(headerProto)) {
schema = headerProto;
}
return schema;
}
}

View File

@ -1,417 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller;
import com.cskefu.cc.acd.ACDWorkMonitor;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.basic.auth.BearerTokenMgr;
import com.cskefu.cc.model.AgentStatus;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.SystemConfig;
import com.cskefu.cc.model.User;
import com.cskefu.cc.model.UserRole;
import com.cskefu.cc.persistence.repository.UserRepository;
import com.cskefu.cc.persistence.repository.UserRoleRepository;
import com.cskefu.cc.proxy.AgentProxy;
import com.cskefu.cc.proxy.AgentSessionProxy;
import com.cskefu.cc.proxy.OrganProxy;
import com.cskefu.cc.proxy.UserProxy;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
/**
* @author CSKefu
* @version 1.0.1
*/
@Controller
public class LoginController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(LoginController.class);
@Autowired
private UserRepository userRepository;
@Autowired
private UserRoleRepository userRoleRes;
@Autowired
private BearerTokenMgr bearerTokenMgr;
@Autowired
private AgentProxy agentProxy;
@Autowired
private AgentSessionProxy agentSessionProxy;
@Autowired
private UserProxy userProxy;
@Autowired
private ACDWorkMonitor acdWorkMonitor;
@Value("${tongji.baidu.sitekey}")
private String tongjiBaiduSiteKey;
@Value("${extras.login.banner}")
private String extrasLoginBanner;
@Value("${extras.login.chatbox}")
private String extrasLoginChatbox;
private void putViewExtras(final ModelAndView view) {
if (StringUtils.isNotBlank(extrasLoginBanner) && !StringUtils.equalsIgnoreCase(extrasLoginBanner, "off")) {
view.addObject("extrasLoginBanner", extrasLoginBanner);
} else {
view.addObject("extrasLoginBanner", "off");
}
if (StringUtils.isNotBlank(extrasLoginChatbox) && !StringUtils.equalsIgnoreCase(extrasLoginChatbox, "off")) {
view.addObject("extrasLoginChatbox", extrasLoginChatbox);
} else {
view.addObject("extrasLoginChatbox", "off");
}
}
/**
* 登录页面
*
* @param request
* @param response
* @param referer
* @param msg
* @return
* @throws NoSuchAlgorithmException
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
@Menu(type = "apps", subtype = "user", access = true)
public ModelAndView login(HttpServletRequest request, HttpServletResponse response,
@RequestHeader(value = "referer", required = false) String referer, @Valid String msg) {
ModelAndView view = new ModelAndView("redirect:/");
if (request.getSession(true).getAttribute(Constants.USER_SESSION_NAME) == null) {
view = new ModelAndView("/login");
if (StringUtils.isNotBlank(request.getParameter("referer"))) {
referer = request.getParameter("referer");
}
if (StringUtils.isNotBlank(referer)) {
view.addObject("referer", referer);
}
Cookie[] cookies = request.getCookies(); // 这样便可以获取一个cookie数组
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie != null && StringUtils.isNotBlank(cookie.getName()) && StringUtils.isNotBlank(
cookie.getValue())) {
if (cookie.getName().equals(Constants.CSKEFU_SYSTEM_COOKIES_FLAG)) {
String flagid;
try {
flagid = MainUtils.decryption(cookie.getValue());
if (StringUtils.isNotBlank(flagid)) {
User user = userRepository.findById(flagid).orElse(null);
if (user != null) {
view = this.processLogin(request, user, referer);
}
}
} catch (EncryptionOperationNotPossibleException e) {
logger.error("[login] error:", e);
view = request(super.createView("/public/clearcookie"));
return view;
} catch (NoSuchAlgorithmException e) {
logger.error("[login] error:", e);
}
}
}
}
}
}
if (StringUtils.isNotBlank(msg)) {
view.addObject("msg", msg);
}
SystemConfig systemConfig = MainUtils.getSystemConfig();
// is Enable reg tenant
// view.addObject("show", false);
if (systemConfig != null) {
view.addObject("systemConfig", systemConfig);
}
if (StringUtils.isNotBlank(tongjiBaiduSiteKey)
&& !StringUtils.equalsIgnoreCase(tongjiBaiduSiteKey, "placeholder")) {
view.addObject("tongjiBaiduSiteKey", tongjiBaiduSiteKey);
}
putViewExtras(view);
return view;
}
/**
* 提交登录表单
*
* @param request
* @param response
* @param user
* @param referer
* @param sla
* @return
* @throws NoSuchAlgorithmException
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@Menu(type = "apps", subtype = "user", access = true)
public ModelAndView login(
final HttpServletRequest request,
final HttpServletResponse response,
@Valid User user,
@Valid String referer,
@Valid String sla) throws NoSuchAlgorithmException {
ModelAndView view = new ModelAndView("redirect:/");
if (request.getSession(true).getAttribute(Constants.USER_SESSION_NAME) == null) {
if (user != null && user.getUsername() != null) {
final User loginUser = userRepository.findByUsernameAndPasswordAndDatastatus(
user.getUsername(), MainUtils.md5(user.getPassword()), false);
if (loginUser != null && StringUtils.isNotBlank(loginUser.getId())) {
view = this.processLogin(request, loginUser, referer);
// 自动登录
if (StringUtils.equals("1", sla)) {
Cookie flagid = new Cookie(
Constants.CSKEFU_SYSTEM_COOKIES_FLAG, MainUtils.encryption(loginUser.getId()));
flagid.setMaxAge(7 * 24 * 60 * 60);
response.addCookie(flagid);
}
// add authorization code for rest api
String uuid = MainUtils.getUUID();
String token = String.format("%s %s", Constants.AUTH_TOKEN_TYPE_BEARER, uuid);
bearerTokenMgr.update(token, loginUser);
userRepository.save(loginUser); // 更新登录状态到数据库
response.addCookie((new Cookie("authorization", uuid)));
// 该登录用户是坐席并且具有坐席对话的角色
if ((loginUser.isAgent() &&
loginUser.getRoleAuthMap().containsKey("A01") &&
((boolean) loginUser.getRoleAuthMap().get("A01") == true))
|| loginUser.isAdmin()) {
try {
/****************************************
* 登录成功设置该坐席为就绪状态默认
****************************************/
// https://gitlab.chatopera.com/chatopera/cosinee.w4l/issues/306
final AgentStatus agentStatus = agentProxy.resolveAgentStatusByAgentno(
loginUser.getId(), loginUser.getSkills());
agentStatus.setBusy(false);
agentProxy.ready(loginUser, agentStatus, false);
// 工作状态记录
acdWorkMonitor.recordAgentStatus(agentStatus.getAgentno(),
agentStatus.getUsername(),
agentStatus.getAgentno(),
user.isAdmin(), // 0代表admin
agentStatus.getAgentno(),
MainContext.AgentStatusEnum.OFFLINE.toString(),
MainContext.AgentStatusEnum.READY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(),
null);
} catch (Exception e) {
logger.error("[login] set agent status", e);
}
}
} else {
view = request(super.createView("/login"));
if (StringUtils.isNotBlank(referer)) {
view.addObject("referer", referer);
}
putViewExtras(view);
view.addObject("msg", "0");
}
}
}
SystemConfig systemConfig = MainUtils.getSystemConfig();
// is Enable reg tenant
// view.addObject("show", false);
if (systemConfig != null) {
view.addObject("systemConfig", systemConfig);
}
return view;
}
/**
* 处理登录事件
*
* @param request
* @param loginUser
* @param referer
* @return
*/
private ModelAndView processLogin(final HttpServletRequest request, final User loginUser, String referer) {
ModelAndView view = new ModelAndView();
if (loginUser != null) {
// 设置登录用户的状态
loginUser.setLogin(true);
// 更新redis session信息用以支持sso
agentSessionProxy.updateUserSession(
loginUser.getId(), MainUtils.getContextID(request.getSession().getId()));
loginUser.setSessionid(MainUtils.getContextID(request.getSession().getId()));
if (StringUtils.isNotBlank(referer)) {
view = new ModelAndView("redirect:" + referer);
} else {
view = new ModelAndView("redirect:/");
}
// 登录成功 判断是否进入多租户页面
SystemConfig systemConfig = MainUtils.getSystemConfig();
if (systemConfig != null && systemConfig.isEnabletneant() && systemConfig.isTenantconsole()
&& !loginUser.isAdmin()) {
view = new ModelAndView("redirect:/apps/tenant/index");
}
List<UserRole> userRoleList = userRoleRes.findByUser(loginUser);
if (userRoleList != null && userRoleList.size() > 0) {
for (UserRole userRole : userRoleList) {
loginUser.getRoleList().add(userRole.getRole());
}
}
// 获取用户所在部门及附属部门的信息
userProxy.attachOrgansPropertiesForUser(loginUser);
Organ currentOrgan = super.getOrgan(request);
userProxy.attachCurrentOrgansPropertiesForUser(loginUser, currentOrgan);
// 添加角色信息
userProxy.attachRolesMap(loginUser, currentOrgan);
loginUser.setLastlogintime(new Date());
if (StringUtils.isNotBlank(loginUser.getId())) {
userRepository.save(loginUser);
}
super.setUser(request, loginUser);
}
return view;
}
/**
* 登出用户
* code代表登出的原因
*
* @param request
* @param response
* @param code 登出的代码
* @return
*/
@RequestMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "code", required = false) String code) throws UnsupportedEncodingException {
final User user = super.getUser(request);
request.getSession().removeAttribute(Constants.USER_SESSION_NAME);
request.getSession().invalidate();
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie != null && StringUtils.isNotBlank(cookie.getName()) && StringUtils.isNotBlank(
cookie.getValue())) {
if (cookie.getName().equals(Constants.CSKEFU_SYSTEM_COOKIES_FLAG)) {
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
}
}
if (StringUtils.isNotBlank(code)) {
return "redirect:/?msg=" + code;
}
return "redirect:/";
}
@RequestMapping(value = "/register")
@Menu(type = "apps", subtype = "user", access = true)
public ModelAndView register(HttpServletRequest request, HttpServletResponse response, @Valid String msg) {
ModelAndView view = request(super.createView("redirect:/"));
if (request.getSession(true).getAttribute(Constants.USER_SESSION_NAME) == null) {
view = request(super.createView("/register"));
}
if (StringUtils.isNotBlank(msg)) {
view.addObject("msg", msg);
}
return view;
}
@RequestMapping("/addAdmin")
@Menu(type = "apps", subtype = "user", access = true)
public ModelAndView addAdmin(HttpServletRequest request, HttpServletResponse response, @Valid User user) {
String msg = "";
msg = validUser(user);
if (StringUtils.isNotBlank(msg)) {
return request(super.createView("redirect:/register.html?msg=" + msg));
} else {
user.setUname(user.getUsername());
user.setAdmin(true);
if (StringUtils.isNotBlank(user.getPassword())) {
user.setPassword(MainUtils.md5(user.getPassword()));
}
userRepository.save(user);
}
ModelAndView view = this.processLogin(request, user, "");
return view;
}
private String validUser(User user) {
String msg = "";
User tempUser = userRepository.findByUsernameAndDatastatus(user.getUsername(), false);
if (tempUser != null) {
msg = "username_exist";
return msg;
}
tempUser = userRepository.findByEmailAndDatastatus(user.getEmail(), false);
if (tempUser != null) {
msg = "email_exist";
return msg;
}
tempUser = userRepository.findByMobileAndDatastatus(user.getMobile(), false);
if (tempUser != null) {
msg = "mobile_exist";
return msg;
}
return msg;
}
}

View File

@ -1,128 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin;
import com.cskefu.cc.acd.ACDWorkMonitor;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository;
import com.cskefu.cc.persistence.repository.UserEventRepository;
import com.cskefu.cc.persistence.repository.UserRepository;
import com.cskefu.cc.proxy.OnlineUserProxy;
import com.cskefu.cc.socketio.client.NettyClients;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
@Controller
public class AdminController extends Handler {
@Autowired
private ACDWorkMonitor acdWorkMonitor;
@Autowired
private UserRepository userRes;
@Autowired
private PassportWebIMUserRepository onlineUserRes;
@Autowired
private UserEventRepository userEventRes;
@Autowired
private Cache cache;
@RequestMapping("/admin")
public ModelAndView index(ModelMap map, HttpServletRequest request) {
ModelAndView view = request(super.createView("redirect:/"));
User user = super.getUser(request);
view.addObject("agentStatusReport", acdWorkMonitor.getAgentReport());
view.addObject("agentStatus", cache.findOneAgentStatusByAgentno(user.getId()));
return view;
}
private void aggValues(ModelMap map, HttpServletRequest request) {
map.put("onlineUserCache", cache.getOnlineUserSize());
map.put("onlineUserClients", OnlineUserProxy.webIMClients.size());
map.put("chatClients", NettyClients.getInstance().size());
map.put("systemCaches", cache.getSystemSize());
map.put("agentReport", acdWorkMonitor.getAgentReport());
map.put("webIMReport", MainUtils.getWebIMReport(userEventRes.findByCreatetimeRange(MainUtils.getStartTime(), MainUtils.getEndTime())));
map.put("agents", getAgent(request).size());
map.put("webIMInvite", MainUtils.getWebIMInviteStatus(onlineUserRes.findByStatus(MainContext.OnlineUserStatusEnum.ONLINE.toString())));
map.put("inviteResult", MainUtils.getWebIMInviteResult(onlineUserRes.findByAgentnoAndCreatetimeRange(super.getUser(request).getId(), MainUtils.getStartTime(), MainUtils.getEndTime())));
map.put("agentUserCount", onlineUserRes.countByAgentForAgentUser(MainContext.AgentUserStatusEnum.INSERVICE.toString(), super.getUser(request).getId(), MainUtils.getStartTime(), MainUtils.getEndTime()));
map.put("agentServicesCount", onlineUserRes.countByAgentForAgentUser(MainContext.AgentUserStatusEnum.END.toString(), super.getUser(request).getId(), MainUtils.getStartTime(), MainUtils.getEndTime()));
map.put("agentServicesAvg", onlineUserRes.countByAgentForAvagTime(MainContext.AgentUserStatusEnum.END.toString(), super.getUser(request).getId(), MainUtils.getStartTime(), MainUtils.getEndTime()));
map.put("webInviteReport", MainUtils.getWebIMInviteAgg(onlineUserRes.findByCreatetimeRange(MainContext.ChannelType.WEBIM.toString(), MainUtils.getLast30Day(), MainUtils.getEndTime())));
map.put("agentConsultReport", MainUtils.getWebIMDataAgg(onlineUserRes.findByCreatetimeRangeForAgent(MainUtils.getLast30Day(), MainUtils.getEndTime())));
map.put("clentConsultReport", MainUtils.getWebIMDataAgg(onlineUserRes.findByCreatetimeRangeForClient(MainUtils.getLast30Day(), MainUtils.getEndTime(), MainContext.ChannelType.WEBIM.toString())));
map.put("browserConsultReport", MainUtils.getWebIMDataAgg(onlineUserRes.findByCreatetimeRangeForBrowser(MainUtils.getLast30Day(), MainUtils.getEndTime(), MainContext.ChannelType.WEBIM.toString())));
}
private List<User> getAgent(HttpServletRequest request) {
//获取当前产品or租户坐席数
List<User> userList = userRes.findByAgentAndDatastatus(true, false);
return userList.isEmpty() ? new ArrayList<>() : userList;
}
@RequestMapping("/admin/content")
@Menu(type = "admin", subtype = "content")
public ModelAndView content(ModelMap map, HttpServletRequest request) {
aggValues(map, request);
return request(super.createView("/admin/content"));
/*if(super.getUser(request).isSuperuser()) {
aggValues(map, request);
return request(super.createAdminTempletResponse("/admin/content"));
}else {
return request(super.createAdminTempletResponse("/admin/user/index"));
}*/
}
@RequestMapping("/admin/auth/infoacq")
@Menu(type = "admin", subtype = "infoacq", admin = true)
public ModelAndView infoacq(ModelMap map, HttpServletRequest request) {
String inacq = (String) request.getSession().getAttribute(Constants.CSKEFU_SYSTEM_INFOACQ);
if (StringUtils.isNotBlank(inacq)) {
request.getSession().removeAttribute(Constants.CSKEFU_SYSTEM_INFOACQ);
} else {
request.getSession().setAttribute(Constants.CSKEFU_SYSTEM_INFOACQ, "true");
}
return request(super.createView("redirect:/"));
}
}

View File

@ -1,377 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.*;
import com.cskefu.cc.proxy.OrganProxy;
import com.cskefu.cc.proxy.UserProxy;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.json.GsonTools;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.*;
/**
* @author 程序猿DD
* @version 1.0.0
* @blog http://blog.didispace.com
*/
@Controller
@RequestMapping("/admin/organ")
public class OrganController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(OrganController.class);
@Autowired
private OrganRepository organRepository;
@Autowired
private OrganUserRepository organUserRes;
@Autowired
private RoleRepository roleRepository;
@Autowired
private SysDicRepository sysDicRepository;
@Autowired
private AreaTypeRepository areaRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private OrganRoleRepository organRoleRes;
@Autowired
private OrganProxy organProxy;
@Autowired
private Cache cache;
@Autowired
private UserProxy userProxy;
private Collection<Organ> getOwnOragans(HttpServletRequest request) {
Organ currentOrgan = super.getOrgan(request);
return organProxy.findAllOrganByParent(currentOrgan).values();
}
@RequestMapping("/index")
@Menu(type = "admin", subtype = "organ")
public ModelAndView index(ModelMap map, HttpServletRequest request, @Valid String organ, @Valid String msg) {
Organ currentOrgan = super.getOrgan(request);
List<Organ> organList = organRepository.findAll();
map.addAttribute("organList", getOwnOragans(request));
if (organList.size() > 0) {
Organ organData = null;
if (!StringUtils.isBlank(organ) && !"null".equals(organ)) {
for (Organ data : organList) {
if (data.getId().equals(organ)) {
map.addAttribute("organData", data);
organData = data;
}
}
} else {
map.addAttribute("organData", organData = currentOrgan);
}
if (organData != null) {
map.addAttribute(
"userList", userProxy.findByOrganAndDatastatus(
organData.getId(),
false));
// 处理附属组织
final Map<String, Organ> affiliates = organProxy.findAllOrganByParent(organData);
List<User> affiliateUsers = new ArrayList<>();
for (final Map.Entry<String, Organ> o : affiliates.entrySet()) {
if (StringUtils.equals(o.getKey(), organData.getId()))
continue;
List<User> ousers = userProxy.findByOrganAndDatastatus(
o.getKey(),
false);
if (ousers != null && ousers.size() > 0) {
for (User u : ousers) {
u.setCurrOrganId(o.getKey());
u.setCurrOrganName(o.getValue().getName());
// copy an object to avoid modify multi times
affiliateUsers.add(GsonTools.copyObject(u));
}
}
}
map.addAttribute("affiliateUsers", affiliateUsers);
}
}
map.addAttribute("currentOrgan", currentOrgan);
map.addAttribute("areaList", areaRepository.findAll());
map.addAttribute("roleList", roleRepository.findAll());
map.put("msg", msg);
return request(super.createView("/admin/organ/index"));
}
@RequestMapping("/add")
@Menu(type = "admin", subtype = "organ")
public ModelAndView add(ModelMap map, HttpServletRequest request, @Valid String parent, @Valid String area) {
map.addAttribute("areaList", areaRepository.findAll());
if (!StringUtils.isBlank(parent)) {
map.addAttribute("organ", organRepository.findById(parent).orElse(null));
}
if (!StringUtils.isBlank(area)) {
map.addAttribute("area", areaRepository.findById(area).orElse(null));
}
map.addAttribute("organList", getOwnOragans(request));
return request(super.createView("/admin/organ/add"));
}
@RequestMapping("/save")
@Menu(type = "admin", subtype = "organ")
public ModelAndView save(HttpServletRequest request, @Valid Organ organ) {
Organ tempOrgan = organRepository.findByName(organ.getName());
String msg = "admin_organ_new_success";
String firstId = null;
if (tempOrgan != null) {
msg = "admin_organ_update_name_not"; // 分类名字重复
} else {
firstId = organ.getId();
organRepository.save(organ);
}
return request(super.createView(
"redirect:/admin/organ/index.html?msg=" + msg + "&organ=" + firstId));
}
/**
* 添加用户到当前部门时选择坐席
*
* @param map
* @param request
* @param organ
* @return
*/
@RequestMapping("/seluser")
@Menu(type = "admin", subtype = "seluser", admin = true)
public ModelAndView seluser(ModelMap map, HttpServletRequest request, @Valid String organ) {
Map<String, Organ> organs = organProxy.findAllOrganByParent(super.getOrgan(request));
map.addAttribute("userList", userProxy.findUserInOrgans(organs.keySet()));
Organ organData = organRepository.findById(organ).orElse(null);
map.addAttribute("userOrganList", userProxy
.findByOrganAndDatastatus(organ, false));
map.addAttribute("organ", organData);
return request(super.createView("/admin/organ/seluser"));
}
/**
* 执行添加用户到组织中
*
* @param request
* @param users
* @param organ
* @return
*/
@RequestMapping("/saveuser")
@Menu(type = "admin", subtype = "saveuser", admin = true)
public ModelAndView saveuser(
HttpServletRequest request,
final @Valid String[] users,
final @Valid String organ) {
logger.info("[saveuser] save users {} into organ {}", StringUtils.join(users, ","), organ);
final User loginUser = super.getUser(request);
if (users != null && users.length > 0) {
List<String> chosen = new ArrayList<>(Arrays.asList(users));
Organ organData = organRepository.findById(organ).orElse(null);
List<User> organUserList = userRepository.findAllById(chosen);
for (final User user : organUserList) {
OrganUser ou = organUserRes.findByUseridAndOrgan(user.getId(), organ);
/**
* 检查人员和技能组关系
*/
if (organData.isSkill()) {
// 该组织机构是技能组
if (!user.isAgent()) {
// 该人员不是坐席
if (ou != null) {
organUserRes.delete(ou);
}
continue;
}
}
if (ou == null) {
ou = new OrganUser();
}
ou.setCreator(loginUser.getId());
ou.setUserid(user.getId());
ou.setOrgan(organ);
organUserRes.save(ou);
if (user.isAgent()) {
/**
* 以下更新技能组状态
*/
AgentStatus agentStatus = cache.findOneAgentStatusByAgentno(user.getId());
// TODO 因为一个用户可以包含在多个技能组中所以skill应该对应
// 一个List列表此处需要重构Skill为列表
if (agentStatus != null) {
userProxy.attachOrgansPropertiesForUser(user);
agentStatus.setSkills(user.getSkills());
cache.putAgentStatus(agentStatus);
}
}
}
userRepository.saveAll(organUserList);
}
return request(super.createView("redirect:/admin/organ/index.html?organ=" + organ));
}
@RequestMapping("/user/delete")
@Menu(type = "admin", subtype = "role")
public ModelAndView userroledelete(
final HttpServletRequest request,
final @Valid String id,
final @Valid String organ) {
logger.info("[userroledelete] user id {}, organ {}", id, organ);
if (id != null) {
List<OrganUser> organUsers = organUserRes.findByUserid(id);
if (organUsers.size() > 1) {
organUserRes.deleteOrganUserByUseridAndOrgan(id, organ);
} else {
return request(super.createView(
"redirect:/admin/organ/index.html?organ=" + organ + "&msg=not_allow_remove_user"));
}
}
return request(super.createView("redirect:/admin/organ/index.html?organ=" + organ));
}
@RequestMapping("/edit")
@Menu(type = "admin", subtype = "organ")
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id) {
ModelAndView view = request(super.createView("/admin/organ/edit"));
Organ currentOrgan = super.getOrgan(request);
map.addAttribute("areaList", areaRepository.findAll());
view.addObject("organData", organRepository.findById(id).orElse(null));
view.addObject("isRootOrgan", id.equals(currentOrgan.getId()));
map.addAttribute("organList", getOwnOragans(request));
return view;
}
@RequestMapping("/update")
@Menu(type = "admin", subtype = "organ")
public ModelAndView update(HttpServletRequest request, @Valid Organ organ) {
String msg = organProxy.updateOrgan(organ, super.getUser(request));
return request(super.createView(
"redirect:/admin/organ/index.html?msg=" + msg + "&organ=" + organ.getId()));
}
@RequestMapping("/area")
@Menu(type = "admin", subtype = "area")
public ModelAndView area(ModelMap map, HttpServletRequest request, @Valid String id) {
SysDic sysDic = sysDicRepository.findByCode(Constants.CSKEFU_SYSTEM_AREA_DIC);
if (sysDic != null) {
map.addAttribute("sysarea", sysDic);
map.addAttribute("areaList", sysDicRepository.findByDicid(sysDic.getId()));
}
map.addAttribute("cacheList", Dict.getInstance().getDic(Constants.CSKEFU_SYSTEM_AREA_DIC));
map.addAttribute("organData", organRepository.findById(id).orElse(null));
return request(super.createView("/admin/organ/area"));
}
@RequestMapping("/area/update")
@Menu(type = "admin", subtype = "organ")
public ModelAndView areaupdate(HttpServletRequest request, @Valid Organ organ) {
Organ tempOrgan = organRepository.findById(organ.getId()).orElse(null);
String msg = "admin_organ_update_success";
if (tempOrgan != null) {
tempOrgan.setArea(organ.getArea());
organRepository.save(tempOrgan);
} else {
msg = "admin_organ_update_not_exist";
}
return request(super.createView(
"redirect:/admin/organ/index.html?msg=" + msg + "&organ=" + organ.getId()));
}
@RequestMapping("/delete")
@Menu(type = "admin", subtype = "organ")
public ModelAndView delete(HttpServletRequest request, @Valid Organ organ) {
String msg = "admin_organ_delete";
Organ organSelf = organRepository.findById(organ.getId()).orElse(null);
List<Organ> organParentAre = organRepository.findByParent(organSelf.getId());
if (organ != null && organParentAre != null && organParentAre.size() > 0) {
msg = "admin_oran_not_delete";
} else if (organ != null) {
List<OrganUser> organUsers = organUserRes.findByOrgan(organ.getId());
if (organUsers.size() > 0) {
msg = "admin_oran_not_empty";
} else {
organRepository.delete(organ);
}
}
return request(super.createView("redirect:/admin/organ/index.html?msg=" + msg));
}
@RequestMapping("/auth/save")
@Menu(type = "admin", subtype = "role")
public ModelAndView authsave(HttpServletRequest request, @Valid String id, @Valid String menus) {
Organ organData = organRepository.findById(id).orElse(null);
List<OrganRole> organRoleList = organRoleRes.findByOrgan(organData);
organRoleRes.deleteAll(organRoleList);
if (!StringUtils.isBlank(menus)) {
String[] menusarray = menus.split(",");
for (String menu : menusarray) {
OrganRole organRole = new OrganRole();
SysDic sysDic = Dict.getInstance().getDicItem(menu);
if (sysDic != null && !"0".equals(sysDic.getParentid())) {
organRole.setDicid(menu);
organRole.setDicvalue(sysDic.getCode());
organRole.setOrgan(organData);
organRole.setCreater(super.getUser(request).getId());
organRole.setCreatetime(new Date());
organRoleRes.save(organRole);
}
}
}
return request(
super.createView("redirect:/admin/organ/index.html?organ=" + organData.getId()));
}
}

View File

@ -1,278 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.RoleAuthRepository;
import com.cskefu.cc.persistence.repository.RoleRepository;
import com.cskefu.cc.persistence.repository.SysDicRepository;
import com.cskefu.cc.persistence.repository.UserRoleRepository;
import com.cskefu.cc.proxy.OrganProxy;
import com.cskefu.cc.proxy.UserProxy;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.json.GsonTools;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/admin/role")
public class RoleController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(RoleController.class);
@Autowired
private RoleRepository roleRepository;
@Autowired
private UserRoleRepository userRoleRes;
@Autowired
private RoleAuthRepository roleAuthRes;
@Autowired
private SysDicRepository sysDicRes;
@Autowired
OrganProxy organProxy;
@Autowired
UserProxy userProxy;
@RequestMapping("/index")
@Menu(type = "admin", subtype = "role")
public ModelAndView index(ModelMap map, HttpServletRequest request, @Valid String role, @Valid String msg) {
Organ currentOrgan = super.getOrgan(request);
List<Role> roleList = roleRepository.findAll();
map.addAttribute("roleList", roleList);
map.addAttribute("msg", msg);
map.addAttribute("currentOrgan", currentOrgan);
if (roleList.size() > 0) {
Role roleData = null;
if (StringUtils.isNotBlank(role)) {
for (Role data : roleList) {
if (data.getId().equals(role)) {
roleData = data;
map.addAttribute("roleData", data);
}
}
} else {
map.addAttribute("roleData", roleData = roleList.get(0));
}
if (roleData != null) {
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
// List<String> userIds = userProxy.findUserIdsInOrgans(organs.keySet());
// Page<UserRole> userRoleList =
// userRoleRes.findByOrganAndRole(currentOrgan.getId(), roleData,
// PageRequest.of(super.getP(request), super.getPs(request)));
Page<UserRole> userRoleList = userRoleRes.findByOrganInAndRole(organs.keySet(), roleData,
PageRequest.of(super.getP(request), super.getPs(request)));
if (userRoleList.getContent().size() > 0) {
for (UserRole ur : userRoleList.getContent()) {
organs.values().stream().filter(o -> o.getId().equals(ur.getOrgan())).findFirst()
.ifPresent(o -> {
User u = GsonTools.copyObject(ur.getUser());
u.setCurrOrganId(o.getId());
u.setCurrOrganName(o.getName());
ur.setUser(u);
});
}
}
map.addAttribute("userRoleList", userRoleList);
}
}
return request(super.createView("/admin/role/index"));
}
@RequestMapping("/add")
@Menu(type = "admin", subtype = "role")
public ModelAndView add(ModelMap map, HttpServletRequest request) {
return request(super.createView("/admin/role/add"));
}
@RequestMapping("/save")
@Menu(type = "admin", subtype = "role")
public ModelAndView save(HttpServletRequest request, @Valid Role role) {
Organ currentOrgan = super.getOrgan(request);
Role tempRole = roleRepository.findByName(role.getName());
String msg = "admin_role_save_success";
if (tempRole != null) {
msg = "admin_role_save_exist";
} else {
role.setCreater(super.getUser(request).getId());
role.setCreatetime(new Date());
role.setUpdatetime(new Date());
role.setOrgan(currentOrgan.getId());
roleRepository.save(role);
}
return request(super.createView("redirect:/admin/role/index.html?msg=" + msg));
}
@RequestMapping("/seluser")
@Menu(type = "admin", subtype = "seluser", admin = true)
public ModelAndView seluser(ModelMap map, HttpServletRequest request, @Valid String role) {
Organ currentOrgan = super.getOrgan(request);
map.addAttribute("userList", userProxy.findUserInOrgans(Arrays.asList(currentOrgan.getId())));
Role roleData = roleRepository.findById(role).orElse(null);
map.addAttribute("userRoleList", userRoleRes.findByRole(roleData));
map.addAttribute("role", roleData);
return request(super.createView("/admin/role/seluser"));
}
@RequestMapping("/saveuser")
@Menu(type = "admin", subtype = "saveuser", admin = true)
public ModelAndView saveuser(HttpServletRequest request, @Valid String[] users, @Valid String role) {
Organ currentOrgan = super.getOrgan(request);
Role roleData = roleRepository.findById(role).orElse(null);
List<UserRole> userRoleList = userRoleRes.findByRole(roleData);
if (users != null && users.length > 0) {
for (String user : users) {
boolean exist = false;
for (UserRole userRole : userRoleList) {
if (user.equals(userRole.getUser().getId())) {
exist = true;
continue;
}
}
if (exist == false) {
UserRole userRole = new UserRole();
userRole.setUser(new User(user));
userRole.setRole(new Role(role));
userRole.setCreater(super.getUser(request).getId());
userRole.setOrgan(currentOrgan.getId());
userRoleRes.save(userRole);
}
}
}
return request(super.createView("redirect:/admin/role/index.html?role=" + role));
}
@RequestMapping("/user/delete")
@Menu(type = "admin", subtype = "role")
public ModelAndView userroledelete(HttpServletRequest request, @Valid String id, @Valid String role) {
if (role != null) {
userRoleRes.deleteById(id);
}
return request(super.createView("redirect:/admin/role/index.html?role=" + role));
}
@RequestMapping("/edit")
@Menu(type = "admin", subtype = "role")
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id) {
ModelAndView view = request(super.createView("/admin/role/edit"));
view.addObject("roleData", roleRepository.findById(id).orElse(null));
return view;
}
@RequestMapping("/update")
@Menu(type = "admin", subtype = "role")
public ModelAndView update(HttpServletRequest request, @Valid Role role) {
Role tempRoleExist = roleRepository.findByName(role.getName());
String msg = "";
if (tempRoleExist == null) {
msg = "admin_role_update_success";
Role tempRole = roleRepository.findById(role.getId()).orElse(null);
tempRole.setName(role.getName());
tempRole.setUpdatetime(new Date());
roleRepository.save(tempRole);
} else if (!role.getId().equals(tempRoleExist.getId())) {
msg = "admin_role_update_not_exist";
}
return request(super.createView("redirect:/admin/role/index.html?msg=" + msg));
}
@RequestMapping("/delete")
@Menu(type = "admin", subtype = "role")
public ModelAndView delete(HttpServletRequest request, @Valid Role role) {
String msg = "admin_role_delete";
if (role != null) {
userRoleRes.deleteAll(userRoleRes.findByRole(role));
roleRepository.delete(role);
} else {
msg = "admin_role_not_exist";
}
return request(super.createView("redirect:/admin/role/index.html?msg=" + msg));
}
@RequestMapping("/auth")
@Menu(type = "admin", subtype = "role")
public ModelAndView auth(ModelMap map, final HttpServletRequest request, final @Valid String id) {
logger.info("[auth] role id {}", id);
SysDic sysDic = sysDicRes.findByCode(Constants.CSKEFU_SYSTEM_AUTH_DIC);
if (sysDic != null) {
map.addAttribute("resourceList", sysDicRes.findByDicid(sysDic.getId()));
}
map.addAttribute("sysDic", sysDic);
Role role = roleRepository.findById(id).orElse(null);
map.addAttribute("role", role);
map.addAttribute("roleAuthList", roleAuthRes.findByRoleid(role.getId()));
return request(super.createView("/admin/role/auth"));
}
@RequestMapping("/auth/save")
@Menu(type = "admin", subtype = "role")
public ModelAndView authsave(HttpServletRequest request, @Valid String id, @Valid String menus) {
// logger.info("[authsave] id {}, menus {}", id, menus);
List<RoleAuth> roleAuthList = roleAuthRes.findByRoleid(id);
roleAuthRes.deleteAll(roleAuthList);
if (StringUtils.isNotBlank(menus)) {
String[] menuarray = menus.split(",");
logger.info("[authsave] menus: {}", menus);
for (String menu : menuarray) {
RoleAuth roleAuth = new RoleAuth();
roleAuth.setRoleid(id);
roleAuth.setDicid(menu);
SysDic sysDic = Dict.getInstance().getDicItem(menu);
if (sysDic != null && (!StringUtils.equals(sysDic.getParentid(), "0"))) {
logger.debug("[authsave] get sysdict {}, code {}, name {}, parent {}", sysDic.getId(),
sysDic.getCode(), sysDic.getName(), sysDic.getParentid());
roleAuth.setCreater(super.getUser(request).getId());
roleAuth.setCreatetime(new Date());
roleAuth.setName(sysDic.getName());
roleAuth.setDicvalue(sysDic.getCode());
logger.debug("[authsave] save role auth {}", roleAuth.toString());
roleAuthRes.save(roleAuth);
}
}
}
return request(super.createView("redirect:/admin/role/index.html?role=" + id));
}
}

View File

@ -1,170 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.ExtensionRepository;
import com.cskefu.cc.persistence.repository.OrganUserRepository;
import com.cskefu.cc.persistence.repository.PbxHostRepository;
import com.cskefu.cc.persistence.repository.RoleRepository;
import com.cskefu.cc.persistence.repository.UserRepository;
import com.cskefu.cc.persistence.repository.UserRoleRepository;
import com.cskefu.cc.proxy.AgentSessionProxy;
import com.cskefu.cc.proxy.OrganProxy;
import com.cskefu.cc.proxy.UserProxy;
import com.cskefu.cc.util.Menu;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* @author 程序猿DD
* @version 1.0.0
* @blog http://blog.didispace.com
*/
@Controller
@RequestMapping("/admin/user")
public class UsersController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(UsersController.class);
@Autowired
private UserRepository userRepository;
@Autowired
private UserRoleRepository userRoleRes;
@Autowired
private RoleRepository roleRes;
@Autowired
OrganProxy organProxy;
@Autowired
UserProxy userProxy;
@Autowired
private OrganUserRepository organUserRes;
@Autowired
private PbxHostRepository pbxHostRes;
@Autowired
private ExtensionRepository extensionRes;
/**
* 只返回根用户只属于该部门的非下级部门的用户
*
* @param map
* @param request
* @return
* @throws IOException
*/
@RequestMapping("/index")
@Menu(type = "admin", subtype = "user")
public ModelAndView index(ModelMap map, HttpServletRequest request) throws IOException {
Organ currentOrgan = super.getOrgan(request);
ArrayList<String> organs = new ArrayList<>();
organs.add(currentOrgan.getId());
map.addAttribute("currentOrgan", currentOrgan);
map.addAttribute("userList", userProxy.findUserInOrgans(organs, PageRequest.of(
super.getP(request),
super.getPs(request),
Sort.Direction.ASC,
"createtime")));
return request(super.createView("/admin/user/index"));
}
@RequestMapping("/add")
@Menu(type = "admin", subtype = "user")
public ModelAndView add(ModelMap map, HttpServletRequest request) {
ModelAndView view = request(super.createView("/admin/user/add"));
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
List<Role> sysRoles = roleRes.findAll();
map.addAttribute("currentOrgan", currentOrgan);
map.addAttribute("organList", organs.values());
map.addAttribute("sysRoles", sysRoles);
return view;
}
@RequestMapping("/edit")
@Menu(type = "admin", subtype = "user")
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id) {
ModelAndView view = request(super.createView("/admin/user/edit"));
User user = userRepository.findById(id).orElse(null);
if (user != null && MainContext.hasModule(Constants.CSKEFU_MODULE_CALLCENTER)) {
// 加载呼叫中心信息
extensionRes.findByAgentno(user.getId()).ifPresent(p -> {
user.setExtensionId(p.getId());
user.setExtension(p);
PbxHost one = pbxHostRes.findById(p.getHostid()).orElse(null);
user.setPbxhostId(one.getId());
user.setPbxHost(one);
});
}
view.addObject("userData", user);
return view;
}
@RequestMapping("/delete")
@Menu(type = "admin", subtype = "user")
public ModelAndView delete(HttpServletRequest request, @Valid User user) {
String msg = "admin_user_delete";
if (user != null) {
User dbUser = userRepository.findById(user.getId()).orElse(null);
if (dbUser.isSuperadmin()) {
msg = "admin_user_abandoned";
} else {
// 删除用户的时候同时删除用户对应的权限数据
List<UserRole> userRole = userRoleRes.findByUser(user);
userRoleRes.deleteAll(userRole);
// 删除用户对应的组织机构关系
List<OrganUser> organUsers = organUserRes.findByUserid(user.getId());
organUserRes.deleteAll(organUsers);
userRepository.delete(dbUser);
AgentSessionProxy agentSessionProxy = MainContext.getContext().getBean(AgentSessionProxy.class);
agentSessionProxy.deleteUserSession(dbUser.getId());
}
} else {
msg = "admin_user_not_exist";
}
return request(super.createView("redirect:/admin/user/index.html?msg=" + msg));
}
}

View File

@ -1,200 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin.channel;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.ConsultInviteRepository;
import com.cskefu.cc.persistence.repository.OrganRepository;
import com.cskefu.cc.persistence.repository.ChannelRepository;
import com.cskefu.cc.persistence.repository.SecretRepository;
import com.cskefu.cc.proxy.OrganProxy;
import com.cskefu.cc.util.Base62;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
*
*/
@Controller
@RequestMapping("/admin/im")
public class ChannelController extends Handler {
@Autowired
private ChannelRepository snsAccountRes;
@Autowired
private ConsultInviteRepository invite;
@Autowired
private SecretRepository secRes;
@Autowired
private OrganProxy organProxy;
@Autowired
private OrganRepository organRes;
@Autowired
private Cache cache;
@RequestMapping("/index")
@Menu(type = "admin", subtype = "im", admin = true)
public ModelAndView index(ModelMap map, HttpServletRequest request, @Valid String execute, @RequestParam(name = "status", required = false) String status) {
Map<String, Organ> organs = organProxy.findAllOrganByParent(super.getOrgan(request));
map.addAttribute("snsAccountList", snsAccountRes.findByTypeAndOrgan(MainContext.ChannelType.WEBIM.toString(), organs.keySet(), PageRequest.of(super.getP(request), super.getPs(request))));
map.addAttribute("status", status);
List<Secret> secretConfig = secRes.findAll();
if (secretConfig != null && secretConfig.size() > 0) {
map.addAttribute("secret", secretConfig.get(0));
}
if (StringUtils.isNotBlank(execute) && execute.equals("false")) {
map.addAttribute("execute", execute);
}
return request(super.createView("/admin/channel/im/index"));
}
@RequestMapping("/add")
@Menu(type = "admin", subtype = "send", admin = true)
public ModelAndView add(ModelMap map, HttpServletRequest request) {
Organ currentOrgan = super.getOrgan(request);
map.put("organ", currentOrgan);
return request(super.createView("/admin/channel/im/add"));
}
@RequestMapping("/save")
@Menu(type = "admin", subtype = "weixin")
public ModelAndView save(HttpServletRequest request,
@Valid Channel channel) throws NoSuchAlgorithmException {
Organ currentOrgan = super.getOrgan(request);
String status = "new_webim_fail";
if (StringUtils.isNotBlank(channel.getBaseURL())) {
channel.setSnsid(Base62.encode(channel.getBaseURL()).toLowerCase());
int count = snsAccountRes.countBySnsid(channel.getSnsid());
if (count == 0) {
status = "new_webim_success";
channel.setType(MainContext.ChannelType.WEBIM.toString());
channel.setCreatetime(new Date());
User curr = super.getUser(request);
channel.setCreater(curr.getId());
channel.setOrgan(currentOrgan.getId());
snsAccountRes.save(channel);
/**
* 同时创建CousultInvite 记录
*/
CousultInvite coultInvite = invite.findBySnsaccountid(channel.getSnsid());
if (coultInvite == null) {
coultInvite = new CousultInvite();
coultInvite.setSnsaccountid(channel.getSnsid());
coultInvite.setCreate_time(new Date());
coultInvite.setName(channel.getName());
coultInvite.setOwner(channel.getCreater());
coultInvite.setSkill(false); // 不启动技能组
coultInvite.setConsult_skill_fixed(false); // 不绑定唯一技能组
coultInvite.setAi(false);
coultInvite.setAifirst(false);
invite.save(coultInvite);
}
}
}
return request(super.createView("redirect:/admin/im/index.html?status=" + status));
}
@RequestMapping("/delete")
@Menu(type = "weixin", subtype = "delete")
public ModelAndView delete(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String confirm) {
boolean execute;
if (execute = MainUtils.secConfirm(secRes, confirm)) {
Channel channel = snsAccountRes.findById(id).orElse(null);
if (snsAccountRes != null) {
// 删除网站渠道记录
snsAccountRes.delete(channel);
/**
* 删除网站渠道客服配置
*/
CousultInvite coultInvite = invite.findBySnsaccountid(channel.getSnsid());
if (coultInvite != null) {
invite.delete(coultInvite);
}
// 删除缓存
cache.deleteConsultInviteBySnsid(channel.getSnsid());
}
}
return request(super.createView("redirect:/admin/im/index.html?execute=" + execute));
}
@RequestMapping("/edit")
@Menu(type = "admin", subtype = "send", admin = true)
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id) {
Channel channel = snsAccountRes.findById(id).orElse(null);
Organ organ = organRes.findById(channel.getOrgan()).orElse(null);
map.put("organ", organ);
map.addAttribute("channel", channel);
return request(super.createView("/admin/channel/im/edit"));
}
@RequestMapping("/update")
@Menu(type = "admin", subtype = "send", admin = true)
public ModelAndView update(HttpServletRequest request, @Valid Channel channel) throws NoSuchAlgorithmException {
Channel oldChannel = snsAccountRes.findById(channel.getId()).orElse(null);
if (oldChannel != null) {
oldChannel.setName(channel.getName());
oldChannel.setBaseURL(channel.getBaseURL());
oldChannel.setUpdatetime(new Date());
/**
* SNSID如果有变更需要同时变更 CoultInvite 表的 记录
*/
if (StringUtils.isNotBlank(oldChannel.getSnsid())) {
CousultInvite coultInvite = invite.findBySnsaccountid(oldChannel.getSnsid());
if (coultInvite == null) {
/**
* 同时创建CousultInvite 记录
*/
coultInvite = new CousultInvite();
coultInvite.setSnsaccountid(oldChannel.getSnsid());
coultInvite.setCreate_time(new Date());
coultInvite.setName(channel.getName());
invite.save(coultInvite);
}
}
oldChannel.setType(MainContext.ChannelType.WEBIM.toString());
snsAccountRes.save(oldChannel);
}
return request(super.createView("redirect:/admin/im/index.html"));
}
}

View File

@ -1,306 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin.channel;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.CousultInvite;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.Channel;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.repository.*;
import com.cskefu.cc.proxy.OnlineUserProxy;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Controller
@RequestMapping("/admin/webim")
public class WebIMController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(WebIMController.class);
@Autowired
private ConsultInviteRepository inviteRes;
@Autowired
private OrganRepository organRes;
@Autowired
private UserRepository userRes;
@Autowired
private ServiceAiRepository serviceAiRes;
@Value("${web.upload-path}")
private String path;
@Autowired
private ChannelRepository snsAccountRes;
@Autowired
private Cache cache;
@RequestMapping("/index")
@Menu(type = "app", subtype = "app", admin = true)
public ModelAndView index(ModelMap map, HttpServletRequest request, @Valid String snsid) {
CousultInvite coultInvite = OnlineUserProxy.consult(snsid);
if (coultInvite != null) {
logger.info("[index] snsaccount Id {}, Ai {}, Aifirst {}, Ainame {}, Aisuccess {}, Aiid {}", coultInvite.getSnsaccountid(), coultInvite.isAi(), coultInvite.isAifirst(), coultInvite.getAiname(), coultInvite.getAisuccesstip(), coultInvite.getAiid());
map.addAttribute("inviteData", coultInvite);
map.addAttribute("skillGroups", getSkillGroups(request));
map.addAttribute("agentList", getUsers(request));
map.addAttribute("port", request.getServerPort());
Optional<Channel> snsAccountOpt = snsAccountRes.findBySnsid(snsid);
snsAccountOpt.ifPresent(snsAccount -> map.addAttribute("channel", snsAccount));
}
return request(super.createView("/admin/webim/index"));
}
/**
* @param request
* @param inviteData
* @param webimlogo
* @param agentheadimg
* @return
* @throws IOException
*/
@RequestMapping("/save")
@Menu(type = "admin", subtype = "app", admin = true)
public ModelAndView save(HttpServletRequest request,
@Valid CousultInvite inviteData,
@RequestParam(value = "webimlogo", required = false) MultipartFile webimlogo,
@RequestParam(value = "agentheadimg", required = false) MultipartFile agentheadimg) throws IOException {
logger.info("[save] snsaccount Id {}, Ai {}, Aifirst {}, Ainame {}, Aisuccess {}, Aiid {}", inviteData.getSnsaccountid(), inviteData.isAi(), inviteData.isAifirst(), inviteData.getAiname(), inviteData.getAisuccesstip(), inviteData.getAiid());
if (StringUtils.isNotBlank(inviteData.getSnsaccountid())) {
CousultInvite tempData = inviteRes.findBySnsaccountid(inviteData.getSnsaccountid());
if (tempData != null) {
tempData.setConsult_vsitorbtn_model(inviteData.getConsult_vsitorbtn_model());
tempData.setConsult_vsitorbtn_color(inviteData.getConsult_vsitorbtn_color());
tempData.setConsult_vsitorbtn_position(inviteData.getConsult_vsitorbtn_position());
tempData.setConsult_vsitorbtn_content(inviteData.getConsult_vsitorbtn_content());
tempData.setConsult_vsitorbtn_delay(inviteData.getConsult_vsitorbtn_delay());
tempData.setConsult_dialog_color(inviteData.getConsult_dialog_color());
inviteData = tempData;
}
} else {
inviteData.setSnsaccountid(super.getUser(request).getId());
}
// 网页品牌标识
if (webimlogo != null && webimlogo.getOriginalFilename().lastIndexOf(".") > 0) {
inviteData.setConsult_dialog_logo(super.saveImageFileWithMultipart(webimlogo));
}
// 网页坐席头像
if (agentheadimg != null && agentheadimg.getOriginalFilename().lastIndexOf(".") > 0) {
inviteData.setConsult_dialog_headimg(super.saveImageFileWithMultipart(agentheadimg));
}
inviteRes.save(inviteData);
cache.putConsultInvite(inviteData);
return request(super.createView("redirect:/admin/webim/index.html?snsid=" + inviteData.getSnsaccountid()));
}
@RequestMapping("/profile")
@Menu(type = "app", subtype = "profile", admin = true)
public ModelAndView profile(ModelMap map, HttpServletRequest request, @Valid String snsid) {
CousultInvite coultInvite = OnlineUserProxy.consult(snsid);
if (coultInvite != null) {
logger.info("[profile] snsaccount Id {}, Ai {}, Aifirst {}, Ainame {}, Aisuccess {}, Aiid {}", coultInvite.getSnsaccountid(), coultInvite.isAi(), coultInvite.isAifirst(), coultInvite.getAiname(), coultInvite.getAisuccesstip(), coultInvite.getAiid());
map.addAttribute("inviteData", coultInvite);
map.addAttribute("skillGroups", getSkillGroups(request));
}
map.addAttribute("import", request.getServerPort());
Optional<Channel> snsAccountOpt = snsAccountRes.findBySnsid(snsid);
snsAccountOpt.ifPresent(snsAccount -> map.addAttribute("channel", snsAccount));
map.put("serviceAiList", serviceAiRes.findAll());
return request(super.createView("/admin/webim/profile"));
}
@RequestMapping("/profile/save")
@Menu(type = "admin", subtype = "profile", admin = true)
public ModelAndView saveprofile(HttpServletRequest request, @Valid CousultInvite inviteData, @RequestParam(value = "dialogad", required = false) MultipartFile dialogad) throws IOException {
CousultInvite tempInviteData;
logger.info("[profile/save] snsaccount Id {}, Ai {}, Aifirst {}, Ainame {}, Aisuccess {}, Aiid {}, traceUser {}",
inviteData.getSnsaccountid(),
inviteData.isAi(),
inviteData.isAifirst(),
inviteData.getAiname(),
inviteData.getAisuccesstip(),
inviteData.getAiid(),
inviteData.isTraceuser());
if (inviteData != null && StringUtils.isNotBlank(inviteData.getId())) {
// 从Cache及DB加载consult
tempInviteData = OnlineUserProxy.consult(inviteData.getSnsaccountid());
if (tempInviteData != null) {
tempInviteData.setDialog_name(inviteData.getDialog_name());
tempInviteData.setDialog_address(inviteData.getDialog_address());
tempInviteData.setDialog_phone(inviteData.getDialog_phone());
tempInviteData.setDialog_mail(inviteData.getDialog_mail());
tempInviteData.setDialog_introduction(inviteData.getDialog_introduction());
tempInviteData.setDialog_message(inviteData.getDialog_message());
tempInviteData.setLeavemessage(inviteData.isLeavemessage());
tempInviteData.setLvmopentype(inviteData.getLvmopentype());
tempInviteData.setLvmname(inviteData.isLvmname());
tempInviteData.setLvmphone(inviteData.isLvmphone());
tempInviteData.setLvmemail(inviteData.isLvmemail());
tempInviteData.setLvmaddress(inviteData.isLvmaddress());
tempInviteData.setLvmqq(inviteData.isLvmqq());
tempInviteData.setConsult_skill_fixed(inviteData.isConsult_skill_fixed());
tempInviteData.setConsult_skill_fixed_id(inviteData.getConsult_skill_fixed_id());
tempInviteData.setSkill(inviteData.isSkill());
tempInviteData.setConsult_skill_title(inviteData.getConsult_skill_title());
tempInviteData.setConsult_skill_msg(inviteData.getConsult_skill_msg());
tempInviteData.setConsult_skill_bottomtitle(inviteData.getConsult_skill_bottomtitle());
tempInviteData.setConsult_skill_maxagent(inviteData.getConsult_skill_maxagent());
tempInviteData.setConsult_skill_numbers(inviteData.getConsult_skill_numbers());
tempInviteData.setConsult_skill_agent(inviteData.isConsult_skill_agent());
tempInviteData.setOnlyareaskill(inviteData.isOnlyareaskill());
tempInviteData.setAreaskilltipmsg(inviteData.getAreaskilltipmsg());
tempInviteData.setConsult_info(inviteData.isConsult_info());
tempInviteData.setConsult_info_email(inviteData.isConsult_info_email());
tempInviteData.setConsult_info_name(inviteData.isConsult_info_name());
tempInviteData.setConsult_info_phone(inviteData.isConsult_info_phone());
tempInviteData.setConsult_info_resion(inviteData.isConsult_info_resion());
tempInviteData.setConsult_info_message(inviteData.getConsult_info_message());
tempInviteData.setConsult_info_cookies(inviteData.isConsult_info_cookies());
tempInviteData.setRecordhis(inviteData.isRecordhis());
tempInviteData.setTraceuser(inviteData.isTraceuser());
tempInviteData.setAi(inviteData.isAi());
tempInviteData.setAifirst(inviteData.isAifirst());
tempInviteData.setAimsg(inviteData.getAimsg());
tempInviteData.setAisuccesstip(inviteData.getAisuccesstip());
tempInviteData.setAiname(inviteData.getAiname());
tempInviteData.setAiid(inviteData.getAiid());
tempInviteData.setMaxwordsnum(inviteData.getMaxwordsnum());
tempInviteData.setCtrlenter(inviteData.isCtrlenter());
tempInviteData.setWhitelist_mode(inviteData.isWhitelist_mode());
if (dialogad != null && StringUtils.isNotBlank(dialogad.getName()) && dialogad.getBytes() != null && dialogad.getBytes().length > 0) {
tempInviteData.setDialog_ad(super.saveImageFileWithMultipart(dialogad));
}
// 保存到DB
inviteRes.save(tempInviteData);
inviteData = tempInviteData;
}
} else {
inviteRes.save(inviteData);
}
cache.putConsultInvite(inviteData);
return request(super.createView("redirect:/admin/webim/profile.html?snsid=" + inviteData.getSnsaccountid()));
}
@RequestMapping("/invote")
@Menu(type = "app", subtype = "invote", admin = true)
public ModelAndView invote(ModelMap map, HttpServletRequest request, @Valid String snsid) {
CousultInvite coultInvite = OnlineUserProxy.consult(snsid);
if (coultInvite != null) {
logger.info("[invote] snsaccount Id {}, Ai {}, Aifirst {}, Ainame {}, Aisuccess {}, Aiid {}", coultInvite.getSnsaccountid(), coultInvite.isAi(), coultInvite.isAifirst(), coultInvite.getAiname(), coultInvite.getAisuccesstip(), coultInvite.getAiid());
map.addAttribute("inviteData", coultInvite);
}
map.addAttribute("import", request.getServerPort());
Optional<Channel> snsAccountOpt = snsAccountRes.findBySnsid(snsid);
snsAccountOpt.ifPresent(snsAccount -> map.addAttribute("channel", snsAccount));
return request(super.createView("/admin/webim/invote"));
}
@RequestMapping("/invote/save")
@Menu(type = "admin", subtype = "profile", admin = true)
public ModelAndView saveinvote(HttpServletRequest request, @Valid CousultInvite inviteData, @RequestParam(value = "invotebg", required = false) MultipartFile invotebg) throws IOException {
CousultInvite tempInviteData;
logger.info("[invote/save] snsaccount Id {}, Ai {}, Aifirst {}, Ainame {}, Aisuccess {}, Aiid {}", inviteData.getSnsaccountid(), inviteData.isAi(), inviteData.isAifirst(), inviteData.getAiname(), inviteData.getAisuccesstip(), inviteData.getAiid());
if (inviteData != null && StringUtils.isNotBlank(inviteData.getId())) {
tempInviteData = OnlineUserProxy.consult(inviteData.getSnsaccountid());
if (tempInviteData != null) {
tempInviteData.setConsult_invite_enable(inviteData.isConsult_invite_enable());
tempInviteData.setConsult_invite_content(inviteData.getConsult_invite_content());
tempInviteData.setConsult_invite_accept(inviteData.getConsult_invite_accept());
tempInviteData.setConsult_invite_later(inviteData.getConsult_invite_later());
tempInviteData.setConsult_invite_delay(inviteData.getConsult_invite_delay());
tempInviteData.setConsult_invite_color(inviteData.getConsult_invite_color());
if (invotebg != null && StringUtils.isNotBlank(invotebg.getName()) && invotebg.getBytes() != null && invotebg.getBytes().length > 0) {
tempInviteData.setConsult_invite_bg(super.saveImageFileWithMultipart(invotebg));
}
inviteRes.save(tempInviteData);
inviteData = tempInviteData;
}
} else {
inviteRes.save(inviteData);
}
cache.putConsultInvite(inviteData);
return request(super.createView("redirect:/admin/webim/invote.html?snsid=" + inviteData.getSnsaccountid()));
}
/**
* 获取当前登录者组织下的技能组列表
*
* @param request
* @return
*/
private List<Organ> getSkillGroups(HttpServletRequest request) {
List<Organ> skillgroups = new ArrayList<>();
List<Organ> allgroups = organRes.findAll();
for (Organ o : allgroups) {
if (o.isSkill()) {
skillgroups.add(o);
}
}
return skillgroups;
}
/**
* 获取当前产品下人员信息
*
* @param request
* @return
*/
private List<User> getUsers(HttpServletRequest request) {
List<User> userList = userRes.findByAgentAndDatastatus(true, false);
return userList;
}
}

View File

@ -1,259 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin.config;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.RedisCommand;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.interceptor.UserExperiencePlanInterceptorHandler;
import com.cskefu.cc.model.Dict;
import com.cskefu.cc.model.Secret;
import com.cskefu.cc.model.SysDic;
import com.cskefu.cc.model.SystemConfig;
import com.cskefu.cc.persistence.repository.SecretRepository;
import com.cskefu.cc.persistence.repository.SystemConfigRepository;
import com.cskefu.cc.persistence.repository.SystemMessageRepository;
import com.cskefu.cc.persistence.repository.TemplateRepository;
import com.cskefu.cc.util.Menu;
import com.corundumstudio.socketio.SocketIOServer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/admin/config")
public class SystemConfigController extends Handler {
@Autowired
private SocketIOServer server;
@Autowired
private SystemConfigRepository systemConfigRes;
@Autowired
private RedisCommand redisCommand;
@Autowired
private SystemMessageRepository systemMessageRes;
@Autowired
private SecretRepository secRes;
@Autowired
private TemplateRepository templateRes;
@RequestMapping("/index")
@Menu(type = "admin", subtype = "config", admin = true)
public ModelAndView index(ModelMap map, HttpServletRequest request, @Valid String execute) throws SQLException {
map.addAttribute("server", server);
if (MainContext.hasModule(Constants.CSKEFU_MODULE_ENTIM)) {
map.addAttribute(Constants.CSKEFU_MODULE_ENTIM, true);
}
if (request.getSession().getAttribute(Constants.CSKEFU_SYSTEM_INFOACQ) != null) {
map.addAttribute(
Constants.CSKEFU_MODULE_ENTIM, request.getSession().getAttribute(Constants.CSKEFU_SYSTEM_INFOACQ));
}
map.addAttribute("server", server);
map.addAttribute("imServerStatus", MainContext.getIMServerStatus());
List<Secret> secretConfig = secRes.findAll();
// check out secretConfig
if (secretConfig != null && secretConfig.size() > 0) {
map.addAttribute("secret", secretConfig.get(0));
}
List<SysDic> dicList = Dict.getInstance().getDic(Constants.CSKEFU_SYSTEM_DIC);
SysDic callCenterDic = null, workOrderDic = null, smsDic = null;
for (SysDic dic : dicList) {
if (dic.getCode().equals(Constants.CSKEFU_SYSTEM_CALLCENTER)) {
callCenterDic = dic;
}
if (dic.getCode().equals(Constants.CSKEFU_SYSTEM_WORKORDEREMAIL)) {
workOrderDic = dic;
}
if (dic.getCode().equals(Constants.CSKEFU_SYSTEM_SMSEMAIL)) {
smsDic = dic;
}
}
if (callCenterDic != null) {
map.addAttribute(
"templateList",
templateRes.findByTemplettype(callCenterDic.getId()));
}
if (workOrderDic != null) {
map.addAttribute(
"workOrderList",
templateRes.findByTemplettype(workOrderDic.getId()));
}
if (smsDic != null) {
map.addAttribute("smsList", templateRes.findByTemplettype(smsDic.getId()));
}
map.addAttribute(
"sysMessageList", systemMessageRes.findByMsgtype(MainContext.SystemMessageType.EMAIL.toString()));
if (StringUtils.isNotBlank(execute) && execute.equals("false")) {
map.addAttribute("execute", execute);
}
if (StringUtils.isNotBlank(request.getParameter("msg"))) {
map.addAttribute("msg", request.getParameter("msg"));
}
String userExpTelemetrySetting = redisCommand.get(UserExperiencePlanInterceptorHandler.FLAG_KEY);
if (StringUtils.isEmpty(userExpTelemetrySetting) || StringUtils.equalsIgnoreCase(userExpTelemetrySetting, UserExperiencePlanInterceptorHandler.USER_EXP_PLAN_ON)) {
map.addAttribute("userExpTelemetrySetting", true);
} else {
map.addAttribute("userExpTelemetrySetting", false);
}
return request(super.createView("/admin/config/index"));
}
@RequestMapping("/stopimserver")
@Menu(type = "admin", subtype = "stopimserver", admin = true)
public ModelAndView stopimserver(ModelMap map, HttpServletRequest request, @Valid String confirm) throws SQLException {
boolean execute;
if (execute = MainUtils.secConfirm(secRes, confirm)) {
server.stop();
MainContext.setIMServerStatus(false);
}
return request(super.createView("redirect:/admin/config/index.html?execute=" + execute));
}
@RequestMapping("/startentim")
@Menu(type = "admin", subtype = "startentim", admin = true)
public ModelAndView startentim(ModelMap map, HttpServletRequest request) throws SQLException {
MainContext.enableModule(Constants.CSKEFU_MODULE_ENTIM);
return request(super.createView("redirect:/admin/config/index.html"));
}
@RequestMapping("/stopentim")
@Menu(type = "admin", subtype = "stopentim", admin = true)
public ModelAndView stopentim(ModelMap map, HttpServletRequest request) throws SQLException {
MainContext.removeModule(Constants.CSKEFU_MODULE_ENTIM);
return request(super.createView("redirect:/admin/config/index.html"));
}
/**
* 危险操作请谨慎调用 WebLogic/WebSphere/Oracle等中间件服务器禁止调用
*
* @param map
* @param request
* @return
* @throws SQLException
*/
@RequestMapping("/stop")
@Menu(type = "admin", subtype = "stop", admin = true)
public ModelAndView stop(ModelMap map, HttpServletRequest request, @Valid String confirm) throws SQLException {
boolean execute = false;
if (execute = MainUtils.secConfirm(secRes, confirm)) {
server.stop();
MainContext.setIMServerStatus(false);
System.exit(0);
}
return request(super.createView("redirect:/admin/config/index.html?execute=" + execute));
}
@RequestMapping("/save")
@Menu(type = "admin", subtype = "save", admin = true)
public ModelAndView save(
ModelMap map, HttpServletRequest request,
@Valid SystemConfig config,
BindingResult result,
@RequestParam(value = "keyfile", required = false) MultipartFile keyfile,
@RequestParam(value = "loginlogo", required = false) MultipartFile loginlogo,
@RequestParam(value = "consolelogo", required = false) MultipartFile consolelogo,
@RequestParam(value = "favlogo", required = false) MultipartFile favlogo,
@Valid Secret secret) throws SQLException, IOException, NoSuchAlgorithmException {
SystemConfig systemConfig = systemConfigRes.findOne();
String msg = "0";
if (systemConfig == null) {
config.setCreater(super.getUser(request).getId());
config.setCreatetime(new Date());
systemConfig = config;
} else {
MainUtils.copyProperties(config, systemConfig);
}
if (loginlogo != null && StringUtils.isNotBlank(
loginlogo.getOriginalFilename()) && loginlogo.getOriginalFilename().lastIndexOf(".") > 0) {
systemConfig.setLoginlogo(super.saveImageFileWithMultipart(loginlogo));
}
if (consolelogo != null && StringUtils.isNotBlank(
consolelogo.getOriginalFilename()) && consolelogo.getOriginalFilename().lastIndexOf(".") > 0) {
systemConfig.setConsolelogo(super.saveImageFileWithMultipart(consolelogo));
}
if (favlogo != null && StringUtils.isNotBlank(
favlogo.getOriginalFilename()) && favlogo.getOriginalFilename().lastIndexOf(".") > 0) {
systemConfig.setFavlogo(super.saveImageFileWithMultipart(favlogo));
}
if (secret != null && StringUtils.isNotBlank(secret.getPassword())) {
List<Secret> secretConfig = secRes.findAll();
String repassword = request.getParameter("repassword");
if (StringUtils.isNotBlank(repassword) && repassword.equals(secret.getPassword())) {
if (secretConfig != null && secretConfig.size() > 0) {
Secret tempSecret = secretConfig.get(0);
String oldpass = request.getParameter("oldpass");
if (StringUtils.isNotBlank(oldpass) && MainUtils.md5(oldpass).equals(tempSecret.getPassword())) {
tempSecret.setPassword(MainUtils.md5(secret.getPassword()));
msg = "1";
tempSecret.setEnable(true);
secRes.save(tempSecret);
} else {
msg = "3";
}
} else {
secret.setCreater(super.getUser(request).getId());
secret.setCreatetime(new Date());
secret.setPassword(MainUtils.md5(secret.getPassword()));
secret.setEnable(true);
msg = "1";
secRes.save(secret);
}
} else {
msg = "2";
}
map.addAttribute("msg", msg);
}
// 设置用户体验计划开关
redisCommand.put(UserExperiencePlanInterceptorHandler.FLAG_KEY, config.getUserExpTelemetrySetting() ? UserExperiencePlanInterceptorHandler.USER_EXP_PLAN_ON : UserExperiencePlanInterceptorHandler.USER_EXP_PLAN_OFF);
// 保存到数据库
systemConfigRes.save(systemConfig);
MainContext.getCache().putSystemById("systemConfig", systemConfig);
map.addAttribute("imServerStatus", MainContext.getIMServerStatus());
return request(super.createView("redirect:/admin/config/index.html?msg=" + msg));
}
}

View File

@ -1,178 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin.config;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.Dict;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.SystemMessage;
import com.cskefu.cc.persistence.repository.OrganRepository;
import com.cskefu.cc.persistence.repository.SystemMessageRepository;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
@Controller
@RequestMapping("/admin")
public class SystemMessageController extends Handler {
@Autowired
private SystemMessageRepository systemMessageRepository;
@Autowired
private OrganRepository organRes;
@RequestMapping("/email/index")
@Menu(type = "setting", subtype = "email")
public ModelAndView index(ModelMap map, HttpServletRequest request) throws IOException {
Page<SystemMessage> emails = systemMessageRepository.findByMsgtype("email", PageRequest.of(super.getP(request), super.getPs(request)));
List<Organ> organs = organRes.findAll();
emails.getContent().stream().forEach(p -> {
organs.stream().filter(o -> StringUtils.equals(p.getOrgan(), o.getId())).findAny().ifPresent(o -> p.setOrgan(o.getName()));
});
map.addAttribute("emailList", emails);
return request(super.createView("/admin/email/index"));
}
@RequestMapping("/email/add")
@Menu(type = "admin", subtype = "email")
public ModelAndView add(ModelMap map, HttpServletRequest request) {
map.put("organList", organRes.findAll());
return request(super.createView("/admin/email/add"));
}
@RequestMapping("/email/save")
@Menu(type = "admin", subtype = "user")
public ModelAndView save(HttpServletRequest request, @Valid SystemMessage email) throws NoSuchAlgorithmException {
email.setMsgtype(MainContext.SystemMessageType.EMAIL.toString());
if (!StringUtils.isBlank(email.getSmtppassword())) {
email.setSmtppassword(MainUtils.encryption(email.getSmtppassword()));
}
systemMessageRepository.save(email);
return request(super.createView("redirect:/admin/email/index.html"));
}
@RequestMapping("/email/edit")
@Menu(type = "admin", subtype = "email")
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id) {
map.put("organList", organRes.findAll());
map.addAttribute("email", systemMessageRepository.findById(id).orElse(null));
return request(super.createView("/admin/email/edit"));
}
@RequestMapping("/email/update")
@Menu(type = "admin", subtype = "user", admin = true)
public ModelAndView update(HttpServletRequest request, @Valid SystemMessage email) throws NoSuchAlgorithmException {
SystemMessage temp = systemMessageRepository.findById(email.getId()).orElse(null);
if (email != null) {
email.setCreatetime(temp.getCreatetime());
email.setMsgtype(MainContext.SystemMessageType.EMAIL.toString());
if (!StringUtils.isBlank(email.getSmtppassword())) {
email.setSmtppassword(MainUtils.encryption(email.getSmtppassword()));
} else {
email.setSmtppassword(temp.getSmtppassword());
}
systemMessageRepository.save(email);
}
return request(super.createView("redirect:/admin/email/index.html"));
}
@RequestMapping("/email/delete")
@Menu(type = "admin", subtype = "user")
public ModelAndView delete(HttpServletRequest request, @Valid SystemMessage email) {
SystemMessage temp = systemMessageRepository.findById(email.getId()).orElse(null);
if (email != null) {
systemMessageRepository.delete(temp);
}
return request(super.createView("redirect:/admin/email/index.html"));
}
@RequestMapping("/sms/index")
@Menu(type = "setting", subtype = "sms")
public ModelAndView smsindex(ModelMap map, HttpServletRequest request) throws IOException {
map.addAttribute("smsList", systemMessageRepository.findByMsgtype("sms", PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/admin/sms/index"));
}
@RequestMapping("/sms/add")
@Menu(type = "admin", subtype = "sms")
public ModelAndView smsadd(ModelMap map, HttpServletRequest request) {
map.addAttribute("smsType", Dict.getInstance().getDic("com.dic.sms.type"));
return request(super.createView("/admin/sms/add"));
}
@RequestMapping("/sms/save")
@Menu(type = "admin", subtype = "sms")
public ModelAndView smssave(HttpServletRequest request, @Valid SystemMessage sms) throws NoSuchAlgorithmException {
sms.setMsgtype(MainContext.SystemMessageType.SMS.toString());
if (!StringUtils.isBlank(sms.getSmtppassword())) {
sms.setSmtppassword(MainUtils.encryption(sms.getSmtppassword()));
}
systemMessageRepository.save(sms);
return request(super.createView("redirect:/admin/sms/index.html"));
}
@RequestMapping("/sms/edit")
@Menu(type = "admin", subtype = "sms")
public ModelAndView smsedit(ModelMap map, HttpServletRequest request, @Valid String id) {
map.addAttribute("smsType", Dict.getInstance().getDic("com.dic.sms.type"));
map.addAttribute("sms", systemMessageRepository.findById(id).orElse(null));
return request(super.createView("/admin/sms/edit"));
}
@RequestMapping("/sms/update")
@Menu(type = "admin", subtype = "sms", admin = true)
public ModelAndView smsupdate(HttpServletRequest request, @Valid SystemMessage sms) throws NoSuchAlgorithmException {
SystemMessage temp = systemMessageRepository.findById(sms.getId()).orElse(null);
if (sms != null) {
sms.setCreatetime(temp.getCreatetime());
sms.setMsgtype(MainContext.SystemMessageType.SMS.toString());
if (!StringUtils.isBlank(sms.getSmtppassword())) {
sms.setSmtppassword(MainUtils.encryption(sms.getSmtppassword()));
} else {
sms.setSmtppassword(temp.getSmtppassword());
}
systemMessageRepository.save(sms);
}
return request(super.createView("redirect:/admin/sms/index.html"));
}
@RequestMapping("/sms/delete")
@Menu(type = "admin", subtype = "sms")
public ModelAndView smsdelete(HttpServletRequest request, @Valid SystemMessage sms) {
SystemMessage temp = systemMessageRepository.findById(sms.getId()).orElse(null);
if (sms != null) {
systemMessageRepository.delete(temp);
}
return request(super.createView("redirect:/admin/sms/index.html"));
}
}

View File

@ -1,301 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin.system;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.hibernate.BaseService;
import com.cskefu.cc.persistence.repository.MetadataRepository;
import com.cskefu.cc.persistence.repository.SysDicRepository;
import com.cskefu.cc.persistence.repository.TablePropertiesRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.metadata.DatabaseMetaDataHandler;
import com.cskefu.cc.util.metadata.UKColumnMetadata;
import com.cskefu.cc.util.metadata.UKTableMetaData;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@Controller
@RequestMapping("/admin/metadata")
public class MetadataController extends Handler {
@Autowired
private MetadataRepository metadataRes;
@Autowired
private BaseService<?> service;
@Autowired
private SysDicRepository sysDicRes;
@Autowired
private TablePropertiesRepository tablePropertiesRes;
private static final Logger logger = LoggerFactory.getLogger(MetadataController.class);
@Autowired
@PersistenceContext
private EntityManager em;
@RequestMapping("/index")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView index(ModelMap map, HttpServletRequest request) throws SQLException {
map.addAttribute("metadataList", metadataRes.findAll(PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/admin/system/metadata/index"));
}
@RequestMapping("/edit")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id) {
map.addAttribute("metadata", metadataRes.findById(id).orElse(null));
return request(super.createView("/admin/system/metadata/edit"));
}
@RequestMapping("/update")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView update(ModelMap map, HttpServletRequest request, @Valid MetadataTable metadata) throws SQLException {
MetadataTable table = metadataRes.findById(metadata.getId()).orElse(null);
table.setName(metadata.getName());
table.setFromdb(metadata.isFromdb());
table.setListblocktemplet(metadata.getListblocktemplet());
table.setPreviewtemplet(metadata.getPreviewtemplet());
metadataRes.save(table);
return request(super.createView("redirect:/admin/metadata/index.html"));
}
@RequestMapping("/properties/edit")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView propertiesedit(ModelMap map, HttpServletRequest request, @Valid String id) {
map.addAttribute("tp", tablePropertiesRes.findById(id).orElse(null));
map.addAttribute("sysdicList", sysDicRes.findByParentid("0"));
map.addAttribute("dataImplList", Dict.getInstance().getDic("com.dic.data.impl"));
return request(super.createView("/admin/system/metadata/tpedit"));
}
@RequestMapping("/properties/update")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView propertiesupdate(ModelMap map, HttpServletRequest request, @Valid TableProperties tp) throws SQLException {
TableProperties tableProperties = tablePropertiesRes.findById(tp.getId()).orElse(null);
tableProperties.setName(tp.getName());
tableProperties.setSeldata(tp.isSeldata());
tableProperties.setSeldatacode(tp.getSeldatacode());
tableProperties.setReffk(tp.isReffk());
tableProperties.setReftbid(tp.getReftbid());
tableProperties.setDefaultvaluetitle(tp.getDefaultvaluetitle());
tableProperties.setDefaultfieldvalue(tp.getDefaultfieldvalue());
tableProperties.setModits(tp.isModits());
tableProperties.setPk(tp.isPk());
tableProperties.setSystemfield(tp.isSystemfield());
tableProperties.setImpfield(tp.isImpfield());
tablePropertiesRes.save(tableProperties);
return request(super.createView("redirect:/admin/metadata/table.html?id=" + tableProperties.getDbtableid()));
}
@RequestMapping("/delete")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView delete(ModelMap map, HttpServletRequest request, @Valid String id) throws SQLException {
MetadataTable table = metadataRes.findById(id).orElse(null);
metadataRes.delete(table);
return request(super.createView("redirect:/admin/metadata/index.html"));
}
@RequestMapping("/batdelete")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView batdelete(ModelMap map, HttpServletRequest request, @Valid String[] ids) throws SQLException {
if (ids != null && ids.length > 0) {
metadataRes.deleteAll(metadataRes.findAllById(Arrays.asList(ids)));
}
return request(super.createView("redirect:/admin/metadata/index.html"));
}
@RequestMapping("/properties/delete")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView propertiesdelete(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String tbid) throws SQLException {
TableProperties prop = tablePropertiesRes.findById(id).orElse(null);
tablePropertiesRes.delete(prop);
return request(super.createView("redirect:/admin/metadata/table.html?id=" + (!StringUtils.isBlank(tbid) ? tbid : prop.getDbtableid())));
}
@RequestMapping("/properties/batdelete")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView propertiesbatdelete(ModelMap map, HttpServletRequest request, @Valid String[] ids, @Valid String tbid) throws SQLException {
if (ids != null && ids.length > 0) {
tablePropertiesRes.deleteAll(tablePropertiesRes.findAllById(Arrays.asList(ids)));
}
return request(super.createView("redirect:/admin/metadata/table.html?id=" + tbid));
}
@RequestMapping("/table")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView table(ModelMap map, HttpServletRequest request, @Valid String id) throws SQLException {
map.addAttribute("propertiesList", tablePropertiesRes.findByDbtableid(id));
map.addAttribute("tbid", id);
map.addAttribute("table", metadataRes.findById(id).orElse(null));
return request(super.createView("/admin/system/metadata/table"));
}
@RequestMapping("/imptb")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView imptb(final ModelMap map, HttpServletRequest request) throws Exception {
Session session = (Session) em.getDelegate();
session.doWork(connection -> {
try {
map.addAttribute("tablesList",
DatabaseMetaDataHandler.getTables(connection));
} catch (Exception e) {
logger.error("When import metadata", e);
}
});
return request(super
.createView("/admin/system/metadata/imptb"));
}
@RequestMapping("/imptbsave")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView imptb(ModelMap map, HttpServletRequest request, final @Valid String[] tables) throws Exception {
final User user = super.getUser(request);
if (tables != null && tables.length > 0) {
Session session = (Session) em.getDelegate();
session.doWork(
connection -> {
try {
for (String table : tables) {
int count = metadataRes.countByTablename(table);
if (count == 0) {
MetadataTable metaDataTable = new MetadataTable();
//当前记录没有被添加过进行正常添加
metaDataTable.setTablename(table);
metaDataTable.setId(MainUtils.md5(metaDataTable.getTablename()));
metaDataTable.setTabledirid("0");
metaDataTable.setCreater(user.getId());
metaDataTable.setCreatername(user.getUsername());
metaDataTable.setName(table);
metaDataTable.setUpdatetime(new Date());
metaDataTable.setCreatetime(new Date());
metadataRes.save(processMetadataTable(DatabaseMetaDataHandler.getTable(connection, metaDataTable.getTablename()), metaDataTable));
}
}
} catch (Exception e) {
logger.error("When import metadata", e);
}
}
);
}
return request(super.createView("redirect:/admin/metadata/index.html"));
}
private MetadataTable processMetadataTable(UKTableMetaData metaData, MetadataTable table) {
table.setTableproperty(new ArrayList<>());
if (metaData != null) {
for (UKColumnMetadata colum : metaData.getColumnMetadatas()) {
TableProperties tablePorperties = new TableProperties(colum.getName().toLowerCase(), colum.getTypeName(), colum.getColumnSize(), metaData.getName().toLowerCase());
tablePorperties.setDatatypecode(0);
tablePorperties.setLength(colum.getColumnSize());
tablePorperties.setDatatypename(getDataTypeName(colum.getTypeName()));
tablePorperties.setName(colum.getTitle().toLowerCase());
if (tablePorperties.getFieldname().equals("create_time") || tablePorperties.getFieldname().equals("createtime") || tablePorperties.getFieldname().equals("update_time")) {
tablePorperties.setDatatypename(getDataTypeName("datetime"));
}
if (colum.getName().startsWith("field")) {
tablePorperties.setFieldstatus(false);
} else {
tablePorperties.setFieldstatus(true);
}
table.getTableproperty().add(tablePorperties);
}
table.setTablename(table.getTablename().toLowerCase());//转小写
}
return table;
}
public String getDataTypeName(String type) {
String typeName = "text";
if (type.contains("varchar")) {
typeName = "text";
} else if (type.equalsIgnoreCase("date") || type.equalsIgnoreCase("datetime")) {
typeName = type.toLowerCase();
} else if (type.equalsIgnoreCase("int") || type.equalsIgnoreCase("float") || type.equalsIgnoreCase("number")) {
typeName = "number";
}
return typeName;
}
@RequestMapping("/clean")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView clean(ModelMap map, HttpServletRequest request, @Valid String id) throws SQLException, BeansException, ClassNotFoundException {
if (!StringUtils.isBlank(id)) {
MetadataTable table = metadataRes.findById(id).orElse(null);
}
return request(super.createView("redirect:/admin/metadata/index.html"));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@RequestMapping("/synctoes")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView synctoes(ModelMap map, HttpServletRequest request, @Valid String id) throws SQLException, BeansException, ClassNotFoundException {
if (!StringUtils.isBlank(id)) {
MetadataTable table = metadataRes.findById(id).orElse(null);
if (table.isFromdb() && !StringUtils.isBlank(table.getListblocktemplet())) {
SysDic dic = Dict.getInstance().getDicItem(table.getListblocktemplet());
}
}
return request(super.createView("redirect:/admin/metadata/index.html"));
}
@SuppressWarnings({"rawtypes"})
@RequestMapping("/synctodb")
@Menu(type = "admin", subtype = "metadata", admin = true)
public ModelAndView synctodb(ModelMap map, HttpServletRequest request, @Valid String id) throws SQLException, BeansException, ClassNotFoundException {
if (!StringUtils.isBlank(id)) {
MetadataTable table = metadataRes.findById(id).orElse(null);
if (table.isFromdb() && !StringUtils.isBlank(table.getListblocktemplet())) {
SysDic dic = Dict.getInstance().getDicItem(table.getListblocktemplet());
}
}
return request(super.createView("redirect:/admin/metadata/index.html"));
}
}

View File

@ -1,244 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin.system;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.SysDic;
import com.cskefu.cc.persistence.repository.SysDicRepository;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/admin/sysdic")
public class SysDicController extends Handler {
@Autowired
private SysDicRepository sysDicRes;
@Autowired
private Cache cache;
@RequestMapping("/index")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView index(ModelMap map, HttpServletRequest request) {
map.addAttribute("sysDicList", sysDicRes.findByParentid("0", PageRequest.of(super.getP(request), super.getPs(request), Direction.DESC, "createtime")));
return request(super.createView("/admin/system/sysdic/index"));
}
@RequestMapping("/add")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView add(ModelMap map, HttpServletRequest request) {
return request(super.createView("/admin/system/sysdic/add"));
}
@RequestMapping("/save")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView save(HttpServletRequest request, @Valid SysDic dic) {
List<SysDic> sysDicList = sysDicRes.findByCodeOrName(dic.getCode(), dic.getName());
String msg = null;
if (sysDicList.size() == 0) {
dic.setParentid("0");
dic.setHaschild(true);
dic.setCreater(super.getUser(request).getId());
dic.setCreatetime(new Date());
sysDicRes.save(dic);
reloadSysDicItem(dic);
} else {
msg = "exist";
}
return request(super.createView("redirect:/admin/sysdic/index.html" + (msg != null ? "?msg=" + msg : "")));
}
@RequestMapping("/edit")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String p) {
map.addAttribute("sysDic", sysDicRes.findById(id).orElse(null));
map.addAttribute("p", p);
return request(super.createView("/admin/system/sysdic/edit"));
}
@RequestMapping("/update")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView update(HttpServletRequest request, @Valid SysDic dic, @Valid String p) {
List<SysDic> sysDicList = sysDicRes.findByCodeOrName(dic.getCode(), dic.getName());
if (sysDicList.size() == 0 || (sysDicList.size() == 1 && sysDicList.get(0).getId().equals(dic.getId()))) {
SysDic sysDic = sysDicRes.findById(dic.getId()).orElse(null);
sysDic.setName(dic.getName());
sysDic.setCode(dic.getCode());
sysDic.setCtype(dic.getCtype());
sysDic.setIconskin(dic.getIconskin());
sysDic.setIconstr(dic.getIconstr());
sysDic.setDescription(dic.getDescription());
sysDicRes.save(sysDic);
reloadSysDicItem(sysDic);
}
return request(super.createView("redirect:/admin/sysdic/index.html?p=" + p));
}
@RequestMapping("/delete")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView delete(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String p) {
SysDic sysDic = sysDicRes.findById(id).orElse(null);
sysDicRes.deleteAll(sysDicRes.findByDicid(id));
sysDicRes.delete(sysDic);
reloadSysDicItem(sysDic);
return request(super.createView("redirect:/admin/sysdic/index.html?p=" + p));
}
@RequestMapping("/dicitem")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitem(ModelMap map, HttpServletRequest request, @Valid String id) {
map.addAttribute("sysDic", sysDicRes.findById(id).orElse(null));
map.addAttribute("sysDicList", sysDicRes.findByParentid(id, PageRequest.of(super.getP(request), super.getPs(request), Direction.DESC, "createtime")));
return request(super.createView("/admin/system/sysdic/dicitem"));
}
@RequestMapping("/dicitem/add")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitemadd(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String p) {
map.addAttribute("sysDic", sysDicRes.findById(id).orElse(null));
map.addAttribute("p", p);
return request(super.createView("/admin/system/sysdic/dicitemadd"));
}
@RequestMapping("/dicitem/save")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitemsave(HttpServletRequest request, @Valid SysDic dic, @Valid String p) {
List<SysDic> sysDicList = sysDicRes.findByDicidAndName(dic.getDicid(), dic.getName());
String msg = null;
if (sysDicList.size() == 0) {
dic.setHaschild(true);
dic.setCreater(super.getUser(request).getId());
dic.setCreatetime(new Date());
sysDicRes.save(dic);
reloadSysDicItem(dic);
} else {
msg = "exist";
}
return request(super.createView("redirect:/admin/sysdic/dicitem.html?id=" + dic.getParentid() + (msg != null ? "&p=" + p + "&msg=" + msg : "")));
}
/**
* 更新系统词典缓存
* @param dic
*/
public void reloadSysDicItem(final SysDic dic) {
cache.putSysDic(dic.getId(), dic);
if (StringUtils.isNotBlank(dic.getDicid())) { // 该数据为某词典的子项
// 首先获得根词典
SysDic root = cache.findOneSysDicById(dic.getDicid());
// 获得其目前的全部子项此处是从数据库提取
// 而不是缓存因为缓存的数据已经旧了
List<SysDic> sysDicList = sysDicRes.findByDicid(dic.getDicid());
// 更新其全部子项数据到缓存
cache.putSysDic(root.getCode(), sysDicList);
} else if (dic.getParentid().equals("0")) {
// 如果该数据为某根词典
List<SysDic> sysDicList = sysDicRes.findByDicid(dic.getId());
cache.putSysDic(dic.getCode(), sysDicList);
}
}
@RequestMapping("/dicitem/batadd")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitembatadd(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String p) {
map.addAttribute("sysDic", sysDicRes.findById(id).orElse(null));
map.addAttribute("p", p);
return request(super.createView("/admin/system/sysdic/batadd"));
}
@RequestMapping("/dicitem/batsave")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitembatsave(HttpServletRequest request, @Valid SysDic sysDic, @Valid String content, @Valid String p) {
String[] dicitems = content.split("[\n\r\n]");
int count = 0;
for (String dicitem : dicitems) {
String[] dicValues = dicitem.split("[\t, ;]{1,}");
if (dicValues.length == 2 && dicValues[0].length() > 0 && dicValues[1].length() > 0) {
SysDic dic = new SysDic();
dic.setName(dicValues[0]);
dic.setCode(dicValues[1]);
dic.setCreater(super.getUser(request).getId());
dic.setParentid(sysDic.getParentid());
dic.setDicid(sysDic.getDicid());
dic.setSortindex(++count);
dic.setCreatetime(new Date());
dic.setUpdatetime(new Date());
if (sysDicRes.findByDicidAndName(dic.getDicid(), dic.getName()).size() == 0) {
sysDicRes.save(dic);
}
}
}
reloadSysDicItem(sysDicRes.findById(sysDic.getParentid()).orElse(null));
return request(super.createView("redirect:/admin/sysdic/dicitem.html?id=" + sysDic.getParentid() + "&p=" + p));
}
@RequestMapping("/dicitem/edit")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitemedit(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String p) {
map.addAttribute("sysDic", sysDicRes.findById(id).orElse(null));
map.addAttribute("p", p);
return request(super.createView("/admin/system/sysdic/dicitemedit"));
}
@RequestMapping("/dicitem/update")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitemupdate(HttpServletRequest request, @Valid SysDic dic, @Valid String p) {
List<SysDic> sysDicList = sysDicRes.findByDicidAndName(dic.getDicid(), dic.getName());
if (sysDicList.size() == 0 || (sysDicList.size() == 1 && sysDicList.get(0).getId().equals(dic.getId()))) {
SysDic sysDic = sysDicRes.findById(dic.getId()).orElse(null);
sysDic.setName(dic.getName());
sysDic.setCode(dic.getCode());
sysDic.setCtype(dic.getCtype());
sysDic.setIconskin(dic.getIconskin());
sysDic.setIconstr(dic.getIconstr());
sysDic.setDiscode(dic.isDiscode());
sysDic.setDescription(dic.getDescription());
sysDicRes.save(sysDic);
reloadSysDicItem(sysDic);
}
return request(super.createView("redirect:/admin/sysdic/dicitem.html?id=" + dic.getParentid() + "&p=" + p));
}
@RequestMapping("/dicitem/delete")
@Menu(type = "admin", subtype = "sysdic")
public ModelAndView dicitemdelete(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String p) {
sysDicRes.deleteAll(sysDicRes.findByDicid(id));
SysDic dic = sysDicRes.findById(id).orElse(null);
sysDicRes.delete(dic);
reloadSysDicItem(dic);
return request(super.createView("redirect:/admin/sysdic/dicitem.html?id=" + dic.getParentid() + "&p=" + p));
}
}

View File

@ -1,189 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.admin.system;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.Dict;
import com.cskefu.cc.model.SysDic;
import com.cskefu.cc.model.Template;
import com.cskefu.cc.persistence.repository.SysDicRepository;
import com.cskefu.cc.persistence.repository.TemplateRepository;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@Controller
@RequestMapping("/admin/template")
public class TemplateController extends Handler {
@Autowired
private TemplateRepository templateRes;
@Autowired
private SysDicRepository dicRes;
@Autowired
private Cache cache;
@RequestMapping("/index")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView index(ModelMap map, HttpServletRequest request) {
map.addAttribute("sysDicList", Dict.getInstance().getDic(Constants.CSKEFU_SYSTEM_DIC));
return request(super.createView("/admin/system/template/index"));
}
@RequestMapping("/expall")
@Menu(type = "admin", subtype = "template", admin = true)
public void expall(ModelMap map, HttpServletRequest request, HttpServletResponse response) throws Exception {
List<Template> templateList = templateRes.findAll();
response.setHeader("content-disposition", "attachment;filename=CSKeFu-Template-Export-" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".data");
response.getOutputStream().write(MainUtils.toBytes(templateList));
return;
}
@RequestMapping("/imp")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView imp(ModelMap map, HttpServletRequest request) {
return request(super.createView("/admin/system/template/imp"));
}
@SuppressWarnings("unchecked")
@RequestMapping("/impsave")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView impsave(ModelMap map, HttpServletRequest request, @RequestParam(value = "dataFile", required = false) MultipartFile dataFile) throws Exception {
if (dataFile != null && dataFile.getSize() > 0) {
List<Template> templateList = (List<Template>) MainUtils.toObject(dataFile.getBytes());
if (templateList != null && templateList.size() > 0) {
templateRes.deleteInBatch(templateList);
for (Template template : templateList) {
templateRes.save(template);
}
}
}
return request(super.createView("redirect:/admin/template/index.html"));
}
@RequestMapping("/list")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView list(ModelMap map, HttpServletRequest request, @Valid String type) {
map.addAttribute("sysDic", dicRes.findById(type).orElse(null));
map.addAttribute("templateList", templateRes.findByTemplettype(type));
return request(super.createView("/admin/system/template/list"));
}
@RequestMapping("/add")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView add(ModelMap map, HttpServletRequest request, @Valid String type) {
map.addAttribute("sysDic", dicRes.findById(type).orElse(null));
return request(super.createView("/admin/system/template/add"));
}
@RequestMapping("/save")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView save(HttpServletRequest request, @Valid Template template) {
template.setCreatetime(new Date());
SysDic dic = dicRes.findById(template.getTemplettype()).orElse(null);
if (dic != null && StringUtils.isBlank(template.getCode())) {
template.setCode(dic.getCode());
}
templateRes.save(template);
return request(super.createView("redirect:/admin/template/list.html?type=" + template.getTemplettype()));
}
@RequestMapping("/edit")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String type) {
map.addAttribute("sysDic", dicRes.findById(type).orElse(null));
map.addAttribute("template", templateRes.findById(id).orElse(null));
return request(super.createView("/admin/system/template/edit"));
}
@RequestMapping("/update")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView update(HttpServletRequest request, @Valid Template template) {
Template oldTemplate = templateRes.findById(template.getId()).orElse(null);
if (oldTemplate != null) {
SysDic dic = dicRes.findById(oldTemplate.getTemplettype()).orElse(null);
if (dic != null) {
oldTemplate.setCode(dic.getCode());
}
if (!StringUtils.isBlank(template.getCode())) {
oldTemplate.setCode(template.getCode());
}
oldTemplate.setName(template.getName());
oldTemplate.setLayoutcols(template.getLayoutcols());
oldTemplate.setIconstr(template.getIconstr());
oldTemplate.setDatatype(template.getDatatype());
oldTemplate.setCharttype(template.getCharttype());
templateRes.save(oldTemplate);
cache.deleteSystembyId(template.getId());
}
return request(super.createView("redirect:/admin/template/list.html?type=" + template.getTemplettype()));
}
@RequestMapping("/code")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView code(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String type) {
map.addAttribute("sysDic", dicRes.findById(type).orElse(null));
map.addAttribute("template", templateRes.findById(id).orElse(null));
return request(super.createView("/admin/system/template/code"));
}
@RequestMapping("/codesave")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView codesave(HttpServletRequest request, @Valid Template template) {
Template oldTemplate = templateRes.findById(template.getId()).orElse(null);
if (oldTemplate != null) {
oldTemplate.setTemplettext(template.getTemplettext());
oldTemplate.setTemplettitle(template.getTemplettitle());
templateRes.save(oldTemplate);
cache.deleteSystembyId(template.getId());
}
return request(super.createView("redirect:/admin/template/list.html?type=" + template.getTemplettype()));
}
@RequestMapping("/delete")
@Menu(type = "admin", subtype = "template", admin = true)
public ModelAndView delete(HttpServletRequest request, @Valid Template template) {
if (template != null) {
templateRes.delete(template);
cache.deleteSystembyId(template.getId());
}
return request(super.createView("redirect:/admin/template/list.html?type=" + template.getTemplettype()));
}
}

View File

@ -1,385 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.acd.ACDAgentDispatcher;
import com.cskefu.cc.acd.ACDAgentService;
import com.cskefu.cc.acd.basic.ACDComposeContext;
import com.cskefu.cc.acd.basic.ACDMessageHelper;
import com.cskefu.cc.basic.MainContext.*;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.exception.CSKefuException;
import com.cskefu.cc.model.*;
import com.cskefu.cc.peer.PeerSyncIM;
import com.cskefu.cc.persistence.repository.AgentServiceRepository;
import com.cskefu.cc.persistence.repository.AgentUserRepository;
import com.cskefu.cc.persistence.repository.UserRepository;
import com.cskefu.cc.proxy.AgentUserProxy;
import com.cskefu.cc.socketio.message.Message;
import com.cskefu.cc.util.Menu;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.Date;
import java.util.List;
/**
* ACD服务 获取当前对话中的访客
*/
@RestController
@RequestMapping("/api/agentuser")
public class ApiAgentUserController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(ApiAgentUserController.class);
@Autowired
private ACDMessageHelper acdMessageHelper;
@Autowired
private AgentUserProxy agentUserProxy;
@Autowired
private ACDAgentService acdAgentService;
@Autowired
private Cache cache;
@Autowired
@Lazy
private PeerSyncIM peerSyncIM;
@Autowired
private AgentUserRepository agentUserRes;
@Autowired
private UserRepository userRes;
@Autowired
private AgentServiceRepository agentServiceRes;
@Autowired
private ACDAgentDispatcher acdAgentDispatcher;
/**
* 获取当前对话中的访客
* 坐席相关 RestAPI
*
* @param request
* @return
*/
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "agentuser", access = true)
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body, @Valid String q) {
logger.info("[operations] body {}, q {}", body, q);
final JsonObject j = StringUtils.isBlank(body) ? (new JsonObject()) : (new JsonParser()).parse(
body).getAsJsonObject();
JsonObject json = new JsonObject();
HttpHeaders headers = RestUtils.header();
if (!j.has("ops")) {
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "inserv":
json = inserv(request, j);
break;
case "withdraw":
json = withdraw(request, j);
break;
case "end":
json = end(request, j);
break;
case "transout":
json = transout(request, j);
break;
default:
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的操作。");
}
}
return new ResponseEntity<>(json.toString(), headers, HttpStatus.OK);
}
/**
* 执行坐席转接
* 将会话转接给别人
*
* @param request
* @param payload
* @return
*/
private JsonObject transout(final HttpServletRequest request, final JsonObject payload) {
logger.info("[transout] payload ", payload.toString());
final User logined = super.getUser(request);
JsonObject resp = new JsonObject();
/**
* 必填参数
*/
// 目标坐席
final String transAgentId = payload.get("agentno").getAsString();
// 当前会话的ID
final String agentUserId = payload.get("agentUserId").getAsString();
// 坐席服务ID
final String agentServiceId = payload.get("agentServiceId").getAsString();
if (StringUtils.isNotBlank(agentUserId) &&
StringUtils.isNotBlank(transAgentId) &&
StringUtils.isNotBlank(agentServiceId)) {
final User targetAgent = userRes.findById(transAgentId).orElse(null);
final AgentService agentService = agentServiceRes.findById(agentServiceId).orElse(null);
/**
* 更新AgentUser
*/
final AgentUser agentUser = agentUserProxy.findOne(agentUserId).orElseGet(null);
if (agentUser != null) {
final AgentUserAudit agentAudits = cache.findOneAgentUserAuditById(agentUserId).orElseGet(
null);
// 当前服务于访客的坐席
final String currentAgentno = agentUser.getAgentno();
// 当前访客的ID
final String userId = agentUser.getUserid();
logger.info(
"[transout] agentuserid {} \n target agent id {}, \n current agent id {}, onlineuserid {}",
agentUserId, transAgentId, currentAgentno, userId);
// 检查权限
if ((!logined.isAdmin()) && (!StringUtils.equals(
agentUser.getAgentno(),
logined.getId())) && (!isTransPermissionAllowed(
agentAudits, logined))) {
// 1. 不是超级用户2. 也是不是会话的所有者; 3. 也不是坐席监控人员
logger.info("[end] Permission not fulfill.");
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Permission denied.");
return resp;
}
agentUser.setAgentno(transAgentId);
agentUser.setAgentname(targetAgent.getUname());
agentUserRes.save(agentUser);
/**
* 坐席状态
*/
// 转接目标坐席
final AgentStatus transAgentStatus = cache.findOneAgentStatusByAgentno(transAgentId);
// 转接源坐席
final AgentStatus currentAgentStatus = cache.findOneAgentStatusByAgentno(currentAgentno);
if (StringUtils.equals(
AgentUserStatusEnum.INSERVICE.toString(),
agentUser.getStatus())) { //转接 发送消息给 目标坐席
// 更新当前坐席的服务访客列表
if (currentAgentStatus != null) {
cache.deleteOnlineUserIdFromAgentStatusByUseridAndAgentno(userId, currentAgentno);
agentUserProxy.updateAgentStatus(currentAgentStatus);
}
if (transAgentStatus != null) {
agentService.setAgentno(transAgentId);
agentService.setAgentusername(transAgentStatus.getUsername());
}
// 转接坐席提示消息
Message outMessage = new Message();
outMessage.setMessage(
acdMessageHelper.getSuccessMessage(agentService, agentUser.getChanneltype()));
outMessage.setMessageType(MediaType.TEXT.toString());
outMessage.setCalltype(CallType.IN.toString());
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
outMessage.setAgentUser(agentUser);
outMessage.setAgentService(agentService);
if (StringUtils.isNotBlank(agentUser.getUserid())) {
peerSyncIM.send(
ReceiverType.VISITOR,
ChannelType.toValue(agentUser.getChanneltype()),
agentUser.getAppid(),
MessageType.STATUS,
agentUser.getUserid(),
outMessage,
true);
}
// 通知转接消息给新坐席
outMessage.setChannelMessage(agentUser);
outMessage.setAgentUser(agentUser);
peerSyncIM.send(
ReceiverType.AGENT, ChannelType.WEBIM,
agentUser.getAppid(), MessageType.NEW, agentService.getAgentno(),
outMessage, true);
// 通知消息给前坐席
if (!StringUtils.equals(logined.getId(), currentAgentno)) {
// 如果当前坐席不是登录用户因为登录用户会从RestAPI返回转接的结果
// 该登录用户可能是坐席监控或当前坐席那么如果是坐席监控就有必要
// 通知前坐席这个事件
peerSyncIM.send(ReceiverType.AGENT, ChannelType.WEBIM, agentUser.getAppid(),
MessageType.TRANSOUT,
currentAgentno, outMessage, true);
}
}
if (agentService != null) {
agentService.setAgentno(transAgentId);
if (payload.has("memo") && StringUtils.isNotBlank(payload.get("memo").getAsString())) {
agentService.setTransmemo(payload.get("memo").getAsString());
}
agentService.setTrans(true);
agentService.setTranstime(new Date());
agentServiceRes.save(agentService);
}
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.addProperty(RestUtils.RESP_KEY_DATA, "success");
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Can not find agent user.");
}
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_5);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Invalid params.");
}
return resp;
}
/**
* 结束对话
* 如果当前对话属于登录用户或登录用户为超级用户则可以结束这个对话
*
* @param request
* @param payload
* @return
*/
private JsonObject end(final HttpServletRequest request, final JsonObject payload) {
logger.info("[end] payload {}", payload.toString());
final User logined = super.getUser(request);
JsonObject resp = new JsonObject();
final AgentUser agentUser = agentUserRes.findById(payload.get("id").getAsString()).orElse(null);
if (agentUser != null) {
if ((StringUtils.equals(
logined.getId(), agentUser.getAgentno()) || logined.isAdmin())) {
// 删除访客-坐席关联关系包括缓存
try {
acdAgentService.finishAgentUser(agentUser);
} catch (CSKefuException e) {
// 未能删除成功
logger.error("[end]", e);
}
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.addProperty(RestUtils.RESP_KEY_DATA, "success");
} else {
logger.info("[end] Permission not fulfill.");
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Permission denied.");
}
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Agent User not found.");
}
return resp;
}
/**
* 撤退一个坐席
* 将当前坐席服务中的访客分配给其他就绪的坐席
*
* @param request
* @param j
* @return
*/
private JsonObject withdraw(final HttpServletRequest request, final JsonObject j) {
JsonObject resp = new JsonObject();
ACDComposeContext ctx = new ACDComposeContext();
ctx.setAgentno(super.getUser(request).getId());
acdAgentDispatcher.dequeue(ctx);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
return resp;
}
/**
* 获得当前访客服务中的访客信息
* 获取当前正在对话的访客信息包含多种渠道来源的访客
*
* @param request
* @param j
* @return
*/
private JsonObject inserv(final HttpServletRequest request, final JsonObject j) {
JsonObject resp = new JsonObject();
JsonArray data = new JsonArray();
List<AgentUser> lis = cache.findInservAgentUsersByAgentno(
super.getUser(request).getId());
for (final AgentUser au : lis) {
JsonObject obj = new JsonObject();
obj.addProperty("id", au.getId());
obj.addProperty("userid", au.getUserid());
obj.addProperty("status", au.getStatus());
obj.addProperty("agentno", au.getAgentno());
obj.addProperty("channel", au.getChanneltype());
obj.addProperty("nickname", au.getNickname());
data.add(obj);
}
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.add("data", data);
return resp;
}
/**
* 检查是否具备该会话的坐席监控权限
*
* @param agentUserAudit
* @param user
* @return
*/
private boolean isTransPermissionAllowed(final AgentUserAudit agentUserAudit, final User user) {
if (agentUserAudit != null && agentUserAudit.getSubscribers().containsKey(user.getId())) {
return true;
}
return false;
}
}

View File

@ -1,126 +0,0 @@
/**
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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 2018-Jun. 2023 Chatopera Inc. <https://www.chatopera.com>. All rights reserved.
*/
package com.cskefu.cc.controller.api;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.model.InviteRecord;
import com.cskefu.cc.model.PassportWebIMUser;
import com.cskefu.cc.persistence.repository.InviteRecordRepository;
import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository;
import com.cskefu.cc.proxy.OnlineUserProxy;
import com.cskefu.cc.util.Menu;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/apps")
public class ApiAppsController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(ApiAppsController.class);
@Autowired
private PassportWebIMUserRepository onlineUserRes;
@Autowired
private InviteRecordRepository inviteRecordRes;
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "apps", access = true)
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body, @Valid String q) {
logger.info("[operations] body {}, q {}", body, q);
final JsonObject j = StringUtils.isBlank(body) ? (new JsonObject()) : (new JsonParser()).parse(
body).getAsJsonObject();
JsonObject json = new JsonObject();
HttpHeaders headers = RestUtils.header();
if (!j.has("ops")) {
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "invite":
json = invite(request, j);
break;
default:
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的操作。");
}
}
return new ResponseEntity<>(json.toString(), headers, HttpStatus.OK);
}
/**
* 邀请访客加入会话
*
* @param request
* @param j
* @return
*/
private JsonObject invite(final HttpServletRequest request, final JsonObject j) {
JsonObject resp = new JsonObject();
final String agentno = super.getUser(request).getId();
final String userid = j.get("userid").getAsString();
logger.info("[invite] agentno {} invite onlineUser {}", agentno, userid);
PassportWebIMUser passportWebIMUser = OnlineUserProxy.onlineuser(userid);
if (passportWebIMUser != null) {
logger.info("[invite] userid {}, agentno {}", userid, agentno);
passportWebIMUser.setInvitestatus(MainContext.OnlineUserInviteStatus.INVITE.toString());
passportWebIMUser.setInvitetimes(passportWebIMUser.getInvitetimes() + 1);
onlineUserRes.save(passportWebIMUser);
InviteRecord record = new InviteRecord();
record.setAgentno(super.getUser(request).getId());
// 对于OnlineUser, 其userId与id是相同的
record.setUserid(passportWebIMUser.getUserid());
record.setAppid(passportWebIMUser.getAppid());
inviteRecordRes.save(record);
logger.info("[invite] new invite record {} of onlineUser id {} saved.", record.getId(), passportWebIMUser.getId());
try {
OnlineUserProxy.sendWebIMClients(passportWebIMUser.getUserid(), "invite:" + agentno);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
} catch (Exception e) {
logger.error("[invite] error", e);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "online user is offline.");
}
} else {
// 找不到的情况不可能发生因为坐席看到的Onlineuser信息是从数据库查找到的
logger.info("[invite] can not find onlineUser {} in database.", userid);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "online user is invalid, not found in db or cache.");
}
return resp;
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.persistence.repository.ChatMessageRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
/**
* 获取对话内容
* 获取访客对话的内容
*/
@RestController
@RequestMapping("/api/chatmessage")
public class ApiChatMessageController extends Handler {
@Autowired
private ChatMessageRepository chatMessageRes;
/**
* 获取访客对话内容
*
* @param request
* @param serviceid AgentServiceID
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "agentuser", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @Valid String serviceid) {
ResponseEntity<RestResult> result = null;
if (!StringUtils.isBlank(serviceid)) {
result = new ResponseEntity<>(new RestResult(RestResultType.OK, chatMessageRes.findByAgentserviceid(serviceid, PageRequest.of(super.getP(request), super.getPs(request), Sort.Direction.DESC, "createtime"))), HttpStatus.OK);
} else {
result = new ResponseEntity<>(new RestResult(RestResultType.LACKDATA, RestResultType.LACKDATA.getMessage()), HttpStatus.OK);
}
return result;
}
}

View File

@ -1,330 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.controller.api;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.exception.CSKefuRestException;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.ContactNotesRepository;
import com.cskefu.cc.persistence.repository.ContactsRepository;
import com.cskefu.cc.persistence.repository.OrganRepository;
import com.cskefu.cc.persistence.repository.OrganUserRepository;
import com.cskefu.cc.persistence.repository.UserRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.json.GsonTools;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
/**
* 联系人笔记
* 管理联系人笔记
*/
@RestController
@RequestMapping("/api/contacts/notes")
public class ApiContactNotesController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(ApiContactNotesController.class);
@Autowired
private ContactNotesRepository contactNotesRes;
@Autowired
private ContactsRepository contactsRes;
@Autowired
private UserRepository userRes;
@Autowired
private OrganRepository organRes;
@Autowired
private OrganUserRepository organUserRes;
/**
* 获取创建人
*
* @param creater
* @return
*/
private JsonObject creater(final String creater) {
JsonObject data = new JsonObject();
// 增加创建人
User u = userRes.findById(creater).orElse(null);
if (u != null) {
data.addProperty("creater", u.getId());
data.addProperty("creatername", u.getUname());
final List<OrganUser> organs = organUserRes.findByUserid(u.getId());
// 获取创建者部门
if (organs != null && organs.size() > 0) {
JsonArray y = new JsonArray();
for (final OrganUser organ : organs) {
Organ o = organRes.findById(organ.getOrgan()).orElse(null);
if (o != null) {
JsonObject x = new JsonObject();
x.addProperty("createrorgan", o.getName());
x.addProperty("createrorganid", o.getId());
y.add(x);
}
}
data.add("organs", y);
}
} else {
logger.warn("[contact notes] detail [{}] 无法得到创建者。", creater);
}
return data;
}
/**
* 获取笔记详情
*
* @param j
* @return
*/
private JsonObject detail(final JsonObject j) throws GsonTools.JsonObjectExtensionConflictException {
logger.info("[contact note] detail {}] {}", j.toString());
JsonObject resp = new JsonObject();
// TODO 增加权限检查
if (j.has("id") && StringUtils.isNotBlank(j.get("id").getAsString())) {
ContactNotes cn = contactNotesRes.findById(j.get("id").getAsString()).orElse(null);
if (cn != null) {
JsonObject data = new JsonObject();
data.addProperty("contactid", cn.getContactid());
data.addProperty("category", cn.getCategory());
data.addProperty("createtime", Constants.DISPLAY_DATE_FORMATTER.format(cn.getCreatetime()));
data.addProperty("updatetime", Constants.DISPLAY_DATE_FORMATTER.format(cn.getUpdatetime()));
data.addProperty("content", cn.getContent());
data.addProperty("agentuser", cn.getAgentuser());
data.addProperty("onlineuser", cn.getOnlineuser());
GsonTools.extendJsonObject(data, GsonTools.ConflictStrategy.PREFER_FIRST_OBJ, creater(cn.getCreater()));
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.add(RestUtils.RESP_KEY_DATA, data);
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不存在该联系人笔记。");
}
}
return resp;
}
/**
* 创建联系人笔记
*
* @param payload
* @return
*/
private JsonObject create(final JsonObject payload) throws GsonTools.JsonObjectExtensionConflictException {
logger.info("[contact note] create {}", payload.toString());
JsonObject resp = new JsonObject();
// validate parameters
String invalid = validateCreatePayload(payload);
if (invalid != null) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, invalid);
return resp;
}
ContactNotes cn = new ContactNotes();
cn.setId(MainUtils.getUUID());
cn.setCategory(payload.get("category").getAsString());
cn.setContent(payload.get("content").getAsString());
cn.setCreater(payload.get("creater").getAsString());
cn.setContactid(payload.get("contactid").getAsString());
cn.setDatastatus(false);
Date dt = new Date();
cn.setCreatetime(dt);
cn.setUpdatetime(dt);
if (payload.has("agentuser")) {
cn.setAgentuser(payload.get("agentuser").getAsString());
}
if (payload.has("onlineuser")) {
cn.setOnlineuser(payload.get("onlineuser").getAsString());
}
contactNotesRes.save(cn);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
JsonObject data = new JsonObject();
data.addProperty("id", cn.getId());
data.addProperty("updatetime", Constants.DISPLAY_DATE_FORMATTER.format(dt));
GsonTools.extendJsonObject(data, GsonTools.ConflictStrategy.PREFER_NON_NULL, creater(cn.getCreater()));
resp.add("data", data);
return resp;
}
/**
* 验证创建数据
*
* @param payload
* @return
*/
private String validateCreatePayload(JsonObject payload) {
if (!payload.has("category")) {
return "参数传递不合法,没有[category]。";
}
if ((!payload.has("content")) || StringUtils.isBlank(payload.get("content").getAsString())) {
return "参数传递不合法,没有[content]。";
}
if ((!payload.has("contactid")) || StringUtils.isBlank(payload.get("contactid").getAsString())) {
return "参数传递不合法,没有[contactid]。";
} else {
Contacts c = contactsRes.findById(payload.get("contactid").getAsString()).orElse(null);
if (c == null)
return "参数不合法,不存在该联系人。";
}
return null;
}
/**
* Build query string
*
* @param j
* @return
*/
private String querybuilder(final JsonObject j) {
StringBuffer sb = new StringBuffer();
return sb.toString();
}
/**
* 根据联系人ID获取联系人笔记列表
*
* @param j
* @param request
* @return
*/
private JsonObject fetch(final JsonObject j, final HttpServletRequest request) throws GsonTools.JsonObjectExtensionConflictException {
logger.info("[contact note] fetch [{}]", j.toString());
JsonObject resp = new JsonObject();
if ((!j.has("contactid")) || StringUtils.isBlank(j.get("contactid").getAsString())) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "参数传递不合法,没有[contactid]。");
return resp;
}
final String cid = j.get("contactid").getAsString();
Contacts c = contactsRes.findById(cid).orElse(null);
if (c == null) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不存在该联系人。");
return resp;
}
String q = querybuilder(j);
Page<ContactNotes> cns = contactNotesRes.findByContactidOrderByCreatetimeDesc(cid, PageRequest.of(super.getP(request), super.getPs(request)));
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.addProperty("size", cns.getSize());
resp.addProperty("number", cns.getNumber());
resp.addProperty("totalPage", cns.getTotalPages());
resp.addProperty("totalElements", cns.getTotalElements());
// 转化 page json
JsonArray data = new JsonArray();
for (ContactNotes cn : cns) {
if (cn != null) {
JsonObject x = new JsonObject();
x.addProperty("contactid", cn.getContactid());
x.addProperty("category", cn.getCategory());
x.addProperty("createtime", Constants.DISPLAY_DATE_FORMATTER.format(cn.getCreatetime()));
x.addProperty("updatetime", Constants.DISPLAY_DATE_FORMATTER.format(cn.getUpdatetime()));
x.addProperty("content", cn.getContent());
x.addProperty("agentuser", cn.getAgentuser());
x.addProperty("onlineuser", cn.getOnlineuser());
GsonTools.extendJsonObject(x, GsonTools.ConflictStrategy.PREFER_FIRST_OBJ, creater(cn.getCreater()));
data.add(x);
}
}
resp.add("data", data);
return resp;
}
/**
* 联系人笔记
*
* @param request
* @param body
* @return
* @throws CSKefuRestException
* @throws GsonTools.JsonObjectExtensionConflictException
*/
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "contactnotes", access = true)
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body) throws CSKefuRestException, GsonTools.JsonObjectExtensionConflictException {
final JsonObject j = (new JsonParser()).parse(body).getAsJsonObject();
logger.info("[contact note] operations payload {}", j.toString());
JsonObject json = new JsonObject();
HttpHeaders headers = RestUtils.header();
j.addProperty("creater", super.getUser(request).getId());
if (!j.has("ops")) {
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "create":
json = create(j);
break;
case "detail":
json = detail(j);
break;
case "fetch":
json = fetch(j, request);
break;
default:
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不支持的操作。");
break;
}
}
return new ResponseEntity<>(json.toString(), headers, HttpStatus.OK);
}
}

View File

@ -1,216 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.controller.api;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.exception.CSKefuRestException;
import com.cskefu.cc.model.Tag;
import com.cskefu.cc.model.TagRelation;
import com.cskefu.cc.persistence.repository.TagRelationRepository;
import com.cskefu.cc.persistence.repository.TagRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.json.GsonTools;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
/**
* 管理联系人标签
*/
@RestController
@RequestMapping("/api/contacts/tags")
public class ApiContactTagsController extends Handler {
private static final Logger logger = LoggerFactory.getLogger(ApiContactTagsController.class);
private static final String TAGTYPE_USER = "user";
@Autowired
private TagRepository tagRes;
@Autowired
private TagRelationRepository tagRelationRes;
/**
* 获取联系人标签
*
* @param j
* @return
*/
private JsonObject fetch(JsonObject j) {
JsonObject resp = new JsonObject();
if (j.has("contactid") && StringUtils.isNotBlank(j.get("contactid").getAsString())) {
String contactid = j.get("contactid").getAsString();
// 获取联系人所有标签
List<TagRelation> rels = tagRelationRes.findByUserid(contactid);
HashMap<String, String> tagged = new HashMap<>();
for (TagRelation t : rels) {
tagged.put(t.getTagid(), t.getId());
}
// 获取所有标签
List<Tag> all = tagRes.findByTagtype(TAGTYPE_USER);
JsonArray data = new JsonArray();
for (Tag t : all) {
JsonObject x = new JsonObject();
x.addProperty("id", t.getId());
x.addProperty("name", t.getTag());
x.addProperty("type", t.getTagtype());
x.addProperty("color", t.getColor());
if (tagged.containsKey(t.getId())) {
x.addProperty("tagged", true);
x.addProperty("xid", tagged.get(t.getId()));
} else {
x.addProperty("tagged", false);
}
data.add(x);
}
resp.add("data", data);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
}
return resp;
}
/**
* 创建联系人标签关系
*
* @param j
* @return
*/
private JsonObject create(JsonObject j) {
JsonObject resp = new JsonObject();
// 验证数据
if ((!j.has("contactid")) || StringUtils.isBlank(j.get("contactid").getAsString())) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
return resp;
}
if ((!j.has("tagId")) || StringUtils.isBlank(j.get("tagId").getAsString())) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
return resp;
}
final String tagId = j.get("tagId").getAsString();
final String contactid = j.get("contactid").getAsString();
Tag tag = tagRes.findById(tagId).orElse(null);
if (tag == null) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不存在该标签。");
return resp;
}
// 创建关系
TagRelation rel = new TagRelation();
rel.setDataid(contactid);
rel.setUserid(contactid);
rel.setTagid(tagId);
tagRelationRes.save(rel);
JsonObject data = new JsonObject();
data.addProperty("id", rel.getId());
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.add(RestUtils.RESP_KEY_DATA, data);
return resp;
}
/**
* 去掉标签
*
* @param j
* @return
*/
private JsonObject remove(JsonObject j) {
JsonObject resp = new JsonObject();
if ((!j.has("xid")) || StringUtils.isBlank(j.get("xid").getAsString())) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
return resp;
}
TagRelation t = tagRelationRes.findById(j.get("xid").getAsString()).orElse(null);
if (t == null) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "该联系人没有打这个标签。");
return resp;
}
tagRelationRes.delete(t);
JsonObject data = new JsonObject();
data.addProperty("msg", "删除成功。");
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.add("data", data);
return resp;
}
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "contacttags", access = true)
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body) throws CSKefuRestException, GsonTools.JsonObjectExtensionConflictException {
final JsonObject j = (new JsonParser()).parse(body).getAsJsonObject();
logger.info("[contact tags] operations payload {}", j.toString());
JsonObject json = new JsonObject();
HttpHeaders headers = RestUtils.header();
j.addProperty("creater", super.getUser(request).getId());
if (!j.has("ops")) {
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "fetch":
json = fetch(j);
break;
case "create":
json = create(j);
break;
case "remove":
json = remove(j);
break;
default:
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不支持的操作。");
break;
}
}
return new ResponseEntity<>(json.toString(), headers, HttpStatus.OK);
}
}

View File

@ -1,262 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.exception.CSKefuException;
import com.cskefu.cc.model.AgentUser;
import com.cskefu.cc.model.Contacts;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.repository.ContactsRepository;
import com.cskefu.cc.proxy.AgentUserProxy;
import com.cskefu.cc.proxy.ContactsProxy;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.Optional;
/**
* 联系人服务
* 联系人管理功能
*/
@RestController
@RequestMapping("/api/contacts")
public class ApiContactsController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(ApiContactsController.class);
@Autowired
private ContactsRepository contactsRepository;
@Autowired
private ContactsRepository contactsRes;
@Autowired
private ContactsProxy contactsProxy;
@Autowired
private AgentUserProxy agentUserProxy;
/**
* 返回用户列表支持分页分页参数为 p=1&ps=50默认分页尺寸为 20条每页
* TODO 该接口需要重构支持传入组织机构没有传入组织结构则需要识别登录用户的全部可见的联系人
* @param request
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "contacts", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @Valid String creater, @Valid String q) {
Page<Contacts> contactsList = null;
if (!StringUtils.isBlank(creater)) {
User user = super.getUser(request);
contactsList = contactsRepository.findByCreaterAndSharesAndDatastatus(user.getId(), "all", false,
PageRequest.of(
super.getP(request),
super.getPs(request)));
} else {
contactsList = contactsRepository.findByDatastatus(false,
PageRequest.of(super.getP(request), super.getPs(request)));
}
return new ResponseEntity<>(new RestResult(RestResultType.OK, contactsList), HttpStatus.OK);
}
/**
* 新增或修改用户用户 在修改用户信息的时候如果用户 密码未改变请设置为 NULL
*
* @param request
* @return
*/
@RequestMapping(method = RequestMethod.PUT)
@Menu(type = "apps", subtype = "contacts", access = true)
public ResponseEntity<RestResult> put(HttpServletRequest request, @Valid Contacts contacts) {
if (contacts != null && !StringUtils.isBlank(contacts.getName())) {
contacts.setCreater(super.getUser(request).getId());
contacts.setUsername(super.getUser(request).getUsername());
contacts.setCreatetime(new Date());
contacts.setUpdatetime(new Date());
contactsRepository.save(contacts);
}
return new ResponseEntity<>(new RestResult(RestResultType.OK), HttpStatus.OK);
}
/**
* 删除用户只提供 按照用户ID删除 并且不能删除系统管理员
* 删除联系人联系人删除是逻辑删除 datastatus字段标记为 true即已删除
*
* @param request
* @param id
* @return
*/
@RequestMapping(method = RequestMethod.DELETE)
@Menu(type = "apps", subtype = "contacts", access = true)
public ResponseEntity<RestResult> delete(HttpServletRequest request, @Valid String id) {
RestResult result = new RestResult(RestResultType.OK);
if (!StringUtils.isBlank(id)) {
Contacts contacts = contactsRepository.findById(id).orElse(null);
if (contacts != null) { //系统管理员 不允许 使用 接口删除
contacts.setDatastatus(true);
contactsRepository.save(contacts);
}
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
/**
* 联系人页面客户点击页面时判断是否有能触达的通道
*
* @param request
* @param body
* @return
*/
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "contacts", access = true)
public ResponseEntity<String> operations(
final HttpServletRequest request,
@RequestBody final String body) {
final JsonObject j = (new JsonParser()).parse(body).getAsJsonObject();
logger.info("[chatbot] operations payload {}", j.toString());
JsonObject json = new JsonObject();
HttpHeaders headers = RestUtils.header();
final User logined = super.getUser(request);
if (!j.has("ops")) {
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "approach":
// 查找立即触达的渠道
json = approach(j, logined);
break;
case "proactive":
// 与联系开始聊天
json = proactive(j, logined);
break;
default:
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的操作。");
}
}
return new ResponseEntity<>(json.toString(), headers, HttpStatus.OK);
}
/**
* 主动与联系人聊天
*
* @param payload
* @param logined
* @return
*/
private JsonObject proactive(final JsonObject payload, User logined) {
JsonObject resp = new JsonObject();
final String channels = payload.has("channels") ? payload.get("channels").getAsString() : null;
final String contactid = payload.has("contactid") ? payload.get("contactid").getAsString() : null;
if (StringUtils.isBlank(channels) || StringUtils.isBlank(contactid)) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Invalid params.");
return resp;
}
try {
AgentUser agentUser = agentUserProxy.figureAgentUserBeforeChatWithContactInfo(channels, contactid, logined);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
JsonObject data = new JsonObject();
data.addProperty("agentuserid", agentUser.getId());
resp.add(RestUtils.RESP_KEY_DATA, data);
} catch (CSKefuException e) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Can not create agent user.");
return resp;
}
return resp;
}
/**
* 根据联系人信息查找立即触达的渠道
*
* @param payload
* @param logined
* @return
*/
private JsonObject approach(final JsonObject payload, final User logined) {
JsonObject resp = new JsonObject();
if (!payload.has("contactsid")) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Invalid params.");
return resp;
}
final String contactsid = payload.get("contactsid").getAsString();
Optional<Contacts> contactOpt = contactsRes.findOneById(contactsid).filter(
p -> !p.isDatastatus());
if (contactOpt.isPresent()) {
List<MainContext.ChannelType> channles;
try {
channles = contactsProxy.liveApproachChannelsByContactid(
logined, contactsid, contactsProxy.isSkypeSetup());
if (channles.size() > 0) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
JsonArray data = new JsonArray();
for (final MainContext.ChannelType e : channles) {
data.add(e.toString());
}
resp.add(RestUtils.RESP_KEY_DATA, data);
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "No available channel to approach contact.");
}
} catch (CSKefuException e) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Contact not found.");
}
} else {
// can not find contact, may is deleted.
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Not found contact.");
}
return resp;
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.AgentService;
import com.cskefu.cc.persistence.repository.AgentServiceRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.springframework.beans.factory.annotation.Autowired;
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.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* 访客留言功能
*/
@RestController
@RequestMapping("/api/leavemsg")
public class ApiLeavemsgController extends Handler {
@Autowired
private AgentServiceRepository agentServiceRepository;
/**
* 获取留言列表
*
* @param request
* @param username 搜索用户名精确搜索
* @return
*/
@RequestMapping("/list")
@Menu(type = "apps", subtype = "app", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @RequestBody RequestValues<AgentService> values) {
Page<AgentService> page = agentServiceRepository.findAll((root, query, cb) -> {
List<Predicate> 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()));
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);
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.PassportWebIMUser;
import com.cskefu.cc.persistence.repository.PassportWebIMUserRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
/**
* 获取在线访客功能
*/
@RestController
@RequestMapping("/api/online/user")
public class ApiOnlineUserController extends Handler {
@Autowired
private PassportWebIMUserRepository passportWebIMUserRepository;
/**
* 获取在线客服
*
* @param request
* @param username 搜索用户名精确搜索
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "sysdic", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @Valid String userid) {
Page<PassportWebIMUser> onlineUserList = null;
if (!StringUtils.isBlank(userid)) {
onlineUserList = passportWebIMUserRepository.findByUserid(userid, PageRequest.of(super.getP(request), super.getPs(request), Sort.Direction.DESC, "createtime"));
} else {
onlineUserList = passportWebIMUserRepository.findByStatus(MainContext.OnlineUserStatusEnum.ONLINE.toString(), PageRequest.of(super.getP(request), super.getPs(request), Sort.Direction.DESC, "createtime"));
}
return new ResponseEntity<>(new RestResult(RestResultType.OK, onlineUserList), HttpStatus.OK);
}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.persistence.repository.OrganRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
/**
* 组织机构/部门/技能组功能
*/
@RestController
@RequestMapping("/api/organ")
public class ApiOrganController extends Handler{
@Autowired
private OrganRepository organRepository;
/**
* 返回所有部门
* @param request
* @param username 搜索用户名精确搜索
* @return
*/
@RequestMapping( method = RequestMethod.GET)
@Menu(type = "apps" , subtype = "organ" , access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request) {
return new ResponseEntity<>(new RestResult(RestResultType.OK, organRepository.findAll()), HttpStatus.OK);
}
/**
* 新增或修改部门
* @param request
* @param user
* @return
*/
@RequestMapping(method = RequestMethod.PUT)
@Menu(type = "apps" , subtype = "organ" , access = true)
public ResponseEntity<RestResult> put(HttpServletRequest request , @Valid Organ organ) {
if(organ != null && !StringUtils.isBlank(organ.getName())){
organRepository.save(organ) ;
}
return new ResponseEntity<>(new RestResult(RestResultType.OK), HttpStatus.OK);
}
/**
* 删除用户只提供 按照用户ID删除 并且不能删除系统管理员
* @param request
* @param id
* @return
*/
@RequestMapping(method = RequestMethod.DELETE)
@Menu(type = "apps" , subtype = "user" , access = true)
public ResponseEntity<RestResult> delete(HttpServletRequest request , @Valid String id) {
RestResult result = new RestResult(RestResultType.OK) ;
Organ organ = null ;
if(!StringUtils.isBlank(id)){
organ = organRepository.findById(id).orElse(null);
if(organ != null){ //系统管理员 不允许 使用 接口删除
organRepository.delete(organ);
}else{
result.setStatus(RestResultType.ORGAN_DELETE);
}
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.AgentService;
import com.cskefu.cc.persistence.repository.AgentServiceRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.springframework.beans.factory.annotation.Autowired;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* 质量检查
* 在线客服质检功能
*/
@RestController
@RequestMapping("/api/quality")
public class ApiQualityController extends Handler {
@Autowired
private AgentServiceRepository agentServiceRepository;
/**
* 获取质检列表
*
* @param request
* @param username 搜索用户名精确搜索
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "app", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @Valid AgentService agentService, @Valid String begin, @Valid String end) {
Page<AgentService> page = agentServiceRepository.findAll((root, query, cb) -> {
List<Predicate> 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));
}, PageRequest.of(super.getP(request), super.getPs(request), Sort.Direction.DESC, "createtime"));
return new ResponseEntity<>(new RestResult(RestResultType.OK, page), HttpStatus.OK);
}
}

View File

@ -1,172 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.acd.ACDAgentService;
import com.cskefu.cc.acd.ACDWorkMonitor;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.AgentStatus;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.repository.AgentStatusRepository;
import com.cskefu.cc.proxy.AgentStatusProxy;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.Date;
/**
* ACD服务
* 获取队列统计信息
*/
@RestController
@RequestMapping("/api/servicequene")
public class ApiServiceQueneController extends Handler {
@Autowired
private AgentStatusProxy agentStatusProxy;
@Autowired
private ACDWorkMonitor acdWorkMonitor;
@Autowired
private AgentStatusRepository agentStatusRes;
@Autowired
private ACDAgentService acdAgentService;
@Autowired
private Cache cache;
/**
* 获取队列统计信息包含当前队列服务中的访客数排队人数坐席数
*
* @param request
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "user", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request) {
return new ResponseEntity<>(
new RestResult(RestResultType.OK, acdWorkMonitor.getAgentReport()),
HttpStatus.OK);
}
/**
* 坐席状态操作就绪未就绪
*
* @param request
* @return
*/
@RequestMapping(method = RequestMethod.PUT)
@Menu(type = "apps", subtype = "user", access = true)
public ResponseEntity<RestResult> agentStatus(
HttpServletRequest request,
@Valid String status) {
User logined = super.getUser(request);
AgentStatus agentStatus = null;
if (StringUtils.isNotBlank(status) && status.equals(MainContext.AgentStatusEnum.READY.toString())) {
agentStatus = agentStatusRes.findOneByAgentno(logined.getId()).orElseGet(() -> {
AgentStatus p = new AgentStatus();
p.setUserid(logined.getId());
p.setUsername(logined.getUname());
p.setAgentno(logined.getId());
p.setLogindate(new Date());
// SessionConfig sessionConfig = acdPolicyService.initSessionConfig();
p.setUpdatetime(new Date());
// p.setMaxusers(sessionConfig.getMaxuser());
return p;
});
/**
* 设置技能组
*/
agentStatus.setSkills(logined.getSkills());
/**
* 更新当前用户状态
*/
agentStatus.setUsers(cache.getInservAgentUsersSizeByAgentno(
agentStatus.getAgentno()));
agentStatus.setStatus(MainContext.AgentStatusEnum.READY.toString());
agentStatusRes.save(agentStatus);
acdWorkMonitor.recordAgentStatus(
agentStatus.getAgentno(), agentStatus.getUsername(), agentStatus.getAgentno(),
logined.isAdmin(), agentStatus.getAgentno(),
MainContext.AgentStatusEnum.OFFLINE.toString(), MainContext.AgentStatusEnum.READY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(), null);
acdAgentService.assignVisitors(agentStatus.getAgentno());
} else if (StringUtils.isNotBlank(status)) {
if (status.equals(MainContext.AgentStatusEnum.NOTREADY.toString())) {
agentStatusRes.findOneByAgentno(
logined.getId()).ifPresent(p -> {
acdWorkMonitor.recordAgentStatus(
p.getAgentno(), p.getUsername(), p.getAgentno(),
logined.isAdmin(),
p.getAgentno(),
p.isBusy() ? MainContext.AgentStatusEnum.BUSY.toString() : MainContext.AgentStatusEnum.READY.toString(),
MainContext.AgentStatusEnum.NOTREADY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(), p.getUpdatetime());
agentStatusRes.delete(p);
});
} else if (StringUtils.isNotBlank(status) && status.equals(MainContext.AgentStatusEnum.BUSY.toString())) {
agentStatusRes.findOneByAgentno(
logined.getId()).ifPresent(p -> {
p.setBusy(true);
acdWorkMonitor.recordAgentStatus(
p.getAgentno(), p.getUsername(), p.getAgentno(),
logined.isAdmin(), p.getAgentno(),
MainContext.AgentStatusEnum.READY.toString(), MainContext.AgentStatusEnum.BUSY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(),
p.getUpdatetime());
p.setUpdatetime(new Date());
agentStatusRes.save(p);
});
} else if (StringUtils.isNotBlank(status) && status.equals(
MainContext.AgentStatusEnum.NOTBUSY.toString())) {
agentStatusRes.findOneByAgentno(
logined.getId()).ifPresent(p -> {
p.setBusy(false);
acdWorkMonitor.recordAgentStatus(
p.getAgentno(), p.getUsername(), p.getAgentno(),
logined.isAdmin(), p.getAgentno(),
MainContext.AgentStatusEnum.BUSY.toString(), MainContext.AgentStatusEnum.READY.toString(),
MainContext.AgentWorkType.MEIDIACHAT.toString(),
p.getUpdatetime());
p.setUpdatetime(new Date());
agentStatusRes.save(p);
});
acdAgentService.assignVisitors(agentStatus.getAgentno());
}
agentStatusProxy.broadcastAgentsStatus("agent", "api", super.getUser(request).getId());
}
return new ResponseEntity<>(new RestResult(RestResultType.OK, agentStatus), HttpStatus.OK);
}
}

View File

@ -1,151 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.controller.admin.system.SysDicController;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.model.Dict;
import com.cskefu.cc.model.SysDic;
import com.cskefu.cc.model.User;
import com.cskefu.cc.persistence.repository.SysDicRepository;
import com.cskefu.cc.util.CskefuIdGenerator;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.Date;
/**
* 数据字典
* 数据字典功能
*/
@RestController
@RequestMapping("/api/sysdic")
public class ApiSysDicController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(ApiSysDicController.class);
@Autowired
private SysDicRepository sysDicRes;
@Autowired
private SysDicController sysDicCtrl;
/**
* 获取数据字典
*
* @param request
* @param code
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "sysdic", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @Valid String code) {
return new ResponseEntity<>(new RestResult(RestResultType.OK, Dict.getInstance().getDic(code)), HttpStatus.OK);
}
/**
* 创建联系人类型
*
* @param creator
* @param payload
* @return
*/
private JsonObject saveCkind(final String creator, final JsonObject payload) {
JsonObject result = new JsonObject();
SysDic parent = sysDicRes.findByCode("com.dic.contacts.ckind");
if (parent == null) {
result.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
result.addProperty(RestUtils.RESP_KEY_ERROR, "系统数据字典中不存在【联系人类型】。");
} else if ((!payload.has("name"))) {
result.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
result.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的参数,缺少`name`");
} else {
SysDic record = new SysDic();
record.setCreater(creator);
record.setCreatetime(new Date());
record.setParentid(parent.getId());
record.setDicid(parent.getId());
record.setName(payload.get("name").getAsString());
record.setCode(CskefuIdGenerator.randomAlphaNumeric(3));
record.setTitle("pub");
record.setHaschild(false);
sysDicRes.save(record);
sysDicCtrl.reloadSysDicItem(record);
JsonObject data = new JsonObject();
data.addProperty("id", record.getId());
data.addProperty("parentid", record.getParentid());
data.addProperty("dicid", record.getDicid());
result.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
result.add(RestUtils.RESP_KEY_DATA, data);
}
return result;
}
/**
* 数据字典
*
* @param request
* @param body
* @return
*/
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "sysdict", access = true)
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body) {
final JsonObject j = (new JsonParser()).parse(body).getAsJsonObject();
logger.info("[sysdict api] operations payload {}", j.toString());
JsonObject result = new JsonObject();
HttpHeaders headers = RestUtils.header();
User logined = super.getUser(request);
if (!j.has("ops")) {
result.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
result.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "com.dic.contacts.ckind::create": // 增加联系人类型
result = saveCkind(logined.getId(), j);
break;
default:
logger.info("[api] unknown operation {}", j.toString());
}
}
return new ResponseEntity<>(result.toString(), headers, HttpStatus.OK);
}
}

View File

@ -1,131 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.controller.api;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.exception.CSKefuRestException;
import com.cskefu.cc.model.Tag;
import com.cskefu.cc.persistence.repository.TagRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.json.GsonTools;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
/**
* 标签
* 管理标签
*/
@RestController
@RequestMapping("/api/repo/tags")
public class ApiTagsController extends Handler {
private static final Logger logger = LoggerFactory.getLogger(ApiTagsController.class);
@Autowired
private TagRepository tagRes;
/**
* 获取标签
*
* @param j
* @param request
* @return
*/
private JsonObject fetch(final JsonObject j, final HttpServletRequest request) {
JsonObject resp = new JsonObject();
String tagType = null;
if (j.has("tagtype"))
tagType = j.get("tagtype").getAsString();
Page<Tag> records = tagRes.findByTagtype(tagType,
PageRequest.of(super.getP(request), super.getPs(request), Sort.Direction.DESC, "createtime"));
JsonArray ja = new JsonArray();
for (Tag t : records) {
JsonObject o = new JsonObject();
o.addProperty("id", t.getId());
o.addProperty("name", t.getTag());
o.addProperty("type", t.getTagtype());
o.addProperty("icon", t.getIcon());
o.addProperty("color", t.getColor());
ja.add(o);
}
resp.add("data", ja);
resp.addProperty("size", records.getSize()); // 每页条数
resp.addProperty("number", records.getNumber()); // 当前页
resp.addProperty("totalPage", records.getTotalPages()); // 所有页
resp.addProperty("totalElements", records.getTotalElements()); // 所有检索结果数量
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
return resp;
}
/**
* 联系人标签
*
* @param request
* @param body
* @return
* @throws CSKefuRestException
* @throws GsonTools.JsonObjectExtensionConflictException
*/
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "tags", access = true)
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body) throws CSKefuRestException, GsonTools.JsonObjectExtensionConflictException {
final JsonObject j = (new JsonParser()).parse(body).getAsJsonObject();
logger.info("[contact tags] operations payload {}", j.toString());
JsonObject json = new JsonObject();
HttpHeaders headers = RestUtils.header();
j.addProperty("creater", super.getUser(request).getId());
if (!j.has("ops")) {
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "fetch":
json = fetch(j, request);
break;
default:
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不支持的操作。");
break;
}
}
return new ResponseEntity<>(json.toString(), headers, HttpStatus.OK);
}
}

View File

@ -1,354 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.util.restapi.RestUtils;
import com.cskefu.cc.model.AgentStatus;
import com.cskefu.cc.model.Organ;
import com.cskefu.cc.model.OrganUser;
import com.cskefu.cc.model.Role;
import com.cskefu.cc.model.User;
import com.cskefu.cc.model.UserRole;
import com.cskefu.cc.persistence.repository.OrganRepository;
import com.cskefu.cc.persistence.repository.OrganUserRepository;
import com.cskefu.cc.persistence.repository.RoleRepository;
import com.cskefu.cc.persistence.repository.UserRepository;
import com.cskefu.cc.persistence.repository.UserRoleRepository;
import com.cskefu.cc.proxy.UserProxy;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/user")
public class ApiUserController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(ApiUserController.class);
@Autowired
private Cache cache;
@Autowired
private UserProxy userProxy;
@Autowired
private UserRepository userRes;
@Autowired
private OrganUserRepository organUserRes;
@Autowired
private OrganRepository organRes;
@Autowired
private RoleRepository roleRes;
@Autowired
private UserRoleRepository userRoleRes;
/**
* 返回用户列表支持分页分页参数为 p=1&ps=50默认分页尺寸为 20条每页
*
* @param request
* @param username 搜索用户名精确搜索
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "user", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @Valid String id, @Valid String username) {
Page<User> userList = null;
if (StringUtils.isNotBlank(id)) {
userList = userRes.findByIdIn(Collections.singleton(id), PageRequest.of(super.getP(request), super.getPs(request)));
} else {
if (StringUtils.isNotBlank(username)) {
userList = userRes.findByDatastatusAndUsernameLike(
false, username, PageRequest.of(
super.getP(request),
super.getPs(request)));
} else {
userList = userRes.findByDatastatus(
false, PageRequest.of(super.getP(request), super.getPs(request)));
}
}
return new ResponseEntity<>(new RestResult(RestResultType.OK, userList), HttpStatus.OK);
}
/**
* 用户管理
*
* @param request
* @param body
* @param q
* @return
*/
@RequestMapping(method = RequestMethod.POST)
@Menu(type = "apps", subtype = "user", access = true)
public ResponseEntity<String> operations(HttpServletRequest request, @RequestBody final String body,
@Valid String q) {
logger.info("[operations] body {}, q {}", body, q);
final JsonObject j = StringUtils.isBlank(body) ? (new JsonObject())
: (new JsonParser()).parse(
body).getAsJsonObject();
JsonObject json = new JsonObject();
HttpHeaders headers = RestUtils.header();
if (!j.has("ops")) {
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_1);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的请求参数。");
} else {
switch (StringUtils.lowerCase(j.get("ops").getAsString())) {
case "create":
json = create(request, j);
break;
case "update":
json = update(request, j);
break;
case "findbyorgan":
json = findByOrgan(j);
break;
case "delete":
json = delete(request, j);
break;
default:
json.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_2);
json.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的操作。");
}
}
return new ResponseEntity<>(json.toString(), headers, HttpStatus.OK);
}
/**
* 创建新用户
*
* @param request
* @param payload
* @return
*/
private JsonObject create(final HttpServletRequest request, final JsonObject payload) {
logger.info("[create] payload {}", payload.toString());
String parent = payload.get("parent").getAsString();
Organ parentOrgan = super.getOrgan(request);
if (StringUtils.isNotEmpty(parent)) {
parentOrgan = organRes.findById(parent).orElse(null);
}
String roleId = payload.get("role").getAsString();
// 创建新用户时阻止传入ID
payload.remove("id");
// 从payload中创建User
User user = userProxy.parseUserFromJson(payload);
JsonObject resp = userProxy.createNewUser(user, parentOrgan);
if (StringUtils.isNotEmpty(roleId)) {
Role role = roleRes.findById(roleId).orElse(null);
UserRole userRole = new UserRole();
userRole.setUser(user);
userRole.setRole(role);
userRole.setCreater(super.getUser(request).getId());
userRole.setOrgan(parentOrgan.getId());
userRoleRes.save(userRole);
}
logger.info("[create] response {}", resp.toString());
return resp;
}
/**
* 更新用户信息
*
* @param request
* @param payload
* @return
*/
private JsonObject update(final HttpServletRequest request, final JsonObject payload) {
logger.info("[update] payload {}", payload.toString());
JsonObject resp = new JsonObject();
final User updated = userProxy.parseUserFromJson(payload);
if (StringUtils.isBlank(updated.getId())) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "不合法的参数。");
return resp;
}
final User previous = userRes.findById(updated.getId()).orElse(null);
if (previous != null) {
String msg = userProxy.validUserUpdate(updated, previous);
if (StringUtils.equals(msg, "edit_user_success")) {
// 由坐席切换成非坐席 判断是否坐席 以及 是否有对话
if (!updated.isAgent()) {
AgentStatus agentStatus = cache.findOneAgentStatusByAgentno(
previous.getId());
if (agentStatus != null && agentStatus.getUsers() > 0) {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.addProperty(RestUtils.RESP_KEY_DATA, "t1");
return resp;
}
// TODO 检查该用户是否在其它技能组
// https://gitlab.chatopera.com/chatopera/cosinee/issues/751
// 如果在其它技能组禁止修改返回提示"该用户在其它技能组中,不支持取消坐席。取消坐席设置前需要不包括在任何技能组中。"
}
// 通过验证可以更新数据库
previous.setUname(updated.getUname());
previous.setUsername(updated.getUsername());
previous.setEmail(updated.getEmail());
previous.setMobile(updated.getMobile());
previous.setAgent(updated.isAgent());
if (MainContext.hasModule(Constants.CSKEFU_MODULE_CALLCENTER)) {
previous.setCallcenter(updated.isCallcenter());
if (updated.isCallcenter()) {
previous.setExtensionId(updated.getExtensionId());
previous.setPbxhostId(updated.getPbxhostId());
}
}
if (StringUtils.isNotBlank(updated.getPassword())) {
previous.setPassword(MainUtils.md5(updated.getPassword()));
}
final Date now = new Date();
if (previous.getCreatetime() == null) {
previous.setCreatetime(now);
}
previous.setUpdatetime(now);
previous.setAdmin(updated.isAdmin());
previous.setSuperadmin(false);
userRes.save(previous);
}
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.addProperty(RestUtils.RESP_KEY_DATA, msg);
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Previous user not exist.");
}
return resp;
}
/**
* 根据组织查找用户
*
* @param payload
* @return
*/
private JsonObject findByOrgan(final JsonObject payload) {
final JsonObject resp = new JsonObject();
if (payload.has("organ")) {
List<OrganUser> organUsers = organUserRes.findByOrgan(payload.get("organ").getAsString());
List<String> userids = organUsers.stream().map(OrganUser::getUserid).collect(Collectors.toList());
List<User> users = userRes.findAllById(userids);
JsonArray data = new JsonArray();
users.stream().forEach(u -> {
JsonObject obj = new JsonObject();
obj.addProperty("id", u.getId());
obj.addProperty("uname", u.getUname());
data.add(obj);
});
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.add(RestUtils.RESP_KEY_DATA, data);
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "Invalid params.");
}
return resp;
}
/**
* 删除账号
*
* @param request
* @param payload
* @return
*/
private JsonObject delete(final HttpServletRequest request, final JsonObject payload) {
logger.info("[create] payload {}", payload.toString());
JsonObject resp = new JsonObject();
if (payload.has("id")) {
String id = payload.get("id").getAsString();
if (StringUtils.isNotBlank(id)) {
User user = userRes.findById(id).orElse(null);
if (user == null) {
// 用户不存在
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.addProperty(RestUtils.RESP_KEY_MSG, "done");
return resp;
}
// 系统管理员 不允许 使用 接口删除
if (!user.isSuperadmin()) {
// 删除用户的时候同时删除用户对应的权限
List<UserRole> userRoles = userRoleRes.findByUser(user);
userRoleRes.deleteAll(userRoles);
// 删除用户对应的组织机构关系
List<OrganUser> organUsers = organUserRes.findByUserid(id);
organUserRes.deleteAll(organUsers);
// 删除用户
userRes.delete(user);
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC);
resp.addProperty(RestUtils.RESP_KEY_MSG, "done");
} else {
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_3);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "USER_DELETE");
}
}
} else {
// 参数不合法
resp.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_FAIL_4);
resp.addProperty(RestUtils.RESP_KEY_ERROR, "INVALID_PARAMS");
}
return resp;
}
}

View File

@ -1,73 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.model.CousultInvite;
import com.cskefu.cc.persistence.repository.ConsultInviteRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
/**
* 在线客服
* 在线客服设置功能
*/
@RestController
@RequestMapping("/api/webim")
public class ApiWebIMController extends Handler {
@Autowired
private ConsultInviteRepository consultInviteRepository;
/**
* 获取在线客服
*
* @param request
* @param username 搜索用户名精确搜索
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "app", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request) {
return new ResponseEntity<>(new RestResult(RestResultType.OK, consultInviteRepository.findAll()), HttpStatus.OK);
}
/**
* 修改在线客服信息只提供修改操作
*
* @param request
* @param user
* @return
*/
@RequestMapping(method = RequestMethod.PUT)
@Menu(type = "apps", subtype = "app", access = true)
public ResponseEntity<RestResult> put(HttpServletRequest request, @Valid CousultInvite consult) {
if (consult != null && !StringUtils.isBlank(consult.getId())) {
consultInviteRepository.save(consult);
}
return new ResponseEntity<>(new RestResult(RestResultType.OK), HttpStatus.OK);
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
public class QueryParams {
private String begin ;
private String end ;
private String id ;
private String p;
private String ps ;
public String getBegin() {
return begin;
}
public void setBegin(String begin) {
this.begin = begin;
}
public String getEnd() {
return end;
}
public void setEnd(String end) {
this.end = end;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getP() {
return p;
}
public void setP(String p) {
this.p = p;
}
public String getPs() {
return ps;
}
public void setPs(String ps) {
this.ps = ps;
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import java.io.Serializable;
public class RequestValues<T> implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private QueryParams query ;
private T data ;
public QueryParams getQuery() {
return query;
}
public void setQuery(QueryParams query) {
this.query = query;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

View File

@ -1,58 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.api;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.persistence.repository.TagRepository;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.RestResult;
import com.cskefu.cc.util.RestResultType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
/**
* 标签功能
* 获取分类标签
*/
@RestController
@RequestMapping("/api/tags")
public class UkefuApiTagsController extends Handler {
@Autowired
private TagRepository tagRes;
/**
* 按照分类获取标签列表
* 按照分类获取标签列表Type 参数类型来自于 枚举可选值目前有三个 user workorders summary
*
* @param request
* @param type 类型
* @return
*/
@RequestMapping(method = RequestMethod.GET)
@Menu(type = "apps", subtype = "tags", access = true)
public ResponseEntity<RestResult> list(HttpServletRequest request, @Valid String type) {
return new ResponseEntity<>(new RestResult(RestResultType.OK, tagRes.findByTagtype(!StringUtils.isBlank(type) ? type : MainContext.ModelType.USER.toString())), HttpStatus.OK);
}
}

View File

@ -1,456 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>, 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.controller.apps;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.exception.CSKefuException;
import com.cskefu.cc.model.*;
import com.cskefu.cc.persistence.repository.ContactsRepository;
import com.cskefu.cc.persistence.repository.AccountRepository;
import com.cskefu.cc.persistence.repository.MetadataRepository;
import com.cskefu.cc.persistence.repository.PropertiesEventRepository;
import com.cskefu.cc.persistence.repository.ReporterRepository;
import com.cskefu.cc.proxy.OrganProxy;
import com.cskefu.cc.util.Menu;
import com.cskefu.cc.util.PinYinTools;
import com.cskefu.cc.util.PropertiesEventUtil;
import com.cskefu.cc.util.dsdata.DSData;
import com.cskefu.cc.util.dsdata.DSDataEvent;
import com.cskefu.cc.util.dsdata.ExcelImportProecess;
import com.cskefu.cc.util.dsdata.export.ExcelExporterProcess;
import com.cskefu.cc.util.dsdata.process.AccountProcess;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 客户管理
*/
@Controller
@RequestMapping("/apps/customer")
public class AccountController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(AccountController.class);
@Autowired
private AccountRepository accountRes;
@Autowired
private ContactsRepository contactsRes;
@Autowired
private ReporterRepository reporterRes;
@Autowired
private MetadataRepository metadataRes;
@Autowired
private PropertiesEventRepository propertiesEventRes;
@Autowired
private OrganProxy organProxy;
@Value("${web.upload-path}")
private String path;
@RequestMapping("/index")
@Menu(type = "customer", subtype = "index")
public ModelAndView index(ModelMap map,
HttpServletRequest request,
final @Valid String q,
final @Valid String ekind,
final @Valid String msg) throws CSKefuException {
logger.info("[index] query {}, ekind {}, msg {}", q, ekind, msg);
final User logined = super.getUser(request);
final Organ currentOrgan = super.getOrgan(request);
map.put("msg", msg);
if (!super.preCheckPermissions(request)) {
return request(super.createView("/apps/customer/index"));
}
if (StringUtils.isNotBlank(q)) {
map.put("q", q);
}
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
map.addAttribute("currentOrgan", currentOrgan);
map.addAttribute("entCustomerList", accountRes.findByOrganInAndSharesAllAndDatastatusFalse(
super.getMyCurrentAffiliatesFlat(logined),
PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/apps/customer/index"));
}
@RequestMapping("/today")
@Menu(type = "customer", subtype = "today")
public ModelAndView today(ModelMap map, HttpServletRequest request, @Valid String q, @Valid String ekind) throws CSKefuException {
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
if (!super.preCheckPermissions(request)) {
return request(super.createView("/apps/customer/index"));
}
if (StringUtils.isNotBlank(q)) {
map.put("q", q);
}
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
map.addAttribute("entCustomerList", accountRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(),
super.getUser(request).getId(),
false,
PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/apps/customer/index"));
}
@RequestMapping("/week")
@Menu(type = "customer", subtype = "week")
public ModelAndView week(ModelMap map, HttpServletRequest request, @Valid String q, @Valid String ekind) throws CSKefuException {
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
if (!super.preCheckPermissions(request)) {
return request(super.createView("/apps/customer/index"));
}
if (StringUtils.isNotBlank(q)) {
map.put("q", q);
}
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
map.addAttribute("entCustomerList", accountRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(), super.getUser(request).getId(),
false,
PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/apps/customer/index"));
}
@RequestMapping("/enterprise")
@Menu(type = "customer", subtype = "enterprise")
public ModelAndView enterprise(ModelMap map, HttpServletRequest request, @Valid String q, @Valid String ekind) throws CSKefuException {
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
if (!super.preCheckPermissions(request)) {
return request(super.createView("/apps/customer/index"));
}
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
if (StringUtils.isNotBlank(q)) {
map.put("q", q);
}
map.addAttribute("entCustomerList", accountRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(),
super.getUser(request).getId(),
false,
PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/apps/customer/index"));
}
@RequestMapping("/personal")
@Menu(type = "customer", subtype = "personal")
public ModelAndView personal(ModelMap map, HttpServletRequest request, @Valid String q, @Valid String ekind) throws CSKefuException {
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
if (!super.preCheckPermissions(request)) {
return request(super.createView("/apps/customer/index"));
}
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
if (StringUtils.isNotBlank(q)) {
map.put("q", q);
}
map.addAttribute("entCustomerList", accountRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(),
super.getUser(request).getId(),
false,
PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/apps/customer/index"));
}
@RequestMapping("/creater")
@Menu(type = "customer", subtype = "creater")
public ModelAndView creater(ModelMap map, HttpServletRequest request, @Valid String q, @Valid String ekind) throws CSKefuException {
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
if (!super.preCheckPermissions(request)) {
return request(super.createView("/apps/customer/index"));
}
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
if (StringUtils.isNotBlank(q)) {
map.put("q", q);
}
map.addAttribute("entCustomerList", accountRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(), super.getUser(request).getId(), false, PageRequest.of(super.getP(request), super.getPs(request))));
return request(super.createView("/apps/customer/index"));
}
@RequestMapping("/add")
@Menu(type = "customer", subtype = "customer")
public ModelAndView add(ModelMap map, HttpServletRequest request, @Valid String ekind) {
map.addAttribute("ekind", ekind);
return request(super.createView("/apps/customer/add"));
}
@RequestMapping("/save")
@Menu(type = "customer", subtype = "customer")
public ModelAndView save(HttpServletRequest request,
@Valid CustomerGroupForm customerGroupForm) {
String msg = "";
msg = "new_entcustomer_success";
customerGroupForm.getEntcustomer().setCreater(super.getUser(request).getId());
final User logined = super.getUser(request);
Organ currentOrgan = super.getOrgan(request);
// customerGroupForm.getEntcustomer().setEtype(MainContext.CustomerTypeEnum.ENTERPRISE.toString());
customerGroupForm.getEntcustomer().setPinyin(PinYinTools.getInstance().getFirstPinYin(customerGroupForm.getEntcustomer().getName()));
if (currentOrgan != null && StringUtils.isBlank(customerGroupForm.getEntcustomer().getOrgan())) {
customerGroupForm.getEntcustomer().setOrgan(currentOrgan.getId());
customerGroupForm.getContacts().setOrgan(currentOrgan.getId());
}
accountRes.save(customerGroupForm.getEntcustomer());
if (customerGroupForm.getContacts() != null && StringUtils.isNotBlank(customerGroupForm.getContacts().getName())) {
customerGroupForm.getContacts().setEntcusid(customerGroupForm.getEntcustomer().getId());
customerGroupForm.getContacts().setCreater(logined.getId());
customerGroupForm.getContacts().setPinyin(PinYinTools.getInstance().getFirstPinYin(customerGroupForm.getContacts().getName()));
if (StringUtils.isBlank(customerGroupForm.getContacts().getCusbirthday())) {
customerGroupForm.getContacts().setCusbirthday(null);
}
contactsRes.save(customerGroupForm.getContacts());
}
return request(super.createView("redirect:/apps/customer/index.html?ekind=" + customerGroupForm.getEntcustomer().getEkind() + "&msg=" + msg));
}
@RequestMapping("/delete")
@Menu(type = "customer", subtype = "customer")
public ModelAndView delete(HttpServletRequest request, @Valid Account account, @Valid String p, @Valid String ekind) {
if (account != null) {
account = accountRes.findById(account.getId()).orElse(null);
account.setDatastatus(true); //客户和联系人都是 逻辑删除
accountRes.save(account);
}
return request(super.createView("redirect:/apps/customer/index.html?p=" + p + "&ekind=" + ekind));
}
@RequestMapping("/edit")
@Menu(type = "customer", subtype = "customer")
public ModelAndView edit(ModelMap map, HttpServletRequest request, @Valid String id, @Valid String ekind) {
map.addAttribute("entCustomer", accountRes.findById(id).orElse(null));
map.addAttribute("ekindId", ekind);
return request(super.createView("/apps/customer/edit"));
}
@RequestMapping("/update")
@Menu(type = "customer", subtype = "customer")
public ModelAndView update(HttpServletRequest request, @Valid CustomerGroupForm customerGroupForm, @Valid String ekindId) {
final User logined = super.getUser(request);
Account customer = accountRes.findById(customerGroupForm.getEntcustomer().getId()).orElse(null);
String msg = "";
List<PropertiesEvent> events = PropertiesEventUtil.processPropertiesModify(request, customerGroupForm.getEntcustomer(), customer, "id", "creater", "createtime", "updatetime"); //记录 数据变更 历史
if (events.size() > 0) {
msg = "edit_entcustomer_success";
String modifyid = MainUtils.getUUID();
Date modifytime = new Date();
for (PropertiesEvent event : events) {
event.setDataid(customerGroupForm.getEntcustomer().getId());
event.setCreater(super.getUser(request).getId());
event.setModifyid(modifyid);
event.setCreatetime(modifytime);
propertiesEventRes.save(event);
}
}
customerGroupForm.getEntcustomer().setOrgan(customer.getOrgan());
customerGroupForm.getEntcustomer().setCreater(customer.getCreater());
customerGroupForm.getEntcustomer().setCreatetime(customer.getCreatetime());
customerGroupForm.getEntcustomer().setPinyin(PinYinTools.getInstance().getFirstPinYin(customerGroupForm.getEntcustomer().getName()));
accountRes.save(customerGroupForm.getEntcustomer());
return request(super.createView("redirect:/apps/customer/index.html?ekind=" + ekindId + "&msg=" + msg));
}
@RequestMapping("/imp")
@Menu(type = "customer", subtype = "customer")
public ModelAndView imp(ModelMap map, HttpServletRequest request, @Valid String ekind) {
map.addAttribute("ekind", ekind);
return request(super.createView("/apps/customer/imp"));
}
@RequestMapping("/impsave")
@Menu(type = "customer", subtype = "customer")
public ModelAndView impsave(ModelMap map, HttpServletRequest request, @RequestParam(value = "cusfile", required = false) MultipartFile cusfile, @Valid String ekind) throws IOException {
DSDataEvent event = new DSDataEvent();
String fileName = "customer/" + MainUtils.getUUID() + cusfile.getOriginalFilename().substring(cusfile.getOriginalFilename().lastIndexOf("."));
File excelFile = new File(path, fileName);
if (!excelFile.getParentFile().exists()) {
excelFile.getParentFile().mkdirs();
}
Organ currentOrgan = super.getOrgan(request);
String organId = currentOrgan != null ? currentOrgan.getId() : null;
MetadataTable table = metadataRes.findByTablename("uk_entcustomer");
if (table != null) {
FileUtils.writeByteArrayToFile(new File(path, fileName), cusfile.getBytes());
event.setDSData(new DSData(table, excelFile, cusfile.getContentType(), super.getUser(request)));
event.getDSData().setClazz(Account.class);
event.getDSData().setProcess(new AccountProcess(accountRes));
/*if(StringUtils.isNotBlank(ekind)){
exchange.getValues().put("ekind", ekind) ;
}*/
event.getValues().put("creater", super.getUser(request).getId());
event.getValues().put("organ", organId);
event.getValues().put("shares", "all");
reporterRes.save(event.getDSData().getReport());
new ExcelImportProecess(event).process(); //启动导入任务
}
return request(super.createView("redirect:/apps/customer/index.html"));
}
@RequestMapping("/expids")
@Menu(type = "customer", subtype = "customer")
public void expids(ModelMap map, HttpServletRequest request, HttpServletResponse response, @Valid String[] ids) throws IOException {
if (ids != null && ids.length > 0) {
Iterable<Account> entCustomerList = accountRes.findAllById(Arrays.asList(ids));
MetadataTable table = metadataRes.findByTablename("uk_entcustomer");
List<Map<String, Object>> values = new ArrayList<>();
for (Account customer : entCustomerList) {
values.add(MainUtils.transBean2Map(customer));
}
response.setHeader("content-disposition", "attachment;filename=CSKeFu-EntCustomer-" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".xls");
ExcelExporterProcess excelProcess = new ExcelExporterProcess(values, table, response.getOutputStream());
excelProcess.process();
}
return;
}
@RequestMapping("/expall")
@Menu(type = "customer", subtype = "customer")
public void expall(ModelMap map, HttpServletRequest request, HttpServletResponse response, @Valid String ekind) throws IOException, CSKefuException {
if (!super.preCheckPermissions(request)) {
// #TODO 提示没有部门
return;
}
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
Iterable<Account> entCustomerList = accountRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(), super.getUser(request).getId(), false, PageRequest.of(super.getP(request), super.getPs(request)));
MetadataTable table = metadataRes.findByTablename("uk_entcustomer");
List<Map<String, Object>> values = new ArrayList<>();
for (Account customer : entCustomerList) {
values.add(MainUtils.transBean2Map(customer));
}
response.setHeader("content-disposition", "attachment;filename=CSKeFu-EntCustomer-" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".xls");
ExcelExporterProcess excelProcess = new ExcelExporterProcess(values, table, response.getOutputStream());
excelProcess.process();
return;
}
@RequestMapping("/expsearch")
@Menu(type = "customer", subtype = "customer")
public void expall(ModelMap map, HttpServletRequest request, HttpServletResponse response, @Valid String q, @Valid String ekind) throws IOException, CSKefuException {
if (!super.preCheckPermissions(request)) {
// #TODO 提示没有部门
return;
}
Organ currentOrgan = super.getOrgan(request);
Map<String, Organ> organs = organProxy.findAllOrganByParent(currentOrgan);
if (StringUtils.isNotBlank(q)) {
map.put("q", q);
}
if (StringUtils.isNotBlank(ekind)) {
map.put("ekind", ekind);
}
Iterable<Account> entCustomerList = accountRes.findByCreaterAndSharesAndDatastatus(super.getUser(request).getId(),
super.getUser(request).getId(),
false,
PageRequest.of(super.getP(request), super.getPs(request)));
MetadataTable table = metadataRes.findByTablename("uk_entcustomer");
List<Map<String, Object>> values = new ArrayList<>();
for (Account customer : entCustomerList) {
values.add(MainUtils.transBean2Map(customer));
}
response.setHeader("content-disposition", "attachment;filename=CSKeFu-EntCustomer-" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".xls");
ExcelExporterProcess excelProcess = new ExcelExporterProcess(values, table, response.getOutputStream());
excelProcess.process();
return;
}
}

View File

@ -1,641 +0,0 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.controller.apps;
import com.alibaba.fastjson.JSONObject;
import com.cskefu.cc.acd.ACDAgentService;
import com.cskefu.cc.acd.basic.ACDMessageHelper;
import com.cskefu.cc.activemq.BrokerPublisher;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.basic.MainContext;
import com.cskefu.cc.basic.MainUtils;
import com.cskefu.cc.cache.Cache;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.exception.CSKefuException;
import com.cskefu.cc.model.*;
import com.cskefu.cc.peer.PeerSyncIM;
import com.cskefu.cc.persistence.repository.*;
import com.cskefu.cc.proxy.*;
import com.cskefu.cc.socketio.message.Message;
import com.cskefu.cc.util.Menu;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Controller
@RequestMapping(value = "/apps/cca")
public class AgentAuditController extends Handler {
private final static Logger logger = LoggerFactory.getLogger(AgentAuditController.class);
@Autowired
private AgentUserProxy agentUserProxy;
@Autowired
private ACDMessageHelper acdMessageHelper;
@Autowired
private AgentUserRepository agentUserRes;
@Autowired
private OrganRepository organRes;
@Autowired
private UserRepository userRes;
@Autowired
private AgentUserRepository agentUserRepository;
@Autowired
private ChatMessageRepository chatMessageRepository;
@Autowired
private AgentServiceRepository agentServiceRes;
@Autowired
private AgentUserTaskRepository agentUserTaskRes;
@Autowired
private ServiceSummaryRepository serviceSummaryRes;
@Autowired
private UserProxy userProxy;
@Autowired
private PassportWebIMUserRepository onlineUserRes;
@Autowired
private TagRepository tagRes;
@Autowired
private Cache cache;
@Autowired
@Lazy
private PeerSyncIM peerSyncIM;
@Autowired
private TagRelationRepository tagRelationRes;
@Autowired
private BlackEntityProxy blackEntityProxy;
@Autowired
private BrokerPublisher brokerPublisher;
@Autowired
private AgentServiceProxy agentServiceProxy;
@Autowired
private ACDAgentService acdAgentService;
@Autowired
private OrganProxy organProxy;
@Autowired
private ChatbotRepository chatbotRes;
@RequestMapping(value = "/index")
@Menu(type = "cca", subtype = "cca", access = true)
public ModelAndView index(
ModelMap map,
HttpServletRequest request,
@Valid final String skill,
@Valid final String agentno,
@Valid String sort
) {
final User logined = super.getUser(request);
logger.info("[index] skill {}, agentno {}, logined {}", skill, agentno, logined.getId());
Map<String, Organ> organs = organProxy.findAllOrganByParent(super.getOrgan(request));
ModelAndView view = request(super.createView("/apps/cca/index"));
Sort defaultSort = null;
if (StringUtils.isNotBlank(sort)) {
List<Sort.Order> criterias = new ArrayList<>();
if (sort.equals("lastmessage")) {
criterias.add(new Sort.Order(Sort.Direction.DESC, "status"));
criterias.add(new Sort.Order(Sort.Direction.DESC, "lastmessage"));
} else if (sort.equals("logintime")) {
criterias.add(new Sort.Order(Sort.Direction.DESC, "status"));
criterias.add(new Sort.Order(Sort.Direction.DESC, "createtime"));
} else if (sort.equals("default")) {
defaultSort = Sort.by(Sort.Direction.DESC, "status");
}
if (criterias.size() > 0) {
defaultSort = Sort.by(criterias);
map.addAttribute("sort", sort);
}
} else {
defaultSort = Sort.by(Sort.Direction.DESC, "status");
}
// 坐席对话列表
List<AgentUser> agentUsers = new ArrayList<>();
if (StringUtils.isBlank(skill) && StringUtils.isBlank(agentno)) {
if (organs.size() > 0) {
agentUsers = agentUserRes.findByStatusAndSkillInAndAgentnoIsNotAndChatbotopsIsFalse(MainContext.AgentUserStatusEnum.INSERVICE.toString(), organs.keySet(), logined.getId(), defaultSort);
}
} else if (StringUtils.isNotBlank(skill) && StringUtils.isNotBlank(agentno)) {
view.addObject("skill", skill);
view.addObject("agentno", agentno);
agentUsers = agentUserRes.findByStatusAndSkillAndAgentno(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), skill, agentno, defaultSort);
} else if (StringUtils.isNotBlank(skill)) {
view.addObject("skill", skill);
agentUsers = agentUserRes.findByStatusAndSkillAndAgentnoIsNot(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), skill, agentno, defaultSort);
} else {
// agent is not Blank
view.addObject("agentno", agentno);
agentUsers = agentUserRes.findByStatusAndAgentno(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), agentno, defaultSort);
}
logger.info("[index] agent users size: {}", agentUsers.size());
if (agentUsers.size() > 0) {
view.addObject("agentUserList", agentUsers);
/**
* 当前对话
*/
final AgentUser currentAgentUser = agentUsers.get(0);
agentServiceProxy.bundleDialogRequiredDataInView(view, map, currentAgentUser, logined);
}
// 查询所有技能组
List<Organ> skills = organRes.findBySkill(true);
List<User> agents = userRes.findByAgentAndDatastatusAndIdIsNot(true, false, logined.getId());
view.addObject("skillGroups", skills.stream().filter(s -> organs.containsKey(s.getId())).collect(Collectors.toList()));
view.addObject("agentList", agents);
return view;
}
@RequestMapping("/query")
@Menu(type = "apps", subtype = "cca")
public ModelAndView query(HttpServletRequest request, String skill, String agentno) {
ModelAndView view = request(super.createView("/apps/cca/chatusers"));
final User logined = super.getUser(request);
Sort defaultSort = Sort.by(Sort.Direction.DESC, "status");
// 坐席对话列表
List<AgentUser> agentUsers;
if (StringUtils.isBlank(skill) && StringUtils.isBlank(agentno)) {
agentUsers = agentUserRes.findByStatusAndAgentnoIsNot(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), logined.getId(), defaultSort);
} else if (StringUtils.isNotBlank(skill) && StringUtils.isNotBlank(agentno)) {
agentUsers = agentUserRes.findByStatusAndSkillAndAgentno(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), skill, agentno, defaultSort);
} else if (StringUtils.isNotBlank(skill)) {
agentUsers = agentUserRes.findByStatusAndSkillAndAgentnoIsNot(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), skill, agentno, defaultSort);
} else {
// agent is not Blank
agentUsers = agentUserRes.findByStatusAndAgentno(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), agentno, defaultSort);
}
view.addObject("agentUserList", agentUsers);
return view;
}
@RequestMapping("/agentusers")
@Menu(type = "apps", subtype = "cca")
public ModelAndView agentusers(HttpServletRequest request, String userid) {
ModelAndView view = request(super.createView("/apps/cca/agentusers"));
User logined = super.getUser(request);
Sort defaultSort = Sort.by(Sort.Direction.DESC, "status");
view.addObject(
"agentUserList", agentUserRes.findByStatusAndAgentnoIsNot(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), logined.getId(), defaultSort));
List<AgentUser> agentUserList = agentUserRepository.findByUserid(userid);
view.addObject(
"curagentuser", agentUserList != null && agentUserList.size() > 0 ? agentUserList.get(0) : null);
return view;
}
@RequestMapping("/agentuser")
@Menu(type = "apps", subtype = "cca")
public ModelAndView agentuser(
ModelMap map,
HttpServletRequest request,
String id,
String channel
) throws IOException {
String mainagentuser = "/apps/cca/mainagentuser";
if (channel.equals("phone")) {
mainagentuser = "/apps/cca/mainagentuser_callout";
}
ModelAndView view = request(super.createView(mainagentuser));
final User logined = super.getUser(request);
AgentUser agentUser = agentUserRepository.findById(id).orElse(null);
if (agentUser != null) {
view.addObject("curagentuser", agentUser);
Chatbot c = chatbotRes.findBySnsAccountIdentifier(agentUser.getAppid());
if (c != null) {
view.addObject("ccaAisuggest", c.isAisuggest());
}
view.addObject("inviteData", OnlineUserProxy.consult(agentUser.getAppid()));
AgentUserTask agentUserTask = agentUserTaskRes.findById(id).orElse(null);
agentUserTask.setTokenum(0);
agentUserTaskRes.save(agentUserTask);
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
List<AgentServiceSummary> summarizes = this.serviceSummaryRes.findByAgentserviceid(
agentUser.getAgentserviceid());
if (summarizes.size() > 0) {
view.addObject("summary", summarizes.get(0));
}
}
view.addObject(
"agentUserMessageList",
this.chatMessageRepository.findByUsession(agentUser.getUserid(),
PageRequest.of(0, 20, Sort.Direction.DESC,"updatetime")
)
);
AgentService agentService = null;
if (StringUtils.isNotBlank(agentUser.getAgentserviceid())) {
agentService = this.agentServiceRes.findById(agentUser.getAgentserviceid()).orElse(null);
view.addObject("curAgentService", agentService);
if (agentService != null) {
/**
* 获取关联数据
*/
agentServiceProxy.processRelaData(logined.getId(), agentService, map);
}
}
if (MainContext.ChannelType.WEBIM.toString().equals(agentUser.getChanneltype())) {
PassportWebIMUser passportWebIMUser = onlineUserRes.findById(agentUser.getUserid()).orElse(null);
if (passportWebIMUser != null) {
if (passportWebIMUser.getLogintime() != null) {
if (MainContext.OnlineUserStatusEnum.OFFLINE.toString().equals(passportWebIMUser.getStatus())) {
passportWebIMUser.setBetweentime(
(int) (passportWebIMUser.getUpdatetime().getTime() - passportWebIMUser.getLogintime().getTime()));
} else {
passportWebIMUser.setBetweentime(
(int) (System.currentTimeMillis() - passportWebIMUser.getLogintime().getTime()));
}
}
view.addObject("onlineUser", passportWebIMUser);
}
}
view.addObject("serviceCount", this.agentServiceRes
.countByUseridAndStatus(agentUser
.getUserid(),
MainContext.AgentUserStatusEnum.END
.toString()));
view.addObject("tagRelationList", tagRelationRes.findByUserid(agentUser.getUserid()));
AgentService service = agentServiceRes.findById(agentUser.getAgentserviceid()).orElse(null);
if (service != null) {
view.addObject("tags", tagRes.findByTagtypeAndSkill(MainContext.ModelType.USER.toString(), service.getSkill()));
}
}
return view;
}
/**
* 坐席转接窗口
*
* @param map
* @param request
* @param userid
* @param agentserviceid
* @param agentuserid
* @return
*/
@RequestMapping(value = "/transfer")
@Menu(type = "apps", subtype = "transfer")
public ModelAndView transfer(
ModelMap map,
final HttpServletRequest request,
final @Valid String userid,
final @Valid String agentserviceid,
final @Valid String agentnoid,
final @Valid String agentuserid
) {
logger.info("[transfer] userId {}, agentUser {}", userid, agentuserid);
Organ targetOrgan = super.getOrgan(request);
Map<String, Organ> ownOrgans = organProxy.findAllOrganByParent(targetOrgan);
if (StringUtils.isNotBlank(userid) && StringUtils.isNotBlank(agentuserid)) {
// 列出所有技能组
List<Organ> skillGroups = organRes.findByIdInAndSkill(ownOrgans.keySet(), true);
// 选择当前用户的默认技能组
AgentService agentService = agentServiceRes.findById(agentserviceid).orElse(null);
String currentOrgan = agentService.getSkill();
if (StringUtils.isBlank(currentOrgan)) {
if (!skillGroups.isEmpty()) {
currentOrgan = skillGroups.get(0).getId();
}
}
// 列出所有在线的坐席排除本身
List<String> userids = new ArrayList<>();
final Map<String, AgentStatus> agentStatusMap = cache.findAllReadyAgentStatus();
for (final String o : agentStatusMap.keySet()) {
if (!StringUtils.equals(o, agentnoid)) {
userids.add(o);
}
}
final List<User> userList = userRes.findAllById(userids);
for (final User o : userList) {
o.setAgentStatus(agentStatusMap.get(o.getId()));
// find user's skills
userProxy.attachOrgansPropertiesForUser(o);
}
map.addAttribute("userList", userList);
map.addAttribute("userid", userid);
map.addAttribute("agentserviceid", agentserviceid);
map.addAttribute("agentuserid", agentuserid);
map.addAttribute("agentno", agentnoid);
map.addAttribute("skillGroups", skillGroups);
map.addAttribute("agentservice", this.agentServiceRes.findById(agentserviceid).orElse(null));
map.addAttribute("currentorgan", currentOrgan);
}
return request(super.createView("/apps/cca/transfer"));
}
/**
* 查找一个组织机构中的在线坐席
*
* @param map
* @param request
* @param organ
* @return
*/
@RequestMapping(value = "/transfer/agent")
@Menu(type = "apps", subtype = "transferagent")
public ModelAndView transferagent(
ModelMap map,
HttpServletRequest request,
@Valid String agentid,
@Valid String organ
) {
if (StringUtils.isNotBlank(organ)) {
List<String> userids = new ArrayList<>();
final Map<String, AgentStatus> agentStatusMap = cache.findAllReadyAgentStatus();
for (final String o : agentStatusMap.keySet()) {
if (!StringUtils.equals(o, agentid)) {
userids.add(o);
}
}
final List<User> userList = userRes.findAllById(userids);
for (final User o : userList) {
o.setAgentStatus(agentStatusMap.get(o.getId()));
// find user's skills
userProxy.attachOrgansPropertiesForUser(o);
}
map.addAttribute("userList", userList);
map.addAttribute("currentorgan", organ);
}
return request(super.createView("/apps/cca/transferagentlist"));
}
/**
* 执行坐席转接
*
* @param map
* @param request
* @param userid
* @param agentserviceid
* @param agentuserid
* @param agentno
* @param memo
* @return
*/
@RequestMapping(value = "/transfer/save")
@Menu(type = "apps", subtype = "transfersave")
public ModelAndView transfersave(
final ModelMap map, HttpServletRequest request,
@Valid final String userid, // 访客ID
@Valid final String agentserviceid, // 服务记录ID
@Valid final String agentuserid, // 坐席访客ID
@Valid final String currentAgentnoid,
@Valid final String agentno, // 会话转接给下一个坐席
@Valid final String memo
) throws CSKefuException {
final String currentAgentno = currentAgentnoid; // 当前会话坐席的agentno
if (StringUtils.isNotBlank(userid) &&
StringUtils.isNotBlank(agentuserid) &&
StringUtils.isNotBlank(agentno)) {
final User targetAgent = userRes.findById(agentno).orElse(null);
final AgentService agentService = agentServiceRes.findById(agentserviceid).orElse(null);
/**
* 更新AgentUser
*/
final AgentUser agentUser = agentUserProxy.resolveAgentUser(userid, agentuserid);
agentUser.setAgentno(agentno);
agentUser.setAgentname(targetAgent.getUname());
agentUserRes.save(agentUser);
/**
* 坐席状态
*/
// 转接目标坐席
final AgentStatus transAgentStatus = cache.findOneAgentStatusByAgentno(agentno);
// 转接源坐席
final AgentStatus currentAgentStatus = cache.findOneAgentStatusByAgentno(currentAgentno);
if (StringUtils.equals(
MainContext.AgentUserStatusEnum.INSERVICE.toString(), agentUser.getStatus())) { //转接 发送消息给 目标坐席
// 更新当前坐席的服务访客列表
if (currentAgentStatus != null) {
cache.deleteOnlineUserIdFromAgentStatusByUseridAndAgentno(userid, currentAgentno);
agentUserProxy.updateAgentStatus(currentAgentStatus);
}
if (transAgentStatus != null) {
agentService.setAgentno(agentno);
agentService.setAgentusername(transAgentStatus.getUsername());
}
// 转接坐席提示消息
try {
Message outMessage = new Message();
outMessage.setMessage(
acdMessageHelper.getSuccessMessage(agentService, agentUser.getChanneltype()));
outMessage.setMessageType(MainContext.MediaType.TEXT.toString());
outMessage.setCalltype(MainContext.CallType.IN.toString());
outMessage.setCreatetime(MainUtils.dateFormate.format(new Date()));
outMessage.setAgentUser(agentUser);
outMessage.setAgentService(agentService);
if (StringUtils.isNotBlank(agentUser.getUserid())) {
peerSyncIM.send(
MainContext.ReceiverType.VISITOR,
MainContext.ChannelType.toValue(agentUser.getChanneltype()),
agentUser.getAppid(),
MainContext.MessageType.STATUS,
agentUser.getUserid(),
outMessage,
true
);
}
// 通知转接消息给新坐席
outMessage.setChannelMessage(agentUser);
outMessage.setAgentUser(agentUser);
peerSyncIM.send(
MainContext.ReceiverType.AGENT, MainContext.ChannelType.WEBIM,
agentUser.getAppid(), MainContext.MessageType.NEW, agentService.getAgentno(),
outMessage, true
);
} catch (Exception ex) {
logger.error("[transfersave]", ex);
}
}
if (agentService != null) {
agentService.setAgentno(agentno);
if (StringUtils.isNotBlank(memo)) {
agentService.setTransmemo(memo);
}
agentService.setTrans(true);
agentService.setTranstime(new Date());
agentServiceRes.save(agentService);
}
}
return request(super.createView("redirect:/apps/cca/index.html"));
}
/**
* 结束对话
* 如果当前对话属于登录用户或登录用户为超级用户则可以结束这个对话
*
* @param request
* @param id
* @return
* @throws Exception
*/
@RequestMapping({"/end"})
@Menu(type = "apps", subtype = "agent")
public ModelAndView end(HttpServletRequest request, @Valid String id) {
final User logined = super.getUser(request);
final AgentUser agentUser = agentUserRes.findById(id).orElse(null);
if (agentUser != null) {
if ((StringUtils.equals(
logined.getId(), agentUser.getAgentno()) || logined.isAdmin())) {
// 删除访客-坐席关联关系包括缓存
try {
acdAgentService.finishAgentUser(agentUser);
} catch (CSKefuException e) {
// 未能删除成功
logger.error("[end]", e);
}
} else {
logger.info("[end] Permission not fulfill.");
}
}
return request(super.createView("redirect:/apps/cca/index.html"));
}
@RequestMapping({"/blacklist/add"})
@Menu(type = "apps", subtype = "blacklist")
public ModelAndView blacklistadd(ModelMap map, HttpServletRequest request, @Valid String agentuserid, @Valid String agentserviceid, @Valid String userid)
throws Exception {
map.addAttribute("agentuserid", agentuserid);
map.addAttribute("agentserviceid", agentserviceid);
map.addAttribute("userid", userid);
map.addAttribute("agentUser", agentUserRes.findById(userid).orElse(null));
return request(super.createView("/apps/cca/blacklistadd"));
}
@RequestMapping({"/blacklist/save"})
@Menu(type = "apps", subtype = "blacklist")
public ModelAndView blacklist(
HttpServletRequest request,
@Valid String agentuserid,
@Valid String agentserviceid,
@Valid String userid,
@Valid BlackEntity blackEntity)
throws Exception {
logger.info("[blacklist] userid {}", userid);
final User logined = super.getUser(request);
if (StringUtils.isBlank(userid)) {
throw new CSKefuException("Invalid userid");
}
/**
* 添加黑名单
* 一定时间后触发函数
*/
JSONObject payload = new JSONObject();
int timeSeconds = blackEntity.getControltime() * 3600;
payload.put("userId", userid);
ModelAndView view = end(request, agentuserid);
// 更新或创建黑名单
blackEntityProxy.updateOrCreateBlackEntity(blackEntity, logined, userid, agentserviceid, agentuserid);
// 创建定时任务 取消拉黑
brokerPublisher.send(
Constants.WEBIM_SOCKETIO_ONLINE_USER_BLACKLIST, payload.toJSONString(), false, timeSeconds);
return view;
}
}

Some files were not shown because too many files have changed in this diff Show More